1 ;;; gnus-group.el --- group mode commands for Gnus
3 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
4 ;; 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
9 ;; This file is part of GNU Emacs.
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
30 (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
34 (defvar tool-bar-mode)
50 (let ((features (cons 'gnus-group features)))
52 (unless (boundp 'gnus-cache-active-hashtb)
53 (defvar gnus-cache-active-hashtb nil)))
55 (autoload 'gnus-agent-total-fetched-for "gnus-agent")
56 (autoload 'gnus-cache-total-fetched-for "gnus-cache")
58 (defcustom gnus-group-archive-directory
59 "/ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list/"
60 "*The address of the (ding) archives."
61 :group 'gnus-group-foreign
64 (defcustom gnus-group-recent-archive-directory
65 "/ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list-recent/"
66 "*The address of the most recent (ding) articles."
67 :group 'gnus-group-foreign
70 (defcustom gnus-no-groups-message "No Gnus is good news"
71 "*Message displayed by Gnus when no groups are available."
75 (defcustom gnus-keep-same-level nil
76 "*Non-nil means that the next newsgroup after the current will be on the same level.
77 When you type, for instance, `n' after reading the last article in the
78 current newsgroup, you will go to the next newsgroup. If this variable
79 is nil, the next newsgroup will be the next from the group
81 If this variable is non-nil, Gnus will either put you in the
82 next newsgroup with the same level, or, if no such newsgroup is
83 available, the next newsgroup with the lowest possible level higher
84 than the current level.
85 If this variable is `best', Gnus will make the next newsgroup the one
87 :group 'gnus-group-levels
88 :type '(choice (const nil)
90 (sexp :tag "other" t)))
92 (defcustom gnus-group-goto-unread t
93 "*If non-nil, movement commands will go to the next unread and subscribed group."
94 :link '(custom-manual "(gnus)Group Maneuvering")
95 :group 'gnus-group-various
98 (defcustom gnus-goto-next-group-when-activating t
99 "*If non-nil, the \\<gnus-group-mode-map>\\[gnus-group-get-new-news-this-group] command will advance point to the next group."
100 :link '(custom-manual "(gnus)Scanning New Messages")
101 :group 'gnus-group-various
104 (defcustom gnus-permanently-visible-groups nil
105 "*Regexp to match groups that should always be listed in the group buffer.
106 This means that they will still be listed even when there are no
107 unread articles in the groups.
109 If nil, no groups are permanently visible."
110 :group 'gnus-group-listing
111 :type '(choice regexp (const nil)))
113 (defcustom gnus-safe-html-newsgroups "\\`nnrss[+:]"
114 "Groups in which links in html articles are considered all safe.
115 The value may be a regexp matching those groups, a list of group names,
116 or nil. This overrides `mm-w3m-safe-url-regexp' (which see). This is
117 effective only when emacs-w3m renders html articles, i.e., in the case
118 `mm-text-html-renderer' is set to `w3m'."
120 :group 'gnus-group-various
121 :type '(choice regexp
122 (repeat :tag "List of group names" (string :tag "Group"))
125 (defcustom gnus-list-groups-with-ticked-articles t
126 "*If non-nil, list groups that have only ticked articles.
127 If nil, only list groups that have unread articles."
128 :group 'gnus-group-listing
131 (defcustom gnus-group-default-list-level gnus-level-subscribed
132 "*Default listing level.
133 Ignored if `gnus-group-use-permanent-levels' is non-nil."
134 :group 'gnus-group-listing
137 (defcustom gnus-group-list-inactive-groups t
138 "*If non-nil, inactive groups will be listed."
139 :group 'gnus-group-listing
140 :group 'gnus-group-levels
143 (defcustom gnus-group-sort-function 'gnus-group-sort-by-alphabet
144 "*Function used for sorting the group buffer.
145 This function will be called with group info entries as the arguments
146 for the groups to be sorted. Pre-made functions include
147 `gnus-group-sort-by-alphabet', `gnus-group-sort-by-real-name',
148 `gnus-group-sort-by-unread', `gnus-group-sort-by-level',
149 `gnus-group-sort-by-score', `gnus-group-sort-by-method',
150 `gnus-group-sort-by-server', and `gnus-group-sort-by-rank'.
152 This variable can also be a list of sorting functions. In that case,
153 the most significant sort function should be the last function in the
155 :group 'gnus-group-listing
156 :link '(custom-manual "(gnus)Sorting Groups")
157 :type '(repeat :value-to-internal (lambda (widget value)
158 (if (listp value) value (list value)))
159 :match (lambda (widget value)
161 (widget-editable-list-match widget value)))
162 (choice (function-item gnus-group-sort-by-alphabet)
163 (function-item gnus-group-sort-by-real-name)
164 (function-item gnus-group-sort-by-unread)
165 (function-item gnus-group-sort-by-level)
166 (function-item gnus-group-sort-by-score)
167 (function-item gnus-group-sort-by-method)
168 (function-item gnus-group-sort-by-server)
169 (function-item gnus-group-sort-by-rank)
170 (function :tag "other" nil))))
172 (defcustom gnus-group-line-format "%M\%S\%p\%P\%5y:%B%(%g%)%O\n"
173 "*Format of group lines.
174 It works along the same lines as a normal formatting string,
175 with some simple extensions.
177 %M Only marked articles (character, \"*\" or \" \")
178 %S Whether the group is subscribed (character, \"U\", \"K\", \"Z\" or \" \")
179 %L Level of subscribedness (integer)
180 %N Number of unread articles (integer)
181 %I Number of dormant articles (integer)
182 %i Number of ticked and dormant (integer)
183 %T Number of ticked articles (integer)
184 %R Number of read articles (integer)
185 %U Number of unseen articles (integer)
186 %t Estimated total number of articles (integer)
187 %y Number of unread, unticked articles (integer)
188 %G Group name (string)
189 %g Qualified group name (string)
190 %c Short (collapsed) group name. See `gnus-group-uncollapsed-levels'.
191 %C Group comment (string)
192 %D Group description (string)
193 %s Select method (string)
194 %o Moderated group (char, \"m\")
195 %p Process mark (char)
196 %B Whether a summary buffer for the group is open (char, \"*\")
197 %O Moderated group (string, \"(m)\" or \"\")
198 %P Topic indentation (string)
199 %m Whether there is new(ish) mail in the group (char, \"%\")
200 %n Select from where (string)
201 %z A string that look like `<%s:%n>' if a foreign select method is used
202 %d The date the group was last entered.
203 %E Icon as defined by `gnus-group-icon-list'.
204 %F The disk space used by the articles fetched by both the cache and agent.
205 %u User defined specifier. The next character in the format string should
206 be a letter. Gnus will call the function gnus-user-format-function-X,
207 where X is the letter following %u. The function will be passed a
208 single dummy parameter as argument. The function should return a
209 string, which will be inserted into the buffer just like information
210 from any other group specifier.
212 Note that this format specification is not always respected. For
213 reasons of efficiency, when listing killed groups, this specification
214 is ignored altogether. If the spec is changed considerably, your
215 output may end up looking strange when listing both alive and killed
218 If you use %o or %O, reading the active file will be slower and quite
219 a bit of extra memory will be used. %D and %F will also worsen
220 performance. Also note that if you change the format specification to
221 include any of these specs, you must probably re-start Gnus to see
224 General format specifiers can also be used.
225 See Info node `(gnus)Formatting Variables'."
226 :link '(custom-manual "(gnus)Formatting Variables")
227 :group 'gnus-group-visual
230 (defcustom gnus-group-mode-line-format "Gnus: %%b {%M\%:%S}"
231 "*The format specification for the group mode line.
232 It works along the same lines as a normal formatting string,
233 with some simple extensions:
235 %S The native news server.
236 %M The native select method.
237 %: \":\" if %S isn't \"\"."
238 :group 'gnus-group-visual
241 ;; Extracted from gnus-xmas-redefine in order to preserve user settings
242 (when (featurep 'xemacs)
243 (add-hook 'gnus-group-mode-hook 'gnus-xmas-group-menu-add)
244 (add-hook 'gnus-group-mode-hook 'gnus-xmas-setup-group-toolbar))
246 (defcustom gnus-group-menu-hook nil
247 "Hook run after the creation of the group mode menu."
248 :group 'gnus-group-various
251 (defcustom gnus-group-catchup-group-hook nil
252 "Hook run when catching up a group from the group buffer."
253 :group 'gnus-group-various
254 :link '(custom-manual "(gnus)Group Data")
257 (defcustom gnus-group-update-group-hook nil
258 "Hook called when updating group lines."
259 :group 'gnus-group-visual
262 (defcustom gnus-group-prepare-function 'gnus-group-prepare-flat
263 "*A function that is called to generate the group buffer.
264 The function is called with three arguments: The first is a number;
265 all group with a level less or equal to that number should be listed,
266 if the second is non-nil, empty groups should also be displayed. If
267 the third is non-nil, it is a number. No groups with a level lower
268 than this number should be displayed.
270 The only current function implemented is `gnus-group-prepare-flat'."
271 :group 'gnus-group-listing
274 (defcustom gnus-group-prepare-hook nil
275 "Hook called after the group buffer has been generated.
276 If you want to modify the group buffer, you can use this hook."
277 :group 'gnus-group-listing
280 (defcustom gnus-suspend-gnus-hook nil
281 "Hook called when suspending (not exiting) Gnus."
285 (defcustom gnus-exit-gnus-hook nil
286 "Hook called when exiting Gnus."
290 (defcustom gnus-after-exiting-gnus-hook nil
291 "Hook called after exiting Gnus."
295 (defcustom gnus-group-update-hook '(gnus-group-highlight-line)
296 "Hook called when a group line is changed.
297 The hook will not be called if `gnus-visual' is nil.
299 The default function `gnus-group-highlight-line' will
300 highlight the line according to the `gnus-group-highlight'
302 :group 'gnus-group-visual
305 (defcustom gnus-useful-groups
306 '(("(ding) mailing list mirrored at gmane.org"
307 "gmane.emacs.gnus.general"
309 (nntp-address "news.gmane.org")))
312 (nntp "news.gnus.org"
313 (nntp-address "news.gnus.org")))
314 ("Local Gnus help group"
317 (nndoc-article-type mbox)
318 (eval `(nndoc-address
319 ,(let ((file (nnheader-find-etc-directory
322 (error "Couldn't find doc group"))
324 "*Alist of useful group-server pairs."
325 :group 'gnus-group-listing
326 :type '(repeat (list (string :tag "Description")
328 (sexp :tag "Method"))))
330 (defcustom gnus-group-highlight
332 ((and mailp (= unread 0) (eq level 1)) .
333 gnus-group-mail-1-empty)
334 ((and mailp (eq level 1)) .
336 ((and mailp (= unread 0) (eq level 2)) .
337 gnus-group-mail-2-empty)
338 ((and mailp (eq level 2)) .
340 ((and mailp (= unread 0) (eq level 3)) .
341 gnus-group-mail-3-empty)
342 ((and mailp (eq level 3)) .
344 ((and mailp (= unread 0)) .
345 gnus-group-mail-low-empty)
349 ((and (= unread 0) (eq level 1)) .
350 gnus-group-news-1-empty)
351 ((and (eq level 1)) .
353 ((and (= unread 0) (eq level 2)) .
354 gnus-group-news-2-empty)
355 ((and (eq level 2)) .
357 ((and (= unread 0) (eq level 3)) .
358 gnus-group-news-3-empty)
359 ((and (eq level 3)) .
361 ((and (= unread 0) (eq level 4)) .
362 gnus-group-news-4-empty)
363 ((and (eq level 4)) .
365 ((and (= unread 0) (eq level 5)) .
366 gnus-group-news-5-empty)
367 ((and (eq level 5)) .
369 ((and (= unread 0) (eq level 6)) .
370 gnus-group-news-6-empty)
371 ((and (eq level 6)) .
373 ((and (= unread 0)) .
374 gnus-group-news-low-empty)
376 gnus-group-news-low))
377 "*Controls the highlighting of group buffer lines.
379 Below is a list of `Form'/`Face' pairs. When deciding how a a
380 particular group line should be displayed, each form is
381 evaluated. The content of the face field after the first true form is
382 used. You can change how those group lines are displayed by
383 editing the face field.
385 It is also possible to change and add form fields, but currently that
386 requires an understanding of Lisp expressions. Hopefully this will
387 change in a future release. For now, you can use the following
388 variables in the Lisp expression:
390 group: The name of the group.
391 unread: The number of unread articles in the group.
392 method: The select method used.
393 mailp: Whether it's a mail group or not.
394 level: The level of the group.
395 score: The score of the group.
396 ticked: The number of ticked articles."
397 :group 'gnus-group-visual
398 :type '(repeat (cons (sexp :tag "Form") face)))
399 (put 'gnus-group-highlight 'risky-local-variable t)
401 (defcustom gnus-new-mail-mark ?%
402 "Mark used for groups with new mail."
403 :group 'gnus-group-visual
406 (defgroup gnus-group-icons nil
407 "Add Icons to your group buffer."
408 :group 'gnus-group-visual)
410 (defcustom gnus-group-icon-list
412 "*Controls the insertion of icons into group buffer lines.
414 Below is a list of `Form'/`File' pairs. When deciding how a
415 particular group line should be displayed, each form is evaluated.
416 The icon from the file field after the first true form is used. You
417 can change how those group lines are displayed by editing the file
418 field. The File will either be found in the
419 `gnus-group-glyph-directory' or by designating absolute name of the
422 It is also possible to change and add form fields, but currently that
423 requires an understanding of Lisp expressions. Hopefully this will
424 change in a future release. For now, you can use the following
425 variables in the Lisp expression:
427 group: The name of the group.
428 unread: The number of unread articles in the group.
429 method: The select method used.
430 mailp: Whether it's a mail group or not.
431 newsp: Whether it's a news group or not
432 level: The level of the group.
433 score: The score of the group.
434 ticked: The number of ticked articles."
435 :group 'gnus-group-icons
436 :type '(repeat (cons (sexp :tag "Form") file)))
437 (put 'gnus-group-icon-list 'risky-local-variable t)
439 (defcustom gnus-group-name-charset-method-alist nil
440 "Alist of method and the charset for group names.
443 (((nntp \"news.com.cn\") . cn-gb-2312))"
446 :type '(repeat (cons (sexp :tag "Method") (symbol :tag "Charset"))))
448 (defcustom gnus-group-name-charset-group-alist
449 (if (or (and (fboundp 'find-coding-system) (find-coding-system 'utf-8))
450 (mm-coding-system-p 'utf-8))
453 "Alist of group regexp and the charset for group names.
456 ((\"\\.com\\.cn:\" . cn-gb-2312))"
458 :type '(repeat (cons (regexp :tag "Group") (symbol :tag "Charset"))))
460 (defcustom gnus-group-jump-to-group-prompt nil
461 "Default prompt for `gnus-group-jump-to-group'.
463 If non-nil, the value should be a string or an alist. If it is a string,
464 e.g. \"nnml:\", in which case `gnus-group-jump-to-group' offers \"Group:
465 nnml:\" in the minibuffer prompt.
467 If it is an alist, it must consist of \(NUMBER . PROMPT\) pairs, for example:
468 \((1 . \"\") (2 . \"nnfolder+archive:\")). The element with number 0 is
469 used when no prefix argument is given to `gnus-group-jump-to-group'."
471 :group 'gnus-group-various
472 :type '(choice (string :tag "Prompt string")
473 (const :tag "Empty" nil)
474 (repeat (cons (integer :tag "Argument")
475 (string :tag "Prompt string")))))
477 (defvar gnus-group-listing-limit 1000
478 "*A limit of the number of groups when listing.
479 If the number of groups is larger than the limit, list them in a
482 ;;; Internal variables
484 (defvar gnus-group-is-exiting-p nil)
485 (defvar gnus-group-is-exiting-without-update-p nil)
486 (defvar gnus-group-sort-alist-function 'gnus-group-sort-flat
487 "Function for sorting the group buffer.")
489 (defvar gnus-group-sort-selected-function 'gnus-group-sort-selected-flat
490 "Function for sorting the selected groups in the group buffer.")
492 (defvar gnus-group-indentation-function nil)
493 (defvar gnus-goto-missing-group-function nil)
494 (defvar gnus-group-update-group-function nil)
495 (defvar gnus-group-goto-next-group-function nil
496 "Function to override finding the next group after listing groups.")
498 (defvar gnus-group-edit-buffer nil)
500 (defvar gnus-group-line-format-alist
501 `((?M gnus-tmp-marked-mark ?c)
502 (?S gnus-tmp-subscribed ?c)
503 (?L gnus-tmp-level ?d)
504 (?N (cond ((eq number t) "*" )
508 (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked)))
509 (gnus-range-length (cdr (assq 'tick gnus-tmp-marked))))))
511 (?R gnus-tmp-number-of-read ?s)
512 (?U (gnus-number-of-unseen-articles-in-group gnus-tmp-group) ?d)
513 (?t gnus-tmp-number-total ?d)
514 (?y gnus-tmp-number-of-unread ?s)
515 (?I (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked))) ?d)
516 (?T (gnus-range-length (cdr (assq 'tick gnus-tmp-marked))) ?d)
517 (?i (+ (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked)))
518 (gnus-range-length (cdr (assq 'tick gnus-tmp-marked)))) ?d)
519 (?g (if (boundp 'gnus-tmp-decoded-group)
520 gnus-tmp-decoded-group
523 (?G gnus-tmp-qualified-group ?s)
524 (?c (gnus-short-group-name (if (boundp 'gnus-tmp-decoded-group)
525 gnus-tmp-decoded-group
528 (?C gnus-tmp-comment ?s)
529 (?D gnus-tmp-newsgroup-description ?s)
530 (?o gnus-tmp-moderated ?c)
531 (?O gnus-tmp-moderated-string ?s)
532 (?p gnus-tmp-process-marked ?c)
533 (?s gnus-tmp-news-server ?s)
534 (?n ,(if (featurep 'xemacs)
535 '(symbol-name gnus-tmp-news-method)
536 'gnus-tmp-news-method)
538 (?P gnus-group-indentation ?s)
539 (?E gnus-tmp-group-icon ?s)
540 (?B gnus-tmp-summary-live ?c)
541 (?z gnus-tmp-news-method-string ?s)
542 (?m (gnus-group-new-mail gnus-tmp-group) ?c)
543 (?d (gnus-group-timestamp-string gnus-tmp-group) ?s)
544 (?u gnus-tmp-user-defined ?s)
545 (?F (gnus-total-fetched-for gnus-tmp-group) ?s)
548 (defvar gnus-group-mode-line-format-alist
549 `((?S gnus-tmp-news-server ?s)
550 (?M gnus-tmp-news-method ?s)
551 (?u gnus-tmp-user-defined ?s)
552 (?: gnus-tmp-colon ?s)))
554 (defvar gnus-topic-topology nil
555 "The complete topic hierarchy.")
557 (defvar gnus-topic-alist nil
558 "The complete topic-group alist.")
560 (defvar gnus-group-marked nil)
562 (defvar gnus-group-list-mode nil)
565 (defvar gnus-group-icon-cache nil)
567 (defvar gnus-group-listed-groups nil)
568 (defvar gnus-group-list-option nil)
574 (put 'gnus-group-mode 'mode-class 'special)
576 (gnus-define-keys gnus-group-mode-map
577 " " gnus-group-read-group
578 "=" gnus-group-select-group
579 "\r" gnus-group-select-group
580 "\M-\r" gnus-group-quick-select-group
581 "\M- " gnus-group-visible-select-group
582 [(meta control return)] gnus-group-select-group-ephemerally
583 "j" gnus-group-jump-to-group
584 "n" gnus-group-next-unread-group
585 "p" gnus-group-prev-unread-group
586 "\177" gnus-group-prev-unread-group
587 [delete] gnus-group-prev-unread-group
588 [backspace] gnus-group-prev-unread-group
589 "N" gnus-group-next-group
590 "P" gnus-group-prev-group
591 "\M-n" gnus-group-next-unread-group-same-level
592 "\M-p" gnus-group-prev-unread-group-same-level
593 "," gnus-group-best-unread-group
594 "." gnus-group-first-unread-group
595 "u" gnus-group-unsubscribe-current-group
596 "U" gnus-group-unsubscribe-group
597 "c" gnus-group-catchup-current
598 "C" gnus-group-catchup-current-all
599 "\M-c" gnus-group-clear-data
600 "l" gnus-group-list-groups
601 "L" gnus-group-list-all-groups
604 "g" gnus-group-get-new-news
605 "\M-g" gnus-group-get-new-news-this-group
606 "R" gnus-group-restart
607 "r" gnus-group-read-init-file
608 "B" gnus-group-browse-foreign-server