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