;;; Code:
-(require 'gnus-load)
+(require 'gnus)
(require 'gnus-group)
(require 'gnus-start)
-(require 'gnus)
(defvar gnus-topic-mode nil
"Minor mode for Gnus group buffers.")
(defvar gnus-topic-indent-level 2
"*How much each subtopic should be indented.")
+(defvar gnus-topic-display-empty-topics t
+ "*If non-nil, display the topic lines even of topics that have no unread articles.")
+
;; Internal variables.
(defvar gnus-topic-active-topology nil)
(if (member group gnus-zombie-list) 8 9)))
(and
unread ; nil means that the group is dead.
- (<= clevel level)
+ (<= clevel level)
(>= clevel lowest) ; Is inside the level we want.
(or all
(if (eq unread t)
(let ((top (gnus-topic-find-topology topic)))
(unless top
(error "No such topic: %s" topic))
- (nth 2 (car top))))
+ (nth 3 (cadr top))))
(defun gnus-topic-set-parameters (topic parameters)
"Set the topic parameters of TOPIC to PARAMETERS."
(error "No such topic: %s" topic))
;; We may have to extend if there is no parameters here
;; to begin with.
- (unless (nthcdr 2 (car top))
- (nconc (car top) (list nil)))
- (setcar (nthcdr 2 (car top)) parameters)))
+ (unless (nthcdr 2 (cadr top))
+ (nconc (cadr top) (list nil)))
+ (unless (nthcdr 3 (cadr top))
+ (nconc (cadr top) (list nil)))
+ (setcar (nthcdr 3 (cadr top)) parameters)))
(defun gnus-group-topic-parameters (group)
- "Compute the group parameters for GROUP taking into account inheretance from topics."
+ "Compute the group parameters for GROUP taking into account inheritance from topics."
(let ((params-list (list (gnus-group-get-parameter group)))
topics params param out)
(save-excursion
;; we remove them. Probably faster than doing this "properly".
(setq params-list (delq nil params-list))
;; Now we have all the parameters, so we go through them
- ;; and do inheretance in the obvious way.
+ ;; and do inheritance in the obvious way.
(while (setq params (pop params-list))
(while (setq param (pop params))
(when (atom param)
;; Return the resulting parameter list.
out)))
-;;; General utility funtions
+;;; General utility functions
(defun gnus-topic-enter-dribble ()
(gnus-dribble-enter
;; List dead groups?
(when (and (>= level gnus-level-zombie) (<= lowest gnus-level-zombie))
(gnus-group-prepare-flat-list-dead
- (setq gnus-zombie-list (sort gnus-zombie-list 'string<))
+ (setq gnus-zombie-list (sort gnus-zombie-list 'string<))
gnus-level-zombie ?Z
regexp))
(gnus-group-insert-group-line
entry (if (member entry gnus-zombie-list) 8 9)
nil (- (1+ (cdr (setq active (gnus-active entry))))
- (car active)) nil)
+ (car active))
+ nil)
;; Living groups.
(when (setq info (nth 2 entry))
(gnus-group-insert-group-line
(gnus-info-group info)
- (gnus-info-level info) (gnus-info-marks info)
+ (gnus-info-level info) (gnus-info-marks info)
(car entry) (gnus-info-method info)))))
(when (and (listp entry)
(numberp (car entry))
(incf unread (car entry))))
(goto-char beg)
;; Insert the topic line.
- (unless silent
+ (when (and (not silent)
+ (or gnus-topic-display-empty-topics
+ (not (zerop unread))))
(gnus-extent-start-open (point))
(gnus-topic-insert-topic-line
(car type) visiblep
(while (and (zerop (forward-line 1))
(> (or (gnus-group-topic-level) (1+ level)) level)))
(delete-region beg (point))
- (setcar (cdadr (gnus-topic-find-topology topic))
- (if insert 'visible 'invisible))
- (when hide
- (setcdr (cdadr (gnus-topic-find-topology topic))
- (list hide)))
- (unless total-remove
+ ;; Do the change in this rather odd manner because it has been
+ ;; reported that some topics share parts of some lists, for some
+ ;; reason. I have been unable to determine why this is the
+ ;; case, but this hack seems to take care of things.
+ (let ((data (cadr (gnus-topic-find-topology topic))))
+ (setcdr data
+ (list (if insert 'visible 'invisible)
+ (if hide 'hide nil)
+ (cadddr data))))
+ (if total-remove
+ (setq gnus-topic-alist
+ (delq (assoc topic gnus-topic-alist) gnus-topic-alist))
(gnus-topic-insert-topic topic in-level)))))
(defun gnus-topic-insert-topic (topic &optional level)
(defun gnus-topic-fold (&optional insert)
"Remove/insert the current topic."
- (let ((topic (gnus-group-topic-name)))
+ (let ((topic (gnus-group-topic-name)))
(when topic
(save-excursion
(if (not (gnus-group-active-topic-p))
;; Insert the text.
(gnus-add-text-properties
(point)
- (prog1 (1+ (point))
+ (prog1 (1+ (point))
(eval gnus-topic-line-format-spec)
(gnus-topic-remove-excess-properties)1)
(list 'gnus-topic (intern name)
'gnus-active active-topic
'gnus-topic-visible visiblep))))
+(defun gnus-topic-update-topics-containing-group (group)
+ "Update all topics that have GROUP as a member."
+ (when (and (eq major-mode 'gnus-group-mode)
+ gnus-topic-mode)
+ (save-excursion
+ (let ((alist gnus-topic-alist))
+ ;; This is probably not entirely correct. If a topic
+ ;; isn't shown, then it's not updated. But the updating
+ ;; should be performed in any case, since the topic's
+ ;; parent should be updated. Pfft.
+ (while alist
+ (when (and (member group (cdar alist))
+ (gnus-topic-goto-topic (caar alist)))
+ (gnus-topic-update-topic-line (caar alist)))
+ (pop alist))))))
+
(defun gnus-topic-update-topic ()
"Update all parent topics to the current group."
(when (and (eq major-mode 'gnus-group-mode)
gnus-topic-mode)
(let ((group (gnus-group-group-name))
(buffer-read-only nil))
- (when (and group (gnus-get-info group)
+ (when (and group
+ (gnus-get-info group)
(gnus-topic-goto-topic (gnus-current-topic)))
(gnus-topic-update-topic-line (gnus-group-topic-name))
(gnus-group-goto-group group)
(gnus-group-position-point)))))
-(defun gnus-topic-goto-missing-group (group)
+(defun gnus-topic-goto-missing-group (group)
"Place point where GROUP is supposed to be inserted."
(let* ((topic (gnus-group-topic group))
(groups (cdr (assoc topic gnus-topic-alist)))
(g (cdr (member group groups)))
(unfound t))
- (while (and g unfound)
- (when (gnus-group-goto-group (pop g))
- (beginning-of-line)
- (setq unfound nil)))
- (when unfound
+ ;; Try to jump to a visible group.
+ (while (and g (not (gnus-group-goto-group (car g) t)))
+ (pop g))
+ ;; It wasn't visible, so we try to see where to insert it.
+ (when (not g)
(setq g (cdr (member group (reverse groups))))
(while (and g unfound)
- (when (gnus-group-goto-group (pop g))
+ (when (gnus-group-goto-group (pop g) t)
(forward-line 1)
(setq unfound nil)))
(when unfound
(make-string
(* gnus-topic-indent-level
(or (save-excursion
+ (forward-line -1)
(gnus-topic-goto-topic (gnus-current-topic))
- (gnus-group-topic-level)) 0)) ? ))
+ (gnus-group-topic-level))
+ 0))
+ ? ))
;;; Initialization
gnus-topic-tallied-groups nil
gnus-topology-checked-p nil))
-(defun gnus-topic-check-topology ()
+(defun gnus-topic-check-topology ()
;; The first time we set the topology to whatever we have
;; gotten here, which can be rather random.
(unless gnus-topic-alist
(let ((topic-name (pop topic))
group filtered-topic)
(while (setq group (pop topic))
- (if (and (gnus-gethash group gnus-active-hashtb)
- (not (gnus-gethash group gnus-killed-hashtb)))
- (push group filtered-topic)))
+ (when (and (or (gnus-gethash group gnus-active-hashtb)
+ (gnus-info-method (gnus-get-info group)))
+ (not (gnus-gethash group gnus-killed-hashtb)))
+ (push group filtered-topic)))
(push (cons topic-name (nreverse filtered-topic)) result)))
(setq gnus-topic-alist (nreverse result))))
(forward-line -1)
(when (setq alist (assoc (gnus-current-topic) gnus-topic-alist))
(setcdr alist (gnus-delete-first group (cdr alist))))))
- ;; If the group is subscribed. then we enter it into the topics.
+ ;; If the group is subscribed. then we enter it into the topics.
(when (and (< level gnus-level-zombie)
(>= oldlevel gnus-level-zombie))
(let* ((prev (gnus-group-group-name))
(* gnus-topic-indent-level
(or (save-excursion
(gnus-topic-goto-topic (gnus-current-topic))
- (gnus-group-topic-level)) 0)) ? ))
+ (gnus-group-topic-level))
+ 0))
+ ? ))
(yanked (list group))
alist talist end)
;; Then we enter the yanked groups into the topics they belong
(if (gnus-group-goto-group group)
t
;; The group is no longer visible.
- (let* ((list (assoc (gnus-current-topic) gnus-topic-alist))
+ (let* ((list (assoc (gnus-group-topic group) gnus-topic-alist))
(after (cdr (member group (cdr list)))))
;; First try to put point on a group after the current one.
(while (and after
(setq gnus-topic-mode-map (make-sparse-keymap))
;; Override certain group mode keys.
- (gnus-define-keys
- gnus-topic-mode-map
- "=" gnus-topic-select-group
- "\r" gnus-topic-select-group
- " " gnus-topic-read-group
- "\C-k" gnus-topic-kill-group
- "\C-y" gnus-topic-yank-group
- "\M-g" gnus-topic-get-new-news-this-topic
- "AT" gnus-topic-list-active
- "Gp" gnus-topic-edit-parameters
- gnus-mouse-2 gnus-mouse-pick-topic)
+ (gnus-define-keys gnus-topic-mode-map
+ "=" gnus-topic-select-group
+ "\r" gnus-topic-select-group
+ " " gnus-topic-read-group
+ "\C-k" gnus-topic-kill-group
+ "\C-y" gnus-topic-yank-group
+ "\M-g" gnus-topic-get-new-news-this-topic
+ "AT" gnus-topic-list-active
+ "Gp" gnus-topic-edit-parameters
+ gnus-mouse-2 gnus-mouse-pick-topic)
;; Define a new submap.
- (gnus-define-keys
- (gnus-group-topic-map "T" gnus-group-mode-map)
- "#" gnus-topic-mark-topic
- "\M-#" gnus-topic-unmark-topic
- "n" gnus-topic-create-topic
- "m" gnus-topic-move-group
- "D" gnus-topic-remove-group
- "c" gnus-topic-copy-group
- "h" gnus-topic-hide-topic
- "s" gnus-topic-show-topic
- "M" gnus-topic-move-matching
- "C" gnus-topic-copy-matching
- "\C-i" gnus-topic-indent
- [tab] gnus-topic-indent
- "r" gnus-topic-rename
- "\177" gnus-topic-delete))
+ (gnus-define-keys (gnus-group-topic-map "T" gnus-group-mode-map)
+ "#" gnus-topic-mark-topic
+ "\M-#" gnus-topic-unmark-topic
+ "n" gnus-topic-create-topic
+ "m" gnus-topic-move-group
+ "D" gnus-topic-remove-group
+ "c" gnus-topic-copy-group
+ "h" gnus-topic-hide-topic
+ "s" gnus-topic-show-topic
+ "M" gnus-topic-move-matching
+ "C" gnus-topic-copy-matching
+ "\C-i" gnus-topic-indent
+ [tab] gnus-topic-indent
+ "r" gnus-topic-rename
+ "\177" gnus-topic-delete)
+
+ (gnus-define-keys (gnus-topic-sort-map "S" gnus-group-topic-map)
+ "s" gnus-topic-sort-groups
+ "a" gnus-topic-sort-groups-by-alphabet
+ "u" gnus-topic-sort-groups-by-unread
+ "l" gnus-topic-sort-groups-by-level
+ "v" gnus-topic-sort-groups-by-score
+ "r" gnus-topic-sort-groups-by-rank
+ "m" gnus-topic-sort-groups-by-method))
(defun gnus-topic-make-menu-bar ()
(unless (boundp 'gnus-topic-menu)
minor-mode-map-alist))
(add-hook 'gnus-summary-exit-hook 'gnus-topic-update-topic)
(add-hook 'gnus-group-catchup-group-hook 'gnus-topic-update-topic)
- (add-hook 'gnus-group-update-group-hook 'gnus-topic-update-topic)
- (make-local-variable 'gnus-group-prepare-function)
- (setq gnus-group-prepare-function 'gnus-group-prepare-topics)
- (make-local-variable 'gnus-group-goto-next-group-function)
- (setq gnus-group-goto-next-group-function
- 'gnus-topic-goto-next-group)
+ (set (make-local-variable 'gnus-group-prepare-function)
+ 'gnus-group-prepare-topics)
+ (set (make-local-variable 'gnus-group-get-parameter-function)
+ 'gnus-group-topic-parameters)
+ (set (make-local-variable 'gnus-group-goto-next-group-function)
+ 'gnus-topic-goto-next-group)
+ (set (make-local-variable 'gnus-group-indentation-function)
+ 'gnus-topic-group-indentation)
+ (set (make-local-variable 'gnus-group-update-group-function)
+ 'gnus-topic-update-topics-containing-group)
+ (set (make-local-variable 'gnus-group-sort-alist-function)
+ 'gnus-group-sort-topic)
(setq gnus-group-change-level-function 'gnus-topic-change-level)
(setq gnus-goto-missing-group-function 'gnus-topic-goto-missing-group)
- (make-local-variable 'gnus-group-indentation-function)
- (setq gnus-group-indentation-function
- 'gnus-topic-group-indentation)
(gnus-make-local-hook 'gnus-check-bogus-groups-hook)
(add-hook 'gnus-check-bogus-groups-hook 'gnus-topic-clean-alist)
(setq gnus-topology-checked-p nil)
(remove-hook 'gnus-group-change-level-function
'gnus-topic-change-level)
(remove-hook 'gnus-check-bogus-groups-hook 'gnus-topic-clean-alist)
- (setq gnus-group-prepare-function 'gnus-group-prepare-flat))
+ (setq gnus-group-prepare-function 'gnus-group-prepare-flat)
+ (setq gnus-group-sort-alist-function 'gnus-group-sort-flat))
(when redisplay
(gnus-group-list-groups))))
(gnus-current-topic)))
;; Check whether this topic already exists.
(when (gnus-topic-find-topology topic)
- (error "Topic aleady exists"))
+ (error "Topic already exists"))
(unless parent
(setq parent (caar gnus-topic-topology)))
(let ((top (cdr (gnus-topic-find-topology parent)))
(completing-read "Move to topic: " gnus-topic-alist nil t)))
(let ((groups (gnus-group-process-prefix n))
(topicl (assoc topic gnus-topic-alist))
+ (start-group (progn (forward-line 1) (gnus-group-group-name)))
+ (start-topic (gnus-group-topic-name))
entry)
- (mapcar (lambda (g)
- (gnus-group-remove-mark g)
- (when (and
- (setq entry (assoc (gnus-current-topic)
- gnus-topic-alist))
- (not copyp))
- (setcdr entry (gnus-delete-first g (cdr entry))))
- (nconc topicl (list g)))
- groups)
- (gnus-group-position-point))
- (gnus-topic-enter-dribble)
- (gnus-group-list-groups))
+ (mapcar
+ (lambda (g)
+ (gnus-group-remove-mark g)
+ (when (and
+ (setq entry (assoc (gnus-current-topic) gnus-topic-alist))
+ (not copyp))
+ (setcdr entry (gnus-delete-first g (cdr entry))))
+ (nconc topicl (list g)))
+ groups)
+ (gnus-topic-enter-dribble)
+ (if start-group
+ (gnus-group-goto-group start-group)
+ (gnus-topic-goto-topic start-topic))
+ (gnus-group-list-groups)))
-(defun gnus-topic-remove-group ()
+(defun gnus-topic-remove-group (&optional arg)
"Remove the current group from the topic."
- (interactive)
- (let ((topicl (assoc (gnus-current-topic) gnus-topic-alist))
- (group (gnus-group-group-name))
- (buffer-read-only nil))
- (when (and topicl group)
- (gnus-delete-line)
- (gnus-delete-first group topicl))
- (gnus-group-position-point)))
+ (interactive "P")
+ (gnus-group-iterate arg
+ (lambda (group)
+ (let ((topicl (assoc (gnus-current-topic) gnus-topic-alist))
+ (buffer-read-only nil))
+ (when (and topicl group)
+ (gnus-delete-line)
+ (gnus-delete-first group topicl))
+ (gnus-topic-update-topic)
+ (gnus-group-position-point)))))
(defun gnus-topic-copy-group (n topic)
"Copy the current group to a topic."
(let ((topic (gnus-group-topic-name)))
(gnus-topic-remove-topic nil t)
(push (gnus-topic-find-topology topic nil nil gnus-topic-topology)
- gnus-topic-killed-topics))
+ gnus-topic-killed-topics)
+ (gnus-topic-enter-dribble))
(gnus-group-kill-group n discard)
(gnus-topic-update-topic)))
(gnus-topic-create-topic
(caar item) (gnus-topic-parent-topic previous) previous
item)
+ (gnus-topic-enter-dribble)
(gnus-topic-goto-topic (caar item)))
(let* ((prev (gnus-group-group-name))
(gnus-topic-inhibit-change-level t)
(* gnus-topic-indent-level
(or (save-excursion
(gnus-topic-goto-topic (gnus-current-topic))
- (gnus-group-topic-level)) 0)) ? ))
+ (gnus-group-topic-level))
+ 0))
+ ? ))
yanked alist)
;; We first yank the groups the normal way...
(setq yanked (gnus-group-yank-group arg))
gnus-killed-list gnus-zombie-list)
(gnus-group-list-groups 9 nil 1)))
+;;; Topic sorting functions
+
(defun gnus-topic-edit-parameters (group)
"Edit the group parameters of GROUP.
If performed on a topic, edit the topic parameters instead."
`(lambda (form)
(gnus-topic-set-parameters ,topic form)))))))
+(defun gnus-group-sort-topic (func reverse)
+ "Sort groups in the topics according to FUNC and REVERSE."
+ (let ((alist gnus-topic-alist))
+ (while alist
+ (gnus-topic-sort-topic (pop alist) func reverse))))
+
+(defun gnus-topic-sort-topic (topic func reverse)
+ ;; Each topic only lists the name of the group, while
+ ;; the sort predicates expect group infos as inputs.
+ ;; So we first transform the group names into infos,
+ ;; then sort, and then transform back into group names.
+ (setcdr
+ topic
+ (mapcar
+ (lambda (info) (gnus-info-group info))
+ (sort
+ (mapcar
+ (lambda (group) (gnus-get-info group))
+ (cdr topic))
+ func)))
+ ;; Do the reversal, if necessary.
+ (when reverse
+ (setcdr topic (nreverse (cdr topic)))))
+
+(defun gnus-topic-sort-groups (func &optional reverse)
+ "Sort the current topic according to FUNC.
+If REVERSE, reverse the sorting order."
+ (interactive (list gnus-group-sort-function current-prefix-arg))
+ (let ((topic (assoc (gnus-current-topic) gnus-topic-alist)))
+ (gnus-topic-sort-topic
+ topic (gnus-make-sort-function func) reverse)
+ (gnus-group-list-groups)))
+
+(defun gnus-topic-sort-groups-by-alphabet (&optional reverse)
+ "Sort the current topic alphabetically by group name.
+If REVERSE, sort in reverse order."
+ (interactive "P")
+ (gnus-topic-sort-groups 'gnus-group-sort-by-alphabet reverse))
+
+(defun gnus-topic-sort-groups-by-unread (&optional reverse)
+ "Sort the current topic by number of unread articles.
+If REVERSE, sort in reverse order."
+ (interactive "P")
+ (gnus-topic-sort-groups 'gnus-group-sort-by-unread reverse))
+
+(defun gnus-topic-sort-groups-by-level (&optional reverse)
+ "Sort the current topic by group level.
+If REVERSE, sort in reverse order."
+ (interactive "P")
+ (gnus-topic-sort-groups 'gnus-group-sort-by-level reverse))
+
+(defun gnus-topic-sort-groups-by-score (&optional reverse)
+ "Sort the current topic by group score.
+If REVERSE, sort in reverse order."
+ (interactive "P")
+ (gnus-topic-sort-groups 'gnus-group-sort-by-score reverse))
+
+(defun gnus-topic-sort-groups-by-rank (&optional reverse)
+ "Sort the current topic by group rank.
+If REVERSE, sort in reverse order."
+ (interactive "P")
+ (gnus-topic-sort-groups 'gnus-group-sort-by-rank reverse))
+
+(defun gnus-topic-sort-groups-by-method (&optional reverse)
+ "Sort the current topic alphabetically by backend name.
+If REVERSE, sort in reverse order."
+ (interactive "P")
+ (gnus-topic-sort-groups 'gnus-group-sort-by-method reverse))
+
(provide 'gnus-topic)
;;; gnus-topic.el ends here