92af017d0eae8dc2e48d41987cd1fda54646e3da
[gnus] / lisp / gnus-group.el
1 ;;; gnus-group.el --- group mode commands for Gnus
2 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: news
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;;; Code:
28
29 (eval-when-compile (require 'cl))
30
31 (require 'gnus)
32 (require 'gnus-start)
33 (require 'nnmail)
34 (require 'gnus-spec)
35 (require 'gnus-int)
36 (require 'gnus-range)
37 (require 'gnus-win)
38 (require 'gnus-undo)
39 (require 'time-date)
40
41 (defcustom gnus-group-archive-directory
42   "*ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list/"
43   "*The address of the (ding) archives."
44   :group 'gnus-group-foreign
45   :type 'directory)
46
47 (defcustom gnus-group-recent-archive-directory
48   "*ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list-recent/"
49   "*The address of the most recent (ding) articles."
50   :group 'gnus-group-foreign
51   :type 'directory)
52
53 (defcustom gnus-no-groups-message "No gnus is bad news"
54   "*Message displayed by Gnus when no groups are available."
55   :group 'gnus-start
56   :type 'string)
57
58 (defcustom gnus-keep-same-level nil
59   "*Non-nil means that the next newsgroup after the current will be on the same level.
60 When you type, for instance, `n' after reading the last article in the
61 current newsgroup, you will go to the next newsgroup.  If this variable
62 is nil, the next newsgroup will be the next from the group
63 buffer.
64 If this variable is non-nil, Gnus will either put you in the
65 next newsgroup with the same level, or, if no such newsgroup is
66 available, the next newsgroup with the lowest possible level higher
67 than the current level.
68 If this variable is `best', Gnus will make the next newsgroup the one
69 with the best level."
70   :group 'gnus-group-levels
71   :type '(choice (const nil)
72                  (const best)
73                  (sexp :tag "other" t)))
74
75 (defcustom gnus-group-goto-unread t
76   "*If non-nil, movement commands will go to the next unread and subscribed group."
77   :link '(custom-manual "(gnus)Group Maneuvering")
78   :group 'gnus-group-various
79   :type 'boolean)
80
81 (defcustom gnus-goto-next-group-when-activating t
82   "*If non-nil, the \\<gnus-group-mode-map>\\[gnus-group-get-new-news-this-group] command will advance point to the next group."
83   :link '(custom-manual "(gnus)Scanning New Messages")
84   :group 'gnus-group-various
85   :type 'boolean)
86
87 (defcustom gnus-permanently-visible-groups nil
88   "*Regexp to match groups that should always be listed in the group buffer.
89 This means that they will still be listed even when there are no
90 unread articles in the groups.
91
92 If nil, no groups are permanently visible."
93   :group 'gnus-group-listing
94   :type '(choice regexp (const nil)))
95
96 (defcustom gnus-list-groups-with-ticked-articles t
97   "*If non-nil, list groups that have only ticked articles.
98 If nil, only list groups that have unread articles."
99   :group 'gnus-group-listing
100   :type 'boolean)
101
102 (defcustom gnus-group-default-list-level gnus-level-subscribed
103   "*Default listing level.
104 Ignored if `gnus-group-use-permanent-levels' is non-nil."
105   :group 'gnus-group-listing
106   :type 'integer)
107
108 (defcustom gnus-group-list-inactive-groups t
109   "*If non-nil, inactive groups will be listed."
110   :group 'gnus-group-listing
111   :group 'gnus-group-levels
112   :type 'boolean)
113
114 (defcustom gnus-group-sort-function 'gnus-group-sort-by-alphabet
115   "*Function used for sorting the group buffer.
116 This function will be called with group info entries as the arguments
117 for the groups to be sorted.  Pre-made functions include
118 `gnus-group-sort-by-alphabet', `gnus-group-sort-by-real-name',
119 `gnus-group-sort-by-unread', `gnus-group-sort-by-level',
120 `gnus-group-sort-by-score', `gnus-group-sort-by-method', and
121 `gnus-group-sort-by-rank'.
122
123 This variable can also be a list of sorting functions.  In that case,
124 the most significant sort function should be the last function in the
125 list."
126   :group 'gnus-group-listing
127   :link '(custom-manual "(gnus)Sorting Groups")
128   :type '(radio (function-item gnus-group-sort-by-alphabet)
129                 (function-item gnus-group-sort-by-real-name)
130                 (function-item gnus-group-sort-by-unread)
131                 (function-item gnus-group-sort-by-level)
132                 (function-item gnus-group-sort-by-score)
133                 (function-item gnus-group-sort-by-method)
134                 (function-item gnus-group-sort-by-rank)
135                 (function :tag "other" nil)))
136
137 (defcustom gnus-group-line-format "%M\%S\%p\%P\%5y: %(%g%)%l\n"
138   "*Format of group lines.
139 It works along the same lines as a normal formatting string,
140 with some simple extensions.
141
142 %M    Only marked articles (character, \"*\" or \" \")
143 %S    Whether the group is subscribed (character, \"U\", \"K\", \"Z\" or \" \")
144 %L    Level of subscribedness (integer)
145 %N    Number of unread articles (integer)
146 %I    Number of dormant articles (integer)
147 %i    Number of ticked and dormant (integer)
148 %T    Number of ticked articles (integer)
149 %R    Number of read articles (integer)
150 %t    Estimated total number of articles (integer)
151 %y    Number of unread, unticked articles (integer)
152 %G    Group name (string)
153 %g    Qualified group name (string)
154 %D    Group description (string)
155 %s    Select method (string)
156 %o    Moderated group (char, \"m\")
157 %p    Process mark (char)
158 %O    Moderated group (string, \"(m)\" or \"\")
159 %P    Topic indentation (string)
160 %m    Whether there is new(ish) mail in the group (char, \"%\")
161 %l    Whether there are GroupLens predictions for this group (string)
162 %n    Select from where (string)
163 %z    A string that look like `<%s:%n>' if a foreign select method is used
164 %d    The date the group was last entered.
165 %E    Icon as defined by `gnus-group-icon-list'.
166 %u    User defined specifier.  The next character in the format string should
167       be a letter.  Gnus will call the function gnus-user-format-function-X,
168       where X is the letter following %u.  The function will be passed the
169       current header as argument.  The function should return a string, which
170       will be inserted into the buffer just like information from any other
171       group specifier.
172
173 Text between %( and %) will be highlighted with `gnus-mouse-face' when
174 the mouse point move inside the area.  There can only be one such area.
175
176 Note that this format specification is not always respected.  For
177 reasons of efficiency, when listing killed groups, this specification
178 is ignored altogether.  If the spec is changed considerably, your
179 output may end up looking strange when listing both alive and killed
180 groups.
181
182 If you use %o or %O, reading the active file will be slower and quite
183 a bit of extra memory will be used.  %D will also worsen performance.
184 Also note that if you change the format specification to include any
185 of these specs, you must probably re-start Gnus to see them go into
186 effect."
187   :group 'gnus-group-visual
188   :type 'string)
189
190 (defcustom gnus-group-mode-line-format "Gnus: %%b {%M\%:%S}"
191   "*The format specification for the group mode line.
192 It works along the same lines as a normal formatting string,
193 with some simple extensions:
194
195 %S   The native news server.
196 %M   The native select method.
197 %:   \":\" if %S isn't \"\"."
198   :group 'gnus-group-visual
199   :type 'string)
200
201 (defcustom gnus-group-mode-hook nil
202   "Hook for Gnus group mode."
203   :group 'gnus-group-various
204   :options '(gnus-topic-mode)
205   :type 'hook)
206
207 (defcustom gnus-group-menu-hook nil
208   "Hook run after the creation of the group mode menu."
209   :group 'gnus-group-various
210   :type 'hook)
211
212 (defcustom gnus-group-catchup-group-hook nil
213   "Hook run when catching up a group from the group buffer."
214   :group 'gnus-group-various
215   :link '(custom-manual "(gnus)Group Data")
216   :type 'hook)
217
218 (defcustom gnus-group-update-group-hook nil
219   "Hook called when updating group lines."
220   :group 'gnus-group-visual
221   :type 'hook)
222
223 (defcustom gnus-group-prepare-function 'gnus-group-prepare-flat
224   "*A function that is called to generate the group buffer.
225 The function is called with three arguments: The first is a number;
226 all group with a level less or equal to that number should be listed,
227 if the second is non-nil, empty groups should also be displayed.  If
228 the third is non-nil, it is a number.  No groups with a level lower
229 than this number should be displayed.
230
231 The only current function implemented is `gnus-group-prepare-flat'."
232   :group 'gnus-group-listing
233   :type 'function)
234
235 (defcustom gnus-group-prepare-hook nil
236   "Hook called after the group buffer has been generated.
237 If you want to modify the group buffer, you can use this hook."
238   :group 'gnus-group-listing
239   :type 'hook)
240
241 (defcustom gnus-suspend-gnus-hook nil
242   "Hook called when suspending (not exiting) Gnus."
243   :group 'gnus-exit
244   :type 'hook)
245
246 (defcustom gnus-exit-gnus-hook nil
247   "Hook called when exiting Gnus."
248   :group 'gnus-exit
249   :type 'hook)
250
251 (defcustom gnus-after-exiting-gnus-hook nil
252   "Hook called after exiting Gnus."
253   :group 'gnus-exit
254   :type 'hook)
255
256 (defcustom gnus-group-update-hook '(gnus-group-highlight-line)
257   "Hook called when a group line is changed.
258 The hook will not be called if `gnus-visual' is nil.
259
260 The default function `gnus-group-highlight-line' will
261 highlight the line according to the `gnus-group-highlight'
262 variable."
263   :group 'gnus-group-visual
264   :type 'hook)
265
266 (defcustom gnus-useful-groups
267   '(("(ding) mailing list mirrored at sunsite.auc.dk"
268      "emacs.ding"
269      (nntp "sunsite.auc.dk"
270            (nntp-address "sunsite.auc.dk")))
271     ("gnus-bug archive"
272      "gnus-bug"
273      (nndir "/ftp@ftp.ifi.uio.no:/pub/emacs/gnus/gnus-bug/"))
274     ("Gnus help group"
275      "gnus-help"
276      (nndoc "gnus-help"
277             (nndoc-article-type mbox)
278             (eval `(nndoc-address
279                     ,(let ((file (nnheader-find-etc-directory
280                                   "gnus-tut.txt" t)))
281                        (unless file
282                          (error "Couldn't find doc group"))
283                        file))))))
284   "*Alist of useful group-server pairs."
285   :group 'gnus-group-listing
286   :type '(repeat (list (string :tag "Description")
287                        (string :tag "Name")
288                        (sexp :tag "Method"))))
289
290 (defcustom gnus-group-highlight
291   '(;; News.
292     ((and (= unread 0) (not mailp) (eq level 1)) .
293      gnus-group-news-1-empty-face)
294     ((and (not mailp) (eq level 1)) .
295      gnus-group-news-1-face)
296     ((and (= unread 0) (not mailp) (eq level 2)) .
297      gnus-group-news-2-empty-face)
298     ((and (not mailp) (eq level 2)) .
299      gnus-group-news-2-face)
300     ((and (= unread 0) (not mailp) (eq level 3)) .
301      gnus-group-news-3-empty-face)
302     ((and (not mailp) (eq level 3)) .
303      gnus-group-news-3-face)
304     ((and (= unread 0) (not mailp) (eq level 4)) .
305      gnus-group-news-4-empty-face)
306     ((and (not mailp) (eq level 4)) .
307      gnus-group-news-4-face)
308     ((and (= unread 0) (not mailp) (eq level 5)) .
309      gnus-group-news-5-empty-face)
310     ((and (not mailp) (eq level 5)) .
311      gnus-group-news-5-face)
312     ((and (= unread 0) (not mailp) (eq level 6)) .
313      gnus-group-news-6-empty-face)
314     ((and (not mailp) (eq level 6)) .
315      gnus-group-news-6-face)
316     ((and (= unread 0) (not mailp)) .
317      gnus-group-news-low-empty-face)
318     ((and (not mailp)) .
319      gnus-group-news-low-face)
320     ;; Mail.
321     ((and (= unread 0) (eq level 1)) .
322      gnus-group-mail-1-empty-face)
323     ((eq level 1) .
324      gnus-group-mail-1-face)
325     ((and (= unread 0) (eq level 2)) .
326      gnus-group-mail-2-empty-face)
327     ((eq level 2) .
328      gnus-group-mail-2-face)
329     ((and (= unread 0) (eq level 3)) .
330      gnus-group-mail-3-empty-face)
331     ((eq level 3) .
332      gnus-group-mail-3-face)
333     ((= unread 0) .
334      gnus-group-mail-low-empty-face)
335     (t .
336        gnus-group-mail-low-face))
337   "*Controls the highlighting of group buffer lines.
338
339 Below is a list of `Form'/`Face' pairs.  When deciding how a a
340 particular group line should be displayed, each form is
341 evaluated.  The content of the face field after the first true form is
342 used.  You can change how those group lines are displayed by
343 editing the face field.
344
345 It is also possible to change and add form fields, but currently that
346 requires an understanding of Lisp expressions.  Hopefully this will
347 change in a future release.  For now, you can use the following
348 variables in the Lisp expression:
349
350 group: The name of the group.
351 unread: The number of unread articles in the group.
352 method: The select method used.
353 mailp: Whether it's a mail group or not.
354 level: The level of the group.
355 score: The score of the group.
356 ticked: The number of ticked articles."
357   :group 'gnus-group-visual
358   :type '(repeat (cons (sexp :tag "Form") face)))
359
360 (defcustom gnus-new-mail-mark ?%
361   "Mark used for groups with new mail."
362   :group 'gnus-group-visual
363   :type 'character)
364
365 (defgroup gnus-group-icons nil
366   "Add Icons to your group buffer.  "
367   :group 'gnus-group-visual)
368
369 (defcustom gnus-group-icon-list
370   nil
371   "*Controls the insertion of icons into group buffer lines.
372
373 Below is a list of `Form'/`File' pairs.  When deciding how a
374 particular group line should be displayed, each form is evaluated.
375 The icon from the file field after the first true form is used.  You
376 can change how those group lines are displayed by editing the file
377 field.  The File will either be found in the
378 `gnus-group-glyph-directory' or by designating absolute path to the
379 file.
380
381 It is also possible to change and add form fields, but currently that
382 requires an understanding of Lisp expressions.  Hopefully this will
383 change in a future release.  For now, you can use the following
384 variables in the Lisp expression:
385
386 group: The name of the group.
387 unread: The number of unread articles in the group.
388 method: The select method used.
389 mailp: Whether it's a mail group or not.
390 newsp: Whether it's a news group or not
391 level: The level of the group.
392 score: The score of the group.
393 ticked: The number of ticked articles."
394   :group 'gnus-group-icons
395   :type '(repeat (cons (sexp :tag "Form") file)))
396
397 (defcustom gnus-group-name-charset-method-alist nil
398   "*Alist of method and the charset for group names.
399
400 For example:
401     (((nntp \"news.com.cn\") . cn-gb-2312))
402 "
403   :group 'gnus-charset
404   :type '(repeat (cons (sexp :tag "Method") (symbol :tag "Charset"))))
405
406 (defcustom gnus-group-name-charset-group-alist nil
407   "*Alist of group regexp and the charset for group names.
408
409 For example:
410     ((\"\\.com\\.cn:\" . cn-gb-2312))
411 "
412   :group 'gnus-charset
413   :type '(repeat (cons (regexp :tag "Group") (symbol :tag "Charset"))))
414
415 ;;; Internal variables
416
417 (defvar gnus-group-sort-alist-function 'gnus-group-sort-flat
418   "Function for sorting the group buffer.")
419
420 (defvar gnus-group-sort-selected-function 'gnus-group-sort-selected-flat
421   "Function for sorting the selected groups in the group buffer.")
422
423 (defvar gnus-group-indentation-function nil)
424 (defvar gnus-goto-missing-group-function nil)
425 (defvar gnus-group-update-group-function nil)
426 (defvar gnus-group-goto-next-group-function nil
427   "Function to override finding the next group after listing groups.")
428
429 (defvar gnus-group-edit-buffer nil)
430
431 (defvar gnus-group-line-format-alist
432   `((?M gnus-tmp-marked-mark ?c)
433     (?S gnus-tmp-subscribed ?c)
434     (?L gnus-tmp-level ?d)
435     (?N (cond ((eq number t) "*" )
436               ((numberp number)
437                (int-to-string
438                 (+ number
439                    (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked)))
440                    (gnus-range-length (cdr (assq 'tick gnus-tmp-marked))))))
441               (t number)) ?s)
442     (?R gnus-tmp-number-of-read ?s)
443     (?t gnus-tmp-number-total ?d)
444     (?y gnus-tmp-number-of-unread ?s)
445     (?I (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked))) ?d)
446     (?T (gnus-range-length (cdr (assq 'tick gnus-tmp-marked))) ?d)
447     (?i (+ (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked)))
448            (gnus-range-length (cdr (assq 'tick gnus-tmp-marked)))) ?d)
449     (?g gnus-tmp-group ?s)
450     (?G gnus-tmp-qualified-group ?s)
451     (?c (gnus-short-group-name gnus-tmp-group) ?s)
452     (?D gnus-tmp-newsgroup-description ?s)
453     (?o gnus-tmp-moderated ?c)
454     (?O gnus-tmp-moderated-string ?s)
455     (?p gnus-tmp-process-marked ?c)
456     (?s gnus-tmp-news-server ?s)
457     (?n gnus-tmp-news-method ?s)
458     (?P gnus-group-indentation ?s)
459     (?E gnus-tmp-group-icon ?s)
460     (?l gnus-tmp-grouplens ?s)
461     (?z gnus-tmp-news-method-string ?s)
462     (?m (gnus-group-new-mail gnus-tmp-group) ?c)
463     (?d (gnus-group-timestamp-string gnus-tmp-group) ?s)
464     (?u gnus-tmp-user-defined ?s)))
465
466 (defvar gnus-group-mode-line-format-alist
467   `((?S gnus-tmp-news-server ?s)
468     (?M gnus-tmp-news-method ?s)
469     (?u gnus-tmp-user-defined ?s)
470     (?: gnus-tmp-colon ?s)))
471
472 (defvar gnus-topic-topology nil
473   "The complete topic hierarchy.")
474
475 (defvar gnus-topic-alist nil
476   "The complete topic-group alist.")
477
478 (defvar gnus-group-marked nil)
479
480 (defvar gnus-group-list-mode nil)
481
482
483 (defvar gnus-group-icon-cache nil)
484
485 (defvar gnus-group-listed-groups nil)
486 (defvar gnus-group-list-option nil)
487
488 ;;;
489 ;;; Gnus group mode
490 ;;;
491
492 (put 'gnus-group-mode 'mode-class 'special)
493
494 (when t
495   (gnus-define-keys gnus-group-mode-map
496     " " gnus-group-read-group
497     "=" gnus-group-select-group
498     "\r" gnus-group-select-group
499     "\M-\r" gnus-group-quick-select-group
500     "\M- " gnus-group-visible-select-group
501     [(meta control return)] gnus-group-select-group-ephemerally
502     "j" gnus-group-jump-to-group
503     "n" gnus-group-next-unread-group
504     "p" gnus-group-prev-unread-group
505     "\177" gnus-group-prev-unread-group
506     [delete] gnus-group-prev-unread-group
507     [backspace] gnus-group-prev-unread-group
508     "N" gnus-group-next-group
509     "P" gnus-group-prev-group
510     "\M-n" gnus-group-next-unread-group-same-level
511     "\M-p" gnus-group-prev-unread-group-same-level
512     "," gnus-group-best-unread-group
513     "." gnus-group-first-unread-group
514     "u" gnus-group-unsubscribe-current-group
515     "U" gnus-group-unsubscribe-group
516     "c" gnus-group-catchup-current
517     "C" gnus-group-catchup-current-all
518     "\M-c" gnus-group-clear-data
519     "l" gnus-group-list-groups
520     "L" gnus-group-list-all-groups
521     "m" gnus-group-mail
522     "g" gnus-group-get-new-news
523     "\M-g" gnus-group-get-new-news-this-group
524     "R" gnus-group-restart
525     "r" gnus-group-read-init-file
526     "B" gnus-group-browse-foreign-server
527     "b" gnus-group-check-bogus-groups
528     "F" gnus-group-find-new-groups
529     "\C-c\C-d" gnus-group-describe-group
530     "\M-d" gnus-group-describe-all-groups
531     "\C-c\C-a" gnus-group-apropos
532     "\C-c\M-\C-a" gnus-group-description-apropos
533     "a" gnus-group-post-news
534     "\ek" gnus-group-edit-local-kill
535     "\eK" gnus-group-edit-global-kill
536     "\C-k" gnus-group-kill-group
537     "\C-y" gnus-group-yank-group
538     "\C-w" gnus-group-kill-region
539     "\C-x\C-t" gnus-group-transpose-groups
540     "\C-c\C-l" gnus-group-list-killed
541     "\C-c\C-x" gnus-group-expire-articles
542     "\C-c\M-\C-x" gnus-group-expire-all-groups
543     "V" gnus-version
544     "s" gnus-group-save-newsrc
545     "z" gnus-group-suspend
546     "q" gnus-group-exit
547     "Q" gnus-group-quit
548     "?" gnus-group-describe-briefly
549     "\C-c\C-i" gnus-info-find-node
550     "\M-e" gnus-group-edit-group-method
551     "^" gnus-group-enter-server-mode
552     gnus-mouse-2 gnus-mouse-pick-group
553     "<" beginning-of-buffer
554     ">" end-of-buffer
555     "\C-c\C-b" gnus-bug
556     "\C-c\C-s" gnus-group-sort-groups
557     "t" gnus-topic-mode
558     "\C-c\M-g" gnus-activate-all-groups
559     "\M-&" gnus-group-universal-argument
560     "#" gnus-group-mark-group
561     "\M-#" gnus-group-unmark-group)
562
563   (gnus-define-keys (gnus-group-mark-map "M" gnus-group-mode-map)
564     "m" gnus-group-mark-group
565     "u" gnus-group-unmark-group
566     "w" gnus-group-mark-region
567     "b" gnus-group-mark-buffer
568     "r" gnus-group-mark-regexp
569     "U" gnus-group-unmark-all-groups)
570
571   (gnus-define-keys (gnus-group-group-map "G" gnus-group-mode-map)
572     "d" gnus-group-make-directory-group
573     "h" gnus-group-make-help-group
574     "u" gnus-group-make-useful-group
575     "a" gnus-group-make-archive-group
576     "k" gnus-group-make-kiboze-group
577     "l" gnus-group-nnimap-edit-acl
578     "m" gnus-group-make-group
579     "E" gnus-group-edit-group
580     "e" gnus-group-edit-group-method
581     "p" gnus-group-edit-group-parameters
582     "v" gnus-group-add-to-virtual
583     "V" gnus-group-make-empty-virtual
584     "D" gnus-group-enter-directory
585     "f" gnus-group-make-doc-group
586     "w" gnus-group-make-web-group
587     "r" gnus-group-rename-group
588     "c" gnus-group-customize
589     "x" gnus-group-nnimap-expunge
590     "\177" gnus-group-delete-group
591     [delete] gnus-group-delete-group)
592
593   (gnus-define-keys (gnus-group-soup-map "s" gnus-group-group-map)
594     "b" gnus-group-brew-soup
595     "w" gnus-soup-save-areas
596     "s" gnus-soup-send-replies
597     "p" gnus-soup-pack-packet
598     "r" nnsoup-pack-replies)
599
600   (gnus-define-keys (gnus-group-sort-map "S" gnus-group-group-map)
601     "s" gnus-group-sort-groups
602     "a" gnus-group-sort-groups-by-alphabet
603     "u" gnus-group-sort-groups-by-unread
604     "l" gnus-group-sort-groups-by-level
605     "v" gnus-group-sort-groups-by-score
606     "r" gnus-group-sort-groups-by-rank
607     "m" gnus-group-sort-groups-by-method)
608
609   (gnus-define-keys (gnus-group-sort-selected-map "P" gnus-group-group-map)
610     "s" gnus-group-sort-selected-groups
611     "a" gnus-group-sort-selected-groups-by-alphabet
612     "u" gnus-group-sort-selected-groups-by-unread
613     "l" gnus-group-sort-selected-groups-by-level
614     "v" gnus-group-sort-selected-groups-by-score
615     "r" gnus-group-sort-selected-groups-by-rank
616     "m" gnus-group-sort-selected-groups-by-method)
617
618   (gnus-define-keys (gnus-group-list-map "A" gnus-group-mode-map)
619     "k" gnus-group-list-killed
620     "z" gnus-group-list-zombies
621     "s" gnus-group-list-groups
622     "u" gnus-group-list-all-groups
623     "A" gnus-group-list-active
624     "a" gnus-group-apropos
625     "d" gnus-group-description-apropos
626     "m" gnus-group-list-matching
627     "M" gnus-group-list-all-matching
628     "l" gnus-group-list-level
629     "c" gnus-group-list-cached
630     "?" gnus-group-list-dormant)
631
632   (gnus-define-keys (gnus-group-list-limit-map "/" gnus-group-list-map)
633     "k"  gnus-group-list-limit
634     "z"  gnus-group-list-limit
635     "s"  gnus-group-list-limit
636     "u"  gnus-group-list-limit
637     "A"  gnus-group-list-limit
638     "m"  gnus-group-list-limit
639     "M"  gnus-group-list-limit
640     "l"  gnus-group-list-limit
641     "c"  gnus-group-list-limit
642     "?"  gnus-group-list-limit)
643
644   (gnus-define-keys (gnus-group-list-flush-map "f" gnus-group-list-map)
645     "k"  gnus-group-list-flush
646     "z"  gnus-group-list-flush
647     "s"  gnus-group-list-flush
648     "u"  gnus-group-list-flush
649     "A"  gnus-group-list-flush
650     "m"  gnus-group-list-flush
651     "M"  gnus-group-list-flush
652     "l"  gnus-group-list-flush
653     "c"  gnus-group-list-flush
654     "?"  gnus-group-list-flush)
655
656   (gnus-define-keys (gnus-group-list-plus-map "p" gnus-group-list-map)
657     "k"  gnus-group-list-plus
658     "z"  gnus-group-list-plus
659     "s"  gnus-group-list-plus
660     "u"  gnus-group-list-plus
661     "A"  gnus-group-list-plus
662     "m"  gnus-group-list-plus
663     "M"  gnus-group-list-plus
664     "l"  gnus-group-list-plus
665     "c"  gnus-group-list-plus
666     "?"  gnus-group-list-plus)
667
668   (gnus-define-keys (gnus-group-score-map "W" gnus-group-mode-map)
669     "f" gnus-score-flush-cache)
670
671   (gnus-define-keys (gnus-group-help-map "H" gnus-group-mode-map)
672     "d" gnus-group-describe-group
673     "f" gnus-group-fetch-faq
674     "v" gnus-version)
675
676   (gnus-define-keys (gnus-group-sub-map "S" gnus-group-mode-map)
677     "l" gnus-group-set-current-level
678     "t" gnus-group-unsubscribe-current-group
679     "s" gnus-group-unsubscribe-group
680     "k" gnus-group-kill-group
681     "y" gnus-group-yank-group
682     "w" gnus-group-kill-region
683     "\C-k" gnus-group-kill-level
684     "z" gnus-group-kill-all-zombies))
685
686 (defun gnus-group-make-menu-bar ()
687   (gnus-turn-off-edit-menu 'group)
688   (unless (boundp 'gnus-group-reading-menu)
689
690     (easy-menu-define
691      gnus-group-reading-menu gnus-group-mode-map ""
692      '("Group"
693        ["Read" gnus-group-read-group (gnus-group-group-name)]
694        ["Select" gnus-group-select-group (gnus-group-group-name)]
695        ["See old articles" (gnus-group-select-group 'all)
696         :keys "C-u SPC" :active (gnus-group-group-name)]
697        ["Catch up" gnus-group-catchup-current (gnus-group-group-name)]
698        ["Catch up all articles" gnus-group-catchup-current-all
699         (gnus-group-group-name)]
700        ["Check for new articles" gnus-group-get-new-news-this-group
701         (gnus-group-group-name)]
702        ["Toggle subscription" gnus-group-unsubscribe-current-group
703         (gnus-group-group-name)]
704        ["Kill" gnus-group-kill-group (gnus-group-group-name)]
705        ["Yank" gnus-group-yank-group gnus-list-of-killed-groups]
706        ["Describe" gnus-group-describe-group (gnus-group-group-name)]
707        ["Fetch FAQ" gnus-group-fetch-faq (gnus-group-group-name)]
708        ;; Actually one should check, if any of the marked groups gives t for
709        ;; (gnus-check-backend-function 'request-expire-articles ...)
710        ["Expire articles" gnus-group-expire-articles
711         (or (and (gnus-group-group-name)
712                  (gnus-check-backend-function
713                   'request-expire-articles
714                   (gnus-group-group-name))) gnus-group-marked)]
715        ["Set group level" gnus-group-set-current-level
716         (gnus-group-group-name)]
717        ["Select quick" gnus-group-quick-select-group (gnus-group-group-name)]
718        ["Customize" gnus-group-customize (gnus-group-group-name)]
719        ("Edit"
720         ["Parameters" gnus-group-edit-group-parameters
721          (gnus-group-group-name)]
722         ["Select method" gnus-group-edit-group-method
723          (gnus-group-group-name)]
724         ["Info" gnus-group-edit-group (gnus-group-group-name)]
725         ["Local kill file" gnus-group-edit-local-kill (gnus-group-group-name)]
726         ["Global kill file" gnus-group-edit-global-kill t])))
727
728     (easy-menu-define
729      gnus-group-group-menu gnus-group-mode-map ""
730      '("Groups"
731        ("Listing"
732         ["List unread subscribed groups" gnus-group-list-groups t]
733         ["List (un)subscribed groups" gnus-group-list-all-groups t]
734         ["List killed groups" gnus-group-list-killed gnus-killed-list]
735         ["List zombie groups" gnus-group-list-zombies gnus-zombie-list]
736         ["List level..." gnus-group-list-level t]
737         ["Describe all groups" gnus-group-describe-all-groups t]
738         ["Group apropos..." gnus-group-apropos t]
739         ["Group and description apropos..." gnus-group-description-apropos t]
740         ["List groups matching..." gnus-group-list-matching t]
741         ["List all groups matching..." gnus-group-list-all-matching t]
742         ["List active file" gnus-group-list-active t]
743         ["List groups with cached" gnus-group-list-cached t]
744         ["List groups with dormant" gnus-group-list-dormant t])
745        ("Sort"
746         ["Default sort" gnus-group-sort-groups t]
747         ["Sort by method" gnus-group-sort-groups-by-method t]
748         ["Sort by rank" gnus-group-sort-groups-by-rank t]
749         ["Sort by score" gnus-group-sort-groups-by-score t]
750         ["Sort by level" gnus-group-sort-groups-by-level t]
751         ["Sort by unread" gnus-group-sort-groups-by-unread t]
752         ["Sort by name" gnus-group-sort-groups-by-alphabet t])
753        ("Sort process/prefixed"
754         ["Default sort" gnus-group-sort-selected-groups
755          (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
756         ["Sort by method" gnus-group-sort-selected-groups-by-method
757          (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
758         ["Sort by rank" gnus-group-sort-selected-groups-by-rank
759          (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
760         ["Sort by score" gnus-group-sort-selected-groups-by-score
761          (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
762         ["Sort by level" gnus-group-sort-selected-groups-by-level
763          (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
764         ["Sort by unread" gnus-group-sort-selected-groups-by-unread
765          (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
766         ["Sort by name" gnus-group-sort-selected-groups-by-alphabet
767          (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))])
768        ("Mark"
769         ["Mark group" gnus-group-mark-group
770          (and (gnus-group-group-name)
771               (not (memq (gnus-group-group-name) gnus-group-marked)))]
772         ["Unmark group" gnus-group-unmark-group
773          (and (gnus-group-group-name)
774               (memq (gnus-group-group-name) gnus-group-marked))]
775         ["Unmark all" gnus-group-unmark-all-groups gnus-group-marked]
776         ["Mark regexp..." gnus-group-mark-regexp t]
777         ["Mark region" gnus-group-mark-region t]
778         ["Mark buffer" gnus-group-mark-buffer t]
779         ["Execute command" gnus-group-universal-argument
780          (or gnus-group-marked (gnus-group-group-name))])
781        ("Subscribe"
782         ["Subscribe to a group" gnus-group-unsubscribe-group t]
783         ["Kill all newsgroups in region" gnus-group-kill-region t]
784         ["Kill all zombie groups" gnus-group-kill-all-zombies
785          gnus-zombie-list]
786         ["Kill all groups on level..." gnus-group-kill-level t])
787        ("Foreign groups"
788         ["Make a foreign group" gnus-group-make-group t]
789         ["Add a directory group" gnus-group-make-directory-group t]
790         ["Add the help group" gnus-group-make-help-group t]
791         ["Add the archive group" gnus-group-make-archive-group t]
792         ["Make a doc group" gnus-group-make-doc-group t]
793         ["Make a web group" gnus-group-make-web-group t]
794         ["Make a kiboze group" gnus-group-make-kiboze-group t]
795         ["Make a virtual group" gnus-group-make-empty-virtual t]
796         ["Add a group to a virtual" gnus-group-add-to-virtual t]
797         ["Rename group" gnus-group-rename-group
798          (gnus-check-backend-function
799           'request-rename-group (gnus-group-group-name))]
800         ["Delete group" gnus-group-delete-group
801          (gnus-check-backend-function
802           'request-delete-group (gnus-group-group-name))])
803        ("Move"
804         ["Next" gnus-group-next-group t]
805         ["Previous" gnus-group-prev-group t]
806         ["Next unread" gnus-group-next-unread-group t]
807         ["Previous unread" gnus-group-prev-unread-group t]
808         ["Next unread same level" gnus-group-next-unread-group-same-level t]
809         ["Previous unread same level"
810          gnus-group-prev-unread-group-same-level t]
811         ["Jump to group" gnus-group-jump-to-group t]
812         ["First unread group" gnus-group-first-unread-group t]
813         ["Best unread group" gnus-group-best-unread-group t])
814        ["Delete bogus groups" gnus-group-check-bogus-groups t]
815        ["Find new newsgroups" gnus-group-find-new-groups t]
816        ["Transpose" gnus-group-transpose-groups
817         (gnus-group-group-name)]
818        ["Read a directory as a group..." gnus-group-enter-directory t]))
819
820     (easy-menu-define
821      gnus-group-misc-menu gnus-group-mode-map ""
822      '("Misc"
823        ("SOUP"
824         ["Pack replies" nnsoup-pack-replies (fboundp 'nnsoup-request-group)]
825         ["Send replies" gnus-soup-send-replies
826          (fboundp 'gnus-soup-pack-packet)]
827         ["Pack packet" gnus-soup-pack-packet (fboundp 'gnus-soup-pack-packet)]
828         ["Save areas" gnus-soup-save-areas (fboundp 'gnus-soup-pack-packet)]
829         ["Brew SOUP" gnus-group-brew-soup (fboundp 'gnus-soup-pack-packet)])
830        ["Send a mail" gnus-group-mail t]
831        ["Post an article..." gnus-group-post-news t]
832        ["Check for new news" gnus-group-get-new-news t]
833        ["Activate all groups" gnus-activate-all-groups t]
834        ["Restart Gnus" gnus-group-restart t]
835        ["Read init file" gnus-group-read-init-file t]
836        ["Browse foreign server" gnus-group-browse-foreign-server t]
837        ["Enter server buffer" gnus-group-enter-server-mode t]
838        ["Expire all expirable articles" gnus-group-expire-all-groups t]
839        ["Generate any kiboze groups" nnkiboze-generate-groups t]
840        ["Gnus version" gnus-version t]
841        ["Save .newsrc files" gnus-group-save-newsrc t]
842        ["Suspend Gnus" gnus-group-suspend t]
843        ["Clear dribble buffer" gnus-group-clear-dribble t]
844        ["Read manual" gnus-info-find-node t]
845        ["Flush score cache" gnus-score-flush-cache t]
846        ["Toggle topics" gnus-topic-mode t]
847        ["Send a bug report" gnus-bug t]
848        ["Exit from Gnus" gnus-group-exit t]
849        ["Exit without saving" gnus-group-quit t]))
850
851     (gnus-run-hooks 'gnus-group-menu-hook)))
852
853 (defun gnus-group-mode ()
854   "Major mode for reading news.
855
856 All normal editing commands are switched off.
857 \\<gnus-group-mode-map>
858 The group buffer lists (some of) the groups available.  For instance,
859 `\\[gnus-group-list-groups]' will list all subscribed groups with unread articles, while `\\[gnus-group-list-zombies]'
860 lists all zombie groups.
861
862 Groups that are displayed can be entered with `\\[gnus-group-read-group]'.  To subscribe
863 to a group not displayed, type `\\[gnus-group-unsubscribe-group]'.
864
865 For more in-depth information on this mode, read the manual (`\\[gnus-info-find-node]').
866
867 The following commands are available:
868
869 \\{gnus-group-mode-map}"
870   (interactive)
871   (when (gnus-visual-p 'group-menu 'menu)
872     (gnus-group-make-menu-bar))
873   (kill-all-local-variables)
874   (gnus-simplify-mode-line)
875   (setq major-mode 'gnus-group-mode)
876   (setq mode-name "Group")
877   (gnus-group-set-mode-line)
878   (setq mode-line-process nil)
879   (use-local-map gnus-group-mode-map)
880   (buffer-disable-undo)
881   (setq truncate-lines t)
882   (setq buffer-read-only t)
883   (gnus-set-default-directory)
884   (gnus-update-format-specifications nil 'group 'group-mode)
885   (gnus-update-group-mark-positions)
886   (when gnus-use-undo
887     (gnus-undo-mode 1))
888   (when gnus-slave
889     (gnus-slave-mode))
890   (gnus-run-hooks 'gnus-group-mode-hook))
891
892 (defun gnus-update-group-mark-positions ()
893   (save-excursion
894     (let ((gnus-process-mark ?\200)
895           (gnus-group-marked '("dummy.group"))
896           (gnus-active-hashtb (make-vector 10 0))
897           (topic ""))
898       (gnus-set-active "dummy.group" '(0 . 0))
899       (gnus-set-work-buffer)
900       (gnus-group-insert-group-line "dummy.group" 0 nil 0 nil)
901       (goto-char (point-min))
902       (setq gnus-group-mark-positions
903             (list (cons 'process (and (search-forward "\200" nil t)
904                                       (- (point) 2))))))))
905
906 (defun gnus-mouse-pick-group (e)
907   "Enter the group under the mouse pointer."
908   (interactive "e")
909   (mouse-set-point e)
910   (gnus-group-read-group nil))
911
912 ;; Look at LEVEL and find out what the level is really supposed to be.
913 ;; If LEVEL is non-nil, LEVEL will be returned, if not, what happens
914 ;; will depend on whether `gnus-group-use-permanent-levels' is used.
915 (defun gnus-group-default-level (&optional level number-or-nil)
916   (cond
917    (gnus-group-use-permanent-levels
918     (or (setq gnus-group-use-permanent-levels
919               (or level (if (numberp gnus-group-use-permanent-levels)
920                             gnus-group-use-permanent-levels
921                           (or gnus-group-default-list-level
922                               gnus-level-subscribed))))
923         gnus-group-default-list-level gnus-level-subscribed))
924    (number-or-nil
925     level)
926    (t
927     (or level gnus-group-default-list-level gnus-level-subscribed))))
928
929 (defun gnus-group-setup-buffer ()
930   (set-buffer (gnus-get-buffer-create gnus-group-buffer))
931   (unless (eq major-mode 'gnus-group-mode)
932     (gnus-group-mode)
933     (when gnus-carpal
934       (gnus-carpal-setup-buffer 'group))))
935
936 (defsubst gnus-group-name-charset (method group)
937   (if (null method)
938       (setq method (gnus-find-method-for-group group)))
939   (let ((item (assoc method gnus-group-name-charset-method-alist))
940         (alist gnus-group-name-charset-group-alist)
941         result)
942     (if item 
943         (cdr item)
944       (while (setq item (pop alist))
945         (if (string-match (car item) group)
946             (setq alist nil
947                   result (cdr item))))
948       result)))
949
950 (defsubst gnus-group-name-decode (string charset)
951   (if (and string charset (featurep 'mule))
952       (mm-decode-coding-string string charset)
953     string))
954
955 (defun gnus-group-decoded-name (string)
956   (let ((charset (gnus-group-name-charset nil string)))
957     (gnus-group-name-decode string charset)))
958
959 (defun gnus-group-list-groups (&optional level unread lowest)
960   "List newsgroups with level LEVEL or lower that have unread articles.
961 Default is all subscribed groups.
962 If argument UNREAD is non-nil, groups with no unread articles are also
963 listed.
964
965 Also see the `gnus-group-use-permanent-levels' variable."
966   (interactive
967    (list (if current-prefix-arg
968              (prefix-numeric-value current-prefix-arg)
969            (or
970             (gnus-group-default-level nil t)
971             gnus-group-default-list-level
972             gnus-level-subscribed))))
973   (unless level
974     (setq level (car gnus-group-list-mode)
975           unread (cdr gnus-group-list-mode)))
976   (setq level (gnus-group-default-level level))
977   (gnus-group-setup-buffer)
978   (gnus-update-format-specifications nil 'group 'group-mode)
979   (let ((case-fold-search nil)
980         (props (text-properties-at (gnus-point-at-bol)))
981         (empty (= (point-min) (point-max)))
982         (group (gnus-group-group-name))
983         number)
984     (set-buffer gnus-group-buffer)
985     (setq number (funcall gnus-group-prepare-function level unread lowest))
986     (when (or (and (numberp number)
987                    (zerop number))
988               (zerop (buffer-size)))
989       ;; No groups in the buffer.
990       (gnus-message 5 gnus-no-groups-message))
991     ;; We have some groups displayed.
992     (goto-char (point-max))
993     (when (or (not gnus-group-goto-next-group-function)
994               (not (funcall gnus-group-goto-next-group-function
995                             group props)))
996       (cond
997        (empty
998         (goto-char (point-min)))
999        ((not group)
1000         ;; Go to the first group with unread articles.
1001         (gnus-group-search-forward t))
1002        (t
1003         ;; Find the right group to put point on.  If the current group
1004         ;; has disappeared in the new listing, try to find the next
1005         ;; one.  If no next one can be found, just leave point at the
1006         ;; first newsgroup in the buffer.
1007         (when (not (gnus-goto-char
1008                     (text-property-any
1009                      (point-min) (point-max)
1010                      'gnus-group (gnus-intern-safe
1011                                   group gnus-active-hashtb))))
1012           (let ((newsrc (cdddr (gnus-gethash group gnus-newsrc-hashtb))))
1013             (while (and newsrc
1014                         (not (gnus-goto-char
1015                               (text-property-any
1016                                (point-min) (point-max) 'gnus-group
1017                                (gnus-intern-safe
1018                                 (caar newsrc) gnus-active-hashtb)))))
1019               (setq newsrc (cdr newsrc)))
1020             (unless newsrc
1021               (goto-char (point-max))
1022               (forward-line -1)))))))
1023     ;; Adjust cursor point.
1024     (gnus-group-position-point)))
1025
1026 (defun gnus-group-list-level (level &optional all)
1027   "List groups on LEVEL.
1028 If ALL (the prefix), also list groups that have no unread articles."
1029   (interactive "nList groups on level: \nP")
1030   (gnus-group-list-groups level all level))
1031
1032 (defun gnus-group-prepare-logic (group test)
1033   (or (and gnus-group-listed-groups
1034            (null gnus-group-list-option)
1035            (member group gnus-group-listed-groups))
1036       (cond 
1037        ((null gnus-group-listed-groups) test)
1038        ((null gnus-group-list-option) test)
1039        (t (and (member group gnus-group-listed-groups)
1040                (if (eq gnus-group-list-option 'flush)
1041                    (not test)
1042                  test))))))
1043
1044 (defun gnus-group-prepare-flat (level &optional predicate lowest regexp)
1045   "List all newsgroups with unread articles of level LEVEL or lower.
1046 If PREDICATE is a function, list groups that the function returns non-nil;
1047 if it is t, list groups that have no unread articles.
1048 If LOWEST is non-nil, list all newsgroups of level LOWEST or higher.
1049 If REGEXP is a function, list dead groups that the function returns non-nil;
1050 if it is a string, only list groups matching REGEXP."
1051   (set-buffer gnus-group-buffer)
1052   (let ((buffer-read-only nil)
1053         (newsrc (cdr gnus-newsrc-alist))
1054         (lowest (or lowest 1))
1055         (not-in-list (and gnus-group-listed-groups
1056                        (not (eq gnus-group-list-option 'limit))
1057                        (copy-sequence gnus-group-listed-groups)))
1058         info clevel unread group params)
1059     (erase-buffer)
1060     (when (or (< lowest gnus-level-zombie)
1061               gnus-group-listed-groups)
1062       ;; List living groups.
1063       (while newsrc
1064         (setq info (car newsrc)
1065               group (gnus-info-group info)
1066               params (gnus-info-params info)
1067               newsrc (cdr newsrc)
1068               unread (car (gnus-gethash group gnus-newsrc-hashtb)))
1069         (if not-in-list 
1070             (setq not-in-list (delete group not-in-list)))
1071         (and 
1072          (gnus-group-prepare-logic 
1073           group
1074           (and unread           ; This group might be unchecked
1075                (or (not (stringp regexp))
1076                    (string-match regexp group))
1077                (<= (setq clevel (gnus-info-level info)) level)
1078                (>= clevel lowest)
1079                (cond
1080                 ((functionp predicate)
1081                  (funcall predicate info))
1082                 (predicate t)           ; We list all groups?
1083                 (t
1084                  (or
1085                   (if (eq unread t)     ; Unactivated?
1086                       gnus-group-list-inactive-groups 
1087                                         ; We list unactivated
1088                     (> unread 0))       
1089                                         ; We list groups with unread articles
1090                   (and gnus-list-groups-with-ticked-articles
1091                        (cdr (assq 'tick (gnus-info-marks info))))
1092                                         ; And groups with tickeds
1093                   ;; Check for permanent visibility.
1094                   (and gnus-permanently-visible-groups
1095                        (string-match gnus-permanently-visible-groups group))
1096                   (memq 'visible params)
1097                   (cdr (assq 'visible params)))))))
1098          (gnus-group-insert-group-line
1099           group (gnus-info-level info)
1100           (gnus-info-marks info) unread (gnus-info-method info)))))
1101       
1102     ;; List dead groups.
1103     (if (or gnus-group-listed-groups
1104             (and (>= level gnus-level-zombie) 
1105                  (<= lowest gnus-level-zombie)))
1106         (gnus-group-prepare-flat-list-dead
1107          (setq gnus-zombie-list (sort gnus-zombie-list 'string<))
1108          gnus-level-zombie ?Z
1109          regexp))
1110     (if not-in-list 
1111         (dolist (group gnus-zombie-list)
1112           (setq not-in-list (delete group not-in-list))))
1113     (if (or gnus-group-listed-groups
1114             (and (>= level gnus-level-killed) (<= lowest gnus-level-killed)))
1115         (gnus-group-prepare-flat-list-dead
1116          (or not-in-list
1117              (setq gnus-killed-list (sort gnus-killed-list 'string<)))
1118          gnus-level-killed ?K regexp))
1119
1120     (gnus-group-set-mode-line)
1121     (setq gnus-group-list-mode (cons level predicate))
1122     (gnus-run-hooks 'gnus-group-prepare-hook)
1123     t))
1124
1125 (defun gnus-group-prepare-flat-list-dead (groups level mark regexp)
1126   ;; List zombies and killed lists somewhat faster, which was
1127   ;; suggested by Jack Vinson <vinson@unagi.cis.upenn.edu>.  It does
1128   ;; this by ignoring the group format specification altogether.
1129   (let (group)
1130     (while groups
1131       (setq group (pop groups))
1132       (when (gnus-group-prepare-logic 
1133              group
1134              (or (not regexp)
1135                  (and (stringp regexp) (string-match regexp group))
1136                  (and (functionp regexp) (funcall regexp group))))
1137 ;;;     (gnus-add-text-properties
1138 ;;;      (point) (prog1 (1+ (point))
1139 ;;;                (insert " " mark "     *: "
1140 ;;;                        (gnus-group-name-decode group 
1141 ;;;                                                (gnus-group-name-charset
1142 ;;;                                                 nil group)) 
1143 ;;;                        "\n"))
1144 ;;;      (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb)
1145 ;;;            'gnus-unread t
1146 ;;;            'gnus-level level))
1147         (gnus-group-insert-group-line 
1148          group level nil
1149          (if gnus-server-browse-hashtb
1150              (gnus-gethash group gnus-server-browse-hashtb) t)
1151          (gnus-method-simplify (gnus-find-method-for-group group)))))))
1152
1153 (defun gnus-group-update-group-line ()
1154   "Update the current line in the group buffer."
1155   (let* ((buffer-read-only nil)
1156          (group (gnus-group-group-name))
1157          (entry (and group (gnus-gethash group gnus-newsrc-hashtb)))
1158          gnus-group-indentation)
1159     (when group
1160       (and entry
1161            (not (gnus-ephemeral-group-p group))
1162            (gnus-dribble-enter
1163             (concat "(gnus-group-set-info '"
1164                     (gnus-prin1-to-string (nth 2 entry))
1165                     ")")))
1166       (setq gnus-group-indentation (gnus-group-group-indentation))
1167       (gnus-delete-line)
1168       (gnus-group-insert-group-line-info group)
1169       (forward-line -1)
1170       (gnus-group-position-point))))
1171
1172 (defun gnus-group-insert-group-line-info (group)
1173   "Insert GROUP on the current line."
1174   (let ((entry (gnus-gethash group gnus-newsrc-hashtb))
1175         (gnus-group-indentation (gnus-group-group-indentation))
1176         active info)
1177     (if entry
1178         (progn
1179           ;; (Un)subscribed group.
1180           (setq info (nth 2 entry))
1181           (gnus-group-insert-group-line
1182            group (gnus-info-level info) (gnus-info-marks info)
1183            (or (car entry) t) (gnus-info-method info)))
1184       ;; This group is dead.
1185       (gnus-group-insert-group-line
1186        group
1187        (if (member group gnus-zombie-list) gnus-level-zombie gnus-level-killed)
1188        nil
1189        (if (setq active (gnus-active group))
1190            (if (zerop (cdr active))
1191                0
1192              (- (1+ (cdr active)) (car active)))
1193          nil)
1194        nil))))
1195
1196 (defun gnus-group-insert-group-line (gnus-tmp-group gnus-tmp-level
1197                                                     gnus-tmp-marked number
1198                                                     gnus-tmp-method)
1199   "Insert a group line in the group buffer."
1200   (let* ((gnus-tmp-method
1201           (gnus-server-get-method gnus-tmp-group gnus-tmp-method)) 
1202          (group-name-charset (gnus-group-name-charset gnus-tmp-method
1203                                                       gnus-tmp-group))
1204          (gnus-tmp-active (gnus-active gnus-tmp-group))
1205          (gnus-tmp-number-total
1206           (if gnus-tmp-active
1207               (1+ (- (cdr gnus-tmp-active) (car gnus-tmp-active)))
1208             0))
1209          (gnus-tmp-number-of-unread
1210           (if (numberp number) (int-to-string (max 0 number))
1211             "*"))
1212          (gnus-tmp-number-of-read
1213           (if (numberp number)
1214               (int-to-string (max 0 (- gnus-tmp-number-total number)))
1215             "*"))
1216          (gnus-tmp-subscribed
1217           (cond ((<= gnus-tmp-level gnus-level-subscribed) ? )
1218                 ((<= gnus-tmp-level gnus-level-unsubscribed) ?U)
1219                 ((= gnus-tmp-level gnus-level-zombie) ?Z)
1220                 (t ?K)))
1221          (gnus-tmp-qualified-group 
1222           (gnus-group-name-decode (gnus-group-real-name gnus-tmp-group)
1223                                   group-name-charset))
1224          (gnus-tmp-newsgroup-description
1225           (if gnus-description-hashtb
1226               (or (gnus-group-name-decode
1227                    (gnus-gethash gnus-tmp-group gnus-description-hashtb) 
1228                    group-name-charset) "")
1229             ""))
1230          (gnus-tmp-moderated
1231           (if (and gnus-moderated-hashtb
1232                    (gnus-gethash gnus-tmp-group gnus-moderated-hashtb))
1233               ?m ? ))
1234          (gnus-tmp-moderated-string
1235           (if (eq gnus-tmp-moderated ?m) "(m)" ""))
1236          (gnus-tmp-group-icon "==&&==")
1237          (gnus-tmp-news-server (or (cadr gnus-tmp-method) ""))
1238          (gnus-tmp-news-method (or (car gnus-tmp-method) ""))
1239          (gnus-tmp-news-method-string
1240           (if gnus-tmp-method
1241               (format "(%s:%s)" (car gnus-tmp-method)
1242                       (cadr gnus-tmp-method)) ""))
1243          (gnus-tmp-marked-mark
1244           (if (and (numberp number)
1245                    (zerop number)
1246                    (cdr (assq 'tick gnus-tmp-marked)))
1247               ?* ? ))
1248          (gnus-tmp-process-marked
1249           (if (member gnus-tmp-group gnus-group-marked)
1250               gnus-process-mark ? ))
1251          (gnus-tmp-grouplens
1252           (or (and gnus-use-grouplens
1253                    (bbb-grouplens-group-p gnus-tmp-group))
1254               ""))
1255          (buffer-read-only nil)
1256          header gnus-tmp-header)        ; passed as parameter to user-funcs.
1257     (beginning-of-line)
1258     (gnus-add-text-properties
1259      (point)
1260      (prog1 (1+ (point))
1261        ;; Insert the text.
1262        (eval gnus-group-line-format-spec))
1263      `(gnus-group ,(gnus-intern-safe gnus-tmp-group gnus-active-hashtb)
1264                   gnus-unread ,(if (numberp number)
1265                                    (string-to-int gnus-tmp-number-of-unread)
1266                                  t)
1267                   gnus-marked ,gnus-tmp-marked-mark
1268                   gnus-indentation ,gnus-group-indentation
1269                   gnus-level ,gnus-tmp-level))
1270     (forward-line -1)
1271     (when (inline (gnus-visual-p 'group-highlight 'highlight))
1272       (gnus-run-hooks 'gnus-group-update-hook))
1273     (forward-line)
1274     ;; Allow XEmacs to remove front-sticky text properties.
1275     (gnus-group-remove-excess-properties)))
1276
1277 (defun gnus-group-highlight-line ()
1278   "Highlight the current line according to `gnus-group-highlight'."
1279   (let* ((list gnus-group-highlight)
1280          (p (point))
1281          (end (progn (end-of-line) (point)))
1282          ;; now find out where the line starts and leave point there.
1283          (beg (progn (beginning-of-line) (point)))
1284          (group (gnus-group-group-name))
1285          (entry (gnus-group-entry group))
1286          (unread (if (numberp (car entry)) (car entry) 0))
1287          (active (gnus-active group))
1288          (total (if active (1+ (- (cdr active) (car active))) 0))
1289          (info (nth 2 entry))
1290          (method (gnus-server-get-method group (gnus-info-method info)))
1291          (marked (gnus-info-marks info))
1292          (mailp (memq 'mail (assoc (symbol-name
1293                                     (car (or method gnus-select-method)))
1294                                    gnus-valid-select-methods)))
1295          (level (or (gnus-info-level info) gnus-level-killed))
1296          (score (or (gnus-info-score info) 0))
1297          (ticked (gnus-range-length (cdr (assq 'tick marked))))
1298          (group-age (gnus-group-timestamp-delta group))
1299          (inhibit-read-only t))
1300     ;; Eval the cars of the lists until we find a match.
1301     (while (and list
1302                 (not (eval (caar list))))
1303       (setq list (cdr list)))
1304     (let ((face (cdar list)))
1305       (unless (eq face (get-text-property beg 'face))
1306         (gnus-put-text-property-excluding-characters-with-faces
1307          beg end 'face
1308          (setq face (if (boundp face) (symbol-value face) face)))
1309         (gnus-extent-start-open beg)))
1310     (goto-char p)))
1311
1312 (defun gnus-group-update-group (group &optional visible-only)
1313   "Update all lines where GROUP appear.
1314 If VISIBLE-ONLY is non-nil, the group won't be displayed if it isn't
1315 already."
1316   ;; Can't use `save-excursion' here, so we do it manually.
1317   (let ((buf (current-buffer))
1318         mark)
1319     (set-buffer gnus-group-buffer)
1320     (setq mark (point-marker))
1321     ;; The buffer may be narrowed.
1322     (save-restriction
1323       (widen)
1324       (let ((ident (gnus-intern-safe group gnus-active-hashtb))
1325             (loc (point-min))
1326             found buffer-read-only)
1327         ;; Enter the current status into the dribble buffer.
1328         (let ((entry (gnus-gethash group gnus-newsrc-hashtb)))
1329           (when (and entry
1330                      (not (gnus-ephemeral-group-p group)))
1331             (gnus-dribble-enter
1332              (concat "(gnus-group-set-info '"
1333                      (gnus-prin1-to-string (nth 2 entry))
1334                      ")"))))
1335         ;; Find all group instances.  If topics are in use, each group
1336         ;; may be listed in more than once.
1337         (while (setq loc (text-property-any
1338                           loc (point-max) 'gnus-group ident))
1339           (setq found t)
1340           (goto-char loc)
1341           (let ((gnus-group-indentation (gnus-group-group-indentation)))
1342             (gnus-delete-line)
1343             (gnus-group-insert-group-line-info group)
1344             (save-excursion
1345               (forward-line -1)
1346               (gnus-run-hooks 'gnus-group-update-group-hook)))
1347           (setq loc (1+ loc)))
1348         (unless (or found visible-only)
1349           ;; No such line in the buffer, find out where it's supposed to
1350           ;; go, and insert it there (or at the end of the buffer).
1351           (if gnus-goto-missing-group-function
1352               (funcall gnus-goto-missing-group-function group)
1353             (let ((entry (cddr (gnus-gethash group gnus-newsrc-hashtb))))
1354               (while (and entry (car entry)
1355                           (not
1356                            (gnus-goto-char
1357                             (text-property-any
1358                              (point-min) (point-max)
1359                              'gnus-group (gnus-intern-safe
1360                                           (caar entry) gnus-active-hashtb)))))
1361                 (setq entry (cdr entry)))
1362               (or entry (goto-char (point-max)))))
1363           ;; Finally insert the line.
1364           (let ((gnus-group-indentation (gnus-group-group-indentation)))
1365             (gnus-group-insert-group-line-info group)
1366             (save-excursion
1367               (forward-line -1)
1368               (gnus-run-hooks 'gnus-group-update-group-hook))))
1369         (when gnus-group-update-group-function
1370           (funcall gnus-group-update-group-function group))
1371         (gnus-group-set-mode-line)))
1372     (goto-char mark)
1373     (set-marker mark nil)
1374     (set-buffer buf)))
1375
1376 (defun gnus-group-set-mode-line ()
1377   "Update the mode line in the group buffer."
1378   (when (memq 'group gnus-updated-mode-lines)
1379     ;; Yes, we want to keep this mode line updated.
1380     (save-excursion
1381       (set-buffer gnus-group-buffer)
1382       (let* ((gformat (or gnus-group-mode-line-format-spec
1383                           (gnus-set-format 'group-mode)))
1384              (gnus-tmp-news-server (cadr gnus-select-method))
1385              (gnus-tmp-news-method (car gnus-select-method))
1386              (gnus-tmp-colon (if (equal gnus-tmp-news-server "") "" ":"))
1387              (max-len 60)
1388              gnus-tmp-header            ;Dummy binding for user-defined formats
1389              ;; Get the resulting string.
1390              (modified
1391               (and gnus-dribble-buffer
1392                    (buffer-name gnus-dribble-buffer)
1393                    (buffer-modified-p gnus-dribble-buffer)
1394                    (save-excursion
1395                      (set-buffer gnus-dribble-buffer)
1396                      (not (zerop (buffer-size))))))
1397              (mode-string (eval gformat)))
1398         ;; Say whether the dribble buffer has been modified.
1399         (setq mode-line-modified
1400               (if modified (car gnus-mode-line-modified)
1401                 (cdr gnus-mode-line-modified)))
1402         ;; If the line is too long, we chop it off.
1403         (when (> (length mode-string) max-len)
1404           (setq mode-string (substring mode-string 0 (- max-len 4))))
1405         (prog1
1406             (setq mode-line-buffer-identification
1407                   (gnus-mode-line-buffer-identification
1408                    (list mode-string)))
1409           (set-buffer-modified-p modified))))))
1410
1411 (defun gnus-group-group-name ()
1412   "Get the name of the newsgroup on the current line."
1413   (let ((group (get-text-property (gnus-point-at-bol) 'gnus-group)))
1414     (when group
1415       (symbol-name group))))
1416
1417 (defun gnus-group-group-level ()
1418   "Get the level of the newsgroup on the current line."
1419   (get-text-property (gnus-point-at-bol) 'gnus-level))
1420
1421 (defun gnus-group-group-indentation ()
1422   "Get the indentation of the newsgroup on the current line."
1423   (or (get-text-property (gnus-point-at-bol) 'gnus-indentation)
1424       (and gnus-group-indentation-function
1425            (funcall gnus-group-indentation-function))
1426       ""))
1427
1428 (defun gnus-group-group-unread ()
1429   "Get the number of unread articles of the newsgroup on the current line."
1430   (get-text-property (gnus-point-at-bol) 'gnus-unread))
1431
1432 (defun gnus-group-new-mail (group)
1433   (if (nnmail-new-mail-p (gnus-group-real-name group))
1434       gnus-new-mail-mark
1435     ? ))
1436
1437 (defun gnus-group-level (group)
1438   "Return the estimated level of GROUP."
1439   (or (gnus-info-level (gnus-get-info group))
1440       (and (member group gnus-zombie-list) gnus-level-zombie)
1441       gnus-level-killed))
1442
1443 (defun gnus-group-search-forward (&optional backward all level first-too)
1444   "Find the next newsgroup with unread articles.
1445 If BACKWARD is non-nil, find the previous newsgroup instead.
1446 If ALL is non-nil, just find any newsgroup.
1447 If LEVEL is non-nil, find group with level LEVEL, or higher if no such
1448 group exists.
1449 If FIRST-TOO, the current line is also eligible as a target."
1450   (let ((way (if backward -1 1))
1451         (low gnus-level-killed)
1452         (beg (point))
1453         pos found lev)
1454     (if (and backward (progn (beginning-of-line)) (bobp))
1455         nil
1456       (unless first-too
1457         (forward-line way))
1458       (while (and
1459               (not (eobp))
1460               (not (setq
1461                     found
1462                     (and
1463                      (get-text-property (point) 'gnus-group)
1464                      (or all
1465                          (and
1466                           (let ((unread
1467                                  (get-text-property (point) 'gnus-unread)))
1468                             (and (numberp unread) (> unread 0)))
1469                           (setq lev (get-text-property (point)
1470                                                        'gnus-level))
1471                           (<= lev gnus-level-subscribed)))
1472                      (or (not level)
1473                          (and (setq lev (get-text-property (point)
1474                                                            'gnus-level))
1475                               (or (= lev level)
1476                                   (and (< lev low)
1477                                        (< level lev)
1478                                        (progn
1479                                          (setq low lev)
1480                                          (setq pos (point))
1481                                          nil))))))))
1482               (zerop (forward-line way)))))
1483     (if found
1484         (progn (gnus-group-position-point) t)
1485       (goto-char (or pos beg))
1486       (and pos t))))
1487
1488 ;;; Gnus group mode commands
1489
1490 ;; Group marking.
1491
1492 (defun gnus-group-mark-line-p ()
1493   (save-excursion
1494     (beginning-of-line)
1495     (forward-char (or (cdr (assq 'process gnus-group-mark-positions)) 2))
1496     (eq (char-after) gnus-process-mark)))
1497
1498 (defun gnus-group-mark-group (n &optional unmark no-advance)
1499   "Mark the current group."
1500   (interactive "p")
1501   (let ((buffer-read-only nil)
1502         group)
1503     (while (and (> n 0)
1504                 (not (eobp)))
1505       (when (setq group (gnus-group-group-name))
1506         ;; Go to the mark position.
1507         (beginning-of-line)
1508         (forward-char (or (cdr (assq 'process gnus-group-mark-positions)) 2))
1509         (subst-char-in-region
1510          (point) (1+ (point)) (char-after)
1511          (if unmark
1512              (progn
1513                (setq gnus-group-marked (delete group gnus-group-marked))
1514                ? )
1515            (setq gnus-group-marked
1516                  (cons group (delete group gnus-group-marked)))
1517            gnus-process-mark)))
1518       (unless no-advance
1519         (gnus-group-next-group 1))
1520       (decf n))
1521     (gnus-summary-position-point)
1522     n))
1523
1524 (defun gnus-group-unmark-group (n)
1525   "Remove the mark from the current group."
1526   (interactive "p")
1527   (gnus-group-mark-group n 'unmark)
1528   (gnus-group-position-point))
1529
1530 (defun gnus-group-unmark-all-groups ()
1531   "Unmark all groups."
1532   (interactive)
1533   (let ((groups gnus-group-marked))
1534     (save-excursion
1535       (while groups
1536         (gnus-group-remove-mark (pop groups)))))
1537   (gnus-group-position-point))
1538
1539 (defun gnus-group-mark-region (unmark beg end)
1540   "Mark all groups between point and mark.
1541 If UNMARK, remove the mark instead."
1542   (interactive "P\nr")
1543   (let ((num (count-lines beg end)))
1544     (save-excursion
1545       (goto-char beg)
1546       (- num (gnus-group-mark-group num unmark)))))
1547
1548 (defun gnus-group-mark-buffer (&optional unmark)
1549   "Mark all groups in the buffer.
1550 If UNMARK, remove the mark instead."
1551   (interactive "P")
1552   (gnus-group-mark-region unmark (point-min) (point-max)))
1553
1554 (defun gnus-group-mark-regexp (regexp)
1555   "Mark all groups that match some regexp."
1556   (interactive "sMark (regexp): ")
1557   (let ((alist (cdr gnus-newsrc-alist))
1558         group)
1559     (while alist
1560       (when (string-match regexp (setq group (gnus-info-group (pop alist))))
1561         (gnus-group-set-mark group))))
1562   (gnus-group-position-point))
1563
1564 (defun gnus-group-remove-mark (group &optional test-marked)
1565   "Remove the process mark from GROUP and move point there.
1566 Return nil if the group isn't displayed."
1567   (if (gnus-group-goto-group group nil test-marked)
1568       (save-excursion
1569         (gnus-group-mark-group 1 'unmark t)
1570         t)
1571     (setq gnus-group-marked
1572           (delete group gnus-group-marked))
1573     nil))
1574
1575 (defun gnus-group-set-mark (group)
1576   "Set the process mark on GROUP."
1577   (if (gnus-group-goto-group group)
1578       (save-excursion
1579         (gnus-group-mark-group 1 nil t))
1580     (setq gnus-group-marked (cons group (delete group gnus-group-marked)))))
1581
1582 (defun gnus-group-universal-argument (arg &optional groups func)
1583   "Perform any command on all groups according to the process/prefix convention."
1584   (interactive "P")
1585   (if (eq (setq func (or func
1586                          (key-binding
1587                           (read-key-sequence
1588                            (substitute-command-keys
1589                             "\\<gnus-group-mode-map>\\[gnus-group-universal-argument]")))))
1590           'undefined)
1591       (gnus-error 1 "Undefined key")
1592     (gnus-group-iterate arg
1593       (lambda (group)
1594         (command-execute func))))
1595   (gnus-group-position-point))
1596
1597 (defun gnus-group-process-prefix (n)
1598   "Return a list of groups to work on.
1599 Take into consideration N (the prefix) and the list of marked groups."
1600   (cond
1601    (n
1602     (setq n (prefix-numeric-value n))
1603     ;; There is a prefix, so we return a list of the N next
1604     ;; groups.
1605     (let ((way (if (< n 0) -1 1))
1606           (n (abs n))
1607           group groups)
1608       (save-excursion
1609         (while (> n 0)
1610           (if (setq group (gnus-group-group-name))
1611               (push group groups))
1612           (setq n (1- n))
1613           (gnus-group-next-group way)))
1614       (nreverse groups)))
1615    ((gnus-region-active-p)
1616     ;; Work on the region between point and mark.
1617     (let ((max (max (point) (mark)))
1618           groups)
1619       (save-excursion
1620         (goto-char (min (point) (mark)))
1621         (while
1622             (and
1623              (push (gnus-group-group-name) groups)
1624              (zerop (gnus-group-next-group 1))
1625              (< (point) max)))
1626         (nreverse groups))))
1627    (gnus-group-marked
1628     ;; No prefix, but a list of marked articles.
1629     (reverse gnus-group-marked))
1630    (t
1631     ;; Neither marked articles or a prefix, so we return the
1632     ;; current group.
1633     (let ((group (gnus-group-group-name)))
1634       (and group (list group))))))
1635
1636 ;;; !!!Surely gnus-group-iterate should be a macro instead?  I can't
1637 ;;; imagine why I went through these contortions...
1638 (eval-and-compile
1639   (let ((function (make-symbol "gnus-group-iterate-function"))
1640         (window (make-symbol "gnus-group-iterate-window"))
1641         (groups (make-symbol "gnus-group-iterate-groups"))
1642         (group (make-symbol "gnus-group-iterate-group")))
1643     (eval
1644      `(defun gnus-group-iterate (arg ,function)
1645         "Iterate FUNCTION over all process/prefixed groups.
1646 FUNCTION will be called with the group name as the parameter
1647 and with point over the group in question."
1648         (let ((,groups (gnus-group-process-prefix arg))
1649               (,window (selected-window))
1650               ,group)
1651           (while ,groups
1652             (setq ,group (car ,groups)
1653                   ,groups (cdr ,groups))
1654             (select-window ,window)
1655             (gnus-group-remove-mark ,group)
1656             (save-selected-window
1657               (save-excursion
1658                 (funcall ,function ,group)))))))))
1659
1660 (put 'gnus-group-iterate 'lisp-indent-function 1)
1661
1662 ;; Selecting groups.
1663
1664 (defun gnus-group-read-group (&optional all no-article group select-articles)
1665   "Read news in this newsgroup.
1666 If the prefix argument ALL is non-nil, already read articles become
1667 readable.  IF ALL is a number, fetch this number of articles.  If the
1668 optional argument NO-ARTICLE is non-nil, no article will be
1669 auto-selected upon group entry.  If GROUP is non-nil, fetch that
1670 group."
1671   (interactive "P")
1672   (let ((no-display (eq all 0))
1673         (group (or group (gnus-group-group-name)))
1674         number active marked entry)
1675     (when (eq all 0)
1676       (setq all nil))
1677     (unless group
1678       (error "No group on current line"))
1679     (setq marked (gnus-info-marks
1680                   (nth 2 (setq entry (gnus-gethash
1681                                       group gnus-newsrc-hashtb)))))
1682     ;; This group might be a dead group.  In that case we have to get
1683     ;; the number of unread articles from `gnus-active-hashtb'.
1684     (setq number
1685           (cond ((numberp all) all)
1686                 (entry (car entry))
1687                 ((setq active (gnus-active group))
1688                  (- (1+ (cdr active)) (car active)))))
1689     (gnus-summary-read-group
1690      group (or all (and (numberp number)
1691                         (zerop (+ number (gnus-range-length
1692                                           (cdr (assq 'tick marked)))
1693                                   (gnus-range-length
1694                                    (cdr (assq 'dormant marked)))))))
1695      no-article nil no-display nil select-articles)))
1696
1697 (defun gnus-group-select-group (&optional all)
1698   "Select this newsgroup.
1699 No article is selected automatically.
1700 If ALL is non-nil, already read articles become readable.
1701 If ALL is a number, fetch this number of articles."
1702   (interactive "P")
1703   (gnus-group-read-group all t))
1704
1705 (defun gnus-group-quick-select-group (&optional all)
1706   "Select the current group \"quickly\".
1707 This means that no highlighting or scoring will be performed.
1708 If ALL (the prefix argument) is 0, don't even generate the summary
1709 buffer.
1710
1711 This might be useful if you want to toggle threading
1712 before entering the group."
1713   (interactive "P")
1714   (require 'gnus-score)
1715   (let (gnus-visual
1716         gnus-score-find-score-files-function
1717         gnus-home-score-file
1718         gnus-apply-kill-hook
1719         gnus-summary-expunge-below)
1720     (gnus-group-read-group all t)))
1721
1722 (defun gnus-group-visible-select-group (&optional all)
1723   "Select the current group without hiding any articles."
1724   (interactive "P")
1725   (let ((gnus-inhibit-limiting t))
1726     (gnus-group-read-group all t)))
1727
1728 (defun gnus-group-select-group-ephemerally ()
1729   "Select the current group without doing any processing whatsoever.
1730 You will actually be entered into a group that's a copy of
1731 the current group; no changes you make while in this group will
1732 be permanent."
1733   (interactive)
1734   (require 'gnus-score)
1735   (let* (gnus-visual
1736          gnus-score-find-score-files-function gnus-apply-kill-hook
1737          gnus-summary-expunge-below gnus-show-threads gnus-suppress-duplicates
1738          gnus-summary-mode-hook gnus-select-group-hook
1739          (group (gnus-group-group-name))
1740          (method (gnus-find-method-for-group group)))
1741     (gnus-group-read-ephemeral-group
1742      (gnus-group-prefixed-name group method) method)))
1743
1744 ;;;###autoload
1745 (defun gnus-fetch-group (group)
1746   "Start Gnus if necessary and enter GROUP.
1747 Returns whether the fetching was successful or not."
1748   (interactive (list (completing-read "Group name: " gnus-active-hashtb)))
1749   (unless (get-buffer gnus-group-buffer)
1750     (gnus-no-server))
1751   (gnus-group-read-group nil nil group))
1752
1753 ;;;###autoload
1754 (defun gnus-fetch-group-other-frame (group)
1755   "Pop up a frame and enter GROUP."
1756   (interactive "P")
1757   (let ((window (get-buffer-window gnus-group-buffer)))
1758     (cond (window
1759            (select-frame (window-frame window)))
1760           ((= (length (frame-list)) 1)
1761            (select-frame (make-frame)))
1762           (t
1763            (other-frame 1))))
1764   (gnus-fetch-group group))
1765
1766 (defvar gnus-ephemeral-group-server 0)
1767
1768 ;; Enter a group that is not in the group buffer.  Non-nil is returned
1769 ;; if selection was successful.
1770 (defun gnus-group-read-ephemeral-group (group method &optional activate
1771                                               quit-config request-only
1772                                               select-articles)
1773   "Read GROUP from METHOD as an ephemeral group.
1774 If ACTIVATE, request the group first.
1775 If QUIT-CONFIG, use that window configuration when exiting from the
1776 ephemeral group.
1777 If REQUEST-ONLY, don't actually read the group; just request it.
1778 If SELECT-ARTICLES, only select those articles.
1779
1780 Return the name of the group if selection was successful."
1781   ;; Transform the select method into a unique server.
1782   (when (stringp method)
1783     (setq method (gnus-server-to-method method)))
1784   (setq method
1785         `(,(car method) ,(concat (cadr method) "-ephemeral")
1786           (,(intern (format "%s-address" (car method))) ,(cadr method))
1787           ,@(cddr method)))
1788   (let ((group (if (gnus-group-foreign-p group) group
1789                  (gnus-group-prefixed-name group method))))
1790     (gnus-sethash
1791      group
1792      `(-1 nil (,group
1793                ,gnus-level-default-subscribed nil nil ,method
1794                ((quit-config .
1795                              ,(if quit-config quit-config
1796                                 (cons gnus-summary-buffer
1797                                       gnus-current-window-configuration))))))
1798      gnus-newsrc-hashtb)
1799     (push method gnus-ephemeral-servers)
1800     (set-buffer gnus-group-buffer)
1801     (unless (gnus-check-server method)
1802       (error "Unable to contact server: %s" (gnus-status-message method)))
1803     (when activate
1804       (gnus-activate-group group 'scan)
1805       (unless (gnus-request-group group)
1806         (error "Couldn't request group: %s"
1807                (nnheader-get-report (car method)))))
1808     (if request-only
1809         group
1810       (condition-case ()
1811           (when (gnus-group-read-group t t group select-articles)
1812             group)
1813         ;;(error nil)
1814         (quit
1815          (message "Quit reading the ephemeral group")
1816          nil)))))
1817
1818 (defun gnus-group-jump-to-group (group)
1819   "Jump to newsgroup GROUP."
1820   (interactive
1821    (list (completing-read
1822           "Group: " gnus-active-hashtb nil
1823           (gnus-read-active-file-p)
1824           nil
1825           'gnus-group-history)))
1826
1827   (when (equal group "")
1828     (error "Empty group name"))
1829
1830   (unless (gnus-ephemeral-group-p group)
1831     ;; Either go to the line in the group buffer...
1832     (unless (gnus-group-goto-group group)
1833       ;; ... or insert the line.
1834       (gnus-group-update-group group)
1835       (gnus-group-goto-group group)))
1836   ;; Adjust cursor point.
1837   (gnus-group-position-point))
1838
1839 (defun gnus-group-goto-group (group &optional far test-marked)
1840   "Goto to newsgroup GROUP.
1841 If FAR, it is likely that the group is not on the current line.
1842 If TEST-MARKED, the line must be marked."
1843   (when group
1844     (beginning-of-line)
1845     (cond
1846      ;; It's quite likely that we are on the right line, so
1847      ;; we check the current line first.
1848      ((and (not far)
1849            (eq (get-text-property (point) 'gnus-group)
1850                (gnus-intern-safe group gnus-active-hashtb))
1851            (or (not test-marked) (gnus-group-mark-line-p)))
1852       (point))
1853      ;; Previous and next line are also likely, so we check them as well.
1854      ((and (not far)
1855            (save-excursion
1856              (forward-line -1)
1857              (and (eq (get-text-property (point) 'gnus-group)
1858                       (gnus-intern-safe group gnus-active-hashtb))
1859                   (or (not test-marked) (gnus-group-mark-line-p)))))
1860       (forward-line -1)
1861       (point))
1862      ((and (not far)
1863            (save-excursion
1864              (forward-line 1)
1865              (and (eq (get-text-property (point) 'gnus-group)
1866                       (gnus-intern-safe group gnus-active-hashtb))
1867                   (or (not test-marked) (gnus-group-mark-line-p)))))
1868       (forward-line 1)
1869       (point))
1870      (test-marked
1871       (goto-char (point-min))
1872       (let (found)
1873         (while (and (not found) 
1874                     (gnus-goto-char
1875                      (text-property-any
1876                       (point) (point-max)
1877                       'gnus-group 
1878                       (gnus-intern-safe group gnus-active-hashtb))))
1879           (if (gnus-group-mark-line-p)
1880               (setq found t)
1881             (forward-line 1)))
1882         found))
1883      (t
1884       ;; Search through the entire buffer.
1885       (gnus-goto-char
1886        (text-property-any
1887         (point-min) (point-max)
1888         'gnus-group (gnus-intern-safe group gnus-active-hashtb)))))))
1889
1890 (defun gnus-group-next-group (n &optional silent)
1891   "Go to next N'th newsgroup.
1892 If N is negative, search backward instead.
1893 Returns the difference between N and the number of skips actually
1894 done."
1895   (interactive "p")
1896   (gnus-group-next-unread-group n t nil silent))
1897
1898 (defun gnus-group-next-unread-group (n &optional all level silent)
1899   "Go to next N'th unread newsgroup.
1900 If N is negative, search backward instead.
1901 If ALL is non-nil, choose any newsgroup, unread or not.
1902 If LEVEL is non-nil, choose the next group with level LEVEL, or, if no
1903 such group can be found, the next group with a level higher than
1904 LEVEL.
1905 Returns the difference between N and the number of skips actually
1906 made."
1907   (interactive "p")
1908   (let ((backward (< n 0))
1909         (n (abs n)))
1910     (while (and (> n 0)
1911                 (gnus-group-search-forward
1912                  backward (or (not gnus-group-goto-unread) all) level))
1913       (setq n (1- n)))
1914     (when (and (/= 0 n)
1915                (not silent))
1916       (gnus-message 7 "No more%s newsgroups%s" (if all "" " unread")
1917                     (if level " on this level or higher" "")))
1918     n))
1919
1920 (defun gnus-group-prev-group (n)
1921   "Go to previous N'th newsgroup.
1922 Returns the difference between N and the number of skips actually
1923 done."
1924   (interactive "p")
1925   (gnus-group-next-unread-group (- n) t))
1926
1927 (defun gnus-group-prev-unread-group (n)
1928   "Go to previous N'th unread newsgroup.
1929 Returns the difference between N and the number of skips actually
1930 done."
1931   (interactive "p")
1932   (gnus-group-next-unread-group (- n)))
1933
1934 (defun gnus-group-next-unread-group-same-level (n)
1935   "Go to next N'th unread newsgroup on the same level.
1936 If N is negative, search backward instead.
1937 Returns the difference between N and the number of skips actually
1938 done."
1939   (interactive "p")
1940   (gnus-group-next-unread-group n t (gnus-group-group-level))
1941   (gnus-group-position-point))
1942
1943 (defun gnus-group-prev-unread-group-same-level (n)
1944   "Go to next N'th unread newsgroup on the same level.
1945 Returns the difference between N and the number of skips actually
1946 done."
1947   (interactive "p")
1948   (gnus-group-next-unread-group (- n) t (gnus-group-group-level))
1949   (gnus-group-position-point))
1950
1951 (defun gnus-group-best-unread-group (&optional exclude-group)
1952   "Go to the group with the highest level.
1953 If EXCLUDE-GROUP, do not go to that group."
1954   (interactive)
1955   (goto-char (point-min))
1956   (let ((best 100000)
1957         unread best-point)
1958     (while (not (eobp))
1959       (setq unread (get-text-property (point) 'gnus-unread))
1960       (when (and (numberp unread) (> unread 0))
1961         (when (and (get-text-property (point) 'gnus-level)
1962                    (< (get-text-property (point) 'gnus-level) best)
1963                    (or (not exclude-group)
1964                        (not (equal exclude-group (gnus-group-group-name)))))
1965           (setq best (get-text-property (point) 'gnus-level))
1966           (setq best-point (point))))
1967       (forward-line 1))
1968     (when best-point
1969       (goto-char best-point))
1970     (gnus-summary-position-point)
1971     (and best-point (gnus-group-group-name))))
1972
1973 (defun gnus-group-first-unread-group ()
1974   "Go to the first group with unread articles."
1975   (interactive)
1976   (prog1
1977       (let ((opoint (point))
1978             unread)
1979         (goto-char (point-min))
1980         (if (or (eq (setq unread (gnus-group-group-unread)) t) ; Not active.
1981                 (and (numberp unread)   ; Not a topic.
1982                      (not (zerop unread))) ; Has unread articles.
1983                 (zerop (gnus-group-next-unread-group 1))) ; Next unread group.
1984             (point)                     ; Success.
1985           (goto-char opoint)
1986           nil))                         ; Not success.
1987     (gnus-group-position-point)))
1988
1989 (defun gnus-group-enter-server-mode ()
1990   "Jump to the server buffer."
1991   (interactive)
1992   (gnus-enter-server-buffer))
1993
1994 (defun gnus-group-make-group (name &optional method address args)
1995   "Add a new newsgroup.
1996 The user will be prompted for a NAME, for a select METHOD, and an
1997 ADDRESS."
1998   (interactive
1999    (list
2000     (gnus-read-group "Group name: ")
2001     (gnus-read-method "From method: ")))
2002
2003   (when (stringp method)
2004     (setq method (or (gnus-server-to-method method) method)))
2005   (let* ((meth (gnus-method-simplify
2006                 (when (and method
2007                            (not (gnus-server-equal method gnus-select-method)))
2008                   (if address (list (intern method) address)
2009                     method))))
2010          (nname (if method (gnus-group-prefixed-name name meth) name))
2011          backend info)
2012     (when (gnus-gethash nname gnus-newsrc-hashtb)
2013       (error "Group %s already exists" nname))
2014     ;; Subscribe to the new group.
2015     (gnus-group-change-level
2016      (setq info (list t nname gnus-level-default-subscribed nil nil meth))
2017      gnus-level-default-subscribed gnus-level-killed
2018      (and (gnus-group-group-name)
2019           (gnus-gethash (gnus-group-group-name)
2020                         gnus-newsrc-hashtb))
2021      t)
2022     ;; Make it active.
2023     (gnus-set-active nname (cons 1 0))
2024     (unless (gnus-ephemeral-group-p name)
2025       (gnus-dribble-enter
2026        (concat "(gnus-group-set-info '"
2027                (gnus-prin1-to-string (cdr info)) ")")))
2028     ;; Insert the line.
2029     (gnus-group-insert-group-line-info nname)
2030     (forward-line -1)
2031     (gnus-group-position-point)
2032
2033     ;; Load the backend and try to make the backend create
2034     ;; the group as well.
2035     (when (assoc (symbol-name (setq backend (car (gnus-server-get-method
2036                                                   nil meth))))
2037                  gnus-valid-select-methods)
2038       (require backend))
2039     (gnus-check-server meth)
2040     (when (gnus-check-backend-function 'request-create-group nname)
2041       (gnus-request-create-group nname nil args))
2042     t))
2043
2044 (defun gnus-group-delete-groups (&optional arg)
2045   "Delete the current group.  Only meaningful with editable groups."
2046   (interactive "P")
2047   (let ((n (length (gnus-group-process-prefix arg))))
2048     (when (gnus-yes-or-no-p
2049            (if (= n 1)
2050                "Delete this 1 group? "
2051              (format "Delete these %d groups? " n)))
2052       (gnus-group-iterate arg
2053         (lambda (group)
2054           (gnus-group-delete-group group nil t))))))
2055
2056 (defun gnus-group-delete-group (group &optional force no-prompt)
2057   "Delete the current group.  Only meaningful with editable groups.
2058 If FORCE (the prefix) is non-nil, all the articles in the group will
2059 be deleted.  This is \"deleted\" as in \"removed forever from the face
2060 of the Earth\".  There is no undo.  The user will be prompted before
2061 doing the deletion."
2062   (interactive
2063    (list (gnus-group-group-name)
2064          current-prefix-arg))
2065   (unless group
2066     (error "No group to rename"))
2067   (unless (gnus-check-backend-function 'request-delete-group group)
2068     (error "This backend does not support group deletion"))
2069   (prog1
2070       (if (and (not no-prompt)
2071                (not (gnus-yes-or-no-p
2072                      (format
2073                       "Do you really want to delete %s%s? "
2074                       group (if force " and all its contents" "")))))
2075           ()                            ; Whew!
2076         (gnus-message 6 "Deleting group %s..." group)
2077         (if (not (gnus-request-delete-group group force))
2078             (gnus-error 3 "Couldn't delete group %s" group)
2079           (gnus-message 6 "Deleting group %s...done" group)
2080           (gnus-group-goto-group group)
2081           (gnus-group-kill-group 1 t)
2082           (gnus-sethash group nil gnus-active-hashtb)
2083           t))
2084     (gnus-group-position-point)))
2085
2086 (defun gnus-group-rename-group (group new-name)
2087   "Rename group from GROUP to NEW-NAME.
2088 When used interactively, GROUP is the group under point
2089 and NEW-NAME will be prompted for."
2090   (interactive
2091    (list
2092     (gnus-group-group-name)
2093     (progn
2094       (unless (gnus-check-backend-function
2095                'request-rename-group (gnus-group-group-name))
2096         (error "This backend does not support renaming groups"))
2097       (gnus-read-group "Rename group to: "
2098                        (gnus-group-real-name (gnus-group-group-name))))))
2099
2100   (unless (gnus-check-backend-function 'request-rename-group group)
2101     (error "This backend does not support renaming groups"))
2102   (unless group
2103     (error "No group to rename"))
2104   (when (equal (gnus-group-real-name group) new-name)
2105     (error "Can't rename to the same name"))
2106
2107   ;; We find the proper prefixed name.
2108   (setq new-name
2109         (if (gnus-group-native-p group)
2110             ;; Native group.
2111             new-name
2112           ;; Foreign group.
2113           (gnus-group-prefixed-name
2114            (gnus-group-real-name new-name)
2115            (gnus-info-method (gnus-get-info group)))))
2116
2117   (gnus-message 6 "Renaming group %s to %s..." group new-name)
2118   (prog1
2119       (if (progn
2120             (gnus-group-goto-group group)
2121             (not (when (< (gnus-group-group-level) gnus-level-zombie)
2122                    (gnus-request-rename-group group new-name))))
2123           (gnus-error 3 "Couldn't rename group %s to %s" group new-name)
2124         ;; We rename the group internally by killing it...
2125         (gnus-group-kill-group)
2126         ;; ... changing its name ...
2127         (setcar (cdar gnus-list-of-killed-groups) new-name)
2128         ;; ... and then yanking it.  Magic!
2129         (gnus-group-yank-group)
2130         (gnus-set-active new-name (gnus-active group))
2131         (gnus-message 6 "Renaming group %s to %s...done" group new-name)
2132         new-name)
2133     (setq gnus-killed-list (delete group gnus-killed-list))
2134     (gnus-set-active group nil)
2135     (gnus-dribble-touch)
2136     (gnus-group-position-point)))
2137
2138 (defun gnus-group-edit-group (group &optional part)
2139   "Edit the group on the current line."
2140   (interactive (list (gnus-group-group-name)))
2141   (let ((part (or part 'info))
2142         info)
2143     (unless group
2144       (error "No group on current line"))
2145     (unless (setq info (gnus-get-info group))
2146       (error "Killed group; can't be edited"))
2147     (ignore-errors
2148       (gnus-close-group group))
2149     (gnus-edit-form
2150      ;; Find the proper form to edit.
2151      (cond ((eq part 'method)
2152             (or (gnus-info-method info) "native"))
2153            ((eq part 'params)
2154             (gnus-info-params info))
2155            (t info))
2156      ;; The proper documentation.
2157      (format
2158       "Editing the %s for `%s'."
2159       (cond
2160        ((eq part 'method) "select method")
2161        ((eq part 'params) "group parameters")
2162        (t "group info"))
2163       (gnus-group-decoded-name group))
2164      `(lambda (form)
2165         (gnus-group-edit-group-done ',part ,group form)))))
2166
2167 (defun gnus-group-edit-group-method (group)
2168   "Edit the select method of GROUP."
2169   (interactive (list (gnus-group-group-name)))
2170   (gnus-group-edit-group group 'method))
2171
2172 (defun gnus-group-edit-group-parameters (group)
2173   "Edit the group parameters of GROUP."
2174   (interactive (list (gnus-group-group-name)))
2175   (gnus-group-edit-group group 'params))
2176
2177 (defun gnus-group-edit-group-done (part group form)
2178   "Update variables."
2179   (let* ((method (cond ((eq part 'info) (nth 4 form))
2180                        ((eq part 'method) form)
2181                        (t nil)))
2182          (info (cond ((eq part 'info) form)
2183                      ((eq part 'method) (gnus-get-info group))
2184                      (t nil)))
2185          (new-group (if info
2186                         (if (or (not method)
2187                                 (gnus-server-equal
2188                                  gnus-select-method method))
2189                             (gnus-group-real-name (car info))
2190                           (gnus-group-prefixed-name
2191                            (gnus-group-real-name (car info)) method))
2192                       nil)))
2193     (when (and new-group
2194                (not (equal new-group group)))
2195       (when (gnus-group-goto-group group)
2196         (gnus-group-kill-group 1))
2197       (gnus-activate-group new-group))
2198     ;; Set the info.
2199     (if (not (and info new-group))
2200         (gnus-group-set-info form (or new-group group) part)
2201       (setq info (gnus-copy-sequence info))
2202       (setcar info new-group)
2203       (unless (gnus-server-equal method "native")
2204         (unless (nthcdr 3 info)
2205           (nconc info (list nil nil)))
2206         (unless (nthcdr 4 info)
2207           (nconc info (list nil)))
2208         (gnus-info-set-method info method))
2209       (gnus-group-set-info info))
2210     (gnus-group-update-group (or new-group group))
2211     (gnus-group-position-point)))
2212
2213 (defun gnus-group-make-useful-group (group method)
2214   "Create one of the groups described in `gnus-useful-groups'."
2215   (interactive
2216    (let ((entry (assoc (completing-read "Create group: " gnus-useful-groups
2217                                         nil t)
2218                        gnus-useful-groups)))
2219      (list (cadr entry) (caddr entry))))
2220   (setq method (gnus-copy-sequence method))
2221   (let (entry)
2222     (while (setq entry (memq (assq 'eval method) method))
2223       (setcar entry (eval (cadar entry)))))
2224   (gnus-group-make-group group method))
2225
2226 (defun gnus-group-make-help-group ()
2227   "Create the Gnus documentation group."
2228   (interactive)
2229   (let ((name (gnus-group-prefixed-name "gnus-help" '(nndoc "gnus-help")))
2230         (file (nnheader-find-etc-directory "gnus-tut.txt" t)))
2231     (when (gnus-gethash name gnus-newsrc-hashtb)
2232       (error "Documentation group already exists"))
2233     (if (not file)
2234         (gnus-message 1 "Couldn't find doc group")
2235       (gnus-group-make-group
2236        (gnus-group-real-name name)
2237        (list 'nndoc "gnus-help"
2238              (list 'nndoc-address file)
2239              (list 'nndoc-article-type 'mbox)))))
2240   (gnus-group-position-point))
2241
2242 (defun gnus-group-make-doc-group (file type)
2243   "Create a group that uses a single file as the source."
2244   (interactive
2245    (list (read-file-name "File name: ")
2246          (and current-prefix-arg 'ask)))
2247   (when (eq type 'ask)
2248     (let ((err "")
2249           char found)
2250       (while (not found)
2251         (message
2252          "%sFile type (mbox, babyl, digest, forward, mmdf, guess) [mbdfag]: "
2253          err)
2254         (setq found (cond ((= (setq char (read-char)) ?m) 'mbox)
2255                           ((= char ?b) 'babyl)
2256                           ((= char ?d) 'digest)
2257                           ((= char ?f) 'forward)
2258                           ((= char ?a) 'mmfd)
2259                           ((= char ?g) 'guess)
2260                           (t (setq err (format "%c unknown. " char))
2261                              nil))))
2262       (setq type found)))
2263   (let* ((file (expand-file-name file))
2264          (name (gnus-generate-new-group-name
2265                 (gnus-group-prefixed-name
2266                  (file-name-nondirectory file) '(nndoc "")))))
2267     (gnus-group-make-group
2268      (gnus-group-real-name name)
2269      (list 'nndoc file
2270            (list 'nndoc-address file)
2271            (list 'nndoc-article-type (or type 'guess))))))
2272
2273 (defvar nnweb-type-definition)
2274 (defvar gnus-group-web-type-history nil)
2275 (defvar gnus-group-web-search-history nil)
2276 (defun gnus-group-make-web-group (&optional solid)
2277   "Create an ephemeral nnweb group.
2278 If SOLID (the prefix), create a solid group."
2279   (interactive "P")
2280   (require 'nnweb)
2281   (let* ((group
2282           (if solid (gnus-read-group "Group name: ")
2283             (message-unique-id)))
2284          (default-type (or (car gnus-group-web-type-history)
2285                            (symbol-name (caar nnweb-type-definition))))
2286          (type
2287           (gnus-string-or
2288            (completing-read
2289             (format "Search engine type (default %s): " default-type)
2290             (mapcar (lambda (elem) (list (symbol-name (car elem))))
2291                     nnweb-type-definition)
2292             nil t nil 'gnus-group-web-type-history)
2293            default-type))
2294          (search
2295           (read-string
2296            "Search string: "
2297            (cons (or (car gnus-group-web-search-history) "") 0)
2298            'gnus-group-web-search-history))
2299          (method
2300           `(nnweb ,group (nnweb-search ,search)
2301                   (nnweb-type ,(intern type))
2302                   (nnweb-ephemeral-p t))))
2303     (if solid
2304         (gnus-group-make-group group "nnweb" "" `(,(intern type) ,search))
2305       (gnus-group-read-ephemeral-group
2306        group method t
2307        (cons (current-buffer)
2308              (if (eq major-mode 'gnus-summary-mode) 'summary 'group))))))
2309
2310 (defvar nnwarchive-type-definition)
2311 (defvar gnus-group-warchive-type-history nil)
2312 (defvar gnus-group-warchive-login-history nil)
2313 (defvar gnus-group-warchive-address-history nil)
2314
2315 (defun gnus-group-make-warchive-group ()
2316   "Create a nnwarchive group."
2317   (interactive)
2318   (require 'nnwarchive)
2319   (let* ((group (gnus-read-group "Group name: "))
2320          (default-type (or (car gnus-group-warchive-type-history)
2321                            (symbol-name (caar nnwarchive-type-definition))))
2322          (type
2323           (gnus-string-or
2324            (completing-read
2325             (format "Warchive type (default %s): " default-type)
2326             (mapcar (lambda (elem) (list (symbol-name (car elem))))
2327                     nnwarchive-type-definition)
2328             nil t nil 'gnus-group-warchive-type-history)
2329            default-type))
2330          (address (read-string "Warchive address: "
2331                                nil 'gnus-group-warchive-address-history))
2332          (default-login (or (car gnus-group-warchive-login-history)
2333                             user-mail-address))
2334          (login
2335           (gnus-string-or
2336            (read-string
2337             (format "Warchive login (default %s): " user-mail-address)
2338             default-login 'gnus-group-warchive-login-history)
2339            user-mail-address))
2340          (method
2341           `(nnwarchive ,address 
2342                        (nnwarchive-type ,(intern type))
2343                        (nnwarchive-login ,login))))
2344     (gnus-group-make-group group method)))
2345
2346 (defun gnus-group-make-archive-group (&optional all)
2347   "Create the (ding) Gnus archive group of the most recent articles.
2348 Given a prefix, create a full group."
2349   (interactive "P")
2350   (let ((group (gnus-group-prefixed-name
2351                 (if all "ding.archives" "ding.recent") '(nndir ""))))
2352     (when (gnus-gethash group gnus-newsrc-hashtb)
2353       (error "Archive group already exists"))
2354     (gnus-group-make-group
2355      (gnus-group-real-name group)
2356      (list 'nndir (if all "hpc" "edu")
2357            (list 'nndir-directory
2358                  (if all gnus-group-archive-directory
2359                    gnus-group-recent-archive-directory))))
2360     (gnus-group-add-parameter group (cons 'to-address "ding@gnus.org"))))
2361
2362 (defun gnus-group-make-directory-group (dir)
2363   "Create an nndir group.
2364 The user will be prompted for a directory.  The contents of this
2365 directory will be used as a newsgroup.  The directory should contain
2366 mail messages or news articles in files that have numeric names."
2367   (interactive
2368    (list (read-file-name "Create group from directory: ")))
2369   (unless (file-exists-p dir)
2370     (error "No such directory"))
2371   (unless (file-directory-p dir)
2372     (error "Not a directory"))
2373   (let ((ext "")
2374         (i 0)
2375         group)
2376     (while (or (not group) (gnus-gethash group gnus-newsrc-hashtb))
2377       (setq group
2378             (gnus-group-prefixed-name
2379              (concat (file-name-as-directory (directory-file-name dir))
2380                      ext)
2381              '(nndir "")))
2382       (setq ext (format "<%d>" (setq i (1+ i)))))
2383     (gnus-group-make-group
2384      (gnus-group-real-name group)
2385      (list 'nndir (gnus-group-real-name group) (list 'nndir-directory dir)))))
2386
2387 (defun gnus-group-make-kiboze-group (group address scores)
2388   "Create an nnkiboze group.
2389 The user will be prompted for a name, a regexp to match groups, and
2390 score file entries for articles to include in the group."
2391   (interactive
2392    (list
2393     (read-string "nnkiboze group name: ")
2394     (read-string "Source groups (regexp): ")
2395     (let ((headers (mapcar (lambda (group) (list group))
2396                            '("subject" "from" "number" "date" "message-id"
2397                              "references" "chars" "lines" "xref"
2398                              "followup" "all" "body" "head")))
2399           scores header regexp regexps)
2400       (while (not (equal "" (setq header (completing-read
2401                                           "Match on header: " headers nil t))))
2402         (setq regexps nil)
2403         (while (not (equal "" (setq regexp (read-string
2404                                             (format "Match on %s (regexp): "
2405                                                     header)))))
2406           (push (list regexp nil nil 'r) regexps))
2407         (push (cons header regexps) scores))
2408       scores)))
2409   (gnus-group-make-group group "nnkiboze" address)
2410   (let* ((nnkiboze-current-group group)
2411          (score-file (car (nnkiboze-score-file "")))
2412          (score-dir (file-name-directory score-file)))
2413     (unless (file-exists-p score-dir)
2414       (make-directory score-dir))
2415     (with-temp-file score-file
2416       (let (emacs-lisp-mode-hook)
2417         (pp scores (current-buffer))))))
2418
2419 (defun gnus-group-add-to-virtual (n vgroup)
2420   "Add the current group to a virtual group."
2421   (interactive
2422    (list current-prefix-arg
2423          (completing-read "Add to virtual group: " gnus-newsrc-hashtb nil t
2424                           "nnvirtual:")))
2425   (unless (eq (car (gnus-find-method-for-group vgroup)) 'nnvirtual)
2426     (error "%s is not an nnvirtual group" vgroup))
2427   (gnus-close-group vgroup)
2428   (let* ((groups (gnus-group-process-prefix n))
2429          (method (gnus-info-method (gnus-get-info vgroup))))
2430     (setcar (cdr method)
2431             (concat
2432              (nth 1 method) "\\|"
2433              (mapconcat
2434               (lambda (s)
2435                 (gnus-group-remove-mark s)
2436                 (concat "\\(^" (regexp-quote s) "$\\)"))
2437               groups "\\|"))))
2438   (gnus-group-position-point))
2439
2440 (defun gnus-group-make-empty-virtual (group)
2441   "Create a new, fresh, empty virtual group."
2442   (interactive "sCreate new, empty virtual group: ")
2443   (let* ((method (list 'nnvirtual "^$"))
2444          (pgroup (gnus-group-prefixed-name group method)))
2445     ;; Check whether it exists already.
2446     (when (gnus-gethash pgroup gnus-newsrc-hashtb)
2447       (error "Group %s already exists" pgroup))
2448     ;; Subscribe the new group after the group on the current line.
2449     (gnus-subscribe-group pgroup (gnus-group-group-name) method)
2450     (gnus-group-update-group pgroup)
2451     (forward-line -1)
2452     (gnus-group-position-point)))
2453
2454 (defun gnus-group-enter-directory (dir)
2455   "Enter an ephemeral nneething group."
2456   (interactive "DDirectory to read: ")
2457   (let* ((method (list 'nneething dir '(nneething-read-only t)))
2458          (leaf (gnus-group-prefixed-name
2459                 (file-name-nondirectory (directory-file-name dir))
2460                 method))
2461          (name (gnus-generate-new-group-name leaf)))
2462     (unless (gnus-group-read-ephemeral-group
2463              name method t
2464              (cons (current-buffer)
2465                    (if (eq major-mode 'gnus-summary-mode)
2466                        'summary 'group)))
2467       (error "Couldn't enter %s" dir))))
2468
2469 (eval-and-compile
2470   (autoload 'nnimap-expunge "nnimap")
2471   (autoload 'nnimap-acl-get "nnimap")
2472   (autoload 'nnimap-acl-edit "nnimap"))
2473
2474 (defun gnus-group-nnimap-expunge (group)
2475   "Expunge deleted articles in current nnimap GROUP."
2476   (interactive (list (gnus-group-group-name)))
2477   (let ((mailbox (gnus-group-real-name group)) method)
2478     (unless group
2479       (error "No group on current line"))
2480     (unless (gnus-get-info group)
2481       (error "Killed group; can't be edited"))
2482     (unless (eq 'nnimap (car (setq method (gnus-find-method-for-group group))))
2483       (error "%s is not an nnimap group" group))
2484     (nnimap-expunge mailbox (cadr method))))
2485
2486 (defun gnus-group-nnimap-edit-acl (group)
2487   "Edit the Access Control List of current nnimap GROUP."
2488   (interactive (list (gnus-group-group-name)))
2489   (let ((mailbox (gnus-group-real-name group)) method acl)
2490     (unless group
2491       (error "No group on current line"))
2492     (unless (gnus-get-info group)
2493       (error "Killed group; can't be edited"))
2494     (unless (eq (car (setq method (gnus-find-method-for-group group))) 'nnimap)
2495       (error "%s is not an nnimap group" group))
2496     (unless (setq acl (nnimap-acl-get mailbox (cadr method)))
2497       (error "Server does not support ACL's"))
2498     (gnus-edit-form acl (format "Editing the access control list for `%s'.
2499
2500    An access control list is a list of (identifier . rights) elements.
2501
2502    The identifier string specifies the corresponding user.  The
2503    identifier \"anyone\" is reserved to refer to the universal identity.
2504
2505    Rights is a string listing a (possibly empty) set of alphanumeric
2506    characters, each character listing a set of operations which is being
2507    controlled.  Letters are reserved for ``standard'' rights, listed
2508    below.  Digits are reserved for implementation or site defined rights.
2509
2510    l - lookup (mailbox is visible to LIST/LSUB commands)
2511    r - read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL,
2512        SEARCH, COPY from mailbox)
2513    s - keep seen/unseen information across sessions (STORE \\SEEN flag)
2514    w - write (STORE flags other than \\SEEN and \\DELETED)
2515    i - insert (perform APPEND, COPY into mailbox)
2516    p - post (send mail to submission address for mailbox,
2517        not enforced by IMAP4 itself)
2518    c - create and delete mailbox (CREATE new sub-mailboxes in any
2519        implementation-defined hierarchy, RENAME or DELETE mailbox)
2520    d - delete messages (STORE \\DELETED flag, perform EXPUNGE)
2521    a - administer (perform SETACL)" group)
2522                     `(lambda (form)
2523                        (nnimap-acl-edit
2524                         ,mailbox ',method ',acl form)))))
2525
2526 ;; Group sorting commands
2527 ;; Suggested by Joe Hildebrand <hildjj@idaho.fuentez.com>.
2528
2529 (defun gnus-group-sort-groups (func &optional reverse)
2530   "Sort the group buffer according to FUNC.
2531 When used interactively, the sorting function used will be
2532 determined by the `gnus-group-sort-function' variable.
2533 If REVERSE (the prefix), reverse the sorting order."
2534   (interactive (list gnus-group-sort-function current-prefix-arg))
2535   (funcall gnus-group-sort-alist-function
2536            (gnus-make-sort-function func) reverse)
2537   (gnus-group-list-groups)
2538   (gnus-dribble-touch))
2539
2540 (defun gnus-group-sort-flat (func reverse)
2541   ;; We peel off the dummy group from the alist.
2542   (when func
2543     (when (equal (gnus-info-group (car gnus-newsrc-alist)) "dummy.group")
2544       (pop gnus-newsrc-alist))
2545     ;; Do the sorting.
2546     (setq gnus-newsrc-alist
2547           (sort gnus-newsrc-alist func))
2548     (when reverse
2549       (setq gnus-newsrc-alist (nreverse gnus-newsrc-alist)))
2550     ;; Regenerate the hash table.
2551     (gnus-make-hashtable-from-newsrc-alist)))
2552
2553 (defun gnus-group-sort-groups-by-alphabet (&optional reverse)
2554   "Sort the group buffer alphabetically by group name.
2555 If REVERSE, sort in reverse order."
2556   (interactive "P")
2557   (gnus-group-sort-groups 'gnus-group-sort-by-alphabet reverse))
2558
2559 (defun gnus-group-sort-groups-by-unread (&optional reverse)
2560   "Sort the group buffer by number of unread articles.
2561 If REVERSE, sort in reverse order."
2562   (interactive "P")
2563   (gnus-group-sort-groups 'gnus-group-sort-by-unread reverse))
2564
2565 (defun gnus-group-sort-groups-by-level (&optional reverse)
2566   "Sort the group buffer by group level.
2567 If REVERSE, sort in reverse order."
2568   (interactive "P")
2569   (gnus-group-sort-groups 'gnus-group-sort-by-level reverse))
2570
2571 (defun gnus-group-sort-groups-by-score (&optional reverse)
2572   "Sort the group buffer by group score.
2573 If REVERSE, sort in reverse order."
2574   (interactive "P")
2575   (gnus-group-sort-groups 'gnus-group-sort-by-score reverse))
2576
2577 (defun gnus-group-sort-groups-by-rank (&optional reverse)
2578   "Sort the group buffer by group rank.
2579 If REVERSE, sort in reverse order."
2580   (interactive "P")
2581   (gnus-group-sort-groups 'gnus-group-sort-by-rank reverse))
2582
2583 (defun gnus-group-sort-groups-by-method (&optional reverse)
2584   "Sort the group buffer alphabetically by backend name.
2585 If REVERSE, sort in reverse order."
2586   (interactive "P")
2587   (gnus-group-sort-groups 'gnus-group-sort-by-method reverse))
2588
2589 ;;; Selected group sorting.
2590
2591 (defun gnus-group-sort-selected-groups (n func &optional reverse)
2592   "Sort the process/prefixed groups."
2593   (interactive (list current-prefix-arg gnus-group-sort-function))
2594   (let ((groups (gnus-group-process-prefix n)))
2595     (funcall gnus-group-sort-selected-function
2596              groups (gnus-make-sort-function func) reverse)
2597     (gnus-group-list-groups)))
2598
2599 (defun gnus-group-sort-selected-flat (groups func reverse)
2600   (let (entries infos)
2601     ;; First find all the group entries for these groups.
2602     (while groups
2603       (push (nthcdr 2 (gnus-gethash (pop groups) gnus-newsrc-hashtb))
2604             entries))
2605     ;; Then sort the infos.
2606     (setq infos
2607           (sort
2608            (mapcar
2609             (lambda (entry) (car entry))
2610             (setq entries (nreverse entries)))
2611            func))
2612     (when reverse
2613       (setq infos (nreverse infos)))
2614     ;; Go through all the infos and replace the old entries
2615     ;; with the new infos.
2616     (while infos
2617       (setcar (car entries) (pop infos))
2618       (pop entries))
2619     ;; Update the hashtable.
2620     (gnus-make-hashtable-from-newsrc-alist)))
2621
2622 (defun gnus-group-sort-selected-groups-by-alphabet (&optional n reverse)
2623   "Sort the group buffer alphabetically by group name.
2624 Obeys the process/prefix convention.  If REVERSE (the symbolic prefix),
2625 sort in reverse order."
2626   (interactive (gnus-interactive "P\ny"))
2627   (gnus-group-sort-selected-groups n 'gnus-group-sort-by-alphabet reverse))
2628
2629 (defun gnus-group-sort-selected-groups-by-unread (&optional n reverse)
2630   "Sort the group buffer by number of unread articles.
2631 Obeys the process/prefix convention.  If REVERSE (the symbolic prefix),
2632 sort in reverse order."
2633   (interactive (gnus-interactive "P\ny"))
2634   (gnus-group-sort-selected-groups n 'gnus-group-sort-by-unread reverse))
2635
2636 (defun gnus-group-sort-selected-groups-by-level (&optional n reverse)
2637   "Sort the group buffer by group level.
2638 Obeys the process/prefix convention.  If REVERSE (the symbolic prefix),
2639 sort in reverse order."
2640   (interactive (gnus-interactive "P\ny"))
2641   (gnus-group-sort-selected-groups n 'gnus-group-sort-by-level reverse))
2642
2643 (defun gnus-group-sort-selected-groups-by-score (&optional n reverse)
2644   "Sort the group buffer by group score.
2645 Obeys the process/prefix convention.  If REVERSE (the symbolic prefix),
2646 sort in reverse order."
2647   (interactive (gnus-interactive "P\ny"))
2648   (gnus-group-sort-selected-groups n 'gnus-group-sort-by-score reverse))
2649
2650 (defun gnus-group-sort-selected-groups-by-rank (&optional n reverse)
2651   "Sort the group buffer by group rank.
2652 Obeys the process/prefix convention.  If REVERSE (the symbolic prefix),
2653 sort in reverse order."
2654   (interactive (gnus-interactive "P\ny"))
2655   (gnus-group-sort-selected-groups n 'gnus-group-sort-by-rank reverse))
2656
2657 (defun gnus-group-sort-selected-groups-by-method (&optional n reverse)
2658   "Sort the group buffer alphabetically by backend name.
2659 Obeys the process/prefix convention.  If REVERSE (the symbolic prefix),
2660 sort in reverse order."
2661   (interactive (gnus-interactive "P\ny"))
2662   (gnus-group-sort-selected-groups n 'gnus-group-sort-by-method reverse))
2663
2664 ;;; Sorting predicates.
2665
2666 (defun gnus-group-sort-by-alphabet (info1 info2)
2667   "Sort alphabetically."
2668   (string< (gnus-info-group info1) (gnus-info-group info2)))
2669
2670 (defun gnus-group-sort-by-real-name (info1 info2)
2671   "Sort alphabetically on real (unprefixed) names."
2672   (string< (gnus-group-real-name (gnus-info-group info1))
2673            (gnus-group-real-name (gnus-info-group info2))))
2674
2675 (defun gnus-group-sort-by-unread (info1 info2)
2676   "Sort by number of unread articles."
2677   (let ((n1 (car (gnus-gethash (gnus-info-group info1) gnus-newsrc-hashtb)))
2678         (n2 (car (gnus-gethash (gnus-info-group info2) gnus-newsrc-hashtb))))
2679     (< (or (and (numberp n1) n1) 0)
2680        (or (and (numberp n2) n2) 0))))
2681
2682 (defun gnus-group-sort-by-level (info1 info2)
2683   "Sort by level."
2684   (< (gnus-info-level info1) (gnus-info-level info2)))
2685
2686 (defun gnus-group-sort-by-method (info1 info2)
2687   "Sort alphabetically by backend name."
2688   (string< (symbol-name (car (gnus-find-method-for-group
2689                               (gnus-info-group info1) info1)))
2690            (symbol-name (car (gnus-find-method-for-group
2691                               (gnus-info-group info2) info2)))))
2692
2693 (defun gnus-group-sort-by-score (info1 info2)
2694   "Sort by group score."
2695   (< (gnus-info-score info1) (gnus-info-score info2)))
2696
2697 (defun gnus-group-sort-by-rank (info1 info2)
2698   "Sort by level and score."
2699   (let ((level1 (gnus-info-level info1))
2700         (level2 (gnus-info-level info2)))
2701     (or (< level1 level2)
2702         (and (= level1 level2)
2703              (> (gnus-info-score info1) (gnus-info-score info2))))))
2704
2705 ;;; Clearing data
2706
2707 (defun gnus-group-clear-data (&optional arg)
2708   "Clear all marks and read ranges from the current group."
2709   (interactive "P")
2710   (gnus-group-iterate arg
2711     (lambda (group)
2712       (let (info)
2713         (gnus-info-clear-data (setq info (gnus-get-info group)))
2714         (gnus-get-unread-articles-in-group info (gnus-active group) t)
2715         (when (gnus-group-goto-group group)
2716           (gnus-group-update-group-line))))))
2717
2718 (defun gnus-group-clear-data-on-native-groups ()
2719   "Clear all marks and read ranges from all native groups."
2720   (interactive)
2721   (when (gnus-yes-or-no-p "Really clear all data from almost all groups? ")
2722     (let ((alist (cdr gnus-newsrc-alist))
2723           info)
2724       (while (setq info (pop alist))
2725         (when (gnus-group-native-p (gnus-info-group info))
2726           (gnus-info-clear-data info)))
2727       (gnus-get-unread-articles)
2728       (gnus-dribble-touch)
2729       (when (gnus-y-or-n-p
2730              "Move the cache away to avoid problems in the future? ")
2731         (call-interactively 'gnus-cache-move-cache)))))
2732
2733 (defun gnus-info-clear-data (info)
2734   "Clear all marks and read ranges from INFO."
2735   (let ((group (gnus-info-group info)))
2736     (gnus-undo-register
2737       `(progn
2738          (gnus-info-set-marks ',info ',(gnus-info-marks info) t)
2739          (gnus-info-set-read ',info ',(gnus-info-read info))
2740          (when (gnus-group-goto-group ,group)
2741            (gnus-group-update-group-line))))
2742     (gnus-info-set-read info nil)
2743     (when (gnus-info-marks info)
2744       (gnus-info-set-marks info nil))))
2745
2746 ;; Group catching up.
2747
2748 (defun gnus-group-catchup-current (&optional n all)
2749   "Mark all unread articles in the current newsgroup as read.
2750 If prefix argument N is numeric, the next N newsgroups will be
2751 caught up.  If ALL is non-nil, marked articles will also be marked as
2752 read.  Cross references (Xref: header) of articles are ignored.
2753 The number of newsgroups that this function was unable to catch
2754 up is returned."
2755   (interactive "P")
2756   (let ((groups (gnus-group-process-prefix n))
2757         (ret 0)
2758         group)
2759     (unless groups (error "No groups selected"))
2760     (if (not
2761          (or (not gnus-interactive-catchup) ;Without confirmation?
2762              gnus-expert-user
2763              (gnus-y-or-n-p
2764               (format
2765                (if all
2766                    "Do you really want to mark all articles in %s as read? "
2767                  "Mark all unread articles in %s as read? ")
2768                (if (= (length groups) 1)
2769                    (car groups)
2770                  (format "these %d groups" (length groups)))))))
2771         n
2772       (while (setq group (pop groups))
2773         (gnus-group-remove-mark group)
2774         ;; Virtual groups have to be given special treatment.
2775         (let ((method (gnus-find-method-for-group group)))
2776           (when (eq 'nnvirtual (car method))
2777             (nnvirtual-catchup-group
2778              (gnus-group-real-name group) (nth 1 method) all)))
2779         (if (>= (gnus-group-level group) gnus-level-zombie)
2780             (gnus-message 2 "Dead groups can't be caught up")
2781           (if (prog1
2782                   (gnus-group-goto-group group)
2783                 (gnus-group-catchup group all))
2784               (gnus-group-update-group-line)
2785             (setq ret (1+ ret)))))
2786       (gnus-group-next-unread-group 1)
2787       ret)))
2788
2789 (defun gnus-group-catchup-current-all (&optional n)
2790   "Mark all articles in current newsgroup as read.
2791 Cross references (Xref: header) of articles are ignored."
2792   (interactive "P")
2793   (gnus-group-catchup-current n 'all))
2794
2795 (defun gnus-group-catchup (group &optional all)
2796   "Mark all articles in GROUP as read.
2797 If ALL is non-nil, all articles are marked as read.
2798 The return value is the number of articles that were marked as read,
2799 or nil if no action could be taken."
2800   (let* ((entry (gnus-gethash group gnus-newsrc-hashtb))
2801          (num (car entry)))
2802     ;; Remove entries for this group.
2803     (nnmail-purge-split-history (gnus-group-real-name group))
2804     ;; Do the updating only if the newsgroup isn't killed.
2805     (if (not (numberp (car entry)))
2806         (gnus-message 1 "Can't catch up %s; non-active group" group)
2807       ;; Do auto-expirable marks if that's required.
2808       (when (gnus-group-auto-expirable-p group)
2809         (gnus-add-marked-articles
2810          group 'expire (gnus-list-of-unread-articles group))
2811         (when all
2812           (let ((marks (nth 3 (nth 2 entry))))
2813             (gnus-add-marked-articles
2814              group 'expire (gnus-uncompress-range (cdr (assq 'tick marks))))
2815             (gnus-add-marked-articles
2816              group 'expire (gnus-uncompress-range (cdr (assq 'tick marks)))))))
2817       (when entry
2818         (gnus-update-read-articles group nil)
2819         ;; Also nix out the lists of marks and dormants.
2820         (when all
2821           (gnus-add-marked-articles group 'tick nil nil 'force)
2822           (gnus-add-marked-articles group 'dormant nil nil 'force))
2823         (let ((gnus-newsgroup-name group))
2824           (gnus-run-hooks 'gnus-group-catchup-group-hook))
2825         num))))
2826
2827 (defun gnus-group-expire-articles (&optional n)
2828   "Expire all expirable articles in the current newsgroup."
2829   (interactive "P")
2830   (let ((groups (gnus-group-process-prefix n))
2831         group)
2832     (unless groups
2833       (error "No groups to expire"))
2834     (while (setq group (pop groups))
2835       (gnus-group-remove-mark group)
2836       (gnus-group-expire-articles-1 group)
2837       (gnus-dribble-touch)
2838       (gnus-group-position-point))))
2839
2840 (defun gnus-group-expire-articles-1 (group)
2841   (when (gnus-check-backend-function 'request-expire-articles group)
2842     (gnus-message 6 "Expiring articles in %s..." group)
2843     (let* ((info (gnus-get-info group))
2844            (expirable (if (gnus-group-total-expirable-p group)
2845                           (cons nil (gnus-list-of-read-articles group))
2846                         (assq 'expire (gnus-info-marks info))))
2847            (expiry-wait (gnus-group-find-parameter group 'expiry-wait))
2848            (nnmail-expiry-target
2849             (or (gnus-group-find-parameter group 'expiry-target)
2850                 nnmail-expiry-target)))
2851       (when expirable
2852         (gnus-check-group group)
2853         (setcdr
2854          expirable
2855          (gnus-compress-sequence
2856           (if expiry-wait
2857               ;; We set the expiry variables to the group
2858               ;; parameter.
2859               (let ((nnmail-expiry-wait-function nil)
2860                     (nnmail-expiry-wait expiry-wait))
2861                 (gnus-request-expire-articles
2862                  (gnus-uncompress-sequence (cdr expirable)) group))
2863             ;; Just expire using the normal expiry values.
2864             (gnus-request-expire-articles
2865              (gnus-uncompress-sequence (cdr expirable)) group))))
2866         (gnus-close-group group))
2867       (gnus-message 6 "Expiring articles in %s...done" group)
2868       ;; Return the list of un-expired articles.
2869       (cdr expirable))))
2870
2871 (defun gnus-group-expire-all-groups ()
2872   "Expire all expirable articles in all newsgroups."
2873   (interactive)
2874   (save-excursion
2875     (gnus-message 5 "Expiring...")
2876     (let ((gnus-group-marked (mapcar (lambda (info) (gnus-info-group info))
2877                                      (cdr gnus-newsrc-alist))))
2878       (gnus-group-expire-articles nil)))
2879   (gnus-group-position-point)
2880   (gnus-message 5 "Expiring...done"))
2881
2882 (defun gnus-group-set-current-level (n level)
2883   "Set the level of the next N groups to LEVEL."
2884   (interactive
2885    (list
2886     current-prefix-arg
2887     (string-to-int
2888      (let ((s (read-string
2889                (format "Level (default %s): "
2890                        (or (gnus-group-group-level)
2891                            gnus-level-default-subscribed)))))
2892        (if (string-match "^\\s-*$" s)
2893            (int-to-string (or (gnus-group-group-level)
2894                               gnus-level-default-subscribed))
2895          s)))))
2896   (unless (and (>= level 1) (<= level gnus-level-killed))
2897     (error "Invalid level: %d" level))
2898   (let ((groups (gnus-group-process-prefix n))
2899         group)
2900     (while (setq group (pop groups))
2901       (gnus-group-remove-mark group)
2902       (gnus-message 6 "Changed level of %s from %d to %d"
2903                     group (or (gnus-group-group-level) gnus-level-killed)
2904                     level)
2905       (gnus-group-change-level
2906        group level (or (gnus-group-group-level) gnus-level-killed))
2907       (gnus-group-update-group-line)))
2908   (gnus-group-position-point))
2909
2910 (defun gnus-group-unsubscribe (&optional n)
2911   "Unsubscribe the current group."
2912   (interactive "P")
2913   (gnus-group-unsubscribe-current-group n 'unsubscribe))
2914
2915 (defun gnus-group-subscribe (&optional n)
2916   "Subscribe the current group."
2917   (interactive "P")
2918   (gnus-group-unsubscribe-current-group n 'subscribe))
2919
2920 (defun gnus-group-unsubscribe-current-group (&optional n do-sub)
2921   "Toggle subscription of the current group.
2922 If given numerical prefix, toggle the N next groups."
2923   (interactive "P")
2924   (let ((groups (gnus-group-process-prefix n))
2925         group)
2926     (while groups
2927       (setq group (car groups)
2928             groups (cdr groups))
2929       (gnus-group-remove-mark group)
2930       (gnus-group-unsubscribe-group
2931        group
2932        (cond
2933         ((eq do-sub 'unsubscribe)
2934          gnus-level-default-unsubscribed)
2935         ((eq do-sub 'subscribe)
2936          gnus-level-default-subscribed)
2937         ((<= (gnus-group-group-level) gnus-level-subscribed)
2938          gnus-level-default-unsubscribed)
2939         (t
2940          gnus-level-default-subscribed))
2941        t)
2942       (gnus-group-update-group-line))
2943     (gnus-group-next-group 1)))
2944
2945 (defun gnus-group-unsubscribe-group (group &optional level silent)
2946   "Toggle subscription to GROUP.
2947 Killed newsgroups are subscribed.  If SILENT, don't try to update the
2948 group line."
2949   (interactive
2950    (list (completing-read
2951           "Group: " gnus-active-hashtb nil
2952           (gnus-read-active-file-p)
2953           nil
2954           'gnus-group-history)))
2955   (let ((newsrc (gnus-gethash group gnus-newsrc-hashtb)))
2956     (cond
2957      ((string-match "^[ \t]*$" group)
2958       (error "Empty group name"))
2959      (newsrc
2960       ;; Toggle subscription flag.
2961       (gnus-group-change-level
2962        newsrc (if level level (if (<= (gnus-info-level (nth 2 newsrc))
2963                                       gnus-level-subscribed)
2964                                   (1+ gnus-level-subscribed)
2965                                 gnus-level-default-subscribed)))
2966       (unless silent
2967         (gnus-group-update-group group)))
2968      ((and (stringp group)
2969            (or (not (gnus-read-active-file-p))
2970                (gnus-active group)))
2971       ;; Add new newsgroup.
2972       (gnus-group-change-level
2973        group
2974        (if level level gnus-level-default-subscribed)
2975        (or (and (member group gnus-zombie-list)
2976                 gnus-level-zombie)
2977            gnus-level-killed)
2978        (when (gnus-group-group-name)
2979          (gnus-gethash (gnus-group-group-name) gnus-newsrc-hashtb)))
2980       (unless silent
2981         (gnus-group-update-group group)))
2982      (t (error "No such newsgroup: %s" group)))
2983     (gnus-group-position-point)))
2984
2985 (defun gnus-group-transpose-groups (n)
2986   "Move the current newsgroup up N places.
2987 If given a negative prefix, move down instead.  The difference between
2988 N and the number of steps taken is returned."
2989   (interactive "p")
2990   (unless (gnus-group-group-name)
2991     (error "No group on current line"))
2992   (gnus-group-kill-group 1)
2993   (prog1
2994       (forward-line (- n))
2995     (gnus-group-yank-group)
2996     (gnus-group-position-point)))
2997
2998 (defun gnus-group-kill-all-zombies (&optional dummy)
2999   "Kill all zombie newsgroups.
3000 The optional DUMMY should always be nil."
3001   (interactive (list (not (gnus-yes-or-no-p "Really kill all zombies? "))))
3002   (unless dummy
3003     (setq gnus-killed-list (nconc gnus-zombie-list gnus-killed-list))
3004     (setq gnus-zombie-list nil)
3005     (gnus-dribble-touch)
3006     (gnus-group-list-groups)))
3007
3008 (defun gnus-group-kill-region (begin end)
3009   "Kill newsgroups in current region (excluding current point).
3010 The killed newsgroups can be yanked by using \\[gnus-group-yank-group]."
3011   (interactive "r")
3012   (let ((lines
3013          ;; Count lines.
3014          (save-excursion
3015            (count-lines
3016             (progn
3017               (goto-char begin)
3018               (beginning-of-line)
3019               (point))
3020             (progn
3021               (goto-char end)
3022               (beginning-of-line)
3023               (point))))))
3024     (goto-char begin)
3025     (beginning-of-line)                 ;Important when LINES < 1
3026     (gnus-group-kill-group lines)))
3027
3028 (defun gnus-group-kill-group (&optional n discard)
3029   "Kill the next N groups.
3030 The killed newsgroups can be yanked by using \\[gnus-group-yank-group].
3031 However, only groups that were alive can be yanked; already killed
3032 groups or zombie groups can't be yanked.
3033 The return value is the name of the group that was killed, or a list
3034 of groups killed."
3035   (interactive "P")
3036   (let ((buffer-read-only nil)
3037         (groups (gnus-group-process-prefix n))
3038         group entry level out)
3039     (if (< (length groups) 10)
3040         ;; This is faster when there are few groups.
3041         (while groups
3042           (push (setq group (pop groups)) out)
3043           (gnus-group-remove-mark group)
3044           (setq level (gnus-group-group-level))
3045           (gnus-delete-line)
3046           (when (and (not discard)
3047                      (setq entry (gnus-gethash group gnus-newsrc-hashtb)))
3048             (gnus-undo-register
3049               `(progn
3050                  (gnus-group-goto-group ,(gnus-group-group-name))
3051                  (gnus-group-yank-group)))
3052             (push (cons (car entry) (nth 2 entry))
3053                   gnus-list-of-killed-groups))
3054           (gnus-group-change-level
3055            (if entry entry group) gnus-level-killed (if entry nil level))
3056           (message "Killed group %s" group))
3057       ;; If there are lots and lots of groups to be killed, we use
3058       ;; this thing instead.
3059       (let (entry)
3060         (setq groups (nreverse groups))
3061         (while groups
3062           (gnus-group-remove-mark (setq group (pop groups)))
3063           (gnus-delete-line)
3064           (push group gnus-killed-list)
3065           (setq gnus-newsrc-alist
3066                 (delq (assoc group gnus-newsrc-alist)
3067                       gnus-newsrc-alist))
3068           (when gnus-group-change-level-function
3069             (funcall gnus-group-change-level-function
3070                      group gnus-level-killed 3))
3071           (cond
3072            ((setq entry (gnus-gethash group gnus-newsrc-hashtb))
3073             (push (cons (car entry) (nth 2 entry))
3074                   gnus-list-of-killed-groups)
3075             (setcdr (cdr entry) (cdddr entry)))
3076            ((member group gnus-zombie-list)
3077             (setq gnus-zombie-list (delete group gnus-zombie-list))))
3078           ;; There may be more than one instance displayed.
3079           (while (gnus-group-goto-group group)
3080             (gnus-delete-line)))
3081         (gnus-make-hashtable-from-newsrc-alist)))
3082
3083     (gnus-group-position-point)
3084     (if (< (length out) 2) (car out) (nreverse out))))
3085
3086 (defun gnus-group-yank-group (&optional arg)
3087   "Yank the last newsgroups killed with \\[gnus-group-kill-group], inserting it before the current newsgroup.
3088 The numeric ARG specifies how many newsgroups are to be yanked.  The
3089 name of the newsgroup yanked is returned, or (if several groups are
3090 yanked) a list of yanked groups is returned."
3091   (interactive "p")
3092   (setq arg (or arg 1))
3093   (let (info group prev out)
3094     (while (>= (decf arg) 0)
3095       (when (not (setq info (pop gnus-list-of-killed-groups)))
3096         (error "No more newsgroups to yank"))
3097       (push (setq group (nth 1 info)) out)
3098       ;; Find which newsgroup to insert this one before - search
3099       ;; backward until something suitable is found.  If there are no
3100       ;; other newsgroups in this buffer, just make this newsgroup the
3101       ;; first newsgroup.
3102       (setq prev (gnus-group-group-name))
3103       (gnus-group-change-level
3104        info (gnus-info-level (cdr info)) gnus-level-killed
3105        (and prev (gnus-gethash prev gnus-newsrc-hashtb))
3106        t)
3107       (gnus-group-insert-group-line-info group)
3108       (gnus-undo-register
3109         `(when (gnus-group-goto-group ,group)
3110            (gnus-group-kill-group 1))))
3111     (forward-line -1)
3112     (gnus-group-position-point)
3113     (if (< (length out) 2) (car out) (nreverse out))))
3114
3115 (defun gnus-group-kill-level (level)
3116   "Kill all groups that is on a certain LEVEL."
3117   (interactive "nKill all groups on level: ")
3118   (cond
3119    ((= level gnus-level-zombie)
3120     (setq gnus-killed-list
3121           (nconc gnus-zombie-list gnus-killed-list))
3122     (setq gnus-zombie-list nil))
3123    ((and (< level gnus-level-zombie)
3124          (> level 0)
3125          (or gnus-expert-user
3126              (gnus-yes-or-no-p
3127               (format
3128                "Do you really want to kill all groups on level %d? "
3129                level))))
3130     (let* ((prev gnus-newsrc-alist)
3131            (alist (cdr prev)))
3132       (while alist
3133         (if (= (gnus-info-level (car alist)) level)
3134             (progn
3135               (push (gnus-info-group (car alist)) gnus-killed-list)
3136               (setcdr prev (cdr alist)))
3137           (setq prev alist))
3138         (setq alist (cdr alist)))
3139       (gnus-make-hashtable-from-newsrc-alist)
3140       (gnus-group-list-groups)))
3141    (t
3142     (error "Can't kill; invalid level: %d" level))))
3143
3144 (defun gnus-group-list-all-groups (&optional arg)
3145   "List all newsgroups with level ARG or lower.
3146 Default is gnus-level-unsubscribed, which lists all subscribed and most
3147 unsubscribed groups."
3148   (interactive "P")
3149   (gnus-group-list-groups (or arg gnus-level-unsubscribed) t))
3150
3151 ;; Redefine this to list ALL killed groups if prefix arg used.
3152 ;; Rewritten by engstrom@src.honeywell.com (Eric Engstrom).
3153 (defun gnus-group-list-killed (&optional arg)
3154   "List all killed newsgroups in the group buffer.
3155 If ARG is non-nil, list ALL killed groups known to Gnus.  This may
3156 entail asking the server for the groups."
3157   (interactive "P")
3158   ;; Find all possible killed newsgroups if arg.
3159   (when arg
3160     (gnus-get-killed-groups))
3161   (if (not gnus-killed-list)
3162       (gnus-message 6 "No killed groups")
3163     (let (gnus-group-list-mode)
3164       (funcall gnus-group-prepare-function
3165                gnus-level-killed t gnus-level-killed))
3166     (goto-char (point-min)))
3167   (gnus-group-position-point))
3168
3169 (defun gnus-group-list-zombies ()
3170   "List all zombie newsgroups in the group buffer."
3171   (interactive)
3172   (if (not gnus-zombie-list)
3173       (gnus-message 6 "No zombie groups")
3174     (let (gnus-group-list-mode)
3175       (funcall gnus-group-prepare-function
3176                gnus-level-zombie t gnus-level-zombie))
3177     (goto-char (point-min)))
3178   (gnus-group-position-point))
3179
3180 (defun gnus-group-list-active ()
3181   "List all groups that are available from the server(s)."
3182   (interactive)
3183   ;; First we make sure that we have really read the active file.
3184   (unless (gnus-read-active-file-p)
3185     (let ((gnus-read-active-file t)
3186           (gnus-agent nil))             ; Trick the agent into ignoring the active file.
3187       (gnus-read-active-file)))
3188   ;; Find all groups and sort them.
3189   (let ((groups
3190          (sort
3191           (let (list)
3192             (mapatoms
3193              (lambda (sym)
3194                (and (boundp sym)
3195                     (symbol-value sym)
3196                     (push (symbol-name sym) list)))
3197              gnus-active-hashtb)
3198             list)
3199           'string<))
3200         (buffer-read-only nil)
3201         group)
3202     (erase-buffer)
3203     (while groups
3204       (setq group (pop groups))
3205       (gnus-add-text-properties
3206        (point) (prog1 (1+ (point))
3207                  (insert "       *: "
3208                          (gnus-group-name-decode group 
3209                                                  (gnus-group-name-charset
3210                                                   nil group))
3211                          "\n"))
3212        (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb)
3213              'gnus-unread t
3214              'gnus-level (inline (gnus-group-level group)))))
3215     (goto-char (point-min))))
3216
3217 (defun gnus-activate-all-groups (level)
3218   "Activate absolutely all groups."
3219   (interactive (list gnus-level-unsubscribed))
3220   (let ((gnus-activate-level level)
3221         (gnus-activate-foreign-newsgroups level))
3222     (gnus-group-get-new-news)))
3223
3224 (defun gnus-group-get-new-news (&optional arg)
3225   "Get newly arrived articles.
3226 If ARG is a number, it specifies which levels you are interested in
3227 re-scanning.  If ARG is non-nil and not a number, this will force
3228 \"hard\" re-reading of the active files from all servers."
3229   (interactive "P")
3230   (require 'nnmail)
3231   (let ((gnus-inhibit-demon t)
3232         ;; Binding this variable will inhibit multiple fetchings
3233         ;; of the same mail source.
3234         (nnmail-fetched-sources (list t)))
3235     (gnus-run-hooks 'gnus-get-new-news-hook)
3236
3237     ;; Read any slave files.
3238     (unless gnus-slave
3239       (gnus-master-read-slave-newsrc))
3240
3241     ;; We might read in new NoCeM messages here.
3242     (when (and gnus-use-nocem
3243                (null arg))
3244       (gnus-nocem-scan-groups))
3245     ;; If ARG is not a number, then we read the active file.
3246     (when (and arg (not (numberp arg)))
3247       (let ((gnus-read-active-file t))
3248         (gnus-read-active-file))
3249       (setq arg nil)
3250
3251       ;; If the user wants it, we scan for new groups.
3252       (when (eq gnus-check-new-newsgroups 'always)
3253         (gnus-find-new-newsgroups)))
3254
3255     (setq arg (gnus-group-default-level arg t))
3256     (if (and gnus-read-active-file (not arg))
3257         (progn
3258           (gnus-read-active-file)
3259           (gnus-get-unread-articles arg))
3260       (let ((gnus-read-active-file (if arg nil gnus-read-active-file)))
3261         (gnus-get-unread-articles arg)))
3262     (gnus-run-hooks 'gnus-after-getting-new-news-hook)
3263     (gnus-group-list-groups (and (numberp arg)
3264                                  (max (car gnus-group-list-mode) arg)))))
3265
3266 (defun gnus-group-get-new-news-this-group (&optional n dont-scan)
3267   "Check for newly arrived news in the current group (and the N-1 next groups).
3268 The difference between N and the number of newsgroup checked is returned.
3269 If N is negative, this group and the N-1 previous groups will be checked."
3270   (interactive "P")
3271   (let* ((groups (gnus-group-process-prefix n))
3272          (ret (if (numberp n) (- n (length groups)) 0))
3273          (beg (unless n
3274                 (point)))
3275          group method
3276          (gnus-inhibit-demon t)
3277          ;; Binding this variable will inhibit multiple fetchings
3278          ;; of the same mail source.
3279          (nnmail-fetched-sources (list t)))
3280     (gnus-run-hooks 'gnus-get-new-news-hook)
3281     (while (setq group (pop groups))
3282       (gnus-group-remove-mark group)
3283       ;; Bypass any previous denials from the server.
3284       (gnus-remove-denial (setq method (gnus-find-method-for-group group)))
3285       (if (gnus-activate-group group (if dont-scan nil 'scan))
3286           (progn
3287             (gnus-get-unread-articles-in-group
3288              (gnus-get-info group) (gnus-active group) t)
3289             (unless (gnus-virtual-group-p group)
3290               (gnus-close-group group))
3291             (when gnus-agent
3292               (gnus-agent-save-group-info
3293                method (gnus-group-real-name group) (gnus-active group)))
3294             (gnus-group-update-group group))
3295         (if (eq (gnus-server-status (gnus-find-method-for-group group))
3296                 'denied)
3297             (gnus-error 3 "Server denied access")
3298           (gnus-error 3 "%s error: %s" group (gnus-status-message group)))))
3299     (when beg
3300       (goto-char beg))
3301     (when gnus-goto-next-group-when-activating
3302       (gnus-group-next-unread-group 1 t))
3303     (gnus-summary-position-point)
3304     ret))
3305
3306 (defun gnus-group-fetch-faq (group &optional faq-dir)
3307   "Fetch the FAQ for the current group.
3308 If given a prefix argument, prompt for the FAQ dir
3309 to use."
3310   (interactive
3311    (list
3312     (gnus-group-group-name)
3313     (when current-prefix-arg
3314       (completing-read
3315        "Faq dir: " (and (listp gnus-group-faq-directory)
3316                         (mapcar (lambda (file) (list file))
3317                                 gnus-group-faq-directory))))))
3318   (unless group
3319     (error "No group name given"))
3320   (let ((dirs (or faq-dir gnus-group-faq-directory))
3321         dir found file)
3322     (unless (listp dirs)
3323       (setq dirs (list dirs)))
3324     (while (and (not found)
3325                 (setq dir (pop dirs)))
3326       (let ((name (gnus-group-real-name group)))
3327         (setq file (concat (file-name-as-directory dir) name)))
3328       (if (not (file-exists-p file))
3329           (gnus-message 1 "No such file: %s" file)
3330         (let ((enable-local-variables nil))
3331           (find-file file)
3332           (setq found t))))))
3333
3334 (defun gnus-group-describe-group (force &optional group)
3335   "Display a description of the current newsgroup."
3336   (interactive (list current-prefix-arg (gnus-group-group-name)))
3337   (let* ((method (gnus-find-method-for-group group))
3338          (mname (gnus-group-prefixed-name "" method))
3339          desc)
3340     (when (and force
3341                gnus-description-hashtb)
3342       (gnus-sethash mname nil gnus-description-hashtb))
3343     (unless group
3344       (error "No group name given"))
3345     (when (or (and gnus-description-hashtb
3346                    ;; We check whether this group's method has been
3347                    ;; queried for a description file.
3348                    (gnus-gethash mname gnus-description-hashtb))
3349               (setq desc (gnus-group-get-description group))
3350               (gnus-read-descriptions-file method))
3351       (gnus-message 1
3352                     (or desc (gnus-gethash group gnus-description-hashtb)
3353                         "No description available")))))
3354
3355 ;; Suggested by Per Abrahamsen <amanda@iesd.auc.dk>.
3356 (defun gnus-group-describe-all-groups (&optional force)
3357   "Pop up a buffer with descriptions of all newsgroups."
3358   (interactive "P")
3359   (when force
3360     (setq gnus-description-hashtb nil))
3361   (when (not (or gnus-description-hashtb
3362                  (gnus-read-all-descriptions-files)))
3363     (error "Couldn't request descriptions file"))
3364   (let ((buffer-read-only nil)
3365         b)
3366     (erase-buffer)
3367     (mapatoms
3368      (lambda (group)
3369        (setq b (point))
3370        (let ((charset (gnus-group-name-charset nil (symbol-name group))))
3371          (insert (format "      *: %-20s %s\n" 
3372                          (gnus-group-name-decode
3373                           (symbol-name group) charset)
3374                          (gnus-group-name-decode
3375                           (symbol-value group) charset))))
3376        (gnus-add-text-properties
3377         b (1+ b) (list 'gnus-group group
3378                        'gnus-unread t 'gnus-marked nil
3379                        'gnus-level (1+ gnus-level-subscribed))))
3380      gnus-description-hashtb)
3381     (goto-char (point-min))
3382     (gnus-group-position-point)))
3383
3384 ;; Suggested by Daniel Quinlan <quinlan@best.com>.
3385 (defun gnus-group-apropos (regexp &optional search-description)
3386   "List all newsgroups that have names that match a regexp."
3387   (interactive "sGnus apropos (regexp): ")
3388   (let ((prev "")
3389         (obuf (current-buffer))
3390         groups des)
3391     ;; Go through all newsgroups that are known to Gnus.
3392     (mapatoms
3393      (lambda (group)
3394        (and (symbol-name group)
3395             (string-match regexp (symbol-name group))
3396             (symbol-value group)
3397             (push (symbol-name group) groups)))
3398      gnus-active-hashtb)
3399     ;; Also go through all descriptions that are known to Gnus.
3400     (when search-description
3401       (mapatoms
3402        (lambda (group)
3403          (and (string-match regexp (symbol-value group))
3404               (push (symbol-name group) groups)))
3405        gnus-description-hashtb))
3406     (if (not groups)
3407         (gnus-message 3 "No groups matched \"%s\"." regexp)
3408       ;; Print out all the groups.
3409       (save-excursion
3410         (pop-to-buffer "*Gnus Help*")
3411         (buffer-disable-undo)
3412         (erase-buffer)
3413         (setq groups (sort groups 'string<))
3414         (while groups
3415           ;; Groups may be entered twice into the list of groups.
3416           (when (not (string= (car groups) prev))
3417             (setq prev (car groups))
3418             (let ((charset (gnus-group-name-charset nil prev)))
3419               (insert (gnus-group-name-decode prev charset) "\n")
3420               (when (and gnus-description-hashtb
3421                          (setq des (gnus-gethash (car groups)
3422                                                  gnus-description-hashtb)))
3423                 (insert "  " (gnus-group-name-decode des charset) "\n"))))
3424           (setq groups (cdr groups)))
3425         (goto-char (point-min))))
3426     (pop-to-buffer obuf)))
3427
3428 (defun gnus-group-description-apropos (regexp)
3429   "List all newsgroups that have names or descriptions that match a regexp."
3430   (interactive "sGnus description apropos (regexp): ")
3431   (when (not (or gnus-description-hashtb
3432                  (gnus-read-all-descriptions-files)))
3433     (error "Couldn't request descriptions file"))
3434   (gnus-group-apropos regexp t))
3435
3436 ;; Suggested by Per Abrahamsen <amanda@iesd.auc.dk>.
3437 (defun gnus-group-list-matching (level regexp &optional all lowest)
3438   "List all groups with unread articles that match REGEXP.
3439 If the prefix LEVEL is non-nil, it should be a number that says which
3440 level to cut off listing groups.
3441 If ALL, also list groups with no unread articles.
3442 If LOWEST, don't list groups with level lower than LOWEST.
3443
3444 This command may read the active file."
3445   (interactive "P\nsList newsgroups matching: ")
3446   ;; First make sure active file has been read.
3447   (when (and level
3448              (> (prefix-numeric-value level) gnus-level-killed))
3449     (gnus-get-killed-groups))
3450   (funcall gnus-group-prepare-function
3451    (or level gnus-level-subscribed) (and all t) (or lowest 1) regexp)
3452   (goto-char (point-min))
3453   (gnus-group-position-point))
3454
3455 (defun gnus-group-list-all-matching (level regexp &optional lowest)
3456   "List all groups that match REGEXP.
3457 If the prefix LEVEL is non-nil, it should be a number that says which
3458 level to cut off listing groups.
3459 If LOWEST, don't list groups with level lower than LOWEST."
3460   (interactive "P\nsList newsgroups matching: ")
3461   (when level
3462     (setq level (prefix-numeric-value level)))
3463   (gnus-group-list-matching (or level gnus-level-killed) regexp t lowest))
3464
3465 ;; Suggested by Jack Vinson <vinson@unagi.cis.upenn.edu>.
3466 (defun gnus-group-save-newsrc (&optional force)
3467   "Save the Gnus startup files.
3468 If FORCE, force saving whether it is necessary or not."
3469   (interactive "P")
3470   (gnus-save-newsrc-file force))
3471
3472 (defun gnus-group-restart (&optional arg)
3473   "Force Gnus to read the .newsrc file."
3474   (interactive "P")
3475   (when (gnus-yes-or-no-p
3476          (format "Are you sure you want to restart Gnus? "))
3477     (gnus-save-newsrc-file)
3478     (gnus-clear-system)
3479     (gnus)))
3480
3481 (defun gnus-group-read-init-file ()
3482   "Read the Gnus elisp init file."
3483   (interactive)
3484   (gnus-read-init-file)
3485   (gnus-message 5 "Read %s" gnus-init-file))
3486
3487 (defun gnus-group-check-bogus-groups (&optional silent)
3488   "Check bogus newsgroups.
3489 If given a prefix, don't ask for confirmation before removing a bogus
3490 group."
3491   (interactive "P")
3492   (gnus-check-bogus-newsgroups (and (not silent) (not gnus-expert-user)))
3493   (gnus-group-list-groups))
3494
3495 (defun gnus-group-find-new-groups (&optional arg)
3496   "Search for new groups and add them.
3497 Each new group will be treated with `gnus-subscribe-newsgroup-method.'
3498 With 1 C-u, use the `ask-server' method to query the server for new
3499 groups.
3500 With 2 C-u's, use most complete method possible to query the server
3501 for new groups, and subscribe the new groups as zombies."
3502   (interactive "p")
3503   (gnus-find-new-newsgroups (or arg 1))
3504   (gnus-group-list-groups))
3505
3506 (defun gnus-group-edit-global-kill (&optional article group)
3507   "Edit the global kill file.
3508 If GROUP, edit that local kill file instead."
3509   (interactive "P")
3510   (setq gnus-current-kill-article article)
3511   (gnus-kill-file-edit-file group)
3512   (gnus-message
3513    6
3514    (substitute-command-keys
3515     (format "Editing a %s kill file (Type \\[gnus-kill-file-exit] to exit)"
3516             (if group "local" "global")))))
3517
3518 (defun gnus-group-edit-local-kill (article group)
3519   "Edit a local kill file."
3520   (interactive (list nil (gnus-group-group-name)))
3521   (gnus-group-edit-global-kill article group))
3522
3523 (defun gnus-group-force-update ()
3524   "Update `.newsrc' file."
3525   (interactive)
3526   (gnus-save-newsrc-file))
3527
3528 (defun gnus-group-suspend ()
3529   "Suspend the current Gnus session.
3530 In fact, cleanup buffers except for group mode buffer.
3531 The hook gnus-suspend-gnus-hook is called before actually suspending."
3532   (interactive)
3533   (gnus-run-hooks 'gnus-suspend-gnus-hook)
3534   ;; Kill Gnus buffers except for group mode buffer.
3535   (let ((group-buf (get-buffer gnus-group-buffer)))
3536     (mapcar (lambda (buf)
3537               (unless (member buf (list group-buf gnus-dribble-buffer))
3538                 (kill-buffer buf)))
3539             (gnus-buffers))
3540     (gnus-kill-gnus-frames)
3541     (when group-buf
3542       (bury-buffer group-buf)
3543       (delete-windows-on group-buf t))))
3544
3545 (defun gnus-group-clear-dribble ()
3546   "Clear all information from the dribble buffer."
3547   (interactive)
3548   (gnus-dribble-clear)
3549   (gnus-message 7 "Cleared dribble buffer"))
3550
3551 (defun gnus-group-exit ()
3552   "Quit reading news after updating .newsrc.eld and .newsrc.
3553 The hook `gnus-exit-gnus-hook' is called before actually exiting."
3554   (interactive)
3555   (when
3556       (or noninteractive                ;For gnus-batch-kill
3557           (not gnus-interactive-exit)   ;Without confirmation
3558           gnus-expert-user
3559           (gnus-y-or-n-p "Are you sure you want to quit reading news? "))
3560     (gnus-run-hooks 'gnus-exit-gnus-hook)
3561     ;; Offer to save data from non-quitted summary buffers.
3562     (gnus-offer-save-summaries)
3563     ;; Save the newsrc file(s).
3564     (gnus-save-newsrc-file)
3565     ;; Kill-em-all.
3566     (gnus-close-backends)
3567     ;; Reset everything.
3568     (gnus-clear-system)
3569     ;; Allow the user to do things after cleaning up.
3570     (gnus-run-hooks 'gnus-after-exiting-gnus-hook)))
3571
3572 (defun gnus-group-quit ()
3573   "Quit reading news without updating .newsrc.eld or .newsrc.
3574 The hook `gnus-exit-gnus-hook' is called before actually exiting."
3575   (interactive)
3576   (when (or noninteractive              ;For gnus-batch-kill
3577             (zerop (buffer-size))
3578             (not (gnus-server-opened gnus-select-method))
3579             gnus-expert-user
3580             (not gnus-current-startup-file)
3581             (gnus-yes-or-no-p
3582              (format "Quit reading news without saving %s? "
3583                      (file-name-nondirectory gnus-current-startup-file))))
3584     (gnus-run-hooks 'gnus-exit-gnus-hook)
3585     (gnus-configure-windows 'group t)
3586     (gnus-dribble-save)
3587     (gnus-close-backends)
3588     (gnus-clear-system)
3589     (gnus-kill-buffer gnus-group-buffer)
3590     ;; Allow the user to do things after cleaning up.
3591     (gnus-run-hooks 'gnus-after-exiting-gnus-hook)))
3592
3593 (defun gnus-group-describe-briefly ()
3594   "Give a one line description of the group mode commands."
3595   (interactive)
3596   (gnus-message 7 (substitute-command-keys "\\<gnus-group-mode-map>\\[gnus-group-read-group]:Select  \\[gnus-group-next-unread-group]:Forward  \\[gnus-group-prev-unread-group]:Backward  \\[gnus-group-exit]:Exit  \\[gnus-info-find-node]:Run Info  \\[gnus-group-describe-briefly]:This help")))
3597
3598 (defun gnus-group-browse-foreign-server (method)
3599   "Browse a foreign news server.
3600 If called interactively, this function will ask for a select method
3601  (nntp, nnspool, etc.) and a server address (eg. nntp.some.where).
3602 If not, METHOD should be a list where the first element is the method
3603 and the second element is the address."
3604   (interactive
3605    (list (let ((how (completing-read
3606                      "Which backend: "
3607                      (append gnus-valid-select-methods gnus-server-alist)
3608                      nil t (cons "nntp" 0) 'gnus-method-history)))
3609            ;; We either got a backend name or a virtual server name.
3610            ;; If the first, we also need an address.
3611            (if (assoc how gnus-valid-select-methods)
3612                (list (intern how)
3613                      ;; Suggested by mapjph@bath.ac.uk.
3614                      (completing-read
3615                       "Address: "
3616                       (mapcar (lambda (server) (list server))
3617                               gnus-secondary-servers)))
3618              ;; We got a server name.
3619              how))))
3620   (gnus-browse-foreign-server method))
3621
3622 (defun gnus-group-set-info (info &optional method-only-group part)
3623   (when (or info part)
3624     (let* ((entry (gnus-gethash
3625                    (or method-only-group (gnus-info-group info))
3626                    gnus-newsrc-hashtb))
3627            (part-info info)
3628            (info (if method-only-group (nth 2 entry) info))
3629            method)
3630       (when method-only-group
3631         (unless entry
3632           (error "Trying to change non-existent group %s" method-only-group))
3633         ;; We have received parts of the actual group info - either the
3634         ;; select method or the group parameters.        We first check
3635         ;; whether we have to extend the info, and if so, do that.
3636         (let ((len (length info))
3637               (total (if (eq part 'method) 5 6)))
3638           (when (< len total)
3639             (setcdr (nthcdr (1- len) info)
3640                     (make-list (- total len) nil)))
3641           ;; Then we enter the new info.
3642           (setcar (nthcdr (1- total) info) part-info)))
3643       (unless entry
3644         ;; This is a new group, so we just create it.
3645         (save-excursion
3646           (set-buffer gnus-group-buffer)
3647           (setq method (gnus-info-method info))
3648           (when (gnus-server-equal method "native")
3649             (setq method nil))
3650           (save-excursion
3651             (set-buffer gnus-group-buffer)
3652             (if method
3653                 ;; It's a foreign group...
3654                 (gnus-group-make-group
3655                  (gnus-group-real-name (gnus-info-group info))
3656                  (if (stringp method) method
3657                    (prin1-to-string (car method)))
3658                  (and (consp method)
3659                       (nth 1 (gnus-info-method info))))
3660               ;; It's a native group.
3661               (gnus-group-make-group (gnus-info-group info))))
3662           (gnus-message 6 "Note: New group created")
3663           (setq entry
3664                 (gnus-gethash (gnus-group-prefixed-name
3665                                (gnus-group-real-name (gnus-info-group info))
3666                                (or (gnus-info-method info) gnus-select-method))
3667                               gnus-newsrc-hashtb))))
3668       ;; Whether it was a new group or not, we now have the entry, so we
3669       ;; can do the update.
3670       (if entry
3671           (progn
3672             (setcar (nthcdr 2 entry) info)
3673             (when (and (not (eq (car entry) t))
3674                        (gnus-active (gnus-info-group info)))
3675               (setcar entry (length (gnus-list-of-unread-articles (car info))))))
3676         (error "No such group: %s" (gnus-info-group info))))))
3677
3678 (defun gnus-group-set-method-info (group select-method)
3679   (gnus-group-set-info select-method group 'method))
3680
3681 (defun gnus-group-set-params-info (group params)
3682   (gnus-group-set-info params group 'params))
3683
3684 (defun gnus-add-marked-articles (group type articles &optional info force)
3685   ;; Add ARTICLES of TYPE to the info of GROUP.
3686   ;; If INFO is non-nil, use that info.  If FORCE is non-nil, don't
3687   ;; add, but replace marked articles of TYPE with ARTICLES.
3688   (let ((info (or info (gnus-get-info group)))
3689         marked m)
3690     (or (not info)
3691         (and (not (setq marked (nthcdr 3 info)))
3692              (or (null articles)
3693                  (setcdr (nthcdr 2 info)
3694                          (list (list (cons type (gnus-compress-sequence
3695                                                  articles t)))))))
3696         (and (not (setq m (assq type (car marked))))
3697              (or (null articles)
3698                  (setcar marked
3699                          (cons (cons type (gnus-compress-sequence articles t) )
3700                                (car marked)))))
3701         (if force
3702             (if (null articles)
3703                 (setcar (nthcdr 3 info)
3704                         (gnus-delete-alist type (car marked)))
3705               (setcdr m (gnus-compress-sequence articles t)))
3706           (setcdr m (gnus-compress-sequence
3707                      (sort (nconc (gnus-uncompress-range (cdr m))
3708                                   (copy-sequence articles)) '<) t))))))
3709
3710 ;;;
3711 ;;; Group timestamps
3712 ;;;
3713
3714 (defun gnus-group-set-timestamp ()
3715   "Change the timestamp of the current group to the current time.
3716 This function can be used in hooks like `gnus-select-group-hook'
3717 or `gnus-group-catchup-group-hook'."
3718   (when gnus-newsgroup-name
3719     (let ((time (current-time)))
3720       (setcdr (cdr time) nil)
3721       (gnus-group-set-parameter gnus-newsgroup-name 'timestamp time))))
3722
3723 (defsubst gnus-group-timestamp (group)
3724   "Return the timestamp for GROUP."
3725   (gnus-group-get-parameter group 'timestamp t))
3726
3727 (defun gnus-group-timestamp-delta (group)
3728   "Return the offset in seconds from the timestamp for GROUP to the current time, as a floating point number."
3729   (let* ((time (or (gnus-group-timestamp group)
3730                    (list 0 0)))
3731          (delta (subtract-time (current-time) time)))
3732     (+ (* (nth 0 delta) 65536.0)
3733        (nth 1 delta))))
3734
3735 (defun gnus-group-timestamp-string (group)
3736   "Return a string of the timestamp for GROUP."
3737   (let ((time (gnus-group-timestamp group)))
3738     (if (not time)
3739         ""
3740       (gnus-time-iso8601 time))))
3741
3742 (defun gnus-group-list-cached (level &optional lowest)
3743   "List all groups with cached articles.
3744 If the prefix LEVEL is non-nil, it should be a number that says which
3745 level to cut off listing groups.
3746 If LOWEST, don't list groups with level lower than LOWEST.
3747
3748 This command may read the active file."
3749   (interactive "P")
3750   (when level
3751     (setq level (prefix-numeric-value level)))
3752   (when (or (not level) (>= level gnus-level-zombie))
3753     (gnus-cache-open))
3754   (funcall gnus-group-prepare-function 
3755            (or level gnus-level-subscribed)
3756            #'(lambda (info)
3757                (let ((marks (gnus-info-marks info)))
3758                  (assq 'cache marks)))
3759            lowest
3760            #'(lambda (group)
3761                (or (gnus-gethash group 
3762                                  gnus-cache-active-hashtb)
3763                    ;; Cache active file might use "." 
3764                    ;; instead of ":".
3765                    (gnus-gethash 
3766                     (mapconcat 'identity
3767                                (split-string group ":")
3768                                ".")
3769                     gnus-cache-active-hashtb))))
3770   (goto-char (point-min))
3771   (gnus-group-position-point))
3772
3773 (defun gnus-group-list-dormant (level &optional lowest)
3774   "List all groups with dormant articles.
3775 If the prefix LEVEL is non-nil, it should be a number that says which
3776 level to cut off listing groups.
3777 If LOWEST, don't list groups with level lower than LOWEST.
3778
3779 This command may read the active file."
3780   (interactive "P")
3781   (when level
3782     (setq level (prefix-numeric-value level)))
3783   (when (or (not level) (>= level gnus-level-zombie))
3784     (gnus-cache-open))
3785   (funcall gnus-group-prepare-function 
3786            (or level gnus-level-subscribed)
3787            #'(lambda (info)
3788                (let ((marks (gnus-info-marks info)))
3789                  (assq 'dormant marks)))
3790            lowest
3791            'ignore)
3792   (goto-char (point-min))
3793   (gnus-group-position-point))
3794
3795 (defun gnus-group-listed-groups ()
3796   "Return a list of listed groups."
3797   (let (point groups)
3798     (goto-char (point-min))
3799     (while (setq point (text-property-not-all (point) (point-max) 
3800                                               'gnus-group nil))
3801       (goto-char point)
3802       (push (symbol-name (get-text-property point 'gnus-group)) groups)
3803       (forward-char 1))
3804     groups))
3805
3806 (defun gnus-group-list-plus (&optional args)
3807   "List groups plus the current selection."
3808   (interactive "P")
3809   (let ((gnus-group-listed-groups (gnus-group-listed-groups))
3810         (gnus-group-list-mode gnus-group-list-mode) ;; Save it.
3811         func)
3812     (push last-command-event unread-command-events)
3813     (if (featurep 'xemacs)
3814         (push (make-event 'key-press '(key ?A)) unread-command-events)
3815       (push ?A unread-command-events))
3816     (let (gnus-pick-mode keys)
3817       (setq keys (if (featurep 'xemacs)
3818                      (events-to-keys (read-key-sequence nil))
3819                    (read-key-sequence nil)))
3820       (setq func (lookup-key (current-local-map) keys)))
3821     (if (or (not func)
3822             (numberp func))
3823         (ding)
3824       (call-interactively func))))
3825
3826 (defun gnus-group-list-flush (&optional args)
3827   "Flush groups from the current selection."
3828   (interactive "P")
3829   (let ((gnus-group-list-option 'flush))
3830     (gnus-group-list-plus args)))
3831
3832 (defun gnus-group-list-limit (&optional args)
3833   "List groups limited within the current selection."
3834   (interactive "P")
3835   (let ((gnus-group-list-option 'limit))
3836     (gnus-group-list-plus args)))
3837
3838 (provide 'gnus-group)
3839
3840 ;;; gnus-group.el ends here