*** empty log message ***
[gnus] / lisp / gnus-topic.el
1 ;;; gnus-topic.el --- a folding minor mode for Gnus group buffers
2 ;; Copyright (C) 1995,96 Free Software Foundation, Inc.
3
4 ;; Author: Ilja Weis <kult@uni-paderborn.de>
5 ;;      Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
6 ;; Keywords: news
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;;; Code:
28
29 (require 'gnus)
30 (eval-when-compile (require 'cl))
31
32 (defvar gnus-topic-mode nil
33   "Minor mode for Gnus group buffers.")
34
35 (defvar gnus-topic-mode-hook nil
36   "Hook run in topic mode buffers.")
37
38 (defvar gnus-topic-line-format "%i[ %(%{%n%}%) -- %A ]%v\n"
39   "Format of topic lines.
40 It works along the same lines as a normal formatting string,
41 with some simple extensions.
42
43 %i  Indentation based on topic level.
44 %n  Topic name.
45 %v  Nothing if the topic is visible, \"...\" otherwise.
46 %g  Number of groups in the topic.
47 %a  Number of unread articles in the groups in the topic.
48 %A  Number of unread articles in the groups in the topic and its subtopics.
49 ")
50
51 (defvar gnus-topic-unique t
52   "*If non-nil, each group will only belong to one topic.")
53
54 (defvar gnus-topic-indent-level 2
55   "*How much each subtopic should be indented.")
56
57 ;; Internal variables.
58
59 (defvar gnus-topic-killed-topics nil)
60 (defvar gnus-topic-inhibit-change-level nil)
61
62
63 (defconst gnus-topic-line-format-alist
64   `((?n name ?s)
65     (?v visible ?s)
66     (?i indentation ?s)
67     (?g number-of-groups ?d)
68     (?a (gnus-topic-articles-in-topic entries) ?d)
69     (?A total-number-of-articles ?d)
70     (?l level ?d)))
71
72 (defvar gnus-topic-line-format-spec nil)
73 (defvar gnus-topic-active-topology nil)
74 (defvar gnus-topic-active-alist nil)
75
76 ;; Functions.
77
78 (defun gnus-group-topic-name ()
79   "The name of the topic on the current line."
80   (get-text-property (gnus-point-at-bol) 'gnus-topic))
81
82 (defun gnus-group-topic-level ()
83   "The level of the topic on the current line."
84   (get-text-property (gnus-point-at-bol) 'gnus-topic-level))
85
86 (defun gnus-topic-init-alist ()
87   "Initialize the topic structures."
88   (setq gnus-topic-topology
89         (cons (list "Gnus" 'visible)
90               (mapcar (lambda (topic)
91                         (list (list (car topic) 'visible)))
92                       '(("misc")))))
93   (setq gnus-topic-alist
94         (list (cons "misc"
95                     (mapcar (lambda (info) (gnus-info-group info))
96                             (cdr gnus-newsrc-alist)))
97               (list "Gnus")))
98   (gnus-topic-enter-dribble))
99
100 (defun gnus-group-prepare-topics (level &optional all lowest regexp list-topic topic-level)
101   "List all newsgroups with unread articles of level LEVEL or lower, and
102 use the `gnus-group-topics' to sort the groups.
103 If ALL is non-nil, list groups that have no unread articles.
104 If LOWEST is non-nil, list all newsgroups of level LOWEST or higher."
105   (set-buffer gnus-group-buffer)
106   (let ((buffer-read-only nil)
107         (lowest (or lowest 1))
108         tlist info)
109
110     (unless list-topic 
111       (erase-buffer))
112     
113     ;; List dead groups?
114     (when (and (>= level gnus-level-zombie) (<= lowest gnus-level-zombie))
115       (gnus-group-prepare-flat-list-dead 
116        (setq gnus-zombie-list (sort gnus-zombie-list 'string<)) 
117        gnus-level-zombie ?Z
118        regexp))
119     
120     (when (and (>= level gnus-level-killed) (<= lowest gnus-level-killed))
121       (gnus-group-prepare-flat-list-dead 
122        (setq gnus-killed-list (sort gnus-killed-list 'string<))
123        gnus-level-killed ?K
124        regexp))
125     
126     ;; Use topics.
127     (when (< lowest gnus-level-zombie)
128       (if list-topic
129           (let ((top (gnus-topic-find-topology list-topic)))
130             (gnus-topic-prepare-topic (cdr top) (car top)
131                                       (or topic-level level) all))
132         (gnus-topic-prepare-topic gnus-topic-topology 0
133                                   (or topic-level level) all))))
134
135   (gnus-group-set-mode-line)
136   (setq gnus-group-list-mode (cons level all))
137   (run-hooks 'gnus-group-prepare-hook))
138
139 (defun gnus-topic-prepare-topic (topic level &optional list-level all silent)
140   "Insert TOPIC into the group buffer.
141 If SILENT, don't insert anything.  Return the number of unread
142 articles in the topic and its subtopics."
143   (let* ((type (pop topic))
144          (entries (gnus-topic-find-groups (car type) list-level all))
145          (visiblep (and (eq (nth 1 type) 'visible) (not silent)))
146          (gnus-group-indentation 
147           (make-string (* gnus-topic-indent-level level) ? ))
148          (beg (progn (beginning-of-line) (point)))
149          (topic (reverse topic))
150          (all-entries entries)
151          (unread 0)
152          info entry end active)
153     ;; Insert any sub-topics.
154     (while topic
155       (incf unread
156             (gnus-topic-prepare-topic 
157              (pop topic) (1+ level) list-level all
158              (not visiblep))))
159     (setq end (point))
160     (goto-char beg)
161     ;; Insert all the groups that belong in this topic.
162     (while (setq entry (pop entries))
163       (when visiblep 
164         (if (stringp entry)
165             ;; Dead groups.
166             (gnus-group-insert-group-line
167              entry (if (member entry gnus-zombie-list) 8 9)
168              nil (- (1+ (cdr (setq active (gnus-active entry))))
169                     (car active)) nil)
170           ;; Living groups.
171           (when (setq info (nth 2 entry))
172             (gnus-group-insert-group-line 
173              (gnus-info-group info)
174              (gnus-info-level info) (gnus-info-marks info) 
175              (car entry) (gnus-info-method info)))))
176       (when (numberp (car entry))
177         (incf unread (car entry))))
178     (goto-char beg)
179     ;; Insert the topic line.
180     (unless silent
181       (gnus-extent-start-open (point))
182       (gnus-topic-insert-topic-line 
183        (car type) visiblep
184        (not (eq (nth 2 type) 'hidden))
185        level all-entries unread))
186     (goto-char end)
187     unread))
188
189 (defun gnus-topic-find-groups (topic &optional level all)
190   "Return entries for all visible groups in TOPIC."
191   (let ((groups (cdr (assoc topic gnus-topic-alist)))
192         info clevel unread group lowest params visible-groups entry active)
193     (setq lowest (or lowest 1))
194     (setq level (or level 7))
195     ;; We go through the newsrc to look for matches.
196     (while groups
197       (setq entry (gnus-gethash (setq group (pop groups)) gnus-newsrc-hashtb)
198             info (nth 2 entry)
199             params (gnus-info-params info)
200             active (gnus-active group)
201             unread (or (car entry)
202                        (and (not (equal group "dummy.group"))
203                             active
204                             (- (1+ (cdr active)) (car active))))
205             clevel (or (gnus-info-level info)
206                        (if (member group gnus-zombie-list) 8 9)))
207       (and 
208        unread                           ; nil means that the group is dead.
209        (<= clevel level) 
210        (>= clevel lowest)               ; Is inside the level we want.
211        (or all
212            (and gnus-group-list-inactive-groups
213                 (eq unread t))
214            (> unread 0)
215            (and gnus-list-groups-with-ticked-articles
216                 (cdr (assq 'tick (gnus-info-marks info))))
217                                         ; Has right readedness.
218            ;; Check for permanent visibility.
219            (and gnus-permanently-visible-groups
220                 (string-match gnus-permanently-visible-groups group))
221            (memq 'visible params)
222            (cdr (assq 'visible params)))
223        ;; Add this group to the list of visible groups.
224        (push (or entry group) visible-groups)))
225     (nreverse visible-groups)))
226
227 (defun gnus-topic-remove-topic (&optional insert total-remove hide in-level)
228   "Remove the current topic."
229   (let ((topic (gnus-group-topic-name))
230         (level (gnus-group-topic-level))
231         (beg (progn (beginning-of-line) (point)))
232         buffer-read-only)
233     (when topic
234       (while (and (zerop (forward-line 1))
235                   (> (or (gnus-group-topic-level) (1+ level)) level)))
236       (delete-region beg (point))
237       (setcar (cdr (car (cdr (gnus-topic-find-topology topic))))
238               (if insert 'visible 'invisible))
239       (when hide
240         (setcdr (cdr (car (cdr (gnus-topic-find-topology topic))))
241                 (list hide)))
242       (unless total-remove
243         (gnus-topic-insert-topic topic in-level)))))
244
245 (defun gnus-topic-insert-topic (topic &optional level)
246   "Insert TOPIC."
247   (gnus-group-prepare-topics 
248    (car gnus-group-list-mode) (cdr gnus-group-list-mode)
249    nil nil topic level))
250   
251 (defun gnus-topic-fold (&optional insert)
252   "Remove/insert the current topic."
253   (let ((topic (gnus-group-topic-name))) 
254     (when topic
255       (save-excursion
256         (if (not (gnus-group-active-topic-p))
257             (gnus-topic-remove-topic
258              (or insert (not (gnus-topic-visible-p))))
259           (let ((gnus-topic-topology gnus-topic-active-topology)
260                 (gnus-topic-alist gnus-topic-active-alist)
261                 (gnus-group-list-mode (cons 5 t)))
262             (gnus-topic-remove-topic
263              (or insert (not (gnus-topic-visible-p))) nil nil 9)))))))
264
265 (defun gnus-group-topic-p ()
266   "Return non-nil if the current line is a topic."
267   (get-text-property (gnus-point-at-bol) 'gnus-topic))
268
269 (defun gnus-topic-visible-p ()
270   "Return non-nil if the current topic is visible."
271   (get-text-property (gnus-point-at-bol) 'gnus-topic-visible))
272
273 (defun gnus-topic-insert-topic-line (name visiblep shownp level entries 
274                                           &optional unread)
275   (let* ((visible (if visiblep "" "..."))
276          (indentation (make-string (* gnus-topic-indent-level level) ? ))
277          (total-number-of-articles unread)
278          (number-of-groups (length entries))
279          (active-topic (eq gnus-topic-alist gnus-topic-active-alist)))
280     (beginning-of-line)
281     ;; Insert the text.
282     (add-text-properties 
283      (point)
284      (prog1 (1+ (point)) 
285        (eval gnus-topic-line-format-spec)
286        (gnus-topic-remove-excess-properties))
287      (list 'gnus-topic name
288            'gnus-topic-level level
289            'gnus-active active-topic
290            'gnus-topic-visible visiblep))))
291
292 (defun gnus-topic-previous-topic (topic)
293   "Return the previous topic on the same level as TOPIC."
294   (let ((top (cdr (cdr (gnus-topic-find-topology
295                         (gnus-topic-parent-topic topic))))))
296     (unless (equal topic (car (car (car top))))
297       (while (and top (not (equal (car (car (car (cdr top)))) topic)))
298         (setq top (cdr top)))
299       (car (car (car top))))))
300
301 (defun gnus-topic-parent-topic (topic &optional topology)
302   "Return the parent of TOPIC."
303   (unless topology
304     (setq topology gnus-topic-topology))
305   (let ((parent (car (pop topology)))
306         result found)
307     (while (and topology
308                 (not (setq found (equal (car (car (car topology))) topic)))
309                 (not (setq result (gnus-topic-parent-topic topic 
310                                                            (car topology)))))
311       (setq topology (cdr topology)))
312     (or result (and found parent))))
313
314 (defun gnus-topic-find-topology (topic &optional topology level remove)
315   "Return the topology of TOPIC."
316   (unless topology
317     (setq topology gnus-topic-topology)
318     (setq level 0))
319   (let ((top topology)
320         result)
321     (if (equal (car (car topology)) topic)
322         (progn
323           (when remove
324             (delq topology remove))
325           (cons level topology))
326       (setq topology (cdr topology))
327       (while (and topology
328                   (not (setq result (gnus-topic-find-topology
329                                      topic (car topology) (1+ level)
330                                      (and remove top)))))
331         (setq topology (cdr topology)))
332       result)))
333
334 (defun gnus-topic-check-topology ()  
335   ;; The first time we set the topology to whatever we have
336   ;; gotten here, which can be rather random.
337   (unless gnus-topic-alist
338     (gnus-topic-init-alist))
339
340   (let ((topics (gnus-topic-list))
341         (alist gnus-topic-alist)
342         changed)
343     (while alist
344       (unless (member (car (car alist)) topics)
345         (nconc gnus-topic-topology
346                (list (list (list (car (car alist)) 'visible))))
347         (setq changed t))
348       (setq alist (cdr alist)))
349     (when changed
350       (gnus-topic-enter-dribble)))
351   (let* ((tgroups (apply 'append (mapcar (lambda (entry) (cdr entry))
352                                          gnus-topic-alist)))
353          (entry (assoc (caar gnus-topic-topology) gnus-topic-alist))
354          (newsrc gnus-newsrc-alist)
355          group)
356     (while newsrc
357       (unless (member (setq group (gnus-info-group (pop newsrc))) tgroups)
358         (setcdr entry (cons group (cdr entry)))))))
359
360 (defvar gnus-tmp-topics nil)
361 (defun gnus-topic-list (&optional topology)
362   (unless topology
363     (setq topology gnus-topic-topology 
364           gnus-tmp-topics nil))
365   (push (car (car topology)) gnus-tmp-topics)
366   (mapcar 'gnus-topic-list (cdr topology))
367   gnus-tmp-topics)
368
369 (defun gnus-topic-enter-dribble ()
370   (gnus-dribble-enter
371    (format "(setq gnus-topic-topology '%S)" gnus-topic-topology)))
372
373 (defun gnus-topic-articles-in-topic (entries)
374   (let ((total 0)
375         number)
376     (while entries
377       (when (numberp (setq number (car (pop entries))))
378         (incf total number)))
379     total))
380
381 (defun gnus-group-parent-topic ()
382   "Return the topic the current group belongs in."
383   (let ((group (gnus-group-group-name)))
384     (if group
385         (gnus-group-topic group)
386       (gnus-group-topic-name))))
387
388 (defun gnus-group-topic (group)
389   "Return the topic GROUP is a member of."
390   (let ((alist gnus-topic-alist)
391         out)
392     (while alist
393       (when (member group (cdr (car alist)))
394         (setq out (car (car alist))
395               alist nil))
396       (setq alist (cdr alist)))
397     out))
398
399 (defun gnus-topic-goto-topic (topic)
400   (let ((orig (point)))
401     (goto-char (point-min))
402     (while (and (not (equal topic (gnus-group-topic-name)))
403                 (zerop (forward-line 1))))
404     (or (gnus-group-topic-name)
405         (progn
406           (goto-char orig)
407           nil))))
408   
409 (defun gnus-topic-update-topic ()
410   "Update all parent topics to the current group."
411   (when (and (eq major-mode 'gnus-group-mode)
412              gnus-topic-mode)
413     (let ((group (gnus-group-group-name)))
414       (gnus-topic-goto-topic (gnus-group-parent-topic))
415       (gnus-topic-update-topic-line)
416       (gnus-group-goto-group group)
417       (gnus-group-position-point))))
418
419 (defun gnus-topic-goto-missing-group (group) 
420   "Place point where GROUP is supposed to be inserted."
421   (let* ((topic (gnus-group-topic group))
422          (groups (cdr (assoc topic gnus-topic-alist)))
423          (g (cdr (member group groups)))
424          (unfound t))
425     (while (and g unfound)
426       (when (gnus-group-goto-group (pop g))
427         (beginning-of-line)
428         (setq unfound nil)))
429     (when unfound
430       (setq g (cdr (member group (reverse groups))))
431       (while (and g unfound)
432         (when (gnus-group-goto-group (pop g))
433           (forward-line 1)
434           (setq unfound nil)))
435       (when unfound
436         (gnus-topic-goto-topic topic)
437         (forward-line 1)))))
438
439 (defun gnus-topic-update-topic-line (&optional topic level)
440   (unless topic
441     (setq topic gnus-topic-topology)
442     (setq level 0))
443   (let* ((type (pop topic))
444          (buffer-read-only nil)
445          (entries (gnus-topic-find-groups 
446                    (car type) (car gnus-group-list-mode)
447                    (cdr gnus-group-list-mode)))
448          (visiblep (eq (nth 1 type) 'visible))
449          (all-entries entries)
450          (unread 0)
451          info entry end)
452     ;; Tally any sub-topics.
453     (while topic
454       (incf unread (gnus-topic-update-topic-line (pop topic) (1+ level))))
455     ;; Tally all the groups that belong in this topic.
456     (while (setq info (nth 2 (setq entry (pop entries))))
457       (when (numberp (car entry))
458         (incf unread (car entry))))
459     ;; Insert the topic line.
460     (when (gnus-topic-goto-topic (car type))
461       (gnus-topic-insert-topic-line 
462        (car type) visiblep
463        (not (eq (nth 2 type) 'hidden))
464        level all-entries unread)
465       (gnus-delete-line))
466     unread))
467
468 (defun gnus-topic-grok-active (&optional force)
469   "Parse all active groups and create topic structures for them."
470   ;; First we make sure that we have really read the active file. 
471   (when (or force
472             (not gnus-topic-active-alist))
473     (when (or force
474               (not gnus-have-read-active-file))
475       (let ((gnus-read-active-file t))
476         (gnus-read-active-file)))
477     (let (topology groups alist)
478       ;; Get a list of all groups available.
479       (mapatoms (lambda (g) (when (symbol-value g)
480                               (push (symbol-name g) groups)))
481                 gnus-active-hashtb)
482       (setq groups (sort groups 'string<))
483       ;; Init the variables.
484       (setq gnus-topic-active-topology '(("" visible)))
485       (setq gnus-topic-active-alist nil)
486       ;; Descend the top-level hierarchy.
487       (gnus-topic-grok-active-1 gnus-topic-active-topology groups)
488       ;; Set the top-level topic names to something nice.
489       (setcar (car gnus-topic-active-topology) "Gnus active")
490       (setcar (car gnus-topic-active-alist) "Gnus active"))))
491
492 (defun gnus-topic-grok-active-1 (topology groups)
493   (let* ((name (caar topology))
494          (prefix (concat "^" (regexp-quote name)))
495          tgroups nprefix ntopology group)
496     (while (and groups
497                 (string-match prefix (setq group (car groups))))
498       (if (not (string-match "\\." group (match-end 0)))
499           ;; There are no further hierarchies here, so we just
500           ;; enter this group into the list belonging to this
501           ;; topic.
502           (push (pop groups) tgroups)
503         ;; New sub-hierarchy, so we add it to the topology.
504         (nconc topology (list (setq ntopology 
505                                     (list (list (substring 
506                                                  group 0 (match-end 0))
507                                                 'invisible)))))
508         ;; Descend the hierarchy.
509         (setq groups (gnus-topic-grok-active-1 ntopology groups))))
510     ;; We remove the trailing "." from the topic name.
511     (setq name
512           (if (string-match "\\.$" name)
513               (substring name 0 (match-beginning 0))
514             name))
515     ;; Add this topic and its groups to the topic alist.
516     (push (cons name (nreverse tgroups)) gnus-topic-active-alist)
517     (setcar (car topology) name)
518     ;; We return the rest of the groups that didn't belong
519     ;; to this topic.
520     groups))
521
522 (defun gnus-group-active-topic-p ()
523   "Return whether the current active comes from the active topics."
524   (save-excursion
525     (beginning-of-line)
526     (get-text-property (point) 'gnus-active)))
527
528 ;;; Topic mode, commands and keymap.
529
530 (defvar gnus-topic-mode-map nil)
531 (defvar gnus-group-topic-map nil)
532
533 (unless gnus-topic-mode-map
534   (setq gnus-topic-mode-map (make-sparse-keymap))
535
536   ;; Override certain group mode keys.
537   (gnus-define-keys
538    gnus-topic-mode-map
539    "=" gnus-topic-select-group
540    "\r" gnus-topic-select-group
541    " " gnus-topic-read-group
542    "\C-k" gnus-topic-kill-group
543    "\C-y" gnus-topic-yank-group
544    "\M-g" gnus-topic-get-new-news-this-topic
545    "\C-i" gnus-topic-indent
546    "AT" gnus-topic-list-active
547    gnus-mouse-2 gnus-mouse-pick-topic)
548
549   ;; Define a new submap.
550   (gnus-define-keys
551    (gnus-group-topic-map "T" gnus-group-mode-map)
552    "#" gnus-topic-mark-topic
553    "n" gnus-topic-create-topic
554    "m" gnus-topic-move-group
555    "D" gnus-topic-remove-group
556    "c" gnus-topic-copy-group
557    "h" gnus-topic-hide-topic
558    "s" gnus-topic-show-topic
559    "M" gnus-topic-move-matching
560    "C" gnus-topic-copy-matching
561    "r" gnus-topic-rename
562    "\177" gnus-topic-delete))
563
564 (defun gnus-topic-make-menu-bar ()
565   (unless (boundp 'gnus-topic-menu)
566     (easy-menu-define
567      gnus-topic-menu gnus-topic-mode-map ""
568      '("Topics"
569        ["Toggle topics" gnus-topic-mode t]
570        ("Groups"
571         ["Copy" gnus-topic-copy-group t]
572         ["Move" gnus-topic-move-group t]
573         ["Remove" gnus-topic-remove-group t]
574         ["Copy matching" gnus-topic-copy-matching t]
575         ["Move matching" gnus-topic-move-matching t])
576        ("Topics"
577         ["Show" gnus-topic-show-topic t]
578         ["Hide" gnus-topic-hide-topic t]
579         ["Delete" gnus-topic-delete t]
580         ["Rename" gnus-topic-rename t]
581         ["Create" gnus-topic-create-topic t]
582         ["Mark" gnus-topic-mark-topic t]
583         ["Indent" gnus-topic-indent t])
584        ["List active" gnus-topic-list-active t]))))
585
586
587 (defun gnus-topic-mode (&optional arg redisplay)
588   "Minor mode for topicsifying Gnus group buffers."
589   (interactive (list current-prefix-arg t))
590   (when (eq major-mode 'gnus-group-mode)
591     (make-local-variable 'gnus-topic-mode)
592     (setq gnus-topic-mode 
593           (if (null arg) (not gnus-topic-mode)
594             (> (prefix-numeric-value arg) 0)))
595     ;; Infest Gnus with topics.
596     (when gnus-topic-mode
597       (when (and menu-bar-mode
598                  (gnus-visual-p 'topic-menu 'menu))
599         (gnus-topic-make-menu-bar))
600       (setq gnus-topic-line-format-spec 
601             (gnus-parse-format gnus-topic-line-format 
602                                gnus-topic-line-format-alist t))
603       (unless (assq 'gnus-topic-mode minor-mode-alist)
604         (push '(gnus-topic-mode " Topic") minor-mode-alist))
605       (unless (assq 'gnus-topic-mode minor-mode-map-alist)
606         (push (cons 'gnus-topic-mode gnus-topic-mode-map)
607               minor-mode-map-alist))
608       (add-hook 'gnus-summary-exit-hook 'gnus-topic-update-topic)
609       (add-hook 'gnus-group-catchup-group-hook 'gnus-topic-update-topic)
610       (make-local-variable 'gnus-group-prepare-function)
611       (setq gnus-group-prepare-function 'gnus-group-prepare-topics)
612       (make-local-variable 'gnus-group-goto-next-group-function)
613       (setq gnus-group-goto-next-group-function 
614             'gnus-topic-goto-next-group)
615       (setq gnus-group-change-level-function 'gnus-topic-change-level)
616       (setq gnus-goto-missing-group-function 'gnus-topic-goto-missing-group)
617       (run-hooks 'gnus-topic-mode-hook)
618       ;; We check the topology.
619       (gnus-topic-check-topology))
620     ;; Remove topic infestation.
621     (unless gnus-topic-mode
622       (remove-hook 'gnus-summary-exit-hook 'gnus-topic-update-topic)
623       (remove-hook 'gnus-group-change-level-function 
624                    'gnus-topic-change-level)
625       (setq gnus-group-prepare-function 'gnus-group-prepare-flat))
626     (when redisplay
627       (gnus-group-list-groups))))
628     
629 (defun gnus-topic-select-group (&optional all)
630   "Select this newsgroup.
631 No article is selected automatically.
632 If ALL is non-nil, already read articles become readable.
633 If ALL is a number, fetch this number of articles."
634   (interactive "P")
635   (if (gnus-group-topic-p)
636       (let ((gnus-group-list-mode 
637              (if all (cons (if (numberp all) all 7) t) gnus-group-list-mode)))
638         (gnus-topic-fold all))
639     (gnus-group-select-group all)))
640
641 (defun gnus-mouse-pick-topic (e)
642   "Select the group or topic under the mouse pointer."
643   (interactive "e")
644   (mouse-set-point e)
645   (gnus-topic-read-group nil))
646
647 (defun gnus-topic-read-group (&optional all no-article group)
648   "Read news in this newsgroup.
649 If the prefix argument ALL is non-nil, already read articles become
650 readable.  IF ALL is a number, fetch this number of articles.  If the
651 optional argument NO-ARTICLE is non-nil, no article will be
652 auto-selected upon group entry.  If GROUP is non-nil, fetch that
653 group."
654   (interactive "P")
655   (if (gnus-group-topic-p)
656       (let ((gnus-group-list-mode 
657              (if all (cons (if (numberp all) all 7) t) gnus-group-list-mode)))
658         (gnus-topic-fold all))
659     (gnus-group-read-group all no-article group)))
660
661 (defun gnus-topic-create-topic (topic parent &optional previous)
662   (interactive 
663    (list
664     (read-string "Create topic: ")
665     (completing-read "Parent topic: " gnus-topic-alist nil t
666                      (cons (gnus-group-parent-topic) 0))))
667   ;; Check whether this topic already exists.
668   (when (gnus-topic-find-topology topic)
669     (error "Topic aleady exists"))
670   (unless parent
671     (setq parent (car (car gnus-topic-topology))))
672   (let ((top (cdr (gnus-topic-find-topology parent))))
673     (unless top
674       (error "No such parent topic: %s" parent))
675     (if previous
676         (progn
677           (while (and (cdr top)
678                       (not (equal (car (car (car (cdr top)))) previous)))
679             (setq top (cdr top)))
680           (setcdr top (cons (list (list topic 'visible)) (cdr top))))
681       (nconc top (list (list (list topic 'visible)))))
682     (unless (assoc topic gnus-topic-alist)
683       (push (list topic) gnus-topic-alist)))
684   (gnus-topic-enter-dribble)
685   (gnus-group-list-groups))
686
687 (defun gnus-topic-move-group (n topic &optional copyp)
688   "Move the current group to a topic."
689   (interactive
690    (list current-prefix-arg
691          (completing-read "Move to topic: " gnus-topic-alist nil t)))
692   (let ((groups (gnus-group-process-prefix n))
693         (topicl (assoc topic gnus-topic-alist))
694         entry)
695     (mapcar (lambda (g) 
696               (gnus-group-remove-mark g)
697               (when (and
698                      (setq entry (assoc (gnus-group-topic g) gnus-topic-alist))
699                      (not copyp))
700                 (setcdr entry (delete g (cdr entry))))
701               (when topicl
702                 (nconc topicl (list g))))
703             groups)
704     (gnus-group-position-point))
705   (gnus-topic-enter-dribble)
706   (gnus-group-list-groups))
707
708 (defun gnus-topic-remove-group (n)
709   "Remove the current group the topic."
710   (interactive "P")
711   (gnus-topic-move-group n nil))
712
713 (defun gnus-topic-copy-group (n topic)
714   "Copy the current group to a topic."
715   (interactive
716    (list current-prefix-arg
717          (completing-read "Copy to topic: " gnus-topic-alist nil t)))
718   (gnus-topic-move-group n topic t))
719
720 (defun gnus-topic-change-level (group level oldlevel)
721   "Run when changing levels to enter/remove groups from topics."
722   (when (and gnus-topic-mode 
723              (not gnus-topic-inhibit-change-level))
724     ;; Remove the group from the topics.
725     (when (and (< oldlevel gnus-level-zombie)
726                (>= level gnus-level-zombie))
727       (let (alist)
728         (when (setq alist (assoc (gnus-group-topic group) gnus-topic-alist))
729           (setcdr alist (delete group (cdr alist))))))
730     ;; If the group is subscribed. then we enter it into the topics.
731     (when (and (< level gnus-level-zombie)
732                (>= oldlevel gnus-level-zombie))
733       (let ((entry (assoc (caar gnus-topic-topology) gnus-topic-alist)))
734         (setcdr entry (cons group (cdr entry)))))))
735
736 (defun gnus-topic-goto-next-group (group props)
737   "Go to group or the next group after group."
738   (if (null group)
739       (gnus-topic-goto-topic (cadr (memq 'gnus-topic props)))
740     (if (gnus-group-goto-group group)
741         t
742       ;; The group is no longer visible.
743       (let* ((list (assoc (gnus-group-topic group) gnus-topic-alist))
744              (after (cdr (member group (cdr list)))))
745         ;; First try to put point on a group after the current one.
746         (while (and after
747                     (not (gnus-group-goto-group (car after))))
748           (setq after (cdr after)))
749         ;; Then try to put point on a group before point.
750         (unless after
751           (setq after (cdr (member group (reverse (cdr list)))))
752           (while (and after 
753                       (not (gnus-group-goto-group (car after))))
754             (setq after (cdr after))))
755         ;; Finally, just put point on the topic.
756         (unless after
757           (gnus-topic-goto-topic (car list))
758           (setq after nil))
759         t))))
760
761 (defun gnus-topic-kill-group (&optional n discard)
762   "Kill the next N groups."
763   (interactive "P")
764   (if (gnus-group-topic-p)
765       (let ((topic (gnus-group-topic-name)))
766         (gnus-topic-remove-topic nil t)
767         (push (gnus-topic-find-topology topic nil nil gnus-topic-topology)
768               gnus-topic-killed-topics))
769     (gnus-group-kill-group n discard)
770     (gnus-topic-update-topic)))
771   
772 (defun gnus-topic-yank-group (&optional arg)
773   "Yank the last topic."
774   (interactive "p")
775   (if gnus-topic-killed-topics
776       (let ((previous (gnus-group-parent-topic))
777             (item (nth 1 (pop gnus-topic-killed-topics))))
778         (gnus-topic-create-topic
779          (car item) (gnus-topic-parent-topic previous) previous))
780     (let* ((prev (gnus-group-group-name))
781            (gnus-topic-inhibit-change-level t)
782            (gnus-group-indentation
783             (make-string 
784              (* gnus-topic-indent-level
785                 (or (save-excursion
786                       (gnus-topic-goto-topic (gnus-group-parent-topic))
787                       (gnus-group-topic-level)) 0)) ? ))
788            yanked group alist)
789       ;; We first yank the groups the normal way...
790       (setq yanked (gnus-group-yank-group arg))
791       ;; Then we enter the yanked groups into the topics they belong
792       ;; to. 
793       (setq alist (assoc (save-excursion
794                            (forward-line -1)
795                            (gnus-group-parent-topic))
796                          gnus-topic-alist))
797       (when (stringp yanked)
798         (setq yanked (list yanked)))
799       (if (not prev)
800           (nconc alist yanked)
801         (if (not (cdr alist))
802             (setcdr alist (nconc yanked (cdr alist)))
803           (while (cdr alist)
804             (when (equal (car (cdr alist)) prev)
805               (setcdr alist (nconc yanked (cdr alist)))
806               (setq alist nil))
807             (setq alist (cdr alist))))))
808     (gnus-topic-update-topic)))
809
810 (defun gnus-topic-hide-topic ()
811   "Hide all subtopics under the current topic."
812   (interactive)
813   (when (gnus-group-topic-p)
814     (gnus-topic-remove-topic nil nil 'hidden)))
815
816 (defun gnus-topic-show-topic ()
817   "Show the hidden topic."
818   (interactive)
819   (when (gnus-group-topic-p)
820     (gnus-topic-remove-topic t nil 'shown)))
821
822 (defun gnus-topic-mark-topic (topic)
823   "Mark all groups in the topic with the process mark."
824   (interactive (list (gnus-group-parent-topic)))
825   (let ((groups (cdr (gnus-topic-find-groups topic))))
826     (while groups
827       (gnus-group-set-mark (gnus-info-group (nth 2 (pop groups)))))))
828
829 (defun gnus-topic-get-new-news-this-topic (&optional n)
830   "Check for new news in the current topic."
831   (interactive "P")
832   (if (not (gnus-group-topic-p))
833       (gnus-group-get-new-news-this-group n)
834     (gnus-topic-mark-topic (gnus-group-topic-name))
835     (gnus-group-get-new-news-this-group)))
836
837 (defun gnus-topic-move-matching (regexp topic &optional copyp)
838   "Move all groups that match REGEXP to some topic."
839   (interactive
840    (let (topic)
841      (nreverse
842       (list
843        (setq topic (completing-read "Move to topic: " gnus-topic-alist nil t))
844        (read-string (format "Move to %s (regexp): " topic))))))
845   (gnus-group-mark-regexp regexp)
846   (gnus-topic-move-group nil topic copyp))
847
848 (defun gnus-topic-copy-matching (regexp topic &optional copyp)
849   "Copy all groups that match REGEXP to some topic."
850   (interactive
851    (let (topic)
852      (nreverse
853       (list
854        (setq topic (completing-read "Copy to topic: " gnus-topic-alist nil t))
855        (read-string (format "Copy to %s (regexp): " topic))))))
856   (gnus-topic-move-matching regexp topic t))
857
858 (defun gnus-topic-delete (topic)
859   "Delete a topic."
860   (interactive (list (gnus-group-topic-name)))
861   (unless topic
862     (error "No topic to be deleted"))
863   (let ((entry (assoc topic gnus-topic-alist))
864         (buffer-read-only nil))
865     (when (cdr entry)
866       (error "Topic not empty"))
867     ;; Delete if visible.
868     (when (gnus-topic-goto-topic topic)
869       (gnus-delete-line))
870     ;; Remove from alist.
871     (setq gnus-topic-alist (delq entry gnus-topic-alist))
872     ;; Remove from topology.
873     (gnus-topic-find-topology topic nil nil 'delete)))
874
875 (defun gnus-topic-rename (old-name new-name)
876   "Rename a topic."
877   (interactive
878    (let (topic)
879      (list
880       (setq topic (completing-read "Rename topic: " gnus-topic-alist nil t
881                                    (cons (gnus-group-parent-topic) 0)))
882       (read-string (format "Rename %s to: " topic)))))
883   (let ((top (gnus-topic-find-topology old-name))
884         (entry (assoc old-name gnus-topic-alist)))
885     (when top
886       (setcar (car (cdr top)) new-name))
887     (when entry 
888       (setcar entry new-name))
889     (gnus-group-list-groups)))
890
891 (defun gnus-topic-indent (&optional unindent)
892   "Indent a topic -- make it a sub-topic of the previous topic.
893 If UNINDENT, remove an indentation."
894   (interactive "P")
895   (if unindent
896       (gnus-topic-unindent)
897     (let* ((topic (gnus-group-parent-topic))
898            (parent (gnus-topic-previous-topic topic)))
899       (unless parent
900         (error "Nothing to indent %s into" topic))
901       (when topic
902         (gnus-topic-goto-topic topic)
903         (gnus-topic-kill-group)
904         (gnus-topic-create-topic topic parent)
905         (gnus-topic-goto-topic topic)))))
906
907 (defun gnus-topic-unindent ()
908   "Unindent a topic."
909   (interactive)
910   (let* ((topic (gnus-group-parent-topic))
911          (parent (gnus-topic-parent-topic topic))
912          (grandparent (gnus-topic-parent-topic parent)))
913     (unless grandparent
914       (error "Nothing to indent %s into" topic))
915     (when topic
916       (gnus-topic-goto-topic topic)
917       (gnus-topic-kill-group)
918       (gnus-topic-create-topic topic grandparent)
919       (gnus-topic-goto-topic topic))))
920
921 (defun gnus-topic-list-active (&optional force)
922   "List all groups that Gnus knows about in a topicsified fashion.
923 If FORCE, always re-read the active file."
924   (interactive "P")
925   (gnus-topic-grok-active)
926   (let ((gnus-topic-topology gnus-topic-active-topology)
927         (gnus-topic-alist gnus-topic-active-alist)
928         gnus-killed-list gnus-zombie-list)
929     (gnus-group-list-groups 9 nil 1)))
930
931 (provide 'gnus-topic)
932
933 ;;; gnus-topic.el ends here