*** empty log message ***
[gnus] / lisp / gnus.el
1 ;;; gnus.el --- a newsreader for GNU Emacs
2 ;; Copyright (C) 1987,88,89,90,93,94,95,96 Free Software Foundation, Inc.
3
4 ;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
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 (eval '(run-hooks 'gnus-load-hook))
30
31 (defconst gnus-version-number "0.2"
32   "Version number for this version of Gnus.")
33
34 (defconst gnus-version (format "Red Gnus v%s" gnus-version-number)
35   "Version string for this version of Gnus.")
36
37 ;;; Splash screen.
38
39 (defvar gnus-group-buffer "*Group*")
40
41 (defvar gnus-inhibit-startup-message nil
42   "*If non-nil, the startup message will not be displayed.")
43
44 (defun gnus-splash ()
45   (save-excursion
46     (switch-to-buffer gnus-group-buffer)
47     (let ((buffer-read-only nil))
48       (erase-buffer)
49       (unless gnus-inhibit-startup-message
50         (gnus-group-startup-message)
51         (sit-for 0)))))
52
53 (defun gnus-indent-rigidly (start end arg)
54   "Indent rigidly using only spaces and no tabs."
55   (save-excursion
56     (save-restriction
57       (narrow-to-region start end)
58       (indent-rigidly start end arg)
59       (goto-char (point-min))
60       (while (search-forward "\t" nil t)
61         (replace-match "        " t t)))))
62
63 (defun gnus-group-startup-message (&optional x y)
64   "Insert startup message in current buffer."
65   ;; Insert the message.
66   (erase-buffer)
67   (insert
68    (format "              %s
69           _    ___ _             _
70           _ ___ __ ___  __    _ ___
71           __   _     ___    __  ___
72               _           ___     _
73              _  _ __             _
74              ___   __            _
75                    __           _
76                     _      _   _
77                    _      _    _
78                       _  _    _
79                   __  ___
80                  _   _ _     _
81                 _   _
82               _    _
83              _    _
84             _
85           __
86
87 "
88            ""))
89   ;; And then hack it.
90   (gnus-indent-rigidly (point-min) (point-max)
91                        (/ (max (- (window-width) (or x 46)) 0) 2))
92   (goto-char (point-min))
93   (forward-line 1)
94   (let* ((pheight (count-lines (point-min) (point-max)))
95          (wheight (window-height))
96          (rest (- wheight pheight)))
97     (insert (make-string (max 0 (* 2 (/ rest 3))) ?\n)))
98   ;; Fontify some.
99   (goto-char (point-min))
100   (and (search-forward "Praxis" nil t)
101        (put-text-property (match-beginning 0) (match-end 0) 'face 'bold))
102   (goto-char (point-min))
103   (setq mode-line-buffer-identification gnus-version)
104   (set-buffer-modified-p t))
105
106 (eval-when (load)
107   (gnus-splash))
108
109 ;;; Do the rest.
110
111 (require 'gnus-load)
112
113 \f
114
115 ;; Fix by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>.
116 ;; If you want the cursor to go somewhere else, set these two
117 ;; functions in some startup hook to whatever you want.
118 (defalias 'gnus-summary-position-point 'gnus-goto-colon)
119 (defalias 'gnus-group-position-point 'gnus-goto-colon)
120
121 ;;; Various macros and substs.
122
123 (defun gnus-header-from (header)
124   (mail-header-from header))
125
126 (defmacro gnus-gethash (string hashtable)
127   "Get hash value of STRING in HASHTABLE."
128   `(symbol-value (intern-soft ,string ,hashtable)))
129
130 (defmacro gnus-sethash (string value hashtable)
131   "Set hash value.  Arguments are STRING, VALUE, and HASHTABLE."
132   `(set (intern ,string ,hashtable) ,value))
133
134 (defmacro gnus-group-unread (group)
135   "Get the currently computed number of unread articles in GROUP."
136   `(car (gnus-gethash ,group gnus-newsrc-hashtb)))
137
138 (defmacro gnus-group-entry (group)
139   "Get the newsrc entry for GROUP."
140   `(gnus-gethash ,group gnus-newsrc-hashtb))
141
142 (defmacro gnus-active (group)
143   "Get active info on GROUP."
144   `(gnus-gethash ,group gnus-active-hashtb))
145
146 (defmacro gnus-set-active (group active)
147   "Set GROUP's active info."
148   `(gnus-sethash ,group ,active gnus-active-hashtb))
149
150 (defun gnus-alive-p ()
151   "Say whether Gnus is running or not."
152   (and gnus-group-buffer
153        (get-buffer gnus-group-buffer)))
154
155 ;; Info access macros.
156
157 (defmacro gnus-info-group (info)
158   `(nth 0 ,info))
159 (defmacro gnus-info-rank (info)
160   `(nth 1 ,info))
161 (defmacro gnus-info-read (info)
162   `(nth 2 ,info))
163 (defmacro gnus-info-marks (info)
164   `(nth 3 ,info))
165 (defmacro gnus-info-method (info)
166   `(nth 4 ,info))
167 (defmacro gnus-info-params (info)
168   `(nth 5 ,info))
169
170 (defmacro gnus-info-level (info)
171   `(let ((rank (gnus-info-rank ,info)))
172      (if (consp rank)
173          (car rank)
174        rank)))
175 (defmacro gnus-info-score (info)
176   `(let ((rank (gnus-info-rank ,info)))
177      (or (and (consp rank) (cdr rank)) 0)))
178
179 (defmacro gnus-info-set-group (info group)
180   `(setcar ,info ,group))
181 (defmacro gnus-info-set-rank (info rank)
182   `(setcar (nthcdr 1 ,info) ,rank))
183 (defmacro gnus-info-set-read (info read)
184   `(setcar (nthcdr 2 ,info) ,read))
185 (defmacro gnus-info-set-marks (info marks)
186   `(setcar (nthcdr 3 ,info) ,marks))
187 (defmacro gnus-info-set-method (info method)
188   `(setcar (nthcdr 4 ,info) ,method))
189 (defmacro gnus-info-set-params (info params)
190   `(setcar (nthcdr 5 ,info) ,params))
191
192 (defmacro gnus-info-set-level (info level)
193   `(let ((rank (cdr ,info)))
194      (if (consp (car rank))
195          (setcar (car rank) ,level)
196        (setcar rank ,level))))
197 (defmacro gnus-info-set-score (info score)
198   `(let ((rank (cdr ,info)))
199      (if (consp (car rank))
200          (setcdr (car rank) ,score)
201        (setcar rank (cons (car rank) ,score)))))
202
203 (defmacro gnus-get-info (group)
204   `(nth 2 (gnus-gethash ,group gnus-newsrc-hashtb)))
205
206 ;; Find out whether the gnus-visual TYPE is wanted.
207 (defun gnus-visual-p (&optional type class)
208   (and gnus-visual                      ; Has to be non-nil, at least.
209        (if (not type)                   ; We don't care about type.
210            gnus-visual
211          (if (listp gnus-visual)        ; It's a list, so we check it.
212              (or (memq type gnus-visual)
213                  (memq class gnus-visual))
214            t))))
215
216 ;;; Load the compatability functions.
217
218 (require 'gnus-cus)
219 (require 'gnus-ems)
220
221 \f
222 ;;;
223 ;;; Shutdown
224 ;;;
225
226 (defvar gnus-shutdown-alist nil)
227
228 (defun gnus-add-shutdown (function &rest symbols)
229   "Run FUNCTION whenever one of SYMBOLS is shut down."
230   (push (cons function symbols) gnus-shutdown-alist))
231
232 (defun gnus-shutdown (symbol)
233   "Shut down everything that waits for SYMBOL."
234   (let ((alist gnus-shutdown-alist)
235         entry)
236     (while (setq entry (pop alist))
237       (when (memq symbol (cdr entry))
238         (funcall (car entry))))))
239
240 \f
241 ;;;
242 ;;; Gnus Utility Functions
243 ;;;
244
245 ;; Add the current buffer to the list of buffers to be killed on exit.
246 (defun gnus-add-current-to-buffer-list ()
247   (or (memq (current-buffer) gnus-buffer-list)
248       (setq gnus-buffer-list (cons (current-buffer) gnus-buffer-list))))
249
250 (defun gnus-version (&optional arg)
251   "Version number of this version of Gnus.
252 If ARG, insert string at point."
253   (interactive "P")
254   (let ((methods gnus-valid-select-methods)
255         (mess gnus-version)
256         meth)
257     ;; Go through all the legal select methods and add their version
258     ;; numbers to the total version string.  Only the backends that are
259     ;; currently in use will have their message numbers taken into
260     ;; consideration.
261     (while methods
262       (setq meth (intern (concat (caar methods) "-version")))
263       (and (boundp meth)
264            (stringp (symbol-value meth))
265            (setq mess (concat mess "; " (symbol-value meth))))
266       (setq methods (cdr methods)))
267     (if arg
268         (insert (message mess))
269       (message mess))))
270
271 (defun gnus-continuum-version (version)
272   "Return VERSION as a floating point number."
273   (when (or (string-match "^\\([^ ]+\\)? ?Gnus v?\\([0-9.]+\\)$" version)
274             (string-match "^\\(.?\\)gnus-\\([0-9.]+\\)$" version))
275     (let* ((alpha (and (match-beginning 1) (match-string 1 version)))
276            (number (match-string 2 version))
277            major minor least)
278       (string-match "\\([0-9]\\)\\.\\([0-9]+\\)\\.?\\([0-9]+\\)?" number)
279       (setq major (string-to-number (match-string 1 number)))
280       (setq minor (string-to-number (match-string 2 number)))
281       (setq least (if (match-beginning 3)
282                       (string-to-number (match-string 3 number))
283                     0))
284       (string-to-number
285        (if (zerop major)
286            (format "%s00%02d%02d"
287                    (cond 
288                     ((member alpha '("(ding)" "d")) "4.99")
289                     ((member alpha '("September" "s")) "5.01")
290                     ((member alpha '("Red" "r")) "5.03"))
291                    minor least)
292          (format "%d.%02d%02d" major minor least))))))
293
294 (defun gnus-info-find-node ()
295   "Find Info documentation of Gnus."
296   (interactive)
297   ;; Enlarge info window if needed.
298   (let ((mode major-mode)
299         gnus-info-buffer)
300     (Info-goto-node (cadr (assq mode gnus-info-nodes)))
301     (setq gnus-info-buffer (current-buffer))
302     (gnus-configure-windows 'info)))
303
304 ;;; More various functions.
305
306 (defun gnus-group-read-only-p (&optional group)
307   "Check whether GROUP supports editing or not.
308 If GROUP is nil, `gnus-newsgroup-name' will be checked instead.  Note
309 that that variable is buffer-local to the summary buffers."
310   (let ((group (or group gnus-newsgroup-name)))
311     (not (gnus-check-backend-function 'request-replace-article group))))
312
313 (defun gnus-group-total-expirable-p (group)
314   "Check whether GROUP is total-expirable or not."
315   (let ((params (gnus-info-params (gnus-get-info group))))
316     (or (memq 'total-expire params)
317         (cdr (assq 'total-expire params)) ; (total-expire . t)
318         (and gnus-total-expirable-newsgroups ; Check var.
319              (string-match gnus-total-expirable-newsgroups group)))))
320
321 (defun gnus-group-auto-expirable-p (group)
322   "Check whether GROUP is total-expirable or not."
323   (let ((params (gnus-info-params (gnus-get-info group))))
324     (or (memq 'auto-expire params)
325         (cdr (assq 'auto-expire params)) ; (auto-expire . t)
326         (and gnus-auto-expirable-newsgroups ; Check var.
327              (string-match gnus-auto-expirable-newsgroups group)))))
328
329 (defun gnus-virtual-group-p (group)
330   "Say whether GROUP is virtual or not."
331   (memq 'virtual (assoc (symbol-name (car (gnus-find-method-for-group group)))
332                         gnus-valid-select-methods)))
333
334 (defun gnus-news-group-p (group &optional article)
335   "Return non-nil if GROUP (and ARTICLE) come from a news server."
336   (or (gnus-member-of-valid 'post group) ; Ordinary news group.
337       (and (gnus-member-of-valid 'post-mail group) ; Combined group.
338            (eq (gnus-request-type group article) 'news))))
339
340 ;; Returns a list of writable groups.
341 (defun gnus-writable-groups ()
342   (let ((alist gnus-newsrc-alist)
343         groups group)
344     (while (setq group (car (pop alist)))
345       (unless (gnus-group-read-only-p group)
346         (push group groups)))
347     (nreverse groups)))
348
349 ;; Check whether to use long file names.
350 (defun gnus-use-long-file-name (symbol)
351   ;; The variable has to be set...
352   (and gnus-use-long-file-name
353        ;; If it isn't a list, then we return t.
354        (or (not (listp gnus-use-long-file-name))
355            ;; If it is a list, and the list contains `symbol', we
356            ;; return nil.
357            (not (memq symbol gnus-use-long-file-name)))))
358
359 ;; Generate a unique new group name.
360 (defun gnus-generate-new-group-name (leaf)
361   (let ((name leaf)
362         (num 0))
363     (while (gnus-gethash name gnus-newsrc-hashtb)
364       (setq name (concat leaf "<" (int-to-string (setq num (1+ num))) ">")))
365     name))
366
367 (defun gnus-ephemeral-group-p (group)
368   "Say whether GROUP is ephemeral or not."
369   (gnus-group-get-parameter group 'quit-config))
370
371 (defun gnus-group-quit-config (group)
372   "Return the quit-config of GROUP."
373   (gnus-group-get-parameter group 'quit-config))
374
375 (defun gnus-simplify-mode-line ()
376   "Make mode lines a bit simpler."
377   (setq mode-line-modified "-- ")
378   (when (listp mode-line-format)
379     (make-local-variable 'mode-line-format)
380     (setq mode-line-format (copy-sequence mode-line-format))
381     (when (equal (nth 3 mode-line-format) "   ")
382       (setcar (nthcdr 3 mode-line-format) " "))))
383
384 ;;; Servers and groups.
385
386 (defsubst gnus-server-add-address (method)
387   (let ((method-name (symbol-name (car method))))
388     (if (and (memq 'address (assoc method-name gnus-valid-select-methods))
389              (not (assq (intern (concat method-name "-address")) method)))
390         (append method (list (list (intern (concat method-name "-address"))
391                                    (nth 1 method))))
392       method)))
393
394 (defsubst gnus-server-get-method (group method)
395   ;; Input either a server name, and extended server name, or a
396   ;; select method, and return a select method.
397   (cond ((stringp method)
398          (gnus-server-to-method method))
399         ((equal method gnus-select-method)
400          gnus-select-method)
401         ((and (stringp (car method)) group)
402          (gnus-server-extend-method group method))
403         ((and method (not group)
404               (equal (cadr method) ""))
405          method)
406         (t
407          (gnus-server-add-address method))))
408
409 (defun gnus-server-to-method (server)
410   "Map virtual server names to select methods."
411   (or 
412    ;; Is this a method, perhaps?
413    (and server (listp server) server)
414    ;; Perhaps this is the native server?
415    (and (equal server "native") gnus-select-method)
416    ;; It should be in the server alist.
417    (cdr (assoc server gnus-server-alist))
418    ;; If not, we look through all the opened server
419    ;; to see whether we can find it there.
420    (let ((opened gnus-opened-servers))
421      (while (and opened
422                  (not (equal server (format "%s:%s" (caaar opened)
423                                             (cadaar opened)))))
424        (pop opened))
425      (caar opened))))
426
427 (defmacro gnus-method-equal (ss1 ss2)
428   "Say whether two servers are equal."
429   `(let ((s1 ,ss1)
430          (s2 ,ss2))
431      (or (equal s1 s2)
432          (and (= (length s1) (length s2))
433               (progn
434                 (while (and s1 (member (car s1) s2))
435                   (setq s1 (cdr s1)))
436                 (null s1))))))
437
438 (defun gnus-server-equal (m1 m2)
439   "Say whether two methods are equal."
440   (let ((m1 (cond ((null m1) gnus-select-method)
441                   ((stringp m1) (gnus-server-to-method m1))
442                   (t m1)))
443         (m2 (cond ((null m2) gnus-select-method)
444                   ((stringp m2) (gnus-server-to-method m2))
445                   (t m2))))
446     (gnus-method-equal m1 m2)))
447
448 (defun gnus-servers-using-backend (backend)
449   "Return a list of known servers using BACKEND."
450   (let ((opened gnus-opened-servers)
451         out)
452     (while opened
453       (when (eq backend (caaar opened))
454         (push (caar opened) out))
455       (pop opened))
456     out))
457
458 (defun gnus-archive-server-wanted-p ()
459   "Say whether the user wants to use the archive server."
460   (cond 
461    ((or (not gnus-message-archive-method)
462         (not gnus-message-archive-group))
463     nil)
464    ((and gnus-message-archive-method gnus-message-archive-group)
465     t)
466    (t
467     (let ((active (cadr (assq 'nnfolder-active-file
468                               gnus-message-archive-method))))
469       (and active
470            (file-exists-p active))))))
471
472 (defun gnus-group-prefixed-name (group method)
473   "Return the whole name from GROUP and METHOD."
474   (and (stringp method) (setq method (gnus-server-to-method method)))
475   (if (not method)
476       group
477     (concat (format "%s" (car method))
478             (if (and
479                  (or (assoc (format "%s" (car method)) 
480                             (gnus-methods-using 'address))
481                      (gnus-server-equal method gnus-message-archive-method))
482                  (nth 1 method)
483                  (not (string= (nth 1 method) "")))
484                 (concat "+" (nth 1 method)))
485             ":" group)))
486
487 (defun gnus-group-real-prefix (group)
488   "Return the prefix of the current group name."
489   (if (string-match "^[^:]+:" group)
490       (substring group 0 (match-end 0))
491     ""))
492
493 (defun gnus-group-method (group)
494   "Return the server or method used for selecting GROUP."
495   (let ((prefix (gnus-group-real-prefix group)))
496     (if (equal prefix "")
497         gnus-select-method
498       (let ((servers gnus-opened-servers)
499             (server "")
500             backend possible found)
501         (if (string-match "^[^\\+]+\\+" prefix)
502             (setq backend (intern (substring prefix 0 (1- (match-end 0))))
503                   server (substring prefix (match-end 0) (1- (length prefix))))
504           (setq backend (intern (substring prefix 0 (1- (length prefix))))))
505         (while servers
506           (when (eq (caaar servers) backend)
507             (setq possible (caar servers))
508             (when (equal (cadaar servers) server)
509               (setq found (caar servers))))
510           (pop servers))
511         (or (car (rassoc found gnus-server-alist))
512             found
513             (car (rassoc possible gnus-server-alist))
514             possible
515             (list backend server))))))
516
517 (defsubst gnus-secondary-method-p (method)
518   "Return whether METHOD is a secondary select method."
519   (let ((methods gnus-secondary-select-methods)
520         (gmethod (gnus-server-get-method nil method)))
521     (while (and methods
522                 (not (equal (gnus-server-get-method nil (car methods))
523                             gmethod)))
524       (setq methods (cdr methods)))
525     methods))
526
527 (defun gnus-group-foreign-p (group)
528   "Say whether a group is foreign or not."
529   (and (not (gnus-group-native-p group))
530        (not (gnus-group-secondary-p group))))
531
532 (defun gnus-group-native-p (group)
533   "Say whether the group is native or not."
534   (not (string-match ":" group)))
535
536 (defun gnus-group-secondary-p (group)
537   "Say whether the group is secondary or not."
538   (gnus-secondary-method-p (gnus-find-method-for-group group)))
539
540 (defun gnus-group-get-parameter (group &optional symbol)
541   "Returns the group parameters for GROUP.
542 If SYMBOL, return the value of that symbol in the group parameters."
543   (let ((params (gnus-info-params (gnus-get-info group))))
544     (if symbol
545         (gnus-group-parameter-value params symbol)
546       params)))
547
548 (defun gnus-group-parameter-value (params symbol)
549   "Return the value of SYMBOL in group PARAMS."
550   (or (car (memq symbol params))        ; It's either a simple symbol
551       (cdr (assq symbol params))))      ; or a cons.
552
553 (defun gnus-group-add-parameter (group param)
554   "Add parameter PARAM to GROUP."
555   (let ((info (gnus-get-info group)))
556     (if (not info)
557         () ; This is a dead group.  We just ignore it.
558       ;; Cons the new param to the old one and update.
559       (gnus-group-set-info (cons param (gnus-info-params info))
560                            group 'params))))
561
562 (defun gnus-group-set-parameter (group name value)
563   "Set parameter NAME to VALUE in GROUP."
564   (let ((info (gnus-get-info group)))
565     (if (not info)
566         () ; This is a dead group.  We just ignore it.
567       (let ((old-params (gnus-info-params info))
568             (new-params (list (cons name value))))
569         (while old-params
570           (if (or (not (listp (car old-params)))
571                   (not (eq (caar old-params) name)))
572               (setq new-params (append new-params (list (car old-params)))))
573           (setq old-params (cdr old-params)))
574         (gnus-group-set-info new-params group 'params)))))
575
576 (defun gnus-group-add-score (group &optional score)
577   "Add SCORE to the GROUP score.
578 If SCORE is nil, add 1 to the score of GROUP."
579   (let ((info (gnus-get-info group)))
580     (when info
581       (gnus-info-set-score info (+ (gnus-info-score info) (or score 1))))))
582
583 ;; Function written by Stainless Steel Rat <ratinox@ccs.neu.edu>.
584 (defun gnus-short-group-name (group &optional levels)
585   "Collapse GROUP name LEVELS."
586   (let* ((name "") 
587          (foreign "")
588          (depth 0) 
589          (skip 1)
590          (levels (or levels
591                      (progn
592                        (while (string-match "\\." group skip)
593                          (setq skip (match-end 0)
594                                depth (+ depth 1)))
595                        depth))))
596     (if (string-match ":" group)
597         (setq foreign (substring group 0 (match-end 0))
598               group (substring group (match-end 0))))
599     (while group
600       (if (and (string-match "\\." group)
601                (> levels (- gnus-group-uncollapsed-levels 1)))
602           (setq name (concat name (substring group 0 1))
603                 group (substring group (match-end 0))
604                 levels (- levels 1)
605                 name (concat name "."))
606         (setq name (concat foreign name group)
607               group nil)))
608     name))
609
610 \f
611 ;;;
612 ;;; Kill file handling.
613 ;;;
614
615 (defun gnus-apply-kill-file ()
616   "Apply a kill file to the current newsgroup.
617 Returns the number of articles marked as read."
618   (if (or (file-exists-p (gnus-newsgroup-kill-file nil))
619           (file-exists-p (gnus-newsgroup-kill-file gnus-newsgroup-name)))
620       (gnus-apply-kill-file-internal)
621     0))
622
623 (defun gnus-kill-save-kill-buffer ()
624   (let ((file (gnus-newsgroup-kill-file gnus-newsgroup-name)))
625     (when (get-file-buffer file)
626       (save-excursion
627         (set-buffer (get-file-buffer file))
628         (and (buffer-modified-p) (save-buffer))
629         (kill-buffer (current-buffer))))))
630
631 (defvar gnus-kill-file-name "KILL"
632   "Suffix of the kill files.")
633
634 (defun gnus-newsgroup-kill-file (newsgroup)
635   "Return the name of a kill file name for NEWSGROUP.
636 If NEWSGROUP is nil, return the global kill file name instead."
637   (cond 
638    ;; The global KILL file is placed at top of the directory.
639    ((or (null newsgroup)
640         (string-equal newsgroup ""))
641     (expand-file-name gnus-kill-file-name
642                       gnus-kill-files-directory))
643    ;; Append ".KILL" to newsgroup name.
644    ((gnus-use-long-file-name 'not-kill)
645     (expand-file-name (concat (gnus-newsgroup-savable-name newsgroup)
646                               "." gnus-kill-file-name)
647                       gnus-kill-files-directory))
648    ;; Place "KILL" under the hierarchical directory.
649    (t
650     (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
651                               "/" gnus-kill-file-name)
652                       gnus-kill-files-directory))))
653
654 ;;; Server things.
655
656 (defun gnus-member-of-valid (symbol group)
657   "Find out if GROUP has SYMBOL as part of its \"valid\" spec."
658   (memq symbol (assoc
659                 (symbol-name (car (gnus-find-method-for-group group)))
660                 gnus-valid-select-methods)))
661
662 (defun gnus-method-option-p (method option)
663   "Return non-nil if select METHOD has OPTION as a parameter."
664   (when (stringp method)
665     (setq method (gnus-server-to-method method)))
666   (memq option (assoc (format "%s" (car method))
667                       gnus-valid-select-methods)))
668
669 (defun gnus-server-extend-method (group method)
670   ;; This function "extends" a virtual server.  If the server is
671   ;; "hello", and the select method is ("hello" (my-var "something"))
672   ;; in the group "alt.alt", this will result in a new virtual server
673   ;; called "hello+alt.alt".
674   (let ((entry
675          (gnus-copy-sequence
676           (if (equal (car method) "native") gnus-select-method
677             (cdr (assoc (car method) gnus-server-alist))))))
678     (setcar (cdr entry) (concat (nth 1 entry) "+" group))
679     (nconc entry (cdr method))))
680
681 (defun gnus-server-status (method)
682   "Return the status of METHOD."
683   (nth 1 (assoc method gnus-opened-servers)))
684
685 (defun gnus-group-name-to-method (group)
686   "Return a select method suitable for GROUP."
687   (if (string-match ":" group)
688       (let ((server (substring group 0 (match-beginning 0))))
689         (if (string-match "\\+" server)
690             (list (intern (substring server 0 (match-beginning 0)))
691                   (substring server (match-end 0)))
692           (list (intern server) "")))
693     gnus-select-method))
694
695 (defun gnus-find-method-for-group (group &optional info)
696   "Find the select method that GROUP uses."
697   (or gnus-override-method
698       (and (not group)
699            gnus-select-method)
700       (let ((info (or info (gnus-get-info group)))
701             method)
702         (if (or (not info)
703                 (not (setq method (gnus-info-method info)))
704                 (equal method "native"))
705             gnus-select-method
706           (setq method
707                 (cond ((stringp method)
708                        (gnus-server-to-method method))
709                       ((stringp (car method))
710                        (gnus-server-extend-method group method))
711                       (t
712                        method)))
713           (cond ((equal (cadr method) "")
714                  method)
715                 ((null (cadr method))
716                  (list (car method) ""))
717                 (t
718                  (gnus-server-add-address method)))))))
719
720 (defun gnus-check-backend-function (func group)
721   "Check whether GROUP supports function FUNC."
722   (let ((method (if (stringp group) (car (gnus-find-method-for-group group))
723                   group)))
724     (fboundp (intern (format "%s-%s" method func)))))
725
726 (defun gnus-methods-using (feature)
727   "Find all methods that have FEATURE."
728   (let ((valids gnus-valid-select-methods)
729         outs)
730     (while valids
731       (if (memq feature (car valids))
732           (setq outs (cons (car valids) outs)))
733       (setq valids (cdr valids)))
734     outs))
735
736 ;;; User-level commands.
737
738 ;;;###autoload
739 (defun gnus-slave-no-server (&optional arg)
740   "Read network news as a slave, without connecting to local server"
741   (interactive "P")
742   (gnus-no-server arg t))
743
744 ;;;###autoload
745 (defun gnus-no-server (&optional arg slave)
746   "Read network news.
747 If ARG is a positive number, Gnus will use that as the
748 startup level.  If ARG is nil, Gnus will be started at level 2.
749 If ARG is non-nil and not a positive number, Gnus will
750 prompt the user for the name of an NNTP server to use.
751 As opposed to `gnus', this command will not connect to the local server."
752   (interactive "P")
753   (gnus-no-server-1 arg slave))
754
755 ;;;###autoload
756 (defun gnus-slave (&optional arg)
757   "Read news as a slave."
758   (interactive "P")
759   (gnus arg nil 'slave))
760
761 ;;;###autoload
762 (defun gnus-other-frame (&optional arg)
763   "Pop up a frame to read news."
764   (interactive "P")
765   (if (get-buffer gnus-group-buffer)
766       (let ((pop-up-frames t))
767         (gnus arg))
768     (select-frame (make-frame))
769     (gnus arg)))
770
771 (defun gnus (&optional arg dont-connect slave)
772   "Read network news.
773 If ARG is non-nil and a positive number, Gnus will use that as the
774 startup level.  If ARG is non-nil and not a positive number, Gnus will
775 prompt the user for the name of an NNTP server to use."
776   (interactive "P")
777   (gnus-1 arg dont-connect slave))
778
779 ;; Allow redefinition of Gnus functions.
780
781 (gnus-ems-redefine)
782
783 (provide 'gnus)
784
785 ;;; gnus.el ends here