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