X-Git-Url: http://git.chise.org/gitweb/?a=blobdiff_plain;f=lisp%2Fgnus-group.el;h=b601cab615f34c21433e5f9e4ea78e63f084bdd8;hb=ab6b58ba032f3baaf4c78e63be9e39e9d8de5e62;hp=d4bfd5446aae4fcf6ac3a63819b56b57f4ec8c30;hpb=aa1506499895e49a74c2ac8c22892333c1a01e48;p=elisp%2Fgnus.git- diff --git a/lisp/gnus-group.el b/lisp/gnus-group.el index d4bfd54..b601cab 100644 --- a/lisp/gnus-group.el +++ b/lisp/gnus-group.el @@ -1,5 +1,7 @@ ;;; gnus-group.el --- group mode commands for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +;; 2005, 2006 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -18,14 +20,16 @@ ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, -;; Boston, MA 02111-1307, USA. +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. ;;; Commentary: ;;; Code: -(eval-when-compile (require 'cl)) +(eval-when-compile + (require 'cl) + (defvar tool-bar-mode)) (require 'gnus) (require 'gnus-start) @@ -35,20 +39,33 @@ (require 'gnus-range) (require 'gnus-win) (require 'gnus-undo) +(require 'gmm-utils) +(require 'time-date) +(require 'gnus-ems) + +(eval-when-compile + (require 'mm-url) + (let ((features (cons 'gnus-group features))) + (require 'gnus-sum)) + (unless (boundp 'gnus-cache-active-hashtb) + (defvar gnus-cache-active-hashtb nil))) + +(autoload 'gnus-agent-total-fetched-for "gnus-agent") +(autoload 'gnus-cache-total-fetched-for "gnus-cache") (defcustom gnus-group-archive-directory - "*ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list/" + "/ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list/" "*The address of the (ding) archives." :group 'gnus-group-foreign :type 'directory) (defcustom gnus-group-recent-archive-directory - "*ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list-recent/" + "/ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list-recent/" "*The address of the most recent (ding) articles." :group 'gnus-group-foreign :type 'directory) -(defcustom gnus-no-groups-message "No news is no news" +(defcustom gnus-no-groups-message "No Gnus is good news" "*Message displayed by Gnus when no groups are available." :group 'gnus-start :type 'string) @@ -89,7 +106,7 @@ unread articles in the groups. If nil, no groups are permanently visible." :group 'gnus-group-listing - :type 'regexp) + :type '(choice regexp (const nil))) (defcustom gnus-list-groups-with-ticked-articles t "*If non-nil, list groups that have only ticked articles. @@ -115,24 +132,30 @@ This function will be called with group info entries as the arguments for the groups to be sorted. Pre-made functions include `gnus-group-sort-by-alphabet', `gnus-group-sort-by-real-name', `gnus-group-sort-by-unread', `gnus-group-sort-by-level', -`gnus-group-sort-by-score', `gnus-group-sort-by-method', and -`gnus-group-sort-by-rank'. +`gnus-group-sort-by-score', `gnus-group-sort-by-method', +`gnus-group-sort-by-server', and `gnus-group-sort-by-rank'. This variable can also be a list of sorting functions. In that case, the most significant sort function should be the last function in the list." :group 'gnus-group-listing :link '(custom-manual "(gnus)Sorting Groups") - :type '(radio (function-item gnus-group-sort-by-alphabet) - (function-item gnus-group-sort-by-real-name) - (function-item gnus-group-sort-by-unread) - (function-item gnus-group-sort-by-level) - (function-item gnus-group-sort-by-score) - (function-item gnus-group-sort-by-method) - (function-item gnus-group-sort-by-rank) - (function :tag "other" nil))) - -(defcustom gnus-group-line-format "%M\%S\%p\%P\%5y: %(%g%)%l\n" + :type '(repeat :value-to-internal (lambda (widget value) + (if (listp value) value (list value))) + :match (lambda (widget value) + (or (symbolp value) + (widget-editable-list-match widget value))) + (choice (function-item gnus-group-sort-by-alphabet) + (function-item gnus-group-sort-by-real-name) + (function-item gnus-group-sort-by-unread) + (function-item gnus-group-sort-by-level) + (function-item gnus-group-sort-by-score) + (function-item gnus-group-sort-by-method) + (function-item gnus-group-sort-by-server) + (function-item gnus-group-sort-by-rank) + (function :tag "other" nil)))) + +(defcustom gnus-group-line-format "%M\%S\%p\%P\%5y:%B%(%g%)%O\n" "*Format of group lines. It works along the same lines as a normal formatting string, with some simple extensions. @@ -145,30 +168,32 @@ with some simple extensions. %i Number of ticked and dormant (integer) %T Number of ticked articles (integer) %R Number of read articles (integer) +%U Number of unseen articles (integer) %t Estimated total number of articles (integer) %y Number of unread, unticked articles (integer) %G Group name (string) %g Qualified group name (string) +%c Short (collapsed) group name. See `gnus-group-uncollapsed-levels'. +%C Group comment (string) %D Group description (string) %s Select method (string) %o Moderated group (char, \"m\") %p Process mark (char) +%B Whether a summary buffer for the group is open (char, \"*\") %O Moderated group (string, \"(m)\" or \"\") %P Topic indentation (string) %m Whether there is new(ish) mail in the group (char, \"%\") -%l Whether there are GroupLens predictions for this group (string) %n Select from where (string) %z A string that look like `<%s:%n>' if a foreign select method is used %d The date the group was last entered. +%E Icon as defined by `gnus-group-icon-list'. +%F The disk space used by the articles fetched by both the cache and agent. %u User defined specifier. The next character in the format string should be a letter. Gnus will call the function gnus-user-format-function-X, - where X is the letter following %u. The function will be passed the - current header as argument. The function should return a string, which - will be inserted into the buffer just like information from any other - group specifier. - -Text between %( and %) will be highlighted with `gnus-mouse-face' when -the mouse point move inside the area. There can only be one such area. + where X is the letter following %u. The function will be passed a + single dummy parameter as argument. The function should return a + string, which will be inserted into the buffer just like information + from any other group specifier. Note that this format specification is not always respected. For reasons of efficiency, when listing killed groups, this specification @@ -177,10 +202,14 @@ output may end up looking strange when listing both alive and killed groups. If you use %o or %O, reading the active file will be slower and quite -a bit of extra memory will be used. %D will also worsen performance. -Also note that if you change the format specification to include any -of these specs, you must probably re-start Gnus to see them go into -effect." +a bit of extra memory will be used. %D and %F will also worsen +performance. Also note that if you change the format specification to +include any of these specs, you must probably re-start Gnus to see +them go into effect. + +General format specifiers can also be used. +See Info node `(gnus)Formatting Variables'." + :link '(custom-manual "(gnus)Formatting Variables") :group 'gnus-group-visual :type 'string) @@ -195,11 +224,10 @@ with some simple extensions: :group 'gnus-group-visual :type 'string) -(defcustom gnus-group-mode-hook nil - "Hook for Gnus group mode." - :group 'gnus-group-various - :options '(gnus-topic-mode) - :type 'hook) +;; Extracted from gnus-xmas-redefine in order to preserve user settings +(when (featurep 'xemacs) + (add-hook 'gnus-group-mode-hook 'gnus-xmas-group-menu-add) + (add-hook 'gnus-group-mode-hook 'gnus-xmas-setup-group-toolbar)) (defcustom gnus-group-menu-hook nil "Hook run after the creation of the group mode menu." @@ -261,14 +289,15 @@ variable." :type 'hook) (defcustom gnus-useful-groups - '(("(ding) mailing list mirrored at sunsite.auc.dk" - "emacs.ding" - (nntp "sunsite.auc.dk" - (nntp-address "sunsite.auc.dk"))) - ("gnus-bug archive" - "gnus-bug" - (nndir "/ftp@ftp.ifi.uio.no:/pub/emacs/gnus/gnus-bug/")) - ("Gnus help group" + '(("(ding) mailing list mirrored at gmane.org" + "gmane.emacs.gnus.general" + (nntp "Gmane" + (nntp-address "news.gmane.org"))) + ("Gnus bug archive" + "gnus.gnus-bug" + (nntp "news.gnus.org" + (nntp-address "news.gnus.org"))) + ("Local Gnus help group" "gnus-help" (nndoc "gnus-help" (nndoc-article-type mbox) @@ -285,40 +314,52 @@ variable." (sexp :tag "Method")))) (defcustom gnus-group-highlight - '(;; News. - ((and (= unread 0) (not mailp) (eq level 1)) . - gnus-group-news-1-empty-face) - ((and (not mailp) (eq level 1)) . - gnus-group-news-1-face) - ((and (= unread 0) (not mailp) (eq level 2)) . - gnus-group-news-2-empty-face) - ((and (not mailp) (eq level 2)) . - gnus-group-news-2-face) - ((and (= unread 0) (not mailp) (eq level 3)) . - gnus-group-news-3-empty-face) - ((and (not mailp) (eq level 3)) . - gnus-group-news-3-face) - ((and (= unread 0) (not mailp)) . - gnus-group-news-low-empty-face) - ((and (not mailp)) . - gnus-group-news-low-face) - ;; Mail. + '(;; Mail. + ((and mailp (= unread 0) (eq level 1)) . + gnus-group-mail-1-empty) + ((and mailp (eq level 1)) . + gnus-group-mail-1) + ((and mailp (= unread 0) (eq level 2)) . + gnus-group-mail-2-empty) + ((and mailp (eq level 2)) . + gnus-group-mail-2) + ((and mailp (= unread 0) (eq level 3)) . + gnus-group-mail-3-empty) + ((and mailp (eq level 3)) . + gnus-group-mail-3) + ((and mailp (= unread 0)) . + gnus-group-mail-low-empty) + ((and mailp) . + gnus-group-mail-low) + ;; News. ((and (= unread 0) (eq level 1)) . - gnus-group-mail-1-empty-face) - ((eq level 1) . - gnus-group-mail-1-face) + gnus-group-news-1-empty) + ((and (eq level 1)) . + gnus-group-news-1) ((and (= unread 0) (eq level 2)) . - gnus-group-mail-2-empty-face) - ((eq level 2) . - gnus-group-mail-2-face) + gnus-group-news-2-empty) + ((and (eq level 2)) . + gnus-group-news-2) ((and (= unread 0) (eq level 3)) . - gnus-group-mail-3-empty-face) - ((eq level 3) . - gnus-group-mail-3-face) - ((= unread 0) . - gnus-group-mail-low-empty-face) + gnus-group-news-3-empty) + ((and (eq level 3)) . + gnus-group-news-3) + ((and (= unread 0) (eq level 4)) . + gnus-group-news-4-empty) + ((and (eq level 4)) . + gnus-group-news-4) + ((and (= unread 0) (eq level 5)) . + gnus-group-news-5-empty) + ((and (eq level 5)) . + gnus-group-news-5) + ((and (= unread 0) (eq level 6)) . + gnus-group-news-6-empty) + ((and (eq level 6)) . + gnus-group-news-6) + ((and (= unread 0)) . + gnus-group-news-low-empty) (t . - gnus-group-mail-low-face)) + gnus-group-news-low)) "*Controls the highlighting of group buffer lines. Below is a list of `Form'/`Face' pairs. When deciding how a a @@ -347,8 +388,85 @@ ticked: The number of ticked articles." :group 'gnus-group-visual :type 'character) +(defgroup gnus-group-icons nil + "Add Icons to your group buffer." + :group 'gnus-group-visual) + +(defcustom gnus-group-icon-list + nil + "*Controls the insertion of icons into group buffer lines. + +Below is a list of `Form'/`File' pairs. When deciding how a +particular group line should be displayed, each form is evaluated. +The icon from the file field after the first true form is used. You +can change how those group lines are displayed by editing the file +field. The File will either be found in the +`gnus-group-glyph-directory' or by designating absolute name of the +file. + +It is also possible to change and add form fields, but currently that +requires an understanding of Lisp expressions. Hopefully this will +change in a future release. For now, you can use the following +variables in the Lisp expression: + +group: The name of the group. +unread: The number of unread articles in the group. +method: The select method used. +mailp: Whether it's a mail group or not. +newsp: Whether it's a news group or not +level: The level of the group. +score: The score of the group. +ticked: The number of ticked articles." + :group 'gnus-group-icons + :type '(repeat (cons (sexp :tag "Form") file))) + +(defcustom gnus-group-name-charset-method-alist nil + "Alist of method and the charset for group names. + +For example: + (((nntp \"news.com.cn\") . cn-gb-2312))" + :version "21.1" + :group 'gnus-charset + :type '(repeat (cons (sexp :tag "Method") (symbol :tag "Charset")))) + +(defcustom gnus-group-name-charset-group-alist + (if (or (and (fboundp 'find-coding-system) (find-coding-system 'utf-8)) + (mm-coding-system-p 'utf-8)) + '((".*" . utf-8)) + nil) + "Alist of group regexp and the charset for group names. + +For example: + ((\"\\.com\\.cn:\" . cn-gb-2312))" + :group 'gnus-charset + :type '(repeat (cons (regexp :tag "Group") (symbol :tag "Charset")))) + +(defcustom gnus-group-jump-to-group-prompt nil + "Default prompt for `gnus-group-jump-to-group'. + +If non-nil, the value should be a string or an alist. If it is a string, +e.g. \"nnml:\", in which case `gnus-group-jump-to-group' offers \"Group: +nnml:\" in the minibuffer prompt. + +If it is an alist, it must consist of \(NUMBER . PROMPT\) pairs, for example: +\((1 . \"\") (2 . \"nnfolder+archive:\")). The element with number 0 is +used when no prefix argument is given to `gnus-group-jump-to-group'." + :version "22.1" + :group 'gnus-group-various + :type '(choice (string :tag "Prompt string") + (const :tag "Empty" nil) + (repeat (cons (integer :tag "Argument") + (string :tag "Prompt string"))))) + +(defvar gnus-group-listing-limit 1000 + "*A limit of the number of groups when listing. +If the number of groups is larger than the limit, list them in a +simple manner.") + ;;; Internal variables +(defvar gnus-group-is-exiting-p nil) +(defvar gnus-group-is-exiting-without-update-p nil) (defvar gnus-group-sort-alist-function 'gnus-group-sort-flat "Function for sorting the group buffer.") @@ -375,27 +493,41 @@ ticked: The number of ticked articles." (gnus-range-length (cdr (assq 'tick gnus-tmp-marked)))))) (t number)) ?s) (?R gnus-tmp-number-of-read ?s) + (?U (gnus-number-of-unseen-articles-in-group gnus-tmp-group) ?d) (?t gnus-tmp-number-total ?d) (?y gnus-tmp-number-of-unread ?s) (?I (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked))) ?d) (?T (gnus-range-length (cdr (assq 'tick gnus-tmp-marked))) ?d) (?i (+ (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked))) (gnus-range-length (cdr (assq 'tick gnus-tmp-marked)))) ?d) - (?g gnus-tmp-group ?s) + (?g (if (boundp 'gnus-tmp-decoded-group) + gnus-tmp-decoded-group + gnus-tmp-group) + ?s) (?G gnus-tmp-qualified-group ?s) - (?c (gnus-short-group-name gnus-tmp-group) ?s) + (?c (gnus-short-group-name (if (boundp 'gnus-tmp-decoded-group) + gnus-tmp-decoded-group + gnus-tmp-group)) + ?s) + (?C gnus-tmp-comment ?s) (?D gnus-tmp-newsgroup-description ?s) (?o gnus-tmp-moderated ?c) (?O gnus-tmp-moderated-string ?s) (?p gnus-tmp-process-marked ?c) (?s gnus-tmp-news-server ?s) - (?n gnus-tmp-news-method ?s) + (?n ,(if (featurep 'xemacs) + '(symbol-name gnus-tmp-news-method) + 'gnus-tmp-news-method) + ?s) (?P gnus-group-indentation ?s) - (?l gnus-tmp-grouplens ?s) + (?E gnus-tmp-group-icon ?s) + (?B gnus-tmp-summary-live ?c) (?z gnus-tmp-news-method-string ?s) (?m (gnus-group-new-mail gnus-tmp-group) ?c) (?d (gnus-group-timestamp-string gnus-tmp-group) ?s) - (?u gnus-tmp-user-defined ?s))) + (?u gnus-tmp-user-defined ?s) + (?F (gnus-total-fetched-for gnus-tmp-group) ?s) + )) (defvar gnus-group-mode-line-format-alist `((?S gnus-tmp-news-server ?s) @@ -413,162 +545,227 @@ ticked: The number of ticked articles." (defvar gnus-group-list-mode nil) + +(defvar gnus-group-icon-cache nil) + +(defvar gnus-group-listed-groups nil) +(defvar gnus-group-list-option nil) + ;;; ;;; Gnus group mode ;;; (put 'gnus-group-mode 'mode-class 'special) -(when t - (gnus-define-keys gnus-group-mode-map - " " gnus-group-read-group - "=" gnus-group-select-group - "\r" gnus-group-select-group - "\M-\r" gnus-group-quick-select-group - [(meta control return)] gnus-group-select-group-ephemerally - "j" gnus-group-jump-to-group - "n" gnus-group-next-unread-group - "p" gnus-group-prev-unread-group - "\177" gnus-group-prev-unread-group - [delete] gnus-group-prev-unread-group - [backspace] gnus-group-prev-unread-group - "N" gnus-group-next-group - "P" gnus-group-prev-group - "\M-n" gnus-group-next-unread-group-same-level - "\M-p" gnus-group-prev-unread-group-same-level - "," gnus-group-best-unread-group - "." gnus-group-first-unread-group - "u" gnus-group-unsubscribe-current-group - "U" gnus-group-unsubscribe-group - "c" gnus-group-catchup-current - "C" gnus-group-catchup-current-all - "\M-c" gnus-group-clear-data - "l" gnus-group-list-groups - "L" gnus-group-list-all-groups - "m" gnus-group-mail - "g" gnus-group-get-new-news - "\M-g" gnus-group-get-new-news-this-group - "R" gnus-group-restart - "r" gnus-group-read-init-file - "B" gnus-group-browse-foreign-server - "b" gnus-group-check-bogus-groups - "F" gnus-group-find-new-groups - "\C-c\C-d" gnus-group-describe-group - "\M-d" gnus-group-describe-all-groups - "\C-c\C-a" gnus-group-apropos - "\C-c\M-\C-a" gnus-group-description-apropos - "a" gnus-group-post-news - "\ek" gnus-group-edit-local-kill - "\eK" gnus-group-edit-global-kill - "\C-k" gnus-group-kill-group - "\C-y" gnus-group-yank-group - "\C-w" gnus-group-kill-region - "\C-x\C-t" gnus-group-transpose-groups - "\C-c\C-l" gnus-group-list-killed - "\C-c\C-x" gnus-group-expire-articles - "\C-c\M-\C-x" gnus-group-expire-all-groups - "V" gnus-version - "s" gnus-group-save-newsrc - "z" gnus-group-suspend - "q" gnus-group-exit - "Q" gnus-group-quit - "?" gnus-group-describe-briefly - "\C-c\C-i" gnus-info-find-node - "\M-e" gnus-group-edit-group-method - "^" gnus-group-enter-server-mode - gnus-mouse-2 gnus-mouse-pick-group - "<" beginning-of-buffer - ">" end-of-buffer - "\C-c\C-b" gnus-bug - "\C-c\C-s" gnus-group-sort-groups - "t" gnus-topic-mode - "\C-c\M-g" gnus-activate-all-groups - "\M-&" gnus-group-universal-argument - "#" gnus-group-mark-group - "\M-#" gnus-group-unmark-group) - - (gnus-define-keys (gnus-group-mark-map "M" gnus-group-mode-map) - "m" gnus-group-mark-group - "u" gnus-group-unmark-group - "w" gnus-group-mark-region - "b" gnus-group-mark-buffer - "r" gnus-group-mark-regexp - "U" gnus-group-unmark-all-groups) - - (gnus-define-keys (gnus-group-group-map "G" gnus-group-mode-map) - "d" gnus-group-make-directory-group - "h" gnus-group-make-help-group - "u" gnus-group-make-useful-group - "a" gnus-group-make-archive-group - "k" gnus-group-make-kiboze-group - "m" gnus-group-make-group - "E" gnus-group-edit-group - "e" gnus-group-edit-group-method - "p" gnus-group-edit-group-parameters - "v" gnus-group-add-to-virtual - "V" gnus-group-make-empty-virtual - "D" gnus-group-enter-directory - "f" gnus-group-make-doc-group - "w" gnus-group-make-web-group - "r" gnus-group-rename-group - "c" gnus-group-customize - "\177" gnus-group-delete-group - [delete] gnus-group-delete-group) - - (gnus-define-keys (gnus-group-soup-map "s" gnus-group-group-map) - "b" gnus-group-brew-soup - "w" gnus-soup-save-areas - "s" gnus-soup-send-replies - "p" gnus-soup-pack-packet - "r" nnsoup-pack-replies) - - (gnus-define-keys (gnus-group-sort-map "S" gnus-group-group-map) - "s" gnus-group-sort-groups - "a" gnus-group-sort-groups-by-alphabet - "u" gnus-group-sort-groups-by-unread - "l" gnus-group-sort-groups-by-level - "v" gnus-group-sort-groups-by-score - "r" gnus-group-sort-groups-by-rank - "m" gnus-group-sort-groups-by-method) - - (gnus-define-keys (gnus-group-sort-selected-map "P" gnus-group-group-map) - "s" gnus-group-sort-selected-groups - "a" gnus-group-sort-selected-groups-by-alphabet - "u" gnus-group-sort-selected-groups-by-unread - "l" gnus-group-sort-selected-groups-by-level - "v" gnus-group-sort-selected-groups-by-score - "r" gnus-group-sort-selected-groups-by-rank - "m" gnus-group-sort-selected-groups-by-method) - - (gnus-define-keys (gnus-group-list-map "A" gnus-group-mode-map) - "k" gnus-group-list-killed - "z" gnus-group-list-zombies - "s" gnus-group-list-groups - "u" gnus-group-list-all-groups - "A" gnus-group-list-active - "a" gnus-group-apropos - "d" gnus-group-description-apropos - "m" gnus-group-list-matching - "M" gnus-group-list-all-matching - "l" gnus-group-list-level) - - (gnus-define-keys (gnus-group-score-map "W" gnus-group-mode-map) - "f" gnus-score-flush-cache) - - (gnus-define-keys (gnus-group-help-map "H" gnus-group-mode-map) - "d" gnus-group-describe-group - "f" gnus-group-fetch-faq - "v" gnus-version) - - (gnus-define-keys (gnus-group-sub-map "S" gnus-group-mode-map) - "l" gnus-group-set-current-level - "t" gnus-group-unsubscribe-current-group - "s" gnus-group-unsubscribe-group - "k" gnus-group-kill-group - "y" gnus-group-yank-group - "w" gnus-group-kill-region - "\C-k" gnus-group-kill-level - "z" gnus-group-kill-all-zombies)) +(gnus-define-keys gnus-group-mode-map + " " gnus-group-read-group + "=" gnus-group-select-group + "\r" gnus-group-select-group + "\M-\r" gnus-group-quick-select-group + "\M- " gnus-group-visible-select-group + [(meta control return)] gnus-group-select-group-ephemerally + "j" gnus-group-jump-to-group + "n" gnus-group-next-unread-group + "p" gnus-group-prev-unread-group + "\177" gnus-group-prev-unread-group + [delete] gnus-group-prev-unread-group + [backspace] gnus-group-prev-unread-group + "N" gnus-group-next-group + "P" gnus-group-prev-group + "\M-n" gnus-group-next-unread-group-same-level + "\M-p" gnus-group-prev-unread-group-same-level + "," gnus-group-best-unread-group + "." gnus-group-first-unread-group + "u" gnus-group-unsubscribe-current-group + "U" gnus-group-unsubscribe-group + "c" gnus-group-catchup-current + "C" gnus-group-catchup-current-all + "\M-c" gnus-group-clear-data + "l" gnus-group-list-groups + "L" gnus-group-list-all-groups + "m" gnus-group-mail + "i" gnus-group-news + "g" gnus-group-get-new-news + "\M-g" gnus-group-get-new-news-this-group + "R" gnus-group-restart + "r" gnus-group-read-init-file + "B" gnus-group-browse-foreign-server + "b" gnus-group-check-bogus-groups + "F" gnus-group-find-new-groups + "\C-c\C-d" gnus-group-describe-group + "\M-d" gnus-group-describe-all-groups + "\C-c\C-a" gnus-group-apropos + "\C-c\M-\C-a" gnus-group-description-apropos + "a" gnus-group-post-news + "\ek" gnus-group-edit-local-kill + "\eK" gnus-group-edit-global-kill + "\C-k" gnus-group-kill-group + "\C-y" gnus-group-yank-group + "\C-w" gnus-group-kill-region + "\C-x\C-t" gnus-group-transpose-groups + "\C-c\C-l" gnus-group-list-killed + "\C-c\C-x" gnus-group-expire-articles + "\C-c\M-\C-x" gnus-group-expire-all-groups + "V" gnus-version + "s" gnus-group-save-newsrc + "z" gnus-group-suspend + "q" gnus-group-exit + "Q" gnus-group-quit + "?" gnus-group-describe-briefly + "\C-c\C-i" gnus-info-find-node + "\M-e" gnus-group-edit-group-method + "^" gnus-group-enter-server-mode + gnus-mouse-2 gnus-mouse-pick-group + [follow-link] mouse-face + "<" beginning-of-buffer + ">" end-of-buffer + "\C-c\C-b" gnus-bug + "\C-c\C-s" gnus-group-sort-groups + "t" gnus-topic-mode + "\C-c\M-g" gnus-activate-all-groups + "\M-&" gnus-group-universal-argument + "#" gnus-group-mark-group + "\M-#" gnus-group-unmark-group) + +(gnus-define-keys (gnus-group-mark-map "M" gnus-group-mode-map) + "m" gnus-group-mark-group + "u" gnus-group-unmark-group + "w" gnus-group-mark-region + "b" gnus-group-mark-buffer + "r" gnus-group-mark-regexp + "U" gnus-group-unmark-all-groups) + +(gnus-define-keys (gnus-group-sieve-map "D" gnus-group-mode-map) + "u" gnus-sieve-update + "g" gnus-sieve-generate) + +(gnus-define-keys (gnus-group-group-map "G" gnus-group-mode-map) + "d" gnus-group-make-directory-group + "h" gnus-group-make-help-group + "u" gnus-group-make-useful-group + "a" gnus-group-make-archive-group + "k" gnus-group-make-kiboze-group + "l" gnus-group-nnimap-edit-acl + "m" gnus-group-make-group + "E" gnus-group-edit-group + "e" gnus-group-edit-group-method + "p" gnus-group-edit-group-parameters + "v" gnus-group-add-to-virtual + "V" gnus-group-make-empty-virtual + "D" gnus-group-enter-directory + "f" gnus-group-make-doc-group + "w" gnus-group-make-web-group + "M" gnus-group-read-ephemeral-group + "r" gnus-group-rename-group + "R" gnus-group-make-rss-group + "c" gnus-group-customize + "z" gnus-group-compact-group + "x" gnus-group-nnimap-expunge + "\177" gnus-group-delete-group + [delete] gnus-group-delete-group) + +(gnus-define-keys (gnus-group-soup-map "s" gnus-group-group-map) + "b" gnus-group-brew-soup + "w" gnus-soup-save-areas + "s" gnus-soup-send-replies + "p" gnus-soup-pack-packet + "r" nnsoup-pack-replies) + +(gnus-define-keys (gnus-group-sort-map "S" gnus-group-group-map) + "s" gnus-group-sort-groups + "a" gnus-group-sort-groups-by-alphabet + "u" gnus-group-sort-groups-by-unread + "l" gnus-group-sort-groups-by-level + "v" gnus-group-sort-groups-by-score + "r" gnus-group-sort-groups-by-rank + "m" gnus-group-sort-groups-by-method + "n" gnus-group-sort-groups-by-real-name) + +(gnus-define-keys (gnus-group-sort-selected-map "P" gnus-group-group-map) + "s" gnus-group-sort-selected-groups + "a" gnus-group-sort-selected-groups-by-alphabet + "u" gnus-group-sort-selected-groups-by-unread + "l" gnus-group-sort-selected-groups-by-level + "v" gnus-group-sort-selected-groups-by-score + "r" gnus-group-sort-selected-groups-by-rank + "m" gnus-group-sort-selected-groups-by-method + "n" gnus-group-sort-selected-groups-by-real-name) + +(gnus-define-keys (gnus-group-list-map "A" gnus-group-mode-map) + "k" gnus-group-list-killed + "z" gnus-group-list-zombies + "s" gnus-group-list-groups + "u" gnus-group-list-all-groups + "A" gnus-group-list-active + "a" gnus-group-apropos + "d" gnus-group-description-apropos + "m" gnus-group-list-matching + "M" gnus-group-list-all-matching + "l" gnus-group-list-level + "c" gnus-group-list-cached + "?" gnus-group-list-dormant) + +(gnus-define-keys (gnus-group-list-limit-map "/" gnus-group-list-map) + "k" gnus-group-list-limit + "z" gnus-group-list-limit + "s" gnus-group-list-limit + "u" gnus-group-list-limit + "A" gnus-group-list-limit + "m" gnus-group-list-limit + "M" gnus-group-list-limit + "l" gnus-group-list-limit + "c" gnus-group-list-limit + "?" gnus-group-list-limit) + +(gnus-define-keys (gnus-group-list-flush-map "f" gnus-group-list-map) + "k" gnus-group-list-flush + "z" gnus-group-list-flush + "s" gnus-group-list-flush + "u" gnus-group-list-flush + "A" gnus-group-list-flush + "m" gnus-group-list-flush + "M" gnus-group-list-flush + "l" gnus-group-list-flush + "c" gnus-group-list-flush + "?" gnus-group-list-flush) + +(gnus-define-keys (gnus-group-list-plus-map "p" gnus-group-list-map) + "k" gnus-group-list-plus + "z" gnus-group-list-plus + "s" gnus-group-list-plus + "u" gnus-group-list-plus + "A" gnus-group-list-plus + "m" gnus-group-list-plus + "M" gnus-group-list-plus + "l" gnus-group-list-plus + "c" gnus-group-list-plus + "?" gnus-group-list-plus) + +(gnus-define-keys (gnus-group-score-map "W" gnus-group-mode-map) + "f" gnus-score-flush-cache + "e" gnus-score-edit-all-score) + +(gnus-define-keys (gnus-group-help-map "H" gnus-group-mode-map) + "c" gnus-group-fetch-charter + "C" gnus-group-fetch-control + "d" gnus-group-describe-group + "f" gnus-group-fetch-faq + "v" gnus-version) + +(gnus-define-keys (gnus-group-sub-map "S" gnus-group-mode-map) + "l" gnus-group-set-current-level + "t" gnus-group-unsubscribe-current-group + "s" gnus-group-unsubscribe-group + "k" gnus-group-kill-group + "y" gnus-group-yank-group + "w" gnus-group-kill-region + "\C-k" gnus-group-kill-level + "z" gnus-group-kill-all-zombies) + +(defun gnus-topic-mode-p () + "Return non-nil in `gnus-topic-mode'." + (and (boundp 'gnus-topic-mode) + (symbol-value 'gnus-topic-mode))) (defun gnus-group-make-menu-bar () (gnus-turn-off-edit-menu 'group) @@ -576,36 +773,79 @@ ticked: The number of ticked articles." (easy-menu-define gnus-group-reading-menu gnus-group-mode-map "" - '("Group" - ["Read" gnus-group-read-group (gnus-group-group-name)] - ["Select" gnus-group-select-group (gnus-group-group-name)] + `("Group" + ["Read" gnus-group-read-group + :included (not (gnus-topic-mode-p)) + :active (gnus-group-group-name)] + ["Read " gnus-topic-read-group + :included (gnus-topic-mode-p)] + ["Select" gnus-group-select-group + :included (not (gnus-topic-mode-p)) + :active (gnus-group-group-name)] + ["Select " gnus-topic-select-group + :included (gnus-topic-mode-p)] ["See old articles" (gnus-group-select-group 'all) :keys "C-u SPC" :active (gnus-group-group-name)] - ["Catch up" gnus-group-catchup-current (gnus-group-group-name)] + ["Catch up" gnus-group-catchup-current + :included (not (gnus-topic-mode-p)) + :active (gnus-group-group-name) + ,@(if (featurep 'xemacs) nil + '(:help "Mark unread articles in the current group as read"))] + ["Catch up " gnus-topic-catchup-articles + :included (gnus-topic-mode-p) + ,@(if (featurep 'xemacs) nil + '(:help "Mark unread articles in the current group or topic as read"))] ["Catch up all articles" gnus-group-catchup-current-all (gnus-group-group-name)] ["Check for new articles" gnus-group-get-new-news-this-group - (gnus-group-group-name)] + :included (not (gnus-topic-mode-p)) + :active (gnus-group-group-name) + ,@(if (featurep 'xemacs) nil + '(:help "Check for new messages in current group"))] + ["Check for new articles " gnus-topic-get-new-news-this-topic + :included (gnus-topic-mode-p) + ,@(if (featurep 'xemacs) nil + '(:help "Check for new messages in current group or topic"))] ["Toggle subscription" gnus-group-unsubscribe-current-group (gnus-group-group-name)] - ["Kill" gnus-group-kill-group (gnus-group-group-name)] + ["Kill" gnus-group-kill-group :active (gnus-group-group-name) + ,@(if (featurep 'xemacs) nil + '(:help "Kill (remove) current group"))] ["Yank" gnus-group-yank-group gnus-list-of-killed-groups] - ["Describe" gnus-group-describe-group (gnus-group-group-name)] + ["Describe" gnus-group-describe-group :active (gnus-group-group-name) + ,@(if (featurep 'xemacs) nil + '(:help "Display description of the current group"))] ["Fetch FAQ" gnus-group-fetch-faq (gnus-group-group-name)] + ["Fetch charter" gnus-group-fetch-charter + :active (gnus-group-group-name) + ,@(if (featurep 'xemacs) nil + '(:help "Display the charter of the current group"))] + ["Fetch control message" gnus-group-fetch-control + :active (gnus-group-group-name) + ,@(if (featurep 'xemacs) nil + '(:help "Display the archived control message for the current group"))] ;; Actually one should check, if any of the marked groups gives t for ;; (gnus-check-backend-function 'request-expire-articles ...) ["Expire articles" gnus-group-expire-articles - (or (and (gnus-group-group-name) - (gnus-check-backend-function - 'request-expire-articles - (gnus-group-group-name))) gnus-group-marked)] - ["Set group level" gnus-group-set-current-level + :included (not (gnus-topic-mode-p)) + :active (or (and (gnus-group-group-name) + (gnus-check-backend-function + 'request-expire-articles + (gnus-group-group-name))) gnus-group-marked)] + ["Expire articles " gnus-topic-expire-articles + :included (gnus-topic-mode-p)] + ["Set group level..." gnus-group-set-current-level (gnus-group-group-name)] ["Select quick" gnus-group-quick-select-group (gnus-group-group-name)] ["Customize" gnus-group-customize (gnus-group-group-name)] + ["Compact" gnus-group-compact-group + :active (gnus-group-group-name)] ("Edit" ["Parameters" gnus-group-edit-group-parameters - (gnus-group-group-name)] + :included (not (gnus-topic-mode-p)) + :active (gnus-group-group-name)] + ["Parameters " gnus-topic-edit-parameters + :included (gnus-topic-mode-p)] ["Select method" gnus-group-edit-group-method (gnus-group-group-name)] ["Info" gnus-group-edit-group (gnus-group-group-name)] @@ -626,7 +866,9 @@ ticked: The number of ticked articles." ["Group and description apropos..." gnus-group-description-apropos t] ["List groups matching..." gnus-group-list-matching t] ["List all groups matching..." gnus-group-list-all-matching t] - ["List active file" gnus-group-list-active t]) + ["List active file" gnus-group-list-active t] + ["List groups with cached" gnus-group-list-cached t] + ["List groups with dormant" gnus-group-list-dormant t]) ("Sort" ["Default sort" gnus-group-sort-groups t] ["Sort by method" gnus-group-sort-groups-by-method t] @@ -634,22 +876,25 @@ ticked: The number of ticked articles." ["Sort by score" gnus-group-sort-groups-by-score t] ["Sort by level" gnus-group-sort-groups-by-level t] ["Sort by unread" gnus-group-sort-groups-by-unread t] - ["Sort by name" gnus-group-sort-groups-by-alphabet t]) + ["Sort by name" gnus-group-sort-groups-by-alphabet t] + ["Sort by real name" gnus-group-sort-groups-by-real-name t]) ("Sort process/prefixed" ["Default sort" gnus-group-sort-selected-groups - (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))] + (not (gnus-topic-mode-p))] ["Sort by method" gnus-group-sort-selected-groups-by-method - (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))] + (not (gnus-topic-mode-p))] ["Sort by rank" gnus-group-sort-selected-groups-by-rank - (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))] + (not (gnus-topic-mode-p))] ["Sort by score" gnus-group-sort-selected-groups-by-score - (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))] + (not (gnus-topic-mode-p))] ["Sort by level" gnus-group-sort-selected-groups-by-level - (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))] + (not (gnus-topic-mode-p))] ["Sort by unread" gnus-group-sort-selected-groups-by-unread - (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))] + (not (gnus-topic-mode-p))] ["Sort by name" gnus-group-sort-selected-groups-by-alphabet - (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]) + (not (gnus-topic-mode-p))] + ["Sort by real name" gnus-group-sort-selected-groups-by-real-name + (not (gnus-topic-mode-p))]) ("Mark" ["Mark group" gnus-group-mark-group (and (gnus-group-group-name) @@ -659,27 +904,30 @@ ticked: The number of ticked articles." (memq (gnus-group-group-name) gnus-group-marked))] ["Unmark all" gnus-group-unmark-all-groups gnus-group-marked] ["Mark regexp..." gnus-group-mark-regexp t] - ["Mark region" gnus-group-mark-region t] + ["Mark region" gnus-group-mark-region :active (gnus-mark-active-p)] ["Mark buffer" gnus-group-mark-buffer t] ["Execute command" gnus-group-universal-argument (or gnus-group-marked (gnus-group-group-name))]) ("Subscribe" - ["Subscribe to a group" gnus-group-unsubscribe-group t] - ["Kill all newsgroups in region" gnus-group-kill-region t] + ["Subscribe to a group..." gnus-group-unsubscribe-group t] + ["Kill all newsgroups in region" gnus-group-kill-region + :active (gnus-mark-active-p)] ["Kill all zombie groups" gnus-group-kill-all-zombies gnus-zombie-list] ["Kill all groups on level..." gnus-group-kill-level t]) ("Foreign groups" - ["Make a foreign group" gnus-group-make-group t] - ["Add a directory group" gnus-group-make-directory-group t] + ["Make a foreign group..." gnus-group-make-group t] + ["Add a directory group..." gnus-group-make-directory-group t] ["Add the help group" gnus-group-make-help-group t] ["Add the archive group" gnus-group-make-archive-group t] - ["Make a doc group" gnus-group-make-doc-group t] - ["Make a web group" gnus-group-make-web-group t] - ["Make a kiboze group" gnus-group-make-kiboze-group t] - ["Make a virtual group" gnus-group-make-empty-virtual t] - ["Add a group to a virtual" gnus-group-add-to-virtual t] - ["Rename group" gnus-group-rename-group + ["Make a doc group..." gnus-group-make-doc-group t] + ["Make a web group..." gnus-group-make-web-group t] + ["Make a kiboze group..." gnus-group-make-kiboze-group t] + ["Make a virtual group..." gnus-group-make-empty-virtual t] + ["Add a group to a virtual..." gnus-group-add-to-virtual t] + ["Make an ephemeral group..." gnus-group-read-ephemeral-group t] + ["Make an RSS group..." gnus-group-make-rss-group t] + ["Rename group..." gnus-group-rename-group (gnus-check-backend-function 'request-rename-group (gnus-group-group-name))] ["Delete group" gnus-group-delete-group @@ -693,9 +941,12 @@ ticked: The number of ticked articles." ["Next unread same level" gnus-group-next-unread-group-same-level t] ["Previous unread same level" gnus-group-prev-unread-group-same-level t] - ["Jump to group" gnus-group-jump-to-group t] + ["Jump to group..." gnus-group-jump-to-group t] ["First unread group" gnus-group-first-unread-group t] ["Best unread group" gnus-group-best-unread-group t]) + ("Sieve" + ["Generate" gnus-sieve-generate t] + ["Generate and update" gnus-sieve-update t]) ["Delete bogus groups" gnus-group-check-bogus-groups t] ["Find new newsgroups" gnus-group-find-new-groups t] ["Transpose" gnus-group-transpose-groups @@ -704,7 +955,7 @@ ticked: The number of ticked articles." (easy-menu-define gnus-group-misc-menu gnus-group-mode-map "" - '("Misc" + `("Gnus" ("SOUP" ["Pack replies" nnsoup-pack-replies (fboundp 'nnsoup-request-group)] ["Send replies" gnus-soup-send-replies @@ -712,14 +963,21 @@ ticked: The number of ticked articles." ["Pack packet" gnus-soup-pack-packet (fboundp 'gnus-soup-pack-packet)] ["Save areas" gnus-soup-save-areas (fboundp 'gnus-soup-pack-packet)] ["Brew SOUP" gnus-group-brew-soup (fboundp 'gnus-soup-pack-packet)]) - ["Send a bug report" gnus-bug t] ["Send a mail" gnus-group-mail t] - ["Post an article..." gnus-group-post-news t] - ["Check for new news" gnus-group-get-new-news t] + ["Send a message (mail or news)" gnus-group-post-news t] + ["Create a local message" gnus-group-news t] + ["Check for new news" gnus-group-get-new-news + ,@(if (featurep 'xemacs) '(t) + '(:help "Get newly arrived articles")) + ] + ["Send queued messages" gnus-delay-send-queue + ,@(if (featurep 'xemacs) '(t) + '(:help "Send all messages that are scheduled to be sent now")) + ] ["Activate all groups" gnus-activate-all-groups t] ["Restart Gnus" gnus-group-restart t] ["Read init file" gnus-group-read-init-file t] - ["Browse foreign server" gnus-group-browse-foreign-server t] + ["Browse foreign server..." gnus-group-browse-foreign-server t] ["Enter server buffer" gnus-group-enter-server-mode t] ["Expire all expirable articles" gnus-group-expire-all-groups t] ["Generate any kiboze groups" nnkiboze-generate-groups t] @@ -730,11 +988,155 @@ ticked: The number of ticked articles." ["Read manual" gnus-info-find-node t] ["Flush score cache" gnus-score-flush-cache t] ["Toggle topics" gnus-topic-mode t] - ["Exit from Gnus" gnus-group-exit t] + ["Send a bug report" gnus-bug t] + ["Exit from Gnus" gnus-group-exit + ,@(if (featurep 'xemacs) '(t) + '(:help "Quit reading news"))] ["Exit without saving" gnus-group-quit t])) (gnus-run-hooks 'gnus-group-menu-hook))) + +(defvar gnus-group-tool-bar-map nil) + +(defun gnus-group-tool-bar-update (&optional symbol value) + "Update group buffer toolbar. +Setter function for custom variables." + (when symbol + (set-default symbol value)) + ;; (setq-default gnus-group-tool-bar-map nil) + ;; (use-local-map gnus-group-mode-map) + (when (gnus-alive-p) + (with-current-buffer gnus-group-buffer + (gnus-group-make-tool-bar t)))) + +(defcustom gnus-group-tool-bar (if (eq gmm-tool-bar-style 'gnome) + 'gnus-group-tool-bar-gnome + 'gnus-group-tool-bar-retro) + "Specifies the Gnus group tool bar. + +It can be either a list or a symbol refering to a list. See +`gmm-tool-bar-from-list' for the format of the list. The +default key map is `gnus-group-mode-map'. + +Pre-defined symbols include `gnus-group-tool-bar-gnome' and +`gnus-group-tool-bar-retro'." + :type '(choice (const :tag "GNOME style" gnus-group-tool-bar-gnome) + (const :tag "Retro look" gnus-group-tool-bar-retro) + (repeat :tag "User defined list" gmm-tool-bar-item) + (symbol)) + :version "23.0" ;; No Gnus + :initialize 'custom-initialize-default + :set 'gnus-group-tool-bar-update + :group 'gnus-group) + +(defcustom gnus-group-tool-bar-gnome + '((gnus-group-post-news "mail/compose") + ;; Some useful agent icons? I don't use the agent so agent users should + ;; suggest useful commands: + (gnus-agent-toggle-plugged "connect" t + :visible (and gnus-agent (not gnus-plugged))) + (gnus-agent-toggle-plugged "disconnect" t + :visible (and gnus-agent gnus-plugged)) + ;; FIXME: gnus-agent-toggle-plugged (in gnus-agent-group-make-menu-bar) + ;; should have a better help text. + (gnus-group-send-queue "mail/outbox" t + :visible (and gnus-agent gnus-plugged) + :help "Send articles from the queue group") + (gnus-group-get-new-news "mail/inbox" nil + :visible (or (not gnus-agent) + gnus-plugged)) + ;; FIXME: gnus-*-read-group should have a better help text. + (gnus-topic-read-group "open" nil + :visible (and (boundp 'gnus-topic-mode) + gnus-topic-mode)) + (gnus-group-read-group "open" nil + :visible (not (and (boundp 'gnus-topic-mode) + gnus-topic-mode))) + ;; (gnus-group-find-new-groups "???" nil) + (gnus-group-save-newsrc "save") + (gnus-group-describe-group "describe") + (gnus-group-unsubscribe-current-group "gnus/toggle-subscription") + (gnus-group-prev-unread-group "left-arrow") + (gnus-group-next-unread-group "right-arrow") + (gnus-group-exit "exit") + (gmm-customize-mode "preferences" t :help "Edit mode preferences") + (gnus-info-find-node "help")) + "List of functions for the group tool bar (GNOME style). + +See `gmm-tool-bar-from-list' for the format of the list." + :type '(repeat gmm-tool-bar-item) + :version "23.0" ;; No Gnus + :initialize 'custom-initialize-default + :set 'gnus-group-tool-bar-update + :group 'gnus-group) + +(defcustom gnus-group-tool-bar-retro + '((gnus-group-get-new-news "gnus/get-news") + (gnus-group-get-new-news-this-group "gnus/gnntg") + (gnus-group-catchup-current "gnus/catchup") + (gnus-group-describe-group "gnus/describe-group") + (gnus-group-subscribe "gnus/subscribe" t + :help "Subscribe to the current group") + (gnus-group-unsubscribe "gnus/unsubscribe" t + :help "Unsubscribe from the current group") + (gnus-group-exit "gnus/exit-gnus" gnus-group-mode-map)) + "List of functions for the group tool bar (retro look). + +See `gmm-tool-bar-from-list' for the format of the list." + :type '(repeat gmm-tool-bar-item) + :version "23.0" ;; No Gnus + :initialize 'custom-initialize-default + :set 'gnus-group-tool-bar-update + :group 'gnus-group) + +;; FIXME: Moving through the Group buffer (in topic mode) e.g. with C-n +;; doesn't update the state (enabled/disabled) of the icon +;; `gnus-group-describe-group'. After `C-l' the state is correct. +;; See the following report on emacs-devel +;; : +;; From: Reiner Steib +;; Subject: tool bar icons not updated according to :active condition +;; Newsgroups: gmane.emacs.devel +;; Date: Mon, 23 Jan 2006 19:59:13 +0100 +;; Message-ID: + +(defcustom gnus-group-tool-bar-zap-list t + "List of icon items from the global tool bar. +These items are not displayed in the Gnus group mode tool bar. + +See `gmm-tool-bar-from-list' for the format of the list." + :type 'gmm-tool-bar-zap-list + :version "23.0" ;; No Gnus + :initialize 'custom-initialize-default + :set 'gnus-group-tool-bar-update + :group 'gnus-group) + +(defvar image-load-path) + +(defun gnus-group-make-tool-bar (&optional force) + "Make a group mode tool bar from `gnus-group-tool-bar'. +When FORCE, rebuild the tool bar." + (when (and (not (featurep 'xemacs)) + (boundp 'tool-bar-mode) + tool-bar-mode + ;; The Gnus 5.10.6 code checked (default-value 'tool-bar-mode). + ;; Why? --rsteib + (or (not gnus-group-tool-bar-map) force)) + (let* ((load-path + (gmm-image-load-path-for-library "gnus" + "gnus/toggle-subscription.xpm" + nil t)) + (image-load-path (cons (car load-path) + (when (boundp 'image-load-path) + image-load-path))) + (map (gmm-tool-bar-from-list gnus-group-tool-bar + gnus-group-tool-bar-zap-list + 'gnus-group-mode-map))) + (if map + (set (make-local-variable 'tool-bar-map) map)))) + gnus-group-tool-bar-map) + (defun gnus-group-mode () "Major mode for reading news. @@ -753,30 +1155,33 @@ The following commands are available: \\{gnus-group-mode-map}" (interactive) - (when (gnus-visual-p 'group-menu 'menu) - (gnus-group-make-menu-bar)) (kill-all-local-variables) + (when (gnus-visual-p 'group-menu 'menu) + (gnus-group-make-menu-bar) + (gnus-group-make-tool-bar)) (gnus-simplify-mode-line) (setq major-mode 'gnus-group-mode) (setq mode-name "Group") (gnus-group-set-mode-line) (setq mode-line-process nil) (use-local-map gnus-group-mode-map) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq truncate-lines t) - (setq buffer-read-only t) + (setq buffer-read-only t + show-trailing-whitespace nil) (gnus-set-default-directory) (gnus-update-format-specifications nil 'group 'group-mode) (gnus-update-group-mark-positions) - (make-local-hook 'post-command-hook) - (add-hook 'post-command-hook 'gnus-clear-inboxes-moved nil t) (when gnus-use-undo (gnus-undo-mode 1)) - (gnus-run-hooks 'gnus-group-mode-hook)) + (when gnus-slave + (gnus-slave-mode)) + (gnus-run-mode-hooks 'gnus-group-mode-hook)) (defun gnus-update-group-mark-positions () (save-excursion (let ((gnus-process-mark ?\200) + (gnus-group-update-hook nil) (gnus-group-marked '("dummy.group")) (gnus-active-hashtb (make-vector 10 0)) (topic "")) @@ -785,12 +1190,10 @@ The following commands are available: (gnus-group-insert-group-line "dummy.group" 0 nil 0 nil) (goto-char (point-min)) (setq gnus-group-mark-positions - (list (cons 'process (and (search-forward "\200" nil t) + (list (cons 'process (and (search-forward + (mm-string-as-multibyte "\200") nil t) (- (point) 2)))))))) -(defun gnus-clear-inboxes-moved () - (setq nnmail-moved-inboxes nil)) - (defun gnus-mouse-pick-group (e) "Enter the group under the mouse pointer." (interactive "e") @@ -815,13 +1218,36 @@ The following commands are available: (or level gnus-group-default-list-level gnus-level-subscribed)))) (defun gnus-group-setup-buffer () - (set-buffer (get-buffer-create gnus-group-buffer)) + (set-buffer (gnus-get-buffer-create gnus-group-buffer)) (unless (eq major-mode 'gnus-group-mode) - (gnus-add-current-to-buffer-list) (gnus-group-mode) (when gnus-carpal (gnus-carpal-setup-buffer 'group)))) +(defun gnus-group-name-charset (method group) + (if (null method) + (setq method (gnus-find-method-for-group group))) + (let ((item (assoc method gnus-group-name-charset-method-alist)) + (alist gnus-group-name-charset-group-alist) + result) + (if item + (cdr item) + (while (setq item (pop alist)) + (if (string-match (car item) group) + (setq alist nil + result (cdr item)))) + result))) + +(defun gnus-group-name-decode (string charset) + ;; Fixme: Don't decode in unibyte mode. + (if (and string charset (featurep 'mule)) + (mm-decode-coding-string string charset) + string)) + +(defun gnus-group-decoded-name (string) + (let ((charset (gnus-group-name-charset nil string))) + (gnus-group-name-decode string charset))) + (defun gnus-group-list-groups (&optional level unread lowest) "List newsgroups with level LEVEL or lower that have unread articles. Default is all subscribed groups. @@ -836,8 +1262,6 @@ Also see the `gnus-group-use-permanent-levels' variable." (gnus-group-default-level nil t) gnus-group-default-list-level gnus-level-subscribed)))) - ;; Just do this here, for no particular good reason. - (gnus-clear-inboxes-moved) (unless level (setq level (car gnus-group-list-mode) unread (cdr gnus-group-list-mode))) @@ -845,7 +1269,7 @@ Also see the `gnus-group-use-permanent-levels' variable." (gnus-group-setup-buffer) (gnus-update-format-specifications nil 'group 'group-mode) (let ((case-fold-search nil) - (props (text-properties-at (gnus-point-at-bol))) + (props (text-properties-at (point-at-bol))) (empty (= (point-min) (point-max))) (group (gnus-group-group-name)) number) @@ -877,7 +1301,7 @@ Also see the `gnus-group-use-permanent-levels' variable." (point-min) (point-max) 'gnus-group (gnus-intern-safe group gnus-active-hashtb)))) - (let ((newsrc (cdddr (gnus-gethash group gnus-newsrc-hashtb)))) + (let ((newsrc (cdddr (gnus-group-entry group)))) (while (and newsrc (not (gnus-goto-char (text-property-any @@ -897,60 +1321,96 @@ If ALL (the prefix), also list groups that have no unread articles." (interactive "nList groups on level: \nP") (gnus-group-list-groups level all level)) -(defun gnus-group-prepare-flat (level &optional all lowest regexp) +(defun gnus-group-prepare-logic (group test) + (or (and gnus-group-listed-groups + (null gnus-group-list-option) + (member group gnus-group-listed-groups)) + (cond + ((null gnus-group-listed-groups) test) + ((null gnus-group-list-option) test) + (t (and (member group gnus-group-listed-groups) + (if (eq gnus-group-list-option 'flush) + (not test) + test)))))) + +(defun gnus-group-prepare-flat (level &optional predicate lowest regexp) "List all newsgroups with unread articles of level LEVEL or lower. -If ALL is non-nil, list groups that have no unread articles. +If PREDICATE is a function, list groups that the function returns non-nil; +if it is t, list groups that have no unread articles. If LOWEST is non-nil, list all newsgroups of level LOWEST or higher. -If REGEXP, only list groups matching REGEXP." +If REGEXP is a function, list dead groups that the function returns non-nil; +if it is a string, only list groups matching REGEXP." (set-buffer gnus-group-buffer) (let ((buffer-read-only nil) (newsrc (cdr gnus-newsrc-alist)) (lowest (or lowest 1)) + (not-in-list (and gnus-group-listed-groups + (copy-sequence gnus-group-listed-groups))) info clevel unread group params) (erase-buffer) - (when (< lowest gnus-level-zombie) + (when (or (< lowest gnus-level-zombie) + gnus-group-listed-groups) ;; List living groups. (while newsrc (setq info (car newsrc) group (gnus-info-group info) params (gnus-info-params info) newsrc (cdr newsrc) - unread (car (gnus-gethash group gnus-newsrc-hashtb))) - (and unread ; This group might be bogus - (or (not regexp) - (string-match regexp group)) - (<= (setq clevel (gnus-info-level info)) level) - (>= clevel lowest) - (or all ; We list all groups? - (if (eq unread t) ; Unactivated? - gnus-group-list-inactive-groups ; We list unactivated - (> unread 0)) ; We list groups with unread articles - (and gnus-list-groups-with-ticked-articles - (cdr (assq 'tick (gnus-info-marks info)))) + unread (gnus-group-unread group)) + (when not-in-list + (setq not-in-list (delete group not-in-list))) + (when (gnus-group-prepare-logic + group + (and unread ; This group might be unchecked + (or (not (stringp regexp)) + (string-match regexp group)) + (<= (setq clevel (gnus-info-level info)) level) + (>= clevel lowest) + (cond + ((functionp predicate) + (funcall predicate info)) + (predicate t) ; We list all groups? + (t + (or + (if (eq unread t) ; Unactivated? + gnus-group-list-inactive-groups + ; We list unactivated + (> unread 0)) + ; We list groups with unread articles + (and gnus-list-groups-with-ticked-articles + (cdr (assq 'tick (gnus-info-marks info)))) ; And groups with tickeds - ;; Check for permanent visibility. - (and gnus-permanently-visible-groups - (string-match gnus-permanently-visible-groups - group)) - (memq 'visible params) - (cdr (assq 'visible params))) - (gnus-group-insert-group-line - group (gnus-info-level info) - (gnus-info-marks info) unread (gnus-info-method info))))) + ;; Check for permanent visibility. + (and gnus-permanently-visible-groups + (string-match gnus-permanently-visible-groups + group)) + (memq 'visible params) + (cdr (assq 'visible params))))))) + (gnus-group-insert-group-line + group (gnus-info-level info) + (gnus-info-marks info) unread (gnus-info-method info))))) ;; List dead groups. - (and (>= level gnus-level-zombie) (<= lowest gnus-level-zombie) - (gnus-group-prepare-flat-list-dead - (setq gnus-zombie-list (sort gnus-zombie-list 'string<)) - gnus-level-zombie ?Z - regexp)) - (and (>= level gnus-level-killed) (<= lowest gnus-level-killed) - (gnus-group-prepare-flat-list-dead - (setq gnus-killed-list (sort gnus-killed-list 'string<)) - gnus-level-killed ?K regexp)) + (when (or gnus-group-listed-groups + (and (>= level gnus-level-zombie) + (<= lowest gnus-level-zombie))) + (gnus-group-prepare-flat-list-dead + (setq gnus-zombie-list (sort gnus-zombie-list 'string<)) + gnus-level-zombie ?Z + regexp)) + (when not-in-list + (dolist (group gnus-zombie-list) + (setq not-in-list (delete group not-in-list)))) + (when (or gnus-group-listed-groups + (and (>= level gnus-level-killed) (<= lowest gnus-level-killed))) + (gnus-group-prepare-flat-list-dead + (gnus-union + not-in-list + (setq gnus-killed-list (sort gnus-killed-list 'string<))) + gnus-level-killed ?K regexp)) (gnus-group-set-mode-line) - (setq gnus-group-list-mode (cons level all)) + (setq gnus-group-list-mode (cons level predicate)) (gnus-run-hooks 'gnus-group-prepare-hook) t)) @@ -959,33 +1419,44 @@ If REGEXP, only list groups matching REGEXP." ;; suggested by Jack Vinson . It does ;; this by ignoring the group format specification altogether. (let (group) - (if regexp - ;; This loop is used when listing groups that match some - ;; regexp. + (if (> (length groups) gnus-group-listing-limit) (while groups (setq group (pop groups)) - (when (string-match regexp group) + (when (gnus-group-prepare-logic + group + (or (not regexp) + (and (stringp regexp) (string-match regexp group)) + (and (functionp regexp) (funcall regexp group)))) (gnus-add-text-properties (point) (prog1 (1+ (point)) - (insert " " mark " *: " group "\n")) + (insert " " mark " *: " + (gnus-group-decoded-name group) + "\n")) (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb) 'gnus-unread t 'gnus-level level)))) - ;; This loop is used when listing all groups. (while groups - (gnus-add-text-properties - (point) (prog1 (1+ (point)) - (insert " " mark " *: " - (setq group (pop groups)) "\n")) - (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb) - 'gnus-unread t - 'gnus-level level)))))) + (setq group (pop groups)) + (when (gnus-group-prepare-logic + group + (or (not regexp) + (and (stringp regexp) (string-match regexp group)) + (and (functionp regexp) (funcall regexp group)))) + (gnus-group-insert-group-line + group level nil + (let ((active (gnus-active group))) + (if active + (if (zerop (cdr active)) + 0 + (- (1+ (cdr active)) (car active))) + nil)) + (gnus-method-simplify (gnus-find-method-for-group group)))))))) (defun gnus-group-update-group-line () "Update the current line in the group buffer." (let* ((buffer-read-only nil) (group (gnus-group-group-name)) - (entry (and group (gnus-gethash group gnus-newsrc-hashtb))) + (entry (and group (gnus-group-entry group))) gnus-group-indentation) (when group (and entry @@ -1002,7 +1473,7 @@ If REGEXP, only list groups matching REGEXP." (defun gnus-group-insert-group-line-info (group) "Insert GROUP on the current line." - (let ((entry (gnus-gethash group gnus-newsrc-hashtb)) + (let ((entry (gnus-group-entry group)) (gnus-group-indentation (gnus-group-group-indentation)) active info) (if entry @@ -1022,13 +1493,48 @@ If REGEXP, only list groups matching REGEXP." 0 (- (1+ (cdr active)) (car active))) nil) - nil)))) + (gnus-method-simplify (gnus-find-method-for-group group)))))) + +(defun gnus-number-of-unseen-articles-in-group (group) + (let* ((info (nth 2 (gnus-group-entry group))) + (marked (gnus-info-marks info)) + (seen (cdr (assq 'seen marked))) + (active (gnus-active group))) + (if (not active) + 0 + (length (gnus-uncompress-range + (gnus-range-difference + (gnus-range-difference (list active) (gnus-info-read info)) + seen)))))) + +(defcustom gnus-group-update-tool-bar + (and (not (featurep 'xemacs)) + (boundp 'tool-bar-mode) + tool-bar-mode + ;; Using `redraw-frame' (see `gnus-tool-bar-update') in Emacs 21 might + ;; be confusing, so maybe we shouldn't call it by default. + (fboundp 'force-window-update)) + "Force updating the group buffer tool bar." + :group 'gnus-group + :version "22.1" + :initialize 'custom-initialize-default + :set (lambda (symbol value) + (set-default symbol value) + (when (gnus-alive-p) + (with-current-buffer gnus-group-buffer + ;; FIXME: Is there a better way to redraw the group buffer? + (gnus-group-get-new-news 0)))) + :type 'boolean) (defun gnus-group-insert-group-line (gnus-tmp-group gnus-tmp-level gnus-tmp-marked number gnus-tmp-method) "Insert a group line in the group buffer." - (let* ((gnus-tmp-active (gnus-active gnus-tmp-group)) + (let* ((gnus-tmp-method + (gnus-server-get-method gnus-tmp-group gnus-tmp-method)) + (group-name-charset (gnus-group-name-charset gnus-tmp-method + gnus-tmp-group)) + (gnus-tmp-active (gnus-active gnus-tmp-group)) (gnus-tmp-number-total (if gnus-tmp-active (1+ (- (cdr gnus-tmp-active) (car gnus-tmp-active))) @@ -1045,10 +1551,17 @@ If REGEXP, only list groups matching REGEXP." ((<= gnus-tmp-level gnus-level-unsubscribed) ?U) ((= gnus-tmp-level gnus-level-zombie) ?Z) (t ?K))) - (gnus-tmp-qualified-group (gnus-group-real-name gnus-tmp-group)) + (gnus-tmp-qualified-group + (gnus-group-name-decode (gnus-group-real-name gnus-tmp-group) + group-name-charset)) + (gnus-tmp-comment + (or (gnus-group-get-parameter gnus-tmp-group 'comment t) + gnus-tmp-group)) (gnus-tmp-newsgroup-description (if gnus-description-hashtb - (or (gnus-gethash gnus-tmp-group gnus-description-hashtb) "") + (or (gnus-group-name-decode + (gnus-gethash gnus-tmp-group gnus-description-hashtb) + group-name-charset) "") "")) (gnus-tmp-moderated (if (and gnus-moderated-hashtb @@ -1056,8 +1569,7 @@ If REGEXP, only list groups matching REGEXP." ?m ? )) (gnus-tmp-moderated-string (if (eq gnus-tmp-moderated ?m) "(m)" "")) - (gnus-tmp-method - (gnus-server-get-method gnus-tmp-group gnus-tmp-method)) ; + (gnus-tmp-group-icon "==&&==") (gnus-tmp-news-server (or (cadr gnus-tmp-method) "")) (gnus-tmp-news-method (or (car gnus-tmp-method) "")) (gnus-tmp-news-method-string @@ -1069,32 +1581,43 @@ If REGEXP, only list groups matching REGEXP." (zerop number) (cdr (assq 'tick gnus-tmp-marked))) ?* ? )) + (gnus-tmp-summary-live + (if (and (not gnus-group-is-exiting-p) + (gnus-buffer-live-p (gnus-summary-buffer-name + gnus-tmp-group))) + ?* ? )) (gnus-tmp-process-marked (if (member gnus-tmp-group gnus-group-marked) gnus-process-mark ? )) - (gnus-tmp-grouplens - (or (and gnus-use-grouplens - (bbb-grouplens-group-p gnus-tmp-group)) - "")) (buffer-read-only nil) + beg end header gnus-tmp-header) ; passed as parameter to user-funcs. (beginning-of-line) + (setq beg (point)) (gnus-add-text-properties (point) (prog1 (1+ (point)) ;; Insert the text. - (eval gnus-group-line-format-spec)) + (let ((gnus-tmp-decoded-group (gnus-group-name-decode + gnus-tmp-group group-name-charset))) + (eval gnus-group-line-format-spec))) `(gnus-group ,(gnus-intern-safe gnus-tmp-group gnus-active-hashtb) gnus-unread ,(if (numberp number) - (string-to-int gnus-tmp-number-of-unread) + (string-to-number gnus-tmp-number-of-unread) t) gnus-marked ,gnus-tmp-marked-mark gnus-indentation ,gnus-group-indentation gnus-level ,gnus-tmp-level)) + (setq end (point)) + (when gnus-group-update-tool-bar + (gnus-put-text-property beg end 'point-entered + 'gnus-tool-bar-update) + (gnus-put-text-property beg end 'point-left + 'gnus-tool-bar-update)) + (forward-line -1) (when (inline (gnus-visual-p 'group-highlight 'highlight)) - (forward-line -1) - (gnus-run-hooks 'gnus-group-update-hook) - (forward-line)) + (gnus-run-hooks 'gnus-group-update-hook)) + (forward-line) ;; Allow XEmacs to remove front-sticky text properties. (gnus-group-remove-excess-properties))) @@ -1102,7 +1625,7 @@ If REGEXP, only list groups matching REGEXP." "Highlight the current line according to `gnus-group-highlight'." (let* ((list gnus-group-highlight) (p (point)) - (end (progn (end-of-line) (point))) + (end (point-at-eol)) ;; now find out where the line starts and leave point there. (beg (progn (beginning-of-line) (point))) (group (gnus-group-group-name)) @@ -1111,11 +1634,15 @@ If REGEXP, only list groups matching REGEXP." (active (gnus-active group)) (total (if active (1+ (- (cdr active) (car active))) 0)) (info (nth 2 entry)) - (method (gnus-server-get-method group (gnus-info-method info))) + (method (inline (gnus-server-get-method group (gnus-info-method info)))) (marked (gnus-info-marks info)) - (mailp (memq 'mail (assoc (symbol-name - (car (or method gnus-select-method))) - gnus-valid-select-methods))) + (mailp (apply 'append + (mapcar + (lambda (x) + (memq x (assoc (symbol-name + (car (or method gnus-select-method))) + gnus-valid-select-methods))) + '(mail post-mail)))) (level (or (gnus-info-level info) gnus-level-killed)) (score (or (gnus-info-score info) 0)) (ticked (gnus-range-length (cdr (assq 'tick marked)))) @@ -1127,7 +1654,7 @@ If REGEXP, only list groups matching REGEXP." (setq list (cdr list))) (let ((face (cdar list))) (unless (eq face (get-text-property beg 'face)) - (gnus-put-text-property + (gnus-put-text-property-excluding-characters-with-faces beg end 'face (setq face (if (boundp face) (symbol-value face) face))) (gnus-extent-start-open beg))) @@ -1149,8 +1676,9 @@ already." (loc (point-min)) found buffer-read-only) ;; Enter the current status into the dribble buffer. - (let ((entry (gnus-gethash group gnus-newsrc-hashtb))) - (when (and entry (not (gnus-ephemeral-group-p group))) + (let ((entry (gnus-group-entry group))) + (when (and entry + (not (gnus-ephemeral-group-p group))) (gnus-dribble-enter (concat "(gnus-group-set-info '" (gnus-prin1-to-string (nth 2 entry)) @@ -1173,7 +1701,7 @@ already." ;; go, and insert it there (or at the end of the buffer). (if gnus-goto-missing-group-function (funcall gnus-goto-missing-group-function group) - (let ((entry (cddr (gnus-gethash group gnus-newsrc-hashtb)))) + (let ((entry (cddr (gnus-group-entry group)))) (while (and entry (car entry) (not (gnus-goto-char @@ -1233,24 +1761,24 @@ already." (defun gnus-group-group-name () "Get the name of the newsgroup on the current line." - (let ((group (get-text-property (gnus-point-at-bol) 'gnus-group))) + (let ((group (get-text-property (point-at-bol) 'gnus-group))) (when group (symbol-name group)))) (defun gnus-group-group-level () "Get the level of the newsgroup on the current line." - (get-text-property (gnus-point-at-bol) 'gnus-level)) + (get-text-property (point-at-bol) 'gnus-level)) (defun gnus-group-group-indentation () "Get the indentation of the newsgroup on the current line." - (or (get-text-property (gnus-point-at-bol) 'gnus-indentation) + (or (get-text-property (point-at-bol) 'gnus-indentation) (and gnus-group-indentation-function (funcall gnus-group-indentation-function)) "")) (defun gnus-group-group-unread () "Get the number of unread articles of the newsgroup on the current line." - (get-text-property (gnus-point-at-bol) 'gnus-unread)) + (get-text-property (point-at-bol) 'gnus-unread)) (defun gnus-group-new-mail (group) (if (nnmail-new-mail-p (gnus-group-real-name group)) @@ -1308,10 +1836,28 @@ If FIRST-TOO, the current line is also eligible as a target." (goto-char (or pos beg)) (and pos t)))) +(defun gnus-total-fetched-for (group) + (let* ((size-in-cache (or (gnus-cache-total-fetched-for group) 0)) + (size-in-agent (or (gnus-agent-total-fetched-for group) 0)) + (size (+ size-in-cache size-in-agent)) + (suffix '("B" "K" "M" "G")) + (scale 1024.0) + (cutoff scale)) + (while (> size cutoff) + (setq size (/ size scale) + suffix (cdr suffix))) + (format "%5.1f%s" size (car suffix)))) + ;;; Gnus group mode commands ;; Group marking. +(defun gnus-group-mark-line-p () + (save-excursion + (beginning-of-line) + (forward-char (or (cdr (assq 'process gnus-group-mark-positions)) 2)) + (eq (char-after) gnus-process-mark))) + (defun gnus-group-mark-group (n &optional unmark no-advance) "Mark the current group." (interactive "p") @@ -1323,15 +1869,14 @@ If FIRST-TOO, the current line is also eligible as a target." ;; Go to the mark position. (beginning-of-line) (forward-char (or (cdr (assq 'process gnus-group-mark-positions)) 2)) - (subst-char-in-region - (point) (1+ (point)) (following-char) - (if unmark - (progn - (setq gnus-group-marked (delete group gnus-group-marked)) - ? ) + (delete-char 1) + (if unmark + (progn + (setq gnus-group-marked (delete group gnus-group-marked)) + (insert-char ? 1 t)) (setq gnus-group-marked (cons group (delete group gnus-group-marked))) - gnus-process-mark))) + (insert-char gnus-process-mark 1 t))) (unless no-advance (gnus-group-next-group 1)) (decf n)) @@ -1347,10 +1892,8 @@ If FIRST-TOO, the current line is also eligible as a target." (defun gnus-group-unmark-all-groups () "Unmark all groups." (interactive) - (let ((groups gnus-group-marked)) - (save-excursion - (while groups - (gnus-group-remove-mark (pop groups))))) + (save-excursion + (mapc 'gnus-group-remove-mark gnus-group-marked)) (gnus-group-position-point)) (defun gnus-group-mark-region (unmark beg end) @@ -1373,15 +1916,17 @@ If UNMARK, remove the mark instead." (interactive "sMark (regexp): ") (let ((alist (cdr gnus-newsrc-alist)) group) - (while alist - (when (string-match regexp (setq group (gnus-info-group (pop alist)))) - (gnus-group-set-mark group)))) + (save-excursion + (while alist + (when (string-match regexp (setq group (gnus-info-group (pop alist)))) + (gnus-group-jump-to-group group) + (gnus-group-set-mark group))))) (gnus-group-position-point)) -(defun gnus-group-remove-mark (group) +(defun gnus-group-remove-mark (group &optional test-marked) "Remove the process mark from GROUP and move point there. Return nil if the group isn't displayed." - (if (gnus-group-goto-group group) + (if (gnus-group-goto-group group nil test-marked) (save-excursion (gnus-group-mark-group 1 'unmark t) t) @@ -1429,7 +1974,7 @@ Take into consideration N (the prefix) and the list of marked groups." (setq n (1- n)) (gnus-group-next-group way))) (nreverse groups))) - ((gnus-region-active-p) + ((and (gnus-region-active-p) (mark)) ;; Work on the region between point and mark. (let ((max (max (point) (mark))) groups) @@ -1460,23 +2005,25 @@ Take into consideration N (the prefix) and the list of marked groups." (eval `(defun gnus-group-iterate (arg ,function) "Iterate FUNCTION over all process/prefixed groups. -FUNCTION will be called with the group name as the paremeter +FUNCTION will be called with the group name as the parameter and with point over the group in question." (let ((,groups (gnus-group-process-prefix arg)) (,window (selected-window)) ,group) - (while (setq ,group (pop ,groups)) + (while ,groups + (setq ,group (car ,groups) + ,groups (cdr ,groups)) (select-window ,window) (gnus-group-remove-mark ,group) (save-selected-window (save-excursion (funcall ,function ,group))))))))) - + (put 'gnus-group-iterate 'lisp-indent-function 1) ;; Selecting groups. -(defun gnus-group-read-group (&optional all no-article group) +(defun gnus-group-read-group (&optional all no-article group select-articles) "Read news in this newsgroup. If the prefix argument ALL is non-nil, already read articles become readable. IF ALL is a number, fetch this number of articles. If the @@ -1492,8 +2039,7 @@ group." (unless group (error "No group on current line")) (setq marked (gnus-info-marks - (nth 2 (setq entry (gnus-gethash - group gnus-newsrc-hashtb))))) + (nth 2 (setq entry (gnus-group-entry group))))) ;; This group might be a dead group. In that case we have to get ;; the number of unread articles from `gnus-active-hashtb'. (setq number @@ -1507,14 +2053,20 @@ group." (cdr (assq 'tick marked))) (gnus-range-length (cdr (assq 'dormant marked))))))) - no-article nil no-display))) + no-article nil no-display nil select-articles))) (defun gnus-group-select-group (&optional all) "Select this newsgroup. No article is selected automatically. +If the group is opened, just switch the summary buffer. If ALL is non-nil, already read articles become readable. -If ALL is a number, fetch this number of articles." +If ALL is a positive number, fetch this number of the latest +articles in the group. +If ALL is a negative number, fetch this number of the earliest +articles in the group." (interactive "P") + (when (and (eobp) (not (gnus-group-group-name))) + (forward-line -1)) (gnus-group-read-group all t)) (defun gnus-group-quick-select-group (&optional all) @@ -1553,56 +2105,100 @@ be permanent." gnus-summary-mode-hook gnus-select-group-hook (group (gnus-group-group-name)) (method (gnus-find-method-for-group group))) - (setq method - `(,(car method) ,(concat (cadr method) "-ephemeral") - (,(intern (format "%s-address" (car method))) ,(cadr method)) - ,@(cddr method))) (gnus-group-read-ephemeral-group (gnus-group-prefixed-name group method) method))) ;;;###autoload -(defun gnus-fetch-group (group) +(defun gnus-fetch-group (group &optional articles) "Start Gnus if necessary and enter GROUP. Returns whether the fetching was successful or not." - (interactive "sGroup name: ") + (interactive (list (completing-read "Group name: " gnus-active-hashtb))) (unless (get-buffer gnus-group-buffer) (gnus-no-server)) - (gnus-group-read-group nil nil group)) + (gnus-group-read-group articles nil group)) + +;;;###autoload +(defun gnus-fetch-group-other-frame (group) + "Pop up a frame and enter GROUP." + (interactive "P") + (let ((window (get-buffer-window gnus-group-buffer))) + (cond (window + (select-frame (window-frame window))) + ((= (length (frame-list)) 1) + (select-frame (make-frame))) + (t + (other-frame 1)))) + (gnus-fetch-group group)) (defvar gnus-ephemeral-group-server 0) +(defcustom gnus-large-ephemeral-newsgroup 200 + "The number of articles which indicates a large ephemeral newsgroup. +Same as `gnus-large-newsgroup', but only used for ephemeral newsgroups. + +If the number of articles in a newsgroup is greater than this value, +confirmation is required for selecting the newsgroup. If it is nil, no +confirmation is required." + :version "22.1" + :group 'gnus-group-select + :type '(choice (const :tag "No limit" nil) + integer)) + +(defcustom gnus-fetch-old-ephemeral-headers nil + "Same as `gnus-fetch-old-headers', but only used for ephemeral newsgroups." + :version "22.1" + :group 'gnus-thread + :type '(choice (const :tag "off" nil) + (const some) + number + (sexp :menu-tag "other" t))) + ;; Enter a group that is not in the group buffer. Non-nil is returned ;; if selection was successful. (defun gnus-group-read-ephemeral-group (group method &optional activate - quit-config request-only) + quit-config request-only + select-articles + parameters + number) "Read GROUP from METHOD as an ephemeral group. If ACTIVATE, request the group first. If QUIT-CONFIG, use that window configuration when exiting from the ephemeral group. If REQUEST-ONLY, don't actually read the group; just request it. +If SELECT-ARTICLES, only select those articles. +If PARAMETERS, use those as the group parameters. +If NUMBER, fetch this number of articles. -Return the name of the group is selection was successful." +Return the name of the group if selection was successful." + (interactive + (list + ;; (gnus-read-group "Group name: ") + (completing-read + "Group: " gnus-active-hashtb + nil nil nil + 'gnus-group-history) + (gnus-read-method "From method: "))) ;; Transform the select method into a unique server. (when (stringp method) (setq method (gnus-server-to-method method))) -;;; (let ((saddr (intern (format "%s-address" (car method))))) -;;; (setq method (gnus-copy-sequence method)) -;;; (require (car method)) -;;; (when (boundp saddr) -;;; (unless (assq saddr method) -;;; (nconc method `((,saddr ,(cadr method)))) -;;; (setf (cadr method) (format "%s-%d" (cadr method) -;;; (incf gnus-ephemeral-group-server)))))) + (setq method + `(,(car method) ,(concat (cadr method) "-ephemeral") + (,(intern (format "%s-address" (car method))) ,(cadr method)) + ,@(cddr method))) (let ((group (if (gnus-group-foreign-p group) group - (gnus-group-prefixed-name group method)))) + (gnus-group-prefixed-name (gnus-group-real-name group) + method)))) (gnus-sethash group `(-1 nil (,group ,gnus-level-default-subscribed nil nil ,method - ((quit-config . - ,(if quit-config quit-config - (cons gnus-summary-buffer - gnus-current-window-configuration)))))) + ,(cons + (if quit-config + (cons 'quit-config quit-config) + (cons 'quit-config + (cons gnus-summary-buffer + gnus-current-window-configuration))) + parameters))) gnus-newsrc-hashtb) (push method gnus-ephemeral-servers) (set-buffer gnus-group-buffer) @@ -1616,19 +2212,33 @@ Return the name of the group is selection was successful." (if request-only group (condition-case () - (when (gnus-group-read-group t t group) + (when (let ((gnus-large-newsgroup gnus-large-ephemeral-newsgroup) + (gnus-fetch-old-headers + gnus-fetch-old-ephemeral-headers)) + (gnus-group-read-group (or number t) t group select-articles)) group) ;;(error nil) - (quit nil))))) + (quit + (message "Quit reading the ephemeral group") + nil))))) + +(defun gnus-group-jump-to-group (group &optional prompt) + "Jump to newsgroup GROUP. -(defun gnus-group-jump-to-group (group) - "Jump to newsgroup GROUP." +If PROMPT (the prefix) is a number, use the prompt specified in +`gnus-group-jump-to-group-prompt'." (interactive - (list (completing-read - "Group: " gnus-active-hashtb nil - (gnus-read-active-file-p) - nil - 'gnus-group-history))) + (list (mm-string-make-unibyte + (completing-read + "Group: " gnus-active-hashtb nil + (gnus-read-active-file-p) + (if current-prefix-arg + (cdr (assq current-prefix-arg gnus-group-jump-to-group-prompt)) + (or (and (stringp gnus-group-jump-to-group-prompt) + gnus-group-jump-to-group-prompt) + (let ((p (cdr (assq 0 gnus-group-jump-to-group-prompt)))) + (and (stringp p) p)))) + 'gnus-group-history)))) (when (equal group "") (error "Empty group name")) @@ -1642,41 +2252,56 @@ Return the name of the group is selection was successful." ;; Adjust cursor point. (gnus-group-position-point)) -(defun gnus-group-goto-group (group &optional far) +(defun gnus-group-goto-group (group &optional far test-marked) "Goto to newsgroup GROUP. -If FAR, it is likely that the group is not on the current line." +If FAR, it is likely that the group is not on the current line. +If TEST-MARKED, the line must be marked." (when group - (if far - (gnus-goto-char - (text-property-any - (point-min) (point-max) - 'gnus-group (gnus-intern-safe group gnus-active-hashtb))) - (beginning-of-line) - (cond - ;; It's quite likely that we are on the right line, so - ;; we check the current line first. - ((eq (get-text-property (point) 'gnus-group) - (gnus-intern-safe group gnus-active-hashtb)) - (point)) - ;; Previous and next line are also likely, so we check them as well. - ((save-excursion - (forward-line -1) - (eq (get-text-property (point) 'gnus-group) - (gnus-intern-safe group gnus-active-hashtb))) - (forward-line -1) - (point)) - ((save-excursion - (forward-line 1) - (eq (get-text-property (point) 'gnus-group) - (gnus-intern-safe group gnus-active-hashtb))) - (forward-line 1) - (point)) - (t - ;; Search through the entire buffer. - (gnus-goto-char - (text-property-any - (point-min) (point-max) - 'gnus-group (gnus-intern-safe group gnus-active-hashtb)))))))) + (beginning-of-line) + (cond + ;; It's quite likely that we are on the right line, so + ;; we check the current line first. + ((and (not far) + (eq (get-text-property (point) 'gnus-group) + (gnus-intern-safe group gnus-active-hashtb)) + (or (not test-marked) (gnus-group-mark-line-p))) + (point)) + ;; Previous and next line are also likely, so we check them as well. + ((and (not far) + (save-excursion + (forward-line -1) + (and (eq (get-text-property (point) 'gnus-group) + (gnus-intern-safe group gnus-active-hashtb)) + (or (not test-marked) (gnus-group-mark-line-p))))) + (forward-line -1) + (point)) + ((and (not far) + (save-excursion + (forward-line 1) + (and (eq (get-text-property (point) 'gnus-group) + (gnus-intern-safe group gnus-active-hashtb)) + (or (not test-marked) (gnus-group-mark-line-p))))) + (forward-line 1) + (point)) + (test-marked + (goto-char (point-min)) + (let (found) + (while (and (not found) + (gnus-goto-char + (text-property-any + (point) (point-max) + 'gnus-group + (gnus-intern-safe group gnus-active-hashtb)))) + (if (gnus-group-mark-line-p) + (setq found t) + (forward-line 1))) + found)) + (t + ;; Search through the entire buffer. + (gnus-goto-char + (text-property-any + (point-min) (point-max) + 'gnus-group (gnus-intern-safe group gnus-active-hashtb))))))) (defun gnus-group-next-group (n &optional silent) "Go to next N'th newsgroup. @@ -1758,9 +2383,28 @@ If EXCLUDE-GROUP, do not go to that group." (forward-line 1)) (when best-point (goto-char best-point)) - (gnus-summary-position-point) + (gnus-group-position-point) (and best-point (gnus-group-group-name)))) +;; Is there something like an after-point-motion-hook? +;; (inhibit-point-motion-hooks?). Is there a tool-bar-update function? + +;; (defun gnus-group-menu-bar-update () +;; (let* ((buf (list (with-current-buffer gnus-group-buffer +;; (current-buffer)))) +;; (name (buffer-name (car buf)))) +;; (setcdr buf +;; (if (> (length name) 27) +;; (concat (substring name 0 12) +;; "..." +;; (substring name -12)) +;; name)) +;; (menu-bar-update-buffers-1 buf))) + +;; (defun gnus-group-position-point () +;; (gnus-goto-colon) +;; (gnus-group-menu-bar-update)) + (defun gnus-group-first-unread-group () "Go to the first group with unread articles." (interactive) @@ -1782,6 +2426,16 @@ If EXCLUDE-GROUP, do not go to that group." (interactive) (gnus-enter-server-buffer)) +(defun gnus-group-make-group-simple (&optional group) + "Add a new newsgroup. +The user will be prompted for GROUP." + (interactive + (list (completing-read "Group: " gnus-active-hashtb + nil nil nil 'gnus-group-history))) + (gnus-group-make-group + (gnus-group-real-name group) + (gnus-group-server group))) + (defun gnus-group-make-group (name &optional method address args) "Add a new newsgroup. The user will be prompted for a NAME, for a select METHOD, and an @@ -1791,21 +2445,23 @@ ADDRESS." (gnus-read-group "Group name: ") (gnus-read-method "From method: "))) - (let* ((meth (when (and method - (not (gnus-server-equal method gnus-select-method))) - (if address (list (intern method) address) - method))) + (when (stringp method) + (setq method (or (gnus-server-to-method method) method))) + (let* ((meth (gnus-method-simplify + (when (and method + (not (gnus-server-equal method gnus-select-method))) + (if address (list (intern method) address) + method)))) (nname (if method (gnus-group-prefixed-name name meth) name)) backend info) - (when (gnus-gethash nname gnus-newsrc-hashtb) - (error "Group %s already exists" nname)) + (when (gnus-group-entry nname) + (error "Group %s already exists" (gnus-group-decoded-name nname))) ;; Subscribe to the new group. (gnus-group-change-level (setq info (list t nname gnus-level-default-subscribed nil nil meth)) gnus-level-default-subscribed gnus-level-killed (and (gnus-group-group-name) - (gnus-gethash (gnus-group-group-name) - gnus-newsrc-hashtb)) + (gnus-group-entry (gnus-group-group-name))) t) ;; Make it active. (gnus-set-active nname (cons 1 0)) @@ -1818,7 +2474,7 @@ ADDRESS." (forward-line -1) (gnus-group-position-point) - ;; Load the backend and try to make the backend create + ;; Load the back end and try to make the back end create ;; the group as well. (when (assoc (symbol-name (setq backend (car (gnus-server-get-method nil meth)))) @@ -1826,36 +2482,54 @@ ADDRESS." (require backend)) (gnus-check-server meth) (when (gnus-check-backend-function 'request-create-group nname) - (gnus-request-create-group nname nil args)) + (unless (gnus-request-create-group nname nil args) + (error "Could not create group on server: %s" + (nnheader-get-report backend)))) t)) -(defun gnus-group-delete-group (group &optional force) - "Delete the current group. Only meaningful with mail groups. +(defun gnus-group-delete-groups (&optional arg) + "Delete the current group. Only meaningful with editable groups." + (interactive "P") + (let ((n (length (gnus-group-process-prefix arg)))) + (when (gnus-yes-or-no-p + (if (= n 1) + "Delete this 1 group? " + (format "Delete these %d groups? " n))) + (gnus-group-iterate arg + (lambda (group) + (gnus-group-delete-group group nil t)))))) + +(defun gnus-group-delete-group (group &optional force no-prompt) + "Delete the current group. Only meaningful with editable groups. If FORCE (the prefix) is non-nil, all the articles in the group will be deleted. This is \"deleted\" as in \"removed forever from the face -of the Earth\". There is no undo. The user will be prompted before -doing the deletion." +of the Earth\". There is no undo. The user will be prompted before +doing the deletion. +Note that you also have to specify FORCE if you want the group to +be removed from the server, even when it's empty." (interactive (list (gnus-group-group-name) current-prefix-arg)) (unless group - (error "No group to rename")) + (error "No group to delete")) (unless (gnus-check-backend-function 'request-delete-group group) - (error "This backend does not support group deletion")) + (error "This back end does not support group deletion")) (prog1 - (if (not (gnus-yes-or-no-p - (format - "Do you really want to delete %s%s? " - group (if force " and all its contents" "")))) - () ; Whew! - (gnus-message 6 "Deleting group %s..." group) - (if (not (gnus-request-delete-group group force)) - (gnus-error 3 "Couldn't delete group %s" group) - (gnus-message 6 "Deleting group %s...done" group) - (gnus-group-goto-group group) - (gnus-group-kill-group 1 t) - (gnus-sethash group nil gnus-active-hashtb) - t)) + (let ((group-decoded (gnus-group-decoded-name group))) + (if (and (not no-prompt) + (not (gnus-yes-or-no-p + (format + "Do you really want to delete %s%s? " + group-decoded (if force " and all its contents" ""))))) + () ; Whew! + (gnus-message 6 "Deleting group %s..." group-decoded) + (if (not (gnus-request-delete-group group force)) + (gnus-error 3 "Couldn't delete group %s" group-decoded) + (gnus-message 6 "Deleting group %s...done" group-decoded) + (gnus-group-goto-group group) + (gnus-group-kill-group 1 t) + (gnus-set-active group nil) + t))) (gnus-group-position-point))) (defun gnus-group-rename-group (group new-name) @@ -1868,12 +2542,12 @@ and NEW-NAME will be prompted for." (progn (unless (gnus-check-backend-function 'request-rename-group (gnus-group-group-name)) - (error "This backend does not support renaming groups")) + (error "This back end does not support renaming groups")) (gnus-read-group "Rename group to: " (gnus-group-real-name (gnus-group-group-name)))))) (unless (gnus-check-backend-function 'request-rename-group group) - (error "This backend does not support renaming groups")) + (error "This back end does not support renaming groups")) (unless group (error "No group to rename")) (when (equal (gnus-group-real-name group) new-name) @@ -1889,12 +2563,17 @@ and NEW-NAME will be prompted for." (gnus-group-real-name new-name) (gnus-info-method (gnus-get-info group))))) + (when (gnus-active new-name) + (error "The group %s already exists" new-name)) + (gnus-message 6 "Renaming group %s to %s..." group new-name) (prog1 - (if (not (gnus-request-rename-group group new-name)) + (if (progn + (gnus-group-goto-group group) + (not (when (< (gnus-group-group-level) gnus-level-zombie) + (gnus-request-rename-group group new-name)))) (gnus-error 3 "Couldn't rename group %s to %s" group new-name) ;; We rename the group internally by killing it... - (gnus-group-goto-group group) (gnus-group-kill-group) ;; ... changing its name ... (setcar (cdar gnus-list-of-killed-groups) new-name) @@ -1903,6 +2582,8 @@ and NEW-NAME will be prompted for." (gnus-set-active new-name (gnus-active group)) (gnus-message 6 "Renaming group %s to %s...done" group new-name) new-name) + (setq gnus-killed-list (delete group gnus-killed-list)) + (gnus-set-active group nil) (gnus-dribble-touch) (gnus-group-position-point))) @@ -1931,9 +2612,19 @@ and NEW-NAME will be prompted for." ((eq part 'method) "select method") ((eq part 'params) "group parameters") (t "group info")) - group) + (gnus-group-decoded-name group)) `(lambda (form) - (gnus-group-edit-group-done ',part ,group form))))) + (gnus-group-edit-group-done ',part ,group form))) + (local-set-key + "\C-c\C-i" + (gnus-create-info-command + (cond + ((eq part 'method) + "(gnus)Select Methods") + ((eq part 'params) + "(gnus)Group Parameters") + (t + "(gnus)Group Info")))))) (defun gnus-group-edit-group-method (group) "Edit the select method of GROUP." @@ -1982,6 +2673,7 @@ and NEW-NAME will be prompted for." (gnus-group-position-point))) (defun gnus-group-make-useful-group (group method) + "Create one of the groups described in `gnus-useful-groups'." (interactive (let ((entry (assoc (completing-read "Create group: " gnus-useful-groups nil t) @@ -1993,25 +2685,39 @@ and NEW-NAME will be prompted for." (setcar entry (eval (cadar entry))))) (gnus-group-make-group group method)) -(defun gnus-group-make-help-group () - "Create the Gnus documentation group." +(defun gnus-group-make-help-group (&optional noerror) + "Create the Gnus documentation group. +Optional argument NOERROR modifies the behavior of this function when the +group already exists: +- if not given, and error is signaled, +- if t, stay silent, +- if anything else, just print a message." (interactive) (let ((name (gnus-group-prefixed-name "gnus-help" '(nndoc "gnus-help"))) - (file (nnheader-find-etc-directory "gnus-tut.txt" t)) - dir) - (when (gnus-gethash name gnus-newsrc-hashtb) - (error "Documentation group already exists")) - (if (not file) - (gnus-message 1 "Couldn't find doc group") - (gnus-group-make-group - (gnus-group-real-name name) - (list 'nndoc "gnus-help" - (list 'nndoc-address file) - (list 'nndoc-article-type 'mbox))))) + (file (nnheader-find-etc-directory "gnus-tut.txt" t))) + (if (gnus-group-entry name) + (cond ((eq noerror nil) + (error "Documentation group already exists")) + ((eq noerror t) + ;; stay silent + ) + (t + (gnus-message 1 "Documentation group already exists"))) + ;; else: + (if (not file) + (gnus-message 1 "Couldn't find doc group") + (gnus-group-make-group + (gnus-group-real-name name) + (list 'nndoc "gnus-help" + (list 'nndoc-address file) + (list 'nndoc-article-type 'mbox)))) + )) (gnus-group-position-point)) (defun gnus-group-make-doc-group (file type) - "Create a group that uses a single file as the source." + "Create a group that uses a single file as the source. + +If called with a prefix argument, ask for the file type." (interactive (list (read-file-name "File name: ") (and current-prefix-arg 'ask))) @@ -2020,13 +2726,14 @@ and NEW-NAME will be prompted for." char found) (while (not found) (message - "%sFile type (mbox, babyl, digest, forward, mmdf, guess) [mbdfag]: " + "%sFile type (mbox, babyl, digest, forward, mmdf, guess) [m, b, d, f, a, g]: " err) (setq found (cond ((= (setq char (read-char)) ?m) 'mbox) ((= char ?b) 'babyl) ((= char ?d) 'digest) ((= char ?f) 'forward) ((= char ?a) 'mmfd) + ((= char ?g) 'guess) (t (setq err (format "%c unknown. " char)) nil)))) (setq type found))) @@ -2071,19 +2778,93 @@ If SOLID (the prefix), create a solid group." (nnweb-type ,(intern type)) (nnweb-ephemeral-p t)))) (if solid - (gnus-group-make-group group "nnweb" "" `(,(intern type) ,search)) + (progn + (gnus-pull 'nnweb-ephemeral-p method) + (gnus-group-make-group group method)) (gnus-group-read-ephemeral-group group method t (cons (current-buffer) (if (eq major-mode 'gnus-summary-mode) 'summary 'group)))))) +(eval-when-compile + (defvar nnrss-group-alist) + (defun nnrss-discover-feed (arg)) + (defun nnrss-save-server-data (arg))) +(defun gnus-group-make-rss-group (&optional url) + "Given a URL, discover if there is an RSS feed. +If there is, use Gnus to create an nnrss group" + (interactive) + (require 'nnrss) + (if (not url) + (setq url (read-from-minibuffer "URL to Search for RSS: "))) + (let ((feedinfo (nnrss-discover-feed url))) + (if feedinfo + (let ((title (gnus-newsgroup-savable-name + (read-from-minibuffer "Title: " + (gnus-newsgroup-savable-name + (or (cdr (assoc 'title + feedinfo)) + ""))))) + (desc (read-from-minibuffer "Description: " + (cdr (assoc 'description + feedinfo)))) + (href (cdr (assoc 'href feedinfo))) + (encodable (mm-coding-system-p 'utf-8))) + (when encodable + ;; Unify non-ASCII text. + (setq title (mm-decode-coding-string + (mm-encode-coding-string title 'utf-8) 'utf-8))) + (gnus-group-make-group (if encodable + (mm-encode-coding-string title 'utf-8) + title) + '(nnrss "")) + (push (list title href desc) nnrss-group-alist) + (nnrss-save-server-data nil)) + (error "No feeds found for %s" url)))) + +(defvar nnwarchive-type-definition) +(defvar gnus-group-warchive-type-history nil) +(defvar gnus-group-warchive-login-history nil) +(defvar gnus-group-warchive-address-history nil) + +(defun gnus-group-make-warchive-group () + "Create a nnwarchive group." + (interactive) + (require 'nnwarchive) + (let* ((group (gnus-read-group "Group name: ")) + (default-type (or (car gnus-group-warchive-type-history) + (symbol-name (caar nnwarchive-type-definition)))) + (type + (gnus-string-or + (completing-read + (format "Warchive type (default %s): " default-type) + (mapcar (lambda (elem) (list (symbol-name (car elem)))) + nnwarchive-type-definition) + nil t nil 'gnus-group-warchive-type-history) + default-type)) + (address (read-string "Warchive address: " + nil 'gnus-group-warchive-address-history)) + (default-login (or (car gnus-group-warchive-login-history) + user-mail-address)) + (login + (gnus-string-or + (read-string + (format "Warchive login (default %s): " user-mail-address) + default-login 'gnus-group-warchive-login-history) + user-mail-address)) + (method + `(nnwarchive ,address + (nnwarchive-type ,(intern type)) + (nnwarchive-login ,login)))) + (gnus-group-make-group group method))) + (defun gnus-group-make-archive-group (&optional all) "Create the (ding) Gnus archive group of the most recent articles. Given a prefix, create a full group." (interactive "P") (let ((group (gnus-group-prefixed-name (if all "ding.archives" "ding.recent") '(nndir "")))) - (when (gnus-gethash group gnus-newsrc-hashtb) + (when (gnus-group-entry group) (error "Archive group already exists")) (gnus-group-make-group (gnus-group-real-name group) @@ -2107,17 +2888,17 @@ mail messages or news articles in files that have numeric names." (let ((ext "") (i 0) group) - (while (or (not group) (gnus-gethash group gnus-newsrc-hashtb)) + (while (or (not group) (gnus-group-entry group)) (setq group (gnus-group-prefixed-name - (concat (file-name-as-directory (directory-file-name dir)) - ext) + (expand-file-name ext dir) '(nndir ""))) (setq ext (format "<%d>" (setq i (1+ i))))) (gnus-group-make-group (gnus-group-real-name group) (list 'nndir (gnus-group-real-name group) (list 'nndir-directory dir))))) +(defvar nnkiboze-score-file) (defun gnus-group-make-kiboze-group (group address scores) "Create an nnkiboze group. The user will be prompted for a name, a regexp to match groups, and @@ -2126,7 +2907,7 @@ score file entries for articles to include in the group." (list (read-string "nnkiboze group name: ") (read-string "Source groups (regexp): ") - (let ((headers (mapcar (lambda (group) (list group)) + (let ((headers (mapcar 'list '("subject" "from" "number" "date" "message-id" "references" "chars" "lines" "xref" "followup" "all" "body" "head"))) @@ -2135,15 +2916,20 @@ score file entries for articles to include in the group." "Match on header: " headers nil t)))) (setq regexps nil) (while (not (equal "" (setq regexp (read-string - (format "Match on %s (string): " + (format "Match on %s (regexp): " header))))) (push (list regexp nil nil 'r) regexps)) (push (cons header regexps) scores)) scores))) (gnus-group-make-group group "nnkiboze" address) - (nnheader-temp-write (gnus-score-file-name (concat "nnkiboze:" group)) - (let (emacs-lisp-mode-hook) - (pp scores (current-buffer))))) + (let* ((nnkiboze-current-group group) + (score-file (car (nnkiboze-score-file ""))) + (score-dir (file-name-directory score-file))) + (unless (file-exists-p score-dir) + (make-directory score-dir)) + (with-temp-file score-file + (let (emacs-lisp-mode-hook) + (gnus-pp scores))))) (defun gnus-group-add-to-virtual (n vgroup) "Add the current group to a virtual group." @@ -2172,7 +2958,7 @@ score file entries for articles to include in the group." (let* ((method (list 'nnvirtual "^$")) (pgroup (gnus-group-prefixed-name group method))) ;; Check whether it exists already. - (when (gnus-gethash pgroup gnus-newsrc-hashtb) + (when (gnus-group-entry pgroup) (error "Group %s already exists" pgroup)) ;; Subscribe the new group after the group on the current line. (gnus-subscribe-group pgroup (gnus-group-group-name) method) @@ -2195,6 +2981,63 @@ score file entries for articles to include in the group." 'summary 'group))) (error "Couldn't enter %s" dir)))) +(eval-and-compile + (autoload 'nnimap-expunge "nnimap") + (autoload 'nnimap-acl-get "nnimap") + (autoload 'nnimap-acl-edit "nnimap")) + +(defun gnus-group-nnimap-expunge (group) + "Expunge deleted articles in current nnimap GROUP." + (interactive (list (gnus-group-group-name))) + (let ((mailbox (gnus-group-real-name group)) method) + (unless group + (error "No group on current line")) + (unless (gnus-get-info group) + (error "Killed group; can't be edited")) + (unless (eq 'nnimap (car (setq method (gnus-find-method-for-group group)))) + (error "%s is not an nnimap group" group)) + (nnimap-expunge mailbox (cadr method)))) + +(defun gnus-group-nnimap-edit-acl (group) + "Edit the Access Control List of current nnimap GROUP." + (interactive (list (gnus-group-group-name))) + (let ((mailbox (gnus-group-real-name group)) method acl) + (unless group + (error "No group on current line")) + (unless (gnus-get-info group) + (error "Killed group; can't be edited")) + (unless (eq (car (setq method (gnus-find-method-for-group group))) 'nnimap) + (error "%s is not an nnimap group" group)) + (unless (setq acl (nnimap-acl-get mailbox (cadr method))) + (error "Server does not support ACL's")) + (gnus-edit-form acl (format "Editing the access control list for `%s'. + + An access control list is a list of (identifier . rights) elements. + + The identifier string specifies the corresponding user. The + identifier \"anyone\" is reserved to refer to the universal identity. + + Rights is a string listing a (possibly empty) set of alphanumeric + characters, each character listing a set of operations which is being + controlled. Letters are reserved for ``standard'' rights, listed + below. Digits are reserved for implementation or site defined rights. + + l - lookup (mailbox is visible to LIST/LSUB commands) + r - read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL, + SEARCH, COPY from mailbox) + s - keep seen/unseen information across sessions (STORE \\SEEN flag) + w - write (STORE flags other than \\SEEN and \\DELETED) + i - insert (perform APPEND, COPY into mailbox) + p - post (send mail to submission address for mailbox, + not enforced by IMAP4 itself) + c - create and delete mailbox (CREATE new sub-mailboxes in any + implementation-defined hierarchy, RENAME or DELETE mailbox) + d - delete messages (STORE \\DELETED flag, perform EXPUNGE) + a - administer (perform SETACL)" group) + `(lambda (form) + (nnimap-acl-edit + ,mailbox ',method ',acl form))))) + ;; Group sorting commands ;; Suggested by Joe Hildebrand . @@ -2206,6 +3049,7 @@ If REVERSE (the prefix), reverse the sorting order." (interactive (list gnus-group-sort-function current-prefix-arg)) (funcall gnus-group-sort-alist-function (gnus-make-sort-function func) reverse) + (gnus-group-unmark-all-groups) (gnus-group-list-groups) (gnus-dribble-touch)) @@ -2228,6 +3072,12 @@ If REVERSE, sort in reverse order." (interactive "P") (gnus-group-sort-groups 'gnus-group-sort-by-alphabet reverse)) +(defun gnus-group-sort-groups-by-real-name (&optional reverse) + "Sort the group buffer alphabetically by real (unprefixed) group name. +If REVERSE, sort in reverse order." + (interactive "P") + (gnus-group-sort-groups 'gnus-group-sort-by-real-name reverse)) + (defun gnus-group-sort-groups-by-unread (&optional reverse) "Sort the group buffer by number of unread articles. If REVERSE, sort in reverse order." @@ -2253,11 +3103,17 @@ If REVERSE, sort in reverse order." (gnus-group-sort-groups 'gnus-group-sort-by-rank reverse)) (defun gnus-group-sort-groups-by-method (&optional reverse) - "Sort the group buffer alphabetically by backend name. + "Sort the group buffer alphabetically by back end name. If REVERSE, sort in reverse order." (interactive "P") (gnus-group-sort-groups 'gnus-group-sort-by-method reverse)) +(defun gnus-group-sort-groups-by-server (&optional reverse) + "Sort the group buffer alphabetically by server name. +If REVERSE, sort in reverse order." + (interactive "P") + (gnus-group-sort-groups 'gnus-group-sort-by-server reverse)) + ;;; Selected group sorting. (defun gnus-group-sort-selected-groups (n func &optional reverse) @@ -2266,13 +3122,15 @@ If REVERSE, sort in reverse order." (let ((groups (gnus-group-process-prefix n))) (funcall gnus-group-sort-selected-function groups (gnus-make-sort-function func) reverse) - (gnus-group-list-groups))) + (gnus-group-unmark-all-groups) + (gnus-group-list-groups) + (gnus-dribble-touch))) (defun gnus-group-sort-selected-flat (groups func reverse) (let (entries infos) ;; First find all the group entries for these groups. (while groups - (push (nthcdr 2 (gnus-gethash (pop groups) gnus-newsrc-hashtb)) + (push (nthcdr 2 (gnus-group-entry (pop groups))) entries)) ;; Then sort the infos. (setq infos @@ -2286,46 +3144,59 @@ If REVERSE, sort in reverse order." ;; Go through all the infos and replace the old entries ;; with the new infos. (while infos - (setcar entries (pop infos)) + (setcar (car entries) (pop infos)) (pop entries)) ;; Update the hashtable. (gnus-make-hashtable-from-newsrc-alist))) -(defun gnus-group-sort-selected-groups-by-alphabet (&optional reverse) +(defun gnus-group-sort-selected-groups-by-alphabet (&optional n reverse) "Sort the group buffer alphabetically by group name. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-alphabet reverse)) - -(defun gnus-group-sort-selected-groups-by-unread (&optional reverse) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-alphabet reverse)) + +(defun gnus-group-sort-selected-groups-by-real-name (&optional n reverse) + "Sort the group buffer alphabetically by real group name. +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-real-name reverse)) + +(defun gnus-group-sort-selected-groups-by-unread (&optional n reverse) "Sort the group buffer by number of unread articles. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-unread reverse)) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-unread reverse)) -(defun gnus-group-sort-selected-groups-by-level (&optional reverse) +(defun gnus-group-sort-selected-groups-by-level (&optional n reverse) "Sort the group buffer by group level. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-level reverse)) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-level reverse)) -(defun gnus-group-sort-selected-groups-by-score (&optional reverse) +(defun gnus-group-sort-selected-groups-by-score (&optional n reverse) "Sort the group buffer by group score. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-score reverse)) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-score reverse)) -(defun gnus-group-sort-selected-groups-by-rank (&optional reverse) +(defun gnus-group-sort-selected-groups-by-rank (&optional n reverse) "Sort the group buffer by group rank. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-rank reverse)) - -(defun gnus-group-sort-selected-groups-by-method (&optional reverse) - "Sort the group buffer alphabetically by backend name. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-method reverse)) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-rank reverse)) + +(defun gnus-group-sort-selected-groups-by-method (&optional n reverse) + "Sort the group buffer alphabetically by back end name. +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-method reverse)) ;;; Sorting predicates. @@ -2340,8 +3211,8 @@ If REVERSE, sort in reverse order." (defun gnus-group-sort-by-unread (info1 info2) "Sort by number of unread articles." - (let ((n1 (car (gnus-gethash (gnus-info-group info1) gnus-newsrc-hashtb))) - (n2 (car (gnus-gethash (gnus-info-group info2) gnus-newsrc-hashtb)))) + (let ((n1 (gnus-group-unread (gnus-info-group info1))) + (n2 (gnus-group-unread (gnus-info-group info1)))) (< (or (and (numberp n1) n1) 0) (or (and (numberp n2) n2) 0)))) @@ -2350,15 +3221,24 @@ If REVERSE, sort in reverse order." (< (gnus-info-level info1) (gnus-info-level info2))) (defun gnus-group-sort-by-method (info1 info2) - "Sort alphabetically by backend name." - (string< (symbol-name (car (gnus-find-method-for-group - (gnus-info-group info1) info1))) - (symbol-name (car (gnus-find-method-for-group - (gnus-info-group info2) info2))))) + "Sort alphabetically by back end name." + (string< (car (gnus-find-method-for-group + (gnus-info-group info1) info1)) + (car (gnus-find-method-for-group + (gnus-info-group info2) info2)))) + +(defun gnus-group-sort-by-server (info1 info2) + "Sort alphabetically by server name." + (string< (gnus-method-to-full-server-name + (gnus-find-method-for-group + (gnus-info-group info1) info1)) + (gnus-method-to-full-server-name + (gnus-find-method-for-group + (gnus-info-group info2) info2)))) (defun gnus-group-sort-by-score (info1 info2) "Sort by group score." - (< (gnus-info-score info1) (gnus-info-score info2))) + (> (gnus-info-score info1) (gnus-info-score info2))) (defun gnus-group-sort-by-rank (info1 info2) "Sort by level and score." @@ -2371,7 +3251,8 @@ If REVERSE, sort in reverse order." ;;; Clearing data (defun gnus-group-clear-data (&optional arg) - "Clear all marks and read ranges from the current group." + "Clear all marks and read ranges from the current group. +Obeys the process/prefix convention." (interactive "P") (gnus-group-iterate arg (lambda (group) @@ -2391,20 +3272,29 @@ If REVERSE, sort in reverse order." (when (gnus-group-native-p (gnus-info-group info)) (gnus-info-clear-data info))) (gnus-get-unread-articles) - (gnus-dribble-enter "") + (gnus-dribble-touch) (when (gnus-y-or-n-p "Move the cache away to avoid problems in the future? ") (call-interactively 'gnus-cache-move-cache))))) (defun gnus-info-clear-data (info) "Clear all marks and read ranges from INFO." - (let ((group (gnus-info-group info))) + (let ((group (gnus-info-group info)) + action) + (dolist (el (gnus-info-marks info)) + (push `(,(cdr el) add (,(car el))) action)) + (push `(,(gnus-info-read info) add (read)) action) (gnus-undo-register `(progn + (gnus-request-set-mark ,group ',action) (gnus-info-set-marks ',info ',(gnus-info-marks info) t) (gnus-info-set-read ',info ',(gnus-info-read info)) (when (gnus-group-goto-group ,group) + (gnus-get-unread-articles-in-group ',info ',(gnus-active group) t) (gnus-group-update-group-line)))) + (setq action (mapcar (lambda (el) (list (nth 0 el) 'del (nth 2 el))) + action)) + (gnus-request-set-mark group action) (gnus-info-set-read info nil) (when (gnus-info-marks info) (gnus-info-set-marks info nil)))) @@ -2412,7 +3302,7 @@ If REVERSE, sort in reverse order." ;; Group catching up. (defun gnus-group-catchup-current (&optional n all) - "Mark all articles not marked as unread in current newsgroup as read. + "Mark all unread articles in the current newsgroup as read. If prefix argument N is numeric, the next N newsgroups will be caught up. If ALL is non-nil, marked articles will also be marked as read. Cross references (Xref: header) of articles are ignored. @@ -2420,7 +3310,8 @@ The number of newsgroups that this function was unable to catch up is returned." (interactive "P") (let ((groups (gnus-group-process-prefix n)) - (ret 0)) + (ret 0) + group) (unless groups (error "No groups selected")) (if (not (or (not gnus-interactive-catchup) ;Without confirmation? @@ -2431,24 +3322,23 @@ up is returned." "Do you really want to mark all articles in %s as read? " "Mark all unread articles in %s as read? ") (if (= (length groups) 1) - (car groups) + (gnus-group-decoded-name (car groups)) (format "these %d groups" (length groups))))))) n - (while groups + (while (setq group (pop groups)) + (gnus-group-remove-mark group) ;; Virtual groups have to be given special treatment. - (let ((method (gnus-find-method-for-group (car groups)))) + (let ((method (gnus-find-method-for-group group))) (when (eq 'nnvirtual (car method)) (nnvirtual-catchup-group - (gnus-group-real-name (car groups)) (nth 1 method) all))) - (gnus-group-remove-mark (car groups)) - (if (>= (gnus-group-group-level) gnus-level-zombie) + (gnus-group-real-name group) (nth 1 method) all))) + (if (>= (gnus-group-level group) gnus-level-zombie) (gnus-message 2 "Dead groups can't be caught up") (if (prog1 - (gnus-group-goto-group (car groups)) - (gnus-group-catchup (car groups) all)) + (gnus-group-goto-group group) + (gnus-group-catchup group all)) (gnus-group-update-group-line) - (setq ret (1+ ret)))) - (setq groups (cdr groups))) + (setq ret (1+ ret))))) (gnus-group-next-unread-group 1) ret))) @@ -2463,33 +3353,40 @@ Cross references (Xref: header) of articles are ignored." If ALL is non-nil, all articles are marked as read. The return value is the number of articles that were marked as read, or nil if no action could be taken." - (let* ((entry (gnus-gethash group gnus-newsrc-hashtb)) - (num (car entry))) + (let* ((entry (gnus-group-entry group)) + (num (car entry)) + (marks (gnus-info-marks (nth 2 entry))) + (unread (gnus-sequence-of-unread-articles group))) + ;; Remove entries for this group. + (nnmail-purge-split-history (gnus-group-real-name group)) ;; Do the updating only if the newsgroup isn't killed. (if (not (numberp (car entry))) (gnus-message 1 "Can't catch up %s; non-active group" group) + (gnus-update-read-articles group nil) + (when all + ;; Nix out the lists of marks and dormants. + (gnus-request-set-mark group (list (list (cdr (assq 'tick marks)) + 'del '(tick)) + (list (cdr (assq 'dormant marks)) + 'del '(dormant)))) + (setq unread (gnus-range-add (gnus-range-add + unread (cdr (assq 'dormant marks))) + (cdr (assq 'tick marks)))) + (gnus-add-marked-articles group 'tick nil nil 'force) + (gnus-add-marked-articles group 'dormant nil nil 'force)) ;; Do auto-expirable marks if that's required. (when (gnus-group-auto-expirable-p group) - (gnus-add-marked-articles - group 'expire (gnus-list-of-unread-articles group)) - (when all - (let ((marks (nth 3 (nth 2 entry)))) - (gnus-add-marked-articles - group 'expire (gnus-uncompress-range (cdr (assq 'tick marks)))) - (gnus-add-marked-articles - group 'expire (gnus-uncompress-range (cdr (assq 'tick marks))))))) - (when entry - (gnus-update-read-articles group nil) - ;; Also nix out the lists of marks and dormants. - (when all - (gnus-add-marked-articles group 'tick nil nil 'force) - (gnus-add-marked-articles group 'dormant nil nil 'force)) - (let ((gnus-newsgroup-name group)) - (gnus-run-hooks 'gnus-group-catchup-group-hook)) - num)))) + (gnus-range-map (lambda (article) + (gnus-add-marked-articles group 'expire (list article)) + (gnus-request-set-mark group (list (list (list article) 'add '(expire))))) + unread)) + (let ((gnus-newsgroup-name group)) + (gnus-run-hooks 'gnus-group-catchup-group-hook)) + num))) (defun gnus-group-expire-articles (&optional n) - "Expire all expirable articles in the current newsgroup." + "Expire all expirable articles in the current newsgroup. +Uses the process/prefix convention." (interactive "P") (let ((groups (gnus-group-process-prefix n)) group) @@ -2497,32 +3394,43 @@ or nil if no action could be taken." (error "No groups to expire")) (while (setq group (pop groups)) (gnus-group-remove-mark group) - (when (gnus-check-backend-function 'request-expire-articles group) - (gnus-message 6 "Expiring articles in %s..." group) - (let* ((info (gnus-get-info group)) - (expirable (if (gnus-group-total-expirable-p group) - (cons nil (gnus-list-of-read-articles group)) - (assq 'expire (gnus-info-marks info)))) - (expiry-wait (gnus-group-find-parameter group 'expiry-wait))) - (when expirable - (setcdr - expirable - (gnus-compress-sequence - (if expiry-wait - ;; We set the expiry variables to the group - ;; parameter. - (let ((nnmail-expiry-wait-function nil) - (nnmail-expiry-wait expiry-wait)) - (gnus-request-expire-articles - (gnus-uncompress-sequence (cdr expirable)) group)) - ;; Just expire using the normal expiry values. - (gnus-request-expire-articles - (gnus-uncompress-sequence (cdr expirable)) group)))) - (gnus-close-group group)) - (gnus-message 6 "Expiring articles in %s...done" group))) + (gnus-group-expire-articles-1 group) (gnus-dribble-touch) (gnus-group-position-point)))) +(defun gnus-group-expire-articles-1 (group) + (when (gnus-check-backend-function 'request-expire-articles group) + (gnus-message 6 "Expiring articles in %s..." + (gnus-group-decoded-name group)) + (let* ((info (gnus-get-info group)) + (expirable (if (gnus-group-total-expirable-p group) + (cons nil (gnus-list-of-read-articles group)) + (assq 'expire (gnus-info-marks info)))) + (expiry-wait (gnus-group-find-parameter group 'expiry-wait)) + (nnmail-expiry-target + (or (gnus-group-find-parameter group 'expiry-target) + nnmail-expiry-target))) + (when expirable + (gnus-check-group group) + (setcdr + expirable + (gnus-compress-sequence + (if expiry-wait + ;; We set the expiry variables to the group + ;; parameter. + (let ((nnmail-expiry-wait-function nil) + (nnmail-expiry-wait expiry-wait)) + (gnus-request-expire-articles + (gnus-uncompress-sequence (cdr expirable)) group)) + ;; Just expire using the normal expiry values. + (gnus-request-expire-articles + (gnus-uncompress-sequence (cdr expirable)) group)))) + (gnus-close-group group)) + (gnus-message 6 "Expiring articles in %s...done" + (gnus-group-decoded-name group)) + ;; Return the list of un-expired articles. + (cdr expirable)))) + (defun gnus-group-expire-all-groups () "Expire all expirable articles in all newsgroups." (interactive) @@ -2539,27 +3447,29 @@ or nil if no action could be taken." (interactive (list current-prefix-arg - (string-to-int - (let ((s (read-string - (format "Level (default %s): " - (or (gnus-group-group-level) - gnus-level-default-subscribed))))) - (if (string-match "^\\s-*$" s) - (int-to-string (or (gnus-group-group-level) - gnus-level-default-subscribed)) - s))))) + (progn + (unless (gnus-group-process-prefix current-prefix-arg) + (error "No group on the current line")) + (string-to-number + (let ((s (read-string + (format "Level (default %s): " + (or (gnus-group-group-level) + gnus-level-default-subscribed))))) + (if (string-match "^\\s-*$" s) + (int-to-string (or (gnus-group-group-level) + gnus-level-default-subscribed)) + s)))))) (unless (and (>= level 1) (<= level gnus-level-killed)) - (error "Illegal level: %d" level)) - (let ((groups (gnus-group-process-prefix n)) - group) - (while (setq group (pop groups)) - (gnus-group-remove-mark group) - (gnus-message 6 "Changed level of %s from %d to %d" - group (or (gnus-group-group-level) gnus-level-killed) - level) - (gnus-group-change-level - group level (or (gnus-group-group-level) gnus-level-killed)) - (gnus-group-update-group-line))) + (error "Invalid level: %d" level)) + (dolist (group (gnus-group-process-prefix n)) + (gnus-group-remove-mark group) + (gnus-message 6 "Changed level of %s from %d to %d" + (gnus-group-decoded-name group) + (or (gnus-group-group-level) gnus-level-killed) + level) + (gnus-group-change-level + group level (or (gnus-group-group-level) gnus-level-killed)) + (gnus-group-update-group-line)) (gnus-group-position-point)) (defun gnus-group-unsubscribe (&optional n) @@ -2576,26 +3486,22 @@ or nil if no action could be taken." "Toggle subscription of the current group. If given numerical prefix, toggle the N next groups." (interactive "P") - (let ((groups (gnus-group-process-prefix n)) - group) - (while groups - (setq group (car groups) - groups (cdr groups)) - (gnus-group-remove-mark group) - (gnus-group-unsubscribe-group - group - (cond - ((eq do-sub 'unsubscribe) - gnus-level-default-unsubscribed) - ((eq do-sub 'subscribe) - gnus-level-default-subscribed) - ((<= (gnus-group-group-level) gnus-level-subscribed) - gnus-level-default-unsubscribed) - (t - gnus-level-default-subscribed)) - t) - (gnus-group-update-group-line)) - (gnus-group-next-group 1))) + (dolist (group (gnus-group-process-prefix n)) + (gnus-group-remove-mark group) + (gnus-group-unsubscribe-group + group + (cond + ((eq do-sub 'unsubscribe) + gnus-level-default-unsubscribed) + ((eq do-sub 'subscribe) + gnus-level-default-subscribed) + ((<= (gnus-group-group-level) gnus-level-subscribed) + gnus-level-default-unsubscribed) + (t + gnus-level-default-subscribed)) + t) + (gnus-group-update-group-line)) + (gnus-group-next-group 1)) (defun gnus-group-unsubscribe-group (group &optional level silent) "Toggle subscription to GROUP. @@ -2607,7 +3513,7 @@ group line." (gnus-read-active-file-p) nil 'gnus-group-history))) - (let ((newsrc (gnus-gethash group gnus-newsrc-hashtb))) + (let ((newsrc (gnus-group-entry group))) (cond ((string-match "^[ \t]*$" group) (error "Empty group name")) @@ -2631,7 +3537,7 @@ group line." gnus-level-zombie) gnus-level-killed) (when (gnus-group-group-name) - (gnus-gethash (gnus-group-group-name) gnus-newsrc-hashtb))) + (gnus-group-entry (gnus-group-group-name)))) (unless silent (gnus-group-update-group group))) (t (error "No such newsgroup: %s" group))) @@ -2650,13 +3556,15 @@ N and the number of steps taken is returned." (gnus-group-yank-group) (gnus-group-position-point))) -(defun gnus-group-kill-all-zombies () - "Kill all zombie newsgroups." - (interactive) - (setq gnus-killed-list (nconc gnus-zombie-list gnus-killed-list)) - (setq gnus-zombie-list nil) - (gnus-dribble-touch) - (gnus-group-list-groups)) +(defun gnus-group-kill-all-zombies (&optional dummy) + "Kill all zombie newsgroups. +The optional DUMMY should always be nil." + (interactive (list (not (gnus-yes-or-no-p "Really kill all zombies? ")))) + (unless dummy + (setq gnus-killed-list (nconc gnus-zombie-list gnus-killed-list)) + (setq gnus-zombie-list nil) + (gnus-dribble-touch) + (gnus-group-list-groups))) (defun gnus-group-kill-region (begin end) "Kill newsgroups in current region (excluding current point). @@ -2668,12 +3576,10 @@ The killed newsgroups can be yanked by using \\[gnus-group-yank-group]." (count-lines (progn (goto-char begin) - (beginning-of-line) - (point)) + (point-at-bol)) (progn (goto-char end) - (beginning-of-line) - (point)))))) + (point-at-bol)))))) (goto-char begin) (beginning-of-line) ;Important when LINES < 1 (gnus-group-kill-group lines))) @@ -2697,7 +3603,7 @@ of groups killed." (setq level (gnus-group-group-level)) (gnus-delete-line) (when (and (not discard) - (setq entry (gnus-gethash group gnus-newsrc-hashtb))) + (setq entry (gnus-group-entry group))) (gnus-undo-register `(progn (gnus-group-goto-group ,(gnus-group-group-name)) @@ -2705,32 +3611,31 @@ of groups killed." (push (cons (car entry) (nth 2 entry)) gnus-list-of-killed-groups)) (gnus-group-change-level - (if entry entry group) gnus-level-killed (if entry nil level))) + (if entry entry group) gnus-level-killed (if entry nil level)) + (message "Killed group %s" (gnus-group-decoded-name group))) ;; If there are lots and lots of groups to be killed, we use ;; this thing instead. - (let (entry) - (setq groups (nreverse groups)) - (while groups - (gnus-group-remove-mark (setq group (pop groups))) - (gnus-delete-line) - (push group gnus-killed-list) - (setq gnus-newsrc-alist - (delq (assoc group gnus-newsrc-alist) - gnus-newsrc-alist)) - (when gnus-group-change-level-function - (funcall gnus-group-change-level-function - group gnus-level-killed 3)) - (cond - ((setq entry (gnus-gethash group gnus-newsrc-hashtb)) - (push (cons (car entry) (nth 2 entry)) - gnus-list-of-killed-groups) - (setcdr (cdr entry) (cdddr entry))) - ((member group gnus-zombie-list) - (setq gnus-zombie-list (delete group gnus-zombie-list)))) - ;; There may be more than one instance displayed. - (while (gnus-group-goto-group group) - (gnus-delete-line))) - (gnus-make-hashtable-from-newsrc-alist))) + (dolist (group (nreverse groups)) + (gnus-group-remove-mark group) + (gnus-delete-line) + (push group gnus-killed-list) + (setq gnus-newsrc-alist + (delq (assoc group gnus-newsrc-alist) + gnus-newsrc-alist)) + (when gnus-group-change-level-function + (funcall gnus-group-change-level-function + group gnus-level-killed 3)) + (cond + ((setq entry (gnus-group-entry group)) + (push (cons (car entry) (nth 2 entry)) + gnus-list-of-killed-groups) + (setcdr (cdr entry) (cdddr entry))) + ((member group gnus-zombie-list) + (setq gnus-zombie-list (delete group gnus-zombie-list)))) + ;; There may be more than one instance displayed. + (while (gnus-group-goto-group group) + (gnus-delete-line))) + (gnus-make-hashtable-from-newsrc-alist)) (gnus-group-position-point) (if (< (length out) 2) (car out) (nreverse out)))) @@ -2754,7 +3659,7 @@ yanked) a list of yanked groups is returned." (setq prev (gnus-group-group-name)) (gnus-group-change-level info (gnus-info-level (cdr info)) gnus-level-killed - (and prev (gnus-gethash prev gnus-newsrc-hashtb)) + (and prev (gnus-group-entry prev)) t) (gnus-group-insert-group-line-info group) (gnus-undo-register @@ -2791,11 +3696,11 @@ yanked) a list of yanked groups is returned." (gnus-make-hashtable-from-newsrc-alist) (gnus-group-list-groups))) (t - (error "Can't kill; illegal level: %d" level)))) + (error "Can't kill; invalid level: %d" level)))) (defun gnus-group-list-all-groups (&optional arg) "List all newsgroups with level ARG or lower. -Default is gnus-level-unsubscribed, which lists all subscribed and most +Default is `gnus-level-unsubscribed', which lists all subscribed and most unsubscribed groups." (interactive "P") (gnus-group-list-groups (or arg gnus-level-unsubscribed) t)) @@ -2834,7 +3739,8 @@ entail asking the server for the groups." (interactive) ;; First we make sure that we have really read the active file. (unless (gnus-read-active-file-p) - (let ((gnus-read-active-file t)) + (let ((gnus-read-active-file t) + (gnus-agent gnus-plugged)); If we're actually plugged, store the active file in the agent. (gnus-read-active-file))) ;; Find all groups and sort them. (let ((groups @@ -2852,10 +3758,12 @@ entail asking the server for the groups." group) (erase-buffer) (while groups + (setq group (pop groups)) (gnus-add-text-properties (point) (prog1 (1+ (point)) (insert " *: " - (setq group (pop groups)) "\n")) + (gnus-group-decoded-name group) + "\n")) (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb) 'gnus-unread t 'gnus-level (inline (gnus-group-level group))))) @@ -2874,7 +3782,12 @@ If ARG is a number, it specifies which levels you are interested in re-scanning. If ARG is non-nil and not a number, this will force \"hard\" re-reading of the active files from all servers." (interactive "P") - (let ((gnus-inhibit-demon t)) + (require 'nnmail) + (let ((gnus-inhibit-demon t) + ;; Binding this variable will inhibit multiple fetchings + ;; of the same mail source. + (nnmail-fetched-sources (list t))) + (gnus-run-hooks 'gnus-get-top-new-news-hook) (gnus-run-hooks 'gnus-get-new-news-hook) ;; Read any slave files. @@ -2883,7 +3796,10 @@ re-scanning. If ARG is non-nil and not a number, this will force ;; We might read in new NoCeM messages here. (when (and gnus-use-nocem - (null arg)) + (or (and (numberp gnus-use-nocem) + (numberp arg) + (>= arg gnus-use-nocem)) + (not arg))) (gnus-nocem-scan-groups)) ;; If ARG is not a number, then we read the active file. (when (and arg (not (numberp arg))) @@ -2902,6 +3818,7 @@ re-scanning. If ARG is non-nil and not a number, this will force (gnus-get-unread-articles arg)) (let ((gnus-read-active-file (if arg nil gnus-read-active-file))) (gnus-get-unread-articles arg))) + (gnus-check-reasonable-setup) (gnus-run-hooks 'gnus-after-getting-new-news-hook) (gnus-group-list-groups (and (numberp arg) (max (car gnus-group-list-mode) arg))))) @@ -2909,23 +3826,34 @@ re-scanning. If ARG is non-nil and not a number, this will force (defun gnus-group-get-new-news-this-group (&optional n dont-scan) "Check for newly arrived news in the current group (and the N-1 next groups). The difference between N and the number of newsgroup checked is returned. -If N is negative, this group and the N-1 previous groups will be checked." +If N is negative, this group and the N-1 previous groups will be checked. +If DONT-SCAN is non-nil, scan non-activated groups as well." (interactive "P") (let* ((groups (gnus-group-process-prefix n)) (ret (if (numberp n) (- n (length groups)) 0)) (beg (unless n (point))) - group) + group method + (gnus-inhibit-demon t) + ;; Binding this variable will inhibit multiple fetchings + ;; of the same mail source. + (nnmail-fetched-sources (list t))) + (gnus-run-hooks 'gnus-get-new-news-hook) (while (setq group (pop groups)) (gnus-group-remove-mark group) ;; Bypass any previous denials from the server. - (gnus-remove-denial (gnus-find-method-for-group group)) - (if (gnus-activate-group group (if dont-scan nil 'scan)) - (progn - (gnus-get-unread-articles-in-group - (gnus-get-info group) (gnus-active group) t) + (gnus-remove-denial (setq method (gnus-find-method-for-group group))) + (if (gnus-activate-group group (if dont-scan nil 'scan) nil method) + (let ((info (gnus-get-info group)) + (active (gnus-active group))) + (when info + (gnus-request-update-info info method)) + (gnus-get-unread-articles-in-group info active) (unless (gnus-virtual-group-p group) (gnus-close-group group)) + (when gnus-agent + (gnus-agent-save-group-info + method (gnus-group-real-name group) active)) (gnus-group-update-group group)) (if (eq (gnus-server-status (gnus-find-method-for-group group)) 'denied) @@ -2947,8 +3875,8 @@ to use." (gnus-group-group-name) (when current-prefix-arg (completing-read - "Faq dir: " (and (listp gnus-group-faq-directory) - (mapcar (lambda (file) (list file)) + "FAQ dir: " (and (listp gnus-group-faq-directory) + (mapcar #'list gnus-group-faq-directory)))))) (unless group (error "No group name given")) @@ -2959,15 +3887,67 @@ to use." (while (and (not found) (setq dir (pop dirs))) (let ((name (gnus-group-real-name group))) - (while (string-match "\\." name) - (setq name (replace-match "/" t t name))) - (setq file (concat (file-name-as-directory dir) name))) + (setq file (expand-file-name name dir))) (if (not (file-exists-p file)) (gnus-message 1 "No such file: %s" file) (let ((enable-local-variables nil)) (find-file file) (setq found t)))))) +(defun gnus-group-fetch-charter (group) + "Fetch the charter for the current group. +If given a prefix argument, prompt for a group." + (interactive + (list (or (when current-prefix-arg + (completing-read "Group: " gnus-active-hashtb)) + (gnus-group-group-name) + gnus-newsgroup-name))) + (unless group + (error "No group name given")) + (require 'mm-url) + (condition-case nil (require 'url-http) (error nil)) + (let ((name (mm-url-form-encode-xwfu (gnus-group-real-name group))) + url hierarchy) + (when (string-match "\\(^[^\\.]+\\)\\..*" name) + (setq hierarchy (match-string 1 name)) + (if (and (setq url (cdr (assoc hierarchy gnus-group-charter-alist))) + (if (fboundp 'url-http-file-exists-p) + (url-http-file-exists-p (eval url)) + t)) + (browse-url (eval url)) + (setq url (concat "http://" hierarchy + ".news-admin.org/charters/" name)) + (if (and (fboundp 'url-http-file-exists-p) + (url-http-file-exists-p url)) + (browse-url url) + (gnus-group-fetch-control group)))))) + +(defun gnus-group-fetch-control (group) + "Fetch the archived control messages for the current group. +If given a prefix argument, prompt for a group." + (interactive + (list (or (when current-prefix-arg + (completing-read "Group: " gnus-active-hashtb)) + (gnus-group-group-name) + gnus-newsgroup-name))) + (unless group + (error "No group name given")) + (let ((name (gnus-group-real-name group)) + hierarchy) + (when (string-match "\\(^[^\\.]+\\)\\..*" name) + (setq hierarchy (match-string 1 name)) + (if gnus-group-fetch-control-use-browse-url + (browse-url (concat "ftp://ftp.isc.org/usenet/control/" + hierarchy "/" name ".gz")) + (let ((enable-local-variables nil)) + (gnus-group-read-ephemeral-group + group + `(nndoc ,group (nndoc-address + ,(find-file-noselect + (concat "/ftp@ftp.isc.org:/usenet/control/" + hierarchy "/" name ".gz"))) + (nndoc-article-type mbox)) t nil nil)))))) + (defun gnus-group-describe-group (force &optional group) "Display a description of the current newsgroup." (interactive (list current-prefix-arg (gnus-group-group-name))) @@ -3004,8 +3984,12 @@ to use." (mapatoms (lambda (group) (setq b (point)) - (insert (format " *: %-20s %s\n" (symbol-name group) - (symbol-value group))) + (let ((charset (gnus-group-name-charset nil (symbol-name group)))) + (insert (format " *: %-20s %s\n" + (gnus-group-name-decode + (symbol-name group) charset) + (gnus-group-name-decode + (symbol-value group) charset)))) (gnus-add-text-properties b (1+ b) (list 'gnus-group group 'gnus-unread t 'gnus-marked nil @@ -3026,6 +4010,7 @@ to use." (lambda (group) (and (symbol-name group) (string-match regexp (symbol-name group)) + (symbol-value group) (push (symbol-name group) groups))) gnus-active-hashtb) ;; Also go through all descriptions that are known to Gnus. @@ -3033,7 +4018,6 @@ to use." (mapatoms (lambda (group) (and (string-match regexp (symbol-value group)) - (gnus-active (symbol-name group)) (push (symbol-name group) groups))) gnus-description-hashtb)) (if (not groups) @@ -3041,23 +4025,25 @@ to use." ;; Print out all the groups. (save-excursion (pop-to-buffer "*Gnus Help*") - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (erase-buffer) (setq groups (sort groups 'string<)) (while groups ;; Groups may be entered twice into the list of groups. (when (not (string= (car groups) prev)) - (insert (setq prev (car groups)) "\n") - (when (and gnus-description-hashtb - (setq des (gnus-gethash (car groups) - gnus-description-hashtb))) - (insert " " des "\n"))) + (setq prev (car groups)) + (let ((charset (gnus-group-name-charset nil prev))) + (insert (gnus-group-name-decode prev charset) "\n") + (when (and gnus-description-hashtb + (setq des (gnus-gethash (car groups) + gnus-description-hashtb))) + (insert " " (gnus-group-name-decode des charset) "\n")))) (setq groups (cdr groups))) (goto-char (point-min)))) (pop-to-buffer obuf))) (defun gnus-group-description-apropos (regexp) - "List all newsgroups that have names or descriptions that match a regexp." + "List all newsgroups that have names or descriptions that match REGEXP." (interactive "sGnus description apropos (regexp): ") (when (not (or gnus-description-hashtb (gnus-read-all-descriptions-files))) @@ -3078,8 +4064,8 @@ This command may read the active file." (when (and level (> (prefix-numeric-value level) gnus-level-killed)) (gnus-get-killed-groups)) - (gnus-group-prepare-flat - (or level gnus-level-subscribed) all (or lowest 1) regexp) + (funcall gnus-group-prepare-function + (or level gnus-level-subscribed) (and all t) (or lowest 1) regexp) (goto-char (point-min)) (gnus-group-position-point)) @@ -3125,13 +4111,15 @@ group." (defun gnus-group-find-new-groups (&optional arg) "Search for new groups and add them. -Each new group will be treated with `gnus-subscribe-newsgroup-method.' -If ARG (the prefix), use the `ask-server' method to query -the server for new groups." - (interactive "P") - (gnus-find-new-newsgroups arg) +Each new group will be treated with `gnus-subscribe-newsgroup-method'. +With 1 C-u, use the `ask-server' method to query the server for new +groups. +With 2 C-u's, use most complete method possible to query the server +for new groups, and subscribe the new groups as zombies." + (interactive "p") + (gnus-find-new-newsgroups (or arg 1)) (gnus-group-list-groups)) - + (defun gnus-group-edit-global-kill (&optional article group) "Edit the global kill file. If GROUP, edit that local kill file instead." @@ -3154,23 +4142,26 @@ If GROUP, edit that local kill file instead." (interactive) (gnus-save-newsrc-file)) +(defvar gnus-backlog-articles) + (defun gnus-group-suspend () "Suspend the current Gnus session. In fact, cleanup buffers except for group mode buffer. -The hook gnus-suspend-gnus-hook is called before actually suspending." +The hook `gnus-suspend-gnus-hook' is called before actually suspending." (interactive) (gnus-run-hooks 'gnus-suspend-gnus-hook) + (gnus-offer-save-summaries) ;; Kill Gnus buffers except for group mode buffer. - (let* ((group-buf (get-buffer gnus-group-buffer)) - ;; Do this on a separate list in case the user does a ^G before we finish - (gnus-buffer-list - (delete group-buf (delete gnus-dribble-buffer - (append gnus-buffer-list nil))))) - (while gnus-buffer-list - (gnus-kill-buffer (pop gnus-buffer-list))) + (let ((group-buf (get-buffer gnus-group-buffer))) + (mapcar (lambda (buf) + (unless (or (member buf (list group-buf gnus-dribble-buffer)) + (with-current-buffer buf + (eq major-mode 'message-mode))) + (gnus-kill-buffer buf))) + (gnus-buffers)) + (setq gnus-backlog-articles nil) (gnus-kill-gnus-frames) (when group-buf - (setq gnus-buffer-list (list group-buf)) (bury-buffer group-buf) (delete-windows-on group-buf t)))) @@ -3215,6 +4206,12 @@ The hook `gnus-exit-gnus-hook' is called before actually exiting." (file-name-nondirectory gnus-current-startup-file)))) (gnus-run-hooks 'gnus-exit-gnus-hook) (gnus-configure-windows 'group t) + (when (and (gnus-buffer-live-p gnus-dribble-buffer) + (not (zerop (save-excursion + (set-buffer gnus-dribble-buffer) + (buffer-size))))) + (gnus-dribble-enter + ";;; Gnus was exited on purpose without saving the .newsrc files.")) (gnus-dribble-save) (gnus-close-backends) (gnus-clear-system) @@ -3235,76 +4232,75 @@ If not, METHOD should be a list where the first element is the method and the second element is the address." (interactive (list (let ((how (completing-read - "Which backend: " + "Which back end: " (append gnus-valid-select-methods gnus-server-alist) nil t (cons "nntp" 0) 'gnus-method-history))) - ;; We either got a backend name or a virtual server name. + ;; We either got a back end name or a virtual server name. ;; If the first, we also need an address. (if (assoc how gnus-valid-select-methods) (list (intern how) ;; Suggested by mapjph@bath.ac.uk. (completing-read "Address: " - (mapcar (lambda (server) (list server)) - gnus-secondary-servers))) + (mapcar 'list gnus-secondary-servers))) ;; We got a server name. how)))) (gnus-browse-foreign-server method)) (defun gnus-group-set-info (info &optional method-only-group part) - (let* ((entry (gnus-gethash - (or method-only-group (gnus-info-group info)) - gnus-newsrc-hashtb)) - (part-info info) - (info (if method-only-group (nth 2 entry) info)) - method) - (when method-only-group + (when (or info part) + (let* ((entry (gnus-group-entry + (or method-only-group (gnus-info-group info)))) + (part-info info) + (info (if method-only-group (nth 2 entry) info)) + method) + (when method-only-group + (unless entry + (error "Trying to change non-existent group %s" method-only-group)) + ;; We have received parts of the actual group info - either the + ;; select method or the group parameters. We first check + ;; whether we have to extend the info, and if so, do that. + (let ((len (length info)) + (total (if (eq part 'method) 5 6))) + (when (< len total) + (setcdr (nthcdr (1- len) info) + (make-list (- total len) nil))) + ;; Then we enter the new info. + (setcar (nthcdr (1- total) info) part-info))) (unless entry - (error "Trying to change non-existent group %s" method-only-group)) - ;; We have received parts of the actual group info - either the - ;; select method or the group parameters. We first check - ;; whether we have to extend the info, and if so, do that. - (let ((len (length info)) - (total (if (eq part 'method) 5 6))) - (when (< len total) - (setcdr (nthcdr (1- len) info) - (make-list (- total len) nil))) - ;; Then we enter the new info. - (setcar (nthcdr (1- total) info) part-info))) - (unless entry - ;; This is a new group, so we just create it. - (save-excursion - (set-buffer gnus-group-buffer) - (setq method (gnus-info-method info)) - (when (gnus-server-equal method "native") - (setq method nil)) + ;; This is a new group, so we just create it. (save-excursion (set-buffer gnus-group-buffer) - (if method - ;; It's a foreign group... - (gnus-group-make-group - (gnus-group-real-name (gnus-info-group info)) - (if (stringp method) method - (prin1-to-string (car method))) - (and (consp method) - (nth 1 (gnus-info-method info)))) - ;; It's a native group. - (gnus-group-make-group (gnus-info-group info)))) - (gnus-message 6 "Note: New group created") - (setq entry - (gnus-gethash (gnus-group-prefixed-name - (gnus-group-real-name (gnus-info-group info)) - (or (gnus-info-method info) gnus-select-method)) - gnus-newsrc-hashtb)))) - ;; Whether it was a new group or not, we now have the entry, so we - ;; can do the update. - (if entry - (progn - (setcar (nthcdr 2 entry) info) - (when (and (not (eq (car entry) t)) - (gnus-active (gnus-info-group info))) - (setcar entry (length (gnus-list-of-unread-articles (car info)))))) - (error "No such group: %s" (gnus-info-group info))))) + (setq method (gnus-info-method info)) + (when (gnus-server-equal method "native") + (setq method nil)) + (save-excursion + (set-buffer gnus-group-buffer) + (if method + ;; It's a foreign group... + (gnus-group-make-group + (gnus-group-real-name (gnus-info-group info)) + (if (stringp method) method + (prin1-to-string (car method))) + (and (consp method) + (nth 1 (gnus-info-method info)))) + ;; It's a native group. + (gnus-group-make-group (gnus-info-group info)))) + (gnus-message 6 "Note: New group created") + (setq entry + (gnus-group-entry (gnus-group-prefixed-name + (gnus-group-real-name (gnus-info-group info)) + (or (gnus-info-method info) gnus-select-method)))))) + ;; Whether it was a new group or not, we now have the entry, so we + ;; can do the update. + (if entry + (progn + (setcar (nthcdr 2 entry) info) + (when (and (not (eq (car entry) t)) + (gnus-active (gnus-info-group info))) + (setcar entry (length + (gnus-list-of-unread-articles (car info)))))) + (error "No such group: %s" (gnus-info-group info)))))) (defun gnus-group-set-method-info (group select-method) (gnus-group-set-info select-method group 'method)) @@ -3314,10 +4310,9 @@ and the second element is the address." (defun gnus-add-marked-articles (group type articles &optional info force) ;; Add ARTICLES of TYPE to the info of GROUP. - ;; If INFO is non-nil, use that info. If FORCE is non-nil, don't + ;; If INFO is non-nil, use that info. If FORCE is non-nil, don't ;; add, but replace marked articles of TYPE with ARTICLES. (let ((info (or info (gnus-get-info group))) - (uncompressed '(score bookmark killed)) marked m) (or (not info) (and (not (setq marked (nthcdr 3 info))) @@ -3333,12 +4328,22 @@ and the second element is the address." (if force (if (null articles) (setcar (nthcdr 3 info) - (delq (assq type (car marked)) (car marked))) + (gnus-delete-alist type (car marked))) (setcdr m (gnus-compress-sequence articles t))) (setcdr m (gnus-compress-sequence (sort (nconc (gnus-uncompress-range (cdr m)) (copy-sequence articles)) '<) t)))))) +(defun gnus-add-mark (group mark article) + "Mark ARTICLE in GROUP with MARK, whether the group is displayed or not." + (let ((buffer (gnus-summary-buffer-name group))) + (if (gnus-buffer-live-p buffer) + (save-excursion + (set-buffer (get-buffer buffer)) + (gnus-summary-add-mark article mark)) + (gnus-add-marked-articles group (cdr (assq mark gnus-article-mark-lists)) + (list article))))) + ;;; ;;; Group timestamps ;;; @@ -3359,8 +4364,8 @@ or `gnus-group-catchup-group-hook'." (defun gnus-group-timestamp-delta (group) "Return the offset in seconds from the timestamp for GROUP to the current time, as a floating point number." (let* ((time (or (gnus-group-timestamp group) - (list 0 0))) - (delta (gnus-time-minus (current-time) time))) + (list 0 0))) + (delta (subtract-time (current-time) time))) (+ (* (nth 0 delta) 65536.0) (nth 1 delta)))) @@ -3371,6 +4376,168 @@ or `gnus-group-catchup-group-hook'." "" (gnus-time-iso8601 time)))) +(defun gnus-group-list-cached (level &optional lowest) + "List all groups with cached articles. +If the prefix LEVEL is non-nil, it should be a number that says which +level to cut off listing groups. +If LOWEST, don't list groups with level lower than LOWEST. + +This command may read the active file." + (interactive "P") + (when level + (setq level (prefix-numeric-value level))) + (when (or (not level) (>= level gnus-level-zombie)) + (gnus-cache-open)) + (funcall gnus-group-prepare-function + (or level gnus-level-subscribed) + #'(lambda (info) + (let ((marks (gnus-info-marks info))) + (assq 'cache marks))) + lowest + #'(lambda (group) + (or (gnus-gethash group + gnus-cache-active-hashtb) + ;; Cache active file might use "." + ;; instead of ":". + (gnus-gethash + (mapconcat 'identity + (split-string group ":") + ".") + gnus-cache-active-hashtb)))) + (goto-char (point-min)) + (gnus-group-position-point)) + +(defun gnus-group-list-dormant (level &optional lowest) + "List all groups with dormant articles. +If the prefix LEVEL is non-nil, it should be a number that says which +level to cut off listing groups. +If LOWEST, don't list groups with level lower than LOWEST. + +This command may read the active file." + (interactive "P") + (when level + (setq level (prefix-numeric-value level))) + (when (or (not level) (>= level gnus-level-zombie)) + (gnus-cache-open)) + (funcall gnus-group-prepare-function + (or level gnus-level-subscribed) + #'(lambda (info) + (let ((marks (gnus-info-marks info))) + (assq 'dormant marks))) + lowest + 'ignore) + (goto-char (point-min)) + (gnus-group-position-point)) + +(defun gnus-group-listed-groups () + "Return a list of listed groups." + (let (point groups) + (goto-char (point-min)) + (while (setq point (text-property-not-all (point) (point-max) + 'gnus-group nil)) + (goto-char point) + (push (symbol-name (get-text-property point 'gnus-group)) groups) + (forward-char 1)) + groups)) + +(defun gnus-group-list-plus (&optional args) + "List groups plus the current selection." + (interactive "P") + (let ((gnus-group-listed-groups (gnus-group-listed-groups)) + (gnus-group-list-mode gnus-group-list-mode) ;; Save it. + func) + (push last-command-event unread-command-events) + (if (featurep 'xemacs) + (push (make-event 'key-press '(key ?A)) unread-command-events) + (push ?A unread-command-events)) + (let (gnus-pick-mode keys) + (setq keys (if (featurep 'xemacs) + (events-to-keys (read-key-sequence nil)) + (read-key-sequence nil))) + (setq func (lookup-key (current-local-map) keys))) + (if (or (not func) + (numberp func)) + (ding) + (call-interactively func)))) + +(defun gnus-group-list-flush (&optional args) + "Flush groups from the current selection." + (interactive "P") + (let ((gnus-group-list-option 'flush)) + (gnus-group-list-plus args))) + +(defun gnus-group-list-limit (&optional args) + "List groups limited within the current selection." + (interactive "P") + (let ((gnus-group-list-option 'limit)) + (gnus-group-list-plus args))) + +(defun gnus-group-mark-article-read (group article) + "Mark ARTICLE read." + (let ((buffer (gnus-summary-buffer-name group)) + (mark gnus-read-mark) + active n) + (if (get-buffer buffer) + (with-current-buffer buffer + (setq active gnus-newsgroup-active) + (gnus-activate-group group) + (when gnus-newsgroup-prepared + (when (and gnus-newsgroup-auto-expire + (memq mark gnus-auto-expirable-marks)) + (setq mark gnus-expirable-mark)) + (setq mark (gnus-request-update-mark + group article mark)) + (gnus-mark-article-as-read article mark) + (setq gnus-newsgroup-active (gnus-active group)) + (when active + (setq n (1+ (cdr active))) + (while (<= n (cdr gnus-newsgroup-active)) + (unless (eq n article) + (push n gnus-newsgroup-unselected)) + (setq n (1+ n))) + (setq gnus-newsgroup-unselected + (nreverse gnus-newsgroup-unselected))))) + (gnus-activate-group group) + (gnus-group-make-articles-read group (list article)) + (when (gnus-group-auto-expirable-p group) + (gnus-add-marked-articles + group 'expire (list article)))))) + + +;;; +;;; Group compaction. -- dvl +;;; + +(defun gnus-group-compact-group (group) + "Compact the current group. +Compaction means removing gaps between article numbers. Hence, this +operation is only meaningful for back ends using one file per article +\(e.g. nnml). + +Note: currently only implemented in nnml." + (interactive (list (gnus-group-group-name))) + (unless group + (error "No group to compact")) + (unless (gnus-check-backend-function 'request-compact-group group) + (error "This back end does not support group compaction")) + (let ((group-decoded (gnus-group-decoded-name group))) + (gnus-message 6 "\ +Compacting group %s... (this may take a long time)" + group-decoded) + (prog1 + (if (not (gnus-request-compact-group group)) + (gnus-error 3 "Couldn't compact group %s" group-decoded) + (gnus-message 6 "Compacting group %s...done" group-decoded) + t) + ;; Invalidate the "original article" buffer which might be out of date. + ;; #### NOTE: Yes, this might be a bit rude, but since compaction + ;; #### will not happen very often, I think this is acceptable. + (let ((original (get-buffer gnus-original-article-buffer))) + (and original (gnus-kill-buffer original))) + ;; Update the group line to reflect new information (art number etc). + (gnus-group-update-group-line)))) + (provide 'gnus-group) +;;; arch-tag: 2eb5440f-0bca-4091-814c-e37817536af6 ;;; gnus-group.el ends here