summaryrefslogtreecommitdiff
path: root/lisp/gnus.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/gnus.el')
-rw-r--r--lisp/gnus.el6081
1 files changed, 6081 insertions, 0 deletions
diff --git a/lisp/gnus.el b/lisp/gnus.el
new file mode 100644
index 00000000000..d37072dd6a6
--- /dev/null
+++ b/lisp/gnus.el
@@ -0,0 +1,6081 @@
+;;; GNUS: an NNTP-based News Reader for GNU Emacs
+;; Copyright (C) 1987, 1988, 1989 Fujitsu Laboratories LTD.
+;; Copyright (C) 1987, 1988, 1989, 1990 Masanobu UMEDA
+;; $Header: gnus.el,v 3.13 90/03/23 13:24:27 umerin Locked $
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY. No author or distributor
+;; accepts responsibility to anyone for the consequences of using it
+;; or for whether it serves any particular purpose or works at all,
+;; unless he says so in writing. Refer to the GNU Emacs General Public
+;; License for full details.
+
+;; Everyone is granted permission to copy, modify and redistribute
+;; GNU Emacs, but only under the conditions described in the
+;; GNU Emacs General Public License. A copy of this license is
+;; supposed to have been given to you along with GNU Emacs so you
+;; can know your rights and responsibilities. It should be in a
+;; file named COPYING. Among other things, the copyright notice
+;; and this notice must be preserved on all copies.
+
+;; GNUS Mailing List:
+;; There are two mailing lists for GNUS lovers in the world:
+;;
+;; info-gnus@flab.fujitsu.co.jp, and
+;; info-gnus-english@tut.cis.ohio-state.edu.
+;;
+;; They are intended to exchange useful information about GNUS, such
+;; as bug fixes, useful hooks, and extensions. The major difference
+;; between the lists is what the official language is. Both Japanese
+;; and English are available in info-gnus, while English is only
+;; available in info-gnus-english. There is no need to subscribe to
+;; info-gnus if you cannot read Japanese messages, because most of the
+;; discussion and important announcements will be sent to
+;; info-gnus-english. Moreover, if you can read gnu.emacs.gnus
+;; newsgroup of USENET, you need not, either. info-gnus-english and
+;; gnu.emacs.gnus are linked each other.
+;;
+;; Please send subscription request to:
+;;
+;; info-gnus-request@flab.fujitsu.co.jp, or
+;; info-gnus-english-request@cis.ohio-state.edu
+
+;; TO DO:
+;; (1) Incremental update of active info.
+;; (2) GNUS own poster.
+;; (3) Multi-GNUS (Talking to many hosts same time).
+;; (4) Asynchronous transmission of large messages.
+
+(provide 'gnus)
+(require 'nntp)
+(require 'mail-utils)
+
+(defvar gnus-nntp-server (or (getenv "NNTPSERVER") gnus-default-nntp-server)
+ "The name of the host running NNTP server.
+If it is a string such as `:DIRECTORY', the user's private DIRECTORY
+is used as a news spool.
+Initialized from the NNTPSERVER environment variable.")
+
+(defvar gnus-signature-file "~/.signature"
+ "*Your .signature file. Use `.signature-DISTRIBUTION' instead if exists.")
+
+(defvar gnus-use-cross-reference t
+ "Specifies what to do with cross references (Xref: field).
+If nil, ignore cross references. If t, mark articles as read in subscribed
+newsgroups. Otherwise, mark articles as read in all newsgroups.")
+
+(defvar gnus-use-followup-to t
+ "*Specifies what to do with Followup-To: field.
+If nil, ignore followup-to: field. If t, use its value execpt for
+`poster'. Otherewise, if not nil nor t, always use its value.")
+
+(defvar gnus-large-newsgroup 50
+ "*The number of articles which indicates a large newsgroup.
+If the number of articles in a newsgroup is greater than the value,
+confirmation is required for selecting the newsgroup.")
+
+(defvar gnus-author-copy (getenv "AUTHORCOPY")
+ "*Filename for saving a copy of an article posted using FCC: field.
+Initialized from the AUTHORCOPY environment variable.
+
+Articles are saved using a function specified by the the variable
+`gnus-author-copy-saver' (`rmail-output' is the default) if a file name
+is given. Instead, if the first character of the name is `|', the
+contents of the article is piped out to the named program. It is
+possible to save an article in an MH folder as follows:
+
+ (setq gnus-author-copy \"|/usr/local/lib/mh/rcvstore +Article\")")
+
+(defvar gnus-author-copy-saver (function rmail-output)
+ "*A function called with a file name to save an author copy to.
+The default function is `rmail-output' which saves in Unix mailbox format.")
+
+(defvar gnus-use-long-file-name
+ (not (memq system-type '(usg-unix-v xenix)))
+ "Non-nil means that a newsgroup name is used as a default file name
+to save articles to. If nil, the directory form of a newsgroup is
+used instead.")
+
+(defvar gnus-article-save-directory (getenv "SAVEDIR")
+ "*The directory in which to save articles; defaults to ~/News.
+Initialized from the SAVEDIR environment variable.")
+
+(defvar gnus-default-article-saver (function gnus-Subject-save-in-rmail)
+ "A function used to save articles in your favorite format.
+The function must be interactively callable (in other words, it must
+be an Emacs command).
+
+GNUS provides the following functions:
+ gnus-Subject-save-in-rmail (in Rmail format)
+ gnus-Subject-save-in-mail (in Unix mail format)
+ gnus-Subject-save-in-folder (in an MH folder)
+ gnus-Subject-save-in-file (in article format).")
+
+(defvar gnus-rmail-save-name (function gnus-plain-save-name)
+ "A function generating a file name to save articles in Rmail format.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE.")
+
+(defvar gnus-mail-save-name (function gnus-plain-save-name)
+ "A function generating a file name to save articles in Unix mail format.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE.")
+
+(defvar gnus-folder-save-name (function gnus-folder-save-name)
+ "A function generating a file name to save articles in MH folder.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FOLDER.")
+
+(defvar gnus-file-save-name (function gnus-numeric-save-name)
+ "A function generating a file name to save articles in article format.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE.")
+
+(defvar gnus-kill-file-name "KILL"
+ "File name of a KILL file.")
+
+(defvar gnus-default-distribution "local"
+ "*Use this value as distribution if no distribution is specified.")
+
+(defvar gnus-novice-user t
+ "*Non-nil means that you are a novice to USENET.
+If non-nil, verbose messages may be displayed or your confirmation
+may be required.")
+
+(defvar gnus-interactive-post t
+ "*Newsgroup, subject, and distribution will be asked for if non-nil.")
+
+(defvar gnus-user-login-name nil
+ "*The login name of the user.
+Uses USER and LOGNAME environment variables if undefined.")
+
+(defvar gnus-user-full-name nil
+ "*The full name of the user.
+Uses from the NAME environment variable if undefined.")
+
+(defvar gnus-show-threads t
+ "*Show conversation threads in Subject Mode if non-nil.")
+
+(defvar gnus-thread-hide-subject t
+ "*Non-nil means hide subjects for thread subtrees.")
+
+(defvar gnus-thread-hide-subtree nil
+ "*Non-nil means hide thread subtrees initially.
+If non-nil, you have to run the command `gnus-Subject-show-thread' by
+hand or by using `gnus-Select-article-hook' to show hidden threads.")
+
+(defvar gnus-thread-hide-killed t
+ "*Non-nil means hide killed thread subtrees automatically.")
+
+(defvar gnus-thread-ignore-subject nil
+ "*Don't take care of subject differences, but only references if non-nil.
+If it is non-nil, some commands work with subjects do not work properly.")
+
+(defvar gnus-thread-indent-level 4
+ "Indentation of thread subtrees.")
+
+(defvar gnus-ignored-headers
+ "^Path:\\|^Posting-Version:\\|^Article-I.D.:\\|^Expires:\\|^Date-Received:\\|^References:\\|^Control:\\|^Xref:\\|^Lines:\\|^Posted:\\|^Relay-Version:\\|^Message-ID:\\|^Nf-ID:\\|^Nf-From:\\|^Approved:\\|^Sender:\\|^In-Reply-To:"
+ "Regexp matching headers not to display in messages.")
+
+(defvar gnus-show-all-headers nil
+ "*Show all headers of an article if non-nil.")
+
+(defvar gnus-save-all-headers nil
+ "*Save all headers of an article if non-nil.")
+
+(defvar gnus-optional-headers (function gnus-optional-lines-and-from)
+ "A function generating a optional string displayed in GNUS Subject
+mode buffer. The function is called with an article HEADER. The
+result must be a string excluding `[' and `]'.")
+
+(defvar gnus-auto-extend-newsgroup t
+ "*Extend visible articles to forward and backward if non-nil.")
+
+(defvar gnus-auto-select-first t
+ "*Select the first unread article automagically if non-nil.
+If you want to prevent automatic selection of the first unread article
+in some newsgroups, set the variable to nil in `gnus-Select-group-hook'
+or `gnus-Apply-kill-hook'.")
+
+(defvar gnus-auto-select-next t
+ "*Select the next newsgroup automagically if non-nil.
+If the value is t and the next newsgroup is empty, GNUS will exit
+Subject mode and go back to Group mode. If the value is neither nil
+nor t, GNUS will select the following unread newsgroup. Especially, if
+the value is the symbol `quietly', the next unread newsgroup will be
+selected without any confirmations.")
+
+(defvar gnus-auto-select-same nil
+ "*Select the next article with the same subject automagically if non-nil.")
+
+(defvar gnus-auto-center-subject t
+ "*Always center the current subject in GNUS Subject mode window if non-nil.")
+
+(defvar gnus-break-pages t
+ "*Break an article into pages if non-nil.
+Page delimiter is specified by the variable `gnus-page-delimiter'.")
+
+(defvar gnus-page-delimiter "^\^L"
+ "*Regexp describing line-beginnings that separate pages of news article.")
+
+(defvar gnus-digest-show-summary t
+ "*Show a summary of undigestified messages if non-nil.")
+
+(defvar gnus-digest-separator "^Subject:[ \t]"
+ "*Regexp that separates messages in a digest article.")
+
+(defvar gnus-use-full-window t
+ "*Non-nil means to take up the entire screen of Emacs.")
+
+(defvar gnus-window-configuration
+ '((SelectNewsgroup (0 1 0))
+ (ExitNewsgroup (1 0 0))
+ (SelectArticle (0 3 10))
+ (ExpandSubject (0 1 0)))
+ "Specify window configurations for each action.
+The format of the variable is a list of (ACTION (G S A)), where
+G, S, and A are the relative height of Group, Subject, and Article
+windows, respectively. ACTION is `SelectNewsgroup', `ExitNewsgroup',
+`SelectArticle', or `ExpandSubject'.")
+
+(defvar gnus-mail-reply-method
+ (function gnus-mail-reply-using-mail)
+ "A function to compose reply mail.
+The function `gnus-mail-reply-using-mail' uses usual the sendmail mail
+program. The function `gnus-mail-reply-using-mhe' uses the mh-e mail
+program. You can use yet another program by customizing this variable.")
+
+(defvar gnus-mail-other-window-method
+ (function gnus-mail-other-window-using-mail)
+ "A function to compose mail in other window.
+The function `gnus-mail-other-window-using-mail' uses usual sendmail
+mail program. The function `gnus-mail-other-window-using-mhe' uses mh-e
+mail program. You can use yet another program by customizing this variable.")
+
+(defvar gnus-subscribe-newsgroup-method
+ (function
+ (lambda (newsgroup)
+ (gnus-subscribe-newsgroup newsgroup
+ (car (car gnus-newsrc-assoc)))))
+ "A function called with a newsgroup name when it is created.")
+
+(defvar gnus-Group-mode-hook nil
+ "A hook for GNUS Group Mode.")
+
+(defvar gnus-Subject-mode-hook nil
+ "A hook for GNUS Subject Mode.")
+
+(defvar gnus-Article-mode-hook nil
+ "A hook for GNUS Article Mode.")
+
+(defvar gnus-Kill-file-mode-hook nil
+ "A hook for GNUS KILL File Mode.")
+
+(defvar gnus-Open-server-hook nil
+ "A hook called just before opening connection to news server.")
+
+(defvar gnus-Startup-hook nil
+ "A hook called at start up time.
+This hook is called after GNUS is connected to the NNTP server.
+So, it is possible to change the behavior of GNUS according to the
+selected NNTP server.")
+
+(defvar gnus-Group-prepare-hook nil
+ "A hook called after newsgroup list is created in the Newsgroup buffer.
+If you want to modify the Newsgroup buffer, you can use this hook.")
+
+(defvar gnus-Subject-prepare-hook nil
+ "A hook called after subject list is created in the Subject buffer.
+If you want to modify the Subject buffer, you can use this hook.")
+
+(defvar gnus-Article-prepare-hook nil
+ "A hook called after an article is prepared in the Article buffer.
+If you want to run a special decoding program like nkf, use this hook.")
+
+(defvar gnus-Select-group-hook nil
+ "A hook called when a newsgroup is selected.
+If you want to sort Subject buffer by date and then by subject, you
+can use the following hook:
+
+(setq gnus-Select-group-hook
+ '(lambda ()
+ ;; First of all, sort by date.
+ (gnus-sort-headers
+ '(lambda (a b)
+ (gnus-date-lessp (gnus-header-date a)
+ (gnus-header-date b))))
+ ;; Then sort by subject string ignoring `Re:'.
+ ;; If case-fold-search is non-nil, case of letters is ignored.
+ (gnus-sort-headers
+ '(lambda (a b)
+ (gnus-string-lessp
+ (gnus-simplify-subject (gnus-header-subject a) 're)
+ (gnus-simplify-subject (gnus-header-subject b) 're)
+ )))))
+
+If you'd like to simplify subjects like the `gnus-Subject-next-same-subject'
+command does, you can use the following hook:
+
+(setq gnus-Select-group-hook
+ '(lambda ()
+ (mapcar (function
+ (lambda (header)
+ (nntp-set-header-subject
+ header
+ (gnus-simplify-subject
+ (gnus-header-subject header) 're-only))))
+ gnus-newsgroup-headers)))
+
+In some newsgroups author name is meaningless. It is possible to
+prevent listing author names in the GNUS Subject buffer as follows:
+
+(setq gnus-Select-group-hook
+ '(lambda ()
+ (cond ((string-equal \"comp.sources.unix\" gnus-newsgroup-name)
+ (setq gnus-optional-headers
+ (function gnus-optional-lines)))
+ (t
+ (setq gnus-optional-headers
+ (function gnus-optional-lines-and-from))))))")
+
+(defvar gnus-Select-article-hook
+ (function (lambda () (gnus-Subject-show-thread)))
+ "Hook called when an article is selected.
+The default hook automatically shows conversation thread subtrees
+of the selected article as follows:
+
+(setq gnus-Select-article-hook
+ '(lambda ()
+ (gnus-Subject-show-thread)))
+
+If you'd like to run RMAIL on a digest article automagically, you can
+use the following hook:
+
+(setq gnus-Select-article-hook
+ '(lambda ()
+ (gnus-Subject-show-thread)
+ (cond ((string-equal \"comp.sys.sun\" gnus-newsgroup-name)
+ (gnus-Subject-rmail-digest))
+ ((and (string-equal \"comp.text\" gnus-newsgroup-name)
+ (string-match \"^TeXhax Digest\"
+ (gnus-header-subject gnus-current-headers)))
+ (gnus-Subject-rmail-digest)
+ ))))")
+
+(defvar gnus-Select-digest-hook
+ (function
+ (lambda ()
+ ;; Reply-To: is required by `undigestify-rmail-message'.
+ (or (mail-position-on-field "Reply-to" t)
+ (progn
+ (mail-position-on-field "Reply-to")
+ (insert (gnus-fetch-field "From"))))))
+ "A hook called when reading digest messages using Rmail.
+This hook can be used to modify incomplete digest articles as follows
+(this is the default):
+
+(setq gnus-Select-digest-hook
+ '(lambda ()
+ ;; Reply-To: is required by `undigestify-rmail-message'.
+ (or (mail-position-on-field \"Reply-to\" t)
+ (progn
+ (mail-position-on-field \"Reply-to\")
+ (insert (gnus-fetch-field \"From\"))))))")
+
+(defvar gnus-Rmail-digest-hook nil
+ "A hook called when reading digest messages using Rmail.
+This hook is intended to customize Rmail mode for reading digest articles.")
+
+(defvar gnus-Apply-kill-hook (function gnus-apply-kill-file)
+ "A hook called when a newsgroup is selected and subject list is prepared.
+This hook is intended to apply a KILL file to the selected newsgroup.
+The function `gnus-apply-kill-file' is called defaultly.
+
+Since a general KILL file is too heavy to use for only a few
+newsgroups, we recommend you use a lighter hook function. For
+example, if you'd like to apply a KILL file to articles which contains
+a string `rmgroup' in subject in newsgroup `control', you can use the
+following hook:
+
+(setq gnus-Apply-kill-hook
+ '(lambda ()
+ (cond ((string-match \"control\" gnus-newsgroup-name)
+ (gnus-kill \"Subject\" \"rmgroup\")
+ (gnus-expunge \"X\")))))")
+
+(defvar gnus-Mark-article-hook
+ (function
+ (lambda ()
+ (or (memq gnus-current-article gnus-newsgroup-marked)
+ (gnus-Subject-mark-as-read gnus-current-article))
+ (gnus-Subject-set-current-mark "+")))
+ "A hook called when an article is selected for the first time.
+The hook is intended to mark an article as read when it is selected.
+If you'd like to mark as unread (-) instead, use the following hook:
+
+(setq gnus-Mark-article-hook
+ '(lambda ()
+ (gnus-Subject-mark-as-unread gnus-current-article)
+ (gnus-Subject-set-current-mark \"+\")))")
+
+(defvar gnus-Inews-article-hook nil
+ "A hook called before posting an article.
+If you'd like to run a special encoding program, use this hook.")
+
+(defvar gnus-Exit-group-hook nil
+ "A hook called when exiting (not quitting) Subject mode.
+If your machine is so slow that exiting from Subject mode takes a
+long time, set the variable `gnus-newsgroup-headers' to nil. This
+inhibits marking articles as read using cross-reference information.")
+
+(defvar gnus-Suspend-gnus-hook nil
+ "A hook called when suspending (not exiting) GNUS.")
+
+(defvar gnus-Exit-gnus-hook nil
+ "A hook called when exiting (not suspending) GNUS.")
+
+(defvar gnus-Save-newsrc-hook nil
+ "A hook called when saving the newsrc file.
+This hook is called before saving .newsrc file.")
+
+(defvar gnus-your-domain nil
+ "*Your domain name without your host name like: \"stars.flab.Fujitsu.CO.JP\"
+The environment variable DOMAINNAME is used instead if defined. If
+the function `system-name' returns the full internet name, there is no
+need to define this variable.")
+
+(defvar gnus-your-organization nil
+ "*Your organization like: \"Fujitsu Laboratories Ltd., Kawasaki, Japan.\"
+The `ORGANIZATION' environment variable is used instead if defined.")
+
+(defvar gnus-use-generic-from nil
+ "*If nil, prepend local host name to the defined domain in the From:
+field; if stringp, use this; if non-nil, strip of the local host name.")
+
+(defvar gnus-use-generic-path nil
+ "*If nil, use the NNTP server name in the Path: field; if stringp,
+use this; if non-nil, use no host name (user name only)")
+
+;; Internal variables.
+
+(defconst gnus-version "GNUS 3.13"
+ "Version numbers of this version of GNUS.")
+
+(defvar gnus-Info-nodes
+ '((gnus-Group-mode . "(gnus)Newsgroup Commands")
+ (gnus-Subject-mode . "(gnus)Subject Commands")
+ (gnus-Article-mode . "(gnus)Article Commands")
+ (gnus-Kill-file-mode . "(gnus)KILL File")
+ (gnus-Browse-killed-mode . "(gnus)Maintenance"))
+ "Assoc list of major modes and related Info nodes.")
+
+(defvar gnus-access-methods
+ '((nntp
+ (gnus-retrieve-headers . nntp-retrieve-headers)
+ (gnus-open-server . nntp-open-server)
+ (gnus-close-server . nntp-close-server)
+ (gnus-server-opened . nntp-server-opened)
+ (gnus-status-message . nntp-status-message)
+ (gnus-request-article . nntp-request-article)
+ (gnus-request-group . nntp-request-group)
+ (gnus-request-list . nntp-request-list)
+ (gnus-request-post . nntp-request-post))
+ (nnspool
+ (gnus-retrieve-headers . nnspool-retrieve-headers)
+ (gnus-open-server . nnspool-open-server)
+ (gnus-close-server . nnspool-close-server)
+ (gnus-server-opened . nnspool-server-opened)
+ (gnus-status-message . nnspool-status-message)
+ (gnus-request-article . nnspool-request-article)
+ (gnus-request-group . nnspool-request-group)
+ (gnus-request-list . nnspool-request-list)
+ (gnus-request-post . nnspool-request-post))
+ (mhspool
+ (gnus-retrieve-headers . mhspool-retrieve-headers)
+ (gnus-open-server . mhspool-open-server)
+ (gnus-close-server . mhspool-close-server)
+ (gnus-server-opened . mhspool-server-opened)
+ (gnus-status-message . mhspool-status-message)
+ (gnus-request-article . mhspool-request-article)
+ (gnus-request-group . mhspool-request-group)
+ (gnus-request-list . mhspool-request-list)
+ (gnus-request-post . mhspool-request-post)))
+ "Access method for NNTP, nnspool, and mhspool.")
+
+(defvar gnus-Group-buffer "*Newsgroup*")
+(defvar gnus-Subject-buffer "*Subject*")
+(defvar gnus-Article-buffer "*Article*")
+(defvar gnus-Digest-buffer "GNUS Digest")
+(defvar gnus-Digest-summary-buffer "GNUS Digest-summary")
+
+(defvar gnus-buffer-list
+ (list gnus-Group-buffer gnus-Subject-buffer gnus-Article-buffer
+ gnus-Digest-buffer gnus-Digest-summary-buffer)
+ "GNUS buffer names which should be killed when exiting.")
+
+(defvar gnus-variable-list
+ '(gnus-newsrc-options
+ gnus-newsrc-options-n-yes gnus-newsrc-options-n-no
+ gnus-newsrc-assoc gnus-killed-assoc gnus-marked-assoc)
+ "GNUS variables saved in the quick startup file.")
+
+(defvar gnus-overload-functions
+ '((news-inews gnus-inews-news "rnewspost")
+ (caesar-region gnus-caesar-region "rnews"))
+ "Functions overloaded by gnus.
+It is a list of `(original overload &optional file)'.")
+
+(defvar gnus-newsrc-options nil
+ "Options line in the .newsrc file.")
+
+(defvar gnus-newsrc-options-n-yes nil
+ "Regexp representing subscribed newsgroups.")
+
+(defvar gnus-newsrc-options-n-no nil
+ "Regexp representing unsubscribed newsgroups.")
+
+(defvar gnus-newsrc-assoc nil
+ "Assoc list of read articles.")
+
+(defvar gnus-killed-assoc nil
+ "Assoc list of newsgroups removed from `gnus-newsrc-assoc'.")
+
+(defvar gnus-marked-assoc nil
+ "Assoc list of articles marked as unread.")
+
+(defvar gnus-unread-hashtb nil
+ "Hashtable of unread articles.")
+
+(defvar gnus-active-hashtb nil
+ "Hashtable of active articles.")
+
+(defvar gnus-octive-hashtb nil
+ "Hashtable of OLD active articles.")
+
+(defvar gnus-current-startup-file nil
+ "Startup file for the current host.")
+
+(defvar gnus-last-search-regexp nil
+ "Default regexp for article search command.")
+
+(defvar gnus-last-shell-command nil
+ "Default shell command on article.")
+
+(defvar gnus-have-all-newsgroups nil)
+
+(defvar gnus-newsgroup-name nil)
+(defvar gnus-newsgroup-begin nil)
+(defvar gnus-newsgroup-end nil)
+(defvar gnus-newsgroup-last-rmail nil)
+(defvar gnus-newsgroup-last-mail nil)
+(defvar gnus-newsgroup-last-folder nil)
+(defvar gnus-newsgroup-last-file nil)
+
+(defvar gnus-newsgroup-unreads nil
+ "List of unread articles in the current newsgroup.")
+
+(defvar gnus-newsgroup-unselected nil
+ "List of unselected unread articles in the current newsgroup.")
+
+(defvar gnus-newsgroup-marked nil
+ "List of marked articles in the current newsgroup (a subset of unread art).")
+
+(defvar gnus-newsgroup-headers nil
+ "List of article headers in the current newsgroup.")
+
+(defvar gnus-current-article nil)
+(defvar gnus-current-headers nil)
+(defvar gnus-current-history nil)
+(defvar gnus-have-all-headers nil)
+(defvar gnus-last-article nil)
+(defvar gnus-current-kill-article nil)
+
+;; Save window configuration.
+(defvar gnus-winconf-kill-file nil)
+
+(defvar gnus-Group-mode-map nil)
+(defvar gnus-Subject-mode-map nil)
+(defvar gnus-Article-mode-map nil)
+(defvar gnus-Kill-file-mode-map nil)
+
+(defvar rmail-last-file (expand-file-name "~/XMBOX"))
+(defvar rmail-last-rmail-file (expand-file-name "~/XNEWS"))
+
+;; Define GNUS Subsystems.
+(autoload 'gnus-Group-post-news "gnuspost"
+ "Post an article." t)
+(autoload 'gnus-Subject-post-news "gnuspost"
+ "Post an article." t)
+(autoload 'gnus-Subject-post-reply "gnuspost"
+ "Post a reply article." t)
+(autoload 'gnus-Subject-post-reply-with-original "gnuspost"
+ "Post a reply article with original article." t)
+(autoload 'gnus-Subject-cancel-article "gnuspost"
+ "Cancel an article you posted." t)
+
+(autoload 'gnus-Subject-mail-reply "gnusmail"
+ "Reply mail to news author." t)
+(autoload 'gnus-Subject-mail-reply-with-original "gnusmail"
+ "Reply mail to news author with original article." t)
+(autoload 'gnus-Subject-mail-other-window "gnusmail"
+ "Compose mail in other window." t)
+
+(autoload 'gnus-Group-kill-group "gnusmisc"
+ "Kill newsgroup on current line." t)
+(autoload 'gnus-Group-yank-group "gnusmisc"
+ "Yank the last killed newsgroup on current line." t)
+(autoload 'gnus-Browse-killed-groups "gnusmisc"
+ "Browse the killed newsgroups." t)
+
+(autoload 'rmail-output "rmailout"
+ "Append this message to Unix mail file named FILE-NAME." t)
+(autoload 'mail-position-on-field "sendmail")
+(autoload 'mh-find-path "mh-e")
+(autoload 'mh-prompt-for-folder "mh-e")
+
+(put 'gnus-Group-mode 'mode-class 'special)
+(put 'gnus-Subject-mode 'mode-class 'special)
+(put 'gnus-Article-mode 'mode-class 'special)
+
+
+;;(put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
+
+(defmacro gnus-eval-in-buffer-window (buffer &rest forms)
+ "Pop to BUFFER, evaluate FORMS, and then returns to original window."
+ (` (let ((GNUSStartBufferWindow (selected-window)))
+ (unwind-protect
+ (progn
+ (pop-to-buffer (, buffer))
+ (,@ forms))
+ (select-window GNUSStartBufferWindow)))))
+
+(defmacro gnus-make-hashtable ()
+ '(make-abbrev-table))
+
+(defmacro gnus-gethash (string hashtable)
+ "Get hash value of STRING in HASHTABLE."
+ ;;(` (symbol-value (abbrev-symbol (, string) (, hashtable))))
+ (` (abbrev-expansion (, string) (, hashtable))))
+
+(defmacro gnus-sethash (string value hashtable)
+ "Set hash value. Arguments are STRING, VALUE, and HASHTABLE."
+ ;; We cannot use define-abbrev since it only accepts string as value.
+ (` (set (intern (, string) (, hashtable)) (, value))))
+
+;; Note: Macros defined here are also defined in nntp.el. I don't like
+;; to put them here, but many users got troubled with the old
+;; definitions in nntp.elc. These codes are NNTP 3.10 version.
+
+(defmacro nntp-header-number (header)
+ "Return article number in HEADER."
+ (` (aref (, header) 0)))
+
+(defmacro nntp-set-header-number (header number)
+ "Set article number of HEADER to NUMBER."
+ (` (aset (, header) 0 (, number))))
+
+(defmacro nntp-header-subject (header)
+ "Return subject string in HEADER."
+ (` (aref (, header) 1)))
+
+(defmacro nntp-set-header-subject (header subject)
+ "Set article subject of HEADER to SUBJECT."
+ (` (aset (, header) 1 (, subject))))
+
+(defmacro nntp-header-from (header)
+ "Return author string in HEADER."
+ (` (aref (, header) 2)))
+
+(defmacro nntp-set-header-from (header from)
+ "Set article author of HEADER to FROM."
+ (` (aset (, header) 2 (, from))))
+
+(defmacro nntp-header-xref (header)
+ "Return xref string in HEADER."
+ (` (aref (, header) 3)))
+
+(defmacro nntp-set-header-xref (header xref)
+ "Set article xref of HEADER to xref."
+ (` (aset (, header) 3 (, xref))))
+
+(defmacro nntp-header-lines (header)
+ "Return lines in HEADER."
+ (` (aref (, header) 4)))
+
+(defmacro nntp-set-header-lines (header lines)
+ "Set article lines of HEADER to LINES."
+ (` (aset (, header) 4 (, lines))))
+
+(defmacro nntp-header-date (header)
+ "Return date in HEADER."
+ (` (aref (, header) 5)))
+
+(defmacro nntp-set-header-date (header date)
+ "Set article date of HEADER to DATE."
+ (` (aset (, header) 5 (, date))))
+
+(defmacro nntp-header-id (header)
+ "Return Id in HEADER."
+ (` (aref (, header) 6)))
+
+(defmacro nntp-set-header-id (header id)
+ "Set article Id of HEADER to ID."
+ (` (aset (, header) 6 (, id))))
+
+(defmacro nntp-header-references (header)
+ "Return references in HEADER."
+ (` (aref (, header) 7)))
+
+(defmacro nntp-set-header-references (header ref)
+ "Set article references of HEADER to REF."
+ (` (aset (, header) 7 (, ref))))
+
+
+;;;
+;;; GNUS Group Mode
+;;;
+
+(if gnus-Group-mode-map
+ nil
+ (setq gnus-Group-mode-map (make-keymap))
+ (suppress-keymap gnus-Group-mode-map)
+ (define-key gnus-Group-mode-map " " 'gnus-Group-read-group)
+ (define-key gnus-Group-mode-map "=" 'gnus-Group-select-group)
+ (define-key gnus-Group-mode-map "j" 'gnus-Group-jump-to-group)
+ (define-key gnus-Group-mode-map "n" 'gnus-Group-next-unread-group)
+ (define-key gnus-Group-mode-map "p" 'gnus-Group-prev-unread-group)
+ (define-key gnus-Group-mode-map "\177" 'gnus-Group-prev-unread-group)
+ (define-key gnus-Group-mode-map "N" 'gnus-Group-next-group)
+ (define-key gnus-Group-mode-map "P" 'gnus-Group-prev-group)
+ (define-key gnus-Group-mode-map "\C-n" 'gnus-Group-next-group)
+ (define-key gnus-Group-mode-map "\C-p" 'gnus-Group-prev-group)
+ (define-key gnus-Group-mode-map "\r" 'next-line)
+ (define-key gnus-Group-mode-map "/" 'isearch-forward)
+ (define-key gnus-Group-mode-map "<" 'beginning-of-buffer)
+ (define-key gnus-Group-mode-map ">" 'end-of-buffer)
+ (define-key gnus-Group-mode-map "u" 'gnus-Group-unsubscribe-current-group)
+ (define-key gnus-Group-mode-map "U" 'gnus-Group-unsubscribe-group)
+ (define-key gnus-Group-mode-map "c" 'gnus-Group-catch-up)
+ (define-key gnus-Group-mode-map "C" 'gnus-Group-catch-up-all)
+ (define-key gnus-Group-mode-map "l" 'gnus-Group-list-groups)
+ (define-key gnus-Group-mode-map "L" 'gnus-Group-list-all-groups)
+ (define-key gnus-Group-mode-map "g" 'gnus-Group-get-new-news)
+ (define-key gnus-Group-mode-map "R" 'gnus-Group-restart)
+ (define-key gnus-Group-mode-map "b" 'gnus-Group-check-bogus-groups)
+ (define-key gnus-Group-mode-map "r" 'gnus-Group-restrict-groups)
+ (define-key gnus-Group-mode-map "a" 'gnus-Group-post-news)
+ (define-key gnus-Group-mode-map "\ek" 'gnus-Group-edit-local-kill)
+ (define-key gnus-Group-mode-map "\eK" 'gnus-Group-edit-global-kill)
+ (define-key gnus-Group-mode-map "\C-k" 'gnus-Group-kill-group)
+ (define-key gnus-Group-mode-map "\C-y" 'gnus-Group-yank-group)
+ (define-key gnus-Group-mode-map "\C-c\C-y" 'gnus-Browse-killed-groups)
+ (define-key gnus-Group-mode-map "V" 'gnus-version)
+ (define-key gnus-Group-mode-map "x" 'gnus-Group-force-update)
+ (define-key gnus-Group-mode-map "s" 'gnus-Group-force-update)
+ (define-key gnus-Group-mode-map "z" 'gnus-Group-suspend)
+ (define-key gnus-Group-mode-map "q" 'gnus-Group-exit)
+ (define-key gnus-Group-mode-map "Q" 'gnus-Group-quit)
+ (define-key gnus-Group-mode-map "?" 'gnus-Group-describe-briefly)
+ (define-key gnus-Group-mode-map "\C-c\C-i" 'gnus-Info-find-node))
+
+(defun gnus-Group-mode ()
+ "Major mode for reading network news.
+All normal editing commands are turned off.
+Instead, these commands are available:
+\\{gnus-Group-mode-map}
+
+ The name of the host running NNTP server is asked for if no default
+host is specified. It is also possible to choose another NNTP server
+even when the default server is defined by giving a prefix argument to
+the command `\\[gnus]'.
+
+ If an NNTP server is preceded by a colon such as `:Mail', the user's
+private directory `~/Mail' is used as a news spool. This makes it
+possible to read mail stored in MH folders or articles saved by GNUS.
+File names of mail or articles must consist of only numeric
+characters. Otherwise, they are ignored.
+
+ If there is a file named `~/.newsrc-SERVER', it is used as the
+startup file instead of standard one when talking to SERVER. It is
+possible to talk to many hosts by using different startup files for
+each.
+
+ Option `-n' of the options line in the startup file is recognized
+properly the same as the Bnews system. For example, if the options
+line is `options -n !talk talk.rumors', newsgroups under the `talk'
+hierarchy except for `talk.rumors' are ignored while checking new
+newsgroups.
+
+ If there is a file named `~/.signature-DISTRIBUTION', it is used as
+signature file instead of standard one when posting a news in
+DISTRIBUTION.
+
+ If an Info file generated from `gnus.texinfo' is installed, you can
+read an appropriate Info node of the Info file according to the
+current major mode of GNUS by \\[gnus-Info-find-node].
+
+ The variable `gnus-version', `nntp-version', `nnspool-version', and
+`mhspool-version' have the version numbers of this version of gnus.el,
+nntp.el, nnspool.el, and mhspoo.el, respectively.
+
+User customizable variables:
+ gnus-nntp-server
+ Specifies the name of the host running the NNTP server. If its
+ value is a string such as `:DIRECTORY', the user's private
+ DIRECTORY is used as a news spool. The variable is initialized
+ from the NNTPSERVER environment variable.
+
+ gnus-nntp-service
+ Specifies a NNTP service name. It is usually \"nntp\" or 119. Nil
+ forces GNUS to use a local news spool if the variable
+ `gnus-nntp-server' is set to the local host name.
+
+ gnus-startup-file
+ Specifies a startup file (.newsrc). If there is a file named
+ `.newsrc-SERVER', it's used instead when talking to SERVER. I
+ recommend you to use the server specific file, if you'd like to
+ talk to many servers. Especially if you'd like to read your
+ private directory, the name of the file must be
+ `.newsrc-:DIRECTORY'.
+
+ gnus-signature-file
+ Specifies a signature file (.signature). If there is a file named
+ `.signature-DISTRIBUTION', it's used instead when posting an
+ article in DISTRIBUTION. Set the variable to nil to prevent
+ appending the file automatically. If you use an NNTP inews which
+ comes with the NNTP package, you may have to set the variable to
+ nil.
+
+ gnus-use-cross-reference
+ Specifies what to do with cross references (Xref: field). If it
+ is nil, cross references are ignored. If it is t, articles in
+ subscribed newsgroups are only marked as read. Otherwise, if it
+ is not nil nor t, articles in all newsgroups are marked as read.
+
+ gnus-use-followup-to
+ Specifies what to do with followup-to: field. If it is nil, its
+ value is ignored. If it is non-nil, its value is used as followup
+ newsgroups. Especially, if it is t and field value is `poster',
+ your confirmation is required.
+
+ gnus-author-copy
+ Specifies a file name to save a copy of article you posted using
+ FCC: field. If the first character of the value is `|', the
+ contents of the article is piped out to a program specified by the
+ rest of the value. The variable is initialized from the
+ AUTHORCOPY environment variable.
+
+ gnus-author-copy-saver
+ Specifies a function to save an author copy. The function is
+ called with a file name. The default function `rmail-output'
+ saves in Unix mail format.
+
+ gnus-kill-file-name
+ Use specified file name as a KILL file (default to `KILL').
+
+ gnus-novice-user
+ Non-nil means that you are a novice to USENET. If non-nil,
+ verbose messages may be displayed or your confirmations may be
+ required.
+
+ gnus-interactive-post
+ Non-nil means that newsgroup, subject and distribution are asked
+ for interactively when posting a new article.
+
+ gnus-use-full-window
+ Non-nil means to take up the entire screen of Emacs.
+
+ gnus-window-configuration
+ Specifies the configuration of Group, Subject, and Article
+ windows. It is a list of (ACTION (G S A)), where G, S, and A are
+ the relative height of Group, Subject, and Article windows,
+ respectively. ACTION is `SelectNewsgroup', `ExitNewsgroup',
+ `SelectArticle', or `ExpandSubject'.
+
+ gnus-subscribe-newsgroup-method
+ Specifies a function called with a newsgroup name when new
+ newsgroup is found. The default definition adds new newsgroup at
+ the beginning of other newsgroups.
+
+Various hooks for customization:
+ gnus-Group-mode-hook
+ Entry to this mode calls the value with no arguments, if that
+ value is non-nil. This hook is called before GNUS is connected to
+ the NNTP server. So, you can change or define the NNTP server in
+ this hook.
+
+ gnus-Startup-hook
+ Called with no arguments after the NNTP server is selected. It is
+ possible to change the behavior of GNUS or initialize the
+ variables according to the selected NNTP server.
+
+ gnus-Group-prepare-hook
+ Called with no arguments after a newsgroup list is created in the
+ Newsgroup buffer, if that value is non-nil.
+
+ gnus-Save-newsrc-hook
+ Called with no arguments when saving newsrc file if that value is
+ non-nil.
+
+ gnus-Inews-article-hook
+ Called with no arguments when posting an article if that value is
+ non-nil. This hook is called just before posting an article, while
+ `news-inews-hook' is called before preparing article headers. If
+ you'd like to convert kanji code of the article, this hook is recommended.
+
+ gnus-Suspend-gnus-hook
+ Called with no arguments when suspending (not exiting) GNUS, if
+ that value is non-nil.
+
+ gnus-Exit-gnus-hook
+ Called with no arguments when exiting (not suspending) GNUS, if
+ that value is non-nil."
+ (interactive)
+ (kill-all-local-variables)
+ ;; Gee. Why don't you upgrade?
+ (cond ((boundp 'mode-line-modified)
+ (setq mode-line-modified "--- "))
+ ((listp (default-value 'mode-line-format))
+ (setq mode-line-format
+ (cons "--- " (cdr (default-value 'mode-line-format)))))
+ (t
+ (setq mode-line-format
+ "--- GNUS: List of Newsgroups %[(%m)%]----%3p-%-")))
+ (setq major-mode 'gnus-Group-mode)
+ (setq mode-name "Newsgroup")
+ (setq mode-line-buffer-identification "GNUS: List of Newsgroups")
+ (setq mode-line-process nil)
+ (use-local-map gnus-Group-mode-map)
+ (buffer-flush-undo (current-buffer))
+ (setq buffer-read-only t) ;Disable modification
+ (run-hooks 'gnus-Group-mode-hook))
+
+;;;###autoload
+(defun gnus (&optional confirm)
+ "Read network news.
+If optional argument CONFIRM is non-nil, ask NNTP server."
+ (interactive "P")
+ (unwind-protect
+ (progn
+ (switch-to-buffer (get-buffer-create gnus-Group-buffer))
+ (gnus-Group-mode)
+ (gnus-start-news-server confirm))
+ (if (not (gnus-server-opened))
+ (gnus-Group-quit)
+ ;; NNTP server is successfully open.
+ (setq mode-line-process (format " {%s}" gnus-nntp-server))
+ (let ((buffer-read-only nil))
+ (erase-buffer)
+ (gnus-Group-startup-message)
+ (sit-for 0))
+ (run-hooks 'gnus-Startup-hook)
+ (gnus-setup-news-info)
+ (if gnus-novice-user
+ (gnus-Group-describe-briefly)) ;Show brief help message.
+ (gnus-Group-list-groups nil)
+ )))
+
+(defun gnus-Group-startup-message ()
+ "Insert startup message in current buffer."
+ ;; Insert the message.
+ (insert "
+ GNUS Version 3.13
+
+ NNTP-based News Reader for GNU Emacs
+
+
+If you have any trouble with this software, please let me
+know. I will fix your problems in the next release.
+
+Comments, suggestions, and bug fixes are welcome.
+
+Masanobu UMEDA
+umerin@tc.Nagasaki.GO.JP")
+ ;; And then hack it.
+ ;; 57 is the longest line.
+ (indent-rigidly (point-min) (point-max) (/ (max (- (window-width) 57) 0) 2))
+ (goto-char (point-min))
+ ;; +4 is fuzzy factor.
+ (insert-char ?\n (/ (max (- (window-height) 18) 0) 2)))
+
+(defun gnus-Group-list-groups (show-all)
+ "List newsgroups in the Newsgroup buffer.
+If argument SHOW-ALL is non-nil, unsubscribed groups are also listed."
+ (interactive "P")
+ (let ((last-group ;Current newsgroup.
+ (gnus-Group-group-name))
+ (next-group ;Next possible newsgroup.
+ (progn
+ (gnus-Group-search-forward nil nil)
+ (gnus-Group-group-name)))
+ (prev-group ;Previous possible newsgroup.
+ (progn
+ (gnus-Group-search-forward t nil)
+ (gnus-Group-group-name))))
+ (gnus-Group-prepare show-all)
+ (if (zerop (buffer-size))
+ (message "No news is good news")
+ ;; Go to last newsgroup if possible. If cannot, try next and
+ ;; previous. If all fail, go to first unread newsgroup.
+ (goto-char (point-min))
+ (or (and last-group
+ (re-search-forward
+ (concat "^.+: " (regexp-quote last-group) "$") nil t))
+ (and next-group
+ (re-search-forward
+ (concat "^.+: " (regexp-quote next-group) "$") nil t))
+ (and prev-group
+ (re-search-forward
+ (concat "^.+: " (regexp-quote prev-group) "$") nil t))
+ (re-search-forward "^[ \t]+[1-9][0-9]*:" nil t))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ )))
+
+(defun gnus-Group-prepare (&optional all)
+ "Prepare list of newsgroups in current buffer.
+If optional argument ALL is non-nil, unsubscribed groups are also listed."
+ (let ((buffer-read-only nil)
+ (newsrc gnus-newsrc-assoc)
+ (group-info nil)
+ (group-name nil)
+ (unread-count 0)
+ ;; This specifies the format of Group buffer.
+ (cntl "%s%s%5d: %s\n"))
+ (erase-buffer)
+ ;; List newsgroups.
+ (while newsrc
+ (setq group-info (car newsrc))
+ (setq group-name (car group-info))
+ (setq unread-count (nth 1 (gnus-gethash group-name gnus-unread-hashtb)))
+ (if (or all
+ (and (nth 1 group-info) ;Subscribed.
+ (> unread-count 0))) ;There are unread articles.
+ ;; Yes, I can use gnus-Group-prepare-line, but this is faster.
+ (insert
+ (format cntl
+ ;; Subscribed or not.
+ (if (nth 1 group-info) " " "U")
+ ;; Has new news?
+ (if (and (> unread-count 0)
+ (>= 0
+ (- unread-count
+ (length
+ (cdr (assoc group-name
+ gnus-marked-assoc))))))
+ "*" " ")
+ ;; Number of unread articles.
+ unread-count
+ ;; Newsgroup name.
+ group-name))
+ )
+ (setq newsrc (cdr newsrc))
+ )
+ (setq gnus-have-all-newsgroups all)
+ (goto-char (point-min))
+ (run-hooks 'gnus-Group-prepare-hook)
+ ))
+
+(defun gnus-Group-prepare-line (info)
+ "Return a string for the Newsgroup buffer from INFO.
+INFO is an element of gnus-newsrc-assoc or gnus-killed-assoc."
+ (let* ((group-name (car info))
+ (unread-count
+ (or (nth 1 (gnus-gethash group-name gnus-unread-hashtb))
+ ;; Not in hash table, so compute it now.
+ (gnus-number-of-articles
+ (gnus-difference-of-range
+ (nth 2 (gnus-gethash group-name gnus-active-hashtb))
+ (nthcdr 2 info)))))
+ ;; This specifies the format of Group buffer.
+ (cntl "%s%s%5d: %s\n"))
+ (format cntl
+ ;; Subscribed or not.
+ (if (nth 1 info) " " "U")
+ ;; Has new news?
+ (if (and (> unread-count 0)
+ (>= 0
+ (- unread-count
+ (length
+ (cdr (assoc group-name gnus-marked-assoc))))))
+ "*" " ")
+ ;; Number of unread articles.
+ unread-count
+ ;; Newsgroup name.
+ group-name
+ )))
+
+(defun gnus-Group-update-group (group &optional visible-only)
+ "Update newsgroup info of GROUP.
+If optional argument VISIBLE-ONLY is non-nil, non displayed group is ignored."
+ (let ((buffer-read-only nil)
+ (visible nil))
+ ;; Buffer may be narrowed.
+ (save-restriction
+ (widen)
+ ;; Search point to modify.
+ (goto-char (point-min))
+ (if (re-search-forward (concat "^.+: " (regexp-quote group) "$") nil t)
+ ;; GROUP is listed in current buffer. So, delete old line.
+ (progn
+ (setq visible t)
+ (beginning-of-line)
+ (delete-region (point) (progn (forward-line 1) (point)))
+ ))
+ (if (or visible (not visible-only))
+ (progn
+ (insert (gnus-Group-prepare-line (assoc group gnus-newsrc-assoc)))
+ (forward-line -1) ;Move point on that line.
+ ))
+ )))
+
+;; GNUS Group mode command
+
+(defun gnus-Group-group-name ()
+ "Get newsgroup name around point."
+ (save-excursion
+ (beginning-of-line)
+ (if (looking-at ".[* \t]*[0-9]+:[ \t]+\\([^ \t\n]+\\)$")
+ (buffer-substring (match-beginning 1) (match-end 1))
+ )))
+
+(defun gnus-Group-read-group (all &optional no-article)
+ "Read news in this newsgroup.
+If argument ALL is non-nil, already read articles become readable.
+If optional argument NO-ARTICLE is non-nil, no article body is displayed."
+ (interactive "P")
+ (let ((group (gnus-Group-group-name))) ;Newsgroup name to read.
+ (if group
+ (gnus-Subject-read-group
+ group
+ (or all
+ ;;(not (nth 1 (assoc group gnus-newsrc-assoc))) ;Unsubscribed
+ (zerop
+ (nth 1 (gnus-gethash group gnus-unread-hashtb)))) ;No unread
+ no-article
+ ))
+ ))
+
+(defun gnus-Group-select-group (all)
+ "Select this newsgroup.
+No article is selected automatically.
+If argument ALL is non-nil, already read articles become readable."
+ (interactive "P")
+ (gnus-Group-read-group all t))
+
+(defun gnus-Group-jump-to-group (group)
+ "Jump to newsgroup GROUP."
+ (interactive
+ (list (completing-read "Newsgroup: " gnus-newsrc-assoc nil 'require-match)))
+ (goto-char (point-min))
+ (or (re-search-forward (concat "^.+: " (regexp-quote group) "$") nil t)
+ (if (assoc group gnus-newsrc-assoc)
+ ;; Add GROUP entry, then seach again.
+ (gnus-Group-update-group group)))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t))
+
+(defun gnus-Group-search-forward (backward any-group)
+ "Search for newsgroup forward.
+If first argument BACKWARD is non-nil, search backward instead.
+If second argument ANY-GROUP is non-nil, unsubscribed or empty
+group may be selected."
+ (let ((func (if backward 're-search-backward 're-search-forward))
+ (regexp
+ (format "^%s[ \t]*\\(%s\\):"
+ (if any-group ".." " [ \t]")
+ (if any-group "[0-9]+" "[1-9][0-9]*")))
+ (found nil))
+ (if backward
+ (beginning-of-line)
+ (end-of-line))
+ (setq found (funcall func regexp nil t))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ ;; Return T if found.
+ found
+ ))
+
+(defun gnus-Group-next-group (n)
+ "Go to next N'th newsgroup."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Group-search-forward nil t))
+ (setq n (1- n)))
+ (or (gnus-Group-search-forward nil t)
+ (message "No more newsgroups")))
+
+(defun gnus-Group-next-unread-group (n)
+ "Go to next N'th unread newsgroup."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Group-search-forward nil nil))
+ (setq n (1- n)))
+ (or (gnus-Group-search-forward nil nil)
+ (message "No more unread newsgroups")))
+
+(defun gnus-Group-prev-group (n)
+ "Go to previous N'th newsgroup."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Group-search-forward t t))
+ (setq n (1- n)))
+ (or (gnus-Group-search-forward t t)
+ (message "No more newsgroups")))
+
+(defun gnus-Group-prev-unread-group (n)
+ "Go to previous N'th unread newsgroup."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Group-search-forward t nil))
+ (setq n (1- n)))
+ (or (gnus-Group-search-forward t nil)
+ (message "No more unread newsgroups")))
+
+(defun gnus-Group-catch-up (all &optional quietly)
+ "Mark all articles not marked as unread in current newsgroup as read.
+If prefix argument ALL is non-nil, all articles are marked as read.
+Cross references (Xref: field) of articles are ignored."
+ (interactive "P")
+ (let* ((group (gnus-Group-group-name))
+ (marked (if (not all)
+ (cdr (assoc group gnus-marked-assoc)))))
+ (and group
+ (or quietly
+ (y-or-n-p
+ (if all
+ "Do you really want to mark everything as read? "
+ "Delete all articles not marked as read? ")))
+ (progn
+ (message "") ;Erase "Yes or No" question.
+ ;; Any marked articles will be preserved.
+ (gnus-update-unread-articles group marked marked)
+ (gnus-Group-update-group group)
+ (gnus-Group-next-group 1)))
+ ))
+
+(defun gnus-Group-catch-up-all (&optional quietly)
+ "Mark all articles in current newsgroup as read.
+Cross references (Xref: field) of articles are ignored."
+ (interactive)
+ (gnus-Group-catch-up t quietly))
+
+(defun gnus-Group-unsubscribe-current-group ()
+ "Toggle subscribe from/to unsubscribe current group."
+ (interactive)
+ (gnus-Group-unsubscribe-group (gnus-Group-group-name))
+ (gnus-Group-next-group 1))
+
+(defun gnus-Group-unsubscribe-group (group)
+ "Toggle subscribe from/to unsubscribe GROUP.
+New newsgroup is added to .newsrc automatically."
+ (interactive
+ (list (completing-read "Newsgroup: "
+ gnus-active-hashtb nil 'require-match)))
+ (let ((newsrc (assoc group gnus-newsrc-assoc)))
+ (cond ((not (null newsrc))
+ ;; Toggle subscription flag.
+ (setcar (nthcdr 1 newsrc) (not (nth 1 newsrc)))
+ (gnus-update-newsrc-buffer group)
+ (gnus-Group-update-group group)
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t))
+ ((and (stringp group)
+ (gnus-gethash group gnus-active-hashtb))
+ ;; Add new newsgroup.
+ (gnus-add-newsgroup group)
+ (gnus-Group-update-group group)
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t))
+ (t (error "No such newsgroup: %s" group)))
+ ))
+
+(defun gnus-Group-list-all-groups ()
+ "List all of newsgroups in the Newsgroup buffer."
+ (interactive)
+ (gnus-Group-list-groups t))
+
+(defun gnus-Group-get-new-news ()
+ "Get newly arrived articles. In fact, read the active file again."
+ (interactive)
+ (gnus-setup-news-info)
+ (gnus-Group-list-groups gnus-have-all-newsgroups))
+
+(defun gnus-Group-restart ()
+ "Force GNUS to read the raw startup file."
+ (interactive)
+ (gnus-save-newsrc-file)
+ (gnus-setup-news-info t) ;Force to read the raw startup file.
+ (gnus-Group-list-groups gnus-have-all-newsgroups))
+
+(defun gnus-Group-check-bogus-groups ()
+ "Check bogus newsgroups."
+ (interactive)
+ (gnus-check-bogus-newsgroups t) ;Require confirmation.
+ (gnus-Group-list-groups gnus-have-all-newsgroups))
+
+(defun gnus-Group-restrict-groups (start end)
+ "Restrict visible newsgroups to the current region (START and END).
+Type \\[widen] to remove restriction."
+ (interactive "r")
+ (save-excursion
+ (narrow-to-region (progn
+ (goto-char start)
+ (beginning-of-line)
+ (point))
+ (progn
+ (goto-char end)
+ (forward-line 1)
+ (point))))
+ (message (substitute-command-keys "Type \\[widen] to remove restriction")))
+
+(defun gnus-Group-edit-global-kill ()
+ "Edit a global KILL file."
+ (interactive)
+ (setq gnus-current-kill-article nil) ;No articles selected.
+ (gnus-Kill-file-edit-file nil) ;Nil stands for global KILL file.
+ (message
+ (substitute-command-keys
+ "Editing a global KILL file (Type \\[gnus-Kill-file-exit] to exit)")))
+
+(defun gnus-Group-edit-local-kill ()
+ "Edit a local KILL file."
+ (interactive)
+ (setq gnus-current-kill-article nil) ;No articles selected.
+ (gnus-Kill-file-edit-file (gnus-Group-group-name))
+ (message
+ (substitute-command-keys
+ "Editing a local KILL file (Type \\[gnus-Kill-file-exit] to exit)")))
+
+(defun gnus-Group-force-update ()
+ "Update .newsrc file."
+ (interactive)
+ (gnus-save-newsrc-file))
+
+(defun gnus-Group-suspend ()
+ "Suspend the current GNUS session.
+In fact, cleanup buffers except for Group Mode buffer.
+The hook `gnus-Suspend-gnus-hook' is called before actually suspending."
+ (interactive)
+ (run-hooks 'gnus-Suspend-gnus-hook)
+ ;; Kill GNUS buffers except for Group Mode buffer.
+ (let ((buffers gnus-buffer-list))
+ (while buffers
+ (and (not (eq (car buffers) gnus-Group-buffer))
+ (get-buffer (car buffers))
+ (kill-buffer (car buffers)))
+ (setq buffers (cdr buffers))
+ ))
+ (bury-buffer))
+
+(defun gnus-Group-exit ()
+ "Quit reading news after updating .newsrc.
+The hook `gnus-Exit-gnus-hook' is called before actually quitting."
+ (interactive)
+ (if (or noninteractive ;For gnus-batch-kill
+ (zerop (buffer-size)) ;No news is good news.
+ (not (gnus-server-opened)) ;NNTP connection closed.
+ (y-or-n-p "Are you sure you want to quit reading news? "))
+ (progn
+ (message "") ;Erase "Yes or No" question.
+ (run-hooks 'gnus-Exit-gnus-hook)
+ (gnus-save-newsrc-file)
+ (gnus-clear-system)
+ (gnus-close-server))
+ ))
+
+(defun gnus-Group-quit ()
+ "Quit reading news without updating .newsrc.
+The hook `gnus-Exit-gnus-hook' is called before actually quitting."
+ (interactive)
+ (if (or (zerop (buffer-size))
+ (not (gnus-server-opened))
+ (yes-or-no-p
+ (format "Quit reading news without saving %s? "
+ (file-name-nondirectory gnus-current-startup-file))))
+ (progn
+ (message "") ;Erase "Yes or No" question.
+ (run-hooks 'gnus-Exit-gnus-hook)
+ (gnus-clear-system)
+ (gnus-close-server))
+ ))
+
+(defun gnus-Group-describe-briefly ()
+ "Describe Group mode commands briefly."
+ (interactive)
+ (message
+ (concat
+ (substitute-command-keys "\\[gnus-Group-read-group]:Select ")
+ (substitute-command-keys "\\[gnus-Group-next-unread-group]:Forward ")
+ (substitute-command-keys "\\[gnus-Group-prev-unread-group]:Backward ")
+ (substitute-command-keys "\\[gnus-Group-exit]:Exit ")
+ (substitute-command-keys "\\[gnus-Info-find-node]:Run Info ")
+ (substitute-command-keys "\\[gnus-Group-describe-briefly]:This help")
+ )))
+
+
+;;;
+;;; GNUS Subject Mode
+;;;
+
+(if gnus-Subject-mode-map
+ nil
+ (setq gnus-Subject-mode-map (make-keymap))
+ (suppress-keymap gnus-Subject-mode-map)
+ (define-key gnus-Subject-mode-map " " 'gnus-Subject-next-page)
+ (define-key gnus-Subject-mode-map "\177" 'gnus-Subject-prev-page)
+ (define-key gnus-Subject-mode-map "\r" 'gnus-Subject-scroll-up)
+ (define-key gnus-Subject-mode-map "n" 'gnus-Subject-next-unread-article)
+ (define-key gnus-Subject-mode-map "p" 'gnus-Subject-prev-unread-article)
+ (define-key gnus-Subject-mode-map "N" 'gnus-Subject-next-article)
+ (define-key gnus-Subject-mode-map "P" 'gnus-Subject-prev-article)
+ (define-key gnus-Subject-mode-map "\e\C-n" 'gnus-Subject-next-same-subject)
+ (define-key gnus-Subject-mode-map "\e\C-p" 'gnus-Subject-prev-same-subject)
+ ;;(define-key gnus-Subject-mode-map "\e\C-n" 'gnus-Subject-next-unread-same-subject)
+ ;;(define-key gnus-Subject-mode-map "\e\C-p" 'gnus-Subject-prev-unread-same-subject)
+ (define-key gnus-Subject-mode-map "\C-c\C-n" 'gnus-Subject-next-digest)
+ (define-key gnus-Subject-mode-map "\C-c\C-p" 'gnus-Subject-prev-digest)
+ (define-key gnus-Subject-mode-map "\C-n" 'gnus-Subject-next-subject)
+ (define-key gnus-Subject-mode-map "\C-p" 'gnus-Subject-prev-subject)
+ (define-key gnus-Subject-mode-map "\en" 'gnus-Subject-next-unread-subject)
+ (define-key gnus-Subject-mode-map "\ep" 'gnus-Subject-prev-unread-subject)
+ ;;(define-key gnus-Subject-mode-map "\C-cn" 'gnus-Subject-next-group)
+ ;;(define-key gnus-Subject-mode-map "\C-cp" 'gnus-Subject-prev-group)
+ (define-key gnus-Subject-mode-map "." 'gnus-Subject-first-unread-article)
+ (define-key gnus-Subject-mode-map "/" 'isearch-forward)
+ (define-key gnus-Subject-mode-map "s" 'gnus-Subject-isearch-article)
+ (define-key gnus-Subject-mode-map "\es" 'gnus-Subject-search-article-forward)
+ (define-key gnus-Subject-mode-map "\eS" 'gnus-Subject-search-article-backward)
+ (define-key gnus-Subject-mode-map "<" 'gnus-Subject-beginning-of-article)
+ (define-key gnus-Subject-mode-map ">" 'gnus-Subject-end-of-article)
+ (define-key gnus-Subject-mode-map "j" 'gnus-Subject-goto-subject)
+ (define-key gnus-Subject-mode-map "J" 'gnus-Subject-goto-article)
+ (define-key gnus-Subject-mode-map "l" 'gnus-Subject-goto-last-article)
+ (define-key gnus-Subject-mode-map "^" 'gnus-Subject-refer-parent-article)
+ (define-key gnus-Subject-mode-map "\er" 'gnus-Subject-refer-article)
+ (define-key gnus-Subject-mode-map "u" 'gnus-Subject-mark-as-unread-forward)
+ (define-key gnus-Subject-mode-map "U" 'gnus-Subject-mark-as-unread-backward)
+ (define-key gnus-Subject-mode-map "d" 'gnus-Subject-mark-as-read-forward)
+ (define-key gnus-Subject-mode-map "D" 'gnus-Subject-mark-as-read-backward)
+ (define-key gnus-Subject-mode-map "\eu" 'gnus-Subject-clear-mark-forward)
+ (define-key gnus-Subject-mode-map "\eU" 'gnus-Subject-clear-mark-backward)
+ (define-key gnus-Subject-mode-map "k" 'gnus-Subject-kill-same-subject-and-select)
+ (define-key gnus-Subject-mode-map "\C-k" 'gnus-Subject-kill-same-subject)
+ (define-key gnus-Subject-mode-map "\e\C-t" 'gnus-Subject-toggle-threads)
+ (define-key gnus-Subject-mode-map "\e\C-s" 'gnus-Subject-show-thread)
+ (define-key gnus-Subject-mode-map "\e\C-h" 'gnus-Subject-hide-thread)
+ (define-key gnus-Subject-mode-map "\e\C-f" 'gnus-Subject-next-thread)
+ (define-key gnus-Subject-mode-map "\e\C-b" 'gnus-Subject-prev-thread)
+ (define-key gnus-Subject-mode-map "\e\C-u" 'gnus-Subject-up-thread)
+ (define-key gnus-Subject-mode-map "\e\C-d" 'gnus-Subject-down-thread)
+ (define-key gnus-Subject-mode-map "\e\C-k" 'gnus-Subject-kill-thread)
+ (define-key gnus-Subject-mode-map "&" 'gnus-Subject-execute-command)
+ ;;(define-key gnus-Subject-mode-map "c" 'gnus-Subject-catch-up)
+ ;;(define-key gnus-Subject-mode-map "c" 'gnus-Subject-catch-up-all)
+ (define-key gnus-Subject-mode-map "c" 'gnus-Subject-catch-up-and-exit)
+ ;;(define-key gnus-Subject-mode-map "c" 'gnus-Subject-catch-up-all-and-exit)
+ (define-key gnus-Subject-mode-map "\C-t" 'gnus-Subject-toggle-truncation)
+ (define-key gnus-Subject-mode-map "x" 'gnus-Subject-delete-marked-as-read)
+ (define-key gnus-Subject-mode-map "X" 'gnus-Subject-delete-marked-with)
+ (define-key gnus-Subject-mode-map "\C-c\C-sn" 'gnus-Subject-sort-by-number)
+ (define-key gnus-Subject-mode-map "\C-c\C-sa" 'gnus-Subject-sort-by-author)
+ (define-key gnus-Subject-mode-map "\C-c\C-ss" 'gnus-Subject-sort-by-subject)
+ (define-key gnus-Subject-mode-map "\C-c\C-sd" 'gnus-Subject-sort-by-date)
+ (define-key gnus-Subject-mode-map "\C-c\C-s\C-n" 'gnus-Subject-sort-by-number)
+ (define-key gnus-Subject-mode-map "\C-c\C-s\C-a" 'gnus-Subject-sort-by-author)
+ (define-key gnus-Subject-mode-map "\C-c\C-s\C-s" 'gnus-Subject-sort-by-subject)
+ (define-key gnus-Subject-mode-map "\C-c\C-s\C-d" 'gnus-Subject-sort-by-date)
+ (define-key gnus-Subject-mode-map "=" 'gnus-Subject-expand-window)
+ (define-key gnus-Subject-mode-map "G" 'gnus-Subject-reselect-current-group)
+ (define-key gnus-Subject-mode-map "w" 'gnus-Subject-stop-page-breaking)
+ (define-key gnus-Subject-mode-map "\C-c\C-r" 'gnus-Subject-caesar-message)
+ (define-key gnus-Subject-mode-map "g" 'gnus-Subject-show-article)
+ (define-key gnus-Subject-mode-map "t" 'gnus-Subject-toggle-header)
+ (define-key gnus-Subject-mode-map "v" 'gnus-Subject-show-all-headers)
+ (define-key gnus-Subject-mode-map "\C-d" 'gnus-Subject-rmail-digest)
+ (define-key gnus-Subject-mode-map "a" 'gnus-Subject-post-news)
+ (define-key gnus-Subject-mode-map "f" 'gnus-Subject-post-reply)
+ (define-key gnus-Subject-mode-map "F" 'gnus-Subject-post-reply-with-original)
+ (define-key gnus-Subject-mode-map "C" 'gnus-Subject-cancel-article)
+ (define-key gnus-Subject-mode-map "r" 'gnus-Subject-mail-reply)
+ (define-key gnus-Subject-mode-map "R" 'gnus-Subject-mail-reply-with-original)
+ (define-key gnus-Subject-mode-map "m" 'gnus-Subject-mail-other-window)
+ (define-key gnus-Subject-mode-map "o" 'gnus-Subject-save-article)
+ (define-key gnus-Subject-mode-map "\C-o" 'gnus-Subject-save-in-mail)
+ (define-key gnus-Subject-mode-map "|" 'gnus-Subject-pipe-output)
+ (define-key gnus-Subject-mode-map "\ek" 'gnus-Subject-edit-local-kill)
+ (define-key gnus-Subject-mode-map "\eK" 'gnus-Subject-edit-global-kill)
+ (define-key gnus-Subject-mode-map "V" 'gnus-version)
+ (define-key gnus-Subject-mode-map "q" 'gnus-Subject-exit)
+ (define-key gnus-Subject-mode-map "Q" 'gnus-Subject-quit)
+ (define-key gnus-Subject-mode-map "?" 'gnus-Subject-describe-briefly)
+ (define-key gnus-Subject-mode-map "\C-c\C-i" 'gnus-Info-find-node))
+
+(defun gnus-Subject-mode ()
+ "Major mode for reading articles in this newsgroup.
+All normal editing commands are turned off.
+Instead, these commands are available:
+\\{gnus-Subject-mode-map}
+
+User customizable variables:
+ gnus-large-newsgroup
+ The number of articles which indicates a large newsgroup. If the
+ number of articles in a newsgroup is greater than the value, the
+ number of articles to be selected is asked for. If the given value
+ N is positive, the last N articles is selected. If N is negative,
+ the first N articles are selected. An empty string means to select
+ all articles.
+
+ gnus-use-long-file-name
+ Non-nil means that a newsgroup name is used as a default file name
+ to save articles to. If it's nil, the directory form of a
+ newsgroup is used instead.
+
+ gnus-default-article-saver
+ Specifies your favorite article saver which is interactively
+ funcallable. Following functions are available:
+
+ gnus-Subject-save-in-rmail (in Rmail format)
+ gnus-Subject-save-in-mail (in Unix mail format)
+ gnus-Subject-save-in-folder (in MH folder)
+ gnus-Subject-save-in-file (in article format).
+
+ gnus-rmail-save-name
+ gnus-mail-save-name
+ gnus-folder-save-name
+ gnus-file-save-name
+ Specifies a function generating a file name to save articles in
+ specified format. The function is called with NEWSGROUP, HEADERS,
+ and optional LAST-FILE. Access macros to the headers are defined
+ as nntp-header-FIELD, and functions are defined as `gnus-header-FIELD'.
+
+ gnus-article-save-directory
+ Specifies a directory name to save articles to using the commands
+ `gnus-Subject-save-in-rmail', `gnus-Subject-save-in-mail' and
+ `gnus-Subject-save-in-file'. The variable is initialized from the
+ SAVEDIR environment variable.
+
+ gnus-show-all-headers
+ Non-nil means that all headers of an article are shown.
+
+ gnus-save-all-headers
+ Non-nil means that all headers of an article are saved in a file.
+
+ gnus-show-threads
+ Non-nil means that conversation threads are shown in tree structure.
+
+ gnus-thread-hide-subject
+ Non-nil means that subjects for thread subtrees are hidden.
+
+ gnus-thread-hide-subtree
+ Non-nil means that thread subtrees are hidden initially.
+
+ gnus-thread-hide-killed
+ Non-nil means that killed thread subtrees are hidden automatically.
+
+ gnus-thread-ignore-subject
+ Non-nil means that subject differences are ignored in constructing
+ thread trees.
+
+ gnus-thread-indent-level
+ Indentation of thread subtrees.
+
+ gnus-optional-headers
+ Specifies a function which generates an optional string displayed
+ in the Subject buffer. The function is called with an article
+ HEADERS. The result must be a string excluding `[' and `]'. The
+ default function returns a string like NNN:AUTHOR, where NNN is
+ the number of lines in an article and AUTHOR is the name of the
+ author.
+
+ gnus-auto-extend-newsgroup
+ Non-nil means visible articles are extended to forward and
+ backward automatically if possible.
+
+ gnus-auto-select-first
+ Non-nil means the first unread article is selected automagically
+ when a newsgroup is selected normally (by gnus-Group-read-group).
+ If you'd like to prevent automatic selection of the first unread
+ article in some newsgroups, set the variable to nil in
+ gnus-Select-group-hook or gnus-Apply-kill-hook.
+
+ gnus-auto-select-next
+ Non-nil means the next newsgroup is selected automagically at the
+ end of the newsgroup. If the value is t and the next newsgroup is
+ empty (no unread articles), GNUS will exit Subject mode and go
+ back to Group mode. If the value is neither nil nor t, GNUS won't
+ exit Subject mode but select the following unread newsgroup.
+ Especially, if the value is the symbol `quietly', the next unread
+ newsgroup will be selected without any confirmations.
+
+ gnus-auto-select-same
+ Non-nil means an article with the same subject as the current
+ article is selected automagically like `rn -S'.
+
+ gnus-auto-center-subject
+ Non-nil means the point of Subject Mode window is always kept
+ centered.
+
+ gnus-break-pages
+ Non-nil means an article is broken into pages at page delimiters.
+ This may not work with some versions of GNU Emacs earlier than
+ version 18.50.
+
+ gnus-page-delimiter
+ Specifies a regexp describing line-beginnings that separate pages
+ of news article.
+
+ [gnus-more-message is obsolete. overlay-arrow-string interfares
+ with other subsystems, such as dbx mode.]
+
+ gnus-digest-show-summary
+ Non-nil means that a summary of digest messages is shown when
+ reading a digest article using `gnus-Subject-rmail-digest' command.
+
+ gnus-digest-separator
+ Specifies a regexp separating messages in a digest article.
+
+ gnus-mail-reply-method
+ gnus-mail-other-window-method
+ Specifies a function to begin composing mail message using
+ commands gnus-Subject-mail-reply and
+ gnus-Subject-mail-other-window. Functions
+ gnus-mail-reply-using-mail and gnus-mail-reply-using-mhe are
+ available for the value of gnus-mail-reply-method. And functions
+ gnus-mail-other-window-using-mail and
+ gnus-mail-other-window-using-mhe are available for the value of
+ gnus-mail-other-window-method.
+
+Various hooks for customization:
+ gnus-Subject-mode-hook
+ Entry to this mode calls the value with no arguments, if that
+ value is non-nil.
+
+ gnus-Select-group-hook
+ Called with no arguments when newsgroup is selected, if that value
+ is non-nil. It is possible to sort subjects in this hook. See the
+ documentation of this variable for more information.
+
+ gnus-Subject-prepare-hook
+ Called with no arguments after a subject list is created in the
+ Subject buffer, if that value is non-nil. If you'd like to modify
+ the buffer, you can use this hook.
+
+ gnus-Select-article-hook
+ Called with no arguments when an article is selected, if that
+ value is non-nil. See the documentation of this variable for
+ more information.
+
+ gnus-Select-digest-hook
+ Called with no arguments when reading digest messages using Rmail,
+ if that value is non-nil. This hook can be used to modify an
+ article so that Rmail can work with it. See the documentation of
+ the variable for more information.
+
+ gnus-Rmail-digest-hook
+ Called with no arguments when reading digest messages using Rmail,
+ if that value is non-nil. This hook is intended to customize Rmail
+ mode.
+
+ gnus-Apply-kill-hook
+ Called with no arguments when a newsgroup is selected and the
+ Subject buffer is prepared. This hook is intended to apply a KILL
+ file to the selected newsgroup. The format of KILL file is
+ completely different from that of version 3.8. You have to rewrite
+ them in the new format. See the documentation of Kill file mode
+ for more information.
+
+ gnus-Mark-article-hook
+ Called with no arguments when an article is selected at the first
+ time. The hook is intended to mark an article as read (or unread)
+ automatically when it is selected. See the documentation of the
+ variable for more information.
+
+ gnus-Exit-group-hook
+ Called with no arguments when exiting the current newsgroup, if
+ that value is non-nil. If your machine is so slow that exiting
+ from Subject mode takes very long time, inhibit marking articles
+ as read using cross-references by setting the variable
+ `gnus-newsgroup-headers' to nil in this hook."
+ (interactive)
+ (kill-all-local-variables)
+ ;; Gee. Why don't you upgrade?
+ (cond ((boundp 'mode-line-modified)
+ (setq mode-line-modified "--- "))
+ ((listp (default-value 'mode-line-format))
+ (setq mode-line-format
+ (cons "--- " (cdr (default-value 'mode-line-format))))))
+ (make-local-variable 'global-mode-string)
+ (setq global-mode-string nil)
+ (setq major-mode 'gnus-Subject-mode)
+ (setq mode-name "Subject")
+ ;;(setq mode-line-process '(" " gnus-newsgroup-name))
+ (make-local-variable 'minor-mode-alist)
+ (or (assq 'gnus-show-threads minor-mode-alist)
+ (setq minor-mode-alist
+ (cons (list 'gnus-show-threads " Thread") minor-mode-alist)))
+ (gnus-Subject-set-mode-line)
+ (use-local-map gnus-Subject-mode-map)
+ (buffer-flush-undo (current-buffer))
+ (setq buffer-read-only t) ;Disable modification
+ (setq truncate-lines t) ;Stop line folding
+ (setq selective-display t)
+ (setq selective-display-ellipses t) ;Display `...'
+ ;;(setq case-fold-search t)
+ (run-hooks 'gnus-Subject-mode-hook))
+
+(defun gnus-Subject-setup-buffer ()
+ "Initialize subject display buffer."
+ (if (get-buffer gnus-Subject-buffer)
+ (set-buffer gnus-Subject-buffer)
+ (set-buffer (get-buffer-create gnus-Subject-buffer))
+ (gnus-Subject-mode)
+ ))
+
+(defun gnus-Subject-read-group (group &optional show-all no-article)
+ "Start reading news in newsgroup GROUP.
+If optional first argument SHOW-ALL is non-nil, already read articles are
+also listed.
+If optional second argument NO-ARTICLE is non-nil, no article is selected
+initially."
+ (message "Retrieving newsgroup: %s..." group)
+ (if (gnus-select-newsgroup group show-all)
+ (progn
+ ;; Don't switch-to-buffer to prevent displaying old contents
+ ;; of the buffer until new subjects list is created.
+ ;; Suggested by Juha Heinanen <jh@tut.fi>
+ (gnus-Subject-setup-buffer)
+ ;; You can change the order of subjects in this hook.
+ (run-hooks 'gnus-Select-group-hook)
+ (gnus-Subject-prepare)
+ ;; Function `gnus-apply-kill-file' must be called in this hook.
+ (run-hooks 'gnus-Apply-kill-hook)
+ (if (zerop (buffer-size))
+ ;; This newsgroup is empty.
+ (progn
+ (gnus-Subject-catch-up-and-exit nil t) ;Without confirmations.
+ (message "No unread news"))
+ ;; Hide conversation thread subtrees. We cannot do this in
+ ;; gnus-Subject-prepare-hook since kill processing may not
+ ;; work with hidden articles.
+ (and gnus-show-threads
+ gnus-thread-hide-subtree
+ (gnus-Subject-hide-all-threads))
+ ;; Show first unread article if requested.
+ (goto-char (point-min))
+ (if (and (not no-article)
+ gnus-auto-select-first
+ (gnus-Subject-first-unread-article))
+ ;; Window is configured automatically.
+ ;; Current buffer may be changed as a result of hook
+ ;; evaluation, especially by gnus-Subject-rmail-digest
+ ;; command, so we should adjust cursor point carefully.
+ (if (eq (current-buffer) (get-buffer gnus-Subject-buffer))
+ (progn
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)))
+ (gnus-configure-windows 'SelectNewsgroup)
+ (pop-to-buffer gnus-Subject-buffer)
+ (gnus-Subject-set-mode-line)
+ ;; I sometime get confused with the old Article buffer.
+ (if (get-buffer gnus-Article-buffer)
+ (if (get-buffer-window gnus-Article-buffer)
+ (save-excursion
+ (set-buffer gnus-Article-buffer)
+ (let ((buffer-read-only nil))
+ (erase-buffer)))
+ (kill-buffer gnus-Article-buffer)))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t))
+ ))
+ ;; Cannot select newsgroup GROUP.
+ (if (gnus-gethash group gnus-active-hashtb)
+ (progn
+ ;; If NNTP is used, nntp_access file may not be installed
+ ;; properly. Otherwise, may be active file problem.
+ (ding)
+ (message "Cannot select %s. May be security or active file problem." group)
+ (sit-for 0))
+ ;; Check bogus newsgroups.
+ ;; We must be in Group Mode buffer.
+ (gnus-Group-check-bogus-groups))
+ ))
+
+(defun gnus-Subject-prepare ()
+ "Prepare subject list of current newsgroup in Subject mode buffer."
+ (let ((buffer-read-only nil))
+ ;; Note: The next codes are not actually used because the user who
+ ;; want it can define them in gnus-Select-group-hook.
+ ;; Print verbose messages if too many articles are selected.
+ ;; (and (numberp gnus-large-newsgroup)
+ ;; (> (length gnus-newsgroup-headers) gnus-large-newsgroup)
+ ;; (message "Preparing headers..."))
+ (erase-buffer)
+ (gnus-Subject-prepare-threads
+ (if gnus-show-threads
+ (gnus-make-threads gnus-newsgroup-headers)
+ gnus-newsgroup-headers) 0)
+ ;; Erase header retrieval message.
+ (message "")
+ ;; Call hooks for modifying Subject mode buffer.
+ ;; Suggested by sven@tde.LTH.Se (Sven Mattisson).
+ (goto-char (point-min))
+ (run-hooks 'gnus-Subject-prepare-hook)
+ ))
+
+;; Basic ideas by Paul Dworkin <paul@media-lab.media.mit.edu>
+
+(defun gnus-Subject-prepare-threads (threads level)
+ "Prepare Subject buffer from THREADS and indentation LEVEL.
+THREADS is a list of `(PARENT [(CHILD1 [(GRANDCHILD ...]...) ...]).'"
+ (let ((thread nil)
+ (header nil)
+ (number nil)
+ ;; `M Indent NUM: [OPT] SUBJECT'
+ (cntl (format "%%s %%s%%%dd: [%%s] %%s\n"
+ (length (prin1-to-string gnus-newsgroup-end)))))
+ (while threads
+ (setq thread (car threads))
+ (setq threads (cdr threads))
+ ;; If thread is a cons, hierarchical threads is given.
+ ;; Otherwise, thread itself is header.
+ (if (consp thread)
+ (setq header (car thread))
+ (setq header thread))
+ ;; Print valid header only.
+ (if (vectorp header) ;Depends on nntp.el.
+ (progn
+ (setq number (nntp-header-number header))
+ (insert
+ (format cntl
+ ;; Read or not.
+ (cond ((memq number gnus-newsgroup-marked) "-")
+ ((memq number gnus-newsgroup-unreads) " ")
+ (t "D"))
+ ;; Thread level.
+ (make-string (* level gnus-thread-indent-level) ? )
+ ;; Article number.
+ number
+ ;; Optional headers.
+ (or (and gnus-optional-headers
+ (funcall gnus-optional-headers header)) "")
+ ;; Its subject string.
+ (concat (if (or (zerop level)
+ (not gnus-thread-hide-subject))
+ nil
+ (make-string (window-width) ? ))
+ (nntp-header-subject header))
+ ))
+ ))
+ ;; Print subthreads.
+ (and (consp thread)
+ (cdr thread)
+ (gnus-Subject-prepare-threads (cdr thread) (1+ level)))
+ )))
+
+(defun gnus-Subject-set-mode-line ()
+ "Set Subject mode line string."
+ ;; The value must be a string to escape %-constructs.
+ (let ((subject
+ (if gnus-current-headers
+ (nntp-header-subject gnus-current-headers) gnus-newsgroup-name)))
+ (setq mode-line-buffer-identification
+ (concat "GNUS: "
+ subject
+ ;; Enough spaces to pad subject to 17 positions.
+ (make-string (max 0 (- 17 (length subject))) ? ))))
+ (set-buffer-modified-p t))
+
+;; GNUS Subject mode command.
+
+(defun gnus-Subject-search-group (&optional backward)
+ "Search for next unread newsgroup.
+If optional argument BACKWARD is non-nil, search backward instead."
+ (save-excursion
+ (set-buffer gnus-Group-buffer)
+ (save-excursion
+ ;; We don't want to alter current point of Group mode buffer.
+ (if (gnus-Group-search-forward backward nil)
+ (gnus-Group-group-name))
+ )))
+
+(defun gnus-Subject-search-subject (backward unread subject)
+ "Search for article forward.
+If first argument BACKWARD is non-nil, search backward.
+If second argument UNREAD is non-nil, only unread article is selected.
+If third argument SUBJECT is non-nil, the article which has
+the same subject will be searched for."
+ (let ((func (if backward 're-search-backward 're-search-forward))
+ (article nil)
+ ;; We have to take care of hidden lines.
+ (regexp
+ (format "^%s[ \t]+\\([0-9]+\\):.\\[[^]\r\n]*\\][ \t]+%s"
+ ;;(if unread " " ".")
+ (cond ((eq unread t) " ") (unread "[ ---]") (t "."))
+ (if subject
+ (concat "\\([Rr][Ee]:[ \t]+\\)*"
+ (regexp-quote (gnus-simplify-subject subject))
+ ;; Ignore words in parentheses.
+ "\\([ \t]*([^\r\n]*)\\)*[ \t]*\\(\r\\|$\\)")
+ "")
+ )))
+ (if backward
+ (beginning-of-line)
+ (end-of-line))
+ (if (funcall func regexp nil t)
+ (setq article
+ (string-to-int
+ (buffer-substring (match-beginning 1) (match-end 1)))))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ ;; This is the result.
+ article
+ ))
+
+(defun gnus-Subject-search-forward (&optional unread subject)
+ "Search for article forward.
+If first optional argument UNREAD is non-nil, only unread article is selected.
+If second optional argument SUBJECT is non-nil, the article which has
+the same subject will be searched for."
+ (gnus-Subject-search-subject nil unread subject))
+
+(defun gnus-Subject-search-backward (&optional unread subject)
+ "Search for article backward.
+If first optional argument UNREAD is non-nil, only unread article is selected.
+If second optional argument SUBJECT is non-nil, the article which has
+the same subject will be searched for."
+ (gnus-Subject-search-subject t unread subject))
+
+(defun gnus-Subject-article-number ()
+ "Article number around point. If nothing, return current number."
+ (save-excursion
+ (beginning-of-line)
+ (if (looking-at ".[ \t]+\\([0-9]+\\):")
+ (string-to-int
+ (buffer-substring (match-beginning 1) (match-end 1)))
+ ;; If search fail, return current article number.
+ gnus-current-article
+ )))
+
+(defun gnus-Subject-subject-string ()
+ "Return current subject string or nil if nothing."
+ (save-excursion
+ ;; It is possible to implement this function using
+ ;; `gnus-Subject-article-number' and `gnus-newsgroup-headers'.
+ (beginning-of-line)
+ ;; We have to take care of hidden lines.
+ (if (looking-at ".[ \t]+[0-9]+:.\\[[^]\r\n]*\\][ \t]+\\([^\r\n]*\\)[\r\n]")
+ (buffer-substring (match-beginning 1) (match-end 1)))
+ ))
+
+(defun gnus-Subject-goto-subject (article)
+ "Move point to ARTICLE's subject."
+ (interactive
+ (list
+ (string-to-int
+ (completing-read "Article number: "
+ (mapcar
+ (function
+ (lambda (headers)
+ (list
+ (int-to-string (nntp-header-number headers)))))
+ gnus-newsgroup-headers)
+ nil 'require-match))))
+ (let ((current (point)))
+ (goto-char (point-min))
+ (or (and article (re-search-forward (format "^.[ \t]+%d:" article) nil t))
+ (progn (goto-char current) nil))
+ ))
+
+(defun gnus-Subject-recenter ()
+ "Center point in Subject mode window."
+ ;; Scroll window so as to cursor comes center of Subject mode window
+ ;; only when article is displayed.
+ ;; Suggested by earle@mahendo.JPL.NASA.GOV (Greg Earle).
+ ;; Recenter only when requested.
+ ;; Suggested by popovich@park.cs.columbia.edu
+ (and gnus-auto-center-subject
+ (get-buffer-window gnus-Article-buffer)
+ (< (/ (- (window-height) 1) 2)
+ (count-lines (point) (point-max)))
+ (recenter (/ (- (window-height) 2) 2))))
+
+;; Walking around Group mode buffer.
+
+(defun gnus-Subject-jump-to-group (newsgroup)
+ "Move point to NEWSGROUP in Group mode buffer."
+ ;; Keep update point of Group mode buffer if visible.
+ (if (eq (current-buffer)
+ (get-buffer gnus-Group-buffer))
+ (save-window-excursion
+ ;; Take care of tree window mode.
+ (if (get-buffer-window gnus-Group-buffer)
+ (pop-to-buffer gnus-Group-buffer))
+ (gnus-Group-jump-to-group newsgroup))
+ (save-excursion
+ ;; Take care of tree window mode.
+ (if (get-buffer-window gnus-Group-buffer)
+ (pop-to-buffer gnus-Group-buffer)
+ (set-buffer gnus-Group-buffer))
+ (gnus-Group-jump-to-group newsgroup))))
+
+(defun gnus-Subject-next-group (no-article)
+ "Exit current newsgroup and then select next unread newsgroup.
+If prefix argument NO-ARTICLE is non-nil, no article is selected initially."
+ (interactive "P")
+ ;; Make sure Group mode buffer point is on current newsgroup.
+ (gnus-Subject-jump-to-group gnus-newsgroup-name)
+ (let ((group (gnus-Subject-search-group)))
+ (if (null group)
+ (progn
+ (message "Exiting %s..." gnus-newsgroup-name)
+ (gnus-Subject-exit)
+ (message ""))
+ (message "Selecting %s..." group)
+ (gnus-Subject-exit t) ;Exit Subject mode temporary.
+ ;; We are now in Group mode buffer.
+ ;; Make sure Group mode buffer point is on GROUP.
+ (gnus-Subject-jump-to-group group)
+ (gnus-Subject-read-group group nil no-article)
+ (or (eq (current-buffer)
+ (get-buffer gnus-Subject-buffer))
+ (eq gnus-auto-select-next t)
+ ;; Expected newsgroup has nothing to read since the articles
+ ;; are marked as read by cross-referencing. So, try next
+ ;; newsgroup. (Make sure we are in Group mode buffer now.)
+ (and (eq (current-buffer)
+ (get-buffer gnus-Group-buffer))
+ (gnus-Group-group-name)
+ (gnus-Subject-read-group
+ (gnus-Group-group-name) nil no-article))
+ )
+ )))
+
+(defun gnus-Subject-prev-group (no-article)
+ "Exit current newsgroup and then select previous unread newsgroup.
+If prefix argument NO-ARTICLE is non-nil, no article is selected initially."
+ (interactive "P")
+ ;; Make sure Group mode buffer point is on current newsgroup.
+ (gnus-Subject-jump-to-group gnus-newsgroup-name)
+ (let ((group (gnus-Subject-search-group t)))
+ (if (null group)
+ (progn
+ (message "Exiting %s..." gnus-newsgroup-name)
+ (gnus-Subject-exit)
+ (message ""))
+ (message "Selecting %s..." group)
+ (gnus-Subject-exit t) ;Exit Subject mode temporary.
+ ;; We are now in Group mode buffer.
+ ;; We have to adjust point of Group mode buffer because current
+ ;; point is moved to next unread newsgroup by exiting.
+ (gnus-Subject-jump-to-group group)
+ (gnus-Subject-read-group group nil no-article)
+ (or (eq (current-buffer)
+ (get-buffer gnus-Subject-buffer))
+ (eq gnus-auto-select-next t)
+ ;; Expected newsgroup has nothing to read since the articles
+ ;; are marked as read by cross-referencing. So, try next
+ ;; newsgroup. (Make sure we are in Group mode buffer now.)
+ (and (eq (current-buffer)
+ (get-buffer gnus-Group-buffer))
+ (gnus-Subject-search-group t)
+ (gnus-Subject-read-group
+ (gnus-Subject-search-group t) nil no-article))
+ )
+ )))
+
+;; Walking around subject lines.
+
+(defun gnus-Subject-next-subject (n &optional unread)
+ "Go to next N'th subject line.
+If optional argument UNREAD is non-nil, only unread article is selected."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Subject-search-forward unread))
+ (setq n (1- n)))
+ (cond ((gnus-Subject-search-forward unread)
+ (gnus-Subject-recenter))
+ (unread
+ (message "No more unread articles"))
+ (t
+ (message "No more articles"))
+ ))
+
+(defun gnus-Subject-next-unread-subject (n)
+ "Go to next N'th unread subject line."
+ (interactive "p")
+ (gnus-Subject-next-subject n t))
+
+(defun gnus-Subject-prev-subject (n &optional unread)
+ "Go to previous N'th subject line.
+If optional argument UNREAD is non-nil, only unread article is selected."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Subject-search-backward unread))
+ (setq n (1- n)))
+ (cond ((gnus-Subject-search-backward unread)
+ (gnus-Subject-recenter))
+ (unread
+ (message "No more unread articles"))
+ (t
+ (message "No more articles"))
+ ))
+
+(defun gnus-Subject-prev-unread-subject (n)
+ "Go to previous N'th unread subject line."
+ (interactive "p")
+ (gnus-Subject-prev-subject n t))
+
+;; Walking around subject lines with displaying articles.
+
+(defun gnus-Subject-expand-window ()
+ "Expand Subject window to show headers full window."
+ (interactive)
+ (gnus-configure-windows 'ExpandSubject)
+ (pop-to-buffer gnus-Subject-buffer))
+
+(defun gnus-Subject-display-article (article &optional all-header)
+ "Display ARTICLE in Article buffer."
+ (if (null article)
+ nil
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Subject-buffer)
+ (gnus-Article-prepare article all-header)
+ (gnus-Subject-recenter)
+ (gnus-Subject-set-mode-line)
+ (run-hooks 'gnus-Select-article-hook)
+ ;; Successfully display article.
+ t
+ ))
+
+(defun gnus-Subject-select-article (&optional all-headers force)
+ "Select the current article.
+Optional argument ALL-HEADERS is non-nil, show all headers."
+ (let ((article (gnus-Subject-article-number)))
+ (if (or (null gnus-current-article)
+ (/= article gnus-current-article)
+ (and force (not (eq all-headers gnus-have-all-headers))))
+ ;; The selected subject is different from that of the current article.
+ (gnus-Subject-display-article article all-headers)
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Subject-buffer))
+ ))
+
+(defun gnus-Subject-set-current-mark (&optional current-mark)
+ "Put `+' at the current article.
+Optional argument specifies CURRENT-MARK instead of `+'."
+ (save-excursion
+ (set-buffer gnus-Subject-buffer)
+ (let ((buffer-read-only nil))
+ (goto-char (point-min))
+ ;; First of all clear mark at last article.
+ (if (re-search-forward "^.[ \t]+[0-9]+:[^ \t]" nil t)
+ (progn
+ (delete-char -1)
+ (insert " ")
+ (goto-char (point-min))))
+ (if (re-search-forward (format "^.[ \t]+%d:" gnus-current-article) nil t)
+ (progn
+ (delete-char 1)
+ (insert (or current-mark "+"))))
+ )))
+
+(defun gnus-Subject-next-article (unread &optional subject)
+ "Select article after current one.
+If argument UNREAD is non-nil, only unread article is selected."
+ (interactive "P")
+ (let ((header nil))
+ (cond ((gnus-Subject-display-article
+ (gnus-Subject-search-forward unread subject)))
+ ((and subject
+ gnus-auto-select-same
+ (gnus-set-difference gnus-newsgroup-unreads
+ gnus-newsgroup-marked)
+ (memq this-command
+ '(gnus-Subject-next-unread-article
+ gnus-Subject-next-page
+ gnus-Subject-kill-same-subject-and-select
+ ;;gnus-Subject-next-article
+ ;;gnus-Subject-next-same-subject
+ ;;gnus-Subject-next-unread-same-subject
+ )))
+ ;; Wrap article pointer if there are unread articles.
+ ;; Hook function, such as gnus-Subject-rmail-digest, may
+ ;; change current buffer, so need check.
+ (let ((buffer (current-buffer))
+ (last-point (point)))
+ ;; No more articles with same subject, so jump to the first
+ ;; unread article.
+ (gnus-Subject-first-unread-article)
+ ;;(and (eq buffer (current-buffer))
+ ;; (= (point) last-point)
+ ;; ;; Ignore given SUBJECT, and try again.
+ ;; (gnus-Subject-next-article unread nil))
+ (and (eq buffer (current-buffer))
+ (< (point) last-point)
+ (message "Wrapped"))
+ ))
+ ((and (not unread)
+ gnus-auto-extend-newsgroup
+ (setq header (gnus-more-header-forward)))
+ ;; Extend to next article if possible.
+ ;; Basic ideas by himacdonald@watdragon.waterloo.edu
+ (gnus-extend-newsgroup header nil)
+ ;; Threads feature must be turned off.
+ (let ((buffer-read-only nil))
+ (goto-char (point-max))
+ (gnus-Subject-prepare-threads (list header) 0))
+ (gnus-Subject-goto-article gnus-newsgroup-end))
+ (t
+ ;; Select next newsgroup automatically if requested.
+ (let ((cmd (string-to-char (this-command-keys)))
+ (group (gnus-Subject-search-group))
+ (auto-select
+ (and gnus-auto-select-next
+ ;;(null (gnus-set-difference gnus-newsgroup-unreads
+ ;; gnus-newsgroup-marked))
+ (memq this-command
+ '(gnus-Subject-next-unread-article
+ gnus-Subject-next-article
+ gnus-Subject-next-page
+ gnus-Subject-next-same-subject
+ gnus-Subject-next-unread-same-subject
+ gnus-Subject-kill-same-subject
+ gnus-Subject-kill-same-subject-and-select
+ ))
+ ;; Ignore characters typed ahead.
+ (not (input-pending-p))
+ )))
+ (message "No more%s articles%s"
+ (if unread " unread" "")
+ (if (and auto-select
+ (not (eq gnus-auto-select-next 'quietly)))
+ (if group
+ (format " (Type %s to %s [%d])"
+ (key-description (char-to-string cmd))
+ group
+ (nth 1 (gnus-gethash group
+ gnus-unread-hashtb)))
+ (format " (Type %s to exit %s)"
+ (key-description (char-to-string cmd))
+ gnus-newsgroup-name
+ ))
+ ""))
+ ;; Select next unread newsgroup automagically.
+ (cond ((and auto-select
+ (eq gnus-auto-select-next 'quietly))
+ ;; Select quietly.
+ (gnus-Subject-next-group nil))
+ (auto-select
+ ;; Confirm auto selection.
+ (let ((char (read-char)))
+ (if (= char cmd)
+ (gnus-Subject-next-group nil)
+ (setq unread-command-char char))))
+ )
+ ))
+ )))
+
+(defun gnus-Subject-next-unread-article ()
+ "Select unread article after current one."
+ (interactive)
+ (gnus-Subject-next-article t (and gnus-auto-select-same
+ (gnus-Subject-subject-string))))
+
+(defun gnus-Subject-prev-article (unread &optional subject)
+ "Select article before current one.
+If argument UNREAD is non-nil, only unread article is selected."
+ (interactive "P")
+ (let ((header nil))
+ (cond ((gnus-Subject-display-article
+ (gnus-Subject-search-backward unread subject)))
+ ((and subject
+ gnus-auto-select-same
+ (gnus-set-difference gnus-newsgroup-unreads
+ gnus-newsgroup-marked)
+ (memq this-command
+ '(gnus-Subject-prev-unread-article
+ ;;gnus-Subject-prev-page
+ ;;gnus-Subject-prev-article
+ ;;gnus-Subject-prev-same-subject
+ ;;gnus-Subject-prev-unread-same-subject
+ )))
+ ;; Ignore given SUBJECT, and try again.
+ (gnus-Subject-prev-article unread nil))
+ (unread
+ (message "No more unread articles"))
+ ((and gnus-auto-extend-newsgroup
+ (setq header (gnus-more-header-backward)))
+ ;; Extend to previous article if possible.
+ ;; Basic ideas by himacdonald@watdragon.waterloo.edu
+ (gnus-extend-newsgroup header t)
+ (let ((buffer-read-only nil))
+ (goto-char (point-min))
+ (gnus-Subject-prepare-threads (list header) 0))
+ (gnus-Subject-goto-article gnus-newsgroup-begin))
+ (t
+ (message "No more articles"))
+ )))
+
+(defun gnus-Subject-prev-unread-article ()
+ "Select unred article before current one."
+ (interactive)
+ (gnus-Subject-prev-article t (and gnus-auto-select-same
+ (gnus-Subject-subject-string))))
+
+(defun gnus-Subject-next-page (lines)
+ "Show next page of selected article.
+If end of artile, select next article.
+Argument LINES specifies lines to be scrolled up."
+ (interactive "P")
+ (let ((article (gnus-Subject-article-number))
+ (endp nil))
+ (if (or (null gnus-current-article)
+ (/= article gnus-current-article))
+ ;; Selected subject is different from current article's.
+ (gnus-Subject-display-article article)
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Subject-buffer)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (setq endp (gnus-Article-next-page lines)))
+ (cond ((and endp lines)
+ (message "End of message"))
+ ((and endp (null lines))
+ (gnus-Subject-next-unread-article)))
+ )))
+
+(defun gnus-Subject-prev-page (lines)
+ "Show previous page of selected article.
+Argument LINES specifies lines to be scrolled down."
+ (interactive "P")
+ (let ((article (gnus-Subject-article-number)))
+ (if (or (null gnus-current-article)
+ (/= article gnus-current-article))
+ ;; Selected subject is different from current article's.
+ (gnus-Subject-display-article article)
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Subject-buffer)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (gnus-Article-prev-page lines))
+ )))
+
+(defun gnus-Subject-scroll-up (lines)
+ "Scroll up (or down) one line current article.
+Argument LINES specifies lines to be scrolled up (or down if negative)."
+ (interactive "p")
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (cond ((> lines 0)
+ (if (gnus-Article-next-page lines)
+ (message "End of message")))
+ ((< lines 0)
+ (gnus-Article-prev-page (- 0 lines))))
+ ))
+
+(defun gnus-Subject-next-same-subject ()
+ "Select next article which has the same subject as current one."
+ (interactive)
+ (gnus-Subject-next-article nil (gnus-Subject-subject-string)))
+
+(defun gnus-Subject-prev-same-subject ()
+ "Select previous article which has the same subject as current one."
+ (interactive)
+ (gnus-Subject-prev-article nil (gnus-Subject-subject-string)))
+
+(defun gnus-Subject-next-unread-same-subject ()
+ "Select next unread article which has the same subject as current one."
+ (interactive)
+ (gnus-Subject-next-article t (gnus-Subject-subject-string)))
+
+(defun gnus-Subject-prev-unread-same-subject ()
+ "Select previous unread article which has the same subject as current one."
+ (interactive)
+ (gnus-Subject-prev-article t (gnus-Subject-subject-string)))
+
+(defun gnus-Subject-refer-parent-article (child)
+ "Refer parent article of current article.
+If a prefix argument CHILD is non-nil, go back to the child article
+using internally maintained articles history.
+NOTE: This command may not work with nnspool.el."
+ (interactive "P")
+ (gnus-Subject-select-article t t) ;Request all headers.
+ (let ((referenced-id nil)) ;Message-id of parent or child article.
+ (if child
+ ;; Go back to child article using history.
+ (gnus-Subject-refer-article nil)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ ;; Look for parent Message-ID.
+ ;; We cannot use gnus-current-headers to get references
+ ;; because we may be looking at parent or refered article.
+ (let ((references (gnus-fetch-field "References")))
+ ;; Get the last message-id in the references.
+ (and references
+ (string-match "\\(<[^<>]+>\\)[^>]*\\'" references)
+ (setq referenced-id
+ (substring references
+ (match-beginning 1) (match-end 1))))
+ ))
+ (if (stringp referenced-id)
+ (gnus-Subject-refer-article referenced-id)
+ (error "No more parents"))
+ )))
+
+(defun gnus-Subject-refer-article (message-id)
+ "Refer article specified by MESSAGE-ID.
+If MESSAGE-ID is nil or an empty string, it is popped from an
+internally maintained articles history.
+NOTE: This command may not work with nnspool.el."
+ (interactive "sMessage-ID: ")
+ ;; Make sure that this command depends on the fact that article
+ ;; related information is not updated when an article is retrieved
+ ;; by Message-ID.
+ (gnus-Subject-select-article t t) ;Request all headers.
+ (if (and (stringp message-id)
+ (> (length message-id) 0))
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ ;; Construct the correct Message-ID if necessary.
+ ;; Suggested by tale@pawl.rpi.edu.
+ (or (string-match "^<" message-id)
+ (setq message-id (concat "<" message-id)))
+ (or (string-match ">$" message-id)
+ (setq message-id (concat message-id ">")))
+ ;; Push current message-id on history.
+ ;; We cannot use gnus-current-headers to get current
+ ;; message-id because we may be looking at parent or refered
+ ;; article.
+ (let ((current (gnus-fetch-field "Message-ID")))
+ (or (equal current message-id) ;Nothing to do.
+ (equal current (car gnus-current-history))
+ (setq gnus-current-history
+ (cons current gnus-current-history)))
+ ))
+ ;; Pop message-id from history.
+ (setq message-id (car gnus-current-history))
+ (setq gnus-current-history (cdr gnus-current-history)))
+ (if (stringp message-id)
+ ;; Retrieve article by message-id. This may not work with nnspool.
+ (gnus-Article-prepare message-id t)
+ (error "No such references"))
+ )
+
+(defun gnus-Subject-next-digest (nth)
+ "Move to head of NTH next digested message."
+ (interactive "p")
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (gnus-Article-next-digest (or nth 1))
+ ))
+
+(defun gnus-Subject-prev-digest (nth)
+ "Move to head of NTH previous digested message."
+ (interactive "p")
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (gnus-Article-prev-digest (or nth 1))
+ ))
+
+(defun gnus-Subject-first-unread-article ()
+ "Select first unread article. Return non-nil if successfully selected."
+ (interactive)
+ (let ((begin (point)))
+ (goto-char (point-min))
+ (if (re-search-forward "^ [ \t]+[0-9]+:" nil t)
+ (gnus-Subject-display-article (gnus-Subject-article-number))
+ ;; If there is no unread articles, stay there.
+ (goto-char begin)
+ ;;(gnus-Subject-display-article (gnus-Subject-article-number))
+ (message "No more unread articles")
+ nil
+ )
+ ))
+
+(defun gnus-Subject-isearch-article ()
+ "Do incremental search forward on current article."
+ (interactive)
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (call-interactively 'isearch-forward)
+ ))
+
+(defun gnus-Subject-search-article-forward (regexp)
+ "Search for an article containing REGEXP forward.
+`gnus-Select-article-hook' is not called during the search."
+ (interactive
+ (list (read-string
+ (concat "Search forward (regexp): "
+ (if gnus-last-search-regexp
+ (concat "(default " gnus-last-search-regexp ") "))))))
+ (if (string-equal regexp "")
+ (setq regexp (or gnus-last-search-regexp ""))
+ (setq gnus-last-search-regexp regexp))
+ (if (gnus-Subject-search-article regexp nil)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (recenter 0)
+ ;;(sit-for 1)
+ )
+ (error "Search failed: \"%s\"" regexp)
+ ))
+
+(defun gnus-Subject-search-article-backward (regexp)
+ "Search for an article containing REGEXP backward.
+`gnus-Select-article-hook' is not called during the search."
+ (interactive
+ (list (read-string
+ (concat "Search backward (regexp): "
+ (if gnus-last-search-regexp
+ (concat "(default " gnus-last-search-regexp ") "))))))
+ (if (string-equal regexp "")
+ (setq regexp (or gnus-last-search-regexp ""))
+ (setq gnus-last-search-regexp regexp))
+ (if (gnus-Subject-search-article regexp t)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (recenter 0)
+ ;;(sit-for 1)
+ )
+ (error "Search failed: \"%s\"" regexp)
+ ))
+
+(defun gnus-Subject-search-article (regexp &optional backward)
+ "Search for an article containing REGEXP.
+Optional argument BACKWARD means do search for backward.
+`gnus-Select-article-hook' is not called during the search."
+ (let ((gnus-Select-article-hook nil) ;Disable hook.
+ (gnus-Mark-article-hook nil) ;Inhibit marking as read.
+ (re-search
+ (if backward
+ (function re-search-backward) (function re-search-forward)))
+ (found nil)
+ (last nil))
+ ;; Hidden thread subtrees must be searched for ,too.
+ (gnus-Subject-show-all-threads)
+ ;; First of all, search current article.
+ ;; We don't want to read article again from NNTP server nor reset
+ ;; current point.
+ (gnus-Subject-select-article)
+ (message "Searching article: %d..." gnus-current-article)
+ (setq last gnus-current-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-restriction
+ (widen)
+ ;; Begin search from current point.
+ (setq found (funcall re-search regexp nil t))))
+ ;; Then search next articles.
+ (while (and (not found)
+ (gnus-Subject-display-article
+ (gnus-Subject-search-subject backward nil nil)))
+ (message "Searching article: %d..." gnus-current-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-restriction
+ (widen)
+ (goto-char (if backward (point-max) (point-min)))
+ (setq found (funcall re-search regexp nil t)))
+ ))
+ (message "")
+ ;; Adjust article pointer.
+ (or (eq last gnus-current-article)
+ (setq gnus-last-article last))
+ ;; Return T if found such article.
+ found
+ ))
+
+(defun gnus-Subject-execute-command (field regexp command &optional backward)
+ "If FIELD of article header matches REGEXP, execute COMMAND string.
+If FIELD is an empty string (or nil), entire article body is searched for.
+If optional (prefix) argument BACKWARD is non-nil, do backward instead."
+ (interactive
+ (list (let ((completion-ignore-case t))
+ (completing-read "Field name: "
+ '(("Number")("Subject")("From")
+ ("Lines")("Date")("Id")
+ ("Xref")("References"))
+ nil 'require-match))
+ (read-string "Regexp: ")
+ (read-key-sequence "Command: ")
+ current-prefix-arg))
+ ;; Hidden thread subtrees must be searched for ,too.
+ (gnus-Subject-show-all-threads)
+ ;; We don't want to change current point nor window configuration.
+ (save-excursion
+ (save-window-excursion
+ (message "Executing %s..." (key-description command))
+ ;; We'd like to execute COMMAND interactively so as to give arguments.
+ (gnus-execute field regexp
+ (` (lambda ()
+ (call-interactively '(, (key-binding command)))))
+ backward)
+ (message "Executing %s... done" (key-description command)))))
+
+(defun gnus-Subject-beginning-of-article ()
+ "Go to beginning of article body"
+ (interactive)
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (widen)
+ (beginning-of-buffer)
+ (if gnus-break-pages
+ (gnus-narrow-to-page))
+ ))
+
+(defun gnus-Subject-end-of-article ()
+ "Go to end of article body"
+ (interactive)
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (widen)
+ (end-of-buffer)
+ (if gnus-break-pages
+ (gnus-narrow-to-page))
+ ))
+
+(defun gnus-Subject-goto-article (article &optional all-headers)
+ "Read ARTICLE if exists.
+Optional argument ALL-HEADERS means all headers are shown."
+ (interactive
+ (list
+ (string-to-int
+ (completing-read "Article number: "
+ (mapcar
+ (function
+ (lambda (headers)
+ (list
+ (int-to-string (nntp-header-number headers)))))
+ gnus-newsgroup-headers)
+ nil 'require-match))))
+ (if (gnus-Subject-goto-subject article)
+ (gnus-Subject-display-article article all-headers)))
+
+(defun gnus-Subject-goto-last-article ()
+ "Go to last subject line."
+ (interactive)
+ (if gnus-last-article
+ (gnus-Subject-goto-article gnus-last-article)))
+
+(defun gnus-Subject-show-article ()
+ "Force to show current article."
+ (interactive)
+ ;; The following is a trick to force to read the current article again.
+ (setq gnus-have-all-headers (not gnus-have-all-headers))
+ (gnus-Subject-select-article (not gnus-have-all-headers) t))
+
+(defun gnus-Subject-toggle-header (arg)
+ "Show original header if pruned header currently shown, or vice versa.
+With arg, show original header iff arg is positive."
+ (interactive "P")
+ ;; Variable gnus-show-all-headers must be NIL to toggle really.
+ (let ((gnus-show-all-headers nil)
+ (all-headers
+ (if (null arg) (not gnus-have-all-headers)
+ (> (prefix-numeric-value arg) 0))))
+ (gnus-Subject-select-article all-headers t)))
+
+(defun gnus-Subject-show-all-headers ()
+ "Show original article header."
+ (interactive)
+ (gnus-Subject-select-article t t))
+
+(defun gnus-Subject-stop-page-breaking ()
+ "Stop page breaking by linefeed temporary (Widen article buffer)."
+ (interactive)
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (widen)))
+
+(defun gnus-Subject-kill-same-subject-and-select (unmark)
+ "Mark articles which has the same subject as read, and then select next.
+If argument UNMARK is positive, remove any kinds of marks.
+If argument UNMARK is negative, mark articles as unread instead."
+ (interactive "P")
+ (if unmark
+ (setq unmark (prefix-numeric-value unmark)))
+ (let ((count
+ (gnus-Subject-mark-same-subject
+ (gnus-Subject-subject-string) unmark)))
+ ;; Select next unread article. If auto-select-same mode, should
+ ;; select the first unread article.
+ (gnus-Subject-next-article t (and gnus-auto-select-same
+ (gnus-Subject-subject-string)))
+ (message "%d articles are marked as %s"
+ count (if unmark "unread" "read"))
+ ))
+
+(defun gnus-Subject-kill-same-subject (unmark)
+ "Mark articles which has the same subject as read.
+If argument UNMARK is positive, remove any kinds of marks.
+If argument UNMARK is negative, mark articles as unread instead."
+ (interactive "P")
+ (if unmark
+ (setq unmark (prefix-numeric-value unmark)))
+ (let ((count
+ (gnus-Subject-mark-same-subject
+ (gnus-Subject-subject-string) unmark)))
+ ;; If marked as read, go to next unread subject.
+ (if (null unmark)
+ ;; Go to next unread subject.
+ (gnus-Subject-next-subject 1 t))
+ (message "%d articles are marked as %s"
+ count (if unmark "unread" "read"))
+ ))
+
+(defun gnus-Subject-mark-same-subject (subject &optional unmark)
+ "Mark articles with same SUBJECT as read, and return marked number.
+If optional argument UNMARK is positive, remove any kinds of marks.
+If optional argument UNMARK is negative, mark articles as unread instead."
+ (let ((count 1))
+ (save-excursion
+ (cond ((null unmark)
+ (gnus-Subject-mark-as-read nil "K"))
+ ((> unmark 0)
+ (gnus-Subject-mark-as-unread nil t))
+ (t
+ (gnus-Subject-mark-as-unread)))
+ (while (and subject
+ (gnus-Subject-search-forward nil subject))
+ (cond ((null unmark)
+ (gnus-Subject-mark-as-read nil "K"))
+ ((> unmark 0)
+ (gnus-Subject-mark-as-unread nil t))
+ (t
+ (gnus-Subject-mark-as-unread)))
+ (setq count (1+ count))
+ ))
+ ;; Hide killed thread subtrees. Does not work properly always.
+ ;;(and (null unmark)
+ ;; gnus-thread-hide-killed
+ ;; (gnus-Subject-hide-thread))
+ ;; Return number of articles marked as read.
+ count
+ ))
+
+(defun gnus-Subject-mark-as-unread-forward (count)
+ "Mark current article as unread, and then go forward.
+Argument COUNT specifies number of articles marked as unread."
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-unread nil nil)
+ (gnus-Subject-next-subject 1 nil)
+ (setq count (1- count))))
+
+(defun gnus-Subject-mark-as-unread-backward (count)
+ "Mark current article as unread, and then go backward.
+Argument COUNT specifies number of articles marked as unread."
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-unread nil nil)
+ (gnus-Subject-prev-subject 1 nil)
+ (setq count (1- count))))
+
+(defun gnus-Subject-mark-as-unread (&optional article clear-mark)
+ "Mark current article as unread.
+Optional first argument ARTICLE specifies article number to be
+marked as unread. Optional second argument CLEAR-MARK removes
+any kind of mark."
+ (save-excursion
+ (set-buffer gnus-Subject-buffer)
+ ;; First of all, show hidden thread subtrees.
+ (gnus-Subject-show-thread)
+ (let* ((buffer-read-only nil)
+ (current (gnus-Subject-article-number))
+ (article (or article current)))
+ (gnus-mark-article-as-unread article clear-mark)
+ (if (or (eq article current)
+ (gnus-Subject-goto-subject article))
+ (progn
+ (beginning-of-line)
+ (delete-char 1)
+ (insert (if clear-mark " " "-"))))
+ )))
+
+(defun gnus-Subject-mark-as-read-forward (count)
+ "Mark current article as read, and then go forward.
+Argument COUNT specifies number of articles marked as read"
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-read)
+ (gnus-Subject-next-subject 1 'unread-only)
+ (setq count (1- count))))
+
+(defun gnus-Subject-mark-as-read-backward (count)
+ "Mark current article as read, and then go backward.
+Argument COUNT specifies number of articles marked as read"
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-read)
+ (gnus-Subject-prev-subject 1 'unread-only)
+ (setq count (1- count))))
+
+(defun gnus-Subject-mark-as-read (&optional article mark)
+ "Mark current article as read.
+Optional first argument ARTICLE specifies article number to be marked as read.
+Optional second argument MARK specifies a string inserted at beginning of line.
+Any kind of string (length 1) except for a space and `-' is ok."
+ (save-excursion
+ (set-buffer gnus-Subject-buffer)
+ ;; First of all, show hidden thread subtrees.
+ (gnus-Subject-show-thread)
+ (let* ((buffer-read-only nil)
+ (mark (or mark "D")) ;Default mark is `D'.
+ (current (gnus-Subject-article-number))
+ (article (or article current)))
+ (gnus-mark-article-as-read article)
+ (if (or (eq article current)
+ (gnus-Subject-goto-subject article))
+ (progn
+ (beginning-of-line)
+ (delete-char 1)
+ (insert mark)))
+ )))
+
+(defun gnus-Subject-clear-mark-forward (count)
+ "Remove current article's mark, and go forward.
+Argument COUNT specifies number of articles unmarked"
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-unread nil t)
+ (gnus-Subject-next-subject 1 nil)
+ (setq count (1- count))))
+
+(defun gnus-Subject-clear-mark-backward (count)
+ "Remove current article's mark, and go backward.
+Argument COUNT specifies number of articles unmarked"
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-unread nil t)
+ (gnus-Subject-prev-subject 1 nil)
+ (setq count (1- count))))
+
+(defun gnus-Subject-delete-marked-as-read ()
+ "Delete lines which are marked as read."
+ (interactive)
+ (if gnus-newsgroup-unreads
+ (let ((buffer-read-only nil))
+ (save-excursion
+ (goto-char (point-min))
+ (delete-non-matching-lines "^[ ---]"))
+ ;; Adjust point.
+ (if (eobp)
+ (gnus-Subject-prev-subject 1)
+ (beginning-of-line)
+ (search-forward ":" nil t)))
+ ;; It is not so good idea to make the buffer empty.
+ (message "All articles are marked as read")
+ ))
+
+(defun gnus-Subject-delete-marked-with (marks)
+ "Delete lines which are marked with MARKS (e.g. \"DK\")."
+ (interactive "sMarks: ")
+ (let ((buffer-read-only nil))
+ (save-excursion
+ (goto-char (point-min))
+ (delete-matching-lines (concat "^[" marks "]")))
+ ;; Adjust point.
+ (or (zerop (buffer-size))
+ (if (eobp)
+ (gnus-Subject-prev-subject 1)
+ (beginning-of-line)
+ (search-forward ":" nil t)))
+ ))
+
+;; Thread-based commands.
+
+(defun gnus-Subject-toggle-threads (arg)
+ "Toggle showing conversation threads.
+With arg, turn showing conversation threads on iff arg is positive."
+ (interactive "P")
+ (let ((current (gnus-Subject-article-number)))
+ (setq gnus-show-threads
+ (if (null arg) (not gnus-show-threads)
+ (> (prefix-numeric-value arg) 0)))
+ (gnus-Subject-prepare)
+ (gnus-Subject-goto-subject current)
+ ))
+
+(defun gnus-Subject-show-all-threads ()
+ "Show all thread subtrees."
+ (interactive)
+ (if gnus-show-threads
+ (save-excursion
+ (let ((buffer-read-only nil))
+ (subst-char-in-region (point-min) (point-max) ?\^M ?\n t)
+ ))))
+
+(defun gnus-Subject-show-thread ()
+ "Show thread subtrees."
+ (interactive)
+ (if gnus-show-threads
+ (save-excursion
+ (let ((buffer-read-only nil))
+ (subst-char-in-region (progn
+ (beginning-of-line) (point))
+ (progn
+ (end-of-line) (point))
+ ?\^M ?\n t)
+ ))))
+
+(defun gnus-Subject-hide-all-threads ()
+ "Hide all thread subtrees."
+ (interactive)
+ (if gnus-show-threads
+ (save-excursion
+ ;; Adjust cursor point.
+ (goto-char (point-min))
+ (search-forward ":" nil t)
+ (let ((level (current-column)))
+ (gnus-Subject-hide-thread)
+ (while (gnus-Subject-search-forward)
+ (and (>= level (current-column))
+ (gnus-Subject-hide-thread)))
+ ))))
+
+(defun gnus-Subject-hide-thread ()
+ "Hide thread subtrees."
+ (interactive)
+ (if gnus-show-threads
+ (save-excursion
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (let ((buffer-read-only nil)
+ (init (point))
+ (last (point))
+ (level (current-column)))
+ (while (and (gnus-Subject-search-forward)
+ (< level (current-column)))
+ ;; Interested in lower levels.
+ (if (< level (current-column))
+ (progn
+ (setq last (point))
+ ))
+ )
+ (subst-char-in-region init last ?\n ?\^M t)
+ ))))
+
+(defun gnus-Subject-next-thread (n)
+ "Go to the same level next thread.
+Argument N specifies the number of threads."
+ (interactive "p")
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (let ((init (point))
+ (last (point))
+ (level (current-column)))
+ (while (and (> n 0)
+ (gnus-Subject-search-forward)
+ (<= level (current-column)))
+ ;; We have to skip lower levels.
+ (if (= level (current-column))
+ (progn
+ (setq last (point))
+ (setq n (1- n))
+ ))
+ )
+ ;; Return non-nil if successfully move to the next.
+ (prog1 (not (= init last))
+ (goto-char last))
+ ))
+
+(defun gnus-Subject-prev-thread (n)
+ "Go to the same level previous thread.
+Argument N specifies the number of threads."
+ (interactive "p")
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (let ((init (point))
+ (last (point))
+ (level (current-column)))
+ (while (and (> n 0)
+ (gnus-Subject-search-backward)
+ (<= level (current-column)))
+ ;; We have to skip lower levels.
+ (if (= level (current-column))
+ (progn
+ (setq last (point))
+ (setq n (1- n))
+ ))
+ )
+ ;; Return non-nil if successfully move to the previous.
+ (prog1 (not (= init last))
+ (goto-char last))
+ ))
+
+(defun gnus-Subject-down-thread (d)
+ "Go downward current thread.
+Argument D specifies the depth goes down."
+ (interactive "p")
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (let ((last (point))
+ (level (current-column)))
+ (while (and (> d 0)
+ (gnus-Subject-search-forward)
+ (<= level (current-column))) ;<= can be <. Which do you like?
+ ;; We have to skip the same levels.
+ (if (< level (current-column))
+ (progn
+ (setq last (point))
+ (setq level (current-column))
+ (setq d (1- d))
+ ))
+ )
+ (goto-char last)
+ ))
+
+(defun gnus-Subject-up-thread (d)
+ "Go upward current thread.
+Argument D specifies the depth goes up."
+ (interactive "p")
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (let ((last (point))
+ (level (current-column)))
+ (while (and (> d 0)
+ (gnus-Subject-search-backward))
+ ;; We have to skip the same levels.
+ (if (> level (current-column))
+ (progn
+ (setq last (point))
+ (setq level (current-column))
+ (setq d (1- d))
+ ))
+ )
+ (goto-char last)
+ ))
+
+(defun gnus-Subject-kill-thread (unmark)
+ "Mark articles under current thread as read.
+If argument UNMARK is positive, remove any kinds of marks.
+If argument UNMARK is negative, mark articles as unread instead."
+ (interactive "P")
+ (if unmark
+ (setq unmark (prefix-numeric-value unmark)))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (save-excursion
+ (let ((level (current-column)))
+ ;; Mark current article.
+ (cond ((null unmark)
+ (gnus-Subject-mark-as-read nil "K"))
+ ((> unmark 0)
+ (gnus-Subject-mark-as-unread nil t))
+ (t
+ (gnus-Subject-mark-as-unread))
+ )
+ ;; Mark following articles.
+ (while (and (gnus-Subject-search-forward)
+ (< level (current-column)))
+ (cond ((null unmark)
+ (gnus-Subject-mark-as-read nil "K"))
+ ((> unmark 0)
+ (gnus-Subject-mark-as-unread nil t))
+ (t
+ (gnus-Subject-mark-as-unread))
+ ))
+ ))
+ ;; Hide killed subtrees.
+ (and (null unmark)
+ gnus-thread-hide-killed
+ (gnus-Subject-hide-thread))
+ ;; If marked as read, go to next unread subject.
+ (if (null unmark)
+ ;; Go to next unread subject.
+ (gnus-Subject-next-subject 1 t))
+ )
+
+(defun gnus-Subject-toggle-truncation (arg)
+ "Toggle truncation of subject lines.
+With ARG, turn line truncation on iff ARG is positive."
+ (interactive "P")
+ (setq truncate-lines
+ (if (null arg) (not truncate-lines)
+ (> (prefix-numeric-value arg) 0)))
+ (redraw-display))
+
+(defun gnus-Subject-sort-by-number (reverse)
+ "Sort subject display buffer by article number.
+Argument REVERSE means reverse order."
+ (interactive "P")
+ (gnus-Subject-sort-subjects
+ (function
+ (lambda (a b)
+ (< (nntp-header-number a) (nntp-header-number b))))
+ reverse
+ ))
+
+(defun gnus-Subject-sort-by-author (reverse)
+ "Sort subject display buffer by author name alphabetically.
+If case-fold-search is non-nil, case of letters is ignored.
+Argument REVERSE means reverse order."
+ (interactive "P")
+ (gnus-Subject-sort-subjects
+ (function
+ (lambda (a b)
+ (gnus-string-lessp (nntp-header-from a) (nntp-header-from b))))
+ reverse
+ ))
+
+(defun gnus-Subject-sort-by-subject (reverse)
+ "Sort subject display buffer by subject alphabetically. `Re:'s are ignored.
+If case-fold-search is non-nil, case of letters is ignored.
+Argument REVERSE means reverse order."
+ (interactive "P")
+ (gnus-Subject-sort-subjects
+ (function
+ (lambda (a b)
+ (gnus-string-lessp
+ (gnus-simplify-subject (nntp-header-subject a) 're-only)
+ (gnus-simplify-subject (nntp-header-subject b) 're-only))))
+ reverse
+ ))
+
+(defun gnus-Subject-sort-by-date (reverse)
+ "Sort subject display buffer by posted date.
+Argument REVERSE means reverse order."
+ (interactive "P")
+ (gnus-Subject-sort-subjects
+ (function
+ (lambda (a b)
+ (gnus-date-lessp (nntp-header-date a) (nntp-header-date b))))
+ reverse
+ ))
+
+(defun gnus-Subject-sort-subjects (predicate &optional reverse)
+ "Sort subject display buffer by PREDICATE.
+Optional argument REVERSE means reverse order."
+ (let ((current (gnus-Subject-article-number)))
+ (gnus-sort-headers predicate reverse)
+ (gnus-Subject-prepare)
+ (gnus-Subject-goto-subject current)
+ ))
+
+(defun gnus-Subject-reselect-current-group (show-all)
+ "Once exit and then reselect the current newsgroup.
+Prefix argument SHOW-ALL means to select all articles."
+ (interactive "P")
+ (let ((current-subject (gnus-Subject-article-number)))
+ (gnus-Subject-exit t)
+ ;; We have to adjust the point of Group mode buffer because the
+ ;; current point was moved to the next unread newsgroup by
+ ;; exiting.
+ (gnus-Subject-jump-to-group gnus-newsgroup-name)
+ (gnus-Group-read-group show-all t)
+ (gnus-Subject-goto-subject current-subject)
+ ))
+
+(defun gnus-Subject-caesar-message (rotnum)
+ "Caesar rotates all letters of current message by 13/47 places.
+With prefix arg, specifies the number of places to rotate each letter forward.
+Caesar rotates Japanese letters by 47 places in any case."
+ (interactive "P")
+ (gnus-Subject-select-article)
+ (gnus-overload-functions)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-restriction
+ (widen)
+ ;; We don't want to jump to the beginning of the message.
+ ;; `save-excursion' does not do its job.
+ (move-to-window-line 0)
+ (let ((last (point)))
+ (news-caesar-buffer-body rotnum)
+ (goto-char last)
+ (recenter 0)
+ ))
+ ))
+
+(defun gnus-Subject-rmail-digest ()
+ "Run RMAIL on current digest article.
+`gnus-Select-digest-hook' will be called with no arguments, if that
+value is non-nil. It is possible to modify the article so that Rmail
+can work with it.
+
+`gnus-Rmail-digest-hook' will be called with no arguments, if that value
+is non-nil. The hook is intended to customize Rmail mode."
+ (interactive)
+ (gnus-Subject-select-article)
+ (require 'rmail)
+ (let ((artbuf gnus-Article-buffer)
+ (digbuf (get-buffer-create gnus-Digest-buffer))
+ (mail-header-separator ""))
+ (set-buffer digbuf)
+ (buffer-flush-undo (current-buffer))
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (insert-buffer-substring artbuf)
+ (run-hooks 'gnus-Select-digest-hook)
+ (gnus-convert-article-to-rmail)
+ (goto-char (point-min))
+ ;; Rmail initializations.
+ (rmail-insert-rmail-file-header)
+ (rmail-mode)
+ (rmail-set-message-counters)
+ (rmail-show-message)
+ (condition-case ()
+ (progn
+ (undigestify-rmail-message)
+ (rmail-expunge) ;Delete original message.
+ ;; File name is meaningless but `save-buffer' requires it.
+ (setq buffer-file-name "GNUS Digest")
+ (setq mode-line-buffer-identification
+ (concat "Digest: "
+ (nntp-header-subject gnus-current-headers)))
+ ;; There is no need to write this buffer to a file.
+ (make-local-variable 'write-file-hooks)
+ (setq write-file-hooks
+ (list (function
+ (lambda ()
+ (set-buffer-modified-p nil)
+ (message "(No changes need to be saved)")
+ 'no-need-to-write-this-buffer))))
+ ;; Default file name saving digest messages.
+ (setq rmail-last-rmail-file
+ (funcall gnus-rmail-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-rmail
+ ))
+ (setq rmail-last-file
+ (funcall gnus-mail-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-mail
+ ))
+ ;; Prevent generating new buffer named ***<N> each time.
+ (setq rmail-summary-buffer
+ (get-buffer-create gnus-Digest-summary-buffer))
+ (run-hooks 'gnus-Rmail-digest-hook)
+ ;; Take all windows safely.
+ (gnus-configure-windows '(1 0 0))
+ (pop-to-buffer gnus-Group-buffer)
+ ;; Use Subject and Article windows for Digest summary and
+ ;; Digest buffers.
+ (if gnus-digest-show-summary
+ (let ((gnus-Subject-buffer gnus-Digest-summary-buffer)
+ (gnus-Article-buffer gnus-Digest-buffer))
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Digest-buffer)
+ (rmail-summary)
+ (pop-to-buffer gnus-Digest-summary-buffer)
+ (message (substitute-command-keys
+ "Type \\[rmail-summary-quit] to return to GNUS")))
+ (let ((gnus-Subject-buffer gnus-Digest-buffer))
+ (gnus-configure-windows 'ExpandSubject)
+ (pop-to-buffer gnus-Digest-buffer)
+ (message (substitute-command-keys
+ "Type \\[rmail-quit] to return to GNUS")))
+ )
+ ;; Move the buffers to the end of buffer list.
+ (bury-buffer gnus-Article-buffer)
+ (bury-buffer gnus-Group-buffer)
+ (bury-buffer gnus-Digest-summary-buffer)
+ (bury-buffer gnus-Digest-buffer))
+ (error (set-buffer-modified-p nil)
+ (kill-buffer digbuf)
+ ;; This command should not signal an error because the
+ ;; command is called from hooks.
+ (ding) (message "Article is not a digest")))
+ ))
+
+(defun gnus-Subject-save-article ()
+ "Save this article using default saver function.
+Variable `gnus-default-article-saver' specifies the saver function."
+ (interactive)
+ (gnus-Subject-select-article
+ (not (null gnus-save-all-headers)) gnus-save-all-headers)
+ (if gnus-default-article-saver
+ (call-interactively gnus-default-article-saver)
+ (error "No default saver is defined.")))
+
+(defun gnus-Subject-save-in-rmail (&optional filename)
+ "Append this article to Rmail file.
+Optional argument FILENAME specifies file name.
+Directory to save to is default to `gnus-article-save-directory' which
+is initialized from the SAVEDIR environment variable."
+ (interactive)
+ (gnus-Subject-select-article
+ (not (null gnus-save-all-headers)) gnus-save-all-headers)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-excursion
+ (save-restriction
+ (widen)
+ (let ((default-name
+ (funcall gnus-rmail-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-rmail
+ )))
+ (or filename
+ (setq filename
+ (read-file-name
+ (concat "Save article in Rmail file: (default "
+ (file-name-nondirectory default-name)
+ ") ")
+ (file-name-directory default-name)
+ default-name)))
+ (gnus-make-directory (file-name-directory filename))
+ (gnus-output-to-rmail filename)
+ ;; Remember the directory name to save articles.
+ (setq gnus-newsgroup-last-rmail filename)
+ )))
+ ))
+
+(defun gnus-Subject-save-in-mail (&optional filename)
+ "Append this article to Unix mail file.
+Optional argument FILENAME specifies file name.
+Directory to save to is default to `gnus-article-save-directory' which
+is initialized from the SAVEDIR environment variable."
+ (interactive)
+ (gnus-Subject-select-article
+ (not (null gnus-save-all-headers)) gnus-save-all-headers)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-excursion
+ (save-restriction
+ (widen)
+ (let ((default-name
+ (funcall gnus-mail-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-mail
+ )))
+ (or filename
+ (setq filename
+ (read-file-name
+ (concat "Save article in Unix mail file: (default "
+ (file-name-nondirectory default-name)
+ ") ")
+ (file-name-directory default-name)
+ default-name)))
+ (gnus-make-directory (file-name-directory filename))
+ (rmail-output filename)
+ ;; Remember the directory name to save articles.
+ (setq gnus-newsgroup-last-mail filename)
+ )))
+ ))
+
+(defun gnus-Subject-save-in-file (&optional filename)
+ "Append this article to file.
+Optional argument FILENAME specifies file name.
+Directory to save to is default to `gnus-article-save-directory' which
+is initialized from the SAVEDIR environment variable."
+ (interactive)
+ (gnus-Subject-select-article
+ (not (null gnus-save-all-headers)) gnus-save-all-headers)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-excursion
+ (save-restriction
+ (widen)
+ (let ((default-name
+ (funcall gnus-file-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-file
+ )))
+ (or filename
+ (setq filename
+ (read-file-name
+ (concat "Save article in file: (default "
+ (file-name-nondirectory default-name)
+ ") ")
+ (file-name-directory default-name)
+ default-name)))
+ (gnus-make-directory (file-name-directory filename))
+ (gnus-output-to-file filename)
+ ;; Remember the directory name to save articles.
+ (setq gnus-newsgroup-last-file filename)
+ )))
+ ))
+
+(defun gnus-Subject-save-in-folder (&optional folder)
+ "Save this article to MH folder (using `rcvstore' in MH library).
+Optional argument FOLDER specifies folder name."
+ (interactive)
+ (gnus-Subject-select-article
+ (not (null gnus-save-all-headers)) gnus-save-all-headers)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-restriction
+ (widen)
+ ;; Thanks to yuki@flab.Fujitsu.JUNET and ohm@kaba.junet.
+ (mh-find-path)
+ (let ((folder
+ (or folder
+ (mh-prompt-for-folder "Save article in"
+ (funcall gnus-folder-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-folder
+ )
+ t
+ )))
+ (errbuf (get-buffer-create " *GNUS rcvstore*")))
+ (unwind-protect
+ (call-process-region (point-min) (point-max)
+ (expand-file-name "rcvstore" mh-lib)
+ nil errbuf nil folder)
+ (set-buffer errbuf)
+ (if (zerop (buffer-size))
+ (message "Article saved in folder: %s" folder)
+ (message "%s" (buffer-string)))
+ (kill-buffer errbuf)
+ (setq gnus-newsgroup-last-folder folder))
+ ))
+ ))
+
+(defun gnus-Subject-pipe-output ()
+ "Pipe this article to subprocess."
+ (interactive)
+ ;; Ignore `gnus-save-all-headers' since this is not save command.
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-restriction
+ (widen)
+ (let ((command (read-string "Shell command on article: "
+ gnus-last-shell-command)))
+ (if (string-equal command "")
+ (setq command gnus-last-shell-command))
+ (shell-command-on-region (point-min) (point-max) command nil)
+ (setq gnus-last-shell-command command)
+ ))
+ ))
+
+(defun gnus-Subject-catch-up (all &optional quietly)
+ "Mark all articles not marked as unread in this newsgroup as read.
+If prefix argument ALL is non-nil, all articles are marked as read."
+ (interactive "P")
+ (if (or quietly
+ (y-or-n-p
+ (if all
+ "Do you really want to mark everything as read? "
+ "Delete all articles not marked as unread? ")))
+ (let ((unmarked
+ (gnus-set-difference gnus-newsgroup-unreads
+ (if (not all) gnus-newsgroup-marked))))
+ (message "") ;Erase "Yes or No" question.
+ (while unmarked
+ (gnus-Subject-mark-as-read (car unmarked) "C")
+ (setq unmarked (cdr unmarked))
+ ))
+ ))
+
+(defun gnus-Subject-catch-up-all (&optional quietly)
+ "Mark all articles in this newsgroup as read."
+ (interactive)
+ (gnus-Subject-catch-up t quietly))
+
+(defun gnus-Subject-catch-up-and-exit (all &optional quietly)
+ "Mark all articles not marked as unread in this newsgroup as read, then exit.
+If prefix argument ALL is non-nil, all articles are marked as read."
+ (interactive "P")
+ (if (or quietly
+ (y-or-n-p
+ (if all
+ "Do you really want to mark everything as read? "
+ "Delete all articles not marked as unread? ")))
+ (let ((unmarked
+ (gnus-set-difference gnus-newsgroup-unreads
+ (if (not all) gnus-newsgroup-marked))))
+ (message "") ;Erase "Yes or No" question.
+ (while unmarked
+ (gnus-mark-article-as-read (car unmarked))
+ (setq unmarked (cdr unmarked)))
+ ;; Select next newsgroup or exit.
+ (cond ((eq gnus-auto-select-next 'quietly)
+ ;; Select next newsgroup quietly.
+ (gnus-Subject-next-group nil))
+ (t
+ (gnus-Subject-exit)))
+ )))
+
+(defun gnus-Subject-catch-up-all-and-exit (&optional quietly)
+ "Mark all articles in this newsgroup as read, and then exit."
+ (interactive)
+ (gnus-Subject-catch-up-and-exit t quietly))
+
+(defun gnus-Subject-edit-global-kill ()
+ "Edit a global KILL file."
+ (interactive)
+ (setq gnus-current-kill-article (gnus-Subject-article-number))
+ (gnus-Kill-file-edit-file nil) ;Nil stands for global KILL file.
+ (message
+ (substitute-command-keys
+ "Editing a global KILL file (Type \\[gnus-Kill-file-exit] to exit)")))
+
+(defun gnus-Subject-edit-local-kill ()
+ "Edit a local KILL file applied to the current newsgroup."
+ (interactive)
+ (setq gnus-current-kill-article (gnus-Subject-article-number))
+ (gnus-Kill-file-edit-file gnus-newsgroup-name)
+ (message
+ (substitute-command-keys
+ "Editing a local KILL file (Type \\[gnus-Kill-file-exit] to exit)")))
+
+(defun gnus-Subject-exit (&optional temporary)
+ "Exit reading current newsgroup, and then return to group selection mode.
+gnus-Exit-group-hook is called with no arguments if that value is non-nil."
+ (interactive)
+ (let ((updated nil)
+ (gnus-newsgroup-headers gnus-newsgroup-headers)
+ (gnus-newsgroup-unreads gnus-newsgroup-unreads)
+ (gnus-newsgroup-unselected gnus-newsgroup-unselected)
+ (gnus-newsgroup-marked gnus-newsgroup-marked))
+ ;; Important internal variables are save, so we can reenter
+ ;; Subject Mode buffer even if hook changes them.
+ (run-hooks 'gnus-Exit-group-hook)
+ (gnus-update-unread-articles gnus-newsgroup-name
+ (append gnus-newsgroup-unselected
+ gnus-newsgroup-unreads)
+ gnus-newsgroup-marked)
+ ;; T means ignore unsubscribed newsgroups.
+ (if gnus-use-cross-reference
+ (setq updated
+ (gnus-mark-as-read-by-xref gnus-newsgroup-name
+ gnus-newsgroup-headers
+ gnus-newsgroup-unreads
+ (eq gnus-use-cross-reference t)
+ )))
+ ;; Do not switch windows but change the buffer to work.
+ (set-buffer gnus-Group-buffer)
+ ;; Update cross referenced group info.
+ (while updated
+ (gnus-Group-update-group (car updated) t) ;Ignore invisible group.
+ (setq updated (cdr updated)))
+ (gnus-Group-update-group gnus-newsgroup-name))
+ ;; Make sure where I was, and go to next newsgroup.
+ (gnus-Group-jump-to-group gnus-newsgroup-name)
+ (gnus-Group-next-unread-group 1)
+ (if temporary
+ ;; If exiting temporary, caller should adjust Group mode
+ ;; buffer point by itself.
+ nil ;Nothing to do.
+ ;; Return to Group mode buffer.
+ (if (get-buffer gnus-Subject-buffer)
+ (bury-buffer gnus-Subject-buffer))
+ (if (get-buffer gnus-Article-buffer)
+ (bury-buffer gnus-Article-buffer))
+ (gnus-configure-windows 'ExitNewsgroup)
+ (pop-to-buffer gnus-Group-buffer)))
+
+(defun gnus-Subject-quit ()
+ "Quit reading current newsgroup without updating read article info."
+ (interactive)
+ (if (y-or-n-p "Do you really wanna quit reading this group? ")
+ (progn
+ (message "") ;Erase "Yes or No" question.
+ ;; Return to Group selection mode.
+ (if (get-buffer gnus-Subject-buffer)
+ (bury-buffer gnus-Subject-buffer))
+ (if (get-buffer gnus-Article-buffer)
+ (bury-buffer gnus-Article-buffer))
+ (gnus-configure-windows 'ExitNewsgroup)
+ (pop-to-buffer gnus-Group-buffer)
+ (gnus-Group-jump-to-group gnus-newsgroup-name) ;Make sure where I was.
+ (gnus-Group-next-group 1) ;(gnus-Group-next-unread-group 1)
+ )))
+
+(defun gnus-Subject-describe-briefly ()
+ "Describe Subject mode commands briefly."
+ (interactive)
+ (message
+ (concat
+ (substitute-command-keys "\\[gnus-Subject-next-page]:Select ")
+ (substitute-command-keys "\\[gnus-Subject-next-unread-article]:Forward ")
+ (substitute-command-keys "\\[gnus-Subject-prev-unread-article]:Backward ")
+ (substitute-command-keys "\\[gnus-Subject-exit]:Exit ")
+ (substitute-command-keys "\\[gnus-Info-find-node]:Run Info ")
+ (substitute-command-keys "\\[gnus-Subject-describe-briefly]:This help")
+ )))
+
+
+;;;
+;;; GNUS Article Mode
+;;;
+
+(if gnus-Article-mode-map
+ nil
+ (setq gnus-Article-mode-map (make-keymap))
+ (suppress-keymap gnus-Article-mode-map)
+ (define-key gnus-Article-mode-map " " 'gnus-Article-next-page)
+ (define-key gnus-Article-mode-map "\177" 'gnus-Article-prev-page)
+ (define-key gnus-Article-mode-map "r" 'gnus-Article-refer-article)
+ (define-key gnus-Article-mode-map "o" 'gnus-Article-pop-article)
+ (define-key gnus-Article-mode-map "h" 'gnus-Article-show-subjects)
+ (define-key gnus-Article-mode-map "s" 'gnus-Article-show-subjects)
+ (define-key gnus-Article-mode-map "?" 'gnus-Article-describe-briefly)
+ (define-key gnus-Article-mode-map "\C-c\C-i" 'gnus-Info-find-node))
+
+(defun gnus-Article-mode ()
+ "Major mode for browsing through an article.
+All normal editing commands are turned off.
+Instead, these commands are available:
+\\{gnus-Article-mode-map}
+
+Various hooks for customization:
+ gnus-Article-mode-hook
+ Entry to this mode calls the value with no arguments, if that
+ value is non-nil.
+
+ gnus-Article-prepare-hook
+ Called with no arguments after an article is prepared for reading,
+ if that value is non-nil."
+ (interactive)
+ (kill-all-local-variables)
+ ;; Gee. Why don't you upgrade?
+ (cond ((boundp 'mode-line-modified)
+ (setq mode-line-modified "--- "))
+ ((listp (default-value 'mode-line-format))
+ (setq mode-line-format
+ (cons "--- " (cdr (default-value 'mode-line-format))))))
+ (make-local-variable 'global-mode-string)
+ (setq global-mode-string nil)
+ (setq major-mode 'gnus-Article-mode)
+ (setq mode-name "Article")
+ (gnus-Article-set-mode-line)
+ (use-local-map gnus-Article-mode-map)
+ (make-local-variable 'page-delimiter)
+ (setq page-delimiter gnus-page-delimiter)
+ (make-local-variable 'mail-header-separator)
+ (setq mail-header-separator "") ;For caesar function.
+ (buffer-flush-undo (current-buffer))
+ (setq buffer-read-only t) ;Disable modification
+ (run-hooks 'gnus-Article-mode-hook))
+
+(defun gnus-Article-setup-buffer ()
+ "Initialize Article mode buffer."
+ (or (get-buffer gnus-Article-buffer)
+ (save-excursion
+ (set-buffer (get-buffer-create gnus-Article-buffer))
+ (gnus-Article-mode))
+ ))
+
+(defun gnus-Article-prepare (article &optional all-headers)
+ "Prepare ARTICLE in Article mode buffer.
+If optional argument ALL-HEADERS is non-nil, all headers are inserted."
+ (save-excursion
+ (set-buffer gnus-Article-buffer)
+ (let ((buffer-read-only nil))
+ (erase-buffer)
+ (if (gnus-request-article article)
+ (progn
+ ;; Prepare article buffer
+ (insert-buffer-substring nntp-server-buffer)
+ (setq gnus-have-all-headers (or all-headers gnus-show-all-headers))
+ (if (and (numberp article)
+ (not (eq article gnus-current-article)))
+ ;; Seems me that a new article is selected.
+ (progn
+ ;; gnus-current-article must be an article number.
+ (setq gnus-last-article gnus-current-article)
+ (setq gnus-current-article article)
+ (setq gnus-current-headers
+ (gnus-find-header-by-number gnus-newsgroup-headers
+ gnus-current-article))
+ ;; Clear articles history only when articles are
+ ;; retrieved by article numbers.
+ (setq gnus-current-history nil)
+ (run-hooks 'gnus-Mark-article-hook)
+ ))
+ ;; Hooks for modifying contents of the article. This hook
+ ;; must be called before being narrowed.
+ (run-hooks 'gnus-Article-prepare-hook)
+ ;; Delete unnecessary headers.
+ (or gnus-have-all-headers
+ (gnus-Article-delete-headers))
+ ;; Do page break.
+ (goto-char (point-min))
+ (if gnus-break-pages
+ (gnus-narrow-to-page))
+ ;; Next function must be called after setting
+ ;; `gnus-current-article' variable and narrowed to page.
+ (gnus-Article-set-mode-line)
+ )
+ (if (numberp article)
+ (gnus-Subject-mark-as-read article))
+ (ding) (message "No such article (may be canceled)"))
+ )))
+
+(defun gnus-Article-show-all-headers ()
+ "Show all article headers in Article mode buffer."
+ (or gnus-have-all-headers
+ (gnus-Article-prepare gnus-current-article t)))
+
+;;(defun gnus-Article-set-mode-line ()
+;; "Set Article mode line string."
+;; (setq mode-line-buffer-identification
+;; (list 17
+;; (format "GNUS: %s {%d-%d} %d"
+;; gnus-newsgroup-name
+;; gnus-newsgroup-begin
+;; gnus-newsgroup-end
+;; gnus-current-article
+;; )))
+;; (set-buffer-modified-p t))
+
+(defun gnus-Article-set-mode-line ()
+ "Set Article mode line string."
+ (let ((unmarked
+ (- (length gnus-newsgroup-unreads)
+ (length (gnus-intersection
+ gnus-newsgroup-unreads gnus-newsgroup-marked))))
+ (unselected
+ (- (length gnus-newsgroup-unselected)
+ (length (gnus-intersection
+ gnus-newsgroup-unselected gnus-newsgroup-marked)))))
+ (setq mode-line-buffer-identification
+ (list 17
+ (format "GNUS: %s{%d} %s"
+ gnus-newsgroup-name
+ gnus-current-article
+ ;; This is proposed by tale@pawl.rpi.edu.
+ (cond ((and (zerop unmarked)
+ (zerop unselected))
+ " ")
+ ((zerop unselected)
+ (format "%d more" unmarked))
+ (t
+ (format "%d(+%d) more" unmarked unselected)))
+ ))))
+ (set-buffer-modified-p t))
+
+(defun gnus-Article-delete-headers ()
+ "Delete unnecessary headers."
+ (save-excursion
+ (save-restriction
+ (goto-char (point-min))
+ (narrow-to-region (point-min)
+ (progn (search-forward "\n\n" nil 'move) (point)))
+ (goto-char (point-min))
+ (and (stringp gnus-ignored-headers)
+ (while (re-search-forward gnus-ignored-headers nil t)
+ (beginning-of-line)
+ (delete-region (point)
+ (progn (re-search-forward "\n[^ \t]")
+ (forward-char -1)
+ (point)))))
+ )))
+
+;; Working on article's buffer
+
+(defun gnus-Article-next-page (lines)
+ "Show next page of current article.
+If end of article, return non-nil. Otherwise return nil.
+Argument LINES specifies lines to be scrolled up."
+ (interactive "P")
+ (move-to-window-line -1)
+ ;; Fixed by enami@ptgd.sony.co.jp (enami tsugutomo)
+ (if (save-excursion
+ (end-of-line)
+ (and (pos-visible-in-window-p) ;Not continuation line.
+ (eobp)))
+ ;; Nothing in this page.
+ (if (or (not gnus-break-pages)
+ (save-excursion
+ (save-restriction
+ (widen) (forward-line 1) (eobp)))) ;Real end-of-buffer?
+ t ;Nothing more.
+ (gnus-narrow-to-page 1) ;Go to next page.
+ nil
+ )
+ ;; More in this page.
+ (condition-case ()
+ (scroll-up lines)
+ (end-of-buffer
+ ;; Long lines may cause an end-of-buffer error.
+ (goto-char (point-max))))
+ nil
+ ))
+
+(defun gnus-Article-prev-page (lines)
+ "Show previous page of current article.
+Argument LINES specifies lines to be scrolled down."
+ (interactive "P")
+ (move-to-window-line 0)
+ (if (and gnus-break-pages
+ (bobp)
+ (not (save-restriction (widen) (bobp)))) ;Real beginning-of-buffer?
+ (progn
+ (gnus-narrow-to-page -1) ;Go to previous page.
+ (goto-char (point-max))
+ (recenter -1))
+ (scroll-down lines)))
+
+(defun gnus-Article-next-digest (nth)
+ "Move to head of NTH next digested message.
+Set mark at end of digested message."
+ ;; Stop page breaking in digest mode.
+ (widen)
+ (end-of-line)
+ ;; Skip NTH - 1 digest.
+ ;; Suggested by Khalid Sattar <admin@cs.exeter.ac.uk>.
+ ;; Digest separator is customizable.
+ ;; Suggested by Skip Montanaro <montanaro@sprite.crd.ge.com>.
+ (while (and (> nth 1)
+ (re-search-forward gnus-digest-separator nil 'move))
+ (setq nth (1- nth)))
+ (if (re-search-forward gnus-digest-separator nil t)
+ (let ((begin (point)))
+ ;; Search for end of this message.
+ (end-of-line)
+ (if (re-search-forward gnus-digest-separator nil t)
+ (progn
+ (search-backward "\n\n") ;This may be incorrect.
+ (forward-line 1))
+ (goto-char (point-max)))
+ (push-mark) ;Set mark at end of digested message.
+ (goto-char begin)
+ (beginning-of-line)
+ ;; Show From: and Subject: fields.
+ (recenter 1))
+ (message "End of message")
+ ))
+
+(defun gnus-Article-prev-digest (nth)
+ "Move to head of NTH previous digested message."
+ ;; Stop page breaking in digest mode.
+ (widen)
+ (beginning-of-line)
+ ;; Skip NTH - 1 digest.
+ ;; Suggested by Khalid Sattar <admin@cs.exeter.ac.uk>.
+ ;; Digest separator is customizable.
+ ;; Suggested by Skip Montanaro <montanaro@sprite.crd.ge.com>.
+ (while (and (> nth 1)
+ (re-search-backward gnus-digest-separator nil 'move))
+ (setq nth (1- nth)))
+ (if (re-search-backward gnus-digest-separator nil t)
+ (let ((begin (point)))
+ ;; Search for end of this message.
+ (end-of-line)
+ (if (re-search-forward gnus-digest-separator nil t)
+ (progn
+ (search-backward "\n\n") ;This may be incorrect.
+ (forward-line 1))
+ (goto-char (point-max)))
+ (push-mark) ;Set mark at end of digested message.
+ (goto-char begin)
+ ;; Show From: and Subject: fields.
+ (recenter 1))
+ (goto-char (point-min))
+ (message "Top of message")
+ ))
+
+(defun gnus-Article-refer-article ()
+ "Read article specified by message-id around point."
+ (interactive)
+ (save-window-excursion
+ (save-excursion
+ (re-search-forward ">" nil t) ;Move point to end of "<....>".
+ (if (re-search-backward "\\(<[^<> \t\n]+>\\)" nil t)
+ (let ((message-id
+ (buffer-substring (match-beginning 1) (match-end 1))))
+ (set-buffer gnus-Subject-buffer)
+ (gnus-Subject-refer-article message-id))
+ (error "No references around point"))
+ )))
+
+(defun gnus-Article-pop-article ()
+ "Pop up article history."
+ (interactive)
+ (save-window-excursion
+ (set-buffer gnus-Subject-buffer)
+ (gnus-Subject-refer-article nil)))
+
+(defun gnus-Article-show-subjects ()
+ "Reconfigure windows to show headers."
+ (interactive)
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Subject-buffer)
+ (gnus-Subject-goto-subject gnus-current-article))
+
+(defun gnus-Article-describe-briefly ()
+ "Describe Article mode commands briefly."
+ (interactive)
+ (message
+ (concat
+ (substitute-command-keys "\\[gnus-Article-next-page]:Next page ")
+ (substitute-command-keys "\\[gnus-Article-prev-page]:Prev page ")
+ (substitute-command-keys "\\[gnus-Article-show-subjects]:Show headers ")
+ (substitute-command-keys "\\[gnus-Info-find-node]:Run Info ")
+ (substitute-command-keys "\\[gnus-Article-describe-briefly]:This help")
+ )))
+
+
+;;;
+;;; GNUS KILL-File Mode
+;;;
+
+(if gnus-Kill-file-mode-map
+ nil
+ (setq gnus-Kill-file-mode-map (copy-keymap emacs-lisp-mode-map))
+ (define-key gnus-Kill-file-mode-map "\C-c\C-k\C-s" 'gnus-Kill-file-kill-by-subject)
+ (define-key gnus-Kill-file-mode-map "\C-c\C-k\C-a" 'gnus-Kill-file-kill-by-author)
+ (define-key gnus-Kill-file-mode-map "\C-c\C-a" 'gnus-Kill-file-apply-buffer)
+ (define-key gnus-Kill-file-mode-map "\C-c\C-e" 'gnus-Kill-file-apply-last-sexp)
+ (define-key gnus-Kill-file-mode-map "\C-c\C-c" 'gnus-Kill-file-exit)
+ (define-key gnus-Kill-file-mode-map "\C-c\C-i" 'gnus-Info-find-node))
+
+(defun gnus-Kill-file-mode ()
+ "Major mode for editing KILL file.
+
+In addition to Emacs-Lisp Mode, the following commands are available:
+
+\\[gnus-Kill-file-kill-by-subject] Insert KILL command for current subject.
+\\[gnus-Kill-file-kill-by-author] Insert KILL command for current author.
+\\[gnus-Kill-file-apply-buffer] Apply current buffer to selected newsgroup.
+\\[gnus-Kill-file-apply-last-sexp] Apply sexp before point to selected newsgroup.
+\\[gnus-Kill-file-exit] Save file and exit editing KILL file.
+\\[gnus-Info-find-node] Read Info about KILL file.
+
+ A KILL file contains lisp expressions to be applied to a selected
+newsgroup. The purpose is to mark articles as read on the basis of
+some set of regexps. A global KILL file is applied to every newsgroup,
+and a local KILL file is applied to a specified newsgroup. Since a
+global KILL file is applied to every newsgroup, for better performance
+use a local one.
+
+ A KILL file can contain any kind of Emacs lisp expressions expected
+to be evaluated in the Subject buffer. Writing lisp programs for this
+purpose is not so easy because the internal working of GNUS must be
+well-known. For this reason, GNUS provides a general function which
+does this easily for non-Lisp programmers.
+
+ The `gnus-kill' function executes commands available in Subject Mode
+by their key sequences. `gnus-kill' should be called with FIELD,
+REGEXP and optional COMMAND and ALL. FIELD is a string representing
+the header field or an empty string. If FIELD is an empty string, the
+entire article body is searched for. REGEXP is a string which is
+compared with FIELD value. COMMAND is a string representing a valid
+key sequence in Subject Mode or Lisp expression. COMMAND is default to
+'(gnus-Subject-mark-as-read nil \"X\"). Make sure that COMMAND is
+executed in the Subject buffer. If the second optional argument ALL
+is non-nil, the COMMAND is applied to articles which are already
+marked as read or unread. Articles which are marked are skipped over
+by default.
+
+ For example, if you want to mark articles of which subjects contain
+the string `AI' as read, a possible KILL file may look like:
+
+ (gnus-kill \"Subject\" \"AI\")
+
+ If you want to mark articles with `D' instead of `X', you can use
+the following expression:
+
+ (gnus-kill \"Subject\" \"AI\" \"d\")
+
+In this example it is assumed that the command
+`gnus-Subject-mark-as-read-forward' is assigned to `d' in Subject Mode.
+
+ It is possible to delete unnecessary headers which are marked with
+`X' in a KILL file as follows:
+
+ (gnus-expunge \"X\")
+
+ If the Subject buffer is empty after applying KILL files, GNUS will
+exit the selected newsgroup normally. If headers which are marked
+with `D' are deleted in a KILL file, it is impossible to read articles
+which are marked as read in the previous GNUS sessions. Marks other
+than `D' should be used for articles which should really be deleted.
+
+Entry to this mode calls emacs-lisp-mode-hook and
+gnus-Kill-file-mode-hook with no arguments, if that value is non-nil."
+ (interactive)
+ (kill-all-local-variables)
+ (use-local-map gnus-Kill-file-mode-map)
+ (set-syntax-table emacs-lisp-mode-syntax-table)
+ (setq major-mode 'gnus-Kill-file-mode)
+ (setq mode-name "KILL-File")
+ (lisp-mode-variables nil)
+ (run-hooks 'emacs-lisp-mode-hook 'gnus-Kill-file-mode-hook))
+
+(defun gnus-Kill-file-edit-file (newsgroup)
+ "Begin editing a KILL file of NEWSGROUP.
+If NEWSGROUP is nil, the global KILL file is selected."
+ (interactive "sNewsgroup: ")
+ (let ((file (gnus-newsgroup-kill-file newsgroup)))
+ (gnus-make-directory (file-name-directory file))
+ ;; Save current window configuration if this is first invocation.
+ (or (and (get-file-buffer file)
+ (get-buffer-window (get-file-buffer file)))
+ (setq gnus-winconf-kill-file (current-window-configuration)))
+ ;; Hack windows.
+ (let ((buffer (find-file-noselect file)))
+ (cond ((get-buffer-window buffer)
+ (pop-to-buffer buffer))
+ ((eq major-mode 'gnus-Group-mode)
+ (gnus-configure-windows '(1 0 0)) ;Take all windows.
+ (pop-to-buffer gnus-Group-buffer)
+ (let ((gnus-Subject-buffer buffer))
+ (gnus-configure-windows '(1 1 0)) ;Split into two.
+ (pop-to-buffer buffer)))
+ ((eq major-mode 'gnus-Subject-mode)
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Article-buffer)
+ (bury-buffer gnus-Article-buffer)
+ (switch-to-buffer buffer))
+ (t ;No good rules.
+ (find-file-other-window file))
+ ))
+ (gnus-Kill-file-mode)
+ ))
+
+(defun gnus-Kill-file-kill-by-subject ()
+ "Insert KILL command for current subject."
+ (interactive)
+ (insert
+ (format "(gnus-kill \"Subject\" %s)\n"
+ (prin1-to-string
+ (if gnus-current-kill-article
+ (regexp-quote
+ (nntp-header-subject
+ (gnus-find-header-by-number gnus-newsgroup-headers
+ gnus-current-kill-article)))
+ "")))))
+
+(defun gnus-Kill-file-kill-by-author ()
+ "Insert KILL command for current author."
+ (interactive)
+ (insert
+ (format "(gnus-kill \"From\" %s)\n"
+ (prin1-to-string
+ (if gnus-current-kill-article
+ (regexp-quote
+ (nntp-header-from
+ (gnus-find-header-by-number gnus-newsgroup-headers
+ gnus-current-kill-article)))
+ "")))))
+
+(defun gnus-Kill-file-apply-buffer ()
+ "Apply current buffer to current newsgroup."
+ (interactive)
+ (if (and gnus-current-kill-article
+ (get-buffer gnus-Subject-buffer))
+ ;; Assume newsgroup is selected.
+ (let ((string (concat "(progn \n" (buffer-string) "\n)" )))
+ (save-excursion
+ (save-window-excursion
+ (pop-to-buffer gnus-Subject-buffer)
+ (eval (car (read-from-string string))))))
+ (ding) (message "No newsgroup is selected.")))
+
+(defun gnus-Kill-file-apply-last-sexp ()
+ "Apply sexp before point in current buffer to current newsgroup."
+ (interactive)
+ (if (and gnus-current-kill-article
+ (get-buffer gnus-Subject-buffer))
+ ;; Assume newsgroup is selected.
+ (let ((string
+ (buffer-substring
+ (save-excursion (forward-sexp -1) (point)) (point))))
+ (save-excursion
+ (save-window-excursion
+ (pop-to-buffer gnus-Subject-buffer)
+ (eval (car (read-from-string string))))))
+ (ding) (message "No newsgroup is selected.")))
+
+(defun gnus-Kill-file-exit ()
+ "Save a KILL file, then return to the previous buffer."
+ (interactive)
+ (save-buffer)
+ (let ((killbuf (current-buffer)))
+ ;; We don't want to return to Article buffer.
+ (and (get-buffer gnus-Article-buffer)
+ (bury-buffer (get-buffer gnus-Article-buffer)))
+ ;; Delete the KILL file windows.
+ (delete-windows-on killbuf)
+ ;; Restore last window configuration if available.
+ (and gnus-winconf-kill-file
+ (set-window-configuration gnus-winconf-kill-file))
+ (setq gnus-winconf-kill-file nil)
+ ;; Kill the KILL file buffer. Suggested by tale@pawl.rpi.edu.
+ (kill-buffer killbuf)))
+
+
+;;;
+;;; Utility functions
+;;;
+
+;; Basic ideas by emv@math.lsa.umich.edu (Edward Vielmetti)
+
+(defun gnus-batch-kill ()
+ "Run batched KILL.
+Usage: emacs -batch -l gnus -f gnus-batch-kill NEWSGROUP ..."
+ (if (not noninteractive)
+ (error "gnus-batch-kill is to be used only with -batch"))
+ (let* ((group nil)
+ (subscribed nil)
+ (newsrc nil)
+ (yes-and-no
+ (gnus-parse-n-options
+ (apply (function concat)
+ (mapcar (function (lambda (g) (concat g " ")))
+ command-line-args-left))))
+ (yes (car yes-and-no))
+ (no (cdr yes-and-no))
+ ;; Disable verbose message.
+ (gnus-novice-user nil)
+ (gnus-large-newsgroup nil)
+ (nntp-large-newsgroup nil))
+ ;; Eat all arguments.
+ (setq command-line-args-left nil)
+ ;; Startup GNUS.
+ (gnus)
+ ;; Apply kills to specified newsgroups in command line arguments.
+ (setq newsrc (copy-sequence gnus-newsrc-assoc))
+ (while newsrc
+ (setq group (car (car newsrc)))
+ (setq subscribed (nth 1 (car newsrc)))
+ (setq newsrc (cdr newsrc))
+ (if (and subscribed
+ (not (zerop (nth 1 (gnus-gethash group gnus-unread-hashtb))))
+ (if yes
+ (string-match yes group) t)
+ (or (null no)
+ (not (string-match no group))))
+ (progn
+ (gnus-Subject-read-group group nil t)
+ (if (eq (current-buffer) (get-buffer gnus-Subject-buffer))
+ (gnus-Subject-exit t))
+ ))
+ )
+ ;; Finally, exit Emacs.
+ (set-buffer gnus-Group-buffer)
+ (gnus-Group-exit)
+ ))
+
+(defun gnus-Numeric-save-name (newsgroup headers &optional last-file)
+ "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
+If variable `gnus-use-long-file-name' is nil, it is ~/News/News.group/num.
+Otherwise, it is like ~/News/news/group/num."
+ (let ((default
+ (expand-file-name
+ (concat (if gnus-use-long-file-name
+ (capitalize newsgroup)
+ (gnus-newsgroup-directory-form newsgroup))
+ "/" (int-to-string (nntp-header-number headers)))
+ (or gnus-article-save-directory "~/News"))))
+ (if (and last-file
+ (string-equal (file-name-directory default)
+ (file-name-directory last-file))
+ (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
+ default
+ (or last-file default))))
+
+(defun gnus-numeric-save-name (newsgroup headers &optional last-file)
+ "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
+If variable `gnus-use-long-file-name' is nil, it is ~/News/news.group/num.
+Otherwise, it is like ~/News/news/group/num."
+ (let ((default
+ (expand-file-name
+ (concat (if gnus-use-long-file-name
+ newsgroup
+ (gnus-newsgroup-directory-form newsgroup))
+ "/" (int-to-string (nntp-header-number headers)))
+ (or gnus-article-save-directory "~/News"))))
+ (if (and last-file
+ (string-equal (file-name-directory default)
+ (file-name-directory last-file))
+ (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
+ default
+ (or last-file default))))
+
+(defun gnus-Plain-save-name (newsgroup headers &optional last-file)
+ "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
+If variable `gnus-use-long-file-name' is nil, it is ~/News/News.group.
+Otherwise, it is like ~/News/news/group/news."
+ (or last-file
+ (expand-file-name
+ (if gnus-use-long-file-name
+ (capitalize newsgroup)
+ (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
+ (or gnus-article-save-directory "~/News"))))
+
+(defun gnus-plain-save-name (newsgroup headers &optional last-file)
+ "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
+If variable `gnus-use-long-file-name' is nil, it is ~/News/news.group.
+Otherwise, it is like ~/News/news/group/news."
+ (or last-file
+ (expand-file-name
+ (if gnus-use-long-file-name
+ newsgroup
+ (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
+ (or gnus-article-save-directory "~/News"))))
+
+(defun gnus-Folder-save-name (newsgroup headers &optional last-folder)
+ "Generate folder name from NEWSGROUP, HEADERS, and optional LAST-FOLDER.
+If variable `gnus-use-long-file-name' is nil, it is +News.group.
+Otherwise, it is like +news/group."
+ (or last-folder
+ (concat "+"
+ (if gnus-use-long-file-name
+ (capitalize newsgroup)
+ (gnus-newsgroup-directory-form newsgroup)))))
+
+(defun gnus-folder-save-name (newsgroup headers &optional last-folder)
+ "Generate folder name from NEWSGROUP, HEADERS, and optional LAST-FOLDER.
+If variable `gnus-use-long-file-name' is nil, it is +news.group.
+Otherwise, it is like +news/group."
+ (or last-folder
+ (concat "+"
+ (if gnus-use-long-file-name
+ newsgroup
+ (gnus-newsgroup-directory-form newsgroup)))))
+
+(defun gnus-apply-kill-file ()
+ "Apply KILL file to the current newsgroup."
+ ;; Apply the global KILL file.
+ (load (gnus-newsgroup-kill-file nil) t nil t)
+ ;; And then apply the local KILL file.
+ (load (gnus-newsgroup-kill-file gnus-newsgroup-name) t nil t))
+
+(defun gnus-Newsgroup-kill-file (newsgroup)
+ "Return the name of a KILL file of NEWSGROUP.
+If NEWSGROUP is nil, return the global KILL file instead."
+ (cond ((or (null newsgroup)
+ (string-equal newsgroup ""))
+ ;; The global KILL file is placed at top of the directory.
+ (expand-file-name gnus-kill-file-name
+ (or gnus-article-save-directory "~/News")))
+ (gnus-use-long-file-name
+ ;; Append ".KILL" to capitalized newsgroup name.
+ (expand-file-name (concat (capitalize newsgroup)
+ "." gnus-kill-file-name)
+ (or gnus-article-save-directory "~/News")))
+ (t
+ ;; Place "KILL" under the hierarchical directory.
+ (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
+ "/" gnus-kill-file-name)
+ (or gnus-article-save-directory "~/News")))
+ ))
+
+(defun gnus-newsgroup-kill-file (newsgroup)
+ "Return the name of a KILL file of NEWSGROUP.
+If NEWSGROUP is nil, return the global KILL file instead."
+ (cond ((or (null newsgroup)
+ (string-equal newsgroup ""))
+ ;; The global KILL file is placed at top of the directory.
+ (expand-file-name gnus-kill-file-name
+ (or gnus-article-save-directory "~/News")))
+ (gnus-use-long-file-name
+ ;; Append ".KILL" to newsgroup name.
+ (expand-file-name (concat newsgroup "." gnus-kill-file-name)
+ (or gnus-article-save-directory "~/News")))
+ (t
+ ;; Place "KILL" under the hierarchical directory.
+ (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
+ "/" gnus-kill-file-name)
+ (or gnus-article-save-directory "~/News")))
+ ))
+
+(defun gnus-newsgroup-directory-form (newsgroup)
+ "Make hierarchical directory name from NEWSGROUP name."
+ (let ((newsgroup (substring newsgroup 0)) ;Copy string.
+ (len (length newsgroup))
+ (idx 0))
+ ;; Replace all occurence of `.' with `/'.
+ (while (< idx len)
+ (if (= (aref newsgroup idx) ?.)
+ (aset newsgroup idx ?/))
+ (setq idx (1+ idx)))
+ newsgroup
+ ))
+
+(defun gnus-make-directory (directory)
+ "Make DIRECTORY recursively."
+ (let ((directory (expand-file-name directory default-directory)))
+ (or (file-exists-p directory)
+ (gnus-make-directory-1 "" directory))
+ ))
+
+(defun gnus-make-directory-1 (head tail)
+ (cond ((string-match "^/\\([^/]+\\)" tail)
+ (setq head
+ (concat (file-name-as-directory head)
+ (substring tail (match-beginning 1) (match-end 1))))
+ (or (file-exists-p head)
+ (call-process "mkdir" nil nil nil head))
+ (gnus-make-directory-1 head (substring tail (match-end 1))))
+ ((string-equal tail "") t)
+ ))
+
+(defun gnus-simplify-subject (subject &optional re-only)
+ "Remove `Re:' and words in parentheses.
+If optional argument RE-ONLY is non-nil, strip `Re:' only."
+ (let ((case-fold-search t)) ;Ignore case.
+ ;; Remove `Re:' and `Re^N:'.
+ (if (string-match "\\`\\(re\\(\\^[0-9]+\\)?:[ \t]+\\)+" subject)
+ (setq subject (substring subject (match-end 0))))
+ ;; Remove words in parentheses from end.
+ (or re-only
+ (while (string-match "[ \t\n]*([^()]*)[ \t\n]*\\'" subject)
+ (setq subject (substring subject 0 (match-beginning 0)))))
+ ;; Return subject string.
+ subject
+ ))
+
+(defun gnus-optional-lines-and-from (header)
+ "Return a string like `NNN:AUTHOR' from HEADER."
+ (let ((name-length (length "umerin@photon")))
+ (substring (format "%3d:%s"
+ ;; Lines of the article.
+ ;; Suggested by dana@bellcore.com.
+ (nntp-header-lines header)
+ ;; Its author.
+ (concat (mail-strip-quoted-names
+ (nntp-header-from header))
+ (make-string name-length ? )))
+ ;; 4 stands for length of `NNN:'.
+ 0 (+ 4 name-length))))
+
+(defun gnus-optional-lines (header)
+ "Return a string like `NNN' from HEADER."
+ (format "%4d" (nntp-header-lines header)))
+
+(defun gnus-sort-headers (predicate &optional reverse)
+ "Sort current group headers by PREDICATE safely.
+*Safely* means C-g quitting is disabled during sorting.
+Optional argument REVERSE means reverse order."
+ (let ((inhibit-quit t))
+ (setq gnus-newsgroup-headers
+ (if reverse
+ (nreverse (sort (nreverse gnus-newsgroup-headers) predicate))
+ (sort gnus-newsgroup-headers predicate)))
+ ))
+
+(defun gnus-string-lessp (a b)
+ "Return T if first arg string is less than second in lexicographic order.
+If case-fold-search is non-nil, case of letters is ignored."
+ (if case-fold-search
+ (string-lessp (downcase a) (downcase b)) (string-lessp a b)))
+
+(defun gnus-date-lessp (date1 date2)
+ "Return T if DATE1 is earlyer than DATE2."
+ (string-lessp (gnus-comparable-date date1)
+ (gnus-comparable-date date2)))
+
+(defun gnus-comparable-date (date)
+ "Make comparable string by string-lessp from DATE."
+ (let ((month '(("JAN" . " 1")("FEB" . " 2")("MAR" . " 3")
+ ("APR" . " 4")("MAY" . " 5")("JUN" . " 6")
+ ("JUL" . " 7")("AUG" . " 8")("SEP" . " 9")
+ ("OCT" . "10")("NOV" . "11")("DEC" . "12")))
+ (date (or date "")))
+ ;; Can understand the following styles:
+ ;; (1) 14 Apr 89 03:20:12 GMT
+ ;; (2) Fri, 17 Mar 89 4:01:33 GMT
+ (if (string-match
+ "\\([0-9]+\\) \\([^ ,]+\\) \\([0-9]+\\) \\([0-9:]+\\)" date)
+ (concat
+ ;; Year
+ (substring date (match-beginning 3) (match-end 3))
+ ;; Month
+ (cdr
+ (assoc
+ (upcase (substring date (match-beginning 2) (match-end 2))) month))
+ ;; Day
+ (format "%2d" (string-to-int
+ (substring date
+ (match-beginning 1) (match-end 1))))
+ ;; Time
+ (substring date (match-beginning 4) (match-end 4)))
+ ;; Cannot understand DATE string.
+ date
+ )
+ ))
+
+(defun gnus-fetch-field (field)
+ "Return the value of the header FIELD of current article."
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (narrow-to-region (point-min)
+ (progn (search-forward "\n\n" nil 'move) (point)))
+ (mail-fetch-field field))))
+
+(fset 'gnus-expunge 'gnus-Subject-delete-marked-with)
+
+(defun gnus-kill (field regexp &optional command all)
+ "If FIELD of an article matches REGEXP, execute COMMAND.
+Optional third argument COMMAND is default to
+ (gnus-Subject-mark-as-read nil \"X\").
+If optional fourth argument ALL is non-nil, articles marked are also applied
+to. If FIELD is an empty string (or nil), entire article body is searched for.
+COMMAND must be a lisp expression or a string representing a key sequence."
+ ;; We don't want to change current point nor window configuration.
+ (save-excursion
+ (save-window-excursion
+ ;; Selected window must be Subject mode buffer to execute
+ ;; keyboard macros correctly. See command_loop_1.
+ (switch-to-buffer gnus-Subject-buffer 'norecord)
+ (goto-char (point-min)) ;From the beginning.
+ (if (null command)
+ (setq command '(gnus-Subject-mark-as-read nil "X")))
+ (gnus-execute field regexp command nil (not all))
+ )))
+
+(defun gnus-execute (field regexp form &optional backward ignore-marked)
+ "If FIELD of article header matches REGEXP, execute lisp FORM (or a string).
+If FIELD is an empty string (or nil), entire article body is searched for.
+If optional fifth argument BACKWARD is non-nil, do backward instead.
+If optional sixth argument IGNORE-MARKED is non-nil, articles which are
+marked as read or unread are ignored."
+ (let ((function nil)
+ (header nil)
+ (article nil))
+ (if (string-equal field "")
+ (setq field nil))
+ (if (null field)
+ nil
+ (or (stringp field)
+ (setq field (symbol-name field)))
+ ;; Get access function of header filed.
+ (setq function (intern-soft (concat "gnus-header-" (downcase field))))
+ (if (and function (fboundp function))
+ (setq function (symbol-function function))
+ (error "Unknown header field: \"%s\"" field)))
+ ;; Make FORM funcallable.
+ (if (and (listp form) (not (eq (car form) 'lambda)))
+ (setq form (list 'lambda nil form)))
+ ;; Starting from the current article.
+ (or (and ignore-marked
+ ;; Articles marked as read and unread should be ignored.
+ (setq article (gnus-Subject-article-number))
+ (or (not (memq article gnus-newsgroup-unreads)) ;Marked as read.
+ (memq article gnus-newsgroup-marked) ;Marked as unread.
+ ))
+ (gnus-execute-1 function regexp form))
+ (while (gnus-Subject-search-subject backward ignore-marked nil)
+ (gnus-execute-1 function regexp form))
+ ))
+
+(defun gnus-execute-1 (function regexp form)
+ (save-excursion
+ ;; The point of Subject mode buffer must be saved during execution.
+ (let ((article (gnus-Subject-article-number)))
+ (if (null article)
+ nil ;Nothing to do.
+ (if function
+ ;; Compare with header field.
+ (let ((header (gnus-find-header-by-number
+ gnus-newsgroup-headers article))
+ (value nil))
+ (and header
+ (progn
+ (setq value (funcall function header))
+ ;; Number (Lines:) or symbol must be converted to string.
+ (or (stringp value)
+ (setq value (prin1-to-string value)))
+ (string-match regexp value))
+ (if (stringp form) ;Keyboard macro.
+ (execute-kbd-macro form)
+ (funcall form))))
+ ;; Search article body.
+ (let ((gnus-current-article nil) ;Save article pointer.
+ (gnus-last-article nil)
+ (gnus-break-pages nil) ;No need to break pages.
+ (gnus-Mark-article-hook nil)) ;Inhibit marking as read.
+ (message "Searching for article: %d..." article)
+ (gnus-Article-setup-buffer)
+ (gnus-Article-prepare article t)
+ (if (save-excursion
+ (set-buffer gnus-Article-buffer)
+ (goto-char (point-min))
+ (re-search-forward regexp nil t))
+ (if (stringp form) ;Keyboard macro.
+ (execute-kbd-macro form)
+ (funcall form))))
+ ))
+ )))
+
+;;; caesar-region written by phr@prep.ai.mit.edu Nov 86
+;;; modified by tower@prep Nov 86
+;;; Modified by umerin@flab.flab.Fujitsu.JUNET for ROT47.
+
+(defun gnus-caesar-region (&optional n)
+ "Caesar rotation of region by N, default 13, for decrypting netnews.
+ROT47 will be performed for Japanese text in any case."
+ (interactive (if current-prefix-arg ; Was there a prefix arg?
+ (list (prefix-numeric-value current-prefix-arg))
+ (list nil)))
+ (cond ((not (numberp n)) (setq n 13))
+ ((< n 0) (setq n (- 26 (% (- n) 26))))
+ (t (setq n (% n 26)))) ;canonicalize N
+ (if (not (zerop n)) ; no action needed for a rot of 0
+ (progn
+ (if (or (not (boundp 'caesar-translate-table))
+ (/= (aref caesar-translate-table ?a) (+ ?a n)))
+ (let ((i 0) (lower "abcdefghijklmnopqrstuvwxyz") upper)
+ (message "Building caesar-translate-table...")
+ (setq caesar-translate-table (make-vector 256 0))
+ (while (< i 256)
+ (aset caesar-translate-table i i)
+ (setq i (1+ i)))
+ (setq lower (concat lower lower) upper (upcase lower) i 0)
+ (while (< i 26)
+ (aset caesar-translate-table (+ ?a i) (aref lower (+ i n)))
+ (aset caesar-translate-table (+ ?A i) (aref upper (+ i n)))
+ (setq i (1+ i)))
+ ;; ROT47 for Japanese text.
+ ;; Thanks to ichikawa@flab.fujitsu.junet.
+ (setq i 161)
+ (let ((t1 (logior ?O 128))
+ (t2 (logior ?! 128))
+ (t3 (logior ?~ 128)))
+ (while (< i 256)
+ (aset caesar-translate-table i
+ (let ((v (aref caesar-translate-table i)))
+ (if (<= v t1) (if (< v t2) v (+ v 47))
+ (if (<= v t3) (- v 47) v))))
+ (setq i (1+ i))))
+ (message "Building caesar-translate-table... done")))
+ (let ((from (region-beginning))
+ (to (region-end))
+ (i 0) str len)
+ (setq str (buffer-substring from to))
+ (setq len (length str))
+ (while (< i len)
+ (aset str i (aref caesar-translate-table (aref str i)))
+ (setq i (1+ i)))
+ (goto-char from)
+ (delete-region from to)
+ (insert str)))))
+
+;; Functions accessing headers.
+;; Functions are more convenient than macros in some case.
+
+(defun gnus-header-number (header)
+ "Return article number in HEADER."
+ (nntp-header-number header))
+
+(defun gnus-header-subject (header)
+ "Return subject string in HEADER."
+ (nntp-header-subject header))
+
+(defun gnus-header-from (header)
+ "Return author string in HEADER."
+ (nntp-header-from header))
+
+(defun gnus-header-xref (header)
+ "Return xref string in HEADER."
+ (nntp-header-xref header))
+
+(defun gnus-header-lines (header)
+ "Return lines in HEADER."
+ (nntp-header-lines header))
+
+(defun gnus-header-date (header)
+ "Return date in HEADER."
+ (nntp-header-date header))
+
+(defun gnus-header-id (header)
+ "Return Id in HEADER."
+ (nntp-header-id header))
+
+(defun gnus-header-references (header)
+ "Return references in HEADER."
+ (nntp-header-references header))
+
+
+;;;
+;;; Article savers.
+;;;
+
+(defun gnus-output-to-rmail (file-name)
+ "Append the current article to an Rmail file named FILE-NAME."
+ (require 'rmail)
+ ;; Most of these codes are borrowed from rmailout.el.
+ (setq file-name (expand-file-name file-name))
+ (setq rmail-last-rmail-file file-name)
+ (let ((artbuf (current-buffer))
+ (tmpbuf (get-buffer-create " *GNUS-output*")))
+ (save-excursion
+ (or (get-file-buffer file-name)
+ (file-exists-p file-name)
+ (if (yes-or-no-p
+ (concat "\"" file-name "\" does not exist, create it? "))
+ (let ((file-buffer (create-file-buffer file-name)))
+ (save-excursion
+ (set-buffer file-buffer)
+ (rmail-insert-rmail-file-header)
+ (let ((require-final-newline nil))
+ (write-region (point-min) (point-max) file-name t 1)))
+ (kill-buffer file-buffer))
+ (error "Output file does not exist")))
+ (set-buffer tmpbuf)
+ (buffer-flush-undo (current-buffer))
+ (erase-buffer)
+ (insert-buffer-substring artbuf)
+ (gnus-convert-article-to-rmail)
+ ;; Decide whether to append to a file or to an Emacs buffer.
+ (let ((outbuf (get-file-buffer file-name)))
+ (if (not outbuf)
+ (append-to-file (point-min) (point-max) file-name)
+ ;; File has been visited, in buffer OUTBUF.
+ (set-buffer outbuf)
+ (let ((buffer-read-only nil)
+ (msg (and (boundp 'rmail-current-message)
+ rmail-current-message)))
+ ;; If MSG is non-nil, buffer is in RMAIL mode.
+ (if msg
+ (progn (widen)
+ (narrow-to-region (point-max) (point-max))))
+ (insert-buffer-substring tmpbuf)
+ (if msg
+ (progn
+ (goto-char (point-min))
+ (widen)
+ (search-backward "\^_")
+ (narrow-to-region (point) (point-max))
+ (goto-char (1+ (point-min)))
+ (rmail-count-new-messages t)
+ (rmail-show-message msg))))))
+ )
+ (kill-buffer tmpbuf)
+ ))
+
+(defun gnus-output-to-file (file-name)
+ "Append the current article to a file named FILE-NAME."
+ (setq file-name (expand-file-name file-name))
+ (let ((artbuf (current-buffer))
+ (tmpbuf (get-buffer-create " *GNUS-output*")))
+ (save-excursion
+ (set-buffer tmpbuf)
+ (buffer-flush-undo (current-buffer))
+ (erase-buffer)
+ (insert-buffer-substring artbuf)
+ ;; Append newline at end of the buffer as separator, and then
+ ;; save it to file.
+ (goto-char (point-max))
+ (insert "\n")
+ (append-to-file (point-min) (point-max) file-name))
+ (kill-buffer tmpbuf)
+ ))
+
+(defun gnus-convert-article-to-rmail ()
+ "Convert article in current buffer to Rmail message format."
+ (let ((buffer-read-only nil))
+ ;; Convert article directly into Babyl format.
+ ;; Suggested by Rob Austein <sra@lcs.mit.edu>
+ (goto-char (point-min))
+ (insert "\^L\n0, unseen,,\n*** EOOH ***\n")
+ (while (search-forward "\n\^_" nil t) ;single char
+ (replace-match "\n^_")) ;2 chars: "^" and "_"
+ (goto-char (point-max))
+ (insert "\^_")))
+
+;;(defun gnus-convert-article-to-rmail ()
+;; "Convert article in current buffer to Rmail message format."
+;; (let ((buffer-read-only nil))
+;; ;; Insert special header of Unix mail.
+;; (goto-char (point-min))
+;; (insert "From "
+;; (or (mail-strip-quoted-names (mail-fetch-field "from"))
+;; "unknown")
+;; " " (current-time-string) "\n")
+;; ;; Stop quoting `From' since this seems unnecessary in most cases.
+;; ;; ``Quote'' "\nFrom " as "\n>From "
+;; ;;(while (search-forward "\nFrom " nil t)
+;; ;; (forward-char -5)
+;; ;; (insert ?>))
+;; ;; Convert article to babyl format.
+;; (rmail-convert-to-babyl-format)
+;; ))
+
+
+;;;
+;;; Internal functions.
+;;;
+
+(defun gnus-start-news-server (&optional confirm)
+ "Open network stream to remote NNTP server.
+If optional argument CONFIRM is non-nil, ask you host that NNTP server
+is running even if it is defined.
+Run gnus-Open-server-hook just before opening news server."
+ (if (gnus-server-opened)
+ ;; Stream is already opened.
+ nil
+ ;; Open NNTP server.
+ (if (or confirm
+ (null gnus-nntp-server))
+ (if (and (boundp 'gnus-secondary-servers) gnus-secondary-servers)
+ ;; Read server name with completion.
+ (setq gnus-nntp-server
+ (completing-read "NNTP server: "
+ (cons (list gnus-nntp-server)
+ gnus-secondary-servers)
+ nil nil gnus-nntp-server))
+ (setq gnus-nntp-server
+ (read-string "NNTP server: " gnus-nntp-server))))
+ ;; If no server name is given, local host is assumed.
+ (if (string-equal gnus-nntp-server "")
+ (setq gnus-nntp-server (system-name)))
+ (cond ((string-match ":" gnus-nntp-server)
+ ;; :DIRECTORY
+ (require 'mhspool)
+ (gnus-define-access-method 'mhspool)
+ (message "Looking up private directory..."))
+ ((and (null gnus-nntp-service)
+ (string-equal gnus-nntp-server (system-name)))
+ (require 'nnspool)
+ (gnus-define-access-method 'nnspool)
+ (message "Looking up local news spool..."))
+ (t
+ (gnus-define-access-method 'nntp)
+ (message "Connecting to NNTP server on %s..." gnus-nntp-server)))
+ (run-hooks 'gnus-Open-server-hook)
+ (cond ((gnus-open-server gnus-nntp-server gnus-nntp-service))
+ ((and (stringp (gnus-status-message))
+ (> (length (gnus-status-message)) 0))
+ ;; Show valuable message if available.
+ (error (gnus-status-message)))
+ (t (error "Cannot open NNTP server on %s" gnus-nntp-server)))
+ ))
+
+;; Dummy functions used only once. Should return nil.
+(defun gnus-server-opened () nil)
+(defun gnus-close-server () nil)
+
+(defun gnus-define-access-method (method &optional access-methods)
+ "Define access functions for the access METHOD.
+Methods defintion is taken from optional argument ACCESS-METHODS or
+the variable gnus-access-methods."
+ (let ((bindings
+ (cdr (assoc method (or access-methods gnus-access-methods)))))
+ (if (null bindings)
+ (error "Unknown access method: %s" method)
+ ;; Should not use symbol-function here since overload does not work.
+ (while bindings
+ (fset (car (car bindings)) (cdr (car bindings)))
+ (setq bindings (cdr bindings)))
+ )))
+
+(defun gnus-select-newsgroup (group &optional show-all)
+ "Select newsgroup GROUP.
+If optional argument SHOW-ALL is non-nil, all of articles in the group
+are selected."
+ (if (gnus-request-group group)
+ (let ((articles nil))
+ (setq gnus-newsgroup-name group)
+ (setq gnus-newsgroup-unreads
+ (gnus-uncompress-sequence
+ (nthcdr 2 (gnus-gethash group gnus-unread-hashtb))))
+ (cond (show-all
+ ;; Select all active articles.
+ (setq articles
+ (gnus-uncompress-sequence
+ (nthcdr 2 (gnus-gethash group gnus-active-hashtb)))))
+ (t
+ ;; Select unread articles only.
+ (setq articles gnus-newsgroup-unreads)))
+ ;; Require confirmation if selecting large newsgroup.
+ (setq gnus-newsgroup-unselected nil)
+ (if (not (numberp gnus-large-newsgroup))
+ nil
+ (let ((selected nil)
+ (number (length articles)))
+ (if (> number gnus-large-newsgroup)
+ (progn
+ (condition-case ()
+ (let ((input
+ (read-string
+ (format
+ "How many articles from %s (default %d): "
+ gnus-newsgroup-name number))))
+ (setq selected
+ (if (string-equal input "")
+ number (string-to-int input))))
+ (quit
+ (setq selected 0)))
+ (cond ((and (> selected 0)
+ (< selected number))
+ ;; Select last N articles.
+ (setq articles (nthcdr (- number selected) articles)))
+ ((and (< selected 0)
+ (< (- 0 selected) number))
+ ;; Select first N articles.
+ (setq selected (- 0 selected))
+ (setq articles (copy-sequence articles))
+ (setcdr (nthcdr (1- selected) articles) nil))
+ ((zerop selected)
+ (setq articles nil))
+ ;; Otherwise select all.
+ )
+ ;; Get unselected unread articles.
+ (setq gnus-newsgroup-unselected
+ (gnus-set-difference gnus-newsgroup-unreads articles))
+ ))
+ ))
+ ;; Get headers list.
+ (setq gnus-newsgroup-headers (gnus-retrieve-headers articles))
+ ;; UNREADS may contain expired articles, so we have to remove
+ ;; them from the list.
+ (setq gnus-newsgroup-unreads
+ (gnus-intersection gnus-newsgroup-unreads
+ (mapcar
+ (function
+ (lambda (header)
+ (nntp-header-number header)))
+ gnus-newsgroup-headers)))
+ ;; Marked article must be a subset of unread articles.
+ (setq gnus-newsgroup-marked
+ (gnus-intersection (append gnus-newsgroup-unselected
+ gnus-newsgroup-unreads)
+ (cdr (assoc group gnus-marked-assoc))))
+ ;; First and last article in this newsgroup.
+ (setq gnus-newsgroup-begin
+ (if gnus-newsgroup-headers
+ (nntp-header-number (car gnus-newsgroup-headers))
+ 0
+ ))
+ (setq gnus-newsgroup-end
+ (if gnus-newsgroup-headers
+ (nntp-header-number
+ (gnus-last-element gnus-newsgroup-headers))
+ 0
+ ))
+ ;; File name that an article was saved last.
+ (setq gnus-newsgroup-last-rmail nil)
+ (setq gnus-newsgroup-last-mail nil)
+ (setq gnus-newsgroup-last-folder nil)
+ (setq gnus-newsgroup-last-file nil)
+ ;; Reset article pointer etc.
+ (setq gnus-current-article nil)
+ (setq gnus-current-headers nil)
+ (setq gnus-current-history nil)
+ (setq gnus-have-all-headers nil)
+ (setq gnus-last-article nil)
+ ;; GROUP is successfully selected.
+ t
+ )
+ ))
+
+(defun gnus-more-header-backward ()
+ "Find new header backward."
+ (let ((first
+ (car (nth 2 (gnus-gethash gnus-newsgroup-name gnus-active-hashtb))))
+ (artnum gnus-newsgroup-begin)
+ (header nil))
+ (while (and (not header)
+ (> artnum first))
+ (setq artnum (1- artnum))
+ (setq header (car (gnus-retrieve-headers (list artnum)))))
+ header
+ ))
+
+(defun gnus-more-header-forward ()
+ "Find new header forward."
+ (let ((last
+ (cdr (nth 2 (gnus-gethash gnus-newsgroup-name gnus-active-hashtb))))
+ (artnum gnus-newsgroup-end)
+ (header nil))
+ (while (and (not header)
+ (< artnum last))
+ (setq artnum (1+ artnum))
+ (setq header (car (gnus-retrieve-headers (list artnum)))))
+ header
+ ))
+
+(defun gnus-extend-newsgroup (header &optional backward)
+ "Extend newsgroup selection with HEADER.
+Optional argument BACKWARD means extend toward backward."
+ (if header
+ (let ((artnum (nntp-header-number header)))
+ (setq gnus-newsgroup-headers
+ (if backward
+ (cons header gnus-newsgroup-headers)
+ (append gnus-newsgroup-headers (list header))))
+ ;; We have to update unreads and unselected, but don't have to
+ ;; care about gnus-newsgroup-marked.
+ (if (memq artnum gnus-newsgroup-unselected)
+ (setq gnus-newsgroup-unreads
+ (cons artnum gnus-newsgroup-unreads)))
+ (setq gnus-newsgroup-unselected
+ (delq artnum gnus-newsgroup-unselected))
+ (setq gnus-newsgroup-begin (min gnus-newsgroup-begin artnum))
+ (setq gnus-newsgroup-end (max gnus-newsgroup-end artnum))
+ )))
+
+(defun gnus-mark-article-as-read (article)
+ "Remember that ARTICLE is marked as read."
+ ;; Remove from unread and marked list.
+ (setq gnus-newsgroup-unreads
+ (delq article gnus-newsgroup-unreads))
+ (setq gnus-newsgroup-marked
+ (delq article gnus-newsgroup-marked)))
+
+(defun gnus-mark-article-as-unread (article &optional clear-mark)
+ "Remember that ARTICLE is marked as unread.
+Optional argument CLEAR-MARK means ARTICLE should not be remembered
+that it was marked as read once."
+ ;; Add to unread list.
+ (or (memq article gnus-newsgroup-unreads)
+ (setq gnus-newsgroup-unreads
+ (cons article gnus-newsgroup-unreads)))
+ ;; If CLEAR-MARK is non-nil, the article must be removed from marked
+ ;; list. Otherwise, it must be added to the list.
+ (if clear-mark
+ (setq gnus-newsgroup-marked
+ (delq article gnus-newsgroup-marked))
+ (or (memq article gnus-newsgroup-marked)
+ (setq gnus-newsgroup-marked
+ (cons article gnus-newsgroup-marked)))))
+
+(defun gnus-clear-system ()
+ "Clear all variables and buffer."
+ ;; Clear GNUS variables.
+ (let ((variables gnus-variable-list))
+ (while variables
+ (set (car variables) nil)
+ (setq variables (cdr variables))))
+ ;; Clear other internal variables.
+ (setq gnus-active-hashtb nil)
+ (setq gnus-unread-hashtb nil)
+ ;; Kill the startup file.
+ (and gnus-current-startup-file
+ (get-file-buffer gnus-current-startup-file)
+ (kill-buffer (get-file-buffer gnus-current-startup-file)))
+ (setq gnus-current-startup-file nil)
+ ;; Kill GNUS buffers.
+ (let ((buffers gnus-buffer-list))
+ (while buffers
+ (if (get-buffer (car buffers))
+ (kill-buffer (car buffers)))
+ (setq buffers (cdr buffers))
+ )))
+
+(defun gnus-configure-windows (action)
+ "Configure GNUS windows according to the next ACTION.
+The ACTION is either a symbol, such as `SelectNewsgroup', or a
+configuration list such as `(1 1 2)'. If ACTION is not a list,
+configuration list is got from the variable gnus-window-configuration."
+ (let* ((windows
+ (if (listp action)
+ action (car (cdr (assq action gnus-window-configuration)))))
+ (grpwin (get-buffer-window gnus-Group-buffer))
+ (subwin (get-buffer-window gnus-Subject-buffer))
+ (artwin (get-buffer-window gnus-Article-buffer))
+ (winsum nil)
+ (height nil)
+ (grpheight 0)
+ (subheight 0)
+ (artheight 0))
+ (if (or (null windows) ;No configuration is specified.
+ (and (eq (null grpwin)
+ (zerop (nth 0 windows)))
+ (eq (null subwin)
+ (zerop (nth 1 windows)))
+ (eq (null artwin)
+ (zerop (nth 2 windows)))))
+ ;; No need to change window configuration.
+ nil
+ (select-window (or grpwin subwin artwin (selected-window)))
+ ;; First of all, compute the height of each window.
+ (cond (gnus-use-full-window
+ ;; Take up the entire screen.
+ (delete-other-windows)
+ (setq height (window-height (selected-window))))
+ (t
+ (setq height (+ (if grpwin (window-height grpwin) 0)
+ (if subwin (window-height subwin) 0)
+ (if artwin (window-height artwin) 0)))))
+ ;; The Newsgroup buffer exits always. So, use it to extend the
+ ;; Group window so as to get enough window space.
+ (switch-to-buffer gnus-Group-buffer 'norecord)
+ (and (get-buffer gnus-Subject-buffer)
+ (delete-windows-on gnus-Subject-buffer))
+ (and (get-buffer gnus-Article-buffer)
+ (delete-windows-on gnus-Article-buffer))
+ ;; Compute expected window height.
+ (setq winsum (apply (function +) windows))
+ (if (not (zerop (nth 0 windows)))
+ (setq grpheight (max window-min-height
+ (/ (* height (nth 0 windows)) winsum))))
+ (if (not (zerop (nth 1 windows)))
+ (setq subheight (max window-min-height
+ (/ (* height (nth 1 windows)) winsum))))
+ (if (not (zerop (nth 2 windows)))
+ (setq artheight (max window-min-height
+ (/ (* height (nth 2 windows)) winsum))))
+ (setq height (+ grpheight subheight artheight))
+ (enlarge-window (max 0 (- height (window-height (selected-window)))))
+ ;; Then split the window.
+ (and (not (zerop artheight))
+ (or (not (zerop grpheight))
+ (not (zerop subheight)))
+ (split-window-vertically (+ grpheight subheight)))
+ (and (not (zerop grpheight))
+ (not (zerop subheight))
+ (split-window-vertically grpheight))
+ ;; Then select buffers in each window.
+ (and (not (zerop grpheight))
+ (progn
+ (switch-to-buffer gnus-Group-buffer 'norecord)
+ (other-window 1)))
+ (and (not (zerop subheight))
+ (progn
+ (switch-to-buffer gnus-Subject-buffer 'norecord)
+ (other-window 1)))
+ (and (not (zerop artheight))
+ (progn
+ ;; If Article buffer does not exist, it will be created
+ ;; and initialized.
+ (gnus-Article-setup-buffer)
+ (switch-to-buffer gnus-Article-buffer 'norecord)))
+ )
+ ))
+
+(defun gnus-find-header-by-number (headers number)
+ "Return a header which is a element of HEADERS and has NUMBER."
+ (let ((found nil))
+ (while (and headers (not found))
+ ;; We cannot use `=' to accept non-numeric NUMBER.
+ (if (eq number (nntp-header-number (car headers)))
+ (setq found (car headers)))
+ (setq headers (cdr headers)))
+ found
+ ))
+
+(defun gnus-find-header-by-id (headers id)
+ "Return a header which is a element of HEADERS and has Message-ID."
+ (let ((found nil))
+ (while (and headers (not found))
+ (if (string-equal id (nntp-header-id (car headers)))
+ (setq found (car headers)))
+ (setq headers (cdr headers)))
+ found
+ ))
+
+(defun gnus-version ()
+ "Version numbers of this version of GNUS."
+ (interactive)
+ (cond ((and (boundp 'mhspool-version) (boundp 'nnspool-version))
+ (message "%s; %s; %s; %s"
+ gnus-version nntp-version nnspool-version mhspool-version))
+ ((boundp 'mhspool-version)
+ (message "%s; %s; %s"
+ gnus-version nntp-version mhspool-version))
+ ((boundp 'nnspool-version)
+ (message "%s; %s; %s"
+ gnus-version nntp-version nnspool-version))
+ (t
+ (message "%s; %s" gnus-version nntp-version))))
+
+(defun gnus-Info-find-node ()
+ "Find Info documentation of GNUS."
+ (interactive)
+ (require 'info)
+ ;; Enlarge info window if needed.
+ (cond ((eq major-mode 'gnus-Group-mode)
+ (gnus-configure-windows '(1 0 0)) ;Take all windows.
+ (pop-to-buffer gnus-Group-buffer))
+ ((eq major-mode 'gnus-Subject-mode)
+ (gnus-configure-windows '(0 1 0)) ;Take all windows.
+ (pop-to-buffer gnus-Subject-buffer)))
+ (Info-goto-node (cdr (assq major-mode gnus-Info-nodes))))
+
+(defun gnus-overload-functions (&optional overloads)
+ "Overload functions specified by optional argument OVERLOADS.
+If nothing is specified, use the variable gnus-overload-functions."
+ (let ((defs nil)
+ (overloads (or overloads gnus-overload-functions)))
+ (while overloads
+ (setq defs (car overloads))
+ (setq overloads (cdr overloads))
+ ;; Load file before overloading function if necessary. Make
+ ;; sure we cannot use `requre' always.
+ (and (not (fboundp (car defs)))
+ (car (cdr (cdr defs)))
+ (load (car (cdr (cdr defs))) nil 'nomessage))
+ (fset (car defs) (car (cdr defs)))
+ )))
+
+(defun gnus-make-threads (newsgroup-headers)
+ "Make conversation threads tree from NEWSGROUP-HEADERS."
+ (let ((headers newsgroup-headers)
+ (h nil)
+ (d nil)
+ (roots nil)
+ (dependencies nil))
+ ;; Make message dependency alist.
+ (while headers
+ (setq h (car headers))
+ (setq headers (cdr headers))
+ ;; Ignore invalid headers.
+ (if (vectorp h) ;Depends on nntp.el.
+ (progn
+ ;; Ignore broken references, e.g "<123@a.b.c".
+ (setq d (and (nntp-header-references h)
+ (string-match "\\(<[^<>]+>\\)[^>]*$"
+ (nntp-header-references h))
+ (gnus-find-header-by-id
+ newsgroup-headers
+ (substring (nntp-header-references h)
+ (match-beginning 1) (match-end 1)))))
+ ;; Check subject equality.
+ (or gnus-thread-ignore-subject
+ (null d)
+ (string-equal (gnus-simplify-subject
+ (nntp-header-subject h) 're)
+ (gnus-simplify-subject
+ (nntp-header-subject d) 're))
+ ;; H should be a thread root.
+ (setq d nil))
+ ;; H depends on D.
+ (setq dependencies
+ (cons (cons h d) dependencies))
+ ;; H is a thread root.
+ (if (null d)
+ (setq roots (cons h roots)))
+ ))
+ )
+ ;; Make complete threads from the roots.
+ ;; Note: dependencies are in reverse order, but
+ ;; gnus-make-threads-1 processes it in reverse order again. So,
+ ;; we don't have to worry about it.
+ (mapcar
+ (function
+ (lambda (root)
+ (gnus-make-threads-1 root dependencies))) (nreverse roots))
+ ))
+
+(defun gnus-make-threads-1 (parent dependencies)
+ (let ((children nil)
+ (d nil)
+ (depends dependencies))
+ ;; Find children.
+ (while depends
+ (setq d (car depends))
+ (setq depends (cdr depends))
+ (and (cdr d)
+ (eq (nntp-header-id parent) (nntp-header-id (cdr d)))
+ (setq children (cons (car d) children))))
+ ;; Go down.
+ (cons parent
+ (mapcar
+ (function
+ (lambda (child)
+ (gnus-make-threads-1 child dependencies))) children))
+ ))
+
+(defun gnus-narrow-to-page (&optional arg)
+ "Make text outside current page invisible except for page delimiter.
+A numeric arg specifies to move forward or backward by that many pages,
+thus showing a page other than the one point was originally in."
+ (interactive "P")
+ (setq arg (if arg (prefix-numeric-value arg) 0))
+ (save-excursion
+ (forward-page -1) ;Beginning of current page.
+ (widen)
+ (if (> arg 0)
+ (forward-page arg)
+ (if (< arg 0)
+ (forward-page (1- arg))))
+ ;; Find the end of the page.
+ (forward-page)
+ ;; If we stopped due to end of buffer, stay there.
+ ;; If we stopped after a page delimiter, put end of restriction
+ ;; at the beginning of that line.
+ ;; These are commented out.
+ ;; (if (save-excursion (beginning-of-line)
+ ;; (looking-at page-delimiter))
+ ;; (beginning-of-line))
+ (narrow-to-region (point)
+ (progn
+ ;; Find the top of the page.
+ (forward-page -1)
+ ;; If we found beginning of buffer, stay there.
+ ;; If extra text follows page delimiter on same line,
+ ;; include it.
+ ;; Otherwise, show text starting with following line.
+ (if (and (eolp) (not (bobp)))
+ (forward-line 1))
+ (point)))
+ ))
+
+(defun gnus-last-element (list)
+ "Return last element of LIST."
+ (let ((last nil))
+ (while list
+ (if (null (cdr list))
+ (setq last (car list)))
+ (setq list (cdr list)))
+ last
+ ))
+
+(defun gnus-set-difference (list1 list2)
+ "Return a list of elements of LIST1 that do not appear in LIST2."
+ (let ((list1 (copy-sequence list1)))
+ (while list2
+ (setq list1 (delq (car list2) list1))
+ (setq list2 (cdr list2)))
+ list1
+ ))
+
+(defun gnus-intersection (list1 list2)
+ "Return a list of elements that appear in both LIST1 and LIST2."
+ (let ((result nil))
+ (while list2
+ (if (memq (car list2) list1)
+ (setq result (cons (car list2) result)))
+ (setq list2 (cdr list2)))
+ result
+ ))
+
+
+;;;
+;;; Get information about active articles, already read articles, and
+;;; still unread articles.
+;;;
+
+;; GNUS internal format of gnus-newsrc-assoc and gnus-killed-assoc:
+;; (("general" t (1 . 1))
+;; ("misc" t (1 . 10) (12 . 15))
+;; ("test" nil (1 . 99)) ...)
+;; GNUS internal format of gnus-marked-assoc:
+;; (("general" 1 2 3)
+;; ("misc" 2) ...)
+;; GNUS internal format of gnus-active-hashtb:
+;; (("general" t (1 . 1))
+;; ("misc" t (1 . 10))
+;; ("test" nil (1 . 99)) ...)
+;; GNUS internal format of gnus-unread-hashtb:
+;; (("general" 1 (1 . 1))
+;; ("misc" 14 (1 . 10) (12 . 15))
+;; ("test" 99 (1 . 99)) ...)
+
+(defun gnus-setup-news-info (&optional rawfile)
+ "Setup news information.
+If optional argument RAWFILE is non-nil, force to read raw startup file."
+ (let ((init (not (and gnus-newsrc-assoc
+ gnus-active-hashtb
+ gnus-unread-hashtb
+ (not rawfile)
+ ))))
+ ;; We have to clear some variables to re-initialize news info.
+ (if init
+ (setq gnus-newsrc-assoc nil
+ gnus-active-hashtb nil
+ gnus-unread-hashtb nil))
+ (if init
+ (gnus-read-newsrc-file rawfile))
+ (gnus-read-active-file)
+ (gnus-expire-marked-articles)
+ (gnus-get-unread-articles)
+ ;; Check new newsgroups and subscribe them.
+ (if init
+ (let ((new-newsgroups (gnus-find-new-newsgroups)))
+ (while new-newsgroups
+ (funcall gnus-subscribe-newsgroup-method (car new-newsgroups))
+ (setq new-newsgroups (cdr new-newsgroups))
+ )))
+ ))
+
+(defun gnus-subscribe-newsgroup (newsgroup &optional next)
+ "Subscribe new NEWSGROUP.
+If optional argument NEXT is non-nil, it is inserted before NEXT."
+ (gnus-insert-newsgroup (list newsgroup t) next)
+ (message "Newsgroup %s is subscribed" newsgroup))
+
+(defun gnus-add-newsgroup (newsgroup)
+ "Subscribe new NEWSGROUP safely and put it at top."
+ (and (null (assoc newsgroup gnus-newsrc-assoc)) ;Really new?
+ (gnus-gethash newsgroup gnus-active-hashtb) ;Really exist?
+ (gnus-insert-newsgroup (or (assoc newsgroup gnus-killed-assoc)
+ (list newsgroup t))
+ (car (car gnus-newsrc-assoc)))))
+
+(defun gnus-find-new-newsgroups ()
+ "Looking for new newsgroups and return names.
+`-n' option of options line in .newsrc file is recognized."
+ (let ((group nil)
+ (new-newsgroups nil))
+ (mapatoms
+ (function
+ (lambda (sym)
+ (setq group (symbol-name sym))
+ ;; Taking account of `-n' option.
+ (and (or (null gnus-newsrc-options-n-no)
+ (not (string-match gnus-newsrc-options-n-no group))
+ (and gnus-newsrc-options-n-yes
+ (string-match gnus-newsrc-options-n-yes group)))
+ (null (assoc group gnus-killed-assoc)) ;Ignore killed.
+ (null (assoc group gnus-newsrc-assoc)) ;Really new.
+ ;; Find new newsgroup.
+ (setq new-newsgroups
+ (cons group new-newsgroups)))
+ ))
+ gnus-active-hashtb)
+ ;; Return new newsgroups.
+ new-newsgroups
+ ))
+
+(defun gnus-kill-newsgroup (group)
+ "Kill GROUP from gnus-newsrc-assoc, .newsrc and gnus-unread-hashtb."
+ (let ((info (assoc group gnus-newsrc-assoc)))
+ (if (null info)
+ nil
+ ;; Delete from gnus-newsrc-assoc
+ (setq gnus-newsrc-assoc (delq info gnus-newsrc-assoc))
+ ;; Add to gnus-killed-assoc.
+ (setq gnus-killed-assoc
+ (cons info
+ (delq (assoc group gnus-killed-assoc) gnus-killed-assoc)))
+ ;; Clear unread hashtable.
+ ;; Thanks cwitty@csli.Stanford.EDU (Carl Witty).
+ (gnus-sethash group nil gnus-unread-hashtb)
+ ;; Then delete from .newsrc
+ (gnus-update-newsrc-buffer group 'delete)
+ ;; Return the deleted newsrc entry.
+ info
+ )))
+
+(defun gnus-insert-newsgroup (info &optional next)
+ "Insert newsrc INFO entry before NEXT.
+If optional argument NEXT is nil, appended to the last."
+ (if (null info)
+ (error "Invalid argument: %s" info))
+ (let* ((group (car info)) ;Newsgroup name.
+ (range
+ (gnus-difference-of-range
+ (nth 2 (gnus-gethash group gnus-active-hashtb)) (nthcdr 2 info))))
+ ;; Check duplication.
+ (if (assoc group gnus-newsrc-assoc)
+ (error "Duplicated: %s" group))
+ ;; Insert to gnus-newsrc-assoc.
+ (if (string-equal next (car (car gnus-newsrc-assoc)))
+ (setq gnus-newsrc-assoc
+ (cons info gnus-newsrc-assoc))
+ (let ((found nil)
+ (rest gnus-newsrc-assoc)
+ (tail (cons nil gnus-newsrc-assoc)))
+ ;; Seach insertion point.
+ (while (and (not found) rest)
+ (if (string-equal next (car (car rest)))
+ (setq found t)
+ (setq rest (cdr rest))
+ (setq tail (cdr tail))
+ ))
+ ;; Find it.
+ (setcdr tail nil)
+ (setq gnus-newsrc-assoc
+ (append gnus-newsrc-assoc (cons info rest)))
+ ))
+ ;; Delete from gnus-killed-assoc.
+ (setq gnus-killed-assoc
+ (delq (assoc group gnus-killed-assoc) gnus-killed-assoc))
+ ;; Then insert to .newsrc.
+ (gnus-update-newsrc-buffer group nil next)
+ ;; Add to gnus-unread-hashtb.
+ (gnus-sethash group
+ (cons group ;Newsgroup name.
+ (cons (gnus-number-of-articles range) range))
+ gnus-unread-hashtb)
+ ))
+
+(defun gnus-check-killed-newsgroups ()
+ "Check consistency between gnus-newsrc-assoc and gnus-killed-assoc."
+ (let ((group nil)
+ (new-killed nil)
+ (old-killed gnus-killed-assoc))
+ (while old-killed
+ (setq group (car (car old-killed)))
+ (and (or (null gnus-newsrc-options-n-no)
+ (not (string-match gnus-newsrc-options-n-no group))
+ (and gnus-newsrc-options-n-yes
+ (string-match gnus-newsrc-options-n-yes group)))
+ (null (assoc group gnus-newsrc-assoc)) ;No duplication.
+ ;; Subscribed in options line and not in gnus-newsrc-assoc.
+ (setq new-killed
+ (cons (car old-killed) new-killed)))
+ (setq old-killed (cdr old-killed))
+ )
+ (setq gnus-killed-assoc (nreverse new-killed))
+ ))
+
+(defun gnus-check-bogus-newsgroups (&optional confirm)
+ "Delete bogus newsgroups.
+If optional argument CONFIRM is non-nil, confirm deletion of newsgroups."
+ (let ((group nil) ;Newsgroup name temporary used.
+ (old-newsrc gnus-newsrc-assoc)
+ (new-newsrc nil)
+ (bogus nil) ;List of bogus newsgroups.
+ (old-killed gnus-killed-assoc)
+ (new-killed nil)
+ (old-marked gnus-marked-assoc)
+ (new-marked nil))
+ (message "Checking bogus newsgroups...")
+ ;; Update gnus-newsrc-assoc.
+ (while old-newsrc
+ (setq group (car (car old-newsrc)))
+ (if (or (gnus-gethash group gnus-active-hashtb)
+ (and confirm
+ (not (y-or-n-p
+ (format "Delete bogus newsgroup: %s " group)))))
+ ;; Active newsgroup.
+ (setq new-newsrc (cons (car old-newsrc) new-newsrc))
+ ;; Found a bogus newsgroup.
+ (setq bogus (cons group bogus)))
+ (setq old-newsrc (cdr old-newsrc))
+ )
+ (setq gnus-newsrc-assoc (nreverse new-newsrc))
+ ;; Update gnus-killed-assoc.
+ ;; The killed newsgroups are deleted without any confirmations.
+ (while old-killed
+ (setq group (car (car old-killed)))
+ (and (gnus-gethash group gnus-active-hashtb)
+ (null (assoc group gnus-newsrc-assoc))
+ ;; Active and really killed newsgroup.
+ (setq new-killed (cons (car old-killed) new-killed)))
+ (setq old-killed (cdr old-killed))
+ )
+ (setq gnus-killed-assoc (nreverse new-killed))
+ ;; Remove BOGUS from .newsrc file.
+ (while bogus
+ (gnus-update-newsrc-buffer (car bogus) 'delete)
+ (setq bogus (cdr bogus)))
+ ;; Update gnus-marked-assoc.
+ (while old-marked
+ (setq group (car (car old-marked)))
+ (if (and (cdr (car old-marked)) ;Non-empty?
+ (assoc group gnus-newsrc-assoc)) ;Not bogus?
+ (setq new-marked (cons (car old-marked) new-marked)))
+ (setq old-marked (cdr old-marked)))
+ (setq gnus-marked-assoc new-marked)
+ (message "Checking bogus newsgroups... done")
+ ))
+
+(defun gnus-get-unread-articles ()
+ "Compute diffs between active and read articles."
+ (let ((read gnus-newsrc-assoc)
+ (group-info nil)
+ (group-name nil)
+ (active nil)
+ (range nil))
+ (message "Checking new news...")
+ (or gnus-unread-hashtb
+ (setq gnus-unread-hashtb (gnus-make-hashtable)))
+ (while read
+ (setq group-info (car read)) ;About one newsgroup
+ (setq group-name (car group-info))
+ (setq active (nth 2 (gnus-gethash group-name gnus-active-hashtb)))
+ (if (and gnus-octive-hashtb
+ ;; Is nothing changed?
+ (equal active
+ (nth 2 (gnus-gethash group-name gnus-octive-hashtb)))
+ ;; Is this newsgroup in the unread hash table?
+ (gnus-gethash group-name gnus-unread-hashtb)
+ )
+ nil ;Nothing to do.
+ (setq range (gnus-difference-of-range active (nthcdr 2 group-info)))
+ (gnus-sethash group-name
+ (cons group-name ;Group name
+ (cons (gnus-number-of-articles range)
+ range)) ;Range of unread articles
+ gnus-unread-hashtb)
+ )
+ (setq read (cdr read))
+ )
+ (message "Checking new news... done")
+ ))
+
+(defun gnus-expire-marked-articles ()
+ "Check expired article which is marked as unread."
+ (let ((marked-assoc gnus-marked-assoc)
+ (updated-assoc nil)
+ (marked nil) ;Current marked info.
+ (articles nil) ;List of marked articles.
+ (updated nil) ;List of real marked.
+ (begin nil))
+ (while marked-assoc
+ (setq marked (car marked-assoc))
+ (setq articles (cdr marked))
+ (setq updated nil)
+ (setq begin
+ (car (nth 2 (gnus-gethash (car marked) gnus-active-hashtb))))
+ (while (and begin articles)
+ (if (>= (car articles) begin)
+ ;; This article is still active.
+ (setq updated (cons (car articles) updated)))
+ (setq articles (cdr articles)))
+ (if updated
+ (setq updated-assoc
+ (cons (cons (car marked) updated) updated-assoc)))
+ (setq marked-assoc (cdr marked-assoc)))
+ (setq gnus-marked-assoc updated-assoc)
+ ))
+
+(defun gnus-mark-as-read-by-xref
+ (group headers unreads &optional subscribed-only)
+ "Mark articles as read using cross references and return updated newsgroups.
+Arguments are GROUP, HEADERS, UNREADS, and optional SUBSCRIBED-ONLY."
+ (let ((xref-list nil)
+ (header nil)
+ (xrefs nil) ;One Xref: field info.
+ (xref nil) ;(NEWSGROUP . ARTICLE)
+ (gname nil) ;Newsgroup name
+ (article nil)) ;Article number
+ (while headers
+ (setq header (car headers))
+ (if (memq (nntp-header-number header) unreads)
+ ;; This article is not yet marked as read.
+ nil
+ (setq xrefs (gnus-parse-xref-field (nntp-header-xref header)))
+ ;; For each cross reference info. in one Xref: field.
+ (while xrefs
+ (setq xref (car xrefs))
+ (setq gname (car xref)) ;Newsgroup name
+ (setq article (cdr xref)) ;Article number
+ (or (string-equal group gname) ;Ignore current newsgroup.
+ ;; Ignore unsubscribed newsgroup if requested.
+ (and subscribed-only
+ (not (nth 1 (assoc gname gnus-newsrc-assoc))))
+ ;; Ignore article marked as unread.
+ (memq article (cdr (assoc gname gnus-marked-assoc)))
+ (let ((group-xref (assoc gname xref-list)))
+ (if group-xref
+ (if (memq article (cdr group-xref))
+ nil ;Alread marked.
+ (setcdr group-xref (cons article (cdr group-xref))))
+ ;; Create new assoc entry for GROUP.
+ (setq xref-list (cons (list gname article) xref-list)))
+ ))
+ (setq xrefs (cdr xrefs))
+ ))
+ (setq headers (cdr headers)))
+ ;; Mark cross referenced articles as read.
+ (gnus-mark-xrefed-as-read xref-list)
+ ;;(message "%s %s" (prin1-to-string unreads) (prin1-to-string xref-list))
+ ;; Return list of updated group name.
+ (mapcar (function car) xref-list)
+ ))
+
+(defun gnus-parse-xref-field (xref-value)
+ "Parse Xref: field value, and return list of `(group . article-id)'."
+ (let ((xref-list nil)
+ (xref-value (or xref-value "")))
+ ;; Remove server host name.
+ (if (string-match "^[ \t]*[^ \t,]+[ \t,]+\\(.*\\)$" xref-value)
+ (setq xref-value (substring xref-value (match-beginning 1)))
+ (setq xref-value nil))
+ ;; Process each xref info.
+ (while xref-value
+ (if (string-match
+ "^[ \t,]*\\([^ \t,]+\\):\\([0-9]+\\)[^0-9]*" xref-value)
+ (progn
+ (setq xref-list
+ (cons
+ (cons
+ ;; Group name
+ (substring xref-value (match-beginning 1) (match-end 1))
+ ;; Article-ID
+ (string-to-int
+ (substring xref-value (match-beginning 2) (match-end 2))))
+ xref-list))
+ (setq xref-value (substring xref-value (match-end 2))))
+ (setq xref-value nil)))
+ ;; Return alist.
+ xref-list
+ ))
+
+(defun gnus-mark-xrefed-as-read (xrefs)
+ "Update unread article information using XREFS alist."
+ (let ((group nil)
+ (idlist nil)
+ (unread nil))
+ (while xrefs
+ (setq group (car (car xrefs)))
+ (setq idlist (cdr (car xrefs)))
+ (setq unread (gnus-uncompress-sequence
+ (nthcdr 2 (gnus-gethash group gnus-unread-hashtb))))
+ (while idlist
+ (setq unread (delq (car idlist) unread))
+ (setq idlist (cdr idlist)))
+ (gnus-update-unread-articles group unread 'ignore)
+ (setq xrefs (cdr xrefs))
+ )))
+
+(defun gnus-update-unread-articles (group unread-list marked-list)
+ "Update unread articles of GROUP using UNREAD-LIST and MARKED-LIST."
+ (let ((active (nth 2 (gnus-gethash group gnus-active-hashtb)))
+ (unread (gnus-gethash group gnus-unread-hashtb)))
+ (if (or (null active) (null unread))
+ ;; Ignore unknown newsgroup.
+ nil
+ ;; Update gnus-unread-hashtb.
+ (if unread-list
+ (setcdr (cdr unread)
+ (gnus-compress-sequence unread-list))
+ ;; All of the articles are read.
+ (setcdr (cdr unread) '((0 . 0))))
+ ;; Number of unread articles.
+ (setcar (cdr unread)
+ (gnus-number-of-articles (nthcdr 2 unread)))
+ ;; Update gnus-newsrc-assoc.
+ (if (> (car active) 0)
+ ;; Articles from 1 to N are not active.
+ (setq active (cons 1 (cdr active))))
+ (setcdr (cdr (assoc group gnus-newsrc-assoc))
+ (gnus-difference-of-range active (nthcdr 2 unread)))
+ ;; Update .newsrc buffer.
+ (gnus-update-newsrc-buffer group)
+ ;; Update gnus-marked-assoc.
+ (if (listp marked-list) ;Includes NIL.
+ (let ((marked (assoc group gnus-marked-assoc)))
+ (cond (marked
+ (setcdr marked marked-list))
+ (marked-list ;Non-NIL.
+ (setq gnus-marked-assoc
+ (cons (cons group marked-list)
+ gnus-marked-assoc)))
+ )))
+ )))
+
+(defun gnus-read-active-file ()
+ "Get active file from NNTP server."
+ (message "Reading active file...")
+ (if (gnus-request-list) ;Get active file from server
+ (save-excursion
+ (set-buffer nntp-server-buffer)
+ ;; Save OLD active info.
+ (setq gnus-octive-hashtb gnus-active-hashtb)
+ (setq gnus-active-hashtb (gnus-make-hashtable))
+ (gnus-active-to-gnus-format)
+ (message "Reading active file... done"))
+ (error "Cannot read active file from NNTP server.")))
+
+(defun gnus-active-to-gnus-format ()
+ "Convert active file format to internal format."
+ ;; Delete unnecessary lines.
+ (goto-char (point-min))
+ (delete-matching-lines "^to\\..*$")
+ ;; Store active file in hashtable.
+ (goto-char (point-min))
+ (while
+ (re-search-forward
+ "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([ymn]\\).*$"
+ nil t)
+ (gnus-sethash
+ (buffer-substring (match-beginning 1) (match-end 1))
+ (list (buffer-substring (match-beginning 1) (match-end 1))
+ (string-equal
+ "y" (buffer-substring (match-beginning 4) (match-end 4)))
+ (cons (string-to-int
+ (buffer-substring (match-beginning 3) (match-end 3)))
+ (string-to-int
+ (buffer-substring (match-beginning 2) (match-end 2)))))
+ gnus-active-hashtb)))
+
+(defun gnus-read-newsrc-file (&optional rawfile)
+ "Read startup FILE.
+If optional argument RAWFILE is non-nil, the raw startup file is read."
+ (setq gnus-current-startup-file
+ (let* ((file (expand-file-name gnus-startup-file nil))
+ (real-file (concat file "-" gnus-nntp-server)))
+ (if (file-exists-p real-file)
+ real-file file)))
+ ;; Reset variables which may be included in the quick startup file.
+ (let ((variables gnus-variable-list))
+ (while variables
+ (set (car variables) nil)
+ (setq variables (cdr variables))))
+ (let* ((newsrc-file gnus-current-startup-file)
+ (quick-file (concat newsrc-file ".el"))
+ (quick-loaded nil)
+ (newsrc-mod (nth 5 (file-attributes newsrc-file)))
+ (quick-mod (nth 5 (file-attributes quick-file))))
+ (save-excursion
+ ;; Prepare .newsrc buffer.
+ (set-buffer (find-file-noselect newsrc-file))
+ ;; It is not so good idea turning off undo.
+ ;;(buffer-flush-undo (current-buffer))
+ ;; Load quick .newsrc to restore gnus-marked-assoc and
+ ;; gnus-killed-assoc even if gnus-newsrc-assoc is out of date.
+ (condition-case nil
+ (setq quick-loaded (load quick-file t t t))
+ (error nil))
+ (cond ((and (not rawfile) ;Not forced to read the raw file.
+ (or (and (fboundp 'file-newer-than-file-p)
+ (file-newer-than-file-p quick-file newsrc-file))
+ (and newsrc-mod quick-mod
+ ;; .newsrc.el is newer than .newsrc.
+ ;; Some older version does not support function
+ ;; `file-newer-than-file-p'.
+ (or (< (car newsrc-mod) (car quick-mod))
+ (and (= (car newsrc-mod) (car quick-mod))
+ (<= (nth 1 newsrc-mod) (nth 1 quick-mod))))
+ ))
+ quick-loaded
+ gnus-newsrc-assoc ;Really loaded?
+ )
+ ;; We don't have to read the raw startup file.
+ )
+ (t
+ ;; Since .newsrc file is newer than quick file, read it.
+ (message "Reading %s..." newsrc-file)
+ (gnus-newsrc-to-gnus-format)
+ (gnus-check-killed-newsgroups)
+ (message "Reading %s... Done" newsrc-file)))
+ )))
+
+(defun gnus-make-newsrc-file (file)
+ "Make server dependent file name by catenating FILE and server host name."
+ (let* ((file (expand-file-name file nil))
+ (real-file (concat file "-" gnus-nntp-server)))
+ (if (file-exists-p real-file)
+ real-file file)
+ ))
+
+(defun gnus-newsrc-to-gnus-format ()
+ "Parse current buffer as .newsrc file."
+ (let ((newsgroup nil)
+ (subscribe nil)
+ (ranges nil)
+ (subrange nil)
+ (read-list nil))
+ ;; We have to re-initialize these variable (except for
+ ;; gnus-marked-assoc and gnus-killed-assoc) because quick startup
+ ;; file may contain bogus values.
+ (setq gnus-newsrc-options nil)
+ (setq gnus-newsrc-options-n-yes nil)
+ (setq gnus-newsrc-options-n-no nil)
+ (setq gnus-newsrc-assoc nil)
+ ;; Save options line to variable.
+ ;; Lines beginning with white spaces are treated as continuation
+ ;; line. Refer man page of newsrc(5).
+ (goto-char (point-min))
+ (if (re-search-forward
+ "^[ \t]*options[ \t]*\\(.*\\(\n[ \t]+.*\\)*\\)[ \t]*$" nil t)
+ (progn
+ ;; Save entire options line.
+ (setq gnus-newsrc-options
+ (buffer-substring (match-beginning 1) (match-end 1)))
+ ;; Compile "-n" option.
+ (if (string-match "\\(^\\|[ \t\n]\\)-n" gnus-newsrc-options)
+ (let ((yes-and-no
+ (gnus-parse-n-options
+ (substring gnus-newsrc-options (match-end 0)))))
+ (setq gnus-newsrc-options-n-yes (car yes-and-no))
+ (setq gnus-newsrc-options-n-no (cdr yes-and-no))
+ ))
+ ))
+ ;; Parse body of .newsrc file
+ ;; Options line continuation lines must be also considered here.
+ ;; Before supporting continuation lines, " newsgroup ! 1-5" was
+ ;; okay, but now it is invalid. It should be "newsgroup! 1-5".
+ (goto-char (point-min))
+ ;; Due to overflows in regex.c, change the following regexp:
+ ;; "^\\([^:! \t\n]+\\)\\([:!]\\)[ \t]*\\(.*\\)$"
+ ;; Suggested by composer@bucsf.bu.edu (Jeff Kellem).
+ (while (re-search-forward
+ "^\\([^:! \t\n]+\\)\\([:!]\\)[ \t]*\\(\\(...\\)*.*\\)$" nil t)
+ (setq newsgroup (buffer-substring (match-beginning 1) (match-end 1)))
+ ;; Check duplications of newsgroups.
+ ;; Note: Checking the duplications takes very long time.
+ (if (assoc newsgroup gnus-newsrc-assoc)
+ (message "Ignore duplicated newsgroup: %s" newsgroup)
+ (setq subscribe
+ (string-equal
+ ":" (buffer-substring (match-beginning 2) (match-end 2))))
+ (setq ranges (buffer-substring (match-beginning 3) (match-end 3)))
+ (setq read-list nil)
+ (while (string-match "^[, \t]*\\([0-9-]+\\)" ranges)
+ (setq subrange (substring ranges (match-beginning 1) (match-end 1)))
+ (setq ranges (substring ranges (match-end 1)))
+ (cond ((string-match "^\\([0-9]+\\)-\\([0-9]+\\)$" subrange)
+ (setq read-list
+ (cons
+ (cons (string-to-int
+ (substring subrange
+ (match-beginning 1) (match-end 1)))
+ (string-to-int
+ (substring subrange
+ (match-beginning 2) (match-end 2))))
+ read-list)))
+ ((string-match "^[0-9]+$" subrange)
+ (setq read-list
+ (cons (cons (string-to-int subrange)
+ (string-to-int subrange))
+ read-list)))
+ (t
+ (ding) (message "Ignoring bogus lines of %s" newsgroup)
+ (sit-for 0))
+ ))
+ (setq gnus-newsrc-assoc
+ (cons (cons newsgroup (cons subscribe (nreverse read-list)))
+ gnus-newsrc-assoc))
+ ))
+ (setq gnus-newsrc-assoc
+ (nreverse gnus-newsrc-assoc))
+ ))
+
+(defun gnus-parse-n-options (options)
+ "Parse -n NEWSGROUPS options and return a cons of YES and NO regexps."
+ (let ((yes nil)
+ (no nil)
+ (yes-or-no nil) ;`!' or not.
+ (newsgroup nil))
+ ;; Parse each newsgroup description such as "comp.all". Commas
+ ;; and white spaces can be a newsgroup separator.
+ (while
+ (string-match "^[ \t\n,]*\\(!?\\)\\([^--- \t\n,][^ \t\n,]*\\)" options)
+ (setq yes-or-no
+ (substring options (match-beginning 1) (match-end 1)))
+ (setq newsgroup
+ (regexp-quote
+ (substring options
+ (match-beginning 2) (match-end 2))))
+ (setq options (substring options (match-end 2)))
+ ;; Rewrite "all" to ".+" not ".*". ".+" requires at least one
+ ;; character.
+ (while (string-match "\\(^\\|\\\\[.]\\)all\\(\\\\[.]\\|$\\)" newsgroup)
+ (setq newsgroup
+ (concat (substring newsgroup 0 (match-end 1))
+ ".+"
+ (substring newsgroup (match-beginning 2)))))
+ (cond ((string-equal yes-or-no "!")
+ (setq no (cons newsgroup no)))
+ ((string-equal newsgroup ".+")) ;Ignore `all'.
+ (t
+ (setq yes (cons newsgroup yes)))
+ ))
+ ;; Make a cons of regexps from parsing result.
+ (cons (if yes
+ (concat "^\\("
+ (apply (function concat)
+ (mapcar
+ (function
+ (lambda (newsgroup)
+ (concat newsgroup "\\|")))
+ (cdr yes)))
+ (car yes) "\\)"))
+ (if no
+ (concat "^\\("
+ (apply (function concat)
+ (mapcar
+ (function
+ (lambda (newsgroup)
+ (concat newsgroup "\\|")))
+ (cdr no)))
+ (car no) "\\)")))
+ ))
+
+(defun gnus-save-newsrc-file ()
+ "Save to .newsrc FILE."
+ ;; Note: We cannot save .newsrc file if all newsgroups are removed
+ ;; from the variable gnus-newsrc-assoc.
+ (and (or gnus-newsrc-assoc gnus-killed-assoc)
+ gnus-current-startup-file
+ (save-excursion
+ ;; A buffer containing .newsrc file may be deleted.
+ (set-buffer (find-file-noselect gnus-current-startup-file))
+ (if (not (buffer-modified-p))
+ (message "(No changes need to be saved)")
+ (message "Saving %s..." gnus-current-startup-file)
+ (let ((make-backup-files t)
+ (version-control nil)
+ (require-final-newline t)) ;Don't ask even if requested.
+ ;; Make backup file of master newsrc.
+ ;; You can stop or change version control of backup file.
+ ;; Suggested by jason@violet.berkeley.edu.
+ (run-hooks 'gnus-Save-newsrc-hook)
+ (save-buffer))
+ ;; Quickly loadable .newsrc.
+ (set-buffer (get-buffer-create " *GNUS-newsrc*"))
+ (buffer-flush-undo (current-buffer))
+ (erase-buffer)
+ (gnus-gnus-to-quick-newsrc-format)
+ (let ((make-backup-files nil)
+ (version-control nil)
+ (require-final-newline t)) ;Don't ask even if requested.
+ (write-file (concat gnus-current-startup-file ".el")))
+ (kill-buffer (current-buffer))
+ (message "Saving %s... Done" gnus-current-startup-file)
+ ))
+ ))
+
+(defun gnus-update-newsrc-buffer (group &optional delete next)
+ "Incrementally update .newsrc buffer about GROUP.
+If optional second argument DELETE is non-nil, delete the group.
+If optional third argument NEXT is non-nil, inserted before it."
+ (save-excursion
+ ;; Taking account of the killed startup file.
+ ;; Suggested by tale@pawl.rpi.edu.
+ (set-buffer (or (get-file-buffer gnus-current-startup-file)
+ (find-file-noselect gnus-current-startup-file)))
+ ;; Options line continuation lines must be also considered here.
+ ;; Before supporting continuation lines, " newsgroup ! 1-5" was
+ ;; okay, but now it is invalid. It should be "newsgroup! 1-5".
+ (let ((deleted nil)
+ (buffer-read-only nil)) ;May be not modifiable.
+ ;; Delete ALL entries which match for GROUP.
+ (goto-char (point-min))
+ (while (re-search-forward
+ (concat "^" (regexp-quote group) "[:!]") nil t)
+ (beginning-of-line)
+ (delete-region (point) (progn (forward-line 1) (point)))
+ (setq deleted t) ;Old entry is deleted.
+ )
+ (if delete
+ nil
+ ;; Insert group entry.
+ (let ((newsrc (assoc group gnus-newsrc-assoc)))
+ (if (null newsrc)
+ nil
+ ;; Find insertion point.
+ (cond (deleted nil) ;Insert here.
+ ((and (stringp next)
+ (progn
+ (goto-char (point-min))
+ (re-search-forward
+ (concat "^" (regexp-quote next) "[:!]") nil t)))
+ (beginning-of-line))
+ (t
+ (goto-char (point-max))
+ (or (bolp)
+ (insert "\n"))))
+ ;; Insert after options line.
+ (if (looking-at "^[ \t]*options\\([ \t]\\|$\\)")
+ (progn
+ (forward-line 1)
+ ;; Skip continuation lines.
+ (while (and (not (eobp))
+ (looking-at "^[ \t]+"))
+ (forward-line 1))))
+ (insert group ;Group name
+ (if (nth 1 newsrc) ": " "! ")) ;Subscribed?
+ (gnus-ranges-to-newsrc-format (nthcdr 2 newsrc)) ;Read articles
+ (insert "\n")
+ )))
+ )))
+
+(defun gnus-gnus-to-quick-newsrc-format ()
+ "Insert GNUS variables such as `gnus-newsrc-assoc' in Lisp format."
+ (insert ";; GNUS internal format of .newsrc.\n")
+ (insert ";; Touch .newsrc instead if you think to remove this file.\n")
+ (let ((variable nil)
+ (variables gnus-variable-list)
+ ;; Temporary rebind to make changes invisible.
+ (gnus-killed-assoc gnus-killed-assoc))
+ ;; Remove duplicated or unsubscribed newsgroups in gnus-killed-assoc.
+ (gnus-check-killed-newsgroups)
+ ;; Then, insert lisp expressions.
+ (while variables
+ (setq variable (car variables))
+ (and (boundp variable)
+ (symbol-value variable)
+ (insert "(setq " (symbol-name variable) " '"
+ (prin1-to-string (symbol-value variable))
+ ")\n"))
+ (setq variables (cdr variables)))
+ ))
+
+(defun gnus-ranges-to-newsrc-format (ranges)
+ "Insert ranges of read articles."
+ (let ((range nil)) ;Range is a pair of BEGIN and END.
+ (while ranges
+ (setq range (car ranges))
+ (setq ranges (cdr ranges))
+ (cond ((= (car range) (cdr range))
+ (if (= (car range) 0)
+ (setq ranges nil) ;No unread articles.
+ (insert (int-to-string (car range)))
+ (if ranges (insert ","))
+ ))
+ (t
+ (insert (int-to-string (car range))
+ "-"
+ (int-to-string (cdr range)))
+ (if ranges (insert ","))
+ ))
+ )))
+
+(defun gnus-compress-sequence (numbers)
+ "Convert list of sorted numbers to ranges."
+ (let* ((numbers (sort (copy-sequence numbers) (function <)))
+ (first (car numbers))
+ (last (car numbers))
+ (result nil))
+ (while numbers
+ (cond ((= last (car numbers)) nil) ;Omit duplicated number
+ ((= (1+ last) (car numbers)) ;Still in sequence
+ (setq last (car numbers)))
+ (t ;End of one sequence
+ (setq result (cons (cons first last) result))
+ (setq first (car numbers))
+ (setq last (car numbers)))
+ )
+ (setq numbers (cdr numbers))
+ )
+ (nreverse (cons (cons first last) result))
+ ))
+
+(defun gnus-uncompress-sequence (ranges)
+ "Expand compressed format of sequence."
+ (let ((first nil)
+ (last nil)
+ (result nil))
+ (while ranges
+ (setq first (car (car ranges)))
+ (setq last (cdr (car ranges)))
+ (while (< first last)
+ (setq result (cons first result))
+ (setq first (1+ first)))
+ (setq result (cons first result))
+ (setq ranges (cdr ranges))
+ )
+ (nreverse result)
+ ))
+
+(defun gnus-number-of-articles (range)
+ "Compute number of articles from RANGE `((beg1 . end1) (beg2 . end2) ...)'."
+ (let ((count 0))
+ (while range
+ (if (/= (cdr (car range)) 0)
+ ;; If end1 is 0, it must be skipped. Usually no articles in
+ ;; this group.
+ (setq count (+ count 1 (- (cdr (car range)) (car (car range))))))
+ (setq range (cdr range))
+ )
+ count ;Result
+ ))
+
+(defun gnus-difference-of-range (src obj)
+ "Compute (SRC - OBJ) on range.
+Range of SRC is expressed as `(beg . end)'.
+Range of OBJ is expressed as `((beg1 . end1) (beg2 . end2) ...)."
+ (let ((beg (car src))
+ (end (cdr src))
+ (range nil)) ;This is result.
+ ;; Src may be nil.
+ (while (and src obj)
+ (let ((beg1 (car (car obj)))
+ (end1 (cdr (car obj))))
+ (cond ((> beg end)
+ (setq obj nil)) ;Terminate loop
+ ((< beg beg1)
+ (setq range (cons (cons beg (min (1- beg1) end)) range))
+ (setq beg (1+ end1)))
+ ((>= beg beg1)
+ (setq beg (max beg (1+ end1))))
+ )
+ (setq obj (cdr obj)) ;Next OBJ
+ ))
+ ;; Src may be nil.
+ (if (and src (<= beg end))
+ (setq range (cons (cons beg end) range)))
+ ;; Result
+ (if range
+ (nreverse range)
+ (list (cons 0 0)))
+ ))
+
+
+;;Local variables:
+;;eval: (put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
+;;end: