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