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