b27fd2dc908173183e3f83b1c9a6320ba09fa05f
[gnus] / lisp / gnus-srvr.el
1 ;;; gnus-srvr.el --- virtual server support for Gnus
2 ;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
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 (eval-when-compile (require 'cl))
30
31 (require 'gnus)
32 (require 'gnus-spec)
33 (require 'gnus-group)
34 (require 'gnus-int)
35 (require 'gnus-range)
36
37 (defcustom gnus-server-mode-hook nil
38   "Hook run in `gnus-server-mode' buffers."
39   :group 'gnus-server
40   :type 'hook)
41
42 (defcustom gnus-server-exit-hook nil
43   "Hook run when exiting the server buffer."
44   :group 'gnus-server
45   :type 'hook)
46
47 (defcustom gnus-server-line-format "     {%(%h:%w%)} %s%a\n"
48   "Format of server lines.
49 It works along the same lines as a normal formatting string,
50 with some simple extensions.
51
52 The following specs are understood:
53
54 %h back end
55 %n name
56 %w address
57 %s status
58 %a agent covered
59
60 General format specifiers can also be used.
61 See Info node `(gnus)Formatting Variables'."
62   :link '(custom-manual "(gnus)Formatting Variables")
63   :group 'gnus-server-visual
64   :type 'string)
65
66 (defcustom gnus-server-mode-line-format "Gnus: %%b"
67   "The format specification for the server mode line."
68   :group 'gnus-server-visual
69   :type 'string)
70
71 (defcustom gnus-server-browse-in-group-buffer nil
72   "Whether server browsing should take place in the group buffer.
73 If nil, a faster, but more primitive, buffer is used instead."
74   :version "22.1"
75   :group 'gnus-server-visual
76   :type 'boolean)
77
78 ;;; Internal variables.
79
80 (defvar gnus-inserted-opened-servers nil)
81
82 (defvar gnus-server-line-format-alist
83   `((?h gnus-tmp-how ?s)
84     (?n gnus-tmp-name ?s)
85     (?w gnus-tmp-where ?s)
86     (?s gnus-tmp-status ?s)
87     (?a gnus-tmp-agent ?s)))
88
89 (defvar gnus-server-mode-line-format-alist
90   `((?S gnus-tmp-news-server ?s)
91     (?M gnus-tmp-news-method ?s)
92     (?u gnus-tmp-user-defined ?s)))
93
94 (defvar gnus-server-line-format-spec nil)
95 (defvar gnus-server-mode-line-format-spec nil)
96 (defvar gnus-server-killed-servers nil)
97
98 (defvar gnus-server-mode-map)
99
100 (defvar gnus-server-menu-hook nil
101   "*Hook run after the creation of the server mode menu.")
102
103 (defun gnus-server-make-menu-bar ()
104   (gnus-turn-off-edit-menu 'server)
105   (unless (boundp 'gnus-server-server-menu)
106     (easy-menu-define
107      gnus-server-server-menu gnus-server-mode-map ""
108      '("Server"
109        ["Add..." gnus-server-add-server t]
110        ["Browse" gnus-server-read-server t]
111        ["Scan" gnus-server-scan-server t]
112        ["List" gnus-server-list-servers t]
113        ["Kill" gnus-server-kill-server t]
114        ["Yank" gnus-server-yank-server t]
115        ["Copy" gnus-server-copy-server t]
116        ["Edit" gnus-server-edit-server t]
117        ["Regenerate" gnus-server-regenerate-server t]
118        ["Exit" gnus-server-exit t]))
119
120     (easy-menu-define
121      gnus-server-connections-menu gnus-server-mode-map ""
122      '("Connections"
123        ["Open" gnus-server-open-server t]
124        ["Close" gnus-server-close-server t]
125        ["Offline" gnus-server-offline-server t]
126        ["Deny" gnus-server-deny-server t]
127        "---"
128        ["Open All" gnus-server-open-all-servers t]
129        ["Close All" gnus-server-close-all-servers t]
130        ["Reset All" gnus-server-remove-denials t]))
131
132     (gnus-run-hooks 'gnus-server-menu-hook)))
133
134 (defvar gnus-server-mode-map nil)
135 (put 'gnus-server-mode 'mode-class 'special)
136
137 (unless gnus-server-mode-map
138   (setq gnus-server-mode-map (make-sparse-keymap))
139   (suppress-keymap gnus-server-mode-map)
140
141   (gnus-define-keys gnus-server-mode-map
142     " " gnus-server-read-server-in-server-buffer
143     "\r" gnus-server-read-server
144     gnus-mouse-2 gnus-server-pick-server
145     "q" gnus-server-exit
146     "l" gnus-server-list-servers
147     "k" gnus-server-kill-server
148     "y" gnus-server-yank-server
149     "c" gnus-server-copy-server
150     "a" gnus-server-add-server
151     "e" gnus-server-edit-server
152     "s" gnus-server-scan-server
153
154     "O" gnus-server-open-server
155     "\M-o" gnus-server-open-all-servers
156     "C" gnus-server-close-server
157     "\M-c" gnus-server-close-all-servers
158     "D" gnus-server-deny-server
159     "L" gnus-server-offline-server
160     "R" gnus-server-remove-denials
161
162     "n" next-line
163     "p" previous-line
164
165     "g" gnus-server-regenerate-server
166
167     "\C-c\C-i" gnus-info-find-node
168     "\C-c\C-b" gnus-bug))
169
170 (defface gnus-server-agent-face
171   '((((class color) (background light)) (:foreground "PaleTurquoise" :bold t))
172     (((class color) (background dark)) (:foreground "PaleTurquoise" :bold t))
173     (t (:bold t)))
174   "Face used for displaying AGENTIZED servers"
175   :group 'gnus-server-visual)
176
177 (defface gnus-server-opened-face
178   '((((class color) (background light)) (:foreground "Green3" :bold t))
179     (((class color) (background dark)) (:foreground "Green1" :bold t))
180     (t (:bold t)))
181   "Face used for displaying OPENED servers"
182   :group 'gnus-server-visual)
183
184 (defface gnus-server-closed-face
185   '((((class color) (background light)) (:foreground "Steel Blue" :italic t))
186     (((class color) (background dark))
187      (:foreground "Light Steel Blue" :italic t))
188     (t (:italic t)))
189   "Face used for displaying CLOSED servers"
190   :group 'gnus-server-visual)
191
192 (defface gnus-server-denied-face
193   '((((class color) (background light)) (:foreground "Red" :bold t))
194     (((class color) (background dark)) (:foreground "Pink" :bold t))
195     (t (:inverse-video t :bold t)))
196   "Face used for displaying DENIED servers"
197   :group 'gnus-server-visual)
198
199 (defface gnus-server-offline-face
200   '((((class color) (background light)) (:foreground "Orange" :bold t))
201     (((class color) (background dark)) (:foreground "Yellow" :bold t))
202     (t (:inverse-video t :bold t)))
203   "Face used for displaying OFFLINE servers"
204   :group 'gnus-server-visual)
205
206 (defcustom gnus-server-agent-face 'gnus-server-agent-face
207   "Face name to use on AGENTIZED servers."
208   :version "22.1"
209   :group 'gnus-server-visual
210   :type 'face)
211
212 (defcustom gnus-server-opened-face 'gnus-server-opened-face
213   "Face name to use on OPENED servers."
214   :version "22.1"
215   :group 'gnus-server-visual
216   :type 'face)
217
218 (defcustom gnus-server-closed-face 'gnus-server-closed-face
219   "Face name to use on CLOSED servers."
220   :version "22.1"
221   :group 'gnus-server-visual
222   :type 'face)
223
224 (defcustom gnus-server-denied-face 'gnus-server-denied-face
225   "Face name to use on DENIED servers."
226   :version "22.1"
227   :group 'gnus-server-visual
228   :type 'face)
229
230 (defcustom gnus-server-offline-face 'gnus-server-offline-face
231   "Face name to use on OFFLINE servers."
232   :version "22.1"
233   :group 'gnus-server-visual
234   :type 'face)
235
236 (defvar gnus-server-font-lock-keywords
237   (list
238    '("(\\(agent\\))" 1 gnus-server-agent-face)
239    '("(\\(opened\\))" 1 gnus-server-opened-face)
240    '("(\\(closed\\))" 1 gnus-server-closed-face)
241    '("(\\(offline\\))" 1 gnus-server-offline-face)
242    '("(\\(denied\\))" 1 gnus-server-denied-face)))
243
244 (defun gnus-server-mode ()
245   "Major mode for listing and editing servers.
246
247 All normal editing commands are switched off.
248 \\<gnus-server-mode-map>
249 For more in-depth information on this mode, read the manual
250 \(`\\[gnus-info-find-node]').
251
252 The following commands are available:
253
254 \\{gnus-server-mode-map}"
255   (interactive)
256   (when (gnus-visual-p 'server-menu 'menu)
257     (gnus-server-make-menu-bar))
258   (kill-all-local-variables)
259   (gnus-simplify-mode-line)
260   (setq major-mode 'gnus-server-mode)
261   (setq mode-name "Server")
262   (gnus-set-default-directory)
263   (setq mode-line-process nil)
264   (use-local-map gnus-server-mode-map)
265   (buffer-disable-undo)
266   (setq truncate-lines t)
267   (setq buffer-read-only t)
268   (if (featurep 'xemacs)
269       (put 'gnus-server-mode 'font-lock-defaults '(gnus-server-font-lock-keywords t))
270     (set (make-local-variable 'font-lock-defaults)
271          '(gnus-server-font-lock-keywords t)))
272   (gnus-run-hooks 'gnus-server-mode-hook))
273
274 (defun gnus-server-insert-server-line (gnus-tmp-name method)
275   (let* ((gnus-tmp-how (car method))
276          (gnus-tmp-where (nth 1 method))
277          (elem (assoc method gnus-opened-servers))
278          (gnus-tmp-status
279           (cond
280            ((eq (nth 1 elem) 'denied) "(denied)")
281            ((eq (nth 1 elem) 'offline) "(offline)")
282            (t
283             (condition-case nil
284                 (if (or (gnus-server-opened method)
285                         (eq (nth 1 elem) 'ok))
286                     "(opened)"
287                   "(closed)")
288               ((error) "(error)")))))
289          (gnus-tmp-agent (if (and gnus-agent
290                                   (gnus-agent-method-p method))
291                              " (agent)"
292                            "")))
293     (beginning-of-line)
294     (gnus-add-text-properties
295      (point)
296      (prog1 (1+ (point))
297        ;; Insert the text.
298        (eval gnus-server-line-format-spec))
299      (list 'gnus-server (intern gnus-tmp-name)
300            'gnus-named-server (intern (gnus-method-to-server method))))))
301
302 (defun gnus-enter-server-buffer ()
303   "Set up the server buffer."
304   (gnus-server-setup-buffer)
305   (gnus-configure-windows 'server)
306   (gnus-server-prepare))
307
308 (defun gnus-server-setup-buffer ()
309   "Initialize the server buffer."
310   (unless (get-buffer gnus-server-buffer)
311     (save-excursion
312       (set-buffer (gnus-get-buffer-create gnus-server-buffer))
313       (gnus-server-mode)
314       (when gnus-carpal
315         (gnus-carpal-setup-buffer 'server)))))
316
317 (defun gnus-server-prepare ()
318   (gnus-set-format 'server-mode)
319   (gnus-set-format 'server t)
320   (let ((alist gnus-server-alist)
321         (buffer-read-only nil)
322         done server op-ser)
323     (erase-buffer)
324     (setq gnus-inserted-opened-servers nil)
325     ;; First we do the real list of servers.
326     (while alist
327       (unless (member (cdar alist) done)
328         (push (cdar alist) done)
329         (setq server (pop alist))
330         (when (and server (car server) (cdr server))
331           (gnus-server-insert-server-line (car server) (cdr server))))
332       (when (member (cdar alist) done)
333         (pop alist)))
334     ;; Then we insert the list of servers that have been opened in
335     ;; this session.
336     (dolist (open gnus-opened-servers)
337       (when (and (not (member (car open) done))
338                  ;; Just ignore ephemeral servers.
339                  (not (member (car open) gnus-ephemeral-servers)))
340         (push (car open) done)
341         (gnus-server-insert-server-line
342          (setq op-ser (format "%s:%s" (caar open) (nth 1 (car open))))
343          (car open))
344         (push (list op-ser (car open)) gnus-inserted-opened-servers))))
345   (goto-char (point-min))
346   (gnus-server-position-point))
347
348 (defun gnus-server-server-name ()
349   (let ((server (get-text-property (point-at-bol) 'gnus-server)))
350     (and server (symbol-name server))))
351
352 (defun gnus-server-named-server ()
353   "Return a server name that matches one of the names returned by
354 `gnus-method-to-server'."
355   (let ((server (get-text-property (point-at-bol) 'gnus-named-server)))
356     (and server (symbol-name server))))
357
358 (defalias 'gnus-server-position-point 'gnus-goto-colon)
359
360 (defconst gnus-server-edit-buffer "*Gnus edit server*")
361
362 (defun gnus-server-update-server (server)
363   (save-excursion
364     (set-buffer gnus-server-buffer)
365     (let* ((buffer-read-only nil)
366            (entry (assoc server gnus-server-alist))
367            (oentry (assoc (gnus-server-to-method server)
368                           gnus-opened-servers)))
369       (when entry
370         (gnus-dribble-enter
371          (concat "(gnus-server-set-info \"" server "\" '"
372                  (gnus-prin1-to-string (cdr entry)) ")\n")))
373       (when (or entry oentry)
374         ;; Buffer may be narrowed.
375         (save-restriction
376           (widen)
377           (when (gnus-server-goto-server server)
378             (gnus-delete-line))
379           (if entry
380               (gnus-server-insert-server-line (car entry) (cdr entry))
381             (gnus-server-insert-server-line
382              (format "%s:%s" (caar oentry) (nth 1 (car oentry)))
383              (car oentry)))
384           (gnus-server-position-point))))))
385
386 (defun gnus-server-set-info (server info)
387   ;; Enter a select method into the virtual server alist.
388   (when (and server info)
389     (gnus-dribble-enter
390      (concat "(gnus-server-set-info \"" server "\" '"
391              (gnus-prin1-to-string info) ")"))
392     (let* ((server (nth 1 info))
393            (entry (assoc server gnus-server-alist))
394            (cached (assoc server gnus-server-method-cache)))
395       (if cached
396           (setq gnus-server-method-cache
397                 (delq cached gnus-server-method-cache)))
398       (if entry
399           (progn
400             ;; Remove the server from `gnus-opened-servers' since
401             ;; it has never been opened with the new `info' yet.
402             (gnus-opened-servers-remove (cdr entry))
403             ;; Don't make a new Lisp object.
404             (setcar (cdr entry) (car info))
405             (setcdr (cdr entry) (cdr info)))
406         (setq gnus-server-alist
407               (nconc gnus-server-alist (list (cons server info))))))))
408
409 ;;; Interactive server functions.
410
411 (defun gnus-server-kill-server (server)
412   "Kill the server on the current line."
413   (interactive (list (gnus-server-server-name)))
414   (unless (gnus-server-goto-server server)
415     (if server (error "No such server: %s" server)
416       (error "No server on the current line")))
417   (unless (assoc server gnus-server-alist)
418     (error "Read-only server %s" server))
419   (gnus-dribble-touch)
420   (let ((buffer-read-only nil))
421     (gnus-delete-line))
422   (push (assoc server gnus-server-alist) gnus-server-killed-servers)
423   (setq gnus-server-alist (delq (car gnus-server-killed-servers)
424                                 gnus-server-alist))
425   (let ((groups (gnus-groups-from-server server)))
426     (when (and groups
427                (gnus-yes-or-no-p
428                 (format "Kill all %s groups from this server? "
429                         (length groups))))
430       (dolist (group groups)
431         (setq gnus-newsrc-alist
432               (delq (assoc group gnus-newsrc-alist)
433                     gnus-newsrc-alist))
434         (when gnus-group-change-level-function
435           (funcall gnus-group-change-level-function
436                    group gnus-level-killed 3)))))
437   (gnus-server-position-point))
438
439 (defun gnus-server-yank-server ()
440   "Yank the previously killed server."
441   (interactive)
442   (unless gnus-server-killed-servers
443     (error "No killed servers to be yanked"))
444   (let ((alist gnus-server-alist)
445         (server (gnus-server-server-name))
446         (killed (car gnus-server-killed-servers)))
447     (if (not server)
448         (setq gnus-server-alist (nconc gnus-server-alist (list killed)))
449       (if (string= server (caar gnus-server-alist))
450           (push killed gnus-server-alist)
451         (while (and (cdr alist)
452                     (not (string= server (caadr alist))))
453           (setq alist (cdr alist)))
454         (if alist
455             (setcdr alist (cons killed (cdr alist)))
456           (setq gnus-server-alist (list killed)))))
457     (gnus-server-update-server (car killed))
458     (setq gnus-server-killed-servers (cdr gnus-server-killed-servers))
459     (gnus-server-position-point)))
460
461 (defun gnus-server-exit ()
462   "Return to the group buffer."
463   (interactive)
464   (gnus-run-hooks 'gnus-server-exit-hook)
465   (gnus-kill-buffer (current-buffer))
466   (gnus-configure-windows 'group t))
467
468 (defun gnus-server-list-servers ()
469   "List all available servers."
470   (interactive)
471   (let ((cur (gnus-server-server-name)))
472     (gnus-server-prepare)
473     (if cur (gnus-server-goto-server cur)
474       (goto-char (point-max))
475       (forward-line -1))
476     (gnus-server-position-point)))
477
478 (defun gnus-server-set-status (method status)
479   "Make METHOD have STATUS."
480   (let ((entry (assoc method gnus-opened-servers)))
481     (if entry
482         (setcar (cdr entry) status)
483       (push (list method status) gnus-opened-servers))))
484
485 (defun gnus-opened-servers-remove (method)
486   "Remove METHOD from the list of opened servers."
487   (setq gnus-opened-servers (delq (assoc method gnus-opened-servers)
488                                   gnus-opened-servers)))
489
490 (defun gnus-server-open-server (server)
491   "Force an open of SERVER."
492   (interactive (list (gnus-server-server-name)))
493   (let ((method (gnus-server-to-method server)))
494     (unless method
495       (error "No such server: %s" server))
496     (gnus-server-set-status method 'ok)
497     (prog1
498         (or (gnus-open-server method)
499             (progn (message "Couldn't open %s" server) nil))
500       (gnus-server-update-server server)
501       (gnus-server-position-point))))
502
503 (defun gnus-server-open-all-servers ()
504   "Open all servers."
505   (interactive)
506   (dolist (server gnus-inserted-opened-servers)
507     (gnus-server-open-server (car server))))
508
509 (defun gnus-server-close-server (server)
510   "Close SERVER."
511   (interactive (list (gnus-server-server-name)))
512   (let ((method (gnus-server-to-method server)))
513     (unless method
514       (error "No such server: %s" server))
515     (gnus-server-set-status method 'closed)
516     (prog1
517         (gnus-close-server method)
518       (gnus-server-update-server server)
519       (gnus-server-position-point))))
520
521 (defun gnus-server-offline-server (server)
522   "Set SERVER to offline."
523   (interactive (list (gnus-server-server-name)))
524   (let ((method (gnus-server-to-method server)))
525     (unless method
526       (error "No such server: %s" server))
527     (prog1
528         (gnus-close-server method)
529       (gnus-server-set-status method 'offline)
530       (gnus-server-update-server server)
531       (gnus-server-position-point))))
532
533 (defun gnus-server-close-all-servers ()
534   "Close all servers."
535   (interactive)
536   (dolist (server gnus-inserted-opened-servers)
537     (gnus-server-close-server (car server))))
538
539 (defun gnus-server-deny-server (server)
540   "Make sure SERVER will never be attempted opened."
541   (interactive (list (gnus-server-server-name)))
542   (let ((method (gnus-server-to-method server)))
543     (unless method
544       (error "No such server: %s" server))
545     (gnus-server-set-status method 'denied))
546   (gnus-server-update-server server)
547   (gnus-server-position-point)
548   t)
549
550 (defun gnus-server-remove-denials ()
551   "Make all denied servers into closed servers."
552   (interactive)
553   (dolist (server gnus-opened-servers)
554     (when (eq (nth 1 server) 'denied)
555       (setcar (nthcdr 1 server) 'closed)))
556   (gnus-server-list-servers))
557
558 (defun gnus-server-copy-server (from to)
559   (interactive
560    (list
561     (or (gnus-server-server-name)
562         (error "No server on the current line"))
563     (read-string "Copy to: ")))
564   (unless from
565     (error "No server on current line"))
566   (unless (and to (not (string= to "")))
567     (error "No name to copy to"))
568   (when (assoc to gnus-server-alist)
569     (error "%s already exists" to))
570   (unless (gnus-server-to-method from)
571     (error "%s: no such server" from))
572   (let ((to-entry (cons from (gnus-copy-sequence
573                               (gnus-server-to-method from)))))
574     (setcar to-entry to)
575     (setcar (nthcdr 2 to-entry) to)
576     (push to-entry gnus-server-killed-servers)
577     (gnus-server-yank-server)))
578
579 (defun gnus-server-add-server (how where)
580   (interactive
581    (list (intern (completing-read "Server method: "
582                                   gnus-valid-select-methods nil t))
583          (read-string "Server name: ")))
584   (when (assq where gnus-server-alist)
585     (error "Server with that name already defined"))
586   (push (list where how where) gnus-server-killed-servers)
587   (gnus-server-yank-server))
588
589 (defun gnus-server-goto-server (server)
590   "Jump to a server line."
591   (interactive
592    (list (completing-read "Goto server: " gnus-server-alist nil t)))
593   (let ((to (text-property-any (point-min) (point-max)
594                                'gnus-server (intern server))))
595     (when to
596       (goto-char to)
597       (gnus-server-position-point))))
598
599 (defun gnus-server-edit-server (server)
600   "Edit the server on the current line."
601   (interactive (list (gnus-server-server-name)))
602   (unless server
603     (error "No server on current line"))
604   (unless (assoc server gnus-server-alist)
605     (error "This server can't be edited"))
606   (let ((info (cdr (assoc server gnus-server-alist))))
607     (gnus-close-server info)
608     (gnus-edit-form
609      info "Editing the server."
610      `(lambda (form)
611         (gnus-server-set-info ,server form)
612         (gnus-server-list-servers)
613         (gnus-server-position-point)))))
614
615 (defun gnus-server-scan-server (server)
616   "Request a scan from the current server."
617   (interactive (list (gnus-server-server-name)))
618   (let ((method (gnus-server-to-method server)))
619     (if (not (gnus-get-function method 'request-scan))
620         (error "Server %s can't scan" (car method))
621       (gnus-message 3 "Scanning %s..." server)
622       (gnus-request-scan nil method)
623       (gnus-message 3 "Scanning %s...done" server))))
624
625 (defun gnus-server-read-server-in-server-buffer (server)
626   "Browse a server in server buffer."
627   (interactive (list (gnus-server-server-name)))
628   (let (gnus-server-browse-in-group-buffer)
629     (gnus-server-read-server server)))
630
631 (defun gnus-server-read-server (server)
632   "Browse a server."
633   (interactive (list (gnus-server-server-name)))
634   (let ((buf (current-buffer)))
635     (prog1
636         (gnus-browse-foreign-server server buf)
637       (save-excursion
638         (set-buffer buf)
639         (gnus-server-update-server (gnus-server-server-name))
640         (gnus-server-position-point)))))
641
642 (defun gnus-server-pick-server (e)
643   (interactive "e")
644   (mouse-set-point e)
645   (gnus-server-read-server (gnus-server-server-name)))
646
647 \f
648 ;;;
649 ;;; Browse Server Mode
650 ;;;
651
652 (defvar gnus-browse-menu-hook nil
653   "*Hook run after the creation of the browse mode menu.")
654
655 (defvar gnus-browse-mode-hook nil)
656 (defvar gnus-browse-mode-map nil)
657 (put 'gnus-browse-mode 'mode-class 'special)
658
659 (unless gnus-browse-mode-map
660   (setq gnus-browse-mode-map (make-keymap))
661   (suppress-keymap gnus-browse-mode-map)
662
663   (gnus-define-keys
664       gnus-browse-mode-map
665     " " gnus-browse-read-group
666     "=" gnus-browse-select-group
667     "n" gnus-browse-next-group
668     "p" gnus-browse-prev-group
669     "\177" gnus-browse-prev-group
670     [delete] gnus-browse-prev-group
671     "N" gnus-browse-next-group
672     "P" gnus-browse-prev-group
673     "\M-n" gnus-browse-next-group
674     "\M-p" gnus-browse-prev-group
675     "\r" gnus-browse-select-group
676     "u" gnus-browse-unsubscribe-current-group
677     "l" gnus-browse-exit
678     "L" gnus-browse-exit
679     "q" gnus-browse-exit
680     "Q" gnus-browse-exit
681     "d" gnus-browse-describe-group
682     "\C-c\C-c" gnus-browse-exit
683     "?" gnus-browse-describe-briefly
684
685     "\C-c\C-i" gnus-info-find-node
686     "\C-c\C-b" gnus-bug))
687
688 (defun gnus-browse-make-menu-bar ()
689   (gnus-turn-off-edit-menu 'browse)
690   (unless (boundp 'gnus-browse-menu)
691     (easy-menu-define
692      gnus-browse-menu gnus-browse-mode-map ""
693      '("Browse"
694        ["Subscribe" gnus-browse-unsubscribe-current-group t]
695        ["Read" gnus-browse-read-group t]
696        ["Select" gnus-browse-select-group t]
697        ["Describe" gnus-browse-describe-group t]
698        ["Next" gnus-browse-next-group t]
699        ["Prev" gnus-browse-prev-group t]
700        ["Exit" gnus-browse-exit t]))
701     (gnus-run-hooks 'gnus-browse-menu-hook)))
702
703 (defvar gnus-browse-current-method nil)
704 (defvar gnus-browse-return-buffer nil)
705
706 (defvar gnus-browse-buffer "*Gnus Browse Server*")
707
708 (defun gnus-browse-foreign-server (server &optional return-buffer)
709   "Browse the server SERVER."
710   (setq gnus-browse-current-method (gnus-server-to-method server))
711   (setq gnus-browse-return-buffer return-buffer)
712   (let* ((method gnus-browse-current-method)
713          (orig-select-method gnus-select-method)
714          (gnus-select-method method)
715          groups group)
716     (gnus-message 5 "Connecting to %s..." (nth 1 method))
717     (cond
718      ((not (gnus-check-server method))
719       (gnus-message
720        1 "Unable to contact server %s: %s" (nth 1 method)
721        (gnus-status-message method))
722       nil)
723      ((not
724        (prog2
725            (gnus-message 6 "Reading active file...")
726            (gnus-request-list method)
727          (gnus-message 6 "Reading active file...done")))
728       (gnus-message
729        1 "Couldn't request list: %s" (gnus-status-message method))
730       nil)
731      (t
732       (with-current-buffer nntp-server-buffer
733         (let ((cur (current-buffer)))
734           (goto-char (point-min))
735           (unless (string= gnus-ignored-newsgroups "")
736             (delete-matching-lines gnus-ignored-newsgroups))
737           ;; We treat NNTP as a special case to avoid problems with
738           ;; garbage group names like `"foo' that appear in some badly
739           ;; managed active files. -jh.
740           (if (eq (car method) 'nntp)
741               (while (not (eobp))
742                 (ignore-errors
743                   (push (cons
744                          (buffer-substring
745                           (point)
746                           (progn
747                             (skip-chars-forward "^ \t")
748                             (point)))
749                          (let ((last (read cur)))
750                            (cons (read cur) last)))
751                         groups))
752                 (forward-line))
753             (while (not (eobp))
754               (ignore-errors
755                 (push (cons
756                        (if (eq (char-after) ?\")
757                            (read cur)
758                          (let ((p (point)) (name ""))
759                            (skip-chars-forward "^ \t\\\\")
760                            (setq name (buffer-substring p (point)))
761                            (while (eq (char-after) ?\\)
762                              (setq p (1+ (point)))
763                              (forward-char 2)
764                              (skip-chars-forward "^ \t\\\\")
765                              (setq name (concat name (buffer-substring
766                                                       p (point)))))
767                            name))
768                        (let ((last (read cur)))
769                          (cons (read cur) last)))
770                       groups))
771               (forward-line)))))
772       (setq groups (sort groups
773                          (lambda (l1 l2)
774                            (string< (car l1) (car l2)))))
775       (if gnus-server-browse-in-group-buffer
776           (let* ((gnus-select-method orig-select-method)
777                  (gnus-group-listed-groups
778                   (mapcar (lambda (group)
779                             (let ((name
780                                    (gnus-group-prefixed-name
781                                     (car group) method)))
782                               (gnus-set-active name (cdr group))
783                               name))
784                           groups)))
785             (gnus-configure-windows 'group)
786             (funcall gnus-group-prepare-function
787                      gnus-level-killed 'ignore 1 'ignore))
788         (gnus-get-buffer-create gnus-browse-buffer)
789         (when gnus-carpal
790           (gnus-carpal-setup-buffer 'browse))
791         (gnus-configure-windows 'browse)
792         (buffer-disable-undo)
793         (let ((buffer-read-only nil))
794           (erase-buffer))
795         (gnus-browse-mode)
796         (setq mode-line-buffer-identification
797               (list
798                (format
799                 "Gnus: %%b {%s:%s}" (car method) (cadr method))))
800         (let ((buffer-read-only nil)
801               name
802               (prefix (let ((gnus-select-method orig-select-method))
803                         (gnus-group-prefixed-name "" method))))
804           (while (setq group (pop groups))
805             (gnus-add-text-properties
806              (point)
807              (prog1 (1+ (point))
808                (insert
809                 (format "%c%7d: %s\n"
810                         (let ((level
811                                (if (string= prefix "")
812                                    (gnus-group-level (setq name (car group)))
813                                  (gnus-group-level
814                                   (concat prefix (setq name (car group)))))))
815                           (cond
816                            ((<= level gnus-level-subscribed) ? )
817                            ((<= level gnus-level-unsubscribed) ?U)
818                            ((= level gnus-level-zombie) ?Z)
819                            (t ?K)))
820                         (max 0 (- (1+ (cddr group)) (cadr group)))
821                         ;; Don't decode if name is ASCII
822                         (if (and (fboundp 'detect-coding-string)
823                                  (eq (detect-coding-string name t) 'undecided))
824                             name
825                           (mm-decode-coding-string
826                            name
827                            (inline (gnus-group-name-charset method name)))))))
828              (list 'gnus-group name)
829              )))
830         (switch-to-buffer (current-buffer)))
831       (goto-char (point-min))
832       (gnus-group-position-point)
833       (gnus-message 5 "Connecting to %s...done" (nth 1 method))
834       t))))
835
836 (defun gnus-browse-mode ()
837   "Major mode for browsing a foreign server.
838
839 All normal editing commands are switched off.
840
841 \\<gnus-browse-mode-map>
842 The only things you can do in this buffer is
843
844 1) `\\[gnus-browse-unsubscribe-current-group]' to subscribe to a group.
845 The group will be inserted into the group buffer upon exit from this
846 buffer.
847
848 2) `\\[gnus-browse-read-group]' to read a group ephemerally.
849
850 3) `\\[gnus-browse-exit]' to return to the group buffer."
851   (interactive)
852   (kill-all-local-variables)
853   (when (gnus-visual-p 'browse-menu 'menu)
854     (gnus-browse-make-menu-bar))
855   (gnus-simplify-mode-line)
856   (setq major-mode 'gnus-browse-mode)
857   (setq mode-name "Browse Server")
858   (setq mode-line-process nil)
859   (use-local-map gnus-browse-mode-map)
860   (buffer-disable-undo)
861   (setq truncate-lines t)
862   (gnus-set-default-directory)
863   (setq buffer-read-only t)
864   (gnus-run-hooks 'gnus-browse-mode-hook))
865
866 (defun gnus-browse-read-group (&optional no-article)
867   "Enter the group at the current line."
868   (interactive)
869   (let ((group (gnus-browse-group-name)))
870     (if (or (not (gnus-get-info group))
871             (gnus-ephemeral-group-p group))
872         (unless (gnus-group-read-ephemeral-group
873                  group gnus-browse-current-method nil
874                  (cons (current-buffer) 'browse))
875           (error "Couldn't enter %s" group))
876       (unless (gnus-group-read-group nil no-article group)
877         (error "Couldn't enter %s" group)))))
878
879 (defun gnus-browse-select-group ()
880   "Select the current group."
881   (interactive)
882   (gnus-browse-read-group 'no))
883
884 (defun gnus-browse-next-group (n)
885   "Go to the next group."
886   (interactive "p")
887   (prog1
888       (forward-line n)
889     (gnus-group-position-point)))
890
891 (defun gnus-browse-prev-group (n)
892   "Go to the next group."
893   (interactive "p")
894   (gnus-browse-next-group (- n)))
895
896 (defun gnus-browse-unsubscribe-current-group (arg)
897   "(Un)subscribe to the next ARG groups."
898   (interactive "p")
899   (when (eobp)
900     (error "No group at current line"))
901   (let ((ward (if (< arg 0) -1 1))
902         (arg (abs arg)))
903     (while (and (> arg 0)
904                 (not (eobp))
905                 (gnus-browse-unsubscribe-group)
906                 (zerop (gnus-browse-next-group ward)))
907       (decf arg))
908     (gnus-group-position-point)
909     (when (/= 0 arg)
910       (gnus-message 7 "No more newsgroups"))
911     arg))
912
913 (defun gnus-browse-group-name ()
914   (save-excursion
915     (beginning-of-line)
916     (let ((name (get-text-property (point) 'gnus-group)))
917       (when (re-search-forward ": \\(.*\\)$" (point-at-eol) t)
918         (concat (gnus-method-to-server-name gnus-browse-current-method) ":"
919                 (or name
920                     (match-string-no-properties 1)))))))
921
922 (defun gnus-browse-describe-group (group)
923   "Describe the current group."
924   (interactive (list (gnus-browse-group-name)))
925   (gnus-group-describe-group nil group))
926
927 (defun gnus-browse-unsubscribe-group ()
928   "Toggle subscription of the current group in the browse buffer."
929   (let ((sub nil)
930         (buffer-read-only nil)
931         group)
932     (save-excursion
933       (beginning-of-line)
934       ;; If this group it killed, then we want to subscribe it.
935       (unless (eq (char-after) ? )
936         (setq sub t))
937       (setq group (gnus-browse-group-name))
938       (when (gnus-server-equal gnus-browse-current-method "native")
939         (setq group (gnus-group-real-name group)))
940       (if sub
941           (progn
942             ;; Make sure the group has been properly removed before we
943             ;; subscribe to it.
944             (gnus-kill-ephemeral-group group)
945             (gnus-group-change-level
946              (list t group gnus-level-default-subscribed
947                    nil nil (if (gnus-server-equal
948                                 gnus-browse-current-method "native")
949                                nil
950                              (gnus-method-simplify
951                               gnus-browse-current-method)))
952              gnus-level-default-subscribed (gnus-group-level group)
953              (and (car (nth 1 gnus-newsrc-alist))
954                   (gnus-group-entry (car (nth 1 gnus-newsrc-alist))))
955              t)
956             (delete-char 1)
957             (insert ? ))
958         (gnus-group-change-level
959          group gnus-level-unsubscribed gnus-level-default-subscribed)
960         (delete-char 1)
961         (insert ?U)))
962     t))
963
964 (defun gnus-browse-exit ()
965   "Quit browsing and return to the group buffer."
966   (interactive)
967   (when (eq major-mode 'gnus-browse-mode)
968     (gnus-kill-buffer (current-buffer)))
969   ;; Insert the newly subscribed groups in the group buffer.
970   (save-excursion
971     (set-buffer gnus-group-buffer)
972     (gnus-group-list-groups nil))
973   (if gnus-browse-return-buffer
974       (gnus-configure-windows 'server 'force)
975     (gnus-configure-windows 'group 'force)))
976
977 (defun gnus-browse-describe-briefly ()
978   "Give a one line description of the group mode commands."
979   (interactive)
980   (gnus-message 6
981                 (substitute-command-keys "\\<gnus-browse-mode-map>\\[gnus-group-next-group]:Forward  \\[gnus-group-prev-group]:Backward  \\[gnus-browse-exit]:Exit  \\[gnus-info-find-node]:Run Info  \\[gnus-browse-describe-briefly]:This help")))
982
983 (defun gnus-server-regenerate-server ()
984   "Issue a command to the server to regenerate all its data structures."
985   (interactive)
986   (let ((server (gnus-server-server-name)))
987     (unless server
988       (error "No server on the current line"))
989     (condition-case ()
990         (gnus-get-function (gnus-server-to-method server)
991                            'request-regenerate)
992       (error
993         (error "This back end doesn't support regeneration")))
994     (gnus-message 5 "Requesting regeneration of %s..." server)
995     (unless (gnus-open-server server)
996       (error "Couldn't open server"))
997     (if (gnus-request-regenerate server)
998         (gnus-message 5 "Requesting regeneration of %s...done" server)
999       (gnus-message 5 "Couldn't regenerate %s" server))))
1000
1001 (provide 'gnus-srvr)
1002
1003 ;;; arch-tag: c0117f64-27ca-475d-9406-8da6854c7a25
1004 ;;; gnus-srvr.el ends here