changeset 17493:e6935c08cf0b

Initial revision
author Lars Magne Ingebrigtsen <larsi@gnus.org>
date Wed, 16 Apr 1997 22:13:18 +0000
parents 59dc7e0fc186
children e0a2371347db
files lisp/gnus/=md5.el lisp/gnus/=nnheaderxm.el lisp/gnus/earcon.el lisp/gnus/gnus-art.el lisp/gnus/gnus-async.el lisp/gnus/gnus-audio.el lisp/gnus/gnus-bcklg.el lisp/gnus/gnus-cache.el lisp/gnus/gnus-cite.el lisp/gnus/gnus-cus.el lisp/gnus/gnus-demon.el lisp/gnus/gnus-dup.el lisp/gnus/gnus-eform.el lisp/gnus/gnus-ems.el lisp/gnus/gnus-gl.el lisp/gnus/gnus-group.el lisp/gnus/gnus-int.el lisp/gnus/gnus-kill.el lisp/gnus/gnus-load.el lisp/gnus/gnus-logic.el lisp/gnus/gnus-mh.el lisp/gnus/gnus-move.el lisp/gnus/gnus-msg.el lisp/gnus/gnus-nocem.el lisp/gnus/gnus-range.el lisp/gnus/gnus-salt.el lisp/gnus/gnus-score.el lisp/gnus/gnus-setup.el lisp/gnus/gnus-soup.el lisp/gnus/gnus-spec.el lisp/gnus/gnus-srvr.el lisp/gnus/gnus-start.el lisp/gnus/gnus-sum.el lisp/gnus/gnus-topic.el lisp/gnus/gnus-undo.el lisp/gnus/gnus-util.el lisp/gnus/gnus-uu.el lisp/gnus/gnus-vm.el lisp/gnus/gnus-win.el lisp/gnus/gnus.el lisp/gnus/message.el lisp/gnus/messcompat.el lisp/gnus/nnbabyl.el lisp/gnus/nndir.el lisp/gnus/nndoc.el lisp/gnus/nndraft.el lisp/gnus/nneething.el lisp/gnus/nnfolder.el lisp/gnus/nngateway.el lisp/gnus/nnheader.el lisp/gnus/nnkiboze.el lisp/gnus/nnmail.el lisp/gnus/nnmbox.el lisp/gnus/nnmh.el lisp/gnus/nnml.el lisp/gnus/nnoo.el lisp/gnus/nnsoup.el lisp/gnus/nnspool.el lisp/gnus/nntp.el lisp/gnus/nnvirtual.el lisp/gnus/nnweb.el lisp/gnus/parse-time.el lisp/gnus/pop3.el lisp/gnus/score-mode.el
diffstat 64 files changed, 55945 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/=md5.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,409 @@
+;;; md5.el -- MD5 Message Digest Algorithm
+;;; Gareth Rees <gdr11@cl.cam.ac.uk>
+
+;; LCD Archive Entry:
+;; md5|Gareth Rees|gdr11@cl.cam.ac.uk|
+;; MD5 cryptographic message digest algorithm|
+;; 13-Nov-95|1.0|~/misc/md5.el.Z|
+
+;;; Details: ------------------------------------------------------------------
+
+;; This is a direct translation into Emacs LISP of the reference C
+;; implementation of the MD5 Message-Digest Algorithm written by RSA
+;; Data Security, Inc.
+;; 
+;; The algorithm takes a message (that is, a string of bytes) and
+;; computes a 16-byte checksum or "digest" for the message.  This digest
+;; is supposed to be cryptographically strong in the sense that if you
+;; are given a 16-byte digest D, then there is no easier way to
+;; construct a message whose digest is D than to exhaustively search the
+;; space of messages.  However, the robustness of the algorithm has not
+;; been proven, and a similar algorithm (MD4) was shown to be unsound,
+;; so treat with caution!
+;; 
+;; The C algorithm uses 32-bit integers; because GNU Emacs
+;; implementations provide 28-bit integers (with 24-bit integers on
+;; versions prior to 19.29), the code represents a 32-bit integer as the
+;; cons of two 16-bit integers.  The most significant word is stored in
+;; the car and the least significant in the cdr.  The algorithm requires
+;; at least 17 bits of integer representation in order to represent the
+;; carry from a 16-bit addition.
+
+;;; Usage: --------------------------------------------------------------------
+
+;; To compute the MD5 Message Digest for a message M (represented as a
+;; string or as a vector of bytes), call
+;; 
+;;   (md5-encode M)
+;; 
+;; which returns the message digest as a vector of 16 bytes.  If you
+;; need to supply the message in pieces M1, M2, ... Mn, then call
+;; 
+;;   (md5-init)
+;;   (md5-update M1)
+;;   (md5-update M2)
+;;   ...
+;;   (md5-update Mn)
+;;   (md5-final)
+
+;;; Copyright and licence: ----------------------------------------------------
+
+;; Copyright (C) 1995 by Gareth Rees
+;; Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
+;; 
+;; md5.el is free software; you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published by the
+;; Free Software Foundation; either version 2, or (at your option) any
+;; later version.
+;; 
+;; md5.el is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+;; FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+;; for more details.
+;; 
+;; The original copyright notice is given below, as required by the
+;; licence for the original code.  This code is distributed under *both*
+;; RSA's original licence and the GNU General Public Licence.  (There
+;; should be no problems, as the former is more liberal than the
+;; latter).
+
+;;; Original copyright notice: ------------------------------------------------
+
+;; Copyright (C) 1990, RSA Data Security, Inc. All rights reserved.
+;;
+;; License to copy and use this software is granted provided that it is
+;; identified as the "RSA Data Security, Inc. MD5 Message- Digest
+;; Algorithm" in all material mentioning or referencing this software or
+;; this function.
+;;
+;; License is also granted to make and use derivative works provided
+;; that such works are identified as "derived from the RSA Data
+;; Security, Inc. MD5 Message-Digest Algorithm" in all material
+;; mentioning or referencing the derived work.
+;;
+;; RSA Data Security, Inc. makes no representations concerning either
+;; the merchantability of this software or the suitability of this
+;; software for any particular purpose.  It is provided "as is" without
+;; express or implied warranty of any kind.
+;;
+;; These notices must be retained in any copies of any part of this
+;; documentation and/or software.
+
+;;; Code: ---------------------------------------------------------------------
+
+(defvar md5-program "md5"
+  "*Program that reads a message on its standard input and writes an
+MD5 digest on its output.")
+
+(defvar md5-maximum-internal-length 4096
+  "*The maximum size of a piece of data that should use the MD5 routines
+written in lisp.  If a message exceeds this, it will be run through an
+external filter for processing.  Also see the `md5-program' variable.
+This variable has no effect if you call the md5-init|update|final
+functions - only used by the `md5' function's simpler interface.")
+
+(defvar md5-bits (make-vector 4 0)
+  "Number of bits handled, modulo 2^64.
+Represented as four 16-bit numbers, least significant first.")
+(defvar md5-buffer (make-vector 4 '(0 . 0))
+  "Scratch buffer (four 32-bit integers).")
+(defvar md5-input (make-vector 64 0)
+  "Input buffer (64 bytes).")
+
+(defun md5-unhex (x)
+  (if (> x ?9)
+      (if (>= x ?a)
+	  (+ 10 (- x ?a))
+	(+ 10 (- x ?A)))
+    (- x ?0)))
+
+(defun md5-encode (message)
+  "Encodes MESSAGE using the MD5 message digest algorithm.
+MESSAGE must be a string or an array of bytes.
+Returns a vector of 16 bytes containing the message digest."
+  (if (<= (length message) md5-maximum-internal-length)
+      (progn
+	(md5-init)
+	(md5-update message)
+	(md5-final))
+    (save-excursion
+      (set-buffer (get-buffer-create " *md5-work*"))
+      (erase-buffer)
+      (insert message)
+      (call-process-region (point-min) (point-max)
+			   (or shell-file-name "/bin/sh")
+			   t (current-buffer) nil
+			   "-c" md5-program)
+      ;; MD5 digest is 32 chars long
+      ;; mddriver adds a newline to make neaten output for tty
+      ;; viewing, make sure we leave it behind.
+      (let ((data (buffer-substring (point-min) (+ (point-min) 32)))
+	    (vec (make-vector 16 0))
+	    (ctr 0))
+	(while (< ctr 16)
+	  (aset vec ctr (+ (* 16 (md5-unhex (aref data (* ctr 2))))
+			   (md5-unhex (aref data (1+ (* ctr 2))))))
+	  (setq ctr (1+ ctr)))))))
+
+(defsubst md5-add (x y)
+  "Return 32-bit sum of 32-bit integers X and Y."
+  (let ((m (+ (car x) (car y)))
+        (l (+ (cdr x) (cdr y))))
+    (cons (logand 65535 (+ m (lsh l -16))) (logand l 65535))))
+
+;; FF, GG, HH and II are basic MD5 functions, providing transformations
+;; for rounds 1, 2, 3 and 4 respectively.  Each function follows this
+;; pattern of computation (where ROTATE(x,y) means rotate 32-bit value x
+;; by y bits to the left):
+;; 
+;;   FF(a,b,c,d,x,s,ac) = ROTATE(a + F(b,c,d) + x + ac,s) + b
+;; 
+;; so we use the macro `md5-make-step' to construct each one.  The
+;; helper functions F, G, H and I operate on 16-bit numbers; the full
+;; operation splits its inputs, operates on the halves separately and
+;; then puts the results together.
+
+(defsubst md5-F (x y z) (logior (logand x y) (logand (lognot x) z)))
+(defsubst md5-G (x y z) (logior (logand x z) (logand y (lognot z))))
+(defsubst md5-H (x y z) (logxor x y z))
+(defsubst md5-I (x y z) (logxor y (logior x (logand 65535 (lognot z)))))
+
+(defmacro md5-make-step (name func)
+  (`
+   (defun (, name) (a b c d x s ac)
+     (let*
+         ((m1 (+ (car a) ((, func) (car b) (car c) (car d)) (car x) (car ac)))
+          (l1 (+ (cdr a) ((, func) (cdr b) (cdr c) (cdr d)) (cdr x) (cdr ac)))
+          (m2 (logand 65535 (+ m1 (lsh l1 -16))))
+          (l2 (logand 65535 l1))
+          (m3 (logand 65535 (if (> s 15)
+                                (+ (lsh m2 (- s 32)) (lsh l2 (- s 16)))
+                              (+ (lsh m2 s) (lsh l2 (- s 16))))))
+          (l3 (logand 65535 (if (> s 15)
+                                (+ (lsh l2 (- s 32)) (lsh m2 (- s 16)))
+                              (+ (lsh l2 s) (lsh m2 (- s 16)))))))
+       (md5-add (cons m3 l3) b)))))
+
+(md5-make-step md5-FF md5-F)
+(md5-make-step md5-GG md5-G)
+(md5-make-step md5-HH md5-H)
+(md5-make-step md5-II md5-I)
+
+(defun md5-init ()
+  "Initialise the state of the message-digest routines."
+  (aset md5-bits 0 0)
+  (aset md5-bits 1 0)
+  (aset md5-bits 2 0)
+  (aset md5-bits 3 0)
+  (aset md5-buffer 0 '(26437 .  8961))
+  (aset md5-buffer 1 '(61389 . 43913))
+  (aset md5-buffer 2 '(39098 . 56574))
+  (aset md5-buffer 3 '( 4146 . 21622)))
+
+(defun md5-update (string)
+  "Update the current MD5 state with STRING (an array of bytes)."
+  (let ((len (length string))
+        (i 0)
+        (j 0))
+    (while (< i len)
+      ;; Compute number of bytes modulo 64
+      (setq j (% (/ (aref md5-bits 0) 8) 64))
+
+      ;; Store this byte (truncating to 8 bits to be sure)
+      (aset md5-input j (logand 255 (aref string i)))
+
+      ;; Update number of bits by 8 (modulo 2^64)
+      (let ((c 8) (k 0))
+        (while (and (> c 0) (< k 4))
+          (let ((b (aref md5-bits k)))
+            (aset md5-bits k (logand 65535 (+ b c)))
+            (setq c (if (> b (- 65535 c)) 1 0)
+                  k (1+ k)))))
+
+      ;; Increment number of bytes processed
+      (setq i (1+ i))
+
+      ;; When 64 bytes accumulated, pack them into sixteen 32-bit
+      ;; integers in the array `in' and then tranform them.
+      (if (= j 63)
+          (let ((in (make-vector 16 (cons 0 0)))
+                (k 0)
+                (kk 0))
+            (while (< k 16)
+              (aset in k (md5-pack md5-input kk))
+              (setq k (+ k 1) kk (+ kk 4)))
+            (md5-transform in))))))
+
+(defun md5-pack (array i)
+  "Pack the four bytes at ARRAY reference I to I+3 into a 32-bit integer."
+  (cons (+ (lsh (aref array (+ i 3)) 8) (aref array (+ i 2)))
+        (+ (lsh (aref array (+ i 1)) 8) (aref array (+ i 0)))))
+
+(defun md5-byte (array n b)
+  "Unpack byte B (0 to 3) from Nth member of ARRAY of 32-bit integers."
+  (let ((e (aref array n)))
+    (cond ((eq b 0) (logand 255 (cdr e)))
+          ((eq b 1) (lsh (cdr e) -8))
+          ((eq b 2) (logand 255 (car e)))
+          ((eq b 3) (lsh (car e) -8)))))
+
+(defun md5-final ()
+  (let ((in (make-vector 16 (cons 0 0)))
+        (j 0)
+        (digest (make-vector 16 0))
+        (padding))
+
+    ;; Save the number of bits in the message
+    (aset in 14 (cons (aref md5-bits 1) (aref md5-bits 0)))
+    (aset in 15 (cons (aref md5-bits 3) (aref md5-bits 2)))
+
+    ;; Compute number of bytes modulo 64
+    (setq j (% (/ (aref md5-bits 0) 8) 64))
+
+    ;; Pad out computation to 56 bytes modulo 64
+    (setq padding (make-vector (if (< j 56) (- 56 j) (- 120 j)) 0))
+    (aset padding 0 128)
+    (md5-update padding)
+
+    ;; Append length in bits and transform
+    (let ((k 0) (kk 0))
+      (while (< k 14)
+        (aset in k (md5-pack md5-input kk))
+        (setq k (+ k 1) kk (+ kk 4))))
+    (md5-transform in)
+
+    ;; Store the results in the digest
+    (let ((k 0) (kk 0))
+      (while (< k 4)
+        (aset digest (+ kk 0) (md5-byte md5-buffer k 0))
+        (aset digest (+ kk 1) (md5-byte md5-buffer k 1))
+        (aset digest (+ kk 2) (md5-byte md5-buffer k 2))
+        (aset digest (+ kk 3) (md5-byte md5-buffer k 3))
+        (setq k (+ k 1) kk (+ kk 4))))
+
+    ;; Return digest
+    digest))
+
+;; It says in the RSA source, "Note that if the Mysterious Constants are
+;; arranged backwards in little-endian order and decrypted with the DES
+;; they produce OCCULT MESSAGES!"  Security through obscurity?
+
+(defun md5-transform (in)
+  "Basic MD5 step. Transform md5-buffer based on array IN."
+  (let ((a (aref md5-buffer 0))
+        (b (aref md5-buffer 1))
+        (c (aref md5-buffer 2))
+        (d (aref md5-buffer 3)))
+    (setq
+     a (md5-FF a b c d (aref in  0)  7 '(55146 . 42104))
+     d (md5-FF d a b c (aref in  1) 12 '(59591 . 46934))
+     c (md5-FF c d a b (aref in  2) 17 '( 9248 . 28891))
+     b (md5-FF b c d a (aref in  3) 22 '(49597 . 52974))
+     a (md5-FF a b c d (aref in  4)  7 '(62844 .  4015))
+     d (md5-FF d a b c (aref in  5) 12 '(18311 . 50730))
+     c (md5-FF c d a b (aref in  6) 17 '(43056 . 17939))
+     b (md5-FF b c d a (aref in  7) 22 '(64838 . 38145))
+     a (md5-FF a b c d (aref in  8)  7 '(27008 . 39128))
+     d (md5-FF d a b c (aref in  9) 12 '(35652 . 63407))
+     c (md5-FF c d a b (aref in 10) 17 '(65535 . 23473))
+     b (md5-FF b c d a (aref in 11) 22 '(35164 . 55230))
+     a (md5-FF a b c d (aref in 12)  7 '(27536 .  4386))
+     d (md5-FF d a b c (aref in 13) 12 '(64920 . 29075))
+     c (md5-FF c d a b (aref in 14) 17 '(42617 . 17294))
+     b (md5-FF b c d a (aref in 15) 22 '(18868 .  2081))
+     a (md5-GG a b c d (aref in  1)  5 '(63006 .  9570))
+     d (md5-GG d a b c (aref in  6)  9 '(49216 . 45888))
+     c (md5-GG c d a b (aref in 11) 14 '( 9822 . 23121))
+     b (md5-GG b c d a (aref in  0) 20 '(59830 . 51114))
+     a (md5-GG a b c d (aref in  5)  5 '(54831 .  4189))
+     d (md5-GG d a b c (aref in 10)  9 '(  580 .  5203))
+     c (md5-GG c d a b (aref in 15) 14 '(55457 . 59009))
+     b (md5-GG b c d a (aref in  4) 20 '(59347 . 64456))
+     a (md5-GG a b c d (aref in  9)  5 '( 8673 . 52710))
+     d (md5-GG d a b c (aref in 14)  9 '(49975 .  2006))
+     c (md5-GG c d a b (aref in  3) 14 '(62677 .  3463))
+     b (md5-GG b c d a (aref in  8) 20 '(17754 .  5357))
+     a (md5-GG a b c d (aref in 13)  5 '(43491 . 59653))
+     d (md5-GG d a b c (aref in  2)  9 '(64751 . 41976))
+     c (md5-GG c d a b (aref in  7) 14 '(26479 .   729))
+     b (md5-GG b c d a (aref in 12) 20 '(36138 . 19594))
+     a (md5-HH a b c d (aref in  5)  4 '(65530 . 14658))
+     d (md5-HH d a b c (aref in  8) 11 '(34673 . 63105))
+     c (md5-HH c d a b (aref in 11) 16 '(28061 . 24866))
+     b (md5-HH b c d a (aref in 14) 23 '(64997 . 14348))
+     a (md5-HH a b c d (aref in  1)  4 '(42174 . 59972))
+     d (md5-HH d a b c (aref in  4) 11 '(19422 . 53161))
+     c (md5-HH c d a b (aref in  7) 16 '(63163 . 19296))
+     b (md5-HH b c d a (aref in 10) 23 '(48831 . 48240))
+     a (md5-HH a b c d (aref in 13)  4 '(10395 . 32454))
+     d (md5-HH d a b c (aref in  0) 11 '(60065 . 10234))
+     c (md5-HH c d a b (aref in  3) 16 '(54511 . 12421))
+     b (md5-HH b c d a (aref in  6) 23 '( 1160 .  7429))
+     a (md5-HH a b c d (aref in  9)  4 '(55764 . 53305))
+     d (md5-HH d a b c (aref in 12) 11 '(59099 . 39397))
+     c (md5-HH c d a b (aref in 15) 16 '( 8098 . 31992))
+     b (md5-HH b c d a (aref in  2) 23 '(50348 . 22117))
+     a (md5-II a b c d (aref in  0)  6 '(62505 .  8772))
+     d (md5-II d a b c (aref in  7) 10 '(17194 . 65431))
+     c (md5-II c d a b (aref in 14) 15 '(43924 .  9127))
+     b (md5-II b c d a (aref in  5) 21 '(64659 . 41017))
+     a (md5-II a b c d (aref in 12)  6 '(25947 . 22979))
+     d (md5-II d a b c (aref in  3) 10 '(36620 . 52370))
+     c (md5-II c d a b (aref in 10) 15 '(65519 . 62589))
+     b (md5-II b c d a (aref in  1) 21 '(34180 . 24017))
+     a (md5-II a b c d (aref in  8)  6 '(28584 . 32335))
+     d (md5-II d a b c (aref in 15) 10 '(65068 . 59104))
+     c (md5-II c d a b (aref in  6) 15 '(41729 . 17172))
+     b (md5-II b c d a (aref in 13) 21 '(19976 .  4513))
+     a (md5-II a b c d (aref in  4)  6 '(63315 . 32386))
+     d (md5-II d a b c (aref in 11) 10 '(48442 . 62005))
+     c (md5-II c d a b (aref in  2) 15 '(10967 . 53947))
+     b (md5-II b c d a (aref in  9) 21 '(60294 . 54161)))
+
+     (aset md5-buffer 0 (md5-add (aref md5-buffer 0) a))
+     (aset md5-buffer 1 (md5-add (aref md5-buffer 1) b))
+     (aset md5-buffer 2 (md5-add (aref md5-buffer 2) c))
+     (aset md5-buffer 3 (md5-add (aref md5-buffer 3) d))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Here begins the merger with the XEmacs API and the md5.el from the URL
+;;; package.  Courtesy wmperry@spry.com
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun md5 (object &optional start end)
+  "Return the MD5 (a secure message digest algorithm) of an object.
+OBJECT is either a string or a buffer.
+Optional arguments START and END denote buffer positions for computing the
+hash of a portion of OBJECT."
+ (let ((buffer nil))
+    (unwind-protect
+	(save-excursion
+	  (setq buffer (generate-new-buffer " *md5-work*"))
+	  (set-buffer buffer)
+	  (cond
+	   ((bufferp object)
+	    (insert-buffer-substring object start end))
+	   ((stringp object)
+	    (insert (if (or start end)
+			(substring object start end)
+		      object)))
+	   (t nil))
+	  (prog1
+	      (if (<= (point-max) md5-maximum-internal-length)
+		  (mapconcat
+		   (function (lambda (node) (format "%02x" node)))
+		   (md5-encode (buffer-string))
+		   "")
+		(call-process-region (point-min) (point-max)
+				     (or shell-file-name "/bin/sh")
+				     t buffer nil
+				     "-c" md5-program)
+		;; MD5 digest is 32 chars long
+		;; mddriver adds a newline to make neaten output for tty
+		;; viewing, make sure we leave it behind.
+		(buffer-substring (point-min) (+ (point-min) 32)))
+	    (kill-buffer buffer)))
+      (and buffer (kill-buffer buffer) nil))))
+
+(provide 'md5)
+
+;;; md5.el ends here ----------------------------------------------------------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/=nnheaderxm.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,156 @@
+;;; nnheaderxm.el --- making Gnus backends work under XEmacs
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(eval-and-compile
+  (autoload 'nnheader-insert-file-contents "nnheader"))
+
+(defun nnheader-xmas-run-at-time (time repeat function &rest args)
+  (start-itimer
+   "nnheader-run-at-time"
+   `(lambda ()
+      (,function ,@args))
+   time repeat))
+
+(defun nnheader-xmas-cancel-timer (timer)
+  (delete-itimer timer))
+
+(defun nnheader-xmas-cancel-function-timers (function)
+  )
+
+(defun nnheader-xmas-find-file-noselect (filename &optional nowarn rawfile)
+  "Read file FILENAME into a buffer and return the buffer.
+If a buffer exists visiting FILENAME, return that one, but
+verify that the file has not changed since visited or saved.
+The buffer is not selected, just returned to the caller."
+  (setq filename
+	(abbreviate-file-name
+	 (expand-file-name filename)))
+  (if (file-directory-p filename)
+      (if find-file-run-dired
+	  (dired-noselect filename)
+	(error "%s is a directory." filename))
+    (let* ((buf (get-file-buffer filename))
+	   (truename (abbreviate-file-name (file-truename filename)))
+	   (number (nthcdr 10 (file-attributes truename)))
+	   ;; Find any buffer for a file which has same truename.
+	   (other (and (not buf)
+		       (get-file-buffer filename)))
+	   error)
+      ;; Let user know if there is a buffer with the same truename.
+      (when other
+	(or nowarn
+	    (string-equal filename (buffer-file-name other))
+	    (message "%s and %s are the same file"
+		     filename (buffer-file-name other)))
+	;; Optionally also find that buffer.
+	(when (or (and (boundp 'find-file-existing-other-name)
+		       find-file-existing-other-name)
+		  find-file-visit-truename)
+	  (setq buf other)))
+      (if buf
+	  (or nowarn
+	      (verify-visited-file-modtime buf)
+	      (cond ((not (file-exists-p filename))
+		     (error "File %s no longer exists!" filename))
+		    ((yes-or-no-p
+		      (if (string= (file-name-nondirectory filename)
+				   (buffer-name buf))
+			  (format
+			   (if (buffer-modified-p buf)
+			       "File %s changed on disk.  Discard your edits? "
+			     "File %s changed on disk.  Reread from disk? ")
+			   (file-name-nondirectory filename))
+			(format
+			 (if (buffer-modified-p buf)
+			     "File %s changed on disk.  Discard your edits in %s? "
+			   "File %s changed on disk.  Reread from disk into %s? ")
+			 (file-name-nondirectory filename)
+			 (buffer-name buf))))
+		     (save-excursion
+		       (set-buffer buf)
+		       (revert-buffer t t)))))
+	(save-excursion
+;;; The truename stuff makes this obsolete.
+;;;	  (let* ((link-name (car (file-attributes filename)))
+;;;		 (linked-buf (and (stringp link-name)
+;;;				  (get-file-buffer link-name))))
+;;;	    (if (bufferp linked-buf)
+;;;		(message "Symbolic link to file in buffer %s"
+;;;			 (buffer-name linked-buf))))
+	  (setq buf (create-file-buffer filename))
+	  ;;	  (set-buffer-major-mode buf)
+	  (set-buffer buf)
+	  (erase-buffer)
+	  (if rawfile
+	      (condition-case ()
+		  (nnheader-insert-file-contents filename t)
+		(file-error
+		 ;; Unconditionally set error
+		 (setq error t)))
+	    (condition-case ()
+		(insert-file-contents filename t)
+	      (file-error
+	       ;; Run find-file-not-found-hooks until one returns non-nil.
+	       (or t			; (run-hook-with-args-until-success 'find-file-not-found-hooks)
+		   ;; If they fail too, set error.
+		   (setq error t)))))
+	  ;; Find the file's truename, and maybe use that as visited name.
+	  (setq buffer-file-truename truename)
+	  (setq buffer-file-number number)
+	  ;; On VMS, we may want to remember which directory in a search list
+	  ;; the file was found in.
+	  (and (eq system-type 'vax-vms)
+	       (let (logical)
+		 (when (string-match ":" (file-name-directory filename))
+		   (setq logical (substring (file-name-directory filename)
+					    0 (match-beginning 0))))
+		 (not (member logical find-file-not-true-dirname-list)))
+	       (setq buffer-file-name buffer-file-truename))
+	  (when find-file-visit-truename
+	    (setq buffer-file-name
+		  (setq filename
+			(expand-file-name buffer-file-truename))))
+	  ;; Set buffer's default directory to that of the file.
+	  (setq default-directory (file-name-directory filename))
+	  ;; Turn off backup files for certain file names.  Since
+	  ;; this is a permanent local, the major mode won't eliminate it.
+	  (when (not (funcall backup-enable-predicate buffer-file-name))
+	    (make-local-variable 'backup-inhibited)
+	    (setq backup-inhibited t))
+	  (if rawfile
+	      nil
+	    (after-find-file error (not nowarn)))))
+      buf)))
+
+(fset 'nnheader-run-at-time 'nnheader-xmas-run-at-time)
+(fset 'nnheader-cancel-timer 'nnheader-xmas-cancel-timer)
+(fset 'nnheader-cancel-function-timers 'nnheader-xmas-cancel-function-timers)
+(fset 'nnheader-find-file-noselect 'nnheader-xmas-find-file-noselect)
+
+(provide 'nnheaderxm)
+
+;;; nnheaderxm.el ends here.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/earcon.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,245 @@
+;;; earcon.el --- Sound effects for messages
+;; Copyright (C) 1996 Free Software Foundation
+
+;; Author: Steven L. Baur <steve@miranova.com>
+;; Keywords: news fun sound
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+;; This file provides access to sound effects in Gnus.
+
+;;; Code:
+
+(if (null (boundp 'running-xemacs))
+    (defvar running-xemacs (string-match "XEmacs\\|Lucid" emacs-version)))
+
+(require 'gnus)
+(require 'gnus-audio)
+(require 'gnus-art)
+(eval-when-compile (require 'cl))
+
+(defgroup earcon nil
+  "Turn ** sounds ** into noise."
+  :group 'gnus-visual)
+
+(defcustom earcon-auto-play nil
+  "When True, automatically play sounds as well as buttonize them."
+  :type 'boolean
+  :group 'earcon)
+
+(defcustom earcon-prefix "**"
+  "String denoting the start of an earcon."
+  :type 'string
+  :group 'earcon)
+
+(defcustom earcon-suffix "**"
+  "String denoting the end of an earcon."
+  :type 'string
+  :group 'earcon)
+
+(defcustom earcon-regexp-alist
+  '(("boring" 1 "Boring.au")
+    ("evil[ \t]+laugh" 1 "Evil_Laugh.au")
+    ("gag\\|puke" 1 "Puke.au")
+    ("snicker" 1 "Snicker.au")
+    ("meow" 1 "catmeow.au")
+    ("sob\\|boohoo" 1 "cry.wav")
+    ("drum[ \t]*roll" 1 "drumroll.au")
+    ("blast" 1 "explosion.au")
+    ("flush\\|plonk!*" 1 "flush.au")
+    ("kiss" 1 "kiss.wav")
+    ("tee[ \t]*hee" 1 "laugh.au")
+    ("shoot" 1 "shotgun.wav")
+    ("yawn" 1 "snore.wav")
+    ("cackle" 1 "witch.au")
+    ("yell\\|roar" 1 "yell2.au")
+    ("whoop-de-doo" 1 "whistle.au"))
+  "A list of regexps to map earcons to real sounds."
+  :type '(repeat (list regexp
+		       (integer :tag "Match")
+		       (string :tag "Sound")))
+  :group 'earcon)
+
+(defvar earcon-button-marker-list nil)
+(make-variable-buffer-local 'earcon-button-marker-list)
+
+
+
+;;; FIXME!! clone of code from gnus-vis.el FIXME!!
+(defun earcon-article-push-button (event)
+  "Check text under the mouse pointer for a callback function.
+If the text under the mouse pointer has a `earcon-callback' property,
+call it with the value of the `earcon-data' text property."
+  (interactive "e")
+  (set-buffer (window-buffer (posn-window (event-start event))))
+  (let* ((pos (posn-point (event-start event)))
+         (data (get-text-property pos 'earcon-data))
+	 (fun (get-text-property pos 'earcon-callback)))
+    (if fun (funcall fun data))))
+
+(defun earcon-article-press-button ()
+  "Check text at point for a callback function.
+If the text at point has a `earcon-callback' property,
+call it with the value of the `earcon-data' text property."
+  (interactive)
+  (let* ((data (get-text-property (point) 'earcon-data))
+	 (fun (get-text-property (point) 'earcon-callback)))
+    (if fun (funcall fun data))))
+
+(defun earcon-article-prev-button (n)
+  "Move point to N buttons backward.
+If N is negative, move forward instead."
+  (interactive "p")
+  (earcon-article-next-button (- n)))
+
+(defun earcon-article-next-button (n)
+  "Move point to N buttons forward.
+If N is negative, move backward instead."
+  (interactive "p")
+  (let ((function (if (< n 0) 'previous-single-property-change
+		    'next-single-property-change))
+	(inhibit-point-motion-hooks t)
+	(backward (< n 0))
+	(limit (if (< n 0) (point-min) (point-max))))
+    (setq n (abs n))
+    (while (and (not (= limit (point)))
+		(> n 0))
+      ;; Skip past the current button.
+      (when (get-text-property (point) 'earcon-callback)
+	(goto-char (funcall function (point) 'earcon-callback nil limit)))
+      ;; Go to the next (or previous) button.
+      (gnus-goto-char (funcall function (point) 'earcon-callback nil limit))
+      ;; Put point at the start of the button.
+      (when (and backward (not (get-text-property (point) 'earcon-callback)))
+	(goto-char (funcall function (point) 'earcon-callback nil limit)))
+      ;; Skip past intangible buttons.
+      (when (get-text-property (point) 'intangible)
+	(incf n))
+      (decf n))
+    (unless (zerop n)
+      (gnus-message 5 "No more buttons"))
+    n))
+
+(defun earcon-article-add-button (from to fun &optional data)
+  "Create a button between FROM and TO with callback FUN and data DATA."
+  (and (boundp gnus-article-button-face)
+       gnus-article-button-face
+       (gnus-overlay-put (gnus-make-overlay from to)
+			 'face gnus-article-button-face))
+  (gnus-add-text-properties
+   from to
+   (nconc (and gnus-article-mouse-face
+	       (list gnus-mouse-face-prop gnus-article-mouse-face))
+	  (list 'gnus-callback fun)
+	  (and data (list 'gnus-data data)))))
+
+(defun earcon-button-entry ()
+  ;; Return the first entry in `gnus-button-alist' matching this place.
+  (let ((alist earcon-regexp-alist)
+	(case-fold-search t)
+	(entry nil))
+    (while alist
+      (setq entry (pop alist))
+      (if (looking-at (car entry))
+	  (setq alist nil)
+	(setq entry nil)))
+    entry))
+
+
+(defun earcon-button-push (marker)
+  ;; Push button starting at MARKER.
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (goto-char marker)
+    (let* ((entry (earcon-button-entry))
+	   (inhibit-point-motion-hooks t)
+	   (fun 'gnus-audio-play)
+	   (args (list (nth 2 entry))))
+      (cond
+       ((fboundp fun)
+	(apply fun args))
+       ((and (boundp fun)
+	     (fboundp (symbol-value fun)))
+	(apply (symbol-value fun) args))
+       (t
+	(gnus-message 1 "You must define `%S' to use this button"
+		      (cons fun args)))))))
+
+;;; FIXME!! clone of code from gnus-vis.el FIXME!!
+
+;;;###interactive
+(defun earcon-region (beg end)
+  "Play Sounds in the region between point and mark."
+  (interactive "r")
+  (earcon-buffer (current-buffer) beg end))
+
+;;;###interactive
+(defun earcon-buffer (&optional buffer st nd)
+  (interactive)
+  (save-excursion
+    ;; clear old markers.
+    (if (boundp 'earcon-button-marker-list)
+	(while earcon-button-marker-list
+	  (set-marker (pop earcon-button-marker-list) nil))
+      (setq earcon-button-marker-list nil))
+    (and buffer (set-buffer buffer))
+    (let ((buffer-read-only nil)
+	  (inhibit-point-motion-hooks t)
+	  (case-fold-search t)
+	  (alist earcon-regexp-alist)
+	  beg entry regexp)
+      (goto-char (point-min))
+      (setq beg (point))
+      (while (setq entry (pop alist))
+	(setq regexp (concat (regexp-quote earcon-prefix)
+			     ".*\\("
+			     (car entry)
+			     "\\).*"
+			     (regexp-quote earcon-suffix)))
+	(goto-char beg)
+	(while (re-search-forward regexp nil t)
+	  (let* ((start (and entry (match-beginning 1)))
+		 (end (and entry (match-end 1)))
+		 (from (match-beginning 1)))
+	    (earcon-article-add-button
+	     start end 'earcon-button-push
+	     (car (push (set-marker (make-marker) from)
+			earcon-button-marker-list)))
+	    (gnus-audio-play (caddr entry))))))))
+
+;;;###autoload
+(defun gnus-earcon-display ()
+  "Play sounds in message buffers."
+  (interactive)
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (goto-char (point-min))
+    ;; Skip headers
+    (unless (search-forward "\n\n" nil t)
+      (goto-char (point-max)))
+    (sit-for 0)
+    (earcon-buffer (current-buffer) (point))))
+
+;;;***
+
+(provide 'earcon)
+
+(run-hooks 'earcon-load-hook)
+
+;;; earcon.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-art.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,3082 @@
+;;; gnus-art.el --- article mode commands for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'custom)
+(require 'gnus)
+(require 'gnus-sum)
+(require 'gnus-spec)
+(require 'gnus-int)
+(require 'browse-url)
+
+(defgroup gnus-article nil
+  "Article display."
+  :link '(custom-manual "(gnus)The Article Buffer")
+  :group 'gnus)
+
+(defgroup gnus-article-hiding nil
+  "Hiding article parts."
+  :link '(custom-manual "(gnus)Article Hiding")
+  :group 'gnus-article)
+
+(defgroup gnus-article-highlight nil
+  "Article highlighting."
+  :link '(custom-manual "(gnus)Article Highlighting")
+  :group 'gnus-article
+  :group 'gnus-visual)
+
+(defgroup gnus-article-signature nil
+  "Article signatures."
+  :link '(custom-manual "(gnus)Article Signature")
+  :group 'gnus-article)
+
+(defgroup gnus-article-headers nil
+  "Article headers."
+  :link '(custom-manual "(gnus)Hiding Headers")
+  :group 'gnus-article)
+
+(defgroup gnus-article-washing nil
+  "Special commands on articles."
+  :link '(custom-manual "(gnus)Article Washing")
+  :group 'gnus-article)
+
+(defgroup gnus-article-emphasis nil
+  "Fontisizing articles."
+  :link '(custom-manual "(gnus)Article Fontisizing")
+  :group 'gnus-article)
+
+(defgroup gnus-article-saving nil
+  "Saving articles."
+  :link '(custom-manual "(gnus)Saving Articles")
+  :group 'gnus-article)
+
+(defgroup gnus-article-mime nil
+  "Worshiping the MIME wonder."
+  :link '(custom-manual "(gnus)Using MIME")
+  :group 'gnus-article)
+
+(defgroup gnus-article-buttons nil
+  "Pushable buttons in the article buffer."
+  :link '(custom-manual "(gnus)Article Buttons")
+  :group 'gnus-article)
+
+(defgroup gnus-article-various nil
+  "Other article options."
+  :link '(custom-manual "(gnus)Misc Article")
+  :group 'gnus-article)
+
+(defcustom gnus-ignored-headers
+  '("^Path:" "^Posting-Version:" "^Article-I.D.:" "^Expires:"
+    "^Date-Received:" "^References:" "^Control:" "^Xref:" "^Lines:"
+    "^Posted:" "^Relay-Version:" "^Message-ID:" "^Nf-ID:" "^Nf-From:"
+    "^Approved:" "^Sender:" "^Received:" "^Mail-from:")
+  "All headers that match this regexp will be hidden.
+This variable can also be a list of regexps of headers to be ignored.
+If `gnus-visible-headers' is non-nil, this variable will be ignored."
+  :type '(choice :custom-show nil
+		 regexp
+		 (repeat regexp))
+  :group 'gnus-article-hiding)
+
+(defcustom gnus-visible-headers
+  "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-From"
+  "All headers that do not match this regexp will be hidden.
+This variable can also be a list of regexp of headers to remain visible.
+If this variable is non-nil, `gnus-ignored-headers' will be ignored."
+  :type '(repeat :value-to-internal (lambda (widget value)
+				      (custom-split-regexp-maybe value))
+		 :match (lambda (widget value)
+			  (or (stringp value)
+			      (widget-editable-list-match widget value)))
+		 regexp)
+  :group 'gnus-article-hiding)
+
+(defcustom gnus-sorted-header-list
+  '("^From:" "^Subject:" "^Summary:" "^Keywords:" "^Newsgroups:"
+    "^Followup-To:" "^To:" "^Cc:" "^Date:" "^Organization:")
+  "This variable is a list of regular expressions.
+If it is non-nil, headers that match the regular expressions will
+be placed first in the article buffer in the sequence specified by
+this list."
+  :type '(repeat regexp)
+  :group 'gnus-article-hiding)
+
+(defcustom gnus-boring-article-headers '(empty followup-to reply-to)
+  "Headers that are only to be displayed if they have interesting data.
+Possible values in this list are `empty', `newsgroups', `followup-to',
+`reply-to', and `date'."
+  :type '(set (const :tag "Headers with no content." empty)
+	      (const :tag "Newsgroups with only one group." newsgroups)
+	      (const :tag "Followup-to identical to newsgroups." followup-to)
+	      (const :tag "Reply-to identical to from." reply-to)
+	      (const :tag "Date less than four days old." date))
+  :group 'gnus-article-hiding)
+
+(defcustom gnus-signature-separator '("^-- $" "^-- *$")
+  "Regexp matching signature separator.
+This can also be a list of regexps.  In that case, it will be checked
+from head to tail looking for a separator.  Searches will be done from
+the end of the buffer."
+  :type '(repeat string)
+  :group 'gnus-article-signature)
+
+(defcustom gnus-signature-limit nil
+   "Provide a limit to what is considered a signature.
+If it is a number, no signature may not be longer (in characters) than
+that number.  If it is a floating point number, no signature may be
+longer (in lines) than that number.  If it is a function, the function
+will be called without any parameters, and if it returns nil, there is
+no signature in the buffer.  If it is a string, it will be used as a
+regexp.  If it matches, the text in question is not a signature."
+  :type '(choice integer number function regexp)
+  :group 'gnus-article-signature)
+
+(defcustom gnus-hidden-properties '(invisible t intangible t)
+  "Property list to use for hiding text."
+  :type 'sexp
+  :group 'gnus-article-hiding)
+
+(defcustom gnus-article-x-face-command
+  "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | xv -quit -"
+  "String or function to be executed to display an X-Face header.
+If it is a string, the command will be executed in a sub-shell
+asynchronously.	 The compressed face will be piped to this command."
+  :type 'string				;Leave function case to Lisp.
+  :group 'gnus-article-washing)
+
+(defcustom gnus-article-x-face-too-ugly nil
+  "Regexp matching posters whose face shouldn't be shown automatically."
+  :type 'regexp
+  :group 'gnus-article-washing)
+
+(defcustom gnus-emphasis-alist
+  (let ((format
+	 "\\(\\s-\\|^\\|[-\"]\\|\\s(\\|\\s)\\)\\(%s\\(\\w+\\(\\s-+\\w+\\)*[.,]?\\)%s\\)\\(\\s-\\|[-?!.,;:\"]\\|\\s(\\|\\s)\\)")
+	(types
+	 '(("_" "_" underline)
+	   ("/" "/" italic)
+	   ("\\*" "\\*" bold)
+	   ("_/" "/_" underline-italic)
+	   ("_\\*" "\\*_" underline-bold)
+	   ("\\*/" "/\\*" bold-italic)
+	   ("_\\*/" "/\\*_" underline-bold-italic))))
+    `(("\\(\\s-\\|^\\)\\(_\\(\\(\\w\\|_[^_]\\)+\\)_\\)\\(\\s-\\|[?!.,;]\\)"
+       2 3 gnus-emphasis-underline)
+      ,@(mapcar
+	 (lambda (spec)
+	   (list
+	    (format format (car spec) (cadr spec))
+	    2 3 (intern (format "gnus-emphasis-%s" (caddr spec)))))
+	 types)))
+  "Alist that says how to fontify certain phrases.
+Each item looks like this:
+
+  (\"_\\\\(\\\\w+\\\\)_\" 0 1 'underline)
+
+The first element is a regular expression to be matched.  The second
+is a number that says what regular expression grouping used to find
+the entire emphasized word.  The third is a number that says what
+regexp grouping should be displayed and highlighted.  The fourth
+is the face used for highlighting."
+  :type '(repeat (list :value ("" 0 0 default)
+		       regexp
+		       (integer :tag "Match group")
+		       (integer :tag "Emphasize group")
+		       face))
+  :group 'gnus-article-emphasis)
+
+(defface gnus-emphasis-bold '((t (:bold t)))
+  "Face used for displaying strong emphasized text (*word*)."
+  :group 'gnus-article-emphasis)
+
+(defface gnus-emphasis-italic '((t (:italic t)))
+  "Face used for displaying italic emphasized text (/word/)."
+  :group 'gnus-article-emphasis)
+
+(defface gnus-emphasis-underline '((t (:underline t)))
+  "Face used for displaying underlined emphasized text (_word_)."
+  :group 'gnus-article-emphasis)
+
+(defface gnus-emphasis-underline-bold '((t (:bold t :underline t)))
+  "Face used for displaying underlined bold emphasized text (_*word*_)."
+  :group 'gnus-article-emphasis)
+
+(defface gnus-emphasis-underline-italic '((t (:italic t :underline t)))
+  "Face used for displaying underlined italic emphasized text (_*word*_)."
+  :group 'gnus-article-emphasis)
+
+(defface gnus-emphasis-bold-italic '((t (:bold t :italic t)))
+  "Face used for displaying bold italic emphasized text (/*word*/)."
+  :group 'gnus-article-emphasis)
+
+(defface gnus-emphasis-underline-bold-italic
+  '((t (:bold t :italic t :underline t)))
+  "Face used for displaying underlined bold italic emphasized text.
+Esample: (_/*word*/_)."
+  :group 'gnus-article-emphasis)
+
+(defcustom gnus-article-time-format "%a, %b %d %Y %T %Z"
+  "Format for display of Date headers in article bodies.
+See `format-time-zone' for the possible values."
+  :type 'string
+  :link '(custom-manual "(gnus)Article Date")
+  :group 'gnus-article-washing)
+
+(eval-and-compile
+  (autoload 'hexl-hex-string-to-integer "hexl")
+  (autoload 'timezone-make-date-arpa-standard "timezone")
+  (autoload 'mail-extract-address-components "mail-extr"))
+
+(defcustom gnus-save-all-headers t
+  "*If non-nil, don't remove any headers before saving."
+  :group 'gnus-article-saving
+  :type 'boolean)
+
+(defcustom gnus-prompt-before-saving 'always
+  "*This variable says how much prompting is to be done when saving articles.
+If it is nil, no prompting will be done, and the articles will be
+saved to the default files.  If this variable is `always', each and
+every article that is saved will be preceded by a prompt, even when
+saving large batches of articles.  If this variable is neither nil not
+`always', there the user will be prompted once for a file name for
+each invocation of the saving commands."
+  :group 'gnus-article-saving
+  :type '(choice (item always)
+		 (item :tag "never" nil)
+		 (sexp :tag "once" :format "%t")))
+
+(defcustom gnus-saved-headers gnus-visible-headers
+  "Headers to keep if `gnus-save-all-headers' is nil.
+If `gnus-save-all-headers' is non-nil, this variable will be ignored.
+If that variable is nil, however, all headers that match this regexp
+will be kept while the rest will be deleted before saving."
+  :group 'gnus-article-saving
+  :type '(repeat string))
+
+(defcustom gnus-default-article-saver 'gnus-summary-save-in-rmail
+  "A function to save articles in your favourite format.
+The function must be interactively callable (in other words, it must
+be an Emacs command).
+
+Gnus provides the following functions:
+
+* gnus-summary-save-in-rmail (Rmail format)
+* gnus-summary-save-in-mail (Unix mail format)
+* gnus-summary-save-in-folder (MH folder)
+* gnus-summary-save-in-file (article format)
+* gnus-summary-save-in-vm (use VM's folder format)
+* gnus-summary-write-to-file (article format -- overwrite)."
+  :group 'gnus-article-saving
+  :type '(radio (function-item gnus-summary-save-in-rmail)
+		(function-item gnus-summary-save-in-mail)
+		(function-item gnus-summary-save-in-folder)
+		(function-item gnus-summary-save-in-file)
+		(function-item gnus-summary-save-in-vm)
+		(function-item gnus-summary-write-to-file)))
+
+(defcustom gnus-rmail-save-name 'gnus-plain-save-name
+  "A function generating a file name to save articles in Rmail format.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE."
+  :group 'gnus-article-saving
+  :type 'function)
+
+(defcustom gnus-mail-save-name 'gnus-plain-save-name
+  "A function generating a file name to save articles in Unix mail format.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE."
+  :group 'gnus-article-saving
+  :type 'function)
+
+(defcustom gnus-folder-save-name 'gnus-folder-save-name
+  "A function generating a file name to save articles in MH folder.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FOLDER."
+  :group 'gnus-article-saving
+  :type 'function)
+
+(defcustom gnus-file-save-name 'gnus-numeric-save-name
+  "A function generating a file name to save articles in article format.
+The function is called with NEWSGROUP, HEADERS, and optional
+LAST-FILE."
+  :group 'gnus-article-saving
+  :type 'function)
+
+(defcustom gnus-split-methods
+  '((gnus-article-archive-name)
+    (gnus-article-nndoc-name))
+  "Variable used to suggest where articles are to be saved.
+For instance, if you would like to save articles related to Gnus in
+the file \"gnus-stuff\", and articles related to VM in \"vm-stuff\",
+you could set this variable to something like:
+
+ '((\"^Subject:.*gnus\\|^Newsgroups:.*gnus\" \"gnus-stuff\")
+   (\"^Subject:.*vm\\|^Xref:.*vm\" \"vm-stuff\"))
+
+This variable is an alist where the where the key is the match and the
+value is a list of possible files to save in if the match is non-nil.
+
+If the match is a string, it is used as a regexp match on the
+article.  If the match is a symbol, that symbol will be funcalled
+from the buffer of the article to be saved with the newsgroup as the
+parameter.  If it is a list, it will be evaled in the same buffer.
+
+If this form or function returns a string, this string will be used as
+a possible file name; and if it returns a non-nil list, that list will
+be used as possible file names."
+  :group 'gnus-article-saving
+  :type '(repeat (choice (list function)
+			 (cons regexp (repeat string))
+			 sexp)))
+
+(defcustom gnus-strict-mime t
+  "*If nil, MIME-decode even if there is no Mime-Version header."
+  :group 'gnus-article-mime
+  :type 'boolean)
+
+(defcustom gnus-show-mime-method 'metamail-buffer
+  "Function to process a MIME message.
+The function is called from the article buffer."
+  :group 'gnus-article-mime
+  :type 'function)
+
+(defcustom gnus-decode-encoded-word-method 'gnus-article-de-quoted-unreadable
+  "*Function to decode MIME encoded words.
+The function is called from the article buffer."
+  :group 'gnus-article-mime
+  :type 'function)
+
+(defcustom gnus-page-delimiter "^\^L"
+  "*Regexp describing what to use as article page delimiters.
+The default value is \"^\^L\", which is a form linefeed at the
+beginning of a line."
+  :type 'regexp
+  :group 'gnus-article-various)
+
+(defcustom gnus-article-mode-line-format "Gnus: %%b %S"
+  "*The format specification for the article mode line.
+See `gnus-summary-mode-line-format' for a closer description."
+  :type 'string
+  :group 'gnus-article-various)
+
+(defcustom gnus-article-mode-hook nil
+  "*A hook for Gnus article mode."
+  :type 'hook
+  :group 'gnus-article-various)
+
+(defcustom gnus-article-menu-hook nil
+  "*Hook run after the creation of the article mode menu."
+  :type 'hook
+  :group 'gnus-article-various)
+
+(defcustom gnus-article-prepare-hook nil
+  "*A hook called after an article has been prepared in the article buffer.
+If you want to run a special decoding program like nkf, use this hook."
+  :type 'hook
+  :group 'gnus-article-various)
+
+(defcustom gnus-article-button-face 'bold
+  "Face used for highlighting buttons in the article buffer.
+
+An article button is a piece of text that you can activate by pressing
+`RET' or `mouse-2' above it."
+  :type 'face
+  :group 'gnus-article-buttons)
+
+(defcustom gnus-article-mouse-face 'highlight
+  "Face used for mouse highlighting in the article buffer.
+
+Article buttons will be displayed in this face when the cursor is
+above them."
+  :type 'face
+  :group 'gnus-article-buttons)
+
+(defcustom gnus-signature-face 'italic
+  "Face used for highlighting a signature in the article buffer."
+  :type 'face
+  :group 'gnus-article-highlight
+  :group 'gnus-article-signature)
+
+(defface gnus-header-from-face
+  '((((class color)
+      (background dark))
+     (:foreground "spring green" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "red3" :bold t))
+    (t
+     (:bold t :italic t)))
+  "Face used for displaying from headers."
+  :group 'gnus-article-headers
+  :group 'gnus-article-highlight)
+
+(defface gnus-header-subject-face
+  '((((class color)
+      (background dark))
+     (:foreground "SeaGreen3" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "red4" :bold t))
+    (t
+     (:bold t :italic t)))
+  "Face used for displaying subject headers."
+  :group 'gnus-article-headers
+  :group 'gnus-article-highlight)
+
+(defface gnus-header-newsgroups-face
+  '((((class color)
+      (background dark))
+     (:foreground "yellow" :bold t :italic t))
+    (((class color)
+      (background light))
+     (:foreground "MidnightBlue" :bold t :italic t))
+    (t
+     (:bold t :italic t)))
+  "Face used for displaying newsgroups headers."
+  :group 'gnus-article-headers
+  :group 'gnus-article-highlight)
+
+(defface gnus-header-name-face
+  '((((class color)
+      (background dark))
+     (:foreground "SeaGreen"))
+    (((class color)
+      (background light))
+     (:foreground "maroon"))
+    (t
+     (:bold t)))
+  "Face used for displaying header names."
+  :group 'gnus-article-headers
+  :group 'gnus-article-highlight)
+
+(defface gnus-header-content-face
+  '((((class color)
+      (background dark))
+     (:foreground "forest green" :italic t))
+    (((class color)
+      (background light))
+     (:foreground "indianred4" :italic t))
+    (t
+     (:italic t)))  "Face used for displaying header content."
+  :group 'gnus-article-headers
+  :group 'gnus-article-highlight)
+
+(defcustom gnus-header-face-alist
+  '(("From" nil gnus-header-from-face)
+    ("Subject" nil gnus-header-subject-face)
+    ("Newsgroups:.*," nil gnus-header-newsgroups-face)
+    ("" gnus-header-name-face gnus-header-content-face))
+  "Controls highlighting of article header.
+
+An alist of the form (HEADER NAME CONTENT).
+
+HEADER is a regular expression which should match the name of an
+header header and NAME and CONTENT are either face names or nil.
+
+The name of each header field will be displayed using the face
+specified by the first element in the list where HEADER match the
+header name and NAME is non-nil.  Similarly, the content will be
+displayed by the first non-nil matching CONTENT face."
+  :group 'gnus-article-headers
+  :group 'gnus-article-highlight
+  :type '(repeat (list (regexp :tag "Header")
+		       (choice :tag "Name"
+			       (item :tag "skip" nil)
+			       (face :value default))
+		       (choice :tag "Content"
+			       (item :tag "skip" nil)
+			       (face :value default)))))
+
+;;; Internal variables
+
+(defvar gnus-article-mode-syntax-table
+  (let ((table (copy-syntax-table text-mode-syntax-table)))
+    (modify-syntax-entry ?- "w" table)
+    (modify-syntax-entry ?> ")" table)
+    (modify-syntax-entry ?< "(" table)
+    table)
+  "Syntax table used in article mode buffers.
+Initialized from `text-mode-syntax-table.")
+
+(defvar gnus-save-article-buffer nil)
+
+(defvar gnus-article-mode-line-format-alist
+    (nconc '((?w (gnus-article-wash-status) ?s))
+	   gnus-summary-mode-line-format-alist))
+
+(defvar gnus-number-of-articles-to-be-saved nil)
+
+(defvar gnus-inhibit-hiding nil)
+
+(defsubst gnus-article-hide-text (b e props)
+  "Set text PROPS on the B to E region, extending `intangible' 1 past B."
+  (add-text-properties b e props)
+  (when (memq 'intangible props)
+    (put-text-property
+     (max (1- b) (point-min))
+     b 'intangible (cddr (memq 'intangible props)))))
+
+(defsubst gnus-article-unhide-text (b e)
+  "Remove hidden text properties from region between B and E."
+  (remove-text-properties b e gnus-hidden-properties)
+  (when (memq 'intangible gnus-hidden-properties)
+    (put-text-property (max (1- b) (point-min))
+		       b 'intangible nil)))
+
+(defun gnus-article-hide-text-type (b e type)
+  "Hide text of TYPE between B and E."
+  (gnus-article-hide-text
+   b e (cons 'article-type (cons type gnus-hidden-properties))))
+
+(defun gnus-article-unhide-text-type (b e type)
+  "Hide text of TYPE between B and E."
+  (remove-text-properties
+   b e (cons 'article-type (cons type gnus-hidden-properties)))
+  (when (memq 'intangible gnus-hidden-properties)
+    (put-text-property (max (1- b) (point-min))
+		       b 'intangible nil)))
+
+(defun gnus-article-hide-text-of-type (type)
+  "Hide text of TYPE in the current buffer."
+  (save-excursion
+    (let ((b (point-min))
+	  (e (point-max)))
+      (while (setq b (text-property-any b e 'article-type type))
+	(add-text-properties b (incf b) gnus-hidden-properties)))))
+
+(defun gnus-article-delete-text-of-type (type)
+  "Delete text of TYPE in the current buffer."
+  (save-excursion
+    (let ((e (point-min))
+	  b)
+      (while (setq b (text-property-any e (point-max) 'article-type type))
+	(setq e (text-property-not-all b (point-max) 'article-type type))
+	(delete-region b e)))))
+
+(defun gnus-article-delete-invisible-text ()
+  "Delete all invisible text in the current buffer."
+  (save-excursion
+    (let ((e (point-min))
+	  b)
+      (while (setq b (text-property-any e (point-max) 'invisible t))
+	(setq e (text-property-not-all b (point-max) 'invisible t))
+	(delete-region b e)))))
+
+(defun gnus-article-text-type-exists-p (type)
+  "Say whether any text of type TYPE exists in the buffer."
+  (text-property-any (point-min) (point-max) 'article-type type))
+
+(defsubst gnus-article-header-rank ()
+  "Give the rank of the string HEADER as given by `gnus-sorted-header-list'."
+  (let ((list gnus-sorted-header-list)
+	(i 0))
+    (while list
+      (when (looking-at (car list))
+	(setq list nil))
+      (setq list (cdr list))
+      (incf i))
+    i))
+
+(defun article-hide-headers (&optional arg delete)
+  "Toggle whether to hide unwanted headers and possibly sort them as well.
+If given a negative prefix, always show; if given a positive prefix,
+always hide."
+  (interactive (gnus-article-hidden-arg))
+  (if (gnus-article-check-hidden-text 'headers arg)
+      ;; Show boring headers as well.
+      (gnus-article-show-hidden-text 'boring-headers)
+    ;; This function might be inhibited.
+    (unless gnus-inhibit-hiding
+      (save-excursion
+	(save-restriction
+	  (let ((buffer-read-only nil)
+		(props (nconc (list 'article-type 'headers)
+			      gnus-hidden-properties))
+		(max (1+ (length gnus-sorted-header-list)))
+		(ignored (when (not gnus-visible-headers)
+			   (cond ((stringp gnus-ignored-headers)
+				  gnus-ignored-headers)
+				 ((listp gnus-ignored-headers)
+				  (mapconcat 'identity gnus-ignored-headers
+					     "\\|")))))
+		(visible
+		 (cond ((stringp gnus-visible-headers)
+			gnus-visible-headers)
+		       ((and gnus-visible-headers
+			     (listp gnus-visible-headers))
+			(mapconcat 'identity gnus-visible-headers "\\|"))))
+		(inhibit-point-motion-hooks t)
+		want-list beg)
+	    ;; First we narrow to just the headers.
+	    (widen)
+	    (goto-char (point-min))
+	    ;; Hide any "From " lines at the beginning of (mail) articles.
+	    (while (looking-at "From ")
+	      (forward-line 1))
+	    (unless (bobp)
+	      (if delete
+		  (delete-region (point-min) (point))
+		(gnus-article-hide-text (point-min) (point) props)))
+	    ;; Then treat the rest of the header lines.
+	    (narrow-to-region
+	     (point)
+	     (if (search-forward "\n\n" nil t) ; if there's a body
+		 (progn (forward-line -1) (point))
+	       (point-max)))
+	    ;; Then we use the two regular expressions
+	    ;; `gnus-ignored-headers' and `gnus-visible-headers' to
+	    ;; select which header lines is to remain visible in the
+	    ;; article buffer.
+	    (goto-char (point-min))
+	    (while (re-search-forward "^[^ \t]*:" nil t)
+	      (beginning-of-line)
+	      ;; Mark the rank of the header.
+	      (put-text-property
+	       (point) (1+ (point)) 'message-rank
+	       (if (or (and visible (looking-at visible))
+		       (and ignored
+			    (not (looking-at ignored))))
+		   (gnus-article-header-rank)
+		 (+ 2 max)))
+	      (forward-line 1))
+	    (message-sort-headers-1)
+	    (when (setq beg (text-property-any
+			     (point-min) (point-max) 'message-rank (+ 2 max)))
+	      ;; We make the unwanted headers invisible.
+	      (if delete
+		  (delete-region beg (point-max))
+		;; Suggested by Sudish Joseph <joseph@cis.ohio-state.edu>.
+		(gnus-article-hide-text-type beg (point-max) 'headers))
+	      ;; Work around XEmacs lossage.
+	      (put-text-property (point-min) beg 'invisible nil))))))))
+
+(defun article-hide-boring-headers (&optional arg)
+  "Toggle hiding of headers that aren't very interesting.
+If given a negative prefix, always show; if given a positive prefix,
+always hide."
+  (interactive (gnus-article-hidden-arg))
+  (when (and (not (gnus-article-check-hidden-text 'boring-headers arg))
+	     (not gnus-show-all-headers))
+    (save-excursion
+      (save-restriction
+	(let ((buffer-read-only nil)
+	      (list gnus-boring-article-headers)
+	      (inhibit-point-motion-hooks t)
+	      elem)
+	  (nnheader-narrow-to-headers)
+	  (while list
+	    (setq elem (pop list))
+	    (goto-char (point-min))
+	    (cond
+	     ;; Hide empty headers.
+	     ((eq elem 'empty)
+	      (while (re-search-forward "^[^:]+:[ \t]*\n[^ \t]" nil t)
+		(forward-line -1)
+		(gnus-article-hide-text-type
+		 (progn (beginning-of-line) (point))
+		 (progn
+		   (end-of-line)
+		   (if (re-search-forward "^[^ \t]" nil t)
+		       (match-beginning 0)
+		     (point-max)))
+		 'boring-headers)))
+	     ;; Hide boring Newsgroups header.
+	     ((eq elem 'newsgroups)
+	      (when (equal (gnus-fetch-field "newsgroups")
+			   (gnus-group-real-name
+			    (if (boundp 'gnus-newsgroup-name)
+				gnus-newsgroup-name
+			      "")))
+		(gnus-article-hide-header "newsgroups")))
+	     ((eq elem 'followup-to)
+	      (when (equal (message-fetch-field "followup-to")
+			   (message-fetch-field "newsgroups"))
+		(gnus-article-hide-header "followup-to")))
+	     ((eq elem 'reply-to)
+	      (let ((from (message-fetch-field "from"))
+		    (reply-to (message-fetch-field "reply-to")))
+		(when (and
+		       from reply-to
+		       (ignore-errors
+			 (equal
+			  (nth 1 (mail-extract-address-components from))
+			  (nth 1 (mail-extract-address-components reply-to)))))
+		  (gnus-article-hide-header "reply-to"))))
+	     ((eq elem 'date)
+	      (let ((date (message-fetch-field "date")))
+		(when (and date
+			   (< (gnus-days-between (current-time-string) date)
+			      4))
+		  (gnus-article-hide-header "date")))))))))))
+
+(defun gnus-article-hide-header (header)
+  (save-excursion
+    (goto-char (point-min))
+    (when (re-search-forward (concat "^" header ":") nil t)
+      (gnus-article-hide-text-type
+       (progn (beginning-of-line) (point))
+       (progn
+	 (end-of-line)
+	 (if (re-search-forward "^[^ \t]" nil t)
+	     (match-beginning 0)
+	   (point-max)))
+       'boring-headers))))
+
+;; Written by Per Abrahamsen <amanda@iesd.auc.dk>.
+(defun article-treat-overstrike ()
+  "Translate overstrikes into bold text."
+  (interactive)
+  (save-excursion
+    (goto-char (point-min))
+    (when (search-forward "\n\n" nil t)
+      (let ((buffer-read-only nil))
+	(while (search-forward "\b" nil t)
+	  (let ((next (following-char))
+		(previous (char-after (- (point) 2))))
+	    ;; We do the boldification/underlining by hiding the
+	    ;; overstrikes and putting the proper text property
+	    ;; on the letters.
+	    (cond
+	     ((eq next previous)
+	      (gnus-article-hide-text-type (- (point) 2) (point) 'overstrike)
+	      (put-text-property (point) (1+ (point)) 'face 'bold))
+	     ((eq next ?_)
+	      (gnus-article-hide-text-type
+	       (1- (point)) (1+ (point)) 'overstrike)
+	      (put-text-property
+	       (- (point) 2) (1- (point)) 'face 'underline))
+	     ((eq previous ?_)
+	      (gnus-article-hide-text-type (- (point) 2) (point) 'overstrike)
+	      (put-text-property
+	       (point) (1+ (point)) 'face 'underline)))))))))
+
+(defun article-fill ()
+  "Format too long lines."
+  (interactive)
+  (save-excursion
+    (let ((buffer-read-only nil))
+      (widen)
+      (goto-char (point-min))
+      (search-forward "\n\n" nil t)
+      (end-of-line 1)
+      (let ((paragraph-start "^[>|#:<;* ]*[ \t]*$")
+	    (adaptive-fill-regexp "[ \t]*\\([|#:<;>*]+ *\\)?")
+	    (adaptive-fill-mode t))
+	(while (not (eobp))
+	  (and (>= (current-column) (min fill-column (window-width)))
+	       (/= (preceding-char) ?:)
+	       (fill-paragraph nil))
+	  (end-of-line 2))))))
+
+(defun article-remove-cr ()
+  "Remove carriage returns from an article."
+  (interactive)
+  (save-excursion
+    (let ((buffer-read-only nil))
+      (goto-char (point-min))
+      (while (search-forward "\r" nil t)
+	(replace-match "" t t)))))
+
+(defun article-remove-trailing-blank-lines ()
+  "Remove all trailing blank lines from the article."
+  (interactive)
+  (save-excursion
+    (let ((buffer-read-only nil))
+      (goto-char (point-max))
+      (delete-region
+       (point)
+       (progn
+	 (while (and (not (bobp))
+		     (looking-at "^[ \t]*$"))
+	   (forward-line -1))
+	 (forward-line 1)
+	 (point))))))
+
+(defun article-display-x-face (&optional force)
+  "Look for an X-Face header and display it if present."
+  (interactive (list 'force))
+  (save-excursion
+    ;; Delete the old process, if any.
+    (when (process-status "article-x-face")
+      (delete-process "article-x-face"))
+    (let ((inhibit-point-motion-hooks t)
+	  (case-fold-search nil)
+	  from)
+      (save-restriction
+	(nnheader-narrow-to-headers)
+	(setq from (message-fetch-field "from"))
+	(goto-char (point-min))
+	(when (and gnus-article-x-face-command
+		   (or force
+		       ;; Check whether this face is censored.
+		       (not gnus-article-x-face-too-ugly)
+		       (and gnus-article-x-face-too-ugly from
+			    (not (string-match gnus-article-x-face-too-ugly
+					       from))))
+		   ;; Has to be present.
+		   (re-search-forward "^X-Face: " nil t))
+	  ;; We now have the area of the buffer where the X-Face is stored.
+	  (let ((beg (point))
+		(end (1- (re-search-forward "^\\($\\|[^ \t]\\)" nil t))))
+	    ;; We display the face.
+	    (if (symbolp gnus-article-x-face-command)
+		;; The command is a lisp function, so we call it.
+		(if (gnus-functionp gnus-article-x-face-command)
+		    (funcall gnus-article-x-face-command beg end)
+		  (error "%s is not a function" gnus-article-x-face-command))
+	      ;; The command is a string, so we interpret the command
+	      ;; as a, well, command, and fork it off.
+	      (let ((process-connection-type nil))
+		(process-kill-without-query
+		 (start-process
+		  "article-x-face" nil shell-file-name shell-command-switch
+		  gnus-article-x-face-command))
+		(process-send-region "article-x-face" beg end)
+		(process-send-eof "article-x-face")))))))))
+
+(defalias 'gnus-decode-rfc1522 'article-decode-rfc1522)
+(defalias 'gnus-article-decode-rfc1522 'article-decode-rfc1522)
+(defun article-decode-rfc1522 ()
+  "Hack to remove QP encoding from headers."
+  (let ((case-fold-search t)
+	(inhibit-point-motion-hooks t)
+	(buffer-read-only nil)
+	string)
+    (save-restriction
+      (narrow-to-region
+       (goto-char (point-min))
+       (or (search-forward "\n\n" nil t) (point-max)))
+      (goto-char (point-min))
+      (while (re-search-forward
+	      "=\\?iso-8859-1\\?q\\?\\([^?\t\n]*\\)\\?=" nil t)
+	(setq string (match-string 1))
+	(save-restriction
+	  (narrow-to-region (match-beginning 0) (match-end 0))
+	  (delete-region (point-min) (point-max))
+	  (insert string)
+	  (article-mime-decode-quoted-printable
+	   (goto-char (point-min)) (point-max))
+	  (subst-char-in-region (point-min) (point-max) ?_ ? )
+	  (goto-char (point-max)))
+	(goto-char (point-min))))))
+
+(defun article-de-quoted-unreadable (&optional force)
+  "Do a naive translation of a quoted-printable-encoded article.
+This is in no way, shape or form meant as a replacement for real MIME
+processing, but is simply a stop-gap measure until MIME support is
+written.
+If FORCE, decode the article whether it is marked as quoted-printable
+or not."
+  (interactive (list 'force))
+  (save-excursion
+    (let ((case-fold-search t)
+	  (buffer-read-only nil)
+	  (type (gnus-fetch-field "content-transfer-encoding")))
+      (gnus-article-decode-rfc1522)
+      (when (or force
+		(and type (string-match "quoted-printable" (downcase type))))
+	(goto-char (point-min))
+	(search-forward "\n\n" nil 'move)
+	(article-mime-decode-quoted-printable (point) (point-max))))))
+
+(defun article-mime-decode-quoted-printable-buffer ()
+  "Decode Quoted-Printable in the current buffer."
+  (article-mime-decode-quoted-printable (point-min) (point-max)))
+
+(defun article-mime-decode-quoted-printable (from to)
+  "Decode Quoted-Printable in the region between FROM and TO."
+  (interactive "r")
+  (goto-char from)
+  (while (search-forward "=" to t)
+    (cond ((eq (following-char) ?\n)
+	   (delete-char -1)
+	   (delete-char 1))
+	  ((looking-at "[0-9A-F][0-9A-F]")
+	   (subst-char-in-region
+	    (1- (point)) (point) ?=
+	    (hexl-hex-string-to-integer
+	     (buffer-substring (point) (+ 2 (point)))))
+	   (delete-char 2))
+	  ((looking-at "=")
+	   (delete-char 1))
+	  ((gnus-message 3 "Malformed MIME quoted-printable message")))))
+
+(defun article-hide-pgp (&optional arg)
+  "Toggle hiding of any PGP headers and signatures in the current article.
+If given a negative prefix, always show; if given a positive prefix,
+always hide."
+  (interactive (gnus-article-hidden-arg))
+  (unless (gnus-article-check-hidden-text 'pgp arg)
+    (save-excursion
+      (let ((inhibit-point-motion-hooks t)
+	    buffer-read-only beg end)
+	(widen)
+	(goto-char (point-min))
+	;; Hide the "header".
+	(when (search-forward "\n-----BEGIN PGP SIGNED MESSAGE-----\n" nil t)
+	  (gnus-article-hide-text-type (1+ (match-beginning 0))
+				       (match-end 0) 'pgp))
+	(setq beg (point))
+	;; Hide the actual signature.
+	(and (search-forward "\n-----BEGIN PGP SIGNATURE-----\n" nil t)
+	     (setq end (1+ (match-beginning 0)))
+	     (gnus-article-hide-text-type
+	      end
+	      (if (search-forward "\n-----END PGP SIGNATURE-----\n" nil t)
+		  (match-end 0)
+		;; Perhaps we shouldn't hide to the end of the buffer
+		;; if there is no end to the signature?
+		(point-max))
+	      'pgp))
+	;; Hide "- " PGP quotation markers.
+	(when (and beg end)
+	  (narrow-to-region beg end)
+	  (goto-char (point-min))
+	  (while (re-search-forward "^- " nil t)
+	    (gnus-article-hide-text-type
+	     (match-beginning 0) (match-end 0) 'pgp))
+	  (widen))))))
+
+(defun article-hide-pem (&optional arg)
+  "Toggle hiding of any PEM headers and signatures in the current article.
+If given a negative prefix, always show; if given a positive prefix,
+always hide."
+  (interactive (gnus-article-hidden-arg))
+  (unless (gnus-article-check-hidden-text 'pem arg)
+    (save-excursion
+      (let (buffer-read-only end)
+	(widen)
+	(goto-char (point-min))
+	;; hide the horrendously ugly "header".
+	(and (search-forward "\n-----BEGIN PRIVACY-ENHANCED MESSAGE-----\n"
+			     nil
+			     t)
+	     (setq end (1+ (match-beginning 0)))
+	     (gnus-article-hide-text-type
+	      end
+	      (if (search-forward "\n\n" nil t)
+		  (match-end 0)
+		(point-max))
+	      'pem))
+	;; hide the trailer as well
+	(and (search-forward "\n-----END PRIVACY-ENHANCED MESSAGE-----\n"
+			     nil
+			     t)
+	     (gnus-article-hide-text-type
+	      (match-beginning 0) (match-end 0) 'pem))))))
+
+(defun article-hide-signature (&optional arg)
+  "Hide the signature in the current article.
+If given a negative prefix, always show; if given a positive prefix,
+always hide."
+  (interactive (gnus-article-hidden-arg))
+  (unless (gnus-article-check-hidden-text 'signature arg)
+    (save-excursion
+      (save-restriction
+	(let ((buffer-read-only nil))
+	  (when (gnus-article-narrow-to-signature)
+	    (gnus-article-hide-text-type
+	     (point-min) (point-max) 'signature)))))))
+
+(defun article-strip-leading-blank-lines ()
+  "Remove all blank lines from the beginning of the article."
+  (interactive)
+  (save-excursion
+    (let ((inhibit-point-motion-hooks t)
+	  buffer-read-only)
+      (goto-char (point-min))
+      (when (search-forward "\n\n" nil t)
+	(while (and (not (eobp))
+		    (looking-at "[ \t]*$"))
+	  (gnus-delete-line))))))
+
+(defun article-strip-multiple-blank-lines ()
+  "Replace consecutive blank lines with one empty line."
+  (interactive)
+  (save-excursion
+    (let ((inhibit-point-motion-hooks t)
+	  buffer-read-only)
+      ;; First make all blank lines empty.
+      (goto-char (point-min))
+      (search-forward "\n\n" nil t)
+      (while (re-search-forward "^[ \t]+$" nil t)
+	(replace-match "" nil t))
+      ;; Then replace multiple empty lines with a single empty line.
+      (goto-char (point-min))
+      (search-forward "\n\n" nil t)
+      (while (re-search-forward "\n\n\n+" nil t)
+	(replace-match "\n\n" t t)))))
+
+(defun article-strip-leading-space ()
+  "Remove all white space from the beginning of the lines in the article."
+  (interactive)
+  (save-excursion
+    (let ((inhibit-point-motion-hooks t)
+	  buffer-read-only)
+      (goto-char (point-min))
+      (search-forward "\n\n" nil t)
+      (while (re-search-forward "^[ \t]+" nil t)
+	(replace-match "" t t)))))
+
+(defun article-strip-blank-lines ()
+  "Strip leading, trailing and multiple blank lines."
+  (interactive)
+  (article-strip-leading-blank-lines)
+  (article-remove-trailing-blank-lines)
+  (article-strip-multiple-blank-lines))
+
+(defvar mime::preview/content-list)
+(defvar mime::preview-content-info/point-min)
+(defun gnus-article-narrow-to-signature ()
+  "Narrow to the signature; return t if a signature is found, else nil."
+  (widen)
+  (when (and (boundp 'mime::preview/content-list)
+	     mime::preview/content-list)
+    ;; We have a MIMEish article, so we use the MIME data to narrow.
+    (let ((pcinfo (car (last mime::preview/content-list))))
+      (ignore-errors
+	(narrow-to-region
+	 (funcall (intern "mime::preview-content-info/point-min") pcinfo)
+	 (point-max)))))
+
+  (when (gnus-article-search-signature)
+    (forward-line 1)
+    ;; Check whether we have some limits to what we consider
+    ;; to be a signature.
+    (let ((limits (if (listp gnus-signature-limit) gnus-signature-limit
+		    (list gnus-signature-limit)))
+	  limit limited)
+      (while (setq limit (pop limits))
+	(if (or (and (integerp limit)
+		     (< (- (point-max) (point)) limit))
+		(and (floatp limit)
+		     (< (count-lines (point) (point-max)) limit))
+		(and (gnus-functionp limit)
+		     (funcall limit))
+		(and (stringp limit)
+		     (not (re-search-forward limit nil t))))
+	    ()				; This limit did not succeed.
+	  (setq limited t
+		limits nil)))
+      (unless limited
+	(narrow-to-region (point) (point-max))
+	t))))
+
+(defun gnus-article-search-signature ()
+  "Search the current buffer for the signature separator.
+Put point at the beginning of the signature separator."
+  (let ((cur (point)))
+    (goto-char (point-max))
+    (if (if (stringp gnus-signature-separator)
+	    (re-search-backward gnus-signature-separator nil t)
+	  (let ((seps gnus-signature-separator))
+	    (while (and seps
+			(not (re-search-backward (car seps) nil t)))
+	      (pop seps))
+	    seps))
+	t
+      (goto-char cur)
+      nil)))
+
+(eval-and-compile
+  (autoload 'w3-parse-buffer "w3-parse"))
+
+(defun gnus-article-treat-html ()
+  "Render HTML."
+  (interactive)
+  (let ((cbuf (current-buffer)))
+    (set-buffer gnus-article-buffer)
+    (let (buf buffer-read-only b e)
+      (goto-char (point-min))
+      (narrow-to-region
+       (if (search-forward "\n\n" nil t)
+	   (setq b (point))
+	 (point-max))
+       (setq e (point-max)))
+      (nnheader-temp-write nil
+	(insert-buffer-substring gnus-article-buffer b e)
+	(save-window-excursion
+	  (setq buf (car (w3-parse-buffer (current-buffer))))))
+      (when buf
+	(delete-region (point-min) (point-max))
+	(insert-buffer-substring buf)
+	(kill-buffer buf))
+      (widen)
+      (goto-char (point-min))
+      (set-window-start (get-buffer-window (current-buffer)) (point-min))
+      (set-buffer cbuf))))
+
+(defun gnus-article-hidden-arg ()
+  "Return the current prefix arg as a number, or 0 if no prefix."
+  (list (if current-prefix-arg
+	    (prefix-numeric-value current-prefix-arg)
+	  0)))
+
+(defun gnus-article-check-hidden-text (type arg)
+  "Return nil if hiding is necessary.
+Arg can be nil or a number.  Nil and positive means hide, negative
+means show, 0 means toggle."
+  (save-excursion
+    (save-restriction
+      (widen)
+      (let ((hide (gnus-article-hidden-text-p type)))
+	(cond
+	 ((or (null arg)
+	      (> arg 0))
+	  nil)
+	 ((< arg 0)
+	  (gnus-article-show-hidden-text type))
+	 (t
+	  (if (eq hide 'hidden)
+	      (gnus-article-show-hidden-text type)
+	    nil)))))))
+
+(defun gnus-article-hidden-text-p (type)
+  "Say whether the current buffer contains hidden text of type TYPE."
+  (let ((start (point-min))
+	(pos (text-property-any (point-min) (point-max) 'article-type type)))
+    (while (and pos
+		(not (get-text-property pos 'invisible)))
+      (setq pos
+	    (text-property-any (1+ pos) (point-max) 'article-type type)))
+    (if pos
+	'hidden
+      'shown)))
+
+(defun gnus-article-show-hidden-text (type &optional hide)
+  "Show all hidden text of type TYPE.
+If HIDE, hide the text instead."
+  (save-excursion
+    (let ((buffer-read-only nil)
+	  (inhibit-point-motion-hooks t)
+	  (end (point-min))
+	  beg)
+      (while (setq beg (text-property-any end (point-max) 'article-type type))
+	(goto-char beg)
+	(setq end (or
+		   (text-property-not-all beg (point-max) 'article-type type)
+		   (point-max)))
+	(if hide
+	    (gnus-article-hide-text beg end gnus-hidden-properties)
+	  (gnus-article-unhide-text beg end))
+	(goto-char end))
+      t)))
+
+(defconst article-time-units
+  `((year . ,(* 365.25 24 60 60))
+    (week . ,(* 7 24 60 60))
+    (day . ,(* 24 60 60))
+    (hour . ,(* 60 60))
+    (minute . 60)
+    (second . 1))
+  "Mapping from time units to seconds.")
+
+(defun article-date-ut (&optional type highlight header)
+  "Convert DATE date to universal time in the current article.
+If TYPE is `local', convert to local time; if it is `lapsed', output
+how much time has lapsed since DATE."
+  (interactive (list 'ut t))
+  (let* ((header (or header
+		     (mail-header-date gnus-current-headers)
+		     (message-fetch-field "date")
+		     ""))
+	 (date (if (vectorp header) (mail-header-date header)
+		 header))
+	 (date-regexp "^Date:[ \t]\\|^X-Sent:[ \t]")
+	 (inhibit-point-motion-hooks t)
+	 bface eface)
+    (when (and date (not (string= date "")))
+      (save-excursion
+	(save-restriction
+	  (nnheader-narrow-to-headers)
+	  (let ((buffer-read-only nil))
+	    ;; Delete any old Date headers.
+	    (if (re-search-forward date-regexp nil t)
+		(progn
+		  (setq bface (get-text-property (gnus-point-at-bol) 'face)
+			eface (get-text-property (1- (gnus-point-at-eol))
+						 'face))
+		  (message-remove-header date-regexp t)
+		  (beginning-of-line))
+	      (goto-char (point-max)))
+	    (insert (article-make-date-line date type))
+	    ;; Do highlighting.
+	    (forward-line -1)
+	    (when (looking-at "\\([^:]+\\): *\\(.*\\)$")
+	      (put-text-property (match-beginning 1) (match-end 1)
+				 'face bface)
+	      (put-text-property (match-beginning 2) (match-end 2)
+				 'face eface))))))))
+
+(defun article-make-date-line (date type)
+  "Return a DATE line of TYPE."
+  (cond
+   ;; Convert to the local timezone.  We have to slap a
+   ;; `condition-case' round the calls to the timezone
+   ;; functions since they aren't particularly resistant to
+   ;; buggy dates.
+   ((eq type 'local)
+    (concat "Date: " (condition-case ()
+			 (timezone-make-date-arpa-standard date)
+		       (error date))
+	    "\n"))
+   ;; Convert to Universal Time.
+   ((eq type 'ut)
+    (concat "Date: "
+	    (condition-case ()
+		(timezone-make-date-arpa-standard date nil "UT")
+	      (error date))
+	    "\n"))
+   ;; Get the original date from the article.
+   ((eq type 'original)
+    (concat "Date: " date "\n"))
+   ;; Let the user define the format.
+   ((eq type 'user)
+    (concat
+     "Date: "
+     (format-time-string gnus-article-time-format
+			 (ignore-errors
+			   (gnus-encode-date
+			    (timezone-make-date-arpa-standard
+			     date nil "UT"))))
+     "\n"))
+   ;; Do an X-Sent lapsed format.
+   ((eq type 'lapsed)
+    ;; If the date is seriously mangled, the timezone functions are
+    ;; liable to bug out, so we ignore all errors.
+    (let* ((now (current-time))
+	   (real-time
+	    (ignore-errors
+	      (gnus-time-minus
+	       (gnus-encode-date
+		(timezone-make-date-arpa-standard
+		 (current-time-string now)
+		 (current-time-zone now) "UT"))
+	       (gnus-encode-date
+		(timezone-make-date-arpa-standard
+		 date nil "UT")))))
+	   (real-sec (and real-time
+			  (+ (* (float (car real-time)) 65536)
+			     (cadr real-time))))
+	   (sec (and real-time (abs real-sec)))
+	   num prev)
+      (cond
+       ((null real-time)
+	"X-Sent: Unknown\n")
+       ((zerop sec)
+	"X-Sent: Now\n")
+       (t
+	(concat
+	 "X-Sent: "
+	 ;; This is a bit convoluted, but basically we go
+	 ;; through the time units for years, weeks, etc,
+	 ;; and divide things to see whether that results
+	 ;; in positive answers.
+	 (mapconcat
+	  (lambda (unit)
+	    (if (zerop (setq num (ffloor (/ sec (cdr unit)))))
+		;; The (remaining) seconds are too few to
+		;; be divided into this time unit.
+		""
+	      ;; It's big enough, so we output it.
+	      (setq sec (- sec (* num (cdr unit))))
+	      (prog1
+		  (concat (if prev ", " "") (int-to-string
+					     (floor num))
+			  " " (symbol-name (car unit))
+			  (if (> num 1) "s" ""))
+		(setq prev t))))
+	  article-time-units "")
+	 ;; If dates are odd, then it might appear like the
+	 ;; article was sent in the future.
+	 (if (> real-sec 0)
+	     " ago\n"
+	   " in the future\n"))))))
+   (t
+    (error "Unknown conversion type: %s" type))))
+
+(defun article-date-local (&optional highlight)
+  "Convert the current article date to the local timezone."
+  (interactive (list t))
+  (article-date-ut 'local highlight))
+
+(defun article-date-original (&optional highlight)
+  "Convert the current article date to what it was originally.
+This is only useful if you have used some other date conversion
+function and want to see what the date was before converting."
+  (interactive (list t))
+  (article-date-ut 'original highlight))
+
+(defun article-date-lapsed (&optional highlight)
+  "Convert the current article date to time lapsed since it was sent."
+  (interactive (list t))
+  (article-date-ut 'lapsed highlight))
+
+(defun article-date-user (&optional highlight)
+  "Convert the current article date to the user-defined format.
+This format is defined by the `gnus-article-time-format' variable."
+  (interactive (list t))
+  (article-date-ut 'user highlight))
+
+(defun article-show-all ()
+  "Show all hidden text in the article buffer."
+  (interactive)
+  (save-excursion
+    (let ((buffer-read-only nil))
+      (gnus-article-unhide-text (point-min) (point-max)))))
+
+(defun article-emphasize (&optional arg)
+  "Emphasize text according to `gnus-emphasis-alist'."
+  (interactive (gnus-article-hidden-arg))
+  (unless (gnus-article-check-hidden-text 'emphasis arg)
+    (save-excursion
+      (let ((alist gnus-emphasis-alist)
+	    (buffer-read-only nil)
+	    (props (append '(article-type emphasis)
+			   gnus-hidden-properties))
+	    regexp elem beg invisible visible face)
+	(goto-char (point-min))
+	(search-forward "\n\n" nil t)
+	(setq beg (point))
+	(while (setq elem (pop alist))
+	  (goto-char beg)
+	  (setq regexp (car elem)
+		invisible (nth 1 elem)
+		visible (nth 2 elem)
+		face (nth 3 elem))
+	  (while (re-search-forward regexp nil t)
+ 	    (when (and (match-beginning visible) (match-beginning invisible))
+ 	      (gnus-article-hide-text
+ 	       (match-beginning invisible) (match-end invisible) props)
+ 	      (gnus-article-unhide-text-type
+ 	       (match-beginning visible) (match-end visible) 'emphasis)
+ 	      (gnus-put-text-property-excluding-newlines
+ 	       (match-beginning visible) (match-end visible) 'face face)
+ 	      (goto-char (match-end invisible)))))))))
+
+(defvar gnus-summary-article-menu)
+(defvar gnus-summary-post-menu)
+
+;;; Saving functions.
+
+(defun gnus-article-save (save-buffer file &optional num)
+  "Save the currently selected article."
+  (unless gnus-save-all-headers
+    ;; Remove headers according to `gnus-saved-headers'.
+    (let ((gnus-visible-headers
+	   (or gnus-saved-headers gnus-visible-headers))
+	  (gnus-article-buffer save-buffer))
+      (gnus-article-hide-headers 1 t)))
+  (save-window-excursion
+    (if (not gnus-default-article-saver)
+	(error "No default saver is defined.")
+      ;; !!! Magic!  The saving functions all save
+      ;; `gnus-original-article-buffer' (or so they think), but we
+      ;; bind that variable to our save-buffer.
+      (set-buffer gnus-article-buffer)
+      (let* ((gnus-save-article-buffer save-buffer)
+	     (filename
+	      (cond
+	       ((not gnus-prompt-before-saving) 'default)
+	       ((eq gnus-prompt-before-saving 'always) nil)
+	       (t file)))
+	     (gnus-number-of-articles-to-be-saved
+	      (when (eq gnus-prompt-before-saving t)
+		num)))			; Magic
+	(set-buffer gnus-summary-buffer)
+	(funcall gnus-default-article-saver filename)))))
+
+(defun gnus-read-save-file-name (prompt &optional filename
+					function group headers variable)
+  (let ((default-name
+	  (funcall function group headers (symbol-value variable)))
+	result)
+    (setq
+     result
+     (cond
+      ((eq filename 'default)
+       default-name)
+      ((eq filename t)
+       default-name)
+      (filename filename)
+      (t
+       (let* ((split-name (gnus-get-split-value gnus-split-methods))
+	      (prompt
+	       (format prompt
+		       (if (and gnus-number-of-articles-to-be-saved
+				(> gnus-number-of-articles-to-be-saved 1))
+			   (format "these %d articles"
+				   gnus-number-of-articles-to-be-saved)
+			 "this article")))
+	      (file
+	       ;; Let the split methods have their say.
+	       (cond
+		;; No split name was found.
+		((null split-name)
+		 (read-file-name
+		  (concat prompt " (default "
+			  (file-name-nondirectory default-name) ") ")
+		  (file-name-directory default-name)
+		  default-name))
+		;; A single group name is returned.
+		((stringp split-name)
+		 (setq default-name
+		       (funcall function split-name headers
+				(symbol-value variable)))
+		 (read-file-name
+		  (concat prompt " (default "
+			  (file-name-nondirectory default-name) ") ")
+		  (file-name-directory default-name)
+		  default-name))
+		;; A single split name was found
+		((= 1 (length split-name))
+		 (let* ((name (car split-name))
+			(dir (cond ((file-directory-p name)
+				    (file-name-as-directory name))
+				   ((file-exists-p name) name)
+				   (t gnus-article-save-directory))))
+		   (read-file-name
+		    (concat prompt " (default " name ") ")
+		    dir name)))
+		;; A list of splits was found.
+		(t
+		 (setq split-name (nreverse split-name))
+		 (let (result)
+		   (let ((file-name-history
+			  (nconc split-name file-name-history)))
+		     (setq result
+			   (expand-file-name
+			    (read-file-name
+			     (concat prompt " (`M-p' for defaults) ")
+			     gnus-article-save-directory
+			     (car split-name))
+			    gnus-article-save-directory)))
+		   (car (push result file-name-history)))))))
+	 ;; Create the directory.
+	 (gnus-make-directory (file-name-directory file))
+	 ;; If we have read a directory, we append the default file name.
+	 (when (file-directory-p file)
+	   (setq file (concat (file-name-as-directory file)
+			      (file-name-nondirectory default-name))))
+	 ;; Possibly translate some characters.
+	 (nnheader-translate-file-chars file)))))
+    (gnus-make-directory (file-name-directory result))
+    (set variable result)))
+
+(defun gnus-article-archive-name (group)
+  "Return the first instance of an \"Archive-name\" in the current buffer."
+  (let ((case-fold-search t))
+    (when (re-search-forward "archive-name: *\\([^ \n\t]+\\)[ \t]*$" nil t)
+      (nnheader-concat gnus-article-save-directory
+		       (match-string 1)))))
+
+(defun gnus-article-nndoc-name (group)
+  "If GROUP is an nndoc group, return the name of the parent group."
+  (when (eq (car (gnus-find-method-for-group group)) 'nndoc)
+    (gnus-group-get-parameter group 'save-article-group)))
+
+(defun gnus-summary-save-in-rmail (&optional filename)
+  "Append this article to Rmail file.
+Optional argument FILENAME specifies file name.
+Directory to save to is default to `gnus-article-save-directory'."
+  (interactive)
+  (gnus-set-global-variables)
+  (setq filename (gnus-read-save-file-name
+		  "Save %s in rmail file:" filename
+		  gnus-rmail-save-name gnus-newsgroup-name
+		  gnus-current-headers 'gnus-newsgroup-last-rmail))
+  (gnus-eval-in-buffer-window gnus-save-article-buffer
+    (save-excursion
+      (save-restriction
+	(widen)
+	(gnus-output-to-rmail filename)))))
+
+(defun gnus-summary-save-in-mail (&optional filename)
+  "Append this article to Unix mail file.
+Optional argument FILENAME specifies file name.
+Directory to save to is default to `gnus-article-save-directory'."
+  (interactive)
+  (gnus-set-global-variables)
+  (setq filename (gnus-read-save-file-name
+		  "Save %s in Unix mail file:" filename
+		  gnus-mail-save-name gnus-newsgroup-name
+		  gnus-current-headers 'gnus-newsgroup-last-mail))
+  (gnus-eval-in-buffer-window gnus-save-article-buffer
+    (save-excursion
+      (save-restriction
+	(widen)
+	(if (and (file-readable-p filename)
+		 (mail-file-babyl-p filename))
+	    (gnus-output-to-rmail filename t)
+	  (gnus-output-to-mail filename))))))
+
+(defun gnus-summary-save-in-file (&optional filename overwrite)
+  "Append this article to file.
+Optional argument FILENAME specifies file name.
+Directory to save to is default to `gnus-article-save-directory'."
+  (interactive)
+  (gnus-set-global-variables)
+  (setq filename (gnus-read-save-file-name
+		  "Save %s in file:" filename
+		  gnus-file-save-name gnus-newsgroup-name
+		  gnus-current-headers 'gnus-newsgroup-last-file))
+  (gnus-eval-in-buffer-window gnus-save-article-buffer
+    (save-excursion
+      (save-restriction
+	(widen)
+	(when (and overwrite
+		   (file-exists-p filename))
+	  (delete-file filename))
+	(gnus-output-to-file filename)))))
+
+(defun gnus-summary-write-to-file (&optional filename)
+  "Write this article to a file.
+Optional argument FILENAME specifies file name.
+The directory to save in defaults to `gnus-article-save-directory'."
+  (interactive)
+  (gnus-summary-save-in-file nil t))
+
+(defun gnus-summary-save-body-in-file (&optional filename)
+  "Append this article body to a file.
+Optional argument FILENAME specifies file name.
+The directory to save in defaults to `gnus-article-save-directory'."
+  (interactive)
+  (gnus-set-global-variables)
+  (setq filename (gnus-read-save-file-name
+		  "Save %s body in file:" filename
+		  gnus-file-save-name gnus-newsgroup-name
+		  gnus-current-headers 'gnus-newsgroup-last-file))
+  (gnus-eval-in-buffer-window gnus-save-article-buffer
+    (save-excursion
+      (save-restriction
+	(widen)
+	(goto-char (point-min))
+	(when (search-forward "\n\n" nil t)
+	  (narrow-to-region (point) (point-max)))
+	(gnus-output-to-file filename)))))
+
+(defun gnus-summary-save-in-pipe (&optional command)
+  "Pipe this article to subprocess."
+  (interactive)
+  (gnus-set-global-variables)
+  (setq command
+	(cond ((eq command 'default)
+	       gnus-last-shell-command)
+	      (command command)
+	      (t (read-string
+		  (format
+		   "Shell command on %s: "
+		   (if (and gnus-number-of-articles-to-be-saved
+			    (> gnus-number-of-articles-to-be-saved 1))
+		       (format "these %d articles"
+			       gnus-number-of-articles-to-be-saved)
+		     "this article"))
+		  gnus-last-shell-command))))
+  (when (string-equal command "")
+    (setq command gnus-last-shell-command))
+  (gnus-eval-in-buffer-window gnus-article-buffer
+    (save-restriction
+      (widen)
+      (shell-command-on-region (point-min) (point-max) command nil)))
+  (setq gnus-last-shell-command command))
+
+;;; Article file names when saving.
+
+(defun gnus-capitalize-newsgroup (newsgroup)
+  "Capitalize NEWSGROUP name."
+  (when (not (zerop (length newsgroup)))
+    (concat (char-to-string (upcase (aref newsgroup 0)))
+	    (substring newsgroup 1))))
+
+(defun gnus-Numeric-save-name (newsgroup headers &optional last-file)
+  "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
+If variable `gnus-use-long-file-name' is non-nil, it is ~/News/News.group/num.
+Otherwise, it is like ~/News/news/group/num."
+  (let ((default
+	  (expand-file-name
+	   (concat (if (gnus-use-long-file-name 'not-save)
+		       (gnus-capitalize-newsgroup newsgroup)
+		     (gnus-newsgroup-directory-form newsgroup))
+		   "/" (int-to-string (mail-header-number headers)))
+	   gnus-article-save-directory)))
+    (if (and last-file
+	     (string-equal (file-name-directory default)
+			   (file-name-directory last-file))
+	     (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
+	default
+      (or last-file default))))
+
+(defun gnus-numeric-save-name (newsgroup headers &optional last-file)
+  "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
+If variable `gnus-use-long-file-name' is non-nil, it is
+~/News/news.group/num.	Otherwise, it is like ~/News/news/group/num."
+  (let ((default
+	  (expand-file-name
+	   (concat (if (gnus-use-long-file-name 'not-save)
+		       newsgroup
+		     (gnus-newsgroup-directory-form newsgroup))
+		   "/" (int-to-string (mail-header-number headers)))
+	   gnus-article-save-directory)))
+    (if (and last-file
+	     (string-equal (file-name-directory default)
+			   (file-name-directory last-file))
+	     (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
+	default
+      (or last-file default))))
+
+(defun gnus-Plain-save-name (newsgroup headers &optional last-file)
+  "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
+If variable `gnus-use-long-file-name' is non-nil, it is
+~/News/News.group.  Otherwise, it is like ~/News/news/group/news."
+  (or last-file
+      (expand-file-name
+       (if (gnus-use-long-file-name 'not-save)
+	   (gnus-capitalize-newsgroup newsgroup)
+	 (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
+       gnus-article-save-directory)))
+
+(defun gnus-plain-save-name (newsgroup headers &optional last-file)
+  "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
+If variable `gnus-use-long-file-name' is non-nil, it is
+~/News/news.group.  Otherwise, it is like ~/News/news/group/news."
+  (or last-file
+      (expand-file-name
+       (if (gnus-use-long-file-name 'not-save)
+	   newsgroup
+	 (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
+       gnus-article-save-directory)))
+
+(eval-and-compile
+  (mapcar
+   (lambda (func)
+     (let (afunc gfunc)
+       (if (consp func)
+	   (setq afunc (car func)
+		 gfunc (cdr func))
+	 (setq afunc func
+	       gfunc (intern (format "gnus-%s" func))))
+       (fset gfunc
+	     (if (not (fboundp afunc))
+		 nil
+	       `(lambda (&optional interactive &rest args)
+		  ,(documentation afunc t)
+		  (interactive (list t))
+		  (save-excursion
+		    (set-buffer gnus-article-buffer)
+		    (if interactive
+			(call-interactively ',afunc)
+		      (apply ',afunc args))))))))
+   '(article-hide-headers
+     article-hide-boring-headers
+     article-treat-overstrike
+     (article-fill . gnus-article-word-wrap)
+     article-remove-cr
+     article-display-x-face
+     article-de-quoted-unreadable
+     article-mime-decode-quoted-printable
+     article-hide-pgp
+     article-hide-pem
+     article-hide-signature
+     article-remove-trailing-blank-lines
+     article-strip-leading-blank-lines
+     article-strip-multiple-blank-lines
+     article-strip-leading-space
+     article-strip-blank-lines
+     article-date-local
+     article-date-original
+     article-date-ut
+     article-date-user
+     article-date-lapsed
+     article-emphasize
+     (article-show-all . gnus-article-show-all-headers))))
+
+;;;
+;;; Gnus article mode
+;;;
+
+(put 'gnus-article-mode 'mode-class 'special)
+
+(when t
+  (gnus-define-keys gnus-article-mode-map
+    " " gnus-article-goto-next-page
+    "\177" gnus-article-goto-prev-page
+    [delete] gnus-article-goto-prev-page
+    "\C-c^" gnus-article-refer-article
+    "h" gnus-article-show-summary
+    "s" gnus-article-show-summary
+    "\C-c\C-m" gnus-article-mail
+    "?" gnus-article-describe-briefly
+    gnus-mouse-2 gnus-article-push-button
+    "\r" gnus-article-press-button
+    "\t" gnus-article-next-button
+    "\M-\t" gnus-article-prev-button
+    "e" gnus-article-edit
+    "<" beginning-of-buffer
+    ">" end-of-buffer
+    "\C-c\C-i" gnus-info-find-node
+    "\C-c\C-b" gnus-bug
+
+    "\C-d" gnus-article-read-summary-keys
+    "\M-*" gnus-article-read-summary-keys
+    "\M-#" gnus-article-read-summary-keys
+    "\M-^" gnus-article-read-summary-keys
+    "\M-g" gnus-article-read-summary-keys)
+
+  (substitute-key-definition
+   'undefined 'gnus-article-read-summary-keys gnus-article-mode-map))
+
+(defun gnus-article-make-menu-bar ()
+  (gnus-turn-off-edit-menu 'article)
+  (unless (boundp 'gnus-article-article-menu)
+    (easy-menu-define
+     gnus-article-article-menu gnus-article-mode-map ""
+     '("Article"
+       ["Scroll forwards" gnus-article-goto-next-page t]
+       ["Scroll backwards" gnus-article-goto-prev-page t]
+       ["Show summary" gnus-article-show-summary t]
+       ["Fetch Message-ID at point" gnus-article-refer-article t]
+       ["Mail to address at point" gnus-article-mail t]))
+
+    (easy-menu-define
+     gnus-article-treatment-menu gnus-article-mode-map ""
+     '("Treatment"
+       ["Hide headers" gnus-article-hide-headers t]
+       ["Hide signature" gnus-article-hide-signature t]
+       ["Hide citation" gnus-article-hide-citation t]
+       ["Treat overstrike" gnus-article-treat-overstrike t]
+       ["Remove carriage return" gnus-article-remove-cr t]
+       ["Remove quoted-unreadable" gnus-article-de-quoted-unreadable t]))
+
+    (when nil
+      (when (boundp 'gnus-summary-article-menu)
+	(define-key gnus-article-mode-map [menu-bar commands]
+	  (cons "Commands" gnus-summary-article-menu))))
+
+    (when (boundp 'gnus-summary-post-menu)
+      (define-key gnus-article-mode-map [menu-bar post]
+	(cons "Post" gnus-summary-post-menu)))
+
+    (run-hooks 'gnus-article-menu-hook)))
+
+(defun gnus-article-mode ()
+  "Major mode for displaying an article.
+
+All normal editing commands are switched off.
+
+The following commands are available in addition to all summary mode
+commands:
+\\<gnus-article-mode-map>
+\\[gnus-article-next-page]\t Scroll the article one page forwards
+\\[gnus-article-prev-page]\t Scroll the article one page backwards
+\\[gnus-article-refer-article]\t Go to the article referred to by an article id near point
+\\[gnus-article-show-summary]\t Display the summary buffer
+\\[gnus-article-mail]\t Send a reply to the address near point
+\\[gnus-article-describe-briefly]\t Describe the current mode briefly
+\\[gnus-info-find-node]\t Go to the Gnus info node"
+  (interactive)
+  (when (gnus-visual-p 'article-menu 'menu)
+    (gnus-article-make-menu-bar))
+  (kill-all-local-variables)
+  (gnus-simplify-mode-line)
+  (setq mode-name "Article")
+  (setq major-mode 'gnus-article-mode)
+  (make-local-variable 'minor-mode-alist)
+  (unless (assq 'gnus-show-mime minor-mode-alist)
+    (push (list 'gnus-show-mime " MIME") minor-mode-alist))
+  (use-local-map gnus-article-mode-map)
+  (gnus-update-format-specifications nil 'article-mode)
+  (set (make-local-variable 'page-delimiter) gnus-page-delimiter)
+  (set (make-local-variable 'gnus-page-broken) nil)
+  (set (make-local-variable 'gnus-button-marker-list) nil)
+  (gnus-set-default-directory)
+  (buffer-disable-undo (current-buffer))
+  (setq buffer-read-only t)
+  (set-syntax-table gnus-article-mode-syntax-table)
+  (run-hooks 'gnus-article-mode-hook))
+
+(defun gnus-article-setup-buffer ()
+  "Initialize the article buffer."
+  (let* ((name (if gnus-single-article-buffer "*Article*"
+		 (concat "*Article " gnus-newsgroup-name "*")))
+	 (original
+	  (progn (string-match "\\*Article" name)
+		 (concat " *Original Article"
+			 (substring name (match-end 0))))))
+    (setq gnus-article-buffer name)
+    (setq gnus-original-article-buffer original)
+    ;; This might be a variable local to the summary buffer.
+    (unless gnus-single-article-buffer
+      (save-excursion
+	(set-buffer gnus-summary-buffer)
+	(setq gnus-article-buffer name)
+	(setq gnus-original-article-buffer original)
+	(gnus-set-global-variables)))
+    ;; Init original article buffer.
+    (save-excursion
+      (set-buffer (get-buffer-create gnus-original-article-buffer))
+      (buffer-disable-undo (current-buffer))
+      (setq major-mode 'gnus-original-article-mode)
+      (gnus-add-current-to-buffer-list)
+      (make-local-variable 'gnus-original-article))
+    (if (get-buffer name)
+	(save-excursion
+	  (set-buffer name)
+	  (buffer-disable-undo (current-buffer))
+	  (setq buffer-read-only t)
+	  (gnus-add-current-to-buffer-list)
+	  (unless (eq major-mode 'gnus-article-mode)
+	    (gnus-article-mode))
+	  (current-buffer))
+      (save-excursion
+	(set-buffer (get-buffer-create name))
+	(gnus-add-current-to-buffer-list)
+	(gnus-article-mode)
+	(make-local-variable 'gnus-summary-buffer)
+	(current-buffer)))))
+
+;; Set article window start at LINE, where LINE is the number of lines
+;; from the head of the article.
+(defun gnus-article-set-window-start (&optional line)
+  (set-window-start
+   (get-buffer-window gnus-article-buffer t)
+   (save-excursion
+     (set-buffer gnus-article-buffer)
+     (goto-char (point-min))
+     (if (not line)
+	 (point-min)
+       (gnus-message 6 "Moved to bookmark")
+       (search-forward "\n\n" nil t)
+       (forward-line line)
+       (point)))))
+
+(defun gnus-article-prepare (article &optional all-headers header)
+  "Prepare ARTICLE in article mode buffer.
+ARTICLE should either be an article number or a Message-ID.
+If ARTICLE is an id, HEADER should be the article headers.
+If ALL-HEADERS is non-nil, no headers are hidden."
+  (save-excursion
+    ;; Make sure we start in a summary buffer.
+    (unless (eq major-mode 'gnus-summary-mode)
+      (set-buffer gnus-summary-buffer))
+    (setq gnus-summary-buffer (current-buffer))
+    ;; Make sure the connection to the server is alive.
+    (unless (gnus-server-opened
+	     (gnus-find-method-for-group gnus-newsgroup-name))
+      (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name))
+      (gnus-request-group gnus-newsgroup-name t))
+    (let* ((gnus-article (if header (mail-header-number header) article))
+	   (summary-buffer (current-buffer))
+	   (internal-hook gnus-article-internal-prepare-hook)
+	   (group gnus-newsgroup-name)
+	   result)
+      (save-excursion
+	(gnus-article-setup-buffer)
+	(set-buffer gnus-article-buffer)
+	;; Deactivate active regions.
+	(when (and (boundp 'transient-mark-mode)
+		   transient-mark-mode)
+	  (setq mark-active nil))
+	(if (not (setq result (let ((buffer-read-only nil))
+				(gnus-request-article-this-buffer
+				 article group))))
+	    ;; There is no such article.
+	    (save-excursion
+	      (when (and (numberp article)
+			 (not (memq article gnus-newsgroup-sparse)))
+		(setq gnus-article-current
+		      (cons gnus-newsgroup-name article))
+		(set-buffer gnus-summary-buffer)
+		(setq gnus-current-article article)
+		(gnus-summary-mark-article article gnus-canceled-mark))
+	      (unless (memq article gnus-newsgroup-sparse)
+		(gnus-error
+		 1 "No such article (may have expired or been canceled)")))
+	  (if (or (eq result 'pseudo) (eq result 'nneething))
+	      (progn
+		(save-excursion
+		  (set-buffer summary-buffer)
+		  (setq gnus-last-article gnus-current-article
+			gnus-newsgroup-history (cons gnus-current-article
+						     gnus-newsgroup-history)
+			gnus-current-article 0
+			gnus-current-headers nil
+			gnus-article-current nil)
+		  (if (eq result 'nneething)
+		      (gnus-configure-windows 'summary)
+		    (gnus-configure-windows 'article))
+		  (gnus-set-global-variables))
+		(gnus-set-mode-line 'article))
+	    ;; The result from the `request' was an actual article -
+	    ;; or at least some text that is now displayed in the
+	    ;; article buffer.
+	    (when (and (numberp article)
+		       (not (eq article gnus-current-article)))
+	      ;; Seems like a new article has been selected.
+	      ;; `gnus-current-article' must be an article number.
+	      (save-excursion
+		(set-buffer summary-buffer)
+		(setq gnus-last-article gnus-current-article
+		      gnus-newsgroup-history (cons gnus-current-article
+						   gnus-newsgroup-history)
+		      gnus-current-article article
+		      gnus-current-headers
+		      (gnus-summary-article-header gnus-current-article)
+		      gnus-article-current
+		      (cons gnus-newsgroup-name gnus-current-article))
+		(unless (vectorp gnus-current-headers)
+		  (setq gnus-current-headers nil))
+		(gnus-summary-show-thread)
+		(run-hooks 'gnus-mark-article-hook)
+		(gnus-set-mode-line 'summary)
+		(when (gnus-visual-p 'article-highlight 'highlight)
+		  (run-hooks 'gnus-visual-mark-article-hook))
+		;; Set the global newsgroup variables here.
+		;; Suggested by Jim Sisolak
+		;; <sisolak@trans4.neep.wisc.edu>.
+		(gnus-set-global-variables)
+		(setq gnus-have-all-headers
+		      (or all-headers gnus-show-all-headers))
+		(and gnus-use-cache
+		     (vectorp (gnus-summary-article-header article))
+		     (gnus-cache-possibly-enter-article
+		      group article
+		      (gnus-summary-article-header article)
+		      (memq article gnus-newsgroup-marked)
+		      (memq article gnus-newsgroup-dormant)
+		      (memq article gnus-newsgroup-unreads)))))
+	    (when (or (numberp article)
+		      (stringp article))
+	      ;; Hooks for getting information from the article.
+	      ;; This hook must be called before being narrowed.
+	      (let (buffer-read-only)
+		(run-hooks 'internal-hook)
+		(run-hooks 'gnus-article-prepare-hook)
+		;; Decode MIME message.
+		(when gnus-show-mime
+		  (if (or (not gnus-strict-mime)
+			  (gnus-fetch-field "Mime-Version"))
+		      (funcall gnus-show-mime-method)
+		    (funcall gnus-decode-encoded-word-method)))
+		;; Perform the article display hooks.
+		(run-hooks 'gnus-article-display-hook))
+	      ;; Do page break.
+	      (goto-char (point-min))
+	      (setq gnus-page-broken
+		    (when gnus-break-pages
+		      (gnus-narrow-to-page)
+		      t)))
+	    (gnus-set-mode-line 'article)
+	    (gnus-configure-windows 'article)
+	    (goto-char (point-min))
+	    t))))))
+
+(defun gnus-article-wash-status ()
+  "Return a string which display status of article washing."
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (let ((cite (gnus-article-hidden-text-p 'cite))
+	  (headers (gnus-article-hidden-text-p 'headers))
+	  (boring (gnus-article-hidden-text-p 'boring-headers))
+	  (pgp (gnus-article-hidden-text-p 'pgp))
+	  (pem (gnus-article-hidden-text-p 'pem))
+	  (signature (gnus-article-hidden-text-p 'signature))
+	  (overstrike (gnus-article-hidden-text-p 'overstrike))
+	  (emphasis (gnus-article-hidden-text-p 'emphasis))
+	  (mime gnus-show-mime))
+      (format "%c%c%c%c%c%c%c"
+	      (if cite ?c ? )
+	      (if (or headers boring) ?h ? )
+	      (if (or pgp pem) ?p ? )
+	      (if signature ?s ? )
+	      (if overstrike ?o ? )
+	      (if mime ?m ? )
+	      (if emphasis ?e ? )))))
+
+(defun gnus-article-hide-headers-if-wanted ()
+  "Hide unwanted headers if `gnus-have-all-headers' is nil.
+Provided for backwards compatibility."
+  (or (save-excursion (set-buffer gnus-summary-buffer) gnus-have-all-headers)
+      gnus-inhibit-hiding
+      (gnus-article-hide-headers)))
+
+;;; Article savers.
+
+(defun gnus-output-to-file (file-name)
+  "Append the current article to a file named FILE-NAME."
+  (let ((artbuf (current-buffer)))
+    (nnheader-temp-write nil
+      (insert-buffer-substring artbuf)
+      ;; Append newline at end of the buffer as separator, and then
+      ;; save it to file.
+      (goto-char (point-max))
+      (insert "\n")
+      (append-to-file (point-min) (point-max) file-name))))
+
+(defun gnus-narrow-to-page (&optional arg)
+  "Narrow the article buffer to a page.
+If given a numerical ARG, move forward ARG pages."
+  (interactive "P")
+  (setq arg (if arg (prefix-numeric-value arg) 0))
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (goto-char (point-min))
+    (widen)
+    ;; Remove any old next/prev buttons.
+    (when (gnus-visual-p 'page-marker)
+      (let ((buffer-read-only nil))
+	(gnus-remove-text-with-property 'gnus-prev)
+	(gnus-remove-text-with-property 'gnus-next)))
+    (when
+	(cond ((< arg 0)
+	       (re-search-backward page-delimiter nil 'move (1+ (abs arg))))
+	      ((> arg 0)
+	       (re-search-forward page-delimiter nil 'move arg)))
+      (goto-char (match-end 0)))
+    (narrow-to-region
+     (point)
+     (if (re-search-forward page-delimiter nil 'move)
+	 (match-beginning 0)
+       (point)))
+    (when (and (gnus-visual-p 'page-marker)
+	       (not (= (point-min) 1)))
+      (save-excursion
+	(goto-char (point-min))
+	(gnus-insert-prev-page-button)))
+    (when (and (gnus-visual-p 'page-marker)
+	       (< (+ (point-max) 2) (buffer-size)))
+      (save-excursion
+	(goto-char (point-max))
+	(gnus-insert-next-page-button)))))
+
+;; Article mode commands
+
+(defun gnus-article-goto-next-page ()
+  "Show the next page of the article."
+  (interactive)
+  (when (gnus-article-next-page)
+    (goto-char (point-min))
+    (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))))
+
+(defun gnus-article-goto-prev-page ()
+  "Show the next page of the article."
+  (interactive)
+  (if (bobp) (gnus-article-read-summary-keys nil (gnus-character-to-event ?p))
+    (gnus-article-prev-page nil)))
+
+(defun gnus-article-next-page (&optional lines)
+  "Show the next page of the current article.
+If end of article, return non-nil.  Otherwise return nil.
+Argument LINES specifies lines to be scrolled up."
+  (interactive "p")
+  (move-to-window-line -1)
+  (if (save-excursion
+	(end-of-line)
+	(and (pos-visible-in-window-p)	;Not continuation line.
+	     (eobp)))
+      ;; Nothing in this page.
+      (if (or (not gnus-page-broken)
+	      (save-excursion
+		(save-restriction
+		  (widen) (forward-line 1) (eobp)))) ;Real end-of-buffer?
+	  t				;Nothing more.
+	(gnus-narrow-to-page 1)		;Go to next page.
+	nil)
+    ;; More in this page.
+    (let ((scroll-in-place nil))
+      (condition-case ()
+	  (scroll-up lines)
+	(end-of-buffer
+	 ;; Long lines may cause an end-of-buffer error.
+	 (goto-char (point-max)))))
+    (move-to-window-line 0)
+    nil))
+
+(defun gnus-article-prev-page (&optional lines)
+  "Show previous page of current article.
+Argument LINES specifies lines to be scrolled down."
+  (interactive "p")
+  (move-to-window-line 0)
+  (if (and gnus-page-broken
+	   (bobp)
+	   (not (save-restriction (widen) (bobp)))) ;Real beginning-of-buffer?
+      (progn
+	(gnus-narrow-to-page -1)	;Go to previous page.
+	(goto-char (point-max))
+	(recenter -1))
+    (let ((scroll-in-place nil))
+      (prog1
+	  (condition-case ()
+	      (scroll-down lines)
+	    (beginning-of-buffer
+	     (goto-char (point-min))))
+	(move-to-window-line 0)))))
+
+(defun gnus-article-refer-article ()
+  "Read article specified by message-id around point."
+  (interactive)
+  (let ((point (point)))
+    (search-forward ">" nil t)		;Move point to end of "<....>".
+    (if (re-search-backward "\\(<[^<> \t\n]+>\\)" nil t)
+	(let ((message-id (match-string 1)))
+	  (goto-char point)
+	  (set-buffer gnus-summary-buffer)
+	  (gnus-summary-refer-article message-id))
+      (goto-char (point))
+      (error "No references around point"))))
+
+(defun gnus-article-show-summary ()
+  "Reconfigure windows to show summary buffer."
+  (interactive)
+  (if (not (gnus-buffer-live-p gnus-summary-buffer))
+      (error "There is no summary buffer for this article buffer")
+    (gnus-configure-windows 'article)
+    (gnus-summary-goto-subject gnus-current-article)))
+
+(defun gnus-article-describe-briefly ()
+  "Describe article mode commands briefly."
+  (interactive)
+  (gnus-message 6
+		(substitute-command-keys "\\<gnus-article-mode-map>\\[gnus-article-goto-next-page]:Next page	 \\[gnus-article-goto-prev-page]:Prev page  \\[gnus-article-show-summary]:Show summary  \\[gnus-info-find-node]:Run Info  \\[gnus-article-describe-briefly]:This help")))
+
+(defun gnus-article-summary-command ()
+  "Execute the last keystroke in the summary buffer."
+  (interactive)
+  (let ((obuf (current-buffer))
+	(owin (current-window-configuration))
+	func)
+    (switch-to-buffer gnus-summary-buffer 'norecord)
+    (setq func (lookup-key (current-local-map) (this-command-keys)))
+    (call-interactively func)
+    (set-buffer obuf)
+    (set-window-configuration owin)
+    (set-window-point (get-buffer-window (current-buffer)) (point))))
+
+(defun gnus-article-summary-command-nosave ()
+  "Execute the last keystroke in the summary buffer."
+  (interactive)
+  (let (func)
+    (pop-to-buffer gnus-summary-buffer 'norecord)
+    (setq func (lookup-key (current-local-map) (this-command-keys)))
+    (call-interactively func)))
+
+(defun gnus-article-read-summary-keys (&optional arg key not-restore-window)
+  "Read a summary buffer key sequence and execute it from the article buffer."
+  (interactive "P")
+  (let ((nosaves
+	 '("q" "Q"  "c" "r" "R" "\C-c\C-f" "m"	"a" "f" "F"
+	   "Zc" "ZC" "ZE" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP"
+	   "=" "^" "\M-^" "|"))
+	(nosave-but-article
+	 '("A\r"))
+	(nosave-in-article
+	 '("\C-d"))
+	keys)
+    (save-excursion
+      (set-buffer gnus-summary-buffer)
+      (let (gnus-pick-mode)
+	(push (or key last-command-event) unread-command-events)
+	(setq keys (read-key-sequence nil))))
+    (message "")
+
+    (if (or (member keys nosaves)
+	    (member keys nosave-but-article)
+	    (member keys nosave-in-article))
+	(let (func)
+	  (save-window-excursion
+	    (pop-to-buffer gnus-summary-buffer 'norecord)
+	    ;; We disable the pick minor mode commands.
+	    (let (gnus-pick-mode)
+	      (setq func (lookup-key (current-local-map) keys))))
+	  (if (not func)
+	      (ding)
+	    (unless (member keys nosave-in-article)
+	      (set-buffer gnus-summary-buffer))
+	    (call-interactively func))
+	  (when (member keys nosave-but-article)
+	    (pop-to-buffer gnus-article-buffer 'norecord)))
+      ;; These commands should restore window configuration.
+      (let ((obuf (current-buffer))
+	    (owin (current-window-configuration))
+	    (opoint (point))
+	    func in-buffer)
+	(if not-restore-window
+	    (pop-to-buffer gnus-summary-buffer 'norecord)
+	  (switch-to-buffer gnus-summary-buffer 'norecord))
+	(setq in-buffer (current-buffer))
+	;; We disable the pick minor mode commands.
+	(if (setq func (let (gnus-pick-mode)
+			 (lookup-key (current-local-map) keys)))
+	    (call-interactively func)
+	  (ding))
+	(when (eq in-buffer (current-buffer))
+	  (set-buffer obuf)
+	  (unless not-restore-window
+	    (set-window-configuration owin))
+	  (set-window-point (get-buffer-window (current-buffer)) opoint))))))
+
+(defun gnus-article-hide (&optional arg force)
+  "Hide all the gruft in the current article.
+This means that PGP stuff, signatures, cited text and (some)
+headers will be hidden.
+If given a prefix, show the hidden text instead."
+  (interactive (list current-prefix-arg 'force))
+  (gnus-article-hide-headers arg)
+  (gnus-article-hide-pgp arg)
+  (gnus-article-hide-citation-maybe arg force)
+  (gnus-article-hide-signature arg))
+
+(defun gnus-article-maybe-highlight ()
+  "Do some article highlighting if `article-visual' is non-nil."
+  (when (gnus-visual-p 'article-highlight 'highlight)
+    (gnus-article-highlight-some)))
+
+(defun gnus-request-article-this-buffer (article group)
+  "Get an article and insert it into this buffer."
+  (let (do-update-line)
+    (prog1
+	(save-excursion
+	  (erase-buffer)
+	  (gnus-kill-all-overlays)
+	  (setq group (or group gnus-newsgroup-name))
+
+	  ;; Open server if it has closed.
+	  (gnus-check-server (gnus-find-method-for-group group))
+
+	  ;; Using `gnus-request-article' directly will insert the article into
+	  ;; `nntp-server-buffer' - so we'll save some time by not having to
+	  ;; copy it from the server buffer into the article buffer.
+
+	  ;; We only request an article by message-id when we do not have the
+	  ;; headers for it, so we'll have to get those.
+	  (when (stringp article)
+	    (let ((gnus-override-method gnus-refer-article-method))
+	      (gnus-read-header article)))
+
+	  ;; If the article number is negative, that means that this article
+	  ;; doesn't belong in this newsgroup (possibly), so we find its
+	  ;; message-id and request it by id instead of number.
+	  (when (and (numberp article)
+		     gnus-summary-buffer
+		     (get-buffer gnus-summary-buffer)
+		     (buffer-name (get-buffer gnus-summary-buffer)))
+	    (save-excursion
+	      (set-buffer gnus-summary-buffer)
+	      (let ((header (gnus-summary-article-header article)))
+		(when (< article 0)
+		  (cond
+		   ((memq article gnus-newsgroup-sparse)
+		    ;; This is a sparse gap article.
+		    (setq do-update-line article)
+		    (setq article (mail-header-id header))
+		    (let ((gnus-override-method gnus-refer-article-method))
+		      (gnus-read-header article))
+		    (setq gnus-newsgroup-sparse
+			  (delq article gnus-newsgroup-sparse)))
+		   ((vectorp header)
+		    ;; It's a real article.
+		    (setq article (mail-header-id header)))
+		   (t
+		    ;; It is an extracted pseudo-article.
+		    (setq article 'pseudo)
+		    (gnus-request-pseudo-article header))))
+
+		(let ((method (gnus-find-method-for-group
+			       gnus-newsgroup-name)))
+		  (if (not (eq (car method) 'nneething))
+		      ()
+		    (let ((dir (concat (file-name-as-directory (nth 1 method))
+				       (mail-header-subject header))))
+		      (when (file-directory-p dir)
+			(setq article 'nneething)
+			(gnus-group-enter-directory dir))))))))
+
+	  (cond
+	   ;; Refuse to select canceled articles.
+	   ((and (numberp article)
+		 gnus-summary-buffer
+		 (get-buffer gnus-summary-buffer)
+		 (buffer-name (get-buffer gnus-summary-buffer))
+		 (eq (cdr (save-excursion
+			    (set-buffer gnus-summary-buffer)
+			    (assq article gnus-newsgroup-reads)))
+		     gnus-canceled-mark))
+	    nil)
+	   ;; We first check `gnus-original-article-buffer'.
+	   ((and (get-buffer gnus-original-article-buffer)
+		 (numberp article)
+		 (save-excursion
+		   (set-buffer gnus-original-article-buffer)
+		   (and (equal (car gnus-original-article) group)
+			(eq (cdr gnus-original-article) article))))
+	    (insert-buffer-substring gnus-original-article-buffer)
+	    'article)
+	   ;; Check the backlog.
+	   ((and gnus-keep-backlog
+		 (gnus-backlog-request-article group article (current-buffer)))
+	    'article)
+	   ;; Check asynchronous pre-fetch.
+	   ((gnus-async-request-fetched-article group article (current-buffer))
+	    (gnus-async-prefetch-next group article gnus-summary-buffer)
+	    'article)
+	   ;; Check the cache.
+	   ((and gnus-use-cache
+		 (numberp article)
+		 (gnus-cache-request-article article group))
+	    'article)
+	   ;; Get the article and put into the article buffer.
+	   ((or (stringp article) (numberp article))
+	    (let ((gnus-override-method
+		   (and (stringp article) gnus-refer-article-method))
+		  (buffer-read-only nil))
+	      (erase-buffer)
+	      (gnus-kill-all-overlays)
+	      (when (gnus-request-article article group (current-buffer))
+		(when (numberp article)
+		  (gnus-async-prefetch-next group article gnus-summary-buffer)
+		  (when gnus-keep-backlog
+		    (gnus-backlog-enter-article
+		     group article (current-buffer))))
+		'article)))
+	   ;; It was a pseudo.
+	   (t article)))
+
+      ;; Take the article from the original article buffer
+      ;; and place it in the buffer it's supposed to be in.
+      (when (and (get-buffer gnus-article-buffer)
+		 ;;(numberp article)
+		 (equal (buffer-name (current-buffer))
+			(buffer-name (get-buffer gnus-article-buffer))))
+	(save-excursion
+	  (if (get-buffer gnus-original-article-buffer)
+	      (set-buffer (get-buffer gnus-original-article-buffer))
+	    (set-buffer (get-buffer-create gnus-original-article-buffer))
+	    (buffer-disable-undo (current-buffer))
+	    (setq major-mode 'gnus-original-article-mode)
+	    (setq buffer-read-only t)
+	    (gnus-add-current-to-buffer-list))
+	  (let (buffer-read-only)
+	    (erase-buffer)
+	    (insert-buffer-substring gnus-article-buffer))
+	  (setq gnus-original-article (cons group article))))
+
+      ;; Update sparse articles.
+      (when (and do-update-line
+		 (or (numberp article)
+		     (stringp article)))
+	(let ((buf (current-buffer)))
+	  (set-buffer gnus-summary-buffer)
+	  (gnus-summary-update-article do-update-line)
+	  (gnus-summary-goto-subject do-update-line nil t)
+	  (set-window-point (get-buffer-window (current-buffer) t)
+			    (point))
+	  (set-buffer buf))))))
+
+;;;
+;;; Article editing
+;;;
+
+(defcustom gnus-article-edit-mode-hook nil
+  "Hook run in article edit mode buffers."
+  :group 'gnus-article-various
+  :type 'hook)
+
+(defvar gnus-article-edit-done-function nil)
+
+(defvar gnus-article-edit-mode-map nil)
+
+(unless gnus-article-edit-mode-map
+  (setq gnus-article-edit-mode-map (copy-keymap text-mode-map))
+
+  (gnus-define-keys gnus-article-edit-mode-map
+    "\C-c\C-c" gnus-article-edit-done
+    "\C-c\C-k" gnus-article-edit-exit)
+
+  (gnus-define-keys (gnus-article-edit-wash-map
+		     "\C-c\C-w" gnus-article-edit-mode-map)
+    "f" gnus-article-edit-full-stops))
+
+(defun gnus-article-edit-mode ()
+  "Major mode for editing articles.
+This is an extended text-mode.
+
+\\{gnus-article-edit-mode-map}"
+  (interactive)
+  (kill-all-local-variables)
+  (setq major-mode 'gnus-article-edit-mode)
+  (setq mode-name "Article Edit")
+  (use-local-map gnus-article-edit-mode-map)
+  (make-local-variable 'gnus-article-edit-done-function)
+  (make-local-variable 'gnus-prev-winconf)
+  (setq buffer-read-only nil)
+  (buffer-enable-undo)
+  (widen)
+  (run-hooks 'text-mode 'gnus-article-edit-mode-hook))
+
+(defun gnus-article-edit (&optional force)
+  "Edit the current article.
+This will have permanent effect only in mail groups.
+If FORCE is non-nil, allow editing of articles even in read-only
+groups."
+  (interactive "P")
+  (when (and (not force)
+	     (gnus-group-read-only-p))
+    (error "The current newsgroup does not support article editing."))
+  (gnus-article-edit-article
+   `(lambda ()
+      (gnus-summary-edit-article-done
+       ,(or (mail-header-references gnus-current-headers) "")
+       ,(gnus-group-read-only-p) ,gnus-summary-buffer))))
+
+(defun gnus-article-edit-article (exit-func)
+  "Start editing the contents of the current article buffer."
+  (let ((winconf (current-window-configuration)))
+    (set-buffer gnus-article-buffer)
+    (gnus-article-edit-mode)
+    (set-text-properties (point-min) (point-max) nil)
+    (gnus-configure-windows 'edit-article)
+    (setq gnus-article-edit-done-function exit-func)
+    (setq gnus-prev-winconf winconf)
+    (gnus-message 6 "C-c C-c to end edits")))
+
+(defun gnus-article-edit-done ()
+  "Update the article edits and exit."
+  (interactive)
+  (let ((func gnus-article-edit-done-function)
+	(buf (current-buffer))
+	(start (window-start)))
+    (gnus-article-edit-exit)
+    (save-excursion
+      (set-buffer buf)
+      (let ((buffer-read-only nil))
+	(funcall func)))
+    (set-buffer buf)
+    (set-window-start (get-buffer-window buf) start)
+    (set-window-point (get-buffer-window buf) (point))))
+
+(defun gnus-article-edit-exit ()
+  "Exit the article editing without updating."
+  (interactive)
+  ;; We remove all text props from the article buffer.
+  (let ((buf (format "%s" (buffer-string)))
+	(curbuf (current-buffer))
+	(p (point))
+	(window-start (window-start)))
+    (erase-buffer)
+    (insert buf)
+    (let ((winconf gnus-prev-winconf))
+      (gnus-article-mode)
+      ;; The cache and backlog have to be flushed somewhat.
+      (when gnus-use-cache
+	(gnus-cache-update-article
+	 (car gnus-article-current) (cdr gnus-article-current)))
+      (when gnus-keep-backlog
+	(gnus-backlog-remove-article
+	 (car gnus-article-current) (cdr gnus-article-current)))
+      ;; Flush original article as well.
+      (save-excursion
+	(when (get-buffer gnus-original-article-buffer)
+	  (set-buffer gnus-original-article-buffer)
+	  (setq gnus-original-article nil)))
+      (set-window-configuration winconf)
+      ;; Tippy-toe some to make sure that point remains where it was.
+      (let ((buf (current-buffer)))
+	(set-buffer curbuf)
+	(set-window-start (get-buffer-window (current-buffer)) window-start)
+	(goto-char p)
+	(set-buffer buf)))))
+
+(defun gnus-article-edit-full-stops ()
+  "Interactively repair spacing at end of sentences."
+  (interactive)
+  (save-excursion
+    (goto-char (point-min))
+    (search-forward-regexp "^$" nil t)
+    (let ((case-fold-search nil))
+      (query-replace-regexp "\\([.!?][])}]* \\)\\([[({A-Z]\\)" "\\1 \\2"))))
+
+;;;
+;;; Article highlights
+;;;
+
+;; Written by Per Abrahamsen <abraham@iesd.auc.dk>.
+
+;;; Internal Variables:
+
+(defcustom gnus-button-url-regexp "\\b\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\):\\(//[-a-zA-Z0-9_.]+:[0-9]*\\)?\\([-a-zA-Z0-9_=!?#$@~`%&*+|\\/:;.,]\\|\\w\\)+\\([-a-zA-Z0-9_=#$@~`%&*+|\\/]\\|\\w\\)"
+  "Regular expression that matches URLs."
+  :group 'gnus-article-buttons
+  :type 'regexp)
+
+(defcustom gnus-button-alist
+  `(("<\\(url: ?\\)?news:\\([^>\n\t ]*@[^>\n\t ]*\\)>" 0 t
+     gnus-button-message-id 2)
+    ("\\bnews:\\([^>\n\t ]*@[^>\n\t ]*+\\)" 0 t gnus-button-message-id 1)
+    ("\\(\\b<\\(url: ?\\)?news:\\(//\\)?\\([^>\n\t ]*\\)>\\)" 1 t
+     gnus-button-fetch-group 4)
+    ("\\bnews:\\(//\\)?\\([^>\n\t ]+\\)" 0 t gnus-button-fetch-group 2)
+    ("\\bin\\( +article\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2
+     t gnus-button-message-id 3)
+    ("\\(<URL: *\\)mailto: *\\([^> \n\t]+\\)>" 0 t gnus-url-mailto 1)
+    ("\\bmailto:\\([^ \n\t]+\\)" 0 t gnus-url-mailto 2)
+    ;; This is how URLs _should_ be embedded in text...
+    ("<URL: *\\([^>]*\\)>" 0 t gnus-button-embedded-url 1)
+    ;; Raw URLs.
+    (,gnus-button-url-regexp 0 t gnus-button-url 0))
+  "Alist of regexps matching buttons in article bodies.
+
+Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where
+REGEXP: is the string matching text around the button,
+BUTTON: is the number of the regexp grouping actually matching the button,
+FORM: is a lisp expression which must eval to true for the button to
+be added,
+CALLBACK: is the function to call when the user push this button, and each
+PAR: is a number of a regexp grouping whose text will be passed to CALLBACK.
+
+CALLBACK can also be a variable, in that case the value of that
+variable it the real callback function."
+  :group 'gnus-article-buttons
+  :type '(repeat (list regexp
+		       (integer :tag "Button")
+		       (sexp :tag "Form")
+		       (function :tag "Callback")
+		       (repeat :tag "Par"
+			       :inline t
+			       (integer :tag "Regexp group")))))
+
+(defcustom gnus-header-button-alist
+  `(("^\\(References\\|Message-I[Dd]\\):" "<[^>]+>"
+     0 t gnus-button-message-id 0)
+    ("^\\(From\\|Reply-To\\):" ": *\\(.+\\)$" 1 t gnus-button-reply 1)
+    ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+"
+     0 t gnus-button-mailto 0)
+    ("^X-[Uu][Rr][Ll]:" ,gnus-button-url-regexp 0 t gnus-button-url 0)
+    ("^[^:]+:" ,gnus-button-url-regexp 0 t gnus-button-url 0)
+    ("^[^:]+:" "\\(<\\(url: \\)?news:\\([^>\n ]*\\)>\\)" 1 t
+     gnus-button-message-id 3))
+  "Alist of headers and regexps to match buttons in article heads.
+
+This alist is very similar to `gnus-button-alist', except that each
+alist has an additional HEADER element first in each entry:
+
+\(HEADER REGEXP BUTTON FORM CALLBACK PAR)
+
+HEADER is a regexp to match a header.  For a fuller explanation, see
+`gnus-button-alist'."
+  :group 'gnus-article-buttons
+  :group 'gnus-article-headers
+  :type '(repeat (list (regexp :tag "Header")
+		       regexp
+		       (integer :tag "Button")
+		       (sexp :tag "Form")
+		       (function :tag "Callback")
+		       (repeat :tag "Par"
+			       :inline t
+			       (integer :tag "Regexp group")))))
+
+(defvar gnus-button-regexp nil)
+(defvar gnus-button-marker-list nil)
+;; Regexp matching any of the regexps from `gnus-button-alist'.
+
+(defvar gnus-button-last nil)
+;; The value of `gnus-button-alist' when `gnus-button-regexp' was build.
+
+;;; Commands:
+
+(defun gnus-article-push-button (event)
+  "Check text under the mouse pointer for a callback function.
+If the text under the mouse pointer has a `gnus-callback' property,
+call it with the value of the `gnus-data' text property."
+  (interactive "e")
+  (set-buffer (window-buffer (posn-window (event-start event))))
+  (let* ((pos (posn-point (event-start event)))
+         (data (get-text-property pos 'gnus-data))
+	 (fun (get-text-property pos 'gnus-callback)))
+    (when fun
+      (funcall fun data))))
+
+(defun gnus-article-press-button ()
+  "Check text at point for a callback function.
+If the text at point has a `gnus-callback' property,
+call it with the value of the `gnus-data' text property."
+  (interactive)
+  (let* ((data (get-text-property (point) 'gnus-data))
+	 (fun (get-text-property (point) 'gnus-callback)))
+    (when fun
+      (funcall fun data))))
+
+(defun gnus-article-prev-button (n)
+  "Move point to N buttons backward.
+If N is negative, move forward instead."
+  (interactive "p")
+  (gnus-article-next-button (- n)))
+
+(defun gnus-article-next-button (n)
+  "Move point to N buttons forward.
+If N is negative, move backward instead."
+  (interactive "p")
+  (let ((function (if (< n 0) 'previous-single-property-change
+		    'next-single-property-change))
+	(inhibit-point-motion-hooks t)
+	(backward (< n 0))
+	(limit (if (< n 0) (point-min) (point-max))))
+    (setq n (abs n))
+    (while (and (not (= limit (point)))
+		(> n 0))
+      ;; Skip past the current button.
+      (when (get-text-property (point) 'gnus-callback)
+	(goto-char (funcall function (point) 'gnus-callback nil limit)))
+      ;; Go to the next (or previous) button.
+      (gnus-goto-char (funcall function (point) 'gnus-callback nil limit))
+      ;; Put point at the start of the button.
+      (when (and backward (not (get-text-property (point) 'gnus-callback)))
+	(goto-char (funcall function (point) 'gnus-callback nil limit)))
+      ;; Skip past intangible buttons.
+      (when (get-text-property (point) 'intangible)
+	(incf n))
+      (decf n))
+    (unless (zerop n)
+      (gnus-message 5 "No more buttons"))
+    n))
+
+(defun gnus-article-highlight (&optional force)
+  "Highlight current article.
+This function calls `gnus-article-highlight-headers',
+`gnus-article-highlight-citation',
+`gnus-article-highlight-signature', and `gnus-article-add-buttons' to
+do the highlighting.  See the documentation for those functions."
+  (interactive (list 'force))
+  (gnus-article-highlight-headers)
+  (gnus-article-highlight-citation force)
+  (gnus-article-highlight-signature)
+  (gnus-article-add-buttons force)
+  (gnus-article-add-buttons-to-head))
+
+(defun gnus-article-highlight-some (&optional force)
+  "Highlight current article.
+This function calls `gnus-article-highlight-headers',
+`gnus-article-highlight-signature', and `gnus-article-add-buttons' to
+do the highlighting.  See the documentation for those functions."
+  (interactive (list 'force))
+  (gnus-article-highlight-headers)
+  (gnus-article-highlight-signature)
+  (gnus-article-add-buttons))
+
+(defun gnus-article-highlight-headers ()
+  "Highlight article headers as specified by `gnus-header-face-alist'."
+  (interactive)
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (save-restriction
+      (let ((alist gnus-header-face-alist)
+	    (buffer-read-only nil)
+	    (case-fold-search t)
+	    (inhibit-point-motion-hooks t)
+	    entry regexp header-face field-face from hpoints fpoints)
+	(message-narrow-to-head)
+	(while (setq entry (pop alist))
+	  (goto-char (point-min))
+	  (setq regexp (concat "^\\("
+			       (if (string-equal "" (nth 0 entry))
+				   "[^\t ]"
+				 (nth 0 entry))
+			       "\\)")
+		header-face (nth 1 entry)
+		field-face (nth 2 entry))
+	  (while (and (re-search-forward regexp nil t)
+		      (not (eobp)))
+	    (beginning-of-line)
+	    (setq from (point))
+	    (unless (search-forward ":" nil t)
+	      (forward-char 1))
+	    (when (and header-face
+		       (not (memq (point) hpoints)))
+	      (push (point) hpoints)
+	      (gnus-put-text-property from (point) 'face header-face))
+	    (when (and field-face
+		       (not (memq (setq from (point)) fpoints)))
+	      (push from fpoints)
+	      (if (re-search-forward "^[^ \t]" nil t)
+		  (forward-char -2)
+		(goto-char (point-max)))
+	      (gnus-put-text-property from (point) 'face field-face))))))))
+
+(defun gnus-article-highlight-signature ()
+  "Highlight the signature in an article.
+It does this by highlighting everything after
+`gnus-signature-separator' using `gnus-signature-face'."
+  (interactive)
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (let ((buffer-read-only nil)
+	  (inhibit-point-motion-hooks t))
+      (save-restriction
+	(when (and gnus-signature-face
+		   (gnus-article-narrow-to-signature))
+	  (gnus-overlay-put (gnus-make-overlay (point-min) (point-max))
+			    'face gnus-signature-face)
+	  (widen)
+	  (gnus-article-search-signature)
+	  (let ((start (match-beginning 0))
+		(end (set-marker (make-marker) (1+ (match-end 0)))))
+	    (gnus-article-add-button start (1- end) 'gnus-signature-toggle
+				     end)))))))
+
+(defun gnus-button-in-region-p (b e prop)
+  "Say whether PROP exists in the region."
+  (text-property-not-all b e prop nil))
+
+(defun gnus-article-add-buttons (&optional force)
+  "Find external references in the article and make buttons of them.
+\"External references\" are things like Message-IDs and URLs, as
+specified by `gnus-button-alist'."
+  (interactive (list 'force))
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (let ((buffer-read-only nil)
+	  (inhibit-point-motion-hooks t)
+	  (case-fold-search t)
+	  (alist gnus-button-alist)
+	  beg entry regexp)
+      ;; Remove all old markers.
+      (let (marker entry)
+	(while (setq marker (pop gnus-button-marker-list))
+	  (goto-char marker)
+	  (when (setq entry (gnus-button-entry))
+	    (put-text-property (match-beginning (nth 1 entry))
+			       (match-end (nth 1 entry))
+			       'gnus-callback nil))
+	  (set-marker marker nil)))
+      ;; We skip the headers.
+      (goto-char (point-min))
+      (unless (search-forward "\n\n" nil t)
+	(goto-char (point-max)))
+      (setq beg (point))
+      (while (setq entry (pop alist))
+	(setq regexp (car entry))
+	(goto-char beg)
+	(while (re-search-forward regexp nil t)
+	  (let* ((start (and entry (match-beginning (nth 1 entry))))
+		 (end (and entry (match-end (nth 1 entry))))
+		 (from (match-beginning 0)))
+	    (when (and (or (eq t (nth 2 entry))
+			   (eval (nth 2 entry)))
+		       (not (gnus-button-in-region-p
+			     start end 'gnus-callback)))
+	      ;; That optional form returned non-nil, so we add the
+	      ;; button.
+	      (gnus-article-add-button
+	       start end 'gnus-button-push
+	       (car (push (set-marker (make-marker) from)
+			  gnus-button-marker-list))))))))))
+
+;; Add buttons to the head of an article.
+(defun gnus-article-add-buttons-to-head ()
+  "Add buttons to the head of the article."
+  (interactive)
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (let ((buffer-read-only nil)
+	  (inhibit-point-motion-hooks t)
+	  (case-fold-search t)
+	  (alist gnus-header-button-alist)
+	  entry beg end)
+      (nnheader-narrow-to-headers)
+      (while alist
+	;; Each alist entry.
+	(setq entry (car alist)
+	      alist (cdr alist))
+	(goto-char (point-min))
+	(while (re-search-forward (car entry) nil t)
+	  ;; Each header matching the entry.
+	  (setq beg (match-beginning 0))
+	  (setq end (or (and (re-search-forward "^[^ \t]" nil t)
+			     (match-beginning 0))
+			(point-max)))
+	  (goto-char beg)
+	  (while (re-search-forward (nth 1 entry) end t)
+	    ;; Each match within a header.
+	    (let* ((entry (cdr entry))
+		   (start (match-beginning (nth 1 entry)))
+		   (end (match-end (nth 1 entry)))
+		   (form (nth 2 entry)))
+	      (goto-char (match-end 0))
+	      (when (eval form)
+		(gnus-article-add-button
+		 start end (nth 3 entry)
+		 (buffer-substring (match-beginning (nth 4 entry))
+				   (match-end (nth 4 entry)))))))
+	  (goto-char end))))
+    (widen)))
+
+;;; External functions:
+
+(defun gnus-article-add-button (from to fun &optional data)
+  "Create a button between FROM and TO with callback FUN and data DATA."
+  (when gnus-article-button-face
+    (gnus-overlay-put (gnus-make-overlay from to)
+		      'face gnus-article-button-face))
+  (gnus-add-text-properties
+   from to
+   (nconc (and gnus-article-mouse-face
+	       (list gnus-mouse-face-prop gnus-article-mouse-face))
+	  (list 'gnus-callback fun)
+	  (and data (list 'gnus-data data)))))
+
+;;; Internal functions:
+
+(defun gnus-signature-toggle (end)
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (let ((buffer-read-only nil)
+	  (inhibit-point-motion-hooks t))
+      (if (get-text-property end 'invisible)
+	  (gnus-article-unhide-text end (point-max))
+	(gnus-article-hide-text end (point-max) gnus-hidden-properties)))))
+
+(defun gnus-button-entry ()
+  ;; Return the first entry in `gnus-button-alist' matching this place.
+  (let ((alist gnus-button-alist)
+	(entry nil))
+    (while alist
+      (setq entry (pop alist))
+      (if (looking-at (car entry))
+	  (setq alist nil)
+	(setq entry nil)))
+    entry))
+
+(defun gnus-button-push (marker)
+  ;; Push button starting at MARKER.
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (goto-char marker)
+    (let* ((entry (gnus-button-entry))
+	   (inhibit-point-motion-hooks t)
+	   (fun (nth 3 entry))
+	   (args (mapcar (lambda (group)
+			   (let ((string (match-string group)))
+			     (gnus-set-text-properties
+			      0 (length string) nil string)
+			     string))
+			 (nthcdr 4 entry))))
+      (cond
+       ((fboundp fun)
+	(apply fun args))
+       ((and (boundp fun)
+	     (fboundp (symbol-value fun)))
+	(apply (symbol-value fun) args))
+       (t
+	(gnus-message 1 "You must define `%S' to use this button"
+		      (cons fun args)))))))
+
+(defun gnus-button-message-id (message-id)
+  "Fetch MESSAGE-ID."
+  (save-excursion
+    (set-buffer gnus-summary-buffer)
+    (gnus-summary-refer-article message-id)))
+
+(defun gnus-button-fetch-group (address)
+  "Fetch GROUP specified by ADDRESS."
+  (if (not (string-match "[:/]" address))
+      ;; This is just a simple group url.
+      (gnus-group-read-ephemeral-group address gnus-select-method)
+    (if (not (string-match "^\\([^:/]+\\)\\(:\\([^/]+\\)/\\)?\\(.*\\)$"
+			   address))
+	(error "Can't parse %s" address)
+      (gnus-group-read-ephemeral-group
+       (match-string 4 address)
+       `(nntp ,(match-string 1 address)
+	      (nntp-address ,(match-string 1 address))
+	      (nntp-port-number ,(if (match-end 3)
+				     (match-string 3 address)
+				   "nntp")))))))
+
+(defun gnus-split-string (string pattern)
+  "Return a list of substrings of STRING which are separated by PATTERN."
+  (let (parts (start 0))
+    (while (string-match pattern string start)
+      (setq parts (cons (substring string start (match-beginning 0)) parts)
+	    start (match-end 0)))
+    (nreverse (cons (substring string start) parts))))
+
+(defun gnus-url-parse-query-string (query &optional downcase)
+  (let (retval pairs cur key val)
+    (setq pairs (gnus-split-string query "&"))
+    (while pairs
+      (setq cur (car pairs)
+            pairs (cdr pairs))
+      (if (not (string-match "=" cur))
+          nil                           ; Grace
+        (setq key (gnus-url-unhex-string (substring cur 0 (match-beginning 0)))
+              val (gnus-url-unhex-string (substring cur (match-end 0) nil)))
+        (if downcase
+            (setq key (downcase key)))
+        (setq cur (assoc key retval))
+        (if cur
+            (setcdr cur (cons val (cdr cur)))
+          (setq retval (cons (list key val) retval)))))
+    retval))
+
+(defun gnus-url-unhex (x)
+  (if (> x ?9)
+      (if (>= x ?a)
+          (+ 10 (- x ?a))
+        (+ 10 (- x ?A)))
+    (- x ?0)))
+
+(defun gnus-url-unhex-string (str &optional allow-newlines)
+  "Remove %XXX embedded spaces, etc in a url.
+If optional second argument ALLOW-NEWLINES is non-nil, then allow the
+decoding of carriage returns and line feeds in the string, which is normally
+forbidden in URL encoding."
+  (setq str (or str ""))
+  (let ((tmp "")
+        (case-fold-search t))
+    (while (string-match "%[0-9a-f][0-9a-f]" str)
+      (let* ((start (match-beginning 0))
+             (ch1 (gnus-url-unhex (elt str (+ start 1))))
+             (code (+ (* 16 ch1)
+                      (gnus-url-unhex (elt str (+ start 2))))))
+        (setq tmp (concat
+                   tmp (substring str 0 start)
+                   (cond
+                    (allow-newlines
+                     (char-to-string code))
+                    ((or (= code ?\n) (= code ?\r))
+                     " ")
+                    (t (char-to-string code))))
+              str (substring str (match-end 0)))))
+    (setq tmp (concat tmp str))
+    tmp))
+
+(defun gnus-url-mailto (url)
+  ;; Send mail to someone
+  (when (string-match "mailto:/*\\(.*\\)" url)
+    (setq url (substring url (match-beginning 1) nil)))
+  (let (to args source-url subject func)
+    (if (string-match (regexp-quote "?") url)
+        (setq to (gnus-url-unhex-string (substring url 0 (match-beginning 0)))
+              args (gnus-url-parse-query-string
+                    (substring url (match-end 0) nil) t))
+      (setq to (gnus-url-unhex-string url)))
+    (setq args (cons (list "to" to) args)
+          subject (cdr-safe (assoc "subject" args)))
+    (message-mail)
+    (while args
+      (setq func (intern-soft (concat "message-goto-" (downcase (caar args)))))
+      (if (fboundp func)
+          (funcall func)
+        (message-position-on-field (caar args)))
+      (insert (mapconcat 'identity (cdar args) ", "))
+      (setq args (cdr args)))
+    (if subject
+        (message-goto-body)
+      (message-goto-subject))))
+
+(defun gnus-button-mailto (address)
+  ;; Mail to ADDRESS.
+  (set-buffer (gnus-copy-article-buffer))
+  (message-reply address))
+
+(defun gnus-button-reply (address)
+  ;; Reply to ADDRESS.
+  (message-reply address))
+
+(defun gnus-button-url (address)
+  "Browse ADDRESS."
+  (funcall browse-url-browser-function address))
+
+(defun gnus-button-embedded-url (address)
+  "Browse ADDRESS."
+  (funcall browse-url-browser-function (gnus-strip-whitespace address)))
+
+;;; Next/prev buttons in the article buffer.
+
+(defvar gnus-next-page-line-format "%{%(Next page...%)%}\n")
+(defvar gnus-prev-page-line-format "%{%(Previous page...%)%}\n")
+
+(defvar gnus-prev-page-map nil)
+(unless gnus-prev-page-map
+  (setq gnus-prev-page-map (make-sparse-keymap))
+  (define-key gnus-prev-page-map gnus-mouse-2 'gnus-button-prev-page)
+  (define-key gnus-prev-page-map "\r" 'gnus-button-prev-page))
+
+(defun gnus-insert-prev-page-button ()
+  (let ((buffer-read-only nil))
+    (gnus-eval-format
+     gnus-prev-page-line-format nil
+     `(gnus-prev t local-map ,gnus-prev-page-map
+		 gnus-callback gnus-article-button-prev-page))))
+
+(defvar gnus-next-page-map nil)
+(unless gnus-next-page-map
+  (setq gnus-next-page-map (make-keymap))
+  (suppress-keymap gnus-prev-page-map)
+  (define-key gnus-next-page-map gnus-mouse-2 'gnus-button-next-page)
+  (define-key gnus-next-page-map "\r" 'gnus-button-next-page))
+
+(defun gnus-button-next-page ()
+  "Go to the next page."
+  (interactive)
+  (let ((win (selected-window)))
+    (select-window (get-buffer-window gnus-article-buffer t))
+    (gnus-article-next-page)
+    (select-window win)))
+
+(defun gnus-button-prev-page ()
+  "Go to the prev page."
+  (interactive)
+  (let ((win (selected-window)))
+    (select-window (get-buffer-window gnus-article-buffer t))
+    (gnus-article-prev-page)
+    (select-window win)))
+
+(defun gnus-insert-next-page-button ()
+  (let ((buffer-read-only nil))
+    (gnus-eval-format gnus-next-page-line-format nil
+		      `(gnus-next t local-map ,gnus-next-page-map
+				  gnus-callback
+				  gnus-article-button-next-page))))
+
+(defun gnus-article-button-next-page (arg)
+  "Go to the next page."
+  (interactive "P")
+  (let ((win (selected-window)))
+    (select-window (get-buffer-window gnus-article-buffer t))
+    (gnus-article-next-page)
+    (select-window win)))
+
+(defun gnus-article-button-prev-page (arg)
+  "Go to the prev page."
+  (interactive "P")
+  (let ((win (selected-window)))
+    (select-window (get-buffer-window gnus-article-buffer t))
+    (gnus-article-prev-page)
+    (select-window win)))
+
+(gnus-ems-redefine)
+
+(provide 'gnus-art)
+
+(run-hooks 'gnus-art-load-hook)
+
+;;; gnus-art.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-async.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,315 @@
+;;; gnus-async.el --- asynchronous support for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-sum)
+(require 'nntp)
+
+(defgroup gnus-asynchronous nil
+  "Support for asynchronous operations."
+  :group 'gnus)
+
+(defcustom gnus-asynchronous t
+  "*If nil, inhibit all Gnus asynchronicity.
+If non-nil, let the other asynch variables be heeded."
+  :group 'gnus-asynchronous
+  :type 'boolean)
+
+(defcustom gnus-use-article-prefetch 30
+  "*If non-nil, prefetch articles in groups that allow this.
+If a number, prefetch only that many articles forward;
+if t, prefetch as many articles as possible."
+  :group 'gnus-asynchronous
+  :type '(choice (const :tag "off" nil)
+		 (const :tag "all" t)
+		 (integer :tag "some" 0)))
+
+(defcustom gnus-prefetched-article-deletion-strategy '(read exit)
+  "List of symbols that say when to remove articles from the prefetch buffer.
+Possible values in this list are `read', which means that
+articles are removed as they are read, and `exit', which means
+that all articles belonging to a group are removed on exit
+from that group."
+  :group 'gnus-asynchronous
+  :type '(set (const read) (const exit)))
+
+(defcustom gnus-use-header-prefetch nil
+  "*If non-nil, prefetch the headers to the next group."
+  :group 'gnus-asynchronous
+  :type 'boolean)
+
+(defcustom gnus-async-prefetch-article-p 'gnus-async-unread-p
+  "Function called to say whether an article should be prefetched or not.
+The function is called with one parameter -- the article data.
+It should return non-nil if the article is to be prefetched."
+  :group 'gnus-asynchronous
+  :type 'function)
+
+;;; Internal variables.
+
+(defvar gnus-async-prefetch-article-buffer " *Async Prefetch Article*")
+(defvar gnus-async-article-alist nil)
+(defvar gnus-async-article-semaphore '(nil))
+(defvar gnus-async-fetch-list nil)
+
+(defvar gnus-async-prefetch-headers-buffer " *Async Prefetch Headers*")
+(defvar gnus-async-header-prefetched nil)
+
+;;; Utility functions.
+
+(defun gnus-group-asynchronous-p (group)
+  "Say whether GROUP is fetched from a server that supports asynchronicity."
+  (gnus-asynchronous-p (gnus-find-method-for-group group)))
+
+;;; Somewhat bogus semaphores.
+
+(defun gnus-async-get-semaphore (semaphore)
+  "Wait until SEMAPHORE is released."
+  (while (/= (length (nconc (symbol-value semaphore) (list nil))) 2)
+    (sleep-for 1)))
+
+(defun gnus-async-release-semaphore (semaphore)
+  "Release SEMAPHORE."
+  (setcdr (symbol-value semaphore) nil))
+
+(defmacro gnus-async-with-semaphore (&rest forms)
+  `(unwind-protect
+       (progn
+	 (gnus-async-get-semaphore 'gnus-async-article-semaphore)
+	 ,@forms)
+     (gnus-async-release-semaphore 'gnus-async-article-semaphore)))
+
+(put 'gnus-asynch-with-semaphore 'lisp-indent-function 0)
+(put 'gnus-asynch-with-semaphore 'edebug-form-spec '(body))
+
+;;;
+;;; Article prefetch
+;;;
+
+(gnus-add-shutdown 'gnus-async-close 'gnus)
+(defun gnus-async-close ()
+  (gnus-kill-buffer gnus-async-prefetch-article-buffer)
+  (gnus-kill-buffer gnus-async-prefetch-headers-buffer)
+  (setq gnus-async-article-alist nil
+	gnus-async-header-prefetched nil))
+
+(defun gnus-async-set-buffer ()
+  (nnheader-set-temp-buffer gnus-async-prefetch-article-buffer t))
+
+(defun gnus-async-halt-prefetch ()
+  "Stop prefetching."
+  (setq gnus-async-fetch-list nil))
+
+(defun gnus-async-prefetch-next (group article summary)
+  "Possibly prefetch several articles starting with the article after ARTICLE."
+  (when (and (gnus-buffer-live-p summary)
+	     gnus-asynchronous
+	     (gnus-group-asynchronous-p group))
+    (save-excursion
+      (set-buffer gnus-summary-buffer)
+      (let ((next (caadr (gnus-data-find-list article))))
+	(when next
+	  (if (not (fboundp 'run-with-idle-timer))
+	      ;; This is either an older Emacs or XEmacs, so we
+	      ;; do this, which leads to slightly slower article
+	      ;; buffer display.
+	      (gnus-async-prefetch-article group next summary)
+	    (run-with-idle-timer
+	     0.1 nil 'gnus-async-prefetch-article group next summary)))))))
+
+(defun gnus-async-prefetch-article (group article summary &optional next)
+  "Possibly prefetch several articles starting with ARTICLE."
+  (if (not (gnus-buffer-live-p summary))
+      (gnus-async-with-semaphore
+       (setq gnus-async-fetch-list nil))
+    (when (and gnus-asynchronous
+	       (gnus-alive-p))
+      (when next
+	(gnus-async-with-semaphore
+	 (pop gnus-async-fetch-list)))
+      (let ((do-fetch next)
+	    (do-message t)) ;(eq major-mode 'gnus-summary-mode)))
+	(when (and (gnus-group-asynchronous-p group)
+		   (gnus-buffer-live-p summary)
+		   (or (not next)
+		       gnus-async-fetch-list))
+	  (gnus-async-with-semaphore
+	   (unless next
+	     (setq do-fetch (not gnus-async-fetch-list))
+	     ;; Nix out any outstanding requests.
+	     (setq gnus-async-fetch-list nil)
+	     ;; Fill in the new list.
+	     (let ((n gnus-use-article-prefetch)
+		   (data (gnus-data-find-list article))
+		   d)
+	       (while (and (setq d (pop data))
+			   (if (numberp n)
+			       (natnump (decf n))
+			     n))
+		 (unless (or (gnus-async-prefetched-article-entry
+			      group (setq article (gnus-data-number d)))
+			     (not (natnump article))
+			     (not (funcall gnus-async-prefetch-article-p d)))
+		   ;; Not already fetched -- so we add it to the list.
+		   (push article gnus-async-fetch-list)))
+	       (setq gnus-async-fetch-list
+		     (nreverse gnus-async-fetch-list))))
+
+	   (when do-fetch
+	     (setq article (car gnus-async-fetch-list))))
+
+	  (when (and do-fetch article)
+	    ;; We want to fetch some more articles.
+	    (save-excursion
+	      (set-buffer summary)
+	      (let (mark)
+		(gnus-async-set-buffer)
+		(goto-char (point-max))
+		(setq mark (point-marker))
+		(let ((nnheader-callback-function
+		       (gnus-make-async-article-function
+			group article mark summary next))
+		      (nntp-server-buffer
+		       (get-buffer gnus-async-prefetch-article-buffer)))
+		  (when do-message
+		    (gnus-message 9 "Prefetching article %d in group %s"
+				  article group))
+		  (gnus-request-article article group))))))))))
+
+(defun gnus-make-async-article-function (group article mark summary next)
+  "Return a callback function."
+  `(lambda (arg)
+     (save-excursion
+       (when arg
+	 (gnus-async-set-buffer)
+	 (gnus-async-with-semaphore
+	  (push (list ',(intern (format "%s-%d" group article))
+		      ,mark (set-marker (make-marker) (point-max))
+		      ,group ,article)
+		gnus-async-article-alist)))
+       (if (not (gnus-buffer-live-p ,summary))
+	   (gnus-async-with-semaphore
+	    (setq gnus-async-fetch-list nil))
+	 (gnus-async-prefetch-article ,group ,next ,summary t)))))
+
+(defun gnus-async-unread-p (data)
+  "Return non-nil if DATA represents an unread article."
+  (gnus-data-unread-p data))
+
+(defun gnus-async-request-fetched-article (group article buffer)
+  "See whether we have ARTICLE from GROUP and put it in BUFFER."
+  (when (numberp article)
+    (let ((entry (gnus-async-prefetched-article-entry group article)))
+      (when entry
+	(save-excursion
+	  (gnus-async-set-buffer)
+	  (copy-to-buffer buffer (cadr entry) (caddr entry))
+	  ;; Remove the read article from the prefetch buffer.
+	  (when (memq 'read gnus-prefetched-article-deletion-strategy)
+	    (gnus-async-delete-prefected-entry entry))
+	  t)))))
+
+(defun gnus-async-delete-prefected-entry (entry)
+  "Delete ENTRY from buffer and alist."
+  (ignore-errors
+    (delete-region (cadr entry) (caddr entry))
+    (set-marker (cadr entry) nil)
+    (set-marker (caddr entry) nil))
+  (gnus-async-with-semaphore
+   (setq gnus-async-article-alist
+	 (delq entry gnus-async-article-alist))))
+
+(defun gnus-async-prefetch-remove-group (group)
+  "Remove all articles belonging to GROUP from the prefetch buffer."
+  (when (and (gnus-group-asynchronous-p group)
+	     (memq 'exit gnus-prefetched-article-deletion-strategy))
+    (let ((alist gnus-async-article-alist))
+      (save-excursion
+	(gnus-async-set-buffer)
+	(while alist
+	  (when (equal group (nth 3 (car alist)))
+	    (gnus-async-delete-prefected-entry (car alist)))
+	  (pop alist))))))
+
+(defun gnus-async-prefetched-article-entry (group article)
+  "Return the entry for ARTICLE in GROUP iff it has been prefetched."
+  (let ((entry (assq (intern (format "%s-%d" group article))
+		     gnus-async-article-alist)))
+    ;; Perhaps something has emptied the buffer?
+    (if (and entry
+	     (= (cadr entry) (caddr entry)))
+	(progn
+	  (ignore-errors
+	    (set-marker (cadr entry) nil)
+	    (set-marker (caddr entry) nil))
+	  (setq gnus-async-article-alist
+		(delq entry gnus-async-article-alist))
+	  nil)
+      entry)))
+
+;;;
+;;; Header prefetch
+;;;
+
+(defun gnus-async-prefetch-headers (group)
+  "Prefetch the headers for group GROUP."
+  (save-excursion
+    (let (unread)
+      (when (and gnus-use-header-prefetch
+		 gnus-asynchronous
+		 (gnus-group-asynchronous-p group)
+		 (listp gnus-async-header-prefetched)
+		 (setq unread (gnus-list-of-unread-articles group)))
+	;; Mark that a fetch is in progress.
+	(setq gnus-async-header-prefetched t)
+	(nnheader-set-temp-buffer gnus-async-prefetch-headers-buffer t)
+	(erase-buffer)
+	(let ((nntp-server-buffer (current-buffer))
+	      (nnheader-callback-function
+	       `(lambda (arg)
+		  (setq gnus-async-header-prefetched
+			,(cons group unread)))))
+	  (gnus-retrieve-headers unread group gnus-fetch-old-headers))))))
+
+(defun gnus-async-retrieve-fetched-headers (articles group)
+  "See whether we have prefetched headers."
+  (when (and gnus-use-header-prefetch
+	     (gnus-group-asynchronous-p group)
+	     (listp gnus-async-header-prefetched)
+	     (equal group (car gnus-async-header-prefetched))
+	     (equal articles (cdr gnus-async-header-prefetched)))
+    (save-excursion
+      (nnheader-set-temp-buffer gnus-async-prefetch-headers-buffer t)
+      (nntp-decode-text)
+      (copy-to-buffer nntp-server-buffer (point-min) (point-max))
+      (erase-buffer)
+      (setq gnus-async-header-prefetched nil)
+      t)))
+
+(provide 'gnus-async)
+
+;;; gnus-async.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-audio.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,132 @@
+;;; gnus-audio.el --- Sound effects for Gnus
+;; Copyright (C) 1996 Free Software Foundation
+
+;; Author: Steven L. Baur <steve@miranova.com>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+;; This file provides access to sound effects in Gnus.
+;; Prerelease:  This file is partially stripped to support earcons.el
+;; You can safely ignore most of it until Red Gnus.  **Evil Laugh**
+;;; Code:
+
+(when (null (boundp 'running-xemacs))
+  (defvar running-xemacs (string-match "XEmacs\\|Lucid" emacs-version)))
+
+(require 'nnheader)
+(eval-when-compile (require 'cl))
+
+(defvar gnus-audio-inline-sound
+  (and (fboundp 'device-sound-enabled-p)
+       (device-sound-enabled-p))
+  "When t, we will not spawn a subprocess to play sounds.")
+
+(defvar gnus-audio-directory (nnheader-find-etc-directory "sounds")
+  "The directory containing the Sound Files.")
+
+(defvar gnus-audio-au-player "/usr/bin/showaudio"
+  "Executable program for playing sun AU format sound files")
+(defvar gnus-audio-wav-player "/usr/local/bin/play"
+  "Executable program for playing WAV files")
+
+
+;;; The following isn't implemented yet.  Wait for Red Gnus.
+;(defvar gnus-audio-effects-enabled t
+;  "When t, Gnus will use sound effects.")
+;(defvar gnus-audio-enable-hooks nil
+;  "Functions run when enabling sound effects.")
+;(defvar gnus-audio-disable-hooks nil
+;  "Functions run when disabling sound effects.")
+;(defvar gnus-audio-theme-song nil
+;  "Theme song for Gnus.")
+;(defvar gnus-audio-enter-group nil
+;  "Sound effect played when selecting a group.")
+;(defvar gnus-audio-exit-group nil
+;  "Sound effect played when exiting a group.")
+;(defvar gnus-audio-score-group nil
+;  "Sound effect played when scoring a group.")
+;(defvar gnus-audio-busy-sound nil
+;  "Sound effect played when going into a ... sequence.")
+
+
+;;;###autoload
+					;(defun gnus-audio-enable-sound ()
+;  "Enable Sound Effects for Gnus."
+;  (interactive)
+;  (setq gnus-audio-effects-enabled t)
+;  (run-hooks gnus-audio-enable-hooks))
+
+;;;###autoload
+					;(defun gnus-audio-disable-sound ()
+;  "Disable Sound Effects for Gnus."
+;  (interactive)
+;  (setq gnus-audio-effects-enabled nil)
+;  (run-hooks gnus-audio-disable-hooks))
+
+;;;###autoload
+(defun gnus-audio-play (file)
+  "Play a sound through the speaker."
+  (interactive)
+  (let ((sound-file (if (file-exists-p file)
+			file
+		      (concat gnus-audio-directory file))))
+    (when (file-exists-p sound-file)
+      (if gnus-audio-inline-sound
+	  (play-sound-file sound-file)
+	(cond ((string-match "\\.wav$" sound-file)
+	       (call-process gnus-audio-wav-player
+			     sound-file
+			     0
+			     nil
+			     sound-file))
+	      ((string-match "\\.au$" sound-file)
+	       (call-process gnus-audio-au-player
+			     sound-file
+			     0
+			     nil
+			     sound-file)))))))
+
+
+;;; The following isn't implemented yet, wait for Red Gnus
+					;(defun gnus-audio-startrek-sounds ()
+;  "Enable sounds from Star Trek the original series."
+;  (interactive)
+;  (setq gnus-audio-busy-sound "working.au")
+;  (setq gnus-audio-enter-group "bulkhead_door.au")
+;  (setq gnus-audio-exit-group "bulkhead_door.au")
+;  (setq gnus-audio-score-group "ST_laser.au")
+;  (setq gnus-audio-theme-song "startrek.au")
+;  (add-hook 'gnus-select-group-hook 'gnus-audio-startrek-select-group)
+;  (add-hook 'gnus-exit-group-hook 'gnus-audio-startrek-exit-group))
+;;;***
+
+(defvar gnus-startup-jingle "Tuxedomoon.Jingle4.au"
+  "Name of the Gnus startup jingle file.")
+
+(defun gnus-play-jingle ()
+  "Play the Gnus startup jingle, unless that's inhibited."
+  (interactive)
+  (gnus-audio-play gnus-startup-jingle))
+
+(provide 'gnus-audio)
+
+(run-hooks 'gnus-audio-load-hook)
+
+;;; gnus-audio.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-bcklg.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,152 @@
+;;; gnus-bcklg.el --- backlog functions for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+
+;;;
+;;; Buffering of read articles.
+;;;
+
+(defvar gnus-backlog-buffer " *Gnus Backlog*")
+(defvar gnus-backlog-articles nil)
+(defvar gnus-backlog-hashtb nil)
+
+(defun gnus-backlog-buffer ()
+  "Return the backlog buffer."
+  (or (get-buffer gnus-backlog-buffer)
+      (save-excursion
+	(set-buffer (get-buffer-create gnus-backlog-buffer))
+	(buffer-disable-undo (current-buffer))
+	(setq buffer-read-only t)
+	(gnus-add-current-to-buffer-list)
+	(get-buffer gnus-backlog-buffer))))
+
+(defun gnus-backlog-setup ()
+  "Initialize backlog variables."
+  (unless gnus-backlog-hashtb
+    (setq gnus-backlog-hashtb (gnus-make-hashtable 1024))))
+
+(gnus-add-shutdown 'gnus-backlog-shutdown 'gnus)
+
+(defun gnus-backlog-shutdown ()
+  "Clear all backlog variables and buffers."
+  (when (get-buffer gnus-backlog-buffer)
+    (kill-buffer gnus-backlog-buffer))
+  (setq gnus-backlog-hashtb nil
+	gnus-backlog-articles nil))
+
+(defun gnus-backlog-enter-article (group number buffer)
+  (gnus-backlog-setup)
+  (let ((ident (intern (concat group ":" (int-to-string number))
+		       gnus-backlog-hashtb))
+	b)
+    (if (memq ident gnus-backlog-articles)
+	()				; It's already kept.
+      ;; Remove the oldest article, if necessary.
+      (and (numberp gnus-keep-backlog)
+	   (>= (length gnus-backlog-articles) gnus-keep-backlog)
+	   (gnus-backlog-remove-oldest-article))
+      (push ident gnus-backlog-articles)
+      ;; Insert the new article.
+      (save-excursion
+	(set-buffer (gnus-backlog-buffer))
+	(let (buffer-read-only)
+	  (goto-char (point-max))
+	  (unless (bolp)
+	    (insert "\n"))
+	  (setq b (point))
+	  (insert-buffer-substring buffer)
+	  ;; Tag the beginning of the article with the ident.
+	  (gnus-put-text-property b (1+ b) 'gnus-backlog ident))))))
+
+(defun gnus-backlog-remove-oldest-article ()
+  (save-excursion
+    (set-buffer (gnus-backlog-buffer))
+    (goto-char (point-min))
+    (if (zerop (buffer-size))
+	()				; The buffer is empty.
+      (let ((ident (get-text-property (point) 'gnus-backlog))
+	    buffer-read-only)
+	;; Remove the ident from the list of articles.
+	(when ident
+	  (setq gnus-backlog-articles (delq ident gnus-backlog-articles)))
+	;; Delete the article itself.
+	(delete-region
+	 (point) (next-single-property-change
+		  (1+ (point)) 'gnus-backlog nil (point-max)))))))
+
+(defun gnus-backlog-remove-article (group number)
+  "Remove article NUMBER in GROUP from the backlog."
+  (when (numberp number)
+    (gnus-backlog-setup)
+    (let ((ident (intern (concat group ":" (int-to-string number))
+			 gnus-backlog-hashtb))
+	  beg end)
+      (when (memq ident gnus-backlog-articles)
+	;; It was in the backlog.
+	(save-excursion
+	  (set-buffer (gnus-backlog-buffer))
+	  (let (buffer-read-only)
+	    (when (setq beg (text-property-any
+			     (point-min) (point-max) 'gnus-backlog
+			     ident))
+	      ;; Find the end (i. e., the beginning of the next article).
+	      (setq end
+		    (next-single-property-change
+		     (1+ beg) 'gnus-backlog (current-buffer) (point-max)))
+	      (delete-region beg end)
+	      ;; Return success.
+	      t)))))))
+
+(defun gnus-backlog-request-article (group number buffer)
+  (when (numberp number)
+    (gnus-backlog-setup)
+    (let ((ident (intern (concat group ":" (int-to-string number))
+			 gnus-backlog-hashtb))
+	  beg end)
+      (when (memq ident gnus-backlog-articles)
+	;; It was in the backlog.
+	(save-excursion
+	  (set-buffer (gnus-backlog-buffer))
+	  (if (not (setq beg (text-property-any
+			      (point-min) (point-max) 'gnus-backlog
+			      ident)))
+	      ;; It wasn't in the backlog after all.
+	      (ignore
+	       (setq gnus-backlog-articles (delq ident gnus-backlog-articles)))
+	    ;; Find the end (i. e., the beginning of the next article).
+	    (setq end
+		  (next-single-property-change
+		   (1+ beg) 'gnus-backlog (current-buffer) (point-max)))))
+	(let ((buffer-read-only nil))
+	  (erase-buffer)
+	  (insert-buffer-substring gnus-backlog-buffer beg end)
+	  t)))))
+
+(provide 'gnus-bcklg)
+
+;;; gnus-bcklg.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-cache.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,657 @@
+;;; gnus-cache.el --- cache interface for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-int)
+(require 'gnus-range)
+(require 'gnus-start)
+(eval-when-compile
+  (require 'gnus-sum))
+
+(defgroup gnus-cache nil
+  "Cache interface."
+  :group 'gnus)
+
+(defcustom gnus-cache-directory
+  (nnheader-concat gnus-directory "cache/")
+  "*The directory where cached articles will be stored."
+  :group 'gnus-cache
+  :type 'directory)
+
+(defcustom gnus-cache-active-file
+  (concat (file-name-as-directory gnus-cache-directory) "active")
+  "*The cache active file."
+  :group 'gnus-cache
+  :type 'file)
+
+(defcustom gnus-cache-enter-articles '(ticked dormant)
+  "Classes of articles to enter into the cache."
+  :group 'gnus-cache
+  :type '(set (const ticked) (const dormant) (const unread) (const read)))
+
+(defcustom gnus-cache-remove-articles '(read)
+  "Classes of articles to remove from the cache."
+  :group 'gnus-cache
+  :type '(set (const ticked) (const dormant) (const unread) (const read)))
+
+(defcustom gnus-uncacheable-groups nil
+  "*Groups that match this regexp will not be cached.
+
+If you want to avoid caching your nnml groups, you could set this
+variable to \"^nnml\"."
+  :group 'gnus-cache
+  :type '(choice (const :tag "off" nil)
+		 regexp))
+
+
+
+;;; Internal variables.
+
+(defvar gnus-cache-removable-articles nil)
+(defvar gnus-cache-buffer nil)
+(defvar gnus-cache-active-hashtb nil)
+(defvar gnus-cache-active-altered nil)
+
+(eval-and-compile
+  (autoload 'nnml-generate-nov-databases-1 "nnml")
+  (autoload 'nnvirtual-find-group-art "nnvirtual"))
+
+
+
+;;; Functions called from Gnus.
+
+(defun gnus-cache-open ()
+  "Initialize the cache."
+  (when (or (file-exists-p gnus-cache-directory)
+	    (and gnus-use-cache
+		 (not (eq gnus-use-cache 'passive))))
+    (gnus-cache-read-active)))
+
+;; Complexities of byte-compiling make this kludge necessary.  Eeek.
+(ignore-errors
+  (gnus-add-shutdown 'gnus-cache-close 'gnus))
+
+(defun gnus-cache-close ()
+  "Shut down the cache."
+  (gnus-cache-write-active)
+  (gnus-cache-save-buffers)
+  (setq gnus-cache-active-hashtb nil))
+
+(defun gnus-cache-save-buffers ()
+  ;; save the overview buffer if it exists and has been modified
+  ;; delete empty cache subdirectories
+  (when gnus-cache-buffer
+    (let ((buffer (cdr gnus-cache-buffer))
+	  (overview-file (gnus-cache-file-name
+			  (car gnus-cache-buffer) ".overview")))
+      ;; write the overview only if it was modified
+      (when (buffer-modified-p buffer)
+	(save-excursion
+	  (set-buffer buffer)
+	  (if (> (buffer-size) 0)
+	      ;; Non-empty overview, write it to a file.
+	      (gnus-write-buffer overview-file)
+	    ;; Empty overview file, remove it
+	    (when (file-exists-p overview-file)
+	      (delete-file overview-file))
+	    ;; If possible, remove group's cache subdirectory.
+	    (condition-case nil
+		;; FIXME: we can detect the error type and warn the user
+		;; of any inconsistencies (articles w/o nov entries?).
+		;; for now, just be conservative...delete only if safe -- sj
+		(delete-directory (file-name-directory overview-file))
+	      (error nil)))))
+      ;; Kill the buffer -- it's either unmodified or saved.
+      (gnus-kill-buffer buffer)
+      (setq gnus-cache-buffer nil))))
+
+(defun gnus-cache-possibly-enter-article
+  (group article headers ticked dormant unread &optional force)
+  (when (and (or force (not (eq gnus-use-cache 'passive)))
+	     (numberp article)
+	     (> article 0)
+	     (vectorp headers))		; This might be a dummy article.
+    ;; If this is a virtual group, we find the real group.
+    (when (gnus-virtual-group-p group)
+      (let ((result (nnvirtual-find-group-art
+		     (gnus-group-real-name group) article)))
+	(setq group (car result)
+	      headers (copy-sequence headers))
+	(mail-header-set-number headers (cdr result))))
+    (let ((number (mail-header-number headers))
+	  file dir)
+      (when (and (> number 0)		; Reffed article.
+		 (or force
+		     (and (or (not gnus-uncacheable-groups)
+			      (not (string-match
+				    gnus-uncacheable-groups group)))
+			  (gnus-cache-member-of-class
+			   gnus-cache-enter-articles ticked dormant unread)))
+		 (not (file-exists-p (setq file (gnus-cache-file-name
+						 group number)))))
+	;; Possibly create the cache directory.
+	(gnus-make-directory (setq dir (file-name-directory file)))
+	;; Save the article in the cache.
+	(if (file-exists-p file)
+	    t				; The article already is saved.
+	  (save-excursion
+	    (set-buffer nntp-server-buffer)
+	    (let ((gnus-use-cache nil))
+	      (gnus-request-article-this-buffer number group))
+	    (when (> (buffer-size) 0)
+	      (gnus-write-buffer file)
+	      (gnus-cache-change-buffer group)
+	      (set-buffer (cdr gnus-cache-buffer))
+	      (goto-char (point-max))
+	      (forward-line -1)
+	      (while (condition-case ()
+			 (when (not (bobp))
+			   (> (read (current-buffer)) number))
+		       (error
+			;; The line was malformed, so we just remove it!!
+			(gnus-delete-line)
+			t))
+		(forward-line -1))
+	      (if (bobp)
+		  (if (not (eobp))
+		      (progn
+			(beginning-of-line)
+			(when (< (read (current-buffer)) number)
+			  (forward-line 1)))
+		    (beginning-of-line))
+		(forward-line 1))
+	      (beginning-of-line)
+	      ;; [number subject from date id references chars lines xref]
+	      (insert (format "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n"
+			      (mail-header-number headers)
+			      (mail-header-subject headers)
+			      (mail-header-from headers)
+			      (mail-header-date headers)
+			      (mail-header-id headers)
+			      (or (mail-header-references headers) "")
+			      (or (mail-header-chars headers) "")
+			      (or (mail-header-lines headers) "")
+			      (or (mail-header-xref headers) "")))
+	      ;; Update the active info.
+	      (set-buffer gnus-summary-buffer)
+	      (gnus-cache-update-active group number)
+	      (push article gnus-newsgroup-cached)
+	      (gnus-summary-update-secondary-mark article))
+	    t))))))
+
+(defun gnus-cache-enter-remove-article (article)
+  "Mark ARTICLE for later possible removal."
+  (when article
+    (push article gnus-cache-removable-articles)))
+
+(defun gnus-cache-possibly-remove-articles ()
+  "Possibly remove some of the removable articles."
+  (if (not (gnus-virtual-group-p gnus-newsgroup-name))
+      (gnus-cache-possibly-remove-articles-1)
+    (let ((arts gnus-cache-removable-articles)
+	  ga)
+      (while arts
+	(when (setq ga (nnvirtual-find-group-art
+			(gnus-group-real-name gnus-newsgroup-name) (pop arts)))
+	  (let ((gnus-cache-removable-articles (list (cdr ga)))
+		(gnus-newsgroup-name (car ga)))
+	    (gnus-cache-possibly-remove-articles-1)))))
+    (setq gnus-cache-removable-articles nil)))
+
+(defun gnus-cache-possibly-remove-articles-1 ()
+  "Possibly remove some of the removable articles."
+  (unless (eq gnus-use-cache 'passive)
+    (let ((articles gnus-cache-removable-articles)
+	  (cache-articles gnus-newsgroup-cached)
+	  article)
+      (gnus-cache-change-buffer gnus-newsgroup-name)
+      (while articles
+	(when (memq (setq article (pop articles)) cache-articles)
+	  ;; The article was in the cache, so we see whether we are
+	  ;; supposed to remove it from the cache.
+	  (gnus-cache-possibly-remove-article
+	   article (memq article gnus-newsgroup-marked)
+	   (memq article gnus-newsgroup-dormant)
+	   (or (memq article gnus-newsgroup-unreads)
+	       (memq article gnus-newsgroup-unselected))))))
+    ;; The overview file might have been modified, save it
+    ;; safe because we're only called at group exit anyway.
+    (gnus-cache-save-buffers)))
+
+(defun gnus-cache-request-article (article group)
+  "Retrieve ARTICLE in GROUP from the cache."
+  (let ((file (gnus-cache-file-name group article))
+	(buffer-read-only nil))
+    (when (file-exists-p file)
+      (erase-buffer)
+      (gnus-kill-all-overlays)
+      (insert-file-contents file)
+      t)))
+
+(defun gnus-cache-possibly-alter-active (group active)
+  "Alter the ACTIVE info for GROUP to reflect the articles in the cache."
+  (when (equal group "no.norsk") (error "hie"))
+  (when gnus-cache-active-hashtb
+    (let ((cache-active (gnus-gethash group gnus-cache-active-hashtb)))
+      (and cache-active
+	   (< (car cache-active) (car active))
+	   (setcar active (car cache-active)))
+      (and cache-active
+	   (> (cdr cache-active) (cdr active))
+	   (setcdr active (cdr cache-active))))))
+
+(defun gnus-cache-retrieve-headers (articles group &optional fetch-old)
+  "Retrieve the headers for ARTICLES in GROUP."
+  (let ((cached
+	 (setq gnus-newsgroup-cached (gnus-cache-articles-in-group group))))
+    (if (not cached)
+	;; No cached articles here, so we just retrieve them
+	;; the normal way.
+	(let ((gnus-use-cache nil))
+	  (gnus-retrieve-headers articles group fetch-old))
+      (let ((uncached-articles (gnus-sorted-intersection
+				(gnus-sorted-complement articles cached)
+				articles))
+	    (cache-file (gnus-cache-file-name group ".overview"))
+	    type)
+	;; We first retrieve all the headers that we don't have in
+	;; the cache.
+	(let ((gnus-use-cache nil))
+	  (when uncached-articles
+	    (setq type (and articles
+			    (gnus-retrieve-headers
+			     uncached-articles group fetch-old)))))
+	(gnus-cache-save-buffers)
+	;; Then we insert the cached headers.
+	(save-excursion
+	  (cond
+	   ((not (file-exists-p cache-file))
+	    ;; There are no cached headers.
+	    type)
+	   ((null type)
+	    ;; There were no uncached headers (or retrieval was
+	    ;; unsuccessful), so we use the cached headers exclusively.
+	    (set-buffer nntp-server-buffer)
+	    (erase-buffer)
+	    (insert-file-contents cache-file)
+	    'nov)
+	   ((eq type 'nov)
+	    ;; We have both cached and uncached NOV headers, so we
+	    ;; braid them.
+	    (gnus-cache-braid-nov group cached)
+	    type)
+	   (t
+	    ;; We braid HEADs.
+	    (gnus-cache-braid-heads group (gnus-sorted-intersection
+					   cached articles))
+	    type)))))))
+
+(defun gnus-cache-enter-article (&optional n)
+  "Enter the next N articles into the cache.
+If not given a prefix, use the process marked articles instead.
+Returns the list of articles entered."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((articles (gnus-summary-work-articles n))
+	article out)
+    (while (setq article (pop articles))
+      (if (natnump article)
+	  (when (gnus-cache-possibly-enter-article
+		 gnus-newsgroup-name article
+		 (gnus-summary-article-header article)
+		 nil nil nil t)
+	    (push article out))
+	(gnus-message 2 "Can't cache article %d" article))
+      (gnus-summary-remove-process-mark article)
+      (gnus-summary-update-secondary-mark article))
+    (gnus-summary-next-subject 1)
+    (gnus-summary-position-point)
+    (nreverse out)))
+
+(defun gnus-cache-remove-article (n)
+  "Remove the next N articles from the cache.
+If not given a prefix, use the process marked articles instead.
+Returns the list of articles removed."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (gnus-cache-change-buffer gnus-newsgroup-name)
+  (let ((articles (gnus-summary-work-articles n))
+	article out)
+    (while articles
+      (setq article (pop articles))
+      (when (gnus-cache-possibly-remove-article article nil nil nil t)
+	(push article out))
+      (gnus-summary-remove-process-mark article)
+      (gnus-summary-update-secondary-mark article))
+    (gnus-summary-next-subject 1)
+    (gnus-summary-position-point)
+    (nreverse out)))
+
+(defun gnus-cached-article-p (article)
+  "Say whether ARTICLE is cached in the current group."
+  (memq article gnus-newsgroup-cached))
+
+(defun gnus-summary-insert-cached-articles ()
+  "Insert all the articles cached for this group into the current buffer."
+  (interactive)
+  (let ((cached gnus-newsgroup-cached)
+	(gnus-verbose (max 6 gnus-verbose)))
+    (unless cached
+      (error "No cached articles for this group"))
+    (while cached
+      (gnus-summary-goto-subject (pop cached) t))))
+
+;;; Internal functions.
+
+(defun gnus-cache-change-buffer (group)
+  (and gnus-cache-buffer
+       ;; See if the current group's overview cache has been loaded.
+       (or (string= group (car gnus-cache-buffer))
+	   ;; Another overview cache is current, save it.
+	   (gnus-cache-save-buffers)))
+  ;; if gnus-cache buffer is nil, create it
+  (unless gnus-cache-buffer
+    ;; Create cache buffer
+    (save-excursion
+      (setq gnus-cache-buffer
+	    (cons group
+		  (set-buffer (get-buffer-create " *gnus-cache-overview*"))))
+      (buffer-disable-undo (current-buffer))
+      ;; Insert the contents of this group's cache overview.
+      (erase-buffer)
+      (let ((file (gnus-cache-file-name group ".overview")))
+	(when (file-exists-p file)
+	  (nnheader-insert-file-contents file)))
+      ;; We have a fresh (empty/just loaded) buffer,
+      ;; mark it as unmodified to save a redundant write later.
+      (set-buffer-modified-p nil))))
+
+;; Return whether an article is a member of a class.
+(defun gnus-cache-member-of-class (class ticked dormant unread)
+  (or (and ticked (memq 'ticked class))
+      (and dormant (memq 'dormant class))
+      (and unread (memq 'unread class))
+      (and (not unread) (not ticked) (not dormant) (memq 'read class))))
+
+(defun gnus-cache-file-name (group article)
+  (concat (file-name-as-directory gnus-cache-directory)
+	  (file-name-as-directory
+	   (nnheader-translate-file-chars
+	    (if (gnus-use-long-file-name 'not-cache)
+		group
+	      (let ((group (nnheader-replace-chars-in-string group ?/ ?_)))
+		;; Translate the first colon into a slash.
+		(when (string-match ":" group)
+		  (aset group (match-beginning 0) ?/))
+		(nnheader-replace-chars-in-string group ?. ?/)))))
+	  (if (stringp article) article (int-to-string article))))
+
+(defun gnus-cache-update-article (group article)
+  "If ARTICLE is in the cache, remove it and re-enter it."
+  (when (gnus-cache-possibly-remove-article article nil nil nil t)
+    (let ((gnus-use-cache nil))
+      (gnus-cache-possibly-enter-article
+       gnus-newsgroup-name article (gnus-summary-article-header article)
+       nil nil nil t))))
+
+(defun gnus-cache-possibly-remove-article (article ticked dormant unread
+						   &optional force)
+  "Possibly remove ARTICLE from the cache."
+  (let ((group gnus-newsgroup-name)
+	(number article)
+	file)
+    ;; If this is a virtual group, we find the real group.
+    (when (gnus-virtual-group-p group)
+      (let ((result (nnvirtual-find-group-art
+		     (gnus-group-real-name group) article)))
+	(setq group (car result)
+	      number (cdr result))))
+    (setq file (gnus-cache-file-name group number))
+    (when (and (file-exists-p file)
+	       (or force
+		   (gnus-cache-member-of-class
+		    gnus-cache-remove-articles ticked dormant unread)))
+      (save-excursion
+	(delete-file file)
+	(set-buffer (cdr gnus-cache-buffer))
+	(goto-char (point-min))
+	(when (or (looking-at (concat (int-to-string number) "\t"))
+		  (search-forward (concat "\n" (int-to-string number) "\t")
+				  (point-max) t))
+	  (delete-region (progn (beginning-of-line) (point))
+			 (progn (forward-line 1) (point)))))
+      (setq gnus-newsgroup-cached
+	    (delq article gnus-newsgroup-cached))
+      (gnus-summary-update-secondary-mark article)
+      t)))
+
+(defun gnus-cache-articles-in-group (group)
+  "Return a sorted list of cached articles in GROUP."
+  (let ((dir (file-name-directory (gnus-cache-file-name group 1))))
+    (when (file-exists-p dir)
+      (sort (mapcar (lambda (name) (string-to-int name))
+		    (directory-files dir nil "^[0-9]+$" t))
+	    '<))))
+
+(defun gnus-cache-braid-nov (group cached)
+  (let ((cache-buf (get-buffer-create " *gnus-cache*"))
+	beg end)
+    (gnus-cache-save-buffers)
+    (save-excursion
+      (set-buffer cache-buf)
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (insert-file-contents (gnus-cache-file-name group ".overview"))
+      (goto-char (point-min))
+      (insert "\n")
+      (goto-char (point-min)))
+    (set-buffer nntp-server-buffer)
+    (goto-char (point-min))
+    (while cached
+      (while (and (not (eobp))
+		  (< (read (current-buffer)) (car cached)))
+	(forward-line 1))
+      (beginning-of-line)
+      (save-excursion
+	(set-buffer cache-buf)
+	(if (search-forward (concat "\n" (int-to-string (car cached)) "\t")
+			    nil t)
+	    (setq beg (progn (beginning-of-line) (point))
+		  end (progn (end-of-line) (point)))
+	  (setq beg nil)))
+      (when beg
+	(insert-buffer-substring cache-buf beg end)
+	(insert "\n"))
+      (setq cached (cdr cached)))
+    (kill-buffer cache-buf)))
+
+(defun gnus-cache-braid-heads (group cached)
+  (let ((cache-buf (get-buffer-create " *gnus-cache*")))
+    (save-excursion
+      (set-buffer cache-buf)
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer))
+    (set-buffer nntp-server-buffer)
+    (goto-char (point-min))
+    (while cached
+      (while (and (not (eobp))
+		  (looking-at "2.. +\\([0-9]+\\) ")
+		  (< (progn (goto-char (match-beginning 1))
+			    (read (current-buffer)))
+		     (car cached)))
+	(search-forward "\n.\n" nil 'move))
+      (beginning-of-line)
+      (save-excursion
+	(set-buffer cache-buf)
+	(erase-buffer)
+	(insert-file-contents (gnus-cache-file-name group (car cached)))
+	(goto-char (point-min))
+	(insert "220 ")
+	(princ (car cached) (current-buffer))
+	(insert " Article retrieved.\n")
+	(search-forward "\n\n" nil 'move)
+	(delete-region (point) (point-max))
+	(forward-char -1)
+	(insert "."))
+      (insert-buffer-substring cache-buf)
+      (setq cached (cdr cached)))
+    (kill-buffer cache-buf)))
+
+;;;###autoload
+(defun gnus-jog-cache ()
+  "Go through all groups and put the articles into the cache.
+
+Usage:
+$ emacs -batch -l ~/.emacs -l gnus -f gnus-jog-cache"
+  (interactive)
+  (let ((gnus-mark-article-hook nil)
+	(gnus-expert-user t)
+	(nnmail-spool-file nil)
+	(gnus-use-dribble-file nil)
+	(gnus-novice-user nil)
+	(gnus-large-newsgroup nil))
+    ;; Start Gnus.
+    (gnus)
+    ;; Go through all groups...
+    (gnus-group-mark-buffer)
+    (gnus-group-universal-argument
+     nil nil
+     (lambda ()
+       (interactive)
+       (gnus-summary-read-group (gnus-group-group-name) nil t)
+       ;; ... and enter the articles into the cache.
+       (when (eq major-mode 'gnus-summary-mode)
+	 (gnus-uu-mark-buffer)
+	 (gnus-cache-enter-article)
+	 (kill-buffer (current-buffer)))))))
+
+(defun gnus-cache-read-active (&optional force)
+  "Read the cache active file."
+  (gnus-make-directory gnus-cache-directory)
+  (if (not (and (file-exists-p gnus-cache-active-file)
+		(or force (not gnus-cache-active-hashtb))))
+      ;; There is no active file, so we generate one.
+      (gnus-cache-generate-active)
+    ;; We simply read the active file.
+    (save-excursion
+      (gnus-set-work-buffer)
+      (insert-file-contents gnus-cache-active-file)
+      (gnus-active-to-gnus-format
+       nil (setq gnus-cache-active-hashtb
+		 (gnus-make-hashtable
+		  (count-lines (point-min) (point-max)))))
+      (setq gnus-cache-active-altered nil))))
+
+(defun gnus-cache-write-active (&optional force)
+  "Write the active hashtb to the active file."
+  (when (or force
+	    (and gnus-cache-active-hashtb
+		 gnus-cache-active-altered))
+    (nnheader-temp-write gnus-cache-active-file
+      (mapatoms
+       (lambda (sym)
+	 (when (and sym (boundp sym))
+	   (insert (format "%s %d %d y\n"
+			   (symbol-name sym) (cdr (symbol-value sym))
+			   (car (symbol-value sym))))))
+       gnus-cache-active-hashtb))
+    ;; Mark the active hashtb as unaltered.
+    (setq gnus-cache-active-altered nil)))
+
+(defun gnus-cache-update-active (group number &optional low)
+  "Update the upper bound of the active info of GROUP to NUMBER.
+If LOW, update the lower bound instead."
+  (let ((active (gnus-gethash group gnus-cache-active-hashtb)))
+    (if (null active)
+	;; We just create a new active entry for this group.
+	(gnus-sethash group (cons number number) gnus-cache-active-hashtb)
+      ;; Update the lower or upper bound.
+      (if low
+	  (setcar active number)
+	(setcdr active number)))
+    ;; Mark the active hashtb as altered.
+    (setq gnus-cache-active-altered t)))
+
+;;;###autoload
+(defun gnus-cache-generate-active (&optional directory)
+  "Generate the cache active file."
+  (interactive)
+  (let* ((top (null directory))
+	 (directory (expand-file-name (or directory gnus-cache-directory)))
+	 (files (directory-files directory 'full))
+	 (group
+	  (if top
+	      ""
+	    (string-match
+	     (concat "^" (file-name-as-directory
+			  (expand-file-name gnus-cache-directory)))
+	     (directory-file-name directory))
+	    (nnheader-replace-chars-in-string
+	     (substring (directory-file-name directory) (match-end 0))
+	     ?/ ?.)))
+	 nums alphs)
+    (when top
+      (gnus-message 5 "Generating the cache active file...")
+      (setq gnus-cache-active-hashtb (gnus-make-hashtable 123)))
+    ;; Separate articles from all other files and directories.
+    (while files
+      (if (string-match "^[0-9]+$" (file-name-nondirectory (car files)))
+	  (push (string-to-int (file-name-nondirectory (pop files))) nums)
+	(push (pop files) alphs)))
+    ;; If we have nums, then this is probably a valid group.
+    (when (setq nums (sort nums '<))
+      (gnus-sethash group (cons (car nums) (gnus-last-element nums))
+		    gnus-cache-active-hashtb))
+    ;; Go through all the other files.
+    (while alphs
+      (when (and (file-directory-p (car alphs))
+		 (not (string-match "^\\.\\.?$"
+				    (file-name-nondirectory (car alphs)))))
+	;; We descend directories.
+	(gnus-cache-generate-active (car alphs)))
+      (setq alphs (cdr alphs)))
+    ;; Write the new active file.
+    (when top
+      (gnus-cache-write-active t)
+      (gnus-message 5 "Generating the cache active file...done"))))
+
+;;;###autoload
+(defun gnus-cache-generate-nov-databases (dir)
+  "Generate NOV files recursively starting in DIR."
+  (interactive (list gnus-cache-directory))
+  (gnus-cache-close)
+  (let ((nnml-generate-active-function 'identity))
+    (nnml-generate-nov-databases-1 dir)))
+
+(defun gnus-cache-move-cache (dir)
+  "Move the cache tree to somewhere else."
+  (interactive "DMove the cache tree to: ")
+  (rename-file gnus-cache-directory dir))
+
+(provide 'gnus-cache)
+
+;;; gnus-cache.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-cite.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,911 @@
+;;; gnus-cite.el --- parse citations in articles for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-art)
+(require 'gnus-range)
+
+;;; Customization:
+
+(defgroup gnus-cite nil
+  "Citation."
+  :prefix "gnus-cite-"
+  :link '(custom-manual "(gnus)Article Highlighting")
+  :group 'gnus-article)
+
+(defcustom gnus-cite-reply-regexp
+  "^\\(Subject: Re\\|In-Reply-To\\|References\\):"
+  "If headers match this regexp it is reasonable to believe that
+article has citations."
+  :group 'gnus-cite
+  :type 'string)
+
+(defcustom gnus-cite-always-check nil
+  "Check article always for citations. Set it t to check all articles."
+  :group 'gnus-cite
+  :type '(choice (const :tag "no" nil)
+		  (const :tag "yes" t)))
+
+(defcustom gnus-cited-text-button-line-format "%(%{[...]%}%)\n"
+  "Format of cited text buttons."
+  :group 'gnus-cite
+  :type 'string)
+
+(defcustom gnus-cited-lines-visible nil
+  "The number of lines of hidden cited text to remain visible."
+  :group 'gnus-cite
+  :type '(choice (const :tag "none" nil)
+		 integer))
+
+(defcustom gnus-cite-parse-max-size 25000
+  "Maximum article size (in bytes) where parsing citations is allowed.
+Set it to nil to parse all articles."
+  :group 'gnus-cite
+  :type '(choice (const :tag "all" nil)
+		 integer))
+
+(defcustom gnus-cite-prefix-regexp
+    "^[]>|:}+ ]*[]>|:}+]\\(.*>\\)?\\|^.*>"
+  "Regexp matching the longest possible citation prefix on a line."
+  :group 'gnus-cite
+  :type 'regexp)
+
+(defcustom gnus-cite-max-prefix 20
+  "Maximum possible length for a citation prefix."
+  :group 'gnus-cite
+  :type 'integer)
+
+(defcustom gnus-supercite-regexp
+  (concat "^\\(" gnus-cite-prefix-regexp "\\)? *"
+	  ">>>>> +\"\\([^\"\n]+\\)\" +==")
+  "Regexp matching normal Supercite attribution lines.
+The first grouping must match prefixes added by other packages."
+  :group 'gnus-cite
+  :type 'regexp)
+
+(defcustom gnus-supercite-secondary-regexp "^.*\"\\([^\"\n]+\\)\" +=="
+  "Regexp matching mangled Supercite attribution lines.
+The first regexp group should match the Supercite attribution."
+  :group 'gnus-cite
+  :type 'regexp)
+
+(defcustom gnus-cite-minimum-match-count 2
+  "Minimum number of identical prefixes before we believe it's a citation."
+  :group 'gnus-cite
+  :type 'integer)
+
+(defcustom gnus-cite-attribution-prefix "in article\\|in <"
+  "Regexp matching the beginning of an attribution line."
+  :group 'gnus-cite
+  :type 'regexp)
+
+(defcustom gnus-cite-attribution-suffix
+  "\\(wrote\\|writes\\|said\\|says\\):[ \t]*$"
+  "Regexp matching the end of an attribution line.
+The text matching the first grouping will be used as a button."
+  :group 'gnus-cite
+  :type 'regexp)
+
+(defface gnus-cite-attribution-face '((t
+				       (:underline t)))
+  "Face used for attribution lines.")
+
+(defcustom gnus-cite-attribution-face 'gnus-cite-attribution-face
+  "Face used for attribution lines.
+It is merged with the face for the cited text belonging to the attribution."
+  :group 'gnus-cite
+  :type 'face)
+
+(defface gnus-cite-face-1 '((((class color)
+			      (background dark))
+			     (:foreground "light blue"))
+			    (((class color)
+			      (background light))
+			     (:foreground "MidnightBlue"))
+			    (t
+			     (:italic t)))
+  "Citation face.")
+
+(defface gnus-cite-face-2 '((((class color)
+			      (background dark))
+			     (:foreground "light cyan"))
+			    (((class color)
+			      (background light))
+			     (:foreground "firebrick"))
+			    (t
+			     (:italic t)))
+  "Citation face.")
+
+(defface gnus-cite-face-3 '((((class color)
+			      (background dark))
+			     (:foreground "light yellow"))
+			    (((class color)
+			      (background light))
+			     (:foreground "dark green"))
+			    (t
+			     (:italic t)))
+  "Citation face.")
+
+(defface gnus-cite-face-4 '((((class color)
+			      (background dark))
+			     (:foreground "light pink"))
+			    (((class color)
+			      (background light))
+			     (:foreground "OrangeRed"))
+			    (t
+			     (:italic t)))
+  "Citation face.")
+
+(defface gnus-cite-face-5 '((((class color)
+			      (background dark))
+			     (:foreground "pale green"))
+			    (((class color)
+			      (background light))
+			     (:foreground "dark khaki"))
+			    (t
+			     (:italic t)))
+  "Citation face.")
+
+(defface gnus-cite-face-6 '((((class color)
+			      (background dark))
+			     (:foreground "beige"))
+			    (((class color)
+			      (background light))
+			     (:foreground "dark violet"))
+			    (t
+			     (:italic t)))
+  "Citation face.")
+
+(defface gnus-cite-face-7 '((((class color)
+			      (background dark))
+			     (:foreground "orange"))
+			    (((class color)
+			      (background light))
+			     (:foreground "SteelBlue4"))
+			    (t
+			     (:italic t)))
+  "Citation face.")
+
+(defface gnus-cite-face-8 '((((class color)
+			      (background dark))
+			     (:foreground "magenta"))
+			    (((class color)
+			      (background light))
+			     (:foreground "magenta"))
+			    (t
+			     (:italic t)))
+  "Citation face.")
+
+(defface gnus-cite-face-9 '((((class color)
+			      (background dark))
+			     (:foreground "violet"))
+			    (((class color)
+			      (background light))
+			     (:foreground "violet"))
+			    (t
+			     (:italic t)))
+  "Citation face.")
+
+(defface gnus-cite-face-10 '((((class color)
+			       (background dark))
+			      (:foreground "medium purple"))
+			     (((class color)
+			       (background light))
+			      (:foreground "medium purple"))
+			     (t
+			      (:italic t)))
+  "Citation face.")
+
+(defface gnus-cite-face-11 '((((class color)
+			       (background dark))
+			      (:foreground "turquoise"))
+			     (((class color)
+			       (background light))
+			      (:foreground "turquoise"))
+			     (t
+			      (:italic t)))
+  "Citation face.")
+
+(defcustom gnus-cite-face-list
+  '(gnus-cite-face-1 gnus-cite-face-2 gnus-cite-face-3 gnus-cite-face-4
+    gnus-cite-face-5 gnus-cite-face-6 gnus-cite-face-7 gnus-cite-face-8
+    gnus-cite-face-9 gnus-cite-face-10 gnus-cite-face-11)
+  "List of faces used for highlighting citations.
+
+When there are citations from multiple articles in the same message,
+Gnus will try to give each citation from each article its own face.
+This should make it easier to see who wrote what."
+  :group 'gnus-cite
+  :type '(repeat face))
+
+(defcustom gnus-cite-hide-percentage 50
+  "Only hide excess citation if above this percentage of the body."
+  :group 'gnus-cite
+  :type 'number)
+
+(defcustom gnus-cite-hide-absolute 10
+  "Only hide excess citation if above this number of lines in the body."
+  :group 'gnus-cite
+  :type 'integer)
+
+;;; Internal Variables:
+
+(defvar gnus-cite-article nil)
+
+(defvar gnus-cite-prefix-alist nil)
+;; Alist of citation prefixes.
+;; The cdr is a list of lines with that prefix.
+
+(defvar gnus-cite-attribution-alist nil)
+;; Alist of attribution lines.
+;; The car is a line number.
+;; The cdr is the prefix for the citation started by that line.
+
+(defvar gnus-cite-loose-prefix-alist nil)
+;; Alist of citation prefixes that have no matching attribution.
+;; The cdr is a list of lines with that prefix.
+
+(defvar gnus-cite-loose-attribution-alist nil)
+;; Alist of attribution lines that have no matching citation.
+;; Each member has the form (WROTE IN PREFIX TAG), where
+;; WROTE: is the attribution line number
+;; IN: is the line number of the previous line if part of the same attribution,
+;; PREFIX: Is the citation prefix of the attribution line(s), and
+;; TAG: Is a Supercite tag, if any.
+
+(defvar gnus-cited-text-button-line-format-alist
+  `((?b (marker-position beg) ?d)
+    (?e (marker-position end) ?d)
+    (?l (- end beg) ?d)))
+(defvar gnus-cited-text-button-line-format-spec nil)
+
+;;; Commands:
+
+(defun gnus-article-highlight-citation (&optional force)
+  "Highlight cited text.
+Each citation in the article will be highlighted with a different face.
+The faces are taken from `gnus-cite-face-list'.
+Attribution lines are highlighted with the same face as the
+corresponding citation merged with `gnus-cite-attribution-face'.
+
+Text is considered cited if at least `gnus-cite-minimum-match-count'
+lines matches `gnus-cite-prefix-regexp' with the same prefix.
+
+Lines matching `gnus-cite-attribution-suffix' and perhaps
+`gnus-cite-attribution-prefix' are considered attribution lines."
+  (interactive (list 'force))
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (gnus-cite-parse-maybe force)
+    (let ((buffer-read-only nil)
+	  (alist gnus-cite-prefix-alist)
+	  (faces gnus-cite-face-list)
+	  (inhibit-point-motion-hooks t)
+	  face entry prefix skip numbers number face-alist)
+      ;; Loop through citation prefixes.
+      (while alist
+	(setq entry (car alist)
+	      alist (cdr alist)
+	      prefix (car entry)
+	      numbers (cdr entry)
+	      face (car faces)
+	      faces (or (cdr faces) gnus-cite-face-list)
+	      face-alist (cons (cons prefix face) face-alist))
+	(while numbers
+	  (setq number (car numbers)
+		numbers (cdr numbers))
+	  (and (not (assq number gnus-cite-attribution-alist))
+	       (not (assq number gnus-cite-loose-attribution-alist))
+	       (gnus-cite-add-face number prefix face))))
+      ;; Loop through attribution lines.
+      (setq alist gnus-cite-attribution-alist)
+      (while alist
+	(setq entry (car alist)
+	      alist (cdr alist)
+	      number (car entry)
+	      prefix (cdr entry)
+	      skip (gnus-cite-find-prefix number)
+	      face (cdr (assoc prefix face-alist)))
+	;; Add attribution button.
+	(goto-line number)
+	(when (re-search-forward gnus-cite-attribution-suffix
+				 (save-excursion (end-of-line 1) (point))
+				 t)
+	  (gnus-article-add-button (match-beginning 1) (match-end 1)
+				   'gnus-cite-toggle prefix))
+	;; Highlight attribution line.
+	(gnus-cite-add-face number skip face)
+	(gnus-cite-add-face number skip gnus-cite-attribution-face))
+      ;; Loop through attribution lines.
+      (setq alist gnus-cite-loose-attribution-alist)
+      (while alist
+	(setq entry (car alist)
+	      alist (cdr alist)
+	      number (car entry)
+	      skip (gnus-cite-find-prefix number))
+	(gnus-cite-add-face number skip gnus-cite-attribution-face)))))
+
+(defun gnus-dissect-cited-text ()
+  "Dissect the article buffer looking for cited text."
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (gnus-cite-parse-maybe)
+    (let ((alist gnus-cite-prefix-alist)
+	  prefix numbers number marks m)
+      ;; Loop through citation prefixes.
+      (while alist
+	(setq numbers (pop alist)
+	      prefix (pop numbers))
+	(while numbers
+	  (setq number (pop numbers))
+	  (goto-char (point-min))
+	  (forward-line number)
+	  (push (cons (point-marker) "") marks)
+	  (while (and numbers
+		      (= (1- number) (car numbers)))
+	    (setq number (pop numbers)))
+	  (goto-char (point-min))
+	  (forward-line (1- number))
+	  (push (cons (point-marker) prefix) marks)))
+      ;; Skip to the beginning of the body.
+      (goto-char (point-min))
+      (search-forward "\n\n" nil t)
+      (push (cons (point-marker) "") marks)
+      ;; Find the end of the body.
+      (goto-char (point-max))
+      (gnus-article-search-signature)
+      (push (cons (point-marker) "") marks)
+      ;; Sort the marks.
+      (setq marks (sort marks (lambda (m1 m2) (< (car m1) (car m2)))))
+      (let ((omarks marks))
+	(setq marks nil)
+	(while (cdr omarks)
+	  (if (= (caar omarks) (caadr omarks))
+	      (progn
+		(unless (equal (cdar omarks) "")
+		  (push (car omarks) marks))
+		(unless (equal (cdadr omarks) "")
+		  (push (cadr omarks) marks))
+		(unless (and (equal (cdar omarks) "")
+			     (equal (cdadr omarks) "")
+			     (not (cddr omarks)))
+		  (setq omarks (cdr omarks))))
+	    (push (car omarks) marks))
+	  (setq omarks (cdr omarks)))
+	(when (car omarks)
+	  (push (car omarks) marks))
+	(setq marks (setq m (nreverse marks)))
+	(while (cddr m)
+	  (if (and (equal (cdadr m) "")
+		   (equal (cdar m) (cdaddr m))
+		   (goto-char (caadr m))
+		   (forward-line 1)
+		   (= (point) (caaddr m)))
+	      (setcdr m (cdddr m))
+	    (setq m (cdr m))))
+	marks))))
+
+(defun gnus-article-fill-cited-article (&optional force width)
+  "Do word wrapping in the current article.
+If WIDTH (the numerical prefix), use that text width when filling."
+  (interactive (list t current-prefix-arg))
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (let ((buffer-read-only nil)
+	  (inhibit-point-motion-hooks t)
+	  (marks (gnus-dissect-cited-text))
+	  (adaptive-fill-mode nil)
+	  (filladapt-mode nil)
+	  (fill-column (if width (prefix-numeric-value width) fill-column)))
+      (save-restriction
+	(while (cdr marks)
+	  (widen)
+	  (narrow-to-region (caar marks) (caadr marks))
+	  (let ((adaptive-fill-regexp
+		 (concat "^" (regexp-quote (cdar marks)) " *"))
+		(fill-prefix (cdar marks)))
+	    (fill-region (point-min) (point-max)))
+	  (set-marker (caar marks) nil)
+	  (setq marks (cdr marks)))
+	(when marks
+	  (set-marker (caar marks) nil))
+	;; All this information is now incorrect.
+	(setq gnus-cite-prefix-alist nil
+	      gnus-cite-attribution-alist nil
+	      gnus-cite-loose-prefix-alist nil
+	      gnus-cite-loose-attribution-alist nil)))))
+
+(defun gnus-article-hide-citation (&optional arg force)
+  "Toggle hiding of all cited text except attribution lines.
+See the documentation for `gnus-article-highlight-citation'.
+If given a negative prefix, always show; if given a positive prefix,
+always hide."
+  (interactive (append (gnus-article-hidden-arg) (list 'force)))
+  (setq gnus-cited-text-button-line-format-spec
+	(gnus-parse-format gnus-cited-text-button-line-format
+			   gnus-cited-text-button-line-format-alist t))
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (cond
+     ((gnus-article-check-hidden-text 'cite arg)
+      t)
+     ((gnus-article-text-type-exists-p 'cite)
+      (let ((buffer-read-only nil))
+	(gnus-article-hide-text-of-type 'cite)))
+     (t
+      (let ((buffer-read-only nil)
+	    (marks (gnus-dissect-cited-text))
+	    (inhibit-point-motion-hooks t)
+	    (props (nconc (list 'article-type 'cite)
+			  gnus-hidden-properties))
+	    beg end)
+	(while marks
+	  (setq beg nil
+		end nil)
+	  (while (and marks (string= (cdar marks) ""))
+	    (setq marks (cdr marks)))
+	  (when marks
+	    (setq beg (caar marks)))
+	  (while (and marks (not (string= (cdar marks) "")))
+	    (setq marks (cdr marks)))
+	  (when marks
+	    (setq end (caar marks)))
+	  ;; Skip past lines we want to leave visible.
+	  (when (and beg end gnus-cited-lines-visible)
+	    (goto-char beg)
+	    (forward-line gnus-cited-lines-visible)
+	    (if (>= (point) end)
+		(setq beg nil)
+	      (setq beg (point-marker))))
+	  (when (and beg end)
+	    (gnus-add-text-properties beg end props)
+	    (goto-char beg)
+	    (unless (save-excursion (search-backward "\n\n" nil t))
+	      (insert "\n"))
+	    (put-text-property
+	     (point)
+	     (progn
+	       (gnus-article-add-button
+		(point)
+		(progn (eval gnus-cited-text-button-line-format-spec) (point))
+		`gnus-article-toggle-cited-text (cons beg end))
+	       (point))
+	     'article-type 'annotation)
+	    (set-marker beg (point)))))))))
+
+(defun gnus-article-toggle-cited-text (region)
+  "Toggle hiding the text in REGION."
+  (let (buffer-read-only)
+    (funcall
+     (if (text-property-any
+	  (car region) (1- (cdr region))
+	  (car gnus-hidden-properties) (cadr gnus-hidden-properties))
+	 'remove-text-properties 'gnus-add-text-properties)
+     (car region) (cdr region) gnus-hidden-properties)))
+
+(defun gnus-article-hide-citation-maybe (&optional arg force)
+  "Toggle hiding of cited text that has an attribution line.
+If given a negative prefix, always show; if given a positive prefix,
+always hide.
+This will do nothing unless at least `gnus-cite-hide-percentage'
+percent and at least `gnus-cite-hide-absolute' lines of the body is
+cited text with attributions.  When called interactively, these two
+variables are ignored.
+See also the documentation for `gnus-article-highlight-citation'."
+  (interactive (append (gnus-article-hidden-arg) (list 'force)))
+  (unless (gnus-article-check-hidden-text 'cite arg)
+    (save-excursion
+      (set-buffer gnus-article-buffer)
+      (gnus-cite-parse-maybe force)
+      (goto-char (point-min))
+      (search-forward "\n\n" nil t)
+      (let ((start (point))
+	    (atts gnus-cite-attribution-alist)
+	    (buffer-read-only nil)
+	    (inhibit-point-motion-hooks t)
+	    (hiden 0)
+	    total)
+	(goto-char (point-max))
+	(gnus-article-search-signature)
+	(setq total (count-lines start (point)))
+	(while atts
+	  (setq hiden (+ hiden (length (cdr (assoc (cdar atts)
+						   gnus-cite-prefix-alist))))
+		atts (cdr atts)))
+	(when (or force
+		  (and (> (* 100 hiden) (* gnus-cite-hide-percentage total))
+		       (> hiden gnus-cite-hide-absolute)))
+	  (setq atts gnus-cite-attribution-alist)
+	  (while atts
+	    (setq total (cdr (assoc (cdar atts) gnus-cite-prefix-alist))
+		  atts (cdr atts))
+	    (while total
+	      (setq hiden (car total)
+		    total (cdr total))
+	      (goto-line hiden)
+	      (unless (assq hiden gnus-cite-attribution-alist)
+		(gnus-add-text-properties
+		 (point) (progn (forward-line 1) (point))
+		 (nconc (list 'article-type 'cite)
+			gnus-hidden-properties))))))))))
+
+(defun gnus-article-hide-citation-in-followups ()
+  "Hide cited text in non-root articles."
+  (interactive)
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (let ((article (cdr gnus-article-current)))
+      (unless (save-excursion
+		(set-buffer gnus-summary-buffer)
+		(gnus-article-displayed-root-p article))
+	(gnus-article-hide-citation)))))
+
+;;; Internal functions:
+
+(defun gnus-cite-parse-maybe (&optional force)
+  ;; Parse if the buffer has changes since last time.
+  (if (equal gnus-cite-article gnus-article-current)
+      ()
+    ;;Reset parser information.
+    (setq gnus-cite-prefix-alist nil
+	  gnus-cite-attribution-alist nil
+	  gnus-cite-loose-prefix-alist nil
+	  gnus-cite-loose-attribution-alist nil)
+    ;; Parse if not too large.
+    (if (and (not force)
+	     gnus-cite-parse-max-size
+	     (> (buffer-size) gnus-cite-parse-max-size))
+	()
+      (setq gnus-cite-article (cons (car gnus-article-current)
+				    (cdr gnus-article-current)))
+      (gnus-cite-parse-wrapper))))
+
+(defun gnus-cite-parse-wrapper ()
+  ;; Wrap chopped gnus-cite-parse
+  (goto-char (point-min))
+  (unless (search-forward "\n\n" nil t)
+    (goto-char (point-max)))
+  (save-excursion
+    (gnus-cite-parse-attributions))
+  ;; Try to avoid check citation if there is no reason to believe
+  ;; that article has citations
+  (if (or gnus-cite-always-check
+	  (save-excursion
+	    (re-search-backward gnus-cite-reply-regexp nil t))
+	  gnus-cite-loose-attribution-alist)
+      (progn (save-excursion
+	       (gnus-cite-parse))
+	     (save-excursion
+	       (gnus-cite-connect-attributions)))))
+
+(defun gnus-cite-parse ()
+  ;; Parse and connect citation prefixes and attribution lines.
+
+  ;; Parse current buffer searching for citation prefixes.
+  (let ((line (1+ (count-lines (point-min) (point))))
+	(case-fold-search t)
+	(max (save-excursion
+	       (goto-char (point-max))
+	       (gnus-article-search-signature)
+	       (point)))
+	alist entry start begin end numbers prefix)
+    ;; Get all potential prefixes in `alist'.
+    (while (< (point) max)
+      ;; Each line.
+      (setq begin (point)
+	    end (progn (beginning-of-line 2) (point))
+	    start end)
+      (goto-char begin)
+      ;; Ignore standard Supercite attribution prefix.
+      (when (looking-at gnus-supercite-regexp)
+	(if (match-end 1)
+	    (setq end (1+ (match-end 1)))
+	  (setq end (1+ begin))))
+      ;; Ignore very long prefixes.
+      (when (> end (+ (point) gnus-cite-max-prefix))
+	(setq end (+ (point) gnus-cite-max-prefix)))
+      (while (re-search-forward gnus-cite-prefix-regexp (1- end) t)
+	;; Each prefix.
+	(setq end (match-end 0)
+	      prefix (buffer-substring begin end))
+	(gnus-set-text-properties 0 (length prefix) nil prefix)
+	(setq entry (assoc prefix alist))
+	(if entry
+	    (setcdr entry (cons line (cdr entry)))
+	  (push (list prefix line) alist))
+	(goto-char begin))
+      (goto-char start)
+      (setq line (1+ line)))
+    ;; We got all the potential prefixes.  Now create
+    ;; `gnus-cite-prefix-alist' containing the oldest prefix for each
+    ;; line that appears at least gnus-cite-minimum-match-count
+    ;; times.  First sort them by length.  Longer is older.
+    (setq alist (sort alist (lambda (a b)
+			      (> (length (car a)) (length (car b))))))
+    (while alist
+      (setq entry (car alist)
+	    prefix (car entry)
+	    numbers (cdr entry)
+	    alist (cdr alist))
+      (cond ((null numbers)
+	     ;; No lines with this prefix that wasn't also part of
+	     ;; a longer prefix.
+	     )
+	    ((< (length numbers) gnus-cite-minimum-match-count)
+	     ;; Too few lines with this prefix.  We keep it a bit
+	     ;; longer in case it is an exact match for an attribution
+	     ;; line, but we don't remove the line from other
+	     ;; prefixes.
+	     (push entry gnus-cite-prefix-alist))
+	    (t
+	     (push entry
+		   gnus-cite-prefix-alist)
+	     ;; Remove articles from other prefixes.
+	     (let ((loop alist)
+		   current)
+	       (while loop
+		 (setq current (car loop)
+		       loop (cdr loop))
+		 (setcdr current
+			 (gnus-set-difference (cdr current) numbers)))))))))
+
+(defun gnus-cite-parse-attributions ()
+  (let (al-alist)
+    ;; Parse attributions
+    (while (re-search-forward gnus-cite-attribution-suffix (point-max) t)
+      (let* ((start (match-beginning 0))
+	     (end (match-end 0))
+	     (wrote (count-lines (point-min) end))
+	     (prefix (gnus-cite-find-prefix wrote))
+	     ;; Check previous line for an attribution leader.
+	     (tag (progn
+		    (beginning-of-line 1)
+		    (when (looking-at gnus-supercite-secondary-regexp)
+		      (buffer-substring (match-beginning 1)
+					(match-end 1)))))
+	     (in (progn
+		   (goto-char start)
+		   (and (re-search-backward gnus-cite-attribution-prefix
+					    (save-excursion
+					      (beginning-of-line 0)
+					      (point))
+					    t)
+			(not (re-search-forward gnus-cite-attribution-suffix
+						start t))
+			(count-lines (point-min) (1+ (point)))))))
+	(when (eq wrote in)
+	  (setq in nil))
+	(goto-char end)
+	;; don't add duplicates
+	(let ((al (buffer-substring (save-excursion (beginning-of-line 0)
+						    (1+ (point)))
+				    end)))
+	  (if (not (assoc al al-alist))
+	      (progn
+		(push (list wrote in prefix tag)
+		      gnus-cite-loose-attribution-alist)
+		(push (cons al t) al-alist))))))))
+
+(defun gnus-cite-connect-attributions ()
+  ;; Connect attributions to citations
+
+  ;; No citations have been connected to attribution lines yet.
+  (setq gnus-cite-loose-prefix-alist (append gnus-cite-prefix-alist nil))
+
+  ;; Parse current buffer searching for attribution lines.
+  ;; Find exact supercite citations.
+  (gnus-cite-match-attributions 'small nil
+				(lambda (prefix tag)
+				  (when tag
+				    (concat "\\`"
+					    (regexp-quote prefix) "[ \t]*"
+					    (regexp-quote tag) ">"))))
+  ;; Find loose supercite citations after attributions.
+  (gnus-cite-match-attributions 'small t
+				(lambda (prefix tag)
+				  (when tag
+				    (concat "\\<"
+					    (regexp-quote tag)
+					    "\\>"))))
+  ;; Find loose supercite citations anywhere.
+  (gnus-cite-match-attributions 'small nil
+				(lambda (prefix tag)
+				  (when tag
+				    (concat "\\<"
+					    (regexp-quote tag)
+					    "\\>"))))
+  ;; Find nested citations after attributions.
+  (gnus-cite-match-attributions 'small-if-unique t
+				(lambda (prefix tag)
+				  (concat "\\`" (regexp-quote prefix) ".+")))
+  ;; Find nested citations anywhere.
+  (gnus-cite-match-attributions 'small nil
+				(lambda (prefix tag)
+				  (concat "\\`" (regexp-quote prefix) ".+")))
+  ;; Remove loose prefixes with too few lines.
+  (let ((alist gnus-cite-loose-prefix-alist)
+	entry)
+    (while alist
+      (setq entry (car alist)
+	    alist (cdr alist))
+      (when (< (length (cdr entry)) gnus-cite-minimum-match-count)
+	(setq gnus-cite-prefix-alist
+	      (delq entry gnus-cite-prefix-alist)
+	      gnus-cite-loose-prefix-alist
+	      (delq entry gnus-cite-loose-prefix-alist)))))
+  ;; Find flat attributions.
+  (gnus-cite-match-attributions 'first t nil)
+  ;; Find any attributions (are we getting desperate yet?).
+  (gnus-cite-match-attributions 'first nil nil))
+
+(defun gnus-cite-match-attributions (sort after fun)
+  ;; Match all loose attributions and citations (SORT AFTER FUN) .
+  ;;
+  ;; If SORT is `small', the citation with the shortest prefix will be
+  ;; used, if it is `first' the first prefix will be used, if it is
+  ;; `small-if-unique' the shortest prefix will be used if the
+  ;; attribution line does not share its own prefix with other
+  ;; loose attribution lines, otherwise the first prefix will be used.
+  ;;
+  ;; If AFTER is non-nil, only citations after the attribution line
+  ;; will be considered.
+  ;;
+  ;; If FUN is non-nil, it will be called with the arguments (WROTE
+  ;; PREFIX TAG) and expected to return a regular expression.  Only
+  ;; citations whose prefix matches the regular expression will be
+  ;; considered.
+  ;;
+  ;; WROTE is the attribution line number.
+  ;; PREFIX is the attribution line prefix.
+  ;; TAG is the Supercite tag on the attribution line.
+  (let ((atts gnus-cite-loose-attribution-alist)
+	(case-fold-search t)
+	att wrote in prefix tag regexp limit smallest best size)
+    (while atts
+      (setq att (car atts)
+	    atts (cdr atts)
+	    wrote (nth 0 att)
+	    in (nth 1 att)
+	    prefix (nth 2 att)
+	    tag (nth 3 att)
+	    regexp (if fun (funcall fun prefix tag) "")
+	    size (cond ((eq sort 'small) t)
+		       ((eq sort 'first) nil)
+		       (t (< (length (gnus-cite-find-loose prefix)) 2)))
+	    limit (if after wrote -1)
+	    smallest 1000000
+	    best nil)
+      (let ((cites gnus-cite-loose-prefix-alist)
+	    cite candidate numbers first compare)
+	(while cites
+	  (setq cite (car cites)
+		cites (cdr cites)
+		candidate (car cite)
+		numbers (cdr cite)
+		first (apply 'min numbers)
+		compare (if size (length candidate) first))
+	  (and (> first limit)
+	       regexp
+	       (string-match regexp candidate)
+	       (< compare smallest)
+	       (setq best cite
+		     smallest compare))))
+      (if (null best)
+	  ()
+	(setq gnus-cite-loose-attribution-alist
+	      (delq att gnus-cite-loose-attribution-alist))
+	(push (cons wrote (car best)) gnus-cite-attribution-alist)
+	(when in
+	  (push (cons in (car best)) gnus-cite-attribution-alist))
+	(when (memq best gnus-cite-loose-prefix-alist)
+	  (let ((loop gnus-cite-prefix-alist)
+		(numbers (cdr best))
+		current)
+	    (setq gnus-cite-loose-prefix-alist
+		  (delq best gnus-cite-loose-prefix-alist))
+	    (while loop
+	      (setq current (car loop)
+		    loop (cdr loop))
+	      (if (eq current best)
+		  ()
+		(setcdr current (gnus-set-difference (cdr current) numbers))
+		(when (null (cdr current))
+		  (setq gnus-cite-loose-prefix-alist
+			(delq current gnus-cite-loose-prefix-alist)
+			atts (delq current atts)))))))))))
+
+(defun gnus-cite-find-loose (prefix)
+  ;; Return a list of loose attribution lines prefixed by PREFIX.
+  (let* ((atts gnus-cite-loose-attribution-alist)
+	 att line lines)
+    (while atts
+      (setq att (car atts)
+	    line (car att)
+	    atts (cdr atts))
+      (when (string-equal (gnus-cite-find-prefix line) prefix)
+	(push line lines)))
+    lines))
+
+(defun gnus-cite-add-face (number prefix face)
+  ;; At line NUMBER, ignore PREFIX and add FACE to the rest of the line.
+  (when face
+    (let ((inhibit-point-motion-hooks t)
+	  from to)
+      (goto-line number)
+      (unless (eobp);; Sometimes things become confused.
+	(forward-char (length prefix))
+	(skip-chars-forward " \t")
+	(setq from (point))
+	(end-of-line 1)
+	(skip-chars-backward " \t")
+	(setq to (point))
+	(when (< from to)
+	  (gnus-overlay-put (gnus-make-overlay from to) 'face face))))))
+
+(defun gnus-cite-toggle (prefix)
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (let ((buffer-read-only nil)
+	  (numbers (cdr (assoc prefix gnus-cite-prefix-alist)))
+	  (inhibit-point-motion-hooks t)
+	  number)
+      (while numbers
+	(setq number (car numbers)
+	      numbers (cdr numbers))
+	(goto-line number)
+	(cond ((get-text-property (point) 'invisible)
+	       (remove-text-properties (point) (progn (forward-line 1) (point))
+				       gnus-hidden-properties))
+	      ((assq number gnus-cite-attribution-alist))
+	      (t
+	       (gnus-add-text-properties
+		(point) (progn (forward-line 1) (point))
+		(nconc (list 'article-type 'cite)
+		       gnus-hidden-properties))))))))
+
+(defun gnus-cite-find-prefix (line)
+  ;; Return citation prefix for LINE.
+  (let ((alist gnus-cite-prefix-alist)
+	(prefix "")
+	entry)
+    (while alist
+      (setq entry (car alist)
+	    alist (cdr alist))
+      (when (memq line (cdr entry))
+	(setq prefix (car entry))))
+    prefix))
+
+(gnus-add-shutdown 'gnus-cache-close 'gnus)
+
+(defun gnus-cache-close ()
+  (setq gnus-cite-prefix-alist nil))
+
+(gnus-ems-redefine)
+
+(provide 'gnus-cite)
+
+;;; gnus-cite.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-cus.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,650 @@
+;;; gnus-cus.el --- customization commands for Gnus
+;;
+;; Copyright (C) 1996 Free Software Foundation, Inc.
+
+;; Author: Per Abrahamsen <abraham@dina.kvl.dk>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'wid-edit)
+(require 'gnus-score)
+
+;;; Widgets:
+
+;; There should be special validation for this.
+(define-widget 'gnus-email-address 'string
+  "An email address")
+
+(defun gnus-custom-mode ()
+  "Major mode for editing Gnus customization buffers.
+
+The following commands are available:
+
+\\[widget-forward]		Move to next button or editable field.
+\\[widget-backward]		Move to previous button or editable field.
+\\[widget-button-click]		Activate button under the mouse pointer.
+\\[widget-button-press]		Activate button under point.
+
+Entry to this mode calls the value of `gnus-custom-mode-hook'
+if that value is non-nil."
+  (kill-all-local-variables)
+  (setq major-mode 'gnus-custom-mode
+	mode-name "Gnus Customize")
+  (use-local-map widget-keymap)
+  (run-hooks 'gnus-custom-mode-hook))
+
+;;; Group Customization:
+
+(defconst gnus-group-parameters
+  '((to-address (gnus-email-address :tag "To Address") "\
+This will be used when doing followups and posts.
+
+This is primarily useful in mail groups that represent closed
+mailing lists--mailing lists where it's expected that everybody that
+writes to the mailing list is subscribed to it.  Since using this
+parameter ensures that the mail only goes to the mailing list itself,
+it means that members won't receive two copies of your followups.
+
+Using `to-address' will actually work whether the group is foreign or
+not.  Let's say there's a group on the server that is called
+`fa.4ad-l'.  This is a real newsgroup, but the server has gotten the
+articles from a mail-to-news gateway.  Posting directly to this group
+is therefore impossible--you have to send mail to the mailing list
+address instead.")
+
+    (to-list (gnus-email-address :tag "To List") "\
+This address will be used when doing a `a' in the group.
+
+It is totally ignored when doing a followup--except that if it is
+present in a news group, you'll get mail group semantics when doing
+`f'.")
+
+    (broken-reply-to (const :tag "Broken Reply To" t) "\
+Ignore `Reply-To' headers in this group.
+
+That can be useful if you're reading a mailing list group where the
+listserv has inserted `Reply-To' headers that point back to the
+listserv itself.  This is broken behavior.  So there!")
+
+    (to-group (string :tag "To Group") "\
+All posts will be send to the specified group.")
+
+    (gcc-self (choice :tag  "GCC"
+		      :value t
+		      (const t)
+		      (const none)
+		      (string :format "%v" :hide-front-space t)) "\
+Specify default value for GCC header.
+
+If this symbol is present in the group parameter list and set to `t',
+new composed messages will be `Gcc''d to the current group. If it is
+present and set to `none', no `Gcc:' header will be generated, if it
+is present and a string, this string will be inserted literally as a
+`gcc' header (this symbol takes precedence over any default `Gcc'
+rules as described later).")
+
+    (auto-expire (const :tag "Automatic Expire" t) "\
+All articles that are read will be marked as expirable.")
+
+    (total-expire (const :tag "Total Expire" t) "\
+All read articles will be put through the expiry process
+
+This happens even if they are not marked as expirable.
+Use with caution.")
+
+    (expiry-wait (choice :tag  "Expire Wait"
+			 :value never
+			 (const never)
+			 (const immediate)
+			 (number :hide-front-space t
+				 :format "%v")) "\
+When to expire.
+
+Overrides any `nnmail-expiry-wait' and `nnmail-expiry-wait-function'
+when expiring expirable messages. The value can either be a number of
+days (not necessarily an integer) or the symbols `never' or
+`immediate'.")
+
+    (score-file (file :tag "Score File") "\
+Make the specified file into the current score file.
+This means that all score commands you issue will end up in this file.")
+
+    (adapt-file (file :tag "Adapt File") "\
+Make the specified file into the current adaptive file.
+All adaptive score entries will be put into this file.")
+
+    (admin-address (gnus-email-address :tag "Admin Address") "\
+Administration address for a mailing list.
+
+When unsubscribing to a mailing list you should never send the
+unsubscription notice to the mailing list itself.  Instead, you'd
+send messages to the administrative address.  This parameter allows
+you to put the admin address somewhere convenient.")
+
+    (display (choice :tag "Display"
+		     :value default
+		     (const all)
+		     (const default)) "\
+Which articles to display on entering the group.
+
+`all'
+     Display all articles, both read and unread.
+
+`default'
+     Display the default visible articles, which normally includes
+     unread and ticked articles.")
+
+    (comment (string :tag  "Comment") "\
+An arbitrary comment on the group."))
+  "Alist of valid group parameters.
+
+Each entry has the form (NAME TYPE DOC), where NAME is the parameter
+itself (a symbol), TYPE is the parameters type (a sexp widget), and
+DOC is a documentation string for the parameter.")
+
+(defvar gnus-custom-params)
+(defvar gnus-custom-method)
+(defvar gnus-custom-group)
+
+(defun gnus-group-customize (group &optional part)
+  "Edit the group on the current line."
+  (interactive (list (gnus-group-group-name)))
+  (let ((part (or part 'info))
+	info
+	(types (mapcar (lambda (entry)
+			 `(cons :format "%v%h\n"
+				:doc ,(nth 2 entry)
+				(const :format "" ,(nth 0 entry))
+				,(nth 1 entry)))
+		       gnus-group-parameters)))
+    (unless group
+      (error "No group on current line"))
+    (unless (setq info (gnus-get-info group))
+      (error "Killed group; can't be edited"))
+    ;; Ready.
+    (kill-buffer (get-buffer-create "*Gnus Customize*"))
+    (switch-to-buffer (get-buffer-create "*Gnus Customize*"))
+    (gnus-custom-mode)
+    (make-local-variable 'gnus-custom-group)
+    (setq gnus-custom-group group)
+    (widget-insert "Customize the ")
+    (widget-create 'info-link
+		   :help-echo "Push me to learn more."
+		   :tag "group parameters"
+		   "(gnus)Group Parameters")
+    (widget-insert " for <")
+    (widget-insert group)
+    (widget-insert "> and press ")
+    (widget-create 'push-button
+		   :tag "done"
+		   :help-echo "Push me when done customizing."
+		   :action 'gnus-group-customize-done)
+    (widget-insert ".\n\n")
+    (make-local-variable 'gnus-custom-params)
+    (setq gnus-custom-params
+	  (widget-create 'group
+			 :value (gnus-info-params info)
+			 `(set :inline t
+			       :greedy t
+			       :tag "Parameters"
+			       :format "%t:\n%h%v"
+			       :doc "\
+These special paramerters are recognized by Gnus.
+Check the [ ] for the parameters you want to apply to this group, then
+edit the value to suit your taste."
+			       ,@types)
+			 '(repeat :inline t
+				  :tag "Variables"
+				  :format "%t:\n%h%v%i\n\n"
+				  :doc "\
+Set variables local to the group you are entering.
+
+If you want to turn threading off in `news.answers', you could put
+`(gnus-show-threads nil)' in the group parameters of that group.
+`gnus-show-threads' will be made into a local variable in the summary
+buffer you enter, and the form `nil' will be `eval'ed there.
+
+This can also be used as a group-specific hook function, if you'd
+like.  If you want to hear a beep when you enter a group, you could
+put something like `(dummy-variable (ding))' in the parameters of that
+group.  `dummy-variable' will be set to the result of the `(ding)'
+form, but who cares?"
+				  (group :value (nil nil)
+					 (symbol :tag "Variable")
+					 (sexp :tag
+					       "Value")))
+
+			 '(repeat :inline t
+				  :tag "Unknown entries"
+				  sexp)))
+    (widget-insert "\n\nYou can also edit the ")
+    (widget-create 'info-link
+		   :tag "select method"
+		   :help-echo "Push me to learn more about select methods."
+		   "(gnus)Select Methods")
+    (widget-insert " for the group.\n")
+    (setq gnus-custom-method
+	  (widget-create 'sexp
+			 :tag "Method"
+			 :value (gnus-info-method info)))
+    (use-local-map widget-keymap)
+    (widget-setup)))
+
+(defun gnus-group-customize-done (&rest ignore)
+  "Apply changes and bury the buffer."
+  (interactive)
+  (gnus-group-edit-group-done 'params gnus-custom-group
+			      (widget-value gnus-custom-params))
+  (gnus-group-edit-group-done 'method gnus-custom-group
+			      (widget-value gnus-custom-method))
+  (bury-buffer))
+
+;;; Score Customization:
+
+(defconst gnus-score-parameters
+  '((mark (number :tag "Mark") "\
+The value of this entry should be a number.
+Any articles with a score lower than this number will be marked as read.")
+
+    (expunge (number :tag "Expunge") "\
+The value of this entry should be a number.
+Any articles with a score lower than this number will be removed from
+the summary buffer.")
+
+    (mark-and-expunge (number :tag "Mark-and-expunge") "\
+The value of this entry should be a number.
+Any articles with a score lower than this number will be marked as
+read and removed from the summary buffer.")
+
+    (thread-mark-and-expunge (number :tag "Thread-mark-and-expunge") "\
+The value of this entry should be a number.
+All articles that belong to a thread that has a total score below this
+number will be marked as read and removed from the summary buffer.
+`gnus-thread-score-function' says how to compute the total score
+for a thread.")
+
+    (files (repeat :tag "Files" file) "\
+The value of this entry should be any number of file names.
+These files are assumed to be score files as well, and will be loaded
+the same way this one was.")
+
+    (exclude-files (repeat :tag "Exclude-files" file) "\
+The clue of this entry should be any number of files.
+These files will not be loaded, even though they would normally be so,
+for some reason or other.")
+
+    (eval (sexp :tag "Eval" :value nil) "\
+The value of this entry will be `eval'el.
+This element will be ignored when handling global score files.")
+
+    (read-only (boolean :tag "Read-only" :value t) "\
+Read-only score files will not be updated or saved.
+Global score files should feature this atom.")
+
+    (orphan (number :tag "Orphan") "\
+The value of this entry should be a number.
+Articles that do not have parents will get this number added to their
+scores.  Imagine you follow some high-volume newsgroup, like
+`comp.lang.c'.  Most likely you will only follow a few of the threads,
+also want to see any new threads.
+
+You can do this with the following two score file entries:
+
+     (orphan -500)
+     (mark-and-expunge -100)
+
+When you enter the group the first time, you will only see the new
+threads.  You then raise the score of the threads that you find
+interesting (with `I T' or `I S'), and ignore (`C y') the rest.
+Next time you enter the group, you will see new articles in the
+interesting threads, plus any new threads.
+
+I.e.---the orphan score atom is for high-volume groups where there
+exist a few interesting threads which can't be found automatically
+by ordinary scoring rules.")
+
+    (adapt (choice :tag "Adapt"
+		   (const t)
+		   (const ignore)
+		   (sexp :format "%v"
+			 :hide-front-space t)) "\
+This entry controls the adaptive scoring.
+If it is `t', the default adaptive scoring rules will be used.  If it
+is `ignore', no adaptive scoring will be performed on this group.  If
+it is a list, this list will be used as the adaptive scoring rules.
+If it isn't present, or is something other than `t' or `ignore', the
+default adaptive scoring rules will be used.  If you want to use
+adaptive scoring on most groups, you'd set `gnus-use-adaptive-scoring'
+to `t', and insert an `(adapt ignore)' in the groups where you do not
+want adaptive scoring.  If you only want adaptive scoring in a few
+groups, you'd set `gnus-use-adaptive-scoring' to `nil', and insert
+`(adapt t)' in the score files of the groups where you want it.")
+
+    (adapt-file (file :tag "Adapt-file") "\
+All adaptive score entries will go to the file named by this entry.
+It will also be applied when entering the group.  This atom might
+be handy if you want to adapt on several groups at once, using the
+same adaptive file for a number of groups.")
+
+    (local (repeat :tag "Local"
+		   (group :value (nil nil)
+			  (symbol :tag "Variable")
+			  (sexp :tag "Value"))) "\
+The value of this entry should be a list of `(VAR VALUE)' pairs.
+Each VAR will be made buffer-local to the current summary buffer,
+and set to the value specified.  This is a convenient, if somewhat
+strange, way of setting variables in some groups if you don't like
+hooks much.")
+    (touched (sexp :format "Touched\n") "Internal variable."))
+  "Alist of valid symbolic score parameters.
+
+Each entry has the form (NAME TYPE DOC), where NAME is the parameter
+itself (a symbol), TYPE is the parameters type (a sexp widget), and DOC is a
+documentation string for the parameter.")
+
+(define-widget 'gnus-score-string 'group
+  "Edit score entries for string-valued headers."
+  :convert-widget 'gnus-score-string-convert)
+
+(defun gnus-score-string-convert (widget)
+  ;; Set args appropriately.
+  (let* ((tag (widget-get widget :tag))
+	 (item `(const :format "" :value ,(downcase tag)))
+	 (match '(string :tag "Match"))
+	 (score '(choice :tag "Score"
+			(const :tag "default" nil)
+			(integer :format "%v"
+				 :hide-front-space t)))
+	 (expire '(choice :tag "Expire"
+			  (const :tag "off" nil)
+			  (integer :format "%v"
+				   :hide-front-space t)))
+	 (type '(choice :tag "Type"
+			:value s
+			;; I should really create a forgiving :match
+			;; function for each type below, that only
+			;; looked at the first letter.
+			(const :tag "Regexp" r)
+			(const :tag "Regexp (fixed case)" R)
+			(const :tag "Substring" s)
+			(const :tag "Substring (fixed case)" S)
+			(const :tag "Exact" e)
+			(const :tag "Exact (fixed case)" E)
+			(const :tag "Word" w)
+			(const :tag "Word (fixed case)" W)
+			(const :tag "default" nil)))
+	 (group `(group ,match ,score ,expire ,type))
+	 (doc (concat (or (widget-get widget :doc)
+			  (concat "Change score based on the " tag
+				  " header.\n"))
+		      "
+You can have an arbitrary number of score entries for this header,
+each score entry has four elements:
+
+1. The \"match element\".  This should be the string to look for in the
+   header.
+
+2. The \"score element\".  This number should be an integer in the
+   neginf to posinf interval.  This number is added to the score
+   of the article if the match is successful.  If this element is
+   not present, the `gnus-score-interactive-default-score' number
+   will be used instead.  This is 1000 by default.
+
+3. The \"date element\".  This date says when the last time this score
+   entry matched, which provides a mechanism for expiring the
+   score entries.  It this element is not present, the score
+   entry is permanent.  The date is represented by the number of
+   days since December 31, 1 ce.
+
+4. The \"type element\".  This element specifies what function should
+   be used to see whether this score entry matches the article.
+
+   There are the regexp, as well as substring types, and exact match,
+   and word match types.  If this element is not present, Gnus will
+   assume that substring matching should be used.  There is case
+   sensitive variants of all match types.")))
+    (widget-put widget :args `(,item
+			       (repeat :inline t
+				       :indent 0
+				       :tag ,tag
+				       :doc ,doc
+				       :format "%t:\n%h%v%i\n\n"
+				       (choice :format "%v"
+					       :value ("" nil nil s)
+					       ,group
+					       sexp)))))
+  widget)
+
+(define-widget 'gnus-score-integer 'group
+  "Edit score entries for integer-valued headers."
+  :convert-widget 'gnus-score-integer-convert)
+
+(defun gnus-score-integer-convert (widget)
+  ;; Set args appropriately.
+  (let* ((tag (widget-get widget :tag))
+	 (item `(const :format "" :value ,(downcase tag)))
+	 (match '(integer :tag "Match"))
+	 (score '(choice :tag "Score"
+			(const :tag "default" nil)
+			(integer :format "%v"
+				 :hide-front-space t)))
+	 (expire '(choice :tag "Expire"
+			  (const :tag "off" nil)
+			  (integer :format "%v"
+				   :hide-front-space t)))
+	 (type '(choice :tag "Type"
+			:value <
+			(const <)
+			(const >)
+			(const =)
+			(const >=)
+			(const <=)))
+	 (group `(group ,match ,score ,expire ,type))
+	 (doc (concat (or (widget-get widget :doc)
+			  (concat "Change score based on the " tag
+				  " header.")))))
+    (widget-put widget :args `(,item
+			       (repeat :inline t
+				       :indent 0
+				       :tag ,tag
+				       :doc ,doc
+				       :format "%t:\n%h%v%i\n\n"
+				       ,group))))
+  widget)
+
+(define-widget 'gnus-score-date 'group
+  "Edit score entries for date-valued headers."
+  :convert-widget 'gnus-score-date-convert)
+
+(defun gnus-score-date-convert (widget)
+  ;; Set args appropriately.
+  (let* ((tag (widget-get widget :tag))
+	 (item `(const :format "" :value ,(downcase tag)))
+	 (match '(string :tag "Match"))
+	 (score '(choice :tag "Score"
+			(const :tag "default" nil)
+			(integer :format "%v"
+				 :hide-front-space t)))
+	 (expire '(choice :tag "Expire"
+			  (const :tag "off" nil)
+			  (integer :format "%v"
+				   :hide-front-space t)))
+	 (type '(choice :tag "Type"
+			:value regexp
+			(const regexp)
+			(const before)
+			(const at)
+			(const after)))
+	 (group `(group ,match ,score ,expire ,type))
+	 (doc (concat (or (widget-get widget :doc)
+			  (concat "Change score based on the " tag
+				  " header."))
+		      "
+For the Date header we have three kinda silly match types: `before',
+`at' and `after'.  I can't really imagine this ever being useful, but,
+like, it would feel kinda silly not to provide this function.  Just in
+case.  You never know.  Better safe than sorry.  Once burnt, twice
+shy.  Don't judge a book by its cover.  Never not have sex on a first
+date.  (I have been told that at least one person, and I quote,
+\"found this function indispensable\", however.)
+
+A more useful match type is `regexp'.  With it, you can match the date
+string using a regular expression.  The date is normalized to ISO8601
+compact format first---`YYYYMMDDTHHMMSS'.  If you want to match all
+articles that have been posted on April 1st in every year, you could
+use `....0401.........' as a match string, for instance.  (Note that
+the date is kept in its original time zone, so this will match
+articles that were posted when it was April 1st where the article was
+posted from.  Time zones are such wholesome fun for the whole family,
+eh?")))
+    (widget-put widget :args `(,item
+			       (repeat :inline t
+				       :indent 0
+				       :tag ,tag
+				       :doc ,doc
+				       :format "%t:\n%h%v%i\n\n"
+				       ,group))))
+  widget)
+
+(defvar gnus-custom-scores)
+(defvar gnus-custom-score-alist)
+
+(defun gnus-score-customize (file)
+  "Customize score file FILE."
+  (interactive (list gnus-current-score-file))
+  (let ((scores (gnus-score-load file))
+	(types (mapcar (lambda (entry)
+		 `(group :format "%v%h\n"
+			 :doc ,(nth 2 entry)
+			 (const :format "" ,(nth 0 entry))
+			 ,(nth 1 entry)))
+	       gnus-score-parameters)))
+    ;; Ready.
+    (kill-buffer (get-buffer-create "*Gnus Customize*"))
+    (switch-to-buffer (get-buffer-create "*Gnus Customize*"))
+    (gnus-custom-mode)
+    (make-local-variable 'gnus-custom-score-alist)
+    (setq gnus-custom-score-alist scores)
+    (widget-insert "Customize the ")
+    (widget-create 'info-link
+		   :help-echo "Push me to learn more."
+		   :tag "score entries"
+		   "(gnus)Score File Format")
+    (widget-insert " for\n\t")
+    (widget-insert file)
+    (widget-insert "\nand press ")
+    (widget-create 'push-button
+		   :tag "done"
+		   :help-echo "Push me when done customizing."
+		   :action 'gnus-score-customize-done)
+    (widget-insert ".\n
+Check the [ ] for the entries you want to apply to this score file, then
+edit the value to suit your taste.  Don't forget to mark the checkbox,
+if you do all your changes will be lost.  ")
+    (widget-create 'push-button
+		   :action (lambda (&rest ignore)
+			     (require 'gnus-audio)
+			     (gnus-audio-play "Evil_Laugh.au"))
+		   "Bhahahah!")
+    (widget-insert "\n\n")
+    (make-local-variable 'gnus-custom-scores)
+    (setq gnus-custom-scores
+	  (widget-create 'group
+			 :value scores
+			 `(checklist :inline t
+				     :greedy t
+				     (gnus-score-string :tag "From")
+				     (gnus-score-string :tag "Subject")
+				     (gnus-score-string :tag "References")
+				     (gnus-score-string :tag "Xref")
+				     (gnus-score-string :tag "Message-ID")
+				     (gnus-score-integer :tag "Lines")
+				     (gnus-score-integer :tag "Chars")
+				     (gnus-score-date :tag "Date")
+				     (gnus-score-string :tag "Head"
+							:doc "\
+Match all headers in the article.
+
+Using one of `Head', `Body', `All' will slow down scoring considerable.
+")
+				     (gnus-score-string :tag "Body"
+							:doc "\
+Match the body sans header of the article.
+
+Using one of `Head', `Body', `All' will slow down scoring considerable.
+")
+				     (gnus-score-string :tag "All"
+							:doc "\
+Match the entire article, including both headers and body.
+
+Using one of `Head', `Body', `All' will slow down scoring
+considerable.
+")
+				     (gnus-score-string :tag
+							"Followup"
+							:doc "\
+Score all followups to the specified authors.
+
+This entry is somewhat special, in that it will match the `From:'
+header, and affect the score of not only the matching articles, but
+also all followups to the matching articles.  This allows you
+e.g. increase the score of followups to your own articles, or decrease
+the score of followups to the articles of some known trouble-maker.
+")
+				     (gnus-score-string :tag "Thread"
+							:doc "\
+Add a score entry on all articles that are part of a thread.
+
+This match key works along the same lines as the `Followup' match key.
+If you say that you want to score on a (sub-)thread that is started by
+an article with a `Message-ID' X, then you add a `thread' match.  This
+will add a new `thread' match for each article that has X in its
+`References' header.  (These new `thread' matches will use the
+`Message-ID's of these matching articles.)  This will ensure that you
+can raise/lower the score of an entire thread, even though some
+articles in the thread may not have complete `References' headers.
+Note that using this may lead to undeterministic scores of the
+articles in the thread.
+")
+				     ,@types)
+			 '(repeat :inline t
+				  :tag "Unknown entries"
+				  sexp)))
+    (use-local-map widget-keymap)
+    (widget-setup)))
+
+(defun gnus-score-customize-done (&rest ignore)
+  "Reset the score alist with the present value."
+  (let ((alist gnus-custom-score-alist)
+	(value (widget-value gnus-custom-scores)))
+    (setcar alist (car value))
+    (setcdr alist (cdr value))
+    (gnus-score-set 'touched '(t) alist))
+  (bury-buffer))
+
+;;; The End:
+
+(provide 'gnus-cus)
+
+;;; gnus-cus.el ends here
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-demon.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,290 @@
+;;; gnus-demon.el --- daemonic Gnus behaviour
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-int)
+(require 'nnheader)
+(eval-and-compile
+  (if (string-match "XEmacs" (emacs-version))
+      (require 'itimer)
+    (require 'timer)))
+
+(defgroup gnus-demon nil
+  "Demonic behaviour."
+  :group 'gnus)
+
+(defcustom gnus-demon-handlers nil
+  "Alist of daemonic handlers to be run at intervals.
+Each handler is a list on the form
+
+\(FUNCTION TIME IDLE)
+
+FUNCTION is the function to be called.
+TIME is the number of `gnus-demon-timestep's between each call.
+If nil, never call.  If t, call each `gnus-demon-timestep'.
+If IDLE is t, only call if Emacs has been idle for a while.  If IDLE
+is a number, only call when Emacs has been idle more than this number
+of `gnus-demon-timestep's.  If IDLE is nil, don't care about
+idleness.  If IDLE is a number and TIME is nil, then call once each
+time Emacs has been idle for IDLE `gnus-demon-timestep's."
+  :group 'gnus-demon
+  :type '(repeat (list function
+		       (choice :tag "Time"
+			       (const :tag "never" nil)
+			       (const :tag "one" t)
+			       (integer :tag "steps" 1))
+		       (choice :tag "Idle"
+			       (const :tag "don't care" nil)
+			       (const :tag "for a while" t)
+			       (integer :tag "steps" 1)))))
+
+(defcustom gnus-demon-timestep 60
+  "*Number of seconds in each demon timestep."
+  :group 'gnus-demon
+  :type 'integer)
+
+;;; Internal variables.
+
+(defvar gnus-demon-timer nil)
+(defvar gnus-demon-idle-has-been-called nil)
+(defvar gnus-demon-idle-time 0)
+(defvar gnus-demon-handler-state nil)
+(defvar gnus-demon-last-keys nil)
+(defvar gnus-inhibit-demon nil
+  "*If non-nil, no daemonic function will be run.")
+
+(eval-and-compile
+  (autoload 'timezone-parse-date "timezone")
+  (autoload 'timezone-make-arpa-date "timezone"))
+
+;;; Functions.
+
+(defun gnus-demon-add-handler (function time idle)
+  "Add the handler FUNCTION to be run at TIME and IDLE."
+  ;; First remove any old handlers that use this function.
+  (gnus-demon-remove-handler function)
+  ;; Then add the new one.
+  (push (list function time idle) gnus-demon-handlers)
+  (gnus-demon-init))
+
+(defun gnus-demon-remove-handler (function &optional no-init)
+  "Remove the handler FUNCTION from the list of handlers."
+  (setq gnus-demon-handlers
+	(delq (assq function gnus-demon-handlers)
+	      gnus-demon-handlers))
+  (unless no-init
+    (gnus-demon-init)))
+
+(defun gnus-demon-init ()
+  "Initialize the Gnus daemon."
+  (interactive)
+  (gnus-demon-cancel)
+  (if (null gnus-demon-handlers)
+      ()				; Nothing to do.
+    ;; Set up timer.
+    (setq gnus-demon-timer
+	  (nnheader-run-at-time
+	   gnus-demon-timestep gnus-demon-timestep 'gnus-demon))
+    ;; Reset control variables.
+    (setq gnus-demon-handler-state
+	  (mapcar
+	   (lambda (handler)
+	     (list (car handler) (gnus-demon-time-to-step (nth 1 handler))
+		   (nth 2 handler)))
+	   gnus-demon-handlers))
+    (setq gnus-demon-idle-time 0)
+    (setq gnus-demon-idle-has-been-called nil)
+    (setq gnus-use-demon t)))
+
+(gnus-add-shutdown 'gnus-demon-cancel 'gnus)
+
+(defun gnus-demon-cancel ()
+  "Cancel any Gnus daemons."
+  (interactive)
+  (when gnus-demon-timer
+    (nnheader-cancel-timer gnus-demon-timer))
+  (setq gnus-demon-timer nil
+	gnus-use-demon nil)
+  (condition-case ()
+      (nnheader-cancel-function-timers 'gnus-demon)
+    (error t)))
+
+(defun gnus-demon-is-idle-p ()
+  "Whether Emacs is idle or not."
+  ;; We do this simply by comparing the 100 most recent keystrokes
+  ;; with the ones we had last time.  If they are the same, one might
+  ;; guess that Emacs is indeed idle.  This only makes sense if one
+  ;; calls this function seldom -- like once a minute, which is what
+  ;; we do here.
+  (let ((keys (recent-keys)))
+    (or (equal keys gnus-demon-last-keys)
+	(progn
+	  (setq gnus-demon-last-keys keys)
+	  nil))))
+
+(defun gnus-demon-time-to-step (time)
+  "Find out how many seconds to TIME, which is on the form \"17:43\"."
+  (if (not (stringp time))
+      time
+    (let* ((date (current-time-string))
+	   (dv (timezone-parse-date date))
+	   (tdate (timezone-make-arpa-date
+		   (string-to-number (aref dv 0))
+		   (string-to-number (aref dv 1))
+		   (string-to-number (aref dv 2)) time
+		   (or (aref dv 4) "UT")))
+	   (nseconds (gnus-time-minus
+		      (gnus-encode-date tdate) (gnus-encode-date date))))
+      (round
+       (/ (+ (if (< (car nseconds) 0)
+ 		 86400 0)
+ 	     (* 65536 (car nseconds))
+ 	     (nth 1 nseconds))
+ 	  gnus-demon-timestep)))))
+
+(defun gnus-demon ()
+  "The Gnus daemon that takes care of running all Gnus handlers."
+  ;; Increase or reset the time Emacs has been idle.
+  (if (gnus-demon-is-idle-p)
+      (incf gnus-demon-idle-time)
+    (setq gnus-demon-idle-time 0)
+    (setq gnus-demon-idle-has-been-called nil))
+  ;; Disable all daemonic stuff if we're in the minibuffer
+  (when (and (not (window-minibuffer-p (selected-window)))
+	     (not gnus-inhibit-demon))
+    ;; Then we go through all the handler and call those that are
+    ;; sufficiently ripe.
+    (let ((handlers gnus-demon-handler-state)
+	  (gnus-inhibit-demon t)
+	  handler time idle)
+      (while handlers
+	(setq handler (pop handlers))
+	(cond
+	 ((numberp (setq time (nth 1 handler)))
+	  ;; These handlers use a regular timeout mechanism.  We decrease
+	  ;; the timer if it hasn't reached zero yet.
+	  (unless (zerop time)
+	    (setcar (nthcdr 1 handler) (decf time)))
+	  (and (zerop time)		; If the timer now is zero...
+	       ;; Test for appropriate idleness
+	       (progn
+		 (setq idle (nth 2 handler))
+		 (cond
+		  ((null idle) t)	; Don't care about idle.
+		  ((numberp idle)	; Numerical idle...
+		   (< idle gnus-demon-idle-time)) ; Idle timed out.
+		  (t (< 0 gnus-demon-idle-time)))) ; Or just need to be idle.
+	       ;; So we call the handler.
+	       (progn
+		 (funcall (car handler))
+		 ;; And reset the timer.
+		 (setcar (nthcdr 1 handler)
+			 (gnus-demon-time-to-step
+			  (nth 1 (assq (car handler) gnus-demon-handlers)))))))
+	 ;; These are only supposed to be called when Emacs is idle.
+	 ((null (setq idle (nth 2 handler)))
+	  ;; We do nothing.
+	  )
+	 ((not (numberp idle))
+	  ;; We want to call this handler each and every time that
+	  ;; Emacs is idle.
+	  (funcall (car handler)))
+	 (t
+	  ;; We want to call this handler only if Emacs has been idle
+	  ;; for a specified number of timesteps.
+	  (and (not (memq (car handler) gnus-demon-idle-has-been-called))
+	       (< idle gnus-demon-idle-time)
+	       (progn
+		 (funcall (car handler))
+		 ;; Make sure the handler won't be called once more in
+		 ;; this idle-cycle.
+		 (push (car handler) gnus-demon-idle-has-been-called)))))))))
+
+(defun gnus-demon-add-nocem ()
+  "Add daemonic NoCeM handling to Gnus."
+  (gnus-demon-add-handler 'gnus-demon-scan-nocem 60 t))
+
+(defun gnus-demon-scan-nocem ()
+  "Scan NoCeM groups for NoCeM messages."
+  (save-window-excursion
+    (gnus-nocem-scan-groups)))
+
+(defun gnus-demon-add-disconnection ()
+  "Add daemonic server disconnection to Gnus."
+  (gnus-demon-add-handler 'gnus-demon-close-connections nil 30))
+
+(defun gnus-demon-close-connections ()
+  (save-window-excursion
+    (gnus-close-backends)))
+
+(defun gnus-demon-add-scanmail ()
+  "Add daemonic scanning of mail from the mail backends."
+  (gnus-demon-add-handler 'gnus-demon-scan-mail 120 60))
+
+(defun gnus-demon-scan-mail ()
+  (save-window-excursion
+    (let ((servers gnus-opened-servers)
+	  server)
+      (while (setq server (car (pop servers)))
+	(and (gnus-check-backend-function 'request-scan (car server))
+	     (or (gnus-server-opened server)
+		 (gnus-open-server server))
+	     (gnus-request-scan nil server))))))
+
+(defun gnus-demon-add-rescan ()
+  "Add daemonic scanning of new articles from all backends."
+  (gnus-demon-add-handler 'gnus-demon-scan-news 120 60))
+
+(defun gnus-demon-scan-news ()
+  (save-window-excursion
+    (when (gnus-alive-p)
+      (save-excursion
+	(set-buffer gnus-group-buffer)
+	(gnus-group-get-new-news)))))
+
+(defun gnus-demon-add-scan-timestamps ()
+  "Add daemonic updating of timestamps in empty newgroups."
+  (gnus-demon-add-handler 'gnus-demon-scan-timestamps nil 30))
+
+(defun gnus-demon-scan-timestamps ()
+  "Set the timestamp on all newsgroups with no unread and no ticked articles."
+  (when (gnus-alive-p)
+    (let ((cur-time (current-time))
+	  (newsrc (cdr gnus-newsrc-alist))
+	  info group unread has-ticked)
+      (while (setq info (pop newsrc))
+	(setq group (gnus-info-group info)
+	      unread (gnus-group-unread group)
+	      has-ticked (cdr (assq 'tick (gnus-info-marks info))))
+	(when (and (numberp unread)
+		   (= unread 0)
+		   (not has-ticked))
+	  (gnus-group-set-parameter group 'timestamp cur-time))))))
+
+(provide 'gnus-demon)
+
+;;; gnus-demon.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-dup.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,158 @@
+;;; gnus-dup.el --- suppression of duplicate articles in Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; This package tries to mark articles as read the second time the
+;; user reads a copy.  This is useful if the server doesn't support
+;; Xref properly, or if the user reads the same group from several
+;; servers.
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-art)
+
+(defgroup gnus-duplicate nil
+  "Suppression of duplicate articles."
+  :group 'gnus)
+
+(defcustom gnus-save-duplicate-list nil
+  "*If non-nil, save the duplicate list when shutting down Gnus.
+If nil, duplicate suppression will only work on duplicates
+seen in the same session."
+  :group 'gnus-duplicate
+  :type 'boolean)
+
+(defcustom gnus-duplicate-list-length 10000
+  "*The number of Message-IDs to keep in the duplicate suppression list."
+  :group 'gnus-duplicate
+  :type 'integer)
+
+(defcustom gnus-duplicate-file (nnheader-concat gnus-directory "suppression")
+  "*The name of the file to store the duplicate suppression list."
+  :group 'gnus-duplicate
+  :type 'file)
+
+;;; Internal variables
+
+(defvar gnus-dup-list nil)
+(defvar gnus-dup-hashtb nil)
+
+(defvar gnus-dup-list-dirty nil)
+
+;;;
+;;; Starting and stopping
+;;;
+
+(gnus-add-shutdown 'gnus-dup-close 'gnus)
+
+(defun gnus-dup-close ()
+  "Possibly save the duplicate suppression list and shut down the subsystem."
+  (gnus-dup-save)
+  (setq gnus-dup-list nil
+	gnus-dup-hashtb nil
+	gnus-dup-list-dirty nil))
+
+(defun gnus-dup-open ()
+  "Possibly read the duplicate suppression list and start the subsystem."
+  (if gnus-save-duplicate-list
+      (gnus-dup-read)
+    (setq gnus-dup-list nil))
+  (setq gnus-dup-hashtb (gnus-make-hashtable gnus-duplicate-list-length))
+  ;; Enter all Message-IDs into the hash table.
+  (let ((list gnus-dup-list)
+	(obarray gnus-dup-hashtb))
+    (while list
+      (intern (pop list)))))
+
+(defun gnus-dup-read ()
+  "Read the duplicate suppression list."
+  (setq gnus-dup-list nil)
+  (when (file-exists-p gnus-duplicate-file)
+    (load gnus-duplicate-file t t t)))
+
+(defun gnus-dup-save ()
+  "Save the duplicate suppression list."
+  (when (and gnus-save-duplicate-list
+	     gnus-dup-list-dirty)
+    (nnheader-temp-write gnus-duplicate-file
+      (gnus-prin1 `(setq gnus-dup-list ',gnus-dup-list))))
+  (setq gnus-dup-list-dirty nil))
+
+;;;
+;;; Interface functions
+;;;
+
+(defun gnus-dup-enter-articles ()
+  "Enter articles from the current group for future duplicate suppression."
+  (unless gnus-dup-list
+    (gnus-dup-open))
+  (setq gnus-dup-list-dirty t)		; mark list for saving
+  (let ((data gnus-newsgroup-data)
+ 	datum msgid)
+    ;; Enter the Message-IDs of all read articles into the list
+    ;; and hash table.
+    (while (setq datum (pop data))
+      (when (and (not (gnus-data-pseudo-p datum))
+		 (> (gnus-data-number datum) 0)
+		 (gnus-data-read-p datum)
+		 (not (= (gnus-data-mark datum) gnus-canceled-mark))
+ 		 (setq msgid (mail-header-id (gnus-data-header datum)))
+ 		 (not (nnheader-fake-message-id-p msgid))
+ 		 (not (intern-soft msgid gnus-dup-hashtb)))
+	(push msgid gnus-dup-list)
+ 	(intern msgid gnus-dup-hashtb))))
+  ;; Chop off excess Message-IDs from the list.
+  (let ((end (nthcdr gnus-duplicate-list-length gnus-dup-list)))
+    (when end
+      (setcdr end nil))))
+
+(defun gnus-dup-suppress-articles ()
+  "Mark duplicate articles as read."
+  (unless gnus-dup-list
+    (gnus-dup-open))
+  (gnus-message 6 "Suppressing duplicates...")
+  (let ((headers gnus-newsgroup-headers)
+	number header)
+    (while (setq header (pop headers))
+      (when (and (intern-soft (mail-header-id header) gnus-dup-hashtb)
+		 (gnus-summary-article-unread-p (mail-header-number header)))
+	(setq gnus-newsgroup-unreads
+	      (delq (setq number (mail-header-number header))
+		    gnus-newsgroup-unreads))
+	(push (cons number gnus-duplicate-mark)
+	      gnus-newsgroup-reads))))
+  (gnus-message 6 "Suppressing duplicates...done"))
+
+(defun gnus-dup-unsuppress-article (article)
+  "Stop suppression of ARTICLE."
+  (let ((id (mail-header-id (gnus-data-header (gnus-data-find article)))))
+    (when id
+      (setq gnus-dup-list-dirty t)
+      (setq gnus-dup-list (delete id gnus-dup-list))
+      (unintern id gnus-dup-hashtb))))
+
+(provide 'gnus-dup)
+
+;;; gnus-dup.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-eform.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,130 @@
+;;; gnus-eform.el --- a mode for editing forms for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-win)
+
+;;;
+;;; Editing forms
+;;;
+
+(defgroup gnus-edit-form nil
+  "A mode for editing forms."
+  :group 'gnus)
+
+(defcustom gnus-edit-form-mode-hook nil
+  "Hook run in `gnus-edit-form-mode' buffers."
+  :group 'gnus-edit-form
+  :type 'hook)
+
+(defcustom gnus-edit-form-menu-hook nil
+  "Hook run when creating menus in `gnus-edit-form-mode' buffers."
+  :group 'gnus-edit-form
+  :type 'hook)
+
+;;; Internal variables
+
+(defvar gnus-edit-form-done-function nil)
+(defvar gnus-edit-form-buffer "*Gnus edit form*")
+
+(defvar gnus-edit-form-mode-map nil)
+(unless gnus-edit-form-mode-map
+  (setq gnus-edit-form-mode-map (copy-keymap emacs-lisp-mode-map))
+  (gnus-define-keys gnus-edit-form-mode-map
+    "\C-c\C-c" gnus-edit-form-done
+    "\C-c\C-k" gnus-edit-form-exit))
+
+(defun gnus-edit-form-make-menu-bar ()
+  (unless (boundp 'gnus-edit-form-menu)
+    (easy-menu-define
+     gnus-edit-form-menu gnus-edit-form-mode-map ""
+     '("Edit Form"
+       ["Exit and save changes" gnus-edit-form-done t]
+       ["Exit" gnus-edit-form-exit t]))
+    (run-hooks 'gnus-edit-form-menu-hook)))
+
+(defun gnus-edit-form-mode ()
+  "Major mode for editing forms.
+It is a slightly enhanced emacs-lisp-mode.
+
+\\{gnus-edit-form-mode-map}"
+  (interactive)
+  (when (gnus-visual-p 'group-menu 'menu)
+    (gnus-edit-form-make-menu-bar))
+  (kill-all-local-variables)
+  (setq major-mode 'gnus-edit-form-mode)
+  (setq mode-name "Edit Form")
+  (use-local-map gnus-edit-form-mode-map)
+  (make-local-variable 'gnus-edit-form-done-function)
+  (make-local-variable 'gnus-prev-winconf)
+  (run-hooks 'gnus-edit-form-mode-hook))
+
+(defun gnus-edit-form (form documentation exit-func)
+  "Edit FORM in a new buffer.
+Call EXIT-FUNC on exit.  Display DOCUMENTATION in the beginning
+of the buffer."
+  (let ((winconf (current-window-configuration)))
+    (set-buffer (get-buffer-create gnus-edit-form-buffer))
+    (gnus-configure-windows 'edit-form)
+    (gnus-add-current-to-buffer-list)
+    (gnus-edit-form-mode)
+    (setq gnus-prev-winconf winconf)
+    (setq gnus-edit-form-done-function exit-func)
+    (erase-buffer)
+    (insert documentation)
+    (unless (bolp)
+      (insert "\n"))
+    (goto-char (point-min))
+    (while (not (eobp))
+      (insert ";;; ")
+      (forward-line 1))
+    (insert ";; Type `C-c C-c' after you've finished editing.\n")
+    (insert "\n")
+    (let ((p (point)))
+      (pp form (current-buffer))
+      (insert "\n")
+      (goto-char p))))
+
+(defun gnus-edit-form-done ()
+  "Update changes and kill the current buffer."
+  (interactive)
+  (goto-char (point-min))
+  (let ((form (read (current-buffer)))
+	(func gnus-edit-form-done-function))
+    (gnus-edit-form-exit)
+    (funcall func form)))
+
+(defun gnus-edit-form-exit ()
+  "Kill the current buffer."
+  (interactive)
+  (let ((winconf gnus-prev-winconf))
+    (kill-buffer (current-buffer))
+    (set-window-configuration winconf)))
+
+(provide 'gnus-eform)
+
+;;; gnus-eform.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-ems.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,212 @@
+;;; gnus-ems.el --- functions for making Gnus work under different Emacsen
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+
+;;; Function aliases later to be redefined for XEmacs usage.
+
+(defvar gnus-xemacs (string-match "XEmacs\\|Lucid" emacs-version)
+  "Non-nil if running under XEmacs.")
+
+(defvar gnus-mouse-2 [mouse-2])
+(defvar gnus-down-mouse-2 [down-mouse-2])
+
+(eval-and-compile
+  (autoload 'gnus-xmas-define "gnus-xmas")
+  (autoload 'gnus-xmas-redefine "gnus-xmas")
+  (autoload 'appt-select-lowest-window "appt.el"))
+
+(or (fboundp 'mail-file-babyl-p)
+    (fset 'mail-file-babyl-p 'rmail-file-p))
+
+;;; Mule functions.
+
+(defun gnus-mule-cite-add-face (number prefix face)
+  ;; At line NUMBER, ignore PREFIX and add FACE to the rest of the line.
+  (when face
+    (let ((inhibit-point-motion-hooks t)
+	  from to)
+      (goto-line number)
+      (if (boundp 'MULE)
+	  (forward-char (chars-in-string prefix))
+	(forward-char (length prefix)))
+      (skip-chars-forward " \t")
+      (setq from (point))
+      (end-of-line 1)
+      (skip-chars-backward " \t")
+      (setq to (point))
+      (when (< from to)
+	(gnus-overlay-put (gnus-make-overlay from to) 'face face)))))
+
+(defun gnus-mule-max-width-function (el max-width)
+  (` (let* ((val (eval (, el)))
+	    (valstr (if (numberp val)
+			(int-to-string val) val)))
+       (if (> (length valstr) (, max-width))
+	   (truncate-string valstr (, max-width))
+	 valstr))))
+
+(eval-and-compile
+  (if (string-match "XEmacs\\|Lucid" emacs-version)
+      nil
+
+    (defvar gnus-mouse-face-prop 'mouse-face
+      "Property used for highlighting mouse regions.")
+
+    (defvar gnus-article-x-face-command
+      "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | xv -quit -"
+      "String or function to be executed to display an X-Face header.
+If it is a string, the command will be executed in a sub-shell
+asynchronously.	 The compressed face will be piped to this command."))
+
+  (cond
+   ((string-match "XEmacs\\|Lucid" emacs-version)
+    (gnus-xmas-define))
+
+   ((or (not (boundp 'emacs-minor-version))
+	(< emacs-minor-version 30))
+    ;; Remove the `intangible' prop.
+    (let ((props (and (boundp 'gnus-hidden-properties)
+		      gnus-hidden-properties)))
+      (while (and props (not (eq (car (cdr props)) 'intangible)))
+	(setq props (cdr props)))
+      (when props
+	(setcdr props (cdr (cdr (cdr props))))))
+    (unless (fboundp 'buffer-substring-no-properties)
+      (defun buffer-substring-no-properties (beg end)
+	(format "%s" (buffer-substring beg end)))))
+
+   ((boundp 'MULE)
+    (provide 'gnusutil))))
+
+(eval-and-compile
+  (cond
+   ((not window-system)
+    (defun gnus-dummy-func (&rest args))
+    (let ((funcs '(mouse-set-point set-face-foreground
+				   set-face-background x-popup-menu)))
+      (while funcs
+	(unless (fboundp (car funcs))
+	  (fset (car funcs) 'gnus-dummy-func))
+	(setq funcs (cdr funcs))))))
+  (unless (fboundp 'file-regular-p)
+    (defun file-regular-p (file)
+      (and (not (file-directory-p file))
+	   (not (file-symlink-p file))
+	   (file-exists-p file))))
+  (unless (fboundp 'face-list)
+    (defun face-list (&rest args))))
+
+(eval-and-compile
+  (let ((case-fold-search t))
+    (cond
+     ((string-match "windows-nt\\|os/2\\|emx" (format "%s" system-type))
+      (setq nnheader-file-name-translation-alist
+	    (append nnheader-file-name-translation-alist
+		    '((?: . ?_)
+		      (?+ . ?-))))))))
+
+(defvar gnus-tmp-unread)
+(defvar gnus-tmp-replied)
+(defvar gnus-tmp-score-char)
+(defvar gnus-tmp-indentation)
+(defvar gnus-tmp-opening-bracket)
+(defvar gnus-tmp-lines)
+(defvar gnus-tmp-name)
+(defvar gnus-tmp-closing-bracket)
+(defvar gnus-tmp-subject-or-nil)
+
+(defun gnus-ems-redefine ()
+  (cond
+   ((string-match "XEmacs\\|Lucid" emacs-version)
+    (gnus-xmas-redefine))
+
+   ((featurep 'mule)
+    ;; Mule and new Emacs definitions
+
+    ;; [Note] Now there are three kinds of mule implementations,
+    ;; original MULE, XEmacs/mule and beta version of Emacs including
+    ;; some mule features. Unfortunately these API are different. In
+    ;; particular, Emacs (including original MULE) and XEmacs are
+    ;; quite different.
+    ;; Predicates to check are following:
+    ;; (boundp 'MULE) is t only if MULE (original; anything older than
+    ;;                     Mule 2.3) is running.
+    ;; (featurep 'mule) is t when every mule variants are running.
+
+    ;; These implementations may be able to share between original
+    ;; MULE and beta version of new Emacs. In addition, it is able to
+    ;; detect XEmacs/mule by (featurep 'mule) and to check variable
+    ;; `emacs-version'. In this case, implementation for XEmacs/mule
+    ;; may be able to share between XEmacs and XEmacs/mule.
+
+    (defalias 'gnus-truncate-string 'truncate-string)
+
+    (defvar gnus-summary-display-table nil
+      "Display table used in summary mode buffers.")
+    (fset 'gnus-cite-add-face 'gnus-mule-cite-add-face)
+    (fset 'gnus-max-width-function 'gnus-mule-max-width-function)
+    (fset 'gnus-summary-set-display-table 'ignore)
+
+    (when (boundp 'gnus-check-before-posting)
+      (setq gnus-check-before-posting
+	    (delq 'long-lines
+		  (delq 'control-chars gnus-check-before-posting))))
+
+    (defun gnus-summary-line-format-spec ()
+      (insert gnus-tmp-unread gnus-tmp-replied
+	      gnus-tmp-score-char gnus-tmp-indentation)
+      (put-text-property
+       (point)
+       (progn
+	 (insert
+	  gnus-tmp-opening-bracket
+	  (format "%4d: %-20s"
+		  gnus-tmp-lines
+		  (if (> (length gnus-tmp-name) 20)
+		      (truncate-string gnus-tmp-name 20)
+		    gnus-tmp-name))
+	  gnus-tmp-closing-bracket)
+	 (point))
+       gnus-mouse-face-prop gnus-mouse-face)
+      (insert " " gnus-tmp-subject-or-nil "\n"))
+    )))
+
+(defun gnus-region-active-p ()
+  "Say whether the region is active."
+  (and (boundp 'transient-mark-mode)
+       transient-mark-mode
+       (boundp 'mark-active)
+       mark-active))
+
+(provide 'gnus-ems)
+
+;; Local Variables:
+;; byte-compile-warnings: '(redefine callargs)
+;; End:
+
+;;; gnus-ems.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-gl.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,862 @@
+;;; gnus-gl.el --- an interface to GroupLens for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Brad Miller <bmiller@cs.umn.edu>
+;; Keywords: news, score
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; GroupLens software and documentation is copyright (c) 1995 by Paul
+;; Resnick (Massachusetts Institute of Technology); Brad Miller, John
+;; Riedl, Jon Herlocker, and Joseph Konstan (University of Minnesota),
+;; and David Maltz (Carnegie-Mellon University).
+;;
+;; Permission to use, copy, modify, and distribute this documentation
+;; for non-commercial and commercial purposes without fee is hereby
+;; granted provided that this copyright notice and permission notice
+;; appears in all copies and that the names of the individuals and
+;; institutions holding this copyright are not used in advertising or
+;; publicity pertaining to this software without specific, written
+;; prior permission.  The copyright holders make no representations
+;; about the suitability of this software and documentation for any
+;; purpose.  It is provided ``as is'' without express or implied
+;; warranty.
+;;
+;; The copyright holders request that they be notified of
+;; modifications of this code.  Please send electronic mail to
+;; grouplens@cs.umn.edu for more information or to announce derived
+;; works.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Author: Brad Miller
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; User Documentation:
+;; To use GroupLens you must load this file.
+;; You must also register a pseudonym with the Better Bit Bureau.
+;; http://www.cs.umn.edu/Research/GroupLens
+;;
+;;    ---------------- For your .emacs or .gnus file ----------------
+;;
+;; As of version 2.5, grouplens now works as a minor mode of
+;; gnus-summary-mode.  To get make that work you just need a couple of
+;; hooks.
+;; (setq gnus-use-grouplens t)
+;; (setq grouplens-pseudonym "")
+;; (setq grouplens-bbb-host "grouplens.cs.umn.edu")
+;;
+;; (setq gnus-summary-default-score 0)
+;;
+;;                              USING GROUPLENS
+;; How do I Rate an article??
+;;   Before you type n to go to the next article, hit a number from 1-5
+;;   Type r in the summary buffer and you will be prompted.
+;;   Note that when you're in grouplens-minor-mode 'r' masks the
+;;   usual reply binding for 'r'
+;;
+;; What if, Gasp, I find a bug???
+;; Please type M-x gnus-gl-submit-bug-report.  This will set up a
+;; mail buffer with the  state of variables and buffers that will help
+;; me debug the problem.  A short description up front would help too!
+;;
+;; How do I display the prediction for an article:
+;;  If you set the gnus-summary-line-format as shown above, the score
+;;  (prediction) will be shown automatically.
+;;
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Programmer  Notes
+;; 10/9/95
+;; gnus-scores-articles contains the articles
+;; When scoring is done, the call tree looks something like:
+;; gnus-possibly-score-headers
+;;  ==> gnus-score-headers
+;;      ==> gnus-score-load-file
+;;          ==> get-all-mids  (from the eval form)
+;;
+;; it would be nice to have one that gets called after all the other
+;; headers have been scored.
+;; we may want a variable gnus-grouplens-scale-factor
+;; and gnus-grouplens-offset  this would probably be either -3 or 0
+;; to make the scores centered around zero or not.
+;; Notes 10/12/95
+;; According to Lars, Norse god of gnus, the simple way to insert a
+;; call to an external function is to have a function added to the
+;; variable gnus-score-find-files-function  This new function
+;; gnus-grouplens-score-alist will return a core alist that
+;; has (("message-id" ("<message-id-xxxx>" score) ("<message-id-xxxy>" score))
+;; This seems like it would be pretty inefficient, though workable.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;  TODO
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; 3. Add some more ways to rate messages
+;; 4. Better error handling for token timeouts.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; bugs
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+
+;;; Code:
+
+(require 'gnus-score)
+(require 'cl)
+(require 'gnus)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;; User variables
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar gnus-summary-grouplens-line-format
+  "%U\%R\%z%l%I\%(%[%4L: %-20,20n%]%) %s\n"
+  "*The line format spec in summary GroupLens mode buffers.")
+
+(defvar grouplens-pseudonym ""
+  "User's pseudonym.
+This pseudonym is obtained during the registration process")
+
+(defvar grouplens-bbb-host "grouplens.cs.umn.edu"
+  "Host where the bbbd is running" )
+
+(defvar grouplens-bbb-port 9000
+  "Port where the bbbd is listening" )
+
+(defvar grouplens-newsgroups
+  '("comp.groupware" "comp.human-factors" "comp.lang.c++"
+    "comp.lang.java" "comp.os.linux.admin" "comp.os.linux.advocacy"
+    "comp.os.linux.announce" "comp.os.linux.answers"
+    "comp.os.linux.development" "comp.os.linux.development.apps"
+    "comp.os.linux.development.system" "comp.os.linux.hardware"
+    "comp.os.linux.help" "comp.os.linux.m68k" "comp.os.linux.misc"
+    "comp.os.linux.networking" "comp.os.linux.setup" "comp.os.linux.x"
+    "mn.general" "rec.arts.movies" "rec.arts.movies.current-films"
+    "rec.food.recipes" "rec.humor")
+  "*Groups that are part of the GroupLens experiment.")
+
+(defvar grouplens-prediction-display 'prediction-spot
+  "valid values are:
+      prediction-spot -- an * corresponding to the prediction between 1 and 5,
+      confidence-interval -- a numeric confidence interval
+      prediction-bar --  |#####     | the longer the bar, the better the article,
+      confidence-bar --  |  -----   } the prediction is in the middle of the bar,
+      confidence-spot -- )  *       | the spot gets bigger with more confidence,
+      prediction-num  --   plain-old numeric value,
+      confidence-plus-minus  -- prediction +/i confidence")
+
+(defvar grouplens-score-offset 0
+  "Offset the prediction by this value.
+Setting this variable to -2 would have the following effect on
+GroupLens scores:
+
+   1   -->   -2
+   2   -->   -1
+   3   -->    0
+   4   -->    1
+   5   -->    2
+
+The reason is that a user might want to do this is to combine
+GroupLens predictions with scores calculated by other score methods.")
+
+(defvar grouplens-score-scale-factor 1
+  "This variable allows the user to magnify the effect of GroupLens scores.
+The scale factor is applied after the offset.")
+
+(defvar gnus-grouplens-override-scoring 'override
+  "Tell GroupLens to override the normal Gnus scoring mechanism.
+GroupLens scores can be combined with gnus scores in one of three ways.
+'override -- just use grouplens predictions for grouplens groups
+'combine  -- combine grouplens scores with gnus scores
+'separate -- treat grouplens scores completely separate from gnus")
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;; Program global variables
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defvar grouplens-bbb-token nil
+  "Current session token number")
+
+(defvar grouplens-bbb-process nil
+  "Process Id of current bbbd network stream process")
+
+(defvar grouplens-bbb-buffer nil
+  "Buffer associated with the BBBD process")
+
+(defvar grouplens-rating-alist nil
+  "Current set of  message-id rating pairs")
+
+(defvar grouplens-current-hashtable nil
+  "A hashtable to hold predictions from the BBB")
+
+(defvar grouplens-current-group nil)
+
+;;(defvar bbb-alist nil)
+
+(defvar bbb-timeout-secs 10
+  "Number of seconds to wait for some response from the BBB.
+If this times out we give up and assume that something has died..." )
+
+(defvar grouplens-previous-article nil
+  "Message-ID of the last article read.")
+
+(defvar bbb-read-point)
+(defvar bbb-response-point)
+
+(defun bbb-renew-hash-table ()
+  (setq grouplens-current-hashtable (make-vector 100 0)))
+
+(bbb-renew-hash-table)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;  Utility Functions
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun bbb-connect-to-bbbd (host port)
+  (unless grouplens-bbb-buffer
+    (setq grouplens-bbb-buffer
+	  (get-buffer-create (format " *BBBD trace: %s*" host)))
+    (save-excursion
+      (set-buffer grouplens-bbb-buffer)
+      (make-local-variable 'bbb-read-point)
+      (make-local-variable 'bbb-response-point)
+      (setq bbb-read-point (point-min))))
+
+  ;; if an old process is still running for some reason, kill it
+  (when grouplens-bbb-process
+    (ignore-errors
+      (when (eq 'open (process-status grouplens-bbb-process))
+	(set-process-buffer grouplens-bbb-process nil)
+	(delete-process grouplens-bbb-process))))
+
+  ;; clear the trace buffer of old output
+  (save-excursion
+    (set-buffer grouplens-bbb-buffer)
+    (erase-buffer))
+
+  ;; open the connection to the server
+  (catch 'done
+    (condition-case error
+	(setq grouplens-bbb-process
+	      (open-network-stream "BBBD" grouplens-bbb-buffer host port))
+      (error (gnus-message 3 "Error: Failed to connect to BBB")
+	     nil))
+    (and (null grouplens-bbb-process)
+	 (throw 'done nil))
+    (save-excursion
+      (set-buffer grouplens-bbb-buffer)
+      (setq bbb-read-point (point-min))
+      (or (bbb-read-response grouplens-bbb-process)
+	  (throw 'done nil))))
+
+  ;; return the process
+  grouplens-bbb-process)
+
+(defun bbb-send-command (process command)
+  (goto-char (point-max))
+  (insert command)
+  (insert "\r\n")
+  (setq bbb-read-point (point))
+  (setq bbb-response-point (point))
+  (set-marker (process-mark process) (point)) ; process output also comes here
+  (process-send-string process command)
+  (process-send-string process "\r\n")
+  (process-send-eof process))
+
+(defun bbb-read-response (process)
+  "This function eats the initial response of OK or ERROR from the BBB."
+  (let ((case-fold-search nil)
+	match-end)
+    (goto-char bbb-read-point)
+    (while (and (not (search-forward "\r\n" nil t))
+		(accept-process-output process bbb-timeout-secs))
+      (goto-char bbb-read-point))
+    (setq match-end (point))
+    (goto-char bbb-read-point)
+    (setq bbb-read-point match-end)
+    (looking-at "OK")))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;       Login Functions
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun bbb-login ()
+  "return the token number if login is successful, otherwise return nil"
+  (interactive)
+  (setq grouplens-bbb-token nil)
+  (if (not (equal grouplens-pseudonym ""))
+      (let ((bbb-process
+	     (bbb-connect-to-bbbd grouplens-bbb-host grouplens-bbb-port)))
+	(if bbb-process
+	    (save-excursion
+	      (set-buffer (process-buffer bbb-process))
+	      (bbb-send-command bbb-process
+				(concat "login " grouplens-pseudonym))
+	      (if (bbb-read-response bbb-process)
+		  (setq grouplens-bbb-token (bbb-extract-token-number))
+	      (gnus-message 3 "Error: GroupLens login failed")))))
+    (gnus-message 3 "Error: you must set a pseudonym"))
+  grouplens-bbb-token)
+
+(defun bbb-extract-token-number ()
+  (let ((token-pos (search-forward "token=" nil t)))
+    (when (looking-at "[0-9]+")
+      (buffer-substring token-pos (match-end 0)))))
+
+(gnus-add-shutdown 'bbb-logout 'gnus)
+
+(defun bbb-logout ()
+  "logout of bbb session"
+  (when grouplens-bbb-token
+    (let ((bbb-process
+	   (bbb-connect-to-bbbd grouplens-bbb-host grouplens-bbb-port)))
+      (when bbb-process
+	(save-excursion
+	  (set-buffer (process-buffer bbb-process))
+	  (bbb-send-command bbb-process (concat "logout " grouplens-bbb-token))
+	  (bbb-read-response bbb-process))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;       Get Predictions
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun bbb-build-mid-scores-alist (groupname)
+  "this function can be called as part of the function to return the
+list of score files to use.  See the gnus variable
+gnus-score-find-score-files-function.
+
+*Note:*  If you want to use grouplens scores along with calculated scores,
+you should see the offset and scale variables.  At this point, I don't
+recommend using both scores and grouplens predictions together."
+  (setq grouplens-current-group groupname)
+  (when (member groupname grouplens-newsgroups)
+    (setq grouplens-previous-article nil)
+    ;; scores-alist should be a list of lists:
+    ;;  ((("message-id" ("<mid1>" score1 nil s) ("<mid2> score2 nil s))))
+    ;;`((("message-id" . ,predict-list))) ; Yes, this is the return value
+    (list
+     (list
+      (list (append (list "message-id")
+		    (bbb-get-predictions (bbb-get-all-mids) groupname)))))))
+
+(defun bbb-get-predictions (midlist groupname)
+  "Ask the bbb for predictions, and build up the score alist."
+  (gnus-message 5 "Fetching Predictions...")
+  (if grouplens-bbb-token
+      (let ((bbb-process (bbb-connect-to-bbbd grouplens-bbb-host
+					      grouplens-bbb-port)))
+	(when bbb-process
+	  (save-excursion
+	    (set-buffer (process-buffer bbb-process))
+	    (bbb-send-command bbb-process
+			      (bbb-build-predict-command midlist groupname
+							 grouplens-bbb-token))
+	    (if (bbb-read-response bbb-process)
+		(bbb-get-prediction-response bbb-process)
+	      (gnus-message 1 "Invalid Token, login and try again")
+	      (ding)))))
+    (gnus-message 3 "Error: You are not logged in to a BBB")
+    (ding)))
+
+(defun bbb-get-all-mids ()
+  (mapcar (function (lambda (x) (mail-header-id x))) gnus-newsgroup-headers))
+
+(defun bbb-build-predict-command (mlist grpname token)
+  (concat "getpredictions " token " " grpname "\r\n"
+	  (mapconcat 'identity mlist "\r\n") "\r\n.\r\n"))
+
+(defun bbb-get-prediction-response (process)
+  (let ((case-fold-search nil))
+    (goto-char bbb-read-point)
+    (while (and (not (search-forward ".\r\n" nil t))
+		(accept-process-output process bbb-timeout-secs))
+      (goto-char bbb-read-point))
+    (goto-char (+ bbb-response-point 4));; we ought to be right before OK
+    (bbb-build-response-alist)))
+
+;; build-response-alist assumes that the cursor has been positioned at
+;; the first line of the list of mid/rating pairs.
+(defun bbb-build-response-alist ()
+  (let (resp mid pred)
+    (while
+	(cond
+	 ((looking-at "\\(<.*>\\) :nopred=")
+	  ;;(push `(,(bbb-get-mid) ,gnus-summary-default-score nil s) resp)
+	  (forward-line 1)
+	  t)
+	 ((looking-at "\\(<.*>\\) :pred=\\([0-9]\.[0-9][0-9]\\) :conflow=\\([0-9]\.[0-9][0-9]\\) :confhigh=\\([0-9]\.[0-9][0-9]\\)")
+	  (setq mid (bbb-get-mid)
+		pred (bbb-get-pred))
+	  (push `(,mid ,pred nil s) resp)
+	  (gnus-sethash mid (list pred (bbb-get-confl) (bbb-get-confh))
+		      grouplens-current-hashtable)
+	  (forward-line 1)
+	  t)
+	 ((looking-at "\\(<.*>\\) :pred=\\([0-9]\.[0-9][0-9]\\)")
+	  (setq mid (bbb-get-mid)
+		pred (bbb-get-pred))
+	  (push `(,mid ,pred nil s) resp)
+	  (gnus-sethash mid (list pred 0 0) grouplens-current-hashtable)
+	  (forward-line 1)
+	  t)
+	 (t nil)))
+    resp))
+
+;; these "get" functions assume that there is an active match lying
+;; around.  Where the first parenthesized expression is the
+;; message-id, and the second is the prediction, the third and fourth
+;; are the confidence interval
+;;
+;; Since gnus assumes that scores are integer values?? we round the
+;; prediction.
+(defun bbb-get-mid ()
+  (buffer-substring (match-beginning 1) (match-end 1)))
+
+(defun bbb-get-pred ()
+  (let ((tpred (string-to-number (buffer-substring (match-beginning 2)
+						   (match-end 2)))))
+    (if (> tpred 0)
+	(round (* grouplens-score-scale-factor
+		  (+ grouplens-score-offset tpred)))
+      1)))
+
+(defun bbb-get-confl ()
+  (string-to-number (buffer-substring (match-beginning 4) (match-end 4))))
+
+(defun bbb-get-confh ()
+  (string-to-number (buffer-substring (match-beginning 4) (match-end 4))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;      Prediction Display
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defconst grplens-rating-range 4.0)
+(defconst grplens-maxrating 5)
+(defconst grplens-minrating 1)
+(defconst grplens-predstringsize 12)
+
+(defvar gnus-tmp-score)
+(defun bbb-grouplens-score (header)
+  (if (eq gnus-grouplens-override-scoring 'separate)
+      (bbb-grouplens-other-score header)
+    (let* ((rate-string (make-string 12 ?\ ))
+	   (mid (mail-header-id header))
+	   (hashent (gnus-gethash mid grouplens-current-hashtable))
+	   (iscore gnus-tmp-score)
+	   (low (car (cdr hashent)))
+	   (high (car (cdr (cdr hashent)))))
+      (aset rate-string 0 ?|)
+      (aset rate-string 11 ?|)
+      (unless (member grouplens-current-group grouplens-newsgroups)
+	(unless (equal grouplens-prediction-display 'prediction-num)
+	  (cond ((< iscore 0)
+		 (setq iscore 1))
+		((> iscore 5)
+		 (setq iscore 5))))
+	(setq low 0)
+	(setq high 0))
+      (if (and (bbb-valid-score iscore)
+	       (not (null mid)))
+	  (cond
+	   ;; prediction-spot
+	   ((equal grouplens-prediction-display 'prediction-spot)
+	    (setq rate-string (bbb-fmt-prediction-spot rate-string iscore)))
+	   ;; confidence-interval
+	   ((equal grouplens-prediction-display 'confidence-interval)
+	    (setq rate-string (bbb-fmt-confidence-interval iscore low high)))
+	   ;; prediction-bar
+	   ((equal grouplens-prediction-display 'prediction-bar)
+	    (setq rate-string (bbb-fmt-prediction-bar rate-string iscore)))
+	   ;; confidence-bar
+	   ((equal grouplens-prediction-display 'confidence-bar)
+	    (setq rate-string (format "|   %4.2f   |" iscore)))
+	   ;; confidence-spot
+	   ((equal grouplens-prediction-display 'confidence-spot)
+	    (setq rate-string (format "|   %4.2f   |" iscore)))
+	   ;; prediction-num
+	   ((equal grouplens-prediction-display 'prediction-num)
+	    (setq rate-string (bbb-fmt-prediction-num iscore)))
+	   ;; confidence-plus-minus
+	   ((equal grouplens-prediction-display 'confidence-plus-minus)
+	    (setq rate-string (bbb-fmt-confidence-plus-minus iscore low high))
+	    )
+	   (t (gnus-message 3 "Invalid prediction display type")))
+	(aset rate-string 5 ?N) (aset rate-string 6 ?A))
+      rate-string)))
+
+;; Gnus user format function that doesn't depend on
+;; bbb-build-mid-scores-alist being used as the score function, but is
+;; instead called from gnus-select-group-hook. -- LAB
+(defun bbb-grouplens-other-score (header)
+  (if (not (member grouplens-current-group grouplens-newsgroups))
+      ;; Return an empty string
+      ""
+    (let* ((rate-string (make-string 12 ?\ ))
+           (mid (mail-header-id header))
+           (hashent (gnus-gethash mid grouplens-current-hashtable))
+           (pred (or (nth 0 hashent) 0))
+           (low (nth 1 hashent))
+           (high (nth 2 hashent)))
+      ;; Init rate-string
+      (aset rate-string 0 ?|)
+      (aset rate-string 11 ?|)
+      (unless (equal grouplens-prediction-display 'prediction-num)
+	(cond ((< pred 0)
+	       (setq pred 1))
+	      ((> pred 5)
+	       (setq pred 5))))
+      ;; If no entry in BBB hash mark rate string as NA and return
+      (cond
+       ((null hashent)
+	(aset rate-string 5 ?N)
+	(aset rate-string 6 ?A)
+	rate-string)
+
+       ((equal grouplens-prediction-display 'prediction-spot)
+	(bbb-fmt-prediction-spot rate-string pred))
+
+       ((equal grouplens-prediction-display 'confidence-interval)
+	(bbb-fmt-confidence-interval pred low high))
+
+       ((equal grouplens-prediction-display 'prediction-bar)
+	(bbb-fmt-prediction-bar rate-string pred))
+
+       ((equal grouplens-prediction-display 'confidence-bar)
+	(format "|   %4.2f   |" pred))
+
+       ((equal grouplens-prediction-display 'confidence-spot)
+	(format "|   %4.2f   |" pred))
+
+       ((equal grouplens-prediction-display 'prediction-num)
+	(bbb-fmt-prediction-num pred))
+
+       ((equal grouplens-prediction-display 'confidence-plus-minus)
+	(bbb-fmt-confidence-plus-minus pred low high))
+
+       (t
+	(gnus-message 3 "Invalid prediction display type")
+	(aset rate-string 0 ?|)
+	(aset rate-string 11 ?|)
+	rate-string)))))
+
+(defun bbb-valid-score (score)
+  (or (equal grouplens-prediction-display 'prediction-num)
+      (and (>= score grplens-minrating)
+	   (<= score grplens-maxrating))))
+
+(defun bbb-requires-confidence (format-type)
+  (or (equal format-type 'confidence-plus-minus)
+      (equal format-type 'confidence-spot)
+      (equal format-type 'confidence-interval)))
+
+(defun bbb-have-confidence (clow chigh)
+  (not (or (null clow)
+	   (null chigh))))
+
+(defun bbb-fmt-prediction-spot (rate-string score)
+  (aset rate-string
+	(round (* (/ (- score grplens-minrating) grplens-rating-range)
+		  (+ (- grplens-predstringsize 4) 1.49)))
+	?*)
+  rate-string)
+
+(defun bbb-fmt-confidence-interval (score low high)
+  (if (bbb-have-confidence low high)
+      (format "|%4.2f-%4.2f |" low high)
+    (bbb-fmt-prediction-num score)))
+
+(defun bbb-fmt-confidence-plus-minus (score low high)
+  (if (bbb-have-confidence low high)
+      (format "|%3.1f+/-%4.2f|" score (/ (- high low) 2.0))
+    (bbb-fmt-prediction-num score)))
+
+(defun bbb-fmt-prediction-bar (rate-string score)
+  (let* ((i 1)
+	 (step (/ grplens-rating-range (- grplens-predstringsize 4)))
+	 (half-step (/ step 2))
+	 (loc (- grplens-minrating half-step)))
+    (while (< i (- grplens-predstringsize 2))
+      (if (> score loc)
+	  (aset rate-string i ?#)
+	(aset rate-string i ?\ ))
+      (setq i (+ i 1))
+      (setq loc (+ loc step)))
+    )
+  rate-string)
+
+(defun bbb-fmt-prediction-num (score)
+  (format "|   %4.2f   |" score))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;       Put Ratings
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun bbb-put-ratings ()
+  (if (and grouplens-bbb-token
+	   grouplens-rating-alist
+	   (member gnus-newsgroup-name grouplens-newsgroups))
+      (let ((bbb-process (bbb-connect-to-bbbd grouplens-bbb-host
+					      grouplens-bbb-port))
+	    (rate-command (bbb-build-rate-command grouplens-rating-alist)))
+	(if bbb-process
+	    (save-excursion
+	      (set-buffer (process-buffer bbb-process))
+	      (gnus-message 5 "Sending Ratings...")
+	      (bbb-send-command bbb-process rate-command)
+	      (if (bbb-read-response bbb-process)
+		  (setq grouplens-rating-alist nil)
+		(gnus-message 1
+			      "Token timed out: call bbb-login and quit again")
+		(ding))
+	      (gnus-message 5 "Sending Ratings...Done"))
+	  (gnus-message 3 "No BBB connection")))
+    (setq grouplens-rating-alist nil)))
+
+(defun bbb-build-rate-command (rate-alist)
+  (concat "putratings " grouplens-bbb-token " " grouplens-current-group " \r\n"
+	  (mapconcat '(lambda (this)	; form (mid . (score . time))
+			(concat (car this)
+				" :rating=" (cadr this) ".00"
+				" :time=" (cddr this)))
+		     rate-alist "\r\n")
+	  "\r\n.\r\n"))
+
+;; Interactive rating functions.
+(defun bbb-summary-rate-article (rating &optional midin)
+  (interactive "nRating: ")
+  (when (member gnus-newsgroup-name grouplens-newsgroups)
+    (let ((mid (or midin (bbb-get-current-id))))
+      (if (and rating
+	       (>= rating grplens-minrating)
+	       (<= rating grplens-maxrating)
+	       mid)
+	  (let ((oldrating (assoc mid grouplens-rating-alist)))
+	    (if oldrating
+		(setcdr oldrating (cons rating 0))
+	      (push `(,mid . (,rating . 0)) grouplens-rating-alist))
+	    (gnus-summary-mark-article nil (int-to-string rating)))
+	(gnus-message 3 "Invalid rating")))))
+
+(defun grouplens-next-unread-article (rating)
+  "Select unread article after current one."
+  (interactive "P")
+  (when rating
+    (bbb-summary-rate-article rating))
+  (gnus-summary-next-unread-article))
+
+(defun grouplens-best-unread-article (rating)
+  "Select unread article after current one."
+  (interactive "P")
+  (when rating
+    (bbb-summary-rate-article rating))
+  (gnus-summary-best-unread-article))
+
+(defun grouplens-summary-catchup-and-exit (rating)
+  "Mark all articles not marked as unread in this newsgroup as read,
+    then exit.   If prefix argument ALL is non-nil, all articles are
+    marked as read."
+  (interactive "P")
+  (when rating
+    (bbb-summary-rate-article rating))
+  (if (numberp rating)
+      (gnus-summary-catchup-and-exit)
+    (gnus-summary-catchup-and-exit rating)))
+
+(defun grouplens-score-thread (score)
+  "Raise the score of the articles in the current thread with SCORE."
+  (interactive "nRating: ")
+  (let (e)
+    (save-excursion
+      (let ((articles (gnus-summary-articles-in-thread))
+	    article)
+	(while (setq article (pop articles))
+	  (gnus-summary-goto-subject article)
+	  (gnus-set-global-variables)
+	  (bbb-summary-rate-article score
+				    (mail-header-id
+				     (gnus-summary-article-header article)))))
+      (setq e (point)))
+    (let ((gnus-summary-check-current t))
+      (or (zerop (gnus-summary-next-subject 1 t))
+	  (goto-char e))))
+  (gnus-summary-recenter)
+  (gnus-summary-position-point)
+  (gnus-set-mode-line 'summary))
+
+(defun bbb-exit-group ()
+  (bbb-put-ratings)
+  (bbb-renew-hash-table))
+
+(defun bbb-get-current-id ()
+  (if gnus-current-headers
+      (mail-header-id gnus-current-headers)
+    (gnus-message 3 "You must select an article before you rate it")))
+
+(defun bbb-grouplens-group-p (group)
+  "Say whether GROUP is a GroupLens group."
+  (if (member group grouplens-newsgroups) " (GroupLens Enhanced)" ""))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;          TIME SPENT READING
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defvar grouplens-current-starting-time nil)
+
+(defun grouplens-start-timer ()
+  (setq grouplens-current-starting-time (current-time)))
+
+(defun grouplens-elapsed-time ()
+  (let ((et (bbb-time-float (current-time))))
+    (- et (bbb-time-float grouplens-current-starting-time))))
+
+(defun bbb-time-float (timeval)
+  (+ (* (car timeval) 65536)
+     (cadr timeval)))
+
+(defun grouplens-do-time ()
+  (when (member gnus-newsgroup-name grouplens-newsgroups)
+    (when grouplens-previous-article
+      (let ((elapsed-time (grouplens-elapsed-time))
+	    (oldrating (assoc grouplens-previous-article
+			      grouplens-rating-alist)))
+	(if (not oldrating)
+	    (push `(,grouplens-previous-article . (0 . ,elapsed-time))
+		  grouplens-rating-alist)
+	  (setcdr oldrating (cons (cadr oldrating) elapsed-time)))))
+    (grouplens-start-timer)
+    (setq grouplens-previous-article (bbb-get-current-id))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;          BUG REPORTING
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defconst gnus-gl-version "gnus-gl.el 2.50")
+(defconst gnus-gl-maintainer-address "grouplens-bug@cs.umn.edu")
+(defun gnus-gl-submit-bug-report ()
+  "Submit via mail a bug report on gnus-gl"
+  (interactive)
+  (require 'reporter)
+  (reporter-submit-bug-report gnus-gl-maintainer-address
+			      (concat "gnus-gl.el " gnus-gl-version)
+			      (list 'grouplens-pseudonym
+				    'grouplens-bbb-host
+				    'grouplens-bbb-port
+				    'grouplens-newsgroups
+				    'grouplens-bbb-token
+				    'grouplens-bbb-process
+				    'grouplens-current-group
+				    'grouplens-previous-article)
+			      nil
+			      'gnus-gl-get-trace))
+
+(defun gnus-gl-get-trace ()
+  "Insert the contents of the BBBD trace buffer"
+  (when grouplens-bbb-buffer
+    (insert-buffer grouplens-bbb-buffer)))
+
+;;
+;; GroupLens minor mode
+;;
+
+(defvar gnus-grouplens-mode nil
+  "Minor mode for providing a GroupLens interface in Gnus summary buffers.")
+
+(defvar gnus-grouplens-mode-map nil)
+
+(unless gnus-grouplens-mode-map
+  (setq gnus-grouplens-mode-map (make-keymap))
+  (gnus-define-keys
+   gnus-grouplens-mode-map
+   "n" grouplens-next-unread-article
+   "r" bbb-summary-rate-article
+   "k" grouplens-score-thread
+   "c" grouplens-summary-catchup-and-exit
+   "," grouplens-best-unread-article))
+
+(defun gnus-grouplens-make-menu-bar ()
+  (unless (boundp 'gnus-grouplens-menu)
+    (easy-menu-define
+     gnus-grouplens-menu gnus-grouplens-mode-map ""
+     '("GroupLens"
+       ["Login" bbb-login t]
+       ["Rate" bbb-summary-rate-article t]
+       ["Next article" grouplens-next-unread-article t]
+       ["Best article" grouplens-best-unread-article t]
+       ["Raise thread" grouplens-score-thread t]
+       ["Report bugs" gnus-gl-submit-bug-report t]))))
+
+(defun gnus-grouplens-mode (&optional arg)
+  "Minor mode for providing a GroupLens interface in Gnus summary buffers."
+  (interactive "P")
+  (when (and (eq major-mode 'gnus-summary-mode)
+	     (member gnus-newsgroup-name grouplens-newsgroups))
+    (make-local-variable 'gnus-grouplens-mode)
+    (setq gnus-grouplens-mode
+	  (if (null arg) (not gnus-grouplens-mode)
+	    (> (prefix-numeric-value arg) 0)))
+    (when gnus-grouplens-mode
+      (make-local-hook 'gnus-select-article-hook)
+      (add-hook 'gnus-select-article-hook 'grouplens-do-time nil 'local)
+      (make-local-hook 'gnus-exit-group-hook)
+      (add-hook 'gnus-exit-group-hook 'bbb-exit-group nil 'local)
+      (make-local-variable 'gnus-score-find-score-files-function)
+
+      (cond
+       ((eq gnus-grouplens-override-scoring 'combine)
+	;; either add bbb-buld-mid-scores-alist to a list
+	;; or make a list
+	(if (listp gnus-score-find-score-files-function)
+	    (setq gnus-score-find-score-files-function
+		  (append 'bbb-build-mid-scores-alist
+			  gnus-score-find-score-files-function))
+	  (setq gnus-score-find-score-files-function
+		(list gnus-score-find-score-files-function
+		      'bbb-build-mid-scores-alist))))
+       ;; leave the gnus-score-find-score-files variable alone
+       ((eq gnus-grouplens-override-scoring 'separate)
+	(add-hook 'gnus-select-group-hook
+		  (lambda ()
+		    (bbb-get-predictions (bbb-get-all-mids)
+					 gnus-newsgroup-name))))
+       ;; default is to override
+       (t
+	(setq gnus-score-find-score-files-function
+	      'bbb-build-mid-scores-alist)))
+
+      ;; Change how summary lines look
+      (make-local-variable 'gnus-summary-line-format)
+      (make-local-variable 'gnus-summary-line-format-spec)
+      (setq gnus-summary-line-format gnus-summary-grouplens-line-format)
+      (setq gnus-summary-line-format-spec nil)
+      (gnus-update-format-specifications nil 'summary)
+      (gnus-update-summary-mark-positions)
+
+      ;; Set up the menu.
+      (when (and menu-bar-mode
+		 (gnus-visual-p 'grouplens-menu 'menu))
+	(gnus-grouplens-make-menu-bar))
+      (unless (assq 'gnus-grouplens-mode minor-mode-alist)
+	(push '(gnus-grouplens-mode " GroupLens") minor-mode-alist))
+      (unless (assq 'gnus-grouplens-mode minor-mode-map-alist)
+	(push (cons 'gnus-grouplens-mode gnus-grouplens-mode-map)
+	      minor-mode-map-alist))
+      (run-hooks 'gnus-grouplens-mode-hook))))
+
+(provide 'gnus-gl)
+
+;;; gnus-gl.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-group.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,3335 @@
+;;; gnus-group.el --- group mode commands for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-start)
+(require 'nnmail)
+(require 'gnus-spec)
+(require 'gnus-int)
+(require 'gnus-range)
+(require 'gnus-win)
+(require 'gnus-undo)
+
+(defcustom gnus-group-archive-directory
+  "/ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list/"
+  "*The address of the (ding) archives."
+  :group 'gnus-group-foreign
+  :type 'directory)
+
+(defcustom gnus-group-recent-archive-directory
+  "/ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list-recent/"
+  "*The address of the most recent (ding) articles."
+  :group 'gnus-group-foreign
+  :type 'directory)
+
+(defcustom gnus-no-groups-message "No news is no news"
+  "*Message displayed by Gnus when no groups are available."
+  :group 'gnus-start
+  :type 'string)
+
+(defcustom gnus-keep-same-level nil
+  "*Non-nil means that the next newsgroup after the current will be on the same level.
+When you type, for instance, `n' after reading the last article in the
+current newsgroup, you will go to the next newsgroup.  If this variable
+is nil, the next newsgroup will be the next from the group
+buffer.
+If this variable is non-nil, Gnus will either put you in the
+next newsgroup with the same level, or, if no such newsgroup is
+available, the next newsgroup with the lowest possible level higher
+than the current level.
+If this variable is `best', Gnus will make the next newsgroup the one
+with the best level."
+  :group 'gnus-group-levels
+  :type '(choice (const nil)
+		 (const best)
+		 (sexp :tag "other" t)))
+
+(defcustom gnus-group-goto-unread t
+  "*If non-nil, movement commands will go to the next unread and subscribed group."
+  :link '(custom-manual "(gnus)Group Maneuvering")
+  :group 'gnus-group-various
+  :type 'boolean)
+
+(defcustom gnus-goto-next-group-when-activating t
+  "*If non-nil, the \\<gnus-group-mode-map>\\[gnus-group-get-new-news-this-group] command will advance point to the next group."
+  :link '(custom-manual "(gnus)Scanning New Messages")
+  :group 'gnus-group-various
+  :type 'boolean)
+
+(defcustom gnus-permanently-visible-groups nil
+  "*Regexp to match groups that should always be listed in the group buffer.
+This means that they will still be listed when there are no unread
+articles in the groups."
+  :group 'gnus-group-listing
+  :type 'regexp)
+
+(defcustom gnus-list-groups-with-ticked-articles t
+  "*If non-nil, list groups that have only ticked articles.
+If nil, only list groups that have unread articles."
+  :group 'gnus-group-listing
+  :type 'boolean)
+
+(defcustom gnus-group-default-list-level gnus-level-subscribed
+  "*Default listing level.
+Ignored if `gnus-group-use-permanent-levels' is non-nil."
+  :group 'gnus-group-listing
+  :type 'integer)
+
+(defcustom gnus-group-list-inactive-groups t
+  "*If non-nil, inactive groups will be listed."
+  :group 'gnus-group-listing
+  :group 'gnus-group-levels
+  :type 'boolean)
+
+(defcustom gnus-group-sort-function 'gnus-group-sort-by-alphabet
+  "*Function used for sorting the group buffer.
+This function will be called with group info entries as the arguments
+for the groups to be sorted.  Pre-made functions include
+`gnus-group-sort-by-alphabet', `gnus-group-sort-by-real-name',
+`gnus-group-sort-by-unread', `gnus-group-sort-by-level',
+`gnus-group-sort-by-score', `gnus-group-sort-by-method', and
+`gnus-group-sort-by-rank'.
+
+This variable can also be a list of sorting functions.	In that case,
+the most significant sort function should be the last function in the
+list."
+  :group 'gnus-group-listing
+  :link '(custom-manual "(gnus)Sorting Groups")
+  :type '(radio (function-item gnus-group-sort-by-alphabet)
+		(function-item gnus-group-sort-by-real-name)
+		(function-item gnus-group-sort-by-unread)
+		(function-item gnus-group-sort-by-level)
+		(function-item gnus-group-sort-by-score)
+		(function-item gnus-group-sort-by-method)
+		(function-item gnus-group-sort-by-rank)
+		(function :tag "other" nil)))
+
+(defcustom gnus-group-line-format "%M\%S\%p\%P\%5y: %(%g%)%l\n"
+  "*Format of group lines.
+It works along the same lines as a normal formatting string,
+with some simple extensions.
+
+%M    Only marked articles (character, \"*\" or \" \")
+%S    Whether the group is subscribed (character, \"U\", \"K\", \"Z\" or \" \")
+%L    Level of subscribedness (integer)
+%N    Number of unread articles (integer)
+%I    Number of dormant articles (integer)
+%i    Number of ticked and dormant (integer)
+%T    Number of ticked articles (integer)
+%R    Number of read articles (integer)
+%t    Estimated total number of articles (integer)
+%y    Number of unread, unticked articles (integer)
+%G    Group name (string)
+%g    Qualified group name (string)
+%D    Group description (string)
+%s    Select method (string)
+%o    Moderated group (char, \"m\")
+%p    Process mark (char)
+%O    Moderated group (string, \"(m)\" or \"\")
+%P    Topic indentation (string)
+%m    Whether there is new(ish) mail in the group (char, \"%\")
+%l    Whether there are GroupLens predictions for this group (string)
+%n    Select from where (string)
+%z    A string that look like `<%s:%n>' if a foreign select method is used
+%d    The date the group was last entered.
+%u    User defined specifier.  The next character in the format string should
+      be a letter.  Gnus will call the function gnus-user-format-function-X,
+      where X is the letter following %u.  The function will be passed the
+      current header as argument.  The function should return a string, which
+      will be inserted into the buffer just like information from any other
+      group specifier.
+
+Text between %( and %) will be highlighted with `gnus-mouse-face' when
+the mouse point move inside the area.  There can only be one such area.
+
+Note that this format specification is not always respected.  For
+reasons of efficiency, when listing killed groups, this specification
+is ignored altogether.	If the spec is changed considerably, your
+output may end up looking strange when listing both alive and killed
+groups.
+
+If you use %o or %O, reading the active file will be slower and quite
+a bit of extra memory will be used.  %D will also worsen performance.
+Also note that if you change the format specification to include any
+of these specs, you must probably re-start Gnus to see them go into
+effect."
+  :group 'gnus-group-visual
+  :type 'string)
+
+(defcustom gnus-group-mode-line-format "Gnus: %%b {%M\%:%S}"
+  "*The format specification for the group mode line.
+It works along the same lines as a normal formatting string,
+with some simple extensions:
+
+%S   The native news server.
+%M   The native select method.
+%:   \":\" if %S isn't \"\"."
+  :group 'gnus-group-visual
+  :type 'string)
+
+(defcustom gnus-group-mode-hook nil
+  "Hook for Gnus group mode."
+  :group 'gnus-group-various
+  :options '(gnus-topic-mode)
+  :type 'hook)
+
+(defcustom gnus-group-menu-hook nil
+  "Hook run after the creation of the group mode menu."
+  :group 'gnus-group-various
+  :type 'hook)
+
+(defcustom gnus-group-catchup-group-hook nil
+  "Hook run when catching up a group from the group buffer."
+  :group 'gnus-group-various
+  :link '(custom-manual "(gnus)Group Data")
+  :type 'hook)
+
+(defcustom gnus-group-update-group-hook nil
+  "Hook called when updating group lines."
+  :group 'gnus-group-visual
+  :type 'hook)
+
+(defcustom gnus-group-prepare-function 'gnus-group-prepare-flat
+  "*A function that is called to generate the group buffer.
+The function is called with three arguments: The first is a number;
+all group with a level less or equal to that number should be listed,
+if the second is non-nil, empty groups should also be displayed.  If
+the third is non-nil, it is a number.  No groups with a level lower
+than this number should be displayed.
+
+The only current function implemented is `gnus-group-prepare-flat'."
+  :group 'gnus-group-listing
+  :type 'function)
+
+(defcustom gnus-group-prepare-hook nil
+  "Hook called after the group buffer has been generated.
+If you want to modify the group buffer, you can use this hook."
+  :group 'gnus-group-listing
+  :type 'hook)
+
+(defcustom gnus-suspend-gnus-hook nil
+  "Hook called when suspending (not exiting) Gnus."
+  :group 'gnus-exit
+  :type 'hook)
+
+(defcustom gnus-exit-gnus-hook nil
+  "Hook called when exiting Gnus."
+  :group 'gnus-exit
+  :type 'hook)
+
+(defcustom gnus-after-exiting-gnus-hook nil
+  "Hook called after exiting Gnus."
+  :group 'gnus-exit
+  :type 'hook)
+
+(defcustom gnus-group-update-hook '(gnus-group-highlight-line)
+  "Hook called when a group line is changed.
+The hook will not be called if `gnus-visual' is nil.
+
+The default function `gnus-group-highlight-line' will
+highlight the line according to the `gnus-group-highlight'
+variable."
+  :group 'gnus-group-visual
+  :type 'hook)
+
+(defcustom gnus-useful-groups
+  `(("(ding) mailing list mirrored at sunsite.auc.dk"
+     "emacs.ding"
+     (nntp "sunsite.auc.dk"
+			(nntp-address "sunsite.auc.dk")))
+    ("Gnus help group"
+     "gnus-help"
+     (nndoc "gnus-help"
+	    (nndoc-article-type mbox)
+	    (eval `(nndoc-address
+		    ,(let ((file (nnheader-find-etc-directory
+				  "gnus-tut.txt" t)))
+		       (unless file
+			 (error "Couldn't find doc group"))
+		       file))))))
+  "Alist of useful group-server pairs."
+  :group 'gnus-group-listing
+  :type '(repeat (list (string :tag "Description")
+		       (string :tag "Name")
+		       (sexp :tag "Method"))))
+
+(defcustom gnus-group-highlight
+  '(;; News.
+    ((and (= unread 0) (not mailp) (eq level 1)) .
+     gnus-group-news-1-empty-face)
+    ((and (not mailp) (eq level 1)) .
+     gnus-group-news-1-face)
+    ((and (= unread 0) (not mailp) (eq level 2)) .
+     gnus-group-news-2-empty-face)
+    ((and (not mailp) (eq level 2)) .
+     gnus-group-news-2-face)
+    ((and (= unread 0) (not mailp) (eq level 3)) .
+     gnus-group-news-3-empty-face)
+    ((and (not mailp) (eq level 3)) .
+     gnus-group-news-3-face)
+    ((and (= unread 0) (not mailp)) .
+     gnus-group-news-low-empty-face)
+    ((and (not mailp)) .
+     gnus-group-news-low-face)
+    ;; Mail.
+    ((and (= unread 0) (eq level 1)) .
+     gnus-group-mail-1-empty-face)
+    ((eq level 1) .
+     gnus-group-mail-1-face)
+    ((and (= unread 0) (eq level 2)) .
+     gnus-group-mail-2-empty-face)
+    ((eq level 2) .
+     gnus-group-mail-2-face)
+    ((and (= unread 0) (eq level 3)) .
+     gnus-group-mail-3-empty-face)
+    ((eq level 3) .
+     gnus-group-mail-3-face)
+    ((= unread 0) .
+     gnus-group-mail-low-empty-face)
+    (t .
+     gnus-group-mail-low-face))
+  "Controls the highlighting of group buffer lines.
+
+Below is a list of `Form'/`Face' pairs.  When deciding how a a
+particular group line should be displayed, each form is
+evaluated.  The content of the face field after the first true form is
+used.  You can change how those group lines are displayed by
+editing the face field.
+
+It is also possible to change and add form fields, but currently that
+requires an understanding of Lisp expressions.  Hopefully this will
+change in a future release.  For now, you can use the following
+variables in the Lisp expression:
+
+group: The name of the group.
+unread: The number of unread articles in the group.
+method: The select method used.
+mailp: Whether it's a mail group or not.
+level: The level of the group.
+score: The score of the group.
+ticked: The number of ticked articles."
+  :group 'gnus-group-visual
+  :type '(repeat (cons (sexp :tag "Form") face)))
+
+(defcustom gnus-new-mail-mark ?%
+  "Mark used for groups with new mail."
+  :group 'gnus-group-visual
+  :type 'character)
+
+;;; Internal variables
+
+(defvar gnus-group-sort-alist-function 'gnus-group-sort-flat
+  "Function for sorting the group buffer.")
+
+(defvar gnus-group-sort-selected-function 'gnus-group-sort-selected-flat
+  "Function for sorting the selected groups in the group buffer.")
+
+(defvar gnus-group-indentation-function nil)
+(defvar gnus-goto-missing-group-function nil)
+(defvar gnus-group-update-group-function nil)
+(defvar gnus-group-goto-next-group-function nil
+  "Function to override finding the next group after listing groups.")
+
+(defvar gnus-group-edit-buffer nil)
+
+(defvar gnus-group-line-format-alist
+  `((?M gnus-tmp-marked-mark ?c)
+    (?S gnus-tmp-subscribed ?c)
+    (?L gnus-tmp-level ?d)
+    (?N (cond ((eq number t) "*" )
+	      ((numberp number)
+	       (int-to-string
+		(+ number
+		   (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked)))
+		   (gnus-range-length (cdr (assq 'tick gnus-tmp-marked))))))
+	      (t number)) ?s)
+    (?R gnus-tmp-number-of-read ?s)
+    (?t gnus-tmp-number-total ?d)
+    (?y gnus-tmp-number-of-unread ?s)
+    (?I (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked))) ?d)
+    (?T (gnus-range-length (cdr (assq 'tick gnus-tmp-marked))) ?d)
+    (?i (+ (gnus-range-length (cdr (assq 'dormant gnus-tmp-marked)))
+	   (gnus-range-length (cdr (assq 'tick gnus-tmp-marked)))) ?d)
+    (?g gnus-tmp-group ?s)
+    (?G gnus-tmp-qualified-group ?s)
+    (?c (gnus-short-group-name gnus-tmp-group) ?s)
+    (?D gnus-tmp-newsgroup-description ?s)
+    (?o gnus-tmp-moderated ?c)
+    (?O gnus-tmp-moderated-string ?s)
+    (?p gnus-tmp-process-marked ?c)
+    (?s gnus-tmp-news-server ?s)
+    (?n gnus-tmp-news-method ?s)
+    (?P gnus-group-indentation ?s)
+    (?l gnus-tmp-grouplens ?s)
+    (?z gnus-tmp-news-method-string ?s)
+    (?m (gnus-group-new-mail gnus-tmp-group) ?c)
+    (?d (gnus-group-timestamp-string gnus-tmp-group) ?s)
+    (?u gnus-tmp-user-defined ?s)))
+
+(defvar gnus-group-mode-line-format-alist
+  `((?S gnus-tmp-news-server ?s)
+    (?M gnus-tmp-news-method ?s)
+    (?u gnus-tmp-user-defined ?s)
+    (?: gnus-tmp-colon ?s)))
+
+(defvar gnus-topic-topology nil
+  "The complete topic hierarchy.")
+
+(defvar gnus-topic-alist nil
+  "The complete topic-group alist.")
+
+(defvar gnus-group-marked nil)
+
+(defvar gnus-group-list-mode nil)
+
+;;;
+;;; Gnus group mode
+;;;
+
+(put 'gnus-group-mode 'mode-class 'special)
+
+(when t
+  (gnus-define-keys gnus-group-mode-map
+    " " gnus-group-read-group
+    "=" gnus-group-select-group
+    "\r" gnus-group-select-group
+    "\M-\r" gnus-group-quick-select-group
+    [(meta control return)] gnus-group-select-group-ephemerally
+    "j" gnus-group-jump-to-group
+    "n" gnus-group-next-unread-group
+    "p" gnus-group-prev-unread-group
+    "\177" gnus-group-prev-unread-group
+    [delete] gnus-group-prev-unread-group
+    "N" gnus-group-next-group
+    "P" gnus-group-prev-group
+    "\M-n" gnus-group-next-unread-group-same-level
+    "\M-p" gnus-group-prev-unread-group-same-level
+    "," gnus-group-best-unread-group
+    "." gnus-group-first-unread-group
+    "u" gnus-group-unsubscribe-current-group
+    "U" gnus-group-unsubscribe-group
+    "c" gnus-group-catchup-current
+    "C" gnus-group-catchup-current-all
+    "\M-c" gnus-group-clear-data
+    "l" gnus-group-list-groups
+    "L" gnus-group-list-all-groups
+    "m" gnus-group-mail
+    "g" gnus-group-get-new-news
+    "\M-g" gnus-group-get-new-news-this-group
+    "R" gnus-group-restart
+    "r" gnus-group-read-init-file
+    "B" gnus-group-browse-foreign-server
+    "b" gnus-group-check-bogus-groups
+    "F" gnus-find-new-newsgroups
+    "\C-c\C-d" gnus-group-describe-group
+    "\M-d" gnus-group-describe-all-groups
+    "\C-c\C-a" gnus-group-apropos
+    "\C-c\M-\C-a" gnus-group-description-apropos
+    "a" gnus-group-post-news
+    "\ek" gnus-group-edit-local-kill
+    "\eK" gnus-group-edit-global-kill
+    "\C-k" gnus-group-kill-group
+    "\C-y" gnus-group-yank-group
+    "\C-w" gnus-group-kill-region
+    "\C-x\C-t" gnus-group-transpose-groups
+    "\C-c\C-l" gnus-group-list-killed
+    "\C-c\C-x" gnus-group-expire-articles
+    "\C-c\M-\C-x" gnus-group-expire-all-groups
+    "V" gnus-version
+    "s" gnus-group-save-newsrc
+    "z" gnus-group-suspend
+    "q" gnus-group-exit
+    "Q" gnus-group-quit
+    "?" gnus-group-describe-briefly
+    "\C-c\C-i" gnus-info-find-node
+    "\M-e" gnus-group-edit-group-method
+    "^" gnus-group-enter-server-mode
+    gnus-mouse-2 gnus-mouse-pick-group
+    "<" beginning-of-buffer
+    ">" end-of-buffer
+    "\C-c\C-b" gnus-bug
+    "\C-c\C-s" gnus-group-sort-groups
+    "t" gnus-topic-mode
+    "\C-c\M-g" gnus-activate-all-groups
+    "\M-&" gnus-group-universal-argument
+    "#" gnus-group-mark-group
+    "\M-#" gnus-group-unmark-group)
+
+  (gnus-define-keys (gnus-group-mark-map "M" gnus-group-mode-map)
+    "m" gnus-group-mark-group
+    "u" gnus-group-unmark-group
+    "w" gnus-group-mark-region
+    "m" gnus-group-mark-buffer
+    "r" gnus-group-mark-regexp
+    "U" gnus-group-unmark-all-groups)
+
+  (gnus-define-keys (gnus-group-group-map "G" gnus-group-mode-map)
+    "d" gnus-group-make-directory-group
+    "h" gnus-group-make-help-group
+    "u" gnus-group-make-useful-group
+    "a" gnus-group-make-archive-group
+    "k" gnus-group-make-kiboze-group
+    "m" gnus-group-make-group
+    "E" gnus-group-edit-group
+    "e" gnus-group-edit-group-method
+    "p" gnus-group-edit-group-parameters
+    "v" gnus-group-add-to-virtual
+    "V" gnus-group-make-empty-virtual
+    "D" gnus-group-enter-directory
+    "f" gnus-group-make-doc-group
+    "w" gnus-group-make-web-group
+    "r" gnus-group-rename-group
+    "c" gnus-group-customize
+    "\177" gnus-group-delete-group
+    [delete] gnus-group-delete-group)
+
+  (gnus-define-keys (gnus-group-soup-map "s" gnus-group-group-map)
+    "b" gnus-group-brew-soup
+    "w" gnus-soup-save-areas
+    "s" gnus-soup-send-replies
+    "p" gnus-soup-pack-packet
+    "r" nnsoup-pack-replies)
+
+  (gnus-define-keys (gnus-group-sort-map "S" gnus-group-group-map)
+    "s" gnus-group-sort-groups
+    "a" gnus-group-sort-groups-by-alphabet
+    "u" gnus-group-sort-groups-by-unread
+    "l" gnus-group-sort-groups-by-level
+    "v" gnus-group-sort-groups-by-score
+    "r" gnus-group-sort-groups-by-rank
+    "m" gnus-group-sort-groups-by-method)
+
+  (gnus-define-keys (gnus-group-sort-selected-map "P" gnus-group-group-map)
+    "s" gnus-group-sort-selected-groups
+    "a" gnus-group-sort-selected-groups-by-alphabet
+    "u" gnus-group-sort-selected-groups-by-unread
+    "l" gnus-group-sort-selected-groups-by-level
+    "v" gnus-group-sort-selected-groups-by-score
+    "r" gnus-group-sort-selected-groups-by-rank
+    "m" gnus-group-sort-selected-groups-by-method)
+
+  (gnus-define-keys (gnus-group-list-map "A" gnus-group-mode-map)
+    "k" gnus-group-list-killed
+    "z" gnus-group-list-zombies
+    "s" gnus-group-list-groups
+    "u" gnus-group-list-all-groups
+    "A" gnus-group-list-active
+    "a" gnus-group-apropos
+    "d" gnus-group-description-apropos
+    "m" gnus-group-list-matching
+    "M" gnus-group-list-all-matching
+    "l" gnus-group-list-level)
+
+  (gnus-define-keys (gnus-group-score-map "W" gnus-group-mode-map)
+    "f" gnus-score-flush-cache)
+
+  (gnus-define-keys (gnus-group-help-map "H" gnus-group-mode-map)
+    "d" gnus-group-describe-group
+    "f" gnus-group-fetch-faq
+    "v" gnus-version)
+
+  (gnus-define-keys (gnus-group-sub-map "S" gnus-group-mode-map)
+    "l" gnus-group-set-current-level
+    "t" gnus-group-unsubscribe-current-group
+    "s" gnus-group-unsubscribe-group
+    "k" gnus-group-kill-group
+    "y" gnus-group-yank-group
+    "w" gnus-group-kill-region
+    "\C-k" gnus-group-kill-level
+    "z" gnus-group-kill-all-zombies))
+
+(defun gnus-group-make-menu-bar ()
+  (gnus-turn-off-edit-menu 'group)
+  (unless (boundp 'gnus-group-reading-menu)
+
+    (easy-menu-define
+     gnus-group-reading-menu gnus-group-mode-map ""
+     '("Group"
+       ["Read" gnus-group-read-group (gnus-group-group-name)]
+       ["Select" gnus-group-select-group (gnus-group-group-name)]
+       ["See old articles" (gnus-group-select-group 'all)
+	:keys "C-u SPC" :active (gnus-group-group-name)]
+       ["Catch up" gnus-group-catchup-current (gnus-group-group-name)]
+       ["Catch up all articles" gnus-group-catchup-current-all
+	(gnus-group-group-name)]
+       ["Check for new articles" gnus-group-get-new-news-this-group
+	(gnus-group-group-name)]
+       ["Toggle subscription" gnus-group-unsubscribe-current-group
+	(gnus-group-group-name)]
+       ["Kill" gnus-group-kill-group (gnus-group-group-name)]
+       ["Yank" gnus-group-yank-group gnus-list-of-killed-groups]
+       ["Describe" gnus-group-describe-group (gnus-group-group-name)]
+       ["Fetch FAQ" gnus-group-fetch-faq (gnus-group-group-name)]
+       ;; Actually one should check, if any of the marked groups gives t for
+       ;; (gnus-check-backend-function 'request-expire-articles ...)
+       ["Expire articles" gnus-group-expire-articles
+	(or (and (gnus-group-group-name)
+		 (gnus-check-backend-function
+		  'request-expire-articles
+		  (gnus-group-group-name))) gnus-group-marked)]
+       ["Set group level" gnus-group-set-current-level
+	(gnus-group-group-name)]
+       ["Select quick" gnus-group-quick-select-group (gnus-group-group-name)]
+       ["Customize" gnus-group-customize (gnus-group-group-name)]
+       ("Edit"
+	["Parameters" gnus-group-edit-group-parameters
+	 (gnus-group-group-name)]
+	["Select method" gnus-group-edit-group-method
+	 (gnus-group-group-name)]
+	["Info" gnus-group-edit-group (gnus-group-group-name)]
+	["Local kill file" gnus-group-edit-local-kill (gnus-group-group-name)]
+	["Global kill file" gnus-group-edit-global-kill t])
+       ))
+
+    (easy-menu-define
+     gnus-group-group-menu gnus-group-mode-map ""
+     '("Groups"
+       ("Listing"
+	["List unread subscribed groups" gnus-group-list-groups t]
+	["List (un)subscribed groups" gnus-group-list-all-groups t]
+	["List killed groups" gnus-group-list-killed gnus-killed-list]
+	["List zombie groups" gnus-group-list-zombies gnus-zombie-list]
+	["List level..." gnus-group-list-level t]
+	["Describe all groups" gnus-group-describe-all-groups t]
+	["Group apropos..." gnus-group-apropos t]
+	["Group and description apropos..." gnus-group-description-apropos t]
+	["List groups matching..." gnus-group-list-matching t]
+	["List all groups matching..." gnus-group-list-all-matching t]
+	["List active file" gnus-group-list-active t])
+       ("Sort"
+	["Default sort" gnus-group-sort-groups t]
+	["Sort by method" gnus-group-sort-groups-by-method t]
+	["Sort by rank" gnus-group-sort-groups-by-rank t]
+	["Sort by score" gnus-group-sort-groups-by-score t]
+	["Sort by level" gnus-group-sort-groups-by-level t]
+	["Sort by unread" gnus-group-sort-groups-by-unread t]
+	["Sort by name" gnus-group-sort-groups-by-alphabet t])
+       ("Sort process/prefixed"
+	["Default sort" gnus-group-sort-selected-groups
+	 (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
+	["Sort by method" gnus-group-sort-selected-groups-by-method
+	 (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
+	["Sort by rank" gnus-group-sort-selected-groups-by-rank
+	 (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
+	["Sort by score" gnus-group-sort-selected-groups-by-score
+	 (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
+	["Sort by level" gnus-group-sort-selected-groups-by-level
+	 (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
+	["Sort by unread" gnus-group-sort-selected-groups-by-unread
+	 (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))]
+	["Sort by name" gnus-group-sort-selected-groups-by-alphabet
+	 (or (not (boundp 'gnus-topic-mode)) (not gnus-topic-mode))])
+       ("Mark"
+	["Mark group" gnus-group-mark-group
+	 (and (gnus-group-group-name)
+	      (not (memq (gnus-group-group-name) gnus-group-marked)))]
+	["Unmark group" gnus-group-unmark-group
+	 (and (gnus-group-group-name)
+	      (memq (gnus-group-group-name) gnus-group-marked))]
+	["Unmark all" gnus-group-unmark-all-groups gnus-group-marked]
+	["Mark regexp..." gnus-group-mark-regexp t]
+	["Mark region" gnus-group-mark-region t]
+	["Mark buffer" gnus-group-mark-buffer t]
+	["Execute command" gnus-group-universal-argument
+	 (or gnus-group-marked (gnus-group-group-name))])
+       ("Subscribe"
+	["Subscribe to a group" gnus-group-unsubscribe-group t]
+	["Kill all newsgroups in region" gnus-group-kill-region t]
+	["Kill all zombie groups" gnus-group-kill-all-zombies
+	 gnus-zombie-list]
+	["Kill all groups on level..." gnus-group-kill-level t])
+       ("Foreign groups"
+	["Make a foreign group" gnus-group-make-group t]
+	["Add a directory group" gnus-group-make-directory-group t]
+	["Add the help group" gnus-group-make-help-group t]
+	["Add the archive group" gnus-group-make-archive-group t]
+	["Make a doc group" gnus-group-make-doc-group t]
+	["Make a web group" gnus-group-make-web-group t]
+	["Make a kiboze group" gnus-group-make-kiboze-group t]
+	["Make a virtual group" gnus-group-make-empty-virtual t]
+	["Add a group to a virtual" gnus-group-add-to-virtual t]
+	["Rename group" gnus-group-rename-group
+	 (gnus-check-backend-function
+	  'request-rename-group (gnus-group-group-name))]
+	["Delete group" gnus-group-delete-group
+	 (gnus-check-backend-function
+	  'request-delete-group (gnus-group-group-name))])
+       ("Move"
+	["Next" gnus-group-next-group t]
+	["Previous" gnus-group-prev-group t]
+	["Next unread" gnus-group-next-unread-group t]
+	["Previous unread" gnus-group-prev-unread-group t]
+	["Next unread same level" gnus-group-next-unread-group-same-level t]
+	["Previous unread same level"
+	 gnus-group-prev-unread-group-same-level t]
+	["Jump to group" gnus-group-jump-to-group t]
+	["First unread group" gnus-group-first-unread-group t]
+	["Best unread group" gnus-group-best-unread-group t])
+       ["Delete bogus groups" gnus-group-check-bogus-groups t]
+       ["Find new newsgroups" gnus-find-new-newsgroups t]
+       ["Transpose" gnus-group-transpose-groups
+	(gnus-group-group-name)]
+       ["Read a directory as a group..." gnus-group-enter-directory t]
+       ))
+
+    (easy-menu-define
+     gnus-group-misc-menu gnus-group-mode-map ""
+     '("Misc"
+       ("SOUP"
+	["Pack replies" nnsoup-pack-replies (fboundp 'nnsoup-request-group)]
+	["Send replies" gnus-soup-send-replies
+	 (fboundp 'gnus-soup-pack-packet)]
+	["Pack packet" gnus-soup-pack-packet (fboundp 'gnus-soup-pack-packet)]
+	["Save areas" gnus-soup-save-areas (fboundp 'gnus-soup-pack-packet)]
+	["Brew SOUP" gnus-soup-brew-soup (fboundp 'gnus-soup-pack-packet)])
+       ["Send a bug report" gnus-bug t]
+       ["Send a mail" gnus-group-mail t]
+       ["Post an article..." gnus-group-post-news t]
+       ["Check for new news" gnus-group-get-new-news t]
+       ["Activate all groups" gnus-activate-all-groups t]
+       ["Restart Gnus" gnus-group-restart t]
+       ["Read init file" gnus-group-read-init-file t]
+       ["Browse foreign server" gnus-group-browse-foreign-server t]
+       ["Enter server buffer" gnus-group-enter-server-mode t]
+       ["Expire all expirable articles" gnus-group-expire-all-groups t]
+       ["Generate any kiboze groups" nnkiboze-generate-groups t]
+       ["Gnus version" gnus-version t]
+       ["Save .newsrc files" gnus-group-save-newsrc t]
+       ["Suspend Gnus" gnus-group-suspend t]
+       ["Clear dribble buffer" gnus-group-clear-dribble t]
+       ["Read manual" gnus-info-find-node t]
+       ["Flush score cache" gnus-score-flush-cache t]
+       ["Toggle topics" gnus-topic-mode t]
+       ["Exit from Gnus" gnus-group-exit t]
+       ["Exit without saving" gnus-group-quit t]
+       ))
+
+    (run-hooks 'gnus-group-menu-hook)))
+
+(defun gnus-group-mode ()
+  "Major mode for reading news.
+
+All normal editing commands are switched off.
+\\<gnus-group-mode-map>
+The group buffer lists (some of) the groups available.	For instance,
+`\\[gnus-group-list-groups]' will list all subscribed groups with unread articles, while `\\[gnus-group-list-zombies]'
+lists all zombie groups.
+
+Groups that are displayed can be entered with `\\[gnus-group-read-group]'.  To subscribe
+to a group not displayed, type `\\[gnus-group-unsubscribe-group]'.
+
+For more in-depth information on this mode, read the manual (`\\[gnus-info-find-node]').
+
+The following commands are available:
+
+\\{gnus-group-mode-map}"
+  (interactive)
+  (when (gnus-visual-p 'group-menu 'menu)
+    (gnus-group-make-menu-bar))
+  (kill-all-local-variables)
+  (gnus-simplify-mode-line)
+  (setq major-mode 'gnus-group-mode)
+  (setq mode-name "Group")
+  (gnus-group-set-mode-line)
+  (setq mode-line-process nil)
+  (use-local-map gnus-group-mode-map)
+  (buffer-disable-undo (current-buffer))
+  (setq truncate-lines t)
+  (setq buffer-read-only t)
+  (gnus-set-default-directory)
+  (gnus-update-format-specifications nil 'group 'group-mode)
+  (gnus-update-group-mark-positions)
+  (make-local-hook 'post-command-hook)
+  (add-hook 'post-command-hook 'gnus-clear-inboxes-moved nil t)
+  (when gnus-use-undo
+    (gnus-undo-mode 1))
+  (run-hooks 'gnus-group-mode-hook))
+
+(defun gnus-update-group-mark-positions ()
+  (save-excursion
+    (let ((gnus-process-mark 128)
+	  (gnus-group-marked '("dummy.group"))
+	  (gnus-active-hashtb (make-vector 10 0)))
+      (gnus-set-active "dummy.group" '(0 . 0))
+      (gnus-set-work-buffer)
+      (gnus-group-insert-group-line "dummy.group" 0 nil 0 nil)
+      (goto-char (point-min))
+      (setq gnus-group-mark-positions
+	    (list (cons 'process (and (search-forward "\200" nil t)
+				      (- (point) 2))))))))
+
+(defun gnus-clear-inboxes-moved ()
+  (setq nnmail-moved-inboxes nil))
+
+(defun gnus-mouse-pick-group (e)
+  "Enter the group under the mouse pointer."
+  (interactive "e")
+  (mouse-set-point e)
+  (gnus-group-read-group nil))
+
+;; Look at LEVEL and find out what the level is really supposed to be.
+;; If LEVEL is non-nil, LEVEL will be returned, if not, what happens
+;; will depend on whether `gnus-group-use-permanent-levels' is used.
+(defun gnus-group-default-level (&optional level number-or-nil)
+  (cond
+   (gnus-group-use-permanent-levels
+    (or (setq gnus-group-use-permanent-levels
+	      (or level (if (numberp gnus-group-use-permanent-levels)
+			    gnus-group-use-permanent-levels
+			  (or gnus-group-default-list-level
+			      gnus-level-subscribed))))
+	gnus-group-default-list-level gnus-level-subscribed))
+   (number-or-nil
+    level)
+   (t
+    (or level gnus-group-default-list-level gnus-level-subscribed))))
+
+(defun gnus-group-setup-buffer ()
+  (switch-to-buffer gnus-group-buffer)
+  (unless (eq major-mode 'gnus-group-mode)
+    (gnus-add-current-to-buffer-list)
+    (gnus-group-mode)
+    (when gnus-carpal
+      (gnus-carpal-setup-buffer 'group))))
+
+(defun gnus-group-list-groups (&optional level unread lowest)
+  "List newsgroups with level LEVEL or lower that have unread articles.
+Default is all subscribed groups.
+If argument UNREAD is non-nil, groups with no unread articles are also
+listed.
+
+Also see the `gnus-group-use-permanent-levels' variable."
+  (interactive
+   (list (if current-prefix-arg
+	     (prefix-numeric-value current-prefix-arg)
+	   (or
+	    (gnus-group-default-level nil t)
+	    gnus-group-default-list-level
+	    gnus-level-subscribed))))
+  ;; Just do this here, for no particular good reason.
+  (gnus-clear-inboxes-moved)
+  (unless level
+    (setq level (car gnus-group-list-mode)
+	  unread (cdr gnus-group-list-mode)))
+  (setq level (gnus-group-default-level level))
+  (gnus-group-setup-buffer)
+  (gnus-update-format-specifications nil 'group 'group-mode)
+  (let ((case-fold-search nil)
+	(props (text-properties-at (gnus-point-at-bol)))
+	(empty (= (point-min) (point-max)))
+	(group (gnus-group-group-name))
+	number)
+    (set-buffer gnus-group-buffer)
+    (setq number (funcall gnus-group-prepare-function level unread lowest))
+    (when (or (and (numberp number)
+		   (zerop number))
+	      (zerop (buffer-size)))
+      ;; No groups in the buffer.
+      (gnus-message 5 gnus-no-groups-message))
+    ;; We have some groups displayed.
+    (goto-char (point-max))
+    (when (or (not gnus-group-goto-next-group-function)
+	      (not (funcall gnus-group-goto-next-group-function
+			    group props)))
+      (cond
+       (empty
+	(goto-char (point-min)))
+       ((not group)
+	;; Go to the first group with unread articles.
+	(gnus-group-search-forward t))
+       (t
+	;; Find the right group to put point on.  If the current group
+	;; has disappeared in the new listing, try to find the next
+	;; one.  If no next one can be found, just leave point at the
+	;; first newsgroup in the buffer.
+	(when (not (gnus-goto-char
+		    (text-property-any
+		     (point-min) (point-max)
+		     'gnus-group (gnus-intern-safe
+				  group gnus-active-hashtb))))
+	  (let ((newsrc (cdddr (gnus-gethash group gnus-newsrc-hashtb))))
+	    (while (and newsrc
+			(not (gnus-goto-char
+			      (text-property-any
+			       (point-min) (point-max) 'gnus-group
+			       (gnus-intern-safe
+				(caar newsrc) gnus-active-hashtb)))))
+	      (setq newsrc (cdr newsrc)))
+	    (unless newsrc
+	      (goto-char (point-max))
+	      (forward-line -1)))))))
+    ;; Adjust cursor point.
+    (gnus-group-position-point)))
+
+(defun gnus-group-list-level (level &optional all)
+  "List groups on LEVEL.
+If ALL (the prefix), also list groups that have no unread articles."
+  (interactive "nList groups on level: \nP")
+  (gnus-group-list-groups level all level))
+
+(defun gnus-group-prepare-flat (level &optional all lowest regexp)
+  "List all newsgroups with unread articles of level LEVEL or lower.
+If ALL is non-nil, list groups that have no unread articles.
+If LOWEST is non-nil, list all newsgroups of level LOWEST or higher.
+If REGEXP, only list groups matching REGEXP."
+  (set-buffer gnus-group-buffer)
+  (let ((buffer-read-only nil)
+	(newsrc (cdr gnus-newsrc-alist))
+	(lowest (or lowest 1))
+	info clevel unread group params)
+    (erase-buffer)
+    (when (< lowest gnus-level-zombie)
+      ;; List living groups.
+      (while newsrc
+	(setq info (car newsrc)
+	      group (gnus-info-group info)
+	      params (gnus-info-params info)
+	      newsrc (cdr newsrc)
+	      unread (car (gnus-gethash group gnus-newsrc-hashtb)))
+	(and unread			; This group might be bogus
+	     (or (not regexp)
+		 (string-match regexp group))
+	     (<= (setq clevel (gnus-info-level info)) level)
+	     (>= clevel lowest)
+	     (or all			; We list all groups?
+		 (if (eq unread t)	; Unactivated?
+		     gnus-group-list-inactive-groups ; We list unactivated
+		   (> unread 0))	; We list groups with unread articles
+		 (and gnus-list-groups-with-ticked-articles
+		      (cdr (assq 'tick (gnus-info-marks info))))
+					; And groups with tickeds
+		 ;; Check for permanent visibility.
+		 (and gnus-permanently-visible-groups
+		      (string-match gnus-permanently-visible-groups
+				    group))
+		 (memq 'visible params)
+		 (cdr (assq 'visible params)))
+	     (gnus-group-insert-group-line
+	      group (gnus-info-level info)
+	      (gnus-info-marks info) unread (gnus-info-method info)))))
+
+    ;; List dead groups.
+    (and (>= level gnus-level-zombie) (<= lowest gnus-level-zombie)
+	 (gnus-group-prepare-flat-list-dead
+	  (setq gnus-zombie-list (sort gnus-zombie-list 'string<))
+	  gnus-level-zombie ?Z
+	  regexp))
+    (and (>= level gnus-level-killed) (<= lowest gnus-level-killed)
+	 (gnus-group-prepare-flat-list-dead
+	  (setq gnus-killed-list (sort gnus-killed-list 'string<))
+	  gnus-level-killed ?K regexp))
+
+    (gnus-group-set-mode-line)
+    (setq gnus-group-list-mode (cons level all))
+    (run-hooks 'gnus-group-prepare-hook)
+    t))
+
+(defun gnus-group-prepare-flat-list-dead (groups level mark regexp)
+  ;; List zombies and killed lists somewhat faster, which was
+  ;; suggested by Jack Vinson <vinson@unagi.cis.upenn.edu>.  It does
+  ;; this by ignoring the group format specification altogether.
+  (let (group)
+    (if regexp
+	;; This loop is used when listing groups that match some
+	;; regexp.
+	(while groups
+	  (setq group (pop groups))
+	  (when (string-match regexp group)
+	    (gnus-add-text-properties
+	     (point) (prog1 (1+ (point))
+		       (insert " " mark "     *: " group "\n"))
+	     (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb)
+		   'gnus-unread t
+		   'gnus-level level))))
+      ;; This loop is used when listing all groups.
+      (while groups
+	(gnus-add-text-properties
+	 (point) (prog1 (1+ (point))
+		   (insert " " mark "     *: "
+			   (setq group (pop groups)) "\n"))
+	 (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb)
+	       'gnus-unread t
+	       'gnus-level level))))))
+
+(defun gnus-group-update-group-line ()
+  "Update the current line in the group buffer."
+  (let* ((buffer-read-only nil)
+	 (group (gnus-group-group-name))
+	 (entry (and group (gnus-gethash group gnus-newsrc-hashtb)))
+	 gnus-group-indentation)
+    (when group
+      (and entry
+	   (not (gnus-ephemeral-group-p group))
+	   (gnus-dribble-enter
+	    (concat "(gnus-group-set-info '"
+		    (gnus-prin1-to-string (nth 2 entry))
+		    ")")))
+      (setq gnus-group-indentation (gnus-group-group-indentation))
+      (gnus-delete-line)
+      (gnus-group-insert-group-line-info group)
+      (forward-line -1)
+      (gnus-group-position-point))))
+
+(defun gnus-group-insert-group-line-info (group)
+  "Insert GROUP on the current line."
+  (let ((entry (gnus-gethash group gnus-newsrc-hashtb))
+	(gnus-group-indentation (gnus-group-group-indentation))
+	active info)
+    (if entry
+	(progn
+	  ;; (Un)subscribed group.
+	  (setq info (nth 2 entry))
+	  (gnus-group-insert-group-line
+	   group (gnus-info-level info) (gnus-info-marks info)
+	   (or (car entry) t) (gnus-info-method info)))
+      ;; This group is dead.
+      (gnus-group-insert-group-line
+       group
+       (if (member group gnus-zombie-list) gnus-level-zombie gnus-level-killed)
+       nil
+       (if (setq active (gnus-active group))
+	   (if (zerop (cdr active))
+	       0
+	     (- (1+ (cdr active)) (car active)))
+	 nil)
+       nil))))
+
+(defun gnus-group-insert-group-line (gnus-tmp-group gnus-tmp-level
+						    gnus-tmp-marked number
+						    gnus-tmp-method)
+  "Insert a group line in the group buffer."
+  (let* ((gnus-tmp-active (gnus-active gnus-tmp-group))
+	 (gnus-tmp-number-total
+	  (if gnus-tmp-active
+	      (1+ (- (cdr gnus-tmp-active) (car gnus-tmp-active)))
+	    0))
+	 (gnus-tmp-number-of-unread
+	  (if (numberp number) (int-to-string (max 0 number))
+	    "*"))
+	 (gnus-tmp-number-of-read
+	  (if (numberp number)
+	      (int-to-string (max 0 (- gnus-tmp-number-total number)))
+	    "*"))
+	 (gnus-tmp-subscribed
+	  (cond ((<= gnus-tmp-level gnus-level-subscribed) ? )
+		((<= gnus-tmp-level gnus-level-unsubscribed) ?U)
+		((= gnus-tmp-level gnus-level-zombie) ?Z)
+		(t ?K)))
+	 (gnus-tmp-qualified-group (gnus-group-real-name gnus-tmp-group))
+	 (gnus-tmp-newsgroup-description
+	  (if gnus-description-hashtb
+	      (or (gnus-gethash gnus-tmp-group gnus-description-hashtb) "")
+	    ""))
+	 (gnus-tmp-moderated
+	  (if (and gnus-moderated-hashtb
+		   (gnus-gethash gnus-tmp-group gnus-moderated-hashtb))
+	      ?m ? ))
+	 (gnus-tmp-moderated-string
+	  (if (eq gnus-tmp-moderated ?m) "(m)" ""))
+	 (gnus-tmp-method
+	  (gnus-server-get-method gnus-tmp-group gnus-tmp-method))
+	 (gnus-tmp-news-server (or (cadr gnus-tmp-method) ""))
+	 (gnus-tmp-news-method (or (car gnus-tmp-method) ""))
+	 (gnus-tmp-news-method-string
+	  (if gnus-tmp-method
+	      (format "(%s:%s)" (car gnus-tmp-method)
+		      (cadr gnus-tmp-method)) ""))
+	 (gnus-tmp-marked-mark
+	  (if (and (numberp number)
+		   (zerop number)
+		   (cdr (assq 'tick gnus-tmp-marked)))
+	      ?* ? ))
+	 (gnus-tmp-process-marked
+	  (if (member gnus-tmp-group gnus-group-marked)
+	      gnus-process-mark ? ))
+	 (gnus-tmp-grouplens
+	  (or (and gnus-use-grouplens
+		   (bbb-grouplens-group-p gnus-tmp-group))
+	      ""))
+	 (buffer-read-only nil)
+	 header gnus-tmp-header)	; passed as parameter to user-funcs.
+    (beginning-of-line)
+    (gnus-add-text-properties
+     (point)
+     (prog1 (1+ (point))
+       ;; Insert the text.
+       (eval gnus-group-line-format-spec))
+     `(gnus-group ,(gnus-intern-safe gnus-tmp-group gnus-active-hashtb)
+		  gnus-unread ,(if (numberp number)
+				   (string-to-int gnus-tmp-number-of-unread)
+				 t)
+		  gnus-marked ,gnus-tmp-marked-mark
+		  gnus-indentation ,gnus-group-indentation
+		  gnus-level ,gnus-tmp-level))
+    (when (inline (gnus-visual-p 'group-highlight 'highlight))
+      (forward-line -1)
+      (run-hooks 'gnus-group-update-hook)
+      (forward-line))
+    ;; Allow XEmacs to remove front-sticky text properties.
+    (gnus-group-remove-excess-properties)))
+
+(defun gnus-group-highlight-line ()
+  "Highlight the current line according to `gnus-group-highlight'."
+  (let* ((list gnus-group-highlight)
+	 (p (point))
+	 (end (progn (end-of-line) (point)))
+	 ;; now find out where the line starts and leave point there.
+	 (beg (progn (beginning-of-line) (point)))
+	 (group (gnus-group-group-name))
+	 (entry (gnus-group-entry group))
+	 (unread (if (numberp (car entry)) (car entry) 0))
+	 (active (gnus-active group))
+	 (total (if active (1+ (- (cdr active) (car active))) 0))
+	 (info (nth 2 entry))
+	 (method (gnus-server-get-method group (gnus-info-method info)))
+	 (marked (gnus-info-marks info))
+	 (mailp (memq 'mail (assoc (symbol-name
+				    (car (or method gnus-select-method)))
+				   gnus-valid-select-methods)))
+	 (level (or (gnus-info-level info) 9))
+	 (score (or (gnus-info-score info) 0))
+	 (ticked (gnus-range-length (cdr (assq 'tick marked))))
+	 (group-age (gnus-group-timestamp-delta group))
+	 (inhibit-read-only t))
+    ;; Eval the cars of the lists until we find a match.
+    (while (and list
+		(not (eval (caar list))))
+      (setq list (cdr list)))
+    (let ((face (cdar list)))
+      (unless (eq face (get-text-property beg 'face))
+	(gnus-put-text-property
+	 beg end 'face
+	 (setq face (if (boundp face) (symbol-value face) face)))
+	(gnus-extent-start-open beg)))
+    (goto-char p)))
+
+(defun gnus-group-update-group (group &optional visible-only)
+  "Update all lines where GROUP appear.
+If VISIBLE-ONLY is non-nil, the group won't be displayed if it isn't
+already."
+  ;; Can't use `save-excursion' here, so we do it manually.
+  (let ((buf (current-buffer))
+	mark)
+    (set-buffer gnus-group-buffer)
+    (setq mark (point-marker))
+    ;; The buffer may be narrowed.
+    (save-restriction
+      (widen)
+      (let ((ident (gnus-intern-safe group gnus-active-hashtb))
+	    (loc (point-min))
+	    found buffer-read-only)
+	;; Enter the current status into the dribble buffer.
+	(let ((entry (gnus-gethash group gnus-newsrc-hashtb)))
+	  (when (and entry (not (gnus-ephemeral-group-p group)))
+	    (gnus-dribble-enter
+	     (concat "(gnus-group-set-info '"
+		     (gnus-prin1-to-string (nth 2 entry))
+		     ")"))))
+	;; Find all group instances.  If topics are in use, each group
+	;; may be listed in more than once.
+	(while (setq loc (text-property-any
+			  loc (point-max) 'gnus-group ident))
+	  (setq found t)
+	  (goto-char loc)
+	  (let ((gnus-group-indentation (gnus-group-group-indentation)))
+	    (gnus-delete-line)
+	    (gnus-group-insert-group-line-info group)
+	    (save-excursion
+	      (forward-line -1)
+	      (run-hooks 'gnus-group-update-group-hook)))
+	  (setq loc (1+ loc)))
+	(unless (or found visible-only)
+	  ;; No such line in the buffer, find out where it's supposed to
+	  ;; go, and insert it there (or at the end of the buffer).
+	  (if gnus-goto-missing-group-function
+	      (funcall gnus-goto-missing-group-function group)
+	    (let ((entry (cddr (gnus-gethash group gnus-newsrc-hashtb))))
+	      (while (and entry (car entry)
+			  (not
+			   (gnus-goto-char
+			    (text-property-any
+			     (point-min) (point-max)
+			     'gnus-group (gnus-intern-safe
+					  (caar entry) gnus-active-hashtb)))))
+		(setq entry (cdr entry)))
+	      (or entry (goto-char (point-max)))))
+	  ;; Finally insert the line.
+	  (let ((gnus-group-indentation (gnus-group-group-indentation)))
+	    (gnus-group-insert-group-line-info group)
+	    (save-excursion
+	      (forward-line -1)
+	      (run-hooks 'gnus-group-update-group-hook))))
+	(when gnus-group-update-group-function
+	  (funcall gnus-group-update-group-function group))
+	(gnus-group-set-mode-line)))
+    (goto-char mark)
+    (set-marker mark nil)
+    (set-buffer buf)))
+
+(defun gnus-group-set-mode-line ()
+  "Update the mode line in the group buffer."
+  (when (memq 'group gnus-updated-mode-lines)
+    ;; Yes, we want to keep this mode line updated.
+    (save-excursion
+      (set-buffer gnus-group-buffer)
+      (let* ((gformat (or gnus-group-mode-line-format-spec
+			  (setq gnus-group-mode-line-format-spec
+				(gnus-parse-format
+				 gnus-group-mode-line-format
+				 gnus-group-mode-line-format-alist))))
+	     (gnus-tmp-news-server (cadr gnus-select-method))
+	     (gnus-tmp-news-method (car gnus-select-method))
+	     (gnus-tmp-colon (if (equal gnus-tmp-news-server "") "" ":"))
+	     (max-len 60)
+	     gnus-tmp-header		;Dummy binding for user-defined formats
+	     ;; Get the resulting string.
+	     (modified
+	      (and gnus-dribble-buffer
+		   (buffer-name gnus-dribble-buffer)
+		   (buffer-modified-p gnus-dribble-buffer)
+		   (save-excursion
+		     (set-buffer gnus-dribble-buffer)
+		     (not (zerop (buffer-size))))))
+	     (mode-string (eval gformat)))
+	;; Say whether the dribble buffer has been modified.
+	(setq mode-line-modified
+	      (if modified "--**- " "----- "))
+	;; If the line is too long, we chop it off.
+	(when (> (length mode-string) max-len)
+	  (setq mode-string (substring mode-string 0 (- max-len 4))))
+	(prog1
+	    (setq mode-line-buffer-identification
+		  (gnus-mode-line-buffer-identification
+		   (list mode-string)))
+	  (set-buffer-modified-p modified))))))
+
+(defun gnus-group-group-name ()
+  "Get the name of the newsgroup on the current line."
+  (let ((group (get-text-property (gnus-point-at-bol) 'gnus-group)))
+    (and group (symbol-name group))))
+
+(defun gnus-group-group-level ()
+  "Get the level of the newsgroup on the current line."
+  (get-text-property (gnus-point-at-bol) 'gnus-level))
+
+(defun gnus-group-group-indentation ()
+  "Get the indentation of the newsgroup on the current line."
+  (or (get-text-property (gnus-point-at-bol) 'gnus-indentation)
+      (and gnus-group-indentation-function
+	   (funcall gnus-group-indentation-function))
+      ""))
+
+(defun gnus-group-group-unread ()
+  "Get the number of unread articles of the newsgroup on the current line."
+  (get-text-property (gnus-point-at-bol) 'gnus-unread))
+
+(defun gnus-group-new-mail (group)
+  (if (nnmail-new-mail-p (gnus-group-real-name group))
+      gnus-new-mail-mark
+    ? ))
+
+(defun gnus-group-level (group)
+  "Return the estimated level of GROUP."
+  (or (gnus-info-level (gnus-get-info group))
+      (and (member group gnus-zombie-list) 8)
+      9))
+
+(defun gnus-group-search-forward (&optional backward all level first-too)
+  "Find the next newsgroup with unread articles.
+If BACKWARD is non-nil, find the previous newsgroup instead.
+If ALL is non-nil, just find any newsgroup.
+If LEVEL is non-nil, find group with level LEVEL, or higher if no such
+group exists.
+If FIRST-TOO, the current line is also eligible as a target."
+  (let ((way (if backward -1 1))
+	(low gnus-level-killed)
+	(beg (point))
+	pos found lev)
+    (if (and backward (progn (beginning-of-line)) (bobp))
+	nil
+      (unless first-too
+	(forward-line way))
+      (while (and
+	      (not (eobp))
+	      (not (setq
+		    found
+		    (and (or all
+			     (and
+			      (let ((unread
+				     (get-text-property (point) 'gnus-unread)))
+				(and (numberp unread) (> unread 0)))
+			      (setq lev (get-text-property (point)
+							   'gnus-level))
+			      (<= lev gnus-level-subscribed)))
+			 (or (not level)
+			     (and (setq lev (get-text-property (point)
+							       'gnus-level))
+				  (or (= lev level)
+				      (and (< lev low)
+					   (< level lev)
+					   (progn
+					     (setq low lev)
+					     (setq pos (point))
+					     nil))))))))
+	      (zerop (forward-line way)))))
+    (if found
+	(progn (gnus-group-position-point) t)
+      (goto-char (or pos beg))
+      (and pos t))))
+
+;;; Gnus group mode commands
+
+;; Group marking.
+
+(defun gnus-group-mark-group (n &optional unmark no-advance)
+  "Mark the current group."
+  (interactive "p")
+  (let ((buffer-read-only nil)
+	group)
+    (while (and (> n 0)
+		(not (eobp)))
+      (when (setq group (gnus-group-group-name))
+	;; Go to the mark position.
+	(beginning-of-line)
+	(forward-char (or (cdr (assq 'process gnus-group-mark-positions)) 2))
+	(subst-char-in-region
+	 (point) (1+ (point)) (following-char)
+	 (if unmark
+	     (progn
+	       (setq gnus-group-marked (delete group gnus-group-marked))
+	       ? )
+	   (setq gnus-group-marked
+		 (cons group (delete group gnus-group-marked)))
+	   gnus-process-mark)))
+      (unless no-advance
+	(gnus-group-next-group 1))
+      (decf n))
+    (gnus-summary-position-point)
+    n))
+
+(defun gnus-group-unmark-group (n)
+  "Remove the mark from the current group."
+  (interactive "p")
+  (gnus-group-mark-group n 'unmark)
+  (gnus-group-position-point))
+
+(defun gnus-group-unmark-all-groups ()
+  "Unmark all groups."
+  (interactive)
+  (let ((groups gnus-group-marked))
+    (save-excursion
+      (while groups
+	(gnus-group-remove-mark (pop groups)))))
+  (gnus-group-position-point))
+
+(defun gnus-group-mark-region (unmark beg end)
+  "Mark all groups between point and mark.
+If UNMARK, remove the mark instead."
+  (interactive "P\nr")
+  (let ((num (count-lines beg end)))
+    (save-excursion
+      (goto-char beg)
+      (- num (gnus-group-mark-group num unmark)))))
+
+(defun gnus-group-mark-buffer (&optional unmark)
+  "Mark all groups in the buffer.
+If UNMARK, remove the mark instead."
+  (interactive "P")
+  (gnus-group-mark-region unmark (point-min) (point-max)))
+
+(defun gnus-group-mark-regexp (regexp)
+  "Mark all groups that match some regexp."
+  (interactive "sMark (regexp): ")
+  (let ((alist (cdr gnus-newsrc-alist))
+	group)
+    (while alist
+      (when (string-match regexp (setq group (gnus-info-group (pop alist))))
+	(gnus-group-set-mark group))))
+  (gnus-group-position-point))
+
+(defun gnus-group-remove-mark (group)
+  "Remove the process mark from GROUP and move point there.
+Return nil if the group isn't displayed."
+  (if (gnus-group-goto-group group)
+      (save-excursion
+	(gnus-group-mark-group 1 'unmark t)
+	t)
+    (setq gnus-group-marked
+	  (delete group gnus-group-marked))
+    nil))
+
+(defun gnus-group-set-mark (group)
+  "Set the process mark on GROUP."
+  (if (gnus-group-goto-group group)
+      (save-excursion
+	(gnus-group-mark-group 1 nil t))
+    (setq gnus-group-marked (cons group (delete group gnus-group-marked)))))
+
+(defun gnus-group-universal-argument (arg &optional groups func)
+  "Perform any command on all groups according to the process/prefix convention."
+  (interactive "P")
+  (if (eq (setq func (or func
+			 (key-binding
+			  (read-key-sequence
+			   (substitute-command-keys
+			    "\\<gnus-group-mode-map>\\[gnus-group-universal-argument]")))))
+	  'undefined)
+      (gnus-error 1 "Undefined key")
+    (gnus-group-iterate arg
+      (lambda (group)
+	(command-execute func))))
+  (gnus-group-position-point))
+
+(defun gnus-group-process-prefix (n)
+  "Return a list of groups to work on.
+Take into consideration N (the prefix) and the list of marked groups."
+  (cond
+   (n
+    (setq n (prefix-numeric-value n))
+    ;; There is a prefix, so we return a list of the N next
+    ;; groups.
+    (let ((way (if (< n 0) -1 1))
+	  (n (abs n))
+	  group groups)
+      (save-excursion
+	(while (and (> n 0)
+		    (setq group (gnus-group-group-name)))
+	  (push group groups)
+	  (setq n (1- n))
+	  (gnus-group-next-group way)))
+      (nreverse groups)))
+   ((gnus-region-active-p)
+    ;; Work on the region between point and mark.
+    (let ((max (max (point) (mark)))
+	  groups)
+      (save-excursion
+	(goto-char (min (point) (mark)))
+	(while
+	    (and
+	     (push (gnus-group-group-name) groups)
+	     (zerop (gnus-group-next-group 1))
+	     (< (point) max)))
+	(nreverse groups))))
+   (gnus-group-marked
+    ;; No prefix, but a list of marked articles.
+    (reverse gnus-group-marked))
+   (t
+    ;; Neither marked articles or a prefix, so we return the
+    ;; current group.
+    (let ((group (gnus-group-group-name)))
+      (and group (list group))))))
+
+(defun gnus-group-iterate (arg function)
+  "Iterate FUNCTION over all process/prefixed groups.
+FUNCTION will be called with the group name as the paremeter
+and with point over the group in question."
+  (let ((groups (gnus-group-process-prefix arg))
+	group)
+    (while (setq group (pop groups))
+      (gnus-group-remove-mark group)
+      (funcall function group))))
+
+(put 'gnus-group-iterate 'lisp-indent-function 1)
+
+;; Selecting groups.
+
+(defun gnus-group-read-group (&optional all no-article group)
+  "Read news in this newsgroup.
+If the prefix argument ALL is non-nil, already read articles become
+readable.  IF ALL is a number, fetch this number of articles.  If the
+optional argument NO-ARTICLE is non-nil, no article will be
+auto-selected upon group entry.	 If GROUP is non-nil, fetch that
+group."
+  (interactive "P")
+  (let ((no-display (eq all 0))
+	(group (or group (gnus-group-group-name)))
+	number active marked entry)
+    (when (eq all 0)
+      (setq all nil))
+    (unless group
+      (error "No group on current line"))
+    (setq marked (gnus-info-marks
+		  (nth 2 (setq entry (gnus-gethash
+				      group gnus-newsrc-hashtb)))))
+    ;; This group might be a dead group.  In that case we have to get
+    ;; the number of unread articles from `gnus-active-hashtb'.
+    (setq number
+	  (cond ((numberp all) all)
+		(entry (car entry))
+		((setq active (gnus-active group))
+		 (- (1+ (cdr active)) (car active)))))
+    (gnus-summary-read-group
+     group (or all (and (numberp number)
+			(zerop (+ number (gnus-range-length
+					  (cdr (assq 'tick marked)))
+				  (gnus-range-length
+				   (cdr (assq 'dormant marked)))))))
+     no-article nil no-display)))
+
+(defun gnus-group-select-group (&optional all)
+  "Select this newsgroup.
+No article is selected automatically.
+If ALL is non-nil, already read articles become readable.
+If ALL is a number, fetch this number of articles."
+  (interactive "P")
+  (gnus-group-read-group all t))
+
+(defun gnus-group-quick-select-group (&optional all)
+  "Select the current group \"quickly\".
+This means that no highlighting or scoring will be performed.
+If ALL (the prefix argument) is 0, don't even generate the summary
+buffer."
+  (interactive "P")
+  (require 'gnus-score)
+  (let (gnus-visual
+	gnus-score-find-score-files-function
+	gnus-home-score-file
+	gnus-apply-kill-hook
+	gnus-summary-expunge-below)
+    (gnus-group-read-group all t)))
+
+(defun gnus-group-visible-select-group (&optional all)
+  "Select the current group without hiding any articles."
+  (interactive "P")
+  (let ((gnus-inhibit-limiting t))
+    (gnus-group-read-group all t)))
+
+(defun gnus-group-select-group-ephemerally ()
+  "Select the current group without doing any processing whatsoever.
+You will actually be entered into a group that's a copy of
+the current group; no changes you make while in this group will
+be permanent."
+  (interactive)
+  (require 'gnus-score)
+  (let* (gnus-visual
+	 gnus-score-find-score-files-function gnus-apply-kill-hook
+	 gnus-summary-expunge-below gnus-show-threads gnus-suppress-duplicates
+	 gnus-summary-mode-hook gnus-select-group-hook
+	 (group (gnus-group-group-name))
+	 (method (gnus-find-method-for-group group)))
+    (setq method
+	  `(,(car method) ,(concat (cadr method) "-ephemeral")
+	    (,(intern (format "%s-address" (car method))) ,(cadr method))
+	    ,@(cddr method)))
+    (gnus-group-read-ephemeral-group
+     (gnus-group-prefixed-name group method) method)))
+
+;;;###autoload
+(defun gnus-fetch-group (group)
+  "Start Gnus if necessary and enter GROUP.
+Returns whether the fetching was successful or not."
+  (interactive "sGroup name: ")
+  (unless (get-buffer gnus-group-buffer)
+    (gnus))
+  (gnus-group-read-group nil nil group))
+
+(defvar gnus-ephemeral-group-server 0)
+
+;; Enter a group that is not in the group buffer.  Non-nil is returned
+;; if selection was successful.
+(defun gnus-group-read-ephemeral-group (group method &optional activate
+					      quit-config request-only)
+  "Read GROUP from METHOD as an ephemeral group.
+If ACTIVATE, request the group first.
+If QUIT-CONFIG, use that window configuration when exiting from the
+ephemeral group.
+If REQUEST-ONLY, don't actually read the group; just request it.
+
+Return the name of the group is selection was successful."
+  ;; Transform the select method into a unique server.
+  (let ((saddr (intern (format "%s-address" (car method)))))
+    (setq method (gnus-copy-sequence method))
+    (require (car method))
+    (when (boundp saddr)
+      (unless (assq saddr method)
+	(nconc method `((,saddr ,(cadr method))))
+	(setf (cadr method) (format "%s-%d" (cadr method)
+				    (incf gnus-ephemeral-group-server))))))
+  (let ((group (if (gnus-group-foreign-p group) group
+		 (gnus-group-prefixed-name group method))))
+    (gnus-sethash
+     group
+     `(-1 nil (,group
+	       ,gnus-level-default-subscribed nil nil ,method
+	       ((quit-config .
+			     ,(if quit-config quit-config
+				(cons gnus-summary-buffer
+				      gnus-current-window-configuration))))))
+     gnus-newsrc-hashtb)
+    (set-buffer gnus-group-buffer)
+    (unless (gnus-check-server method)
+      (error "Unable to contact server: %s" (gnus-status-message method)))
+    (when activate
+      (gnus-activate-group group 'scan)
+      (unless (gnus-request-group group)
+	(error "Couldn't request group: %s"
+	       (nnheader-get-report (car method)))))
+    (if request-only
+	group
+      (condition-case ()
+	  (when (gnus-group-read-group t t group)
+	    group)
+	;;(error nil)
+	(quit nil)))))
+
+(defun gnus-group-jump-to-group (group)
+  "Jump to newsgroup GROUP."
+  (interactive
+   (list (completing-read
+	  "Group: " gnus-active-hashtb nil
+	  (gnus-read-active-file-p)
+	  nil
+	  'gnus-group-history)))
+
+  (when (equal group "")
+    (error "Empty group name"))
+
+  (unless (gnus-ephemeral-group-p group)
+    ;; Either go to the line in the group buffer...
+    (unless (gnus-group-goto-group group)
+      ;; ... or insert the line.
+      (gnus-group-update-group group)
+      (gnus-group-goto-group group)))
+  ;; Adjust cursor point.
+  (gnus-group-position-point))
+
+(defun gnus-group-goto-group (group &optional far)
+  "Goto to newsgroup GROUP.
+If FAR, it is likely that the group is not on the current line."
+  (when group
+    (if far
+	(gnus-goto-char
+	 (text-property-any
+	  (point-min) (point-max)
+	  'gnus-group (gnus-intern-safe group gnus-active-hashtb)))
+      (beginning-of-line)
+      (cond
+       ;; It's quite likely that we are on the right line, so
+       ;; we check the current line first.
+       ((eq (get-text-property (point) 'gnus-group)
+	    (gnus-intern-safe group gnus-active-hashtb))
+	(point))
+       ;; Previous and next line are also likely, so we check them as well.
+       ((save-excursion
+	  (forward-line -1)
+	  (eq (get-text-property (point) 'gnus-group)
+	      (gnus-intern-safe group gnus-active-hashtb)))
+	(forward-line -1)
+	(point))
+       ((save-excursion
+	  (forward-line 1)
+	  (eq (get-text-property (point) 'gnus-group)
+	      (gnus-intern-safe group gnus-active-hashtb)))
+	(forward-line 1)
+	(point))
+       (t
+	;; Search through the entire buffer.
+	(gnus-goto-char
+	 (text-property-any
+	  (point-min) (point-max)
+	  'gnus-group (gnus-intern-safe group gnus-active-hashtb))))))))
+
+(defun gnus-group-next-group (n &optional silent)
+  "Go to next N'th newsgroup.
+If N is negative, search backward instead.
+Returns the difference between N and the number of skips actually
+done."
+  (interactive "p")
+  (gnus-group-next-unread-group n t nil silent))
+
+(defun gnus-group-next-unread-group (n &optional all level silent)
+  "Go to next N'th unread newsgroup.
+If N is negative, search backward instead.
+If ALL is non-nil, choose any newsgroup, unread or not.
+If LEVEL is non-nil, choose the next group with level LEVEL, or, if no
+such group can be found, the next group with a level higher than
+LEVEL.
+Returns the difference between N and the number of skips actually
+made."
+  (interactive "p")
+  (let ((backward (< n 0))
+	(n (abs n)))
+    (while (and (> n 0)
+		(gnus-group-search-forward
+		 backward (or (not gnus-group-goto-unread) all) level))
+      (setq n (1- n)))
+    (when (and (/= 0 n)
+	       (not silent))
+      (gnus-message 7 "No more%s newsgroups%s" (if all "" " unread")
+		    (if level " on this level or higher" "")))
+    n))
+
+(defun gnus-group-prev-group (n)
+  "Go to previous N'th newsgroup.
+Returns the difference between N and the number of skips actually
+done."
+  (interactive "p")
+  (gnus-group-next-unread-group (- n) t))
+
+(defun gnus-group-prev-unread-group (n)
+  "Go to previous N'th unread newsgroup.
+Returns the difference between N and the number of skips actually
+done."
+  (interactive "p")
+  (gnus-group-next-unread-group (- n)))
+
+(defun gnus-group-next-unread-group-same-level (n)
+  "Go to next N'th unread newsgroup on the same level.
+If N is negative, search backward instead.
+Returns the difference between N and the number of skips actually
+done."
+  (interactive "p")
+  (gnus-group-next-unread-group n t (gnus-group-group-level))
+  (gnus-group-position-point))
+
+(defun gnus-group-prev-unread-group-same-level (n)
+  "Go to next N'th unread newsgroup on the same level.
+Returns the difference between N and the number of skips actually
+done."
+  (interactive "p")
+  (gnus-group-next-unread-group (- n) t (gnus-group-group-level))
+  (gnus-group-position-point))
+
+(defun gnus-group-best-unread-group (&optional exclude-group)
+  "Go to the group with the highest level.
+If EXCLUDE-GROUP, do not go to that group."
+  (interactive)
+  (goto-char (point-min))
+  (let ((best 100000)
+	unread best-point)
+    (while (not (eobp))
+      (setq unread (get-text-property (point) 'gnus-unread))
+      (when (and (numberp unread) (> unread 0))
+	(when (and (get-text-property (point) 'gnus-level)
+		   (< (get-text-property (point) 'gnus-level) best)
+		   (or (not exclude-group)
+		       (not (equal exclude-group (gnus-group-group-name)))))
+	  (setq best (get-text-property (point) 'gnus-level))
+	  (setq best-point (point))))
+      (forward-line 1))
+    (when best-point
+      (goto-char best-point))
+    (gnus-summary-position-point)
+    (and best-point (gnus-group-group-name))))
+
+(defun gnus-group-first-unread-group ()
+  "Go to the first group with unread articles."
+  (interactive)
+  (prog1
+      (let ((opoint (point))
+	    unread)
+	(goto-char (point-min))
+	(if (or (eq (setq unread (gnus-group-group-unread)) t) ; Not active.
+		(and (numberp unread)	; Not a topic.
+		     (not (zerop unread))) ; Has unread articles.
+		(zerop (gnus-group-next-unread-group 1))) ; Next unread group.
+	    (point)			; Success.
+	  (goto-char opoint)
+	  nil))				; Not success.
+    (gnus-group-position-point)))
+
+(defun gnus-group-enter-server-mode ()
+  "Jump to the server buffer."
+  (interactive)
+  (gnus-enter-server-buffer))
+
+(defun gnus-group-make-group (name &optional method address args)
+  "Add a new newsgroup.
+The user will be prompted for a NAME, for a select METHOD, and an
+ADDRESS."
+  (interactive
+   (list
+    (gnus-read-group "Group name: ")
+    (gnus-read-method "From method: ")))
+
+  (let* ((meth (when (and method
+			  (not (gnus-server-equal method gnus-select-method)))
+		 (if address (list (intern method) address)
+		   method)))
+	 (nname (if method (gnus-group-prefixed-name name meth) name))
+	 backend info)
+    (when (gnus-gethash nname gnus-newsrc-hashtb)
+      (error "Group %s already exists" nname))
+    ;; Subscribe to the new group.
+    (gnus-group-change-level
+     (setq info (list t nname gnus-level-default-subscribed nil nil meth))
+     gnus-level-default-subscribed gnus-level-killed
+     (and (gnus-group-group-name)
+	  (gnus-gethash (gnus-group-group-name)
+			gnus-newsrc-hashtb))
+     t)
+    ;; Make it active.
+    (gnus-set-active nname (cons 1 0))
+    (unless (gnus-ephemeral-group-p name)
+      (gnus-dribble-enter
+       (concat "(gnus-group-set-info '"
+	       (gnus-prin1-to-string (cdr info)) ")")))
+    ;; Insert the line.
+    (gnus-group-insert-group-line-info nname)
+    (forward-line -1)
+    (gnus-group-position-point)
+
+    ;; Load the backend and try to make the backend create
+    ;; the group as well.
+    (when (assoc (symbol-name (setq backend (car (gnus-server-get-method
+						  nil meth))))
+		 gnus-valid-select-methods)
+      (require backend))
+    (gnus-check-server meth)
+    (when (gnus-check-backend-function 'request-create-group nname)
+      (gnus-request-create-group nname nil args))
+    t))
+
+(defun gnus-group-delete-group (group &optional force)
+  "Delete the current group.  Only meaningful with mail groups.
+If FORCE (the prefix) is non-nil, all the articles in the group will
+be deleted.  This is \"deleted\" as in \"removed forever from the face
+of the Earth\".	 There is no undo.  The user will be prompted before
+doing the deletion."
+  (interactive
+   (list (gnus-group-group-name)
+	 current-prefix-arg))
+  (unless group
+    (error "No group to rename"))
+  (unless (gnus-check-backend-function 'request-delete-group group)
+    (error "This backend does not support group deletion"))
+  (prog1
+      (if (not (gnus-yes-or-no-p
+		(format
+		 "Do you really want to delete %s%s? "
+		 group (if force " and all its contents" ""))))
+	  ()				; Whew!
+	(gnus-message 6 "Deleting group %s..." group)
+	(if (not (gnus-request-delete-group group force))
+	    (gnus-error 3 "Couldn't delete group %s" group)
+	  (gnus-message 6 "Deleting group %s...done" group)
+	  (gnus-group-goto-group group)
+	  (gnus-group-kill-group 1 t)
+	  (gnus-sethash group nil gnus-active-hashtb)
+	  t))
+    (gnus-group-position-point)))
+
+(defun gnus-group-rename-group (group new-name)
+  "Rename group from GROUP to NEW-NAME.
+When used interactively, GROUP is the group under point
+and NEW-NAME will be prompted for."
+  (interactive
+   (list
+    (gnus-group-group-name)
+    (progn
+      (unless (gnus-check-backend-function
+	       'request-rename-group (gnus-group-group-name))
+	(error "This backend does not support renaming groups"))
+      (gnus-read-group "Rename group to: "
+		       (gnus-group-real-name (gnus-group-group-name))))))
+
+  (unless (gnus-check-backend-function 'request-rename-group group)
+    (error "This backend does not support renaming groups"))
+  (unless group
+    (error "No group to rename"))
+  (when (equal (gnus-group-real-name group) new-name)
+    (error "Can't rename to the same name"))
+
+  ;; We find the proper prefixed name.
+  (setq new-name
+	(if (gnus-group-native-p group)
+	    ;; Native group.
+	    new-name
+	  ;; Foreign group.
+	  (gnus-group-prefixed-name
+	   (gnus-group-real-name new-name)
+	   (gnus-info-method (gnus-get-info group)))))
+
+  (gnus-message 6 "Renaming group %s to %s..." group new-name)
+  (prog1
+      (if (not (gnus-request-rename-group group new-name))
+	  (gnus-error 3 "Couldn't rename group %s to %s" group new-name)
+	;; We rename the group internally by killing it...
+	(gnus-group-goto-group group)
+	(gnus-group-kill-group)
+	;; ... changing its name ...
+	(setcar (cdar gnus-list-of-killed-groups) new-name)
+	;; ... and then yanking it.  Magic!
+	(gnus-group-yank-group)
+	(gnus-set-active new-name (gnus-active group))
+	(gnus-message 6 "Renaming group %s to %s...done" group new-name)
+	new-name)
+    (gnus-group-position-point)))
+
+(defun gnus-group-edit-group (group &optional part)
+  "Edit the group on the current line."
+  (interactive (list (gnus-group-group-name)))
+  (let ((part (or part 'info))
+	info)
+    (unless group
+      (error "No group on current line"))
+    (unless (setq info (gnus-get-info group))
+      (error "Killed group; can't be edited"))
+    (ignore-errors
+      (gnus-close-group group))
+    (gnus-edit-form
+     ;; Find the proper form to edit.
+     (cond ((eq part 'method)
+	    (or (gnus-info-method info) "native"))
+	   ((eq part 'params)
+	    (gnus-info-params info))
+	   (t info))
+     ;; The proper documentation.
+     (format
+      "Editing the %s for `%s'."
+      (cond
+       ((eq part 'method) "select method")
+       ((eq part 'params) "group parameters")
+       (t "group info"))
+      group)
+     `(lambda (form)
+	(gnus-group-edit-group-done ',part ,group form)))))
+
+(defun gnus-group-edit-group-method (group)
+  "Edit the select method of GROUP."
+  (interactive (list (gnus-group-group-name)))
+  (gnus-group-edit-group group 'method))
+
+(defun gnus-group-edit-group-parameters (group)
+  "Edit the group parameters of GROUP."
+  (interactive (list (gnus-group-group-name)))
+  (gnus-group-edit-group group 'params))
+
+(defun gnus-group-edit-group-done (part group form)
+  "Update variables."
+  (let* ((method (cond ((eq part 'info) (nth 4 form))
+		       ((eq part 'method) form)
+		       (t nil)))
+	 (info (cond ((eq part 'info) form)
+		     ((eq part 'method) (gnus-get-info group))
+		     (t nil)))
+	 (new-group (if info
+			(if (or (not method)
+				(gnus-server-equal
+				 gnus-select-method method))
+			    (gnus-group-real-name (car info))
+			  (gnus-group-prefixed-name
+			   (gnus-group-real-name (car info)) method))
+		      nil)))
+    (when (and new-group
+	       (not (equal new-group group)))
+      (when (gnus-group-goto-group group)
+	(gnus-group-kill-group 1))
+      (gnus-activate-group new-group))
+    ;; Set the info.
+    (if (not (and info new-group))
+	(gnus-group-set-info form (or new-group group) part)
+      (setq info (gnus-copy-sequence info))
+      (setcar info new-group)
+      (unless (gnus-server-equal method "native")
+	(unless (nthcdr 3 info)
+	  (nconc info (list nil nil)))
+	(unless (nthcdr 4 info)
+	  (nconc info (list nil)))
+	(gnus-info-set-method info method))
+      (gnus-group-set-info info))
+    (gnus-group-update-group (or new-group group))
+    (gnus-group-position-point)))
+
+(defun gnus-group-make-useful-group (group method)
+  (interactive
+   (let ((entry (assoc (completing-read "Create group: " gnus-useful-groups
+					nil t)
+		       gnus-useful-groups)))
+     (list (cadr entry) (caddr entry))))
+  (setq method (gnus-copy-sequence method))
+  (let (entry)
+    (while (setq entry (memq (assq 'eval method) method))
+      (setcar entry (eval (cadar entry)))))
+  (gnus-group-make-group group method))
+
+(defun gnus-group-make-help-group ()
+  "Create the Gnus documentation group."
+  (interactive)
+  (let ((name (gnus-group-prefixed-name "gnus-help" '(nndoc "gnus-help")))
+	(file (nnheader-find-etc-directory "gnus-tut.txt" t))
+	dir)
+    (when (gnus-gethash name gnus-newsrc-hashtb)
+      (error "Documentation group already exists"))
+    (if (not file)
+	(gnus-message 1 "Couldn't find doc group")
+      (gnus-group-make-group
+       (gnus-group-real-name name)
+       (list 'nndoc "gnus-help"
+	     (list 'nndoc-address file)
+	     (list 'nndoc-article-type 'mbox)))))
+  (gnus-group-position-point))
+
+(defun gnus-group-make-doc-group (file type)
+  "Create a group that uses a single file as the source."
+  (interactive
+   (list (read-file-name "File name: ")
+	 (and current-prefix-arg 'ask)))
+  (when (eq type 'ask)
+    (let ((err "")
+	  char found)
+      (while (not found)
+	(message
+	 "%sFile type (mbox, babyl, digest, forward, mmdf, guess) [mbdfag]: "
+	 err)
+	(setq found (cond ((= (setq char (read-char)) ?m) 'mbox)
+			  ((= char ?b) 'babyl)
+			  ((= char ?d) 'digest)
+			  ((= char ?f) 'forward)
+			  ((= char ?a) 'mmfd)
+			  (t (setq err (format "%c unknown. " char))
+			     nil))))
+      (setq type found)))
+  (let* ((file (expand-file-name file))
+	 (name (gnus-generate-new-group-name
+		(gnus-group-prefixed-name
+		 (file-name-nondirectory file) '(nndoc "")))))
+    (gnus-group-make-group
+     (gnus-group-real-name name)
+     (list 'nndoc file
+	   (list 'nndoc-address file)
+	   (list 'nndoc-article-type (or type 'guess))))))
+
+(defvar nnweb-type-definition)
+(defvar gnus-group-web-type-history nil)
+(defvar gnus-group-web-search-history nil)
+(defun gnus-group-make-web-group (&optional solid)
+  "Create an ephemeral nnweb group.
+If SOLID (the prefix), create a solid group."
+  (interactive "P")
+  (require 'nnweb)
+  (let* ((group
+	  (if solid (gnus-read-group "Group name: ")
+	    (message-unique-id)))
+	 (type
+	  (completing-read
+	   "Search engine type: "
+	   (mapcar (lambda (elem) (list (symbol-name (car elem))))
+		   nnweb-type-definition)
+	   nil t (cons (or (car gnus-group-web-type-history)
+			   (symbol-name (caar nnweb-type-definition)))
+		       0)
+	   'gnus-group-web-type-history))
+	 (search
+	  (read-string
+	   "Search string: "
+	   (cons (or (car gnus-group-web-search-history) "") 0)
+	   'gnus-group-web-search-history))
+	 (method
+	  `(nnweb ,group (nnweb-search ,search)
+		  (nnweb-type ,(intern type))
+		  (nnweb-ephemeral-p t))))
+    (if solid
+	(gnus-group-make-group group "nnweb" "" `(,(intern type) ,search))
+      (gnus-group-read-ephemeral-group
+       group method t
+       (cons (current-buffer)
+	     (if (eq major-mode 'gnus-summary-mode) 'summary 'group))))))
+
+(defun gnus-group-make-archive-group (&optional all)
+  "Create the (ding) Gnus archive group of the most recent articles.
+Given a prefix, create a full group."
+  (interactive "P")
+  (let ((group (gnus-group-prefixed-name
+		(if all "ding.archives" "ding.recent") '(nndir ""))))
+    (when (gnus-gethash group gnus-newsrc-hashtb)
+      (error "Archive group already exists"))
+    (gnus-group-make-group
+     (gnus-group-real-name group)
+     (list 'nndir (if all "hpc" "edu")
+	   (list 'nndir-directory
+		 (if all gnus-group-archive-directory
+		   gnus-group-recent-archive-directory))))
+    (gnus-group-add-parameter group (cons 'to-address "ding@gnus.org"))))
+
+(defun gnus-group-make-directory-group (dir)
+  "Create an nndir group.
+The user will be prompted for a directory.  The contents of this
+directory will be used as a newsgroup.	The directory should contain
+mail messages or news articles in files that have numeric names."
+  (interactive
+   (list (read-file-name "Create group from directory: ")))
+  (unless (file-exists-p dir)
+    (error "No such directory"))
+  (unless (file-directory-p dir)
+    (error "Not a directory"))
+  (let ((ext "")
+	(i 0)
+	group)
+    (while (or (not group) (gnus-gethash group gnus-newsrc-hashtb))
+      (setq group
+	    (gnus-group-prefixed-name
+	     (concat (file-name-as-directory (directory-file-name dir))
+		     ext)
+	     '(nndir "")))
+      (setq ext (format "<%d>" (setq i (1+ i)))))
+    (gnus-group-make-group
+     (gnus-group-real-name group)
+     (list 'nndir (gnus-group-real-name group) (list 'nndir-directory dir)))))
+
+(defun gnus-group-make-kiboze-group (group address scores)
+  "Create an nnkiboze group.
+The user will be prompted for a name, a regexp to match groups, and
+score file entries for articles to include in the group."
+  (interactive
+   (list
+    (read-string "nnkiboze group name: ")
+    (read-string "Source groups (regexp): ")
+    (let ((headers (mapcar (lambda (group) (list group))
+			   '("subject" "from" "number" "date" "message-id"
+			     "references" "chars" "lines" "xref"
+			     "followup" "all" "body" "head")))
+	  scores header regexp regexps)
+      (while (not (equal "" (setq header (completing-read
+					  "Match on header: " headers nil t))))
+	(setq regexps nil)
+	(while (not (equal "" (setq regexp (read-string
+					    (format "Match on %s (string): "
+						    header)))))
+	  (push (list regexp nil nil 'r) regexps))
+	(push (cons header regexps) scores))
+      scores)))
+  (gnus-group-make-group group "nnkiboze" address)
+  (nnheader-temp-write (gnus-score-file-name (concat "nnkiboze:" group))
+    (let (emacs-lisp-mode-hook)
+      (pp scores (current-buffer)))))
+
+(defun gnus-group-add-to-virtual (n vgroup)
+  "Add the current group to a virtual group."
+  (interactive
+   (list current-prefix-arg
+	 (completing-read "Add to virtual group: " gnus-newsrc-hashtb nil t
+			  "nnvirtual:")))
+  (unless (eq (car (gnus-find-method-for-group vgroup)) 'nnvirtual)
+    (error "%s is not an nnvirtual group" vgroup))
+  (gnus-close-group vgroup)
+  (let* ((groups (gnus-group-process-prefix n))
+	 (method (gnus-info-method (gnus-get-info vgroup))))
+    (setcar (cdr method)
+	    (concat
+	     (nth 1 method) "\\|"
+	     (mapconcat
+	      (lambda (s)
+		(gnus-group-remove-mark s)
+		(concat "\\(^" (regexp-quote s) "$\\)"))
+	      groups "\\|"))))
+  (gnus-group-position-point))
+
+(defun gnus-group-make-empty-virtual (group)
+  "Create a new, fresh, empty virtual group."
+  (interactive "sCreate new, empty virtual group: ")
+  (let* ((method (list 'nnvirtual "^$"))
+	 (pgroup (gnus-group-prefixed-name group method)))
+    ;; Check whether it exists already.
+    (when (gnus-gethash pgroup gnus-newsrc-hashtb)
+      (error "Group %s already exists." pgroup))
+    ;; Subscribe the new group after the group on the current line.
+    (gnus-subscribe-group pgroup (gnus-group-group-name) method)
+    (gnus-group-update-group pgroup)
+    (forward-line -1)
+    (gnus-group-position-point)))
+
+(defun gnus-group-enter-directory (dir)
+  "Enter an ephemeral nneething group."
+  (interactive "DDirectory to read: ")
+  (let* ((method (list 'nneething dir '(nneething-read-only t)))
+	 (leaf (gnus-group-prefixed-name
+		(file-name-nondirectory (directory-file-name dir))
+		method))
+	 (name (gnus-generate-new-group-name leaf)))
+    (unless (gnus-group-read-ephemeral-group
+	     name method t
+	     (cons (current-buffer)
+		   (if (eq major-mode 'gnus-summary-mode)
+		       'summary 'group)))
+      (error "Couldn't enter %s" dir))))
+
+;; Group sorting commands
+;; Suggested by Joe Hildebrand <hildjj@idaho.fuentez.com>.
+
+(defun gnus-group-sort-groups (func &optional reverse)
+  "Sort the group buffer according to FUNC.
+When used interactively, the sorting function used will be
+determined by the `gnus-group-sort-function' variable.
+If REVERSE (the prefix), reverse the sorting order."
+  (interactive (list gnus-group-sort-function current-prefix-arg))
+  (funcall gnus-group-sort-alist-function
+	   (gnus-make-sort-function func) reverse)
+  (gnus-group-list-groups)
+  (gnus-dribble-touch))
+
+(defun gnus-group-sort-flat (func reverse)
+  ;; We peel off the dummy group from the alist.
+  (when func
+    (when (equal (gnus-info-group (car gnus-newsrc-alist)) "dummy.group")
+      (pop gnus-newsrc-alist))
+    ;; Do the sorting.
+    (setq gnus-newsrc-alist
+	  (sort gnus-newsrc-alist func))
+    (when reverse
+      (setq gnus-newsrc-alist (nreverse gnus-newsrc-alist)))
+    ;; Regenerate the hash table.
+    (gnus-make-hashtable-from-newsrc-alist)))
+
+(defun gnus-group-sort-groups-by-alphabet (&optional reverse)
+  "Sort the group buffer alphabetically by group name.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-groups 'gnus-group-sort-by-alphabet reverse))
+
+(defun gnus-group-sort-groups-by-unread (&optional reverse)
+  "Sort the group buffer by number of unread articles.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-groups 'gnus-group-sort-by-unread reverse))
+
+(defun gnus-group-sort-groups-by-level (&optional reverse)
+  "Sort the group buffer by group level.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-groups 'gnus-group-sort-by-level reverse))
+
+(defun gnus-group-sort-groups-by-score (&optional reverse)
+  "Sort the group buffer by group score.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-groups 'gnus-group-sort-by-score reverse))
+
+(defun gnus-group-sort-groups-by-rank (&optional reverse)
+  "Sort the group buffer by group rank.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-groups 'gnus-group-sort-by-rank reverse))
+
+(defun gnus-group-sort-groups-by-method (&optional reverse)
+  "Sort the group buffer alphabetically by backend name.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-groups 'gnus-group-sort-by-method reverse))
+
+;;; Selected group sorting.
+
+(defun gnus-group-sort-selected-groups (n func &optional reverse)
+  "Sort the process/prefixed groups."
+  (interactive (list current-prefix-arg gnus-group-sort-function))
+  (let ((groups (gnus-group-process-prefix n)))
+    (funcall gnus-group-sort-selected-function
+	     groups (gnus-make-sort-function func) reverse)
+    (gnus-group-list-groups)))
+
+(defun gnus-group-sort-selected-flat (groups func reverse)
+  (let (entries infos)
+    ;; First find all the group entries for these groups.
+    (while groups
+      (push (nthcdr 2 (gnus-gethash (pop groups) gnus-newsrc-hashtb))
+	    entries))
+    ;; Then sort the infos.
+    (setq infos
+	  (sort
+	   (mapcar
+	    (lambda (entry) (car entry))
+	    (setq entries (nreverse entries)))
+	   func))
+    (when reverse
+      (setq infos (nreverse infos)))
+    ;; Go through all the infos and replace the old entries
+    ;; with the new infos.
+    (while infos
+      (setcar entries (pop infos))
+      (pop entries))
+    ;; Update the hashtable.
+    (gnus-make-hashtable-from-newsrc-alist)))
+
+(defun gnus-group-sort-selected-groups-by-alphabet (&optional reverse)
+  "Sort the group buffer alphabetically by group name.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-selected-groups 'gnus-group-sort-by-alphabet reverse))
+
+(defun gnus-group-sort-selected-groups-by-unread (&optional reverse)
+  "Sort the group buffer by number of unread articles.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-selected-groups 'gnus-group-sort-by-unread reverse))
+
+(defun gnus-group-sort-selected-groups-by-level (&optional reverse)
+  "Sort the group buffer by group level.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-selected-groups 'gnus-group-sort-by-level reverse))
+
+(defun gnus-group-sort-selected-groups-by-score (&optional reverse)
+  "Sort the group buffer by group score.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-selected-groups 'gnus-group-sort-by-score reverse))
+
+(defun gnus-group-sort-selected-groups-by-rank (&optional reverse)
+  "Sort the group buffer by group rank.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-selected-groups 'gnus-group-sort-by-rank reverse))
+
+(defun gnus-group-sort-selected-groups-by-method (&optional reverse)
+  "Sort the group buffer alphabetically by backend name.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-group-sort-selected-groups 'gnus-group-sort-by-method reverse))
+
+;;; Sorting predicates.
+
+(defun gnus-group-sort-by-alphabet (info1 info2)
+  "Sort alphabetically."
+  (string< (gnus-info-group info1) (gnus-info-group info2)))
+
+(defun gnus-group-sort-by-real-name (info1 info2)
+  "Sort alphabetically on real (unprefixed) names."
+  (string< (gnus-group-real-name (gnus-info-group info1))
+	   (gnus-group-real-name (gnus-info-group info2))))
+
+(defun gnus-group-sort-by-unread (info1 info2)
+  "Sort by number of unread articles."
+  (let ((n1 (car (gnus-gethash (gnus-info-group info1) gnus-newsrc-hashtb)))
+	(n2 (car (gnus-gethash (gnus-info-group info2) gnus-newsrc-hashtb))))
+    (< (or (and (numberp n1) n1) 0)
+       (or (and (numberp n2) n2) 0))))
+
+(defun gnus-group-sort-by-level (info1 info2)
+  "Sort by level."
+  (< (gnus-info-level info1) (gnus-info-level info2)))
+
+(defun gnus-group-sort-by-method (info1 info2)
+  "Sort alphabetically by backend name."
+  (string< (symbol-name (car (gnus-find-method-for-group
+			      (gnus-info-group info1) info1)))
+	   (symbol-name (car (gnus-find-method-for-group
+			      (gnus-info-group info2) info2)))))
+
+(defun gnus-group-sort-by-score (info1 info2)
+  "Sort by group score."
+  (< (gnus-info-score info1) (gnus-info-score info2)))
+
+(defun gnus-group-sort-by-rank (info1 info2)
+  "Sort by level and score."
+  (let ((level1 (gnus-info-level info1))
+	(level2 (gnus-info-level info2)))
+    (or (< level1 level2)
+	(and (= level1 level2)
+	     (> (gnus-info-score info1) (gnus-info-score info2))))))
+
+;;; Clearing data
+
+(defun gnus-group-clear-data (&optional arg)
+  "Clear all marks and read ranges from the current group."
+  (interactive "P")
+  (gnus-group-iterate arg
+    (lambda (group)
+      (let (info)
+	(gnus-info-clear-data (setq info (gnus-get-info group)))
+	(gnus-get-unread-articles-in-group info (gnus-active group) t)
+	(when (gnus-group-goto-group group)
+	  (gnus-group-update-group-line))))))
+
+(defun gnus-group-clear-data-on-native-groups ()
+  "Clear all marks and read ranges from all native groups."
+  (interactive)
+  (when (gnus-yes-or-no-p "Really clear all data from almost all groups? ")
+    (let ((alist (cdr gnus-newsrc-alist))
+	  info)
+      (while (setq info (pop alist))
+	(when (gnus-group-native-p (gnus-info-group info))
+	  (gnus-info-clear-data info)))
+      (gnus-get-unread-articles)
+      (gnus-dribble-enter "")
+      (when (gnus-y-or-n-p
+	     "Move the cache away to avoid problems in the future? ")
+	(call-interactively 'gnus-cache-move-cache)))))
+
+(defun gnus-info-clear-data (info)
+  "Clear all marks and read ranges from INFO."
+  (let ((group (gnus-info-group info)))
+    (gnus-undo-register
+      `(progn
+	 (gnus-info-set-marks ',info ',(gnus-info-marks info) t)
+	 (gnus-info-set-read ',info ',(gnus-info-read info))
+	 (when (gnus-group-goto-group ,group)
+	   (gnus-group-update-group-line))))
+    (gnus-info-set-read info nil)
+    (when (gnus-info-marks info)
+      (gnus-info-set-marks info nil))))
+
+;; Group catching up.
+
+(defun gnus-group-catchup-current (&optional n all)
+  "Mark all articles not marked as unread in current newsgroup as read.
+If prefix argument N is numeric, the ARG next newsgroups will be
+caught up.  If ALL is non-nil, marked articles will also be marked as
+read.  Cross references (Xref: header) of articles are ignored.
+The difference between N and actual number of newsgroups that were
+caught up is returned."
+  (interactive "P")
+  (unless (gnus-group-group-name)
+    (error "No group on the current line"))
+  (let ((groups (gnus-group-process-prefix n))
+	(ret 0))
+    (if (not
+	 (or (not gnus-interactive-catchup) ;Without confirmation?
+	     gnus-expert-user
+	     (gnus-y-or-n-p
+	      (format
+	       (if all
+		   "Do you really want to mark all articles in %s as read? "
+		 "Mark all unread articles in %s as read? ")
+	       (if (= (length groups) 1)
+		   (car groups)
+		 (format "these %d groups" (length groups)))))))
+	n
+      (while groups
+	;; Virtual groups have to be given special treatment.
+	(let ((method (gnus-find-method-for-group (car groups))))
+	  (when (eq 'nnvirtual (car method))
+	    (nnvirtual-catchup-group
+	     (gnus-group-real-name (car groups)) (nth 1 method) all)))
+	(gnus-group-remove-mark (car groups))
+	(if (>= (gnus-group-group-level) gnus-level-zombie)
+	    (gnus-message 2 "Dead groups can't be caught up")
+	  (if (prog1
+		  (gnus-group-goto-group (car groups))
+		(gnus-group-catchup (car groups) all))
+	      (gnus-group-update-group-line)
+	    (setq ret (1+ ret))))
+	(setq groups (cdr groups)))
+      (gnus-group-next-unread-group 1)
+      ret)))
+
+(defun gnus-group-catchup-current-all (&optional n)
+  "Mark all articles in current newsgroup as read.
+Cross references (Xref: header) of articles are ignored."
+  (interactive "P")
+  (gnus-group-catchup-current n 'all))
+
+(defun gnus-group-catchup (group &optional all)
+  "Mark all articles in GROUP as read.
+If ALL is non-nil, all articles are marked as read.
+The return value is the number of articles that were marked as read,
+or nil if no action could be taken."
+  (let* ((entry (gnus-gethash group gnus-newsrc-hashtb))
+	 (num (car entry)))
+    ;; Do the updating only if the newsgroup isn't killed.
+    (if (not (numberp (car entry)))
+	(gnus-message 1 "Can't catch up %s; non-active group" group)
+      ;; Do auto-expirable marks if that's required.
+      (when (gnus-group-auto-expirable-p group)
+	(gnus-add-marked-articles
+	 group 'expire (gnus-list-of-unread-articles group))
+	(when all
+	  (let ((marks (nth 3 (nth 2 entry))))
+	    (gnus-add-marked-articles
+	     group 'expire (gnus-uncompress-range (cdr (assq 'tick marks))))
+	    (gnus-add-marked-articles
+	     group 'expire (gnus-uncompress-range (cdr (assq 'tick marks)))))))
+      (when entry
+	(gnus-update-read-articles group nil)
+	;; Also nix out the lists of marks and dormants.
+	(when all
+	  (gnus-add-marked-articles group 'tick nil nil 'force)
+	  (gnus-add-marked-articles group 'dormant nil nil 'force))
+	(let ((gnus-newsgroup-name group))
+	  (run-hooks 'gnus-group-catchup-group-hook))
+	num))))
+
+(defun gnus-group-expire-articles (&optional n)
+  "Expire all expirable articles in the current newsgroup."
+  (interactive "P")
+  (let ((groups (gnus-group-process-prefix n))
+	group)
+    (unless groups
+      (error "No groups to expire"))
+    (while (setq group (pop groups))
+      (gnus-group-remove-mark group)
+      (when (gnus-check-backend-function 'request-expire-articles group)
+	(gnus-message 6 "Expiring articles in %s..." group)
+	(let* ((info (gnus-get-info group))
+	       (expirable (if (gnus-group-total-expirable-p group)
+			      (cons nil (gnus-list-of-read-articles group))
+			    (assq 'expire (gnus-info-marks info))))
+	       (expiry-wait (gnus-group-find-parameter group 'expiry-wait)))
+	  (when expirable
+	    (setcdr
+	     expirable
+	     (gnus-compress-sequence
+	      (if expiry-wait
+		  ;; We set the expiry variables to the group
+		  ;; parameter.
+		  (let ((nnmail-expiry-wait-function nil)
+			(nnmail-expiry-wait expiry-wait))
+		    (gnus-request-expire-articles
+		     (gnus-uncompress-sequence (cdr expirable)) group))
+		;; Just expire using the normal expiry values.
+		(gnus-request-expire-articles
+		 (gnus-uncompress-sequence (cdr expirable)) group))))
+	    (gnus-close-group group))
+	  (gnus-message 6 "Expiring articles in %s...done" group)))
+      (gnus-dribble-touch)
+      (gnus-group-position-point))))
+
+(defun gnus-group-expire-all-groups ()
+  "Expire all expirable articles in all newsgroups."
+  (interactive)
+  (save-excursion
+    (gnus-message 5 "Expiring...")
+    (let ((gnus-group-marked (mapcar (lambda (info) (gnus-info-group info))
+				     (cdr gnus-newsrc-alist))))
+      (gnus-group-expire-articles nil)))
+  (gnus-group-position-point)
+  (gnus-message 5 "Expiring...done"))
+
+(defun gnus-group-set-current-level (n level)
+  "Set the level of the next N groups to LEVEL."
+  (interactive
+   (list
+    current-prefix-arg
+    (string-to-int
+     (let ((s (read-string
+	       (format "Level (default %s): "
+		       (or (gnus-group-group-level)
+			   gnus-level-default-subscribed)))))
+       (if (string-match "^\\s-*$" s)
+	   (int-to-string (or (gnus-group-group-level)
+			      gnus-level-default-subscribed))
+	 s)))))
+  (unless (and (>= level 1) (<= level gnus-level-killed))
+    (error "Illegal level: %d" level))
+  (let ((groups (gnus-group-process-prefix n))
+	group)
+    (while (setq group (pop groups))
+      (gnus-group-remove-mark group)
+      (gnus-message 6 "Changed level of %s from %d to %d"
+		    group (or (gnus-group-group-level) gnus-level-killed)
+		    level)
+      (gnus-group-change-level
+       group level (or (gnus-group-group-level) gnus-level-killed))
+      (gnus-group-update-group-line)))
+  (gnus-group-position-point))
+
+(defun gnus-group-unsubscribe (&optional n)
+  "Unsubscribe the current group."
+  (interactive "P")
+  (gnus-group-unsubscribe-current-group n 'unsubscribe))
+
+(defun gnus-group-subscribe (&optional n)
+  "Subscribe the current group."
+  (interactive "P")
+  (gnus-group-unsubscribe-current-group n 'subscribe))
+
+(defun gnus-group-unsubscribe-current-group (&optional n do-sub)
+  "Toggle subscription of the current group.
+If given numerical prefix, toggle the N next groups."
+  (interactive "P")
+  (let ((groups (gnus-group-process-prefix n))
+	group)
+    (while groups
+      (setq group (car groups)
+	    groups (cdr groups))
+      (gnus-group-remove-mark group)
+      (gnus-group-unsubscribe-group
+       group
+       (cond
+	((eq do-sub 'unsubscribe)
+	 gnus-level-default-unsubscribed)
+	((eq do-sub 'subscribe)
+	 gnus-level-default-subscribed)
+	((<= (gnus-group-group-level) gnus-level-subscribed)
+	 gnus-level-default-unsubscribed)
+	(t
+	 gnus-level-default-subscribed))
+       t)
+      (gnus-group-update-group-line))
+    (gnus-group-next-group 1)))
+
+(defun gnus-group-unsubscribe-group (group &optional level silent)
+  "Toggle subscription to GROUP.
+Killed newsgroups are subscribed.  If SILENT, don't try to update the
+group line."
+  (interactive
+   (list (completing-read
+	  "Group: " gnus-active-hashtb nil
+	  (gnus-read-active-file-p)
+	  nil
+	  'gnus-group-history)))
+  (let ((newsrc (gnus-gethash group gnus-newsrc-hashtb)))
+    (cond
+     ((string-match "^[ \t]$" group)
+      (error "Empty group name"))
+     (newsrc
+      ;; Toggle subscription flag.
+      (gnus-group-change-level
+       newsrc (if level level (if (<= (gnus-info-level (nth 2 newsrc))
+				      gnus-level-subscribed)
+				  (1+ gnus-level-subscribed)
+				gnus-level-default-subscribed)))
+      (unless silent
+	(gnus-group-update-group group)))
+     ((and (stringp group)
+	   (or (not (gnus-read-active-file-p))
+	       (gnus-active group)))
+      ;; Add new newsgroup.
+      (gnus-group-change-level
+       group
+       (if level level gnus-level-default-subscribed)
+       (or (and (member group gnus-zombie-list)
+		gnus-level-zombie)
+	   gnus-level-killed)
+       (when (gnus-group-group-name)
+	 (gnus-gethash (gnus-group-group-name) gnus-newsrc-hashtb)))
+      (unless silent
+	(gnus-group-update-group group)))
+     (t (error "No such newsgroup: %s" group)))
+    (gnus-group-position-point)))
+
+(defun gnus-group-transpose-groups (n)
+  "Move the current newsgroup up N places.
+If given a negative prefix, move down instead.	The difference between
+N and the number of steps taken is returned."
+  (interactive "p")
+  (unless (gnus-group-group-name)
+    (error "No group on current line"))
+  (gnus-group-kill-group 1)
+  (prog1
+      (forward-line (- n))
+    (gnus-group-yank-group)
+    (gnus-group-position-point)))
+
+(defun gnus-group-kill-all-zombies ()
+  "Kill all zombie newsgroups."
+  (interactive)
+  (setq gnus-killed-list (nconc gnus-zombie-list gnus-killed-list))
+  (setq gnus-zombie-list nil)
+  (gnus-dribble-touch)
+  (gnus-group-list-groups))
+
+(defun gnus-group-kill-region (begin end)
+  "Kill newsgroups in current region (excluding current point).
+The killed newsgroups can be yanked by using \\[gnus-group-yank-group]."
+  (interactive "r")
+  (let ((lines
+	 ;; Count lines.
+	 (save-excursion
+	   (count-lines
+	    (progn
+	      (goto-char begin)
+	      (beginning-of-line)
+	      (point))
+	    (progn
+	      (goto-char end)
+	      (beginning-of-line)
+	      (point))))))
+    (goto-char begin)
+    (beginning-of-line)			;Important when LINES < 1
+    (gnus-group-kill-group lines)))
+
+(defun gnus-group-kill-group (&optional n discard)
+  "Kill the next N groups.
+The killed newsgroups can be yanked by using \\[gnus-group-yank-group].
+However, only groups that were alive can be yanked; already killed
+groups or zombie groups can't be yanked.
+The return value is the name of the group that was killed, or a list
+of groups killed."
+  (interactive "P")
+  (let ((buffer-read-only nil)
+	(groups (gnus-group-process-prefix n))
+	group entry level out)
+    (if (< (length groups) 10)
+	;; This is faster when there are few groups.
+	(while groups
+	  (push (setq group (pop groups)) out)
+	  (gnus-group-remove-mark group)
+	  (setq level (gnus-group-group-level))
+	  (gnus-delete-line)
+	  (when (and (not discard)
+		     (setq entry (gnus-gethash group gnus-newsrc-hashtb)))
+	    (gnus-undo-register
+	      `(progn
+		 (gnus-group-goto-group ,(gnus-group-group-name))
+		 (gnus-group-yank-group)))
+	    (push (cons (car entry) (nth 2 entry))
+		  gnus-list-of-killed-groups))
+	  (gnus-group-change-level
+	   (if entry entry group) gnus-level-killed (if entry nil level)))
+      ;; If there are lots and lots of groups to be killed, we use
+      ;; this thing instead.
+      (let (entry)
+	(setq groups (nreverse groups))
+	(while groups
+	  (gnus-group-remove-mark (setq group (pop groups)))
+	  (gnus-delete-line)
+	  (push group gnus-killed-list)
+	  (setq gnus-newsrc-alist
+		(delq (assoc group gnus-newsrc-alist)
+		      gnus-newsrc-alist))
+	  (when gnus-group-change-level-function
+	    (funcall gnus-group-change-level-function group 9 3))
+	  (cond
+	   ((setq entry (gnus-gethash group gnus-newsrc-hashtb))
+	    (push (cons (car entry) (nth 2 entry))
+		  gnus-list-of-killed-groups)
+	    (setcdr (cdr entry) (cdddr entry)))
+	   ((member group gnus-zombie-list)
+	    (setq gnus-zombie-list (delete group gnus-zombie-list)))))
+	(gnus-make-hashtable-from-newsrc-alist)))
+
+    (gnus-group-position-point)
+    (if (< (length out) 2) (car out) (nreverse out))))
+
+(defun gnus-group-yank-group (&optional arg)
+  "Yank the last newsgroups killed with \\[gnus-group-kill-group],
+inserting it before the current newsgroup.  The numeric ARG specifies
+how many newsgroups are to be yanked.  The name of the newsgroup yanked
+is returned, or (if several groups are yanked) a list of yanked groups
+is returned."
+  (interactive "p")
+  (setq arg (or arg 1))
+  (let (info group prev out)
+    (while (>= (decf arg) 0)
+      (when (not (setq info (pop gnus-list-of-killed-groups)))
+	(error "No more newsgroups to yank"))
+      (push (setq group (nth 1 info)) out)
+      ;; Find which newsgroup to insert this one before - search
+      ;; backward until something suitable is found.  If there are no
+      ;; other newsgroups in this buffer, just make this newsgroup the
+      ;; first newsgroup.
+      (setq prev (gnus-group-group-name))
+      (gnus-group-change-level
+       info (gnus-info-level (cdr info)) gnus-level-killed
+       (and prev (gnus-gethash prev gnus-newsrc-hashtb))
+       t)
+      (gnus-group-insert-group-line-info group)
+      (gnus-undo-register
+	`(when (gnus-group-goto-group ,group)
+	   (gnus-group-kill-group 1))))
+    (forward-line -1)
+    (gnus-group-position-point)
+    (if (< (length out) 2) (car out) (nreverse out))))
+
+(defun gnus-group-kill-level (level)
+  "Kill all groups that is on a certain LEVEL."
+  (interactive "nKill all groups on level: ")
+  (cond
+   ((= level gnus-level-zombie)
+    (setq gnus-killed-list
+	  (nconc gnus-zombie-list gnus-killed-list))
+    (setq gnus-zombie-list nil))
+   ((and (< level gnus-level-zombie)
+	 (> level 0)
+	 (or gnus-expert-user
+	     (gnus-yes-or-no-p
+	      (format
+	       "Do you really want to kill all groups on level %d? "
+	       level))))
+    (let* ((prev gnus-newsrc-alist)
+	   (alist (cdr prev)))
+      (while alist
+	(if (= (gnus-info-level (car alist)) level)
+	    (progn
+	      (push (gnus-info-group (car alist)) gnus-killed-list)
+	      (setcdr prev (cdr alist)))
+	  (setq prev alist))
+	(setq alist (cdr alist)))
+      (gnus-make-hashtable-from-newsrc-alist)
+      (gnus-group-list-groups)))
+   (t
+    (error "Can't kill; illegal level: %d" level))))
+
+(defun gnus-group-list-all-groups (&optional arg)
+  "List all newsgroups with level ARG or lower.
+Default is gnus-level-unsubscribed, which lists all subscribed and most
+unsubscribed groups."
+  (interactive "P")
+  (gnus-group-list-groups (or arg gnus-level-unsubscribed) t))
+
+;; Redefine this to list ALL killed groups if prefix arg used.
+;; Rewritten by engstrom@src.honeywell.com (Eric Engstrom).
+(defun gnus-group-list-killed (&optional arg)
+  "List all killed newsgroups in the group buffer.
+If ARG is non-nil, list ALL killed groups known to Gnus.  This may
+entail asking the server for the groups."
+  (interactive "P")
+  ;; Find all possible killed newsgroups if arg.
+  (when arg
+    (gnus-get-killed-groups))
+  (if (not gnus-killed-list)
+      (gnus-message 6 "No killed groups")
+    (let (gnus-group-list-mode)
+      (funcall gnus-group-prepare-function
+	       gnus-level-killed t gnus-level-killed))
+    (goto-char (point-min)))
+  (gnus-group-position-point))
+
+(defun gnus-group-list-zombies ()
+  "List all zombie newsgroups in the group buffer."
+  (interactive)
+  (if (not gnus-zombie-list)
+      (gnus-message 6 "No zombie groups")
+    (let (gnus-group-list-mode)
+      (funcall gnus-group-prepare-function
+	       gnus-level-zombie t gnus-level-zombie))
+    (goto-char (point-min)))
+  (gnus-group-position-point))
+
+(defun gnus-group-list-active ()
+  "List all groups that are available from the server(s)."
+  (interactive)
+  ;; First we make sure that we have really read the active file.
+  (unless (gnus-read-active-file-p)
+    (let ((gnus-read-active-file t))
+      (gnus-read-active-file)))
+  ;; Find all groups and sort them.
+  (let ((groups
+	 (sort
+	  (let (list)
+	    (mapatoms
+	     (lambda (sym)
+	       (and (boundp sym)
+		    (symbol-value sym)
+		    (push (symbol-name sym) list)))
+	     gnus-active-hashtb)
+	    list)
+	  'string<))
+	(buffer-read-only nil)
+	group)
+    (erase-buffer)
+    (while groups
+      (gnus-add-text-properties
+       (point) (prog1 (1+ (point))
+		 (insert "       *: "
+			 (setq group (pop groups)) "\n"))
+       (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb)
+	     'gnus-unread t
+	     'gnus-level (inline (gnus-group-level group)))))
+    (goto-char (point-min))))
+
+(defun gnus-activate-all-groups (level)
+  "Activate absolutely all groups."
+  (interactive (list 7))
+  (let ((gnus-activate-level level)
+	(gnus-activate-foreign-newsgroups level))
+    (gnus-group-get-new-news)))
+
+(defun gnus-group-get-new-news (&optional arg)
+  "Get newly arrived articles.
+If ARG is a number, it specifies which levels you are interested in
+re-scanning.  If ARG is non-nil and not a number, this will force
+\"hard\" re-reading of the active files from all servers."
+  (interactive "P")
+  (let ((gnus-inhibit-demon t))
+    (run-hooks 'gnus-get-new-news-hook)
+
+    ;; Read any slave files.
+    (unless gnus-slave
+      (gnus-master-read-slave-newsrc))
+
+    ;; We might read in new NoCeM messages here.
+    (when (and gnus-use-nocem
+	       (null arg))
+      (gnus-nocem-scan-groups))
+    ;; If ARG is not a number, then we read the active file.
+    (when (and arg (not (numberp arg)))
+      (let ((gnus-read-active-file t))
+	(gnus-read-active-file))
+      (setq arg nil)
+
+      ;; If the user wants it, we scan for new groups.
+      (when (eq gnus-check-new-newsgroups 'always)
+	(gnus-find-new-newsgroups)))
+
+    (setq arg (gnus-group-default-level arg t))
+    (if (and gnus-read-active-file (not arg))
+	(progn
+	  (gnus-read-active-file)
+	  (gnus-get-unread-articles arg))
+      (let ((gnus-read-active-file (if arg nil gnus-read-active-file)))
+	(gnus-get-unread-articles arg)))
+    (run-hooks 'gnus-after-getting-new-news-hook)
+    (gnus-group-list-groups (and (numberp arg)
+				 (max (car gnus-group-list-mode) arg)))))
+
+(defun gnus-group-get-new-news-this-group (&optional n)
+  "Check for newly arrived news in the current group (and the N-1 next groups).
+The difference between N and the number of newsgroup checked is returned.
+If N is negative, this group and the N-1 previous groups will be checked."
+  (interactive "P")
+  (let* ((groups (gnus-group-process-prefix n))
+	 (ret (if (numberp n) (- n (length groups)) 0))
+	 (beg (unless n
+		(point)))
+	 group)
+    (while (setq group (pop groups))
+      (gnus-group-remove-mark group)
+      ;; Bypass any previous denials from the server.
+      (gnus-remove-denial (gnus-find-method-for-group group))
+      (if (gnus-activate-group group 'scan)
+	  (progn
+	    (gnus-get-unread-articles-in-group
+	     (gnus-get-info group) (gnus-active group) t)
+	    (unless (gnus-virtual-group-p group)
+	      (gnus-close-group group))
+	    (gnus-group-update-group group))
+	(if (eq (gnus-server-status (gnus-find-method-for-group group))
+		'denied)
+	    (gnus-error 3 "Server denied access")
+	  (gnus-error 3 "%s error: %s" group (gnus-status-message group)))))
+    (when beg
+      (goto-char beg))
+    (when gnus-goto-next-group-when-activating
+      (gnus-group-next-unread-group 1 t))
+    (gnus-summary-position-point)
+    ret))
+
+(defun gnus-group-fetch-faq (group &optional faq-dir)
+  "Fetch the FAQ for the current group.
+If given a prefix argument, prompt for the FAQ dir
+to use."
+  (interactive
+   (list
+    (gnus-group-group-name)
+    (cond (current-prefix-arg
+	   (completing-read
+	    "Faq dir: " (and (listp gnus-group-faq-directory)
+			     (mapcar (lambda (file) (list file))
+				     gnus-group-faq-directory)))))))
+  (unless group
+    (error "No group name given"))
+  (let ((dirs (or faq-dir gnus-group-faq-directory))
+	dir found file)
+    (unless (listp dirs)
+      (setq dirs (list dirs)))
+    (while (and (not found)
+		(setq dir (pop dirs)))
+      (setq file (concat (file-name-as-directory dir)
+			 (gnus-group-real-name group)))
+      (if (not (file-exists-p file))
+	  (gnus-message 1 "No such file: %s" file)
+	(let ((enable-local-variables nil))
+	  (find-file file)
+	  (setq found t))))))
+
+(defun gnus-group-describe-group (force &optional group)
+  "Display a description of the current newsgroup."
+  (interactive (list current-prefix-arg (gnus-group-group-name)))
+  (let* ((method (gnus-find-method-for-group group))
+	 (mname (gnus-group-prefixed-name "" method))
+	 desc)
+    (when (and force
+	       gnus-description-hashtb)
+      (gnus-sethash mname nil gnus-description-hashtb))
+    (unless group
+      (error "No group name given"))
+    (when (or (and gnus-description-hashtb
+		   ;; We check whether this group's method has been
+		   ;; queried for a description file.
+		   (gnus-gethash mname gnus-description-hashtb))
+	      (setq desc (gnus-group-get-description group))
+	      (gnus-read-descriptions-file method))
+      (gnus-message 1
+		    (or desc (gnus-gethash group gnus-description-hashtb)
+			"No description available")))))
+
+;; Suggested by Per Abrahamsen <amanda@iesd.auc.dk>.
+(defun gnus-group-describe-all-groups (&optional force)
+  "Pop up a buffer with descriptions of all newsgroups."
+  (interactive "P")
+  (when force
+    (setq gnus-description-hashtb nil))
+  (when (not (or gnus-description-hashtb
+		 (gnus-read-all-descriptions-files)))
+    (error "Couldn't request descriptions file"))
+  (let ((buffer-read-only nil)
+	b)
+    (erase-buffer)
+    (mapatoms
+     (lambda (group)
+       (setq b (point))
+       (insert (format "      *: %-20s %s\n" (symbol-name group)
+		       (symbol-value group)))
+       (gnus-add-text-properties
+	b (1+ b) (list 'gnus-group group
+		       'gnus-unread t 'gnus-marked nil
+		       'gnus-level (1+ gnus-level-subscribed))))
+     gnus-description-hashtb)
+    (goto-char (point-min))
+    (gnus-group-position-point)))
+
+;; Suggested by Daniel Quinlan <quinlan@best.com>.
+(defun gnus-group-apropos (regexp &optional search-description)
+  "List all newsgroups that have names that match a regexp."
+  (interactive "sGnus apropos (regexp): ")
+  (let ((prev "")
+	(obuf (current-buffer))
+	groups des)
+    ;; Go through all newsgroups that are known to Gnus.
+    (mapatoms
+     (lambda (group)
+       (and (symbol-name group)
+	    (string-match regexp (symbol-name group))
+	    (push (symbol-name group) groups)))
+     gnus-active-hashtb)
+    ;; Also go through all descriptions that are known to Gnus.
+    (when search-description
+      (mapatoms
+       (lambda (group)
+	 (and (string-match regexp (symbol-value group))
+	      (gnus-active (symbol-name group))
+	      (push (symbol-name group) groups)))
+       gnus-description-hashtb))
+    (if (not groups)
+	(gnus-message 3 "No groups matched \"%s\"." regexp)
+      ;; Print out all the groups.
+      (save-excursion
+	(pop-to-buffer "*Gnus Help*")
+	(buffer-disable-undo (current-buffer))
+	(erase-buffer)
+	(setq groups (sort groups 'string<))
+	(while groups
+	  ;; Groups may be entered twice into the list of groups.
+	  (when (not (string= (car groups) prev))
+	    (insert (setq prev (car groups)) "\n")
+	    (when (and gnus-description-hashtb
+		       (setq des (gnus-gethash (car groups)
+					       gnus-description-hashtb)))
+	      (insert "  " des "\n")))
+	  (setq groups (cdr groups)))
+	(goto-char (point-min))))
+    (pop-to-buffer obuf)))
+
+(defun gnus-group-description-apropos (regexp)
+  "List all newsgroups that have names or descriptions that match a regexp."
+  (interactive "sGnus description apropos (regexp): ")
+  (when (not (or gnus-description-hashtb
+		 (gnus-read-all-descriptions-files)))
+    (error "Couldn't request descriptions file"))
+  (gnus-group-apropos regexp t))
+
+;; Suggested by Per Abrahamsen <amanda@iesd.auc.dk>.
+(defun gnus-group-list-matching (level regexp &optional all lowest)
+  "List all groups with unread articles that match REGEXP.
+If the prefix LEVEL is non-nil, it should be a number that says which
+level to cut off listing groups.
+If ALL, also list groups with no unread articles.
+If LOWEST, don't list groups with level lower than LOWEST.
+
+This command may read the active file."
+  (interactive "P\nsList newsgroups matching: ")
+  ;; First make sure active file has been read.
+  (when (and level
+	     (> (prefix-numeric-value level) gnus-level-killed))
+    (gnus-get-killed-groups))
+  (gnus-group-prepare-flat
+   (or level gnus-level-subscribed) all (or lowest 1) regexp)
+  (goto-char (point-min))
+  (gnus-group-position-point))
+
+(defun gnus-group-list-all-matching (level regexp &optional lowest)
+  "List all groups that match REGEXP.
+If the prefix LEVEL is non-nil, it should be a number that says which
+level to cut off listing groups.
+If LOWEST, don't list groups with level lower than LOWEST."
+  (interactive "P\nsList newsgroups matching: ")
+  (when level
+    (setq level (prefix-numeric-value level)))
+  (gnus-group-list-matching (or level gnus-level-killed) regexp t lowest))
+
+;; Suggested by Jack Vinson <vinson@unagi.cis.upenn.edu>.
+(defun gnus-group-save-newsrc (&optional force)
+  "Save the Gnus startup files.
+If FORCE, force saving whether it is necessary or not."
+  (interactive "P")
+  (gnus-save-newsrc-file force))
+
+(defun gnus-group-restart (&optional arg)
+  "Force Gnus to read the .newsrc file."
+  (interactive "P")
+  (when (gnus-yes-or-no-p
+	 (format "Are you sure you want to restart Gnus? "))
+    (gnus-save-newsrc-file)
+    (gnus-clear-system)
+    (gnus)))
+
+(defun gnus-group-read-init-file ()
+  "Read the Gnus elisp init file."
+  (interactive)
+  (gnus-read-init-file))
+
+(defun gnus-group-check-bogus-groups (&optional silent)
+  "Check bogus newsgroups.
+If given a prefix, don't ask for confirmation before removing a bogus
+group."
+  (interactive "P")
+  (gnus-check-bogus-newsgroups (and (not silent) (not gnus-expert-user)))
+  (gnus-group-list-groups))
+
+(defun gnus-group-edit-global-kill (&optional article group)
+  "Edit the global kill file.
+If GROUP, edit that local kill file instead."
+  (interactive "P")
+  (setq gnus-current-kill-article article)
+  (gnus-kill-file-edit-file group)
+  (gnus-message
+   6
+   (substitute-command-keys
+    (format "Editing a %s kill file (Type \\[gnus-kill-file-exit] to exit)"
+	    (if group "local" "global")))))
+
+(defun gnus-group-edit-local-kill (article group)
+  "Edit a local kill file."
+  (interactive (list nil (gnus-group-group-name)))
+  (gnus-group-edit-global-kill article group))
+
+(defun gnus-group-force-update ()
+  "Update `.newsrc' file."
+  (interactive)
+  (gnus-save-newsrc-file))
+
+(defun gnus-group-suspend ()
+  "Suspend the current Gnus session.
+In fact, cleanup buffers except for group mode buffer.
+The hook gnus-suspend-gnus-hook is called before actually suspending."
+  (interactive)
+  (run-hooks 'gnus-suspend-gnus-hook)
+  ;; Kill Gnus buffers except for group mode buffer.
+  (let* ((group-buf (get-buffer gnus-group-buffer))
+	 ;; Do this on a separate list in case the user does a ^G before we finish
+	 (gnus-buffer-list
+	  (delete group-buf (delete gnus-dribble-buffer
+				    (append gnus-buffer-list nil)))))
+    (while gnus-buffer-list
+      (gnus-kill-buffer (pop gnus-buffer-list)))
+    (gnus-kill-gnus-frames)
+    (when group-buf
+      (setq gnus-buffer-list (list group-buf))
+      (bury-buffer group-buf)
+      (delete-windows-on group-buf t))))
+
+(defun gnus-group-clear-dribble ()
+  "Clear all information from the dribble buffer."
+  (interactive)
+  (gnus-dribble-clear)
+  (gnus-message 7 "Cleared dribble buffer"))
+
+(defun gnus-group-exit ()
+  "Quit reading news after updating .newsrc.eld and .newsrc.
+The hook `gnus-exit-gnus-hook' is called before actually exiting."
+  (interactive)
+  (when
+      (or noninteractive		;For gnus-batch-kill
+	  (not gnus-interactive-exit)	;Without confirmation
+	  gnus-expert-user
+	  (gnus-y-or-n-p "Are you sure you want to quit reading news? "))
+    (run-hooks 'gnus-exit-gnus-hook)
+    ;; Offer to save data from non-quitted summary buffers.
+    (gnus-offer-save-summaries)
+    ;; Save the newsrc file(s).
+    (gnus-save-newsrc-file)
+    ;; Kill-em-all.
+    (gnus-close-backends)
+    ;; Reset everything.
+    (gnus-clear-system)
+    ;; Allow the user to do things after cleaning up.
+    (run-hooks 'gnus-after-exiting-gnus-hook)))
+
+(defun gnus-group-quit ()
+  "Quit reading news without updating .newsrc.eld or .newsrc.
+The hook `gnus-exit-gnus-hook' is called before actually exiting."
+  (interactive)
+  (when (or noninteractive		;For gnus-batch-kill
+	    (zerop (buffer-size))
+	    (not (gnus-server-opened gnus-select-method))
+	    gnus-expert-user
+	    (not gnus-current-startup-file)
+	    (gnus-yes-or-no-p
+	     (format "Quit reading news without saving %s? "
+		     (file-name-nondirectory gnus-current-startup-file))))
+    (run-hooks 'gnus-exit-gnus-hook)
+    (gnus-configure-windows 'group t)
+    (gnus-dribble-save)
+    (gnus-close-backends)
+    (gnus-clear-system)
+    (gnus-kill-buffer gnus-group-buffer)
+    ;; Allow the user to do things after cleaning up.
+    (run-hooks 'gnus-after-exiting-gnus-hook)))
+
+(defun gnus-group-describe-briefly ()
+  "Give a one line description of the group mode commands."
+  (interactive)
+  (gnus-message 7 (substitute-command-keys "\\<gnus-group-mode-map>\\[gnus-group-read-group]:Select  \\[gnus-group-next-unread-group]:Forward  \\[gnus-group-prev-unread-group]:Backward  \\[gnus-group-exit]:Exit  \\[gnus-info-find-node]:Run Info  \\[gnus-group-describe-briefly]:This help")))
+
+(defun gnus-group-browse-foreign-server (method)
+  "Browse a foreign news server.
+If called interactively, this function will ask for a select method
+ (nntp, nnspool, etc.) and a server address (eg. nntp.some.where).
+If not, METHOD should be a list where the first element is the method
+and the second element is the address."
+  (interactive
+   (list (let ((how (completing-read
+		     "Which backend: "
+		     (append gnus-valid-select-methods gnus-server-alist)
+		     nil t (cons "nntp" 0) 'gnus-method-history)))
+	   ;; We either got a backend name or a virtual server name.
+	   ;; If the first, we also need an address.
+	   (if (assoc how gnus-valid-select-methods)
+	       (list (intern how)
+		     ;; Suggested by mapjph@bath.ac.uk.
+		     (completing-read
+		      "Address: "
+		      (mapcar (lambda (server) (list server))
+			      gnus-secondary-servers)))
+	     ;; We got a server name.
+	     how))))
+  (gnus-browse-foreign-server method))
+
+(defun gnus-group-set-info (info &optional method-only-group part)
+  (let* ((entry (gnus-gethash
+		 (or method-only-group (gnus-info-group info))
+		 gnus-newsrc-hashtb))
+	 (part-info info)
+	 (info (if method-only-group (nth 2 entry) info))
+	 method)
+    (when method-only-group
+      (unless entry
+	(error "Trying to change non-existent group %s" method-only-group))
+      ;; We have received parts of the actual group info - either the
+      ;; select method or the group parameters.	 We first check
+      ;; whether we have to extend the info, and if so, do that.
+      (let ((len (length info))
+	    (total (if (eq part 'method) 5 6)))
+	(when (< len total)
+	  (setcdr (nthcdr (1- len) info)
+		  (make-list (- total len) nil)))
+	;; Then we enter the new info.
+	(setcar (nthcdr (1- total) info) part-info)))
+    (unless entry
+      ;; This is a new group, so we just create it.
+      (save-excursion
+	(set-buffer gnus-group-buffer)
+	(setq method (gnus-info-method info))
+	(when (gnus-server-equal method "native")
+	  (setq method nil))
+	(save-excursion
+	  (set-buffer gnus-group-buffer)
+	  (if method
+	      ;; It's a foreign group...
+	      (gnus-group-make-group
+	       (gnus-group-real-name (gnus-info-group info))
+	       (if (stringp method) method
+		 (prin1-to-string (car method)))
+	       (and (consp method)
+		    (nth 1 (gnus-info-method info))))
+	    ;; It's a native group.
+	    (gnus-group-make-group (gnus-info-group info))))
+	(gnus-message 6 "Note: New group created")
+	(setq entry
+	      (gnus-gethash (gnus-group-prefixed-name
+			     (gnus-group-real-name (gnus-info-group info))
+			     (or (gnus-info-method info) gnus-select-method))
+			    gnus-newsrc-hashtb))))
+    ;; Whether it was a new group or not, we now have the entry, so we
+    ;; can do the update.
+    (if entry
+	(progn
+	  (setcar (nthcdr 2 entry) info)
+	  (when (and (not (eq (car entry) t))
+		     (gnus-active (gnus-info-group info)))
+	    (setcar entry (length (gnus-list-of-unread-articles (car info))))))
+      (error "No such group: %s" (gnus-info-group info)))))
+
+(defun gnus-group-set-method-info (group select-method)
+  (gnus-group-set-info select-method group 'method))
+
+(defun gnus-group-set-params-info (group params)
+  (gnus-group-set-info params group 'params))
+
+(defun gnus-add-marked-articles (group type articles &optional info force)
+  ;; Add ARTICLES of TYPE to the info of GROUP.
+  ;; If INFO is non-nil, use that info.	 If FORCE is non-nil, don't
+  ;; add, but replace marked articles of TYPE with ARTICLES.
+  (let ((info (or info (gnus-get-info group)))
+	(uncompressed '(score bookmark killed))
+	marked m)
+    (or (not info)
+	(and (not (setq marked (nthcdr 3 info)))
+	     (or (null articles)
+		 (setcdr (nthcdr 2 info)
+			 (list (list (cons type (gnus-compress-sequence
+						 articles t)))))))
+	(and (not (setq m (assq type (car marked))))
+	     (or (null articles)
+		 (setcar marked
+			 (cons (cons type (gnus-compress-sequence articles t) )
+			       (car marked)))))
+	(if force
+	    (if (null articles)
+		(setcar (nthcdr 3 info)
+			(delq (assq type (car marked)) (car marked)))
+	      (setcdr m (gnus-compress-sequence articles t)))
+	  (setcdr m (gnus-compress-sequence
+		     (sort (nconc (gnus-uncompress-range (cdr m))
+				  (copy-sequence articles)) '<) t))))))
+
+;;;
+;;; Group timestamps
+;;;
+
+(defun gnus-group-set-timestamp ()
+  "Change the timestamp of the current group to the current time.
+This function can be used in hooks like `gnus-select-group-hook'
+or `gnus-group-catchup-group-hook'."
+  (when gnus-newsgroup-name
+    (let ((time (current-time)))
+      (setcdr (cdr time) nil)
+      (gnus-group-set-parameter gnus-newsgroup-name 'timestamp time))))
+
+(defsubst gnus-group-timestamp (group)
+  "Return the timestamp for GROUP."
+  (gnus-group-get-parameter group 'timestamp))
+
+(defun gnus-group-timestamp-delta (group)
+  "Return the offset in seconds from the timestamp for GROUP to the current time, as a floating point number."
+  (let* ((time (or (gnus-group-timestamp group)
+		  (list 0 0)))
+         (delta (gnus-time-minus (current-time) time)))
+    (+ (* (nth 0 delta) 65536.0)
+       (nth 1 delta))))
+
+(defun gnus-group-timestamp-string (group)
+  "Return a string of the timestamp for GROUP."
+  (let ((time (gnus-group-timestamp group)))
+    (if (not time)
+	""
+      (gnus-time-iso8601 time))))
+
+(provide 'gnus-group)
+
+;;; gnus-group.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-int.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,438 @@
+;;; gnus-int.el --- backend interface functions for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+
+(defcustom gnus-open-server-hook nil
+  "Hook called just before opening connection to the news server."
+  :group 'gnus-start
+  :type 'hook)
+
+;;;
+;;; Server Communication
+;;;
+
+(defun gnus-start-news-server (&optional confirm)
+  "Open a method for getting news.
+If CONFIRM is non-nil, the user will be asked for an NNTP server."
+  (let (how)
+    (if gnus-current-select-method
+	;; Stream is already opened.
+	nil
+      ;; Open NNTP server.
+      (unless gnus-nntp-service
+	(setq gnus-nntp-server nil))
+      (when confirm
+	;; Read server name with completion.
+	(setq gnus-nntp-server
+	      (completing-read "NNTP server: "
+			       (mapcar (lambda (server) (list server))
+				       (cons (list gnus-nntp-server)
+					     gnus-secondary-servers))
+			       nil nil gnus-nntp-server)))
+
+      (when (and gnus-nntp-server
+		 (stringp gnus-nntp-server)
+		 (not (string= gnus-nntp-server "")))
+	(setq gnus-select-method
+	      (cond ((or (string= gnus-nntp-server "")
+			 (string= gnus-nntp-server "::"))
+		     (list 'nnspool (system-name)))
+		    ((string-match "^:" gnus-nntp-server)
+		     (list 'nnmh gnus-nntp-server
+			   (list 'nnmh-directory
+				 (file-name-as-directory
+				  (expand-file-name
+				   (concat "~/" (substring
+						 gnus-nntp-server 1)))))
+			   (list 'nnmh-get-new-mail nil)))
+		    (t
+		     (list 'nntp gnus-nntp-server)))))
+
+      (setq how (car gnus-select-method))
+      (cond
+       ((eq how 'nnspool)
+	(require 'nnspool)
+	(gnus-message 5 "Looking up local news spool..."))
+       ((eq how 'nnmh)
+	(require 'nnmh)
+	(gnus-message 5 "Looking up mh spool..."))
+       (t
+	(require 'nntp)))
+      (setq gnus-current-select-method gnus-select-method)
+      (run-hooks 'gnus-open-server-hook)
+      (or
+       ;; gnus-open-server-hook might have opened it
+       (gnus-server-opened gnus-select-method)
+       (gnus-open-server gnus-select-method)
+       (gnus-y-or-n-p
+	(format
+	 "%s (%s) open error: '%s'.  Continue? "
+	 (car gnus-select-method) (cadr gnus-select-method)
+	 (gnus-status-message gnus-select-method)))
+       (gnus-error 1 "Couldn't open server on %s"
+		   (nth 1 gnus-select-method))))))
+
+(defun gnus-check-group (group)
+  "Try to make sure that the server where GROUP exists is alive."
+  (let ((method (gnus-find-method-for-group group)))
+    (or (gnus-server-opened method)
+	(gnus-open-server method))))
+
+(defun gnus-check-server (&optional method silent)
+  "Check whether the connection to METHOD is down.
+If METHOD is nil, use `gnus-select-method'.
+If it is down, start it up (again)."
+  (let ((method (or method gnus-select-method)))
+    ;; Transform virtual server names into select methods.
+    (when (stringp method)
+      (setq method (gnus-server-to-method method)))
+    (if (gnus-server-opened method)
+	;; The stream is already opened.
+	t
+      ;; Open the server.
+      (unless silent
+	(gnus-message 5 "Opening %s server%s..." (car method)
+		      (if (equal (nth 1 method) "") ""
+			(format " on %s" (nth 1 method)))))
+      (run-hooks 'gnus-open-server-hook)
+      (prog1
+	  (gnus-open-server method)
+	(unless silent
+	  (message ""))))))
+
+(defun gnus-get-function (method function &optional noerror)
+  "Return a function symbol based on METHOD and FUNCTION."
+  ;; Translate server names into methods.
+  (unless method
+    (error "Attempted use of a nil select method"))
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (let ((func (intern (format "%s-%s" (car method) function))))
+    ;; If the functions isn't bound, we require the backend in
+    ;; question.
+    (unless (fboundp func)
+      (require (car method))
+      (when (and (not (fboundp func))
+		 (not noerror))
+	;; This backend doesn't implement this function.
+	(error "No such function: %s" func)))
+    func))
+
+
+;;;
+;;; Interface functions to the backends.
+;;;
+
+(defun gnus-open-server (method)
+  "Open a connection to METHOD."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (let ((elem (assoc method gnus-opened-servers)))
+    ;; If this method was previously denied, we just return nil.
+    (if (eq (nth 1 elem) 'denied)
+	(progn
+	  (gnus-message 1 "Denied server")
+	  nil)
+      ;; Open the server.
+      (let ((result
+	     (funcall (gnus-get-function method 'open-server)
+		      (nth 1 method) (nthcdr 2 method))))
+	;; If this hasn't been opened before, we add it to the list.
+	(unless elem
+	  (setq elem (list method nil)
+		gnus-opened-servers (cons elem gnus-opened-servers)))
+	;; Set the status of this server.
+	(setcar (cdr elem) (if result 'ok 'denied))
+	;; Return the result from the "open" call.
+	result))))
+
+(defun gnus-close-server (method)
+  "Close the connection to METHOD."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (funcall (gnus-get-function method 'close-server) (nth 1 method)))
+
+(defun gnus-request-list (method)
+  "Request the active file from METHOD."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (funcall (gnus-get-function method 'request-list) (nth 1 method)))
+
+(defun gnus-request-list-newsgroups (method)
+  "Request the newsgroups file from METHOD."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (funcall (gnus-get-function method 'request-list-newsgroups) (nth 1 method)))
+
+(defun gnus-request-newgroups (date method)
+  "Request all new groups since DATE from METHOD."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (let ((func (gnus-get-function method 'request-newgroups t)))
+    (when func
+      (funcall func date (nth 1 method)))))
+
+(defun gnus-server-opened (method)
+  "Check whether a connection to METHOD has been opened."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (funcall (inline (gnus-get-function method 'server-opened)) (nth 1 method)))
+
+(defun gnus-status-message (method)
+  "Return the status message from METHOD.
+If METHOD is a string, it is interpreted as a group name.   The method
+this group uses will be queried."
+  (let ((method (if (stringp method) (gnus-find-method-for-group method)
+		  method)))
+    (funcall (gnus-get-function method 'status-message) (nth 1 method))))
+
+(defun gnus-request-regenerate (method)
+  "Request a data generation from METHOD."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (funcall (gnus-get-function method 'request-regenerate) (nth 1 method)))
+
+(defun gnus-request-group (group &optional dont-check method)
+  "Request GROUP.  If DONT-CHECK, no information is required."
+  (let ((method (or method (inline (gnus-find-method-for-group group)))))
+    (when (stringp method)
+      (setq method (inline (gnus-server-to-method method))))
+    (funcall (inline (gnus-get-function method 'request-group))
+	     (gnus-group-real-name group) (nth 1 method) dont-check)))
+
+(defun gnus-list-active-group (group)
+  "Request active information on GROUP."
+  (let ((method (gnus-find-method-for-group group))
+	(func 'list-active-group))
+    (when (gnus-check-backend-function func group)
+      (funcall (gnus-get-function method func)
+	       (gnus-group-real-name group) (nth 1 method)))))
+
+(defun gnus-request-group-description (group)
+  "Request a description of GROUP."
+  (let ((method (gnus-find-method-for-group group))
+	(func 'request-group-description))
+    (when (gnus-check-backend-function func group)
+      (funcall (gnus-get-function method func)
+	       (gnus-group-real-name group) (nth 1 method)))))
+
+(defun gnus-close-group (group)
+  "Request the GROUP be closed."
+  (let ((method (inline (gnus-find-method-for-group group))))
+    (funcall (gnus-get-function method 'close-group)
+	     (gnus-group-real-name group) (nth 1 method))))
+
+(defun gnus-retrieve-headers (articles group &optional fetch-old)
+  "Request headers for ARTICLES in GROUP.
+If FETCH-OLD, retrieve all headers (or some subset thereof) in the group."
+  (let ((method (gnus-find-method-for-group group)))
+    (if (and gnus-use-cache (numberp (car articles)))
+	(gnus-cache-retrieve-headers articles group fetch-old)
+      (funcall (gnus-get-function method 'retrieve-headers)
+	       articles (gnus-group-real-name group) (nth 1 method)
+	       fetch-old))))
+
+(defun gnus-retrieve-groups (groups method)
+  "Request active information on GROUPS from METHOD."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (funcall (gnus-get-function method 'retrieve-groups) groups (nth 1 method)))
+
+(defun gnus-request-type (group &optional article)
+  "Return the type (`post' or `mail') of GROUP (and ARTICLE)."
+  (let ((method (gnus-find-method-for-group group)))
+    (if (not (gnus-check-backend-function 'request-type (car method)))
+	'unknown
+      (funcall (gnus-get-function method 'request-type)
+	       (gnus-group-real-name group) article))))
+
+(defun gnus-request-update-mark (group article mark)
+  "Return the type (`post' or `mail') of GROUP (and ARTICLE)."
+  (let ((method (gnus-find-method-for-group group)))
+    (if (not (gnus-check-backend-function 'request-update-mark (car method)))
+	mark
+      (funcall (gnus-get-function method 'request-update-mark)
+	       (gnus-group-real-name group) article mark))))
+
+(defun gnus-request-article (article group &optional buffer)
+  "Request the ARTICLE in GROUP.
+ARTICLE can either be an article number or an article Message-ID.
+If BUFFER, insert the article in that group."
+  (let ((method (gnus-find-method-for-group group)))
+    (funcall (gnus-get-function method 'request-article)
+	     article (gnus-group-real-name group) (nth 1 method) buffer)))
+
+(defun gnus-request-head (article group)
+  "Request the head of ARTICLE in GROUP."
+  (let* ((method (gnus-find-method-for-group group))
+	 (head (gnus-get-function method 'request-head t))
+	 res clean-up)
+    (cond
+     ;; Check the cache.
+     ((and gnus-use-cache
+	   (numberp article)
+	   (gnus-cache-request-article article group))
+      (setq res (cons group article)
+	    clean-up t))
+     ;; Use `head' function.
+     ((fboundp head)
+      (setq res (funcall head article (gnus-group-real-name group)
+			 (nth 1 method))))
+     ;; Use `article' function.
+     (t
+      (setq res (gnus-request-article article group)
+	    clean-up t)))
+    (when clean-up
+      (save-excursion
+	(set-buffer nntp-server-buffer)
+	(goto-char (point-min))
+	(when (search-forward "\n\n" nil t)
+	  (delete-region (1- (point)) (point-max)))
+	(nnheader-fold-continuation-lines)))
+    res))
+
+(defun gnus-request-body (article group)
+  "Request the body of ARTICLE in GROUP."
+  (let ((method (gnus-find-method-for-group group)))
+    (funcall (gnus-get-function method 'request-body)
+	     article (gnus-group-real-name group) (nth 1 method))))
+
+(defun gnus-request-post (method)
+  "Post the current buffer using METHOD."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (funcall (gnus-get-function method 'request-post) (nth 1 method)))
+
+(defun gnus-request-scan (group method)
+  "Request a SCAN being performed in GROUP from METHOD.
+If GROUP is nil, all groups on METHOD are scanned."
+  (let ((method (if group (gnus-find-method-for-group group) method))
+	(gnus-inhibit-demon t))
+    (funcall (gnus-get-function method 'request-scan)
+	     (and group (gnus-group-real-name group)) (nth 1 method))))
+
+(defsubst gnus-request-update-info (info method)
+  "Request that METHOD update INFO."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (when (gnus-check-backend-function 'request-update-info (car method))
+    (funcall (gnus-get-function method 'request-update-info)
+	     (gnus-group-real-name (gnus-info-group info))
+	     info (nth 1 method))))
+
+(defun gnus-request-expire-articles (articles group &optional force)
+  (let ((method (gnus-find-method-for-group group)))
+    (funcall (gnus-get-function method 'request-expire-articles)
+	     articles (gnus-group-real-name group) (nth 1 method)
+	     force)))
+
+(defun gnus-request-move-article
+  (article group server accept-function &optional last)
+  (let ((method (gnus-find-method-for-group group)))
+    (funcall (gnus-get-function method 'request-move-article)
+	     article (gnus-group-real-name group)
+	     (nth 1 method) accept-function last)))
+
+(defun gnus-request-accept-article (group method &optional last)
+  ;; Make sure there's a newline at the end of the article.
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (when (and (not method)
+	     (stringp group))
+    (setq method (gnus-group-name-to-method group)))
+  (goto-char (point-max))
+  (unless (bolp)
+    (insert "\n"))
+  (let ((func (car (or method (gnus-find-method-for-group group)))))
+    (funcall (intern (format "%s-request-accept-article" func))
+	     (if (stringp group) (gnus-group-real-name group) group)
+	     (cadr method)
+	     last)))
+
+(defun gnus-request-replace-article (article group buffer)
+  (let ((func (car (gnus-find-method-for-group group))))
+    (funcall (intern (format "%s-request-replace-article" func))
+	     article (gnus-group-real-name group) buffer)))
+
+(defun gnus-request-associate-buffer (group)
+  (let ((method (gnus-find-method-for-group group)))
+    (funcall (gnus-get-function method 'request-associate-buffer)
+	     (gnus-group-real-name group))))
+
+(defun gnus-request-restore-buffer (article group)
+  "Request a new buffer restored to the state of ARTICLE."
+  (let ((method (gnus-find-method-for-group group)))
+    (funcall (gnus-get-function method 'request-restore-buffer)
+	     article (gnus-group-real-name group) (nth 1 method))))
+
+(defun gnus-request-create-group (group &optional method args)
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (let ((method (or method (gnus-find-method-for-group group))))
+    (funcall (gnus-get-function method 'request-create-group)
+	     (gnus-group-real-name group) (nth 1 method) args)))
+
+(defun gnus-request-delete-group (group &optional force)
+  (let ((method (gnus-find-method-for-group group)))
+    (funcall (gnus-get-function method 'request-delete-group)
+	     (gnus-group-real-name group) force (nth 1 method))))
+
+(defun gnus-request-rename-group (group new-name)
+  (let ((method (gnus-find-method-for-group group)))
+    (funcall (gnus-get-function method 'request-rename-group)
+	     (gnus-group-real-name group)
+	     (gnus-group-real-name new-name) (nth 1 method))))
+
+(defun gnus-close-backends ()
+  ;; Send a close request to all backends that support such a request.
+  (let ((methods gnus-valid-select-methods)
+	(gnus-inhibit-demon t)
+	func method)
+    (while (setq method (pop methods))
+      (when (fboundp (setq func (intern
+				 (concat (car method) "-request-close"))))
+	(funcall func)))))
+
+(defun gnus-asynchronous-p (method)
+  (let ((func (gnus-get-function method 'asynchronous-p t)))
+    (when (fboundp func)
+      (funcall func))))
+
+(defun gnus-remove-denial (method)
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (let* ((elem (assoc method gnus-opened-servers))
+	 (status (cadr elem)))
+    ;; If this hasn't been opened before, we add it to the list.
+    (when (eq status 'denied)
+      ;; Set the status of this server.
+      (setcar (cdr elem) 'closed))))
+
+(provide 'gnus-int)
+
+;;; gnus-int.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-kill.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,717 @@
+;;; gnus-kill.el --- kill commands for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;;	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-art)
+(require 'gnus-range)
+
+(defcustom gnus-kill-file-mode-hook nil
+  "Hook for Gnus kill file mode."
+  :group 'gnus-score-kill
+  :type 'hook)
+
+(defcustom gnus-kill-expiry-days 7
+  "*Number of days before expiring unused kill file entries."
+  :group 'gnus-score-kill
+  :group 'gnus-score-expire
+  :type 'integer)
+
+(defcustom gnus-kill-save-kill-file nil
+  "*If non-nil, will save kill files after processing them."
+  :group 'gnus-score-kill
+  :type 'boolean)
+
+(defcustom gnus-winconf-kill-file nil
+  "What does this do, Lars?"
+  :group 'gnus-score-kill
+  :type 'sexp)
+
+(defcustom gnus-kill-killed t
+  "*If non-nil, Gnus will apply kill files to already killed articles.
+If it is nil, Gnus will never apply kill files to articles that have
+already been through the scoring process, which might very well save lots
+of time."
+  :group 'gnus-score-kill
+  :type 'boolean)
+
+
+
+(defmacro gnus-raise (field expression level)
+  `(gnus-kill ,field ,expression
+	      (function (gnus-summary-raise-score ,level)) t))
+
+(defmacro gnus-lower (field expression level)
+  `(gnus-kill ,field ,expression
+	      (function (gnus-summary-raise-score (- ,level))) t))
+
+;;;
+;;; Gnus Kill File Mode
+;;;
+
+(defvar gnus-kill-file-mode-map nil)
+
+(unless gnus-kill-file-mode-map
+  (gnus-define-keymap (setq gnus-kill-file-mode-map
+			    (copy-keymap emacs-lisp-mode-map))
+    "\C-c\C-k\C-s" gnus-kill-file-kill-by-subject
+    "\C-c\C-k\C-a" gnus-kill-file-kill-by-author
+    "\C-c\C-k\C-t" gnus-kill-file-kill-by-thread
+    "\C-c\C-k\C-x" gnus-kill-file-kill-by-xref
+    "\C-c\C-a" gnus-kill-file-apply-buffer
+    "\C-c\C-e" gnus-kill-file-apply-last-sexp
+    "\C-c\C-c" gnus-kill-file-exit))
+
+(defun gnus-kill-file-mode ()
+  "Major mode for editing kill files.
+
+If you are using this mode - you probably shouldn't.  Kill files
+perform badly and paint with a pretty broad brush.  Score files, on
+the other hand, are vastly faster (40x speedup) and give you more
+control over what to do.
+
+In addition to Emacs-Lisp Mode, the following commands are available:
+
+\\{gnus-kill-file-mode-map}
+
+  A kill file contains Lisp expressions to be applied to a selected
+newsgroup.  The purpose is to mark articles as read on the basis of
+some set of regexps.  A global kill file is applied to every newsgroup,
+and a local kill file is applied to a specified newsgroup.  Since a
+global kill file is applied to every newsgroup, for better performance
+use a local one.
+
+  A kill file can contain any kind of Emacs Lisp expressions expected
+to be evaluated in the Summary buffer.  Writing Lisp programs for this
+purpose is not so easy because the internal working of Gnus must be
+well-known.  For this reason, Gnus provides a general function which
+does this easily for non-Lisp programmers.
+
+  The `gnus-kill' function executes commands available in Summary Mode
+by their key sequences.  `gnus-kill' should be called with FIELD,
+REGEXP and optional COMMAND and ALL.  FIELD is a string representing
+the header field or an empty string.  If FIELD is an empty string, the
+entire article body is searched for.  REGEXP is a string which is
+compared with FIELD value.  COMMAND is a string representing a valid
+key sequence in Summary mode or Lisp expression.  COMMAND defaults to
+'(gnus-summary-mark-as-read nil \"X\").  Make sure that COMMAND is
+executed in the Summary buffer.  If the second optional argument ALL
+is non-nil, the COMMAND is applied to articles which are already
+marked as read or unread.  Articles which are marked are skipped over
+by default.
+
+  For example, if you want to mark articles of which subjects contain
+the string `AI' as read, a possible kill file may look like:
+
+	(gnus-kill \"Subject\" \"AI\")
+
+  If you want to mark articles with `D' instead of `X', you can use
+the following expression:
+
+	(gnus-kill \"Subject\" \"AI\" \"d\")
+
+In this example it is assumed that the command
+`gnus-summary-mark-as-read-forward' is assigned to `d' in Summary Mode.
+
+  It is possible to delete unnecessary headers which are marked with
+`X' in a kill file as follows:
+
+	(gnus-expunge \"X\")
+
+  If the Summary buffer is empty after applying kill files, Gnus will
+exit the selected newsgroup normally.  If headers which are marked
+with `D' are deleted in a kill file, it is impossible to read articles
+which are marked as read in the previous Gnus sessions.  Marks other
+than `D' should be used for articles which should really be deleted.
+
+Entry to this mode calls emacs-lisp-mode-hook and
+gnus-kill-file-mode-hook with no arguments, if that value is non-nil."
+  (interactive)
+  (kill-all-local-variables)
+  (use-local-map gnus-kill-file-mode-map)
+  (set-syntax-table emacs-lisp-mode-syntax-table)
+  (setq major-mode 'gnus-kill-file-mode)
+  (setq mode-name "Kill")
+  (lisp-mode-variables nil)
+  (run-hooks 'emacs-lisp-mode-hook 'gnus-kill-file-mode-hook))
+
+(defun gnus-kill-file-edit-file (newsgroup)
+  "Begin editing a kill file for NEWSGROUP.
+If NEWSGROUP is nil, the global kill file is selected."
+  (interactive "sNewsgroup: ")
+  (let ((file (gnus-newsgroup-kill-file newsgroup)))
+    (gnus-make-directory (file-name-directory file))
+    ;; Save current window configuration if this is first invocation.
+    (or (and (get-file-buffer file)
+	     (get-buffer-window (get-file-buffer file)))
+	(setq gnus-winconf-kill-file (current-window-configuration)))
+    ;; Hack windows.
+    (let ((buffer (find-file-noselect file)))
+      (cond ((get-buffer-window buffer)
+	     (pop-to-buffer buffer))
+	    ((eq major-mode 'gnus-group-mode)
+	     (gnus-configure-windows 'group) ;Take all windows.
+	     (pop-to-buffer buffer))
+	    ((eq major-mode 'gnus-summary-mode)
+	     (gnus-configure-windows 'article)
+	     (pop-to-buffer gnus-article-buffer)
+	     (bury-buffer gnus-article-buffer)
+	     (switch-to-buffer buffer))
+	    (t				;No good rules.
+	     (find-file-other-window file))))
+    (gnus-kill-file-mode)))
+
+;; Fix by Sudish Joseph <joseph@cis.ohio-state.edu>.
+(defun gnus-kill-set-kill-buffer ()
+  (let* ((file (gnus-newsgroup-kill-file gnus-newsgroup-name))
+	 (buffer (find-file-noselect file)))
+    (set-buffer buffer)
+    (gnus-kill-file-mode)
+    (bury-buffer buffer)))
+
+(defun gnus-kill-file-enter-kill (field regexp &optional dont-move)
+  ;; Enter kill file entry.
+  ;; FIELD: String containing the name of the header field to kill.
+  ;; REGEXP: The string to kill.
+  (save-excursion
+    (let (string)
+      (unless (eq major-mode 'gnus-kill-file-mode)
+	(gnus-kill-set-kill-buffer))
+      (unless dont-move
+	(goto-char (point-max)))
+      (insert (setq string (format "(gnus-kill %S %S)\n" field regexp)))
+      (gnus-kill-file-apply-string string))))
+
+(defun gnus-kill-file-kill-by-subject ()
+  "Kill by subject."
+  (interactive)
+  (gnus-kill-file-enter-kill
+   "Subject"
+   (if (vectorp gnus-current-headers)
+       (regexp-quote
+	(gnus-simplify-subject (mail-header-subject gnus-current-headers)))
+     "")
+   t))
+
+(defun gnus-kill-file-kill-by-author ()
+  "Kill by author."
+  (interactive)
+  (gnus-kill-file-enter-kill
+   "From"
+   (if (vectorp gnus-current-headers)
+       (regexp-quote (mail-header-from gnus-current-headers))
+     "") t))
+
+(defun gnus-kill-file-kill-by-thread ()
+  "Kill by author."
+  (interactive)
+  (gnus-kill-file-enter-kill
+   "References"
+   (if (vectorp gnus-current-headers)
+       (regexp-quote (mail-header-id gnus-current-headers))
+     "")))
+
+(defun gnus-kill-file-kill-by-xref ()
+  "Kill by Xref."
+  (interactive)
+  (let ((xref (and (vectorp gnus-current-headers)
+		   (mail-header-xref gnus-current-headers)))
+	(start 0)
+	group)
+    (if xref
+	(while (string-match " \\([^ \t]+\\):" xref start)
+	  (setq start (match-end 0))
+	  (when (not (string=
+		      (setq group
+			    (substring xref (match-beginning 1) (match-end 1)))
+		      gnus-newsgroup-name))
+	    (gnus-kill-file-enter-kill
+	     "Xref" (concat " " (regexp-quote group) ":") t)))
+      (gnus-kill-file-enter-kill "Xref" "" t))))
+
+(defun gnus-kill-file-raise-followups-to-author (level)
+  "Raise score for all followups to the current author."
+  (interactive "p")
+  (let ((name (mail-header-from gnus-current-headers))
+	string)
+    (save-excursion
+      (gnus-kill-set-kill-buffer)
+      (goto-char (point-min))
+      (setq name (read-string (concat "Add " level
+				      " to followup articles to: ")
+			      (regexp-quote name)))
+      (setq
+       string
+       (format
+	"(gnus-kill %S %S '(gnus-summary-temporarily-raise-by-thread %S))\n"
+	"From" name level))
+      (insert string)
+      (gnus-kill-file-apply-string string))
+    (gnus-message
+     6 "Added temporary score file entry for followups to %s." name)))
+
+(defun gnus-kill-file-apply-buffer ()
+  "Apply current buffer to current newsgroup."
+  (interactive)
+  (if (and gnus-current-kill-article
+	   (get-buffer gnus-summary-buffer))
+      ;; Assume newsgroup is selected.
+      (gnus-kill-file-apply-string (buffer-string))
+    (ding) (gnus-message 2 "No newsgroup is selected.")))
+
+(defun gnus-kill-file-apply-string (string)
+  "Apply STRING to current newsgroup."
+  (interactive)
+  (let ((string (concat "(progn \n" string "\n)")))
+    (save-excursion
+      (save-window-excursion
+	(pop-to-buffer gnus-summary-buffer)
+	(eval (car (read-from-string string)))))))
+
+(defun gnus-kill-file-apply-last-sexp ()
+  "Apply sexp before point in current buffer to current newsgroup."
+  (interactive)
+  (if (and gnus-current-kill-article
+	   (get-buffer gnus-summary-buffer))
+      ;; Assume newsgroup is selected.
+      (let ((string
+	     (buffer-substring
+	      (save-excursion (forward-sexp -1) (point)) (point))))
+	(save-excursion
+	  (save-window-excursion
+	    (pop-to-buffer gnus-summary-buffer)
+	    (eval (car (read-from-string string))))))
+    (ding) (gnus-message 2 "No newsgroup is selected.")))
+
+(defun gnus-kill-file-exit ()
+  "Save a kill file, then return to the previous buffer."
+  (interactive)
+  (save-buffer)
+  (let ((killbuf (current-buffer)))
+    ;; We don't want to return to article buffer.
+    (when (get-buffer gnus-article-buffer)
+      (bury-buffer gnus-article-buffer))
+    ;; Delete the KILL file windows.
+    (delete-windows-on killbuf)
+    ;; Restore last window configuration if available.
+    (when gnus-winconf-kill-file
+      (set-window-configuration gnus-winconf-kill-file))
+    (setq gnus-winconf-kill-file nil)
+    ;; Kill the KILL file buffer.  Suggested by tale@pawl.rpi.edu.
+    (kill-buffer killbuf)))
+
+;; For kill files
+
+(defun gnus-Newsgroup-kill-file (newsgroup)
+  "Return the name of a kill file for NEWSGROUP.
+If NEWSGROUP is nil, return the global kill file instead."
+  (cond ((or (null newsgroup)
+	     (string-equal newsgroup ""))
+	 ;; The global kill file is placed at top of the directory.
+	 (expand-file-name gnus-kill-file-name gnus-kill-files-directory))
+	(gnus-use-long-file-name
+	 ;; Append ".KILL" to capitalized newsgroup name.
+	 (expand-file-name (concat (gnus-capitalize-newsgroup newsgroup)
+				   "." gnus-kill-file-name)
+			   gnus-kill-files-directory))
+	(t
+	 ;; Place "KILL" under the hierarchical directory.
+	 (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
+				   "/" gnus-kill-file-name)
+			   gnus-kill-files-directory))))
+
+(defun gnus-expunge (marks)
+  "Remove lines marked with MARKS."
+  (save-excursion
+    (set-buffer gnus-summary-buffer)
+    (gnus-summary-limit-to-marks marks 'reverse)))
+
+(defun gnus-apply-kill-file-unless-scored ()
+  "Apply .KILL file, unless a .SCORE file for the same newsgroup exists."
+  (cond ((file-exists-p (gnus-score-file-name gnus-newsgroup-name))
+         ;; Ignores global KILL.
+         (when (file-exists-p (gnus-newsgroup-kill-file gnus-newsgroup-name))
+	   (gnus-message 3 "Note: Ignoring %s.KILL; preferring .SCORE"
+			 gnus-newsgroup-name))
+         0)
+        ((or (file-exists-p (gnus-newsgroup-kill-file nil))
+             (file-exists-p (gnus-newsgroup-kill-file gnus-newsgroup-name)))
+         (gnus-apply-kill-file-internal))
+        (t
+         0)))
+
+(defun gnus-apply-kill-file-internal ()
+  "Apply a kill file to the current newsgroup.
+Returns the number of articles marked as read."
+  (let* ((kill-files (list (gnus-newsgroup-kill-file nil)
+			   (gnus-newsgroup-kill-file gnus-newsgroup-name)))
+	 (unreads (length gnus-newsgroup-unreads))
+	 (gnus-summary-inhibit-highlight t)
+	 beg)
+    (setq gnus-newsgroup-kill-headers nil)
+    ;; If there are any previously scored articles, we remove these
+    ;; from the `gnus-newsgroup-headers' list that the score functions
+    ;; will see.  This is probably pretty wasteful when it comes to
+    ;; conses, but is, I think, faster than having to assq in every
+    ;; single score function.
+    (let ((files kill-files))
+      (while files
+	(if (file-exists-p (car files))
+	    (let ((headers gnus-newsgroup-headers))
+	      (if gnus-kill-killed
+		  (setq gnus-newsgroup-kill-headers
+			(mapcar (lambda (header) (mail-header-number header))
+				headers))
+		(while headers
+		  (unless (gnus-member-of-range
+			   (mail-header-number (car headers))
+			   gnus-newsgroup-killed)
+		    (push (mail-header-number (car headers))
+			  gnus-newsgroup-kill-headers))
+		  (setq headers (cdr headers))))
+	      (setq files nil))
+ 	  (setq files (cdr files)))))
+    (if (not gnus-newsgroup-kill-headers)
+	()
+      (save-window-excursion
+	(save-excursion
+	  (while kill-files
+	    (if (not (file-exists-p (car kill-files)))
+		()
+	      (gnus-message 6 "Processing kill file %s..." (car kill-files))
+	      (find-file (car kill-files))
+	      (gnus-add-current-to-buffer-list)
+	      (goto-char (point-min))
+
+	      (if (consp (ignore-errors (read (current-buffer))))
+		  (gnus-kill-parse-gnus-kill-file)
+		(gnus-kill-parse-rn-kill-file))
+
+	      (gnus-message
+	       6 "Processing kill file %s...done" (car kill-files)))
+	    (setq kill-files (cdr kill-files)))))
+
+      (gnus-set-mode-line 'summary)
+
+      (if beg
+	  (let ((nunreads (- unreads (length gnus-newsgroup-unreads))))
+	    (or (eq nunreads 0)
+		(gnus-message 6 "Marked %d articles as read" nunreads))
+	    nunreads)
+	0))))
+
+;; Parse a Gnus killfile.
+(defun gnus-score-insert-help (string alist idx)
+  (save-excursion
+    (pop-to-buffer "*Score Help*")
+    (buffer-disable-undo (current-buffer))
+    (erase-buffer)
+    (insert string ":\n\n")
+    (while alist
+      (insert (format " %c: %s\n" (caar alist) (nth idx (car alist))))
+      (setq alist (cdr alist)))))
+
+(defun gnus-kill-parse-gnus-kill-file ()
+  (goto-char (point-min))
+  (gnus-kill-file-mode)
+  (let (beg form)
+    (while (progn
+	     (setq beg (point))
+	     (setq form (ignore-errors (read (current-buffer)))))
+      (unless (listp form)
+	(error "Illegal kill entry (possibly rn kill file?): %s" form))
+      (if (or (eq (car form) 'gnus-kill)
+	      (eq (car form) 'gnus-raise)
+	      (eq (car form) 'gnus-lower))
+	  (progn
+	    (delete-region beg (point))
+	    (insert (or (eval form) "")))
+	(save-excursion
+	  (set-buffer gnus-summary-buffer)
+	  (ignore-errors (eval form)))))
+    (and (buffer-modified-p)
+	 gnus-kill-save-kill-file
+	 (save-buffer))
+    (set-buffer-modified-p nil)))
+
+;; Parse an rn killfile.
+(defun gnus-kill-parse-rn-kill-file ()
+  (goto-char (point-min))
+  (gnus-kill-file-mode)
+  (let ((mod-to-header
+	 '((?a . "")
+	   (?h . "")
+	   (?f . "from")
+	   (?: . "subject")))
+	(com-to-com
+	 '((?m . " ")
+	   (?j . "X")))
+	pattern modifier commands)
+    (while (not (eobp))
+      (if (not (looking-at "[ \t]*/\\([^/]*\\)/\\([ahfcH]\\)?:\\([a-z=:]*\\)"))
+	  ()
+	(setq pattern (buffer-substring (match-beginning 1) (match-end 1)))
+	(setq modifier (if (match-beginning 2) (char-after (match-beginning 2))
+			 ?s))
+	(setq commands (buffer-substring (match-beginning 3) (match-end 3)))
+
+	;; The "f:+" command marks everything *but* the matches as read,
+	;; so we simply first match everything as read, and then unmark
+	;; PATTERN later.
+	(when (string-match "\\+" commands)
+	  (gnus-kill "from" ".")
+	  (setq commands "m"))
+
+	(gnus-kill
+	 (or (cdr (assq modifier mod-to-header)) "subject")
+	 pattern
+	 (if (string-match "m" commands)
+	     '(gnus-summary-mark-as-unread nil " ")
+	   '(gnus-summary-mark-as-read nil "X"))
+	 nil t))
+      (forward-line 1))))
+
+;; Kill changes and new format by suggested by JWZ and Sudish Joseph
+;; <joseph@cis.ohio-state.edu>.
+(defun gnus-kill (field regexp &optional exe-command all silent)
+  "If FIELD of an article matches REGEXP, execute COMMAND.
+Optional 1st argument COMMAND is default to
+	(gnus-summary-mark-as-read nil \"X\").
+If optional 2nd argument ALL is non-nil, articles marked are also applied to.
+If FIELD is an empty string (or nil), entire article body is searched for.
+COMMAND must be a lisp expression or a string representing a key sequence."
+  ;; We don't want to change current point nor window configuration.
+  (let ((old-buffer (current-buffer)))
+    (save-excursion
+      (save-window-excursion
+	;; Selected window must be summary buffer to execute keyboard
+	;; macros correctly.  See command_loop_1.
+	(switch-to-buffer gnus-summary-buffer 'norecord)
+	(goto-char (point-min))		;From the beginning.
+	(let ((kill-list regexp)
+	      (date (current-time-string))
+	      (command (or exe-command '(gnus-summary-mark-as-read
+					 nil gnus-kill-file-mark)))
+	      kill kdate prev)
+	  (if (listp kill-list)
+	      ;; It is a list.
+	      (if (not (consp (cdr kill-list)))
+		  ;; It's on the form (regexp . date).
+		  (if (zerop (gnus-execute field (car kill-list)
+					   command nil (not all)))
+		      (when (> (gnus-days-between date (cdr kill-list))
+			       gnus-kill-expiry-days)
+			(setq regexp nil))
+		    (setcdr kill-list date))
+		(while (setq kill (car kill-list))
+		  (if (consp kill)
+		      ;; It's a temporary kill.
+		      (progn
+			(setq kdate (cdr kill))
+			(if (zerop (gnus-execute
+				    field (car kill) command nil (not all)))
+			    (when (> (gnus-days-between date kdate)
+				     gnus-kill-expiry-days)
+			      ;; Time limit has been exceeded, so we
+			      ;; remove the match.
+			      (if prev
+				  (setcdr prev (cdr kill-list))
+				(setq regexp (cdr regexp))))
+			  ;; Successful kill.  Set the date to today.
+			  (setcdr kill date)))
+		    ;; It's a permanent kill.
+		    (gnus-execute field kill command nil (not all)))
+		  (setq prev kill-list)
+		  (setq kill-list (cdr kill-list))))
+	    (gnus-execute field kill-list command nil (not all))))))
+    (switch-to-buffer old-buffer)
+    (when (and (eq major-mode 'gnus-kill-file-mode) regexp (not silent))
+      (gnus-pp-gnus-kill
+       (nconc (list 'gnus-kill field
+		    (if (consp regexp) (list 'quote regexp) regexp))
+	      (when (or exe-command all)
+		(list (list 'quote exe-command)))
+	      (if all (list t) nil))))))
+
+(defun gnus-pp-gnus-kill (object)
+  (if (or (not (consp (nth 2 object)))
+	  (not (consp (cdr (nth 2 object))))
+	  (and (eq 'quote (car (nth 2 object)))
+	       (not (consp (cdadr (nth 2 object))))))
+      (concat "\n" (gnus-prin1-to-string object))
+    (save-excursion
+      (set-buffer (get-buffer-create "*Gnus PP*"))
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (insert (format "\n(%S %S\n  '(" (nth 0 object) (nth 1 object)))
+      (let ((klist (cadr (nth 2 object)))
+	    (first t))
+	(while klist
+	  (insert (if first (progn (setq first nil) "")  "\n    ")
+		  (gnus-prin1-to-string (car klist)))
+	  (setq klist (cdr klist))))
+      (insert ")")
+      (and (nth 3 object)
+	   (insert "\n  "
+		   (if (and (consp (nth 3 object))
+			    (not (eq 'quote (car (nth 3 object)))))
+		       "'" "")
+		   (gnus-prin1-to-string (nth 3 object))))
+      (when (nth 4 object)
+	(insert "\n  t"))
+      (insert ")")
+      (prog1
+	  (buffer-substring (point-min) (point-max))
+	(kill-buffer (current-buffer))))))
+
+(defun gnus-execute-1 (function regexp form header)
+  (save-excursion
+    (let (did-kill)
+      (if (null header)
+	  nil				;Nothing to do.
+	(if function
+	    ;; Compare with header field.
+	    (let (value)
+	      (and header
+		   (progn
+		     (setq value (funcall function header))
+		     ;; Number (Lines:) or symbol must be converted to string.
+		     (unless (stringp value)
+		       (setq value (gnus-prin1-to-string value)))
+		     (setq did-kill (string-match regexp value)))
+		   (cond ((stringp form) ;Keyboard macro.
+			  (execute-kbd-macro form))
+			 ((gnus-functionp form)
+			  (funcall form))
+			 (t
+			  (eval form)))))
+	  ;; Search article body.
+	  (let ((gnus-current-article nil) ;Save article pointer.
+		(gnus-last-article nil)
+		(gnus-break-pages nil)	;No need to break pages.
+		(gnus-mark-article-hook nil)) ;Inhibit marking as read.
+	    (gnus-message
+	     6 "Searching for article: %d..." (mail-header-number header))
+	    (gnus-article-setup-buffer)
+	    (gnus-article-prepare (mail-header-number header) t)
+	    (when (save-excursion
+		    (set-buffer gnus-article-buffer)
+		    (goto-char (point-min))
+		    (setq did-kill (re-search-forward regexp nil t)))
+	      (cond ((stringp form)	;Keyboard macro.
+		     (execute-kbd-macro form))
+		    ((gnus-functionp form)
+		     (funcall form))
+		    (t
+		     (eval form)))))))
+      did-kill)))
+
+(defun gnus-execute (field regexp form &optional backward unread)
+  "If FIELD of article header matches REGEXP, execute lisp FORM (or a string).
+If FIELD is an empty string (or nil), entire article body is searched for.
+If optional 1st argument BACKWARD is non-nil, do backward instead.
+If optional 2nd argument UNREAD is non-nil, articles which are
+marked as read or ticked are ignored."
+  (save-excursion
+    (let ((killed-no 0)
+	  function article header)
+      (cond
+       ;; Search body.
+       ((or (null field)
+	    (string-equal field ""))
+	(setq function nil))
+       ;; Get access function of header field.
+       ((fboundp
+	 (setq function
+	       (intern-soft
+		(concat "mail-header-" (downcase field)))))
+	(setq function `(lambda (h) (,function h))))
+       ;; Signal error.
+       (t
+	(error "Unknown header field: \"%s\"" field)))
+      ;; Starting from the current article.
+      (while (or
+	      ;; First article.
+	      (and (not article)
+		   (setq article (gnus-summary-article-number)))
+	      ;; Find later articles.
+	      (setq article
+		    (gnus-summary-search-forward unread nil backward)))
+	(and (or (null gnus-newsgroup-kill-headers)
+		 (memq article gnus-newsgroup-kill-headers))
+	     (vectorp (setq header (gnus-summary-article-header article)))
+	     (gnus-execute-1 function regexp form header)
+	     (setq killed-no (1+ killed-no))))
+      ;; Return the number of killed articles.
+      killed-no)))
+
+;;;###autoload
+(defalias 'gnus-batch-kill 'gnus-batch-score)
+;;;###autoload
+(defun gnus-batch-score ()
+  "Run batched scoring.
+Usage: emacs -batch -l gnus -f gnus-batch-score <newsgroups> ...
+Newsgroups is a list of strings in Bnews format.  If you want to score
+the comp hierarchy, you'd say \"comp.all\".  If you would not like to
+score the alt hierarchy, you'd say \"!alt.all\"."
+  (interactive)
+  (let* ((gnus-newsrc-options-n
+	  (gnus-newsrc-parse-options
+	   (concat "options -n "
+		   (mapconcat 'identity command-line-args-left " "))))
+	 (gnus-expert-user t)
+	 (nnmail-spool-file nil)
+	 (gnus-use-dribble-file nil)
+	 (gnus-batch-mode t)
+	 group newsrc entry
+	 ;; Disable verbose message.
+	 gnus-novice-user gnus-large-newsgroup
+	 gnus-options-subscribe gnus-auto-subscribed-groups
+	 gnus-options-not-subscribe)
+    ;; Eat all arguments.
+    (setq command-line-args-left nil)
+    (gnus-slave)
+    ;; Apply kills to specified newsgroups in command line arguments.
+    (setq newsrc (cdr gnus-newsrc-alist))
+    (while (setq group (car (pop newsrc)))
+      (setq entry (gnus-gethash group gnus-newsrc-hashtb))
+      (when (and (<= (gnus-info-level (car newsrc)) gnus-level-subscribed)
+		 (and (car entry)
+		      (or (eq (car entry) t)
+			  (not (zerop (car entry)))))
+		 ;;(eq (gnus-matches-options-n group) 'subscribe)
+		 )
+	(gnus-summary-read-group group nil t nil t)
+	(when (eq (current-buffer) (get-buffer gnus-summary-buffer))
+	  (gnus-summary-exit))))
+    ;; Exit Emacs.
+    (switch-to-buffer gnus-group-buffer)
+    (gnus-group-save-newsrc)))
+
+(provide 'gnus-kill)
+
+;;; gnus-kill.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-load.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,103 @@
+;;; gnus-load.el --- automatically extracted custom dependencies
+;;
+;;; Code:
+
+(put 'nnmail 'custom-loads '("nnmail"))
+(put 'gnus-article-emphasis 'custom-loads '("gnus-art"))
+(put 'gnus-article-headers 'custom-loads '("gnus-sum" "gnus-art"))
+(put 'nnmail-procmail 'custom-loads '("nnmail"))
+(put 'gnus-score-kill 'custom-loads '("gnus-kill"))
+(put 'gnus-visual 'custom-loads '("smiley" "gnus" "gnus-picon" "gnus-art" "earcon"))
+(put 'gnus-score-expire 'custom-loads '("gnus-score" "gnus-kill"))
+(put 'gnus-summary-maneuvering 'custom-loads '("gnus-sum"))
+(put 'gnus-start 'custom-loads '("gnus" "gnus-util" "gnus-start" "gnus-int" "gnus-group"))
+(put 'gnus-extract-view 'custom-loads '("gnus-uu" "gnus-sum"))
+(put 'gnus-various 'custom-loads '("gnus-sum"))
+(put 'gnus-article-washing 'custom-loads '("gnus-art"))
+(put 'gnus-score-files 'custom-loads '("gnus-score"))
+(put 'message-news 'custom-loads '("message"))
+(put 'gnus-thread 'custom-loads '("gnus-sum"))
+(put 'languages 'custom-loads '("cus-edit"))
+(put 'development 'custom-loads '("cus-edit"))
+(put 'gnus-treading 'custom-loads '("gnus-sum"))
+(put 'nnmail-various 'custom-loads '("nnmail"))
+(put 'extensions 'custom-loads '("wid-edit"))
+(put 'message-various 'custom-loads '("message"))
+(put 'gnus-summary-exit 'custom-loads '("gnus-sum"))
+(put 'news 'custom-loads '("message" "gnus"))
+(put 'gnus 'custom-loads '("nnmail" "gnus" "gnus-win" "gnus-uu" "gnus-eform" "gnus-dup" "gnus-demon" "gnus-cache" "gnus-async" "gnus-art"))
+(put 'gnus-summary-visual 'custom-loads '("gnus-sum"))
+(put 'gnus-group-listing 'custom-loads '("gnus-group"))
+(put 'gnus-score 'custom-loads '("gnus" "gnus-nocem"))
+(put 'gnus-group-select 'custom-loads '("gnus-sum"))
+(put 'message-buffers 'custom-loads '("message"))
+(put 'gnus-threading 'custom-loads '("gnus-sum"))
+(put 'gnus-score-decay 'custom-loads '("gnus-score"))
+(put 'help 'custom-loads '("cus-edit"))
+(put 'gnus-nocem 'custom-loads '("gnus-nocem"))
+(put 'gnus-cite 'custom-loads '("gnus-cite"))
+(put 'gnus-demon 'custom-loads '("gnus-demon"))
+(put 'gnus-message 'custom-loads '("message"))
+(put 'gnus-score-default 'custom-loads '("gnus-sum" "gnus-score"))
+(put 'nnmail-duplicate 'custom-loads '("nnmail"))
+(put 'message-interface 'custom-loads '("message"))
+(put 'nnmail-files 'custom-loads '("nnmail"))
+(put 'gnus-edit-form 'custom-loads '("gnus-eform"))
+(put 'emacs 'custom-loads '("cus-edit"))
+(put 'gnus-summary-mail 'custom-loads '("gnus-sum"))
+(put 'gnus-topic 'custom-loads '("gnus-topic"))
+(put 'wp 'custom-loads '("cus-edit"))
+(put 'gnus-summary-choose 'custom-loads '("gnus-sum"))
+(put 'widget-browse 'custom-loads '("wid-browse"))
+(put 'external 'custom-loads '("cus-edit"))
+(put 'message-headers 'custom-loads '("message"))
+(put 'message-forwarding 'custom-loads '("message"))
+(put 'message-faces 'custom-loads '("message"))
+(put 'environment 'custom-loads '("cus-edit"))
+(put 'gnus-article-mime 'custom-loads '("gnus-sum" "gnus-art"))
+(put 'gnus-duplicate 'custom-loads '("gnus-dup"))
+(put 'nnmail-retrieve 'custom-loads '("nnmail"))
+(put 'widgets 'custom-loads '("wid-edit" "wid-browse"))
+(put 'earcon 'custom-loads '("earcon"))
+(put 'hypermedia 'custom-loads '("wid-edit"))
+(put 'gnus-group-levels 'custom-loads '("gnus-group"))
+(put 'gnus-summary-format 'custom-loads '("gnus-sum"))
+(put 'gnus-files 'custom-loads '("nnmail" "gnus"))
+(put 'gnus-windows 'custom-loads '("gnus-win"))
+(put 'gnus-article-buttons 'custom-loads '("gnus-art"))
+(put 'gnus-summary 'custom-loads '("gnus" "gnus-sum"))
+(put 'gnus-article-hiding 'custom-loads '("gnus-sum" "gnus-art"))
+(put 'gnus-group 'custom-loads '("gnus" "gnus-topic"))
+(put 'gnus-article-various 'custom-loads '("gnus-sum" "gnus-art"))
+(put 'gnus-summary-marks 'custom-loads '("gnus-sum"))
+(put 'gnus-article-saving 'custom-loads '("gnus-art"))
+(put 'nnmail-expire 'custom-loads '("nnmail"))
+(put 'message-mail 'custom-loads '("message"))
+(put 'faces 'custom-loads '("wid-edit" "cus-edit" "message" "gnus"))
+(put 'gnus-summary-various 'custom-loads '("gnus-sum"))
+(put 'applications 'custom-loads '("cus-edit"))
+(put 'gnus-extract-archive 'custom-loads '("gnus-uu"))
+(put 'message 'custom-loads '("message"))
+(put 'message-sending 'custom-loads '("message"))
+(put 'editing 'custom-loads '("cus-edit"))
+(put 'gnus-score-adapt 'custom-loads '("gnus-score"))
+(put 'message-insertion 'custom-loads '("message"))
+(put 'gnus-extract-post 'custom-loads '("gnus-uu"))
+(put 'mail 'custom-loads '("message" "gnus"))
+(put 'gnus-summary-sort 'custom-loads '("gnus-sum"))
+(put 'customize 'custom-loads '("wid-edit" "custom" "cus-face" "cus-edit"))
+(put 'nnmail-split 'custom-loads '("nnmail"))
+(put 'gnus-asynchronous 'custom-loads '("gnus-async"))
+(put 'gnus-article-highlight 'custom-loads '("gnus-art"))
+(put 'gnus-extract 'custom-loads '("gnus-uu"))
+(put 'gnus-article 'custom-loads '("gnus-cite" "gnus-art"))
+(put 'gnus-group-foreign 'custom-loads '("gnus-group"))
+(put 'programming 'custom-loads '("cus-edit"))
+(put 'nnmail-prepare 'custom-loads '("nnmail"))
+(put 'picons 'custom-loads '("gnus-picon"))
+(put 'gnus-article-signature 'custom-loads '("gnus-art"))
+(put 'gnus-group-various 'custom-loads '("gnus-group"))
+
+(provide 'gnus-load)
+
+;;; gnus-load.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-logic.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,227 @@
+;;; gnus-logic.el --- advanced scoring code for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-score)
+(require 'gnus-util)
+
+;;; Internal variables.
+
+(defvar gnus-advanced-headers nil)
+
+;; To avoid having 8-bit characters in the source file.
+(defvar gnus-advanced-not (intern (format "%c" 172)))
+
+(defconst gnus-advanced-index
+  ;; Name to index alist.
+  '(("number" 0 gnus-advanced-integer)
+    ("subject" 1 gnus-advanced-string)
+    ("from" 2 gnus-advanced-string)
+    ("date" 3 gnus-advanced-date)
+    ("message-id" 4 gnus-advanced-string)
+    ("references" 5 gnus-advanced-string)
+    ("chars" 6 gnus-advanced-integer)
+    ("lines" 7 gnus-advanced-integer)
+    ("xref" 8 gnus-advanced-string)
+    ("head" nil gnus-advanced-body)
+    ("body" nil gnus-advanced-body)
+    ("all" nil gnus-advanced-body)))
+
+(eval-and-compile
+  (autoload 'parse-time-string "parse-time"))
+
+(defun gnus-score-advanced (rule &optional trace)
+  "Apply advanced scoring RULE to all the articles in the current group."
+  (let ((headers gnus-newsgroup-headers)
+	gnus-advanced-headers score)
+    (while (setq gnus-advanced-headers (pop headers))
+      (when (gnus-advanced-score-rule (car rule))
+	;; This rule was successful, so we add the score to
+	;; this article.
+	(if (setq score (assq (mail-header-number gnus-advanced-headers)
+			      gnus-newsgroup-scored))
+	    (setcdr score
+		    (+ (cdr score)
+		       (or (nth 1 rule)
+			   gnus-score-interactive-default-score)))
+	  (push (cons (mail-header-number gnus-advanced-headers)
+		      (or (nth 1 rule)
+			  gnus-score-interactive-default-score))
+		gnus-newsgroup-scored)
+	  (when trace
+	    (push (cons "A file" rule)
+		  gnus-score-trace)))))))
+
+(defun gnus-advanced-score-rule (rule)
+  "Apply RULE to `gnus-advanced-headers'."
+  (let ((type (car rule)))
+    (cond
+     ;; "And" rule.
+     ((or (eq type '&) (eq type 'and))
+      (pop rule)
+      (if (not rule)
+	  t				; Empty rule is true.
+	(while (and rule
+		    (gnus-advanced-score-rule (car rule)))
+	  (pop rule))
+	;; If all the rules were true, then `rule' should be nil.
+	(not rule)))
+     ;; "Or" rule.
+     ((or (eq type '|) (eq type 'or))
+      (pop rule)
+      (if (not rule)
+	  nil
+	(while (and rule
+		    (not (gnus-advanced-score-rule (car rule))))
+	  (pop rule))
+	;; If one of the rules returned true, then `rule' should be non-nil.
+	rule))
+     ;; "Not" rule.
+     ((or (eq type '!) (eq type 'not) (eq type gnus-advanced-not))
+      (not (gnus-advanced-score-rule (nth 1 rule))))
+     ;; This is a `1-'-type redirection rule.
+     ((and (symbolp type)
+	   (string-match "^[0-9]+-$\\|^\\^+$" (symbol-name type)))
+      (let ((gnus-advanced-headers
+	     (gnus-parent-headers
+	      gnus-advanced-headers
+	      (if (string-match "^\\([0-9]+\\)-$" (symbol-name type))
+		  ;; 1- type redirection.
+		  (string-to-number
+		   (substring (symbol-name type)
+			      (match-beginning 0) (match-end 0)))
+		;; ^^^ type redirection.
+		(length (symbol-name type))))))
+	(when gnus-advanced-headers
+	  (gnus-advanced-score-rule (nth 1 rule)))))
+     ;; Plain scoring rule.
+     ((stringp type)
+      (gnus-advanced-score-article rule))
+     ;; Bug-out time!
+     (t
+      (error "Unknown advanced score type: %s" rule)))))
+
+(defun gnus-advanced-score-article (rule)
+  ;; `rule' is a semi-normal score rule, so we find out
+  ;; what function that's supposed to do the actual
+  ;; processing.
+  (let* ((header (car rule))
+	 (func (assoc (downcase header) gnus-advanced-index)))
+    (if (not func)
+	(error "No such header: %s" rule)
+      ;; Call the score function.
+      (funcall (caddr func) (or (cadr func) header)
+	       (cadr rule) (caddr rule)))))
+
+(defun gnus-advanced-string (index match type)
+  "See whether string MATCH of TYPE matches `gnus-advanced-headers' in INDEX."
+  (let* ((type (or type 's))
+	 (case-fold-search (not (eq (downcase (symbol-name type))
+				    (symbol-name type))))
+	 (header (aref gnus-advanced-headers index)))
+    (cond
+     ((memq type '(r R regexp Regexp))
+      (string-match match header))
+     ((memq type '(s S string String))
+      (string-match (regexp-quote match) header))
+     ((memq type '(e E exact Exact))
+      (string= match header))
+     ((memq type '(f F fuzzy Fuzzy))
+      (string-match (regexp-quote (gnus-simplify-subject-fuzzy match))
+		    header))
+     (t
+      (error "No such string match type: %s" type)))))
+
+(defun gnus-advanced-integer (index match type)
+  (if (not (memq type '(< > <= >= =)))
+      (error "No such integer score type: %s" type)
+    (funcall type match (or (aref gnus-advanced-headers index) 0))))
+
+(defun gnus-advanced-date (index match type)
+  (let ((date (encode-time (parse-time-string
+			    (aref gnus-advanced-headers index))))
+	(match (encode-time (parse-time-string match))))
+    (cond
+     ((eq type 'at)
+      (equal date match))
+     ((eq type 'before)
+      (gnus-time-less match date))
+     ((eq type 'after)
+      (gnus-time-less date match))
+     (t
+      (error "No such date score type: %s" type)))))
+
+(defun gnus-advanced-body (header match type)
+  (when (string= header "all")
+    (setq header "article"))
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (let* ((request-func (cond ((string= "head" header)
+				'gnus-request-head)
+			       ((string= "body" header)
+				'gnus-request-body)
+			       (t 'gnus-request-article)))
+	   ofunc article)
+      ;; Not all backends support partial fetching.  In that case,
+      ;; we just fetch the entire article.
+      (unless (gnus-check-backend-function
+	       (intern (concat "request-" header))
+	       gnus-newsgroup-name)
+	(setq ofunc request-func)
+	(setq request-func 'gnus-request-article))
+      (setq article (mail-header-number gnus-advanced-headers))
+      (gnus-message 7 "Scoring article %s..." article)
+      (when (funcall request-func article gnus-newsgroup-name)
+	(goto-char (point-min))
+	;; If just parts of the article is to be searched and the
+	;; backend didn't support partial fetching, we just narrow
+	;; to the relevant parts.
+	(when ofunc
+	  (if (eq ofunc 'gnus-request-head)
+	      (narrow-to-region
+	       (point)
+	       (or (search-forward "\n\n" nil t) (point-max)))
+	    (narrow-to-region
+	     (or (search-forward "\n\n" nil t) (point))
+	     (point-max))))
+	(let* ((case-fold-search (not (eq (downcase (symbol-name type))
+					  (symbol-name type))))
+	       (search-func
+		(cond ((memq type '(r R regexp Regexp))
+		       're-search-forward)
+		      ((memq type '(s S string String))
+		       'search-forward)
+		      (t
+		       (error "Illegal match type: %s" type)))))
+	  (goto-char (point-min))
+	  (prog1
+	      (funcall search-func match nil t)
+	    (widen)))))))
+
+(provide 'gnus-logic)
+
+;;; gnus-logic.el ends here.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-mh.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,105 @@
+;;; gnus-mh.el --- mh-e interface for Gnus
+;; Copyright (C) 1994,95,96,97 Free Software Foundation, Inc.
+
+;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;;	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Send mail using mh-e.
+
+;; The following mh-e interface is all cooperative works of
+;; tanaka@flab.fujitsu.CO.JP (TANAKA Hiroshi), kawabe@sra.CO.JP
+;; (Yoshikatsu Kawabe), and shingu@casund.cpr.canon.co.jp (Toshiaki
+;; SHINGU).
+
+;;; Code:
+
+(require 'gnus)
+(require 'mh-e)
+(require 'mh-comp)
+(require 'gnus-msg)
+(require 'gnus-sum)
+
+(defun gnus-summary-save-article-folder (&optional arg)
+  "Append the current article to an mh folder.
+If N is a positive number, save the N next articles.
+If N is a negative number, save the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+save those articles instead."
+  (interactive "P")
+  (let ((gnus-default-article-saver 'gnus-summary-save-in-folder))
+    (gnus-summary-save-article arg)))
+
+(defun gnus-summary-save-in-folder (&optional folder)
+  "Save this article to MH folder (using `rcvstore' in MH library).
+Optional argument FOLDER specifies folder name."
+  ;; Thanks to yuki@flab.Fujitsu.JUNET and ohm@kaba.junet.
+  (mh-find-path)
+  (let ((folder
+	 (cond ((and (eq folder 'default)
+		     gnus-newsgroup-last-folder)
+		gnus-newsgroup-last-folder)
+	       (folder folder)
+	       (t (mh-prompt-for-folder
+		   "Save article in"
+		   (funcall gnus-folder-save-name gnus-newsgroup-name
+			    gnus-current-headers gnus-newsgroup-last-folder)
+		   t))))
+	(errbuf (get-buffer-create " *Gnus rcvstore*"))
+	;; Find the rcvstore program.
+	(exec-path (if mh-lib (cons mh-lib exec-path) exec-path)))
+    (gnus-eval-in-buffer-window gnus-original-article-buffer
+      (save-restriction
+	(widen)
+	(unwind-protect
+	    (call-process-region
+	     (point-min) (point-max) "rcvstore" nil errbuf nil folder)
+	  (set-buffer errbuf)
+	  (if (zerop (buffer-size))
+	      (message "Article saved in folder: %s" folder)
+	    (message "%s" (buffer-string)))
+	  (kill-buffer errbuf))))
+    (setq gnus-newsgroup-last-folder folder)))
+
+(defun gnus-Folder-save-name (newsgroup headers &optional last-folder)
+  "Generate folder name from NEWSGROUP, HEADERS, and optional LAST-FOLDER.
+If variable `gnus-use-long-file-name' is nil, it is +News.group.
+Otherwise, it is like +news/group."
+  (or last-folder
+      (concat "+"
+	      (if gnus-use-long-file-name
+		  (gnus-capitalize-newsgroup newsgroup)
+		(gnus-newsgroup-directory-form newsgroup)))))
+
+(defun gnus-folder-save-name (newsgroup headers &optional last-folder)
+  "Generate folder name from NEWSGROUP, HEADERS, and optional LAST-FOLDER.
+If variable `gnus-use-long-file-name' is nil, it is +news.group.
+Otherwise, it is like +news/group."
+  (or last-folder
+      (concat "+"
+	      (if gnus-use-long-file-name
+		  newsgroup
+		(gnus-newsgroup-directory-form newsgroup)))))
+
+(provide 'gnus-mh)
+
+;;; gnus-mh.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-move.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,172 @@
+;;; gnus-move.el --- commands for moving Gnus from one server to another
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-start)
+(require 'gnus-int)
+(require 'gnus-range)
+
+;;;
+;;; Moving by comparing Message-ID's.
+;;;
+
+;;;###autoload
+(defun gnus-change-server (from-server to-server)
+  "Move from FROM-SERVER to TO-SERVER.
+Update the .newsrc.eld file to reflect the change of nntp server."
+  (interactive
+   (list gnus-select-method (gnus-read-method "Move to method: ")))
+
+  ;; First start Gnus.
+  (let ((gnus-activate-level 0)
+	(nnmail-spool-file nil))
+    (gnus))
+
+  (save-excursion
+    ;; Go through all groups and translate.
+    (let ((newsrc gnus-newsrc-alist)
+	  (nntp-nov-gap nil)
+	  info)
+      (while (setq info (pop newsrc))
+	(when (gnus-group-native-p (gnus-info-group info))
+	  (gnus-move-group-to-server info from-server to-server))))))
+
+(defun gnus-move-group-to-server (info from-server to-server)
+  "Move group INFO from FROM-SERVER to TO-SERVER."
+  (let ((group (gnus-info-group info))
+	to-active hashtb type mark marks
+	to-article to-reads to-marks article)
+    (gnus-message 7 "Translating %s..." group)
+    (when (gnus-request-group group nil to-server)
+      (setq to-active (gnus-parse-active)
+	    hashtb (gnus-make-hashtable 1024))
+      ;; Fetch the headers from the `to-server'.
+      (when (and to-active
+		 (setq type (gnus-retrieve-headers
+			     (gnus-uncompress-range to-active)
+			     group to-server)))
+	;; Convert HEAD headers.  I don't care.
+	(when (eq type 'headers)
+	  (nnvirtual-convert-headers))
+	;; Create a mapping from Message-ID to article number.
+	(set-buffer nntp-server-buffer)
+	(goto-char (point-min))
+	(while (looking-at
+		"^[0-9]+\t[^\t]*\t[^\t]*\t[^\t]*\t\\([^\t]*\\)\t")
+	  (gnus-sethash
+	   (buffer-substring (match-beginning 1) (match-end 1))
+	   (read (current-buffer))
+	   hashtb)
+	  (forward-line 1))
+	;; Then we read the headers from the `from-server'.
+	(when (and (gnus-request-group group nil from-server)
+		   (gnus-active group)
+		   (setq type (gnus-retrieve-headers
+			       (gnus-uncompress-range
+				(gnus-active group))
+			       group from-server)))
+	  ;; Make it easier to map marks.
+	  (let ((mark-lists (gnus-info-marks info))
+		ms type m)
+	    (while mark-lists
+	      (setq type (caar mark-lists)
+		    ms (gnus-uncompress-range (cdr (pop mark-lists))))
+	      (while ms
+		(if (setq m (assq (car ms) marks))
+		    (setcdr m (cons type (cdr m)))
+		  (push (list (car ms) type) marks))
+		(pop ms))))
+	  ;; Convert.
+	  (when (eq type 'headers)
+	    (nnvirtual-convert-headers))
+	  ;; Go through the headers and map away.
+	  (set-buffer nntp-server-buffer)
+	  (goto-char (point-min))
+	  (while (looking-at
+		  "^[0-9]+\t[^\t]*\t[^\t]*\t[^\t]*\t\\([^\t]*\\)\t")
+	    (setq to-article
+		  (gnus-gethash
+		   (buffer-substring (match-beginning 1) (match-end 1))
+		   hashtb))
+	    ;; Add this article to the list of read articles.
+	    (push to-article to-reads)
+	    ;; See if there are any marks and then add them.
+	    (when (setq mark (assq (read (current-buffer)) marks))
+	      (setq marks (delq mark marks))
+	      (setcar mark to-article)
+	      (push mark to-marks))
+	    (forward-line 1))
+	  ;; Now we know what the read articles are and what the
+	  ;; article marks are.  We transform the information
+	  ;; into the Gnus info format.
+	  (setq to-reads
+		(gnus-range-add
+		 (gnus-compress-sequence (sort to-reads '<) t)
+		 (cons 1 (1- (car to-active)))))
+	  (gnus-info-set-read info to-reads)
+	  ;; Do the marks.  I'm sure y'all understand what's
+	  ;; going on down below, so I won't bother with any
+	  ;; further comments.  <duck>
+	  (let ((mlists gnus-article-mark-lists)
+		lists ms a)
+	    (while mlists
+	      (push (list (cdr (pop mlists))) lists))
+	    (while (setq ms (pop marks))
+	      (setq article (pop ms))
+	      (while ms
+		(setcdr (setq a (assq (pop ms) lists))
+			(cons article (cdr a)))))
+	    (setq a lists)
+	    (while a
+	      (setcdr (car a) (gnus-compress-sequence (sort (cdar a) '<)))
+	      (pop a))
+	    (gnus-info-set-marks info lists t)))))
+    (gnus-message 7 "Translating %s...done" group)))
+
+(defun gnus-group-move-group-to-server (info from-server to-server)
+  "Move the group on the current line from FROM-SERVER to TO-SERVER."
+  (interactive
+   (let ((info (gnus-get-info (gnus-group-group-name))))
+     (list info (gnus-find-method-for-group (gnus-info-group info))
+	   (gnus-read-method (format "Move group %s to method: "
+				     (gnus-info-group info))))))
+  (save-excursion
+    (gnus-move-group-to-server info from-server to-server)
+    ;; We have to update the group info to point use the right server.
+    (gnus-info-set-method info to-server t)
+    ;; We also have to change the name of the group and stuff.
+    (let* ((group (gnus-info-group info))
+	   (new-name (gnus-group-prefixed-name
+		      (gnus-group-real-name group) to-server)))
+      (gnus-info-set-group info new-name)
+      (gnus-sethash new-name (gnus-gethash group gnus-newsrc-hashtb)
+		    gnus-newsrc-hashtb)
+      (gnus-sethash group nil gnus-newsrc-hashtb))))
+
+(provide 'gnus-move)
+
+;;; gnus-move.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-msg.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,1074 @@
+;;; gnus-msg.el --- mail and post interface for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;;	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-ems)
+(require 'message)
+(require 'gnus-art)
+
+;; Added by Sudish Joseph <joseph@cis.ohio-state.edu>.
+(defvar gnus-post-method nil
+  "*Preferred method for posting USENET news.
+If this variable is nil, Gnus will use the current method to decide
+which method to use when posting.  If it is non-nil, it will override
+the current method.  This method will not be used in mail groups and
+the like, only in \"real\" newsgroups.
+
+The value must be a valid method as discussed in the documentation of
+`gnus-select-method'.  It can also be a list of methods.  If that is
+the case, the user will be queried for what select method to use when
+posting.")
+
+(defvar gnus-outgoing-message-group nil
+  "*All outgoing messages will be put in this group.
+If you want to store all your outgoing mail and articles in the group
+\"nnml:archive\", you set this variable to that value.  This variable
+can also be a list of group names.
+
+If you want to have greater control over what group to put each
+message in, you can set this variable to a function that checks the
+current newsgroup name and then returns a suitable group name (or list
+of names).")
+
+(defvar gnus-mailing-list-groups nil
+  "*Regexp matching groups that are really mailing lists.
+This is useful when you're reading a mailing list that has been
+gatewayed to a newsgroup, and you want to followup to an article in
+the group.")
+
+(defvar gnus-add-to-list nil
+  "*If non-nil, add a `to-list' parameter automatically.")
+
+(defvar gnus-sent-message-ids-file
+  (nnheader-concat gnus-directory "Sent-Message-IDs")
+  "File where Gnus saves a cache of sent message ids.")
+
+(defvar gnus-sent-message-ids-length 1000
+  "The number of sent Message-IDs to save.")
+
+(defvar gnus-crosspost-complaint
+  "Hi,
+
+You posted the article below with the following Newsgroups header:
+
+Newsgroups: %s
+
+The %s group, at least, was an inappropriate recipient
+of this message.  Please trim your Newsgroups header to exclude this
+group before posting in the future.
+
+Thank you.
+
+"
+  "Format string to be inserted when complaining about crossposts.
+The first %s will be replaced by the Newsgroups header;
+the second with the current group name.")
+
+(defvar gnus-message-setup-hook nil
+  "Hook run after setting up a message buffer.")
+
+;;; Internal variables.
+
+(defvar gnus-message-buffer "*Mail Gnus*")
+(defvar gnus-article-copy nil)
+(defvar gnus-last-posting-server nil)
+
+(defconst gnus-bug-message
+  "Sending a bug report to the Gnus Towers.
+========================================
+
+The buffer below is a mail buffer.  When you press `C-c C-c', it will
+be sent to the Gnus Bug Exterminators.
+
+At the bottom of the buffer you'll see lots of variable settings.
+Please do not delete those.  They will tell the Bug People what your
+environment is, so that it will be easier to locate the bugs.
+
+If you have found a bug that makes Emacs go \"beep\", set
+debug-on-error to t (`M-x set-variable RET debug-on-error RET t RET')
+and include the backtrace in your bug report.
+
+Please describe the bug in annoying, painstaking detail.
+
+Thank you for your help in stamping out bugs.
+")
+
+(eval-and-compile
+  (autoload 'gnus-uu-post-news "gnus-uu" nil t)
+  (autoload 'news-setup "rnewspost")
+  (autoload 'news-reply-mode "rnewspost")
+  (autoload 'rmail-dont-reply-to "mail-utils")
+  (autoload 'rmail-output "rmailout"))
+
+
+;;;
+;;; Gnus Posting Functions
+;;;
+
+(gnus-define-keys (gnus-summary-send-map "S" gnus-summary-mode-map)
+  "p" gnus-summary-post-news
+  "f" gnus-summary-followup
+  "F" gnus-summary-followup-with-original
+  "c" gnus-summary-cancel-article
+  "s" gnus-summary-supersede-article
+  "r" gnus-summary-reply
+  "R" gnus-summary-reply-with-original
+  "w" gnus-summary-wide-reply
+  "W" gnus-summary-wide-reply-with-original
+  "n" gnus-summary-followup-to-mail
+  "N" gnus-summary-followup-to-mail-with-original
+  "m" gnus-summary-mail-other-window
+  "u" gnus-uu-post-news
+  "\M-c" gnus-summary-mail-crosspost-complaint
+  "om" gnus-summary-mail-forward
+  "op" gnus-summary-post-forward
+  "Om" gnus-uu-digest-mail-forward
+  "Op" gnus-uu-digest-post-forward)
+
+(gnus-define-keys (gnus-send-bounce-map "D" gnus-summary-send-map)
+  "b" gnus-summary-resend-bounced-mail
+  ;; "c" gnus-summary-send-draft
+  "r" gnus-summary-resend-message)
+
+;;; Internal functions.
+
+(defvar gnus-article-reply nil)
+(defmacro gnus-setup-message (config &rest forms)
+  (let ((winconf (make-symbol "winconf"))
+	(buffer (make-symbol "buffer"))
+	(article (make-symbol "article")))
+    `(let ((,winconf (current-window-configuration))
+	   (,buffer (buffer-name (current-buffer)))
+	   (,article (and gnus-article-reply (gnus-summary-article-number)))
+	   (message-header-setup-hook
+	    (copy-sequence message-header-setup-hook)))
+       (add-hook 'message-header-setup-hook 'gnus-inews-insert-gcc)
+       (add-hook 'message-header-setup-hook 'gnus-inews-insert-archive-gcc)
+       (unwind-protect
+	   ,@forms
+	 (gnus-inews-add-send-actions ,winconf ,buffer ,article)
+	 (setq gnus-message-buffer (current-buffer))
+	 (make-local-variable 'gnus-newsgroup-name)
+	 (run-hooks 'gnus-message-setup-hook))
+       (gnus-configure-windows ,config t)
+       (set-buffer-modified-p nil))))
+
+(defun gnus-inews-add-send-actions (winconf buffer article)
+  (make-local-hook 'message-sent-hook)
+  (add-hook 'message-sent-hook 'gnus-inews-do-gcc nil t)
+  (setq message-post-method
+	`(lambda (arg)
+	   (gnus-post-method arg ,gnus-newsgroup-name)))
+  (setq message-newsreader (setq message-mailer (gnus-extended-version)))
+  (message-add-action
+   `(set-window-configuration ,winconf) 'exit 'postpone 'kill)
+  (message-add-action
+   `(when (buffer-name (get-buffer ,buffer))
+      (save-excursion
+	(set-buffer (get-buffer ,buffer))
+	,(when article
+	   `(gnus-summary-mark-article-as-replied ,article))))
+   'send))
+
+(put 'gnus-setup-message 'lisp-indent-function 1)
+(put 'gnus-setup-message 'edebug-form-spec '(form body))
+
+;;; Post news commands of Gnus group mode and summary mode
+
+(defun gnus-group-mail ()
+  "Start composing a mail."
+  (interactive)
+  (gnus-setup-message 'message
+    (message-mail)))
+
+(defun gnus-group-post-news (&optional arg)
+  "Start composing a news message.
+If ARG, post to the group under point.
+If ARG is 1, prompt for a group name."
+  (interactive "P")
+  ;; Bind this variable here to make message mode hooks
+  ;; work ok.
+  (let ((gnus-newsgroup-name
+	 (if arg
+	     (if (= 1 (prefix-numeric-value arg))
+		 (completing-read "Newsgroup: " gnus-active-hashtb nil
+				  (gnus-read-active-file-p))
+	       (gnus-group-group-name))
+	   "")))
+    (gnus-post-news 'post gnus-newsgroup-name)))
+
+(defun gnus-summary-post-news ()
+  "Start composing a news message."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-post-news 'post gnus-newsgroup-name))
+
+(defun gnus-summary-followup (yank &optional force-news)
+  "Compose a followup to an article.
+If prefix argument YANK is non-nil, original article is yanked automatically."
+  (interactive
+   (list (and current-prefix-arg
+	      (gnus-summary-work-articles 1))))
+  (gnus-set-global-variables)
+  (when yank
+    (gnus-summary-goto-subject (car yank)))
+  (save-window-excursion
+    (gnus-summary-select-article))
+  (let ((headers (gnus-summary-article-header (gnus-summary-article-number)))
+	(gnus-newsgroup-name gnus-newsgroup-name))
+    ;; Send a followup.
+    (gnus-post-news nil gnus-newsgroup-name
+		    headers gnus-article-buffer
+		    yank nil force-news)))
+
+(defun gnus-summary-followup-with-original (n &optional force-news)
+  "Compose a followup to an article and include the original article."
+  (interactive "P")
+  (gnus-summary-followup (gnus-summary-work-articles n) force-news))
+
+(defun gnus-summary-followup-to-mail (&optional arg)
+  "Followup to the current mail message via news."
+  (interactive
+   (list (and current-prefix-arg
+	      (gnus-summary-work-articles 1))))
+  (gnus-summary-followup arg t))
+
+(defun gnus-summary-followup-to-mail-with-original (&optional arg)
+  "Followup to the current mail message via news."
+  (interactive "P")
+  (gnus-summary-followup (gnus-summary-work-articles arg) t))
+
+(defun gnus-inews-yank-articles (articles)
+  (let (beg article)
+    (message-goto-body)
+    (while (setq article (pop articles))
+      (save-window-excursion
+	(set-buffer gnus-summary-buffer)
+	(gnus-summary-select-article nil nil nil article)
+	(gnus-summary-remove-process-mark article))
+      (gnus-copy-article-buffer)
+      (let ((message-reply-buffer gnus-article-copy)
+	    (message-reply-headers gnus-current-headers))
+	(message-yank-original)
+	(setq beg (or beg (mark t))))
+      (when articles
+	(insert "\n")))
+    (push-mark)
+    (goto-char beg)))
+
+(defun gnus-summary-cancel-article (n)
+  "Cancel an article you posted."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((articles (gnus-summary-work-articles n))
+	(message-post-method
+	 `(lambda (arg)
+	    (gnus-post-method nil ,gnus-newsgroup-name)))
+	article)
+    (while (setq article (pop articles))
+      (when (gnus-summary-select-article t nil nil article)
+	(when (gnus-eval-in-buffer-window gnus-original-article-buffer
+		(message-cancel-news))
+	  (gnus-summary-mark-as-read article gnus-canceled-mark)
+	  (gnus-cache-remove-article 1))
+	(gnus-article-hide-headers-if-wanted))
+      (gnus-summary-remove-process-mark article))))
+
+(defun gnus-summary-supersede-article ()
+  "Compose an article that will supersede a previous article.
+This is done simply by taking the old article and adding a Supersedes
+header line with the old Message-ID."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((article (gnus-summary-article-number)))
+    (gnus-setup-message 'reply-yank
+      (gnus-summary-select-article t)
+      (set-buffer gnus-original-article-buffer)
+      (message-supersede)
+      (push
+       `((lambda ()
+	   (when (buffer-name (get-buffer ,gnus-summary-buffer))
+	     (save-excursion
+	       (set-buffer (get-buffer ,gnus-summary-buffer))
+	       (gnus-cache-possibly-remove-article ,article nil nil nil t)
+	       (gnus-summary-mark-as-read ,article gnus-canceled-mark)))))
+       message-send-actions))))
+
+
+
+(defun gnus-copy-article-buffer (&optional article-buffer)
+  ;; make a copy of the article buffer with all text properties removed
+  ;; this copy is in the buffer gnus-article-copy.
+  ;; if ARTICLE-BUFFER is nil, gnus-article-buffer is used
+  ;; this buffer should be passed to all mail/news reply/post routines.
+  (setq gnus-article-copy (get-buffer-create " *gnus article copy*"))
+  (buffer-disable-undo gnus-article-copy)
+  (or (memq gnus-article-copy gnus-buffer-list)
+      (push gnus-article-copy gnus-buffer-list))
+  (let ((article-buffer (or article-buffer gnus-article-buffer))
+	end beg contents)
+    (if (not (and (get-buffer article-buffer)
+		  (buffer-name (get-buffer article-buffer))))
+	(error "Can't find any article buffer")
+      (save-excursion
+	(set-buffer article-buffer)
+	(save-restriction
+	  ;; Copy over the (displayed) article buffer, delete
+	  ;; hidden text and remove text properties.
+	  (widen)
+	  (copy-to-buffer gnus-article-copy (point-min) (point-max))
+	  (set-buffer gnus-article-copy)
+	  (gnus-article-delete-text-of-type 'annotation)
+	  (gnus-remove-text-with-property 'gnus-prev)
+	  (gnus-remove-text-with-property 'gnus-next)
+	  (insert
+	   (prog1
+	       (format "%s" (buffer-string))
+	     (erase-buffer)))
+	  ;; Find the original headers.
+	  (set-buffer gnus-original-article-buffer)
+	  (goto-char (point-min))
+	  (while (looking-at message-unix-mail-delimiter)
+	    (forward-line 1))
+	  (setq beg (point))
+	  (setq end (or (search-forward "\n\n" nil t) (point)))
+	  ;; Delete the headers from the displayed articles.
+	  (set-buffer gnus-article-copy)
+	  (delete-region (goto-char (point-min))
+			 (or (search-forward "\n\n" nil t) (point)))
+	  ;; Insert the original article headers.
+	  (insert-buffer-substring gnus-original-article-buffer beg end)
+	  (gnus-article-decode-rfc1522)))
+      gnus-article-copy)))
+
+(defun gnus-post-news (post &optional group header article-buffer yank subject
+			    force-news)
+  (when article-buffer
+    (gnus-copy-article-buffer))
+  (let ((gnus-article-reply article-buffer)
+	(add-to-list gnus-add-to-list))
+    (gnus-setup-message (cond (yank 'reply-yank)
+			      (article-buffer 'reply)
+			      (t 'message))
+      (let* ((group (or group gnus-newsgroup-name))
+	     (pgroup group)
+	     to-address to-group mailing-list to-list
+	     newsgroup-p)
+	(when group
+	  (setq to-address (gnus-group-find-parameter group 'to-address)
+		to-group (gnus-group-find-parameter group 'to-group)
+		to-list (gnus-group-find-parameter group 'to-list)
+		newsgroup-p (gnus-group-find-parameter group 'newsgroup)
+		mailing-list (when gnus-mailing-list-groups
+			       (string-match gnus-mailing-list-groups group))
+		group (gnus-group-real-name group)))
+	(if (or (and to-group
+		     (gnus-news-group-p to-group))
+		newsgroup-p
+		force-news
+		(and (gnus-news-group-p
+		      (or pgroup gnus-newsgroup-name)
+		      (if header (mail-header-number header)
+			gnus-current-article))
+		     (not mailing-list)
+		     (not to-list)
+		     (not to-address)))
+	    ;; This is news.
+	    (if post
+		(message-news (or to-group group))
+	      (set-buffer gnus-article-copy)
+	      (message-followup (if (or newsgroup-p force-news) nil to-group)))
+	  ;; The is mail.
+	  (if post
+	      (progn
+		(message-mail (or to-address to-list))
+		;; Arrange for mail groups that have no `to-address' to
+		;; get that when the user sends off the mail.
+		(when (and (not to-list)
+			   (not to-address)
+			   add-to-list)
+		  (push (list 'gnus-inews-add-to-address pgroup)
+			message-send-actions)))
+	    (set-buffer gnus-article-copy)
+	    (message-wide-reply to-address
+				(gnus-group-find-parameter
+				 gnus-newsgroup-name 'broken-reply-to))))
+	(when yank
+	  (gnus-inews-yank-articles yank))))))
+
+(defun gnus-post-method (arg group &optional silent)
+  "Return the posting method based on GROUP and ARG.
+If SILENT, don't prompt the user."
+  (let ((group-method (gnus-find-method-for-group group)))
+    (cond
+     ;; If the group-method is nil (which shouldn't happen) we use
+     ;; the default method.
+     ((null group-method)
+      (or gnus-post-method gnus-select-method message-post-method))
+     ;; We want this group's method.
+     ((and arg (not (eq arg 0)))
+      group-method)
+     ;; We query the user for a post method.
+     ((or arg
+	  (and gnus-post-method
+	       (listp (car gnus-post-method))))
+      (let* ((methods
+	      ;; Collect all methods we know about.
+	      (append
+	       (when gnus-post-method
+		 (if (listp (car gnus-post-method))
+		     gnus-post-method
+		   (list gnus-post-method)))
+	       gnus-secondary-select-methods
+	       (list gnus-select-method)
+	       (list group-method)))
+	     method-alist post-methods method)
+	;; Weed out all mail methods.
+	(while methods
+	  (setq method (gnus-server-get-method "" (pop methods)))
+	  (when (or (gnus-method-option-p method 'post)
+		    (gnus-method-option-p method 'post-mail))
+	    (push method post-methods)))
+	;; Create a name-method alist.
+	(setq method-alist
+	      (mapcar
+	       (lambda (m)
+		 (list (concat (cadr m) " (" (symbol-name (car m)) ")") m))
+	       post-methods))
+	;; Query the user.
+	(cadr
+	 (assoc
+	  (setq gnus-last-posting-server
+		(if (and silent
+			 gnus-last-posting-server)
+		    ;; Just use the last value.
+		    gnus-last-posting-server
+		  (completing-read
+		   "Posting method: " method-alist nil t
+		   (cons (or gnus-last-posting-server "") 0))))
+	  method-alist))))
+     ;; Override normal method.
+     (gnus-post-method
+      gnus-post-method)
+     ;; Use the normal select method.
+     (t gnus-select-method))))
+
+;;;
+;;; Check whether the message has been sent already.
+;;;
+
+(defvar gnus-inews-sent-ids nil)
+
+(defun gnus-inews-reject-message ()
+  "Check whether this message has already been sent."
+  (when gnus-sent-message-ids-file
+    (let ((message-id (save-restriction (message-narrow-to-headers)
+					(mail-fetch-field "message-id")))
+	  end)
+      (when message-id
+	(unless gnus-inews-sent-ids
+	  (ignore-errors
+	    (load t t t)))
+	(if (member message-id gnus-inews-sent-ids)
+	    ;; Reject this message.
+	    (not (gnus-yes-or-no-p
+		  (format "Message %s already sent.  Send anyway? "
+			  message-id)))
+	  (push message-id gnus-inews-sent-ids)
+	  ;; Chop off the last Message-IDs.
+	  (when (setq end (nthcdr gnus-sent-message-ids-length
+				  gnus-inews-sent-ids))
+	    (setcdr end nil))
+	  (nnheader-temp-write gnus-sent-message-ids-file
+	    (gnus-prin1 `(setq gnus-inews-sent-ids ',gnus-inews-sent-ids)))
+	  nil)))))
+
+
+
+;; Dummy to avoid byte-compile warning.
+(defvar nnspool-rejected-article-hook)
+
+;;; Since the X-Newsreader/X-Mailer are ``vanity'' headers, they might
+;;; as well include the Emacs version as well.
+;;; The following function works with later GNU Emacs, and XEmacs.
+(defun gnus-extended-version ()
+  "Stringified Gnus version and Emacs version"
+  (interactive)
+  (concat
+   gnus-version
+   "/"
+   (cond
+    ((string-match "^\\([0-9]+\\.[0-9]+\\)\\.[.0-9]+$" emacs-version)
+     (concat "Emacs " (substring emacs-version
+				 (match-beginning 1)
+				 (match-end 1))))
+    ((string-match "\\([A-Z]*[Mm][Aa][Cc][Ss]\\)[^(]*\\(\\((beta.*)\\|'\\)\\)?"
+		   emacs-version)
+     (concat (substring emacs-version
+			(match-beginning 1)
+			(match-end 1))
+	     (format " %d.%d" emacs-major-version emacs-minor-version)
+	     (if (match-beginning 3)
+		 (substring emacs-version
+			    (match-beginning 3)
+			    (match-end 3))
+	       "")))
+    (t emacs-version))))
+
+;; Written by "Mr. Per Persson" <pp@gnu.ai.mit.edu>.
+(defun gnus-inews-insert-mime-headers ()
+  (goto-char (point-min))
+  (let ((mail-header-separator
+	 (progn
+	   (goto-char (point-min))
+	   (if (and (search-forward (concat "\n" mail-header-separator "\n")
+				    nil t)
+		    (not (search-backward "\n\n" nil t)))
+	       mail-header-separator
+	     ""))))
+    (or (mail-position-on-field "Mime-Version")
+	(insert "1.0")
+	(cond ((save-restriction
+		 (widen)
+		 (goto-char (point-min))
+		 (re-search-forward "[\200-\377]" nil t))
+	       (or (mail-position-on-field "Content-Type")
+		   (insert "text/plain; charset=ISO-8859-1"))
+	       (or (mail-position-on-field "Content-Transfer-Encoding")
+		   (insert "8bit")))
+	      (t (or (mail-position-on-field "Content-Type")
+		     (insert "text/plain; charset=US-ASCII"))
+		 (or (mail-position-on-field "Content-Transfer-Encoding")
+		     (insert "7bit")))))))
+
+
+;;;
+;;; Gnus Mail Functions
+;;;
+
+;;; Mail reply commands of Gnus summary mode
+
+(defun gnus-summary-reply (&optional yank wide)
+  "Start composing a reply mail to the current message.
+If prefix argument YANK is non-nil, the original article is yanked
+automatically."
+  (interactive
+   (list (and current-prefix-arg
+	      (gnus-summary-work-articles 1))))
+  ;; Stripping headers should be specified with mail-yank-ignored-headers.
+  (gnus-set-global-variables)
+  (when yank
+    (gnus-summary-goto-subject (car yank)))
+  (let ((gnus-article-reply t))
+    (gnus-setup-message (if yank 'reply-yank 'reply)
+      (gnus-summary-select-article)
+      (set-buffer (gnus-copy-article-buffer))
+      (message-reply nil wide (gnus-group-find-parameter
+			       gnus-newsgroup-name 'broken-reply-to))
+      (when yank
+	(gnus-inews-yank-articles yank)))))
+
+(defun gnus-summary-reply-with-original (n &optional wide)
+  "Start composing a reply mail to the current message.
+The original article will be yanked."
+  (interactive "P")
+  (gnus-summary-reply (gnus-summary-work-articles n) wide))
+
+(defun gnus-summary-wide-reply (&optional yank)
+  "Start composing a wide reply mail to the current message.
+If prefix argument YANK is non-nil, the original article is yanked
+automatically."
+  (interactive
+   (list (and current-prefix-arg
+	      (gnus-summary-work-articles 1))))
+  (gnus-summary-reply yank t))
+
+(defun gnus-summary-wide-reply-with-original (n)
+  "Start composing a wide reply mail to the current message.
+The original article will be yanked."
+  (interactive "P")
+  (gnus-summary-reply-with-original n t))
+
+(defun gnus-summary-mail-forward (&optional full-headers post)
+  "Forward the current message to another user.
+If FULL-HEADERS (the prefix), include full headers when forwarding."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (gnus-setup-message 'forward
+    (gnus-summary-select-article)
+    (set-buffer gnus-original-article-buffer)
+    (let ((message-included-forward-headers
+	   (if full-headers "" message-included-forward-headers)))
+      (message-forward post))))
+
+(defun gnus-summary-resend-message (address n)
+  "Resend the current article to ADDRESS."
+  (interactive "sResend message(s) to: \nP")
+  (let ((articles (gnus-summary-work-articles n))
+	article)
+    (while (setq article (pop articles))
+      (gnus-summary-select-article nil nil nil article)
+      (save-excursion
+	(set-buffer gnus-original-article-buffer)
+	(message-resend address)))))
+
+(defun gnus-summary-post-forward (&optional full-headers)
+  "Forward the current article to a newsgroup.
+If FULL-HEADERS (the prefix), include full headers when forwarding."
+  (interactive "P")
+  (gnus-summary-mail-forward full-headers t))
+
+(defvar gnus-nastygram-message
+  "The following article was inappropriately posted to %s.\n\n"
+  "Format string to insert in nastygrams.
+The current group name will be inserted at \"%s\".")
+
+(defun gnus-summary-mail-nastygram (n)
+  "Send a nastygram to the author of the current article."
+  (interactive "P")
+  (when (or gnus-expert-user
+	    (gnus-y-or-n-p
+	     "Really send a nastygram to the author of the current article? "))
+    (let ((group gnus-newsgroup-name))
+      (gnus-summary-reply-with-original n)
+      (set-buffer gnus-message-buffer)
+      (message-goto-body)
+      (insert (format gnus-nastygram-message group))
+      (message-send-and-exit))))
+
+(defun gnus-summary-mail-crosspost-complaint (n)
+  "Send a complaint about crossposting to the current article(s)."
+  (interactive "P")
+  (let ((articles (gnus-summary-work-articles n))
+	article)
+    (while (setq article (pop articles))
+      (set-buffer gnus-summary-buffer)
+      (gnus-summary-goto-subject article)
+      (let ((group (gnus-group-real-name gnus-newsgroup-name))
+	    newsgroups followup-to)
+	(gnus-summary-select-article)
+	(set-buffer gnus-original-article-buffer)
+	(if (and (<= (length (message-tokenize-header
+			      (setq newsgroups (mail-fetch-field "newsgroups"))
+			      ", "))
+		     1)
+		 (or (not (setq followup-to (mail-fetch-field "followup-to")))
+		     (not (member group (message-tokenize-header
+					 followup-to ", ")))))
+	    (if followup-to
+		(gnus-message 1 "Followup-to restricted")
+	      (gnus-message 1 "Not a crossposted article"))
+	  (set-buffer gnus-summary-buffer)
+	  (gnus-summary-reply-with-original 1)
+	  (set-buffer gnus-message-buffer)
+	  (message-goto-body)
+	  (insert (format gnus-crosspost-complaint newsgroups group))
+	  (message-goto-subject)
+	  (re-search-forward " *$")
+	  (replace-match " (crosspost notification)" t t)
+	  (when (gnus-y-or-n-p "Send this complaint? ")
+	    (message-send-and-exit)))))))
+
+(defun gnus-summary-mail-other-window ()
+  "Compose mail in other window."
+  (interactive)
+  (gnus-setup-message 'message
+    (message-mail)))
+
+(defun gnus-mail-parse-comma-list ()
+  (let (accumulated
+	beg)
+    (skip-chars-forward " ")
+    (while (not (eobp))
+      (setq beg (point))
+      (skip-chars-forward "^,")
+      (while (zerop
+	      (save-excursion
+		(save-restriction
+		  (let ((i 0))
+		    (narrow-to-region beg (point))
+		    (goto-char beg)
+		    (logand (progn
+			      (while (search-forward "\"" nil t)
+				(incf i))
+			      (if (zerop i) 2 i))
+			    2)))))
+	(skip-chars-forward ",")
+	(skip-chars-forward "^,"))
+      (skip-chars-backward " ")
+      (push (buffer-substring beg (point))
+	    accumulated)
+      (skip-chars-forward "^,")
+      (skip-chars-forward ", "))
+    accumulated))
+
+(defun gnus-inews-add-to-address (group)
+  (let ((to-address (mail-fetch-field "to")))
+    (when (and to-address
+	       (gnus-alive-p))
+      ;; This mail group doesn't have a `to-list', so we add one
+      ;; here.  Magic!
+      (when (gnus-y-or-n-p
+	     (format "Do you want to add this as `to-list': %s " to-address))
+	(gnus-group-add-parameter group (cons 'to-list to-address))))))
+
+(defun gnus-put-message ()
+  "Put the current message in some group and return to Gnus."
+  (interactive)
+  (let ((reply gnus-article-reply)
+	(winconf gnus-prev-winconf)
+	(group gnus-newsgroup-name))
+
+    (or (and group (not (gnus-group-read-only-p group)))
+	(setq group (read-string "Put in group: " nil
+				 (gnus-writable-groups))))
+    (when (gnus-gethash group gnus-newsrc-hashtb)
+      (error "No such group: %s" group))
+
+    (save-excursion
+      (save-restriction
+	(widen)
+	(message-narrow-to-headers)
+	(let (gnus-deletable-headers)
+	  (if (message-news-p)
+	      (message-generate-headers message-required-news-headers)
+	    (message-generate-headers message-required-mail-headers)))
+	(goto-char (point-max))
+	(insert "Gcc: " group "\n")
+	(widen)))
+
+    (gnus-inews-do-gcc)
+
+    (when (get-buffer gnus-group-buffer)
+      (when (gnus-buffer-exists-p (car-safe reply))
+	(set-buffer (car reply))
+	(and (cdr reply)
+	     (gnus-summary-mark-article-as-replied
+	      (cdr reply))))
+      (when winconf
+	(set-window-configuration winconf)))))
+
+(defun gnus-article-mail (yank)
+  "Send a reply to the address near point.
+If YANK is non-nil, include the original article."
+  (interactive "P")
+  (let ((address
+	 (buffer-substring
+	  (save-excursion (re-search-backward "[ \t\n]" nil t) (1+ (point)))
+	  (save-excursion (re-search-forward "[ \t\n]" nil t) (1- (point))))))
+    (when address
+      (message-reply address)
+      (when yank
+	(gnus-inews-yank-articles (list (cdr gnus-article-current)))))))
+
+(defvar nntp-server-type)
+(defun gnus-bug ()
+  "Send a bug report to the Gnus maintainers."
+  (interactive)
+  (unless (gnus-alive-p)
+    (error "Gnus has been shut down"))
+  (gnus-setup-message 'bug
+    (delete-other-windows)
+    (switch-to-buffer "*Gnus Help Bug*")
+    (erase-buffer)
+    (insert gnus-bug-message)
+    (goto-char (point-min))
+    (message-pop-to-buffer "*Gnus Bug*")
+    (message-setup `((To . ,gnus-maintainer) (Subject . "")))
+    (push `(gnus-bug-kill-buffer) message-send-actions)
+    (goto-char (point-min))
+    (re-search-forward (concat "^" (regexp-quote mail-header-separator) "$"))
+    (forward-line 1)
+    (insert (gnus-version) "\n")
+    (insert (emacs-version) "\n")
+    (when (and (boundp 'nntp-server-type)
+	       (stringp nntp-server-type))
+      (insert nntp-server-type))
+    (insert "\n\n\n\n\n")
+    (gnus-debug)
+    (goto-char (point-min))
+    (search-forward "Subject: " nil t)
+    (message "")))
+
+(defun gnus-bug-kill-buffer ()
+  (when (get-buffer "*Gnus Help Bug*")
+    (kill-buffer "*Gnus Help Bug*")))
+
+(defun gnus-debug ()
+  "Attempts to go through the Gnus source file and report what variables have been changed.
+The source file has to be in the Emacs load path."
+  (interactive)
+  (let ((files '("gnus.el" "gnus-sum.el" "gnus-group.el"
+		 "gnus-art.el" "gnus-start.el" "gnus-async.el"
+		 "gnus-msg.el" "gnus-score.el" "gnus-win.el" "gnus-topic.el"
+		 "nnmail.el" "message.el"))
+	file expr olist sym)
+    (gnus-message 4 "Please wait while we snoop your variables...")
+    (sit-for 0)
+    ;; Go through all the files looking for non-default values for variables.
+    (save-excursion
+      (set-buffer (get-buffer-create " *gnus bug info*"))
+      (buffer-disable-undo (current-buffer))
+      (while files
+	(erase-buffer)
+	(when (and (setq file (locate-library (pop files)))
+		   (file-exists-p file))
+	  (insert-file-contents file)
+	  (goto-char (point-min))
+	  (if (not (re-search-forward "^;;* *Internal variables" nil t))
+	      (gnus-message 4 "Malformed sources in file %s" file)
+	    (narrow-to-region (point-min) (point))
+	    (goto-char (point-min))
+	    (while (setq expr (ignore-errors (read (current-buffer))))
+	      (ignore-errors
+		(and (or (eq (car expr) 'defvar)
+			 (eq (car expr) 'defcustom))
+		     (stringp (nth 3 expr))
+		     (or (not (boundp (nth 1 expr)))
+			 (not (equal (eval (nth 2 expr))
+				     (symbol-value (nth 1 expr)))))
+		     (push (nth 1 expr) olist)))))))
+      (kill-buffer (current-buffer)))
+    (when (setq olist (nreverse olist))
+      (insert "------------------ Environment follows ------------------\n\n"))
+    (while olist
+      (if (boundp (car olist))
+	  (condition-case ()
+	      (pp `(setq ,(car olist)
+			 ,(if (or (consp (setq sym (symbol-value (car olist))))
+				  (and (symbolp sym)
+				       (not (or (eq sym nil)
+						(eq sym t)))))
+			      (list 'quote (symbol-value (car olist)))
+			    (symbol-value (car olist))))
+		  (current-buffer))
+	    (error
+	     (format "(setq %s 'whatever)\n" (car olist))))
+	(insert ";; (makeunbound '" (symbol-name (car olist)) ")\n"))
+      (setq olist (cdr olist)))
+    (insert "\n\n")
+    ;; Remove any null chars - they seem to cause trouble for some
+    ;; mailers.  (Byte-compiled output from the stuff above.)
+    (goto-char (point-min))
+    (while (re-search-forward "[\000\200]" nil t)
+      (replace-match "" t t))))
+
+;;; Treatment of rejected articles.
+;;; Bounced mail.
+
+(defun gnus-summary-resend-bounced-mail (&optional fetch)
+  "Re-mail the current message.
+This only makes sense if the current message is a bounce message than
+contains some mail you have written which has been bounced back to
+you.
+If FETCH, try to fetch the article that this is a reply to, if indeed
+this is a reply."
+  (interactive "P")
+  (gnus-summary-select-article t)
+  (set-buffer gnus-original-article-buffer)
+  (gnus-setup-message 'compose-bounce
+    (let* ((references (mail-fetch-field "references"))
+	   (parent (and references (gnus-parent-id references))))
+      (message-bounce)
+      ;; If there are references, we fetch the article we answered to.
+      (and fetch parent
+	   (gnus-summary-refer-article parent)
+	   (gnus-summary-show-all-headers)))))
+
+;;; Gcc handling.
+
+;; Do Gcc handling, which copied the message over to some group.
+(defun gnus-inews-do-gcc (&optional gcc)
+  (interactive)
+  (when (gnus-alive-p)
+    (save-excursion
+      (save-restriction
+	(message-narrow-to-headers)
+	(let ((gcc (or gcc (mail-fetch-field "gcc" nil t)))
+	      (cur (current-buffer))
+	      groups group method)
+	  (when gcc
+	    (message-remove-header "gcc")
+	    (widen)
+	    (setq groups (message-tokenize-header gcc " ,"))
+	    ;; Copy the article over to some group(s).
+	    (while (setq group (pop groups))
+	      (gnus-check-server
+	       (setq method
+		     (cond ((and (null (gnus-get-info group))
+				 (eq (car gnus-message-archive-method)
+				     (car
+				      (gnus-server-to-method
+				       (gnus-group-method group)))))
+			    ;; If the group doesn't exist, we assume
+			    ;; it's an archive group...
+			    gnus-message-archive-method)
+			   ;; Use the method.
+			   ((gnus-info-method (gnus-get-info group))
+			    (gnus-info-method (gnus-get-info group)))
+			   ;; Find the method.
+			   (t (gnus-group-method group)))))
+	      (gnus-check-server method)
+	      (unless (gnus-request-group group t method)
+		(gnus-request-create-group group method))
+	      (save-excursion
+		(nnheader-set-temp-buffer " *acc*")
+		(insert-buffer-substring cur)
+		(goto-char (point-min))
+		(when (re-search-forward
+		       (concat "^" (regexp-quote mail-header-separator) "$")
+		       nil t)
+		  (replace-match "" t t ))
+		(unless (gnus-request-accept-article group method t)
+		  (gnus-message 1 "Couldn't store article in group %s: %s"
+				group (gnus-status-message method))
+		  (sit-for 2))
+		(kill-buffer (current-buffer))))))))))
+
+(defun gnus-inews-insert-gcc ()
+  "Insert Gcc headers based on `gnus-outgoing-message-group'."
+  (save-excursion
+    (save-restriction
+      (message-narrow-to-headers)
+      (let* ((group gnus-outgoing-message-group)
+	     (gcc (cond
+		   ((gnus-functionp group)
+		    (funcall group))
+		   ((or (stringp group) (list group))
+		    group))))
+	(when gcc
+	  (insert "Gcc: "
+		  (if (stringp gcc) gcc
+		    (mapconcat 'identity gcc " "))
+		  "\n"))))))
+
+(defun gnus-inews-insert-archive-gcc (&optional group)
+  "Insert the Gcc to say where the article is to be archived."
+  (let* ((var gnus-message-archive-group)
+	 (group (or group gnus-newsgroup-name ""))
+	 result
+	 gcc-self-val
+	 (groups
+	  (cond
+	   ((null gnus-message-archive-method)
+	    ;; Ignore.
+	    nil)
+	   ((stringp var)
+	    ;; Just a single group.
+	    (list var))
+	   ((null var)
+	    ;; We don't want this.
+	    nil)
+	   ((and (listp var) (stringp (car var)))
+	    ;; A list of groups.
+	    var)
+	   ((gnus-functionp var)
+	    ;; A function.
+	    (funcall var group))
+	   (t
+	    ;; An alist of regexps/functions/forms.
+	    (while (and var
+			(not
+			 (setq result
+			       (cond
+				((stringp (caar var))
+				 ;; Regexp.
+				 (when (string-match (caar var) group)
+				   (cdar var)))
+				((gnus-functionp (car var))
+				 ;; Function.
+				 (funcall (car var) group))
+				(t
+				 (eval (car var)))))))
+	      (setq var (cdr var)))
+	    result)))
+	 name)
+    (when groups
+      (when (stringp groups)
+	(setq groups (list groups)))
+      (save-excursion
+	(save-restriction
+	  (message-narrow-to-headers)
+	  (goto-char (point-max))
+	  (insert "Gcc: ")
+	  (if (and gnus-newsgroup-name
+		   (setq gcc-self-val
+			 (gnus-group-find-parameter
+			  gnus-newsgroup-name 'gcc-self)))
+	      (progn
+		(insert
+		 (if (stringp gcc-self-val)
+		     gcc-self-val
+		   group))
+		(if (not (eq gcc-self-val 'none))
+		    (insert "\n")
+		  (progn
+		    (beginning-of-line)
+		    (kill-line))))
+	    (while (setq name (pop groups))
+	      (insert (if (string-match ":" name)
+			  name
+			(gnus-group-prefixed-name
+			 name gnus-message-archive-method)))
+	      (when groups
+		(insert " ")))
+	    (insert "\n")))))))
+
+(defun gnus-summary-send-draft ()
+  "Enter a mail/post buffer to edit and send the draft."
+  (interactive)
+  (gnus-set-global-variables)
+  (let (buf)
+    (if (not (setq buf (gnus-request-restore-buffer
+			(gnus-summary-article-number) gnus-newsgroup-name)))
+	(error "Couldn't restore the article")
+      (switch-to-buffer buf)
+      (when (eq major-mode 'news-reply-mode)
+	(local-set-key "\C-c\C-c" 'gnus-inews-news))
+      ;; Insert the separator.
+      (goto-char (point-min))
+      (search-forward "\n\n")
+      (forward-char -1)
+      (insert mail-header-separator)
+      ;; Configure windows.
+      (let ((gnus-draft-buffer (current-buffer)))
+	(gnus-configure-windows 'draft t)
+	(goto-char (point))))))
+
+(gnus-add-shutdown 'gnus-inews-close 'gnus)
+
+(defun gnus-inews-close ()
+  (setq gnus-inews-sent-ids nil))
+
+;;; Allow redefinition of functions.
+
+(gnus-ems-redefine)
+
+(provide 'gnus-msg)
+
+;;; gnus-msg.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-nocem.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,303 @@
+;;; gnus-nocem.el --- NoCeM pseudo-cancellation treatment
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'nnmail)
+(require 'gnus-art)
+(require 'gnus-sum)
+(require 'gnus-range)
+
+(defgroup gnus-nocem nil
+  "NoCeM pseudo-cancellation treatment"
+  :group 'gnus-score)
+
+(defcustom gnus-nocem-groups
+  '("news.lists.filters" "news.admin.net-abuse.bulletins"
+    "alt.nocem.misc" "news.admin.net-abuse.announce")
+  "List of groups that will be searched for NoCeM messages."
+  :group 'gnus-nocem
+  :type '(repeat (string :tag "Group")))
+
+(defcustom gnus-nocem-issuers
+ '("AutoMoose-1" "Automoose-1"   ; CancelMoose[tm]
+   "rbraver@ohww.norman.ok.us"   ; Robert Braver
+   "clewis@ferret.ocunix.on.ca;" ; Chris Lewis
+   "jem@xpat.com;"		 ; Despammer from Korea
+   "snowhare@xmission.com"       ; Benjamin "Snowhare" Franz
+   "red@redpoll.mrfs.oh.us (Richard E. Depew)" ; ARMM! ARMM!
+   )
+  "List of NoCeM issuers to pay attention to."
+  :group 'gnus-nocem
+  :type '(repeat string))
+
+(defcustom gnus-nocem-directory
+  (nnheader-concat gnus-article-save-directory "NoCeM/")
+  "*Directory where NoCeM files will be stored."
+  :group 'gnus-nocem
+  :type 'directory)
+
+(defcustom gnus-nocem-expiry-wait 15
+  "*Number of days to keep NoCeM headers in the cache."
+  :group 'gnus-nocem
+  :type 'integer)
+
+(defcustom gnus-nocem-verifyer 'mc-verify
+  "*Function called to verify that the NoCeM message is valid.
+One likely value is `mc-verify'.  If the function in this variable
+isn't bound, the message will be used unconditionally."
+  :group 'gnus-nocem
+  :type '(radio (function-item mc-verify)
+		(function :tag "other")))
+
+(defcustom gnus-nocem-liberal-fetch nil
+  "*If t try to fetch all messages which have @@NCM in the subject.
+Otherwise don't fetch messages which have references or whose message-id
+matches an previously scanned and verified nocem message."
+  :group 'gnus-nocem
+  :type 'boolean)
+
+;;; Internal variables
+
+(defvar gnus-nocem-active nil)
+(defvar gnus-nocem-alist nil)
+(defvar gnus-nocem-touched-alist nil)
+(defvar gnus-nocem-hashtb nil)
+(defvar gnus-nocem-seen-message-ids nil)
+
+;;; Functions
+
+(defun gnus-nocem-active-file ()
+  (concat (file-name-as-directory gnus-nocem-directory) "active"))
+
+(defun gnus-nocem-cache-file ()
+  (concat (file-name-as-directory gnus-nocem-directory) "cache"))
+
+(defun gnus-nocem-scan-groups ()
+  "Scan all NoCeM groups for new NoCeM messages."
+  (interactive)
+  (let ((groups gnus-nocem-groups)
+	(gnus-inhibit-demon t)
+	group active gactive articles)
+    (gnus-make-directory gnus-nocem-directory)
+    ;; Load any previous NoCeM headers.
+    (gnus-nocem-load-cache)
+    ;; Read the active file if it hasn't been read yet.
+    (and (file-exists-p (gnus-nocem-active-file))
+	 (not gnus-nocem-active)
+	 (ignore-errors
+	   (load (gnus-nocem-active-file) t t t)))
+    ;; Go through all groups and see whether new articles have
+    ;; arrived.
+    (while (setq group (pop groups))
+      (if (not (setq gactive (gnus-activate-group group)))
+	  ()				; This group doesn't exist.
+	(setq active (nth 1 (assoc group gnus-nocem-active)))
+	(when (and (not (< (cdr gactive) (car gactive))) ; Empty group.
+		   (or (not active)
+		       (< (cdr active) (cdr gactive))))
+	  ;; Ok, there are new articles in this group, se we fetch the
+	  ;; headers.
+	  (save-excursion
+	    (let ((dependencies (make-vector 10 nil))
+		  headers header)
+	      (nnheader-temp-write nil
+		(setq headers
+		      (if (eq 'nov
+			      (gnus-retrieve-headers
+			       (setq articles
+				     (gnus-uncompress-range
+				      (cons
+				       (if active (1+ (cdr active))
+					 (car gactive))
+				       (cdr gactive))))
+			       group))
+			  (gnus-get-newsgroup-headers-xover
+			   articles nil dependencies)
+			(gnus-get-newsgroup-headers dependencies)))
+		(while (setq header (pop headers))
+		  ;; We take a closer look on all articles that have
+		  ;; "@@NCM" in the subject.  Unless we already read
+		  ;; this cross posted message.  Nocem messages
+		  ;; are not allowed to have references, so we can
+		  ;; ignore scanning followups.
+		  (and (string-match "@@NCM" (mail-header-subject header))
+		       (or gnus-nocem-liberal-fetch
+			   (and (or (string= "" (mail-header-references
+						 header))
+				    (null (mail-header-references header)))
+				(not (member (mail-header-message-id header)
+					     gnus-nocem-seen-message-ids))))
+		       (gnus-nocem-check-article group header)))))))
+	(setq gnus-nocem-active
+	      (cons (list group gactive)
+		    (delq (assoc group gnus-nocem-active)
+			  gnus-nocem-active)))))
+    ;; Save the results, if any.
+    (gnus-nocem-save-cache)
+    (gnus-nocem-save-active)))
+
+(defun gnus-nocem-check-article (group header)
+  "Check whether the current article is an NCM article and that we want it."
+  ;; Get the article.
+  (gnus-message 7 "Checking article %d in %s for NoCeM..."
+		(mail-header-number header) group)
+  (let ((date (mail-header-date header))
+	issuer b e)
+    (when (or (not date)
+	      (nnmail-time-less
+	       (nnmail-time-since (nnmail-date-to-time date))
+	       (nnmail-days-to-time gnus-nocem-expiry-wait)))
+      (gnus-request-article-this-buffer (mail-header-number header) group)
+      (goto-char (point-min))
+      (when (re-search-forward "-----BEGIN PGP MESSAGE-----" nil t)
+	(delete-region (point-min) (match-beginning 0)))
+      (when (re-search-forward "-----END PGP MESSAGE-----\n?" nil t)
+	(delete-region (match-end 0) (point-max)))
+      (goto-char (point-min))
+      ;; The article has to have proper NoCeM headers.
+      (when (and (setq b (search-forward "\n@@BEGIN NCM HEADERS\n" nil t))
+		 (setq e (search-forward "\n@@BEGIN NCM BODY\n" nil t)))
+	;; We get the name of the issuer.
+	(narrow-to-region b e)
+	(setq issuer (mail-fetch-field "issuer"))
+	(widen)
+	(and (member issuer gnus-nocem-issuers) ; We like her....
+	     (gnus-nocem-verify-issuer issuer) ; She is who she says she is...
+	     (gnus-nocem-enter-article)	; We gobble the message..
+	     (push (mail-header-message-id header) ; But don't come back for
+		   gnus-nocem-seen-message-ids)))))) ; second helpings.
+
+(defun gnus-nocem-verify-issuer (person)
+  "Verify using PGP that the canceler is who she says she is."
+  (if (fboundp gnus-nocem-verifyer)
+      (funcall gnus-nocem-verifyer)
+    ;; If we don't have Mailcrypt, then we use the message anyway.
+    t))
+
+(defun gnus-nocem-enter-article ()
+  "Enter the current article into the NoCeM cache."
+  (goto-char (point-min))
+  (let ((b (search-forward "\n@@BEGIN NCM BODY\n" nil t))
+	(e (search-forward "\n@@END NCM BODY\n" nil t))
+	(buf (current-buffer))
+	ncm id group)
+    (when (and b e)
+      (narrow-to-region b (1+ (match-beginning 0)))
+      (goto-char (point-min))
+      (while (search-forward "\t" nil t)
+	(cond
+	 ((not (ignore-errors
+		 (setq group (let ((obarray gnus-active-hashtb)) (read buf)))))
+	  ;; An error.
+	  )
+	 ((not (symbolp group))
+	  ;; Ignore invalid entries.
+	  )
+	 ((not (boundp group))
+	  ;; Make sure all entries in the hashtb are bound.
+	  (set group nil))
+	 (t
+	  (when (gnus-gethash (symbol-name group) gnus-newsrc-hashtb)
+	    ;; Valid group.
+	    (beginning-of-line)
+	    (while (= (following-char) ?\t)
+	      (forward-line -1))
+	    (setq id (buffer-substring (point) (1- (search-forward "\t"))))
+	    (unless (gnus-gethash id gnus-nocem-hashtb)
+	      ;; only store if not already present
+	      (gnus-sethash id t gnus-nocem-hashtb)
+	      (push id ncm))
+	    (forward-line 1)
+	    (while (= (following-char) ?\t)
+	      (forward-line 1))))))
+      (when ncm
+	(setq gnus-nocem-touched-alist t)
+	(push (cons (let ((time (current-time))) (setcdr (cdr time) nil) time)
+		    ncm)
+	      gnus-nocem-alist))
+      t)))
+
+(defun gnus-nocem-load-cache ()
+  "Load the NoCeM cache."
+  (interactive)
+  (unless gnus-nocem-alist
+    ;; The buffer doesn't exist, so we create it and load the NoCeM
+    ;; cache.
+    (when (file-exists-p (gnus-nocem-cache-file))
+      (load (gnus-nocem-cache-file) t t t)
+      (gnus-nocem-alist-to-hashtb))))
+
+(defun gnus-nocem-save-cache ()
+  "Save the NoCeM cache."
+  (when (and gnus-nocem-alist
+	     gnus-nocem-touched-alist)
+    (nnheader-temp-write (gnus-nocem-cache-file)
+      (gnus-prin1 `(setq gnus-nocem-alist ',gnus-nocem-alist)))
+    (setq gnus-nocem-touched-alist nil)))
+
+(defun gnus-nocem-save-active ()
+  "Save the NoCeM active file."
+  (nnheader-temp-write (gnus-nocem-active-file)
+    (gnus-prin1 `(setq gnus-nocem-active ',gnus-nocem-active))))
+
+(defun gnus-nocem-alist-to-hashtb ()
+  "Create a hashtable from the Message-IDs we have."
+  (let* ((alist gnus-nocem-alist)
+	 (pprev (cons nil alist))
+	 (prev pprev)
+	 (expiry (nnmail-days-to-time gnus-nocem-expiry-wait))
+	 entry)
+    (setq gnus-nocem-hashtb (gnus-make-hashtable (* (length alist) 51)))
+    (while (setq entry (car alist))
+      (if (not (nnmail-time-less (nnmail-time-since (car entry)) expiry))
+	  ;; This entry has expired, so we remove it.
+	  (setcdr prev (cdr alist))
+	(setq prev alist)
+	;; This is ok, so we enter it into the hashtable.
+	(setq entry (cdr entry))
+	(while entry
+	  (gnus-sethash (car entry) t gnus-nocem-hashtb)
+	  (setq entry (cdr entry))))
+      (setq alist (cdr alist)))))
+
+(gnus-add-shutdown 'gnus-nocem-close 'gnus)
+
+(defun gnus-nocem-close ()
+  "Clear internal NoCeM variables."
+  (setq gnus-nocem-alist nil
+	gnus-nocem-hashtb nil
+	gnus-nocem-active nil
+	gnus-nocem-touched-alist nil
+	gnus-nocem-seen-message-ids nil))
+
+(defun gnus-nocem-unwanted-article-p (id)
+  "Say whether article ID in the current group is wanted."
+  (gnus-gethash id gnus-nocem-hashtb))
+
+(provide 'gnus-nocem)
+
+;;; gnus-nocem.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-range.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,281 @@
+;;; gnus-range.el --- range and sequence functions for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+;;; List and range functions
+
+(defun gnus-last-element (list)
+  "Return last element of LIST."
+  (while (cdr list)
+    (setq list (cdr list)))
+  (car list))
+
+(defun gnus-copy-sequence (list)
+  "Do a complete, total copy of a list."
+  (let (out)
+    (while (consp list)
+      (if (consp (car list))
+	  (push (gnus-copy-sequence (pop list)) out)
+	(push (pop list) out)))
+    (if list
+	(nconc (nreverse out) list)
+      (nreverse out))))
+
+(defun gnus-set-difference (list1 list2)
+  "Return a list of elements of LIST1 that do not appear in LIST2."
+  (let ((list1 (copy-sequence list1)))
+    (while list2
+      (setq list1 (delq (car list2) list1))
+      (setq list2 (cdr list2)))
+    list1))
+
+(defun gnus-sorted-complement (list1 list2)
+  "Return a list of elements of LIST1 that do not appear in LIST2.
+Both lists have to be sorted over <."
+  (let (out)
+    (if (or (null list1) (null list2))
+	(or list1 list2)
+      (while (and list1 list2)
+	(cond ((= (car list1) (car list2))
+	       (setq list1 (cdr list1)
+		     list2 (cdr list2)))
+	      ((< (car list1) (car list2))
+	       (setq out (cons (car list1) out))
+	       (setq list1 (cdr list1)))
+	      (t
+	       (setq out (cons (car list2) out))
+	       (setq list2 (cdr list2)))))
+      (nconc (nreverse out) (or list1 list2)))))
+
+(defun gnus-intersection (list1 list2)
+  (let ((result nil))
+    (while list2
+      (when (memq (car list2) list1)
+	(setq result (cons (car list2) result)))
+      (setq list2 (cdr list2)))
+    result))
+
+(defun gnus-sorted-intersection (list1 list2)
+  ;; LIST1 and LIST2 have to be sorted over <.
+  (let (out)
+    (while (and list1 list2)
+      (cond ((= (car list1) (car list2))
+	     (setq out (cons (car list1) out)
+		   list1 (cdr list1)
+		   list2 (cdr list2)))
+	    ((< (car list1) (car list2))
+	     (setq list1 (cdr list1)))
+	    (t
+	     (setq list2 (cdr list2)))))
+    (nreverse out)))
+
+(defun gnus-set-sorted-intersection (list1 list2)
+  ;; LIST1 and LIST2 have to be sorted over <.
+  ;; This function modifies LIST1.
+  (let* ((top (cons nil list1))
+	 (prev top))
+    (while (and list1 list2)
+      (cond ((= (car list1) (car list2))
+	     (setq prev list1
+		   list1 (cdr list1)
+		   list2 (cdr list2)))
+	    ((< (car list1) (car list2))
+	     (setcdr prev (cdr list1))
+	     (setq list1 (cdr list1)))
+	    (t
+	     (setq list2 (cdr list2)))))
+    (setcdr prev nil)
+    (cdr top)))
+
+(defun gnus-compress-sequence (numbers &optional always-list)
+  "Convert list of numbers to a list of ranges or a single range.
+If ALWAYS-LIST is non-nil, this function will always release a list of
+ranges."
+  (let* ((first (car numbers))
+	 (last (car numbers))
+	 result)
+    (if (null numbers)
+	nil
+      (if (not (listp (cdr numbers)))
+	  numbers
+	(while numbers
+	  (cond ((= last (car numbers)) nil) ;Omit duplicated number
+		((= (1+ last) (car numbers)) ;Still in sequence
+		 (setq last (car numbers)))
+		(t			;End of one sequence
+		 (setq result
+		       (cons (if (= first last) first
+			       (cons first last))
+			     result))
+		 (setq first (car numbers))
+		 (setq last  (car numbers))))
+	  (setq numbers (cdr numbers)))
+	(if (and (not always-list) (null result))
+	    (if (= first last) (list first) (cons first last))
+	  (nreverse (cons (if (= first last) first (cons first last))
+			  result)))))))
+
+(defalias 'gnus-uncompress-sequence 'gnus-uncompress-range)
+(defun gnus-uncompress-range (ranges)
+  "Expand a list of ranges into a list of numbers.
+RANGES is either a single range on the form `(num . num)' or a list of
+these ranges."
+  (let (first last result)
+    (cond
+     ((null ranges)
+      nil)
+     ((not (listp (cdr ranges)))
+      (setq first (car ranges))
+      (setq last (cdr ranges))
+      (while (<= first last)
+	(setq result (cons first result))
+	(setq first (1+ first)))
+      (nreverse result))
+     (t
+      (while ranges
+	(if (atom (car ranges))
+	    (when (numberp (car ranges))
+	      (setq result (cons (car ranges) result)))
+	  (setq first (caar ranges))
+	  (setq last  (cdar ranges))
+	  (while (<= first last)
+	    (setq result (cons first result))
+	    (setq first (1+ first))))
+	(setq ranges (cdr ranges)))
+      (nreverse result)))))
+
+(defun gnus-add-to-range (ranges list)
+  "Return a list of ranges that has all articles from both RANGES and LIST.
+Note: LIST has to be sorted over `<'."
+  (if (not ranges)
+      (gnus-compress-sequence list t)
+    (setq list (copy-sequence list))
+    (unless (listp (cdr ranges))
+      (setq ranges (list ranges)))
+    (let ((out ranges)
+	  ilist lowest highest temp)
+      (while (and ranges list)
+	(setq ilist list)
+	(setq lowest (or (and (atom (car ranges)) (car ranges))
+			 (caar ranges)))
+	(while (and list (cdr list) (< (cadr list) lowest))
+	  (setq list (cdr list)))
+	(when (< (car ilist) lowest)
+	  (setq temp list)
+	  (setq list (cdr list))
+	  (setcdr temp nil)
+	  (setq out (nconc (gnus-compress-sequence ilist t) out)))
+	(setq highest (or (and (atom (car ranges)) (car ranges))
+			  (cdar ranges)))
+	(while (and list (<= (car list) highest))
+	  (setq list (cdr list)))
+	(setq ranges (cdr ranges)))
+      (when list
+	(setq out (nconc (gnus-compress-sequence list t) out)))
+      (setq out (sort out (lambda (r1 r2)
+			    (< (or (and (atom r1) r1) (car r1))
+			       (or (and (atom r2) r2) (car r2))))))
+      (setq ranges out)
+      (while ranges
+	(if (atom (car ranges))
+	    (when (cdr ranges)
+	      (if (atom (cadr ranges))
+		  (when (= (1+ (car ranges)) (cadr ranges))
+		    (setcar ranges (cons (car ranges)
+					 (cadr ranges)))
+		    (setcdr ranges (cddr ranges)))
+		(when (= (1+ (car ranges)) (caadr ranges))
+		  (setcar (cadr ranges) (car ranges))
+		  (setcar ranges (cadr ranges))
+		  (setcdr ranges (cddr ranges)))))
+	  (when (cdr ranges)
+	    (if (atom (cadr ranges))
+		(when (= (1+ (cdar ranges)) (cadr ranges))
+		  (setcdr (car ranges) (cadr ranges))
+		  (setcdr ranges (cddr ranges)))
+	      (when (= (1+ (cdar ranges)) (caadr ranges))
+		(setcdr (car ranges) (cdadr ranges))
+		(setcdr ranges (cddr ranges))))))
+	(setq ranges (cdr ranges)))
+      out)))
+
+(defun gnus-remove-from-range (ranges list)
+  "Return a list of ranges that has all articles from LIST removed from RANGES.
+Note: LIST has to be sorted over `<'."
+  ;; !!! This function shouldn't look like this, but I've got a headache.
+  (gnus-compress-sequence
+   (gnus-sorted-complement
+    (gnus-uncompress-range ranges) list)))
+
+(defun gnus-member-of-range (number ranges)
+  (if (not (listp (cdr ranges)))
+      (and (>= number (car ranges))
+	   (<= number (cdr ranges)))
+    (let ((not-stop t))
+      (while (and ranges
+		  (if (numberp (car ranges))
+		      (>= number (car ranges))
+		    (>= number (caar ranges)))
+		  not-stop)
+	(when (if (numberp (car ranges))
+		  (= number (car ranges))
+		(and (>= number (caar ranges))
+		     (<= number (cdar ranges))))
+	  (setq not-stop nil))
+	(setq ranges (cdr ranges)))
+      (not not-stop))))
+
+(defun gnus-range-length (range)
+  "Return the length RANGE would have if uncompressed."
+  (length (gnus-uncompress-range range)))
+
+(defun gnus-sublist-p (list sublist)
+  "Test whether all elements in SUBLIST are members of LIST."
+  (let ((sublistp t))
+    (while sublist
+      (unless (memq (pop sublist) list)
+	(setq sublistp nil
+	      sublist nil)))
+    sublistp))
+
+(defun gnus-range-add (range1 range2)
+  "Add RANGE2 to RANGE1 destructively."
+  (cond
+   ;; If either are nil, then the job is quite easy.
+   ((or (null range1) (null range2))
+    (or range1 range2))
+   (t
+    ;; I don't like thinking.
+    (gnus-compress-sequence
+     (sort
+      (nconc
+       (gnus-uncompress-range range1)
+       (gnus-uncompress-range range2))
+      '<)))))
+
+(provide 'gnus-range)
+
+;;; gnus-range.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-salt.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,991 @@
+;;; gnus-salt.el --- alternate summary mode interfaces for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-sum)
+
+;;;
+;;; gnus-pick-mode
+;;;
+
+(defvar gnus-pick-mode nil
+  "Minor mode for providing a pick-and-read interface in Gnus summary buffers.")
+
+(defvar gnus-pick-display-summary nil
+  "*Display summary while reading.")
+
+(defvar gnus-pick-mode-hook nil
+  "Hook run in summary pick mode buffers.")
+
+(defvar gnus-mark-unpicked-articles-as-read nil
+  "*If non-nil, mark all unpicked articles as read.")
+
+(defvar gnus-pick-elegant-flow t
+  "If non-nil, gnus-pick-start-reading will run gnus-summary-next-group when no articles have been picked.")
+
+(defvar gnus-summary-pick-line-format
+  "%-5P %U\%R\%z\%I\%(%[%4L: %-20,20n%]%) %s\n"
+  "*The format specification of the lines in pick buffers.
+It accepts the same format specs that `gnus-summary-line-format' does.")
+
+;;; Internal variables.
+
+(defvar gnus-pick-mode-map nil)
+
+(unless gnus-pick-mode-map
+  (setq gnus-pick-mode-map (make-sparse-keymap))
+
+  (gnus-define-keys
+   gnus-pick-mode-map
+   "t" gnus-uu-mark-thread
+   "T" gnus-uu-unmark-thread
+   " " gnus-pick-next-page
+   "u" gnus-summary-unmark-as-processable
+   "U" gnus-summary-unmark-all-processable
+   "v" gnus-uu-mark-over
+   "r" gnus-uu-mark-region
+   "R" gnus-uu-unmark-region
+   "e" gnus-uu-mark-by-regexp
+   "E" gnus-uu-mark-by-regexp
+   "b" gnus-uu-mark-buffer
+   "B" gnus-uu-unmark-buffer
+   "." gnus-pick-article
+   gnus-down-mouse-2 gnus-pick-mouse-pick-region
+   ;;gnus-mouse-2 gnus-pick-mouse-pick
+   "X" gnus-pick-start-reading
+   "\r" gnus-pick-start-reading))
+
+(defun gnus-pick-make-menu-bar ()
+  (unless (boundp 'gnus-pick-menu)
+    (easy-menu-define
+     gnus-pick-menu gnus-pick-mode-map ""
+     '("Pick"
+       ("Pick"
+	["Article" gnus-summary-mark-as-processable t]
+	["Thread" gnus-uu-mark-thread t]
+	["Region" gnus-uu-mark-region t]
+	["Regexp" gnus-uu-mark-regexp t]
+	["Buffer" gnus-uu-mark-buffer t])
+       ("Unpick"
+	["Article" gnus-summary-unmark-as-processable t]
+	["Thread" gnus-uu-unmark-thread t]
+	["Region" gnus-uu-unmark-region t]
+	["Regexp" gnus-uu-unmark-regexp t]
+	["Buffer" gnus-uu-unmark-buffer t])
+       ["Start reading" gnus-pick-start-reading t]
+       ["Switch pick mode off" gnus-pick-mode gnus-pick-mode]))))
+
+(defun gnus-pick-mode (&optional arg)
+  "Minor mode for providing a pick-and-read interface in Gnus summary buffers.
+
+\\{gnus-pick-mode-map}"
+  (interactive "P")
+  (when (eq major-mode 'gnus-summary-mode)
+    (if (not (set (make-local-variable 'gnus-pick-mode)
+		  (if (null arg) (not gnus-pick-mode)
+		    (> (prefix-numeric-value arg) 0))))
+	(remove-hook 'gnus-message-setup-hook 'gnus-pick-setup-message)
+      ;; Make sure that we don't select any articles upon group entry.
+      (set (make-local-variable 'gnus-auto-select-first) nil)
+      ;; Change line format.
+      (setq gnus-summary-line-format gnus-summary-pick-line-format)
+      (setq gnus-summary-line-format-spec nil)
+      (gnus-update-format-specifications nil 'summary)
+      (gnus-update-summary-mark-positions)
+      (add-hook 'gnus-message-setup-hook 'gnus-pick-setup-message)
+      (set (make-local-variable 'gnus-summary-goto-unread) 'never)
+      ;; Set up the menu.
+      (when (gnus-visual-p 'pick-menu 'menu)
+	(gnus-pick-make-menu-bar))
+      (unless (assq 'gnus-pick-mode minor-mode-alist)
+	(push '(gnus-pick-mode " Pick") minor-mode-alist))
+      (unless (assq 'gnus-pick-mode minor-mode-map-alist)
+	(push (cons 'gnus-pick-mode gnus-pick-mode-map)
+	      minor-mode-map-alist))
+      (run-hooks 'gnus-pick-mode-hook))))
+
+(defun gnus-pick-setup-message ()
+  "Make Message do the right thing on exit."
+  (when (and (gnus-buffer-live-p gnus-summary-buffer)
+	     (save-excursion
+	       (set-buffer gnus-summary-buffer)
+	       gnus-pick-mode))
+    (message-add-action
+     '(gnus-configure-windows 'pick t) 'send 'exit 'postpone 'kill)))
+
+(defvar gnus-pick-line-number 1)
+(defun gnus-pick-line-number ()
+  "Return the current line number."
+  (if (bobp)
+      (setq gnus-pick-line-number 1)
+    (incf gnus-pick-line-number)))
+
+(defun gnus-pick-start-reading (&optional catch-up)
+  "Start reading the picked articles.
+If given a prefix, mark all unpicked articles as read."
+  (interactive "P")
+  (if gnus-newsgroup-processable
+      (progn
+        (gnus-summary-limit-to-articles nil)
+        (when (or catch-up gnus-mark-unpicked-articles-as-read)
+	  (gnus-summary-limit-mark-excluded-as-read))
+        (gnus-summary-first-article)
+        (gnus-configure-windows
+	 (if gnus-pick-display-summary 'article 'pick) t))
+    (if gnus-pick-elegant-flow
+	(progn
+	  (when (or catch-up gnus-mark-unpicked-articles-as-read)
+	    (gnus-summary-limit-mark-excluded-as-read))
+	  (if (gnus-group-quit-config gnus-newsgroup-name)
+	      (gnus-summary-exit)
+	    (gnus-summary-next-group)))
+      (error "No articles have been picked"))))
+
+(defun gnus-pick-article (&optional arg)
+  "Pick the article on the current line.
+If ARG, pick the article on that line instead."
+  (interactive "P")
+  (when arg
+    (let (pos)
+      (save-excursion
+	(goto-char (point-min))
+	(when (zerop (forward-line (1- (prefix-numeric-value arg))))
+	  (setq pos (point))))
+      (if (not pos)
+	  (gnus-error 2 "No such line: %s" arg)
+	(goto-char pos))))
+  (gnus-summary-mark-as-processable 1))
+
+(defun gnus-pick-mouse-pick (e)
+  (interactive "e")
+  (mouse-set-point e)
+  (save-excursion
+    (gnus-summary-mark-as-processable 1)))
+
+(defun gnus-pick-mouse-pick-region (start-event)
+  "Pick articles that the mouse is dragged over.
+This must be bound to a button-down mouse event."
+  (interactive "e")
+  (mouse-minibuffer-check start-event)
+  (let* ((echo-keystrokes 0)
+	 (start-posn (event-start start-event))
+	 (start-point (posn-point start-posn))
+         (start-line (1+ (count-lines 1 start-point)))
+	 (start-window (posn-window start-posn))
+	 (start-frame (window-frame start-window))
+	 (bounds (window-edges start-window))
+	 (top (nth 1 bounds))
+	 (bottom (if (window-minibuffer-p start-window)
+		     (nth 3 bounds)
+		   ;; Don't count the mode line.
+		   (1- (nth 3 bounds))))
+	 (click-count (1- (event-click-count start-event))))
+    (setq mouse-selection-click-count click-count)
+    (setq mouse-selection-click-count-buffer (current-buffer))
+    (mouse-set-point start-event)
+    ;; In case the down click is in the middle of some intangible text,
+    ;; use the end of that text, and put it in START-POINT.
+    (when (< (point) start-point)
+      (goto-char start-point))
+    (gnus-pick-article)
+    (setq start-point (point))
+    ;; end-of-range is used only in the single-click case.
+    ;; It is the place where the drag has reached so far
+    ;; (but not outside the window where the drag started).
+    (let (event end end-point last-end-point (end-of-range (point)))
+      (track-mouse
+       (while (progn
+		(setq event (read-event))
+		(or (mouse-movement-p event)
+		    (eq (car-safe event) 'switch-frame)))
+	 (if (eq (car-safe event) 'switch-frame)
+	     nil
+	   (setq end (event-end event)
+		 end-point (posn-point end))
+	   (when end-point
+	     (setq last-end-point end-point))
+
+	   (cond
+	    ;; Are we moving within the original window?
+	    ((and (eq (posn-window end) start-window)
+		  (integer-or-marker-p end-point))
+	     ;; Go to START-POINT first, so that when we move to END-POINT,
+	     ;; if it's in the middle of intangible text,
+	     ;; point jumps in the direction away from START-POINT.
+	     (goto-char start-point)
+	     (goto-char end-point)
+	     (gnus-pick-article)
+	     ;; In case the user moved his mouse really fast, pick
+	     ;; articles on the line between this one and the last one.
+	     (let* ((this-line (1+ (count-lines 1 end-point)))
+		    (min-line (min this-line start-line))
+		    (max-line (max this-line start-line)))
+	       (while (< min-line max-line)
+		 (goto-line min-line)
+		 (gnus-pick-article)
+		 (setq min-line (1+ min-line)))
+	       (setq start-line this-line))
+	     (when (zerop (% click-count 3))
+	       (setq end-of-range (point))))
+	    (t
+	     (let ((mouse-row (cdr (cdr (mouse-position)))))
+	       (cond
+		((null mouse-row))
+		((< mouse-row top)
+		 (mouse-scroll-subr start-window (- mouse-row top)))
+		((>= mouse-row bottom)
+		 (mouse-scroll-subr start-window
+				    (1+ (- mouse-row bottom)))))))))))
+      (when (consp event)
+	(let ((fun (key-binding (vector (car event)))))
+	  ;; Run the binding of the terminating up-event, if possible.
+	  ;; In the case of a multiple click, it gives the wrong results,
+	  ;; because it would fail to set up a region.
+	  (when nil
+	    ;; (and (= (mod mouse-selection-click-count 3) 0) (fboundp fun))
+	    ;; In this case, we can just let the up-event execute normally.
+	    (let ((end (event-end event)))
+	      ;; Set the position in the event before we replay it,
+	      ;; because otherwise it may have a position in the wrong
+	      ;; buffer.
+	      (setcar (cdr end) end-of-range)
+	      ;; Delete the overlay before calling the function,
+	      ;; because delete-overlay increases buffer-modified-tick.
+	      (push event unread-command-events))))))))
+
+(defun gnus-pick-next-page ()
+  "Go to the next page.  If at the end of the buffer, start reading articles."
+  (interactive)
+  (let ((scroll-in-place nil))
+    (condition-case nil
+	(scroll-up)
+      (end-of-buffer (gnus-pick-start-reading)))))
+
+;;;
+;;; gnus-binary-mode
+;;;
+
+(defvar gnus-binary-mode nil
+  "Minor mode for providing a binary group interface in Gnus summary buffers.")
+
+(defvar gnus-binary-mode-hook nil
+  "Hook run in summary binary mode buffers.")
+
+(defvar gnus-binary-mode-map nil)
+
+(unless gnus-binary-mode-map
+  (setq gnus-binary-mode-map (make-sparse-keymap))
+
+  (gnus-define-keys
+   gnus-binary-mode-map
+   "g" gnus-binary-show-article))
+
+(defun gnus-binary-make-menu-bar ()
+  (unless (boundp 'gnus-binary-menu)
+    (easy-menu-define
+     gnus-binary-menu gnus-binary-mode-map ""
+     '("Pick"
+       ["Switch binary mode off" gnus-binary-mode t]))))
+
+(defun gnus-binary-mode (&optional arg)
+  "Minor mode for providing a binary group interface in Gnus summary buffers."
+  (interactive "P")
+  (when (eq major-mode 'gnus-summary-mode)
+    (make-local-variable 'gnus-binary-mode)
+    (setq gnus-binary-mode
+	  (if (null arg) (not gnus-binary-mode)
+	    (> (prefix-numeric-value arg) 0)))
+    (when gnus-binary-mode
+      ;; Make sure that we don't select any articles upon group entry.
+      (make-local-variable 'gnus-auto-select-first)
+      (setq gnus-auto-select-first nil)
+      (make-local-variable 'gnus-summary-display-article-function)
+      (setq gnus-summary-display-article-function 'gnus-binary-display-article)
+      ;; Set up the menu.
+      (when (gnus-visual-p 'binary-menu 'menu)
+	(gnus-binary-make-menu-bar))
+      (unless (assq 'gnus-binary-mode minor-mode-alist)
+	(push '(gnus-binary-mode " Binary") minor-mode-alist))
+      (unless (assq 'gnus-binary-mode minor-mode-map-alist)
+	(push (cons 'gnus-binary-mode gnus-binary-mode-map)
+	      minor-mode-map-alist))
+      (run-hooks 'gnus-binary-mode-hook))))
+
+(defun gnus-binary-display-article (article &optional all-header)
+  "Run ARTICLE through the binary decode functions."
+  (when (gnus-summary-goto-subject article)
+    (let ((gnus-view-pseudos 'automatic))
+      (gnus-uu-decode-uu))))
+
+(defun gnus-binary-show-article (&optional arg)
+  "Bypass the binary functions and show the article."
+  (interactive "P")
+  (let (gnus-summary-display-article-function)
+    (gnus-summary-show-article arg)))
+
+;;;
+;;; gnus-tree-mode
+;;;
+
+(defvar gnus-tree-line-format "%(%[%3,3n%]%)"
+  "Format of tree elements.")
+
+(defvar gnus-tree-minimize-window t
+  "If non-nil, minimize the tree buffer window.
+If a number, never let the tree buffer grow taller than that number of
+lines.")
+
+(defvar gnus-selected-tree-face 'modeline
+  "*Face used for highlighting selected articles in the thread tree.")
+
+(defvar gnus-tree-brackets '((?\[ . ?\]) (?\( . ?\))
+			     (?\{ . ?\}) (?< . ?>))
+  "Brackets used in tree nodes.")
+
+(defvar gnus-tree-parent-child-edges '(?- ?\\ ?|)
+  "Characters used to connect parents with children.")
+
+(defvar gnus-tree-mode-line-format "Gnus: %%b %S %Z"
+  "*The format specification for the tree mode line.")
+
+(defvar gnus-generate-tree-function 'gnus-generate-vertical-tree
+  "*Function for generating a thread tree.
+Two predefined functions are available:
+`gnus-generate-horizontal-tree' and `gnus-generate-vertical-tree'.")
+
+(defvar gnus-tree-mode-hook nil
+  "*Hook run in tree mode buffers.")
+
+;;; Internal variables.
+
+(defvar gnus-tree-line-format-alist
+  `((?n gnus-tmp-name ?s)
+    (?f gnus-tmp-from ?s)
+    (?N gnus-tmp-number ?d)
+    (?\[ gnus-tmp-open-bracket ?c)
+    (?\] gnus-tmp-close-bracket ?c)
+    (?s gnus-tmp-subject ?s)))
+
+(defvar gnus-tree-mode-line-format-alist gnus-summary-mode-line-format-alist)
+
+(defvar gnus-tree-mode-line-format-spec nil)
+(defvar gnus-tree-line-format-spec nil)
+
+(defvar gnus-tree-node-length nil)
+(defvar gnus-selected-tree-overlay nil)
+
+(defvar gnus-tree-displayed-thread nil)
+
+(defvar gnus-tree-mode-map nil)
+(put 'gnus-tree-mode 'mode-class 'special)
+
+(unless gnus-tree-mode-map
+  (setq gnus-tree-mode-map (make-keymap))
+  (suppress-keymap gnus-tree-mode-map)
+  (gnus-define-keys
+   gnus-tree-mode-map
+   "\r" gnus-tree-select-article
+   gnus-mouse-2 gnus-tree-pick-article
+   "\C-?" gnus-tree-read-summary-keys
+
+   "\C-c\C-i" gnus-info-find-node)
+
+  (substitute-key-definition
+   'undefined 'gnus-tree-read-summary-keys gnus-tree-mode-map))
+
+(defun gnus-tree-make-menu-bar ()
+  (unless (boundp 'gnus-tree-menu)
+    (easy-menu-define
+     gnus-tree-menu gnus-tree-mode-map ""
+     '("Tree"
+       ["Select article" gnus-tree-select-article t]))))
+
+(defun gnus-tree-mode ()
+  "Major mode for displaying thread trees."
+  (interactive)
+  (setq gnus-tree-mode-line-format-spec
+	(gnus-parse-format gnus-tree-mode-line-format
+			   gnus-summary-mode-line-format-alist))
+  (setq gnus-tree-line-format-spec
+	(gnus-parse-format gnus-tree-line-format
+			   gnus-tree-line-format-alist t))
+  (when (gnus-visual-p 'tree-menu 'menu)
+    (gnus-tree-make-menu-bar))
+  (kill-all-local-variables)
+  (gnus-simplify-mode-line)
+  (setq mode-name "Tree")
+  (setq major-mode 'gnus-tree-mode)
+  (use-local-map gnus-tree-mode-map)
+  (buffer-disable-undo (current-buffer))
+  (setq buffer-read-only t)
+  (setq truncate-lines t)
+  (save-excursion
+    (gnus-set-work-buffer)
+    (gnus-tree-node-insert (make-mail-header "") nil)
+    (setq gnus-tree-node-length (1- (point))))
+  (run-hooks 'gnus-tree-mode-hook))
+
+(defun gnus-tree-read-summary-keys (&optional arg)
+  "Read a summary buffer key sequence and execute it."
+  (interactive "P")
+  (let ((buf (current-buffer))
+	win)
+    (gnus-article-read-summary-keys arg nil t)
+    (when (setq win (get-buffer-window buf))
+      (select-window win)
+      (when gnus-selected-tree-overlay
+	(goto-char (or (gnus-overlay-end gnus-selected-tree-overlay) 1)))
+      (gnus-tree-minimize))))
+
+(defun gnus-tree-select-article (article)
+  "Select the article under point, if any."
+  (interactive (list (gnus-tree-article-number)))
+  (let ((buf (current-buffer)))
+    (when article
+      (save-excursion
+	(set-buffer gnus-summary-buffer)
+	(gnus-summary-goto-article article))
+      (select-window (get-buffer-window buf)))))
+
+(defun gnus-tree-pick-article (e)
+  "Select the article under the mouse pointer."
+  (interactive "e")
+  (mouse-set-point e)
+  (gnus-tree-select-article (gnus-tree-article-number)))
+
+(defun gnus-tree-article-number ()
+  (get-text-property (point) 'gnus-number))
+
+(defun gnus-tree-article-region (article)
+  "Return a cons with BEG and END of the article region."
+  (let ((pos (text-property-any (point-min) (point-max) 'gnus-number article)))
+    (when pos
+      (cons pos (next-single-property-change pos 'gnus-number)))))
+
+(defun gnus-tree-goto-article (article)
+  (let ((pos (text-property-any (point-min) (point-max) 'gnus-number article)))
+    (when pos
+      (goto-char pos))))
+
+(defun gnus-tree-recenter ()
+  "Center point in the tree window."
+  (let ((selected (selected-window))
+	(tree-window (get-buffer-window gnus-tree-buffer t)))
+    (when tree-window
+      (select-window tree-window)
+      (when gnus-selected-tree-overlay
+	(goto-char (or (gnus-overlay-end gnus-selected-tree-overlay) 1)))
+      (let* ((top (cond ((< (window-height) 4) 0)
+			((< (window-height) 7) 1)
+			(t 2)))
+	     (height (1- (window-height)))
+	     (bottom (save-excursion (goto-char (point-max))
+				     (forward-line (- height))
+				     (point))))
+	;; Set the window start to either `bottom', which is the biggest
+	;; possible valid number, or the second line from the top,
+	;; whichever is the least.
+	(set-window-start
+	 tree-window (min bottom (save-excursion
+				   (forward-line (- top)) (point)))))
+      (select-window selected))))
+
+(defun gnus-get-tree-buffer ()
+  "Return the tree buffer properly initialized."
+  (save-excursion
+    (set-buffer (get-buffer-create gnus-tree-buffer))
+    (unless (eq major-mode 'gnus-tree-mode)
+      (gnus-add-current-to-buffer-list)
+      (gnus-tree-mode))
+    (current-buffer)))
+
+(defun gnus-tree-minimize ()
+  (when (and gnus-tree-minimize-window
+	     (not (one-window-p)))
+    (let ((windows 0)
+	  tot-win-height)
+      (walk-windows (lambda (window) (incf windows)))
+      (setq tot-win-height
+	    (- (frame-height)
+	       (* window-min-height (1- windows))
+	       2))
+      (let* ((window-min-height 2)
+	     (height (count-lines (point-min) (point-max)))
+	     (min (max (1- window-min-height) height))
+	     (tot (if (numberp gnus-tree-minimize-window)
+		      (min gnus-tree-minimize-window min)
+		    min))
+	     (win (get-buffer-window (current-buffer)))
+	     (wh (and win (1- (window-height win)))))
+	(setq tot (min tot tot-win-height))
+	(when (and win
+		   (not (eq tot wh)))
+	  (let ((selected (selected-window)))
+	    (when (ignore-errors (select-window win))
+	      (enlarge-window (- tot wh))
+	      (select-window selected))))))))
+
+;;; Generating the tree.
+
+(defun gnus-tree-node-insert (header sparse &optional adopted)
+  (let* ((dummy (stringp header))
+	 (header (if (vectorp header) header
+		   (progn
+		     (setq header (make-mail-header "*****"))
+		     (mail-header-set-number header 0)
+		     (mail-header-set-lines header 0)
+		     (mail-header-set-chars header 0)
+		     header)))
+	 (gnus-tmp-from (mail-header-from header))
+	 (gnus-tmp-subject (mail-header-subject header))
+	 (gnus-tmp-number (mail-header-number header))
+	 (gnus-tmp-name
+	  (cond
+	   ((string-match "(.+)" gnus-tmp-from)
+	    (substring gnus-tmp-from
+		       (1+ (match-beginning 0)) (1- (match-end 0))))
+	   ((string-match "<[^>]+> *$" gnus-tmp-from)
+	    (let ((beg (match-beginning 0)))
+	      (or (and (string-match "^\"[^\"]*\"" gnus-tmp-from)
+		       (substring gnus-tmp-from (1+ (match-beginning 0))
+				  (1- (match-end 0))))
+		  (substring gnus-tmp-from 0 beg))))
+	   ((memq gnus-tmp-number sparse)
+	    "***")
+	   (t gnus-tmp-from)))
+	 (gnus-tmp-open-bracket
+	  (cond ((memq gnus-tmp-number sparse)
+		 (caadr gnus-tree-brackets))
+		(dummy (caaddr gnus-tree-brackets))
+		(adopted (car (nth 3 gnus-tree-brackets)))
+		(t (caar gnus-tree-brackets))))
+	 (gnus-tmp-close-bracket
+	  (cond ((memq gnus-tmp-number sparse)
+		 (cdadr gnus-tree-brackets))
+		(adopted (cdr (nth 3 gnus-tree-brackets)))
+		(dummy
+		 (cdaddr gnus-tree-brackets))
+		(t (cdar gnus-tree-brackets))))
+	 (buffer-read-only nil)
+	 beg end)
+    (gnus-add-text-properties
+     (setq beg (point))
+     (setq end (progn (eval gnus-tree-line-format-spec) (point)))
+     (list 'gnus-number gnus-tmp-number))
+    (when (or t (gnus-visual-p 'tree-highlight 'highlight))
+      (gnus-tree-highlight-node gnus-tmp-number beg end))))
+
+(defun gnus-tree-highlight-node (article beg end)
+  "Highlight current line according to `gnus-summary-highlight'."
+  (let ((list gnus-summary-highlight)
+	face)
+    (save-excursion
+      (set-buffer gnus-summary-buffer)
+      (let* ((score (or (cdr (assq article gnus-newsgroup-scored))
+			gnus-summary-default-score 0))
+	     (default gnus-summary-default-score)
+	     (mark (or (gnus-summary-article-mark article) gnus-unread-mark)))
+	;; Eval the cars of the lists until we find a match.
+	(while (and list
+		    (not (eval (caar list))))
+	  (setq list (cdr list)))))
+    (unless (eq (setq face (cdar list)) (get-text-property beg 'face))
+      (gnus-put-text-property
+       beg end 'face
+       (if (boundp face) (symbol-value face) face)))))
+
+(defun gnus-tree-indent (level)
+  (insert (make-string (1- (* (1+ gnus-tree-node-length) level)) ? )))
+
+(defvar gnus-tmp-limit)
+(defvar gnus-tmp-sparse)
+(defvar gnus-tmp-indent)
+
+(defun gnus-generate-tree (thread)
+  "Generate a thread tree for THREAD."
+  (save-excursion
+    (set-buffer (gnus-get-tree-buffer))
+    (let ((buffer-read-only nil)
+	  (gnus-tmp-indent 0))
+      (erase-buffer)
+      (funcall gnus-generate-tree-function thread 0)
+      (gnus-set-mode-line 'tree)
+      (goto-char (point-min))
+      (gnus-tree-minimize)
+      (gnus-tree-recenter)
+      (let ((selected (selected-window)))
+	(when (get-buffer-window (set-buffer gnus-tree-buffer) t)
+	  (select-window (get-buffer-window (set-buffer gnus-tree-buffer) t))
+	  (gnus-horizontal-recenter)
+	  (select-window selected))))))
+
+(defun gnus-generate-horizontal-tree (thread level &optional dummyp adopted)
+  "Generate a horizontal tree."
+  (let* ((dummy (stringp (car thread)))
+	 (do (or dummy
+		 (memq (mail-header-number (car thread)) gnus-tmp-limit)))
+	 col beg)
+    (if (not do)
+	;; We don't want this article.
+	(setq thread (cdr thread))
+      (if (not (bolp))
+	  ;; Not the first article on the line, so we insert a "-".
+	  (insert (car gnus-tree-parent-child-edges))
+	;; If the level isn't zero, then we insert some indentation.
+	(unless (zerop level)
+	  (gnus-tree-indent level)
+	  (insert (cadr gnus-tree-parent-child-edges))
+	  (setq col (- (setq beg (point)) (gnus-point-at-bol) 1))
+	  ;; Draw "|" lines upwards.
+	  (while (progn
+		   (forward-line -1)
+		   (forward-char col)
+		   (= (following-char) ? ))
+	    (delete-char 1)
+	    (insert (caddr gnus-tree-parent-child-edges)))
+	  (goto-char beg)))
+      (setq dummyp nil)
+      ;; Insert the article node.
+      (gnus-tree-node-insert (pop thread) gnus-tmp-sparse adopted))
+    (if (null thread)
+	;; End of the thread, so we go to the next line.
+	(unless (bolp)
+	  (insert "\n"))
+      ;; Recurse downwards in all children of this article.
+      (while thread
+	(gnus-generate-horizontal-tree
+	 (pop thread) (if do (1+ level) level)
+	 (or dummyp dummy) dummy)))))
+
+(defsubst gnus-tree-indent-vertical ()
+  (let ((len (- (* (1+ gnus-tree-node-length) gnus-tmp-indent)
+		(- (point) (gnus-point-at-bol)))))
+    (when (> len 0)
+      (insert (make-string len ? )))))
+
+(defsubst gnus-tree-forward-line (n)
+  (while (>= (decf n) 0)
+    (unless (zerop (forward-line 1))
+      (end-of-line)
+      (insert "\n")))
+  (end-of-line))
+
+(defun gnus-generate-vertical-tree (thread level &optional dummyp adopted)
+  "Generate a vertical tree."
+  (let* ((dummy (stringp (car thread)))
+	 (do (or dummy
+		 (and (car thread)
+		      (memq (mail-header-number (car thread))
+			    gnus-tmp-limit))))
+	 beg)
+    (if (not do)
+	;; We don't want this article.
+	(setq thread (cdr thread))
+      (if (not (save-excursion (beginning-of-line) (bobp)))
+	  ;; Not the first article on the line, so we insert a "-".
+	  (progn
+	    (gnus-tree-indent-vertical)
+	    (insert (make-string (/ gnus-tree-node-length 2) ? ))
+	    (insert (caddr gnus-tree-parent-child-edges))
+	    (gnus-tree-forward-line 1))
+	;; If the level isn't zero, then we insert some indentation.
+	(unless (zerop gnus-tmp-indent)
+	  (gnus-tree-forward-line (1- (* 2 level)))
+	  (gnus-tree-indent-vertical)
+	  (delete-char -1)
+	  (insert (cadr gnus-tree-parent-child-edges))
+	  (setq beg (point))
+	  ;; Draw "-" lines leftwards.
+	  (while (progn
+		   (unless (bolp)
+		     (forward-char -2))
+		   (= (following-char) ? ))
+	    (delete-char 1)
+	    (insert (car gnus-tree-parent-child-edges)))
+	  (goto-char beg)
+	  (gnus-tree-forward-line 1)))
+      (setq dummyp nil)
+      ;; Insert the article node.
+      (gnus-tree-indent-vertical)
+      (gnus-tree-node-insert (pop thread) gnus-tmp-sparse adopted)
+      (gnus-tree-forward-line 1))
+    (if (null thread)
+	;; End of the thread, so we go to the next line.
+	(progn
+	  (goto-char (point-min))
+	  (end-of-line)
+	  (incf gnus-tmp-indent))
+      ;; Recurse downwards in all children of this article.
+      (while thread
+	(gnus-generate-vertical-tree
+	 (pop thread) (if do (1+ level) level)
+	 (or dummyp dummy) dummy)))))
+
+;;; Interface functions.
+
+(defun gnus-possibly-generate-tree (article &optional force)
+  "Generate the thread tree for ARTICLE if it isn't displayed already."
+  (when (save-excursion
+	  (set-buffer gnus-summary-buffer)
+	  (and gnus-use-trees
+	       gnus-show-threads
+	       (vectorp (gnus-summary-article-header article))))
+    (save-excursion
+      (let ((top (save-excursion
+		   (set-buffer gnus-summary-buffer)
+		   (gnus-cut-thread
+		    (gnus-remove-thread
+		     (mail-header-id
+		      (gnus-summary-article-header article))
+		     t))))
+	    (gnus-tmp-limit gnus-newsgroup-limit)
+	    (gnus-tmp-sparse gnus-newsgroup-sparse))
+	(when (or force
+		  (not (eq top gnus-tree-displayed-thread)))
+	  (gnus-generate-tree top)
+	  (setq gnus-tree-displayed-thread top))))))
+
+(defun gnus-tree-open (group)
+  (gnus-get-tree-buffer))
+
+(defun gnus-tree-close (group)
+					;(gnus-kill-buffer gnus-tree-buffer)
+  )
+
+(defun gnus-highlight-selected-tree (article)
+  "Highlight the selected article in the tree."
+  (let ((buf (current-buffer))
+	region)
+    (set-buffer gnus-tree-buffer)
+    (when (setq region (gnus-tree-article-region article))
+      (when (or (not gnus-selected-tree-overlay)
+		(gnus-extent-detached-p gnus-selected-tree-overlay))
+	;; Create a new overlay.
+	(gnus-overlay-put
+	 (setq gnus-selected-tree-overlay (gnus-make-overlay 1 2))
+	 'face gnus-selected-tree-face))
+      ;; Move the overlay to the article.
+      (gnus-move-overlay
+       gnus-selected-tree-overlay (goto-char (car region)) (cdr region))
+      (gnus-tree-minimize)
+      (gnus-tree-recenter)
+      (let ((selected (selected-window)))
+	(when (get-buffer-window (set-buffer gnus-tree-buffer) t)
+	  (select-window (get-buffer-window (set-buffer gnus-tree-buffer) t))
+	  (gnus-horizontal-recenter)
+	  (select-window selected))))
+    ;; If we remove this save-excursion, it updates the wrong mode lines?!?
+    (save-excursion
+      (set-buffer gnus-tree-buffer)
+      (gnus-set-mode-line 'tree))
+    (set-buffer buf)))
+
+(defun gnus-tree-highlight-article (article face)
+  (save-excursion
+    (set-buffer (gnus-get-tree-buffer))
+    (let (region)
+      (when (setq region (gnus-tree-article-region article))
+	(gnus-put-text-property (car region) (cdr region) 'face face)
+	(set-window-point
+	 (get-buffer-window (current-buffer) t) (cdr region))))))
+
+;;;
+;;; gnus-carpal
+;;;
+
+(defvar gnus-carpal-group-buffer-buttons
+  '(("next" . gnus-group-next-unread-group)
+    ("prev" . gnus-group-prev-unread-group)
+    ("read" . gnus-group-read-group)
+    ("select" . gnus-group-select-group)
+    ("catch-up" . gnus-group-catchup-current)
+    ("new-news" . gnus-group-get-new-news-this-group)
+    ("toggle-sub" . gnus-group-unsubscribe-current-group)
+    ("subscribe" . gnus-group-unsubscribe-group)
+    ("kill" . gnus-group-kill-group)
+    ("yank" . gnus-group-yank-group)
+    ("describe" . gnus-group-describe-group)
+    "list"
+    ("subscribed" . gnus-group-list-groups)
+    ("all" . gnus-group-list-all-groups)
+    ("killed" . gnus-group-list-killed)
+    ("zombies" . gnus-group-list-zombies)
+    ("matching" . gnus-group-list-matching)
+    ("post" . gnus-group-post-news)
+    ("mail" . gnus-group-mail)
+    ("rescan" . gnus-group-get-new-news)
+    ("browse-foreign" . gnus-group-browse-foreign)
+    ("exit" . gnus-group-exit)))
+
+(defvar gnus-carpal-summary-buffer-buttons
+  '("mark"
+    ("read" . gnus-summary-mark-as-read-forward)
+    ("tick" . gnus-summary-tick-article-forward)
+    ("clear" . gnus-summary-clear-mark-forward)
+    ("expirable" . gnus-summary-mark-as-expirable)
+    "move"
+    ("scroll" . gnus-summary-next-page)
+    ("next-unread" . gnus-summary-next-unread-article)
+    ("prev-unread" . gnus-summary-prev-unread-article)
+    ("first" . gnus-summary-first-unread-article)
+    ("best" . gnus-summary-best-unread-article)
+    "article"
+    ("headers" . gnus-summary-toggle-header)
+    ("uudecode" . gnus-uu-decode-uu)
+    ("enter-digest" . gnus-summary-enter-digest-group)
+    ("fetch-parent" . gnus-summary-refer-parent-article)
+    "mail"
+    ("move" . gnus-summary-move-article)
+    ("copy" . gnus-summary-copy-article)
+    ("respool" . gnus-summary-respool-article)
+    "threads"
+    ("lower" . gnus-summary-lower-thread)
+    ("kill" . gnus-summary-kill-thread)
+    "post"
+    ("post" . gnus-summary-post-news)
+    ("mail" . gnus-summary-mail)
+    ("followup" . gnus-summary-followup-with-original)
+    ("reply" . gnus-summary-reply-with-original)
+    ("cancel" . gnus-summary-cancel-article)
+    "misc"
+    ("exit" . gnus-summary-exit)
+    ("fed-up" . gnus-summary-catchup-and-goto-next-group)))
+
+(defvar gnus-carpal-server-buffer-buttons
+  '(("add" . gnus-server-add-server)
+    ("browse" . gnus-server-browse-server)
+    ("list" . gnus-server-list-servers)
+    ("kill" . gnus-server-kill-server)
+    ("yank" . gnus-server-yank-server)
+    ("copy" . gnus-server-copy-server)
+    ("exit" . gnus-server-exit)))
+
+(defvar gnus-carpal-browse-buffer-buttons
+  '(("subscribe" . gnus-browse-unsubscribe-current-group)
+    ("exit" . gnus-browse-exit)))
+
+(defvar gnus-carpal-group-buffer "*Carpal Group*")
+(defvar gnus-carpal-summary-buffer "*Carpal Summary*")
+(defvar gnus-carpal-server-buffer "*Carpal Server*")
+(defvar gnus-carpal-browse-buffer "*Carpal Browse*")
+
+(defvar gnus-carpal-attached-buffer nil)
+
+(defvar gnus-carpal-mode-hook nil
+  "*Hook run in carpal mode buffers.")
+
+(defvar gnus-carpal-button-face 'bold
+  "*Face used on carpal buttons.")
+
+(defvar gnus-carpal-header-face 'bold-italic
+  "*Face used on carpal buffer headers.")
+
+(defvar gnus-carpal-mode-map nil)
+(put 'gnus-carpal-mode 'mode-class 'special)
+
+(if gnus-carpal-mode-map
+    nil
+  (setq gnus-carpal-mode-map (make-keymap))
+  (suppress-keymap gnus-carpal-mode-map)
+  (define-key gnus-carpal-mode-map " " 'gnus-carpal-select)
+  (define-key gnus-carpal-mode-map "\r" 'gnus-carpal-select)
+  (define-key gnus-carpal-mode-map gnus-mouse-2 'gnus-carpal-mouse-select))
+
+(defun gnus-carpal-mode ()
+  "Major mode for clicking buttons.
+
+All normal editing commands are switched off.
+\\<gnus-carpal-mode-map>
+The following commands are available:
+
+\\{gnus-carpal-mode-map}"
+  (interactive)
+  (kill-all-local-variables)
+  (setq mode-line-modified "-- ")
+  (setq major-mode 'gnus-carpal-mode)
+  (setq mode-name "Gnus Carpal")
+  (setq mode-line-process nil)
+  (use-local-map gnus-carpal-mode-map)
+  (buffer-disable-undo (current-buffer))
+  (setq buffer-read-only t)
+  (make-local-variable 'gnus-carpal-attached-buffer)
+  (run-hooks 'gnus-carpal-mode-hook))
+
+(defun gnus-carpal-setup-buffer (type)
+  (let ((buffer (symbol-value (intern (format "gnus-carpal-%s-buffer" type)))))
+    (if (get-buffer buffer)
+	()
+      (save-excursion
+	(set-buffer (get-buffer-create buffer))
+	(gnus-carpal-mode)
+	(setq gnus-carpal-attached-buffer
+	      (intern (format "gnus-%s-buffer" type)))
+	(gnus-add-current-to-buffer-list)
+	(let ((buttons (symbol-value
+			(intern (format "gnus-carpal-%s-buffer-buttons"
+					type))))
+	      (buffer-read-only nil)
+	      button)
+	  (while buttons
+	    (setq button (car buttons)
+		  buttons (cdr buttons))
+	    (if (stringp button)
+		(gnus-set-text-properties
+		 (point)
+		 (prog2 (insert button) (point) (insert " "))
+		 (list 'face gnus-carpal-header-face))
+	      (gnus-set-text-properties
+	       (point)
+	       (prog2 (insert (car button)) (point) (insert " "))
+	       (list 'gnus-callback (cdr button)
+		     'face gnus-carpal-button-face
+		     gnus-mouse-face-prop 'highlight))))
+	  (let ((fill-column (- (window-width) 2)))
+	    (fill-region (point-min) (point-max)))
+	  (set-window-point (get-buffer-window (current-buffer))
+			    (point-min)))))))
+
+(defun gnus-carpal-select ()
+  "Select the button under point."
+  (interactive)
+  (let ((func (get-text-property (point) 'gnus-callback)))
+    (if (null func)
+	()
+      (pop-to-buffer (symbol-value gnus-carpal-attached-buffer))
+      (call-interactively func))))
+
+(defun gnus-carpal-mouse-select (event)
+  "Select the button under the mouse pointer."
+  (interactive "e")
+  (mouse-set-point event)
+  (gnus-carpal-select))
+
+;;; Allow redefinition of functions.
+(gnus-ems-redefine)
+
+(provide 'gnus-salt)
+
+;;; gnus-salt.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-score.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,2761 @@
+;;; gnus-score.el --- scoring code for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Per Abrahamsen <amanda@iesd.auc.dk>
+;;	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-sum)
+(require 'gnus-range)
+
+(defcustom gnus-global-score-files nil
+  "List of global score files and directories.
+Set this variable if you want to use people's score files.  One entry
+for each score file or each score file directory.  Gnus will decide
+by itself what score files are applicable to which group.
+
+Say you want to use the single score file
+\"/ftp.gnus.org@ftp:/pub/larsi/ding/score/soc.motss.SCORE\" and all
+score files in the \"/ftp.some-where:/pub/score\" directory.
+
+ (setq gnus-global-score-files
+       '(\"/ftp.gnus.org:/pub/larsi/ding/score/soc.motss.SCORE\"
+         \"/ftp.some-where:/pub/score\"))"
+  :group 'gnus-score-files
+  :type '(repeat file))
+
+(defcustom gnus-score-file-single-match-alist nil
+  "Alist mapping regexps to lists of score files.
+Each element of this alist should be of the form
+	(\"REGEXP\" [ \"SCORE-FILE-1\" ] [ \"SCORE-FILE-2\" ] ... )
+
+If the name of a group is matched by REGEXP, the corresponding scorefiles
+will be used for that group.
+The first match found is used, subsequent matching entries are ignored (to
+use multiple matches, see gnus-score-file-multiple-match-alist).
+
+These score files are loaded in addition to any files returned by
+gnus-score-find-score-files-function (which see)."
+  :group 'gnus-score-files
+  :type '(repeat (cons regexp (repeat file))))
+
+(defcustom gnus-score-file-multiple-match-alist nil
+  "Alist mapping regexps to lists of score files.
+Each element of this alist should be of the form
+	(\"REGEXP\" [ \"SCORE-FILE-1\" ] [ \"SCORE-FILE-2\" ] ... )
+
+If the name of a group is matched by REGEXP, the corresponding scorefiles
+will be used for that group.
+If multiple REGEXPs match a group, the score files corresponding to each
+match will be used (for only one match to be used, see
+gnus-score-file-single-match-alist).
+
+These score files are loaded in addition to any files returned by
+gnus-score-find-score-files-function (which see)."
+  :group 'gnus-score-files
+  :type '(repeat (cons regexp (repeat file))))
+
+(defcustom gnus-score-file-suffix "SCORE"
+  "Suffix of the score files."
+  :group 'gnus-score-files
+  :type 'string)
+
+(defcustom gnus-adaptive-file-suffix "ADAPT"
+  "Suffix of the adaptive score files."
+  :group 'gnus-score-files
+  :group 'gnus-score-adapt
+  :type 'string)
+
+(defcustom gnus-score-find-score-files-function 'gnus-score-find-bnews
+  "Function used to find score files.
+The function will be called with the group name as the argument, and
+should return a list of score files to apply to that group.  The score
+files do not actually have to exist.
+
+Predefined values are:
+
+gnus-score-find-single: Only apply the group's own score file.
+gnus-score-find-hierarchical: Also apply score files from parent groups.
+gnus-score-find-bnews: Apply score files whose names matches.
+
+See the documentation to these functions for more information.
+
+This variable can also be a list of functions to be called.  Each
+function should either return a list of score files, or a list of
+score alists."
+  :group 'gnus-score-files
+  :type '(radio (function-item gnus-score-find-single)
+		(function-item gnus-score-find-hierarchical)
+		(function-item gnus-score-find-bnews)
+		(function :tag "Other")))
+
+(defcustom gnus-score-interactive-default-score 1000
+  "*Scoring commands will raise/lower the score with this number as the default."
+  :group 'gnus-score-default
+  :type 'integer)
+
+(defcustom gnus-score-expiry-days 7
+  "*Number of days before unused score file entries are expired.
+If this variable is nil, no score file entries will be expired."
+  :group 'gnus-score-expire
+  :type '(choice (const :tag "never" nil)
+		 number))
+
+(defcustom gnus-update-score-entry-dates t
+  "*In non-nil, update matching score entry dates.
+If this variable is nil, then score entries that provide matches
+will be expired along with non-matching score entries."
+  :group 'gnus-score-expire
+  :type 'boolean)
+
+(defcustom gnus-orphan-score nil
+  "*All orphans get this score added.  Set in the score file."
+  :group 'gnus-score-default
+  :type 'integer)
+
+(defcustom gnus-decay-scores nil
+  "*If non-nil, decay non-permanent scores."
+  :group 'gnus-score-decay
+  :type 'boolean)
+
+(defcustom gnus-decay-score-function 'gnus-decay-score
+  "*Function called to decay a score.
+It is called with one parameter -- the score to be decayed."
+  :group 'gnus-score-decay
+  :type '(radio (function-item gnus-decay-score)
+		(function :tag "Other")))
+
+(defcustom gnus-score-decay-constant 3
+  "*Decay all \"small\" scores with this amount."
+  :group 'gnus-score-decay
+  :type 'integer)
+
+(defcustom gnus-score-decay-scale .05
+  "*Decay all \"big\" scores with this factor."
+  :group 'gnus-score-decay
+  :type 'number)
+
+(defcustom gnus-home-score-file nil
+  "Variable to control where interactive score entries are to go.
+It can be:
+
+ * A string
+   This file file will be used as the home score file.
+
+ * A function
+   The result of this function will be used as the home score file.
+   The function will be passed the name of the group as its
+   parameter.
+
+ * A list
+   The elements in this list can be:
+
+   * `(regexp file-name ...)'
+     If the `regexp' matches the group name, the first `file-name' will
+     will be used as the home score file.  (Multiple filenames are
+     allowed so that one may use gnus-score-file-single-match-alist to
+     set this variable.)
+
+   * A function.
+     If the function returns non-nil, the result will be used
+     as the home score file.  The function will be passed the
+     name of the group as its parameter.
+
+   * A string.  Use the string as the home score file.
+
+   The list will be traversed from the beginning towards the end looking
+   for matches."
+  :group 'gnus-score-files
+  :type '(choice string
+		 (repeat (choice string
+				 (cons regexp (repeat file))
+				 function))
+		 function))
+
+(defcustom gnus-home-adapt-file nil
+  "Variable to control where new adaptive score entries are to go.
+This variable allows the same syntax as `gnus-home-score-file'."
+  :group 'gnus-score-adapt
+  :group 'gnus-score-files
+  :type '(choice string
+		 (repeat (choice string
+				 (cons regexp (repeat file))
+				 function))
+		 function))
+
+(defcustom gnus-default-adaptive-score-alist
+  '((gnus-kill-file-mark)
+    (gnus-unread-mark)
+    (gnus-read-mark (from 3) (subject 30))
+    (gnus-catchup-mark (subject -10))
+    (gnus-killed-mark (from -1) (subject -20))
+    (gnus-del-mark (from -2) (subject -15)))
+"Alist of marks and scores."
+:group 'gnus-score-adapt
+:type '(repeat (cons (symbol :tag "Mark")
+		     (repeat (list (choice :tag "Header"
+					   (const from)
+					   (const subject)
+					   (symbol :tag "other"))
+				   (integer :tag "Score"))))))
+
+(defcustom gnus-ignored-adaptive-words nil
+  "List of words to be ignored when doing adaptive word scoring."
+  :group 'gnus-score-adapt
+  :type '(repeat string))
+
+(defcustom gnus-default-ignored-adaptive-words
+  '("a" "i" "the" "to" "of" "and" "in" "is" "it" "for" "that" "if" "you"
+    "this" "be" "on" "with" "not" "have" "are" "or" "as" "from" "can"
+    "but" "by" "at" "an" "will" "no" "all" "was" "do" "there" "my" "one"
+    "so" "we" "they" "what" "would" "any" "which" "about" "get" "your"
+    "use" "some" "me" "then" "name" "like" "out" "when" "up" "time"
+    "other" "more" "only" "just" "end" "also" "know" "how" "new" "should"
+    "been" "than" "them" "he" "who" "make" "may" "people" "these" "now"
+    "their" "here" "into" "first" "could" "way" "had" "see" "work" "well"
+    "were" "two" "very" "where" "while" "us" "because" "good" "same"
+    "even" "much" "most" "many" "such" "long" "his" "over" "last" "since"
+    "right" "before" "our" "without" "too" "those" "why" "must" "part"
+    "being" "current" "back" "still" "go" "point" "value" "each" "did"
+    "both" "true" "off" "say" "another" "state" "might" "under" "start"
+    "try" "re")
+  "Default list of words to be ignored when doing adaptive word scoring."
+  :group 'gnus-score-adapt
+  :type '(repeat string))
+
+(defcustom gnus-default-adaptive-word-score-alist
+  `((,gnus-read-mark . 30)
+    (,gnus-catchup-mark . -10)
+    (,gnus-killed-mark . -20)
+    (,gnus-del-mark . -15))
+"Alist of marks and scores."
+:group 'gnus-score-adapt
+:type '(repeat (cons (character :tag "Mark")
+		     (integer :tag "Score"))))
+
+(defcustom gnus-score-mimic-keymap nil
+  "*Have the score entry functions pretend that they are a keymap."
+  :group 'gnus-score-default
+  :type 'boolean)
+
+(defcustom gnus-score-exact-adapt-limit 10
+  "*Number that says how long a match has to be before using substring matching.
+When doing adaptive scoring, one normally uses fuzzy or substring
+matching.  However, if the header one matches is short, the possibility
+for false positives is great, so if the length of the match is less
+than this variable, exact matching will be used.
+
+If this variable is nil, exact matching will always be used."
+  :group 'gnus-score-adapt
+  :type '(choice (const nil) integer))
+
+(defcustom gnus-score-uncacheable-files "ADAPT$"
+  "All score files that match this regexp will not be cached."
+  :group 'gnus-score-adapt
+  :group 'gnus-score-files
+  :type 'regexp)
+
+(defcustom gnus-score-default-header nil
+  "Default header when entering new scores.
+
+Should be one of the following symbols.
+
+ a: from
+ s: subject
+ b: body
+ h: head
+ i: message-id
+ t: references
+ x: xref
+ l: lines
+ d: date
+ f: followup
+
+If nil, the user will be asked for a header."
+  :group 'gnus-score-default
+  :type '(choice (const :tag "from" a)
+		 (const :tag "subject" s)
+		 (const :tag "body" b)
+		 (const :tag "head" h)
+		 (const :tag "message-id" i)
+		 (const :tag "references" t)
+		 (const :tag "xref" x)
+		 (const :tag "lines" l)
+		 (const :tag "date" d)
+		 (const :tag "followup" f)))
+
+(defcustom gnus-score-default-type nil
+  "Default match type when entering new scores.
+
+Should be one of the following symbols.
+
+ s: substring
+ e: exact string
+ f: fuzzy string
+ r: regexp string
+ b: before date
+ a: at date
+ n: this date
+ <: less than number
+ >: greater than number
+ =: equal to number
+
+If nil, the user will be asked for a match type."
+  :group 'gnus-score-default
+  :type '(choice (const :tag "substring" s)
+		 (const :tag "exact string" e)
+		 (const :tag "fuzzy string" f)
+		 (const :tag "regexp string" r)
+		 (const :tag "before date" b)
+		 (const :tag "at date" a)
+		 (const :tag "this date" n)
+		 (const :tag "less than number" <)
+		 (const :tag "greater than number" >)
+		 (const :tag "equal than number" =)))
+
+(defcustom gnus-score-default-fold nil
+  "Use case folding for new score file entries iff not nil."
+  :group 'gnus-score-default
+  :type 'boolean)
+
+(defcustom gnus-score-default-duration nil
+  "Default duration of effect when entering new scores.
+
+Should be one of the following symbols.
+
+ t: temporary
+ p: permanent
+ i: immediate
+
+If nil, the user will be asked for a duration."
+  :group 'gnus-score-default
+  :type '(choice (const :tag "temporary" t)
+		 (const :tag "permanent" p)
+		 (const :tag "immediate" i)
+		 (const :tag "ask" nil)))
+
+(defcustom gnus-score-after-write-file-function nil
+  "Function called with the name of the score file just written to disk."
+  :group 'gnus-score-files
+  :type 'function)
+
+
+
+;; Internal variables.
+
+(defvar gnus-adaptive-word-syntax-table
+  (let ((table (copy-syntax-table (standard-syntax-table)))
+	(numbers '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
+    (while numbers
+      (modify-syntax-entry (pop numbers) " " table))
+    (modify-syntax-entry ?' "w" table)
+    table)
+  "Syntax table used when doing adaptive word scoring.")
+
+(defvar gnus-scores-exclude-files nil)
+(defvar gnus-internal-global-score-files nil)
+(defvar gnus-score-file-list nil)
+
+(defvar gnus-short-name-score-file-cache nil)
+
+(defvar gnus-score-help-winconf nil)
+(defvar gnus-adaptive-score-alist gnus-default-adaptive-score-alist)
+(defvar gnus-adaptive-word-score-alist gnus-default-adaptive-word-score-alist)
+(defvar gnus-score-trace nil)
+(defvar gnus-score-edit-buffer nil)
+
+(defvar gnus-score-alist nil
+  "Alist containing score information.
+The keys can be symbols or strings.  The following symbols are defined.
+
+touched: If this alist has been modified.
+mark:    Automatically mark articles below this.
+expunge: Automatically expunge articles below this.
+files:   List of other score files to load when loading this one.
+eval:    Sexp to be evaluated when the score file is loaded.
+
+String entries have the form (HEADER (MATCH TYPE SCORE DATE) ...)
+where HEADER is the header being scored, MATCH is the string we are
+looking for, TYPE is a flag indicating whether it should use regexp or
+substring matching, SCORE is the score to add and DATE is the date
+of the last successful match.")
+
+(defvar gnus-score-cache nil)
+(defvar gnus-scores-articles nil)
+(defvar gnus-score-index nil)
+
+
+(defconst gnus-header-index
+  ;; Name to index alist.
+  '(("number" 0 gnus-score-integer)
+    ("subject" 1 gnus-score-string)
+    ("from" 2 gnus-score-string)
+    ("date" 3 gnus-score-date)
+    ("message-id" 4 gnus-score-string)
+    ("references" 5 gnus-score-string)
+    ("chars" 6 gnus-score-integer)
+    ("lines" 7 gnus-score-integer)
+    ("xref" 8 gnus-score-string)
+    ("head" -1 gnus-score-body)
+    ("body" -1 gnus-score-body)
+    ("all" -1 gnus-score-body)
+    ("followup" 2 gnus-score-followup)
+    ("thread" 5 gnus-score-thread)))
+
+;;; Summary mode score maps.
+
+(gnus-define-keys (gnus-summary-score-map "V" gnus-summary-mode-map)
+  "s" gnus-summary-set-score
+  "a" gnus-summary-score-entry
+  "S" gnus-summary-current-score
+  "c" gnus-score-change-score-file
+  "C" gnus-score-customize
+  "m" gnus-score-set-mark-below
+  "x" gnus-score-set-expunge-below
+  "R" gnus-summary-rescore
+  "e" gnus-score-edit-current-scores
+  "f" gnus-score-edit-file
+  "F" gnus-score-flush-cache
+  "t" gnus-score-find-trace
+  "w" gnus-score-find-favourite-words)
+
+;; Summary score file commands
+
+;; Much modification of the kill (ahem, score) code and lots of the
+;; functions are written by Per Abrahamsen <amanda@iesd.auc.dk>.
+
+(defun gnus-summary-lower-score (&optional score)
+  "Make a score entry based on the current article.
+The user will be prompted for header to score on, match type,
+permanence, and the string to be used.  The numerical prefix will be
+used as score."
+  (interactive "P")
+  (gnus-summary-increase-score (- (gnus-score-default score))))
+
+(defun gnus-score-kill-help-buffer ()
+  (when (get-buffer "*Score Help*")
+    (kill-buffer "*Score Help*")
+    (when gnus-score-help-winconf
+      (set-window-configuration gnus-score-help-winconf))))
+
+(defun gnus-summary-increase-score (&optional score)
+  "Make a score entry based on the current article.
+The user will be prompted for header to score on, match type,
+permanence, and the string to be used.  The numerical prefix will be
+used as score."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let* ((nscore (gnus-score-default score))
+	 (prefix (if (< nscore 0) ?L ?I))
+	 (increase (> nscore 0))
+	 (char-to-header
+	  '((?a "from" nil nil string)
+	    (?s "subject" nil nil string)
+	    (?b "body" "" nil body-string)
+	    (?h "head" "" nil body-string)
+	    (?i "message-id" nil t string)
+	    (?t "references" "message-id" nil string)
+	    (?x "xref" nil nil string)
+	    (?l "lines" nil nil number)
+	    (?d "date" nil nil date)
+	    (?f "followup" nil nil string)
+	    (?T "thread" nil nil string)))
+	 (char-to-type
+	  '((?s s "substring" string)
+	    (?e e "exact string" string)
+	    (?f f "fuzzy string" string)
+	    (?r r "regexp string" string)
+	    (?z s "substring" body-string)
+	    (?p r "regexp string" body-string)
+	    (?b before "before date" date)
+	    (?a at "at date" date)
+	    (?n now "this date" date)
+	    (?< < "less than number" number)
+	    (?> > "greater than number" number)
+	    (?= = "equal to number" number)))
+	 (char-to-perm
+	  (list (list ?t (current-time-string) "temporary")
+		'(?p perm "permanent") '(?i now "immediate")))
+	 (mimic gnus-score-mimic-keymap)
+	 (hchar (and gnus-score-default-header
+		     (aref (symbol-name gnus-score-default-header) 0)))
+	 (tchar (and gnus-score-default-type
+		     (aref (symbol-name gnus-score-default-type) 0)))
+	 (pchar (and gnus-score-default-duration
+		     (aref (symbol-name gnus-score-default-duration) 0)))
+	 entry temporary type match)
+
+    (unwind-protect
+	(progn
+
+	  ;; First we read the header to score.
+	  (while (not hchar)
+	    (if mimic
+		(progn
+		  (sit-for 1)
+		  (message "%c-" prefix))
+	      (message "%s header (%s?): " (if increase "Increase" "Lower")
+		       (mapconcat (lambda (s) (char-to-string (car s)))
+				  char-to-header "")))
+	    (setq hchar (read-char))
+	    (when (or (= hchar ??) (= hchar ?\C-h))
+	      (setq hchar nil)
+	      (gnus-score-insert-help "Match on header" char-to-header 1)))
+
+	  (gnus-score-kill-help-buffer)
+	  (unless (setq entry (assq (downcase hchar) char-to-header))
+	    (if mimic (error "%c %c" prefix hchar) (error "")))
+
+	  (when (/= (downcase hchar) hchar)
+	    ;; This was a majuscule, so we end reading and set the defaults.
+	    (if mimic (message "%c %c" prefix hchar) (message ""))
+	    (setq tchar (or tchar ?s)
+		  pchar (or pchar ?t)))
+
+	  ;; We continue reading - the type.
+	  (while (not tchar)
+	    (if mimic
+		(progn
+		  (sit-for 1) (message "%c %c-" prefix hchar))
+	      (message "%s header '%s' with match type (%s?): "
+		       (if increase "Increase" "Lower")
+		       (nth 1 entry)
+		       (mapconcat (lambda (s)
+				    (if (eq (nth 4 entry)
+					    (nth 3 s))
+					(char-to-string (car s))
+				      ""))
+				  char-to-type "")))
+	    (setq tchar (read-char))
+	    (when (or (= tchar ??) (= tchar ?\C-h))
+	      (setq tchar nil)
+	      (gnus-score-insert-help
+	       "Match type"
+	       (delq nil
+		     (mapcar (lambda (s)
+			       (if (eq (nth 4 entry)
+				       (nth 3 s))
+				   s nil))
+			     char-to-type))
+	       2)))
+
+	  (gnus-score-kill-help-buffer)
+	  (unless (setq type (nth 1 (assq (downcase tchar) char-to-type)))
+	    (if mimic (error "%c %c" prefix hchar) (error "")))
+
+	  (when (/= (downcase tchar) tchar)
+	    ;; It was a majuscule, so we end reading and use the default.
+	    (if mimic (message "%c %c %c" prefix hchar tchar)
+	      (message ""))
+	    (setq pchar (or pchar ?p)))
+
+	  ;; We continue reading.
+	  (while (not pchar)
+	    (if mimic
+		(progn
+		  (sit-for 1) (message "%c %c %c-" prefix hchar tchar))
+	      (message "%s permanence (%s?): " (if increase "Increase" "Lower")
+		       (mapconcat (lambda (s) (char-to-string (car s)))
+				  char-to-perm "")))
+	    (setq pchar (read-char))
+	    (when (or (= pchar ??) (= pchar ?\C-h))
+	      (setq pchar nil)
+	      (gnus-score-insert-help "Match permanence" char-to-perm 2)))
+
+	  (gnus-score-kill-help-buffer)
+	  (if mimic (message "%c %c %c" prefix hchar tchar pchar)
+	    (message ""))
+	  (unless (setq temporary (cadr (assq pchar char-to-perm)))
+	    ;; Deal with der(r)ided superannuated paradigms.
+	    (when (and (eq (1+ prefix) 77)
+		       (eq (+ hchar 12) 109)
+		       (eq tchar 114)
+		       (eq (- pchar 4) 111))
+	      (error "You rang?"))
+	    (if mimic
+		(error "%c %c %c %c" prefix hchar tchar pchar)
+	      (error ""))))
+      ;; Always kill the score help buffer.
+      (gnus-score-kill-help-buffer))
+
+    ;; We have all the data, so we enter this score.
+    (setq match (if (string= (nth 2 entry) "") ""
+		  (gnus-summary-header (or (nth 2 entry) (nth 1 entry)))))
+
+    ;; Modify the match, perhaps.
+    (cond
+     ((equal (nth 1 entry) "xref")
+      (when (string-match "^Xref: *" match)
+	(setq match (substring match (match-end 0))))
+      (when (string-match "^[^:]* +" match)
+	(setq match (substring match (match-end 0))))))
+
+    (when (memq type '(r R regexp Regexp))
+      (setq match (regexp-quote match)))
+
+    (gnus-summary-score-entry
+     (nth 1 entry)			; Header
+     match				; Match
+     type				; Type
+     (if (eq score 's) nil score)	; Score
+     (if (eq temporary 'perm)		; Temp
+	 nil
+       temporary)
+     (not (nth 3 entry)))		; Prompt
+    ))
+
+(defun gnus-score-insert-help (string alist idx)
+  (setq gnus-score-help-winconf (current-window-configuration))
+  (save-excursion
+    (set-buffer (get-buffer-create "*Score Help*"))
+    (buffer-disable-undo (current-buffer))
+    (delete-windows-on (current-buffer))
+    (erase-buffer)
+    (insert string ":\n\n")
+    (let ((max -1)
+	  (list alist)
+	  (i 0)
+	  n width pad format)
+      ;; find the longest string to display
+      (while list
+	(setq n (length (nth idx (car list))))
+	(unless (> max n)
+	  (setq max n))
+	(setq list (cdr list)))
+      (setq max (+ max 4))		; %c, `:', SPACE, a SPACE at end
+      (setq n (/ (1- (window-width)) max)) ; items per line
+      (setq width (/ (1- (window-width)) n)) ; width of each item
+      ;; insert `n' items, each in a field of width `width'
+      (while alist
+	(if (< i n)
+	    ()
+	  (setq i 0)
+	  (delete-char -1)		; the `\n' takes a char
+	  (insert "\n"))
+	(setq pad (- width 3))
+	(setq format (concat "%c: %-" (int-to-string pad) "s"))
+	(insert (format format (caar alist) (nth idx (car alist))))
+	(setq alist (cdr alist))
+	(setq i (1+ i))))
+    ;; display ourselves in a small window at the bottom
+    (gnus-appt-select-lowest-window)
+    (split-window)
+    (pop-to-buffer "*Score Help*")
+    (let ((window-min-height 1))
+      (shrink-window-if-larger-than-buffer))
+    (select-window (get-buffer-window gnus-summary-buffer))))
+
+(defun gnus-summary-header (header &optional no-err)
+  ;; Return HEADER for current articles, or error.
+  (let ((article (gnus-summary-article-number))
+	headers)
+    (if article
+	(if (and (setq headers (gnus-summary-article-header article))
+		 (vectorp headers))
+	    (aref headers (nth 1 (assoc header gnus-header-index)))
+	  (if no-err
+	      nil
+	    (error "Pseudo-articles can't be scored")))
+      (if no-err
+	  (error "No article on current line")
+	nil))))
+
+(defun gnus-newsgroup-score-alist ()
+  (or
+   (let ((param-file (gnus-group-find-parameter
+		      gnus-newsgroup-name 'score-file)))
+     (when param-file
+       (gnus-score-load param-file)))
+   (gnus-score-load
+    (gnus-score-file-name gnus-newsgroup-name)))
+  gnus-score-alist)
+
+(defsubst gnus-score-get (symbol &optional alist)
+  ;; Get SYMBOL's definition in ALIST.
+  (cdr (assoc symbol
+	      (or alist
+		  gnus-score-alist
+		  (gnus-newsgroup-score-alist)))))
+
+(defun gnus-summary-score-entry (header match type score date
+					&optional prompt silent)
+  "Enter score file entry.
+HEADER is the header being scored.
+MATCH is the string we are looking for.
+TYPE is the match type: substring, regexp, exact, fuzzy.
+SCORE is the score to add.
+DATE is the expire date, or nil for no expire, or 'now for immediate expire.
+If optional argument `PROMPT' is non-nil, allow user to edit match.
+If optional argument `SILENT' is nil, show effect of score entry."
+  (interactive
+   (list (completing-read "Header: "
+			  gnus-header-index
+			  (lambda (x) (fboundp (nth 2 x)))
+			  t)
+	 (read-string "Match: ")
+	 (if (y-or-n-p "Use regexp match? ") 'r 's)
+	 (and current-prefix-arg
+	      (prefix-numeric-value current-prefix-arg))
+	 (cond ((not (y-or-n-p "Add to score file? "))
+		'now)
+	       ((y-or-n-p "Expire kill? ")
+		(current-time-string))
+	       (t nil))))
+  ;; Regexp is the default type.
+  (when (eq type t)
+    (setq type 'r))
+  ;; Simplify matches...
+  (cond ((or (eq type 'r) (eq type 's) (eq type nil))
+	 (setq match (if match (gnus-simplify-subject-re match) "")))
+	((eq type 'f)
+	 (setq match (gnus-simplify-subject-fuzzy match))))
+  (let ((score (gnus-score-default score))
+	(header (format "%s" (downcase header)))
+	new)
+    (when prompt
+      (setq match (read-string
+		   (format "Match %s on %s, %s: "
+			   (cond ((eq date 'now)
+				  "now")
+				 ((stringp date)
+				  "temp")
+				 (t "permanent"))
+			   header
+			   (if (< score 0) "lower" "raise"))
+		   (if (numberp match)
+		       (int-to-string match)
+		     match))))
+
+    ;; Get rid of string props.
+    (setq match (format "%s" match))
+
+    ;; If this is an integer comparison, we transform from string to int.
+    (when (eq (nth 2 (assoc header gnus-header-index)) 'gnus-score-integer)
+      (setq match (string-to-int match)))
+
+    (unless (eq date 'now)
+      ;; Add the score entry to the score file.
+      (when (= score gnus-score-interactive-default-score)
+	(setq score nil))
+      (let ((old (gnus-score-get header))
+	    elem)
+	(setq new
+	      (cond
+	       (type
+		(list match score
+		      (and date (if (numberp date) date
+				  (gnus-day-number date)))
+		      type))
+	       (date (list match score (gnus-day-number date)))
+	       (score (list match score))
+	       (t (list match))))
+	;; We see whether we can collapse some score entries.
+	;; This isn't quite correct, because there may be more elements
+	;; later on with the same key that have matching elems...  Hm.
+	(if (and old
+		 (setq elem (assoc match old))
+		 (eq (nth 3 elem) (nth 3 new))
+		 (or (and (numberp (nth 2 elem)) (numberp (nth 2 new)))
+		     (and (not (nth 2 elem)) (not (nth 2 new)))))
+	    ;; Yup, we just add this new score to the old elem.
+	    (setcar (cdr elem) (+ (or (nth 1 elem)
+				      gnus-score-interactive-default-score)
+				  (or (nth 1 new)
+				      gnus-score-interactive-default-score)))
+	  ;; Nope, we have to add a new elem.
+	  (gnus-score-set header (if old (cons new old) (list new))))
+	(gnus-score-set 'touched '(t))))
+
+    ;; Score the current buffer.
+    (unless silent
+      (if (and (>= (nth 1 (assoc header gnus-header-index)) 0)
+	       (eq (nth 2 (assoc header gnus-header-index))
+		   'gnus-score-string))
+	  (gnus-summary-score-effect header match type score)
+	(gnus-summary-rescore)))
+
+    ;; Return the new scoring rule.
+    new))
+
+(defun gnus-summary-score-effect (header match type score)
+  "Simulate the effect of a score file entry.
+HEADER is the header being scored.
+MATCH is the string we are looking for.
+TYPE is the score type.
+SCORE is the score to add."
+  (interactive (list (completing-read "Header: "
+				      gnus-header-index
+				      (lambda (x) (fboundp (nth 2 x)))
+				      t)
+		     (read-string "Match: ")
+		     (y-or-n-p "Use regexp match? ")
+		     (prefix-numeric-value current-prefix-arg)))
+  (save-excursion
+    (unless (and (stringp match) (> (length match) 0))
+      (error "No match"))
+    (goto-char (point-min))
+    (let ((regexp (cond ((eq type 'f)
+			 (gnus-simplify-subject-fuzzy match))
+			((eq type 'r)
+			 match)
+			((eq type 'e)
+			 (concat "\\`" (regexp-quote match) "\\'"))
+			(t
+			 (regexp-quote match)))))
+      (while (not (eobp))
+	(let ((content (gnus-summary-header header 'noerr))
+	      (case-fold-search t))
+	  (and content
+	       (when (if (eq type 'f)
+			 (string-equal (gnus-simplify-subject-fuzzy content)
+				       regexp)
+		       (string-match regexp content))
+		 (gnus-summary-raise-score score))))
+	(beginning-of-line 2))))
+  (gnus-set-mode-line 'summary))
+
+(defun gnus-summary-score-crossposting (score date)
+  ;; Enter score file entry for current crossposting.
+  ;; SCORE is the score to add.
+  ;; DATE is the expire date.
+  (let ((xref (gnus-summary-header "xref"))
+	(start 0)
+	group)
+    (unless xref
+      (error "This article is not crossposted"))
+    (while (string-match " \\([^ \t]+\\):" xref start)
+      (setq start (match-end 0))
+      (when (not (string=
+		  (setq group
+			(substring xref (match-beginning 1) (match-end 1)))
+		  gnus-newsgroup-name))
+	(gnus-summary-score-entry
+	 "xref" (concat " " group ":") nil score date t)))))
+
+
+;;;
+;;; Gnus Score Files
+;;;
+
+;; All score code written by Per Abrahamsen <abraham@iesd.auc.dk>.
+
+;; Added by Per Abrahamsen <amanda@iesd.auc.dk>.
+(defun gnus-score-set-mark-below (score)
+  "Automatically mark articles with score below SCORE as read."
+  (interactive
+   (list (or (and current-prefix-arg (prefix-numeric-value current-prefix-arg))
+	     (string-to-int (read-string "Mark below: ")))))
+  (setq score (or score gnus-summary-default-score 0))
+  (gnus-score-set 'mark (list score))
+  (gnus-score-set 'touched '(t))
+  (setq gnus-summary-mark-below score)
+  (gnus-score-update-lines))
+
+(defun gnus-score-update-lines ()
+  "Update all lines in the summary buffer."
+  (save-excursion
+    (goto-char (point-min))
+    (while (not (eobp))
+      (gnus-summary-update-line)
+      (forward-line 1))))
+
+(defun gnus-score-update-all-lines ()
+  "Update all lines in the summary buffer, even the hidden ones."
+  (save-excursion
+    (goto-char (point-min))
+    (let (hidden)
+      (while (not (eobp))
+	(when (gnus-summary-show-thread)
+	  (push (point) hidden))
+	(gnus-summary-update-line)
+	(forward-line 1))
+      ;; Re-hide the hidden threads.
+      (while hidden
+	(goto-char (pop hidden))
+	(gnus-summary-hide-thread)))))
+
+(defun gnus-score-set-expunge-below (score)
+  "Automatically expunge articles with score below SCORE."
+  (interactive
+   (list (or (and current-prefix-arg (prefix-numeric-value current-prefix-arg))
+	     (string-to-int (read-string "Set expunge below: ")))))
+  (setq score (or score gnus-summary-default-score 0))
+  (gnus-score-set 'expunge (list score))
+  (gnus-score-set 'touched '(t)))
+
+(defun gnus-score-followup-article (&optional score)
+  "Add SCORE to all followups to the article in the current buffer."
+  (interactive "P")
+  (setq score (gnus-score-default score))
+  (when (gnus-buffer-live-p gnus-summary-buffer)
+    (save-excursion
+      (save-restriction
+	(message-narrow-to-headers)
+	(let ((id (mail-fetch-field "message-id")))
+	  (when id
+	    (set-buffer gnus-summary-buffer)
+	    (gnus-summary-score-entry
+	     "references" (concat id "[ \t]*$") 'r
+	     score (current-time-string) nil t)))))))
+
+(defun gnus-score-followup-thread (&optional score)
+  "Add SCORE to all later articles in the thread the current buffer is part of."
+  (interactive "P")
+  (setq score (gnus-score-default score))
+  (when (gnus-buffer-live-p gnus-summary-buffer)
+    (save-excursion
+      (save-restriction
+	(goto-char (point-min))
+	(let ((id (mail-fetch-field "message-id")))
+	  (when id
+	    (set-buffer gnus-summary-buffer)
+	    (gnus-summary-score-entry
+	     "references" id 's
+	     score (current-time-string))))))))
+
+(defun gnus-score-set (symbol value &optional alist)
+  ;; Set SYMBOL to VALUE in ALIST.
+  (let* ((alist
+	  (or alist
+	      gnus-score-alist
+	      (gnus-newsgroup-score-alist)))
+	 (entry (assoc symbol alist)))
+    (cond ((gnus-score-get 'read-only alist)
+	   ;; This is a read-only score file, so we do nothing.
+	   )
+	  (entry
+	   (setcdr entry value))
+	  ((null alist)
+	   (error "Empty alist"))
+	  (t
+	   (setcdr alist
+		   (cons (cons symbol value) (cdr alist)))))))
+
+(defun gnus-summary-raise-score (n)
+  "Raise the score of the current article by N."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (gnus-summary-set-score (+ (gnus-summary-article-score)
+			     (or n gnus-score-interactive-default-score ))))
+
+(defun gnus-summary-set-score (n)
+  "Set the score of the current article to N."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (save-excursion
+    (gnus-summary-show-thread)
+    (let ((buffer-read-only nil))
+      ;; Set score.
+      (gnus-summary-update-mark
+       (if (= n (or gnus-summary-default-score 0)) ? 
+	 (if (< n (or gnus-summary-default-score 0))
+	     gnus-score-below-mark gnus-score-over-mark))
+       'score))
+    (let* ((article (gnus-summary-article-number))
+	   (score (assq article gnus-newsgroup-scored)))
+      (if score (setcdr score n)
+	(push (cons article n) gnus-newsgroup-scored)))
+    (gnus-summary-update-line)))
+
+(defun gnus-summary-current-score ()
+  "Return the score of the current article."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-message 1 "%s" (gnus-summary-article-score)))
+
+(defun gnus-score-change-score-file (file)
+  "Change current score alist."
+  (interactive
+   (list (read-file-name "Change to score file: " gnus-kill-files-directory)))
+  (gnus-score-load-file file)
+  (gnus-set-mode-line 'summary))
+
+(defvar gnus-score-edit-exit-function)
+(defun gnus-score-edit-current-scores (file)
+  "Edit the current score alist."
+  (interactive (list gnus-current-score-file))
+  (gnus-set-global-variables)
+  (let ((winconf (current-window-configuration)))
+    (when (buffer-name gnus-summary-buffer)
+      (gnus-score-save))
+    (gnus-make-directory (file-name-directory file))
+    (setq gnus-score-edit-buffer (find-file-noselect file))
+    (gnus-configure-windows 'edit-score)
+    (gnus-score-mode)
+    (setq gnus-score-edit-exit-function 'gnus-score-edit-done)
+    (make-local-variable 'gnus-prev-winconf)
+    (setq gnus-prev-winconf winconf))
+  (gnus-message
+   4 (substitute-command-keys
+      "\\<gnus-score-mode-map>\\[gnus-score-edit-exit] to save edits")))
+
+(defun gnus-score-edit-file (file)
+  "Edit a score file."
+  (interactive
+   (list (read-file-name "Edit score file: " gnus-kill-files-directory)))
+  (gnus-make-directory (file-name-directory file))
+  (when (buffer-name gnus-summary-buffer)
+    (gnus-score-save))
+  (let ((winconf (current-window-configuration)))
+    (setq gnus-score-edit-buffer (find-file-noselect file))
+    (gnus-configure-windows 'edit-score)
+    (gnus-score-mode)
+    (setq gnus-score-edit-exit-function 'gnus-score-edit-done)
+    (make-local-variable 'gnus-prev-winconf)
+    (setq gnus-prev-winconf winconf))
+  (gnus-message
+   4 (substitute-command-keys
+      "\\<gnus-score-mode-map>\\[gnus-score-edit-exit] to save edits")))
+
+(defun gnus-score-load-file (file)
+  ;; Load score file FILE.  Returns a list a retrieved score-alists.
+  (let* ((file (expand-file-name
+		(or (and (string-match
+			  (concat "^" (expand-file-name
+				       gnus-kill-files-directory))
+			  (expand-file-name file))
+			 file)
+		    (concat (file-name-as-directory gnus-kill-files-directory)
+			    file))))
+	 (cached (assoc file gnus-score-cache))
+	 (global (member file gnus-internal-global-score-files))
+	 lists alist)
+    (if cached
+	;; The score file was already loaded.
+	(setq alist (cdr cached))
+      ;; We load the score file.
+      (setq gnus-score-alist nil)
+      (setq alist (gnus-score-load-score-alist file))
+      ;; We add '(touched) to the alist to signify that it hasn't been
+      ;; touched (yet).
+      (unless (assq 'touched alist)
+	(push (list 'touched nil) alist))
+      ;; If it is a global score file, we make it read-only.
+      (and global
+	   (not (assq 'read-only alist))
+	   (push (list 'read-only t) alist))
+      (push (cons file alist) gnus-score-cache))
+    (let ((a alist)
+	  found)
+      (while a
+	;; Downcase all header names.
+	(when (stringp (caar a))
+	  (setcar (car a) (downcase (caar a)))
+	  (setq found t))
+	(pop a))
+      ;; If there are actual scores in the alist, we add it to the
+      ;; return value of this function.
+      (when found
+	(setq lists (list alist))))
+    ;; Treat the other possible atoms in the score alist.
+    (let ((mark (car (gnus-score-get 'mark alist)))
+	  (expunge (car (gnus-score-get 'expunge alist)))
+	  (mark-and-expunge (car (gnus-score-get 'mark-and-expunge alist)))
+	  (files (gnus-score-get 'files alist))
+	  (exclude-files (gnus-score-get 'exclude-files alist))
+          (orphan (car (gnus-score-get 'orphan alist)))
+	  (adapt (gnus-score-get 'adapt alist))
+	  (thread-mark-and-expunge
+	   (car (gnus-score-get 'thread-mark-and-expunge alist)))
+	  (adapt-file (car (gnus-score-get 'adapt-file alist)))
+	  (local (gnus-score-get 'local alist))
+	  (decay (car (gnus-score-get 'decay alist)))
+	  (eval (car (gnus-score-get 'eval alist))))
+      ;; Perform possible decays.
+      (when (and gnus-decay-scores
+		 (gnus-decay-scores
+		  alist (or decay (gnus-time-to-day (current-time)))))
+	(gnus-score-set 'touched '(t) alist)
+	(gnus-score-set 'decay (list (gnus-time-to-day (current-time)))))
+      ;; We do not respect eval and files atoms from global score
+      ;; files.
+      (and files (not global)
+	   (setq lists (apply 'append lists
+			      (mapcar (lambda (file)
+					(gnus-score-load-file file))
+				      (if adapt-file (cons adapt-file files)
+					files)))))
+      (and eval (not global) (eval eval))
+      ;; We then expand any exclude-file directives.
+      (setq gnus-scores-exclude-files
+	    (nconc
+	     (mapcar
+	      (lambda (sfile)
+		(expand-file-name sfile (file-name-directory file)))
+	      exclude-files)
+	     gnus-scores-exclude-files))
+      (if (not local)
+	  ()
+	(save-excursion
+	  (set-buffer gnus-summary-buffer)
+	  (while local
+	    (and (consp (car local))
+		 (symbolp (caar local))
+		 (progn
+		   (make-local-variable (caar local))
+		   (set (caar local) (nth 1 (car local)))))
+	    (setq local (cdr local)))))
+      (when orphan
+	(setq gnus-orphan-score orphan))
+      (setq gnus-adaptive-score-alist
+	    (cond ((equal adapt '(t))
+		   (setq gnus-newsgroup-adaptive t)
+		   gnus-default-adaptive-score-alist)
+		  ((equal adapt '(ignore))
+		   (setq gnus-newsgroup-adaptive nil))
+		  ((consp adapt)
+		   (setq gnus-newsgroup-adaptive t)
+		   adapt)
+		  (t
+		   ;;(setq gnus-newsgroup-adaptive gnus-use-adaptive-scoring)
+		   gnus-default-adaptive-score-alist)))
+      (setq gnus-thread-expunge-below
+	    (or thread-mark-and-expunge gnus-thread-expunge-below))
+      (setq gnus-summary-mark-below
+	    (or mark mark-and-expunge gnus-summary-mark-below))
+      (setq gnus-summary-expunge-below
+	    (or expunge mark-and-expunge gnus-summary-expunge-below))
+      (setq gnus-newsgroup-adaptive-score-file
+	    (or adapt-file gnus-newsgroup-adaptive-score-file)))
+    (setq gnus-current-score-file file)
+    (setq gnus-score-alist alist)
+    lists))
+
+(defun gnus-score-load (file)
+  ;; Load score FILE.
+  (let ((cache (assoc file gnus-score-cache)))
+    (if cache
+	(setq gnus-score-alist (cdr cache))
+      (setq gnus-score-alist nil)
+      (gnus-score-load-score-alist file)
+      (unless gnus-score-alist
+	(setq gnus-score-alist (copy-alist '((touched nil)))))
+      (push (cons file gnus-score-alist) gnus-score-cache))))
+
+(defun gnus-score-remove-from-cache (file)
+  (setq gnus-score-cache
+	(delq (assoc file gnus-score-cache) gnus-score-cache)))
+
+(defun gnus-score-load-score-alist (file)
+  "Read score FILE."
+  (let (alist)
+    (if (not (file-readable-p file))
+	;; Couldn't read file.
+	(setq gnus-score-alist nil)
+      ;; Read file.
+      (save-excursion
+	(gnus-set-work-buffer)
+	(insert-file-contents file)
+	(goto-char (point-min))
+	;; Only do the loading if the score file isn't empty.
+	(when (save-excursion (re-search-forward "[()0-9a-zA-Z]" nil t))
+	  (setq alist
+		(condition-case ()
+		    (read (current-buffer))
+		  (error
+		   (gnus-error 3.2 "Problem with score file %s" file))))))
+      (if (eq (car alist) 'setq)
+	  ;; This is an old-style score file.
+	  (setq gnus-score-alist (gnus-score-transform-old-to-new alist))
+	(setq gnus-score-alist alist))
+      ;; Check the syntax of the score file.
+      (setq gnus-score-alist
+	    (gnus-score-check-syntax gnus-score-alist file)))))
+
+(defun gnus-score-check-syntax (alist file)
+  "Check the syntax of the score ALIST."
+  (cond
+   ((null alist)
+    nil)
+   ((not (consp alist))
+    (gnus-message 1 "Score file is not a list: %s" file)
+    (ding)
+    nil)
+   (t
+    (let ((a alist)
+	  sr err s type)
+      (while (and a (not err))
+	(setq
+	 err
+	 (cond
+	  ((not (listp (car a)))
+	   (format "Illegal score element %s in %s" (car a) file))
+	  ((stringp (caar a))
+	   (cond
+	    ((not (listp (setq sr (cdar a))))
+	     (format "Illegal header match %s in %s" (nth 1 (car a)) file))
+	    (t
+	     (setq type (caar a))
+	     (while (and sr (not err))
+	       (setq s (pop sr))
+	       (setq
+		err
+		(cond
+		 ((if (member (downcase type) '("lines" "chars"))
+		      (not (numberp (car s)))
+		    (not (stringp (car s))))
+		  (format "Illegal match %s in %s" (car s) file))
+		 ((and (cadr s) (not (integerp (cadr s))))
+		  (format "Non-integer score %s in %s" (cadr s) file))
+		 ((and (caddr s) (not (integerp (caddr s))))
+		  (format "Non-integer date %s in %s" (caddr s) file))
+		 ((and (cadddr s) (not (symbolp (cadddr s))))
+		  (format "Non-symbol match type %s in %s" (cadddr s) file)))))
+	     err)))))
+	(setq a (cdr a)))
+      (if err
+	  (progn
+	    (ding)
+	    (gnus-message 3 err)
+	    (sit-for 2)
+	    nil)
+	alist)))))
+
+(defun gnus-score-transform-old-to-new (alist)
+  (let* ((alist (nth 2 alist))
+	 out entry)
+    (when (eq (car alist) 'quote)
+      (setq alist (nth 1 alist)))
+    (while alist
+      (setq entry (car alist))
+      (if (stringp (car entry))
+	  (let ((scor (cdr entry)))
+	    (push entry out)
+	    (while scor
+	      (setcar scor
+		      (list (caar scor) (nth 2 (car scor))
+			    (and (nth 3 (car scor))
+				 (gnus-day-number (nth 3 (car scor))))
+			    (if (nth 1 (car scor)) 'r 's)))
+	      (setq scor (cdr scor))))
+	(push (if (not (listp (cdr entry)))
+		  (list (car entry) (cdr entry))
+		entry)
+	      out))
+      (setq alist (cdr alist)))
+    (cons (list 'touched t) (nreverse out))))
+
+(defun gnus-score-save ()
+  ;; Save all score information.
+  (let ((cache gnus-score-cache)
+	entry score file)
+    (save-excursion
+      (setq gnus-score-alist nil)
+      (nnheader-set-temp-buffer " *Gnus Scores*")
+      (while cache
+	(current-buffer)
+	(setq entry (pop cache)
+	      file (car entry)
+	      score (cdr entry))
+	(if (or (not (equal (gnus-score-get 'touched score) '(t)))
+		(gnus-score-get 'read-only score)
+		(and (file-exists-p file)
+		     (not (file-writable-p file))))
+	    ()
+	  (setq score (setcdr entry (delq (assq 'touched score) score)))
+	  (erase-buffer)
+	  (let (emacs-lisp-mode-hook)
+	    (if (string-match
+		 (concat (regexp-quote gnus-adaptive-file-suffix)
+			 "$")
+		 file)
+		;; This is an adaptive score file, so we do not run
+		;; it through `pp'.  These files can get huge, and
+		;; are not meant to be edited by human hands.
+		(gnus-prin1 score)
+	      ;; This is a normal score file, so we print it very
+	      ;; prettily.
+	      (pp score (current-buffer))))
+	  (gnus-make-directory (file-name-directory file))
+	  ;; If the score file is empty, we delete it.
+	  (if (zerop (buffer-size))
+	      (delete-file file)
+	    ;; There are scores, so we write the file.
+	    (when (file-writable-p file)
+	      (gnus-write-buffer file)
+	      (when gnus-score-after-write-file-function
+		(funcall gnus-score-after-write-file-function file)))))
+	(and gnus-score-uncacheable-files
+	     (string-match gnus-score-uncacheable-files file)
+	     (gnus-score-remove-from-cache file)))
+      (kill-buffer (current-buffer)))))
+
+(defun gnus-score-load-files (score-files)
+  "Load all score files in SCORE-FILES."
+  ;; Load the score files.
+  (let (scores)
+    (while score-files
+      (if (stringp (car score-files))
+	  ;; It is a string, which means that it's a score file name,
+	  ;; so we load the score file and add the score alist to
+	  ;; the list of alists.
+	  (setq scores (nconc (gnus-score-load-file (car score-files)) scores))
+	;; It is an alist, so we just add it to the list directly.
+	(setq scores (nconc (car score-files) scores)))
+      (setq score-files (cdr score-files)))
+    ;; Prune the score files that are to be excluded, if any.
+    (when gnus-scores-exclude-files
+      (let ((s scores)
+	    c)
+	(while s
+	  (and (setq c (rassq (car s) gnus-score-cache))
+	       (member (car c) gnus-scores-exclude-files)
+	       (setq scores (delq (car s) scores)))
+	  (setq s (cdr s)))))
+    scores))
+
+(defun gnus-score-headers (score-files &optional trace)
+  ;; Score `gnus-newsgroup-headers'.
+  (let (scores news)
+    ;; PLM: probably this is not the best place to clear orphan-score
+    (setq gnus-orphan-score nil
+	  gnus-scores-articles nil
+	  gnus-scores-exclude-files nil
+	  scores (gnus-score-load-files score-files))
+    (setq news scores)
+    ;; Do the scoring.
+    (while news
+      (setq scores news
+	    news nil)
+      (when (and gnus-summary-default-score
+		 scores)
+	(let* ((entries gnus-header-index)
+	       (now (gnus-day-number (current-time-string)))
+	       (expire (and gnus-score-expiry-days
+			    (- now gnus-score-expiry-days)))
+	       (headers gnus-newsgroup-headers)
+	       (current-score-file gnus-current-score-file)
+	       entry header new)
+	  (gnus-message 5 "Scoring...")
+	  ;; Create articles, an alist of the form `(HEADER . SCORE)'.
+	  (while (setq header (pop headers))
+	    ;; WARNING: The assq makes the function O(N*S) while it could
+	    ;; be written as O(N+S), where N is (length gnus-newsgroup-headers)
+	    ;; and S is (length gnus-newsgroup-scored).
+	    (unless (assq (mail-header-number header) gnus-newsgroup-scored)
+	      (setq gnus-scores-articles ;Total of 2 * N cons-cells used.
+		    (cons (cons header (or gnus-summary-default-score 0))
+			  gnus-scores-articles))))
+
+	  (save-excursion
+	    (set-buffer (get-buffer-create "*Headers*"))
+	    (buffer-disable-undo (current-buffer))
+
+	    ;; Set the global variant of this variable.
+	    (setq gnus-current-score-file current-score-file)
+	    ;; score orphans
+	    (when gnus-orphan-score
+	      (setq gnus-score-index
+		    (nth 1 (assoc "references" gnus-header-index)))
+	      (gnus-score-orphans gnus-orphan-score))
+	    ;; Run each header through the score process.
+	    (while entries
+	      (setq entry (pop entries)
+		    header (nth 0 entry)
+		    gnus-score-index (nth 1 (assoc header gnus-header-index)))
+	      (when (< 0 (apply 'max (mapcar
+				      (lambda (score)
+					(length (gnus-score-get header score)))
+				      scores)))
+		;; Call the scoring function for this type of "header".
+		(when (setq new (funcall (nth 2 entry) scores header
+					 now expire trace))
+		  (push new news))))
+	    ;; Remove the buffer.
+	    (kill-buffer (current-buffer)))
+
+	  ;; Add articles to `gnus-newsgroup-scored'.
+	  (while gnus-scores-articles
+	    (when (or (/= gnus-summary-default-score
+			  (cdar gnus-scores-articles))
+		      gnus-save-score)
+	      (push (cons (mail-header-number (caar gnus-scores-articles))
+			  (cdar gnus-scores-articles))
+		    gnus-newsgroup-scored))
+	    (setq gnus-scores-articles (cdr gnus-scores-articles)))
+
+	  (let (score)
+	    (while (setq score (pop scores))
+	      (while score
+		(when (listp (caar score))
+		  (gnus-score-advanced (car score) trace))
+		(pop score))))
+
+	  (gnus-message 5 "Scoring...done"))))))
+
+
+(defun gnus-get-new-thread-ids (articles)
+  (let ((index (nth 1 (assoc "message-id" gnus-header-index)))
+        (refind gnus-score-index)
+        id-list art this tref)
+    (while articles
+      (setq art (car articles)
+            this (aref (car art) index)
+            tref (aref (car art) refind)
+            articles (cdr articles))
+      (when (string-equal tref "")	;no references line
+	(push this id-list)))
+    id-list))
+
+;; Orphan functions written by plm@atcmp.nl (Peter Mutsaers).
+(defun gnus-score-orphans (score)
+  (let ((new-thread-ids (gnus-get-new-thread-ids gnus-scores-articles))
+        alike articles art arts this last this-id)
+
+    (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<)
+	  articles gnus-scores-articles)
+
+    ;;more or less the same as in gnus-score-string
+    (erase-buffer)
+    (while articles
+      (setq art (car articles)
+            this (aref (car art) gnus-score-index)
+            articles (cdr articles))
+      ;;completely skip if this is empty (not a child, so not an orphan)
+      (when (not (string= this ""))
+	(if (equal last this)
+	    ;; O(N*H) cons-cells used here, where H is the number of
+	    ;; headers.
+	    (push art alike)
+	  (when last
+	    ;; Insert the line, with a text property on the
+	    ;; terminating newline referring to the articles with
+	    ;; this line.
+	    (insert last ?\n)
+	    (put-text-property (1- (point)) (point) 'articles alike))
+	  (setq alike (list art)
+		last this))))
+    (when last				; Bwadr, duplicate code.
+      (insert last ?\n)
+      (put-text-property (1- (point)) (point) 'articles alike))
+
+    ;; PLM: now delete those lines that contain an entry from new-thread-ids
+    (while new-thread-ids
+      (setq this-id (car new-thread-ids)
+            new-thread-ids (cdr new-thread-ids))
+      (goto-char (point-min))
+      (while (search-forward this-id nil t)
+        ;; found a match.  remove this line
+	(beginning-of-line)
+	(kill-line 1)))
+
+    ;; now for each line: update its articles with score by moving to
+    ;; every end-of-line in the buffer and read the articles property
+    (goto-char (point-min))
+    (while (eq 0 (progn
+                   (end-of-line)
+                   (setq arts (get-text-property (point) 'articles))
+                   (while arts
+                     (setq art (car arts)
+                           arts (cdr arts))
+                     (setcdr art (+ score (cdr art))))
+                   (forward-line))))))
+
+
+(defun gnus-score-integer (scores header now expire &optional trace)
+  (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
+	entries alist)
+
+    ;; Find matches.
+    (while scores
+      (setq alist (car scores)
+	    scores (cdr scores)
+	    entries (assoc header alist))
+      (while (cdr entries)		;First entry is the header index.
+	(let* ((rest (cdr entries))
+	       (kill (car rest))
+	       (match (nth 0 kill))
+	       (type (or (nth 3 kill) '>))
+	       (score (or (nth 1 kill) gnus-score-interactive-default-score))
+	       (date (nth 2 kill))
+	       (found nil)
+	       (match-func (if (or (eq type '>) (eq type '<) (eq type '<=)
+				   (eq type '>=) (eq type '=))
+			       type
+			     (error "Illegal match type: %s" type)))
+	       (articles gnus-scores-articles))
+	  ;; Instead of doing all the clever stuff that
+	  ;; `gnus-score-string' does to minimize searches and stuff,
+	  ;; I will assume that people generally will put so few
+	  ;; matches on numbers that any cleverness will take more
+	  ;; time than one would gain.
+	  (while articles
+	    (when (funcall match-func
+			   (or (aref (caar articles) gnus-score-index) 0)
+			   match)
+	      (when trace
+		(push (cons (car-safe (rassq alist gnus-score-cache)) kill)
+		      gnus-score-trace))
+	      (setq found t)
+	      (setcdr (car articles) (+ score (cdar articles))))
+	    (setq articles (cdr articles)))
+	  ;; Update expire date
+	  (cond ((null date))		;Permanent entry.
+		((and found gnus-update-score-entry-dates) ;Match, update date.
+		 (gnus-score-set 'touched '(t) alist)
+		 (setcar (nthcdr 2 kill) now))
+		((and expire (< date expire)) ;Old entry, remove.
+		 (gnus-score-set 'touched '(t) alist)
+		 (setcdr entries (cdr rest))
+		 (setq rest entries)))
+	  (setq entries rest)))))
+  nil)
+
+(defun gnus-score-date (scores header now expire &optional trace)
+  (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
+	entries alist match match-func article)
+
+    ;; Find matches.
+    (while scores
+      (setq alist (car scores)
+	    scores (cdr scores)
+	    entries (assoc header alist))
+      (while (cdr entries)		;First entry is the header index.
+	(let* ((rest (cdr entries))
+	       (kill (car rest))
+	       (type (or (nth 3 kill) 'before))
+	       (score (or (nth 1 kill) gnus-score-interactive-default-score))
+	       (date (nth 2 kill))
+	       (found nil)
+	       (articles gnus-scores-articles)
+	       l)
+	  (cond
+	   ((eq type 'after)
+	    (setq match-func 'string<
+		  match (gnus-date-iso8601 (nth 0 kill))))
+	   ((eq type 'before)
+	    (setq match-func 'gnus-string>
+		  match (gnus-date-iso8601 (nth 0 kill))))
+	   ((eq type 'at)
+	    (setq match-func 'string=
+		  match (gnus-date-iso8601 (nth 0 kill))))
+	   ((eq type 'regexp)
+	    (setq match-func 'string-match
+		  match (nth 0 kill)))
+	   (t (error "Illegal match type: %s" type)))
+	  ;; Instead of doing all the clever stuff that
+	  ;; `gnus-score-string' does to minimize searches and stuff,
+	  ;; I will assume that people generally will put so few
+	  ;; matches on numbers that any cleverness will take more
+	  ;; time than one would gain.
+	  (while (setq article (pop articles))
+	    (when (and
+		   (setq l (aref (car article) gnus-score-index))
+		   (funcall match-func match (gnus-date-iso8601 l)))
+	      (when trace
+		(push (cons (car-safe (rassq alist gnus-score-cache)) kill)
+		      gnus-score-trace))
+	      (setq found t)
+	      (setcdr article (+ score (cdr article)))))
+	  ;; Update expire date
+	  (cond ((null date))		;Permanent entry.
+		((and found gnus-update-score-entry-dates) ;Match, update date.
+		 (gnus-score-set 'touched '(t) alist)
+		 (setcar (nthcdr 2 kill) now))
+		((and expire (< date expire)) ;Old entry, remove.
+		 (gnus-score-set 'touched '(t) alist)
+		 (setcdr entries (cdr rest))
+		 (setq rest entries)))
+	  (setq entries rest)))))
+  nil)
+
+(defun gnus-score-body (scores header now expire &optional trace)
+  (save-excursion
+    (setq gnus-scores-articles
+	  (sort gnus-scores-articles
+		(lambda (a1 a2)
+		  (< (mail-header-number (car a1))
+		     (mail-header-number (car a2))))))
+    (set-buffer nntp-server-buffer)
+    (save-restriction
+      (let* ((buffer-read-only nil)
+	     (articles gnus-scores-articles)
+	     (all-scores scores)
+	     (request-func (cond ((string= "head" header)
+				  'gnus-request-head)
+				 ((string= "body" header)
+				  'gnus-request-body)
+				 (t 'gnus-request-article)))
+	     entries alist ofunc article last)
+	(when articles
+	  (setq last (mail-header-number (caar (last articles))))
+	  ;; Not all backends support partial fetching.  In that case,
+	  ;; we just fetch the entire article.
+	  (unless (gnus-check-backend-function
+		   (and (string-match "^gnus-" (symbol-name request-func))
+			(intern (substring (symbol-name request-func)
+					   (match-end 0))))
+		   gnus-newsgroup-name)
+	    (setq ofunc request-func)
+	    (setq request-func 'gnus-request-article))
+	  (while articles
+	    (setq article (mail-header-number (caar articles)))
+	    (gnus-message 7 "Scoring on article %s of %s..." article last)
+	    (when (funcall request-func article gnus-newsgroup-name)
+	      (widen)
+	      (goto-char (point-min))
+	      ;; If just parts of the article is to be searched, but the
+	      ;; backend didn't support partial fetching, we just narrow
+	      ;; to the relevant parts.
+	      (when ofunc
+		(if (eq ofunc 'gnus-request-head)
+		    (narrow-to-region
+		     (point)
+		     (or (search-forward "\n\n" nil t) (point-max)))
+		  (narrow-to-region
+		   (or (search-forward "\n\n" nil t) (point))
+		   (point-max))))
+	      (setq scores all-scores)
+	      ;; Find matches.
+	      (while scores
+		(setq alist (pop scores)
+		      entries (assoc header alist))
+		(while (cdr entries)	;First entry is the header index.
+		  (let* ((rest (cdr entries))
+			 (kill (car rest))
+			 (match (nth 0 kill))
+			 (type (or (nth 3 kill) 's))
+			 (score (or (nth 1 kill)
+				    gnus-score-interactive-default-score))
+			 (date (nth 2 kill))
+			 (found nil)
+			 (case-fold-search
+			  (not (or (eq type 'R) (eq type 'S)
+				   (eq type 'Regexp) (eq type 'String))))
+			 (search-func
+			  (cond ((or (eq type 'r) (eq type 'R)
+				     (eq type 'regexp) (eq type 'Regexp))
+				 're-search-forward)
+				((or (eq type 's) (eq type 'S)
+				     (eq type 'string) (eq type 'String))
+				 'search-forward)
+				(t
+				 (error "Illegal match type: %s" type)))))
+		    (goto-char (point-min))
+		    (when (funcall search-func match nil t)
+		      ;; Found a match, update scores.
+		      (setcdr (car articles) (+ score (cdar articles)))
+		      (setq found t)
+		      (when trace
+			(push
+			 (cons (car-safe (rassq alist gnus-score-cache)) kill)
+			 gnus-score-trace)))
+		    ;; Update expire date
+		    (unless trace
+		      (cond
+		       ((null date))	;Permanent entry.
+		       ((and found gnus-update-score-entry-dates)
+			;; Match, update date.
+			(gnus-score-set 'touched '(t) alist)
+			(setcar (nthcdr 2 kill) now))
+		       ((and expire (< date expire)) ;Old entry, remove.
+			(gnus-score-set 'touched '(t) alist)
+			(setcdr entries (cdr rest))
+			(setq rest entries))))
+		    (setq entries rest)))))
+	    (setq articles (cdr articles)))))))
+  nil)
+
+(defun gnus-score-thread (scores header now expire &optional trace)
+  (gnus-score-followup scores header now expire trace t))
+
+(defun gnus-score-followup (scores header now expire &optional trace thread)
+  ;; Insert the unique article headers in the buffer.
+  (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
+	(current-score-file gnus-current-score-file)
+	(all-scores scores)
+	;; gnus-score-index is used as a free variable.
+	alike last this art entries alist articles
+	new news)
+
+    ;; Change score file to the adaptive score file.  All entries that
+    ;; this function makes will be put into this file.
+    (save-excursion
+      (set-buffer gnus-summary-buffer)
+      (gnus-score-load-file
+       (or gnus-newsgroup-adaptive-score-file
+	   (gnus-score-file-name
+	    gnus-newsgroup-name gnus-adaptive-file-suffix))))
+
+    (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<)
+	  articles gnus-scores-articles)
+
+    (erase-buffer)
+    (while articles
+      (setq art (car articles)
+	    this (aref (car art) gnus-score-index)
+	    articles (cdr articles))
+      (if (equal last this)
+	  (push art alike)
+	(when last
+	  (insert last ?\n)
+	  (put-text-property (1- (point)) (point) 'articles alike))
+	(setq alike (list art)
+	      last this)))
+    (when last				; Bwadr, duplicate code.
+      (insert last ?\n)
+      (put-text-property (1- (point)) (point) 'articles alike))
+
+    ;; Find matches.
+    (while scores
+      (setq alist (car scores)
+	    scores (cdr scores)
+	    entries (assoc header alist))
+      (while (cdr entries)		;First entry is the header index.
+	(let* ((rest (cdr entries))
+	       (kill (car rest))
+	       (match (nth 0 kill))
+	       (type (or (nth 3 kill) 's))
+	       (score (or (nth 1 kill) gnus-score-interactive-default-score))
+	       (date (nth 2 kill))
+	       (found nil)
+	       (mt (aref (symbol-name type) 0))
+	       (case-fold-search
+		(not (or (= mt ?R) (= mt ?S) (= mt ?E) (= mt ?F))))
+	       (dmt (downcase mt))
+	       (search-func
+		(cond ((= dmt ?r) 're-search-forward)
+		      ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward)
+		      (t (error "Illegal match type: %s" type))))
+	       arts art)
+	  (goto-char (point-min))
+	  (if (= dmt ?e)
+	      (while (funcall search-func match nil t)
+		(and (= (progn (beginning-of-line) (point))
+			(match-beginning 0))
+		     (= (progn (end-of-line) (point))
+			(match-end 0))
+		     (progn
+		       (setq found (setq arts (get-text-property
+					       (point) 'articles)))
+		       ;; Found a match, update scores.
+		       (while arts
+			 (setq art (car arts)
+			       arts (cdr arts))
+			 (gnus-score-add-followups
+			  (car art) score all-scores thread))))
+		(end-of-line))
+	    (while (funcall search-func match nil t)
+	      (end-of-line)
+	      (setq found (setq arts (get-text-property (point) 'articles)))
+	      ;; Found a match, update scores.
+	      (while (setq art (pop arts))
+		(when (setq new (gnus-score-add-followups
+				 (car art) score all-scores thread))
+		  (push new news)))))
+	  ;; Update expire date
+	  (cond ((null date))		;Permanent entry.
+		((and found gnus-update-score-entry-dates) ;Match, update date.
+		 (gnus-score-set 'touched '(t) alist)
+		 (setcar (nthcdr 2 kill) now))
+		((and expire (< date expire)) ;Old entry, remove.
+		 (gnus-score-set 'touched '(t) alist)
+		 (setcdr entries (cdr rest))
+		 (setq rest entries)))
+	  (setq entries rest))))
+    ;; We change the score file back to the previous one.
+    (save-excursion
+      (set-buffer gnus-summary-buffer)
+      (gnus-score-load-file current-score-file))
+    (list (cons "references" news))))
+
+(defun gnus-score-add-followups (header score scores &optional thread)
+  "Add a score entry to the adapt file."
+  (save-excursion
+    (set-buffer gnus-summary-buffer)
+    (let* ((id (mail-header-id header))
+	   (scores (car scores))
+	   entry dont)
+      ;; Don't enter a score if there already is one.
+      (while (setq entry (pop scores))
+	(and (equal "references" (car entry))
+	     (or (null (nth 3 (cadr entry)))
+		 (eq 's (nth 3 (cadr entry))))
+	     (assoc id entry)
+	     (setq dont t)))
+      (unless dont
+	(gnus-summary-score-entry
+	 (if thread "thread" "references")
+	 id 's score (current-time-string) nil t)))))
+
+(defun gnus-score-string (score-list header now expire &optional trace)
+  ;; Score ARTICLES according to HEADER in SCORE-LIST.
+  ;; Update matching entries to NOW and remove unmatched entries older
+  ;; than EXPIRE.
+
+  ;; Insert the unique article headers in the buffer.
+  (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
+	;; gnus-score-index is used as a free variable.
+	alike last this art entries alist articles
+	fuzzies arts words kill)
+
+    ;; Sorting the articles costs os O(N*log N) but will allow us to
+    ;; only match with each unique header.  Thus the actual matching
+    ;; will be O(M*U) where M is the number of strings to match with,
+    ;; and U is the number of unique headers.  It is assumed (but
+    ;; untested) this will be a net win because of the large constant
+    ;; factor involved with string matching.
+    (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<)
+	  articles gnus-scores-articles)
+
+    (erase-buffer)
+    (while (setq art (pop articles))
+      (setq this (aref (car art) gnus-score-index))
+      (if (equal last this)
+	  ;; O(N*H) cons-cells used here, where H is the number of
+	  ;; headers.
+	  (push art alike)
+	(when last
+	  ;; Insert the line, with a text property on the
+	  ;; terminating newline referring to the articles with
+	  ;; this line.
+	  (insert last ?\n)
+	  (put-text-property (1- (point)) (point) 'articles alike))
+	(setq alike (list art)
+	      last this)))
+    (when last				; Bwadr, duplicate code.
+      (insert last ?\n)
+      (put-text-property (1- (point)) (point) 'articles alike))
+
+    ;; Go through all the score alists and pick out the entries
+    ;; for this header.
+    (while score-list
+      (setq alist (pop score-list)
+	    ;; There's only one instance of this header for
+	    ;; each score alist.
+	    entries (assoc header alist))
+      (while (cdr entries)		;First entry is the header index.
+	(let* ((kill (cadr entries))
+	       (match (nth 0 kill))
+	       (type (or (nth 3 kill) 's))
+	       (score (or (nth 1 kill) gnus-score-interactive-default-score))
+	       (date (nth 2 kill))
+	       (found nil)
+	       (mt (aref (symbol-name type) 0))
+	       (case-fold-search (not (memq mt '(?R ?S ?E ?F))))
+	       (dmt (downcase mt))
+	       (search-func
+		(cond ((= dmt ?r) 're-search-forward)
+		      ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward)
+		      ((= dmt ?w) nil)
+		      (t (error "Illegal match type: %s" type)))))
+	  (cond
+	   ;; Fuzzy matches.  We save these for later.
+	   ((= dmt ?f)
+	    (push (cons entries alist) fuzzies))
+	   ;; Word matches.  Save these for even later.
+	   ((= dmt ?w)
+	    (push (cons entries alist) words))
+	   ;; Exact matches.
+	   ((= dmt ?e)
+	    ;; Do exact matching.
+	    (goto-char (point-min))
+	    (while (and (not (eobp))
+			(funcall search-func match nil t))
+	      ;; Is it really exact?
+	      (and (eolp)
+		   (= (gnus-point-at-bol) (match-beginning 0))
+		   ;; Yup.
+		   (progn
+		     (setq found (setq arts (get-text-property
+					     (point) 'articles)))
+		     ;; Found a match, update scores.
+		     (if trace
+			 (while (setq art (pop arts))
+			   (setcdr art (+ score (cdr art)))
+			   (push
+			    (cons
+			     (car-safe (rassq alist gnus-score-cache))
+			     kill)
+			    gnus-score-trace))
+		       (while (setq art (pop arts))
+			 (setcdr art (+ score (cdr art)))))))
+	      (forward-line 1)))
+	   ;; Regexp and substring matching.
+	   (t
+	    (goto-char (point-min))
+	    (when (string= match "")
+	      (setq match "\n"))
+	    (while (and (not (eobp))
+			(funcall search-func match nil t))
+	      (goto-char (match-beginning 0))
+	      (end-of-line)
+	      (setq found (setq arts (get-text-property (point) 'articles)))
+	      ;; Found a match, update scores.
+	      (if trace
+		  (while (setq art (pop arts))
+		    (setcdr art (+ score (cdr art)))
+		    (push (cons (car-safe (rassq alist gnus-score-cache)) kill)
+			  gnus-score-trace))
+		(while (setq art (pop arts))
+		  (setcdr art (+ score (cdr art)))))
+	      (forward-line 1))))
+	  ;; Update expiry date
+	  (if trace
+	      (setq entries (cdr entries))
+	    (cond
+	     ;; Permanent entry.
+	     ((null date)
+	      (setq entries (cdr entries)))
+	     ;; We have a match, so we update the date.
+	     ((and found gnus-update-score-entry-dates)
+	      (gnus-score-set 'touched '(t) alist)
+	      (setcar (nthcdr 2 kill) now)
+	      (setq entries (cdr entries)))
+	     ;; This entry has expired, so we remove it.
+	     ((and expire (< date expire))
+	      (gnus-score-set 'touched '(t) alist)
+	      (setcdr entries (cddr entries)))
+	     ;; No match; go to next entry.
+	     (t
+	      (setq entries (cdr entries))))))))
+
+    ;; Find fuzzy matches.
+    (when fuzzies
+      ;; Simplify the entire buffer for easy matching.
+      (gnus-simplify-buffer-fuzzy)
+      (while (setq kill (cadaar fuzzies))
+	(let* ((match (nth 0 kill))
+	       (type (nth 3 kill))
+	       (score (or (nth 1 kill) gnus-score-interactive-default-score))
+	       (date (nth 2 kill))
+	       (mt (aref (symbol-name type) 0))
+	       (case-fold-search (not (= mt ?F)))
+	       found)
+	  (goto-char (point-min))
+	  (while (and (not (eobp))
+		      (search-forward match nil t))
+	    (when (and (= (gnus-point-at-bol) (match-beginning 0))
+		       (eolp))
+	      (setq found (setq arts (get-text-property (point) 'articles)))
+	      (if trace
+		  (while (setq art (pop arts))
+		    (setcdr art (+ score (cdr art)))
+		    (push (cons
+			   (car-safe (rassq (cdar fuzzies) gnus-score-cache))
+			   kill)
+			  gnus-score-trace))
+		;; Found a match, update scores.
+		(while (setq art (pop arts))
+		  (setcdr art (+ score (cdr art))))))
+	    (forward-line 1))
+	  ;; Update expiry date
+	  (cond
+	   ;; Permanent.
+	   ((null date)
+	    )
+	   ;; Match, update date.
+	   ((and found gnus-update-score-entry-dates)
+	    (gnus-score-set 'touched '(t) (cdar fuzzies))
+	    (setcar (nthcdr 2 kill) now))
+	   ;; Old entry, remove.
+	   ((and expire (< date expire))
+	    (gnus-score-set 'touched '(t) (cdar fuzzies))
+	    (setcdr (caar fuzzies) (cddaar fuzzies))))
+	  (setq fuzzies (cdr fuzzies)))))
+
+    (when words
+      ;; Enter all words into the hashtb.
+      (let ((hashtb (gnus-make-hashtable
+		     (* 10 (count-lines (point-min) (point-max))))))
+	(gnus-enter-score-words-into-hashtb hashtb)
+	(while (setq kill (cadaar words))
+	  (let* ((score (or (nth 1 kill) gnus-score-interactive-default-score))
+		 (date (nth 2 kill))
+		 found)
+	    (when (setq arts (intern-soft (nth 0 kill) hashtb))
+	      (setq arts (symbol-value arts))
+	      (setq found t)
+	      (if trace
+		  (while (setq art (pop arts))
+		    (setcdr art (+ score (cdr art)))
+		    (push (cons
+			   (car-safe (rassq (cdar words) gnus-score-cache))
+			   kill)
+			  gnus-score-trace))
+		;; Found a match, update scores.
+		(while (setq art (pop arts))
+		  (setcdr art (+ score (cdr art))))))
+	    ;; Update expiry date
+	    (cond
+	     ;; Permanent.
+	     ((null date)
+	      )
+	     ;; Match, update date.
+	     ((and found gnus-update-score-entry-dates)
+	      (gnus-score-set 'touched '(t) (cdar words))
+	      (setcar (nthcdr 2 kill) now))
+	     ;; Old entry, remove.
+	     ((and expire (< date expire))
+	      (gnus-score-set 'touched '(t) (cdar words))
+	      (setcdr (caar words) (cddaar words))))
+	    (setq words (cdr words))))))
+    nil))
+
+(defun gnus-enter-score-words-into-hashtb (hashtb)
+  ;; Find all the words in the buffer and enter them into
+  ;; the hashtable.
+  (let ((syntab (syntax-table))
+	word val)
+    (goto-char (point-min))
+    (unwind-protect
+	(progn
+	  (set-syntax-table gnus-adaptive-word-syntax-table)
+	  (while (re-search-forward "\\b\\w+\\b" nil t)
+	    (setq val
+		  (gnus-gethash
+		   (setq word (downcase (buffer-substring
+					 (match-beginning 0) (match-end 0))))
+		   hashtb))
+	    (gnus-sethash
+	     word
+	     (append (get-text-property (gnus-point-at-eol) 'articles) val)
+	     hashtb)))
+      (set-syntax-table syntab))
+    ;; Make all the ignorable words ignored.
+    (let ((ignored (append gnus-ignored-adaptive-words
+			   gnus-default-ignored-adaptive-words)))
+      (while ignored
+	(gnus-sethash (pop ignored) nil hashtb)))))
+
+(defun gnus-score-string< (a1 a2)
+  ;; Compare headers in articles A2 and A2.
+  ;; The header index used is the free variable `gnus-score-index'.
+  (string-lessp (aref (car a1) gnus-score-index)
+		(aref (car a2) gnus-score-index)))
+
+(defun gnus-current-score-file-nondirectory (&optional score-file)
+  (let ((score-file (or score-file gnus-current-score-file)))
+    (if score-file
+	(gnus-short-group-name (file-name-nondirectory score-file))
+      "none")))
+
+(defun gnus-score-adaptive ()
+  "Create adaptive score rules for this newsgroup."
+  (when gnus-newsgroup-adaptive
+    ;; We change the score file to the adaptive score file.
+    (save-excursion
+      (set-buffer gnus-summary-buffer)
+      (gnus-score-load-file
+       (or gnus-newsgroup-adaptive-score-file
+	   (gnus-score-file-name
+	    gnus-newsgroup-name gnus-adaptive-file-suffix))))
+    ;; Perform ordinary line scoring.
+    (when (or (not (listp gnus-newsgroup-adaptive))
+	      (memq 'line gnus-newsgroup-adaptive))
+      (save-excursion
+	(let* ((malist (gnus-copy-sequence gnus-adaptive-score-alist))
+	       (alist malist)
+	       (date (current-time-string))
+	       (data gnus-newsgroup-data)
+	       elem headers match)
+	  ;; First we transform the adaptive rule alist into something
+	  ;; that's faster to process.
+	  (while malist
+	    (setq elem (car malist))
+	    (when (symbolp (car elem))
+	      (setcar elem (symbol-value (car elem))))
+	    (setq elem (cdr elem))
+	    (while elem
+	      (setcdr (car elem)
+		      (cons (if (eq (caar elem) 'followup)
+				"references"
+			      (symbol-name (caar elem)))
+			    (cdar elem)))
+	      (setcar (car elem)
+		      `(lambda (h)
+			 (,(intern
+			    (concat "mail-header-"
+				    (if (eq (caar elem) 'followup)
+					"message-id"
+				      (downcase (symbol-name (caar elem))))))
+			  h)))
+	      (setq elem (cdr elem)))
+	    (setq malist (cdr malist)))
+	  ;; Then we score away.
+	  (while data
+	    (setq elem (cdr (assq (gnus-data-mark (car data)) alist)))
+	    (if (or (not elem)
+		    (gnus-data-pseudo-p (car data)))
+		()
+	      (when (setq headers (gnus-data-header (car data)))
+		(while elem
+		  (setq match (funcall (caar elem) headers))
+		  (gnus-summary-score-entry
+		   (nth 1 (car elem)) match
+		   (cond
+		    ((numberp match)
+		     '=)
+		    ((equal (nth 1 (car elem)) "date")
+		     'a)
+		    (t
+		     ;; Whether we use substring or exact matches is
+		     ;; controlled here.
+		     (if (or (not gnus-score-exact-adapt-limit)
+			     (< (length match) gnus-score-exact-adapt-limit))
+			 'e
+		       (if (equal (nth 1 (car elem)) "subject")
+			   'f 's))))
+		   (nth 2 (car elem)) date nil t)
+		  (setq elem (cdr elem)))))
+	    (setq data (cdr data))))))
+
+    ;; Perform adaptive word scoring.
+    (when (and (listp gnus-newsgroup-adaptive)
+	       (memq 'word gnus-newsgroup-adaptive))
+      (nnheader-temp-write nil
+	(let* ((hashtb (gnus-make-hashtable 1000))
+	       (date (gnus-day-number (current-time-string)))
+	       (data gnus-newsgroup-data)
+	       (syntab (syntax-table))
+	       word d score val)
+	  (unwind-protect
+	      (progn
+		(set-syntax-table gnus-adaptive-word-syntax-table)
+		;; Go through all articles.
+		(while (setq d (pop data))
+		  (when (and
+			 (not (gnus-data-pseudo-p d))
+			 (setq score
+			       (cdr (assq
+				     (gnus-data-mark d)
+				     gnus-adaptive-word-score-alist))))
+		    ;; This article has a mark that should lead to
+		    ;; adaptive word rules, so we insert the subject
+		    ;; and find all words in that string.
+		    (insert (mail-header-subject (gnus-data-header d)))
+		    (downcase-region (point-min) (point-max))
+		    (goto-char (point-min))
+		    (while (re-search-forward "\\b\\w+\\b" nil t)
+		      ;; Put the word and score into the hashtb.
+		      (setq val (gnus-gethash (setq word (match-string 0))
+					      hashtb))
+		      (gnus-sethash word (+ (or val 0) score) hashtb))
+		    (erase-buffer))))
+	    (set-syntax-table syntab))
+	  ;; Make all the ignorable words ignored.
+	  (let ((ignored (append gnus-ignored-adaptive-words
+				 gnus-default-ignored-adaptive-words)))
+	    (while ignored
+	      (gnus-sethash (pop ignored) nil hashtb)))
+	  ;; Now we have all the words and scores, so we
+	  ;; add these rules to the ADAPT file.
+	  (set-buffer gnus-summary-buffer)
+	  (mapatoms
+	   (lambda (word)
+	     (when (symbol-value word)
+	       (gnus-summary-score-entry
+		"subject" (symbol-name word) 'w (symbol-value word)
+		date nil t)))
+	   hashtb))))))
+
+(defun gnus-score-edit-done ()
+  (let ((bufnam (buffer-file-name (current-buffer)))
+	(winconf gnus-prev-winconf))
+    (when winconf
+      (set-window-configuration winconf))
+    (gnus-score-remove-from-cache bufnam)
+    (gnus-score-load-file bufnam)))
+
+(defun gnus-score-find-trace ()
+  "Find all score rules that applies to the current article."
+  (interactive)
+  (let ((old-scored gnus-newsgroup-scored))
+    (let ((gnus-newsgroup-headers
+	   (list (gnus-summary-article-header)))
+	  (gnus-newsgroup-scored nil)
+	  trace)
+      (save-excursion
+	(nnheader-set-temp-buffer "*Score Trace*"))
+      (setq gnus-score-trace nil)
+      (gnus-possibly-score-headers 'trace)
+      (if (not (setq trace gnus-score-trace))
+	  (gnus-error
+	   1 "No score rules apply to the current article (default score %d)."
+	   gnus-summary-default-score)
+	(set-buffer "*Score Trace*")
+	(gnus-add-current-to-buffer-list)
+	(while trace
+	  (insert (format "%S  ->  %s\n" (cdar trace)
+			  (file-name-nondirectory (caar trace))))
+	  (setq trace (cdr trace)))
+	(goto-char (point-min))
+	(gnus-configure-windows 'score-trace)))
+    (set-buffer gnus-summary-buffer)
+    (setq gnus-newsgroup-scored old-scored)))
+
+(defun gnus-score-find-favourite-words ()
+  "List words used in scoring."
+  (interactive)
+  (let ((alists (gnus-score-load-files (gnus-all-score-files)))
+	alist rule rules kill)
+    ;; Go through all the score alists for this group
+    ;; and find all `w' rules.
+    (while (setq alist (pop alists))
+      (while (setq rule (pop alist))
+	(when (and (stringp (car rule))
+		   (equal "subject" (downcase (pop rule))))
+	  (while (setq kill (pop rule))
+	    (when (memq (nth 3 kill) '(w W word Word))
+	      (push (cons (or (nth 1 kill)
+			      gnus-score-interactive-default-score)
+			  (car kill))
+		    rules))))))
+    (setq rules (sort rules (lambda (r1 r2)
+			      (string-lessp (cdr r1) (cdr r2)))))
+    ;; Add up words that have appeared several times.
+    (let ((r rules))
+      (while (cdr r)
+	(if (equal (cdar r) (cdadr r))
+	    (progn
+	      (setcar (car r) (+ (caar r) (caadr r)))
+	      (setcdr r (cddr r)))
+	  (pop r))))
+    ;; Insert the words.
+    (nnheader-set-temp-buffer "*Score Words*")
+    (if (not (setq rules (sort rules (lambda (r1 r2) (> (car r1) (car r2))))))
+	(gnus-error 3 "No word score rules")
+      (while rules
+	(insert (format "%-5d: %s\n" (caar rules) (cdar rules)))
+	(pop rules))
+      (gnus-add-current-to-buffer-list)
+      (goto-char (point-min))
+      (gnus-configure-windows 'score-words))))
+
+(defun gnus-summary-rescore ()
+  "Redo the entire scoring process in the current summary."
+  (interactive)
+  (gnus-score-save)
+  (setq gnus-score-cache nil)
+  (setq gnus-newsgroup-scored nil)
+  (gnus-possibly-score-headers)
+  (gnus-score-update-all-lines))
+
+(defun gnus-score-flush-cache ()
+  "Flush the cache of score files."
+  (interactive)
+  (gnus-score-save)
+  (setq gnus-score-cache nil
+	gnus-score-alist nil
+	gnus-short-name-score-file-cache nil)
+  (gnus-message 6 "The score cache is now flushed"))
+
+(gnus-add-shutdown 'gnus-score-close 'gnus)
+
+(defvar gnus-score-file-alist-cache nil)
+
+(defun gnus-score-close ()
+  "Clear all internal score variables."
+  (setq gnus-score-cache nil
+	gnus-internal-global-score-files nil
+	gnus-score-file-list nil
+	gnus-score-file-alist-cache nil))
+
+;; Summary score marking commands.
+
+(defun gnus-summary-raise-same-subject-and-select (score)
+  "Raise articles which has the same subject with SCORE and select the next."
+  (interactive "p")
+  (let ((subject (gnus-summary-article-subject)))
+    (gnus-summary-raise-score score)
+    (while (gnus-summary-find-subject subject)
+      (gnus-summary-raise-score score))
+    (gnus-summary-next-article t)))
+
+(defun gnus-summary-raise-same-subject (score)
+  "Raise articles which has the same subject with SCORE."
+  (interactive "p")
+  (let ((subject (gnus-summary-article-subject)))
+    (gnus-summary-raise-score score)
+    (while (gnus-summary-find-subject subject)
+      (gnus-summary-raise-score score))
+    (gnus-summary-next-subject 1 t)))
+
+(defun gnus-score-default (level)
+  (if level (prefix-numeric-value level)
+    gnus-score-interactive-default-score))
+
+(defun gnus-summary-raise-thread (&optional score)
+  "Raise the score of the articles in the current thread with SCORE."
+  (interactive "P")
+  (setq score (gnus-score-default score))
+  (let (e)
+    (save-excursion
+      (let ((articles (gnus-summary-articles-in-thread)))
+	(while articles
+	  (gnus-summary-goto-subject (car articles))
+	  (gnus-summary-raise-score score)
+	  (setq articles (cdr articles))))
+      (setq e (point)))
+    (let ((gnus-summary-check-current t))
+      (unless (zerop (gnus-summary-next-subject 1 t))
+	(goto-char e))))
+  (gnus-summary-recenter)
+  (gnus-summary-position-point)
+  (gnus-set-mode-line 'summary))
+
+(defun gnus-summary-lower-same-subject-and-select (score)
+  "Raise articles which has the same subject with SCORE and select the next."
+  (interactive "p")
+  (gnus-summary-raise-same-subject-and-select (- score)))
+
+(defun gnus-summary-lower-same-subject (score)
+  "Raise articles which has the same subject with SCORE."
+  (interactive "p")
+  (gnus-summary-raise-same-subject (- score)))
+
+(defun gnus-summary-lower-thread (&optional score)
+  "Lower score of articles in the current thread with SCORE."
+  (interactive "P")
+  (gnus-summary-raise-thread (- (1- (gnus-score-default score)))))
+
+;;; Finding score files.
+
+(defun gnus-score-score-files (group)
+  "Return a list of all possible score files."
+  ;; Search and set any global score files.
+  (when gnus-global-score-files
+    (unless gnus-internal-global-score-files
+      (gnus-score-search-global-directories gnus-global-score-files)))
+  ;; Fix the kill-file dir variable.
+  (setq gnus-kill-files-directory
+	(file-name-as-directory gnus-kill-files-directory))
+  ;; If we can't read it, there are no score files.
+  (if (not (file-exists-p (expand-file-name gnus-kill-files-directory)))
+      (setq gnus-score-file-list nil)
+    (if (not (gnus-use-long-file-name 'not-score))
+	;; We do not use long file names, so we have to do some
+	;; directory traversing.
+	(setq gnus-score-file-list
+	      (cons nil
+		    (or gnus-short-name-score-file-cache
+			(prog2
+			    (gnus-message 6 "Finding all score files...")
+			    (setq gnus-short-name-score-file-cache
+				  (gnus-score-score-files-1
+				   gnus-kill-files-directory))
+			  (gnus-message 6 "Finding all score files...done")))))
+      ;; We want long file names.
+      (when (or (not gnus-score-file-list)
+		(not (car gnus-score-file-list))
+		(gnus-file-newer-than gnus-kill-files-directory
+				      (car gnus-score-file-list)))
+	(setq gnus-score-file-list
+	      (cons (nth 5 (file-attributes gnus-kill-files-directory))
+		    (nreverse
+		     (directory-files
+		      gnus-kill-files-directory t
+		      (gnus-score-file-regexp)))))))
+    (cdr gnus-score-file-list)))
+
+(defun gnus-score-score-files-1 (dir)
+  "Return all possible score files under DIR."
+  (let ((files (list (expand-file-name dir)))
+	(regexp (gnus-score-file-regexp))
+	(case-fold-search nil)
+	seen out file)
+    (while (setq file (pop files))
+      (cond
+       ;; Ignore "." and "..".
+       ((member (file-name-nondirectory file) '("." ".."))
+	nil)
+       ;; Add subtrees of directory to also be searched.
+       ((and (file-directory-p file)
+	     (not (member (file-truename file) seen)))
+	(push (file-truename file) seen)
+	(setq files (nconc (directory-files file t nil t) files)))
+       ;; Add files to the list of score files.
+       ((string-match regexp file)
+	(push file out))))
+    (or out
+	;; Return a dummy value.
+	(list "~/News/this.file.does.not.exist.SCORE"))))
+
+(defun gnus-score-file-regexp ()
+  "Return a regexp that match all score files."
+  (concat "\\(" (regexp-quote gnus-score-file-suffix )
+	  "\\|" (regexp-quote gnus-adaptive-file-suffix) "\\)\\'"))
+
+(defun gnus-score-find-bnews (group)
+  "Return a list of score files for GROUP.
+The score files are those files in the ~/News/ directory which matches
+GROUP using BNews sys file syntax."
+  (let* ((sfiles (append (gnus-score-score-files group)
+			 gnus-internal-global-score-files))
+	 (kill-dir (file-name-as-directory
+		    (expand-file-name gnus-kill-files-directory)))
+	 (klen (length kill-dir))
+	 (score-regexp (gnus-score-file-regexp))
+	 (trans (cdr (assq ?: nnheader-file-name-translation-alist)))
+	 ofiles not-match regexp)
+    (save-excursion
+      (set-buffer (get-buffer-create "*gnus score files*"))
+      (buffer-disable-undo (current-buffer))
+      ;; Go through all score file names and create regexp with them
+      ;; as the source.
+      (while sfiles
+	(erase-buffer)
+	(insert (car sfiles))
+	(goto-char (point-min))
+	;; First remove the suffix itself.
+	(when (re-search-forward (concat "." score-regexp) nil t)
+	  (replace-match "" t t)
+	  (goto-char (point-min))
+	  (if (looking-at (regexp-quote kill-dir))
+	      ;; If the file name was just "SCORE", `klen' is one character
+	      ;; too much.
+	      (delete-char (min (1- (point-max)) klen))
+	    (goto-char (point-max))
+	    (search-backward "/")
+	    (delete-region (1+ (point)) (point-min)))
+	  ;; If short file names were used, we have to translate slashes.
+	  (goto-char (point-min))
+	  (let ((regexp (concat
+			 "[/:" (if trans (char-to-string trans) "") "]")))
+	    (while (re-search-forward regexp nil t)
+	      (replace-match "." t t)))
+	  ;; Kludge to get rid of "nntp+" problems.
+	  (goto-char (point-min))
+	  (when (looking-at "nn[a-z]+\\+")
+	    (search-forward "+")
+	    (forward-char -1)
+	    (insert "\\")
+	    (forward-char 1))
+	  ;; Kludge to deal with "++".
+	  (while (search-forward "+" nil t)
+	    (replace-match "\\+" t t))
+	  ;; Translate "all" to ".*".
+	  (goto-char (point-min))
+	  (while (search-forward "all" nil t)
+	    (replace-match ".*" t t))
+	  (goto-char (point-min))
+	  ;; Deal with "not."s.
+	  (if (looking-at "not.")
+	      (progn
+		(setq not-match t)
+		(setq regexp (concat "^" (buffer-substring 5 (point-max)))))
+	    (setq regexp (concat "^" (buffer-substring 1 (point-max))))
+	    (setq not-match nil))
+	  ;; Finally - if this resulting regexp matches the group name,
+	  ;; we add this score file to the list of score files
+	  ;; applicable to this group.
+	  (when (or (and not-match
+			 (not (string-match regexp group)))
+		    (and (not not-match)
+			 (string-match regexp group)))
+	    (push (car sfiles) ofiles)))
+	(setq sfiles (cdr sfiles)))
+      (kill-buffer (current-buffer))
+      ;; Slight kludge here - the last score file returned should be
+      ;; the local score file, whether it exists or not.  This is so
+      ;; that any score commands the user enters will go to the right
+      ;; file, and not end up in some global score file.
+      (let ((localscore (gnus-score-file-name group)))
+	(setq ofiles (cons localscore (delete localscore ofiles))))
+      (gnus-sort-score-files (nreverse ofiles)))))
+
+(defun gnus-score-find-single (group)
+  "Return list containing the score file for GROUP."
+  (list (or gnus-newsgroup-adaptive-score-file
+	    (gnus-score-file-name group gnus-adaptive-file-suffix))
+	(gnus-score-file-name group)))
+
+(defun gnus-score-find-hierarchical (group)
+  "Return list of score files for GROUP.
+This includes the score file for the group and all its parents."
+  (let* ((prefix (gnus-group-real-prefix group))
+	 (all (list nil))
+	 (group (gnus-group-real-name group))
+	 (start 0))
+    (while (string-match "\\." group (1+ start))
+      (setq start (match-beginning 0))
+      (push (substring group 0 start) all))
+    (push group all)
+    (setq all
+	  (nconc
+	   (mapcar (lambda (group)
+		     (gnus-score-file-name group gnus-adaptive-file-suffix))
+		   (setq all (nreverse all)))
+	   (mapcar 'gnus-score-file-name all)))
+    (if (equal prefix "")
+	all
+      (mapcar
+       (lambda (file)
+	 (nnheader-translate-file-chars
+	  (concat (file-name-directory file) prefix
+		  (file-name-nondirectory file))))
+       all))))
+
+(defun gnus-score-file-rank (file)
+  "Return a number that says how specific score FILE is.
+Destroys the current buffer."
+  (if (member file gnus-internal-global-score-files)
+      0
+    (when (string-match
+	   (concat "^" (regexp-quote
+			(expand-file-name
+			 (file-name-as-directory gnus-kill-files-directory))))
+	   file)
+      (setq file (substring file (match-end 0))))
+    (insert file)
+    (goto-char (point-min))
+    (let ((beg (point))
+	  elems)
+      (while (re-search-forward "[./]" nil t)
+	(push (buffer-substring beg (1- (point)))
+	      elems))
+      (erase-buffer)
+      (setq elems (delete "all" elems))
+      (length elems))))
+
+(defun gnus-sort-score-files (files)
+  "Sort FILES so that the most general files come first."
+  (nnheader-temp-write nil
+    (let ((alist
+	   (mapcar
+	    (lambda (file)
+	      (cons (inline (gnus-score-file-rank file)) file))
+	    files)))
+      (mapcar
+       (lambda (f) (cdr f))
+       (sort alist (lambda (f1 f2) (< (car f1) (car f2))))))))
+
+(defun gnus-score-find-alist (group)
+  "Return list of score files for GROUP.
+The list is determined from the variable gnus-score-file-alist."
+  (let ((alist gnus-score-file-multiple-match-alist)
+	score-files)
+    ;; if this group has been seen before, return the cached entry
+    (if (setq score-files (assoc group gnus-score-file-alist-cache))
+	(cdr score-files)		;ensures caching groups with no matches
+      ;; handle the multiple match alist
+      (while alist
+	(when (string-match (caar alist) group)
+	  (setq score-files
+		(nconc score-files (copy-sequence (cdar alist)))))
+	(setq alist (cdr alist)))
+      (setq alist gnus-score-file-single-match-alist)
+      ;; handle the single match alist
+      (while alist
+	(when (string-match (caar alist) group)
+	  ;; progn used just in case ("regexp") has no files
+	  ;; and score-files is still nil.  -sj
+	  ;; this can be construed as a "stop searching here" feature :>
+	  ;; and used to simplify regexps in the single-alist
+	  (setq score-files
+		(nconc score-files (copy-sequence (cdar alist))))
+	  (setq alist nil))
+	(setq alist (cdr alist)))
+      ;; cache the score files
+      (push (cons group score-files) gnus-score-file-alist-cache)
+      score-files)))
+
+(defun gnus-all-score-files (&optional group)
+  "Return a list of all score files for the current group."
+  (let ((funcs gnus-score-find-score-files-function)
+	(group (or group gnus-newsgroup-name))
+	score-files)
+    ;; Make sure funcs is a list.
+    (and funcs
+	 (not (listp funcs))
+	 (setq funcs (list funcs)))
+    ;; Get the initial score files for this group.
+    (when funcs
+      (setq score-files (nreverse (gnus-score-find-alist group))))
+    ;; Add any home adapt files.
+    (let ((home (gnus-home-score-file group t)))
+      (when home
+	(push home score-files)
+	(setq gnus-newsgroup-adaptive-score-file home)))
+    ;; Check whether there is a `adapt-file' group parameter.
+    (let ((param-file (gnus-group-find-parameter group 'adapt-file)))
+      (when param-file
+	(push param-file score-files)
+	(setq gnus-newsgroup-adaptive-score-file param-file)))
+    ;; Go through all the functions for finding score files (or actual
+    ;; scores) and add them to a list.
+    (while funcs
+      (when (gnus-functionp (car funcs))
+	(setq score-files
+	      (nconc score-files (nreverse (funcall (car funcs) group)))))
+      (setq funcs (cdr funcs)))
+    ;; Add any home score files.
+    (let ((home (gnus-home-score-file group)))
+      (when home
+	(push home score-files)))
+    ;; Check whether there is a `score-file' group parameter.
+    (let ((param-file (gnus-group-find-parameter group 'score-file)))
+      (when param-file
+	(push param-file score-files)))
+    ;; Expand all files names.
+    (let ((files score-files))
+      (while files
+	(when (stringp (car files))
+	  (setcar files (expand-file-name
+			 (car files) gnus-kill-files-directory)))
+	(pop files)))
+    (setq score-files (nreverse score-files))
+    ;; Remove any duplicate score files.
+    (while (and score-files
+		(member (car score-files) (cdr score-files)))
+      (pop score-files))
+    (let ((files score-files))
+      (while (cdr files)
+ 	(if (member (cadr files) (cddr files))
+ 	    (setcdr files (cddr files))
+ 	  (pop files))))
+    ;; Do the scoring if there are any score files for this group.
+    score-files))
+
+(defun gnus-possibly-score-headers (&optional trace)
+  "Do scoring if scoring is required."
+  (let ((score-files (gnus-all-score-files)))
+    (when score-files
+      (gnus-score-headers score-files trace))))
+
+(defun gnus-score-file-name (newsgroup &optional suffix)
+  "Return the name of a score file for NEWSGROUP."
+  (let ((suffix (or suffix gnus-score-file-suffix)))
+    (nnheader-translate-file-chars
+     (cond
+      ((or (null newsgroup)
+	   (string-equal newsgroup ""))
+       ;; The global score file is placed at top of the directory.
+       (expand-file-name
+	suffix gnus-kill-files-directory))
+      ((gnus-use-long-file-name 'not-score)
+       ;; Append ".SCORE" to newsgroup name.
+       (expand-file-name (concat (gnus-newsgroup-savable-name newsgroup)
+				 "." suffix)
+			 gnus-kill-files-directory))
+      (t
+       ;; Place "SCORE" under the hierarchical directory.
+       (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
+				 "/" suffix)
+			 gnus-kill-files-directory))))))
+
+(defun gnus-score-search-global-directories (files)
+  "Scan all global score directories for score files."
+  ;; Set the variable `gnus-internal-global-score-files' to all
+  ;; available global score files.
+  (interactive (list gnus-global-score-files))
+  (let (out)
+    (while files
+      (if (string-match "/$" (car files))
+	  (setq out (nconc (directory-files
+			    (car files) t
+			    (concat (gnus-score-file-regexp) "$"))))
+	(push (car files) out))
+      (setq files (cdr files)))
+    (setq gnus-internal-global-score-files out)))
+
+(defun gnus-score-default-fold-toggle ()
+  "Toggle folding for new score file entries."
+  (interactive)
+  (setq gnus-score-default-fold (not gnus-score-default-fold))
+  (if gnus-score-default-fold
+      (gnus-message 1 "New score file entries will be case insensitive.")
+    (gnus-message 1 "New score file entries will be case sensitive.")))
+
+;;; Home score file.
+
+(defun gnus-home-score-file (group &optional adapt)
+  "Return the home score file for GROUP.
+If ADAPT, return the home adaptive file instead."
+  (let ((list (if adapt gnus-home-adapt-file gnus-home-score-file))
+	elem found)
+    ;; Make sure we have a list.
+    (unless (listp list)
+      (setq list (list list)))
+    ;; Go through the list and look for matches.
+    (while (and (not found)
+		(setq elem (pop list)))
+      (setq found
+	    (cond
+	     ;; Simple string.
+	     ((stringp elem)
+	      elem)
+	     ;; Function.
+	     ((gnus-functionp elem)
+	      (funcall elem group))
+	     ;; Regexp-file cons
+	     ((consp elem)
+	      (when (string-match (car elem) group)
+		(cadr elem))))))
+    (when found
+      (nnheader-concat gnus-kill-files-directory found))))
+
+(defun gnus-hierarchial-home-score-file (group)
+  "Return the score file of the top-level hierarchy of GROUP."
+  (if (string-match "^[^.]+\\." group)
+      (concat (match-string 0 group) gnus-score-file-suffix)
+    ;; Group name without any dots.
+    (concat group (if (gnus-use-long-file-name 'not-score) "." "/")
+	    gnus-score-file-suffix)))
+
+(defun gnus-hierarchial-home-adapt-file (group)
+  "Return the adapt file of the top-level hierarchy of GROUP."
+  (if (string-match "^[^.]+\\." group)
+      (concat (match-string 0 group) gnus-adaptive-file-suffix)
+    ;; Group name without any dots.
+    (concat group (if (gnus-use-long-file-name 'not-score) "." "/")
+	    gnus-adaptive-file-suffix)))
+
+;;;
+;;; Score decays
+;;;
+
+(defun gnus-decay-score (score)
+  "Decay SCORE."
+  (floor
+   (- score
+      (* (if (< score 0) 1 -1)
+	 (min score
+	      (max gnus-score-decay-constant
+		   (* (abs score)
+		      gnus-score-decay-scale)))))))
+
+(defun gnus-decay-scores (alist day)
+  "Decay non-permanent scores in ALIST."
+  (let ((times (- (gnus-time-to-day (current-time)) day))
+	kill entry updated score n)
+    (unless (zerop times)		;Done decays today already?
+      (while (setq entry (pop alist))
+	(when (stringp (car entry))
+	  (setq entry (cdr entry))
+	  (while (setq kill (pop entry))
+	    (when (nth 2 kill)
+	      (setq updated t)
+	      (setq score (or (car kill) gnus-score-interactive-default-score)
+		    n times)
+	      (while (natnump (decf n))
+		(setq score (funcall gnus-decay-score-function score)))
+	      (setcar kill score))))))
+    ;; Return whether this score file needs to be saved.  By Je-haysuss!
+    updated))
+
+(provide 'gnus-score)
+
+;;; gnus-score.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-setup.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,217 @@
+;;; gnus-setup.el --- Initialization & Setup for Gnus 5
+;; Copyright (C) 1995, 96 Free Software Foundation, Inc.
+
+;; Author: Steven L. Baur <steve@miranova.com>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+;; My head is starting to spin with all the different mail/news packages.
+;; Stop The Madness!
+
+;; Given that Emacs Lisp byte codes may be diverging, it is probably best
+;; not to byte compile this, and just arrange to have the .el loaded out
+;; of .emacs.
+
+;;; Code:
+
+(require 'cl)
+
+(defvar running-xemacs (string-match "XEmacs\\|Lucid" emacs-version))
+
+(defvar gnus-use-installed-gnus t
+  "*If non-nil Use installed version of Gnus.")
+
+(defvar gnus-use-installed-tm running-xemacs
+  "*If non-nil use installed version of tm.")
+
+(defvar gnus-use-installed-mailcrypt running-xemacs
+  "*If non-nil use installed version of mailcrypt.")
+
+(defvar gnus-emacs-lisp-directory (if running-xemacs
+				      "/usr/local/lib/xemacs/"
+				    "/usr/local/share/emacs/")
+  "Directory where Emacs site lisp is located.")
+
+(defvar gnus-gnus-lisp-directory (concat gnus-emacs-lisp-directory
+					 "gnus-5.0.15/lisp/")
+  "Directory where Gnus Emacs lisp is found.")
+
+(defvar gnus-tm-lisp-directory (concat gnus-emacs-lisp-directory
+				       "site-lisp/")
+  "Directory where TM Emacs lisp is found.")
+
+(defvar gnus-mailcrypt-lisp-directory (concat gnus-emacs-lisp-directory
+					      "site-lisp/mailcrypt-3.4/")
+  "Directory where Mailcrypt Emacs Lisp is found.")
+
+(defvar gnus-bbdb-lisp-directory (concat gnus-emacs-lisp-directory
+					 "site-lisp/bbdb-1.51/")
+  "Directory where Big Brother Database is found.")
+
+(defvar gnus-use-tm running-xemacs
+  "Set this if you want MIME support for Gnus")
+(defvar gnus-use-mhe nil
+  "Set this if you want to use MH-E for mail reading")
+(defvar gnus-use-rmail nil
+  "Set this if you want to use RMAIL for mail reading")
+(defvar gnus-use-sendmail t
+  "Set this if you want to use SENDMAIL for mail reading")
+(defvar gnus-use-vm nil
+  "Set this if you want to use the VM package for mail reading")
+(defvar gnus-use-sc nil
+  "Set this if you want to use Supercite")
+(defvar gnus-use-mailcrypt t
+  "Set this if you want to use Mailcrypt for dealing with PGP messages")
+(defvar gnus-use-bbdb nil
+  "Set this if you want to use the Big Brother DataBase")
+
+(when (and (not gnus-use-installed-gnus)
+	   (null (member gnus-gnus-lisp-directory load-path)))
+  (push gnus-gnus-lisp-directory load-path))
+
+;;; We can't do this until we know where Gnus is.
+(require 'message)
+
+;;; Tools for MIME by
+;;; UMEDA Masanobu <umerin@mse.kyutech.ac.jp>
+;;; MORIOKA Tomohiko <morioka@jaist.ac.jp>
+
+(when gnus-use-tm
+  (when (and (not gnus-use-installed-tm)
+	     (null (member gnus-tm-lisp-directory load-path)))
+    (setq load-path (cons gnus-tm-lisp-directory load-path)))
+  ;; tm may or may not be dumped with XEmacs.  In Sunpro it is, otherwise
+  ;; it isn't.
+  (unless (featurep 'mime-setup)
+    (load "mime-setup")))
+
+;;; Mailcrypt by
+;;; Jin Choi <jin@atype.com>
+;;; Patrick LoPresti <patl@lcs.mit.edu>
+
+(when gnus-use-mailcrypt
+  (when (and (not gnus-use-installed-mailcrypt)
+	     (null (member gnus-mailcrypt-lisp-directory load-path)))
+    (setq load-path (cons gnus-mailcrypt-lisp-directory load-path)))
+  (autoload 'mc-install-write-mode "mailcrypt" nil t)
+  (autoload 'mc-install-read-mode "mailcrypt" nil t)
+  (add-hook 'message-mode-hook 'mc-install-write-mode)
+  (add-hook 'gnus-summary-mode-hook 'mc-install-read-mode)
+  (when gnus-use-mhe
+    (add-hook 'mh-folder-mode-hook 'mc-install-read-mode)
+    (add-hook 'mh-letter-mode-hook 'mc-install-write-mode)))
+
+;;; BBDB by
+;;; Jamie Zawinski <jwz@lucid.com>
+
+(when gnus-use-bbdb
+  ;; bbdb will never be installed with emacs.
+  (when (null (member gnus-bbdb-lisp-directory load-path))
+    (setq load-path (cons gnus-bbdb-lisp-directory load-path)))
+  (autoload 'bbdb "bbdb-com"
+    "Insidious Big Brother Database" t)
+  (autoload 'bbdb-name "bbdb-com"
+    "Insidious Big Brother Database" t)
+  (autoload 'bbdb-company "bbdb-com"
+    "Insidious Big Brother Database" t)
+  (autoload 'bbdb-net "bbdb-com"
+    "Insidious Big Brother Database" t)
+  (autoload 'bbdb-notes "bbdb-com"
+    "Insidious Big Brother Database" t)
+
+  (when gnus-use-vm
+    (autoload 'bbdb-insinuate-vm "bbdb-vm"
+      "Hook BBDB into VM" t))
+
+  (when gnus-use-rmail
+    (autoload 'bbdb-insinuate-rmail "bbdb-rmail"
+      "Hook BBDB into RMAIL" t)
+    (add-hook 'rmail-mode-hook 'bbdb-insinuate-rmail))
+
+  (when gnus-use-mhe
+    (autoload 'bbdb-insinuate-mh "bbdb-mh"
+      "Hook BBDB into MH-E" t)
+    (add-hook 'mh-folder-mode-hook 'bbdb-insinuate-mh))
+
+  (autoload 'bbdb-insinuate-gnus "bbdb-gnus"
+    "Hook BBDB into Gnus" t)
+  (add-hook 'gnus-startup-hook 'bbdb-insinuate-gnus)
+
+  (when gnus-use-sendmail
+    (autoload 'bbdb-insinuate-sendmail "bbdb"
+      "Insidious Big Brother Database" t)
+    (add-hook 'mail-setup-hook 'bbdb-insinuate-sendmail)
+    (add-hook 'message-setup-hook 'bbdb-insinuate-sendmail)))
+
+(when gnus-use-sc
+  (add-hook 'mail-citation-hook 'sc-cite-original)
+  (setq message-cite-function 'sc-cite-original)
+  (autoload 'sc-cite-original "supercite"))
+
+;;;### (autoloads (gnus gnus-slave gnus-no-server) "gnus" "lisp/gnus.el" (12473 2137))
+;;; Generated autoloads from lisp/gnus.el
+
+;; Don't redo this if autoloads already exist
+(unless (fboundp 'gnus)
+  (autoload 'gnus-slave-no-server "gnus" "\
+Read network news as a slave without connecting to local server." t nil)
+
+  (autoload 'gnus-no-server "gnus" "\
+Read network news.
+If ARG is a positive number, Gnus will use that as the
+startup level.  If ARG is nil, Gnus will be started at level 2.
+If ARG is non-nil and not a positive number, Gnus will
+prompt the user for the name of an NNTP server to use.
+As opposed to `gnus', this command will not connect to the local server." t nil)
+
+  (autoload 'gnus-slave "gnus" "\
+Read news as a slave." t nil)
+
+  (autoload 'gnus "gnus" "\
+Read network news.
+If ARG is non-nil and a positive number, Gnus will use that as the
+startup level.  If ARG is non-nil and not a positive number, Gnus will
+prompt the user for the name of an NNTP server to use." t nil)
+
+;;;***
+
+;;; These have moved out of gnus.el into other files.
+;;; FIX FIX FIX: should other things be in gnus-setup? or these not in it?
+  (autoload 'gnus-update-format "gnus-spec" "\
+Update the format specification near point." t nil)
+
+  (autoload 'gnus-fetch-group "gnus-group" "\
+Start Gnus if necessary and enter GROUP.
+Returns whether the fetching was successful or not." t nil)
+
+  (defalias 'gnus-batch-kill 'gnus-batch-score)
+
+  (autoload 'gnus-batch-score "gnus-kill" "\
+Run batched scoring.
+Usage: emacs -batch -l gnus -f gnus-batch-score <newsgroups> ...
+Newsgroups is a list of strings in Bnews format.  If you want to score
+the comp hierarchy, you'd say \"comp.all\".  If you would not like to
+score the alt hierarchy, you'd say \"!alt.all\"." t nil))
+
+(provide 'gnus-setup)
+
+(run-hooks 'gnus-setup-load-hook)
+
+;;; gnus-setup.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-soup.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,565 @@
+;;; gnus-soup.el --- SOUP packet writing support for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
+;;	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-art)
+(require 'message)
+(require 'gnus-start)
+(require 'gnus-range)
+
+;;; User Variables:
+
+(defvar gnus-soup-directory (nnheader-concat gnus-home-directory "SoupBrew/")
+  "*Directory containing an unpacked SOUP packet.")
+
+(defvar gnus-soup-replies-directory
+  (nnheader-concat gnus-soup-directory "SoupReplies/")
+  "*Directory where Gnus will do processing of replies.")
+
+(defvar gnus-soup-prefix-file "gnus-prefix"
+  "*Name of the file where Gnus stores the last used prefix.")
+
+(defvar gnus-soup-packer "tar cf - %s | gzip > $HOME/Soupout%d.tgz"
+  "Format string command for packing a SOUP packet.
+The SOUP files will be inserted where the %s is in the string.
+This string MUST contain both %s and %d.  The file number will be
+inserted where %d appears.")
+
+(defvar gnus-soup-unpacker "gunzip -c %s | tar xvf -"
+  "*Format string command for unpacking a SOUP packet.
+The SOUP packet file name will be inserted at the %s.")
+
+(defvar gnus-soup-packet-directory gnus-home-directory
+  "*Where gnus-soup will look for REPLIES packets.")
+
+(defvar gnus-soup-packet-regexp "Soupin"
+  "*Regular expression matching SOUP REPLIES packets in `gnus-soup-packet-directory'.")
+
+(defvar gnus-soup-ignored-headers "^Xref:"
+  "*Regexp to match headers to be removed when brewing SOUP packets.")
+
+;;; Internal Variables:
+
+(defvar gnus-soup-encoding-type ?n
+  "*Soup encoding type.
+`n' is news format, `m' is Unix mbox format, and `M' is MMDF mailbox
+format.")
+
+(defvar gnus-soup-index-type ?c
+  "*Soup index type.
+`n' means no index file and `c' means standard Cnews overview
+format.")
+
+(defvar gnus-soup-areas nil)
+(defvar gnus-soup-last-prefix nil)
+(defvar gnus-soup-prev-prefix nil)
+(defvar gnus-soup-buffers nil)
+
+;;; Access macros:
+
+(defmacro gnus-soup-area-prefix (area)
+  `(aref ,area 0))
+(defmacro gnus-soup-set-area-prefix (area prefix)
+  `(aset ,area 0 ,prefix))
+(defmacro gnus-soup-area-name (area)
+  `(aref ,area 1))
+(defmacro gnus-soup-area-encoding (area)
+  `(aref ,area 2))
+(defmacro gnus-soup-area-description (area)
+  `(aref ,area 3))
+(defmacro gnus-soup-area-number (area)
+  `(aref ,area 4))
+(defmacro gnus-soup-area-set-number (area value)
+  `(aset ,area 4 ,value))
+
+(defmacro gnus-soup-encoding-format (encoding)
+  `(aref ,encoding 0))
+(defmacro gnus-soup-encoding-index (encoding)
+  `(aref ,encoding 1))
+(defmacro gnus-soup-encoding-kind (encoding)
+  `(aref ,encoding 2))
+
+(defmacro gnus-soup-reply-prefix (reply)
+  `(aref ,reply 0))
+(defmacro gnus-soup-reply-kind (reply)
+  `(aref ,reply 1))
+(defmacro gnus-soup-reply-encoding (reply)
+  `(aref ,reply 2))
+
+;;; Commands:
+
+(defun gnus-soup-send-replies ()
+  "Unpack and send all replies in the reply packet."
+  (interactive)
+  (let ((packets (directory-files
+		  gnus-soup-packet-directory t gnus-soup-packet-regexp)))
+    (while packets
+      (when (gnus-soup-send-packet (car packets))
+	(delete-file (car packets)))
+      (setq packets (cdr packets)))))
+
+(defun gnus-soup-add-article (n)
+  "Add the current article to SOUP packet.
+If N is a positive number, add the N next articles.
+If N is a negative number, add the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+move those articles instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let* ((articles (gnus-summary-work-articles n))
+	 (tmp-buf (get-buffer-create "*soup work*"))
+	 (area (gnus-soup-area gnus-newsgroup-name))
+	 (prefix (gnus-soup-area-prefix area))
+	 headers)
+    (buffer-disable-undo tmp-buf)
+    (save-excursion
+      (while articles
+	;; Find the header of the article.
+	(set-buffer gnus-summary-buffer)
+	(when (setq headers (gnus-summary-article-header (car articles)))
+	  ;; Put the article in a buffer.
+	  (set-buffer tmp-buf)
+	  (when (gnus-request-article-this-buffer
+		 (car articles) gnus-newsgroup-name)
+	    (save-restriction
+	      (message-narrow-to-head)
+	      (message-remove-header gnus-soup-ignored-headers t))
+	    (gnus-soup-store gnus-soup-directory prefix headers
+			     gnus-soup-encoding-type
+			     gnus-soup-index-type)
+	    (gnus-soup-area-set-number
+	     area (1+ (or (gnus-soup-area-number area) 0)))))
+	;; Mark article as read.
+	(set-buffer gnus-summary-buffer)
+	(gnus-summary-remove-process-mark (car articles))
+	(gnus-summary-mark-as-read (car articles) gnus-souped-mark)
+	(setq articles (cdr articles)))
+      (kill-buffer tmp-buf))
+    (gnus-soup-save-areas)))
+
+(defun gnus-soup-pack-packet ()
+  "Make a SOUP packet from the SOUP areas."
+  (interactive)
+  (gnus-soup-read-areas)
+  (unless (file-exists-p gnus-soup-directory)
+    (message "No such directory: %s" gnus-soup-directory))
+  (when (null (directory-files gnus-soup-directory nil "\\.MSG$"))
+    (message "No files to pack."))
+  (gnus-soup-pack gnus-soup-directory gnus-soup-packer))
+
+(defun gnus-group-brew-soup (n)
+  "Make a soup packet from the current group.
+Uses the process/prefix convention."
+  (interactive "P")
+  (let ((groups (gnus-group-process-prefix n)))
+    (while groups
+      (gnus-group-remove-mark (car groups))
+      (gnus-soup-group-brew (car groups) t)
+      (setq groups (cdr groups)))
+    (gnus-soup-save-areas)))
+
+(defun gnus-brew-soup (&optional level)
+  "Go through all groups on LEVEL or less and make a soup packet."
+  (interactive "P")
+  (let ((level (or level gnus-level-subscribed))
+	(newsrc (cdr gnus-newsrc-alist)))
+    (while newsrc
+      (when (<= (nth 1 (car newsrc)) level)
+	(gnus-soup-group-brew (caar newsrc) t))
+      (setq newsrc (cdr newsrc)))
+    (gnus-soup-save-areas)))
+
+;;;###autoload
+(defun gnus-batch-brew-soup ()
+  "Brew a SOUP packet from groups mention on the command line.
+Will use the remaining command line arguments as regular expressions
+for matching on group names.
+
+For instance, if you want to brew on all the nnml groups, as well as
+groups with \"emacs\" in the name, you could say something like:
+
+$ emacs -batch -f gnus-batch-brew-soup ^nnml \".*emacs.*\""
+  (interactive)
+  nil)
+
+;;; Internal Functions:
+
+;; Store the current buffer.
+(defun gnus-soup-store (directory prefix headers format index)
+  ;; Create the directory, if needed.
+  (gnus-make-directory directory)
+  (let* ((msg-buf (nnheader-find-file-noselect
+		   (concat directory prefix ".MSG")))
+	 (idx-buf (if (= index ?n)
+		      nil
+		    (nnheader-find-file-noselect
+		     (concat directory prefix ".IDX"))))
+	 (article-buf (current-buffer))
+	 from head-line beg type)
+    (setq gnus-soup-buffers (cons msg-buf (delq msg-buf gnus-soup-buffers)))
+    (buffer-disable-undo msg-buf)
+    (when idx-buf
+      (push idx-buf gnus-soup-buffers)
+      (buffer-disable-undo idx-buf))
+    (save-excursion
+      ;; Make sure the last char in the buffer is a newline.
+      (goto-char (point-max))
+      (unless (= (current-column) 0)
+	(insert "\n"))
+      ;; Find the "from".
+      (goto-char (point-min))
+      (setq from
+	    (gnus-mail-strip-quoted-names
+	     (or (mail-fetch-field "from")
+		 (mail-fetch-field "really-from")
+		 (mail-fetch-field "sender"))))
+      (goto-char (point-min))
+      ;; Depending on what encoding is supposed to be used, we make
+      ;; a soup header.
+      (setq head-line
+	    (cond
+	     ((= gnus-soup-encoding-type ?n)
+	      (format "#! rnews %d\n" (buffer-size)))
+	     ((= gnus-soup-encoding-type ?m)
+	      (while (search-forward "\nFrom " nil t)
+		(replace-match "\n>From " t t))
+	      (concat "From " (or from "unknown")
+		      " " (current-time-string) "\n"))
+	     ((= gnus-soup-encoding-type ?M)
+	      "\^a\^a\^a\^a\n")
+	     (t (error "Unsupported type: %c" gnus-soup-encoding-type))))
+      ;; Insert the soup header and the article in the MSG buf.
+      (set-buffer msg-buf)
+      (goto-char (point-max))
+      (insert head-line)
+      (setq beg (point))
+      (insert-buffer-substring article-buf)
+      ;; Insert the index in the IDX buf.
+      (cond ((= index ?c)
+	     (set-buffer idx-buf)
+	     (gnus-soup-insert-idx beg headers))
+	    ((/= index ?n)
+	     (error "Unknown index type: %c" type)))
+      ;; Return the MSG buf.
+      msg-buf)))
+
+(defun gnus-soup-group-brew (group &optional not-all)
+  "Enter GROUP and add all articles to a SOUP package.
+If NOT-ALL, don't pack ticked articles."
+  (let ((gnus-expert-user t)
+	(gnus-large-newsgroup nil)
+	(entry (gnus-gethash group gnus-newsrc-hashtb)))
+    (when (or (null entry)
+	      (eq (car entry) t)
+	      (and (car entry)
+		   (> (car entry) 0))
+	      (and (not not-all)
+		   (gnus-range-length (cdr (assq 'tick (gnus-info-marks
+							(nth 2 entry)))))))
+      (when (gnus-summary-read-group group nil t)
+	(setq gnus-newsgroup-processable
+	      (reverse
+	       (if (not not-all)
+		   (append gnus-newsgroup-marked gnus-newsgroup-unreads)
+		 gnus-newsgroup-unreads)))
+	(gnus-soup-add-article nil)
+	(gnus-summary-exit)))))
+
+(defun gnus-soup-insert-idx (offset header)
+  ;; [number subject from date id references chars lines xref]
+  (goto-char (point-max))
+  (insert
+   (format "%d\t%s\t%s\t%s\t%s\t%s\t%d\t%s\t\t\n"
+	   offset
+	   (or (mail-header-subject header) "(none)")
+	   (or (mail-header-from header) "(nobody)")
+	   (or (mail-header-date header) "")
+	   (or (mail-header-id header)
+	       (concat "soup-dummy-id-"
+		       (mapconcat
+			(lambda (time) (int-to-string time))
+			(current-time) "-")))
+	   (or (mail-header-references header) "")
+	   (or (mail-header-chars header) 0)
+	   (or (mail-header-lines header) "0"))))
+
+(defun gnus-soup-save-areas ()
+  (gnus-soup-write-areas)
+  (save-excursion
+    (let (buf)
+      (while gnus-soup-buffers
+	(setq buf (car gnus-soup-buffers)
+	      gnus-soup-buffers (cdr gnus-soup-buffers))
+	(if (not (buffer-name buf))
+	    ()
+	  (set-buffer buf)
+	  (when (buffer-modified-p)
+	    (save-buffer))
+	  (kill-buffer (current-buffer)))))
+    (gnus-soup-write-prefixes)))
+
+(defun gnus-soup-write-prefixes ()
+  (let ((prefixes gnus-soup-last-prefix)
+	prefix)
+    (save-excursion
+      (gnus-set-work-buffer)
+      (while (setq prefix (pop prefixes))
+	(erase-buffer)
+	(insert (format "(setq gnus-soup-prev-prefix %d)\n" (cdr prefix)))
+	(gnus-write-buffer (concat (car prefix) gnus-soup-prefix-file))))))
+
+(defun gnus-soup-pack (dir packer)
+  (let* ((files (mapconcat 'identity
+			   '("AREAS" "*.MSG" "*.IDX" "INFO"
+			     "LIST" "REPLIES" "COMMANDS" "ERRORS")
+			   " "))
+	 (packer (if (< (string-match "%s" packer)
+			(string-match "%d" packer))
+		     (format packer files
+			     (string-to-int (gnus-soup-unique-prefix dir)))
+		   (format packer
+			   (string-to-int (gnus-soup-unique-prefix dir))
+			   files)))
+	 (dir (expand-file-name dir)))
+    (gnus-make-directory dir)
+    (setq gnus-soup-areas nil)
+    (gnus-message 4 "Packing %s..." packer)
+    (if (zerop (call-process shell-file-name
+			     nil nil nil shell-command-switch
+			     (concat "cd " dir " ; " packer)))
+	(progn
+	  (call-process shell-file-name nil nil nil shell-command-switch
+			(concat "cd " dir " ; rm " files))
+	  (gnus-message 4 "Packing...done" packer))
+      (error "Couldn't pack packet."))))
+
+(defun gnus-soup-parse-areas (file)
+  "Parse soup area file FILE.
+The result is a of vectors, each containing one entry from the AREA file.
+The vector contain five strings,
+  [prefix name encoding description number]
+though the two last may be nil if they are missing."
+  (let (areas)
+    (save-excursion
+      (set-buffer (nnheader-find-file-noselect file 'force))
+      (buffer-disable-undo (current-buffer))
+      (goto-char (point-min))
+      (while (not (eobp))
+	(push (vector (gnus-soup-field)
+		      (gnus-soup-field)
+		      (gnus-soup-field)
+		      (and (eq (preceding-char) ?\t)
+			   (gnus-soup-field))
+		      (and (eq (preceding-char) ?\t)
+			   (string-to-int (gnus-soup-field))))
+	      areas)
+	(when (eq (preceding-char) ?\t)
+	  (beginning-of-line 2)))
+      (kill-buffer (current-buffer)))
+    areas))
+
+(defun gnus-soup-parse-replies (file)
+  "Parse soup REPLIES file FILE.
+The result is a of vectors, each containing one entry from the REPLIES
+file.  The vector contain three strings, [prefix name encoding]."
+  (let (replies)
+    (save-excursion
+      (set-buffer (nnheader-find-file-noselect file))
+      (buffer-disable-undo (current-buffer))
+      (goto-char (point-min))
+      (while (not (eobp))
+	(push (vector (gnus-soup-field) (gnus-soup-field)
+		      (gnus-soup-field))
+	      replies)
+	(when (eq (preceding-char) ?\t)
+	  (beginning-of-line 2)))
+      (kill-buffer (current-buffer)))
+    replies))
+
+(defun gnus-soup-field ()
+  (prog1
+      (buffer-substring (point) (progn (skip-chars-forward "^\t\n") (point)))
+    (forward-char 1)))
+
+(defun gnus-soup-read-areas ()
+  (or gnus-soup-areas
+      (setq gnus-soup-areas
+	    (gnus-soup-parse-areas (concat gnus-soup-directory "AREAS")))))
+
+(defun gnus-soup-write-areas ()
+  "Write the AREAS file."
+  (interactive)
+  (when gnus-soup-areas
+    (nnheader-temp-write (concat gnus-soup-directory "AREAS")
+      (let ((areas gnus-soup-areas)
+	    area)
+	(while (setq area (pop areas))
+	  (insert
+	   (format
+	    "%s\t%s\t%s%s\n"
+	    (gnus-soup-area-prefix area)
+	    (gnus-soup-area-name area)
+	    (gnus-soup-area-encoding area)
+	    (if (or (gnus-soup-area-description area)
+		    (gnus-soup-area-number area))
+		(concat "\t" (or (gnus-soup-area-description
+				  area) "")
+			(if (gnus-soup-area-number area)
+			    (concat "\t" (int-to-string
+					  (gnus-soup-area-number area)))
+			  "")) ""))))))))
+
+(defun gnus-soup-write-replies (dir areas)
+  "Write a REPLIES file in DIR containing AREAS."
+  (nnheader-temp-write (concat dir "REPLIES")
+    (let (area)
+      (while (setq area (pop areas))
+	(insert (format "%s\t%s\t%s\n"
+			(gnus-soup-reply-prefix area)
+			(gnus-soup-reply-kind area)
+			(gnus-soup-reply-encoding area)))))))
+
+(defun gnus-soup-area (group)
+  (gnus-soup-read-areas)
+  (let ((areas gnus-soup-areas)
+	(real-group (gnus-group-real-name group))
+	area result)
+    (while areas
+      (setq area (car areas)
+	    areas (cdr areas))
+      (when (equal (gnus-soup-area-name area) real-group)
+	(setq result area)))
+    (unless result
+      (setq result
+	    (vector (gnus-soup-unique-prefix)
+		    real-group
+		    (format "%c%c%c"
+			    gnus-soup-encoding-type
+			    gnus-soup-index-type
+			    (if (gnus-member-of-valid 'mail group) ?m ?n))
+		    nil nil)
+	    gnus-soup-areas (cons result gnus-soup-areas)))
+    result))
+
+(defun gnus-soup-unique-prefix (&optional dir)
+  (let* ((dir (file-name-as-directory (or dir gnus-soup-directory)))
+	 (entry (assoc dir gnus-soup-last-prefix))
+	 gnus-soup-prev-prefix)
+    (if entry
+	()
+      (when (file-exists-p (concat dir gnus-soup-prefix-file))
+	(ignore-errors
+	  (load (concat dir gnus-soup-prefix-file) nil t t)))
+      (push (setq entry (cons dir (or gnus-soup-prev-prefix 0)))
+	    gnus-soup-last-prefix))
+    (setcdr entry (1+ (cdr entry)))
+    (gnus-soup-write-prefixes)
+    (int-to-string (cdr entry))))
+
+(defun gnus-soup-unpack-packet (dir unpacker packet)
+  "Unpack PACKET into DIR using UNPACKER.
+Return whether the unpacking was successful."
+  (gnus-make-directory dir)
+  (gnus-message 4 "Unpacking: %s" (format unpacker packet))
+  (prog1
+      (zerop (call-process
+	      shell-file-name nil nil nil shell-command-switch
+	      (format "cd %s ; %s" (expand-file-name dir)
+		      (format unpacker packet))))
+    (gnus-message 4 "Unpacking...done")))
+
+(defun gnus-soup-send-packet (packet)
+  (gnus-soup-unpack-packet
+   gnus-soup-replies-directory gnus-soup-unpacker packet)
+  (let ((replies (gnus-soup-parse-replies
+		  (concat gnus-soup-replies-directory "REPLIES"))))
+    (save-excursion
+      (while replies
+	(let* ((msg-file (concat gnus-soup-replies-directory
+				 (gnus-soup-reply-prefix (car replies))
+				 ".MSG"))
+	       (msg-buf (and (file-exists-p msg-file)
+			     (nnheader-find-file-noselect msg-file)))
+	       (tmp-buf (get-buffer-create " *soup send*"))
+	       beg end)
+	  (cond
+	   ((/= (gnus-soup-encoding-format
+		 (gnus-soup-reply-encoding (car replies)))
+		?n)
+	    (error "Unsupported encoding"))
+	   ((null msg-buf)
+	    t)
+	   (t
+	    (buffer-disable-undo msg-buf)
+	    (buffer-disable-undo tmp-buf)
+	    (set-buffer msg-buf)
+	    (goto-char (point-min))
+	    (while (not (eobp))
+	      (unless (looking-at "#! *rnews +\\([0-9]+\\)")
+		(error "Bad header."))
+	      (forward-line 1)
+	      (setq beg (point)
+		    end (+ (point) (string-to-int
+				    (buffer-substring
+				     (match-beginning 1) (match-end 1)))))
+	      (switch-to-buffer tmp-buf)
+	      (erase-buffer)
+	      (insert-buffer-substring msg-buf beg end)
+	      (goto-char (point-min))
+	      (search-forward "\n\n")
+	      (forward-char -1)
+	      (insert mail-header-separator)
+	      (setq message-newsreader (setq message-mailer
+					     (gnus-extended-version)))
+	      (cond
+	       ((string= (gnus-soup-reply-kind (car replies)) "news")
+		(gnus-message 5 "Sending news message to %s..."
+			      (mail-fetch-field "newsgroups"))
+		(sit-for 1)
+		(let ((message-syntax-checks
+		       'dont-check-for-anything-just-trust-me))
+		  (funcall message-send-news-function)))
+	       ((string= (gnus-soup-reply-kind (car replies)) "mail")
+		(gnus-message 5 "Sending mail to %s..."
+			      (mail-fetch-field "to"))
+		(sit-for 1)
+		(message-send-mail))
+	       (t
+		(error "Unknown reply kind")))
+	      (set-buffer msg-buf)
+	      (goto-char end))
+	    (delete-file (buffer-file-name))
+	    (kill-buffer msg-buf)
+	    (kill-buffer tmp-buf)
+	    (gnus-message 4 "Sent packet"))))
+	(setq replies (cdr replies)))
+      t)))
+
+(provide 'gnus-soup)
+
+;;; gnus-soup.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-spec.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,528 @@
+;;; gnus-spec.el --- format spec functions for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+
+;;; Internal variables.
+
+(defvar gnus-summary-mark-positions nil)
+(defvar gnus-group-mark-positions nil)
+(defvar gnus-group-indentation "")
+
+;; Format specs.  The chunks below are the machine-generated forms
+;; that are to be evaled as the result of the default format strings.
+;; We write them in here to get them byte-compiled.  That way the
+;; default actions will be quite fast, while still retaining the full
+;; flexibility of the user-defined format specs.
+
+;; First we have lots of dummy defvars to let the compiler know these
+;; are really dynamic variables.
+
+(defvar gnus-tmp-unread)
+(defvar gnus-tmp-replied)
+(defvar gnus-tmp-score-char)
+(defvar gnus-tmp-indentation)
+(defvar gnus-tmp-opening-bracket)
+(defvar gnus-tmp-lines)
+(defvar gnus-tmp-name)
+(defvar gnus-tmp-closing-bracket)
+(defvar gnus-tmp-subject-or-nil)
+(defvar gnus-tmp-subject)
+(defvar gnus-tmp-marked)
+(defvar gnus-tmp-marked-mark)
+(defvar gnus-tmp-subscribed)
+(defvar gnus-tmp-process-marked)
+(defvar gnus-tmp-number-of-unread)
+(defvar gnus-tmp-group-name)
+(defvar gnus-tmp-group)
+(defvar gnus-tmp-article-number)
+(defvar gnus-tmp-unread-and-unselected)
+(defvar gnus-tmp-news-method)
+(defvar gnus-tmp-news-server)
+(defvar gnus-tmp-article-number)
+(defvar gnus-mouse-face)
+(defvar gnus-mouse-face-prop)
+
+(defun gnus-summary-line-format-spec ()
+  (insert gnus-tmp-unread gnus-tmp-replied
+	  gnus-tmp-score-char gnus-tmp-indentation)
+  (gnus-put-text-property
+   (point)
+   (progn
+     (insert
+      gnus-tmp-opening-bracket
+      (format "%4d: %-20s"
+	      gnus-tmp-lines
+	      (if (> (length gnus-tmp-name) 20)
+		  (substring gnus-tmp-name 0 20)
+		gnus-tmp-name))
+      gnus-tmp-closing-bracket)
+     (point))
+   gnus-mouse-face-prop gnus-mouse-face)
+  (insert " " gnus-tmp-subject-or-nil "\n"))
+
+(defvar gnus-summary-line-format-spec
+  (gnus-byte-code 'gnus-summary-line-format-spec))
+
+(defun gnus-summary-dummy-line-format-spec ()
+  (insert "*  ")
+  (gnus-put-text-property
+   (point)
+   (progn
+     (insert ":				 :")
+     (point))
+   gnus-mouse-face-prop gnus-mouse-face)
+  (insert " " gnus-tmp-subject "\n"))
+
+(defvar gnus-summary-dummy-line-format-spec
+  (gnus-byte-code 'gnus-summary-dummy-line-format-spec))
+
+(defun gnus-group-line-format-spec ()
+  (insert gnus-tmp-marked-mark gnus-tmp-subscribed
+	  gnus-tmp-process-marked
+	  gnus-group-indentation
+	  (format "%5s: " gnus-tmp-number-of-unread))
+  (gnus-put-text-property
+   (point)
+   (progn
+     (insert gnus-tmp-group "\n")
+     (1- (point)))
+   gnus-mouse-face-prop gnus-mouse-face))
+(defvar gnus-group-line-format-spec
+  (gnus-byte-code 'gnus-group-line-format-spec))
+
+(defvar gnus-format-specs
+  `((version . ,emacs-version)
+    (group "%M\%S\%p\%P\%5y: %(%g%)%l\n" ,gnus-group-line-format-spec)
+    (summary-dummy "*  %(:                          :%) %S\n"
+		   ,gnus-summary-dummy-line-format-spec)
+    (summary "%U\%R\%z\%I\%(%[%4L: %-20,20n%]%) %s\n"
+	     ,gnus-summary-line-format-spec))
+  "Alist of format specs.")
+
+(defvar gnus-article-mode-line-format-spec nil)
+(defvar gnus-summary-mode-line-format-spec nil)
+(defvar gnus-group-mode-line-format-spec nil)
+
+;;; Phew.  All that gruft is over, fortunately.
+
+;;;###autoload
+(defun gnus-update-format (var)
+  "Update the format specification near point."
+  (interactive
+   (list
+    (save-excursion
+      (eval-defun nil)
+      ;; Find the end of the current word.
+      (re-search-forward "[ \t\n]" nil t)
+      ;; Search backward.
+      (when (re-search-backward "\\(gnus-[-a-z]+-line-format\\)" nil t)
+	(match-string 1)))))
+  (let* ((type (intern (progn (string-match "gnus-\\([-a-z]+\\)-line" var)
+			      (match-string 1 var))))
+	 (entry (assq type gnus-format-specs))
+	 value spec)
+    (when entry
+      (setq gnus-format-specs (delq entry gnus-format-specs)))
+    (set
+     (intern (format "%s-spec" var))
+     (gnus-parse-format (setq value (symbol-value (intern var)))
+			(symbol-value (intern (format "%s-alist" var)))
+			(not (string-match "mode" var))))
+    (setq spec (symbol-value (intern (format "%s-spec" var))))
+    (push (list type value spec) gnus-format-specs)
+
+    (pop-to-buffer "*Gnus Format*")
+    (erase-buffer)
+    (lisp-interaction-mode)
+    (insert (pp-to-string spec))))
+
+(defun gnus-update-format-specifications (&optional force &rest types)
+  "Update all (necessary) format specifications."
+  ;; Make the indentation array.
+  ;; See whether all the stored info needs to be flushed.
+  (when (or force
+	    (not (equal emacs-version
+			(cdr (assq 'version gnus-format-specs)))))
+    (setq gnus-format-specs nil))
+
+  ;; Go through all the formats and see whether they need updating.
+  (let (new-format entry type val)
+    (while (setq type (pop types))
+      ;; Jump to the proper buffer to find out the value of
+      ;; the variable, if possible.  (It may be buffer-local.)
+      (save-excursion
+	(let ((buffer (intern (format "gnus-%s-buffer" type)))
+	      val)
+	  (when (and (boundp buffer)
+		     (setq val (symbol-value buffer))
+		     (get-buffer val)
+		     (buffer-name (get-buffer val)))
+	    (set-buffer (get-buffer val)))
+	  (setq new-format (symbol-value
+			    (intern (format "gnus-%s-line-format" type)))))
+	(setq entry (cdr (assq type gnus-format-specs)))
+	(if (and (car entry)
+		 (equal (car entry) new-format))
+	    ;; Use the old format.
+	    (set (intern (format "gnus-%s-line-format-spec" type))
+		 (cadr entry))
+	  ;; This is a new format.
+	  (setq val
+		(if (not (stringp new-format))
+		    ;; This is a function call or something.
+		    new-format
+		  ;; This is a "real" format.
+		  (gnus-parse-format
+		   new-format
+		   (symbol-value
+		    (intern (format "gnus-%s-line-format-alist"
+				    (if (eq type 'article-mode)
+					'summary-mode type))))
+		   (not (string-match "mode$" (symbol-name type))))))
+	  ;; Enter the new format spec into the list.
+	  (if entry
+	      (progn
+		(setcar (cdr entry) val)
+		(setcar entry new-format))
+	    (push (list type new-format val) gnus-format-specs))
+	  (set (intern (format "gnus-%s-line-format-spec" type)) val)))))
+
+  (unless (assq 'version gnus-format-specs)
+    (push (cons 'version emacs-version) gnus-format-specs)))
+
+(defvar gnus-mouse-face-0 'highlight)
+(defvar gnus-mouse-face-1 'highlight)
+(defvar gnus-mouse-face-2 'highlight)
+(defvar gnus-mouse-face-3 'highlight)
+(defvar gnus-mouse-face-4 'highlight)
+
+(defun gnus-mouse-face-function (form type)
+  `(gnus-put-text-property
+    (point) (progn ,@form (point))
+    gnus-mouse-face-prop
+    ,(if (equal type 0)
+	 'gnus-mouse-face
+       `(quote ,(symbol-value (intern (format "gnus-mouse-face-%d" type)))))))
+
+(defvar gnus-face-0 'bold)
+(defvar gnus-face-1 'italic)
+(defvar gnus-face-2 'bold-italic)
+(defvar gnus-face-3 'bold)
+(defvar gnus-face-4 'bold)
+
+(defun gnus-face-face-function (form type)
+  `(gnus-put-text-property
+    (point) (progn ,@form (point))
+    'face ',(symbol-value (intern (format "gnus-face-%d" type)))))
+
+(defun gnus-tilde-max-form (el max-width)
+  "Return a form that limits EL to MAX-WIDTH."
+  (let ((max (abs max-width)))
+    (if (symbolp el)
+	`(if (> (length ,el) ,max)
+	     ,(if (< max-width 0)
+		  `(substring ,el (- (length el) ,max))
+		`(substring ,el 0 ,max))
+	   ,el)
+      `(let ((val (eval ,el)))
+	 (if (> (length val) ,max)
+	     ,(if (< max-width 0)
+		  `(substring val (- (length val) ,max))
+		`(substring val 0 ,max))
+	   val)))))
+
+(defun gnus-tilde-cut-form (el cut-width)
+  "Return a form that cuts CUT-WIDTH off of EL."
+  (let ((cut (abs cut-width)))
+    (if (symbolp el)
+	`(if (> (length ,el) ,cut)
+	     ,(if (< cut-width 0)
+		  `(substring ,el 0 (- (length el) ,cut))
+		`(substring ,el ,cut))
+	   ,el)
+      `(let ((val (eval ,el)))
+	 (if (> (length val) ,cut)
+	     ,(if (< cut-width 0)
+		  `(substring val 0 (- (length val) ,cut))
+		`(substring val ,cut))
+	   val)))))
+
+(defun gnus-tilde-ignore-form (el ignore-value)
+  "Return a form that is blank when EL is IGNORE-VALUE."
+  (if (symbolp el)
+      `(if (equal ,el ,ignore-value)
+	   "" ,el)
+    `(let ((val (eval ,el)))
+       (if (equal val ,ignore-value)
+	   "" val))))
+
+(defun gnus-parse-format (format spec-alist &optional insert)
+  ;; This function parses the FORMAT string with the help of the
+  ;; SPEC-ALIST and returns a list that can be eval'ed to return the
+  ;; string.  If the FORMAT string contains the specifiers %( and %)
+  ;; the text between them will have the mouse-face text property.
+  (if (string-match
+       "\\`\\(.*\\)%[0-9]?[{(]\\(.*\\)%[0-9]?[})]\\(.*\n?\\)\\'"
+       format)
+      (gnus-parse-complex-format format spec-alist)
+    ;; This is a simple format.
+    (gnus-parse-simple-format format spec-alist insert)))
+
+(defun gnus-parse-complex-format (format spec-alist)
+  (save-excursion
+    (gnus-set-work-buffer)
+    (insert format)
+    (goto-char (point-min))
+    (while (re-search-forward "\"" nil t)
+      (replace-match "\\\"" nil t))
+    (goto-char (point-min))
+    (insert "(\"")
+    (while (re-search-forward "%\\([0-9]+\\)?\\([{}()]\\)" nil t)
+      (let ((number (if (match-beginning 1)
+			(match-string 1) "0"))
+	    (delim (aref (match-string 2) 0)))
+	(if (or (= delim ?\() (= delim ?\{))
+	    (replace-match (concat "\"(" (if (= delim ?\() "mouse" "face")
+				   " " number " \""))
+	  (replace-match "\")\""))))
+    (goto-char (point-max))
+    (insert "\")")
+    (goto-char (point-min))
+    (let ((form (read (current-buffer))))
+      (cons 'progn (gnus-complex-form-to-spec form spec-alist)))))
+
+(defun gnus-complex-form-to-spec (form spec-alist)
+  (delq nil
+	(mapcar
+	 (lambda (sform)
+	   (if (stringp sform)
+	       (gnus-parse-simple-format sform spec-alist t)
+	     (funcall (intern (format "gnus-%s-face-function" (car sform)))
+		      (gnus-complex-form-to-spec (cddr sform) spec-alist)
+		      (nth 1 sform))))
+	 form)))
+
+(defun gnus-parse-simple-format (format spec-alist &optional insert)
+  ;; This function parses the FORMAT string with the help of the
+  ;; SPEC-ALIST and returns a list that can be eval'ed to return a
+  ;; string.
+  (let ((max-width 0)
+	spec flist fstring elem result dontinsert user-defined
+	type value pad-width spec-beg cut-width ignore-value
+	tilde-form tilde elem-type)
+    (save-excursion
+      (gnus-set-work-buffer)
+      (insert format)
+      (goto-char (point-min))
+      (while (re-search-forward "%" nil t)
+	(setq user-defined nil
+	      spec-beg nil
+	      pad-width nil
+	      max-width nil
+	      cut-width nil
+	      ignore-value nil
+	      tilde-form nil)
+	(setq spec-beg (1- (point)))
+
+	;; Parse this spec fully.
+	(while
+	    (cond
+	     ((looking-at "\\([-.0-9]+\\)\\(,[-0-9]+\\)?")
+	      (setq pad-width (string-to-number (match-string 1)))
+	      (when (match-beginning 2)
+		(setq max-width (string-to-number (buffer-substring
+						   (1+ (match-beginning 2))
+						   (match-end 2)))))
+	      (goto-char (match-end 0)))
+	     ((looking-at "~")
+	      (forward-char 1)
+	      (setq tilde (read (current-buffer))
+		    type (car tilde)
+		    value (cadr tilde))
+	      (cond
+	       ((memq type '(pad pad-left))
+		(setq pad-width value))
+	       ((eq type 'pad-right)
+		(setq pad-width (- value)))
+	       ((memq type '(max-right max))
+		(setq max-width value))
+	       ((eq type 'max-left)
+		(setq max-width (- value)))
+	       ((memq type '(cut cut-left))
+		(setq cut-width value))
+	       ((eq type 'cut-right)
+		(setq cut-width (- value)))
+	       ((eq type 'ignore)
+		(setq ignore-value
+		      (if (stringp value) value (format "%s" value))))
+	       ((eq type 'form)
+		(setq tilde-form value))
+	       (t
+		(error "Unknown tilde type: %s" tilde)))
+	      t)
+	     (t
+	      nil)))
+	;; User-defined spec -- find the spec name.
+	(when (= (setq spec (following-char)) ?u)
+	  (forward-char 1)
+	  (setq user-defined (following-char)))
+	(forward-char 1)
+	(delete-region spec-beg (point))
+
+	;; Now we have all the relevant data on this spec, so
+	;; we start doing stuff.
+	(insert "%")
+	(if (eq spec ?%)
+	    ;; "%%" just results in a "%".
+	    (insert "%")
+	  (cond
+	   ;; Do tilde forms.
+	   ((eq spec ?@)
+	    (setq elem (list tilde-form ?s)))
+	   ;; Treat user defined format specifiers specially.
+	   (user-defined
+	    (setq elem
+		  (list
+		   (list (intern (format "gnus-user-format-function-%c"
+					 user-defined))
+			 'gnus-tmp-header)
+		   ?s)))
+	   ;; Find the specification from `spec-alist'.
+	   ((setq elem (cdr (assq spec spec-alist))))
+	   (t
+	    (setq elem '("*" ?s))))
+	  (setq elem-type (cadr elem))
+	  ;; Insert the new format elements.
+	  (when pad-width
+	    (insert (number-to-string pad-width)))
+	  ;; Create the form to be evaled.
+	  (if (or max-width cut-width ignore-value)
+	      (progn
+		(insert ?s)
+		(let ((el (car elem)))
+		  (cond ((= (cadr elem) ?c)
+			 (setq el (list 'char-to-string el)))
+			((= (cadr elem) ?d)
+			 (setq el (list 'int-to-string el))))
+		  (when ignore-value
+		    (setq el (gnus-tilde-ignore-form el ignore-value)))
+		  (when cut-width
+		    (setq el (gnus-tilde-cut-form el cut-width)))
+		  (when max-width
+		    (setq el (gnus-tilde-max-form el max-width)))
+		  (push el flist)))
+	    (insert elem-type)
+	    (push (car elem) flist))))
+      (setq fstring (buffer-string)))
+
+    ;; Do some postprocessing to increase efficiency.
+    (setq
+     result
+     (cond
+      ;; Emptyness.
+      ((string= fstring "")
+       nil)
+      ;; Not a format string.
+      ((not (string-match "%" fstring))
+       (list fstring))
+      ;; A format string with just a single string spec.
+      ((string= fstring "%s")
+       (list (car flist)))
+      ;; A single character.
+      ((string= fstring "%c")
+       (list (car flist)))
+      ;; A single number.
+      ((string= fstring "%d")
+       (setq dontinsert)
+       (if insert
+	   (list `(princ ,(car flist)))
+	 (list `(int-to-string ,(car flist)))))
+      ;; Just lots of chars and strings.
+      ((string-match "\\`\\(%[cs]\\)+\\'" fstring)
+       (nreverse flist))
+      ;; A single string spec at the beginning of the spec.
+      ((string-match "\\`%[sc][^%]+\\'" fstring)
+       (list (car flist) (substring fstring 2)))
+      ;; A single string spec in the middle of the spec.
+      ((string-match "\\`\\([^%]+\\)%[sc]\\([^%]+\\)\\'" fstring)
+       (list (match-string 1 fstring) (car flist) (match-string 2 fstring)))
+      ;; A single string spec in the end of the spec.
+      ((string-match "\\`\\([^%]+\\)%[sc]\\'" fstring)
+       (list (match-string 1 fstring) (car flist)))
+      ;; A more complex spec.
+      (t
+       (list (cons 'format (cons fstring (nreverse flist)))))))
+
+    (if insert
+	(when result
+	  (if dontinsert
+	      result
+	    (cons 'insert result)))
+      (cond ((stringp result)
+	     result)
+	    ((consp result)
+	     (cons 'concat result))
+	    (t "")))))
+
+(defun gnus-eval-format (format &optional alist props)
+  "Eval the format variable FORMAT, using ALIST.
+If PROPS, insert the result."
+  (let ((form (gnus-parse-format format alist props)))
+    (if props
+	(gnus-add-text-properties (point) (progn (eval form) (point)) props)
+      (eval form))))
+
+(defun gnus-compile ()
+  "Byte-compile the user-defined format specs."
+  (interactive)
+  (when gnus-xemacs
+    (error "Can't compile specs under XEmacs"))
+  (let ((entries gnus-format-specs)
+	(byte-compile-warnings '(unresolved callargs redefine))
+	entry gnus-tmp-func)
+    (save-excursion
+      (gnus-message 7 "Compiling format specs...")
+
+      (while entries
+	(setq entry (pop entries))
+	(if (eq (car entry) 'version)
+	    (setq gnus-format-specs (delq entry gnus-format-specs))
+	  (when (and (listp (caddr entry))
+		     (not (eq 'byte-code (caaddr entry))))
+	    (fset 'gnus-tmp-func `(lambda () ,(caddr entry)))
+	    (byte-compile 'gnus-tmp-func)
+	    (setcar (cddr entry) (gnus-byte-code 'gnus-tmp-func)))))
+
+      (push (cons 'version emacs-version) gnus-format-specs)
+      ;; Mark the .newsrc.eld file as "dirty".
+      (gnus-dribble-enter " ")
+      (gnus-message 7 "Compiling user specs...done"))))
+
+(provide 'gnus-spec)
+
+;;; gnus-spec.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-srvr.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,752 @@
+;;; gnus-srvr.el --- virtual server support for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-spec)
+(require 'gnus-group)
+(require 'gnus-int)
+(require 'gnus-range)
+
+(defvar gnus-server-mode-hook nil
+  "Hook run in `gnus-server-mode' buffers.")
+
+(defconst gnus-server-line-format "     {%(%h:%w%)} %s\n"
+  "Format of server lines.
+It works along the same lines as a normal formatting string,
+with some simple extensions.")
+
+(defvar gnus-server-mode-line-format "Gnus  List of servers"
+  "The format specification for the server mode line.")
+
+(defvar gnus-server-exit-hook nil
+  "*Hook run when exiting the server buffer.")
+
+;;; Internal variables.
+
+(defvar gnus-inserted-opened-servers nil)
+
+(defvar gnus-server-line-format-alist
+  `((?h how ?s)
+    (?n name ?s)
+    (?w where ?s)
+    (?s status ?s)))
+
+(defvar gnus-server-mode-line-format-alist
+  `((?S news-server ?s)
+    (?M news-method ?s)
+    (?u user-defined ?s)))
+
+(defvar gnus-server-line-format-spec nil)
+(defvar gnus-server-mode-line-format-spec nil)
+(defvar gnus-server-killed-servers nil)
+
+(defvar gnus-server-mode-map)
+
+(defvar gnus-server-menu-hook nil
+  "*Hook run after the creation of the server mode menu.")
+
+(defun gnus-server-make-menu-bar ()
+  (gnus-turn-off-edit-menu 'server)
+  (unless (boundp 'gnus-server-server-menu)
+    (easy-menu-define
+     gnus-server-server-menu gnus-server-mode-map ""
+     '("Server"
+       ["Add" gnus-server-add-server t]
+       ["Browse" gnus-server-read-server t]
+       ["Scan" gnus-server-scan-server t]
+       ["List" gnus-server-list-servers t]
+       ["Kill" gnus-server-kill-server t]
+       ["Yank" gnus-server-yank-server t]
+       ["Copy" gnus-server-copy-server t]
+       ["Edit" gnus-server-edit-server t]
+       ["Regenerate" gnus-server-regenerate-server t]
+       ["Exit" gnus-server-exit t]))
+
+    (easy-menu-define
+     gnus-server-connections-menu gnus-server-mode-map ""
+     '("Connections"
+       ["Open" gnus-server-open-server t]
+       ["Close" gnus-server-close-server t]
+       ["Deny" gnus-server-deny-server t]
+       "---"
+       ["Open All" gnus-server-open-all-servers t]
+       ["Close All" gnus-server-close-all-servers t]
+       ["Reset All" gnus-server-remove-denials t]))
+
+    (run-hooks 'gnus-server-menu-hook)))
+
+(defvar gnus-server-mode-map nil)
+(put 'gnus-server-mode 'mode-class 'special)
+
+(unless gnus-server-mode-map
+  (setq gnus-server-mode-map (make-sparse-keymap))
+  (suppress-keymap gnus-server-mode-map)
+
+  (gnus-define-keys
+   gnus-server-mode-map
+   " " gnus-server-read-server
+   "\r" gnus-server-read-server
+   gnus-mouse-2 gnus-server-pick-server
+   "q" gnus-server-exit
+   "l" gnus-server-list-servers
+   "k" gnus-server-kill-server
+   "y" gnus-server-yank-server
+   "c" gnus-server-copy-server
+   "a" gnus-server-add-server
+   "e" gnus-server-edit-server
+   "s" gnus-server-scan-server
+
+   "O" gnus-server-open-server
+   "\M-o" gnus-server-open-all-servers
+   "C" gnus-server-close-server
+   "\M-c" gnus-server-close-all-servers
+   "D" gnus-server-deny-server
+   "R" gnus-server-remove-denials
+
+   "g" gnus-server-regenerate-server
+
+    "\C-c\C-i" gnus-info-find-node
+    "\C-c\C-b" gnus-bug))
+
+(defun gnus-server-mode ()
+  "Major mode for listing and editing servers.
+
+All normal editing commands are switched off.
+\\<gnus-server-mode-map>
+For more in-depth information on this mode, read the manual
+(`\\[gnus-info-find-node]').
+
+The following commands are available:
+
+\\{gnus-server-mode-map}"
+  (interactive)
+  (when (gnus-visual-p 'server-menu 'menu)
+    (gnus-server-make-menu-bar))
+  (kill-all-local-variables)
+  (gnus-simplify-mode-line)
+  (setq major-mode 'gnus-server-mode)
+  (setq mode-name "Server")
+  (gnus-set-default-directory)
+  (setq mode-line-process nil)
+  (use-local-map gnus-server-mode-map)
+  (buffer-disable-undo (current-buffer))
+  (setq truncate-lines t)
+  (setq buffer-read-only t)
+  (run-hooks 'gnus-server-mode-hook))
+
+(defun gnus-server-insert-server-line (name method)
+  (let* ((how (car method))
+	 (where (nth 1 method))
+	 (elem (assoc method gnus-opened-servers))
+	 (status (cond ((eq (nth 1 elem) 'denied)
+			"(denied)")
+		       ((or (gnus-server-opened method)
+			    (eq (nth 1 elem) 'ok))
+			"(opened)")
+		       (t
+			"(closed)"))))
+    (beginning-of-line)
+    (gnus-add-text-properties
+     (point)
+     (prog1 (1+ (point))
+       ;; Insert the text.
+       (eval gnus-server-line-format-spec))
+     (list 'gnus-server (intern name)))))
+
+(defun gnus-enter-server-buffer ()
+  "Set up the server buffer."
+  (gnus-server-setup-buffer)
+  (gnus-configure-windows 'server)
+  (gnus-server-prepare))
+
+(defun gnus-server-setup-buffer ()
+  "Initialize the server buffer."
+  (unless (get-buffer gnus-server-buffer)
+    (save-excursion
+      (set-buffer (get-buffer-create gnus-server-buffer))
+      (gnus-server-mode)
+      (when gnus-carpal
+	(gnus-carpal-setup-buffer 'server)))))
+
+(defun gnus-server-prepare ()
+  (setq gnus-server-mode-line-format-spec
+	(gnus-parse-format gnus-server-mode-line-format
+			   gnus-server-mode-line-format-alist))
+  (setq gnus-server-line-format-spec
+	(gnus-parse-format gnus-server-line-format
+			   gnus-server-line-format-alist t))
+  (let ((alist gnus-server-alist)
+	(buffer-read-only nil)
+	(opened gnus-opened-servers)
+	done server op-ser)
+    (erase-buffer)
+    (setq gnus-inserted-opened-servers nil)
+    ;; First we do the real list of servers.
+    (while alist
+      (unless (member (cdar alist) done)
+	(push (cdar alist) done)
+	(cdr (setq server (pop alist)))
+	(when (and server (car server) (cdr server))
+	  (gnus-server-insert-server-line (car server) (cdr server))))
+      (when (member (cdar alist) done)
+	(pop alist)))
+    ;; Then we insert the list of servers that have been opened in
+    ;; this session.
+    (while opened
+      (unless (member (caar opened) done)
+	(push (caar opened) done)
+	(gnus-server-insert-server-line
+	 (setq op-ser (format "%s:%s" (caaar opened) (nth 1 (caar opened))))
+	 (caar opened))
+	(push (list op-ser (caar opened)) gnus-inserted-opened-servers))
+      (setq opened (cdr opened))))
+  (goto-char (point-min))
+  (gnus-server-position-point))
+
+(defun gnus-server-server-name ()
+  (let ((server (get-text-property (gnus-point-at-bol) 'gnus-server)))
+    (and server (symbol-name server))))
+
+(defalias 'gnus-server-position-point 'gnus-goto-colon)
+
+(defconst gnus-server-edit-buffer "*Gnus edit server*")
+
+(defun gnus-server-update-server (server)
+  (save-excursion
+    (set-buffer gnus-server-buffer)
+    (let* ((buffer-read-only nil)
+	   (entry (assoc server gnus-server-alist))
+	   (oentry (assoc (gnus-server-to-method server)
+			  gnus-opened-servers)))
+      (when entry
+	(gnus-dribble-enter
+	 (concat "(gnus-server-set-info \"" server "\" '"
+		 (prin1-to-string (cdr entry)) ")\n")))
+      (when (or entry oentry)
+	;; Buffer may be narrowed.
+	(save-restriction
+	  (widen)
+	  (when (gnus-server-goto-server server)
+	    (gnus-delete-line))
+	  (if entry
+	      (gnus-server-insert-server-line (car entry) (cdr entry))
+	    (gnus-server-insert-server-line
+	     (format "%s:%s" (caar oentry) (nth 1 (car oentry)))
+	     (car oentry)))
+	  (gnus-server-position-point))))))
+
+(defun gnus-server-set-info (server info)
+  ;; Enter a select method into the virtual server alist.
+  (when (and server info)
+    (gnus-dribble-enter
+     (concat "(gnus-server-set-info \"" server "\" '"
+	     (prin1-to-string info) ")"))
+    (let* ((server (nth 1 info))
+	   (entry (assoc server gnus-server-alist)))
+      (if entry (setcdr entry info)
+	(setq gnus-server-alist
+	      (nconc gnus-server-alist (list (cons server info))))))))
+
+;;; Interactive server functions.
+
+(defun gnus-server-kill-server (server)
+  "Kill the server on the current line."
+  (interactive (list (gnus-server-server-name)))
+  (unless (gnus-server-goto-server server)
+    (if server (error "No such server: %s" server)
+      (error "No server on the current line")))
+  (unless (assoc server gnus-server-alist)
+    (error "Read-only server %s" server))
+  (gnus-dribble-enter "")
+  (let ((buffer-read-only nil))
+    (gnus-delete-line))
+  (push (assoc server gnus-server-alist) gnus-server-killed-servers)
+  (setq gnus-server-alist (delq (car gnus-server-killed-servers)
+				gnus-server-alist))
+  (gnus-server-position-point))
+
+(defun gnus-server-yank-server ()
+  "Yank the previously killed server."
+  (interactive)
+  (unless gnus-server-killed-servers
+    (error "No killed servers to be yanked"))
+  (let ((alist gnus-server-alist)
+	(server (gnus-server-server-name))
+	(killed (car gnus-server-killed-servers)))
+    (if (not server)
+	(setq gnus-server-alist (nconc gnus-server-alist (list killed)))
+      (if (string= server (caar gnus-server-alist))
+	  (push killed gnus-server-alist)
+	(while (and (cdr alist)
+		    (not (string= server (caadr alist))))
+	  (setq alist (cdr alist)))
+	(if alist
+	    (setcdr alist (cons killed (cdr alist)))
+ 	  (setq gnus-server-alist (list killed)))))
+    (gnus-server-update-server (car killed))
+    (setq gnus-server-killed-servers (cdr gnus-server-killed-servers))
+    (gnus-server-position-point)))
+
+(defun gnus-server-exit ()
+  "Return to the group buffer."
+  (interactive)
+  (run-hooks 'gnus-server-exit-hook)
+  (kill-buffer (current-buffer))
+  (gnus-configure-windows 'group t))
+
+(defun gnus-server-list-servers ()
+  "List all available servers."
+  (interactive)
+  (let ((cur (gnus-server-server-name)))
+    (gnus-server-prepare)
+    (if cur (gnus-server-goto-server cur)
+      (goto-char (point-max))
+      (forward-line -1))
+    (gnus-server-position-point)))
+
+(defun gnus-server-set-status (method status)
+  "Make METHOD have STATUS."
+  (let ((entry (assoc method gnus-opened-servers)))
+    (if entry
+	(setcar (cdr entry) status)
+      (push (list method status) gnus-opened-servers))))
+
+(defun gnus-opened-servers-remove (method)
+  "Remove METHOD from the list of opened servers."
+  (setq gnus-opened-servers (delq (assoc method gnus-opened-servers)
+				  gnus-opened-servers)))
+
+(defun gnus-server-open-server (server)
+  "Force an open of SERVER."
+  (interactive (list (gnus-server-server-name)))
+  (let ((method (gnus-server-to-method server)))
+    (unless method
+      (error "No such server: %s" server))
+    (gnus-server-set-status method 'ok)
+    (prog1
+	(or (gnus-open-server method)
+	    (progn (message "Couldn't open %s" server) nil))
+      (gnus-server-update-server server)
+      (gnus-server-position-point))))
+
+(defun gnus-server-open-all-servers ()
+  "Open all servers."
+  (interactive)
+  (let ((servers gnus-inserted-opened-servers))
+    (while servers
+      (gnus-server-open-server (car (pop servers))))))
+
+(defun gnus-server-close-server (server)
+  "Close SERVER."
+  (interactive (list (gnus-server-server-name)))
+  (let ((method (gnus-server-to-method server)))
+    (unless method
+      (error "No such server: %s" server))
+    (gnus-server-set-status method 'closed)
+    (prog1
+	(gnus-close-server method)
+      (gnus-server-update-server server)
+      (gnus-server-position-point))))
+
+(defun gnus-server-close-all-servers ()
+  "Close all servers."
+  (interactive)
+  (let ((servers gnus-inserted-opened-servers))
+    (while servers
+      (gnus-server-close-server (car (pop servers))))))
+
+(defun gnus-server-deny-server (server)
+  "Make sure SERVER will never be attempted opened."
+  (interactive (list (gnus-server-server-name)))
+  (let ((method (gnus-server-to-method server)))
+    (unless method
+      (error "No such server: %s" server))
+    (gnus-server-set-status method 'denied))
+  (gnus-server-update-server server)
+  (gnus-server-position-point)
+  t)
+
+(defun gnus-server-remove-denials ()
+  "Make all denied servers into closed servers."
+  (interactive)
+  (let ((servers gnus-opened-servers))
+    (while servers
+      (when (eq (nth 1 (car servers)) 'denied)
+	(setcar (nthcdr 1 (car servers)) 'closed))
+      (setq servers (cdr servers))))
+  (gnus-server-list-servers))
+
+(defun gnus-server-copy-server (from to)
+  (interactive
+   (list
+    (or (gnus-server-server-name)
+	(error "No server on the current line"))
+    (read-string "Copy to: ")))
+  (unless from
+    (error "No server on current line"))
+  (unless (and to (not (string= to "")))
+    (error "No name to copy to"))
+  (when (assoc to gnus-server-alist)
+    (error "%s already exists" to))
+  (unless (gnus-server-to-method from)
+    (error "%s: no such server" from))
+  (let ((to-entry (cons from (gnus-copy-sequence
+			      (gnus-server-to-method from)))))
+    (setcar to-entry to)
+    (setcar (nthcdr 2 to-entry) to)
+    (push to-entry gnus-server-killed-servers)
+    (gnus-server-yank-server)))
+
+(defun gnus-server-add-server (how where)
+  (interactive
+   (list (intern (completing-read "Server method: "
+				  gnus-valid-select-methods nil t))
+	 (read-string "Server name: ")))
+  (when (assq where gnus-server-alist)
+    (error "Server with that name already defined"))
+  (push (list where how where) gnus-server-killed-servers)
+  (gnus-server-yank-server))
+
+(defun gnus-server-goto-server (server)
+  "Jump to a server line."
+  (interactive
+   (list (completing-read "Goto server: " gnus-server-alist nil t)))
+  (let ((to (text-property-any (point-min) (point-max)
+			       'gnus-server (intern server))))
+    (when to
+      (goto-char to)
+      (gnus-server-position-point))))
+
+(defun gnus-server-edit-server (server)
+  "Edit the server on the current line."
+  (interactive (list (gnus-server-server-name)))
+  (unless server
+    (error "No server on current line"))
+  (unless (assoc server gnus-server-alist)
+    (error "This server can't be edited"))
+  (let ((info (cdr (assoc server gnus-server-alist))))
+    (gnus-close-server info)
+    (gnus-edit-form
+     info "Editing the server."
+     `(lambda (form)
+	(gnus-server-set-info ,server form)
+	(gnus-server-list-servers)
+	(gnus-server-position-point)))))
+
+(defun gnus-server-scan-server (server)
+  "Request a scan from the current server."
+  (interactive (list (gnus-server-server-name)))
+  (gnus-message 3 "Scanning %s...done" server)
+  (gnus-request-scan nil (gnus-server-to-method server))
+  (gnus-message 3 "Scanning %s...done" server))
+
+(defun gnus-server-read-server (server)
+  "Browse a server."
+  (interactive (list (gnus-server-server-name)))
+  (let ((buf (current-buffer)))
+    (prog1
+	(gnus-browse-foreign-server (gnus-server-to-method server) buf)
+      (save-excursion
+	(set-buffer buf)
+	(gnus-server-update-server (gnus-server-server-name))
+	(gnus-server-position-point)))))
+
+(defun gnus-server-pick-server (e)
+  (interactive "e")
+  (mouse-set-point e)
+  (gnus-server-read-server (gnus-server-server-name)))
+
+
+;;;
+;;; Browse Server Mode
+;;;
+
+(defvar gnus-browse-menu-hook nil
+  "*Hook run after the creation of the browse mode menu.")
+
+(defvar gnus-browse-mode-hook nil)
+(defvar gnus-browse-mode-map nil)
+(put 'gnus-browse-mode 'mode-class 'special)
+
+(unless gnus-browse-mode-map
+  (setq gnus-browse-mode-map (make-keymap))
+  (suppress-keymap gnus-browse-mode-map)
+
+  (gnus-define-keys
+   gnus-browse-mode-map
+   " " gnus-browse-read-group
+   "=" gnus-browse-select-group
+   "n" gnus-browse-next-group
+   "p" gnus-browse-prev-group
+   "\177" gnus-browse-prev-group
+   "N" gnus-browse-next-group
+   "P" gnus-browse-prev-group
+   "\M-n" gnus-browse-next-group
+   "\M-p" gnus-browse-prev-group
+   "\r" gnus-browse-select-group
+   "u" gnus-browse-unsubscribe-current-group
+   "l" gnus-browse-exit
+   "L" gnus-browse-exit
+   "q" gnus-browse-exit
+   "Q" gnus-browse-exit
+   "\C-c\C-c" gnus-browse-exit
+   "?" gnus-browse-describe-briefly
+
+   "\C-c\C-i" gnus-info-find-node
+   "\C-c\C-b" gnus-bug))
+
+(defun gnus-browse-make-menu-bar ()
+  (gnus-turn-off-edit-menu 'browse)
+  (unless (boundp 'gnus-browse-menu)
+    (easy-menu-define
+     gnus-browse-menu gnus-browse-mode-map ""
+     '("Browse"
+       ["Subscribe" gnus-browse-unsubscribe-current-group t]
+       ["Read" gnus-browse-read-group t]
+       ["Select" gnus-browse-read-group t]
+       ["Next" gnus-browse-next-group t]
+       ["Prev" gnus-browse-next-group t]
+       ["Exit" gnus-browse-exit t]))
+    (run-hooks 'gnus-browse-menu-hook)))
+
+(defvar gnus-browse-current-method nil)
+(defvar gnus-browse-return-buffer nil)
+
+(defvar gnus-browse-buffer "*Gnus Browse Server*")
+
+(defun gnus-browse-foreign-server (method &optional return-buffer)
+  "Browse the server METHOD."
+  (setq gnus-browse-current-method method)
+  (setq gnus-browse-return-buffer return-buffer)
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (let ((gnus-select-method method)
+	groups group)
+    (gnus-message 5 "Connecting to %s..." (nth 1 method))
+    (cond
+     ((not (gnus-check-server method))
+      (gnus-message
+       1 "Unable to contact server: %s" (gnus-status-message method))
+      nil)
+     ((not
+       (prog2
+	   (gnus-message 6 "Reading active file...")
+	   (gnus-request-list method)
+	 (gnus-message 6 "Reading active file...done")))
+      (gnus-message
+       1 "Couldn't request list: %s" (gnus-status-message method))
+      nil)
+     (t
+      (get-buffer-create gnus-browse-buffer)
+      (gnus-add-current-to-buffer-list)
+      (when gnus-carpal
+	(gnus-carpal-setup-buffer 'browse))
+      (gnus-configure-windows 'browse)
+      (buffer-disable-undo (current-buffer))
+      (let ((buffer-read-only nil))
+	(erase-buffer))
+      (gnus-browse-mode)
+      (setq mode-line-buffer-identification
+	    (list
+	     (format
+	      "Gnus: %%b {%s:%s}" (car method) (cadr method))))
+      (save-excursion
+	(set-buffer nntp-server-buffer)
+	(let ((cur (current-buffer)))
+	  (goto-char (point-min))
+	  (unless (string= gnus-ignored-newsgroups "")
+	    (delete-matching-lines gnus-ignored-newsgroups))
+	  (while (re-search-forward
+		  "\\(^[^ \t]+\\)[ \t]+[0-9]+[ \t]+[0-9]+" nil t)
+	    (goto-char (match-end 1))
+	    (push (cons (match-string 1)
+			(max 0 (- (1+ (read cur)) (read cur))))
+		  groups))))
+      (setq groups (sort groups
+			 (lambda (l1 l2)
+			   (string< (car l1) (car l2)))))
+      (let ((buffer-read-only nil))
+	(while groups
+	  (setq group (car groups))
+	  (insert
+	   (format "K%7d: %s\n" (cdr group) (car group)))
+	  (setq groups (cdr groups))))
+      (switch-to-buffer (current-buffer))
+      (goto-char (point-min))
+      (gnus-group-position-point)
+      (gnus-message 5 "Connecting to %s...done" (nth 1 method))
+      t))))
+
+(defun gnus-browse-mode ()
+  "Major mode for browsing a foreign server.
+
+All normal editing commands are switched off.
+
+\\<gnus-browse-mode-map>
+The only things you can do in this buffer is
+
+1) `\\[gnus-browse-unsubscribe-current-group]' to subscribe to a group.
+The group will be inserted into the group buffer upon exit from this
+buffer.
+
+2) `\\[gnus-browse-read-group]' to read a group ephemerally.
+
+3) `\\[gnus-browse-exit]' to return to the group buffer."
+  (interactive)
+  (kill-all-local-variables)
+  (when (gnus-visual-p 'browse-menu 'menu)
+    (gnus-browse-make-menu-bar))
+  (gnus-simplify-mode-line)
+  (setq major-mode 'gnus-browse-mode)
+  (setq mode-name "Browse Server")
+  (setq mode-line-process nil)
+  (use-local-map gnus-browse-mode-map)
+  (buffer-disable-undo (current-buffer))
+  (setq truncate-lines t)
+  (gnus-set-default-directory)
+  (setq buffer-read-only t)
+  (run-hooks 'gnus-browse-mode-hook))
+
+(defun gnus-browse-read-group (&optional no-article)
+  "Enter the group at the current line."
+  (interactive)
+  (let ((group (gnus-group-real-name (gnus-browse-group-name))))
+    (unless (gnus-group-read-ephemeral-group
+	     group gnus-browse-current-method nil
+	     (cons (current-buffer) 'browse))
+      (error "Couldn't enter %s" group))))
+
+(defun gnus-browse-select-group ()
+  "Select the current group."
+  (interactive)
+  (gnus-browse-read-group 'no))
+
+(defun gnus-browse-next-group (n)
+  "Go to the next group."
+  (interactive "p")
+  (prog1
+      (forward-line n)
+    (gnus-group-position-point)))
+
+(defun gnus-browse-prev-group (n)
+  "Go to the next group."
+  (interactive "p")
+  (gnus-browse-next-group (- n)))
+
+(defun gnus-browse-unsubscribe-current-group (arg)
+  "(Un)subscribe to the next ARG groups."
+  (interactive "p")
+  (when (eobp)
+    (error "No group at current line."))
+  (let ((ward (if (< arg 0) -1 1))
+	(arg (abs arg)))
+    (while (and (> arg 0)
+		(not (eobp))
+		(gnus-browse-unsubscribe-group)
+		(zerop (gnus-browse-next-group ward)))
+      (decf arg))
+    (gnus-group-position-point)
+    (when (/= 0 arg)
+      (gnus-message 7 "No more newsgroups"))
+    arg))
+
+(defun gnus-browse-group-name ()
+  (save-excursion
+    (beginning-of-line)
+    (when (re-search-forward ": \\(.*\\)$" (gnus-point-at-eol) t)
+      (gnus-group-prefixed-name
+       ;; Remove text props.
+       (format "%s" (match-string 1))
+       gnus-browse-current-method))))
+
+(defun gnus-browse-unsubscribe-group ()
+  "Toggle subscription of the current group in the browse buffer."
+  (let ((sub nil)
+	(buffer-read-only nil)
+	group)
+    (save-excursion
+      (beginning-of-line)
+      ;; If this group it killed, then we want to subscribe it.
+      (when (= (following-char) ?K)
+	(setq sub t))
+      (setq group (gnus-browse-group-name))
+      ;; Make sure the group has been properly removed before we
+      ;; subscribe to it.
+      (gnus-kill-ephemeral-group group)
+      (delete-char 1)
+      (if sub
+	  (progn
+	    (gnus-group-change-level
+	     (list t group gnus-level-default-subscribed
+		   nil nil gnus-browse-current-method)
+	     gnus-level-default-subscribed gnus-level-killed
+	     (and (car (nth 1 gnus-newsrc-alist))
+		  (gnus-gethash (car (nth 1 gnus-newsrc-alist))
+				gnus-newsrc-hashtb))
+	     t)
+	    (insert ? ))
+	(gnus-group-change-level
+	 group gnus-level-killed gnus-level-default-subscribed)
+	(insert ?K)))
+    t))
+
+(defun gnus-browse-exit ()
+  "Quit browsing and return to the group buffer."
+  (interactive)
+  (when (eq major-mode 'gnus-browse-mode)
+    (kill-buffer (current-buffer)))
+  ;; Insert the newly subscribed groups in the group buffer.
+  (save-excursion
+    (set-buffer gnus-group-buffer)
+    (gnus-group-list-groups nil))
+  (if gnus-browse-return-buffer
+      (gnus-configure-windows 'server 'force)
+    (gnus-configure-windows 'group 'force)))
+
+(defun gnus-browse-describe-briefly ()
+  "Give a one line description of the group mode commands."
+  (interactive)
+  (gnus-message 6
+		(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")))
+
+(defun gnus-server-regenerate-server ()
+  "Issue a command to the server to regenerate all its data structures."
+  (interactive)
+  (let ((server (gnus-server-server-name)))
+    (unless server
+      (error "No server on the current line"))
+    (if (not (gnus-check-backend-function
+	      'request-regenerate (car (gnus-server-to-method server))))
+	(error "This backend doesn't support regeneration")
+      (gnus-message 5 "Requesting regeneration of %s..." server)
+      (if (gnus-request-regenerate server)
+	  (gnus-message 5 "Requesting regeneration of %s...done" server)
+	(gnus-message 5 "Couldn't regenerate %s" server)))))
+
+(provide 'gnus-srvr)
+
+;;; gnus-srvr.el ends here.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-start.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,2461 @@
+;;; gnus-start.el --- startup functions for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-win)
+(require 'gnus-int)
+(require 'gnus-spec)
+(require 'gnus-range)
+(require 'gnus-util)
+(require 'message)
+
+(defcustom gnus-startup-file (nnheader-concat gnus-home-directory ".newsrc")
+  "Your `.newsrc' file.
+`.newsrc-SERVER' will be used instead if that exists."
+  :group 'gnus-start
+  :type 'file)
+
+(defcustom gnus-init-file (nnheader-concat gnus-home-directory ".gnus")
+  "Your Gnus elisp startup file.
+If a file with the .el or .elc suffixes exist, it will be read
+instead."
+  :group 'gnus-start
+  :type 'file)
+
+(defcustom gnus-site-init-file
+  (ignore-errors
+    (concat (file-name-directory
+	     (directory-file-name installation-directory))
+	    "site-lisp/gnus-init"))
+  "The site-wide Gnus elisp startup file.
+If a file with the .el or .elc suffixes exist, it will be read
+instead."
+  :group 'gnus-start
+  :type 'file)
+
+(defcustom gnus-default-subscribed-newsgroups nil
+  "This variable lists what newsgroups should be subscribed the first time Gnus is used.
+It should be a list of strings.
+If it is `t', Gnus will not do anything special the first time it is
+started; it'll just use the normal newsgroups subscription methods."
+  :group 'gnus-start
+  :type '(repeat string))
+
+(defcustom gnus-use-dribble-file t
+  "*Non-nil means that Gnus will use a dribble file to store user updates.
+If Emacs should crash without saving the .newsrc files, complete
+information can be restored from the dribble file."
+  :group 'gnus-dribble-file
+  :type 'boolean)
+
+(defcustom gnus-dribble-directory nil
+  "*The directory where dribble files will be saved.
+If this variable is nil, the directory where the .newsrc files are
+saved will be used."
+  :group 'gnus-dribble-file
+  :type '(choice directory (const nil)))
+
+(defcustom gnus-check-new-newsgroups t
+  "*Non-nil means that Gnus will run gnus-find-new-newsgroups at startup.
+This normally finds new newsgroups by comparing the active groups the
+servers have already reported with those Gnus already knows, either alive
+or killed.
+
+When any of the following are true, gnus-find-new-newsgroups will instead
+ask the servers (primary, secondary, and archive servers) to list new
+groups since the last time it checked:
+  1. This variable is `ask-server'.
+  2. This variable is a list of select methods (see below).
+  3. `gnus-read-active-file' is nil or `some'.
+  4. A prefix argument is given to gnus-find-new-newsgroups interactively.
+
+Thus, if this variable is `ask-server' or a list of select methods or
+`gnus-read-active-file' is nil or `some', then the killed list is no
+longer necessary, so you could safely set `gnus-save-killed-list' to nil.
+
+This variable can be a list of select methods which Gnus will query with
+the `ask-server' method in addition to the primary, secondary, and archive
+servers.
+
+Eg.
+  (setq gnus-check-new-newsgroups
+	'((nntp \"some.server\") (nntp \"other.server\")))
+
+If this variable is nil, then you have to tell Gnus explicitly to
+check for new newsgroups with \\<gnus-group-mode-map>\\[gnus-find-new-newsgroups]."
+  :group 'gnus-start
+  :type '(choice (const :tag "no" nil)
+		 (const :tag "by brute force" t)
+		 (const :tag "ask servers" ask-server)
+		 (repeat :menu-tag "ask additional servers"
+			 :tag "ask additional servers"
+			 :value ((nntp ""))
+			 (sexp :format "%v"))))
+
+(defcustom gnus-check-bogus-newsgroups nil
+  "*Non-nil means that Gnus will check and remove bogus newsgroup at startup.
+If this variable is nil, then you have to tell Gnus explicitly to
+check for bogus newsgroups with \\<gnus-group-mode-map>\\[gnus-group-check-bogus-groups]."
+  :group 'gnus-start-server
+  :type 'boolean)
+
+(defcustom gnus-read-active-file t
+  "*Non-nil means that Gnus will read the entire active file at startup.
+If this variable is nil, Gnus will only know about the groups in your
+`.newsrc' file.
+
+If this variable is `some', Gnus will try to only read the relevant
+parts of the active file from the server.  Not all servers support
+this, and it might be quite slow with other servers, but this should
+generally be faster than both the t and nil value.
+
+If you set this variable to nil or `some', you probably still want to
+be told about new newsgroups that arrive.  To do that, set
+`gnus-check-new-newsgroups' to `ask-server'.  This may not work
+properly with all servers."
+  :group 'gnus-start-server
+  :type '(choice (const nil)
+		 (const some)
+		 (const t)))
+
+(defcustom gnus-level-subscribed 5
+  "*Groups with levels less than or equal to this variable are subscribed."
+  :group 'gnus-group-levels
+  :type 'integer)
+
+(defcustom gnus-level-unsubscribed 7
+  "*Groups with levels less than or equal to this variable are unsubscribed.
+Groups with levels less than `gnus-level-subscribed', which should be
+less than this variable, are subscribed."
+  :group 'gnus-group-levels
+  :type 'integer)
+
+(defcustom gnus-level-zombie 8
+  "*Groups with this level are zombie groups."
+  :group 'gnus-group-levels
+  :type 'integer)
+
+(defcustom gnus-level-killed 9
+  "*Groups with this level are killed."
+  :group 'gnus-group-levels
+  :type 'integer)
+
+(defcustom gnus-level-default-subscribed 3
+  "*New subscribed groups will be subscribed at this level."
+  :group 'gnus-group-levels
+  :type 'integer)
+
+(defcustom gnus-level-default-unsubscribed 6
+  "*New unsubscribed groups will be unsubscribed at this level."
+  :group 'gnus-group-levels
+  :type 'integer)
+
+(defcustom gnus-activate-level (1+ gnus-level-subscribed)
+  "*Groups higher than this level won't be activated on startup.
+Setting this variable to something low might save lots of time when
+you have many groups that you aren't interested in."
+  :group 'gnus-group-levels
+  :type 'integer)
+
+(defcustom gnus-activate-foreign-newsgroups 4
+  "*If nil, Gnus will not check foreign newsgroups at startup.
+If it is non-nil, it should be a number between one and nine.  Foreign
+newsgroups that have a level lower or equal to this number will be
+activated on startup.  For instance, if you want to active all
+subscribed newsgroups, but not the rest, you'd set this variable to
+`gnus-level-subscribed'.
+
+If you subscribe to lots of newsgroups from different servers, startup
+might take a while.  By setting this variable to nil, you'll save time,
+but you won't be told how many unread articles there are in the
+groups."
+  :group 'gnus-group-levels
+  :type 'integer)
+
+(defcustom gnus-save-newsrc-file t
+  "*Non-nil means that Gnus will save the `.newsrc' file.
+Gnus always saves its own startup file, which is called
+\".newsrc.eld\".  The file called \".newsrc\" is in a format that can
+be readily understood by other newsreaders.  If you don't plan on
+using other newsreaders, set this variable to nil to save some time on
+exit."
+  :group 'gnus-newsrc
+  :type 'boolean)
+
+(defcustom gnus-save-killed-list t
+  "*If non-nil, save the list of killed groups to the startup file.
+If you set this variable to nil, you'll save both time (when starting
+and quitting) and space (both memory and disk), but it will also mean
+that Gnus has no record of which groups are new and which are old, so
+the automatic new newsgroups subscription methods become meaningless.
+
+You should always set `gnus-check-new-newsgroups' to `ask-server' or
+nil if you set this variable to nil.
+
+This variable can also be a regexp.  In that case, all groups that do
+not match this regexp will be removed before saving the list."
+  :group 'gnus-newsrc
+  :type 'boolean)
+
+(defcustom gnus-ignored-newsgroups
+  (purecopy (mapconcat 'identity
+		       '("^to\\."	; not "real" groups
+			 "^[0-9. \t]+ " ; all digits in name
+			 "[][\"#'()]"	; bogus characters
+			 )
+		       "\\|"))
+  "A regexp to match uninteresting newsgroups in the active file.
+Any lines in the active file matching this regular expression are
+removed from the newsgroup list before anything else is done to it,
+thus making them effectively non-existent."
+  :group 'gnus-group-new
+  :type 'regexp)
+
+(defcustom gnus-subscribe-newsgroup-method 'gnus-subscribe-zombies
+  "*Function called with a group name when new group is detected.
+A few pre-made functions are supplied: `gnus-subscribe-randomly'
+inserts new groups at the beginning of the list of groups;
+`gnus-subscribe-alphabetically' inserts new groups in strict
+alphabetic order; `gnus-subscribe-hierarchically' inserts new groups
+in hierarchical newsgroup order; `gnus-subscribe-interactively' asks
+for your decision; `gnus-subscribe-killed' kills all new groups;
+`gnus-subscribe-zombies' will make all new groups into zombies."
+  :group 'gnus-group-new
+  :type '(radio (function-item gnus-subscribe-randomly)
+		(function-item gnus-subscribe-alphabetically)
+		(function-item gnus-subscribe-hierarchically)
+		(function-item gnus-subscribe-interactively)
+		(function-item gnus-subscribe-killed)
+		(function-item gnus-subscribe-zombies)
+		function))
+
+;; Suggested by a bug report by Hallvard B Furuseth.
+;; <h.b.furuseth@usit.uio.no>.
+(defcustom gnus-subscribe-options-newsgroup-method
+  'gnus-subscribe-alphabetically
+  "*This function is called to subscribe newsgroups mentioned on \"options -n\" lines.
+If, for instance, you want to subscribe to all newsgroups in the
+\"no\" and \"alt\" hierarchies, you'd put the following in your
+.newsrc file:
+
+options -n no.all alt.all
+
+Gnus will the subscribe all new newsgroups in these hierarchies with
+the subscription method in this variable."
+  :group 'gnus-group-new
+  :type '(radio (function-item gnus-subscribe-randomly)
+		(function-item gnus-subscribe-alphabetically)
+		(function-item gnus-subscribe-hierarchically)
+		(function-item gnus-subscribe-interactively)
+		(function-item gnus-subscribe-killed)
+		(function-item gnus-subscribe-zombies)
+		function))
+
+(defcustom gnus-subscribe-hierarchical-interactive nil
+  "*If non-nil, Gnus will offer to subscribe hierarchically.
+When a new hierarchy appears, Gnus will ask the user:
+
+'alt.binaries': Do you want to subscribe to this hierarchy? ([d]ys):
+
+If the user pressed `d', Gnus will descend the hierarchy, `y' will
+subscribe to all newsgroups in the hierarchy and `s' will skip this
+hierarchy in its entirety."
+  :group 'gnus-group-new
+  :type 'boolean)
+
+(defcustom gnus-auto-subscribed-groups
+  "^nnml\\|^nnfolder\\|^nnmbox\\|^nnmh\\|^nnbabyl"
+  "*All new groups that match this regexp will be subscribed automatically.
+Note that this variable only deals with new groups.  It has no effect
+whatsoever on old groups.
+
+New groups that match this regexp will not be handled by
+`gnus-subscribe-newsgroup-method'.  Instead, they will
+be subscribed using `gnus-subscribe-options-newsgroup-method'."
+  :group 'gnus-group-new
+  :type 'regexp)
+
+(defcustom gnus-options-subscribe nil
+  "*All new groups matching this regexp will be subscribed unconditionally.
+Note that this variable deals only with new newsgroups.	 This variable
+does not affect old newsgroups.
+
+New groups that match this regexp will not be handled by
+`gnus-subscribe-newsgroup-method'.  Instead, they will
+be subscribed using `gnus-subscribe-options-newsgroup-method'."
+  :group 'gnus-group-new
+  :type '(choice regexp
+		 (const :tag "none" nil)))
+
+(defcustom gnus-options-not-subscribe nil
+  "*All new groups matching this regexp will be ignored.
+Note that this variable deals only with new newsgroups.	 This variable
+does not affect old (already subscribed) newsgroups."
+  :group 'gnus-group-new
+  :type '(choice regexp
+		 (const :tag "none" nil)))
+
+(defcustom gnus-modtime-botch nil
+  "*Non-nil means .newsrc should be deleted prior to save.
+Its use is due to the bogus appearance that .newsrc was modified on
+disc."
+  :group 'gnus-newsrc
+  :type 'boolean)
+
+(defcustom gnus-check-bogus-groups-hook nil
+  "A hook run after removing bogus groups."
+  :group 'gnus-start-server
+  :type 'hook)
+
+(defcustom gnus-startup-hook nil
+  "A hook called at startup.
+This hook is called after Gnus is connected to the NNTP server."
+  :group 'gnus-start
+  :type 'hook)
+
+(defcustom gnus-started-hook nil
+  "A hook called as the last thing after startup."
+  :group 'gnus-start
+  :type 'hook)
+
+(defcustom gnus-get-new-news-hook nil
+  "A hook run just before Gnus checks for new news."
+  :group 'gnus-group-new
+  :type 'hook)
+
+(defcustom gnus-after-getting-new-news-hook
+  (when (gnus-boundp 'display-time-timer)
+    '(display-time-event-handler))
+  "A hook run after Gnus checks for new news."
+  :group 'gnus-group-new
+  :type 'hook)
+
+(defcustom gnus-save-newsrc-hook nil
+  "A hook called before saving any of the newsrc files."
+  :group 'gnus-newsrc
+  :type 'hook)
+
+(defcustom gnus-save-quick-newsrc-hook nil
+  "A hook called just before saving the quick newsrc file.
+Can be used to turn version control on or off."
+  :group 'gnus-newsrc
+  :type 'hook)
+
+(defcustom gnus-save-standard-newsrc-hook nil
+  "A hook called just before saving the standard newsrc file.
+Can be used to turn version control on or off."
+  :group 'gnus-newsrc
+  :type 'hook)
+
+;;; Internal variables
+
+(defvar gnus-newsrc-file-version nil)
+(defvar gnus-override-subscribe-method nil)
+(defvar gnus-dribble-buffer nil)
+(defvar gnus-newsrc-options nil
+  "Options line in the .newsrc file.")
+
+(defvar gnus-newsrc-options-n nil
+  "List of regexps representing groups to be subscribed/ignored unconditionally.")
+
+(defvar gnus-newsrc-last-checked-date nil
+  "Date Gnus last asked server for new newsgroups.")
+
+(defvar gnus-current-startup-file nil
+  "Startup file for the current host.")
+
+;; Byte-compiler warning.
+(defvar gnus-group-line-format)
+
+;; Suggested by Brian Edmonds <edmonds@cs.ubc.ca>.
+(defvar gnus-init-inhibit nil)
+(defun gnus-read-init-file (&optional inhibit-next)
+  ;; Don't load .gnus if the -q option was used.
+  (when init-file-user
+    (if gnus-init-inhibit
+	(setq gnus-init-inhibit nil)
+      (setq gnus-init-inhibit inhibit-next)
+      (let ((files (list gnus-site-init-file gnus-init-file))
+	    file)
+	(while files
+	  (and (setq file (pop files))
+	       (or (and (file-exists-p file)
+			;; Don't try to load a directory.
+			(not (file-directory-p file)))
+		   (file-exists-p (concat file ".el"))
+		   (file-exists-p (concat file ".elc")))
+	       (condition-case var
+		   (load file nil t)
+		 (error
+		  (error "Error in %s: %s" file var)))))))))
+
+;; For subscribing new newsgroup
+
+(defun gnus-subscribe-hierarchical-interactive (groups)
+  (let ((groups (sort groups 'string<))
+	prefixes prefix start ans group starts)
+    (while groups
+      (setq prefixes (list "^"))
+      (while (and groups prefixes)
+	(while (not (string-match (car prefixes) (car groups)))
+	  (setq prefixes (cdr prefixes)))
+	(setq prefix (car prefixes))
+	(setq start (1- (length prefix)))
+	(if (and (string-match "[^\\.]\\." (car groups) start)
+		 (cdr groups)
+		 (setq prefix
+		       (concat "^" (substring (car groups) 0 (match-end 0))))
+		 (string-match prefix (cadr groups)))
+	    (progn
+	      (push prefix prefixes)
+	      (message "Descend hierarchy %s? ([y]nsq): "
+		       (substring prefix 1 (1- (length prefix))))
+	      (while (not (memq (setq ans (read-char)) '(?y ?\n ?\r ?n ?s ?q)))
+		(ding)
+		(message "Descend hierarchy %s? ([y]nsq): "
+			 (substring prefix 1 (1- (length prefix)))))
+	      (cond ((= ans ?n)
+		     (while (and groups
+				 (string-match prefix
+					       (setq group (car groups))))
+		       (push group gnus-killed-list)
+		       (gnus-sethash group group gnus-killed-hashtb)
+		       (setq groups (cdr groups)))
+		     (setq starts (cdr starts)))
+		    ((= ans ?s)
+		     (while (and groups
+				 (string-match prefix
+					       (setq group (car groups))))
+		       (gnus-sethash group group gnus-killed-hashtb)
+		       (gnus-subscribe-alphabetically (car groups))
+		       (setq groups (cdr groups)))
+		     (setq starts (cdr starts)))
+		    ((= ans ?q)
+		     (while groups
+		       (setq group (car groups))
+		       (push group gnus-killed-list)
+		       (gnus-sethash group group gnus-killed-hashtb)
+		       (setq groups (cdr groups))))
+		    (t nil)))
+	  (message "Subscribe %s? ([n]yq)" (car groups))
+	  (while (not (memq (setq ans (read-char)) '(?y ?\n ?\r ?q ?n)))
+	    (ding)
+	    (message "Subscribe %s? ([n]yq)" (car groups)))
+	  (setq group (car groups))
+	  (cond ((= ans ?y)
+		 (gnus-subscribe-alphabetically (car groups))
+		 (gnus-sethash group group gnus-killed-hashtb))
+		((= ans ?q)
+		 (while groups
+		   (setq group (car groups))
+		   (push group gnus-killed-list)
+		   (gnus-sethash group group gnus-killed-hashtb)
+		   (setq groups (cdr groups))))
+		(t
+		 (push group gnus-killed-list)
+		 (gnus-sethash group group gnus-killed-hashtb)))
+	  (setq groups (cdr groups)))))))
+
+(defun gnus-subscribe-randomly (newsgroup)
+  "Subscribe new NEWSGROUP by making it the first newsgroup."
+  (gnus-subscribe-newsgroup newsgroup))
+
+(defun gnus-subscribe-alphabetically (newgroup)
+  "Subscribe new NEWSGROUP and insert it in alphabetical order."
+  (let ((groups (cdr gnus-newsrc-alist))
+	before)
+    (while (and (not before) groups)
+      (if (string< newgroup (caar groups))
+	  (setq before (caar groups))
+	(setq groups (cdr groups))))
+    (gnus-subscribe-newsgroup newgroup before)))
+
+(defun gnus-subscribe-hierarchically (newgroup)
+  "Subscribe new NEWSGROUP and insert it in hierarchical newsgroup order."
+  ;; Basic ideas by mike-w@cs.aukuni.ac.nz (Mike Williams)
+  (save-excursion
+    (set-buffer (nnheader-find-file-noselect gnus-current-startup-file))
+    (let ((groupkey newgroup)
+	  before)
+      (while (and (not before) groupkey)
+	(goto-char (point-min))
+	(let ((groupkey-re
+	       (concat "^\\(" (regexp-quote groupkey) ".*\\)[!:]")))
+	  (while (and (re-search-forward groupkey-re nil t)
+		      (progn
+			(setq before (match-string 1))
+			(string< before newgroup)))))
+	;; Remove tail of newsgroup name (eg. a.b.c -> a.b)
+	(setq groupkey
+	      (when (string-match "^\\(.*\\)\\.[^.]+$" groupkey)
+		(substring groupkey (match-beginning 1) (match-end 1)))))
+      (gnus-subscribe-newsgroup newgroup before))
+    (kill-buffer (current-buffer))))
+
+(defun gnus-subscribe-interactively (group)
+  "Subscribe the new GROUP interactively.
+It is inserted in hierarchical newsgroup order if subscribed.  If not,
+it is killed."
+  (if (gnus-y-or-n-p (format "Subscribe new newsgroup: %s " group))
+      (gnus-subscribe-hierarchically group)
+    (push group gnus-killed-list)))
+
+(defun gnus-subscribe-zombies (group)
+  "Make the new GROUP into a zombie group."
+  (push group gnus-zombie-list))
+
+(defun gnus-subscribe-killed (group)
+  "Make the new GROUP a killed group."
+  (push group gnus-killed-list))
+
+(defun gnus-subscribe-newsgroup (newsgroup &optional next)
+  "Subscribe new NEWSGROUP.
+If NEXT is non-nil, it is inserted before NEXT.	 Otherwise it is made
+the first newsgroup."
+  (save-excursion
+    (goto-char (point-min))
+    ;; We subscribe the group by changing its level to `subscribed'.
+    (gnus-group-change-level
+     newsgroup gnus-level-default-subscribed
+     gnus-level-killed (gnus-gethash (or next "dummy.group")
+				     gnus-newsrc-hashtb))
+    (gnus-message 5 "Subscribe newsgroup: %s" newsgroup)))
+
+(defun gnus-read-active-file-p ()
+  "Say whether the active file has been read from `gnus-select-method'."
+  (memq gnus-select-method gnus-have-read-active-file))
+
+;;; General various misc type functions.
+
+;; Silence byte-compiler.
+(defvar gnus-current-headers)
+(defvar gnus-thread-indent-array)
+(defvar gnus-newsgroup-name)
+(defvar gnus-newsgroup-headers)
+(defvar gnus-group-list-mode)
+(defvar gnus-group-mark-positions)
+(defvar gnus-newsgroup-data)
+(defvar gnus-newsgroup-unreads)
+(defvar nnoo-state-alist)
+(defvar gnus-current-select-method)
+(defun gnus-clear-system ()
+  "Clear all variables and buffers."
+  ;; Clear Gnus variables.
+  (let ((variables gnus-variable-list))
+    (while variables
+      (set (car variables) nil)
+      (setq variables (cdr variables))))
+  ;; Clear other internal variables.
+  (setq gnus-list-of-killed-groups nil
+	gnus-have-read-active-file nil
+	gnus-newsrc-alist nil
+	gnus-newsrc-hashtb nil
+	gnus-killed-list nil
+	gnus-zombie-list nil
+	gnus-killed-hashtb nil
+	gnus-active-hashtb nil
+	gnus-moderated-hashtb nil
+	gnus-description-hashtb nil
+	gnus-current-headers nil
+	gnus-thread-indent-array nil
+	gnus-newsgroup-headers nil
+	gnus-newsgroup-name nil
+	gnus-server-alist nil
+	gnus-group-list-mode nil
+	gnus-opened-servers nil
+	gnus-group-mark-positions nil
+	gnus-newsgroup-data nil
+	gnus-newsgroup-unreads nil
+	nnoo-state-alist nil
+	gnus-current-select-method nil)
+  (gnus-shutdown 'gnus)
+  ;; Kill the startup file.
+  (and gnus-current-startup-file
+       (get-file-buffer gnus-current-startup-file)
+       (kill-buffer (get-file-buffer gnus-current-startup-file)))
+  ;; Clear the dribble buffer.
+  (gnus-dribble-clear)
+  ;; Kill global KILL file buffer.
+  (when (get-file-buffer (gnus-newsgroup-kill-file nil))
+    (kill-buffer (get-file-buffer (gnus-newsgroup-kill-file nil))))
+  (gnus-kill-buffer nntp-server-buffer)
+  ;; Kill Gnus buffers.
+  (while gnus-buffer-list
+    (gnus-kill-buffer (pop gnus-buffer-list)))
+  ;; Remove Gnus frames.
+  (gnus-kill-gnus-frames))
+
+(defun gnus-no-server-1 (&optional arg slave)
+  "Read network news.
+If ARG is a positive number, Gnus will use that as the
+startup level.	If ARG is nil, Gnus will be started at level 2.
+If ARG is non-nil and not a positive number, Gnus will
+prompt the user for the name of an NNTP server to use.
+As opposed to `gnus', this command will not connect to the local server."
+  (interactive "P")
+  (let ((val (or arg (1- gnus-level-default-subscribed))))
+    (gnus val t slave)
+    (make-local-variable 'gnus-group-use-permanent-levels)
+    (setq gnus-group-use-permanent-levels val)))
+
+(defun gnus-1 (&optional arg dont-connect slave)
+  "Read network news.
+If ARG is non-nil and a positive number, Gnus will use that as the
+startup level.	If ARG is non-nil and not a positive number, Gnus will
+prompt the user for the name of an NNTP server to use."
+  (interactive "P")
+
+  (if (and (get-buffer gnus-group-buffer)
+	   (save-excursion
+	     (set-buffer gnus-group-buffer)
+	     (eq major-mode 'gnus-group-mode)))
+      (progn
+	(switch-to-buffer gnus-group-buffer)
+	(gnus-group-get-new-news
+	 (and (numberp arg)
+	      (> arg 0)
+	      (max (car gnus-group-list-mode) arg))))
+
+    (gnus-splash)
+    (gnus-clear-system)
+    (nnheader-init-server-buffer)
+    (gnus-read-init-file)
+    (setq gnus-slave slave)
+
+    (when (and (string-match "XEmacs" (emacs-version))
+	       gnus-simple-splash)
+      (setq gnus-simple-splash nil)
+      (gnus-xmas-splash))
+
+    (let ((level (and (numberp arg) (> arg 0) arg))
+	  did-connect)
+      (unwind-protect
+	  (progn
+	    (unless dont-connect
+	      (setq did-connect
+		    (gnus-start-news-server (and arg (not level))))))
+	(if (and (not dont-connect)
+		 (not did-connect))
+	    (gnus-group-quit)
+	  (run-hooks 'gnus-startup-hook)
+	  ;; NNTP server is successfully open.
+
+	  ;; Find the current startup file name.
+	  (setq gnus-current-startup-file
+		(gnus-make-newsrc-file gnus-startup-file))
+
+	  ;; Read the dribble file.
+	  (when (or gnus-slave gnus-use-dribble-file)
+	    (gnus-dribble-read-file))
+
+	  ;; Allow using GroupLens predictions.
+	  (when gnus-use-grouplens
+	    (bbb-login)
+	    (add-hook 'gnus-summary-mode-hook 'gnus-grouplens-mode))
+
+	  ;; Do the actual startup.
+	  (gnus-setup-news nil level dont-connect)
+	  ;; Generate the group buffer.
+	  (gnus-group-list-groups level)
+	  (gnus-group-first-unread-group)
+	  (gnus-configure-windows 'group)
+	  (gnus-group-set-mode-line)
+	  (run-hooks 'gnus-started-hook))))))
+
+;;;###autoload
+(defun gnus-unload ()
+  "Unload all Gnus features."
+  (interactive)
+  (unless (boundp 'load-history)
+    (error "Sorry, `gnus-unload' is not implemented in this Emacs version."))
+  (let ((history load-history)
+	feature)
+    (while history
+      (and (string-match "^\\(gnus\\|nn\\)" (caar history))
+	   (setq feature (cdr (assq 'provide (car history))))
+	   (unload-feature feature 'force))
+      (setq history (cdr history)))))
+
+
+;;;
+;;; Dribble file
+;;;
+
+(defvar gnus-dribble-ignore nil)
+(defvar gnus-dribble-eval-file nil)
+
+(defun gnus-dribble-file-name ()
+  "Return the dribble file for the current .newsrc."
+  (concat
+   (if gnus-dribble-directory
+       (concat (file-name-as-directory gnus-dribble-directory)
+	       (file-name-nondirectory gnus-current-startup-file))
+     gnus-current-startup-file)
+   "-dribble"))
+
+(defun gnus-dribble-enter (string)
+  "Enter STRING into the dribble buffer."
+  (when (and (not gnus-dribble-ignore)
+	     gnus-dribble-buffer
+	     (buffer-name gnus-dribble-buffer))
+    (let ((obuf (current-buffer)))
+      (set-buffer gnus-dribble-buffer)
+      (goto-char (point-max))
+      (insert string "\n")
+      (set-window-point (get-buffer-window (current-buffer)) (point-max))
+      (bury-buffer gnus-dribble-buffer)
+      (set-buffer obuf))))
+
+(defun gnus-dribble-touch ()
+  "Touch the dribble buffer."
+  (gnus-dribble-enter ""))
+
+(defun gnus-dribble-read-file ()
+  "Read the dribble file from disk."
+  (let ((dribble-file (gnus-dribble-file-name)))
+    (save-excursion
+      (set-buffer (setq gnus-dribble-buffer
+			(get-buffer-create
+			 (file-name-nondirectory dribble-file))))
+      (gnus-add-current-to-buffer-list)
+      (erase-buffer)
+      (setq buffer-file-name dribble-file)
+      (auto-save-mode t)
+      (buffer-disable-undo (current-buffer))
+      (bury-buffer (current-buffer))
+      (set-buffer-modified-p nil)
+      (let ((auto (make-auto-save-file-name))
+	    (gnus-dribble-ignore t)
+	    modes)
+	(when (or (file-exists-p auto) (file-exists-p dribble-file))
+	  ;; Load whichever file is newest -- the auto save file
+	  ;; or the "real" file.
+	  (if (file-newer-than-file-p auto dribble-file)
+	      (nnheader-insert-file-contents auto)
+	    (nnheader-insert-file-contents dribble-file))
+	  (unless (zerop (buffer-size))
+	    (set-buffer-modified-p t))
+	  ;; Set the file modes to reflect the .newsrc file modes.
+	  (save-buffer)
+	  (when (and (file-exists-p gnus-current-startup-file)
+		     (setq modes (file-modes gnus-current-startup-file)))
+	    (set-file-modes dribble-file modes))
+	  ;; Possibly eval the file later.
+	  (when (gnus-y-or-n-p
+		 "Gnus auto-save file exists.  Do you want to read it? ")
+	    (setq gnus-dribble-eval-file t)))))))
+
+(defun gnus-dribble-eval-file ()
+  (when gnus-dribble-eval-file
+    (setq gnus-dribble-eval-file nil)
+    (save-excursion
+      (let ((gnus-dribble-ignore t))
+	(set-buffer gnus-dribble-buffer)
+	(eval-buffer (current-buffer))))))
+
+(defun gnus-dribble-delete-file ()
+  (when (file-exists-p (gnus-dribble-file-name))
+    (delete-file (gnus-dribble-file-name)))
+  (when gnus-dribble-buffer
+    (save-excursion
+      (set-buffer gnus-dribble-buffer)
+      (let ((auto (make-auto-save-file-name)))
+	(when (file-exists-p auto)
+	  (delete-file auto))
+	(erase-buffer)
+	(set-buffer-modified-p nil)))))
+
+(defun gnus-dribble-save ()
+  (when (and gnus-dribble-buffer
+	     (buffer-name gnus-dribble-buffer))
+    (save-excursion
+      (set-buffer gnus-dribble-buffer)
+      (save-buffer))))
+
+(defun gnus-dribble-clear ()
+  (when (gnus-buffer-exists-p gnus-dribble-buffer)
+    (save-excursion
+      (set-buffer gnus-dribble-buffer)
+      (erase-buffer)
+      (set-buffer-modified-p nil)
+      (setq buffer-saved-size (buffer-size)))))
+
+
+;;;
+;;; Active & Newsrc File Handling
+;;;
+
+(defun gnus-setup-news (&optional rawfile level dont-connect)
+  "Setup news information.
+If RAWFILE is non-nil, the .newsrc file will also be read.
+If LEVEL is non-nil, the news will be set up at level LEVEL."
+  (let ((init (not (and gnus-newsrc-alist gnus-active-hashtb (not rawfile)))))
+
+    (when init
+      ;; Clear some variables to re-initialize news information.
+      (setq gnus-newsrc-alist nil
+	    gnus-active-hashtb nil)
+      ;; Read the newsrc file and create `gnus-newsrc-hashtb'.
+      (gnus-read-newsrc-file rawfile))
+
+    (when (and (not (assoc "archive" gnus-server-alist))
+	       (gnus-archive-server-wanted-p))
+      (push (cons "archive" gnus-message-archive-method)
+	    gnus-server-alist))
+
+    ;; If we don't read the complete active file, we fill in the
+    ;; hashtb here.
+    (when (or (null gnus-read-active-file)
+	      (eq gnus-read-active-file 'some))
+      (gnus-update-active-hashtb-from-killed))
+
+    ;; Read the active file and create `gnus-active-hashtb'.
+    ;; If `gnus-read-active-file' is nil, then we just create an empty
+    ;; hash table.  The partial filling out of the hash table will be
+    ;; done in `gnus-get-unread-articles'.
+    (and gnus-read-active-file
+	 (not level)
+	 (gnus-read-active-file))
+
+    (unless gnus-active-hashtb
+      (setq gnus-active-hashtb (gnus-make-hashtable 4096)))
+
+    ;; Initialize the cache.
+    (when gnus-use-cache
+      (gnus-cache-open))
+
+    ;; Possibly eval the dribble file.
+    (and init
+	 (or gnus-use-dribble-file gnus-slave)
+	 (gnus-dribble-eval-file))
+
+    ;; Slave Gnusii should then clear the dribble buffer.
+    (when (and init gnus-slave)
+      (gnus-dribble-clear))
+
+    (gnus-update-format-specifications)
+
+    ;; See whether we need to read the description file.
+    (when (and (boundp 'gnus-group-line-format)
+	       (string-match "%[-,0-9]*D" gnus-group-line-format)
+	       (not gnus-description-hashtb)
+	       (not dont-connect)
+	       gnus-read-active-file)
+      (gnus-read-all-descriptions-files))
+
+    ;; Find new newsgroups and treat them.
+    (when (and init gnus-check-new-newsgroups (not level)
+	       (gnus-check-server gnus-select-method)
+	       (not gnus-slave))
+      (gnus-find-new-newsgroups))
+
+    ;; We might read in new NoCeM messages here.
+    (when (and gnus-use-nocem
+	       (not level)
+	       (not dont-connect))
+      (gnus-nocem-scan-groups))
+
+    ;; Read any slave files.
+    (gnus-master-read-slave-newsrc)
+
+    ;; Find the number of unread articles in each non-dead group.
+    (let ((gnus-read-active-file (and (not level) gnus-read-active-file)))
+      (gnus-get-unread-articles level))
+
+    (when (and init gnus-check-bogus-newsgroups
+	       gnus-read-active-file (not level)
+	       (gnus-server-opened gnus-select-method))
+      (gnus-check-bogus-newsgroups))))
+
+(defun gnus-find-new-newsgroups (&optional arg)
+  "Search for new newsgroups and add them.
+Each new newsgroup will be treated with `gnus-subscribe-newsgroup-method.'
+The `-n' option line from .newsrc is respected.
+If ARG (the prefix), use the `ask-server' method to query
+the server for new groups."
+  (interactive "P")
+  (let ((check (if (or (and arg (not (listp gnus-check-new-newsgroups)))
+		       (null gnus-read-active-file)
+		       (eq gnus-read-active-file 'some))
+		   'ask-server gnus-check-new-newsgroups)))
+    (unless (gnus-check-first-time-used)
+      (if (or (consp check)
+	      (eq check 'ask-server))
+	  ;; Ask the server for new groups.
+	  (gnus-ask-server-for-new-groups)
+	;; Go through the active hashtb and look for new groups.
+	(let ((groups 0)
+	      group new-newsgroups)
+	  (gnus-message 5 "Looking for new newsgroups...")
+	  (unless gnus-have-read-active-file
+	    (gnus-read-active-file))
+	  (setq gnus-newsrc-last-checked-date (current-time-string))
+	  (unless gnus-killed-hashtb
+	    (gnus-make-hashtable-from-killed))
+	  ;; Go though every newsgroup in `gnus-active-hashtb' and compare
+	  ;; with `gnus-newsrc-hashtb' and `gnus-killed-hashtb'.
+	  (mapatoms
+	   (lambda (sym)
+	     (if (or (null (setq group (symbol-name sym)))
+		     (not (boundp sym))
+		     (null (symbol-value sym))
+		     (gnus-gethash group gnus-killed-hashtb)
+		     (gnus-gethash group gnus-newsrc-hashtb))
+		 ()
+	       (let ((do-sub (gnus-matches-options-n group)))
+		 (cond
+		  ((eq do-sub 'subscribe)
+		   (setq groups (1+ groups))
+		   (gnus-sethash group group gnus-killed-hashtb)
+		   (funcall gnus-subscribe-options-newsgroup-method group))
+		  ((eq do-sub 'ignore)
+		   nil)
+		  (t
+		   (setq groups (1+ groups))
+		   (gnus-sethash group group gnus-killed-hashtb)
+		   (if gnus-subscribe-hierarchical-interactive
+		       (push group new-newsgroups)
+		     (funcall gnus-subscribe-newsgroup-method group)))))))
+	   gnus-active-hashtb)
+	  (when new-newsgroups
+	    (gnus-subscribe-hierarchical-interactive new-newsgroups))
+	  (if (> groups 0)
+	      (gnus-message 5 "%d new newsgroup%s arrived."
+			    groups (if (> groups 1) "s have" " has"))
+	    (gnus-message 5 "No new newsgroups.")))))))
+
+(defun gnus-matches-options-n (group)
+  ;; Returns `subscribe' if the group is to be unconditionally
+  ;; subscribed, `ignore' if it is to be ignored, and nil if there is
+  ;; no match for the group.
+
+  ;; First we check the two user variables.
+  (cond
+   ((and gnus-options-subscribe
+	 (string-match gnus-options-subscribe group))
+    'subscribe)
+   ((and gnus-auto-subscribed-groups
+	 (string-match gnus-auto-subscribed-groups group))
+    'subscribe)
+   ((and gnus-options-not-subscribe
+	 (string-match gnus-options-not-subscribe group))
+    'ignore)
+   ;; Then we go through the list that was retrieved from the .newsrc
+   ;; file.  This list has elements on the form
+   ;; `(REGEXP . {ignore,subscribe})'.  The first match found (the list
+   ;; is in the reverse order of the options line) is returned.
+   (t
+    (let ((regs gnus-newsrc-options-n))
+      (while (and regs
+		  (not (string-match (caar regs) group)))
+	(setq regs (cdr regs)))
+      (and regs (cdar regs))))))
+
+(defun gnus-ask-server-for-new-groups ()
+  (let* ((date (or gnus-newsrc-last-checked-date (current-time-string)))
+	 (methods (cons gnus-select-method
+			(nconc
+			 (when (gnus-archive-server-wanted-p)
+			   (list "archive"))
+			 (append
+			  (and (consp gnus-check-new-newsgroups)
+			       gnus-check-new-newsgroups)
+			  gnus-secondary-select-methods))))
+	 (groups 0)
+	 (new-date (current-time-string))
+	 group new-newsgroups got-new method hashtb
+	 gnus-override-subscribe-method)
+    ;; Go through both primary and secondary select methods and
+    ;; request new newsgroups.
+    (while (setq method (gnus-server-get-method nil (pop methods)))
+      (setq new-newsgroups nil)
+      (setq gnus-override-subscribe-method method)
+      (when (and (gnus-check-server method)
+		 (gnus-request-newgroups date method))
+	(save-excursion
+	  (setq got-new t)
+	  (setq hashtb (gnus-make-hashtable 100))
+	  (set-buffer nntp-server-buffer)
+	  ;; Enter all the new groups into a hashtable.
+	  (gnus-active-to-gnus-format method hashtb 'ignore))
+	;; Now all new groups from `method' are in `hashtb'.
+	(mapatoms
+	 (lambda (group-sym)
+	   (if (or (null (setq group (symbol-name group-sym)))
+		   (not (boundp group-sym))
+		   (null (symbol-value group-sym))
+		   (gnus-gethash group gnus-newsrc-hashtb)
+		   (member group gnus-zombie-list)
+		   (member group gnus-killed-list))
+	       ;; The group is already known.
+	       ()
+	     ;; Make this group active.
+	     (when (symbol-value group-sym)
+	       (gnus-set-active group (symbol-value group-sym)))
+	     ;; Check whether we want it or not.
+	     (let ((do-sub (gnus-matches-options-n group)))
+	       (cond
+		((eq do-sub 'subscribe)
+		 (incf groups)
+		 (gnus-sethash group group gnus-killed-hashtb)
+		 (funcall gnus-subscribe-options-newsgroup-method group))
+		((eq do-sub 'ignore)
+		 nil)
+		(t
+		 (incf groups)
+		 (gnus-sethash group group gnus-killed-hashtb)
+		 (if gnus-subscribe-hierarchical-interactive
+		     (push group new-newsgroups)
+		   (funcall gnus-subscribe-newsgroup-method group)))))))
+	 hashtb))
+      (when new-newsgroups
+	(gnus-subscribe-hierarchical-interactive new-newsgroups)))
+    ;; Suggested by Per Abrahamsen <amanda@iesd.auc.dk>.
+    (when (> groups 0)
+      (gnus-message 6 "%d new newsgroup%s arrived."
+		    groups (if (> groups 1) "s have" " has")))
+    (when got-new
+      (setq gnus-newsrc-last-checked-date new-date))
+    got-new))
+
+(defun gnus-check-first-time-used ()
+  (if (or (> (length gnus-newsrc-alist) 1)
+	  (file-exists-p gnus-startup-file)
+	  (file-exists-p (concat gnus-startup-file ".el"))
+	  (file-exists-p (concat gnus-startup-file ".eld")))
+      nil
+    (gnus-message 6 "First time user; subscribing you to default groups")
+    (unless (gnus-read-active-file-p)
+      (gnus-read-active-file))
+    (setq gnus-newsrc-last-checked-date (current-time-string))
+    (let ((groups gnus-default-subscribed-newsgroups)
+	  group)
+      (if (eq groups t)
+	  nil
+	(setq groups (or groups gnus-backup-default-subscribed-newsgroups))
+	(mapatoms
+	 (lambda (sym)
+	   (if (null (setq group (symbol-name sym)))
+	       ()
+	     (let ((do-sub (gnus-matches-options-n group)))
+	       (cond
+		((eq do-sub 'subscribe)
+		 (gnus-sethash group group gnus-killed-hashtb)
+		 (funcall gnus-subscribe-options-newsgroup-method group))
+		((eq do-sub 'ignore)
+		 nil)
+		(t
+		 (push group gnus-killed-list))))))
+	 gnus-active-hashtb)
+	(while groups
+	  (when (gnus-active (car groups))
+	    (gnus-group-change-level
+	     (car groups) gnus-level-default-subscribed gnus-level-killed))
+	  (setq groups (cdr groups)))
+	(gnus-group-make-help-group)
+	(when gnus-novice-user
+	  (gnus-message 7 "`A k' to list killed groups"))))))
+
+(defun gnus-subscribe-group (group previous &optional method)
+  (gnus-group-change-level
+   (if method
+       (list t group gnus-level-default-subscribed nil nil method)
+     group)
+   gnus-level-default-subscribed gnus-level-killed previous t))
+
+;; `gnus-group-change-level' is the fundamental function for changing
+;; subscription levels of newsgroups.  This might mean just changing
+;; from level 1 to 2, which is pretty trivial, from 2 to 6 or back
+;; again, which subscribes/unsubscribes a group, which is equally
+;; trivial.  Changing from 1-7 to 8-9 means that you kill a group, and
+;; from 8-9 to 1-7 means that you remove the group from the list of
+;; killed (or zombie) groups and add them to the (kinda) subscribed
+;; groups.  And last but not least, moving from 8 to 9 and 9 to 8,
+;; which is trivial.
+;; ENTRY can either be a string (newsgroup name) or a list (if
+;; FROMKILLED is t, it's a list on the format (NUM INFO-LIST),
+;; otherwise it's a list in the format of the `gnus-newsrc-hashtb'
+;; entries.
+;; LEVEL is the new level of the group, OLDLEVEL is the old level and
+;; PREVIOUS is the group (in hashtb entry format) to insert this group
+;; after.
+(defun gnus-group-change-level (entry level &optional oldlevel
+				      previous fromkilled)
+  (let (group info active num)
+    ;; Glean what info we can from the arguments
+    (if (consp entry)
+	(if fromkilled (setq group (nth 1 entry))
+	  (setq group (car (nth 2 entry))))
+      (setq group entry))
+    (when (and (stringp entry)
+	       oldlevel
+	       (< oldlevel gnus-level-zombie))
+      (setq entry (gnus-gethash entry gnus-newsrc-hashtb)))
+    (if (and (not oldlevel)
+	     (consp entry))
+	(setq oldlevel (gnus-info-level (nth 2 entry)))
+      (setq oldlevel (or oldlevel 9)))
+    (when (stringp previous)
+      (setq previous (gnus-gethash previous gnus-newsrc-hashtb)))
+
+    (if (and (>= oldlevel gnus-level-zombie)
+	     (gnus-gethash group gnus-newsrc-hashtb))
+	;; We are trying to subscribe a group that is already
+	;; subscribed.
+	()				; Do nothing.
+
+      (unless (gnus-ephemeral-group-p group)
+	(gnus-dribble-enter
+	 (format "(gnus-group-change-level %S %S %S %S %S)"
+		 group level oldlevel (car (nth 2 previous)) fromkilled)))
+
+      ;; Then we remove the newgroup from any old structures, if needed.
+      ;; If the group was killed, we remove it from the killed or zombie
+      ;; list.  If not, and it is in fact going to be killed, we remove
+      ;; it from the newsrc hash table and assoc.
+      (cond
+       ((>= oldlevel gnus-level-zombie)
+	(if (= oldlevel gnus-level-zombie)
+	    (setq gnus-zombie-list (delete group gnus-zombie-list))
+	  (setq gnus-killed-list (delete group gnus-killed-list))))
+       (t
+	(when (and (>= level gnus-level-zombie)
+		   entry)
+	  (gnus-sethash (car (nth 2 entry)) nil gnus-newsrc-hashtb)
+	  (when (nth 3 entry)
+	    (setcdr (gnus-gethash (car (nth 3 entry))
+				  gnus-newsrc-hashtb)
+		    (cdr entry)))
+	  (setcdr (cdr entry) (cdddr entry)))))
+
+      ;; Finally we enter (if needed) the list where it is supposed to
+      ;; go, and change the subscription level.  If it is to be killed,
+      ;; we enter it into the killed or zombie list.
+      (cond
+       ((>= level gnus-level-zombie)
+	;; Remove from the hash table.
+	(gnus-sethash group nil gnus-newsrc-hashtb)
+	;; We do not enter foreign groups into the list of dead
+	;; groups.
+	(unless (gnus-group-foreign-p group)
+	  (if (= level gnus-level-zombie)
+	      (push group gnus-zombie-list)
+	    (push group gnus-killed-list))))
+       (t
+	;; If the list is to be entered into the newsrc assoc, and
+	;; it was killed, we have to create an entry in the newsrc
+	;; hashtb format and fix the pointers in the newsrc assoc.
+	(if (< oldlevel gnus-level-zombie)
+	    ;; It was alive, and it is going to stay alive, so we
+	    ;; just change the level and don't change any pointers or
+	    ;; hash table entries.
+	    (setcar (cdaddr entry) level)
+	  (if (listp entry)
+	      (setq info (cdr entry)
+		    num (car entry))
+	    (setq active (gnus-active group))
+	    (setq num
+		  (if active (- (1+ (cdr active)) (car active)) t))
+	    ;; Check whether the group is foreign.  If so, the
+	    ;; foreign select method has to be entered into the
+	    ;; info.
+	    (let ((method (or gnus-override-subscribe-method
+			      (gnus-group-method group))))
+	      (if (eq method gnus-select-method)
+		  (setq info (list group level nil))
+		(setq info (list group level nil nil method)))))
+	  (unless previous
+	    (setq previous
+		  (let ((p gnus-newsrc-alist))
+		    (while (cddr p)
+		      (setq p (cdr p)))
+		    p)))
+	  (setq entry (cons info (cddr previous)))
+	  (if (cdr previous)
+	      (progn
+		(setcdr (cdr previous) entry)
+		(gnus-sethash group (cons num (cdr previous))
+			      gnus-newsrc-hashtb))
+	    (setcdr previous entry)
+	    (gnus-sethash group (cons num previous)
+			  gnus-newsrc-hashtb))
+	  (when (cdr entry)
+	    (setcdr (gnus-gethash (caadr entry) gnus-newsrc-hashtb) entry))
+	  (gnus-dribble-enter
+	   (format
+	    "(gnus-group-set-info '%S)" info)))))
+      (when gnus-group-change-level-function
+	(funcall gnus-group-change-level-function group level oldlevel)))))
+
+(defun gnus-kill-newsgroup (newsgroup)
+  "Obsolete function.  Kills a newsgroup."
+  (gnus-group-change-level
+   (gnus-gethash newsgroup gnus-newsrc-hashtb) gnus-level-killed))
+
+(defun gnus-check-bogus-newsgroups (&optional confirm)
+  "Remove bogus newsgroups.
+If CONFIRM is non-nil, the user has to confirm the deletion of every
+newsgroup."
+  (let ((newsrc (cdr gnus-newsrc-alist))
+	bogus group entry info)
+    (gnus-message 5 "Checking bogus newsgroups...")
+    (unless (gnus-read-active-file-p)
+      (gnus-read-active-file t))
+    (when (gnus-read-active-file-p)
+      ;; Find all bogus newsgroup that are subscribed.
+      (while newsrc
+	(setq info (pop newsrc)
+	      group (gnus-info-group info))
+	(unless (or (gnus-active group)	; Active
+		    (gnus-info-method info)) ; Foreign
+	  ;; Found a bogus newsgroup.
+	  (push group bogus)))
+      (if confirm
+	  (map-y-or-n-p
+	   "Remove bogus group %s? "
+	   (lambda (group)
+	     ;; Remove all bogus subscribed groups by first killing them, and
+	     ;; then removing them from the list of killed groups.
+	     (when (setq entry (gnus-gethash group gnus-newsrc-hashtb))
+	       (gnus-group-change-level entry gnus-level-killed)
+	       (setq gnus-killed-list (delete group gnus-killed-list))))
+	   bogus '("group" "groups" "remove"))
+	(while (setq group (pop bogus))
+	  ;; Remove all bogus subscribed groups by first killing them, and
+	  ;; then removing them from the list of killed groups.
+	  (when (setq entry (gnus-gethash group gnus-newsrc-hashtb))
+	    (gnus-group-change-level entry gnus-level-killed)
+	    (setq gnus-killed-list (delete group gnus-killed-list)))))
+      ;; Then we remove all bogus groups from the list of killed and
+      ;; zombie groups.  They are removed without confirmation.
+      (let ((dead-lists '(gnus-killed-list gnus-zombie-list))
+	    killed)
+	(while dead-lists
+	  (setq killed (symbol-value (car dead-lists)))
+	  (while killed
+	    (unless (gnus-active (setq group (pop killed)))
+	      ;; The group is bogus.
+	      ;; !!!Slow as hell.
+	      (set (car dead-lists)
+		   (delete group (symbol-value (car dead-lists))))))
+	  (setq dead-lists (cdr dead-lists))))
+      (run-hooks 'gnus-check-bogus-groups-hook)
+      (gnus-message 5 "Checking bogus newsgroups...done"))))
+
+(defun gnus-check-duplicate-killed-groups ()
+  "Remove duplicates from the list of killed groups."
+  (interactive)
+  (let ((killed gnus-killed-list))
+    (while killed
+      (gnus-message 9 "%d" (length killed))
+      (setcdr killed (delete (car killed) (cdr killed)))
+      (setq killed (cdr killed)))))
+
+;; We want to inline a function from gnus-cache, so we cheat here:
+(eval-when-compile
+  (defvar gnus-cache-active-hashtb)
+  (defun gnus-cache-possibly-alter-active (group active)
+    "Alter the ACTIVE info for GROUP to reflect the articles in the cache."
+    (when gnus-cache-active-hashtb
+      (let ((cache-active (gnus-gethash group gnus-cache-active-hashtb)))
+	(and cache-active
+	     (< (car cache-active) (car active))
+	     (setcar active (car cache-active)))
+	(and cache-active
+	     (> (cdr cache-active) (cdr active))
+	     (setcdr active (cdr cache-active)))))))
+
+(defun gnus-activate-group (group &optional scan dont-check method)
+  ;; Check whether a group has been activated or not.
+  ;; If SCAN, request a scan of that group as well.
+  (let ((method (or method (inline (gnus-find-method-for-group group))))
+	active)
+    (and (inline (gnus-check-server method))
+	 ;; We escape all bugs and quit here to make it possible to
+	 ;; continue if a group is so out-there that it reports bugs
+	 ;; and stuff.
+	 (progn
+	   (and scan
+		(gnus-check-backend-function 'request-scan (car method))
+		(gnus-request-scan group method))
+	   t)
+	 (condition-case ()
+	     (inline (gnus-request-group group dont-check method))
+	   (error nil)
+	   (quit nil))
+	 (gnus-set-active group (setq active (gnus-parse-active)))
+	 ;; Return the new active info.
+	 active)))
+
+(defun gnus-get-unread-articles-in-group (info active &optional update)
+  (when active
+    ;; Allow the backend to update the info in the group.
+    (when (and update
+	       (gnus-request-update-info
+		info (inline (gnus-find-method-for-group
+			      (gnus-info-group info)))))
+      (gnus-activate-group (gnus-info-group info) nil t))
+    (let* ((range (gnus-info-read info))
+	   (num 0))
+      ;; If a cache is present, we may have to alter the active info.
+      (when (and gnus-use-cache info)
+	(inline (gnus-cache-possibly-alter-active
+		 (gnus-info-group info) active)))
+      ;; Modify the list of read articles according to what articles
+      ;; are available; then tally the unread articles and add the
+      ;; number to the group hash table entry.
+      (cond
+       ((zerop (cdr active))
+	(setq num 0))
+       ((not range)
+	(setq num (- (1+ (cdr active)) (car active))))
+       ((not (listp (cdr range)))
+	;; Fix a single (num . num) range according to the
+	;; active hash table.
+	;; Fix by Carsten Bormann <cabo@Informatik.Uni-Bremen.DE>.
+	(and (< (cdr range) (car active)) (setcdr range (1- (car active))))
+	(and (> (cdr range) (cdr active)) (setcdr range (cdr active)))
+	;; Compute number of unread articles.
+	(setq num (max 0 (- (cdr active) (- (1+ (cdr range)) (car range))))))
+       (t
+	;; The read list is a list of ranges.  Fix them according to
+	;; the active hash table.
+	;; First peel off any elements that are below the lower
+	;; active limit.
+	(while (and (cdr range)
+		    (>= (car active)
+			(or (and (atom (cadr range)) (cadr range))
+			    (caadr range))))
+	  (if (numberp (car range))
+	      (setcar range
+		      (cons (car range)
+			    (or (and (numberp (cadr range))
+				     (cadr range))
+				(cdadr range))))
+	    (setcdr (car range)
+		    (or (and (numberp (nth 1 range)) (nth 1 range))
+			(cdadr range))))
+	  (setcdr range (cddr range)))
+	;; Adjust the first element to be the same as the lower limit.
+	(when (and (not (atom (car range)))
+		   (< (cdar range) (car active)))
+	  (setcdr (car range) (1- (car active))))
+	;; Then we want to peel off any elements that are higher
+	;; than the upper active limit.
+	(let ((srange range))
+	  ;; Go past all legal elements.
+	  (while (and (cdr srange)
+		      (<= (or (and (atom (cadr srange))
+				   (cadr srange))
+			      (caadr srange))
+			  (cdr active)))
+	    (setq srange (cdr srange)))
+	  (when (cdr srange)
+	    ;; Nuke all remaining illegal elements.
+	    (setcdr srange nil))
+
+	  ;; Adjust the final element.
+	  (when (and (not (atom (car srange)))
+		     (> (cdar srange) (cdr active)))
+	    (setcdr (car srange) (cdr active))))
+	;; Compute the number of unread articles.
+	(while range
+	  (setq num (+ num (- (1+ (or (and (atom (car range)) (car range))
+				      (cdar range)))
+			      (or (and (atom (car range)) (car range))
+				  (caar range)))))
+	  (setq range (cdr range)))
+	(setq num (max 0 (- (cdr active) num)))))
+      ;; Set the number of unread articles.
+      (when info
+	(setcar (gnus-gethash (gnus-info-group info) gnus-newsrc-hashtb) num))
+      num)))
+
+;; Go though `gnus-newsrc-alist' and compare with `gnus-active-hashtb'
+;; and compute how many unread articles there are in each group.
+(defun gnus-get-unread-articles (&optional level)
+  (let* ((newsrc (cdr gnus-newsrc-alist))
+	 (level (or level gnus-activate-level (1+ gnus-level-subscribed)))
+	 (foreign-level
+	  (min
+	   (cond ((and gnus-activate-foreign-newsgroups
+		       (not (numberp gnus-activate-foreign-newsgroups)))
+		  (1+ gnus-level-subscribed))
+		 ((numberp gnus-activate-foreign-newsgroups)
+		  gnus-activate-foreign-newsgroups)
+		 (t 0))
+	   level))
+	 info group active method)
+    (gnus-message 5 "Checking new news...")
+
+    (while newsrc
+      (setq active (gnus-active (setq group (gnus-info-group
+					     (setq info (pop newsrc))))))
+
+      ;; Check newsgroups.  If the user doesn't want to check them, or
+      ;; they can't be checked (for instance, if the news server can't
+      ;; be reached) we just set the number of unread articles in this
+      ;; newsgroup to t.  This means that Gnus thinks that there are
+      ;; unread articles, but it has no idea how many.
+      (if (and (setq method (gnus-info-method info))
+	       (not (inline
+		      (gnus-server-equal
+		       gnus-select-method
+		       (setq method (gnus-server-get-method nil method)))))
+	       (not (gnus-secondary-method-p method)))
+	  ;; These groups are foreign.  Check the level.
+	  (when (<= (gnus-info-level info) foreign-level)
+	    (setq active (gnus-activate-group group 'scan))
+	    (unless (inline (gnus-virtual-group-p group))
+	      (inline (gnus-close-group group)))
+	    (when (fboundp (intern (concat (symbol-name (car method))
+					   "-request-update-info")))
+	      (inline (gnus-request-update-info info method))))
+	;; These groups are native or secondary.
+	(when (and (<= (gnus-info-level info) level)
+		   (not gnus-read-active-file))
+	  (setq active (gnus-activate-group group 'scan))
+	  (inline (gnus-close-group group))))
+
+      ;; Get the number of unread articles in the group.
+      (if active
+	  (inline (gnus-get-unread-articles-in-group info active t))
+	;; The group couldn't be reached, so we nix out the number of
+	;; unread articles and stuff.
+	(gnus-set-active group nil)
+	(setcar (gnus-gethash group gnus-newsrc-hashtb) t)))
+
+    (gnus-message 5 "Checking new news...done")))
+
+;; Create a hash table out of the newsrc alist.  The `car's of the
+;; alist elements are used as keys.
+(defun gnus-make-hashtable-from-newsrc-alist ()
+  (let ((alist gnus-newsrc-alist)
+	(ohashtb gnus-newsrc-hashtb)
+	prev)
+    (setq gnus-newsrc-hashtb (gnus-make-hashtable (length alist)))
+    (setq alist
+	  (setq prev (setq gnus-newsrc-alist
+			   (if (equal (caar gnus-newsrc-alist)
+				      "dummy.group")
+			       gnus-newsrc-alist
+			     (cons (list "dummy.group" 0 nil) alist)))))
+    (while alist
+      (gnus-sethash
+       (caar alist)
+       ;; Preserve number of unread articles in groups.
+       (cons (and ohashtb (car (gnus-gethash (caar alist) ohashtb)))
+	     prev)
+       gnus-newsrc-hashtb)
+      (setq prev alist
+	    alist (cdr alist)))))
+
+(defun gnus-make-hashtable-from-killed ()
+  "Create a hash table from the killed and zombie lists."
+  (let ((lists '(gnus-killed-list gnus-zombie-list))
+	list)
+    (setq gnus-killed-hashtb
+	  (gnus-make-hashtable
+	   (+ (length gnus-killed-list) (length gnus-zombie-list))))
+    (while lists
+      (setq list (symbol-value (pop lists)))
+      (while list
+	(gnus-sethash (car list) (pop list) gnus-killed-hashtb)))))
+
+(defun gnus-parse-active ()
+  "Parse active info in the nntp server buffer."
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (goto-char (point-min))
+    ;; Parse the result we got from `gnus-request-group'.
+    (when (looking-at "[0-9]+ [0-9]+ \\([0-9]+\\) [0-9]+")
+      (goto-char (match-beginning 1))
+      (cons (read (current-buffer))
+	    (read (current-buffer))))))
+
+(defun gnus-make-articles-unread (group articles)
+  "Mark ARTICLES in GROUP as unread."
+  (let* ((info (nth 2 (or (gnus-gethash group gnus-newsrc-hashtb)
+			  (gnus-gethash (gnus-group-real-name group)
+					gnus-newsrc-hashtb))))
+	 (ranges (gnus-info-read info))
+	 news article)
+    (while articles
+      (when (gnus-member-of-range
+	     (setq article (pop articles)) ranges)
+	(push article news)))
+    (when news
+      (gnus-info-set-read
+       info (gnus-remove-from-range (gnus-info-read info) (nreverse news)))
+      (gnus-group-update-group group t))))
+
+;; Enter all dead groups into the hashtb.
+(defun gnus-update-active-hashtb-from-killed ()
+  (let ((hashtb (setq gnus-active-hashtb (gnus-make-hashtable 4096)))
+	(lists (list gnus-killed-list gnus-zombie-list))
+	killed)
+    (while lists
+      (setq killed (car lists))
+      (while killed
+	(gnus-sethash (car killed) nil hashtb)
+	(setq killed (cdr killed)))
+      (setq lists (cdr lists)))))
+
+(defun gnus-get-killed-groups ()
+  "Go through the active hashtb and mark all unknown groups as killed."
+  ;; First make sure active file has been read.
+  (unless (gnus-read-active-file-p)
+    (let ((gnus-read-active-file t))
+      (gnus-read-active-file)))
+  (unless gnus-killed-hashtb
+    (gnus-make-hashtable-from-killed))
+  ;; Go through all newsgroups that are known to Gnus - enlarge kill list.
+  (mapatoms
+   (lambda (sym)
+     (let ((groups 0)
+	   (group (symbol-name sym)))
+       (if (or (null group)
+	       (gnus-gethash group gnus-killed-hashtb)
+	       (gnus-gethash group gnus-newsrc-hashtb))
+	   ()
+	 (let ((do-sub (gnus-matches-options-n group)))
+	   (if (or (eq do-sub 'subscribe) (eq do-sub 'ignore))
+	       ()
+	     (setq groups (1+ groups))
+	     (push group gnus-killed-list)
+	     (gnus-sethash group group gnus-killed-hashtb))))))
+   gnus-active-hashtb)
+  (gnus-dribble-touch))
+
+;; Get the active file(s) from the backend(s).
+(defun gnus-read-active-file (&optional force)
+  (gnus-group-set-mode-line)
+  (let ((methods
+	 (append
+	  (if (gnus-check-server gnus-select-method)
+	      ;; The native server is available.
+	      (cons gnus-select-method gnus-secondary-select-methods)
+	    ;; The native server is down, so we just do the
+	    ;; secondary ones.
+	    gnus-secondary-select-methods)
+	  ;; Also read from the archive server.
+	  (when (gnus-archive-server-wanted-p)
+	    (list "archive"))))
+	list-type)
+    (setq gnus-have-read-active-file nil)
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (while methods
+	(let* ((method (if (stringp (car methods))
+			   (gnus-server-get-method nil (car methods))
+			 (car methods)))
+	       (where (nth 1 method))
+	       (mesg (format "Reading active file%s via %s..."
+			     (if (and where (not (zerop (length where))))
+				 (concat " from " where) "")
+			     (car method))))
+	  (gnus-message 5 mesg)
+	  (when (gnus-check-server method)
+	    ;; Request that the backend scan its incoming messages.
+	    (when (gnus-check-backend-function 'request-scan (car method))
+	      (gnus-request-scan nil method))
+	    (cond
+	     ((and (eq gnus-read-active-file 'some)
+		   (gnus-check-backend-function 'retrieve-groups (car method))
+		   (not force))
+	      (let ((newsrc (cdr gnus-newsrc-alist))
+		    (gmethod (gnus-server-get-method nil method))
+		    groups info)
+		(while (setq info (pop newsrc))
+		  (when (inline
+			  (gnus-server-equal
+			   (inline
+			     (gnus-find-method-for-group
+			      (gnus-info-group info) info))
+			   gmethod))
+		    (push (gnus-group-real-name (gnus-info-group info))
+			  groups)))
+		(when groups
+		  (gnus-check-server method)
+		  (setq list-type (gnus-retrieve-groups groups method))
+		  (cond
+		   ((not list-type)
+		    (gnus-error
+		     1.2 "Cannot read partial active file from %s server."
+		     (car method)))
+		   ((eq list-type 'active)
+		    (gnus-active-to-gnus-format method gnus-active-hashtb))
+		   (t
+		    (gnus-groups-to-gnus-format method gnus-active-hashtb))))))
+	     ((null method)
+	      t)
+	     (t
+	      (if (not (gnus-request-list method))
+		  (unless (equal method gnus-message-archive-method)
+		    (gnus-error 1 "Cannot read active file from %s server."
+				(car method)))
+		(gnus-message 5 mesg)
+		(gnus-active-to-gnus-format method gnus-active-hashtb)
+		;; We mark this active file as read.
+		(push method gnus-have-read-active-file)
+		(gnus-message 5 "%sdone" mesg))))))
+	(setq methods (cdr methods))))))
+
+
+(defun gnus-ignored-newsgroups-has-to-p ()
+  "T only when gnus-ignored-newsgroups includes \"^to\\\\.\" as an element."
+  ;; note this regexp is the same as:
+  ;; (concat (regexp-quote "^to\\.") "\\($\\|" (regexp-quote "\\|") "\\)")
+  (string-match "\\^to\\\\\\.\\($\\|\\\\|\\)"
+		gnus-ignored-newsgroups))
+
+;; Read an active file and place the results in `gnus-active-hashtb'.
+(defun gnus-active-to-gnus-format (&optional method hashtb ignore-errors)
+  (unless method
+    (setq method gnus-select-method))
+  (let ((cur (current-buffer))
+	(hashtb (or hashtb
+		    (if (and gnus-active-hashtb
+			     (not (equal method gnus-select-method)))
+			gnus-active-hashtb
+		      (setq gnus-active-hashtb
+			    (if (equal method gnus-select-method)
+				(gnus-make-hashtable
+				 (count-lines (point-min) (point-max)))
+			      (gnus-make-hashtable 4096)))))))
+    ;; Delete unnecessary lines, cleaned up dmoore@ucsd.edu 31.10.1996
+    (goto-char (point-min))
+    (cond ((gnus-ignored-newsgroups-has-to-p)
+	   (delete-matching-lines gnus-ignored-newsgroups))
+	  ((string= gnus-ignored-newsgroups "")
+	   (delete-matching-lines "^to\\."))
+	  (t
+	   (delete-matching-lines (concat "^to\\.\\|"
+					  gnus-ignored-newsgroups))))
+
+    ;; Make the group names readable as a lisp expression even if they
+    ;; contain special characters.
+    ;; Fix by Luc Van Eycken <Luc.VanEycken@esat.kuleuven.ac.be>.
+    (goto-char (point-max))
+    (while (re-search-backward "[][';?()#]" nil t)
+      (insert ?\\))
+
+    ;; If these are groups from a foreign select method, we insert the
+    ;; group prefix in front of the group names.
+    (and method (not (gnus-server-equal
+		      (gnus-server-get-method nil method)
+		      (gnus-server-get-method nil gnus-select-method)))
+	 (let ((prefix (gnus-group-prefixed-name "" method)))
+	   (goto-char (point-min))
+	   (while (and (not (eobp))
+		       (progn (insert prefix)
+			      (zerop (forward-line 1)))))))
+    ;; Store the active file in a hash table.
+    (goto-char (point-min))
+    (let (group max min)
+      (while (not (eobp))
+	(condition-case ()
+	    (progn
+	      (narrow-to-region (point) (gnus-point-at-eol))
+	      ;; group gets set to a symbol interned in the hash table
+	      ;; (what a hack!!) - jwz
+	      (setq group (let ((obarray hashtb)) (read cur)))
+	      (if (and (numberp (setq max (read cur)))
+		       (numberp (setq min (read cur)))
+		       (progn
+			 (skip-chars-forward " \t")
+			 (not
+			  (or (= (following-char) ?=)
+			      (= (following-char) ?x)
+			      (= (following-char) ?j)))))
+		  (progn
+		    (set group (cons min max))
+		    ;; if group is moderated, stick in moderation table
+		    (when (= (following-char) ?m)
+		      (unless gnus-moderated-hashtb
+			(setq gnus-moderated-hashtb (gnus-make-hashtable)))
+		      (gnus-sethash (symbol-name group) t
+				    gnus-moderated-hashtb)))
+		(set group nil)))
+	  (error
+	   (and group
+		(symbolp group)
+		(set group nil))
+	   (unless ignore-errors
+	     (gnus-message 3 "Warning - illegal active: %s"
+			   (buffer-substring
+			    (gnus-point-at-bol) (gnus-point-at-eol))))))
+	(widen)
+	(forward-line 1)))))
+
+(defun gnus-groups-to-gnus-format (method &optional hashtb)
+  ;; Parse a "groups" active file.
+  (let ((cur (current-buffer))
+	(hashtb (or hashtb
+		    (if (and method gnus-active-hashtb)
+			gnus-active-hashtb
+		      (setq gnus-active-hashtb
+			    (gnus-make-hashtable
+			     (count-lines (point-min) (point-max)))))))
+	(prefix (and method
+		     (not (gnus-server-equal
+			   (gnus-server-get-method nil method)
+			   (gnus-server-get-method nil gnus-select-method)))
+		     (gnus-group-prefixed-name "" method))))
+
+    (goto-char (point-min))
+    ;; We split this into to separate loops, one with the prefix
+    ;; and one without to speed the reading up somewhat.
+    (if prefix
+	(let (min max opoint group)
+	  (while (not (eobp))
+	    (condition-case ()
+		(progn
+		  (read cur) (read cur)
+		  (setq min (read cur)
+			max (read cur)
+			opoint (point))
+		  (skip-chars-forward " \t")
+		  (insert prefix)
+		  (goto-char opoint)
+		  (set (let ((obarray hashtb)) (read cur))
+		       (cons min max)))
+	      (error (and group (symbolp group) (set group nil))))
+	    (forward-line 1)))
+      (let (min max group)
+	(while (not (eobp))
+	  (condition-case ()
+	      (when (= (following-char) ?2)
+		(read cur) (read cur)
+		(setq min (read cur)
+		      max (read cur))
+		(set (setq group (let ((obarray hashtb)) (read cur)))
+		     (cons min max)))
+	    (error (and group (symbolp group) (set group nil))))
+	  (forward-line 1))))))
+
+(defun gnus-read-newsrc-file (&optional force)
+  "Read startup file.
+If FORCE is non-nil, the .newsrc file is read."
+  ;; Reset variables that might be defined in the .newsrc.eld file.
+  (let ((variables gnus-variable-list))
+    (while variables
+      (set (car variables) nil)
+      (setq variables (cdr variables))))
+  (let* ((newsrc-file gnus-current-startup-file)
+	 (quick-file (concat newsrc-file ".el")))
+    (save-excursion
+      ;; We always load the .newsrc.eld file.  If always contains
+      ;; much information that can not be gotten from the .newsrc
+      ;; file (ticked articles, killed groups, foreign methods, etc.)
+      (gnus-read-newsrc-el-file quick-file)
+
+      (when (and (file-exists-p gnus-current-startup-file)
+		 (or force
+		     (and (file-newer-than-file-p newsrc-file quick-file)
+			  (file-newer-than-file-p newsrc-file
+						  (concat quick-file "d")))
+		     (not gnus-newsrc-alist)))
+	;; We read the .newsrc file.  Note that if there if a
+	;; .newsrc.eld file exists, it has already been read, and
+	;; the `gnus-newsrc-hashtb' has been created.  While reading
+	;; the .newsrc file, Gnus will only use the information it
+	;; can find there for changing the data already read -
+	;; i. e., reading the .newsrc file will not trash the data
+	;; already read (except for read articles).
+	(save-excursion
+	  (gnus-message 5 "Reading %s..." newsrc-file)
+	  (set-buffer (nnheader-find-file-noselect newsrc-file))
+	  (buffer-disable-undo (current-buffer))
+	  (gnus-newsrc-to-gnus-format)
+	  (kill-buffer (current-buffer))
+	  (gnus-message 5 "Reading %s...done" newsrc-file)))
+
+      ;; Convert old to new.
+      (gnus-convert-old-newsrc))))
+
+(defun gnus-convert-old-newsrc ()
+  "Convert old newsrc into the new format, if needed."
+  (let ((fcv (and gnus-newsrc-file-version
+		  (gnus-continuum-version gnus-newsrc-file-version))))
+    (cond
+     ;; No .newsrc.eld file was loaded.
+     ((null fcv) nil)
+     ;; Gnus 5 .newsrc.eld was loaded.
+     ((< fcv (gnus-continuum-version "September Gnus v0.1"))
+      (gnus-convert-old-ticks)))))
+
+(defun gnus-convert-old-ticks ()
+  (let ((newsrc (cdr gnus-newsrc-alist))
+	marks info dormant ticked)
+    (while (setq info (pop newsrc))
+      (when (setq marks (gnus-info-marks info))
+	(setq dormant (cdr (assq 'dormant marks))
+	      ticked (cdr (assq 'tick marks)))
+	(when (or dormant ticked)
+	  (gnus-info-set-read
+	   info
+	   (gnus-add-to-range
+	    (gnus-info-read info)
+	    (nconc (gnus-uncompress-range dormant)
+		   (gnus-uncompress-range ticked)))))))))
+
+(defun gnus-read-newsrc-el-file (file)
+  (let ((ding-file (concat file "d")))
+    ;; We always, always read the .eld file.
+    (gnus-message 5 "Reading %s..." ding-file)
+    (let (gnus-newsrc-assoc)
+      (condition-case nil
+	  (load ding-file t t t)
+	(error
+	 (ding)
+	 (unless (gnus-yes-or-no-p
+		  (format "Error in %s; continue? " ding-file))
+	   (error "Error in %s" ding-file))))
+      (when gnus-newsrc-assoc
+	(setq gnus-newsrc-alist gnus-newsrc-assoc)))
+    (gnus-make-hashtable-from-newsrc-alist)
+    (when (file-newer-than-file-p file ding-file)
+      ;; Old format quick file
+      (gnus-message 5 "Reading %s..." file)
+      ;; The .el file is newer than the .eld file, so we read that one
+      ;; as well.
+      (gnus-read-old-newsrc-el-file file))))
+
+;; Parse the old-style quick startup file
+(defun gnus-read-old-newsrc-el-file (file)
+  (let (newsrc killed marked group m info)
+    (prog1
+	(let ((gnus-killed-assoc nil)
+	      gnus-marked-assoc gnus-newsrc-alist gnus-newsrc-assoc)
+	  (prog1
+	      (ignore-errors
+		(load file t t t))
+	    (setq newsrc gnus-newsrc-assoc
+		  killed gnus-killed-assoc
+		  marked gnus-marked-assoc)))
+      (setq gnus-newsrc-alist nil)
+      (while (setq group (pop newsrc))
+	(if (setq info (gnus-get-info (car group)))
+	    (progn
+	      (gnus-info-set-read info (cddr group))
+	      (gnus-info-set-level
+	       info (if (nth 1 group) gnus-level-default-subscribed
+		      gnus-level-default-unsubscribed))
+	      (push info gnus-newsrc-alist))
+	  (push (setq info
+		      (list (car group)
+			    (if (nth 1 group) gnus-level-default-subscribed
+			      gnus-level-default-unsubscribed)
+			    (cddr group)))
+		gnus-newsrc-alist))
+	;; Copy marks into info.
+	(when (setq m (assoc (car group) marked))
+	  (unless (nthcdr 3 info)
+	    (nconc info (list nil)))
+	  (gnus-info-set-marks
+	   info (list (cons 'tick (gnus-compress-sequence
+				   (sort (cdr m) '<) t))))))
+      (setq newsrc killed)
+      (while newsrc
+	(setcar newsrc (caar newsrc))
+	(setq newsrc (cdr newsrc)))
+      (setq gnus-killed-list killed))
+    ;; The .el file version of this variable does not begin with
+    ;; "options", while the .eld version does, so we just add it if it
+    ;; isn't there.
+    (when
+	gnus-newsrc-options
+      (when (not (string-match "^ *options" gnus-newsrc-options))
+	(setq gnus-newsrc-options (concat "options " gnus-newsrc-options)))
+      (when (not (string-match "\n$" gnus-newsrc-options))
+	(setq gnus-newsrc-options (concat gnus-newsrc-options "\n")))
+      ;; Finally, if we read some options lines, we parse them.
+      (unless (string= gnus-newsrc-options "")
+	(gnus-newsrc-parse-options gnus-newsrc-options)))
+
+    (setq gnus-newsrc-alist (nreverse gnus-newsrc-alist))
+    (gnus-make-hashtable-from-newsrc-alist)))
+
+(defun gnus-make-newsrc-file (file)
+  "Make server dependent file name by catenating FILE and server host name."
+  (let* ((file (expand-file-name file nil))
+	 (real-file (concat file "-" (nth 1 gnus-select-method))))
+    (if (or (file-exists-p real-file)
+	    (file-exists-p (concat real-file ".el"))
+	    (file-exists-p (concat real-file ".eld")))
+	real-file file)))
+
+(defun gnus-newsrc-to-gnus-format ()
+  (setq gnus-newsrc-options "")
+  (setq gnus-newsrc-options-n nil)
+
+  (unless gnus-active-hashtb
+    (setq gnus-active-hashtb (gnus-make-hashtable 4096)))
+  (let ((buf (current-buffer))
+	(already-read (> (length gnus-newsrc-alist) 1))
+	group subscribed options-symbol newsrc Options-symbol
+	symbol reads num1)
+    (goto-char (point-min))
+    ;; We intern the symbol `options' in the active hashtb so that we
+    ;; can `eq' against it later.
+    (set (setq options-symbol (intern "options" gnus-active-hashtb)) nil)
+    (set (setq Options-symbol (intern "Options" gnus-active-hashtb)) nil)
+
+    (while (not (eobp))
+      ;; We first read the first word on the line by narrowing and
+      ;; then reading into `gnus-active-hashtb'.  Most groups will
+      ;; already exist in that hashtb, so this will save some string
+      ;; space.
+      (narrow-to-region
+       (point)
+       (progn (skip-chars-forward "^ \t!:\n") (point)))
+      (goto-char (point-min))
+      (setq symbol
+	    (and (/= (point-min) (point-max))
+		 (let ((obarray gnus-active-hashtb)) (read buf))))
+      (widen)
+      ;; Now, the symbol we have read is either `options' or a group
+      ;; name.  If it is an options line, we just add it to a string.
+      (cond
+       ((or (eq symbol options-symbol)
+	    (eq symbol Options-symbol))
+	(setq gnus-newsrc-options
+	      ;; This concating is quite inefficient, but since our
+	      ;; thorough studies show that approx 99.37% of all
+	      ;; .newsrc files only contain a single options line, we
+	      ;; don't give a damn, frankly, my dear.
+	      (concat gnus-newsrc-options
+		      (buffer-substring
+		       (gnus-point-at-bol)
+		       ;; Options may continue on the next line.
+		       (or (and (re-search-forward "^[^ \t]" nil 'move)
+				(progn (beginning-of-line) (point)))
+			   (point)))))
+	(forward-line -1))
+       (symbol
+	;; Group names can be just numbers.
+	(when (numberp symbol)
+	  (setq symbol (intern (int-to-string symbol) gnus-active-hashtb)))
+	(unless (boundp symbol)
+	  (set symbol nil))
+	;; It was a group name.
+	(setq subscribed (= (following-char) ?:)
+	      group (symbol-name symbol)
+	      reads nil)
+	(if (eolp)
+	    ;; If the line ends here, this is clearly a buggy line, so
+	    ;; we put point a the beginning of line and let the cond
+	    ;; below do the error handling.
+	    (beginning-of-line)
+	  ;; We skip to the beginning of the ranges.
+	  (skip-chars-forward "!: \t"))
+	;; We are now at the beginning of the list of read articles.
+	;; We read them range by range.
+	(while
+	    (cond
+	     ((looking-at "[0-9]+")
+	      ;; We narrow and read a number instead of buffer-substring/
+	      ;; string-to-int because it's faster.  narrow/widen is
+	      ;; faster than save-restriction/narrow, and save-restriction
+	      ;; produces a garbage object.
+	      (setq num1 (progn
+			   (narrow-to-region (match-beginning 0) (match-end 0))
+			   (read buf)))
+	      (widen)
+	      ;; If the next character is a dash, then this is a range.
+	      (if (= (following-char) ?-)
+		  (progn
+		    ;; We read the upper bound of the range.
+		    (forward-char 1)
+		    (if (not (looking-at "[0-9]+"))
+			;; This is a buggy line, by we pretend that
+			;; it's kinda OK.  Perhaps the user should be
+			;; dinged?
+			(push num1 reads)
+		      (push
+		       (cons num1
+			     (progn
+			       (narrow-to-region (match-beginning 0)
+						 (match-end 0))
+			       (read buf)))
+		       reads)
+		      (widen)))
+		;; It was just a simple number, so we add it to the
+		;; list of ranges.
+		(push num1 reads))
+	      ;; If the next char in ?\n, then we have reached the end
+	      ;; of the line and return nil.
+	      (/= (following-char) ?\n))
+	     ((= (following-char) ?\n)
+	      ;; End of line, so we end.
+	      nil)
+	     (t
+	      ;; Not numbers and not eol, so this might be a buggy
+	      ;; line...
+	      (unless (eobp)
+		;; If it was eob instead of ?\n, we allow it.
+		;; The line was buggy.
+		(setq group nil)
+		(gnus-error 3.1 "Mangled line: %s"
+			    (buffer-substring (gnus-point-at-bol)
+					      (gnus-point-at-eol))))
+	      nil))
+	  ;; Skip past ", ".  Spaces are illegal in these ranges, but
+	  ;; we allow them, because it's a common mistake to put a
+	  ;; space after the comma.
+	  (skip-chars-forward ", "))
+
+	;; We have already read .newsrc.eld, so we gently update the
+	;; data in the hash table with the information we have just
+	;; read.
+	(when group
+	  (let ((info (gnus-get-info group))
+		level)
+	    (if info
+		;; There is an entry for this file in the alist.
+		(progn
+		  (gnus-info-set-read info (nreverse reads))
+		  ;; We update the level very gently.  In fact, we
+		  ;; only change it if there's been a status change
+		  ;; from subscribed to unsubscribed, or vice versa.
+		  (setq level (gnus-info-level info))
+		  (cond ((and (<= level gnus-level-subscribed)
+			      (not subscribed))
+			 (setq level (if reads
+					 gnus-level-default-unsubscribed
+				       (1+ gnus-level-default-unsubscribed))))
+			((and (> level gnus-level-subscribed) subscribed)
+			 (setq level gnus-level-default-subscribed)))
+		  (gnus-info-set-level info level))
+	      ;; This is a new group.
+	      (setq info (list group
+			       (if subscribed
+				   gnus-level-default-subscribed
+				 (if reads
+				     (1+ gnus-level-subscribed)
+				   gnus-level-default-unsubscribed))
+			       (nreverse reads))))
+	    (push info newsrc)))))
+      (forward-line 1))
+
+    (setq newsrc (nreverse newsrc))
+
+    (if (not already-read)
+	()
+      ;; We now have two newsrc lists - `newsrc', which is what we
+      ;; have read from .newsrc, and `gnus-newsrc-alist', which is
+      ;; what we've read from .newsrc.eld.  We have to merge these
+      ;; lists.  We do this by "attaching" any (foreign) groups in the
+      ;; gnus-newsrc-alist to the (native) group that precedes them.
+      (let ((rc (cdr gnus-newsrc-alist))
+	    (prev gnus-newsrc-alist)
+	    entry mentry)
+	(while rc
+	  (or (null (nth 4 (car rc)))	; It's a native group.
+	      (assoc (caar rc) newsrc)	; It's already in the alist.
+	      (if (setq entry (assoc (caar prev) newsrc))
+		  (setcdr (setq mentry (memq entry newsrc))
+			  (cons (car rc) (cdr mentry)))
+		(push (car rc) newsrc)))
+	  (setq prev rc
+		rc (cdr rc)))))
+
+    (setq gnus-newsrc-alist newsrc)
+    ;; We make the newsrc hashtb.
+    (gnus-make-hashtable-from-newsrc-alist)
+
+    ;; Finally, if we read some options lines, we parse them.
+    (unless (string= gnus-newsrc-options "")
+      (gnus-newsrc-parse-options gnus-newsrc-options))))
+
+;; Parse options lines to find "options -n !all rec.all" and stuff.
+;; The return value will be a list on the form
+;; ((regexp1 . ignore)
+;;  (regexp2 . subscribe)...)
+;; When handling new newsgroups, groups that match a `ignore' regexp
+;; will be ignored, and groups that match a `subscribe' regexp will be
+;; subscribed.  A line like
+;; options -n !all rec.all
+;; will lead to a list that looks like
+;; (("^rec\\..+" . subscribe)
+;;  ("^.+" . ignore))
+;; So all "rec.*" groups will be subscribed, while all the other
+;; groups will be ignored.  Note that "options -n !all rec.all" is very
+;; different from "options -n rec.all !all".
+(defun gnus-newsrc-parse-options (options)
+  (let (out eol)
+    (save-excursion
+      (gnus-set-work-buffer)
+      (insert (regexp-quote options))
+      ;; First we treat all continuation lines.
+      (goto-char (point-min))
+      (while (re-search-forward "\n[ \t]+" nil t)
+	(replace-match " " t t))
+      ;; Then we transform all "all"s into ".+"s.
+      (goto-char (point-min))
+      (while (re-search-forward "\\ball\\b" nil t)
+	(replace-match ".+" t t))
+      (goto-char (point-min))
+      ;; We remove all other options than the "-n" ones.
+      (while (re-search-forward "[ \t]-[^n][^-]*" nil t)
+	(replace-match " ")
+	(forward-char -1))
+      (goto-char (point-min))
+
+      ;; We are only interested in "options -n" lines - we
+      ;; ignore the other option lines.
+      (while (re-search-forward "[ \t]-n" nil t)
+	(setq eol
+	      (or (save-excursion
+		    (and (re-search-forward "[ \t]-n" (gnus-point-at-eol) t)
+			 (- (point) 2)))
+		  (gnus-point-at-eol)))
+	;; Search for all "words"...
+	(while (re-search-forward "[^ \t,\n]+" eol t)
+	  (if (= (char-after (match-beginning 0)) ?!)
+	      ;; If the word begins with a bang (!), this is a "not"
+	      ;; spec.  We put this spec (minus the bang) and the
+	      ;; symbol `ignore' into the list.
+	      (push (cons (concat
+			   "^" (buffer-substring
+				(1+ (match-beginning 0))
+				(match-end 0)))
+			  'ignore)
+		    out)
+	    ;; There was no bang, so this is a "yes" spec.
+	    (push (cons (concat "^" (match-string 0))
+			'subscribe)
+		  out))))
+
+      (setq gnus-newsrc-options-n out))))
+
+(defun gnus-save-newsrc-file (&optional force)
+  "Save .newsrc file."
+  ;; Note: We cannot save .newsrc file if all newsgroups are removed
+  ;; from the variable gnus-newsrc-alist.
+  (when (and (or gnus-newsrc-alist gnus-killed-list)
+	     gnus-current-startup-file)
+    (save-excursion
+      (if (and (or gnus-use-dribble-file gnus-slave)
+	       (not force)
+	       (or (not gnus-dribble-buffer)
+		   (not (buffer-name gnus-dribble-buffer))
+		   (zerop (save-excursion
+			    (set-buffer gnus-dribble-buffer)
+			    (buffer-size)))))
+	  (gnus-message 4 "(No changes need to be saved)")
+	(run-hooks 'gnus-save-newsrc-hook)
+	(if gnus-slave
+	    (gnus-slave-save-newsrc)
+	  ;; Save .newsrc.
+	  (when gnus-save-newsrc-file
+	    (gnus-message 8 "Saving %s..." gnus-current-startup-file)
+	    (gnus-gnus-to-newsrc-format)
+	    (gnus-message 8 "Saving %s...done" gnus-current-startup-file))
+	  ;; Save .newsrc.eld.
+	  (set-buffer (get-buffer-create " *Gnus-newsrc*"))
+	  (make-local-variable 'version-control)
+	  (setq version-control 'never)
+	  (setq buffer-file-name
+		(concat gnus-current-startup-file ".eld"))
+	  (setq default-directory (file-name-directory buffer-file-name))
+	  (gnus-add-current-to-buffer-list)
+	  (buffer-disable-undo (current-buffer))
+	  (erase-buffer)
+	  (gnus-message 5 "Saving %s.eld..." gnus-current-startup-file)
+	  (gnus-gnus-to-quick-newsrc-format)
+	  (run-hooks 'gnus-save-quick-newsrc-hook)
+	  (save-buffer)
+	  (kill-buffer (current-buffer))
+	  (gnus-message
+	   5 "Saving %s.eld...done" gnus-current-startup-file))
+	(gnus-dribble-delete-file)
+	(gnus-group-set-mode-line)))))
+
+(defun gnus-gnus-to-quick-newsrc-format ()
+  "Insert Gnus variables such as gnus-newsrc-alist in lisp format."
+  (let ((print-quoted t))
+    (insert ";; -*- emacs-lisp -*-\n")
+    (insert ";; Gnus startup file.\n")
+    (insert
+     ";; Never delete this file - touch .newsrc instead to force Gnus\n")
+    (insert ";; to read .newsrc.\n")
+    (insert "(setq gnus-newsrc-file-version "
+	    (prin1-to-string gnus-version) ")\n")
+    (let* ((gnus-killed-list
+	    (if (and gnus-save-killed-list
+		     (stringp gnus-save-killed-list))
+		(gnus-strip-killed-list)
+	      gnus-killed-list))
+	   (variables
+	    (if gnus-save-killed-list gnus-variable-list
+	      ;; Remove the `gnus-killed-list' from the list of variables
+	      ;; to be saved, if required.
+	      (delq 'gnus-killed-list (copy-sequence gnus-variable-list))))
+	   ;; Peel off the "dummy" group.
+	   (gnus-newsrc-alist (cdr gnus-newsrc-alist))
+	   variable)
+      ;; Insert the variables into the file.
+      (while variables
+	(when (and (boundp (setq variable (pop variables)))
+		   (symbol-value variable))
+	  (insert "(setq " (symbol-name variable) " '")
+	  (gnus-prin1 (symbol-value variable))
+	  (insert ")\n"))))))
+
+(defun gnus-strip-killed-list ()
+  "Return the killed list minus the groups that match `gnus-save-killed-list'."
+  (let ((list gnus-killed-list)
+	olist)
+    (while list
+      (when (string-match gnus-save-killed-list)
+	(push (car list) olist))
+      (pop list))
+    (nreverse olist)))
+
+(defun gnus-gnus-to-newsrc-format ()
+  ;; Generate and save the .newsrc file.
+  (save-excursion
+    (set-buffer (create-file-buffer gnus-current-startup-file))
+    (let ((newsrc (cdr gnus-newsrc-alist))
+	  (standard-output (current-buffer))
+	  info ranges range method)
+      (setq buffer-file-name gnus-current-startup-file)
+      (setq default-directory (file-name-directory buffer-file-name))
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      ;; Write options.
+      (when gnus-newsrc-options
+	(insert gnus-newsrc-options))
+      ;; Write subscribed and unsubscribed.
+      (while (setq info (pop newsrc))
+	;; Don't write foreign groups to .newsrc.
+	(when (or (null (setq method (gnus-info-method info)))
+		  (equal method "native")
+		  (inline (gnus-server-equal method gnus-select-method)))
+	  (insert (gnus-info-group info)
+		  (if (> (gnus-info-level info) gnus-level-subscribed)
+		      "!" ":"))
+	  (when (setq ranges (gnus-info-read info))
+	    (insert " ")
+	    (if (not (listp (cdr ranges)))
+		(if (= (car ranges) (cdr ranges))
+		    (princ (car ranges))
+		  (princ (car ranges))
+		  (insert "-")
+		  (princ (cdr ranges)))
+	      (while (setq range (pop ranges))
+		(if (or (atom range) (= (car range) (cdr range)))
+		    (princ (or (and (atom range) range) (car range)))
+		  (princ (car range))
+		  (insert "-")
+		  (princ (cdr range)))
+		(when ranges
+		  (insert ",")))))
+	  (insert "\n")))
+      (make-local-variable 'version-control)
+      (setq version-control 'never)
+      ;; It has been reported that sometime the modtime on the .newsrc
+      ;; file seems to be off.  We really do want to overwrite it, so
+      ;; we clear the modtime here before saving.  It's a bit odd,
+      ;; though...
+      ;; sometimes the modtime clear isn't sufficient.  most brute force:
+      ;; delete the silly thing entirely first.  but this fails to provide
+      ;; such niceties as .newsrc~ creation.
+      (if gnus-modtime-botch
+	  (delete-file gnus-startup-file)
+	(clear-visited-file-modtime))
+      (run-hooks 'gnus-save-standard-newsrc-hook)
+      (save-buffer)
+      (kill-buffer (current-buffer)))))
+
+
+;;;
+;;; Slave functions.
+;;;
+
+(defun gnus-slave-save-newsrc ()
+  (save-excursion
+    (set-buffer gnus-dribble-buffer)
+    (let ((slave-name
+	   (make-temp-name (concat gnus-current-startup-file "-slave-")))
+	  (modes (ignore-errors
+		   (file-modes (concat gnus-current-startup-file ".eld")))))
+      (gnus-write-buffer slave-name)
+      (when modes
+	(set-file-modes slave-name modes)))))
+
+(defun gnus-master-read-slave-newsrc ()
+  (let ((slave-files
+	 (directory-files
+	  (file-name-directory gnus-current-startup-file)
+	  t (concat
+	     "^" (regexp-quote
+		  (concat
+		   (file-name-nondirectory gnus-current-startup-file)
+		   "-slave-")))
+	  t))
+	file)
+    (if (not slave-files)
+	()				; There are no slave files to read.
+      (gnus-message 7 "Reading slave newsrcs...")
+      (save-excursion
+	(set-buffer (get-buffer-create " *gnus slave*"))
+	(buffer-disable-undo (current-buffer))
+	(setq slave-files
+	      (sort (mapcar (lambda (file)
+			      (list (nth 5 (file-attributes file)) file))
+			    slave-files)
+		    (lambda (f1 f2)
+		      (or (< (caar f1) (caar f2))
+			  (< (nth 1 (car f1)) (nth 1 (car f2)))))))
+	(while slave-files
+	  (erase-buffer)
+	  (setq file (nth 1 (car slave-files)))
+	  (insert-file-contents file)
+	  (when (condition-case ()
+		    (progn
+		      (eval-buffer (current-buffer))
+		      t)
+		  (error
+		   (gnus-error 3.2 "Possible error in %s" file)
+		   nil))
+	    (unless gnus-slave		; Slaves shouldn't delete these files.
+	      (ignore-errors
+		(delete-file file))))
+	  (setq slave-files (cdr slave-files))))
+      (gnus-dribble-touch)
+      (gnus-message 7 "Reading slave newsrcs...done"))))
+
+
+;;;
+;;; Group description.
+;;;
+
+(defun gnus-read-all-descriptions-files ()
+  (let ((methods (cons gnus-select-method
+		       (nconc
+			(when (gnus-archive-server-wanted-p)
+			  (list "archive"))
+			gnus-secondary-select-methods))))
+    (while methods
+      (gnus-read-descriptions-file (car methods))
+      (setq methods (cdr methods)))
+    t))
+
+(defun gnus-read-descriptions-file (&optional method)
+  (let ((method (or method gnus-select-method))
+	group)
+    (when (stringp method)
+      (setq method (gnus-server-to-method method)))
+    ;; We create the hashtable whether we manage to read the desc file
+    ;; to avoid trying to re-read after a failed read.
+    (unless gnus-description-hashtb
+      (setq gnus-description-hashtb
+	    (gnus-make-hashtable (length gnus-active-hashtb))))
+    ;; Mark this method's desc file as read.
+    (gnus-sethash (gnus-group-prefixed-name "" method) "Has read"
+		  gnus-description-hashtb)
+
+    (gnus-message 5 "Reading descriptions file via %s..." (car method))
+    (cond
+     ((not (gnus-check-server method))
+      (gnus-message 1 "Couldn't open server")
+      nil)
+     ((not (gnus-request-list-newsgroups method))
+      (gnus-message 1 "Couldn't read newsgroups descriptions")
+      nil)
+     (t
+      (save-excursion
+	(save-restriction
+	  (set-buffer nntp-server-buffer)
+	  (goto-char (point-min))
+	  (when (or (search-forward "\n.\n" nil t)
+		    (goto-char (point-max)))
+	    (beginning-of-line)
+	    (narrow-to-region (point-min) (point)))
+	  ;; If these are groups from a foreign select method, we insert the
+	  ;; group prefix in front of the group names.
+	  (and method (not (inline
+			     (gnus-server-equal
+			      (gnus-server-get-method nil method)
+			      (gnus-server-get-method
+			       nil gnus-select-method))))
+	       (let ((prefix (gnus-group-prefixed-name "" method)))
+		 (goto-char (point-min))
+		 (while (and (not (eobp))
+			     (progn (insert prefix)
+				    (zerop (forward-line 1)))))))
+	  (goto-char (point-min))
+	  (while (not (eobp))
+	    ;; If we get an error, we set group to 0, which is not a
+	    ;; symbol...
+	    (setq group
+		  (condition-case ()
+		      (let ((obarray gnus-description-hashtb))
+			;; Group is set to a symbol interned in this
+			;; hash table.
+			(read nntp-server-buffer))
+		    (error 0)))
+	    (skip-chars-forward " \t")
+	    ;; ...  which leads to this line being effectively ignored.
+	    (when (symbolp group)
+	      (set group (buffer-substring
+			  (point) (progn (end-of-line) (point)))))
+	    (forward-line 1))))
+      (gnus-message 5 "Reading descriptions file...done")
+      t))))
+
+(defun gnus-group-get-description (group)
+  "Get the description of a group by sending XGTITLE to the server."
+  (when (gnus-request-group-description group)
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (goto-char (point-min))
+      (when (looking-at "[^ \t]+[ \t]+\\(.*\\)")
+	(match-string 1)))))
+
+;;;###autoload
+(defun gnus-declare-backend (name &rest abilities)
+  "Declare backend NAME with ABILITIES as a Gnus backend."
+  (setq gnus-valid-select-methods
+	(nconc gnus-valid-select-methods
+	       (list (apply 'list name abilities)))))
+
+(defun gnus-set-default-directory ()
+  "Set the default directory in the current buffer to `gnus-default-directory'.
+If this variable is nil, don't do anything."
+  (setq default-directory
+	(if (and gnus-default-directory
+		 (file-exists-p gnus-default-directory))
+	    (file-name-as-directory (expand-file-name gnus-default-directory))
+	  default-directory)))
+
+(provide 'gnus-start)
+
+;;; gnus-start.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-sum.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,8686 @@
+;;; gnus-sum.el --- summary mode commands for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-group)
+(require 'gnus-spec)
+(require 'gnus-range)
+(require 'gnus-int)
+(require 'gnus-undo)
+
+(defcustom gnus-kill-summary-on-exit t
+  "*If non-nil, kill the summary buffer when you exit from it.
+If nil, the summary will become a \"*Dead Summary*\" buffer, and
+it will be killed sometime later."
+  :group 'gnus-summary-exit
+  :type 'boolean)
+
+(defcustom gnus-fetch-old-headers nil
+  "*Non-nil means that Gnus will try to build threads by grabbing old headers.
+If an unread article in the group refers to an older, already read (or
+just marked as read) article, the old article will not normally be
+displayed in the Summary buffer.  If this variable is non-nil, Gnus
+will attempt to grab the headers to the old articles, and thereby
+build complete threads.	 If it has the value `some', only enough
+headers to connect otherwise loose threads will be displayed.
+This variable can also be a number.  In that case, no more than that
+number of old headers will be fetched.
+
+The server has to support NOV for any of this to work."
+  :group 'gnus-thread
+  :type '(choice (const :tag "off" nil)
+		 (const some)
+		 number
+		 (sexp :menu-tag "other" t)))
+
+(defcustom gnus-summary-make-false-root 'adopt
+  "*nil means that Gnus won't gather loose threads.
+If the root of a thread has expired or been read in a previous
+session, the information necessary to build a complete thread has been
+lost.  Instead of having many small sub-threads from this original thread
+scattered all over the summary buffer, Gnus can gather them.
+
+If non-nil, Gnus will try to gather all loose sub-threads from an
+original thread into one large thread.
+
+If this variable is non-nil, it should be one of `none', `adopt',
+`dummy' or `empty'.
+
+If this variable is `none', Gnus will not make a false root, but just
+present the sub-threads after another.
+If this variable is `dummy', Gnus will create a dummy root that will
+have all the sub-threads as children.
+If this variable is `adopt', Gnus will make one of the \"children\"
+the parent and mark all the step-children as such.
+If this variable is `empty', the \"children\" are printed with empty
+subject fields.	 (Or rather, they will be printed with a string
+given by the `gnus-summary-same-subject' variable.)"
+  :group 'gnus-thread
+  :type '(choice (const :tag "off" nil)
+		 (const none)
+		 (const dummy)
+		 (const adopt)
+		 (const empty)))
+
+(defcustom gnus-summary-gather-exclude-subject "^ *$\\|^(none)$"
+  "*A regexp to match subjects to be excluded from loose thread gathering.
+As loose thread gathering is done on subjects only, that means that
+there can be many false gatherings performed.  By rooting out certain
+common subjects, gathering might become saner."
+  :group 'gnus-thread
+  :type 'regexp)
+
+(defcustom gnus-summary-gather-subject-limit nil
+  "*Maximum length of subject comparisons when gathering loose threads.
+Use nil to compare full subjects.  Setting this variable to a low
+number will help gather threads that have been corrupted by
+newsreaders chopping off subject lines, but it might also mean that
+unrelated articles that have subject that happen to begin with the
+same few characters will be incorrectly gathered.
+
+If this variable is `fuzzy', Gnus will use a fuzzy algorithm when
+comparing subjects."
+  :group 'gnus-thread
+  :type '(choice (const :tag "off" nil)
+		 (const fuzzy)
+		 (sexp :menu-tag "on" t)))
+
+(defcustom gnus-simplify-ignored-prefixes nil
+  "*Regexp, matches for which are removed from subject lines when simplifying fuzzily."
+  :group 'gnus-thread
+  :type '(choice (const :tag "off" nil)
+		 regexp))
+
+(defcustom gnus-build-sparse-threads nil
+  "*If non-nil, fill in the gaps in threads.
+If `some', only fill in the gaps that are needed to tie loose threads
+together.  If `more', fill in all leaf nodes that Gnus can find.  If
+non-nil and non-`some', fill in all gaps that Gnus manages to guess."
+  :group 'gnus-thread
+  :type '(choice (const :tag "off" nil)
+		 (const some)
+		 (const more)
+		 (sexp :menu-tag "all" t)))
+
+(defcustom gnus-summary-thread-gathering-function
+  'gnus-gather-threads-by-subject
+  "Function used for gathering loose threads.
+There are two pre-defined functions: `gnus-gather-threads-by-subject',
+which only takes Subjects into consideration; and
+`gnus-gather-threads-by-references', which compared the References
+headers of the articles to find matches."
+  :group 'gnus-thread
+  :type '(set (function-item gnus-gather-threads-by-subject)
+	      (function-item gnus-gather-threads-by-references)
+	      (function :tag "other")))
+
+;; Added by Per Abrahamsen <amanda@iesd.auc.dk>.
+(defcustom gnus-summary-same-subject ""
+  "*String indicating that the current article has the same subject as the previous.
+This variable will only be used if the value of
+`gnus-summary-make-false-root' is `empty'."
+  :group 'gnus-summary-format
+  :type 'string)
+
+(defcustom gnus-summary-goto-unread t
+  "*If t, marking commands will go to the next unread article.
+If `never', commands that usually go to the next unread article, will
+go to the next article, whether it is read or not.
+If nil, only the marking commands will go to the next (un)read article."
+  :group 'gnus-summary-marks
+  :link '(custom-manual "(gnus)Setting Marks")
+  :type '(choice (const :tag "off" nil)
+		 (const never)
+		 (sexp :menu-tag "on" t)))
+
+(defcustom gnus-summary-default-score 0
+  "*Default article score level.
+All scores generated by the score files will be added to this score.
+If this variable is nil, scoring will be disabled."
+  :group 'gnus-score-default
+  :type '(choice (const :tag "disable")
+		 integer))
+
+(defcustom gnus-summary-zcore-fuzz 0
+  "*Fuzziness factor for the zcore in the summary buffer.
+Articles with scores closer than this to `gnus-summary-default-score'
+will not be marked."
+  :group 'gnus-summary-format
+  :type 'integer)
+
+(defcustom gnus-simplify-subject-fuzzy-regexp nil
+  "*Strings to be removed when doing fuzzy matches.
+This can either be a regular expression or list of regular expressions
+that will be removed from subject strings if fuzzy subject
+simplification is selected."
+  :group 'gnus-thread
+  :type '(repeat regexp))
+
+(defcustom gnus-show-threads t
+  "*If non-nil, display threads in summary mode."
+  :group 'gnus-thread
+  :type 'boolean)
+
+(defcustom gnus-thread-hide-subtree nil
+  "*If non-nil, hide all threads initially.
+If threads are hidden, you have to run the command
+`gnus-summary-show-thread' by hand or use `gnus-select-article-hook'
+to expose hidden threads."
+  :group 'gnus-thread
+  :type 'boolean)
+
+(defcustom gnus-thread-hide-killed t
+  "*If non-nil, hide killed threads automatically."
+  :group 'gnus-thread
+  :type 'boolean)
+
+(defcustom gnus-thread-ignore-subject nil
+  "*If non-nil, ignore subjects and do all threading based on the Reference header.
+If nil, which is the default, articles that have different subjects
+from their parents will start separate threads."
+  :group 'gnus-thread
+  :type 'boolean)
+
+(defcustom gnus-thread-operation-ignore-subject t
+  "*If non-nil, subjects will be ignored when doing thread commands.
+This affects commands like `gnus-summary-kill-thread' and
+`gnus-summary-lower-thread'.
+
+If this variable is nil, articles in the same thread with different
+subjects will not be included in the operation in question.  If this
+variable is `fuzzy', only articles that have subjects that are fuzzily
+equal will be included."
+  :group 'gnus-thread
+  :type '(choice (const :tag "off" nil)
+		 (const fuzzy)
+		 (sexp :tag "on" t)))
+
+(defcustom gnus-thread-indent-level 4
+  "*Number that says how much each sub-thread should be indented."
+  :group 'gnus-thread
+  :type 'integer)
+
+(defcustom gnus-auto-extend-newsgroup t
+  "*If non-nil, extend newsgroup forward and backward when requested."
+  :group 'gnus-summary-choose
+  :type 'boolean)
+
+(defcustom gnus-auto-select-first t
+  "*If nil, don't select the first unread article when entering a group.
+If this variable is `best', select the highest-scored unread article
+in the group.  If neither nil nor `best', select the first unread
+article.
+
+If you want to prevent automatic selection of the first unread article
+in some newsgroups, set the variable to nil in
+`gnus-select-group-hook'."
+  :group 'gnus-group-select
+  :type '(choice (const :tag "none" nil)
+		 (const best)
+		 (sexp :menu-tag "first" t)))
+
+(defcustom gnus-auto-select-next t
+  "*If non-nil, offer to go to the next group from the end of the previous.
+If the value is t and the next newsgroup is empty, Gnus will exit
+summary mode and go back to group mode.	 If the value is neither nil
+nor t, Gnus will select the following unread newsgroup.	 In
+particular, if the value is the symbol `quietly', the next unread
+newsgroup will be selected without any confirmation, and if it is
+`almost-quietly', the next group will be selected without any
+confirmation if you are located on the last article in the group.
+Finally, if this variable is `slightly-quietly', the `Z n' command
+will go to the next group without confirmation."
+  :group 'gnus-summary-maneuvering
+  :type '(choice (const :tag "off" nil)
+		 (const quietly)
+		 (const almost-quietly)
+		 (const slightly-quietly)
+		 (sexp :menu-tag "on" t)))
+
+(defcustom gnus-auto-select-same nil
+  "*If non-nil, select the next article with the same subject."
+  :group 'gnus-summary-maneuvering
+  :type 'boolean)
+
+(defcustom gnus-summary-check-current nil
+  "*If non-nil, consider the current article when moving.
+The \"unread\" movement commands will stay on the same line if the
+current article is unread."
+  :group 'gnus-summary-maneuvering
+  :type 'boolean)
+
+(defcustom gnus-auto-center-summary t
+  "*If non-nil, always center the current summary buffer.
+In particular, if `vertical' do only vertical recentering.  If non-nil
+and non-`vertical', do both horizontal and vertical recentering."
+  :group 'gnus-summary-maneuvering
+  :type '(choice (const :tag "none" nil)
+		 (const vertical)
+		 (sexp :menu-tag "both" t)))
+
+(defcustom gnus-show-all-headers nil
+  "*If non-nil, don't hide any headers."
+  :group 'gnus-article-hiding
+  :group 'gnus-article-headers
+  :type 'boolean)
+
+(defcustom gnus-summary-ignore-duplicates nil
+  "*If non-nil, ignore articles with identical Message-ID headers."
+  :group 'gnus-summary
+  :type 'boolean)
+  
+(defcustom gnus-single-article-buffer t
+  "*If non-nil, display all articles in the same buffer.
+If nil, each group will get its own article buffer."
+  :group 'gnus-article-various
+  :type 'boolean)
+
+(defcustom gnus-break-pages t
+  "*If non-nil, do page breaking on articles.
+The page delimiter is specified by the `gnus-page-delimiter'
+variable."
+  :group 'gnus-article-various
+  :type 'boolean)
+
+(defcustom gnus-show-mime nil
+  "*If non-nil, do mime processing of articles.
+The articles will simply be fed to the function given by
+`gnus-show-mime-method'."
+  :group 'gnus-article-mime
+  :type 'boolean)
+
+(defcustom gnus-move-split-methods nil
+  "*Variable used to suggest where articles are to be moved to.
+It uses the same syntax as the `gnus-split-methods' variable."
+  :group 'gnus-summary-mail
+  :type '(repeat (choice (list function)
+			 (cons regexp (repeat string))
+			 sexp)))
+
+(defcustom gnus-unread-mark ? 
+  "*Mark used for unread articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-ticked-mark ?!
+  "*Mark used for ticked articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-dormant-mark ??
+  "*Mark used for dormant articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-del-mark ?r
+  "*Mark used for del'd articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-read-mark ?R
+  "*Mark used for read articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-expirable-mark ?E
+  "*Mark used for expirable articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-killed-mark ?K
+  "*Mark used for killed articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-souped-mark ?F
+  "*Mark used for killed articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-kill-file-mark ?X
+  "*Mark used for articles killed by kill files."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-low-score-mark ?Y
+  "*Mark used for articles with a low score."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-catchup-mark ?C
+  "*Mark used for articles that are caught up."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-replied-mark ?A
+  "*Mark used for articles that have been replied to."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-cached-mark ?*
+  "*Mark used for articles that are in the cache."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-saved-mark ?S
+  "*Mark used for articles that have been saved to."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-ancient-mark ?O
+  "*Mark used for ancient articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-sparse-mark ?Q
+  "*Mark used for sparsely reffed articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-canceled-mark ?G
+  "*Mark used for canceled articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-duplicate-mark ?M
+  "*Mark used for duplicate articles."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-score-over-mark ?+
+  "*Score mark used for articles with high scores."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-score-below-mark ?-
+  "*Score mark used for articles with low scores."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-empty-thread-mark ? 
+  "*There is no thread under the article."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-not-empty-thread-mark ?=
+  "*There is a thread under the article."
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-view-pseudo-asynchronously nil
+  "*If non-nil, Gnus will view pseudo-articles asynchronously."
+  :group 'gnus-extract-view
+  :type 'boolean)
+
+(defcustom gnus-view-pseudos nil
+  "*If `automatic', pseudo-articles will be viewed automatically.
+If `not-confirm', pseudos will be viewed automatically, and the user
+will not be asked to confirm the command."
+  :group 'gnus-extract-view
+  :type '(choice (const :tag "off" nil)
+		 (const automatic)
+		 (const not-confirm)))
+
+(defcustom gnus-view-pseudos-separately t
+  "*If non-nil, one pseudo-article will be created for each file to be viewed.
+If nil, all files that use the same viewing command will be given as a
+list of parameters to that command."
+  :group 'gnus-extract-view
+  :type 'boolean)
+
+(defcustom gnus-insert-pseudo-articles t
+  "*If non-nil, insert pseudo-articles when decoding articles."
+  :group 'gnus-extract-view
+  :type 'boolean)
+
+(defcustom gnus-summary-dummy-line-format
+  "*  %(:                          :%) %S\n"
+  "*The format specification for the dummy roots in the summary buffer.
+It works along the same lines as a normal formatting string,
+with some simple extensions.
+
+%S  The subject"
+  :group 'gnus-threading
+  :type 'string)
+
+(defcustom gnus-summary-mode-line-format "Gnus: %%b [%A] %Z"
+  "*The format specification for the summary mode line.
+It works along the same lines as a normal formatting string,
+with some simple extensions:
+
+%G  Group name
+%p  Unprefixed group name
+%A  Current article number
+%V  Gnus version
+%U  Number of unread articles in the group
+%e  Number of unselected articles in the group
+%Z  A string with unread/unselected article counts
+%g  Shortish group name
+%S  Subject of the current article
+%u  User-defined spec
+%s  Current score file name
+%d  Number of dormant articles
+%r  Number of articles that have been marked as read in this session
+%E  Number of articles expunged by the score files"
+  :group 'gnus-summary-format
+  :type 'string)
+
+(defcustom gnus-summary-mark-below 0
+  "*Mark all articles with a score below this variable as read.
+This variable is local to each summary buffer and usually set by the
+score file."
+  :group 'gnus-score-default
+  :type 'integer)
+
+(defcustom gnus-article-sort-functions '(gnus-article-sort-by-number)
+  "*List of functions used for sorting articles in the summary buffer.
+This variable is only used when not using a threaded display."
+  :group 'gnus-summary-sort
+  :type '(repeat (choice (function-item gnus-article-sort-by-number)
+			 (function-item gnus-article-sort-by-author)
+			 (function-item gnus-article-sort-by-subject)
+			 (function-item gnus-article-sort-by-date)
+			 (function-item gnus-article-sort-by-score)
+			 (function :tag "other"))))
+
+(defcustom gnus-thread-sort-functions '(gnus-thread-sort-by-number)
+  "*List of functions used for sorting threads in the summary buffer.
+By default, threads are sorted by article number.
+
+Each function takes two threads and return non-nil if the first thread
+should be sorted before the other.  If you use more than one function,
+the primary sort function should be the last.  You should probably
+always include `gnus-thread-sort-by-number' in the list of sorting
+functions -- preferably first.
+
+Ready-made functions include `gnus-thread-sort-by-number',
+`gnus-thread-sort-by-author', `gnus-thread-sort-by-subject',
+`gnus-thread-sort-by-date', `gnus-thread-sort-by-score' and
+`gnus-thread-sort-by-total-score' (see `gnus-thread-score-function')."
+  :group 'gnus-summary-sort
+  :type '(repeat (choice (function-item gnus-thread-sort-by-number)
+			 (function-item gnus-thread-sort-by-author)
+			 (function-item gnus-thread-sort-by-subject)
+			 (function-item gnus-thread-sort-by-date)
+			 (function-item gnus-thread-sort-by-score)
+			 (function-item gnus-thread-sort-by-total-score)
+			 (function :tag "other"))))
+
+(defcustom gnus-thread-score-function '+
+  "*Function used for calculating the total score of a thread.
+
+The function is called with the scores of the article and each
+subthread and should then return the score of the thread.
+
+Some functions you can use are `+', `max', or `min'."
+  :group 'gnus-summary-sort
+  :type 'function)
+
+(defcustom gnus-summary-expunge-below nil
+  "All articles that have a score less than this variable will be expunged."
+  :group 'gnus-score-default
+  :type '(choice (const :tag "off" nil)
+		 integer))
+
+(defcustom gnus-thread-expunge-below nil
+  "All threads that have a total score less than this variable will be expunged.
+See `gnus-thread-score-function' for en explanation of what a
+\"thread score\" is."
+  :group 'gnus-treading
+  :group 'gnus-score-default
+  :type '(choice (const :tag "off" nil)
+		 integer))
+
+(defcustom gnus-summary-mode-hook nil
+  "*A hook for Gnus summary mode.
+This hook is run before any variables are set in the summary buffer."
+  :group 'gnus-summary-various
+  :type 'hook)
+
+(defcustom gnus-summary-menu-hook nil
+  "*Hook run after the creation of the summary mode menu."
+  :group 'gnus-summary-visual
+  :type 'hook)
+
+(defcustom gnus-summary-exit-hook nil
+  "*A hook called on exit from the summary buffer.
+It will be called with point in the group buffer."
+  :group 'gnus-summary-exit
+  :type 'hook)
+
+(defcustom gnus-summary-prepare-hook nil
+  "*A hook called after the summary buffer has been generated.
+If you want to modify the summary buffer, you can use this hook."
+  :group 'gnus-summary-various
+  :type 'hook)
+
+(defcustom gnus-summary-generate-hook nil
+  "*A hook run just before generating the summary buffer.
+This hook is commonly used to customize threading variables and the
+like."
+  :group 'gnus-summary-various
+  :type 'hook)
+
+(defcustom gnus-select-group-hook nil
+  "*A hook called when a newsgroup is selected.
+
+If you'd like to simplify subjects like the
+`gnus-summary-next-same-subject' command does, you can use the
+following hook:
+
+ (setq gnus-select-group-hook
+      (list
+	(lambda ()
+	  (mapcar (lambda (header)
+		     (mail-header-set-subject
+		      header
+		      (gnus-simplify-subject
+		       (mail-header-subject header) 're-only)))
+		  gnus-newsgroup-headers))))"
+  :group 'gnus-group-select
+  :type 'hook)
+
+(defcustom gnus-select-article-hook nil
+  "*A hook called when an article is selected."
+  :group 'gnus-summary-choose
+  :type 'hook)
+
+(defcustom gnus-visual-mark-article-hook
+  (list 'gnus-highlight-selected-summary)
+  "*Hook run after selecting an article in the summary buffer.
+It is meant to be used for highlighting the article in some way.  It
+is not run if `gnus-visual' is nil."
+  :group 'gnus-summary-visual
+  :type 'hook)
+
+(defcustom gnus-parse-headers-hook
+  (list 'gnus-decode-rfc1522)
+  "*A hook called before parsing the headers."
+  :group 'gnus-various
+  :type 'hook)
+
+(defcustom gnus-exit-group-hook nil
+  "*A hook called when exiting (not quitting) summary mode."
+  :group 'gnus-various
+  :type 'hook)
+
+(defcustom gnus-summary-update-hook
+  (list 'gnus-summary-highlight-line)
+  "*A hook called when a summary line is changed.
+The hook will not be called if `gnus-visual' is nil.
+
+The default function `gnus-summary-highlight-line' will
+highlight the line according to the `gnus-summary-highlight'
+variable."
+  :group 'gnus-summary-visual
+  :type 'hook)
+
+(defcustom gnus-mark-article-hook '(gnus-summary-mark-read-and-unread-as-read)
+  "*A hook called when an article is selected for the first time.
+The hook is intended to mark an article as read (or unread)
+automatically when it is selected."
+  :group 'gnus-summary-choose
+  :type 'hook)
+
+(defcustom gnus-group-no-more-groups-hook nil
+  "*A hook run when returning to group mode having no more (unread) groups."
+  :group 'gnus-group-select
+  :type 'hook)
+
+(defcustom gnus-ps-print-hook nil
+  "*A hook run before ps-printing something from Gnus."
+  :group 'gnus-summary
+  :type 'hook)
+
+(defcustom gnus-summary-selected-face 'gnus-summary-selected-face
+  "Face used for highlighting the current article in the summary buffer."
+  :group 'gnus-summary-visual
+  :type 'face)
+
+(defcustom gnus-summary-highlight
+  '(((= mark gnus-canceled-mark)
+     . gnus-summary-cancelled-face)
+    ((and (> score default)
+	  (or (= mark gnus-dormant-mark)
+	      (= mark gnus-ticked-mark)))
+     . gnus-summary-high-ticked-face)
+    ((and (< score default)
+	  (or (= mark gnus-dormant-mark)
+	      (= mark gnus-ticked-mark)))
+     . gnus-summary-low-ticked-face)
+    ((or (= mark gnus-dormant-mark)
+	 (= mark gnus-ticked-mark))
+     . gnus-summary-normal-ticked-face)
+    ((and (> score default) (= mark gnus-ancient-mark))
+     . gnus-summary-high-ancient-face)
+    ((and (< score default) (= mark gnus-ancient-mark))
+     . gnus-summary-low-ancient-face)
+    ((= mark gnus-ancient-mark)
+     . gnus-summary-normal-ancient-face)
+    ((and (> score default) (= mark gnus-unread-mark))
+     . gnus-summary-high-unread-face)
+    ((and (< score default) (= mark gnus-unread-mark))
+     . gnus-summary-low-unread-face)
+    ((and (= mark gnus-unread-mark))
+     . gnus-summary-normal-unread-face)
+    ((> score default)
+     . gnus-summary-high-read-face)
+    ((< score default)
+     . gnus-summary-low-read-face)
+    (t
+     . gnus-summary-normal-read-face))
+  "Controls the highlighting of summary buffer lines.
+
+A list of (FORM . FACE) pairs.  When deciding how a a particular
+summary line should be displayed, each form is evaluated.  The content
+of the face field after the first true form is used.  You can change
+how those summary lines are displayed, by editing the face field.
+
+You can use the following variables in the FORM field.
+
+score:   The articles score
+default: The default article score.
+below:   The score below which articles are automatically marked as read.
+mark:    The articles mark."
+  :group 'gnus-summary-visual
+  :type '(repeat (cons (sexp :tag "Form" nil)
+		       face)))
+
+
+;;; Internal variables
+
+(defvar gnus-scores-exclude-files nil)
+(defvar gnus-page-broken nil)
+
+(defvar gnus-original-article nil)
+(defvar gnus-article-internal-prepare-hook nil)
+(defvar gnus-newsgroup-process-stack nil)
+
+(defvar gnus-thread-indent-array nil)
+(defvar gnus-thread-indent-array-level gnus-thread-indent-level)
+
+;; Avoid highlighting in kill files.
+(defvar gnus-summary-inhibit-highlight nil)
+(defvar gnus-newsgroup-selected-overlay nil)
+(defvar gnus-inhibit-limiting nil)
+(defvar gnus-newsgroup-adaptive-score-file nil)
+(defvar gnus-current-score-file nil)
+(defvar gnus-current-move-group nil)
+(defvar gnus-current-copy-group nil)
+(defvar gnus-current-crosspost-group nil)
+
+(defvar gnus-newsgroup-dependencies nil)
+(defvar gnus-newsgroup-adaptive nil)
+(defvar gnus-summary-display-article-function nil)
+(defvar gnus-summary-highlight-line-function nil
+  "Function called after highlighting a summary line.")
+
+(defvar gnus-summary-line-format-alist
+  `((?N ,(macroexpand '(mail-header-number gnus-tmp-header)) ?d)
+    (?S ,(macroexpand '(mail-header-subject gnus-tmp-header)) ?s)
+    (?s gnus-tmp-subject-or-nil ?s)
+    (?n gnus-tmp-name ?s)
+    (?A (car (cdr (funcall gnus-extract-address-components gnus-tmp-from)))
+	?s)
+    (?a (or (car (funcall gnus-extract-address-components gnus-tmp-from))
+	    gnus-tmp-from) ?s)
+    (?F gnus-tmp-from ?s)
+    (?x ,(macroexpand '(mail-header-xref gnus-tmp-header)) ?s)
+    (?D ,(macroexpand '(mail-header-date gnus-tmp-header)) ?s)
+    (?d (gnus-dd-mmm (mail-header-date gnus-tmp-header)) ?s)
+    (?o (gnus-date-iso8601 gnus-tmp-header) ?s)
+    (?M ,(macroexpand '(mail-header-id gnus-tmp-header)) ?s)
+    (?r ,(macroexpand '(mail-header-references gnus-tmp-header)) ?s)
+    (?c (or (mail-header-chars gnus-tmp-header) 0) ?d)
+    (?L gnus-tmp-lines ?d)
+    (?I gnus-tmp-indentation ?s)
+    (?T (if (= gnus-tmp-level 0) "" (make-string (frame-width) ? )) ?s)
+    (?R gnus-tmp-replied ?c)
+    (?\[ gnus-tmp-opening-bracket ?c)
+    (?\] gnus-tmp-closing-bracket ?c)
+    (?\> (make-string gnus-tmp-level ? ) ?s)
+    (?\< (make-string (max 0 (- 20 gnus-tmp-level)) ? ) ?s)
+    (?i gnus-tmp-score ?d)
+    (?z gnus-tmp-score-char ?c)
+    (?l (bbb-grouplens-score gnus-tmp-header) ?s)
+    (?V (gnus-thread-total-score (and (boundp 'thread) (car thread))) ?d)
+    (?U gnus-tmp-unread ?c)
+    (?t (gnus-summary-number-of-articles-in-thread
+	 (and (boundp 'thread) (car thread)) gnus-tmp-level)
+	?d)
+    (?e (gnus-summary-number-of-articles-in-thread
+	 (and (boundp 'thread) (car thread)) gnus-tmp-level t)
+	?c)
+    (?u gnus-tmp-user-defined ?s)
+    (?P (gnus-pick-line-number) ?d))
+  "An alist of format specifications that can appear in summary lines,
+and what variables they correspond with, along with the type of the
+variable (string, integer, character, etc).")
+
+(defvar gnus-summary-dummy-line-format-alist
+  `((?S gnus-tmp-subject ?s)
+    (?N gnus-tmp-number ?d)
+    (?u gnus-tmp-user-defined ?s)))
+
+(defvar gnus-summary-mode-line-format-alist
+  `((?G gnus-tmp-group-name ?s)
+    (?g (gnus-short-group-name gnus-tmp-group-name) ?s)
+    (?p (gnus-group-real-name gnus-tmp-group-name) ?s)
+    (?A gnus-tmp-article-number ?d)
+    (?Z gnus-tmp-unread-and-unselected ?s)
+    (?V gnus-version ?s)
+    (?U gnus-tmp-unread-and-unticked ?d)
+    (?S gnus-tmp-subject ?s)
+    (?e gnus-tmp-unselected ?d)
+    (?u gnus-tmp-user-defined ?s)
+    (?d (length gnus-newsgroup-dormant) ?d)
+    (?t (length gnus-newsgroup-marked) ?d)
+    (?r (length gnus-newsgroup-reads) ?d)
+    (?E gnus-newsgroup-expunged-tally ?d)
+    (?s (gnus-current-score-file-nondirectory) ?s)))
+
+(defvar gnus-last-search-regexp nil
+  "Default regexp for article search command.")
+
+(defvar gnus-last-shell-command nil
+  "Default shell command on article.")
+
+(defvar gnus-newsgroup-begin nil)
+(defvar gnus-newsgroup-end nil)
+(defvar gnus-newsgroup-last-rmail nil)
+(defvar gnus-newsgroup-last-mail nil)
+(defvar gnus-newsgroup-last-folder nil)
+(defvar gnus-newsgroup-last-file nil)
+(defvar gnus-newsgroup-auto-expire nil)
+(defvar gnus-newsgroup-active nil)
+
+(defvar gnus-newsgroup-data nil)
+(defvar gnus-newsgroup-data-reverse nil)
+(defvar gnus-newsgroup-limit nil)
+(defvar gnus-newsgroup-limits nil)
+
+(defvar gnus-newsgroup-unreads nil
+  "List of unread articles in the current newsgroup.")
+
+(defvar gnus-newsgroup-unselected nil
+  "List of unselected unread articles in the current newsgroup.")
+
+(defvar gnus-newsgroup-reads nil
+  "Alist of read articles and article marks in the current newsgroup.")
+
+(defvar gnus-newsgroup-expunged-tally nil)
+
+(defvar gnus-newsgroup-marked nil
+  "List of ticked articles in the current newsgroup (a subset of unread art).")
+
+(defvar gnus-newsgroup-killed nil
+  "List of ranges of articles that have been through the scoring process.")
+
+(defvar gnus-newsgroup-cached nil
+  "List of articles that come from the article cache.")
+
+(defvar gnus-newsgroup-saved nil
+  "List of articles that have been saved.")
+
+(defvar gnus-newsgroup-kill-headers nil)
+
+(defvar gnus-newsgroup-replied nil
+  "List of articles that have been replied to in the current newsgroup.")
+
+(defvar gnus-newsgroup-expirable nil
+  "List of articles in the current newsgroup that can be expired.")
+
+(defvar gnus-newsgroup-processable nil
+  "List of articles in the current newsgroup that can be processed.")
+
+(defvar gnus-newsgroup-bookmarks nil
+  "List of articles in the current newsgroup that have bookmarks.")
+
+(defvar gnus-newsgroup-dormant nil
+  "List of dormant articles in the current newsgroup.")
+
+(defvar gnus-newsgroup-scored nil
+  "List of scored articles in the current newsgroup.")
+
+(defvar gnus-newsgroup-headers nil
+  "List of article headers in the current newsgroup.")
+
+(defvar gnus-newsgroup-threads nil)
+
+(defvar gnus-newsgroup-prepared nil
+  "Whether the current group has been prepared properly.")
+
+(defvar gnus-newsgroup-ancient nil
+  "List of `gnus-fetch-old-headers' articles in the current newsgroup.")
+
+(defvar gnus-newsgroup-sparse nil)
+
+(defvar gnus-current-article nil)
+(defvar gnus-article-current nil)
+(defvar gnus-current-headers nil)
+(defvar gnus-have-all-headers nil)
+(defvar gnus-last-article nil)
+(defvar gnus-newsgroup-history nil)
+
+(defconst gnus-summary-local-variables
+  '(gnus-newsgroup-name
+    gnus-newsgroup-begin gnus-newsgroup-end
+    gnus-newsgroup-last-rmail gnus-newsgroup-last-mail
+    gnus-newsgroup-last-folder gnus-newsgroup-last-file
+    gnus-newsgroup-auto-expire gnus-newsgroup-unreads
+    gnus-newsgroup-unselected gnus-newsgroup-marked
+    gnus-newsgroup-reads gnus-newsgroup-saved
+    gnus-newsgroup-replied gnus-newsgroup-expirable
+    gnus-newsgroup-processable gnus-newsgroup-killed
+    gnus-newsgroup-bookmarks gnus-newsgroup-dormant
+    gnus-newsgroup-headers gnus-newsgroup-threads
+    gnus-newsgroup-prepared gnus-summary-highlight-line-function
+    gnus-current-article gnus-current-headers gnus-have-all-headers
+    gnus-last-article gnus-article-internal-prepare-hook
+    gnus-newsgroup-dependencies gnus-newsgroup-selected-overlay
+    gnus-newsgroup-scored gnus-newsgroup-kill-headers
+    gnus-thread-expunge-below
+    gnus-score-alist gnus-current-score-file gnus-summary-expunge-below
+    (gnus-summary-mark-below . global)
+    gnus-newsgroup-active gnus-scores-exclude-files
+    gnus-newsgroup-history gnus-newsgroup-ancient
+    gnus-newsgroup-sparse gnus-newsgroup-process-stack
+    (gnus-newsgroup-adaptive . gnus-use-adaptive-scoring)
+    gnus-newsgroup-adaptive-score-file (gnus-reffed-article-number . -1)
+    (gnus-newsgroup-expunged-tally . 0)
+    gnus-cache-removable-articles gnus-newsgroup-cached
+    gnus-newsgroup-data gnus-newsgroup-data-reverse
+    gnus-newsgroup-limit gnus-newsgroup-limits)
+  "Variables that are buffer-local to the summary buffers.")
+
+;; Byte-compiler warning.
+(defvar gnus-article-mode-map)
+
+;; Subject simplification.
+
+(defsubst gnus-simplify-subject-re (subject)
+  "Remove \"Re:\" from subject lines."
+  (if (string-match "^[Rr][Ee]: *" subject)
+      (substring subject (match-end 0))
+    subject))
+
+(defun gnus-simplify-subject (subject &optional re-only)
+  "Remove `Re:' and words in parentheses.
+If RE-ONLY is non-nil, strip leading `Re:'s only."
+  (let ((case-fold-search t))		;Ignore case.
+    ;; Remove `Re:', `Re^N:', `Re(n)', and `Re[n]:'.
+    (when (string-match "\\`\\(re\\([[(^][0-9]+[])]?\\)?:[ \t]*\\)+" subject)
+      (setq subject (substring subject (match-end 0))))
+    ;; Remove uninteresting prefixes.
+    (when (and (not re-only)
+	       gnus-simplify-ignored-prefixes
+	       (string-match gnus-simplify-ignored-prefixes subject))
+      (setq subject (substring subject (match-end 0))))
+    ;; Remove words in parentheses from end.
+    (unless re-only
+      (while (string-match "[ \t\n]*([^()]*)[ \t\n]*\\'" subject)
+	(setq subject (substring subject 0 (match-beginning 0)))))
+    ;; Return subject string.
+    subject))
+
+;; Remove any leading "re:"s, any trailing paren phrases, and simplify
+;; all whitespace.
+(defsubst gnus-simplify-buffer-fuzzy-step (regexp &optional newtext)
+  (goto-char (point-min))
+  (while (re-search-forward regexp nil t)
+      (replace-match (or newtext ""))))
+
+(defun gnus-simplify-buffer-fuzzy ()
+  "Simplify string in the buffer fuzzily.
+The string in the accessible portion of the current buffer is simplified.
+It is assumed to be a single-line subject.
+Whitespace is generally cleaned up, and miscellaneous leading/trailing
+matter is removed.  Additional things can be deleted by setting
+gnus-simplify-subject-fuzzy-regexp."
+  (let ((case-fold-search t)
+	(modified-tick))
+    (gnus-simplify-buffer-fuzzy-step "\t" " ")
+
+    (while (not (eq modified-tick (buffer-modified-tick)))
+      (setq modified-tick (buffer-modified-tick))
+      (cond
+       ((listp gnus-simplify-subject-fuzzy-regexp)
+	(mapcar 'gnus-simplify-buffer-fuzzy-step
+		gnus-simplify-subject-fuzzy-regexp))
+       (gnus-simplify-subject-fuzzy-regexp
+	(gnus-simplify-buffer-fuzzy-step gnus-simplify-subject-fuzzy-regexp)))
+      (gnus-simplify-buffer-fuzzy-step "^ *\\[[-+?*!][-+?*!]\\] *")
+      (gnus-simplify-buffer-fuzzy-step
+       "^ *\\(re\\|fw\\|fwd\\)[[{(^0-9]*[])}]?[:;] *")
+      (gnus-simplify-buffer-fuzzy-step "^[[].*:\\( .*\\)[]]$" "\\1"))
+
+    (gnus-simplify-buffer-fuzzy-step " *[[{(][^()\n]*[]})] *$")
+    (gnus-simplify-buffer-fuzzy-step "  +" " ")
+    (gnus-simplify-buffer-fuzzy-step " $")
+    (gnus-simplify-buffer-fuzzy-step "^ +")))
+
+(defun gnus-simplify-subject-fuzzy (subject)
+  "Simplify a subject string fuzzily.
+See gnus-simplify-buffer-fuzzy for details."
+  (save-excursion
+    (gnus-set-work-buffer)
+    (let ((case-fold-search t))
+      (insert subject)
+      (inline (gnus-simplify-buffer-fuzzy))
+      (buffer-string))))
+
+(defsubst gnus-simplify-subject-fully (subject)
+  "Simplify a subject string according to gnus-summary-gather-subject-limit."
+  (cond
+   ((null gnus-summary-gather-subject-limit)
+    (gnus-simplify-subject-re subject))
+   ((eq gnus-summary-gather-subject-limit 'fuzzy)
+    (gnus-simplify-subject-fuzzy subject))
+   ((numberp gnus-summary-gather-subject-limit)
+    (gnus-limit-string (gnus-simplify-subject-re subject)
+		       gnus-summary-gather-subject-limit))
+   (t
+    subject)))
+
+(defsubst gnus-subject-equal (s1 s2 &optional simple-first)
+  "Check whether two subjects are equal.  If optional argument
+simple-first is t, first argument is already simplified."
+  (cond
+   ((null simple-first)
+    (equal (gnus-simplify-subject-fully s1)
+	   (gnus-simplify-subject-fully s2)))
+   (t
+    (equal s1
+	   (gnus-simplify-subject-fully s2)))))
+
+(defun gnus-summary-bubble-group ()
+  "Increase the score of the current group.
+This is a handy function to add to `gnus-summary-exit-hook' to
+increase the score of each group you read."
+  (gnus-group-add-score gnus-newsgroup-name))
+
+
+;;;
+;;; Gnus summary mode
+;;;
+
+(put 'gnus-summary-mode 'mode-class 'special)
+
+(when t
+  ;; Non-orthogonal keys
+
+  (gnus-define-keys gnus-summary-mode-map
+    " " gnus-summary-next-page
+    "\177" gnus-summary-prev-page
+    [delete] gnus-summary-prev-page
+    "\r" gnus-summary-scroll-up
+    "n" gnus-summary-next-unread-article
+    "p" gnus-summary-prev-unread-article
+    "N" gnus-summary-next-article
+    "P" gnus-summary-prev-article
+    "\M-\C-n" gnus-summary-next-same-subject
+    "\M-\C-p" gnus-summary-prev-same-subject
+    "\M-n" gnus-summary-next-unread-subject
+    "\M-p" gnus-summary-prev-unread-subject
+    "." gnus-summary-first-unread-article
+    "," gnus-summary-best-unread-article
+    "\M-s" gnus-summary-search-article-forward
+    "\M-r" gnus-summary-search-article-backward
+    "<" gnus-summary-beginning-of-article
+    ">" gnus-summary-end-of-article
+    "j" gnus-summary-goto-article
+    "^" gnus-summary-refer-parent-article
+    "\M-^" gnus-summary-refer-article
+    "u" gnus-summary-tick-article-forward
+    "!" gnus-summary-tick-article-forward
+    "U" gnus-summary-tick-article-backward
+    "d" gnus-summary-mark-as-read-forward
+    "D" gnus-summary-mark-as-read-backward
+    "E" gnus-summary-mark-as-expirable
+    "\M-u" gnus-summary-clear-mark-forward
+    "\M-U" gnus-summary-clear-mark-backward
+    "k" gnus-summary-kill-same-subject-and-select
+    "\C-k" gnus-summary-kill-same-subject
+    "\M-\C-k" gnus-summary-kill-thread
+    "\M-\C-l" gnus-summary-lower-thread
+    "e" gnus-summary-edit-article
+    "#" gnus-summary-mark-as-processable
+    "\M-#" gnus-summary-unmark-as-processable
+    "\M-\C-t" gnus-summary-toggle-threads
+    "\M-\C-s" gnus-summary-show-thread
+    "\M-\C-h" gnus-summary-hide-thread
+    "\M-\C-f" gnus-summary-next-thread
+    "\M-\C-b" gnus-summary-prev-thread
+    "\M-\C-u" gnus-summary-up-thread
+    "\M-\C-d" gnus-summary-down-thread
+    "&" gnus-summary-execute-command
+    "c" gnus-summary-catchup-and-exit
+    "\C-w" gnus-summary-mark-region-as-read
+    "\C-t" gnus-summary-toggle-truncation
+    "?" gnus-summary-mark-as-dormant
+    "\C-c\M-\C-s" gnus-summary-limit-include-expunged
+    "\C-c\C-s\C-n" gnus-summary-sort-by-number
+    "\C-c\C-s\C-l" gnus-summary-sort-by-lines
+    "\C-c\C-s\C-a" gnus-summary-sort-by-author
+    "\C-c\C-s\C-s" gnus-summary-sort-by-subject
+    "\C-c\C-s\C-d" gnus-summary-sort-by-date
+    "\C-c\C-s\C-i" gnus-summary-sort-by-score
+    "=" gnus-summary-expand-window
+    "\C-x\C-s" gnus-summary-reselect-current-group
+    "\M-g" gnus-summary-rescan-group
+    "w" gnus-summary-stop-page-breaking
+    "\C-c\C-r" gnus-summary-caesar-message
+    "\M-t" gnus-summary-toggle-mime
+    "f" gnus-summary-followup
+    "F" gnus-summary-followup-with-original
+    "C" gnus-summary-cancel-article
+    "r" gnus-summary-reply
+    "R" gnus-summary-reply-with-original
+    "\C-c\C-f" gnus-summary-mail-forward
+    "o" gnus-summary-save-article
+    "\C-o" gnus-summary-save-article-mail
+    "|" gnus-summary-pipe-output
+    "\M-k" gnus-summary-edit-local-kill
+    "\M-K" gnus-summary-edit-global-kill
+    ;; "V" gnus-version
+    "\C-c\C-d" gnus-summary-describe-group
+    "q" gnus-summary-exit
+    "Q" gnus-summary-exit-no-update
+    "\C-c\C-i" gnus-info-find-node
+    gnus-mouse-2 gnus-mouse-pick-article
+    "m" gnus-summary-mail-other-window
+    "a" gnus-summary-post-news
+    "x" gnus-summary-limit-to-unread
+    "s" gnus-summary-isearch-article
+    "t" gnus-article-hide-headers
+    "g" gnus-summary-show-article
+    "l" gnus-summary-goto-last-article
+    "\C-c\C-v\C-v" gnus-uu-decode-uu-view
+    "\C-d" gnus-summary-enter-digest-group
+    "\M-\C-d" gnus-summary-read-document
+    "\C-c\C-b" gnus-bug
+    "*" gnus-cache-enter-article
+    "\M-*" gnus-cache-remove-article
+    "\M-&" gnus-summary-universal-argument
+    "\C-l" gnus-recenter
+    "I" gnus-summary-increase-score
+    "L" gnus-summary-lower-score
+
+    "V" gnus-summary-score-map
+    "X" gnus-uu-extract-map
+    "S" gnus-summary-send-map)
+
+  ;; Sort of orthogonal keymap
+  (gnus-define-keys (gnus-summary-mark-map "M" gnus-summary-mode-map)
+    "t" gnus-summary-tick-article-forward
+    "!" gnus-summary-tick-article-forward
+    "d" gnus-summary-mark-as-read-forward
+    "r" gnus-summary-mark-as-read-forward
+    "c" gnus-summary-clear-mark-forward
+    " " gnus-summary-clear-mark-forward
+    "e" gnus-summary-mark-as-expirable
+    "x" gnus-summary-mark-as-expirable
+    "?" gnus-summary-mark-as-dormant
+    "b" gnus-summary-set-bookmark
+    "B" gnus-summary-remove-bookmark
+    "#" gnus-summary-mark-as-processable
+    "\M-#" gnus-summary-unmark-as-processable
+    "S" gnus-summary-limit-include-expunged
+    "C" gnus-summary-catchup
+    "H" gnus-summary-catchup-to-here
+    "\C-c" gnus-summary-catchup-all
+    "k" gnus-summary-kill-same-subject-and-select
+    "K" gnus-summary-kill-same-subject
+    "P" gnus-uu-mark-map)
+
+  (gnus-define-keys (gnus-summary-mscore-map "V" gnus-summary-mark-map)
+    "c" gnus-summary-clear-above
+    "u" gnus-summary-tick-above
+    "m" gnus-summary-mark-above
+    "k" gnus-summary-kill-below)
+
+  (gnus-define-keys (gnus-summary-limit-map "/" gnus-summary-mode-map)
+    "/" gnus-summary-limit-to-subject
+    "n" gnus-summary-limit-to-articles
+    "w" gnus-summary-pop-limit
+    "s" gnus-summary-limit-to-subject
+    "a" gnus-summary-limit-to-author
+    "u" gnus-summary-limit-to-unread
+    "m" gnus-summary-limit-to-marks
+    "v" gnus-summary-limit-to-score
+    "D" gnus-summary-limit-include-dormant
+    "d" gnus-summary-limit-exclude-dormant
+    "t" gnus-summary-limit-to-age
+    "E" gnus-summary-limit-include-expunged
+    "c" gnus-summary-limit-exclude-childless-dormant
+    "C" gnus-summary-limit-mark-excluded-as-read)
+
+  (gnus-define-keys (gnus-summary-goto-map "G" gnus-summary-mode-map)
+    "n" gnus-summary-next-unread-article
+    "p" gnus-summary-prev-unread-article
+    "N" gnus-summary-next-article
+    "P" gnus-summary-prev-article
+    "\C-n" gnus-summary-next-same-subject
+    "\C-p" gnus-summary-prev-same-subject
+    "\M-n" gnus-summary-next-unread-subject
+    "\M-p" gnus-summary-prev-unread-subject
+    "f" gnus-summary-first-unread-article
+    "b" gnus-summary-best-unread-article
+    "j" gnus-summary-goto-article
+    "g" gnus-summary-goto-subject
+    "l" gnus-summary-goto-last-article
+    "p" gnus-summary-pop-article)
+
+  (gnus-define-keys (gnus-summary-thread-map "T" gnus-summary-mode-map)
+    "k" gnus-summary-kill-thread
+    "l" gnus-summary-lower-thread
+    "i" gnus-summary-raise-thread
+    "T" gnus-summary-toggle-threads
+    "t" gnus-summary-rethread-current
+    "^" gnus-summary-reparent-thread
+    "s" gnus-summary-show-thread
+    "S" gnus-summary-show-all-threads
+    "h" gnus-summary-hide-thread
+    "H" gnus-summary-hide-all-threads
+    "n" gnus-summary-next-thread
+    "p" gnus-summary-prev-thread
+    "u" gnus-summary-up-thread
+    "o" gnus-summary-top-thread
+    "d" gnus-summary-down-thread
+    "#" gnus-uu-mark-thread
+    "\M-#" gnus-uu-unmark-thread)
+
+  (gnus-define-keys (gnus-summary-buffer-map "Y" gnus-summary-mode-map)
+    "g" gnus-summary-prepare
+    "c" gnus-summary-insert-cached-articles)
+
+  (gnus-define-keys (gnus-summary-exit-map "Z" gnus-summary-mode-map)
+    "c" gnus-summary-catchup-and-exit
+    "C" gnus-summary-catchup-all-and-exit
+    "E" gnus-summary-exit-no-update
+    "Q" gnus-summary-exit
+    "Z" gnus-summary-exit
+    "n" gnus-summary-catchup-and-goto-next-group
+    "R" gnus-summary-reselect-current-group
+    "G" gnus-summary-rescan-group
+    "N" gnus-summary-next-group
+    "s" gnus-summary-save-newsrc
+    "P" gnus-summary-prev-group)
+
+  (gnus-define-keys (gnus-summary-article-map "A" gnus-summary-mode-map)
+    " " gnus-summary-next-page
+    "n" gnus-summary-next-page
+    "\177" gnus-summary-prev-page
+    [delete] gnus-summary-prev-page
+    "p" gnus-summary-prev-page
+    "\r" gnus-summary-scroll-up
+    "<" gnus-summary-beginning-of-article
+    ">" gnus-summary-end-of-article
+    "b" gnus-summary-beginning-of-article
+    "e" gnus-summary-end-of-article
+    "^" gnus-summary-refer-parent-article
+    "r" gnus-summary-refer-parent-article
+    "R" gnus-summary-refer-references
+    "g" gnus-summary-show-article
+    "s" gnus-summary-isearch-article
+    "P" gnus-summary-print-article)
+
+  (gnus-define-keys (gnus-summary-wash-map "W" gnus-summary-mode-map)
+    "b" gnus-article-add-buttons
+    "B" gnus-article-add-buttons-to-head
+    "o" gnus-article-treat-overstrike
+    "e" gnus-article-emphasize
+    "w" gnus-article-fill-cited-article
+    "c" gnus-article-remove-cr
+    "q" gnus-article-de-quoted-unreadable
+    "f" gnus-article-display-x-face
+    "l" gnus-summary-stop-page-breaking
+    "r" gnus-summary-caesar-message
+    "t" gnus-article-hide-headers
+    "v" gnus-summary-verbose-headers
+    "m" gnus-summary-toggle-mime
+    "h" gnus-article-treat-html)
+
+  (gnus-define-keys (gnus-summary-wash-hide-map "W" gnus-summary-wash-map)
+    "a" gnus-article-hide
+    "h" gnus-article-hide-headers
+    "b" gnus-article-hide-boring-headers
+    "s" gnus-article-hide-signature
+    "c" gnus-article-hide-citation
+    "p" gnus-article-hide-pgp
+    "P" gnus-article-hide-pem
+    "\C-c" gnus-article-hide-citation-maybe)
+
+  (gnus-define-keys (gnus-summary-wash-highlight-map "H" gnus-summary-wash-map)
+    "a" gnus-article-highlight
+    "h" gnus-article-highlight-headers
+    "c" gnus-article-highlight-citation
+    "s" gnus-article-highlight-signature)
+
+  (gnus-define-keys (gnus-summary-wash-time-map "T" gnus-summary-wash-map)
+    "z" gnus-article-date-ut
+    "u" gnus-article-date-ut
+    "l" gnus-article-date-local
+    "e" gnus-article-date-lapsed
+    "o" gnus-article-date-original
+    "s" gnus-article-date-user)
+
+  (gnus-define-keys (gnus-summary-wash-empty-map "E" gnus-summary-wash-map)
+    "t" gnus-article-remove-trailing-blank-lines
+    "l" gnus-article-strip-leading-blank-lines
+    "m" gnus-article-strip-multiple-blank-lines
+    "a" gnus-article-strip-blank-lines
+    "s" gnus-article-strip-leading-space)
+
+  (gnus-define-keys (gnus-summary-help-map "H" gnus-summary-mode-map)
+    "v" gnus-version
+    "f" gnus-summary-fetch-faq
+    "d" gnus-summary-describe-group
+    "h" gnus-summary-describe-briefly
+    "i" gnus-info-find-node)
+
+  (gnus-define-keys (gnus-summary-backend-map "B" gnus-summary-mode-map)
+    "e" gnus-summary-expire-articles
+    "\M-\C-e" gnus-summary-expire-articles-now
+    "\177" gnus-summary-delete-article
+    [delete] gnus-summary-delete-article
+    "m" gnus-summary-move-article
+    "r" gnus-summary-respool-article
+    "w" gnus-summary-edit-article
+    "c" gnus-summary-copy-article
+    "B" gnus-summary-crosspost-article
+    "q" gnus-summary-respool-query
+    "i" gnus-summary-import-article
+    "p" gnus-summary-article-posted-p)
+
+  (gnus-define-keys (gnus-summary-save-map "O" gnus-summary-mode-map)
+    "o" gnus-summary-save-article
+    "m" gnus-summary-save-article-mail
+    "F" gnus-summary-write-article-file
+    "r" gnus-summary-save-article-rmail
+    "f" gnus-summary-save-article-file
+    "b" gnus-summary-save-article-body-file
+    "h" gnus-summary-save-article-folder
+    "v" gnus-summary-save-article-vm
+    "p" gnus-summary-pipe-output
+    "s" gnus-soup-add-article))
+
+(defun gnus-summary-make-menu-bar ()
+  (gnus-turn-off-edit-menu 'summary)
+
+  (unless (boundp 'gnus-summary-misc-menu)
+
+    (easy-menu-define
+     gnus-summary-kill-menu gnus-summary-mode-map ""
+     (cons
+      "Score"
+      (nconc
+       (list
+	["Enter score..." gnus-summary-score-entry t]
+	["Customize" gnus-score-customize t])
+       (gnus-make-score-map 'increase)
+       (gnus-make-score-map 'lower)
+       '(("Mark"
+	  ["Kill below" gnus-summary-kill-below t]
+	  ["Mark above" gnus-summary-mark-above t]
+	  ["Tick above" gnus-summary-tick-above t]
+	  ["Clear above" gnus-summary-clear-above t])
+	 ["Current score" gnus-summary-current-score t]
+	 ["Set score" gnus-summary-set-score t]
+	 ["Switch current score file..." gnus-score-change-score-file t]
+	 ["Set mark below..." gnus-score-set-mark-below t]
+	 ["Set expunge below..." gnus-score-set-expunge-below t]
+	 ["Edit current score file" gnus-score-edit-current-scores t]
+	 ["Edit score file" gnus-score-edit-file t]
+	 ["Trace score" gnus-score-find-trace t]
+	 ["Find words" gnus-score-find-favourite-words t]
+	 ["Rescore buffer" gnus-summary-rescore t]
+	 ["Increase score..." gnus-summary-increase-score t]
+	 ["Lower score..." gnus-summary-lower-score t]))))
+
+    '(("Default header"
+       ["Ask" (gnus-score-set-default 'gnus-score-default-header nil)
+	:style radio
+	:selected (null gnus-score-default-header)]
+       ["From" (gnus-score-set-default 'gnus-score-default-header 'a)
+	:style radio
+	:selected (eq gnus-score-default-header 'a)]
+       ["Subject" (gnus-score-set-default 'gnus-score-default-header 's)
+	:style radio
+	:selected (eq gnus-score-default-header 's)]
+       ["Article body"
+	(gnus-score-set-default 'gnus-score-default-header 'b)
+	:style radio
+	:selected (eq gnus-score-default-header 'b )]
+       ["All headers"
+	(gnus-score-set-default 'gnus-score-default-header 'h)
+	:style radio
+	:selected (eq gnus-score-default-header 'h )]
+       ["Message-ID" (gnus-score-set-default 'gnus-score-default-header 'i)
+	:style radio
+	:selected (eq gnus-score-default-header 'i )]
+       ["Thread" (gnus-score-set-default 'gnus-score-default-header 't)
+	:style radio
+	:selected (eq gnus-score-default-header 't )]
+       ["Crossposting"
+	(gnus-score-set-default 'gnus-score-default-header 'x)
+	:style radio
+	:selected (eq gnus-score-default-header 'x )]
+       ["Lines" (gnus-score-set-default 'gnus-score-default-header 'l)
+	:style radio
+	:selected (eq gnus-score-default-header 'l )]
+       ["Date" (gnus-score-set-default 'gnus-score-default-header 'd)
+	:style radio
+	:selected (eq gnus-score-default-header 'd )]
+       ["Followups to author"
+	(gnus-score-set-default 'gnus-score-default-header 'f)
+	:style radio
+	:selected (eq gnus-score-default-header 'f )])
+      ("Default type"
+       ["Ask" (gnus-score-set-default 'gnus-score-default-type nil)
+	:style radio
+	:selected (null gnus-score-default-type)]
+       ;; The `:active' key is commented out in the following,
+       ;; because the GNU Emacs hack to support radio buttons use
+       ;; active to indicate which button is selected.
+       ["Substring" (gnus-score-set-default 'gnus-score-default-type 's)
+	:style radio
+	;; :active (not (memq gnus-score-default-header '(l d)))
+	:selected (eq gnus-score-default-type 's)]
+       ["Regexp" (gnus-score-set-default 'gnus-score-default-type 'r)
+	:style radio
+	;; :active (not (memq gnus-score-default-header '(l d)))
+	:selected (eq gnus-score-default-type 'r)]
+       ["Exact" (gnus-score-set-default 'gnus-score-default-type 'e)
+	:style radio
+	;; :active (not (memq gnus-score-default-header '(l d)))
+	:selected (eq gnus-score-default-type 'e)]
+       ["Fuzzy" (gnus-score-set-default 'gnus-score-default-type 'f)
+	:style radio
+	;; :active (not (memq gnus-score-default-header '(l d)))
+	:selected (eq gnus-score-default-type 'f)]
+       ["Before date" (gnus-score-set-default 'gnus-score-default-type 'b)
+	:style radio
+	;; :active (eq (gnus-score-default-header 'd))
+	:selected (eq gnus-score-default-type 'b)]
+       ["At date" (gnus-score-set-default 'gnus-score-default-type 'n)
+	:style radio
+	;; :active (eq (gnus-score-default-header 'd))
+	:selected (eq gnus-score-default-type 'n)]
+       ["After date" (gnus-score-set-default 'gnus-score-default-type 'a)
+	:style radio
+	;; :active (eq (gnus-score-default-header 'd))
+	:selected (eq gnus-score-default-type 'a)]
+       ["Less than number"
+	(gnus-score-set-default 'gnus-score-default-type '<)
+	:style radio
+	;; :active (eq (gnus-score-default-header 'l))
+	:selected (eq gnus-score-default-type '<)]
+       ["Equal to number"
+	(gnus-score-set-default 'gnus-score-default-type '=)
+	:style radio
+	;; :active (eq (gnus-score-default-header 'l))
+	:selected (eq gnus-score-default-type '=)]
+       ["Greater than number"
+	(gnus-score-set-default 'gnus-score-default-type '>)
+	:style radio
+	;; :active (eq (gnus-score-default-header 'l))
+	:selected (eq gnus-score-default-type '>)])
+      ["Default fold" gnus-score-default-fold-toggle
+       :style toggle
+       :selected gnus-score-default-fold]
+      ("Default duration"
+       ["Ask" (gnus-score-set-default 'gnus-score-default-duration nil)
+	:style radio
+	:selected (null gnus-score-default-duration)]
+       ["Permanent"
+	(gnus-score-set-default 'gnus-score-default-duration 'p)
+	:style radio
+	:selected (eq gnus-score-default-duration 'p)]
+       ["Temporary"
+	(gnus-score-set-default 'gnus-score-default-duration 't)
+	:style radio
+	:selected (eq gnus-score-default-duration 't)]
+       ["Immediate"
+	(gnus-score-set-default 'gnus-score-default-duration 'i)
+	:style radio
+	:selected (eq gnus-score-default-duration 'i)]))
+
+    (easy-menu-define
+     gnus-summary-article-menu gnus-summary-mode-map ""
+     '("Article"
+       ("Hide"
+	["All" gnus-article-hide t]
+	["Headers" gnus-article-hide-headers t]
+	["Signature" gnus-article-hide-signature t]
+	["Citation" gnus-article-hide-citation t]
+	["PGP" gnus-article-hide-pgp t]
+	["Boring headers" gnus-article-hide-boring-headers t])
+       ("Highlight"
+	["All" gnus-article-highlight t]
+	["Headers" gnus-article-highlight-headers t]
+	["Signature" gnus-article-highlight-signature t]
+	["Citation" gnus-article-highlight-citation t])
+       ("Date"
+	["Local" gnus-article-date-local t]
+	["UT" gnus-article-date-ut t]
+	["Original" gnus-article-date-original t]
+	["Lapsed" gnus-article-date-lapsed t]
+	["User-defined" gnus-article-date-user t])
+       ("Washing"
+	("Remove Blanks"
+	 ["Leading" gnus-article-strip-leading-blank-lines t]
+	 ["Multiple" gnus-article-strip-multiple-blank-lines t]
+	 ["Trailing" gnus-article-remove-trailing-blank-lines t]
+	 ["All of the above" gnus-article-strip-blank-lines t]
+	 ["Leading space" gnus-article-strip-leading-space t])
+	["Overstrike" gnus-article-treat-overstrike t]
+	["Emphasis" gnus-article-emphasize t]
+	["Word wrap" gnus-article-fill-cited-article t]
+	["CR" gnus-article-remove-cr t]
+	["Show X-Face" gnus-article-display-x-face t]
+	["Quoted-Printable" gnus-article-de-quoted-unreadable t]
+	["UnHTMLize" gnus-article-treat-html t]
+	["Rot 13" gnus-summary-caesar-message t]
+	["Unix pipe" gnus-summary-pipe-message t]
+	["Add buttons" gnus-article-add-buttons t]
+	["Add buttons to head" gnus-article-add-buttons-to-head t]
+	["Stop page breaking" gnus-summary-stop-page-breaking t]
+	["Toggle MIME" gnus-summary-toggle-mime t]
+	["Verbose header" gnus-summary-verbose-headers t]
+	["Toggle header" gnus-summary-toggle-header t])
+       ("Output"
+	["Save in default format" gnus-summary-save-article t]
+	["Save in file" gnus-summary-save-article-file t]
+	["Save in Unix mail format" gnus-summary-save-article-mail t]
+	["Write to file" gnus-summary-write-article-mail t]
+	["Save in MH folder" gnus-summary-save-article-folder t]
+	["Save in VM folder" gnus-summary-save-article-vm t]
+	["Save in RMAIL mbox" gnus-summary-save-article-rmail t]
+	["Save body in file" gnus-summary-save-article-body-file t]
+	["Pipe through a filter" gnus-summary-pipe-output t]
+	["Add to SOUP packet" gnus-soup-add-article t]
+	["Print" gnus-summary-print-article t])
+       ("Backend"
+	["Respool article..." gnus-summary-respool-article t]
+	["Move article..." gnus-summary-move-article
+	 (gnus-check-backend-function
+	  'request-move-article gnus-newsgroup-name)]
+	["Copy article..." gnus-summary-copy-article t]
+	["Crosspost article..." gnus-summary-crosspost-article
+	 (gnus-check-backend-function
+	  'request-replace-article gnus-newsgroup-name)]
+	["Import file..." gnus-summary-import-article t]
+	["Check if posted" gnus-summary-article-posted-p t]
+	["Edit article" gnus-summary-edit-article
+	 (not (gnus-group-read-only-p))]
+	["Delete article" gnus-summary-delete-article
+	 (gnus-check-backend-function
+	  'request-expire-articles gnus-newsgroup-name)]
+	["Query respool" gnus-summary-respool-query t]
+	["Delete expirable articles" gnus-summary-expire-articles-now
+	 (gnus-check-backend-function
+	  'request-expire-articles gnus-newsgroup-name)])
+       ("Extract"
+	["Uudecode" gnus-uu-decode-uu t]
+	["Uudecode and save" gnus-uu-decode-uu-and-save t]
+	["Unshar" gnus-uu-decode-unshar t]
+	["Unshar and save" gnus-uu-decode-unshar-and-save t]
+	["Save" gnus-uu-decode-save t]
+	["Binhex" gnus-uu-decode-binhex t]
+	["Postscript" gnus-uu-decode-postscript t])
+       ("Cache"
+	["Enter article" gnus-cache-enter-article t]
+	["Remove article" gnus-cache-remove-article t])
+       ["Enter digest buffer" gnus-summary-enter-digest-group t]
+       ["Isearch article..." gnus-summary-isearch-article t]
+       ["Beginning of the article" gnus-summary-beginning-of-article t]
+       ["End of the article" gnus-summary-end-of-article t]
+       ["Fetch parent of article" gnus-summary-refer-parent-article t]
+       ["Fetch referenced articles" gnus-summary-refer-references t]
+       ["Fetch article with id..." gnus-summary-refer-article t]
+       ["Redisplay" gnus-summary-show-article t]))
+
+    (easy-menu-define
+     gnus-summary-thread-menu gnus-summary-mode-map ""
+     '("Threads"
+       ["Toggle threading" gnus-summary-toggle-threads t]
+       ["Hide threads" gnus-summary-hide-all-threads t]
+       ["Show threads" gnus-summary-show-all-threads t]
+       ["Hide thread" gnus-summary-hide-thread t]
+       ["Show thread" gnus-summary-show-thread t]
+       ["Go to next thread" gnus-summary-next-thread t]
+       ["Go to previous thread" gnus-summary-prev-thread t]
+       ["Go down thread" gnus-summary-down-thread t]
+       ["Go up thread" gnus-summary-up-thread t]
+       ["Top of thread" gnus-summary-top-thread t]
+       ["Mark thread as read" gnus-summary-kill-thread t]
+       ["Lower thread score" gnus-summary-lower-thread t]
+       ["Raise thread score" gnus-summary-raise-thread t]
+       ["Rethread current" gnus-summary-rethread-current t]
+       ))
+
+    (easy-menu-define
+     gnus-summary-post-menu gnus-summary-mode-map ""
+     '("Post"
+       ["Post an article" gnus-summary-post-news t]
+       ["Followup" gnus-summary-followup t]
+       ["Followup and yank" gnus-summary-followup-with-original t]
+       ["Supersede article" gnus-summary-supersede-article t]
+       ["Cancel article" gnus-summary-cancel-article t]
+       ["Reply" gnus-summary-reply t]
+       ["Reply and yank" gnus-summary-reply-with-original t]
+       ["Wide reply" gnus-summary-wide-reply t]
+       ["Wide reply and yank" gnus-summary-wide-reply-with-original t]
+       ["Mail forward" gnus-summary-mail-forward t]
+       ["Post forward" gnus-summary-post-forward t]
+       ["Digest and mail" gnus-uu-digest-mail-forward t]
+       ["Digest and post" gnus-uu-digest-post-forward t]
+       ["Resend message" gnus-summary-resend-message t]
+       ["Send bounced mail" gnus-summary-resend-bounced-mail t]
+       ["Send a mail" gnus-summary-mail-other-window t]
+       ["Uuencode and post" gnus-uu-post-news t]
+       ["Followup via news" gnus-summary-followup-to-mail t]
+       ["Followup via news and yank"
+	gnus-summary-followup-to-mail-with-original t]
+       ;;("Draft"
+       ;;["Send" gnus-summary-send-draft t]
+       ;;["Send bounced" gnus-resend-bounced-mail t])
+       ))
+
+    (easy-menu-define
+     gnus-summary-misc-menu gnus-summary-mode-map ""
+     '("Misc"
+       ("Mark Read"
+	["Mark as read" gnus-summary-mark-as-read-forward t]
+	["Mark same subject and select"
+	 gnus-summary-kill-same-subject-and-select t]
+	["Mark same subject" gnus-summary-kill-same-subject t]
+	["Catchup" gnus-summary-catchup t]
+	["Catchup all" gnus-summary-catchup-all t]
+	["Catchup to here" gnus-summary-catchup-to-here t]
+	["Catchup region" gnus-summary-mark-region-as-read t]
+	["Mark excluded" gnus-summary-limit-mark-excluded-as-read t])
+       ("Mark Various"
+	["Tick" gnus-summary-tick-article-forward t]
+	["Mark as dormant" gnus-summary-mark-as-dormant t]
+	["Remove marks" gnus-summary-clear-mark-forward t]
+	["Set expirable mark" gnus-summary-mark-as-expirable t]
+	["Set bookmark" gnus-summary-set-bookmark t]
+	["Remove bookmark" gnus-summary-remove-bookmark t])
+       ("Mark Limit"
+	["Marks..." gnus-summary-limit-to-marks t]
+	["Subject..." gnus-summary-limit-to-subject t]
+	["Author..." gnus-summary-limit-to-author t]
+	["Age..." gnus-summary-limit-to-age t]
+	["Score" gnus-summary-limit-to-score t]
+	["Unread" gnus-summary-limit-to-unread t]
+	["Non-dormant" gnus-summary-limit-exclude-dormant t]
+	["Articles" gnus-summary-limit-to-articles t]
+	["Pop limit" gnus-summary-pop-limit t]
+	["Show dormant" gnus-summary-limit-include-dormant t]
+	["Hide childless dormant"
+	 gnus-summary-limit-exclude-childless-dormant t]
+	;;["Hide thread" gnus-summary-limit-exclude-thread t]
+	["Show expunged" gnus-summary-show-all-expunged t])
+       ("Process Mark"
+	["Set mark" gnus-summary-mark-as-processable t]
+	["Remove mark" gnus-summary-unmark-as-processable t]
+	["Remove all marks" gnus-summary-unmark-all-processable t]
+	["Mark above" gnus-uu-mark-over t]
+	["Mark series" gnus-uu-mark-series t]
+	["Mark region" gnus-uu-mark-region t]
+	["Mark by regexp..." gnus-uu-mark-by-regexp t]
+	["Mark all" gnus-uu-mark-all t]
+	["Mark buffer" gnus-uu-mark-buffer t]
+	["Mark sparse" gnus-uu-mark-sparse t]
+	["Mark thread" gnus-uu-mark-thread t]
+	["Unmark thread" gnus-uu-unmark-thread t]
+	("Process Mark Sets"
+	 ["Kill" gnus-summary-kill-process-mark t]
+	 ["Yank" gnus-summary-yank-process-mark
+	  gnus-newsgroup-process-stack]
+	 ["Save" gnus-summary-save-process-mark t]))
+       ("Scroll article"
+	["Page forward" gnus-summary-next-page t]
+	["Page backward" gnus-summary-prev-page t]
+	["Line forward" gnus-summary-scroll-up t])
+       ("Move"
+	["Next unread article" gnus-summary-next-unread-article t]
+	["Previous unread article" gnus-summary-prev-unread-article t]
+	["Next article" gnus-summary-next-article t]
+	["Previous article" gnus-summary-prev-article t]
+	["Next unread subject" gnus-summary-next-unread-subject t]
+	["Previous unread subject" gnus-summary-prev-unread-subject t]
+	["Next article same subject" gnus-summary-next-same-subject t]
+	["Previous article same subject" gnus-summary-prev-same-subject t]
+	["First unread article" gnus-summary-first-unread-article t]
+	["Best unread article" gnus-summary-best-unread-article t]
+	["Go to subject number..." gnus-summary-goto-subject t]
+	["Go to article number..." gnus-summary-goto-article t]
+	["Go to the last article" gnus-summary-goto-last-article t]
+	["Pop article off history" gnus-summary-pop-article t])
+       ("Sort"
+	["Sort by number" gnus-summary-sort-by-number t]
+	["Sort by author" gnus-summary-sort-by-author t]
+	["Sort by subject" gnus-summary-sort-by-subject t]
+	["Sort by date" gnus-summary-sort-by-date t]
+	["Sort by score" gnus-summary-sort-by-score t]
+	["Sort by lines" gnus-summary-sort-by-lines t])
+       ("Help"
+	["Fetch group FAQ" gnus-summary-fetch-faq t]
+	["Describe group" gnus-summary-describe-group t]
+	["Read manual" gnus-info-find-node t])
+       ("Modes"
+	["Pick and read" gnus-pick-mode t]
+	["Binary" gnus-binary-mode t])
+       ("Regeneration"
+	["Regenerate" gnus-summary-prepare t]
+	["Insert cached articles" gnus-summary-insert-cached-articles t]
+	["Toggle threading" gnus-summary-toggle-threads t])
+       ["Filter articles..." gnus-summary-execute-command t]
+       ["Run command on subjects..." gnus-summary-universal-argument t]
+       ["Search articles forward..." gnus-summary-search-article-forward t]
+       ["Search articles backward..." gnus-summary-search-article-backward t]
+       ["Toggle line truncation" gnus-summary-toggle-truncation t]
+       ["Expand window" gnus-summary-expand-window t]
+       ["Expire expirable articles" gnus-summary-expire-articles
+	(gnus-check-backend-function
+	 'request-expire-articles gnus-newsgroup-name)]
+       ["Edit local kill file" gnus-summary-edit-local-kill t]
+       ["Edit main kill file" gnus-summary-edit-global-kill t]
+       ("Exit"
+	["Catchup and exit" gnus-summary-catchup-and-exit t]
+	["Catchup all and exit" gnus-summary-catchup-and-exit t]
+	["Catchup and goto next" gnus-summary-catchup-and-goto-next-group t]
+	["Exit group" gnus-summary-exit t]
+	["Exit group without updating" gnus-summary-exit-no-update t]
+	["Exit and goto next group" gnus-summary-next-group t]
+	["Exit and goto prev group" gnus-summary-prev-group t]
+	["Reselect group" gnus-summary-reselect-current-group t]
+	["Rescan group" gnus-summary-rescan-group t]
+	["Update dribble" gnus-summary-save-newsrc t])))
+
+    (run-hooks 'gnus-summary-menu-hook)))
+
+(defun gnus-score-set-default (var value)
+  "A version of set that updates the GNU Emacs menu-bar."
+  (set var value)
+  ;; It is the message that forces the active status to be updated.
+  (message ""))
+
+(defun gnus-make-score-map (type)
+  "Make a summary score map of type TYPE."
+  (if t
+      nil
+    (let ((headers '(("author" "from" string)
+		     ("subject" "subject" string)
+		     ("article body" "body" string)
+		     ("article head" "head" string)
+		     ("xref" "xref" string)
+		     ("lines" "lines" number)
+		     ("followups to author" "followup" string)))
+	  (types '((number ("less than" <)
+			   ("greater than" >)
+			   ("equal" =))
+		   (string ("substring" s)
+			   ("exact string" e)
+			   ("fuzzy string" f)
+			   ("regexp" r))))
+	  (perms '(("temporary" (current-time-string))
+		   ("permanent" nil)
+		   ("immediate" now)))
+	  header)
+      (list
+       (apply
+	'nconc
+	(list
+	 (if (eq type 'lower)
+	     "Lower score"
+	   "Increase score"))
+	(let (outh)
+	  (while headers
+	    (setq header (car headers))
+	    (setq outh
+		  (cons
+		   (apply
+		    'nconc
+		    (list (car header))
+		    (let ((ts (cdr (assoc (nth 2 header) types)))
+			  outt)
+		      (while ts
+			(setq outt
+			      (cons
+			       (apply
+				'nconc
+				(list (caar ts))
+				(let ((ps perms)
+				      outp)
+				  (while ps
+				    (setq outp
+					  (cons
+					   (vector
+					    (caar ps)
+					    (list
+					     'gnus-summary-score-entry
+					     (nth 1 header)
+					     (if (or (string= (nth 1 header)
+							      "head")
+						     (string= (nth 1 header)
+							      "body"))
+						 ""
+					       (list 'gnus-summary-header
+						     (nth 1 header)))
+					     (list 'quote (nth 1 (car ts)))
+					     (list 'gnus-score-default nil)
+					     (nth 1 (car ps))
+					     t)
+					    t)
+					   outp))
+				    (setq ps (cdr ps)))
+				  (list (nreverse outp))))
+			       outt))
+			(setq ts (cdr ts)))
+		      (list (nreverse outt))))
+		   outh))
+	    (setq headers (cdr headers)))
+	  (list (nreverse outh))))))))
+
+
+
+(defun gnus-summary-mode (&optional group)
+  "Major mode for reading articles.
+
+All normal editing commands are switched off.
+\\<gnus-summary-mode-map>
+Each line in this buffer represents one article.  To read an
+article, you can, for instance, type `\\[gnus-summary-next-page]'.  To move forwards
+and backwards while displaying articles, type `\\[gnus-summary-next-unread-article]' and `\\[gnus-summary-prev-unread-article]',
+respectively.
+
+You can also post articles and send mail from this buffer.  To
+follow up an article, type `\\[gnus-summary-followup]'.	 To mail a reply to the author
+of an article, type `\\[gnus-summary-reply]'.
+
+There are approx. one gazillion commands you can execute in this
+buffer; read the info pages for more information (`\\[gnus-info-find-node]').
+
+The following commands are available:
+
+\\{gnus-summary-mode-map}"
+  (interactive)
+  (when (gnus-visual-p 'summary-menu 'menu)
+    (gnus-summary-make-menu-bar))
+  (kill-all-local-variables)
+  (gnus-summary-make-local-variables)
+  (gnus-make-thread-indent-array)
+  (gnus-simplify-mode-line)
+  (setq major-mode 'gnus-summary-mode)
+  (setq mode-name "Summary")
+  (make-local-variable 'minor-mode-alist)
+  (use-local-map gnus-summary-mode-map)
+  (buffer-disable-undo (current-buffer))
+  (setq buffer-read-only t)		;Disable modification
+  (setq truncate-lines t)
+  (setq selective-display t)
+  (setq selective-display-ellipses t)	;Display `...'
+  (gnus-summary-set-display-table)
+  (gnus-set-default-directory)
+  (setq gnus-newsgroup-name group)
+  (make-local-variable 'gnus-summary-line-format)
+  (make-local-variable 'gnus-summary-line-format-spec)
+  (make-local-variable 'gnus-summary-mark-positions)
+  (make-local-hook 'post-command-hook)
+  (add-hook 'post-command-hook 'gnus-clear-inboxes-moved nil t)
+  (run-hooks 'gnus-summary-mode-hook)
+  (gnus-update-format-specifications nil 'summary 'summary-mode 'summary-dummy)
+  (gnus-update-summary-mark-positions))
+
+(defun gnus-summary-make-local-variables ()
+  "Make all the local summary buffer variables."
+  (let ((locals gnus-summary-local-variables)
+	global local)
+    (while (setq local (pop locals))
+      (if (consp local)
+	  (progn
+	    (if (eq (cdr local) 'global)
+		;; Copy the global value of the variable.
+		(setq global (symbol-value (car local)))
+	      ;; Use the value from the list.
+	      (setq global (eval (cdr local))))
+	    (make-local-variable (car local))
+	    (set (car local) global))
+	;; Simple nil-valued local variable.
+	(make-local-variable local)
+	(set local nil)))))
+
+(defun gnus-summary-clear-local-variables ()
+  (let ((locals gnus-summary-local-variables))
+    (while locals
+      (if (consp (car locals))
+	  (and (vectorp (caar locals))
+	       (set (caar locals) nil))
+	(and (vectorp (car locals))
+	     (set (car locals) nil)))
+      (setq locals (cdr locals)))))
+
+;; Summary data functions.
+
+(defmacro gnus-data-number (data)
+  `(car ,data))
+
+(defmacro gnus-data-set-number (data number)
+  `(setcar ,data ,number))
+
+(defmacro gnus-data-mark (data)
+  `(nth 1 ,data))
+
+(defmacro gnus-data-set-mark (data mark)
+  `(setcar (nthcdr 1 ,data) ,mark))
+
+(defmacro gnus-data-pos (data)
+  `(nth 2 ,data))
+
+(defmacro gnus-data-set-pos (data pos)
+  `(setcar (nthcdr 2 ,data) ,pos))
+
+(defmacro gnus-data-header (data)
+  `(nth 3 ,data))
+
+(defmacro gnus-data-set-header (data header)
+  `(setf (nth 3 ,data) ,header))
+
+(defmacro gnus-data-level (data)
+  `(nth 4 ,data))
+
+(defmacro gnus-data-unread-p (data)
+  `(= (nth 1 ,data) gnus-unread-mark))
+
+(defmacro gnus-data-read-p (data)
+  `(/= (nth 1 ,data) gnus-unread-mark))
+
+(defmacro gnus-data-pseudo-p (data)
+  `(consp (nth 3 ,data)))
+
+(defmacro gnus-data-find (number)
+  `(assq ,number gnus-newsgroup-data))
+
+(defmacro gnus-data-find-list (number &optional data)
+  `(let ((bdata ,(or data 'gnus-newsgroup-data)))
+     (memq (assq ,number bdata)
+	   bdata)))
+
+(defmacro gnus-data-make (number mark pos header level)
+  `(list ,number ,mark ,pos ,header ,level))
+
+(defun gnus-data-enter (after-article number mark pos header level offset)
+  (let ((data (gnus-data-find-list after-article)))
+    (unless data
+      (error "No such article: %d" after-article))
+    (setcdr data (cons (gnus-data-make number mark pos header level)
+		       (cdr data)))
+    (setq gnus-newsgroup-data-reverse nil)
+    (gnus-data-update-list (cddr data) offset)))
+
+(defun gnus-data-enter-list (after-article list &optional offset)
+  (when list
+    (let ((data (and after-article (gnus-data-find-list after-article)))
+	  (ilist list))
+      (or data (not after-article) (error "No such article: %d" after-article))
+      ;; Find the last element in the list to be spliced into the main
+      ;; list.
+      (while (cdr list)
+	(setq list (cdr list)))
+      (if (not data)
+	  (progn
+	    (setcdr list gnus-newsgroup-data)
+	    (setq gnus-newsgroup-data ilist)
+	    (when offset
+	      (gnus-data-update-list (cdr list) offset)))
+	(setcdr list (cdr data))
+	(setcdr data ilist)
+	(when offset
+	  (gnus-data-update-list (cdr list) offset)))
+      (setq gnus-newsgroup-data-reverse nil))))
+
+(defun gnus-data-remove (article &optional offset)
+  (let ((data gnus-newsgroup-data))
+    (if (= (gnus-data-number (car data)) article)
+	(progn
+	  (setq gnus-newsgroup-data (cdr gnus-newsgroup-data)
+		gnus-newsgroup-data-reverse nil)
+	  (when offset
+	    (gnus-data-update-list gnus-newsgroup-data offset)))
+      (while (cdr data)
+	(when (= (gnus-data-number (cadr data)) article)
+	  (setcdr data (cddr data))
+	  (when offset
+	    (gnus-data-update-list (cdr data) offset))
+	  (setq data nil
+		gnus-newsgroup-data-reverse nil))
+	(setq data (cdr data))))))
+
+(defmacro gnus-data-list (backward)
+  `(if ,backward
+       (or gnus-newsgroup-data-reverse
+	   (setq gnus-newsgroup-data-reverse
+		 (reverse gnus-newsgroup-data)))
+     gnus-newsgroup-data))
+
+(defun gnus-data-update-list (data offset)
+  "Add OFFSET to the POS of all data entries in DATA."
+  (while data
+    (setcar (nthcdr 2 (car data)) (+ offset (nth 2 (car data))))
+    (setq data (cdr data))))
+
+(defun gnus-data-compute-positions ()
+  "Compute the positions of all articles."
+  (let ((data gnus-newsgroup-data)
+	pos)
+    (while data
+      (when (setq pos (text-property-any
+		       (point-min) (point-max)
+		       'gnus-number (gnus-data-number (car data))))
+	(gnus-data-set-pos (car data) (+ pos 3)))
+      (setq data (cdr data)))))
+
+(defun gnus-summary-article-pseudo-p (article)
+  "Say whether this article is a pseudo article or not."
+  (not (vectorp (gnus-data-header (gnus-data-find article)))))
+
+(defmacro gnus-summary-article-sparse-p (article)
+  "Say whether this article is a sparse article or not."
+  ` (memq ,article gnus-newsgroup-sparse))
+
+(defmacro gnus-summary-article-ancient-p (article)
+  "Say whether this article is a sparse article or not."
+  `(memq ,article gnus-newsgroup-ancient))
+
+(defun gnus-article-parent-p (number)
+  "Say whether this article is a parent or not."
+  (let ((data (gnus-data-find-list number)))
+    (and (cdr data)			; There has to be an article after...
+	 (< (gnus-data-level (car data)) ; And it has to have a higher level.
+	    (gnus-data-level (nth 1 data))))))
+
+(defun gnus-article-children (number)
+  "Return a list of all children to NUMBER."
+  (let* ((data (gnus-data-find-list number))
+	 (level (gnus-data-level (car data)))
+	 children)
+    (setq data (cdr data))
+    (while (and data
+		(= (gnus-data-level (car data)) (1+ level)))
+      (push (gnus-data-number (car data)) children)
+      (setq data (cdr data)))
+    children))
+
+(defmacro gnus-summary-skip-intangible ()
+  "If the current article is intangible, then jump to a different article."
+  '(let ((to (get-text-property (point) 'gnus-intangible)))
+     (and to (gnus-summary-goto-subject to))))
+
+(defmacro gnus-summary-article-intangible-p ()
+  "Say whether this article is intangible or not."
+  '(get-text-property (point) 'gnus-intangible))
+
+(defun gnus-article-read-p (article)
+  "Say whether ARTICLE is read or not."
+  (not (or (memq article gnus-newsgroup-marked)
+	   (memq article gnus-newsgroup-unreads)
+	   (memq article gnus-newsgroup-unselected)
+	   (memq article gnus-newsgroup-dormant))))
+
+;; Some summary mode macros.
+
+(defmacro gnus-summary-article-number ()
+  "The article number of the article on the current line.
+If there isn's an article number here, then we return the current
+article number."
+  '(progn
+     (gnus-summary-skip-intangible)
+     (or (get-text-property (point) 'gnus-number)
+	 (gnus-summary-last-subject))))
+
+(defmacro gnus-summary-article-header (&optional number)
+  `(gnus-data-header (gnus-data-find
+		      ,(or number '(gnus-summary-article-number)))))
+
+(defmacro gnus-summary-thread-level (&optional number)
+  `(if (and (eq gnus-summary-make-false-root 'dummy)
+	    (get-text-property (point) 'gnus-intangible))
+       0
+     (gnus-data-level (gnus-data-find
+		       ,(or number '(gnus-summary-article-number))))))
+
+(defmacro gnus-summary-article-mark (&optional number)
+  `(gnus-data-mark (gnus-data-find
+		    ,(or number '(gnus-summary-article-number)))))
+
+(defmacro gnus-summary-article-pos (&optional number)
+  `(gnus-data-pos (gnus-data-find
+		   ,(or number '(gnus-summary-article-number)))))
+
+(defalias 'gnus-summary-subject-string 'gnus-summary-article-subject)
+(defmacro gnus-summary-article-subject (&optional number)
+  "Return current subject string or nil if nothing."
+  `(let ((headers
+	  ,(if number
+	       `(gnus-data-header (assq ,number gnus-newsgroup-data))
+	     '(gnus-data-header (assq (gnus-summary-article-number)
+				      gnus-newsgroup-data)))))
+     (and headers
+	  (vectorp headers)
+	  (mail-header-subject headers))))
+
+(defmacro gnus-summary-article-score (&optional number)
+  "Return current article score."
+  `(or (cdr (assq ,(or number '(gnus-summary-article-number))
+		  gnus-newsgroup-scored))
+       gnus-summary-default-score 0))
+
+(defun gnus-summary-article-children (&optional number)
+  (let* ((data (gnus-data-find-list (or number (gnus-summary-article-number))))
+	 (level (gnus-data-level (car data)))
+	 l children)
+    (while (and (setq data (cdr data))
+		(> (setq l (gnus-data-level (car data))) level))
+      (and (= (1+ level) l)
+	   (push (gnus-data-number (car data))
+		 children)))
+    (nreverse children)))
+
+(defun gnus-summary-article-parent (&optional number)
+  (let* ((data (gnus-data-find-list (or number (gnus-summary-article-number))
+				    (gnus-data-list t)))
+	 (level (gnus-data-level (car data))))
+    (if (zerop level)
+	()				; This is a root.
+      ;; We search until we find an article with a level less than
+      ;; this one.  That function has to be the parent.
+      (while (and (setq data (cdr data))
+		  (not (< (gnus-data-level (car data)) level))))
+      (and data (gnus-data-number (car data))))))
+
+(defun gnus-unread-mark-p (mark)
+  "Say whether MARK is the unread mark."
+  (= mark gnus-unread-mark))
+
+(defun gnus-read-mark-p (mark)
+  "Say whether MARK is one of the marks that mark as read.
+This is all marks except unread, ticked, dormant, and expirable."
+  (not (or (= mark gnus-unread-mark)
+	   (= mark gnus-ticked-mark)
+	   (= mark gnus-dormant-mark)
+	   (= mark gnus-expirable-mark))))
+
+(defmacro gnus-article-mark (number)
+  `(cond
+    ((memq ,number gnus-newsgroup-unreads) gnus-unread-mark)
+    ((memq ,number gnus-newsgroup-marked) gnus-ticked-mark)
+    ((memq ,number gnus-newsgroup-dormant) gnus-dormant-mark)
+    ((memq ,number gnus-newsgroup-expirable) gnus-expirable-mark)
+    (t (or (cdr (assq ,number gnus-newsgroup-reads))
+	   gnus-ancient-mark))))
+
+;; Saving hidden threads.
+
+(put 'gnus-save-hidden-threads 'lisp-indent-function 0)
+(put 'gnus-save-hidden-threads 'edebug-form-spec '(body))
+
+(defmacro gnus-save-hidden-threads (&rest forms)
+  "Save hidden threads, eval FORMS, and restore the hidden threads."
+  (let ((config (make-symbol "config")))
+    `(let ((,config (gnus-hidden-threads-configuration)))
+       (unwind-protect
+	   (save-excursion
+	     ,@forms)
+	 (gnus-restore-hidden-threads-configuration ,config)))))
+
+(defun gnus-hidden-threads-configuration ()
+  "Return the current hidden threads configuration."
+  (save-excursion
+    (let (config)
+      (goto-char (point-min))
+      (while (search-forward "\r" nil t)
+	(push (1- (point)) config))
+      config)))
+
+(defun gnus-restore-hidden-threads-configuration (config)
+  "Restore hidden threads configuration from CONFIG."
+  (let (point buffer-read-only)
+    (while (setq point (pop config))
+      (when (and (< point (point-max))
+		 (goto-char point)
+		 (= (following-char) ?\n))
+	(subst-char-in-region point (1+ point) ?\n ?\r)))))
+
+;; Various summary mode internalish functions.
+
+(defun gnus-mouse-pick-article (e)
+  (interactive "e")
+  (mouse-set-point e)
+  (gnus-summary-next-page nil t))
+
+(defun gnus-summary-set-display-table ()
+  ;; Change the display table.  Odd characters have a tendency to mess
+  ;; up nicely formatted displays - we make all possible glyphs
+  ;; display only a single character.
+
+  ;; We start from the standard display table, if any.
+  (let ((table (or (copy-sequence standard-display-table)
+		   (make-display-table)))
+	(i 32))
+    ;; Nix out all the control chars...
+    (while (>= (setq i (1- i)) 0)
+      (aset table i [??]))
+    ;; ... but not newline and cr, of course.  (cr is necessary for the
+    ;; selective display).
+    (aset table ?\n nil)
+    (aset table ?\r nil)
+    ;; We nix out any glyphs over 126 that are not set already.
+    (let ((i 256))
+      (while (>= (setq i (1- i)) 127)
+	;; Only modify if the entry is nil.
+	(unless (aref table i)
+	  (aset table i [??]))))
+    (setq buffer-display-table table)))
+
+(defun gnus-summary-setup-buffer (group)
+  "Initialize summary buffer."
+  (let ((buffer (concat "*Summary " group "*")))
+    (if (get-buffer buffer)
+	(progn
+	  (set-buffer buffer)
+	  (setq gnus-summary-buffer (current-buffer))
+	  (not gnus-newsgroup-prepared))
+      ;; Fix by Sudish Joseph <joseph@cis.ohio-state.edu>
+      (setq gnus-summary-buffer (set-buffer (get-buffer-create buffer)))
+      (gnus-add-current-to-buffer-list)
+      (gnus-summary-mode group)
+      (when gnus-carpal
+	(gnus-carpal-setup-buffer 'summary))
+      (unless gnus-single-article-buffer
+	(make-local-variable 'gnus-article-buffer)
+	(make-local-variable 'gnus-article-current)
+	(make-local-variable 'gnus-original-article-buffer))
+      (setq gnus-newsgroup-name group)
+      t)))
+
+(defun gnus-set-global-variables ()
+  ;; Set the global equivalents of the summary buffer-local variables
+  ;; to the latest values they had.  These reflect the summary buffer
+  ;; that was in action when the last article was fetched.
+  (when (eq major-mode 'gnus-summary-mode)
+    (setq gnus-summary-buffer (current-buffer))
+    (let ((name gnus-newsgroup-name)
+	  (marked gnus-newsgroup-marked)
+	  (unread gnus-newsgroup-unreads)
+	  (headers gnus-current-headers)
+	  (data gnus-newsgroup-data)
+	  (summary gnus-summary-buffer)
+	  (article-buffer gnus-article-buffer)
+	  (original gnus-original-article-buffer)
+	  (gac gnus-article-current)
+	  (reffed gnus-reffed-article-number)
+	  (score-file gnus-current-score-file))
+      (save-excursion
+	(set-buffer gnus-group-buffer)
+	(setq gnus-newsgroup-name name)
+	(setq gnus-newsgroup-marked marked)
+	(setq gnus-newsgroup-unreads unread)
+	(setq gnus-current-headers headers)
+	(setq gnus-newsgroup-data data)
+	(setq gnus-article-current gac)
+	(setq gnus-summary-buffer summary)
+	(setq gnus-article-buffer article-buffer)
+	(setq gnus-original-article-buffer original)
+	(setq gnus-reffed-article-number reffed)
+	(setq gnus-current-score-file score-file)
+	;; The article buffer also has local variables.
+	(when (gnus-buffer-live-p gnus-article-buffer)
+	  (set-buffer gnus-article-buffer)
+	  (setq gnus-summary-buffer summary))))))
+
+(defun gnus-summary-article-unread-p (article)
+  "Say whether ARTICLE is unread or not."
+  (memq article gnus-newsgroup-unreads))
+
+(defun gnus-summary-first-article-p (&optional article)
+  "Return whether ARTICLE is the first article in the buffer."
+  (if (not (setq article (or article (gnus-summary-article-number))))
+      nil
+    (eq article (caar gnus-newsgroup-data))))
+
+(defun gnus-summary-last-article-p (&optional article)
+  "Return whether ARTICLE is the last article in the buffer."
+  (if (not (setq article (or article (gnus-summary-article-number))))
+      t		; All non-existent numbers are the last article.  :-)
+    (not (cdr (gnus-data-find-list article)))))
+
+(defun gnus-make-thread-indent-array ()
+  (let ((n 200))
+    (unless (and gnus-thread-indent-array
+		 (= gnus-thread-indent-level gnus-thread-indent-array-level))
+      (setq gnus-thread-indent-array (make-vector 201 "")
+	    gnus-thread-indent-array-level gnus-thread-indent-level)
+      (while (>= n 0)
+	(aset gnus-thread-indent-array n
+	      (make-string (* n gnus-thread-indent-level) ? ))
+	(setq n (1- n))))))
+
+(defun gnus-update-summary-mark-positions ()
+  "Compute where the summary marks are to go."
+  (save-excursion
+    (when (and gnus-summary-buffer
+	       (get-buffer gnus-summary-buffer)
+	       (buffer-name (get-buffer gnus-summary-buffer)))
+      (set-buffer gnus-summary-buffer))
+    (let ((gnus-replied-mark 129)
+	  (gnus-score-below-mark 130)
+	  (gnus-score-over-mark 130)
+	  (spec gnus-summary-line-format-spec)
+	  thread gnus-visual pos)
+      (save-excursion
+	(gnus-set-work-buffer)
+	(let ((gnus-summary-line-format-spec spec))
+	  (gnus-summary-insert-line
+	   [0 "" "" "" "" "" 0 0 ""]  0 nil 128 t nil "" nil 1)
+	  (goto-char (point-min))
+	  (setq pos (list (cons 'unread (and (search-forward "\200" nil t)
+					     (- (point) 2)))))
+	  (goto-char (point-min))
+	  (push (cons 'replied (and (search-forward "\201" nil t)
+				    (- (point) 2)))
+		pos)
+	  (goto-char (point-min))
+	  (push (cons 'score (and (search-forward "\202" nil t) (- (point) 2)))
+		pos)))
+      (setq gnus-summary-mark-positions pos))))
+
+(defun gnus-summary-insert-dummy-line (gnus-tmp-subject gnus-tmp-number)
+  "Insert a dummy root in the summary buffer."
+  (beginning-of-line)
+  (gnus-add-text-properties
+   (point) (progn (eval gnus-summary-dummy-line-format-spec) (point))
+   (list 'gnus-number gnus-tmp-number 'gnus-intangible gnus-tmp-number)))
+
+(defun gnus-summary-insert-line (gnus-tmp-header
+				 gnus-tmp-level gnus-tmp-current
+				 gnus-tmp-unread gnus-tmp-replied
+				 gnus-tmp-expirable gnus-tmp-subject-or-nil
+				 &optional gnus-tmp-dummy gnus-tmp-score
+				 gnus-tmp-process)
+  (let* ((gnus-tmp-indentation (aref gnus-thread-indent-array gnus-tmp-level))
+	 (gnus-tmp-lines (mail-header-lines gnus-tmp-header))
+	 (gnus-tmp-score (or gnus-tmp-score gnus-summary-default-score 0))
+	 (gnus-tmp-score-char
+	  (if (or (null gnus-summary-default-score)
+		  (<= (abs (- gnus-tmp-score gnus-summary-default-score))
+		      gnus-summary-zcore-fuzz))
+	      ? 
+	    (if (< gnus-tmp-score gnus-summary-default-score)
+		gnus-score-below-mark gnus-score-over-mark)))
+	 (gnus-tmp-replied
+	  (cond (gnus-tmp-process gnus-process-mark)
+		((memq gnus-tmp-current gnus-newsgroup-cached)
+		 gnus-cached-mark)
+		(gnus-tmp-replied gnus-replied-mark)
+		((memq gnus-tmp-current gnus-newsgroup-saved)
+		 gnus-saved-mark)
+		(t gnus-unread-mark)))
+	 (gnus-tmp-from (mail-header-from gnus-tmp-header))
+	 (gnus-tmp-name
+	  (cond
+	   ((string-match "<[^>]+> *$" gnus-tmp-from)
+	    (let ((beg (match-beginning 0)))
+	      (or (and (string-match "^\"[^\"]*\"" gnus-tmp-from)
+		       (substring gnus-tmp-from (1+ (match-beginning 0))
+				  (1- (match-end 0))))
+		  (substring gnus-tmp-from 0 beg))))
+	   ((string-match "(.+)" gnus-tmp-from)
+	    (substring gnus-tmp-from
+		       (1+ (match-beginning 0)) (1- (match-end 0))))
+	   (t gnus-tmp-from)))
+	 (gnus-tmp-subject (mail-header-subject gnus-tmp-header))
+	 (gnus-tmp-number (mail-header-number gnus-tmp-header))
+	 (gnus-tmp-opening-bracket (if gnus-tmp-dummy ?\< ?\[))
+	 (gnus-tmp-closing-bracket (if gnus-tmp-dummy ?\> ?\]))
+	 (buffer-read-only nil))
+    (when (string= gnus-tmp-name "")
+      (setq gnus-tmp-name gnus-tmp-from))
+    (unless (numberp gnus-tmp-lines)
+      (setq gnus-tmp-lines 0))
+    (gnus-put-text-property
+     (point)
+     (progn (eval gnus-summary-line-format-spec) (point))
+     'gnus-number gnus-tmp-number)
+    (when (gnus-visual-p 'summary-highlight 'highlight)
+      (forward-line -1)
+      (run-hooks 'gnus-summary-update-hook)
+      (forward-line 1))))
+
+(defun gnus-summary-update-line (&optional dont-update)
+  ;; Update summary line after change.
+  (when (and gnus-summary-default-score
+	     (not gnus-summary-inhibit-highlight))
+    (let* ((gnus-summary-inhibit-highlight t) ; Prevent recursion.
+	   (article (gnus-summary-article-number))
+	   (score (gnus-summary-article-score article)))
+      (unless dont-update
+	(if (and gnus-summary-mark-below
+		 (< (gnus-summary-article-score)
+		    gnus-summary-mark-below))
+	    ;; This article has a low score, so we mark it as read.
+	    (when (memq article gnus-newsgroup-unreads)
+	      (gnus-summary-mark-article-as-read gnus-low-score-mark))
+	  (when (eq (gnus-summary-article-mark) gnus-low-score-mark)
+	    ;; This article was previously marked as read on account
+	    ;; of a low score, but now it has risen, so we mark it as
+	    ;; unread.
+	    (gnus-summary-mark-article-as-unread gnus-unread-mark)))
+	(gnus-summary-update-mark
+	 (if (or (null gnus-summary-default-score)
+		 (<= (abs (- score gnus-summary-default-score))
+		     gnus-summary-zcore-fuzz))
+	     ? 
+	   (if (< score gnus-summary-default-score)
+	       gnus-score-below-mark gnus-score-over-mark))
+	 'score))
+      ;; Do visual highlighting.
+      (when (gnus-visual-p 'summary-highlight 'highlight)
+	(run-hooks 'gnus-summary-update-hook)))))
+
+(defvar gnus-tmp-new-adopts nil)
+
+(defun gnus-summary-number-of-articles-in-thread (thread &optional level char)
+  "Return the number of articles in THREAD.
+This may be 0 in some cases -- if none of the articles in
+the thread are to be displayed."
+  (let* ((number
+	  ;; Fix by Luc Van Eycken <Luc.VanEycken@esat.kuleuven.ac.be>.
+	  (cond
+	   ((not (listp thread))
+	    1)
+	   ((and (consp thread) (cdr thread))
+	    (apply
+	     '+ 1 (mapcar
+		   'gnus-summary-number-of-articles-in-thread (cdr thread))))
+	   ((null thread)
+	    1)
+	   ((memq (mail-header-number (car thread)) gnus-newsgroup-limit)
+	    1)
+	   (t 0))))
+    (when (and level (zerop level) gnus-tmp-new-adopts)
+      (incf number
+	    (apply '+ (mapcar
+		       'gnus-summary-number-of-articles-in-thread
+		       gnus-tmp-new-adopts))))
+    (if char
+	(if (> number 1) gnus-not-empty-thread-mark
+	  gnus-empty-thread-mark)
+      number)))
+
+(defun gnus-summary-set-local-parameters (group)
+  "Go through the local params of GROUP and set all variable specs in that list."
+  (let ((params (gnus-group-find-parameter group))
+	elem)
+    (while params
+      (setq elem (car params)
+	    params (cdr params))
+      (and (consp elem)			; Has to be a cons.
+	   (consp (cdr elem))		; The cdr has to be a list.
+	   (symbolp (car elem))		; Has to be a symbol in there.
+	   (not (memq (car elem)
+		      '(quit-config to-address to-list to-group)))
+	   (ignore-errors		; So we set it.
+	     (make-local-variable (car elem))
+	     (set (car elem) (eval (nth 1 elem))))))))
+
+(defun gnus-summary-read-group (group &optional show-all no-article
+				      kill-buffer no-display)
+  "Start reading news in newsgroup GROUP.
+If SHOW-ALL is non-nil, already read articles are also listed.
+If NO-ARTICLE is non-nil, no article is selected initially.
+If NO-DISPLAY, don't generate a summary buffer."
+  (let (result)
+    (while (and group
+		(null (setq result
+			    (let ((gnus-auto-select-next nil))
+			      (gnus-summary-read-group-1
+			       group show-all no-article
+			       kill-buffer no-display))))
+		(eq gnus-auto-select-next 'quietly))
+      (set-buffer gnus-group-buffer)
+      (if (not (equal group (gnus-group-group-name)))
+	  (setq group (gnus-group-group-name))
+	(setq group nil)))
+    result))
+
+(defun gnus-summary-read-group-1 (group show-all no-article
+					kill-buffer no-display)
+  ;; Killed foreign groups can't be entered.
+  (when (and (not (gnus-group-native-p group))
+	     (not (gnus-gethash group gnus-newsrc-hashtb)))
+    (error "Dead non-native groups can't be entered"))
+  (gnus-message 5 "Retrieving newsgroup: %s..." group)
+  (let* ((new-group (gnus-summary-setup-buffer group))
+	 (quit-config (gnus-group-quit-config group))
+	 (did-select (and new-group (gnus-select-newsgroup group show-all))))
+    (cond
+     ;; This summary buffer exists already, so we just select it.
+     ((not new-group)
+      (gnus-set-global-variables)
+      (when kill-buffer
+	(gnus-kill-or-deaden-summary kill-buffer))
+      (gnus-configure-windows 'summary 'force)
+      (gnus-set-mode-line 'summary)
+      (gnus-summary-position-point)
+      (message "")
+      t)
+     ;; We couldn't select this group.
+     ((null did-select)
+      (when (and (eq major-mode 'gnus-summary-mode)
+		 (not (equal (current-buffer) kill-buffer)))
+	(kill-buffer (current-buffer))
+	(if (not quit-config)
+	    (progn
+	      (set-buffer gnus-group-buffer)
+	      (gnus-group-jump-to-group group)
+	      (gnus-group-next-unread-group 1))
+	  (gnus-handle-ephemeral-exit quit-config)))
+      (gnus-message 3 "Can't select group")
+      nil)
+     ;; The user did a `C-g' while prompting for number of articles,
+     ;; so we exit this group.
+     ((eq did-select 'quit)
+      (and (eq major-mode 'gnus-summary-mode)
+	   (not (equal (current-buffer) kill-buffer))
+	   (kill-buffer (current-buffer)))
+      (when kill-buffer
+	(gnus-kill-or-deaden-summary kill-buffer))
+      (if (not quit-config)
+	  (progn
+	    (set-buffer gnus-group-buffer)
+	    (gnus-group-jump-to-group group)
+	    (gnus-group-next-unread-group 1)
+	    (gnus-configure-windows 'group 'force))
+	(gnus-handle-ephemeral-exit quit-config))
+      ;; Finally signal the quit.
+      (signal 'quit nil))
+     ;; The group was successfully selected.
+     (t
+      (gnus-set-global-variables)
+      ;; Save the active value in effect when the group was entered.
+      (setq gnus-newsgroup-active
+	    (gnus-copy-sequence
+	     (gnus-active gnus-newsgroup-name)))
+      ;; You can change the summary buffer in some way with this hook.
+      (run-hooks 'gnus-select-group-hook)
+      ;; Set any local variables in the group parameters.
+      (gnus-summary-set-local-parameters gnus-newsgroup-name)
+      (gnus-update-format-specifications
+       nil 'summary 'summary-mode 'summary-dummy)
+      ;; Do score processing.
+      (when gnus-use-scoring
+	(gnus-possibly-score-headers))
+      ;; Check whether to fill in the gaps in the threads.
+      (when gnus-build-sparse-threads
+	(gnus-build-sparse-threads))
+      ;; Find the initial limit.
+      (if gnus-show-threads
+	  (if show-all
+	      (let ((gnus-newsgroup-dormant nil))
+		(gnus-summary-initial-limit show-all))
+	    (gnus-summary-initial-limit show-all))
+	(setq gnus-newsgroup-limit
+	      (mapcar
+	       (lambda (header) (mail-header-number header))
+	       gnus-newsgroup-headers)))
+      ;; Generate the summary buffer.
+      (unless no-display
+	(gnus-summary-prepare))
+      (when gnus-use-trees
+	(gnus-tree-open group)
+	(setq gnus-summary-highlight-line-function
+	      'gnus-tree-highlight-article))
+      ;; If the summary buffer is empty, but there are some low-scored
+      ;; articles or some excluded dormants, we include these in the
+      ;; buffer.
+      (when (and (zerop (buffer-size))
+		 (not no-display))
+	(cond (gnus-newsgroup-dormant
+	       (gnus-summary-limit-include-dormant))
+	      ((and gnus-newsgroup-scored show-all)
+	       (gnus-summary-limit-include-expunged t))))
+      ;; Function `gnus-apply-kill-file' must be called in this hook.
+      (run-hooks 'gnus-apply-kill-hook)
+      (if (and (zerop (buffer-size))
+	       (not no-display))
+	  (progn
+	    ;; This newsgroup is empty.
+	    (gnus-summary-catchup-and-exit nil t)
+	    (gnus-message 6 "No unread news")
+	    (when kill-buffer
+	      (gnus-kill-or-deaden-summary kill-buffer))
+	    ;; Return nil from this function.
+	    nil)
+	;; Hide conversation thread subtrees.  We cannot do this in
+	;; gnus-summary-prepare-hook since kill processing may not
+	;; work with hidden articles.
+	(and gnus-show-threads
+	     gnus-thread-hide-subtree
+	     (gnus-summary-hide-all-threads))
+	;; Show first unread article if requested.
+	(if (and (not no-article)
+		 (not no-display)
+		 gnus-newsgroup-unreads
+		 gnus-auto-select-first)
+	    (unless (if (eq gnus-auto-select-first 'best)
+			(gnus-summary-best-unread-article)
+		      (gnus-summary-first-unread-article))
+	      (gnus-configure-windows 'summary))
+	  ;; Don't select any articles, just move point to the first
+	  ;; article in the group.
+	  (goto-char (point-min))
+	  (gnus-summary-position-point)
+	  (gnus-set-mode-line 'summary)
+	  (gnus-configure-windows 'summary 'force))
+	(when kill-buffer
+	  (gnus-kill-or-deaden-summary kill-buffer))
+	(when (get-buffer-window gnus-group-buffer t)
+	  ;; Gotta use windows, because recenter does weird stuff if
+	  ;; the current buffer ain't the displayed window.
+	  (let ((owin (selected-window)))
+	    (select-window (get-buffer-window gnus-group-buffer t))
+	    (when (gnus-group-goto-group group)
+	      (recenter))
+	    (select-window owin)))
+	;; Mark this buffer as "prepared".
+	(setq gnus-newsgroup-prepared t)
+	t)))))
+
+(defun gnus-summary-prepare ()
+  "Generate the summary buffer."
+  (interactive)
+  (let ((buffer-read-only nil))
+    (erase-buffer)
+    (setq gnus-newsgroup-data nil
+	  gnus-newsgroup-data-reverse nil)
+    (run-hooks 'gnus-summary-generate-hook)
+    ;; Generate the buffer, either with threads or without.
+    (when gnus-newsgroup-headers
+      (gnus-summary-prepare-threads
+       (if gnus-show-threads
+	   (gnus-sort-gathered-threads
+	    (funcall gnus-summary-thread-gathering-function
+		     (gnus-sort-threads
+		      (gnus-cut-threads (gnus-make-threads)))))
+	 ;; Unthreaded display.
+	 (gnus-sort-articles gnus-newsgroup-headers))))
+    (setq gnus-newsgroup-data (nreverse gnus-newsgroup-data))
+    ;; Call hooks for modifying summary buffer.
+    (goto-char (point-min))
+    (run-hooks 'gnus-summary-prepare-hook)))
+
+(defsubst gnus-general-simplify-subject (subject)
+  "Simply subject by the same rules as gnus-gather-threads-by-subject."
+  (setq subject
+	(cond
+	 ;; Truncate the subject.
+	 ((numberp gnus-summary-gather-subject-limit)
+	  (setq subject (gnus-simplify-subject-re subject))
+	  (if (> (length subject) gnus-summary-gather-subject-limit)
+	      (substring subject 0 gnus-summary-gather-subject-limit)
+	    subject))
+	 ;; Fuzzily simplify it.
+	 ((eq 'fuzzy gnus-summary-gather-subject-limit)
+	  (gnus-simplify-subject-fuzzy subject))
+	 ;; Just remove the leading "Re:".
+	 (t
+	  (gnus-simplify-subject-re subject))))
+
+  (if (and gnus-summary-gather-exclude-subject
+	   (string-match gnus-summary-gather-exclude-subject subject))
+      nil				; This article shouldn't be gathered
+    subject))
+
+(defun gnus-summary-simplify-subject-query ()
+  "Query where the respool algorithm would put this article."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-summary-select-article)
+  (message (gnus-general-simplify-subject (gnus-summary-article-subject))))
+
+(defun gnus-gather-threads-by-subject (threads)
+  "Gather threads by looking at Subject headers."
+  (if (not gnus-summary-make-false-root)
+      threads
+    (let ((hashtb (gnus-make-hashtable 1024))
+	  (prev threads)
+	  (result threads)
+	  subject hthread whole-subject)
+      (while threads
+	(setq subject (gnus-general-simplify-subject
+		       (setq whole-subject (mail-header-subject
+					    (caar threads)))))
+	(when subject
+	  (if (setq hthread (gnus-gethash subject hashtb))
+	      (progn
+		;; We enter a dummy root into the thread, if we
+		;; haven't done that already.
+		(unless (stringp (caar hthread))
+		  (setcar hthread (list whole-subject (car hthread))))
+		;; We add this new gathered thread to this gathered
+		;; thread.
+		(setcdr (car hthread)
+			(nconc (cdar hthread) (list (car threads))))
+		;; Remove it from the list of threads.
+		(setcdr prev (cdr threads))
+		(setq threads prev))
+	    ;; Enter this thread into the hash table.
+	    (gnus-sethash subject threads hashtb)))
+	(setq prev threads)
+	(setq threads (cdr threads)))
+      result)))
+
+(defun gnus-gather-threads-by-references (threads)
+  "Gather threads by looking at References headers."
+  (let ((idhashtb (gnus-make-hashtable 1024))
+	(thhashtb (gnus-make-hashtable 1024))
+	(prev threads)
+	(result threads)
+	ids references id gthread gid entered ref)
+    (while threads
+      (when (setq references (mail-header-references (caar threads)))
+	(setq id (mail-header-id (caar threads))
+	      ids (gnus-split-references references)
+	      entered nil)
+	(while (setq ref (pop ids))
+	  (setq ids (delete ref ids))
+	  (if (not (setq gid (gnus-gethash ref idhashtb)))
+	      (progn
+		(gnus-sethash ref id idhashtb)
+		(gnus-sethash id threads thhashtb))
+	    (setq gthread (gnus-gethash gid thhashtb))
+	    (unless entered
+	      ;; We enter a dummy root into the thread, if we
+	      ;; haven't done that already.
+	      (unless (stringp (caar gthread))
+		(setcar gthread (list (mail-header-subject (caar gthread))
+				      (car gthread))))
+	      ;; We add this new gathered thread to this gathered
+	      ;; thread.
+	      (setcdr (car gthread)
+		      (nconc (cdar gthread) (list (car threads)))))
+	    ;; Add it into the thread hash table.
+	    (gnus-sethash id gthread thhashtb)
+	    (setq entered t)
+	    ;; Remove it from the list of threads.
+	    (setcdr prev (cdr threads))
+	    (setq threads prev))))
+      (setq prev threads)
+      (setq threads (cdr threads)))
+    result))
+
+(defun gnus-sort-gathered-threads (threads)
+  "Sort subtreads inside each gathered thread by article number."
+  (let ((result threads))
+    (while threads
+      (when (stringp (caar threads))
+	(setcdr (car threads)
+		(sort (cdar threads) 'gnus-thread-sort-by-number)))
+      (setq threads (cdr threads)))
+    result))
+
+(defun gnus-thread-loop-p (root thread)
+  "Say whether ROOT is in THREAD."
+  (let ((stack (list thread))
+	(infloop 0)
+	th)
+    (while (setq thread (pop stack))
+      (setq th (cdr thread))
+      (while (and th
+		  (not (eq (caar th) root)))
+	(pop th))
+      (if th
+	  ;; We have found a loop.
+	  (let (ref-dep)
+	    (setcdr thread (delq (car th) (cdr thread)))
+	    (if (boundp (setq ref-dep (intern "none"
+					      gnus-newsgroup-dependencies)))
+		(setcdr (symbol-value ref-dep)
+			(nconc (cdr (symbol-value ref-dep))
+			       (list (car th))))
+	      (set ref-dep (list nil (car th))))
+	    (setq infloop 1
+		  stack nil))
+	;; Push all the subthreads onto the stack.
+	(push (cdr thread) stack)))
+    infloop))
+
+(defun gnus-make-threads ()
+  "Go through the dependency hashtb and find the roots.	 Return all threads."
+  (let (threads)
+    (while (catch 'infloop
+	     (mapatoms
+	      (lambda (refs)
+		;; Deal with self-referencing References loops.
+		(when (and (car (symbol-value refs))
+			   (not (zerop
+				 (apply
+				  '+
+				  (mapcar
+				   (lambda (thread)
+				     (gnus-thread-loop-p
+				      (car (symbol-value refs)) thread))
+				   (cdr (symbol-value refs)))))))
+		  (setq threads nil)
+		  (throw 'infloop t))
+		(unless (car (symbol-value refs))
+		  ;; These threads do not refer back to any other articles,
+		  ;; so they're roots.
+		  (setq threads (append (cdr (symbol-value refs)) threads))))
+	      gnus-newsgroup-dependencies)))
+    threads))
+
+(defun gnus-build-sparse-threads ()
+  (let ((headers gnus-newsgroup-headers)
+	(deps gnus-newsgroup-dependencies)
+	header references generation relations
+	cthread subject child end pthread relation)
+    ;; First we create an alist of generations/relations, where
+    ;; generations is how much we trust the relation, and the relation
+    ;; is parent/child.
+    (gnus-message 7 "Making sparse threads...")
+    (save-excursion
+      (nnheader-set-temp-buffer " *gnus sparse threads*")
+      (while (setq header (pop headers))
+	(when (and (setq references (mail-header-references header))
+		   (not (string= references "")))
+	  (insert references)
+	  (setq child (mail-header-id header)
+		subject (mail-header-subject header))
+	  (setq generation 0)
+	  (while (search-backward ">" nil t)
+	    (setq end (1+ (point)))
+	    (when (search-backward "<" nil t)
+	      (push (list (incf generation)
+			  child (setq child (buffer-substring (point) end))
+			  subject)
+		    relations)))
+	  (push (list (1+ generation) child nil subject) relations)
+	  (erase-buffer)))
+      (kill-buffer (current-buffer)))
+    ;; Sort over trustworthiness.
+    (setq relations (sort relations (lambda (r1 r2) (< (car r1) (car r2)))))
+    (while (setq relation (pop relations))
+      (when (if (boundp (setq cthread (intern (cadr relation) deps)))
+		(unless (car (symbol-value cthread))
+		  ;; Make this article the parent of these threads.
+		  (setcar (symbol-value cthread)
+			  (vector gnus-reffed-article-number
+				  (cadddr relation)
+				  "" ""
+				  (cadr relation)
+				  (or (caddr relation) "") 0 0 "")))
+	      (set cthread (list (vector gnus-reffed-article-number
+					 (cadddr relation)
+					 "" "" (cadr relation)
+					 (or (caddr relation) "") 0 0 ""))))
+	(push gnus-reffed-article-number gnus-newsgroup-limit)
+	(push gnus-reffed-article-number gnus-newsgroup-sparse)
+	(push (cons gnus-reffed-article-number gnus-sparse-mark)
+	      gnus-newsgroup-reads)
+	(decf gnus-reffed-article-number)
+	;; Make this new thread the child of its parent.
+	(if (boundp (setq pthread (intern (or (caddr relation) "none") deps)))
+	    (setcdr (symbol-value pthread)
+		    (nconc (cdr (symbol-value pthread))
+			   (list (symbol-value cthread))))
+	  (set pthread (list nil (symbol-value cthread))))))
+    (gnus-message 7 "Making sparse threads...done")))
+
+(defun gnus-build-old-threads ()
+  ;; Look at all the articles that refer back to old articles, and
+  ;; fetch the headers for the articles that aren't there.  This will
+  ;; build complete threads - if the roots haven't been expired by the
+  ;; server, that is.
+  (let (id heads)
+    (mapatoms
+     (lambda (refs)
+       (when (not (car (symbol-value refs)))
+	 (setq heads (cdr (symbol-value refs)))
+	 (while heads
+	   (if (memq (mail-header-number (caar heads))
+		     gnus-newsgroup-dormant)
+	       (setq heads (cdr heads))
+	     (setq id (symbol-name refs))
+	     (while (and (setq id (gnus-build-get-header id))
+			 (not (car (gnus-gethash
+				    id gnus-newsgroup-dependencies)))))
+	     (setq heads nil)))))
+     gnus-newsgroup-dependencies)))
+
+(defun gnus-build-get-header (id)
+  ;; Look through the buffer of NOV lines and find the header to
+  ;; ID.  Enter this line into the dependencies hash table, and return
+  ;; the id of the parent article (if any).
+  (let ((deps gnus-newsgroup-dependencies)
+	found header)
+    (prog1
+	(save-excursion
+	  (set-buffer nntp-server-buffer)
+	  (let ((case-fold-search nil))
+	    (goto-char (point-min))
+	    (while (and (not found)
+			(search-forward id nil t))
+	      (beginning-of-line)
+	      (setq found (looking-at
+			   (format "^[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t%s"
+				   (regexp-quote id))))
+	      (or found (beginning-of-line 2)))
+	    (when found
+	      (beginning-of-line)
+	      (and
+	       (setq header (gnus-nov-parse-line
+			     (read (current-buffer)) deps))
+	       (gnus-parent-id (mail-header-references header))))))
+      (when header
+	(let ((number (mail-header-number header)))
+	  (push number gnus-newsgroup-limit)
+	  (push header gnus-newsgroup-headers)
+	  (if (memq number gnus-newsgroup-unselected)
+	      (progn
+		(push number gnus-newsgroup-unreads)
+		(setq gnus-newsgroup-unselected
+		      (delq number gnus-newsgroup-unselected)))
+	    (push number gnus-newsgroup-ancient)))))))
+
+(defun gnus-summary-update-article-line (article header)
+  "Update the line for ARTICLE using HEADERS."
+  (let* ((id (mail-header-id header))
+	 (thread (gnus-id-to-thread id)))
+    (unless thread
+      (error "Article in no thread"))
+    ;; Update the thread.
+    (setcar thread header)
+    (gnus-summary-goto-subject article)
+    (let* ((datal (gnus-data-find-list article))
+	   (data (car datal))
+	   (length (when (cdr datal)
+		     (- (gnus-data-pos data)
+			(gnus-data-pos (cadr datal)))))
+	   (buffer-read-only nil)
+	   (level (gnus-summary-thread-level)))
+      (gnus-delete-line)
+      (gnus-summary-insert-line
+       header level nil (gnus-article-mark article)
+       (memq article gnus-newsgroup-replied)
+       (memq article gnus-newsgroup-expirable)
+       ;; Only insert the Subject string when it's different
+       ;; from the previous Subject string.
+       (if (gnus-subject-equal
+	    (condition-case ()
+		(mail-header-subject
+		 (gnus-data-header
+		  (cadr
+		   (gnus-data-find-list
+		    article
+		    (gnus-data-list t)))))
+	      ;; Error on the side of excessive subjects.
+	      (error ""))
+	    (mail-header-subject header))
+	   ""
+	 (mail-header-subject header))
+       nil (cdr (assq article gnus-newsgroup-scored))
+       (memq article gnus-newsgroup-processable))
+      (when length
+	(gnus-data-update-list
+	 (cdr datal) (- length (- (gnus-data-pos data) (point))))))))
+
+(defun gnus-summary-update-article (article &optional iheader)
+  "Update ARTICLE in the summary buffer."
+  (set-buffer gnus-summary-buffer)
+  (let* ((header (or iheader (gnus-summary-article-header article)))
+	 (id (mail-header-id header))
+	 (data (gnus-data-find article))
+	 (thread (gnus-id-to-thread id))
+	 (references (mail-header-references header))
+	 (parent
+	  (gnus-id-to-thread
+	   (or (gnus-parent-id
+		(when (and references
+			   (not (equal "" references)))
+		  references))
+	       "none")))
+	 (buffer-read-only nil)
+	 (old (car thread))
+	 (number (mail-header-number header))
+	 pos)
+    (when thread
+      ;; !!! Should this be in or not?
+      (unless iheader
+	(setcar thread nil))
+      (when parent
+	(delq thread parent))
+      (if (gnus-summary-insert-subject id header iheader)
+	  ;; Set the (possibly) new article number in the data structure.
+	  (gnus-data-set-number data (gnus-id-to-article id))
+	(setcar thread old)
+	nil))))
+
+(defun gnus-rebuild-thread (id)
+  "Rebuild the thread containing ID."
+  (let ((buffer-read-only nil)
+	old-pos current thread data)
+    (if (not gnus-show-threads)
+	(setq thread (list (car (gnus-id-to-thread id))))
+      ;; Get the thread this article is part of.
+      (setq thread (gnus-remove-thread id)))
+    (setq old-pos (gnus-point-at-bol))
+    (setq current (save-excursion
+		    (and (zerop (forward-line -1))
+			 (gnus-summary-article-number))))
+    ;; If this is a gathered thread, we have to go some re-gathering.
+    (when (stringp (car thread))
+      (let ((subject (car thread))
+	    roots thr)
+	(setq thread (cdr thread))
+	(while thread
+	  (unless (memq (setq thr (gnus-id-to-thread
+				   (gnus-root-id
+				    (mail-header-id (caar thread)))))
+			roots)
+	    (push thr roots))
+	  (setq thread (cdr thread)))
+	;; We now have all (unique) roots.
+	(if (= (length roots) 1)
+	    ;; All the loose roots are now one solid root.
+	    (setq thread (car roots))
+	  (setq thread (cons subject (gnus-sort-threads roots))))))
+    (let (threads)
+      ;; We then insert this thread into the summary buffer.
+      (let (gnus-newsgroup-data gnus-newsgroup-threads)
+	(if gnus-show-threads
+	    (gnus-summary-prepare-threads (gnus-cut-threads (list thread)))
+	  (gnus-summary-prepare-unthreaded thread))
+	(setq data (nreverse gnus-newsgroup-data))
+	(setq threads gnus-newsgroup-threads))
+      ;; We splice the new data into the data structure.
+      (gnus-data-enter-list current data (- (point) old-pos))
+      (setq gnus-newsgroup-threads (nconc threads gnus-newsgroup-threads)))))
+
+(defun gnus-number-to-header (number)
+  "Return the header for article NUMBER."
+  (let ((headers gnus-newsgroup-headers))
+    (while (and headers
+		(not (= number (mail-header-number (car headers)))))
+      (pop headers))
+    (when headers
+      (car headers))))
+
+(defun gnus-parent-headers (headers &optional generation)
+  "Return the headers of the GENERATIONeth parent of HEADERS."
+  (unless generation
+    (setq generation 1))
+  (let (references parent)
+    (while (and headers (not (zerop generation)))
+      (setq references (mail-header-references headers))
+      (when (and references
+		 (setq parent (gnus-parent-id references))
+		 (setq headers (car (gnus-id-to-thread parent))))
+	(decf generation)))
+    headers))
+
+(defun gnus-id-to-thread (id)
+  "Return the (sub-)thread where ID appears."
+  (gnus-gethash id gnus-newsgroup-dependencies))
+
+(defun gnus-id-to-article (id)
+  "Return the article number of ID."
+  (let ((thread (gnus-id-to-thread id)))
+    (when (and thread
+	       (car thread))
+      (mail-header-number (car thread)))))
+
+(defun gnus-id-to-header (id)
+  "Return the article headers of ID."
+  (car (gnus-id-to-thread id)))
+
+(defun gnus-article-displayed-root-p (article)
+  "Say whether ARTICLE is a root(ish) article."
+  (let ((level (gnus-summary-thread-level article))
+	(refs (mail-header-references  (gnus-summary-article-header article)))
+	particle)
+    (cond
+     ((null level) nil)
+     ((zerop level) t)
+     ((null refs) t)
+     ((null (gnus-parent-id refs)) t)
+     ((and (= 1 level)
+	   (null (setq particle (gnus-id-to-article
+				 (gnus-parent-id refs))))
+	   (null (gnus-summary-thread-level particle)))))))
+
+(defun gnus-root-id (id)
+  "Return the id of the root of the thread where ID appears."
+  (let (last-id prev)
+    (while (and id (setq prev (car (gnus-gethash
+				    id gnus-newsgroup-dependencies))))
+      (setq last-id id
+	    id (gnus-parent-id (mail-header-references prev))))
+    last-id))
+
+(defun gnus-remove-thread (id &optional dont-remove)
+  "Remove the thread that has ID in it."
+  (let ((dep gnus-newsgroup-dependencies)
+	headers thread last-id)
+    ;; First go up in this thread until we find the root.
+    (setq last-id (gnus-root-id id))
+    (setq headers (list (car (gnus-id-to-thread last-id))
+			(caadr (gnus-id-to-thread last-id))))
+    ;; We have now found the real root of this thread.	It might have
+    ;; been gathered into some loose thread, so we have to search
+    ;; through the threads to find the thread we wanted.
+    (let ((threads gnus-newsgroup-threads)
+	  sub)
+      (while threads
+	(setq sub (car threads))
+	(if (stringp (car sub))
+	    ;; This is a gathered thread, so we look at the roots
+	    ;; below it to find whether this article is in this
+	    ;; gathered root.
+	    (progn
+	      (setq sub (cdr sub))
+	      (while sub
+		(when (member (caar sub) headers)
+		  (setq thread (car threads)
+			threads nil
+			sub nil))
+		(setq sub (cdr sub))))
+	  ;; It's an ordinary thread, so we check it.
+	  (when (eq (car sub) (car headers))
+	    (setq thread sub
+		  threads nil)))
+	(setq threads (cdr threads)))
+      ;; If this article is in no thread, then it's a root.
+      (if thread
+	  (unless dont-remove
+	    (setq gnus-newsgroup-threads (delq thread gnus-newsgroup-threads)))
+	(setq thread (gnus-gethash last-id dep)))
+      (when thread
+	(prog1
+	    thread			; We return this thread.
+	  (unless dont-remove
+	    (if (stringp (car thread))
+		(progn
+		  ;; If we use dummy roots, then we have to remove the
+		  ;; dummy root as well.
+		  (when (eq gnus-summary-make-false-root 'dummy)
+		    (gnus-delete-line)
+		    (gnus-data-compute-positions))
+		  (setq thread (cdr thread))
+		  (while thread
+		    (gnus-remove-thread-1 (car thread))
+		    (setq thread (cdr thread))))
+	      (gnus-remove-thread-1 thread))))))))
+
+(defun gnus-remove-thread-1 (thread)
+  "Remove the thread THREAD recursively."
+  (let ((number (mail-header-number (pop thread)))
+	d)
+    (setq thread (reverse thread))
+    (while thread
+      (gnus-remove-thread-1 (pop thread)))
+    (when (setq d (gnus-data-find number))
+      (goto-char (gnus-data-pos d))
+      (gnus-data-remove
+       number
+       (- (gnus-point-at-bol)
+	  (prog1
+	      (1+ (gnus-point-at-eol))
+	    (gnus-delete-line)))))))
+
+(defun gnus-sort-threads (threads)
+  "Sort THREADS."
+  (if (not gnus-thread-sort-functions)
+      threads
+    (gnus-message 7 "Sorting threads...")
+    (prog1
+	(sort threads (gnus-make-sort-function gnus-thread-sort-functions))
+      (gnus-message 7 "Sorting threads...done"))))
+
+(defun gnus-sort-articles (articles)
+  "Sort ARTICLES."
+  (when gnus-article-sort-functions
+    (gnus-message 7 "Sorting articles...")
+    (prog1
+	(setq gnus-newsgroup-headers
+	      (sort articles (gnus-make-sort-function
+			      gnus-article-sort-functions)))
+      (gnus-message 7 "Sorting articles...done"))))
+
+;; Written by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>.
+(defmacro gnus-thread-header (thread)
+  ;; Return header of first article in THREAD.
+  ;; Note that THREAD must never, ever be anything else than a variable -
+  ;; using some other form will lead to serious barfage.
+  (or (symbolp thread) (signal 'wrong-type-argument '(symbolp thread)))
+  ;; (8% speedup to gnus-summary-prepare, just for fun :-)
+  (list 'byte-code "\10\211:\203\17\0\211@;\203\16\0A@@\207" ;
+	(vector thread) 2))
+
+(defsubst gnus-article-sort-by-number (h1 h2)
+  "Sort articles by article number."
+  (< (mail-header-number h1)
+     (mail-header-number h2)))
+
+(defun gnus-thread-sort-by-number (h1 h2)
+  "Sort threads by root article number."
+  (gnus-article-sort-by-number
+   (gnus-thread-header h1) (gnus-thread-header h2)))
+
+(defsubst gnus-article-sort-by-lines (h1 h2)
+  "Sort articles by article Lines header."
+  (< (mail-header-lines h1)
+     (mail-header-lines h2)))
+
+(defun gnus-thread-sort-by-lines (h1 h2)
+  "Sort threads by root article Lines header."
+  (gnus-article-sort-by-lines
+   (gnus-thread-header h1) (gnus-thread-header h2)))
+
+(defsubst gnus-article-sort-by-author (h1 h2)
+  "Sort articles by root author."
+  (string-lessp
+   (let ((extract (funcall
+		   gnus-extract-address-components
+		   (mail-header-from h1))))
+     (or (car extract) (cadr extract) ""))
+   (let ((extract (funcall
+		   gnus-extract-address-components
+		   (mail-header-from h2))))
+     (or (car extract) (cadr extract) ""))))
+
+(defun gnus-thread-sort-by-author (h1 h2)
+  "Sort threads by root author."
+  (gnus-article-sort-by-author
+   (gnus-thread-header h1)  (gnus-thread-header h2)))
+
+(defsubst gnus-article-sort-by-subject (h1 h2)
+  "Sort articles by root subject."
+  (string-lessp
+   (downcase (gnus-simplify-subject-re (mail-header-subject h1)))
+   (downcase (gnus-simplify-subject-re (mail-header-subject h2)))))
+
+(defun gnus-thread-sort-by-subject (h1 h2)
+  "Sort threads by root subject."
+  (gnus-article-sort-by-subject
+   (gnus-thread-header h1) (gnus-thread-header h2)))
+
+(defsubst gnus-article-sort-by-date (h1 h2)
+  "Sort articles by root article date."
+  (gnus-time-less
+   (gnus-date-get-time (mail-header-date h1))
+   (gnus-date-get-time (mail-header-date h2))))
+
+(defun gnus-thread-sort-by-date (h1 h2)
+  "Sort threads by root article date."
+  (gnus-article-sort-by-date
+   (gnus-thread-header h1) (gnus-thread-header h2)))
+
+(defsubst gnus-article-sort-by-score (h1 h2)
+  "Sort articles by root article score.
+Unscored articles will be counted as having a score of zero."
+  (> (or (cdr (assq (mail-header-number h1)
+		    gnus-newsgroup-scored))
+	 gnus-summary-default-score 0)
+     (or (cdr (assq (mail-header-number h2)
+		    gnus-newsgroup-scored))
+	 gnus-summary-default-score 0)))
+
+(defun gnus-thread-sort-by-score (h1 h2)
+  "Sort threads by root article score."
+  (gnus-article-sort-by-score
+   (gnus-thread-header h1) (gnus-thread-header h2)))
+
+(defun gnus-thread-sort-by-total-score (h1 h2)
+  "Sort threads by the sum of all scores in the thread.
+Unscored articles will be counted as having a score of zero."
+  (> (gnus-thread-total-score h1) (gnus-thread-total-score h2)))
+
+(defun gnus-thread-total-score (thread)
+  ;;  This function find the total score of THREAD.
+  (cond ((null thread)
+	 0)
+	((consp thread)
+	 (if (stringp (car thread))
+	     (apply gnus-thread-score-function 0
+		    (mapcar 'gnus-thread-total-score-1 (cdr thread)))
+	   (gnus-thread-total-score-1 thread)))
+	(t
+	 (gnus-thread-total-score-1 (list thread)))))
+
+(defun gnus-thread-total-score-1 (root)
+  ;; This function find the total score of the thread below ROOT.
+  (setq root (car root))
+  (apply gnus-thread-score-function
+	 (or (append
+	      (mapcar 'gnus-thread-total-score
+		      (cdr (gnus-gethash (mail-header-id root)
+					 gnus-newsgroup-dependencies)))
+	      (when (> (mail-header-number root) 0)
+		(list (or (cdr (assq (mail-header-number root)
+				     gnus-newsgroup-scored))
+			  gnus-summary-default-score 0))))
+	     (list gnus-summary-default-score)
+	     '(0))))
+
+;; Added by Per Abrahamsen <amanda@iesd.auc.dk>.
+(defvar gnus-tmp-prev-subject nil)
+(defvar gnus-tmp-false-parent nil)
+(defvar gnus-tmp-root-expunged nil)
+(defvar gnus-tmp-dummy-line nil)
+
+(defun gnus-summary-prepare-threads (threads)
+  "Prepare summary buffer from THREADS and indentation LEVEL.
+THREADS is either a list of `(PARENT [(CHILD1 [(GRANDCHILD ...]...) ...])'
+or a straight list of headers."
+  (gnus-message 7 "Generating summary...")
+
+  (setq gnus-newsgroup-threads threads)
+  (beginning-of-line)
+
+  (let ((gnus-tmp-level 0)
+	(default-score (or gnus-summary-default-score 0))
+	(gnus-visual-p (gnus-visual-p 'summary-highlight 'highlight))
+	thread number subject stack state gnus-tmp-gathered beg-match
+	new-roots gnus-tmp-new-adopts thread-end
+	gnus-tmp-header gnus-tmp-unread
+	gnus-tmp-replied gnus-tmp-subject-or-nil
+	gnus-tmp-dummy gnus-tmp-indentation gnus-tmp-lines gnus-tmp-score
+	gnus-tmp-score-char gnus-tmp-from gnus-tmp-name
+	gnus-tmp-number gnus-tmp-opening-bracket gnus-tmp-closing-bracket)
+
+    (setq gnus-tmp-prev-subject nil)
+
+    (if (vectorp (car threads))
+	;; If this is a straight (sic) list of headers, then a
+	;; threaded summary display isn't required, so we just create
+	;; an unthreaded one.
+	(gnus-summary-prepare-unthreaded threads)
+
+      ;; Do the threaded display.
+
+      (while (or threads stack gnus-tmp-new-adopts new-roots)
+
+	(if (and (= gnus-tmp-level 0)
+		 (not (setq gnus-tmp-dummy-line nil))
+		 (or (not stack)
+		     (= (caar stack) 0))
+		 (not gnus-tmp-false-parent)
+		 (or gnus-tmp-new-adopts new-roots))
+	    (if gnus-tmp-new-adopts
+		(setq gnus-tmp-level (if gnus-tmp-root-expunged 0 1)
+		      thread (list (car gnus-tmp-new-adopts))
+		      gnus-tmp-header (caar thread)
+		      gnus-tmp-new-adopts (cdr gnus-tmp-new-adopts))
+	      (when new-roots
+		(setq thread (list (car new-roots))
+		      gnus-tmp-header (caar thread)
+		      new-roots (cdr new-roots))))
+
+	  (if threads
+	      ;; If there are some threads, we do them before the
+	      ;; threads on the stack.
+	      (setq thread threads
+		    gnus-tmp-header (caar thread))
+	    ;; There were no current threads, so we pop something off
+	    ;; the stack.
+	    (setq state (car stack)
+		  gnus-tmp-level (car state)
+		  thread (cdr state)
+		  stack (cdr stack)
+		  gnus-tmp-header (caar thread))))
+
+	(setq gnus-tmp-false-parent nil)
+	(setq gnus-tmp-root-expunged nil)
+	(setq thread-end nil)
+
+	(if (stringp gnus-tmp-header)
+	    ;; The header is a dummy root.
+	    (cond
+	     ((eq gnus-summary-make-false-root 'adopt)
+	      ;; We let the first article adopt the rest.
+	      (setq gnus-tmp-new-adopts (nconc gnus-tmp-new-adopts
+					       (cddar thread)))
+	      (setq gnus-tmp-gathered
+		    (nconc (mapcar
+			    (lambda (h) (mail-header-number (car h)))
+			    (cddar thread))
+			   gnus-tmp-gathered))
+	      (setq thread (cons (list (caar thread)
+				       (cadar thread))
+				 (cdr thread)))
+	      (setq gnus-tmp-level -1
+		    gnus-tmp-false-parent t))
+	     ((eq gnus-summary-make-false-root 'empty)
+	      ;; We print adopted articles with empty subject fields.
+	      (setq gnus-tmp-gathered
+		    (nconc (mapcar
+			    (lambda (h) (mail-header-number (car h)))
+			    (cddar thread))
+			   gnus-tmp-gathered))
+	      (setq gnus-tmp-level -1))
+	     ((eq gnus-summary-make-false-root 'dummy)
+	      ;; We remember that we probably want to output a dummy
+	      ;; root.
+	      (setq gnus-tmp-dummy-line gnus-tmp-header)
+	      (setq gnus-tmp-prev-subject gnus-tmp-header))
+	     (t
+	      ;; We do not make a root for the gathered
+	      ;; sub-threads at all.
+	      (setq gnus-tmp-level -1)))
+
+	  (setq number (mail-header-number gnus-tmp-header)
+		subject (mail-header-subject gnus-tmp-header))
+
+	  (cond
+	   ;; If the thread has changed subject, we might want to make
+	   ;; this subthread into a root.
+	   ((and (null gnus-thread-ignore-subject)
+		 (not (zerop gnus-tmp-level))
+		 gnus-tmp-prev-subject
+		 (not (inline
+			(gnus-subject-equal gnus-tmp-prev-subject subject))))
+	    (setq new-roots (nconc new-roots (list (car thread)))
+		  thread-end t
+		  gnus-tmp-header nil))
+	   ;; If the article lies outside the current limit,
+	   ;; then we do not display it.
+	   ((not (memq number gnus-newsgroup-limit))
+	    (setq gnus-tmp-gathered
+		  (nconc (mapcar
+			  (lambda (h) (mail-header-number (car h)))
+			  (cdar thread))
+			 gnus-tmp-gathered))
+	    (setq gnus-tmp-new-adopts (if (cdar thread)
+					  (append gnus-tmp-new-adopts
+						  (cdar thread))
+					gnus-tmp-new-adopts)
+		  thread-end t
+		  gnus-tmp-header nil)
+	    (when (zerop gnus-tmp-level)
+	      (setq gnus-tmp-root-expunged t)))
+	   ;; Perhaps this article is to be marked as read?
+	   ((and gnus-summary-mark-below
+		 (< (or (cdr (assq number gnus-newsgroup-scored))
+			default-score)
+		    gnus-summary-mark-below)
+		 ;; Don't touch sparse articles.
+		 (not (gnus-summary-article-sparse-p number))
+		 (not (gnus-summary-article-ancient-p number)))
+	    (setq gnus-newsgroup-unreads
+		  (delq number gnus-newsgroup-unreads))
+	    (if gnus-newsgroup-auto-expire
+		(push number gnus-newsgroup-expirable)
+	      (push (cons number gnus-low-score-mark)
+		    gnus-newsgroup-reads))))
+
+	  (when gnus-tmp-header
+	    ;; We may have an old dummy line to output before this
+	    ;; article.
+	    (when gnus-tmp-dummy-line
+	      (gnus-summary-insert-dummy-line
+	       gnus-tmp-dummy-line (mail-header-number gnus-tmp-header))
+	      (setq gnus-tmp-dummy-line nil))
+
+	    ;; Compute the mark.
+	    (setq gnus-tmp-unread (gnus-article-mark number))
+
+	    (push (gnus-data-make number gnus-tmp-unread (1+ (point))
+				  gnus-tmp-header gnus-tmp-level)
+		  gnus-newsgroup-data)
+
+	    ;; Actually insert the line.
+	    (setq
+	     gnus-tmp-subject-or-nil
+	     (cond
+	      ((and gnus-thread-ignore-subject
+		    gnus-tmp-prev-subject
+		    (not (inline (gnus-subject-equal
+				  gnus-tmp-prev-subject subject))))
+	       subject)
+	      ((zerop gnus-tmp-level)
+	       (if (and (eq gnus-summary-make-false-root 'empty)
+			(memq number gnus-tmp-gathered)
+			gnus-tmp-prev-subject
+			(inline (gnus-subject-equal
+				 gnus-tmp-prev-subject subject)))
+		   gnus-summary-same-subject
+		 subject))
+	      (t gnus-summary-same-subject)))
+	    (if (and (eq gnus-summary-make-false-root 'adopt)
+		     (= gnus-tmp-level 1)
+		     (memq number gnus-tmp-gathered))
+		(setq gnus-tmp-opening-bracket ?\<
+		      gnus-tmp-closing-bracket ?\>)
+	      (setq gnus-tmp-opening-bracket ?\[
+		    gnus-tmp-closing-bracket ?\]))
+	    (setq
+	     gnus-tmp-indentation
+	     (aref gnus-thread-indent-array gnus-tmp-level)
+	     gnus-tmp-lines (mail-header-lines gnus-tmp-header)
+	     gnus-tmp-score (or (cdr (assq number gnus-newsgroup-scored))
+				gnus-summary-default-score 0)
+	     gnus-tmp-score-char
+	     (if (or (null gnus-summary-default-score)
+		     (<= (abs (- gnus-tmp-score gnus-summary-default-score))
+			 gnus-summary-zcore-fuzz))
+		 ? 
+	       (if (< gnus-tmp-score gnus-summary-default-score)
+		   gnus-score-below-mark gnus-score-over-mark))
+	     gnus-tmp-replied
+	     (cond ((memq number gnus-newsgroup-processable)
+		    gnus-process-mark)
+		   ((memq number gnus-newsgroup-cached)
+		    gnus-cached-mark)
+		   ((memq number gnus-newsgroup-replied)
+		    gnus-replied-mark)
+		   ((memq number gnus-newsgroup-saved)
+		    gnus-saved-mark)
+		   (t gnus-unread-mark))
+	     gnus-tmp-from (mail-header-from gnus-tmp-header)
+	     gnus-tmp-name
+	     (cond
+	      ((string-match "<[^>]+> *$" gnus-tmp-from)
+	       (setq beg-match (match-beginning 0))
+	       (or (and (string-match "^\"[^\"]*\"" gnus-tmp-from)
+			(substring gnus-tmp-from (1+ (match-beginning 0))
+				   (1- (match-end 0))))
+		   (substring gnus-tmp-from 0 beg-match)))
+	      ((string-match "(.+)" gnus-tmp-from)
+	       (substring gnus-tmp-from
+			  (1+ (match-beginning 0)) (1- (match-end 0))))
+	      (t gnus-tmp-from)))
+	    (when (string= gnus-tmp-name "")
+	      (setq gnus-tmp-name gnus-tmp-from))
+	    (unless (numberp gnus-tmp-lines)
+	      (setq gnus-tmp-lines 0))
+	    (gnus-put-text-property
+	     (point)
+	     (progn (eval gnus-summary-line-format-spec) (point))
+	     'gnus-number number)
+	    (when gnus-visual-p
+	      (forward-line -1)
+	      (run-hooks 'gnus-summary-update-hook)
+	      (forward-line 1))
+
+	    (setq gnus-tmp-prev-subject subject)))
+
+	(when (nth 1 thread)
+	  (push (cons (max 0 gnus-tmp-level) (nthcdr 1 thread)) stack))
+	(incf gnus-tmp-level)
+	(setq threads (if thread-end nil (cdar thread)))
+	(unless threads
+	  (setq gnus-tmp-level 0)))))
+  (gnus-message 7 "Generating summary...done"))
+
+(defun gnus-summary-prepare-unthreaded (headers)
+  "Generate an unthreaded summary buffer based on HEADERS."
+  (let (header number mark)
+
+    (beginning-of-line)
+
+    (while headers
+      ;; We may have to root out some bad articles...
+      (when (memq (setq number (mail-header-number
+				(setq header (pop headers))))
+		  gnus-newsgroup-limit)
+	;; Mark article as read when it has a low score.
+	(when (and gnus-summary-mark-below
+		   (< (or (cdr (assq number gnus-newsgroup-scored))
+			  gnus-summary-default-score 0)
+		      gnus-summary-mark-below)
+		   (not (gnus-summary-article-ancient-p number)))
+	  (setq gnus-newsgroup-unreads
+		(delq number gnus-newsgroup-unreads))
+	  (if gnus-newsgroup-auto-expire
+	      (push number gnus-newsgroup-expirable)
+	    (push (cons number gnus-low-score-mark)
+		  gnus-newsgroup-reads)))
+
+	(setq mark (gnus-article-mark number))
+	(push (gnus-data-make number mark (1+ (point)) header 0)
+	      gnus-newsgroup-data)
+	(gnus-summary-insert-line
+	 header 0 number
+	 mark (memq number gnus-newsgroup-replied)
+	 (memq number gnus-newsgroup-expirable)
+	 (mail-header-subject header) nil
+	 (cdr (assq number gnus-newsgroup-scored))
+	 (memq number gnus-newsgroup-processable))))))
+
+(defun gnus-select-newsgroup (group &optional read-all)
+  "Select newsgroup GROUP.
+If READ-ALL is non-nil, all articles in the group are selected."
+  (let* ((entry (gnus-gethash group gnus-newsrc-hashtb))
+	 ;;!!! Dirty hack; should be removed.
+	 (gnus-summary-ignore-duplicates
+	  (if (eq (car (gnus-find-method-for-group group)) 'nnvirtual)
+	      t
+	    gnus-summary-ignore-duplicates))
+	 (info (nth 2 entry))
+	 articles fetched-articles cached)
+
+    (unless (gnus-check-server
+	     (setq gnus-current-select-method
+		   (gnus-find-method-for-group group)))
+      (error "Couldn't open server"))
+
+    (or (and entry (not (eq (car entry) t))) ; Either it's active...
+	(gnus-activate-group group)	; Or we can activate it...
+	(progn				; Or we bug out.
+	  (when (equal major-mode 'gnus-summary-mode)
+	    (kill-buffer (current-buffer)))
+	  (error "Couldn't request group %s: %s"
+		 group (gnus-status-message group))))
+
+    (unless (gnus-request-group group t)
+      (when (equal major-mode 'gnus-summary-mode)
+	(kill-buffer (current-buffer)))
+      (error "Couldn't request group %s: %s"
+	     group (gnus-status-message group)))
+
+    (setq gnus-newsgroup-name group)
+    (setq gnus-newsgroup-unselected nil)
+    (setq gnus-newsgroup-unreads (gnus-list-of-unread-articles group))
+
+    ;; Adjust and set lists of article marks.
+    (when info
+      (gnus-adjust-marked-articles info))
+
+    ;; Kludge to avoid having cached articles nixed out in virtual groups.
+    (when (gnus-virtual-group-p group)
+      (setq cached gnus-newsgroup-cached))
+
+    (setq gnus-newsgroup-unreads
+	  (gnus-set-difference
+	   (gnus-set-difference gnus-newsgroup-unreads gnus-newsgroup-marked)
+	   gnus-newsgroup-dormant))
+
+    (setq gnus-newsgroup-processable nil)
+
+    (gnus-update-read-articles group gnus-newsgroup-unreads)
+    (unless (gnus-ephemeral-group-p gnus-newsgroup-name)
+      (gnus-group-update-group group))
+
+    (setq articles (gnus-articles-to-read group read-all))
+
+    (cond
+     ((null articles)
+      ;;(gnus-message 3 "Couldn't select newsgroup -- no articles to display")
+      'quit)
+     ((eq articles 0) nil)
+     (t
+      ;; Init the dependencies hash table.
+      (setq gnus-newsgroup-dependencies
+	    (gnus-make-hashtable (length articles)))
+      ;; Retrieve the headers and read them in.
+      (gnus-message 5 "Fetching headers for %s..." gnus-newsgroup-name)
+      (setq gnus-newsgroup-headers
+	    (if (eq 'nov
+		    (setq gnus-headers-retrieved-by
+			  (gnus-retrieve-headers
+			   articles gnus-newsgroup-name
+			   ;; We might want to fetch old headers, but
+			   ;; not if there is only 1 article.
+			   (and gnus-fetch-old-headers
+				(or (and
+				     (not (eq gnus-fetch-old-headers 'some))
+				     (not (numberp gnus-fetch-old-headers)))
+				    (> (length articles) 1))))))
+		(gnus-get-newsgroup-headers-xover
+		 articles nil nil gnus-newsgroup-name t)
+	      (gnus-get-newsgroup-headers)))
+      (gnus-message 5 "Fetching headers for %s...done" gnus-newsgroup-name)
+
+      ;; Kludge to avoid having cached articles nixed out in virtual groups.
+      (when cached
+	(setq gnus-newsgroup-cached cached))
+
+      ;; Suppress duplicates?
+      (when gnus-suppress-duplicates
+	(gnus-dup-suppress-articles))
+
+      ;; Set the initial limit.
+      (setq gnus-newsgroup-limit (copy-sequence articles))
+      ;; Remove canceled articles from the list of unread articles.
+      (setq gnus-newsgroup-unreads
+	    (gnus-set-sorted-intersection
+	     gnus-newsgroup-unreads
+	     (setq fetched-articles
+		   (mapcar (lambda (headers) (mail-header-number headers))
+			   gnus-newsgroup-headers))))
+      ;; Removed marked articles that do not exist.
+      (gnus-update-missing-marks
+       (gnus-sorted-complement fetched-articles articles))
+      ;; We might want to build some more threads first.
+      (and gnus-fetch-old-headers
+	   (eq gnus-headers-retrieved-by 'nov)
+	   (gnus-build-old-threads))
+      ;; Check whether auto-expire is to be done in this group.
+      (setq gnus-newsgroup-auto-expire
+	    (gnus-group-auto-expirable-p group))
+      ;; Set up the article buffer now, if necessary.
+      (unless gnus-single-article-buffer
+	(gnus-article-setup-buffer))
+      ;; First and last article in this newsgroup.
+      (when gnus-newsgroup-headers
+	(setq gnus-newsgroup-begin
+	      (mail-header-number (car gnus-newsgroup-headers))
+	      gnus-newsgroup-end
+	      (mail-header-number
+	       (gnus-last-element gnus-newsgroup-headers))))
+      ;; GROUP is successfully selected.
+      (or gnus-newsgroup-headers t)))))
+
+(defun gnus-articles-to-read (group &optional read-all)
+  ;; Find out what articles the user wants to read.
+  (let* ((articles
+	  ;; Select all articles if `read-all' is non-nil, or if there
+	  ;; are no unread articles.
+	  (if (or read-all
+		  (and (zerop (length gnus-newsgroup-marked))
+		       (zerop (length gnus-newsgroup-unreads)))
+		  (eq (gnus-group-find-parameter group 'display)
+		      'all))
+	      (gnus-uncompress-range (gnus-active group))
+	    (sort (append gnus-newsgroup-dormant gnus-newsgroup-marked
+			  (copy-sequence gnus-newsgroup-unreads))
+		  '<)))
+	 (scored-list (gnus-killed-articles gnus-newsgroup-killed articles))
+	 (scored (length scored-list))
+	 (number (length articles))
+	 (marked (+ (length gnus-newsgroup-marked)
+		    (length gnus-newsgroup-dormant)))
+	 (select
+	  (cond
+	   ((numberp read-all)
+	    read-all)
+	   (t
+	    (condition-case ()
+		(cond
+		 ((and (or (<= scored marked) (= scored number))
+		       (numberp gnus-large-newsgroup)
+		       (> number gnus-large-newsgroup))
+		  (let ((input
+			 (read-string
+			  (format
+			   "How many articles from %s (default %d): "
+			   (gnus-limit-string gnus-newsgroup-name 35)
+			   number))))
+		    (if (string-match "^[ \t]*$" input) number input)))
+		 ((and (> scored marked) (< scored number)
+		       (> (- scored number) 20))
+		  (let ((input
+			 (read-string
+			  (format "%s %s (%d scored, %d total): "
+				  "How many articles from"
+				  group scored number))))
+		    (if (string-match "^[ \t]*$" input)
+			number input)))
+		 (t number))
+	      (quit nil))))))
+    (setq select (if (stringp select) (string-to-number select) select))
+    (if (or (null select) (zerop select))
+	select
+      (if (and (not (zerop scored)) (<= (abs select) scored))
+	  (progn
+	    (setq articles (sort scored-list '<))
+	    (setq number (length articles)))
+	(setq articles (copy-sequence articles)))
+
+      (when (< (abs select) number)
+	(if (< select 0)
+	    ;; Select the N oldest articles.
+	    (setcdr (nthcdr (1- (abs select)) articles) nil)
+	  ;; Select the N most recent articles.
+	  (setq articles (nthcdr (- number select) articles))))
+      (setq gnus-newsgroup-unselected
+	    (gnus-sorted-intersection
+	     gnus-newsgroup-unreads
+	     (gnus-sorted-complement gnus-newsgroup-unreads articles)))
+      articles)))
+
+(defun gnus-killed-articles (killed articles)
+  (let (out)
+    (while articles
+      (when (inline (gnus-member-of-range (car articles) killed))
+	(push (car articles) out))
+      (setq articles (cdr articles)))
+    out))
+
+(defun gnus-uncompress-marks (marks)
+  "Uncompress the mark ranges in MARKS."
+  (let ((uncompressed '(score bookmark))
+	out)
+    (while marks
+      (if (memq (caar marks) uncompressed)
+	  (push (car marks) out)
+	(push (cons (caar marks) (gnus-uncompress-range (cdar marks))) out))
+      (setq marks (cdr marks)))
+    out))
+
+(defun gnus-adjust-marked-articles (info)
+  "Set all article lists and remove all marks that are no longer legal."
+  (let* ((marked-lists (gnus-info-marks info))
+	 (active (gnus-active (gnus-info-group info)))
+	 (min (car active))
+	 (max (cdr active))
+	 (types gnus-article-mark-lists)
+	 (uncompressed '(score bookmark killed))
+	 marks var articles article mark)
+
+    (while marked-lists
+      (setq marks (pop marked-lists))
+      (set (setq var (intern (format "gnus-newsgroup-%s"
+				     (car (rassq (setq mark (car marks))
+						 types)))))
+	   (if (memq (car marks) uncompressed) (cdr marks)
+	     (gnus-uncompress-range (cdr marks))))
+
+      (setq articles (symbol-value var))
+
+      ;; All articles have to be subsets of the active articles.
+      (cond
+       ;; Adjust "simple" lists.
+       ((memq mark '(tick dormant expire reply save))
+	(while articles
+	  (when (or (< (setq article (pop articles)) min) (> article max))
+	    (set var (delq article (symbol-value var))))))
+       ;; Adjust assocs.
+       ((memq mark uncompressed)
+	(while articles
+	  (when (or (not (consp (setq article (pop articles))))
+		    (< (car article) min)
+		    (> (car article) max))
+	    (set var (delq article (symbol-value var))))))))))
+
+(defun gnus-update-missing-marks (missing)
+  "Go through the list of MISSING articles and remove them mark lists."
+  (when missing
+    (let ((types gnus-article-mark-lists)
+	  var m)
+      ;; Go through all types.
+      (while types
+	(setq var (intern (format "gnus-newsgroup-%s" (car (pop types)))))
+	(when (symbol-value var)
+	  ;; This list has articles.  So we delete all missing articles
+	  ;; from it.
+	  (setq m missing)
+	  (while m
+	    (set var (delq (pop m) (symbol-value var)))))))))
+
+(defun gnus-update-marks ()
+  "Enter the various lists of marked articles into the newsgroup info list."
+  (let ((types gnus-article-mark-lists)
+	(info (gnus-get-info gnus-newsgroup-name))
+	(uncompressed '(score bookmark killed))
+	type list newmarked symbol)
+    (when info
+      ;; Add all marks lists that are non-nil to the list of marks lists.
+      (while (setq type (pop types))
+	(when (setq list (symbol-value
+			  (setq symbol
+				(intern (format "gnus-newsgroup-%s"
+						(car type))))))
+
+	  ;; Get rid of the entries of the articles that have the
+	  ;; default score.
+	  (when (and (eq (cdr type) 'score)
+		     gnus-save-score
+		     list)
+	    (let* ((arts list)
+		   (prev (cons nil list))
+		   (all prev))
+	      (while arts
+		(if (or (not (consp (car arts)))
+			(= (cdar arts) gnus-summary-default-score))
+		    (setcdr prev (cdr arts))
+		  (setq prev arts))
+		(setq arts (cdr arts)))
+	      (setq list (cdr all))))
+
+	  (push (cons (cdr type)
+		      (if (memq (cdr type) uncompressed) list
+			(gnus-compress-sequence
+			 (set symbol (sort list '<)) t)))
+		newmarked)))
+
+      ;; Enter these new marks into the info of the group.
+      (if (nthcdr 3 info)
+	  (setcar (nthcdr 3 info) newmarked)
+	;; Add the marks lists to the end of the info.
+	(when newmarked
+	  (setcdr (nthcdr 2 info) (list newmarked))))
+
+      ;; Cut off the end of the info if there's nothing else there.
+      (let ((i 5))
+	(while (and (> i 2)
+		    (not (nth i info)))
+	  (when (nthcdr (decf i) info)
+	    (setcdr (nthcdr i info) nil)))))))
+
+(defun gnus-set-mode-line (where)
+  "This function sets the mode line of the article or summary buffers.
+If WHERE is `summary', the summary mode line format will be used."
+  ;; Is this mode line one we keep updated?
+  (when (memq where gnus-updated-mode-lines)
+    (let (mode-string)
+      (save-excursion
+	;; We evaluate this in the summary buffer since these
+	;; variables are buffer-local to that buffer.
+	(set-buffer gnus-summary-buffer)
+	;; We bind all these variables that are used in the `eval' form
+	;; below.
+	(let* ((mformat (symbol-value
+			 (intern
+			  (format "gnus-%s-mode-line-format-spec" where))))
+	       (gnus-tmp-group-name gnus-newsgroup-name)
+	       (gnus-tmp-article-number (or gnus-current-article 0))
+	       (gnus-tmp-unread gnus-newsgroup-unreads)
+	       (gnus-tmp-unread-and-unticked (length gnus-newsgroup-unreads))
+	       (gnus-tmp-unselected (length gnus-newsgroup-unselected))
+	       (gnus-tmp-unread-and-unselected
+		(cond ((and (zerop gnus-tmp-unread-and-unticked)
+			    (zerop gnus-tmp-unselected))
+		       "")
+		      ((zerop gnus-tmp-unselected)
+		       (format "{%d more}" gnus-tmp-unread-and-unticked))
+		      (t (format "{%d(+%d) more}"
+				 gnus-tmp-unread-and-unticked
+				 gnus-tmp-unselected))))
+	       (gnus-tmp-subject
+		(if (and gnus-current-headers
+			 (vectorp gnus-current-headers))
+		    (gnus-mode-string-quote
+		     (mail-header-subject gnus-current-headers))
+		  ""))
+	       bufname-length max-len
+	       gnus-tmp-header);; passed as argument to any user-format-funcs
+	  (setq mode-string (eval mformat))
+	  (setq bufname-length (if (string-match "%b" mode-string)
+				   (- (length
+				       (buffer-name
+					(if (eq where 'summary)
+					    nil
+					  (get-buffer gnus-article-buffer))))
+				      2)
+				 0))
+	  (setq max-len (max 4 (if gnus-mode-non-string-length
+				   (- (window-width)
+				      gnus-mode-non-string-length
+				      bufname-length)
+				 (length mode-string))))
+	  ;; We might have to chop a bit of the string off...
+	  (when (> (length mode-string) max-len)
+	    (setq mode-string
+		  (concat (gnus-truncate-string mode-string (- max-len 3))
+			  "...")))
+	  ;; Pad the mode string a bit.
+	  (setq mode-string (format (format "%%-%ds" max-len) mode-string))))
+      ;; Update the mode line.
+      (setq mode-line-buffer-identification
+	    (gnus-mode-line-buffer-identification (list mode-string)))
+      (set-buffer-modified-p t))))
+
+(defun gnus-create-xref-hashtb (from-newsgroup headers unreads)
+  "Go through the HEADERS list and add all Xrefs to a hash table.
+The resulting hash table is returned, or nil if no Xrefs were found."
+  (let* ((virtual (gnus-virtual-group-p from-newsgroup))
+	 (prefix (if virtual "" (gnus-group-real-prefix from-newsgroup)))
+	 (xref-hashtb (gnus-make-hashtable))
+	 start group entry number xrefs header)
+    (while headers
+      (setq header (pop headers))
+      (when (and (setq xrefs (mail-header-xref header))
+		 (not (memq (setq number (mail-header-number header))
+			    unreads)))
+	(setq start 0)
+	(while (string-match "\\([^ ]+\\)[:/]\\([0-9]+\\)" xrefs start)
+	  (setq start (match-end 0))
+	  (setq group (if prefix
+			  (concat prefix (substring xrefs (match-beginning 1)
+						    (match-end 1)))
+			(substring xrefs (match-beginning 1) (match-end 1))))
+	  (setq number
+		(string-to-int (substring xrefs (match-beginning 2)
+					  (match-end 2))))
+	  (if (setq entry (gnus-gethash group xref-hashtb))
+	      (setcdr entry (cons number (cdr entry)))
+	    (gnus-sethash group (cons number nil) xref-hashtb)))))
+    (and start xref-hashtb)))
+
+(defun gnus-mark-xrefs-as-read (from-newsgroup headers unreads)
+  "Look through all the headers and mark the Xrefs as read."
+  (let ((virtual (gnus-virtual-group-p from-newsgroup))
+	name entry info xref-hashtb idlist method nth4)
+    (save-excursion
+      (set-buffer gnus-group-buffer)
+      (when (setq xref-hashtb
+		  (gnus-create-xref-hashtb from-newsgroup headers unreads))
+	(mapatoms
+	 (lambda (group)
+	   (unless (string= from-newsgroup (setq name (symbol-name group)))
+	     (setq idlist (symbol-value group))
+	     ;; Dead groups are not updated.
+	     (and (prog1
+		      (setq entry (gnus-gethash name gnus-newsrc-hashtb)
+			    info (nth 2 entry))
+		    (when (stringp (setq nth4 (gnus-info-method info)))
+		      (setq nth4 (gnus-server-to-method nth4))))
+		  ;; Only do the xrefs if the group has the same
+		  ;; select method as the group we have just read.
+		  (or (gnus-methods-equal-p
+		       nth4 (gnus-find-method-for-group from-newsgroup))
+		      virtual
+		      (equal nth4 (setq method (gnus-find-method-for-group
+						from-newsgroup)))
+		      (and (equal (car nth4) (car method))
+			   (equal (nth 1 nth4) (nth 1 method))))
+		  gnus-use-cross-reference
+		  (or (not (eq gnus-use-cross-reference t))
+		      virtual
+		      ;; Only do cross-references on subscribed
+		      ;; groups, if that is what is wanted.
+		      (<= (gnus-info-level info) gnus-level-subscribed))
+		  (gnus-group-make-articles-read name idlist))))
+	 xref-hashtb)))))
+
+(defun gnus-group-make-articles-read (group articles)
+  "Update the info of GROUP to say that ARTICLES are read."
+  (let* ((num 0)
+	 (entry (gnus-gethash group gnus-newsrc-hashtb))
+	 (info (nth 2 entry))
+	 (active (gnus-active group))
+	 range)
+    ;; First peel off all illegal article numbers.
+    (when active
+      (let ((ids articles)
+	    id first)
+	(while (setq id (pop ids))
+	  (when (and first (> id (cdr active)))
+	    ;; We'll end up in this situation in one particular
+	    ;; obscure situation.  If you re-scan a group and get
+	    ;; a new article that is cross-posted to a different
+	    ;; group that has not been re-scanned, you might get
+	    ;; crossposted article that has a higher number than
+	    ;; Gnus believes possible.  So we re-activate this
+	    ;; group as well.  This might mean doing the
+	    ;; crossposting thingy will *increase* the number
+	    ;; of articles in some groups.  Tsk, tsk.
+	    (setq active (or (gnus-activate-group group) active)))
+	  (when (or (> id (cdr active))
+		    (< id (car active)))
+	    (setq articles (delq id articles))))))
+    (save-excursion
+      (set-buffer gnus-group-buffer)
+      (gnus-undo-register
+	`(progn
+	   (gnus-info-set-marks ',info ',(gnus-info-marks info) t)
+	   (gnus-info-set-read ',info ',(gnus-info-read info))
+	   (gnus-get-unread-articles-in-group ',info (gnus-active ,group))
+	   (gnus-group-update-group ,group t))))
+    ;; If the read list is nil, we init it.
+    (and active
+	 (null (gnus-info-read info))
+	 (> (car active) 1)
+	 (gnus-info-set-read info (cons 1 (1- (car active)))))
+    ;; Then we add the read articles to the range.
+    (gnus-info-set-read
+     info
+     (setq range
+	   (gnus-add-to-range
+	    (gnus-info-read info) (setq articles (sort articles '<)))))
+    ;; Then we have to re-compute how many unread
+    ;; articles there are in this group.
+    (when active
+      (cond
+       ((not range)
+	(setq num (- (1+ (cdr active)) (car active))))
+       ((not (listp (cdr range)))
+	(setq num (- (cdr active) (- (1+ (cdr range))
+				     (car range)))))
+       (t
+	(while range
+	  (if (numberp (car range))
+	      (setq num (1+ num))
+	    (setq num (+ num (- (1+ (cdar range)) (caar range)))))
+	  (setq range (cdr range)))
+	(setq num (- (cdr active) num))))
+      ;; Update the number of unread articles.
+      (setcar entry num)
+      ;; Update the group buffer.
+      (gnus-group-update-group group t))))
+
+(defun gnus-methods-equal-p (m1 m2)
+  (let ((m1 (or m1 gnus-select-method))
+	(m2 (or m2 gnus-select-method)))
+    (or (equal m1 m2)
+	(and (eq (car m1) (car m2))
+	     (or (not (memq 'address (assoc (symbol-name (car m1))
+					    gnus-valid-select-methods)))
+		 (equal (nth 1 m1) (nth 1 m2)))))))
+
+(defvar gnus-newsgroup-none-id 0)
+
+(defun gnus-get-newsgroup-headers (&optional dependencies force-new)
+  (let ((cur nntp-server-buffer)
+	(dependencies
+	 (or dependencies
+	     (save-excursion (set-buffer gnus-summary-buffer)
+			     gnus-newsgroup-dependencies)))
+	headers id id-dep ref-dep end ref)
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      ;; Translate all TAB characters into SPACE characters.
+      (subst-char-in-region (point-min) (point-max) ?\t ?  t)
+      (run-hooks 'gnus-parse-headers-hook)
+      (let ((case-fold-search t)
+	    in-reply-to header p lines)
+	(goto-char (point-min))
+	;; Search to the beginning of the next header.	Error messages
+	;; do not begin with 2 or 3.
+	(while (re-search-forward "^[23][0-9]+ " nil t)
+	  (setq id nil
+		ref nil)
+	  ;; This implementation of this function, with nine
+	  ;; search-forwards instead of the one re-search-forward and
+	  ;; a case (which basically was the old function) is actually
+	  ;; about twice as fast, even though it looks messier.	 You
+	  ;; can't have everything, I guess.  Speed and elegance
+	  ;; doesn't always go hand in hand.
+	  (setq
+	   header
+	   (vector
+	    ;; Number.
+	    (prog1
+		(read cur)
+	      (end-of-line)
+	      (setq p (point))
+	      (narrow-to-region (point)
+				(or (and (search-forward "\n.\n" nil t)
+					 (- (point) 2))
+				    (point))))
+	    ;; Subject.
+	    (progn
+	      (goto-char p)
+	      (if (search-forward "\nsubject: " nil t)
+		  (nnheader-header-value) "(none)"))
+	    ;; From.
+	    (progn
+	      (goto-char p)
+	      (if (search-forward "\nfrom: " nil t)
+		  (nnheader-header-value) "(nobody)"))
+	    ;; Date.
+	    (progn
+	      (goto-char p)
+	      (if (search-forward "\ndate: " nil t)
+		  (nnheader-header-value) ""))
+	    ;; Message-ID.
+	    (progn
+	      (goto-char p)
+	      (setq id (if (search-forward "\nmessage-id:" nil t)
+			   (buffer-substring
+			    (1- (or (search-forward "<" nil t) (point)))
+			    (or (search-forward ">" nil t) (point)))
+			 ;; If there was no message-id, we just fake one
+			 ;; to make subsequent routines simpler.
+			 (nnheader-generate-fake-message-id))))
+	    ;; References.
+	    (progn
+	      (goto-char p)
+	      (if (search-forward "\nreferences: " nil t)
+		  (progn
+		    (setq end (point))
+		    (prog1
+			(nnheader-header-value)
+		      (setq ref
+			    (buffer-substring
+			     (progn
+			       (end-of-line)
+			       (search-backward ">" end t)
+			       (1+ (point)))
+			     (progn
+			       (search-backward "<" end t)
+			       (point))))))
+		;; Get the references from the in-reply-to header if there
+		;; were no references and the in-reply-to header looks
+		;; promising.
+		(if (and (search-forward "\nin-reply-to: " nil t)
+			 (setq in-reply-to (nnheader-header-value))
+			 (string-match "<[^>]+>" in-reply-to))
+		    (setq ref (substring in-reply-to (match-beginning 0)
+					 (match-end 0)))
+		  (setq ref nil))))
+	    ;; Chars.
+	    0
+	    ;; Lines.
+	    (progn
+	      (goto-char p)
+	      (if (search-forward "\nlines: " nil t)
+		  (if (numberp (setq lines (read cur)))
+		      lines 0)
+		0))
+	    ;; Xref.
+	    (progn
+	      (goto-char p)
+	      (and (search-forward "\nxref: " nil t)
+		   (nnheader-header-value)))))
+	  (when (equal id ref)
+	    (setq ref nil))
+	  ;; We do the threading while we read the headers.  The
+	  ;; message-id and the last reference are both entered into
+	  ;; the same hash table.  Some tippy-toeing around has to be
+	  ;; done in case an article has arrived before the article
+	  ;; which it refers to.
+	  (if (boundp (setq id-dep (intern id dependencies)))
+	      (if (and (car (symbol-value id-dep))
+		       (not force-new))
+		  ;; An article with this Message-ID has already been seen.
+		  (if gnus-summary-ignore-duplicates
+		      ;; We ignore this one, except we add
+		      ;; any additional Xrefs (in case the two articles
+		      ;; came from different servers).
+		      (progn
+			(mail-header-set-xref
+			 (car (symbol-value id-dep))
+			 (concat (or (mail-header-xref
+				      (car (symbol-value id-dep)))
+				     "")
+				 (or (mail-header-xref header) "")))
+			(setq header nil))
+		    ;; We rename the Message-ID.
+		    (set
+		     (setq id-dep (intern (setq id (nnmail-message-id))
+					  dependencies))
+		     (list header))
+		    (mail-header-set-id header id))
+		(setcar (symbol-value id-dep) header))
+	    (set id-dep (list header)))
+	  (when  header
+	    (if (boundp (setq ref-dep (intern (or ref "none") dependencies)))
+		(setcdr (symbol-value ref-dep)
+			(nconc (cdr (symbol-value ref-dep))
+			       (list (symbol-value id-dep))))
+	      (set ref-dep (list nil (symbol-value id-dep))))
+	    (push header headers))
+	  (goto-char (point-max))
+	  (widen))
+	(nreverse headers)))))
+
+;; The following macros and functions were written by Felix Lee
+;; <flee@cse.psu.edu>.
+
+(defmacro gnus-nov-read-integer ()
+  '(prog1
+       (if (= (following-char) ?\t)
+	   0
+	 (let ((num (ignore-errors (read buffer))))
+	   (if (numberp num) num 0)))
+     (unless (eobp)
+       (forward-char 1))))
+
+(defmacro gnus-nov-skip-field ()
+  '(search-forward "\t" eol 'move))
+
+(defmacro gnus-nov-field ()
+  '(buffer-substring (point) (if (gnus-nov-skip-field) (1- (point)) eol)))
+
+;; (defvar gnus-nov-none-counter 0)
+
+;; This function has to be called with point after the article number
+;; on the beginning of the line.
+(defun gnus-nov-parse-line (number dependencies &optional force-new)
+  (let ((eol (gnus-point-at-eol))
+	(buffer (current-buffer))
+	header ref id id-dep ref-dep)
+
+    ;; overview: [num subject from date id refs chars lines misc]
+    (unwind-protect
+	(progn
+	  (narrow-to-region (point) eol)
+	  (unless (eobp)
+	    (forward-char))
+
+	  (setq header
+		(vector
+		 number			; number
+		 (gnus-nov-field)	; subject
+		 (gnus-nov-field)	; from
+		 (gnus-nov-field)	; date
+		 (setq id (or (gnus-nov-field)
+			      (nnheader-generate-fake-message-id))) ; id
+		 (progn
+		   (let ((beg (point)))
+		     (search-forward "\t" eol)
+		     (if (search-backward ">" beg t)
+			 (setq ref
+			       (buffer-substring
+				(1+ (point))
+				(search-backward "<" beg t)))
+		       (setq ref nil))
+		     (goto-char beg))
+		   (gnus-nov-field))	; refs
+		 (gnus-nov-read-integer) ; chars
+		 (gnus-nov-read-integer) ; lines
+		 (if (= (following-char) ?\n)
+		     nil
+		   (gnus-nov-field)))))	; misc
+
+      (widen))
+
+    ;; We build the thread tree.
+    (when (equal id ref)
+      ;; This article refers back to itself.  Naughty, naughty.
+      (setq ref nil))
+    (if (boundp (setq id-dep (intern id dependencies)))
+	(if (and (car (symbol-value id-dep))
+		 (not force-new))
+	    ;; An article with this Message-ID has already been seen.
+	    (if gnus-summary-ignore-duplicates
+		;; We ignore this one, except we add any additional
+		;; Xrefs (in case the two articles came from different
+		;; servers.
+		(progn
+		  (mail-header-set-xref
+		   (car (symbol-value id-dep))
+		   (concat (or (mail-header-xref
+				(car (symbol-value id-dep)))
+			       "")
+			   (or (mail-header-xref header) "")))
+		  (setq header nil))
+	      ;; We rename the Message-ID.
+	      (set
+	       (setq id-dep (intern (setq id (nnmail-message-id))
+				    dependencies))
+	       (list header))
+	      (mail-header-set-id header id))
+	  (setcar (symbol-value id-dep) header))
+      (set id-dep (list header)))
+    (when header
+      (if (boundp (setq ref-dep (intern (or ref "none") dependencies)))
+	  (setcdr (symbol-value ref-dep)
+		  (nconc (cdr (symbol-value ref-dep))
+			 (list (symbol-value id-dep))))
+	(set ref-dep (list nil (symbol-value id-dep)))))
+    header))
+
+;; Goes through the xover lines and returns a list of vectors
+(defun gnus-get-newsgroup-headers-xover (sequence &optional
+						  force-new dependencies
+						  group also-fetch-heads)
+  "Parse the news overview data in the server buffer, and return a
+list of headers that match SEQUENCE (see `nntp-retrieve-headers')."
+  ;; Get the Xref when the users reads the articles since most/some
+  ;; NNTP servers do not include Xrefs when using XOVER.
+  (setq gnus-article-internal-prepare-hook '(gnus-article-get-xrefs))
+  (let ((cur nntp-server-buffer)
+	(dependencies (or dependencies gnus-newsgroup-dependencies))
+	number headers header)
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      ;; Allow the user to mangle the headers before parsing them.
+      (run-hooks 'gnus-parse-headers-hook)
+      (goto-char (point-min))
+      (while (not (eobp))
+	(condition-case ()
+	    (while (and sequence (not (eobp)))
+	      (setq number (read cur))
+	      (while (and sequence
+			  (< (car sequence) number))
+		(setq sequence (cdr sequence)))
+	      (and sequence
+		   (eq number (car sequence))
+		   (progn
+		     (setq sequence (cdr sequence))
+		     (setq header (inline
+				    (gnus-nov-parse-line
+				     number dependencies force-new))))
+		   (push header headers))
+	      (forward-line 1))
+	  (error
+	   (gnus-error 4 "Strange nov line (%d)"
+		       (count-lines (point-min) (point)))))
+	(forward-line 1))
+      ;; A common bug in inn is that if you have posted an article and
+      ;; then retrieves the active file, it will answer correctly --
+      ;; the new article is included.  However, a NOV entry for the
+      ;; article may not have been generated yet, so this may fail.
+      ;; We work around this problem by retrieving the last few
+      ;; headers using HEAD.
+      (if (or (not also-fetch-heads)
+	      (not sequence))
+	  ;; We (probably) got all the headers.
+	  (nreverse headers)
+	(let ((gnus-nov-is-evil t))
+	  (nconc
+	   (nreverse headers)
+	   (when (gnus-retrieve-headers sequence group)
+	     (gnus-get-newsgroup-headers))))))))
+
+(defun gnus-article-get-xrefs ()
+  "Fill in the Xref value in `gnus-current-headers', if necessary.
+This is meant to be called in `gnus-article-internal-prepare-hook'."
+  (let ((headers (save-excursion (set-buffer gnus-summary-buffer)
+				 gnus-current-headers)))
+    (or (not gnus-use-cross-reference)
+	(not headers)
+	(and (mail-header-xref headers)
+	     (not (string= (mail-header-xref headers) "")))
+	(let ((case-fold-search t)
+	      xref)
+	  (save-restriction
+	    (nnheader-narrow-to-headers)
+	    (goto-char (point-min))
+	    (when (or (and (eq (downcase (following-char)) ?x)
+			   (looking-at "Xref:"))
+		      (search-forward "\nXref:" nil t))
+	      (goto-char (1+ (match-end 0)))
+	      (setq xref (buffer-substring (point)
+					   (progn (end-of-line) (point))))
+	      (mail-header-set-xref headers xref)))))))
+
+(defun gnus-summary-insert-subject (id &optional old-header use-old-header)
+  "Find article ID and insert the summary line for that article."
+  (let ((header (if (and old-header use-old-header)
+		    old-header (gnus-read-header id)))
+	(number (and (numberp id) id))
+	pos d)
+    (when header
+      ;; Rebuild the thread that this article is part of and go to the
+      ;; article we have fetched.
+      (when (and (not gnus-show-threads)
+		 old-header)
+	(when (setq d (gnus-data-find (mail-header-number old-header)))
+	  (goto-char (gnus-data-pos d))
+	  (gnus-data-remove
+	   number
+	   (- (gnus-point-at-bol)
+	      (prog1
+		  (1+ (gnus-point-at-eol))
+		(gnus-delete-line))))))
+      (when old-header
+	(mail-header-set-number header (mail-header-number old-header)))
+      (setq gnus-newsgroup-sparse
+	    (delq (setq number (mail-header-number header))
+		  gnus-newsgroup-sparse))
+      (setq gnus-newsgroup-ancient (delq number gnus-newsgroup-ancient))
+      (gnus-rebuild-thread (mail-header-id header))
+      (gnus-summary-goto-subject number nil t))
+    (when (and (numberp number)
+	       (> number 0))
+      ;; We have to update the boundaries even if we can't fetch the
+      ;; article if ID is a number -- so that the next `P' or `N'
+      ;; command will fetch the previous (or next) article even
+      ;; if the one we tried to fetch this time has been canceled.
+      (when (> number gnus-newsgroup-end)
+	(setq gnus-newsgroup-end number))
+      (when (< number gnus-newsgroup-begin)
+	(setq gnus-newsgroup-begin number))
+      (setq gnus-newsgroup-unselected
+	    (delq number gnus-newsgroup-unselected)))
+    ;; Report back a success?
+    (and header (mail-header-number header))))
+
+;;; Process/prefix in the summary buffer
+
+(defun gnus-summary-work-articles (n)
+  "Return a list of articles to be worked upon.	 The prefix argument,
+the list of process marked articles, and the current article will be
+taken into consideration."
+  (cond
+   (n
+    ;; A numerical prefix has been given.
+    (setq n (prefix-numeric-value n))
+    (let ((backward (< n 0))
+	  (n (abs (prefix-numeric-value n)))
+	  articles article)
+      (save-excursion
+	(while
+	    (and (> n 0)
+		 (push (setq article (gnus-summary-article-number))
+		       articles)
+		 (if backward
+		     (gnus-summary-find-prev nil article)
+		   (gnus-summary-find-next nil article)))
+	  (decf n)))
+      (nreverse articles)))
+   ((gnus-region-active-p)
+    ;; Work on the region between point and mark.
+    (let ((max (max (point) (mark)))
+	  articles article)
+      (save-excursion
+	(goto-char (min (point) (mark)))
+	(while
+	    (and
+	     (push (setq article (gnus-summary-article-number)) articles)
+	     (gnus-summary-find-next nil article)
+	     (< (point) max)))
+	(nreverse articles))))
+   (gnus-newsgroup-processable
+    ;; There are process-marked articles present.
+    ;; Save current state.
+    (gnus-summary-save-process-mark)
+    ;; Return the list.
+    (reverse gnus-newsgroup-processable))
+   (t
+    ;; Just return the current article.
+    (list (gnus-summary-article-number)))))
+
+(defun gnus-summary-save-process-mark ()
+  "Push the current set of process marked articles on the stack."
+  (interactive)
+  (push (copy-sequence gnus-newsgroup-processable)
+	gnus-newsgroup-process-stack))
+
+(defun gnus-summary-kill-process-mark ()
+  "Push the current set of process marked articles on the stack and unmark."
+  (interactive)
+  (gnus-summary-save-process-mark)
+  (gnus-summary-unmark-all-processable))
+
+(defun gnus-summary-yank-process-mark ()
+  "Pop the last process mark state off the stack and restore it."
+  (interactive)
+  (unless gnus-newsgroup-process-stack
+    (error "Empty mark stack"))
+  (gnus-summary-process-mark-set (pop gnus-newsgroup-process-stack)))
+
+(defun gnus-summary-process-mark-set (set)
+  "Make SET into the current process marked articles."
+  (gnus-summary-unmark-all-processable)
+  (while set
+    (gnus-summary-set-process-mark (pop set))))
+
+;;; Searching and stuff
+
+(defun gnus-summary-search-group (&optional backward use-level)
+  "Search for next unread newsgroup.
+If optional argument BACKWARD is non-nil, search backward instead."
+  (save-excursion
+    (set-buffer gnus-group-buffer)
+    (when (gnus-group-search-forward
+	   backward nil (if use-level (gnus-group-group-level) nil))
+      (gnus-group-group-name))))
+
+(defun gnus-summary-best-group (&optional exclude-group)
+  "Find the name of the best unread group.
+If EXCLUDE-GROUP, do not go to this group."
+  (save-excursion
+    (set-buffer gnus-group-buffer)
+    (save-excursion
+      (gnus-group-best-unread-group exclude-group))))
+
+(defun gnus-summary-find-next (&optional unread article backward)
+  (if backward (gnus-summary-find-prev)
+    (let* ((dummy (gnus-summary-article-intangible-p))
+	   (article (or article (gnus-summary-article-number)))
+	   (arts (gnus-data-find-list article))
+	   result)
+      (when (and (not dummy)
+		 (or (not gnus-summary-check-current)
+		     (not unread)
+		     (not (gnus-data-unread-p (car arts)))))
+	(setq arts (cdr arts)))
+      (when (setq result
+		  (if unread
+		      (progn
+			(while arts
+			  (when (gnus-data-unread-p (car arts))
+			    (setq result (car arts)
+				  arts nil))
+			  (setq arts (cdr arts)))
+			result)
+		    (car arts)))
+	(goto-char (gnus-data-pos result))
+	(gnus-data-number result)))))
+
+(defun gnus-summary-find-prev (&optional unread article)
+  (let* ((eobp (eobp))
+	 (article (or article (gnus-summary-article-number)))
+	 (arts (gnus-data-find-list article (gnus-data-list 'rev)))
+	 result)
+    (when (and (not eobp)
+	       (or (not gnus-summary-check-current)
+		   (not unread)
+		   (not (gnus-data-unread-p (car arts)))))
+      (setq arts (cdr arts)))
+    (when (setq result
+		(if unread
+		    (progn
+		      (while arts
+			(when (gnus-data-unread-p (car arts))
+			  (setq result (car arts)
+				arts nil))
+			(setq arts (cdr arts)))
+		      result)
+		  (car arts)))
+      (goto-char (gnus-data-pos result))
+      (gnus-data-number result))))
+
+(defun gnus-summary-find-subject (subject &optional unread backward article)
+  (let* ((simp-subject (gnus-simplify-subject-fully subject))
+	 (article (or article (gnus-summary-article-number)))
+	 (articles (gnus-data-list backward))
+	 (arts (gnus-data-find-list article articles))
+	 result)
+    (when (or (not gnus-summary-check-current)
+	      (not unread)
+	      (not (gnus-data-unread-p (car arts))))
+      (setq arts (cdr arts)))
+    (while arts
+      (and (or (not unread)
+	       (gnus-data-unread-p (car arts)))
+	   (vectorp (gnus-data-header (car arts)))
+	   (gnus-subject-equal
+	    simp-subject (mail-header-subject (gnus-data-header (car arts))) t)
+	   (setq result (car arts)
+		 arts nil))
+      (setq arts (cdr arts)))
+    (and result
+	 (goto-char (gnus-data-pos result))
+	 (gnus-data-number result))))
+
+(defun gnus-summary-search-forward (&optional unread subject backward)
+  "Search forward for an article.
+If UNREAD, look for unread articles.  If SUBJECT, look for
+articles with that subject.  If BACKWARD, search backward instead."
+  (cond (subject (gnus-summary-find-subject subject unread backward))
+	(backward (gnus-summary-find-prev unread))
+	(t (gnus-summary-find-next unread))))
+
+(defun gnus-recenter (&optional n)
+  "Center point in window and redisplay frame.
+Also do horizontal recentering."
+  (interactive "P")
+  (when (and gnus-auto-center-summary
+	     (not (eq gnus-auto-center-summary 'vertical)))
+    (gnus-horizontal-recenter))
+  (recenter n))
+
+(defun gnus-summary-recenter ()
+  "Center point in the summary window.
+If `gnus-auto-center-summary' is nil, or the article buffer isn't
+displayed, no centering will be performed."
+  ;; Suggested by earle@mahendo.JPL.NASA.GOV (Greg Earle).
+  ;; Recenter only when requested.  Suggested by popovich@park.cs.columbia.edu.
+  (let* ((top (cond ((< (window-height) 4) 0)
+		    ((< (window-height) 7) 1)
+		    (t 2)))
+	 (height (1- (window-height)))
+	 (bottom (save-excursion (goto-char (point-max))
+				 (forward-line (- height))
+				 (point)))
+	 (window (get-buffer-window (current-buffer))))
+    ;; The user has to want it.
+    (when gnus-auto-center-summary
+      (when (get-buffer-window gnus-article-buffer)
+	;; Only do recentering when the article buffer is displayed,
+	;; Set the window start to either `bottom', which is the biggest
+	;; possible valid number, or the second line from the top,
+	;; whichever is the least.
+	(set-window-start
+	 window (min bottom (save-excursion
+			      (forward-line (- top)) (point)))))
+      ;; Do horizontal recentering while we're at it.
+      (when (and (get-buffer-window (current-buffer) t)
+		 (not (eq gnus-auto-center-summary 'vertical)))
+	(let ((selected (selected-window)))
+	  (select-window (get-buffer-window (current-buffer) t))
+	  (gnus-summary-position-point)
+	  (gnus-horizontal-recenter)
+	  (select-window selected))))))
+
+(defun gnus-summary-jump-to-group (newsgroup)
+  "Move point to NEWSGROUP in group mode buffer."
+  ;; Keep update point of group mode buffer if visible.
+  (if (eq (current-buffer) (get-buffer gnus-group-buffer))
+      (save-window-excursion
+	;; Take care of tree window mode.
+	(when (get-buffer-window gnus-group-buffer)
+	  (pop-to-buffer gnus-group-buffer))
+	(gnus-group-jump-to-group newsgroup))
+    (save-excursion
+      ;; Take care of tree window mode.
+      (if (get-buffer-window gnus-group-buffer)
+	  (pop-to-buffer gnus-group-buffer)
+	(set-buffer gnus-group-buffer))
+      (gnus-group-jump-to-group newsgroup))))
+
+;; This function returns a list of article numbers based on the
+;; difference between the ranges of read articles in this group and
+;; the range of active articles.
+(defun gnus-list-of-unread-articles (group)
+  (let* ((read (gnus-info-read (gnus-get-info group)))
+	 (active (or (gnus-active group) (gnus-activate-group group)))
+	 (last (cdr active))
+	 first nlast unread)
+    ;; If none are read, then all are unread.
+    (if (not read)
+	(setq first (car active))
+      ;; If the range of read articles is a single range, then the
+      ;; first unread article is the article after the last read
+      ;; article.  Sounds logical, doesn't it?
+      (if (not (listp (cdr read)))
+	  (setq first (1+ (cdr read)))
+	;; `read' is a list of ranges.
+	(when (/= (setq nlast (or (and (numberp (car read)) (car read))
+				  (caar read)))
+		  1)
+	  (setq first 1))
+	(while read
+	  (when first
+	    (while (< first nlast)
+	      (push first unread)
+	      (setq first (1+ first))))
+	  (setq first (1+ (if (atom (car read)) (car read) (cdar read))))
+	  (setq nlast (if (atom (cadr read)) (cadr read) (caadr read)))
+	  (setq read (cdr read)))))
+    ;; And add the last unread articles.
+    (while (<= first last)
+      (push first unread)
+      (setq first (1+ first)))
+    ;; Return the list of unread articles.
+    (nreverse unread)))
+
+(defun gnus-list-of-read-articles (group)
+  "Return a list of unread, unticked and non-dormant articles."
+  (let* ((info (gnus-get-info group))
+	 (marked (gnus-info-marks info))
+	 (active (gnus-active group)))
+    (and info active
+	 (gnus-set-difference
+	  (gnus-sorted-complement
+	   (gnus-uncompress-range active)
+	   (gnus-list-of-unread-articles group))
+	  (append
+	   (gnus-uncompress-range (cdr (assq 'dormant marked)))
+	   (gnus-uncompress-range (cdr (assq 'tick marked))))))))
+
+;; Various summary commands
+
+(defun gnus-summary-universal-argument (arg)
+  "Perform any operation on all articles that are process/prefixed."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((articles (gnus-summary-work-articles arg))
+	func article)
+    (if (eq
+	 (setq
+	  func
+	  (key-binding
+	   (read-key-sequence
+	    (substitute-command-keys
+	     "\\<gnus-summary-mode-map>\\[gnus-summary-universal-argument]"
+	     ))))
+	 'undefined)
+	(gnus-error 1 "Undefined key")
+      (save-excursion
+	(while articles
+	  (gnus-summary-goto-subject (setq article (pop articles)))
+	  (let (gnus-newsgroup-processable)
+	    (command-execute func))
+	  (gnus-summary-remove-process-mark article)))))
+  (gnus-summary-position-point))
+
+(defun gnus-summary-toggle-truncation (&optional arg)
+  "Toggle truncation of summary lines.
+With arg, turn line truncation on iff arg is positive."
+  (interactive "P")
+  (setq truncate-lines
+	(if (null arg) (not truncate-lines)
+	  (> (prefix-numeric-value arg) 0)))
+  (redraw-display))
+
+(defun gnus-summary-reselect-current-group (&optional all rescan)
+  "Exit and then reselect the current newsgroup.
+The prefix argument ALL means to select all articles."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (when (gnus-ephemeral-group-p gnus-newsgroup-name)
+    (error "Ephemeral groups can't be reselected"))
+  (let ((current-subject (gnus-summary-article-number))
+	(group gnus-newsgroup-name))
+    (setq gnus-newsgroup-begin nil)
+    (gnus-summary-exit)
+    ;; We have to adjust the point of group mode buffer because
+    ;; point was moved to the next unread newsgroup by exiting.
+    (gnus-summary-jump-to-group group)
+    (when rescan
+      (save-excursion
+	(gnus-group-get-new-news-this-group 1)))
+    (gnus-group-read-group all t)
+    (gnus-summary-goto-subject current-subject nil t)))
+
+(defun gnus-summary-rescan-group (&optional all)
+  "Exit the newsgroup, ask for new articles, and select the newsgroup."
+  (interactive "P")
+  (gnus-summary-reselect-current-group all t))
+
+(defun gnus-summary-update-info (&optional non-destructive)
+  (save-excursion
+    (let ((group gnus-newsgroup-name))
+      (when gnus-newsgroup-kill-headers
+	(setq gnus-newsgroup-killed
+	      (gnus-compress-sequence
+	       (nconc
+		(gnus-set-sorted-intersection
+		 (gnus-uncompress-range gnus-newsgroup-killed)
+		 (setq gnus-newsgroup-unselected
+		       (sort gnus-newsgroup-unselected '<)))
+		(setq gnus-newsgroup-unreads
+		      (sort gnus-newsgroup-unreads '<)))
+	       t)))
+      (unless (listp (cdr gnus-newsgroup-killed))
+	(setq gnus-newsgroup-killed (list gnus-newsgroup-killed)))
+      (let ((headers gnus-newsgroup-headers))
+	(when (and (not gnus-save-score)
+		   (not non-destructive))
+	  (setq gnus-newsgroup-scored nil))
+	;; Set the new ranges of read articles.
+	(gnus-update-read-articles
+	 group (append gnus-newsgroup-unreads gnus-newsgroup-unselected))
+	;; Set the current article marks.
+	(gnus-update-marks)
+	;; Do the cross-ref thing.
+	(when gnus-use-cross-reference
+	  (gnus-mark-xrefs-as-read group headers gnus-newsgroup-unreads))
+	;; Do adaptive scoring, and possibly save score files.
+	(when gnus-newsgroup-adaptive
+	  (gnus-score-adaptive))
+	(when gnus-use-scoring
+	  (gnus-score-save))
+	;; Do not switch windows but change the buffer to work.
+	(set-buffer gnus-group-buffer)
+	(unless (gnus-ephemeral-group-p gnus-newsgroup-name)
+	  (gnus-group-update-group group))))))
+
+(defun gnus-summary-save-newsrc (&optional force)
+  "Save the current number of read/marked articles in the dribble buffer.
+The dribble buffer will then be saved.
+If FORCE (the prefix), also save the .newsrc file(s)."
+  (interactive "P")
+  (gnus-summary-update-info t)
+  (if force
+      (gnus-save-newsrc-file)
+    (gnus-dribble-save)))
+
+(defun gnus-summary-exit (&optional temporary)
+  "Exit reading current newsgroup, and then return to group selection mode.
+gnus-exit-group-hook is called with no arguments if that value is non-nil."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-kill-save-kill-buffer)
+  (let* ((group gnus-newsgroup-name)
+	 (quit-config (gnus-group-quit-config gnus-newsgroup-name))
+	 (mode major-mode)
+	 (buf (current-buffer)))
+    (run-hooks 'gnus-summary-prepare-exit-hook)
+    ;; If we have several article buffers, we kill them at exit.
+    (unless gnus-single-article-buffer
+      (gnus-kill-buffer gnus-original-article-buffer)
+      (setq gnus-article-current nil))
+    (when gnus-use-cache
+      (gnus-cache-possibly-remove-articles)
+      (gnus-cache-save-buffers))
+    (gnus-async-prefetch-remove-group group)
+    (when gnus-suppress-duplicates
+      (gnus-dup-enter-articles))
+    (when gnus-use-trees
+      (gnus-tree-close group))
+    ;; Make all changes in this group permanent.
+    (unless quit-config
+      (run-hooks 'gnus-exit-group-hook)
+      (gnus-summary-update-info))
+    (gnus-close-group group)
+    ;; Make sure where we were, and go to next newsgroup.
+    (set-buffer gnus-group-buffer)
+    (unless quit-config
+      (gnus-group-jump-to-group group))
+    (run-hooks 'gnus-summary-exit-hook)
+    (unless quit-config
+      (gnus-group-next-unread-group 1))
+    (if temporary
+	nil				;Nothing to do.
+      ;; If we have several article buffers, we kill them at exit.
+      (unless gnus-single-article-buffer
+	(gnus-kill-buffer gnus-article-buffer)
+	(gnus-kill-buffer gnus-original-article-buffer)
+	(setq gnus-article-current nil))
+      (set-buffer buf)
+      (if (not gnus-kill-summary-on-exit)
+	  (gnus-deaden-summary)
+	;; We set all buffer-local variables to nil.  It is unclear why
+	;; this is needed, but if we don't, buffer-local variables are
+	;; not garbage-collected, it seems.  This would the lead to en
+	;; ever-growing Emacs.
+	(gnus-summary-clear-local-variables)
+	(when (get-buffer gnus-article-buffer)
+	  (bury-buffer gnus-article-buffer))
+	;; We clear the global counterparts of the buffer-local
+	;; variables as well, just to be on the safe side.
+	(set-buffer gnus-group-buffer)
+	(gnus-summary-clear-local-variables)
+	;; Return to group mode buffer.
+	(when (eq mode 'gnus-summary-mode)
+	  (gnus-kill-buffer buf)))
+      (setq gnus-current-select-method gnus-select-method)
+      (pop-to-buffer gnus-group-buffer)
+      ;; Clear the current group name.
+      (if (not quit-config)
+	  (progn
+	    (gnus-group-jump-to-group group)
+	    (gnus-group-next-unread-group 1)
+	    (gnus-configure-windows 'group 'force))
+	(gnus-handle-ephemeral-exit quit-config))
+      (unless quit-config
+	(setq gnus-newsgroup-name nil)))))
+
+(defalias 'gnus-summary-quit 'gnus-summary-exit-no-update)
+(defun gnus-summary-exit-no-update (&optional no-questions)
+  "Quit reading current newsgroup without updating read article info."
+  (interactive)
+  (gnus-set-global-variables)
+  (let* ((group gnus-newsgroup-name)
+	 (quit-config (gnus-group-quit-config group)))
+    (when (or no-questions
+	      gnus-expert-user
+	      (gnus-y-or-n-p "Discard changes to this group and exit? "))
+      ;; If we have several article buffers, we kill them at exit.
+      (unless gnus-single-article-buffer
+	(gnus-kill-buffer gnus-article-buffer)
+	(gnus-kill-buffer gnus-original-article-buffer)
+	(setq gnus-article-current nil))
+      (if (not gnus-kill-summary-on-exit)
+	  (gnus-deaden-summary)
+	(gnus-close-group group)
+	(gnus-summary-clear-local-variables)
+	(set-buffer gnus-group-buffer)
+	(gnus-summary-clear-local-variables)
+	(when (get-buffer gnus-summary-buffer)
+	  (kill-buffer gnus-summary-buffer)))
+      (unless gnus-single-article-buffer
+	(setq gnus-article-current nil))
+      (when gnus-use-trees
+	(gnus-tree-close group))
+      (gnus-async-prefetch-remove-group group)
+      (when (get-buffer gnus-article-buffer)
+	(bury-buffer gnus-article-buffer))
+      ;; Return to the group buffer.
+      (gnus-configure-windows 'group 'force)
+      ;; Clear the current group name.
+      (setq gnus-newsgroup-name nil)
+      (when (equal (gnus-group-group-name) group)
+	(gnus-group-next-unread-group 1))
+      (when quit-config
+        (gnus-handle-ephemeral-exit quit-config)))))
+
+(defun gnus-handle-ephemeral-exit (quit-config)
+  "Handle movement when leaving an ephemeral group.  The state
+which existed when entering the ephemeral is reset."
+  (if (not (buffer-name (car quit-config)))
+      (gnus-configure-windows 'group 'force)
+    (set-buffer (car quit-config))
+    (cond ((eq major-mode 'gnus-summary-mode)
+           (gnus-set-global-variables))
+          ((eq major-mode 'gnus-article-mode)
+           (save-excursion
+             ;; The `gnus-summary-buffer' variable may point
+             ;; to the old summary buffer when using a single
+             ;; article buffer.
+             (unless (gnus-buffer-live-p gnus-summary-buffer)
+               (set-buffer gnus-group-buffer))
+             (set-buffer gnus-summary-buffer)
+             (gnus-set-global-variables))))
+    (if (or (eq (cdr quit-config) 'article)
+            (eq (cdr quit-config) 'pick))
+        (progn
+          ;; The current article may be from the ephemeral group
+          ;; thus it is best that we reload this article
+          (gnus-summary-show-article)
+          (if (and (boundp 'gnus-pick-mode) (symbol-value 'gnus-pick-mode))
+              (gnus-configure-windows 'pick 'force)
+            (gnus-configure-windows (cdr quit-config) 'force)))
+      (gnus-configure-windows (cdr quit-config) 'force))
+    (when (eq major-mode 'gnus-summary-mode)
+      (gnus-summary-next-subject 1 nil t)
+      (gnus-summary-recenter)
+      (gnus-summary-position-point))))
+
+;;; Dead summaries.
+
+(defvar gnus-dead-summary-mode-map nil)
+
+(unless gnus-dead-summary-mode-map
+  (setq gnus-dead-summary-mode-map (make-keymap))
+  (suppress-keymap gnus-dead-summary-mode-map)
+  (substitute-key-definition
+   'undefined 'gnus-summary-wake-up-the-dead gnus-dead-summary-mode-map)
+  (let ((keys '("\C-d" "\r" "\177")))
+    (while keys
+      (define-key gnus-dead-summary-mode-map
+	(pop keys) 'gnus-summary-wake-up-the-dead))))
+
+(defvar gnus-dead-summary-mode nil
+  "Minor mode for Gnus summary buffers.")
+
+(defun gnus-dead-summary-mode (&optional arg)
+  "Minor mode for Gnus summary buffers."
+  (interactive "P")
+  (when (eq major-mode 'gnus-summary-mode)
+    (make-local-variable 'gnus-dead-summary-mode)
+    (setq gnus-dead-summary-mode
+	  (if (null arg) (not gnus-dead-summary-mode)
+	    (> (prefix-numeric-value arg) 0)))
+    (when gnus-dead-summary-mode
+      (unless (assq 'gnus-dead-summary-mode minor-mode-alist)
+	(push '(gnus-dead-summary-mode " Dead") minor-mode-alist))
+      (unless (assq 'gnus-dead-summary-mode minor-mode-map-alist)
+	(push (cons 'gnus-dead-summary-mode gnus-dead-summary-mode-map)
+	      minor-mode-map-alist)))))
+
+(defun gnus-deaden-summary ()
+  "Make the current summary buffer into a dead summary buffer."
+  ;; Kill any previous dead summary buffer.
+  (when (and gnus-dead-summary
+	     (buffer-name gnus-dead-summary))
+    (save-excursion
+      (set-buffer gnus-dead-summary)
+      (when gnus-dead-summary-mode
+	(kill-buffer (current-buffer)))))
+  ;; Make this the current dead summary.
+  (setq gnus-dead-summary (current-buffer))
+  (gnus-dead-summary-mode 1)
+  (let ((name (buffer-name)))
+    (when (string-match "Summary" name)
+      (rename-buffer
+       (concat (substring name 0 (match-beginning 0)) "Dead "
+	       (substring name (match-beginning 0)))
+       t))))
+
+(defun gnus-kill-or-deaden-summary (buffer)
+  "Kill or deaden the summary BUFFER."
+  (when (and (buffer-name buffer)
+	     (not gnus-single-article-buffer))
+    (save-excursion
+      (set-buffer buffer)
+      (gnus-kill-buffer gnus-article-buffer)
+      (gnus-kill-buffer gnus-original-article-buffer)))
+  (cond (gnus-kill-summary-on-exit
+	 (when (and gnus-use-trees
+		    (and (get-buffer buffer)
+			 (buffer-name (get-buffer buffer))))
+	   (save-excursion
+	     (set-buffer (get-buffer buffer))
+	     (gnus-tree-close gnus-newsgroup-name)))
+	 (gnus-kill-buffer buffer))
+	((and (get-buffer buffer)
+	      (buffer-name (get-buffer buffer)))
+	 (save-excursion
+	   (set-buffer buffer)
+	   (gnus-deaden-summary)))))
+
+(defun gnus-summary-wake-up-the-dead (&rest args)
+  "Wake up the dead summary buffer."
+  (interactive)
+  (gnus-dead-summary-mode -1)
+  (let ((name (buffer-name)))
+    (when (string-match "Dead " name)
+      (rename-buffer
+       (concat (substring name 0 (match-beginning 0))
+	       (substring name (match-end 0)))
+       t)))
+  (gnus-message 3 "This dead summary is now alive again"))
+
+;; Suggested by Andrew Eskilsson <pi92ae@pt.hk-r.se>.
+(defun gnus-summary-fetch-faq (&optional faq-dir)
+  "Fetch the FAQ for the current group.
+If FAQ-DIR (the prefix), prompt for a directory to search for the faq
+in."
+  (interactive
+   (list
+    (when current-prefix-arg
+      (completing-read
+       "Faq dir: " (and (listp gnus-group-faq-directory)
+			gnus-group-faq-directory)))))
+  (let (gnus-faq-buffer)
+    (when (setq gnus-faq-buffer
+		(gnus-group-fetch-faq gnus-newsgroup-name faq-dir))
+      (gnus-configure-windows 'summary-faq))))
+
+;; Suggested by Per Abrahamsen <amanda@iesd.auc.dk>.
+(defun gnus-summary-describe-group (&optional force)
+  "Describe the current newsgroup."
+  (interactive "P")
+  (gnus-group-describe-group force gnus-newsgroup-name))
+
+(defun gnus-summary-describe-briefly ()
+  "Describe summary mode commands briefly."
+  (interactive)
+  (gnus-message 6
+		(substitute-command-keys "\\<gnus-summary-mode-map>\\[gnus-summary-next-page]:Select  \\[gnus-summary-next-unread-article]:Forward  \\[gnus-summary-prev-unread-article]:Backward  \\[gnus-summary-exit]:Exit  \\[gnus-info-find-node]:Run Info	 \\[gnus-summary-describe-briefly]:This help")))
+
+;; Walking around group mode buffer from summary mode.
+
+(defun gnus-summary-next-group (&optional no-article target-group backward)
+  "Exit current newsgroup and then select next unread newsgroup.
+If prefix argument NO-ARTICLE is non-nil, no article is selected
+initially.  If NEXT-GROUP, go to this group.  If BACKWARD, go to
+previous group instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  ;; Stop pre-fetching.
+  (gnus-async-halt-prefetch)
+  (let ((current-group gnus-newsgroup-name)
+	(current-buffer (current-buffer))
+	entered)
+    ;; First we semi-exit this group to update Xrefs and all variables.
+    ;; We can't do a real exit, because the window conf must remain
+    ;; the same in case the user is prompted for info, and we don't
+    ;; want the window conf to change before that...
+    (gnus-summary-exit t)
+    (while (not entered)
+      ;; Then we find what group we are supposed to enter.
+      (set-buffer gnus-group-buffer)
+      (gnus-group-jump-to-group current-group)
+      (setq target-group
+	    (or target-group
+		(if (eq gnus-keep-same-level 'best)
+		    (gnus-summary-best-group gnus-newsgroup-name)
+		  (gnus-summary-search-group backward gnus-keep-same-level))))
+      (if (not target-group)
+	  ;; There are no further groups, so we return to the group
+	  ;; buffer.
+	  (progn
+	    (gnus-message 5 "Returning to the group buffer")
+	    (setq entered t)
+	    (when (gnus-buffer-live-p current-buffer)
+	      (set-buffer current-buffer)
+	      (gnus-summary-exit))
+	    (run-hooks 'gnus-group-no-more-groups-hook))
+	;; We try to enter the target group.
+	(gnus-group-jump-to-group target-group)
+	(let ((unreads (gnus-group-group-unread)))
+	  (if (and (or (eq t unreads)
+		       (and unreads (not (zerop unreads))))
+		   (gnus-summary-read-group
+		    target-group nil no-article current-buffer))
+	      (setq entered t)
+	    (setq current-group target-group
+		  target-group nil)))))))
+
+(defun gnus-summary-prev-group (&optional no-article)
+  "Exit current newsgroup and then select previous unread newsgroup.
+If prefix argument NO-ARTICLE is non-nil, no article is selected initially."
+  (interactive "P")
+  (gnus-summary-next-group no-article nil t))
+
+;; Walking around summary lines.
+
+(defun gnus-summary-first-subject (&optional unread)
+  "Go to the first unread subject.
+If UNREAD is non-nil, go to the first unread article.
+Returns the article selected or nil if there are no unread articles."
+  (interactive "P")
+  (prog1
+      (cond
+       ;; Empty summary.
+       ((null gnus-newsgroup-data)
+	(gnus-message 3 "No articles in the group")
+	nil)
+       ;; Pick the first article.
+       ((not unread)
+	(goto-char (gnus-data-pos (car gnus-newsgroup-data)))
+	(gnus-data-number (car gnus-newsgroup-data)))
+       ;; No unread articles.
+       ((null gnus-newsgroup-unreads)
+	(gnus-message 3 "No more unread articles")
+	nil)
+       ;; Find the first unread article.
+       (t
+	(let ((data gnus-newsgroup-data))
+	  (while (and data
+		      (not (gnus-data-unread-p (car data))))
+	    (setq data (cdr data)))
+	  (when data
+	    (goto-char (gnus-data-pos (car data)))
+	    (gnus-data-number (car data))))))
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-next-subject (n &optional unread dont-display)
+  "Go to next N'th summary line.
+If N is negative, go to the previous N'th subject line.
+If UNREAD is non-nil, only unread articles are selected.
+The difference between N and the actual number of steps taken is
+returned."
+  (interactive "p")
+  (let ((backward (< n 0))
+	(n (abs n)))
+    (while (and (> n 0)
+		(if backward
+		    (gnus-summary-find-prev unread)
+		  (gnus-summary-find-next unread)))
+      (setq n (1- n)))
+    (when (/= 0 n)
+      (gnus-message 7 "No more%s articles"
+		    (if unread " unread" "")))
+    (unless dont-display
+      (gnus-summary-recenter)
+      (gnus-summary-position-point))
+    n))
+
+(defun gnus-summary-next-unread-subject (n)
+  "Go to next N'th unread summary line."
+  (interactive "p")
+  (gnus-summary-next-subject n t))
+
+(defun gnus-summary-prev-subject (n &optional unread)
+  "Go to previous N'th summary line.
+If optional argument UNREAD is non-nil, only unread article is selected."
+  (interactive "p")
+  (gnus-summary-next-subject (- n) unread))
+
+(defun gnus-summary-prev-unread-subject (n)
+  "Go to previous N'th unread summary line."
+  (interactive "p")
+  (gnus-summary-next-subject (- n) t))
+
+(defun gnus-summary-goto-subject (article &optional force silent)
+  "Go the subject line of ARTICLE.
+If FORCE, also allow jumping to articles not currently shown."
+  (interactive "nArticle number: ")
+  (let ((b (point))
+	(data (gnus-data-find article)))
+    ;; We read in the article if we have to.
+    (and (not data)
+	 force
+	 (gnus-summary-insert-subject article (and (vectorp force) force) t)
+	 (setq data (gnus-data-find article)))
+    (goto-char b)
+    (if (not data)
+	(progn
+	  (unless silent
+	    (gnus-message 3 "Can't find article %d" article))
+	  nil)
+      (goto-char (gnus-data-pos data))
+      article)))
+
+;; Walking around summary lines with displaying articles.
+
+(defun gnus-summary-expand-window (&optional arg)
+  "Make the summary buffer take up the entire Emacs frame.
+Given a prefix, will force an `article' buffer configuration."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (if arg
+      (gnus-configure-windows 'article 'force)
+    (gnus-configure-windows 'summary 'force)))
+
+(defun gnus-summary-display-article (article &optional all-header)
+  "Display ARTICLE in article buffer."
+  (gnus-set-global-variables)
+  (if (null article)
+      nil
+    (prog1
+	(if gnus-summary-display-article-function
+	    (funcall gnus-summary-display-article-function article all-header)
+	  (gnus-article-prepare article all-header))
+      (run-hooks 'gnus-select-article-hook)
+      (when (and gnus-current-article
+		 (not (zerop gnus-current-article)))
+	(gnus-summary-goto-subject gnus-current-article))
+      (gnus-summary-recenter)
+      (when (and gnus-use-trees gnus-show-threads)
+	(gnus-possibly-generate-tree article)
+	(gnus-highlight-selected-tree article))
+      ;; Successfully display article.
+      (gnus-article-set-window-start
+       (cdr (assq article gnus-newsgroup-bookmarks))))))
+
+(defun gnus-summary-select-article (&optional all-headers force pseudo article)
+  "Select the current article.
+If ALL-HEADERS is non-nil, show all header fields.  If FORCE is
+non-nil, the article will be re-fetched even if it already present in
+the article buffer.  If PSEUDO is non-nil, pseudo-articles will also
+be displayed."
+  ;; Make sure we are in the summary buffer to work around bbdb bug.
+  (unless (eq major-mode 'gnus-summary-mode)
+    (set-buffer gnus-summary-buffer))
+  (let ((article (or article (gnus-summary-article-number)))
+	(all-headers (not (not all-headers))) ;Must be T or NIL.
+	gnus-summary-display-article-function
+	did)
+    (and (not pseudo)
+	 (gnus-summary-article-pseudo-p article)
+	 (error "This is a pseudo-article."))
+    (prog1
+	(save-excursion
+	  (set-buffer gnus-summary-buffer)
+	  (if (or (and gnus-single-article-buffer
+		       (or (null gnus-current-article)
+			   (null gnus-article-current)
+			   (null (get-buffer gnus-article-buffer))
+			   (not (eq article (cdr gnus-article-current)))
+			   (not (equal (car gnus-article-current)
+				       gnus-newsgroup-name))))
+		  (and (not gnus-single-article-buffer)
+		       (or (null gnus-current-article)
+			   (not (eq gnus-current-article article))))
+		  force)
+	      ;; The requested article is different from the current article.
+	      (prog1
+		  (gnus-summary-display-article article all-headers)
+		(setq did article))
+	    (when (or all-headers gnus-show-all-headers)
+	      (gnus-article-show-all-headers))
+	    'old))
+      (when did
+	(gnus-article-set-window-start
+	 (cdr (assq article gnus-newsgroup-bookmarks)))))))
+
+(defun gnus-summary-set-current-mark (&optional current-mark)
+  "Obsolete function."
+  nil)
+
+(defun gnus-summary-next-article (&optional unread subject backward push)
+  "Select the next article.
+If UNREAD, only unread articles are selected.
+If SUBJECT, only articles with SUBJECT are selected.
+If BACKWARD, the previous article is selected instead of the next."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (cond
+   ;; Is there such an article?
+   ((and (gnus-summary-search-forward unread subject backward)
+	 (or (gnus-summary-display-article (gnus-summary-article-number))
+	     (eq (gnus-summary-article-mark) gnus-canceled-mark)))
+    (gnus-summary-position-point))
+   ;; If not, we try the first unread, if that is wanted.
+   ((and subject
+	 gnus-auto-select-same
+	 (gnus-summary-first-unread-article))
+    (gnus-summary-position-point)
+    (gnus-message 6 "Wrapped"))
+   ;; Try to get next/previous article not displayed in this group.
+   ((and gnus-auto-extend-newsgroup
+	 (not unread) (not subject))
+    (gnus-summary-goto-article
+     (if backward (1- gnus-newsgroup-begin) (1+ gnus-newsgroup-end))
+     nil t))
+   ;; Go to next/previous group.
+   (t
+    (unless (gnus-ephemeral-group-p gnus-newsgroup-name)
+      (gnus-summary-jump-to-group gnus-newsgroup-name))
+    (let ((cmd last-command-char)
+	  (point
+	   (save-excursion
+	     (set-buffer gnus-group-buffer)
+	     (point)))
+	  (group
+	   (if (eq gnus-keep-same-level 'best)
+	       (gnus-summary-best-group gnus-newsgroup-name)
+	     (gnus-summary-search-group backward gnus-keep-same-level))))
+      ;; For some reason, the group window gets selected.  We change
+      ;; it back.
+      (select-window (get-buffer-window (current-buffer)))
+      ;; Select next unread newsgroup automagically.
+      (cond
+       ((or (not gnus-auto-select-next)
+	    (not cmd))
+	(gnus-message 7 "No more%s articles" (if unread " unread" "")))
+       ((or (eq gnus-auto-select-next 'quietly)
+	    (and (eq gnus-auto-select-next 'slightly-quietly)
+		 push)
+	    (and (eq gnus-auto-select-next 'almost-quietly)
+		 (gnus-summary-last-article-p)))
+	;; Select quietly.
+	(if (gnus-ephemeral-group-p gnus-newsgroup-name)
+	    (gnus-summary-exit)
+	  (gnus-message 7 "No more%s articles (%s)..."
+			(if unread " unread" "")
+			(if group (concat "selecting " group)
+			  "exiting"))
+	  (gnus-summary-next-group nil group backward)))
+       (t
+	(when (gnus-key-press-event-p last-input-event)
+	  (gnus-summary-walk-group-buffer
+	   gnus-newsgroup-name cmd unread backward point))))))))
+
+(defun gnus-summary-walk-group-buffer (from-group cmd unread backward start)
+  (let ((keystrokes '((?\C-n (gnus-group-next-unread-group 1))
+		      (?\C-p (gnus-group-prev-unread-group 1))))
+	(cursor-in-echo-area t)
+	keve key group ended)
+    (save-excursion
+      (set-buffer gnus-group-buffer)
+      (goto-char start)
+      (setq group
+	    (if (eq gnus-keep-same-level 'best)
+		(gnus-summary-best-group gnus-newsgroup-name)
+	      (gnus-summary-search-group backward gnus-keep-same-level))))
+    (while (not ended)
+      (gnus-message
+       5 "No more%s articles%s" (if unread " unread" "")
+       (if (and group
+		(not (gnus-ephemeral-group-p gnus-newsgroup-name)))
+	   (format " (Type %s for %s [%s])"
+		   (single-key-description cmd) group
+		   (car (gnus-gethash group gnus-newsrc-hashtb)))
+	 (format " (Type %s to exit %s)"
+		 (single-key-description cmd)
+		 gnus-newsgroup-name)))
+      ;; Confirm auto selection.
+      (setq key (car (setq keve (gnus-read-event-char))))
+      (setq ended t)
+      (cond
+       ((assq key keystrokes)
+	(let ((obuf (current-buffer)))
+	  (switch-to-buffer gnus-group-buffer)
+	  (when group
+	    (gnus-group-jump-to-group group))
+	  (eval (cadr (assq key keystrokes)))
+	  (setq group (gnus-group-group-name))
+	  (switch-to-buffer obuf))
+	(setq ended nil))
+       ((equal key cmd)
+	(if (or (not group)
+		(gnus-ephemeral-group-p gnus-newsgroup-name))
+	    (gnus-summary-exit)
+	  (gnus-summary-next-group nil group backward)))
+       (t
+	(push (cdr keve) unread-command-events))))))
+
+(defun gnus-summary-next-unread-article ()
+  "Select unread article after current one."
+  (interactive)
+  (gnus-summary-next-article
+   (or (not (eq gnus-summary-goto-unread 'never))
+       (gnus-summary-last-article-p (gnus-summary-article-number)))
+   (and gnus-auto-select-same
+	(gnus-summary-article-subject))))
+
+(defun gnus-summary-prev-article (&optional unread subject)
+  "Select the article after the current one.
+If UNREAD is non-nil, only unread articles are selected."
+  (interactive "P")
+  (gnus-summary-next-article unread subject t))
+
+(defun gnus-summary-prev-unread-article ()
+  "Select unread article before current one."
+  (interactive)
+  (gnus-summary-prev-article
+   (or (not (eq gnus-summary-goto-unread 'never))
+       (gnus-summary-first-article-p (gnus-summary-article-number)))
+   (and gnus-auto-select-same
+	(gnus-summary-article-subject))))
+
+(defun gnus-summary-next-page (&optional lines circular)
+  "Show next page of the selected article.
+If at the end of the current article, select the next article.
+LINES says how many lines should be scrolled up.
+
+If CIRCULAR is non-nil, go to the start of the article instead of
+selecting the next article when reaching the end of the current
+article."
+  (interactive "P")
+  (setq gnus-summary-buffer (current-buffer))
+  (gnus-set-global-variables)
+  (let ((article (gnus-summary-article-number))
+	(article-window (get-buffer-window gnus-article-buffer t))
+	endp)
+    (gnus-configure-windows 'article)
+    (if (eq (cdr (assq article gnus-newsgroup-reads)) gnus-canceled-mark)
+	(if (and (eq gnus-summary-goto-unread 'never)
+		 (not (gnus-summary-last-article-p article)))
+	    (gnus-summary-next-article)
+	  (gnus-summary-next-unread-article))
+      (if (or (null gnus-current-article)
+	      (null gnus-article-current)
+	      (/= article (cdr gnus-article-current))
+	      (not (equal (car gnus-article-current) gnus-newsgroup-name)))
+	  ;; Selected subject is different from current article's.
+	  (gnus-summary-display-article article)
+	(when article-window
+	  (gnus-eval-in-buffer-window gnus-article-buffer
+	    (setq endp (gnus-article-next-page lines)))
+	  (when endp
+	    (cond (circular
+		   (gnus-summary-beginning-of-article))
+		  (lines
+		   (gnus-message 3 "End of message"))
+		  ((null lines)
+		   (if (and (eq gnus-summary-goto-unread 'never)
+			    (not (gnus-summary-last-article-p article)))
+		       (gnus-summary-next-article)
+		     (gnus-summary-next-unread-article))))))))
+    (gnus-summary-recenter)
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-prev-page (&optional lines move)
+  "Show previous page of selected article.
+Argument LINES specifies lines to be scrolled down.
+If MOVE, move to the previous unread article if point is at
+the beginning of the buffer."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((article (gnus-summary-article-number))
+	(article-window (get-buffer-window gnus-article-buffer t))
+	endp)
+    (gnus-configure-windows 'article)
+    (if (or (null gnus-current-article)
+	    (null gnus-article-current)
+	    (/= article (cdr gnus-article-current))
+	    (not (equal (car gnus-article-current) gnus-newsgroup-name)))
+	;; Selected subject is different from current article's.
+	(gnus-summary-display-article article)
+      (gnus-summary-recenter)
+      (when article-window
+	(gnus-eval-in-buffer-window gnus-article-buffer
+	  (setq endp (gnus-article-prev-page lines)))
+	(when (and move endp)
+	  (cond (lines
+		 (gnus-message 3 "Beginning of message"))
+		((null lines)
+		 (if (and (eq gnus-summary-goto-unread 'never)
+			  (not (gnus-summary-first-article-p article)))
+		     (gnus-summary-prev-article)
+		   (gnus-summary-prev-unread-article))))))))
+  (gnus-summary-position-point))
+
+(defun gnus-summary-prev-page-or-article (&optional lines)
+  "Show previous page of selected article.
+Argument LINES specifies lines to be scrolled down.
+If at the beginning of the article, go to the next article."
+  (interactive "P")
+  (gnus-summary-prev-page lines t))
+
+(defun gnus-summary-scroll-up (lines)
+  "Scroll up (or down) one line current article.
+Argument LINES specifies lines to be scrolled up (or down if negative)."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (gnus-configure-windows 'article)
+  (gnus-summary-show-thread)
+  (when (eq (gnus-summary-select-article nil nil 'pseudo) 'old)
+    (gnus-eval-in-buffer-window gnus-article-buffer
+      (cond ((> lines 0)
+	     (when (gnus-article-next-page lines)
+	       (gnus-message 3 "End of message")))
+	    ((< lines 0)
+	     (gnus-article-prev-page (- lines))))))
+  (gnus-summary-recenter)
+  (gnus-summary-position-point))
+
+(defun gnus-summary-next-same-subject ()
+  "Select next article which has the same subject as current one."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-summary-next-article nil (gnus-summary-article-subject)))
+
+(defun gnus-summary-prev-same-subject ()
+  "Select previous article which has the same subject as current one."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-summary-prev-article nil (gnus-summary-article-subject)))
+
+(defun gnus-summary-next-unread-same-subject ()
+  "Select next unread article which has the same subject as current one."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-summary-next-article t (gnus-summary-article-subject)))
+
+(defun gnus-summary-prev-unread-same-subject ()
+  "Select previous unread article which has the same subject as current one."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-summary-prev-article t (gnus-summary-article-subject)))
+
+(defun gnus-summary-first-unread-article ()
+  "Select the first unread article.
+Return nil if there are no unread articles."
+  (interactive)
+  (gnus-set-global-variables)
+  (prog1
+      (when (gnus-summary-first-subject t)
+	(gnus-summary-show-thread)
+	(gnus-summary-first-subject t)
+	(gnus-summary-display-article (gnus-summary-article-number)))
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-first-article ()
+  "Select the first article.
+Return nil if there are no articles."
+  (interactive)
+  (gnus-set-global-variables)
+  (prog1
+      (when (gnus-summary-first-subject)
+      (gnus-summary-show-thread)
+      (gnus-summary-first-subject)
+      (gnus-summary-display-article (gnus-summary-article-number)))
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-best-unread-article ()
+  "Select the unread article with the highest score."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((best -1000000)
+	(data gnus-newsgroup-data)
+	article score)
+    (while data
+      (and (gnus-data-unread-p (car data))
+	   (> (setq score
+		    (gnus-summary-article-score (gnus-data-number (car data))))
+	      best)
+	   (setq best score
+		 article (gnus-data-number (car data))))
+      (setq data (cdr data)))
+    (prog1
+	(if article
+	    (gnus-summary-goto-article article)
+	  (error "No unread articles"))
+      (gnus-summary-position-point))))
+
+(defun gnus-summary-last-subject ()
+  "Go to the last displayed subject line in the group."
+  (let ((article (gnus-data-number (car (gnus-data-list t)))))
+    (when article
+      (gnus-summary-goto-subject article))))
+
+(defun gnus-summary-goto-article (article &optional all-headers force)
+  "Fetch ARTICLE and display it if it exists.
+If ALL-HEADERS is non-nil, no header lines are hidden."
+  (interactive
+   (list
+    (string-to-int
+     (completing-read
+      "Article number: "
+      (mapcar (lambda (number) (list (int-to-string number)))
+	      gnus-newsgroup-limit)))
+    current-prefix-arg
+    t))
+  (prog1
+      (if (gnus-summary-goto-subject article force)
+	  (gnus-summary-display-article article all-headers)
+	(gnus-message 4 "Couldn't go to article %s" article) nil)
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-goto-last-article ()
+  "Go to the previously read article."
+  (interactive)
+  (prog1
+      (when gnus-last-article
+	(gnus-summary-goto-article gnus-last-article))
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-pop-article (number)
+  "Pop one article off the history and go to the previous.
+NUMBER articles will be popped off."
+  (interactive "p")
+  (let (to)
+    (setq gnus-newsgroup-history
+	  (cdr (setq to (nthcdr number gnus-newsgroup-history))))
+    (if to
+	(gnus-summary-goto-article (car to))
+      (error "Article history empty")))
+  (gnus-summary-position-point))
+
+;; Summary commands and functions for limiting the summary buffer.
+
+(defun gnus-summary-limit-to-articles (n)
+  "Limit the summary buffer to the next N articles.
+If not given a prefix, use the process marked articles instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (prog1
+      (let ((articles (gnus-summary-work-articles n)))
+	(setq gnus-newsgroup-processable nil)
+	(gnus-summary-limit articles))
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-pop-limit (&optional total)
+  "Restore the previous limit.
+If given a prefix, remove all limits."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (when total
+    (setq gnus-newsgroup-limits
+	  (list (mapcar (lambda (h) (mail-header-number h))
+			gnus-newsgroup-headers))))
+  (unless gnus-newsgroup-limits
+    (error "No limit to pop"))
+  (prog1
+      (gnus-summary-limit nil 'pop)
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-limit-to-subject (subject &optional header)
+  "Limit the summary buffer to articles that have subjects that match a regexp."
+  (interactive "sLimit to subject (regexp): ")
+  (unless header
+    (setq header "subject"))
+  (when (not (equal "" subject))
+    (prog1
+	(let ((articles (gnus-summary-find-matching
+			 (or header "subject") subject 'all)))
+	  (unless articles
+	    (error "Found no matches for \"%s\"" subject))
+	  (gnus-summary-limit articles))
+      (gnus-summary-position-point))))
+
+(defun gnus-summary-limit-to-author (from)
+  "Limit the summary buffer to articles that have authors that match a regexp."
+  (interactive "sLimit to author (regexp): ")
+  (gnus-summary-limit-to-subject from "from"))
+
+(defun gnus-summary-limit-to-age (age &optional younger-p)
+  "Limit the summary buffer to articles that are older than (or equal) AGE days.
+If YOUNGER-P (the prefix) is non-nil, limit the summary buffer to
+articles that are younger than AGE days."
+  (interactive "nTime in days: \nP")
+  (prog1
+      (let ((data gnus-newsgroup-data)
+	    (cutoff (nnmail-days-to-time age))
+	    articles d date is-younger)
+	(while (setq d (pop data))
+	  (when (and (vectorp (gnus-data-header d))
+		     (setq date (mail-header-date (gnus-data-header d))))
+	    (setq is-younger (nnmail-time-less
+			      (nnmail-time-since (nnmail-date-to-time date))
+			      cutoff))
+	    (when (if younger-p is-younger (not is-younger))
+	      (push (gnus-data-number d) articles))))
+	(gnus-summary-limit (nreverse articles)))
+    (gnus-summary-position-point)))
+
+(defalias 'gnus-summary-delete-marked-as-read 'gnus-summary-limit-to-unread)
+(make-obsolete
+ 'gnus-summary-delete-marked-as-read 'gnus-summary-limit-to-unread)
+
+(defun gnus-summary-limit-to-unread (&optional all)
+  "Limit the summary buffer to articles that are not marked as read.
+If ALL is non-nil, limit strictly to unread articles."
+  (interactive "P")
+  (if all
+      (gnus-summary-limit-to-marks (char-to-string gnus-unread-mark))
+    (gnus-summary-limit-to-marks
+     ;; Concat all the marks that say that an article is read and have
+     ;; those removed.
+     (list gnus-del-mark gnus-read-mark gnus-ancient-mark
+	   gnus-killed-mark gnus-kill-file-mark
+	   gnus-low-score-mark gnus-expirable-mark
+	   gnus-canceled-mark gnus-catchup-mark gnus-sparse-mark
+	   gnus-duplicate-mark gnus-souped-mark)
+     'reverse)))
+
+(defalias 'gnus-summary-delete-marked-with 'gnus-summary-limit-exclude-marks)
+(make-obsolete 'gnus-summary-delete-marked-with
+	       'gnus-summary-limit-exlude-marks)
+
+(defun gnus-summary-limit-exclude-marks (marks &optional reverse)
+  "Exclude articles that are marked with MARKS (e.g. \"DK\").
+If REVERSE, limit the summary buffer to articles that are marked
+with MARKS.  MARKS can either be a string of marks or a list of marks.
+Returns how many articles were removed."
+  (interactive "sMarks: ")
+  (gnus-summary-limit-to-marks marks t))
+
+(defun gnus-summary-limit-to-marks (marks &optional reverse)
+  "Limit the summary buffer to articles that are marked with MARKS (e.g. \"DK\").
+If REVERSE (the prefix), limit the summary buffer to articles that are
+not marked with MARKS.  MARKS can either be a string of marks or a
+list of marks.
+Returns how many articles were removed."
+  (interactive (list (read-string "Marks: ") current-prefix-arg))
+  (gnus-set-global-variables)
+  (prog1
+      (let ((data gnus-newsgroup-data)
+	    (marks (if (listp marks) marks
+		     (append marks nil))) ; Transform to list.
+	    articles)
+	(while data
+	  (when (if reverse (not (memq (gnus-data-mark (car data)) marks))
+		  (memq (gnus-data-mark (car data)) marks))
+	    (push (gnus-data-number (car data)) articles))
+	  (setq data (cdr data)))
+	(gnus-summary-limit articles))
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-limit-to-score (&optional score)
+  "Limit to articles with score at or above SCORE."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (setq score (if score
+		  (prefix-numeric-value score)
+		(or gnus-summary-default-score 0)))
+  (let ((data gnus-newsgroup-data)
+	articles)
+    (while data
+      (when (>= (gnus-summary-article-score (gnus-data-number (car data)))
+		score)
+	(push (gnus-data-number (car data)) articles))
+      (setq data (cdr data)))
+    (prog1
+	(gnus-summary-limit articles)
+      (gnus-summary-position-point))))
+
+(defun gnus-summary-limit-include-dormant ()
+  "Display all the hidden articles that are marked as dormant."
+  (interactive)
+  (gnus-set-global-variables)
+  (unless gnus-newsgroup-dormant
+    (error "There are no dormant articles in this group"))
+  (prog1
+      (gnus-summary-limit (append gnus-newsgroup-dormant gnus-newsgroup-limit))
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-limit-exclude-dormant ()
+  "Hide all dormant articles."
+  (interactive)
+  (gnus-set-global-variables)
+  (prog1
+      (gnus-summary-limit-to-marks (list gnus-dormant-mark) 'reverse)
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-limit-exclude-childless-dormant ()
+  "Hide all dormant articles that have no children."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((data (gnus-data-list t))
+	articles d children)
+    ;; Find all articles that are either not dormant or have
+    ;; children.
+    (while (setq d (pop data))
+      (when (or (not (= (gnus-data-mark d) gnus-dormant-mark))
+		(and (setq children
+			   (gnus-article-children (gnus-data-number d)))
+		     (let (found)
+		       (while children
+			 (when (memq (car children) articles)
+			   (setq children nil
+				 found t))
+			 (pop children))
+		       found)))
+	(push (gnus-data-number d) articles)))
+    ;; Do the limiting.
+    (prog1
+	(gnus-summary-limit articles)
+      (gnus-summary-position-point))))
+
+(defun gnus-summary-limit-mark-excluded-as-read (&optional all)
+  "Mark all unread excluded articles as read.
+If ALL, mark even excluded ticked and dormants as read."
+  (interactive "P")
+  (let ((articles (gnus-sorted-complement
+		   (sort
+		    (mapcar (lambda (h) (mail-header-number h))
+			    gnus-newsgroup-headers)
+		    '<)
+		   (sort gnus-newsgroup-limit '<)))
+	article)
+    (setq gnus-newsgroup-unreads nil)
+    (if all
+	(setq gnus-newsgroup-dormant nil
+	      gnus-newsgroup-marked nil
+	      gnus-newsgroup-reads
+	      (nconc
+	       (mapcar (lambda (n) (cons n gnus-catchup-mark)) articles)
+	       gnus-newsgroup-reads))
+      (while (setq article (pop articles))
+	(unless (or (memq article gnus-newsgroup-dormant)
+		    (memq article gnus-newsgroup-marked))
+	  (push (cons article gnus-catchup-mark) gnus-newsgroup-reads))))))
+
+(defun gnus-summary-limit (articles &optional pop)
+  (if pop
+      ;; We pop the previous limit off the stack and use that.
+      (setq articles (car gnus-newsgroup-limits)
+	    gnus-newsgroup-limits (cdr gnus-newsgroup-limits))
+    ;; We use the new limit, so we push the old limit on the stack.
+    (push gnus-newsgroup-limit gnus-newsgroup-limits))
+  ;; Set the limit.
+  (setq gnus-newsgroup-limit articles)
+  (let ((total (length gnus-newsgroup-data))
+	(data (gnus-data-find-list (gnus-summary-article-number)))
+	(gnus-summary-mark-below nil)	; Inhibit this.
+	found)
+    ;; This will do all the work of generating the new summary buffer
+    ;; according to the new limit.
+    (gnus-summary-prepare)
+    ;; Hide any threads, possibly.
+    (and gnus-show-threads
+	 gnus-thread-hide-subtree
+	 (gnus-summary-hide-all-threads))
+    ;; Try to return to the article you were at, or one in the
+    ;; neighborhood.
+    (when data
+      ;; We try to find some article after the current one.
+      (while data
+	(when (gnus-summary-goto-subject (gnus-data-number (car data)) nil t)
+	  (setq data nil
+		found t))
+	(setq data (cdr data))))
+    (unless found
+      ;; If there is no data, that means that we were after the last
+      ;; article.  The same goes when we can't find any articles
+      ;; after the current one.
+      (goto-char (point-max))
+      (gnus-summary-find-prev))
+    ;; We return how many articles were removed from the summary
+    ;; buffer as a result of the new limit.
+    (- total (length gnus-newsgroup-data))))
+
+(defsubst gnus-invisible-cut-children (threads)
+  (let ((num 0))
+    (while threads
+      (when (memq (mail-header-number (caar threads)) gnus-newsgroup-limit)
+	(incf num))
+      (pop threads))
+    (< num 2)))
+
+(defsubst gnus-cut-thread (thread)
+  "Go forwards in the thread until we find an article that we want to display."
+  (when (or (eq gnus-fetch-old-headers 'some)
+	    (eq gnus-build-sparse-threads 'some)
+	    (eq gnus-build-sparse-threads 'more))
+    ;; Deal with old-fetched headers and sparse threads.
+    (while (and
+	    thread
+	    (or
+	     (gnus-summary-article-sparse-p (mail-header-number (car thread)))
+	     (gnus-summary-article-ancient-p
+	      (mail-header-number (car thread))))
+	    (progn
+	      (if (<= (length (cdr thread)) 1)
+		  (setq thread (cadr thread))
+		(when (gnus-invisible-cut-children (cdr thread))
+		  (let ((th (cdr thread)))
+		    (while th
+		      (if (memq (mail-header-number (caar th))
+				gnus-newsgroup-limit)
+			  (setq thread (car th)
+				th nil)
+			(setq th (cdr th)))))))))
+      ))
+  thread)
+
+(defun gnus-cut-threads (threads)
+  "Cut off all uninteresting articles from the beginning of threads."
+  (when (or (eq gnus-fetch-old-headers 'some)
+	    (eq gnus-build-sparse-threads 'some)
+	    (eq gnus-build-sparse-threads 'more))
+    (let ((th threads))
+      (while th
+	(setcar th (gnus-cut-thread (car th)))
+	(setq th (cdr th)))))
+  ;; Remove nixed out threads.
+  (delq nil threads))
+
+(defun gnus-summary-initial-limit (&optional show-if-empty)
+  "Figure out what the initial limit is supposed to be on group entry.
+This entails weeding out unwanted dormants, low-scored articles,
+fetch-old-headers verbiage, and so on."
+  ;; Most groups have nothing to remove.
+  (if (or gnus-inhibit-limiting
+	  (and (null gnus-newsgroup-dormant)
+	       (not (eq gnus-fetch-old-headers 'some))
+	       (null gnus-summary-expunge-below)
+	       (not (eq gnus-build-sparse-threads 'some))
+	       (not (eq gnus-build-sparse-threads 'more))
+	       (null gnus-thread-expunge-below)
+	       (not gnus-use-nocem)))
+      ()				; Do nothing.
+    (push gnus-newsgroup-limit gnus-newsgroup-limits)
+    (setq gnus-newsgroup-limit nil)
+    (mapatoms
+     (lambda (node)
+       (unless (car (symbol-value node))
+	 ;; These threads have no parents -- they are roots.
+	 (let ((nodes (cdr (symbol-value node)))
+	       thread)
+	   (while nodes
+	     (if (and gnus-thread-expunge-below
+		      (< (gnus-thread-total-score (car nodes))
+			 gnus-thread-expunge-below))
+		 (gnus-expunge-thread (pop nodes))
+	       (setq thread (pop nodes))
+	       (gnus-summary-limit-children thread))))))
+     gnus-newsgroup-dependencies)
+    ;; If this limitation resulted in an empty group, we might
+    ;; pop the previous limit and use it instead.
+    (when (and (not gnus-newsgroup-limit)
+	       show-if-empty)
+      (setq gnus-newsgroup-limit (pop gnus-newsgroup-limits)))
+    gnus-newsgroup-limit))
+
+(defun gnus-summary-limit-children (thread)
+  "Return 1 if this subthread is visible and 0 if it is not."
+  ;; First we get the number of visible children to this thread.  This
+  ;; is done by recursing down the thread using this function, so this
+  ;; will really go down to a leaf article first, before slowly
+  ;; working its way up towards the root.
+  (when thread
+    (let ((children
+	   (if (cdr thread)
+	       (apply '+ (mapcar 'gnus-summary-limit-children
+				 (cdr thread)))
+	     0))
+	  (number (mail-header-number (car thread)))
+	  score)
+      (if (and
+	   (not (memq number gnus-newsgroup-marked))
+	   (or
+	    ;; If this article is dormant and has absolutely no visible
+	    ;; children, then this article isn't visible.
+	    (and (memq number gnus-newsgroup-dormant)
+		 (zerop children))
+	    ;; If this is "fetch-old-headered" and there is no
+	    ;; visible children, then we don't want this article.
+	    (and (eq gnus-fetch-old-headers 'some)
+		 (gnus-summary-article-ancient-p number)
+		 (zerop children))
+	    ;; If this is a sparsely inserted article with no children,
+	    ;; we don't want it.
+	    (and (eq gnus-build-sparse-threads 'some)
+		 (gnus-summary-article-sparse-p number)
+		 (zerop children))
+	    ;; If we use expunging, and this article is really
+	    ;; low-scored, then we don't want this article.
+	    (when (and gnus-summary-expunge-below
+		       (< (setq score
+				(or (cdr (assq number gnus-newsgroup-scored))
+				    gnus-summary-default-score))
+			  gnus-summary-expunge-below))
+	      ;; We increase the expunge-tally here, but that has
+	      ;; nothing to do with the limits, really.
+	      (incf gnus-newsgroup-expunged-tally)
+	      ;; We also mark as read here, if that's wanted.
+	      (when (and gnus-summary-mark-below
+			 (< score gnus-summary-mark-below))
+		(setq gnus-newsgroup-unreads
+		      (delq number gnus-newsgroup-unreads))
+		(if gnus-newsgroup-auto-expire
+		    (push number gnus-newsgroup-expirable)
+		  (push (cons number gnus-low-score-mark)
+			gnus-newsgroup-reads)))
+	      t)
+	    ;; Check NoCeM things.
+	    (if (and gnus-use-nocem
+		     (gnus-nocem-unwanted-article-p
+		      (mail-header-id (car thread))))
+		(progn
+		  (setq gnus-newsgroup-reads
+			(delq number gnus-newsgroup-unreads))
+		  t))))
+	  ;; Nope, invisible article.
+	  0
+	;; Ok, this article is to be visible, so we add it to the limit
+	;; and return 1.
+	(push number gnus-newsgroup-limit)
+	1))))
+
+(defun gnus-expunge-thread (thread)
+  "Mark all articles in THREAD as read."
+  (let* ((number (mail-header-number (car thread))))
+    (incf gnus-newsgroup-expunged-tally)
+    ;; We also mark as read here, if that's wanted.
+    (setq gnus-newsgroup-unreads
+	  (delq number gnus-newsgroup-unreads))
+    (if gnus-newsgroup-auto-expire
+	(push number gnus-newsgroup-expirable)
+      (push (cons number gnus-low-score-mark)
+	    gnus-newsgroup-reads)))
+  ;; Go recursively through all subthreads.
+  (mapcar 'gnus-expunge-thread (cdr thread)))
+
+;; Summary article oriented commands
+
+(defun gnus-summary-refer-parent-article (n)
+  "Refer parent article N times.
+If N is negative, go to ancestor -N instead.
+The difference between N and the number of articles fetched is returned."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (let ((skip 1)
+	error header ref)
+    (when (not (natnump n))
+      (setq skip (abs n)
+	    n 1))
+    (while (and (> n 0)
+		(not error))
+      (setq header (gnus-summary-article-header))
+      (if (and (eq (mail-header-number header)
+		   (cdr gnus-article-current))
+	       (equal gnus-newsgroup-name
+		      (car gnus-article-current)))
+	  ;; If we try to find the parent of the currently
+	  ;; displayed article, then we take a look at the actual
+	  ;; References header, since this is slightly more
+	  ;; reliable than the References field we got from the
+	  ;; server.
+	  (save-excursion
+	    (set-buffer gnus-original-article-buffer)
+	    (nnheader-narrow-to-headers)
+	    (unless (setq ref (message-fetch-field "references"))
+	      (setq ref (message-fetch-field "in-reply-to")))
+	    (widen))
+	(setq ref
+	      ;; It's not the current article, so we take a bet on
+	      ;; the value we got from the server.
+	      (mail-header-references header)))
+      (if (and ref
+	       (not (equal ref "")))
+	  (unless (gnus-summary-refer-article (gnus-parent-id ref skip))
+	    (gnus-message 1 "Couldn't find parent"))
+	(gnus-message 1 "No references in article %d"
+		      (gnus-summary-article-number))
+	(setq error t))
+      (decf n))
+    (gnus-summary-position-point)
+    n))
+
+(defun gnus-summary-refer-references ()
+  "Fetch all articles mentioned in the References header.
+Return how many articles were fetched."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((ref (mail-header-references (gnus-summary-article-header)))
+	(current (gnus-summary-article-number))
+	(n 0))
+    (if (or (not ref)
+	    (equal ref ""))
+	(error "No References in the current article")
+      ;; For each Message-ID in the References header...
+      (while (string-match "<[^>]*>" ref)
+	(incf n)
+	;; ... fetch that article.
+	(gnus-summary-refer-article
+	 (prog1 (match-string 0 ref)
+	   (setq ref (substring ref (match-end 0))))))
+      (gnus-summary-goto-subject current)
+      (gnus-summary-position-point)
+      n)))
+
+(defun gnus-summary-refer-article (message-id &optional arg)
+  "Fetch an article specified by MESSAGE-ID.
+If ARG (the prefix), fetch the article using `gnus-refer-article-method'
+or `gnus-select-method', no matter what backend the article comes from."
+  (interactive "sMessage-ID: \nP")
+  (when (and (stringp message-id)
+	     (not (zerop (length message-id))))
+    ;; Construct the correct Message-ID if necessary.
+    ;; Suggested by tale@pawl.rpi.edu.
+    (unless (string-match "^<" message-id)
+      (setq message-id (concat "<" message-id)))
+    (unless (string-match ">$" message-id)
+      (setq message-id (concat message-id ">")))
+    (let* ((header (gnus-id-to-header message-id))
+	   (sparse (and header
+			(gnus-summary-article-sparse-p
+			 (mail-header-number header)))))
+      (if header
+	  (prog1
+	      ;; The article is present in the buffer, to we just go to it.
+	      (gnus-summary-goto-article
+	       (mail-header-number header) nil header)
+	    (when sparse
+	      (gnus-summary-update-article (mail-header-number header))))
+	;; We fetch the article
+	(let ((gnus-override-method
+	       (cond ((gnus-news-group-p gnus-newsgroup-name)
+		      gnus-refer-article-method)
+		     (arg
+		      (or gnus-refer-article-method gnus-select-method))
+		     (t nil)))
+	      number)
+	  ;; Start the special refer-article method, if necessary.
+	  (when (and gnus-refer-article-method
+		     (gnus-news-group-p gnus-newsgroup-name))
+	    (gnus-check-server gnus-refer-article-method))
+	  ;; Fetch the header, and display the article.
+	  (if (setq number (gnus-summary-insert-subject message-id))
+	      (gnus-summary-select-article nil nil nil number)
+	    (gnus-message 3 "Couldn't fetch article %s" message-id)))))))
+
+(defun gnus-summary-enter-digest-group (&optional force)
+  "Enter an nndoc group based on the current article.
+If FORCE, force a digest interpretation.  If not, try
+to guess what the document format is."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((conf gnus-current-window-configuration))
+    (save-excursion
+      (gnus-summary-select-article))
+    (setq gnus-current-window-configuration conf)
+    (let* ((name (format "%s-%d"
+			 (gnus-group-prefixed-name
+			  gnus-newsgroup-name (list 'nndoc ""))
+			 (save-excursion
+			   (set-buffer gnus-summary-buffer)
+			   gnus-current-article)))
+	   (ogroup gnus-newsgroup-name)
+	   (params (append (gnus-info-params (gnus-get-info ogroup))
+			   (list (cons 'to-group ogroup))
+			   (list (cons 'save-article-group ogroup))))
+	   (case-fold-search t)
+	   (buf (current-buffer))
+	   dig)
+      (save-excursion
+	(setq dig (nnheader-set-temp-buffer " *gnus digest buffer*"))
+	(insert-buffer-substring gnus-original-article-buffer)
+	;; Remove lines that may lead nndoc to misinterpret the
+	;; document type.
+	(narrow-to-region
+	 (goto-char (point-min))
+	 (or (search-forward "\n\n" nil t) (point)))
+	(goto-char (point-min))
+	(delete-matching-lines "^\\(Path\\):\\|^From ")
+	(widen))
+      (unwind-protect
+          (if (gnus-group-read-ephemeral-group
+               name `(nndoc ,name (nndoc-address ,(get-buffer dig))
+                            (nndoc-article-type
+                             ,(if force 'digest 'guess))) t)
+              ;; Make all postings to this group go to the parent group.
+              (nconc (gnus-info-params (gnus-get-info name))
+                     params)
+            ;; Couldn't select this doc group.
+            (switch-to-buffer buf)
+            (gnus-set-global-variables)
+            (gnus-configure-windows 'summary)
+            (gnus-message 3 "Article couldn't be entered?"))
+	(kill-buffer dig)))))
+
+(defun gnus-summary-read-document (n)
+  "Open a new group based on the current article(s).
+This will allow you to read digests and other similar
+documents as newsgroups.
+Obeys the standard process/prefix convention."
+  (interactive "P")
+  (let* ((articles (gnus-summary-work-articles n))
+	 (ogroup gnus-newsgroup-name)
+	 (params (append (gnus-info-params (gnus-get-info ogroup))
+			 (list (cons 'to-group ogroup))))
+	 article group egroup groups vgroup)
+    (while (setq article (pop articles))
+      (setq group (format "%s-%d" gnus-newsgroup-name article))
+      (gnus-summary-remove-process-mark article)
+      (when (gnus-summary-display-article article)
+	(save-excursion
+	  (nnheader-temp-write nil
+	    (insert-buffer-substring gnus-original-article-buffer)
+	    ;; Remove some headers that may lead nndoc to make
+	    ;; the wrong guess.
+	    (message-narrow-to-head)
+	    (goto-char (point-min))
+	    (delete-matching-lines "^\\(Path\\):\\|^From ")
+	    (widen)
+	    (if (setq egroup
+		      (gnus-group-read-ephemeral-group
+		       group `(nndoc ,group (nndoc-address ,(current-buffer))
+				     (nndoc-article-type guess))
+		       t nil t))
+		(progn
+		  ;; Make all postings to this group go to the parent group.
+		  (nconc (gnus-info-params (gnus-get-info egroup))
+			 params)
+		  (push egroup groups))
+	      ;; Couldn't select this doc group.
+	      (gnus-error 3 "Article couldn't be entered"))))))
+    ;; Now we have selected all the documents.
+    (cond
+     ((not groups)
+      (error "None of the articles could be interpreted as documents"))
+     ((gnus-group-read-ephemeral-group
+       (setq vgroup (format
+		     "nnvirtual:%s-%s" gnus-newsgroup-name
+		     (format-time-string "%Y%m%dT%H%M%S" (current-time))))
+       `(nnvirtual ,vgroup (nnvirtual-component-groups ,groups))
+       t
+       (cons (current-buffer) 'summary)))
+     (t
+      (error "Couldn't select virtual nndoc group")))))
+
+(defun gnus-summary-isearch-article (&optional regexp-p)
+  "Do incremental search forward on the current article.
+If REGEXP-P (the prefix) is non-nil, do regexp isearch."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (gnus-summary-select-article)
+  (gnus-configure-windows 'article)
+  (gnus-eval-in-buffer-window gnus-article-buffer
+    ;;(goto-char (point-min))
+    (isearch-forward regexp-p)))
+
+(defun gnus-summary-search-article-forward (regexp &optional backward)
+  "Search for an article containing REGEXP forward.
+If BACKWARD, search backward instead."
+  (interactive
+   (list (read-string
+	  (format "Search article %s (regexp%s): "
+		  (if current-prefix-arg "backward" "forward")
+		  (if gnus-last-search-regexp
+		      (concat ", default " gnus-last-search-regexp)
+		    "")))
+	 current-prefix-arg))
+  (gnus-set-global-variables)
+  (if (string-equal regexp "")
+      (setq regexp (or gnus-last-search-regexp ""))
+    (setq gnus-last-search-regexp regexp))
+  (if (gnus-summary-search-article regexp backward)
+      (gnus-summary-show-thread)
+    (error "Search failed: \"%s\"" regexp)))
+
+(defun gnus-summary-search-article-backward (regexp)
+  "Search for an article containing REGEXP backward."
+  (interactive
+   (list (read-string
+	  (format "Search article backward (regexp%s): "
+		  (if gnus-last-search-regexp
+		      (concat ", default " gnus-last-search-regexp)
+		    "")))))
+  (gnus-summary-search-article-forward regexp 'backward))
+
+(defun gnus-summary-search-article (regexp &optional backward)
+  "Search for an article containing REGEXP.
+Optional argument BACKWARD means do search for backward.
+`gnus-select-article-hook' is not called during the search."
+  (let ((gnus-select-article-hook nil)	;Disable hook.
+	(gnus-article-display-hook nil)
+	(gnus-mark-article-hook nil)	;Inhibit marking as read.
+	(gnus-use-article-prefetch nil)
+	(gnus-xmas-force-redisplay nil)	;Inhibit XEmacs redisplay.
+	(sum (current-buffer))
+	(found nil)
+	point)
+    (gnus-save-hidden-threads
+      (gnus-summary-select-article)
+      (set-buffer gnus-article-buffer)
+      (when backward
+	(forward-line -1))
+      (while (not found)
+	(gnus-message 7 "Searching article: %d..." (cdr gnus-article-current))
+	(if (if backward
+		(re-search-backward regexp nil t)
+	      (re-search-forward regexp nil t))
+	    ;; We found the regexp.
+	    (progn
+	      (setq found 'found)
+	      (beginning-of-line)
+	      (set-window-start
+	       (get-buffer-window (current-buffer))
+	       (point))
+	      (forward-line 1)
+	      (set-buffer sum)
+	      (setq point (point)))
+	  ;; We didn't find it, so we go to the next article.
+	  (set-buffer sum)
+	  (setq found 'not)
+	  (while (eq found 'not)
+	    (if (not (if backward (gnus-summary-find-prev)
+		       (gnus-summary-find-next)))
+		;; No more articles.
+		(setq found t)
+	      ;; Select the next article and adjust point.
+	      (unless (gnus-summary-article-sparse-p
+		       (gnus-summary-article-number))
+		(setq found nil)
+		(gnus-summary-select-article)
+		(set-buffer gnus-article-buffer)
+		(widen)
+		(goto-char (if backward (point-max) (point-min))))))))
+      (gnus-message 7 ""))
+    ;; Return whether we found the regexp.
+    (when (eq found 'found)
+      (goto-char point)
+      (gnus-summary-show-thread)
+      (gnus-summary-goto-subject gnus-current-article)
+      (gnus-summary-position-point)
+      t)))
+
+(defun gnus-summary-find-matching (header regexp &optional backward unread
+					  not-case-fold)
+  "Return a list of all articles that match REGEXP on HEADER.
+The search stars on the current article and goes forwards unless
+BACKWARD is non-nil.  If BACKWARD is `all', do all articles.
+If UNREAD is non-nil, only unread articles will
+be taken into consideration.  If NOT-CASE-FOLD, case won't be folded
+in the comparisons."
+  (let ((data (if (eq backward 'all) gnus-newsgroup-data
+		(gnus-data-find-list
+		 (gnus-summary-article-number) (gnus-data-list backward))))
+	(func `(lambda (h) (,(intern (concat "mail-header-" header)) h)))
+	(case-fold-search (not not-case-fold))
+	articles d)
+    (unless (fboundp (intern (concat "mail-header-" header)))
+      (error "%s is not a valid header" header))
+    (while data
+      (setq d (car data))
+      (and (or (not unread)		; We want all articles...
+	       (gnus-data-unread-p d))	; Or just unreads.
+	   (vectorp (gnus-data-header d)) ; It's not a pseudo.
+	   (string-match regexp (funcall func (gnus-data-header d))) ; Match.
+	   (push (gnus-data-number d) articles)) ; Success!
+      (setq data (cdr data)))
+    (nreverse articles)))
+
+(defun gnus-summary-execute-command (header regexp command &optional backward)
+  "Search forward for an article whose HEADER matches REGEXP and execute COMMAND.
+If HEADER is an empty string (or nil), the match is done on the entire
+article.  If BACKWARD (the prefix) is non-nil, search backward instead."
+  (interactive
+   (list (let ((completion-ignore-case t))
+	   (completing-read
+	    "Header name: "
+	    (mapcar (lambda (string) (list string))
+		    '("Number" "Subject" "From" "Lines" "Date"
+		      "Message-ID" "Xref" "References" "Body"))
+	    nil 'require-match))
+	 (read-string "Regexp: ")
+	 (read-key-sequence "Command: ")
+	 current-prefix-arg))
+  (when (equal header "Body")
+    (setq header ""))
+  (gnus-set-global-variables)
+  ;; Hidden thread subtrees must be searched as well.
+  (gnus-summary-show-all-threads)
+  ;; We don't want to change current point nor window configuration.
+  (save-excursion
+    (save-window-excursion
+      (gnus-message 6 "Executing %s..." (key-description command))
+      ;; We'd like to execute COMMAND interactively so as to give arguments.
+      (gnus-execute header regexp
+		    `(call-interactively ',(key-binding command))
+		    backward)
+      (gnus-message 6 "Executing %s...done" (key-description command)))))
+
+(defun gnus-summary-beginning-of-article ()
+  "Scroll the article back to the beginning."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-summary-select-article)
+  (gnus-configure-windows 'article)
+  (gnus-eval-in-buffer-window gnus-article-buffer
+    (widen)
+    (goto-char (point-min))
+    (when gnus-page-broken
+      (gnus-narrow-to-page))))
+
+(defun gnus-summary-end-of-article ()
+  "Scroll to the end of the article."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-summary-select-article)
+  (gnus-configure-windows 'article)
+  (gnus-eval-in-buffer-window gnus-article-buffer
+    (widen)
+    (goto-char (point-max))
+    (recenter -3)
+    (when gnus-page-broken
+      (gnus-narrow-to-page))))
+
+(defun gnus-summary-print-article (&optional filename)
+  "Generate and print a PostScript image of the article buffer.
+
+If the optional argument FILENAME is nil, send the image to the printer.
+If FILENAME is a string, save the PostScript image in a file with that
+name.  If FILENAME is a number, prompt the user for the name of the file
+to save in."
+  (interactive (list (ps-print-preprint current-prefix-arg)))
+  (gnus-summary-select-article)
+  (gnus-eval-in-buffer-window gnus-article-buffer
+    (let ((buffer (generate-new-buffer " *print*")))
+      (unwind-protect
+	  (progn
+	    (copy-to-buffer buffer (point-min) (point-max))
+	    (set-buffer buffer)
+	    (gnus-article-delete-invisible-text)
+	    (run-hooks 'gnus-ps-print-hook)
+	    (ps-print-buffer-with-faces filename))
+	(kill-buffer buffer)))))
+
+(defun gnus-summary-show-article (&optional arg)
+  "Force re-fetching of the current article.
+If ARG (the prefix) is non-nil, show the raw article without any
+article massaging functions being run."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (if (not arg)
+      ;; Select the article the normal way.
+      (gnus-summary-select-article nil 'force)
+    ;; Bind the article treatment functions to nil.
+    (let ((gnus-have-all-headers t)
+	  gnus-article-display-hook
+	  gnus-article-prepare-hook
+	  gnus-break-pages
+	  gnus-show-mime
+	  gnus-visual)
+      (gnus-summary-select-article nil 'force)))
+  (gnus-summary-goto-subject gnus-current-article)
+  (gnus-summary-position-point))
+
+(defun gnus-summary-verbose-headers (&optional arg)
+  "Toggle permanent full header display.
+If ARG is a positive number, turn header display on.
+If ARG is a negative number, turn header display off."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (setq gnus-show-all-headers
+	(cond ((or (not (numberp arg))
+		   (zerop arg))
+	       (not gnus-show-all-headers))
+	      ((natnump arg)
+	       t)))
+  (gnus-summary-show-article))
+
+(defun gnus-summary-toggle-header (&optional arg)
+  "Show the headers if they are hidden, or hide them if they are shown.
+If ARG is a positive number, show the entire header.
+If ARG is a negative number, hide the unwanted header lines."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (save-excursion
+    (set-buffer gnus-article-buffer)
+    (let* ((buffer-read-only nil)
+	   (inhibit-point-motion-hooks t)
+	   (hidden (text-property-any
+		    (goto-char (point-min)) (search-forward "\n\n")
+		    'invisible t))
+	   e)
+      (goto-char (point-min))
+      (when (search-forward "\n\n" nil t)
+	(delete-region (point-min) (1- (point))))
+      (goto-char (point-min))
+      (save-excursion
+	(set-buffer gnus-original-article-buffer)
+	(goto-char (point-min))
+	(setq e (1- (or (search-forward "\n\n" nil t) (point-max)))))
+      (insert-buffer-substring gnus-original-article-buffer 1 e)
+      (let ((article-inhibit-hiding t))
+	(run-hooks 'gnus-article-display-hook))
+      (when (or (not hidden) (and (numberp arg) (< arg 0)))
+	(gnus-article-hide-headers)))))
+
+(defun gnus-summary-show-all-headers ()
+  "Make all header lines visible."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-article-show-all-headers))
+
+(defun gnus-summary-toggle-mime (&optional arg)
+  "Toggle MIME processing.
+If ARG is a positive number, turn MIME processing on."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (setq gnus-show-mime
+	(if (null arg) (not gnus-show-mime)
+	  (> (prefix-numeric-value arg) 0)))
+  (gnus-summary-select-article t 'force))
+
+(defun gnus-summary-caesar-message (&optional arg)
+  "Caesar rotate the current article by 13.
+The numerical prefix specifies how many places to rotate each letter
+forward."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (gnus-summary-select-article)
+  (let ((mail-header-separator ""))
+    (gnus-eval-in-buffer-window gnus-article-buffer
+      (save-restriction
+	(widen)
+	(let ((start (window-start))
+	      buffer-read-only)
+	  (message-caesar-buffer-body arg)
+	  (set-window-start (get-buffer-window (current-buffer)) start))))))
+
+(defun gnus-summary-stop-page-breaking ()
+  "Stop page breaking in the current article."
+  (interactive)
+  (gnus-set-global-variables)
+  (gnus-summary-select-article)
+  (gnus-eval-in-buffer-window gnus-article-buffer
+    (widen)
+    (when (gnus-visual-p 'page-marker)
+      (let ((buffer-read-only nil))
+	(gnus-remove-text-with-property 'gnus-prev)
+	(gnus-remove-text-with-property 'gnus-next)))))
+
+(defun gnus-summary-move-article (&optional n to-newsgroup
+					    select-method action)
+  "Move the current article to a different newsgroup.
+If N is a positive number, move the N next articles.
+If N is a negative number, move the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+move those articles instead.
+If TO-NEWSGROUP is string, do not prompt for a newsgroup to move to.
+If SELECT-METHOD is non-nil, do not move to a specific newsgroup, but
+re-spool using this method.
+
+For this function to work, both the current newsgroup and the
+newsgroup that you want to move to have to support the `request-move'
+and `request-accept' functions."
+  (interactive "P")
+  (unless action
+    (setq action 'move))
+  (gnus-set-global-variables)
+  ;; Disable marking as read.
+  (let (gnus-mark-article-hook)
+    (save-window-excursion
+      (gnus-summary-select-article)))
+  ;; Check whether the source group supports the required functions.
+  (cond ((and (eq action 'move)
+	      (not (gnus-check-backend-function
+		    'request-move-article gnus-newsgroup-name)))
+	 (error "The current group does not support article moving"))
+	((and (eq action 'crosspost)
+	      (not (gnus-check-backend-function
+		    'request-replace-article gnus-newsgroup-name)))
+	 (error "The current group does not support article editing")))
+  (let ((articles (gnus-summary-work-articles n))
+	(prefix (gnus-group-real-prefix gnus-newsgroup-name))
+	(names '((move "Move" "Moving")
+		 (copy "Copy" "Copying")
+		 (crosspost "Crosspost" "Crossposting")))
+	(copy-buf (save-excursion
+		    (nnheader-set-temp-buffer " *copy article*")))
+	art-group to-method new-xref article to-groups)
+    (unless (assq action names)
+      (error "Unknown action %s" action))
+    ;; Read the newsgroup name.
+    (when (and (not to-newsgroup)
+	       (not select-method))
+      (setq to-newsgroup
+	    (gnus-read-move-group-name
+	     (cadr (assq action names))
+	     (symbol-value (intern (format "gnus-current-%s-group" action)))
+	     articles prefix))
+      (set (intern (format "gnus-current-%s-group" action)) to-newsgroup))
+    (setq to-method (or select-method
+			(gnus-group-name-to-method to-newsgroup)))
+    ;; Check the method we are to move this article to...
+    (unless (gnus-check-backend-function
+	     'request-accept-article (car to-method))
+      (error "%s does not support article copying" (car to-method)))
+    (unless (gnus-check-server to-method)
+      (error "Can't open server %s" (car to-method)))
+    (gnus-message 6 "%s to %s: %s..."
+		  (caddr (assq action names))
+		  (or (car select-method) to-newsgroup) articles)
+    (while articles
+      (setq article (pop articles))
+      (setq
+       art-group
+       (cond
+	;; Move the article.
+	((eq action 'move)
+	 (gnus-request-move-article
+	  article			; Article to move
+	  gnus-newsgroup-name		; From newsgroup
+	  (nth 1 (gnus-find-method-for-group
+		  gnus-newsgroup-name)) ; Server
+	  (list 'gnus-request-accept-article
+		to-newsgroup (list 'quote select-method)
+		(not articles))		; Accept form
+	  (not articles)))		; Only save nov last time
+	;; Copy the article.
+	((eq action 'copy)
+	 (save-excursion
+	   (set-buffer copy-buf)
+	   (gnus-request-article-this-buffer article gnus-newsgroup-name)
+	   (gnus-request-accept-article
+	    to-newsgroup select-method (not articles))))
+	;; Crosspost the article.
+	((eq action 'crosspost)
+	 (let ((xref (message-tokenize-header
+		      (mail-header-xref (gnus-summary-article-header article))
+		      " ")))
+	   (setq new-xref (concat (gnus-group-real-name gnus-newsgroup-name)
+				  ":" article))
+	   (unless xref
+	     (setq xref (list (system-name))))
+	   (setq new-xref
+		 (concat
+		  (mapconcat 'identity
+			     (delete "Xref:" (delete new-xref xref))
+			     " ")
+		  " " new-xref))
+	   (save-excursion
+	     (set-buffer copy-buf)
+	     ;; First put the article in the destination group.
+	     (gnus-request-article-this-buffer article gnus-newsgroup-name)
+	     (when (consp (setq art-group
+				(gnus-request-accept-article
+				 to-newsgroup select-method (not articles))))
+	       (setq new-xref (concat new-xref " " (car art-group)
+				      ":" (cdr art-group)))
+	       ;; Now we have the new Xrefs header, so we insert
+	       ;; it and replace the new article.
+	       (nnheader-replace-header "Xref" new-xref)
+	       (gnus-request-replace-article
+		(cdr art-group) to-newsgroup (current-buffer))
+	       art-group))))))
+      (cond
+       ((not art-group)
+	(gnus-message 1 "Couldn't %s article %s"
+		      (cadr (assq action names)) article))
+       ((and (eq art-group 'junk)
+	     (eq action 'move))
+	(gnus-summary-mark-article article gnus-canceled-mark)
+	(gnus-message 4 "Deleted article %s" article))
+       (t
+	(let* ((entry
+		(or
+		 (gnus-gethash (car art-group) gnus-newsrc-hashtb)
+		 (gnus-gethash
+		  (gnus-group-prefixed-name
+		   (car art-group)
+		   (or select-method
+		       (gnus-find-method-for-group to-newsgroup)))
+		  gnus-newsrc-hashtb)))
+	       (info (nth 2 entry))
+	       (to-group (gnus-info-group info)))
+	  ;; Update the group that has been moved to.
+	  (when (and info
+		     (memq action '(move copy)))
+	    (unless (member to-group to-groups)
+	      (push to-group to-groups))
+
+	    (unless (memq article gnus-newsgroup-unreads)
+	      (gnus-info-set-read
+	       info (gnus-add-to-range (gnus-info-read info)
+				       (list (cdr art-group)))))
+
+	    ;; Copy any marks over to the new group.
+	    (let ((marks gnus-article-mark-lists)
+		  (to-article (cdr art-group)))
+
+	      ;; See whether the article is to be put in the cache.
+	      (when gnus-use-cache
+		(gnus-cache-possibly-enter-article
+		 to-group to-article
+		 (let ((header (copy-sequence
+				(gnus-summary-article-header article))))
+		   (mail-header-set-number header to-article)
+		   header)
+		 (memq article gnus-newsgroup-marked)
+		 (memq article gnus-newsgroup-dormant)
+		 (memq article gnus-newsgroup-unreads)))
+
+	      (when (and (equal to-group gnus-newsgroup-name)
+			 (not (memq article gnus-newsgroup-unreads)))
+		;; Mark this article as read in this group.
+		(push (cons to-article gnus-read-mark) gnus-newsgroup-reads)
+		(setcdr (gnus-active to-group) to-article)
+		(setcdr gnus-newsgroup-active to-article))
+
+	      (while marks
+		(when (memq article (symbol-value
+				     (intern (format "gnus-newsgroup-%s"
+						     (caar marks)))))
+		  ;; If the other group is the same as this group,
+		  ;; then we have to add the mark to the list.
+		  (when (equal to-group gnus-newsgroup-name)
+		    (set (intern (format "gnus-newsgroup-%s" (caar marks)))
+			 (cons to-article
+			       (symbol-value
+				(intern (format "gnus-newsgroup-%s"
+						(caar marks)))))))
+		  ;; Copy the marks to other group.
+		  (gnus-add-marked-articles
+		   to-group (cdar marks) (list to-article) info))
+		(setq marks (cdr marks)))
+
+	      (gnus-dribble-enter
+	       (concat "(gnus-group-set-info '"
+		       (gnus-prin1-to-string (gnus-get-info to-group))
+		       ")"))))
+
+	  ;; Update the Xref header in this article to point to
+	  ;; the new crossposted article we have just created.
+	  (when (eq action 'crosspost)
+	    (save-excursion
+	      (set-buffer copy-buf)
+	      (gnus-request-article-this-buffer article gnus-newsgroup-name)
+	      (nnheader-replace-header "Xref" new-xref)
+	      (gnus-request-replace-article
+	       article gnus-newsgroup-name (current-buffer)))))
+
+	(gnus-summary-goto-subject article)
+	(when (eq action 'move)
+	  (gnus-summary-mark-article article gnus-canceled-mark))))
+      (gnus-summary-remove-process-mark article))
+    ;; Re-activate all groups that have been moved to.
+    (while to-groups
+      (save-excursion
+	(set-buffer gnus-group-buffer)
+	(when (gnus-group-goto-group (car to-groups) t)
+	  (gnus-group-get-new-news-this-group 1))
+	(pop to-groups)))
+
+    (gnus-kill-buffer copy-buf)
+    (gnus-summary-position-point)
+    (gnus-set-mode-line 'summary)))
+
+(defun gnus-summary-copy-article (&optional n to-newsgroup select-method)
+  "Move the current article to a different newsgroup.
+If TO-NEWSGROUP is string, do not prompt for a newsgroup to move to.
+If SELECT-METHOD is non-nil, do not move to a specific newsgroup, but
+re-spool using this method."
+  (interactive "P")
+  (gnus-summary-move-article n to-newsgroup select-method 'copy))
+
+(defun gnus-summary-crosspost-article (&optional n)
+  "Crosspost the current article to some other group."
+  (interactive "P")
+  (gnus-summary-move-article n nil nil 'crosspost))
+
+(defcustom gnus-summary-respool-default-method nil
+  "Default method for respooling an article.
+If nil, use to the current newsgroup method."
+  :type 'gnus-select-method-name
+  :group 'gnus-summary-mail)
+
+(defun gnus-summary-respool-article (&optional n method)
+  "Respool the current article.
+The article will be squeezed through the mail spooling process again,
+which means that it will be put in some mail newsgroup or other
+depending on `nnmail-split-methods'.
+If N is a positive number, respool the N next articles.
+If N is a negative number, respool the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+respool those articles instead.
+
+Respooling can be done both from mail groups and \"real\" newsgroups.
+In the former case, the articles in question will be moved from the
+current group into whatever groups they are destined to.  In the
+latter case, they will be copied into the relevant groups."
+  (interactive
+   (list current-prefix-arg
+	 (let* ((methods (gnus-methods-using 'respool))
+		(methname
+		 (symbol-name (or gnus-summary-respool-default-method
+				  (car (gnus-find-method-for-group
+					gnus-newsgroup-name)))))
+		(method
+		 (gnus-completing-read
+		  methname "What backend do you want to use when respooling?"
+		  methods nil t nil 'gnus-mail-method-history))
+		ms)
+	   (cond
+	    ((zerop (length (setq ms (gnus-servers-using-backend
+				      (intern method)))))
+	     (list (intern method) ""))
+	    ((= 1 (length ms))
+	     (car ms))
+	    (t
+	     (let ((ms-alist (mapcar (lambda (m) (cons (cadr m) m)) ms)))
+	       (cdr (assoc (completing-read "Server name: " ms-alist nil t)
+			   ms-alist))))))))
+  (gnus-set-global-variables)
+  (unless method
+    (error "No method given for respooling"))
+  (if (assoc (symbol-name
+	      (car (gnus-find-method-for-group gnus-newsgroup-name)))
+	     (gnus-methods-using 'respool))
+      (gnus-summary-move-article n nil method)
+    (gnus-summary-copy-article n nil method)))
+
+(defun gnus-summary-import-article (file)
+  "Import a random file into a mail newsgroup."
+  (interactive "fImport file: ")
+  (gnus-set-global-variables)
+  (let ((group gnus-newsgroup-name)
+	(now (current-time))
+	atts lines)
+    (unless (gnus-check-backend-function 'request-accept-article group)
+      (error "%s does not support article importing" group))
+    (or (file-readable-p file)
+	(not (file-regular-p file))
+	(error "Can't read %s" file))
+    (save-excursion
+      (set-buffer (get-buffer-create " *import file*"))
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (insert-file-contents file)
+      (goto-char (point-min))
+      (unless (nnheader-article-p)
+	;; This doesn't look like an article, so we fudge some headers.
+	(setq atts (file-attributes file)
+	      lines (count-lines (point-min) (point-max)))
+	(insert "From: " (read-string "From: ") "\n"
+		"Subject: " (read-string "Subject: ") "\n"
+		"Date: " (timezone-make-date-arpa-standard
+			  (current-time-string (nth 5 atts))
+			  (current-time-zone now)
+			  (current-time-zone now))
+		"\n"
+		"Message-ID: " (message-make-message-id) "\n"
+		"Lines: " (int-to-string lines) "\n"
+		"Chars: " (int-to-string (nth 7 atts)) "\n\n"))
+      (gnus-request-accept-article group nil t)
+      (kill-buffer (current-buffer)))))
+
+(defun gnus-summary-article-posted-p ()
+  "Say whether the current (mail) article is available from `gnus-select-method' as well.
+This will be the case if the article has both been mailed and posted."
+  (interactive)
+  (let ((id (mail-header-references (gnus-summary-article-header)))
+	(gnus-override-method
+	 (or gnus-refer-article-method gnus-select-method)))
+    (if (gnus-request-head id "")
+	(gnus-message 2 "The current message was found on %s"
+		      gnus-override-method)
+      (gnus-message 2 "The current message couldn't be found on %s"
+		    gnus-override-method)
+      nil)))
+
+(defun gnus-summary-expire-articles (&optional now)
+  "Expire all articles that are marked as expirable in the current group."
+  (interactive)
+  (gnus-set-global-variables)
+  (when (gnus-check-backend-function
+	 'request-expire-articles gnus-newsgroup-name)
+    ;; This backend supports expiry.
+    (let* ((total (gnus-group-total-expirable-p gnus-newsgroup-name))
+	   (expirable (if total
+			  (progn
+			    ;; We need to update the info for
+			    ;; this group for `gnus-list-of-read-articles'
+			    ;; to give us the right answer.
+			    (run-hooks 'gnus-exit-group-hook)
+			    (gnus-summary-update-info)
+			    (gnus-list-of-read-articles gnus-newsgroup-name))
+			(setq gnus-newsgroup-expirable
+			      (sort gnus-newsgroup-expirable '<))))
+	   (expiry-wait (if now 'immediate
+			  (gnus-group-find-parameter
+			   gnus-newsgroup-name 'expiry-wait)))
+	   es)
+      (when expirable
+	;; There are expirable articles in this group, so we run them
+	;; through the expiry process.
+	(gnus-message 6 "Expiring articles...")
+	;; The list of articles that weren't expired is returned.
+	(if expiry-wait
+	    (let ((nnmail-expiry-wait-function nil)
+		  (nnmail-expiry-wait expiry-wait))
+	      (setq es (gnus-request-expire-articles
+			expirable gnus-newsgroup-name)))
+	  (setq es (gnus-request-expire-articles
+		    expirable gnus-newsgroup-name)))
+	(unless total
+	  (setq gnus-newsgroup-expirable es))
+	;; We go through the old list of expirable, and mark all
+	;; really expired articles as nonexistent.
+	(unless (eq es expirable)	;If nothing was expired, we don't mark.
+	  (let ((gnus-use-cache nil))
+	    (while expirable
+	      (unless (memq (car expirable) es)
+		(when (gnus-data-find (car expirable))
+		  (gnus-summary-mark-article
+		   (car expirable) gnus-canceled-mark)))
+	      (setq expirable (cdr expirable)))))
+	(gnus-message 6 "Expiring articles...done")))))
+
+(defun gnus-summary-expire-articles-now ()
+  "Expunge all expirable articles in the current group.
+This means that *all* articles that are marked as expirable will be
+deleted forever, right now."
+  (interactive)
+  (gnus-set-global-variables)
+  (or gnus-expert-user
+      (gnus-yes-or-no-p
+       "Are you really, really, really sure you want to delete all these messages? ")
+      (error "Phew!"))
+  (gnus-summary-expire-articles t))
+
+;; Suggested by Jack Vinson <vinson@unagi.cis.upenn.edu>.
+(defun gnus-summary-delete-article (&optional n)
+  "Delete the N next (mail) articles.
+This command actually deletes articles.	 This is not a marking
+command.  The article will disappear forever from your life, never to
+return.
+If N is negative, delete backwards.
+If N is nil and articles have been marked with the process mark,
+delete these instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (unless (gnus-check-backend-function 'request-expire-articles
+				       gnus-newsgroup-name)
+    (error "The current newsgroup does not support article deletion."))
+  ;; Compute the list of articles to delete.
+  (let ((articles (gnus-summary-work-articles n))
+	not-deleted)
+    (if (and gnus-novice-user
+	     (not (gnus-yes-or-no-p
+		   (format "Do you really want to delete %s forever? "
+			   (if (> (length articles) 1)
+			       (format "these %s articles" (length articles))
+			     "this article")))))
+	()
+      ;; Delete the articles.
+      (setq not-deleted (gnus-request-expire-articles
+			 articles gnus-newsgroup-name 'force))
+      (while articles
+	(gnus-summary-remove-process-mark (car articles))
+	;; The backend might not have been able to delete the article
+	;; after all.
+	(unless (memq (car articles) not-deleted)
+	  (gnus-summary-mark-article (car articles) gnus-canceled-mark))
+	(setq articles (cdr articles)))
+      (when not-deleted
+	(gnus-message 4 "Couldn't delete articles %s" not-deleted)))
+    (gnus-summary-position-point)
+    (gnus-set-mode-line 'summary)
+    not-deleted))
+
+(defun gnus-summary-edit-article (&optional force)
+  "Edit the current article.
+This will have permanent effect only in mail groups.
+If FORCE is non-nil, allow editing of articles even in read-only
+groups."
+  (interactive "P")
+  (save-excursion
+    (set-buffer gnus-summary-buffer)
+    (gnus-set-global-variables)
+    (when (and (not force)
+	       (gnus-group-read-only-p))
+      (error "The current newsgroup does not support article editing."))
+    ;; Select article if needed.
+    (unless (eq (gnus-summary-article-number)
+		gnus-current-article)
+      (gnus-summary-select-article t))
+    (gnus-article-edit-article
+     `(lambda ()
+	(gnus-summary-edit-article-done
+	 ,(or (mail-header-references gnus-current-headers) "")
+	 ,(gnus-group-read-only-p) ,gnus-summary-buffer)))))
+
+(defalias 'gnus-summary-edit-article-postpone 'gnus-article-edit-exit)
+
+(defun gnus-summary-edit-article-done (&optional references read-only buffer)
+  "Make edits to the current article permanent."
+  (interactive)
+  ;; Replace the article.
+  (if (and (not read-only)
+	   (not (gnus-request-replace-article
+		 (cdr gnus-article-current) (car gnus-article-current)
+		 (current-buffer))))
+      (error "Couldn't replace article.")
+    ;; Update the summary buffer.
+    (if (and references
+	     (equal (message-tokenize-header references " ")
+		    (message-tokenize-header
+		     (or (message-fetch-field "references") "") " ")))
+	;; We only have to update this line.
+	(save-excursion
+	  (save-restriction
+	    (message-narrow-to-head)
+	    (let ((head (buffer-string))
+		  header)
+	      (nnheader-temp-write nil
+		(insert (format "211 %d Article retrieved.\n"
+				(cdr gnus-article-current)))
+		(insert head)
+		(insert ".\n")
+		(let ((nntp-server-buffer (current-buffer)))
+		  (setq header (car (gnus-get-newsgroup-headers
+				     (save-excursion
+				       (set-buffer gnus-summary-buffer)
+				       gnus-newsgroup-dependencies)
+				     t))))
+		(save-excursion
+		  (set-buffer gnus-summary-buffer)
+		  (gnus-data-set-header
+		   (gnus-data-find (cdr gnus-article-current))
+		   header)
+		  (gnus-summary-update-article-line
+		   (cdr gnus-article-current) header))))))
+      ;; Update threads.
+      (set-buffer (or buffer gnus-summary-buffer))
+      (gnus-summary-update-article (cdr gnus-article-current)))
+    ;; Prettify the article buffer again.
+    (save-excursion
+      (set-buffer gnus-article-buffer)
+      (run-hooks 'gnus-article-display-hook)
+      (set-buffer gnus-original-article-buffer)
+      (gnus-request-article
+       (cdr gnus-article-current) (car gnus-article-current) (current-buffer)))
+    ;; Prettify the summary buffer line.
+    (when (gnus-visual-p 'summary-highlight 'highlight)
+      (run-hooks 'gnus-visual-mark-article-hook))))
+
+(defun gnus-summary-edit-wash (key)
+  "Perform editing command in the article buffer."
+  (interactive
+   (list
+    (progn
+      (message "%s" (concat (this-command-keys) "- "))
+      (read-char))))
+  (message "")
+  (gnus-summary-edit-article)
+  (execute-kbd-macro (concat (this-command-keys) key))
+  (gnus-article-edit-done))
+
+;;; Respooling
+
+(defun gnus-summary-respool-query (&optional silent)
+  "Query where the respool algorithm would put this article."
+  (interactive)
+  (gnus-set-global-variables)
+  (let (gnus-mark-article-hook)
+    (gnus-summary-select-article)
+    (save-excursion
+      (set-buffer gnus-original-article-buffer)
+      (save-restriction
+	(message-narrow-to-head)
+	(let ((groups (nnmail-article-group 'identity)))
+	  (unless silent
+	    (if groups
+		(message "This message would go to %s"
+			 (mapconcat 'car groups ", "))
+	      (message "This message would go to no groups"))
+	    groups))))))
+
+;; Summary marking commands.
+
+(defun gnus-summary-kill-same-subject-and-select (&optional unmark)
+  "Mark articles which has the same subject as read, and then select the next.
+If UNMARK is positive, remove any kind of mark.
+If UNMARK is negative, tick articles."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (when unmark
+    (setq unmark (prefix-numeric-value unmark)))
+  (let ((count
+	 (gnus-summary-mark-same-subject
+	  (gnus-summary-article-subject) unmark)))
+    ;; Select next unread article.  If auto-select-same mode, should
+    ;; select the first unread article.
+    (gnus-summary-next-article t (and gnus-auto-select-same
+				      (gnus-summary-article-subject)))
+    (gnus-message 7 "%d article%s marked as %s"
+		  count (if (= count 1) " is" "s are")
+		  (if unmark "unread" "read"))))
+
+(defun gnus-summary-kill-same-subject (&optional unmark)
+  "Mark articles which has the same subject as read.
+If UNMARK is positive, remove any kind of mark.
+If UNMARK is negative, tick articles."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (when unmark
+    (setq unmark (prefix-numeric-value unmark)))
+  (let ((count
+	 (gnus-summary-mark-same-subject
+	  (gnus-summary-article-subject) unmark)))
+    ;; If marked as read, go to next unread subject.
+    (when (null unmark)
+      ;; Go to next unread subject.
+      (gnus-summary-next-subject 1 t))
+    (gnus-message 7 "%d articles are marked as %s"
+		  count (if unmark "unread" "read"))))
+
+(defun gnus-summary-mark-same-subject (subject &optional unmark)
+  "Mark articles with same SUBJECT as read, and return marked number.
+If optional argument UNMARK is positive, remove any kinds of marks.
+If optional argument UNMARK is negative, mark articles as unread instead."
+  (let ((count 1))
+    (save-excursion
+      (cond
+       ((null unmark)			; Mark as read.
+	(while (and
+		(progn
+		  (gnus-summary-mark-article-as-read gnus-killed-mark)
+		  (gnus-summary-show-thread) t)
+		(gnus-summary-find-subject subject))
+	  (setq count (1+ count))))
+       ((> unmark 0)			; Tick.
+	(while (and
+		(progn
+		  (gnus-summary-mark-article-as-unread gnus-ticked-mark)
+		  (gnus-summary-show-thread) t)
+		(gnus-summary-find-subject subject))
+	  (setq count (1+ count))))
+       (t				; Mark as unread.
+	(while (and
+		(progn
+		  (gnus-summary-mark-article-as-unread gnus-unread-mark)
+		  (gnus-summary-show-thread) t)
+		(gnus-summary-find-subject subject))
+	  (setq count (1+ count)))))
+      (gnus-set-mode-line 'summary)
+      ;; Return the number of marked articles.
+      count)))
+
+(defun gnus-summary-mark-as-processable (n &optional unmark)
+  "Set the process mark on the next N articles.
+If N is negative, mark backward instead.  If UNMARK is non-nil, remove
+the process mark instead.  The difference between N and the actual
+number of articles marked is returned."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (let ((backward (< n 0))
+	(n (abs n)))
+    (while (and
+	    (> n 0)
+	    (if unmark
+		(gnus-summary-remove-process-mark
+		 (gnus-summary-article-number))
+	      (gnus-summary-set-process-mark (gnus-summary-article-number)))
+	    (zerop (gnus-summary-next-subject (if backward -1 1) nil t)))
+      (setq n (1- n)))
+    (when (/= 0 n)
+      (gnus-message 7 "No more articles"))
+    (gnus-summary-recenter)
+    (gnus-summary-position-point)
+    n))
+
+(defun gnus-summary-unmark-as-processable (n)
+  "Remove the process mark from the next N articles.
+If N is negative, mark backward instead.  The difference between N and
+the actual number of articles marked is returned."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (gnus-summary-mark-as-processable n t))
+
+(defun gnus-summary-unmark-all-processable ()
+  "Remove the process mark from all articles."
+  (interactive)
+  (gnus-set-global-variables)
+  (save-excursion
+    (while gnus-newsgroup-processable
+      (gnus-summary-remove-process-mark (car gnus-newsgroup-processable))))
+  (gnus-summary-position-point))
+
+(defun gnus-summary-mark-as-expirable (n)
+  "Mark N articles forward as expirable.
+If N is negative, mark backward instead.  The difference between N and
+the actual number of articles marked is returned."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (gnus-summary-mark-forward n gnus-expirable-mark))
+
+(defun gnus-summary-mark-article-as-replied (article)
+  "Mark ARTICLE replied and update the summary line."
+  (push article gnus-newsgroup-replied)
+  (let ((buffer-read-only nil))
+    (when (gnus-summary-goto-subject article)
+      (gnus-summary-update-secondary-mark article))))
+
+(defun gnus-summary-set-bookmark (article)
+  "Set a bookmark in current article."
+  (interactive (list (gnus-summary-article-number)))
+  (gnus-set-global-variables)
+  (when (or (not (get-buffer gnus-article-buffer))
+	    (not gnus-current-article)
+	    (not gnus-article-current)
+	    (not (equal gnus-newsgroup-name (car gnus-article-current))))
+    (error "No current article selected"))
+  ;; Remove old bookmark, if one exists.
+  (let ((old (assq article gnus-newsgroup-bookmarks)))
+    (when old
+      (setq gnus-newsgroup-bookmarks
+	    (delq old gnus-newsgroup-bookmarks))))
+  ;; Set the new bookmark, which is on the form
+  ;; (article-number . line-number-in-body).
+  (push
+   (cons article
+	 (save-excursion
+	   (set-buffer gnus-article-buffer)
+	   (count-lines
+	    (min (point)
+		 (save-excursion
+		   (goto-char (point-min))
+		   (search-forward "\n\n" nil t)
+		   (point)))
+	    (point))))
+   gnus-newsgroup-bookmarks)
+  (gnus-message 6 "A bookmark has been added to the current article."))
+
+(defun gnus-summary-remove-bookmark (article)
+  "Remove the bookmark from the current article."
+  (interactive (list (gnus-summary-article-number)))
+  (gnus-set-global-variables)
+  ;; Remove old bookmark, if one exists.
+  (let ((old (assq article gnus-newsgroup-bookmarks)))
+    (if old
+	(progn
+	  (setq gnus-newsgroup-bookmarks
+		(delq old gnus-newsgroup-bookmarks))
+	  (gnus-message 6 "Removed bookmark."))
+      (gnus-message 6 "No bookmark in current article."))))
+
+;; Suggested by Daniel Quinlan <quinlan@best.com>.
+(defun gnus-summary-mark-as-dormant (n)
+  "Mark N articles forward as dormant.
+If N is negative, mark backward instead.  The difference between N and
+the actual number of articles marked is returned."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (gnus-summary-mark-forward n gnus-dormant-mark))
+
+(defun gnus-summary-set-process-mark (article)
+  "Set the process mark on ARTICLE and update the summary line."
+  (setq gnus-newsgroup-processable
+	(cons article
+	      (delq article gnus-newsgroup-processable)))
+  (when (gnus-summary-goto-subject article)
+    (gnus-summary-show-thread)
+    (gnus-summary-update-secondary-mark article)))
+
+(defun gnus-summary-remove-process-mark (article)
+  "Remove the process mark from ARTICLE and update the summary line."
+  (setq gnus-newsgroup-processable (delq article gnus-newsgroup-processable))
+  (when (gnus-summary-goto-subject article)
+    (gnus-summary-show-thread)
+    (gnus-summary-update-secondary-mark article)))
+
+(defun gnus-summary-set-saved-mark (article)
+  "Set the process mark on ARTICLE and update the summary line."
+  (push article gnus-newsgroup-saved)
+  (when (gnus-summary-goto-subject article)
+    (gnus-summary-update-secondary-mark article)))
+
+(defun gnus-summary-mark-forward (n &optional mark no-expire)
+  "Mark N articles as read forwards.
+If N is negative, mark backwards instead.  Mark with MARK, ?r by default.
+The difference between N and the actual number of articles marked is
+returned."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (let ((backward (< n 0))
+	(gnus-summary-goto-unread
+	 (and gnus-summary-goto-unread
+	      (not (eq gnus-summary-goto-unread 'never))
+	      (not (memq mark (list gnus-unread-mark
+				    gnus-ticked-mark gnus-dormant-mark)))))
+	(n (abs n))
+	(mark (or mark gnus-del-mark)))
+    (while (and (> n 0)
+		(gnus-summary-mark-article nil mark no-expire)
+		(zerop (gnus-summary-next-subject
+			(if backward -1 1)
+			(and gnus-summary-goto-unread
+			     (not (eq gnus-summary-goto-unread 'never)))
+			t)))
+      (setq n (1- n)))
+    (when (/= 0 n)
+      (gnus-message 7 "No more %sarticles" (if mark "" "unread ")))
+    (gnus-summary-recenter)
+    (gnus-summary-position-point)
+    (gnus-set-mode-line 'summary)
+    n))
+
+(defun gnus-summary-mark-article-as-read (mark)
+  "Mark the current article quickly as read with MARK."
+  (let ((article (gnus-summary-article-number)))
+    (setq gnus-newsgroup-unreads (delq article gnus-newsgroup-unreads))
+    (setq gnus-newsgroup-marked (delq article gnus-newsgroup-marked))
+    (setq gnus-newsgroup-dormant (delq article gnus-newsgroup-dormant))
+    (push (cons article mark) gnus-newsgroup-reads)
+    ;; Possibly remove from cache, if that is used.
+    (when gnus-use-cache
+      (gnus-cache-enter-remove-article article))
+    ;; Allow the backend to change the mark.
+    (setq mark (gnus-request-update-mark gnus-newsgroup-name article mark))
+    ;; Check for auto-expiry.
+    (when (and gnus-newsgroup-auto-expire
+	       (or (= mark gnus-killed-mark) (= mark gnus-del-mark)
+		   (= mark gnus-catchup-mark) (= mark gnus-low-score-mark)
+		   (= mark gnus-ancient-mark)
+		   (= mark gnus-read-mark) (= mark gnus-souped-mark)
+		   (= mark gnus-duplicate-mark)))
+      (setq mark gnus-expirable-mark)
+      (push article gnus-newsgroup-expirable))
+    ;; Set the mark in the buffer.
+    (gnus-summary-update-mark mark 'unread)
+    t))
+
+(defun gnus-summary-mark-article-as-unread (mark)
+  "Mark the current article quickly as unread with MARK."
+  (let ((article (gnus-summary-article-number)))
+    (if (< article 0)
+	(gnus-error 1 "Unmarkable article")
+      (setq gnus-newsgroup-marked (delq article gnus-newsgroup-marked))
+      (setq gnus-newsgroup-dormant (delq article gnus-newsgroup-dormant))
+      (setq gnus-newsgroup-expirable (delq article gnus-newsgroup-expirable))
+      (setq gnus-newsgroup-reads (delq article gnus-newsgroup-reads))
+      (cond ((= mark gnus-ticked-mark)
+	     (push article gnus-newsgroup-marked))
+	    ((= mark gnus-dormant-mark)
+	     (push article gnus-newsgroup-dormant))
+	    (t
+	     (push article gnus-newsgroup-unreads)))
+      (setq gnus-newsgroup-reads
+	    (delq (assq article gnus-newsgroup-reads)
+		  gnus-newsgroup-reads))
+
+      ;; See whether the article is to be put in the cache.
+      (and gnus-use-cache
+	   (vectorp (gnus-summary-article-header article))
+	   (save-excursion
+	     (gnus-cache-possibly-enter-article
+	      gnus-newsgroup-name article
+	      (gnus-summary-article-header article)
+	      (= mark gnus-ticked-mark)
+	      (= mark gnus-dormant-mark) (= mark gnus-unread-mark))))
+
+      ;; Fix the mark.
+      (gnus-summary-update-mark mark 'unread))
+    t))
+
+(defun gnus-summary-mark-article (&optional article mark no-expire)
+  "Mark ARTICLE with MARK.  MARK can be any character.
+Four MARK strings are reserved: `? ' (unread), `?!' (ticked),
+`??' (dormant) and `?E' (expirable).
+If MARK is nil, then the default character `?D' is used.
+If ARTICLE is nil, then the article on the current line will be
+marked."
+  ;; The mark might be a string.
+  (when (stringp mark)
+    (setq mark (aref mark 0)))
+  ;; If no mark is given, then we check auto-expiring.
+  (and (not no-expire)
+       gnus-newsgroup-auto-expire
+       (or (not mark)
+	   (and (gnus-characterp mark)
+		(or (= mark gnus-killed-mark) (= mark gnus-del-mark)
+		    (= mark gnus-catchup-mark) (= mark gnus-low-score-mark)
+		    (= mark gnus-read-mark) (= mark gnus-souped-mark)
+		    (= mark gnus-duplicate-mark))))
+       (setq mark gnus-expirable-mark))
+  (let* ((mark (or mark gnus-del-mark))
+	 (article (or article (gnus-summary-article-number))))
+    (unless article
+      (error "No article on current line"))
+    (if (or (= mark gnus-unread-mark)
+	    (= mark gnus-ticked-mark)
+	    (= mark gnus-dormant-mark))
+	(gnus-mark-article-as-unread article mark)
+      (gnus-mark-article-as-read article mark))
+
+    ;; See whether the article is to be put in the cache.
+    (and gnus-use-cache
+	 (not (= mark gnus-canceled-mark))
+	 (vectorp (gnus-summary-article-header article))
+	 (save-excursion
+	   (gnus-cache-possibly-enter-article
+	    gnus-newsgroup-name article
+	    (gnus-summary-article-header article)
+	    (= mark gnus-ticked-mark)
+	    (= mark gnus-dormant-mark) (= mark gnus-unread-mark))))
+
+    (when (gnus-summary-goto-subject article nil t)
+      (let ((buffer-read-only nil))
+	(gnus-summary-show-thread)
+	;; Fix the mark.
+	(gnus-summary-update-mark mark 'unread)
+	t))))
+
+(defun gnus-summary-update-secondary-mark (article)
+  "Update the secondary (read, process, cache) mark."
+  (gnus-summary-update-mark
+   (cond ((memq article gnus-newsgroup-processable)
+	  gnus-process-mark)
+	 ((memq article gnus-newsgroup-cached)
+	  gnus-cached-mark)
+	 ((memq article gnus-newsgroup-replied)
+	  gnus-replied-mark)
+	 ((memq article gnus-newsgroup-saved)
+	  gnus-saved-mark)
+	 (t gnus-unread-mark))
+   'replied)
+  (when (gnus-visual-p 'summary-highlight 'highlight)
+    (run-hooks 'gnus-summary-update-hook))
+  t)
+
+(defun gnus-summary-update-mark (mark type)
+  (let ((forward (cdr (assq type gnus-summary-mark-positions)))
+        (buffer-read-only nil))
+    (re-search-backward "[\n\r]" (gnus-point-at-bol) 'move-to-limit)
+    (when (looking-at "\r")
+      (incf forward))
+    (when (and forward
+               (<= (+ forward (point)) (point-max)))
+      ;; Go to the right position on the line.
+      (goto-char (+ forward (point)))
+      ;; Replace the old mark with the new mark.
+      (subst-char-in-region (point) (1+ (point)) (following-char) mark)
+      ;; Optionally update the marks by some user rule.
+      (when (eq type 'unread)
+        (gnus-data-set-mark
+         (gnus-data-find (gnus-summary-article-number)) mark)
+        (gnus-summary-update-line (eq mark gnus-unread-mark))))))
+
+(defun gnus-mark-article-as-read (article &optional mark)
+  "Enter ARTICLE in the pertinent lists and remove it from others."
+  ;; Make the article expirable.
+  (let ((mark (or mark gnus-del-mark)))
+    (if (= mark gnus-expirable-mark)
+	(push article gnus-newsgroup-expirable)
+      (setq gnus-newsgroup-expirable (delq article gnus-newsgroup-expirable)))
+    ;; Remove from unread and marked lists.
+    (setq gnus-newsgroup-unreads (delq article gnus-newsgroup-unreads))
+    (setq gnus-newsgroup-marked (delq article gnus-newsgroup-marked))
+    (setq gnus-newsgroup-dormant (delq article gnus-newsgroup-dormant))
+    (push (cons article mark) gnus-newsgroup-reads)
+    ;; Possibly remove from cache, if that is used.
+    (when gnus-use-cache
+      (gnus-cache-enter-remove-article article))))
+
+(defun gnus-mark-article-as-unread (article &optional mark)
+  "Enter ARTICLE in the pertinent lists and remove it from others."
+  (let ((mark (or mark gnus-ticked-mark)))
+    (setq gnus-newsgroup-marked (delq article gnus-newsgroup-marked)
+	  gnus-newsgroup-dormant (delq article gnus-newsgroup-dormant)
+	  gnus-newsgroup-expirable (delq article gnus-newsgroup-expirable)
+	  gnus-newsgroup-unreads (delq article gnus-newsgroup-unreads))
+
+    ;; Unsuppress duplicates?
+    (when gnus-suppress-duplicates
+      (gnus-dup-unsuppress-article article))
+
+    (cond ((= mark gnus-ticked-mark)
+	   (push article gnus-newsgroup-marked))
+	  ((= mark gnus-dormant-mark)
+	   (push article gnus-newsgroup-dormant))
+	  (t
+	   (push article gnus-newsgroup-unreads)))
+    (setq gnus-newsgroup-reads
+	  (delq (assq article gnus-newsgroup-reads)
+		gnus-newsgroup-reads))))
+
+(defalias 'gnus-summary-mark-as-unread-forward
+  'gnus-summary-tick-article-forward)
+(make-obsolete 'gnus-summary-mark-as-unread-forward
+	       'gnus-summary-tick-article-forward)
+(defun gnus-summary-tick-article-forward (n)
+  "Tick N articles forwards.
+If N is negative, tick backwards instead.
+The difference between N and the number of articles ticked is returned."
+  (interactive "p")
+  (gnus-summary-mark-forward n gnus-ticked-mark))
+
+(defalias 'gnus-summary-mark-as-unread-backward
+  'gnus-summary-tick-article-backward)
+(make-obsolete 'gnus-summary-mark-as-unread-backward
+	       'gnus-summary-tick-article-backward)
+(defun gnus-summary-tick-article-backward (n)
+  "Tick N articles backwards.
+The difference between N and the number of articles ticked is returned."
+  (interactive "p")
+  (gnus-summary-mark-forward (- n) gnus-ticked-mark))
+
+(defalias 'gnus-summary-mark-as-unread 'gnus-summary-tick-article)
+(make-obsolete 'gnus-summary-mark-as-unread 'gnus-summary-tick-article)
+(defun gnus-summary-tick-article (&optional article clear-mark)
+  "Mark current article as unread.
+Optional 1st argument ARTICLE specifies article number to be marked as unread.
+Optional 2nd argument CLEAR-MARK remove any kinds of mark."
+  (interactive)
+  (gnus-summary-mark-article article (if clear-mark gnus-unread-mark
+				       gnus-ticked-mark)))
+
+(defun gnus-summary-mark-as-read-forward (n)
+  "Mark N articles as read forwards.
+If N is negative, mark backwards instead.
+The difference between N and the actual number of articles marked is
+returned."
+  (interactive "p")
+  (gnus-summary-mark-forward n gnus-del-mark t))
+
+(defun gnus-summary-mark-as-read-backward (n)
+  "Mark the N articles as read backwards.
+The difference between N and the actual number of articles marked is
+returned."
+  (interactive "p")
+  (gnus-summary-mark-forward (- n) gnus-del-mark t))
+
+(defun gnus-summary-mark-as-read (&optional article mark)
+  "Mark current article as read.
+ARTICLE specifies the article to be marked as read.
+MARK specifies a string to be inserted at the beginning of the line."
+  (gnus-summary-mark-article article mark))
+
+(defun gnus-summary-clear-mark-forward (n)
+  "Clear marks from N articles forward.
+If N is negative, clear backward instead.
+The difference between N and the number of marks cleared is returned."
+  (interactive "p")
+  (gnus-summary-mark-forward n gnus-unread-mark))
+
+(defun gnus-summary-clear-mark-backward (n)
+  "Clear marks from N articles backward.
+The difference between N and the number of marks cleared is returned."
+  (interactive "p")
+  (gnus-summary-mark-forward (- n) gnus-unread-mark))
+
+(defun gnus-summary-mark-unread-as-read ()
+  "Intended to be used by `gnus-summary-mark-article-hook'."
+  (when (memq gnus-current-article gnus-newsgroup-unreads)
+    (gnus-summary-mark-article gnus-current-article gnus-read-mark)))
+
+(defun gnus-summary-mark-read-and-unread-as-read ()
+  "Intended to be used by `gnus-summary-mark-article-hook'."
+  (let ((mark (gnus-summary-article-mark)))
+    (when (or (gnus-unread-mark-p mark)
+	      (gnus-read-mark-p mark))
+      (gnus-summary-mark-article gnus-current-article gnus-read-mark))))
+
+(defun gnus-summary-mark-region-as-read (point mark all)
+  "Mark all unread articles between point and mark as read.
+If given a prefix, mark all articles between point and mark as read,
+even ticked and dormant ones."
+  (interactive "r\nP")
+  (save-excursion
+    (let (article)
+      (goto-char point)
+      (beginning-of-line)
+      (while (and
+	      (< (point) mark)
+	      (progn
+		(when (or all
+			  (memq (setq article (gnus-summary-article-number))
+				gnus-newsgroup-unreads))
+		  (gnus-summary-mark-article article gnus-del-mark))
+		t)
+	      (gnus-summary-find-next))))))
+
+(defun gnus-summary-mark-below (score mark)
+  "Mark articles with score less than SCORE with MARK."
+  (interactive "P\ncMark: ")
+  (gnus-set-global-variables)
+  (setq score (if score
+		  (prefix-numeric-value score)
+		(or gnus-summary-default-score 0)))
+  (save-excursion
+    (set-buffer gnus-summary-buffer)
+    (goto-char (point-min))
+    (while
+	(progn
+	  (and (< (gnus-summary-article-score) score)
+	       (gnus-summary-mark-article nil mark))
+	  (gnus-summary-find-next)))))
+
+(defun gnus-summary-kill-below (&optional score)
+  "Mark articles with score below SCORE as read."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (gnus-summary-mark-below score gnus-killed-mark))
+
+(defun gnus-summary-clear-above (&optional score)
+  "Clear all marks from articles with score above SCORE."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (gnus-summary-mark-above score gnus-unread-mark))
+
+(defun gnus-summary-tick-above (&optional score)
+  "Tick all articles with score above SCORE."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (gnus-summary-mark-above score gnus-ticked-mark))
+
+(defun gnus-summary-mark-above (score mark)
+  "Mark articles with score over SCORE with MARK."
+  (interactive "P\ncMark: ")
+  (gnus-set-global-variables)
+  (setq score (if score
+		  (prefix-numeric-value score)
+		(or gnus-summary-default-score 0)))
+  (save-excursion
+    (set-buffer gnus-summary-buffer)
+    (goto-char (point-min))
+    (while (and (progn
+		  (when (> (gnus-summary-article-score) score)
+		    (gnus-summary-mark-article nil mark))
+		  t)
+		(gnus-summary-find-next)))))
+
+;; Suggested by Daniel Quinlan <quinlan@best.com>.
+(defalias 'gnus-summary-show-all-expunged 'gnus-summary-limit-include-expunged)
+(defun gnus-summary-limit-include-expunged (&optional no-error)
+  "Display all the hidden articles that were expunged for low scores."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((buffer-read-only nil))
+    (let ((scored gnus-newsgroup-scored)
+	  headers h)
+      (while scored
+	(unless (gnus-summary-goto-subject (caar scored))
+	  (and (setq h (gnus-summary-article-header (caar scored)))
+	       (< (cdar scored) gnus-summary-expunge-below)
+	       (push h headers)))
+	(setq scored (cdr scored)))
+      (if (not headers)
+	  (when (not no-error)
+	    (error "No expunged articles hidden."))
+	(goto-char (point-min))
+	(gnus-summary-prepare-unthreaded (nreverse headers))
+	(goto-char (point-min))
+	(gnus-summary-position-point)
+	t))))
+
+(defun gnus-summary-catchup (&optional all quietly to-here not-mark)
+  "Mark all unread articles in this newsgroup as read.
+If prefix argument ALL is non-nil, ticked and dormant articles will
+also be marked as read.
+If QUIETLY is non-nil, no questions will be asked.
+If TO-HERE is non-nil, it should be a point in the buffer.  All
+articles before this point will be marked as read.
+Note that this function will only catch up the unread article
+in the current summary buffer limitation.
+The number of articles marked as read is returned."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (prog1
+      (save-excursion
+	(when (or quietly
+		  (not gnus-interactive-catchup) ;Without confirmation?
+		  gnus-expert-user
+		  (gnus-y-or-n-p
+		   (if all
+		       "Mark absolutely all articles as read? "
+		     "Mark all unread articles as read? ")))
+	  (if (and not-mark
+		   (not gnus-newsgroup-adaptive)
+		   (not gnus-newsgroup-auto-expire)
+		   (not gnus-suppress-duplicates))
+	      (progn
+		(when all
+		  (setq gnus-newsgroup-marked nil
+			gnus-newsgroup-dormant nil))
+		(setq gnus-newsgroup-unreads nil))
+	    ;; We actually mark all articles as canceled, which we
+	    ;; have to do when using auto-expiry or adaptive scoring.
+	    (gnus-summary-show-all-threads)
+	    (when (gnus-summary-first-subject (not all))
+	      (while (and
+		      (if to-here (< (point) to-here) t)
+		      (gnus-summary-mark-article-as-read gnus-catchup-mark)
+		      (gnus-summary-find-next (not all)))))
+	    (gnus-set-mode-line 'summary))
+	  t))
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-catchup-to-here (&optional all)
+  "Mark all unticked articles before the current one as read.
+If ALL is non-nil, also mark ticked and dormant articles as read."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (save-excursion
+    (gnus-save-hidden-threads
+      (let ((beg (point)))
+	;; We check that there are unread articles.
+	(when (or all (gnus-summary-find-prev))
+	  (gnus-summary-catchup all t beg)))))
+  (gnus-summary-position-point))
+
+(defun gnus-summary-catchup-all (&optional quietly)
+  "Mark all articles in this newsgroup as read."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (gnus-summary-catchup t quietly))
+
+(defun gnus-summary-catchup-and-exit (&optional all quietly)
+  "Mark all articles not marked as unread in this newsgroup as read, then exit.
+If prefix argument ALL is non-nil, all articles are marked as read."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (when (gnus-summary-catchup all quietly nil 'fast)
+    ;; Select next newsgroup or exit.
+    (if (eq gnus-auto-select-next 'quietly)
+	(gnus-summary-next-group nil)
+      (gnus-summary-exit))))
+
+(defun gnus-summary-catchup-all-and-exit (&optional quietly)
+  "Mark all articles in this newsgroup as read, and then exit."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (gnus-summary-catchup-and-exit t quietly))
+
+;; Suggested by "Arne Eofsson" <arne@hodgkin.mbi.ucla.edu>.
+(defun gnus-summary-catchup-and-goto-next-group (&optional all)
+  "Mark all articles in this group as read and select the next group.
+If given a prefix, mark all articles, unread as well as ticked, as
+read."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (save-excursion
+    (gnus-summary-catchup all))
+  (gnus-summary-next-article t nil nil t))
+
+;; Thread-based commands.
+
+(defun gnus-summary-articles-in-thread (&optional article)
+  "Return a list of all articles in the current thread.
+If ARTICLE is non-nil, return all articles in the thread that starts
+with that article."
+  (let* ((article (or article (gnus-summary-article-number)))
+	 (data (gnus-data-find-list article))
+	 (top-level (gnus-data-level (car data)))
+	 (top-subject
+	  (cond ((null gnus-thread-operation-ignore-subject)
+		 (gnus-simplify-subject-re
+		  (mail-header-subject (gnus-data-header (car data)))))
+		((eq gnus-thread-operation-ignore-subject 'fuzzy)
+		 (gnus-simplify-subject-fuzzy
+		  (mail-header-subject (gnus-data-header (car data)))))
+		(t nil)))
+	 (end-point (save-excursion
+		      (if (gnus-summary-go-to-next-thread)
+			  (point) (point-max))))
+	 articles)
+    (while (and data
+		(< (gnus-data-pos (car data)) end-point))
+      (when (or (not top-subject)
+		(string= top-subject
+			 (if (eq gnus-thread-operation-ignore-subject 'fuzzy)
+			     (gnus-simplify-subject-fuzzy
+			      (mail-header-subject
+			       (gnus-data-header (car data))))
+			   (gnus-simplify-subject-re
+			    (mail-header-subject
+			     (gnus-data-header (car data)))))))
+	(push (gnus-data-number (car data)) articles))
+      (unless (and (setq data (cdr data))
+		   (> (gnus-data-level (car data)) top-level))
+	(setq data nil)))
+    ;; Return the list of articles.
+    (nreverse articles)))
+
+(defun gnus-summary-rethread-current ()
+  "Rethread the thread the current article is part of."
+  (interactive)
+  (gnus-set-global-variables)
+  (let* ((gnus-show-threads t)
+	 (article (gnus-summary-article-number))
+	 (id (mail-header-id (gnus-summary-article-header)))
+	 (gnus-newsgroup-threads (list (gnus-id-to-thread (gnus-root-id id)))))
+    (unless id
+      (error "No article on the current line"))
+    (gnus-rebuild-thread id)
+    (gnus-summary-goto-subject article)))
+
+(defun gnus-summary-reparent-thread ()
+  "Make the current article child of the marked (or previous) article.
+
+Note that the re-threading will only work if `gnus-thread-ignore-subject'
+is non-nil or the Subject: of both articles are the same."
+  (interactive)
+  (unless (not (gnus-group-read-only-p))
+    (error "The current newsgroup does not support article editing."))
+  (unless (<= (length gnus-newsgroup-processable) 1)
+    (error "No more than one article may be marked."))
+  (save-window-excursion
+    (let ((gnus-article-buffer " *reparent*")
+	  (current-article (gnus-summary-article-number))
+	  ;; First grab the marked article, otherwise one line up.
+	  (parent-article (if (not (null gnus-newsgroup-processable))
+			      (car gnus-newsgroup-processable)
+			    (save-excursion
+			      (if (eq (forward-line -1) 0)
+				  (gnus-summary-article-number)
+				(error "Beginning of summary buffer."))))))
+      (unless (not (eq current-article parent-article))
+	(error "An article may not be self-referential."))
+      (let ((message-id (mail-header-id
+			 (gnus-summary-article-header parent-article))))
+	(unless (and message-id (not (equal message-id "")))
+	  (error "No message-id in desired parent."))
+	(gnus-summary-select-article t t nil current-article)
+	(set-buffer gnus-original-article-buffer)
+	(let ((buf (format "%s" (buffer-string))))
+	  (nnheader-temp-write nil
+	    (insert buf)
+	    (goto-char (point-min))
+	    (if (search-forward-regexp "^References: " nil t)
+		(insert message-id " " )
+	      (insert "References: " message-id "\n"))
+	    (unless (gnus-request-replace-article
+		     current-article (car gnus-article-current)
+		     (current-buffer))
+	      (error "Couldn't replace article."))))
+	(set-buffer gnus-summary-buffer)
+	(gnus-summary-unmark-all-processable)
+	(gnus-summary-rethread-current)
+	(gnus-message 3 "Article %d is now the child of article %d."
+		      current-article parent-article)))))
+
+(defun gnus-summary-toggle-threads (&optional arg)
+  "Toggle showing conversation threads.
+If ARG is positive number, turn showing conversation threads on."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((current (or (gnus-summary-article-number) gnus-newsgroup-end)))
+    (setq gnus-show-threads
+	  (if (null arg) (not gnus-show-threads)
+	    (> (prefix-numeric-value arg) 0)))
+    (gnus-summary-prepare)
+    (gnus-summary-goto-subject current)
+    (gnus-message 6 "Threading is now %s" (if gnus-show-threads "on" "off"))
+    (gnus-summary-position-point)))
+
+(defun gnus-summary-show-all-threads ()
+  "Show all threads."
+  (interactive)
+  (gnus-set-global-variables)
+  (save-excursion
+    (let ((buffer-read-only nil))
+      (subst-char-in-region (point-min) (point-max) ?\^M ?\n t)))
+  (gnus-summary-position-point))
+
+(defun gnus-summary-show-thread ()
+  "Show thread subtrees.
+Returns nil if no thread was there to be shown."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((buffer-read-only nil)
+	(orig (point))
+	;; first goto end then to beg, to have point at beg after let
+	(end (progn (end-of-line) (point)))
+	(beg (progn (beginning-of-line) (point))))
+    (prog1
+	;; Any hidden lines here?
+	(search-forward "\r" end t)
+      (subst-char-in-region beg end ?\^M ?\n t)
+      (goto-char orig)
+      (gnus-summary-position-point))))
+
+(defun gnus-summary-hide-all-threads ()
+  "Hide all thread subtrees."
+  (interactive)
+  (gnus-set-global-variables)
+  (save-excursion
+    (goto-char (point-min))
+    (gnus-summary-hide-thread)
+    (while (zerop (gnus-summary-next-thread 1 t))
+      (gnus-summary-hide-thread)))
+  (gnus-summary-position-point))
+
+(defun gnus-summary-hide-thread ()
+  "Hide thread subtrees.
+Returns nil if no threads were there to be hidden."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((buffer-read-only nil)
+	(start (point))
+	(article (gnus-summary-article-number)))
+    (goto-char start)
+    ;; Go forward until either the buffer ends or the subthread
+    ;; ends.
+    (when (and (not (eobp))
+	       (or (zerop (gnus-summary-next-thread 1 t))
+		   (goto-char (point-max))))
+      (prog1
+	  (if (and (> (point) start)
+		   (search-backward "\n" start t))
+	      (progn
+		(subst-char-in-region start (point) ?\n ?\^M)
+		(gnus-summary-goto-subject article))
+	    (goto-char start)
+	    nil)
+	;;(gnus-summary-position-point)
+	))))
+
+(defun gnus-summary-go-to-next-thread (&optional previous)
+  "Go to the same level (or less) next thread.
+If PREVIOUS is non-nil, go to previous thread instead.
+Return the article number moved to, or nil if moving was impossible."
+  (let ((level (gnus-summary-thread-level))
+	(way (if previous -1 1))
+	(beg (point)))
+    (forward-line way)
+    (while (and (not (eobp))
+		(< level (gnus-summary-thread-level)))
+      (forward-line way))
+    (if (eobp)
+	(progn
+	  (goto-char beg)
+	  nil)
+      (setq beg (point))
+      (prog1
+	  (gnus-summary-article-number)
+	(goto-char beg)))))
+
+(defun gnus-summary-next-thread (n &optional silent)
+  "Go to the same level next N'th thread.
+If N is negative, search backward instead.
+Returns the difference between N and the number of skips actually
+done.
+
+If SILENT, don't output messages."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (let ((backward (< n 0))
+	(n (abs n)))
+    (while (and (> n 0)
+		(gnus-summary-go-to-next-thread backward))
+      (decf n))
+    (unless silent
+      (gnus-summary-position-point))
+    (when (and (not silent) (/= 0 n))
+      (gnus-message 7 "No more threads"))
+    n))
+
+(defun gnus-summary-prev-thread (n)
+  "Go to the same level previous N'th thread.
+Returns the difference between N and the number of skips actually
+done."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (gnus-summary-next-thread (- n)))
+
+(defun gnus-summary-go-down-thread ()
+  "Go down one level in the current thread."
+  (let ((children (gnus-summary-article-children)))
+    (when children
+      (gnus-summary-goto-subject (car children)))))
+
+(defun gnus-summary-go-up-thread ()
+  "Go up one level in the current thread."
+  (let ((parent (gnus-summary-article-parent)))
+    (when parent
+      (gnus-summary-goto-subject parent))))
+
+(defun gnus-summary-down-thread (n)
+  "Go down thread N steps.
+If N is negative, go up instead.
+Returns the difference between N and how many steps down that were
+taken."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (let ((up (< n 0))
+	(n (abs n)))
+    (while (and (> n 0)
+		(if up (gnus-summary-go-up-thread)
+		  (gnus-summary-go-down-thread)))
+      (setq n (1- n)))
+    (gnus-summary-position-point)
+    (when (/= 0 n)
+      (gnus-message 7 "Can't go further"))
+    n))
+
+(defun gnus-summary-up-thread (n)
+  "Go up thread N steps.
+If N is negative, go up instead.
+Returns the difference between N and how many steps down that were
+taken."
+  (interactive "p")
+  (gnus-set-global-variables)
+  (gnus-summary-down-thread (- n)))
+
+(defun gnus-summary-top-thread ()
+  "Go to the top of the thread."
+  (interactive)
+  (gnus-set-global-variables)
+  (while (gnus-summary-go-up-thread))
+  (gnus-summary-article-number))
+
+(defun gnus-summary-kill-thread (&optional unmark)
+  "Mark articles under current thread as read.
+If the prefix argument is positive, remove any kinds of marks.
+If the prefix argument is negative, tick articles instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (when unmark
+    (setq unmark (prefix-numeric-value unmark)))
+  (let ((articles (gnus-summary-articles-in-thread)))
+    (save-excursion
+      ;; Expand the thread.
+      (gnus-summary-show-thread)
+      ;; Mark all the articles.
+      (while articles
+	(gnus-summary-goto-subject (car articles))
+	(cond ((null unmark)
+	       (gnus-summary-mark-article-as-read gnus-killed-mark))
+	      ((> unmark 0)
+	       (gnus-summary-mark-article-as-unread gnus-unread-mark))
+	      (t
+	       (gnus-summary-mark-article-as-unread gnus-ticked-mark)))
+	(setq articles (cdr articles))))
+    ;; Hide killed subtrees.
+    (and (null unmark)
+	 gnus-thread-hide-killed
+	 (gnus-summary-hide-thread))
+    ;; If marked as read, go to next unread subject.
+    (when (null unmark)
+      ;; Go to next unread subject.
+      (gnus-summary-next-subject 1 t)))
+  (gnus-set-mode-line 'summary))
+
+;; Summary sorting commands
+
+(defun gnus-summary-sort-by-number (&optional reverse)
+  "Sort the summary buffer by article number.
+Argument REVERSE means reverse order."
+  (interactive "P")
+  (gnus-summary-sort 'number reverse))
+
+(defun gnus-summary-sort-by-author (&optional reverse)
+  "Sort the summary buffer by author name alphabetically.
+If case-fold-search is non-nil, case of letters is ignored.
+Argument REVERSE means reverse order."
+  (interactive "P")
+  (gnus-summary-sort 'author reverse))
+
+(defun gnus-summary-sort-by-subject (&optional reverse)
+  "Sort the summary buffer by subject alphabetically.  `Re:'s are ignored.
+If case-fold-search is non-nil, case of letters is ignored.
+Argument REVERSE means reverse order."
+  (interactive "P")
+  (gnus-summary-sort 'subject reverse))
+
+(defun gnus-summary-sort-by-date (&optional reverse)
+  "Sort the summary buffer by date.
+Argument REVERSE means reverse order."
+  (interactive "P")
+  (gnus-summary-sort 'date reverse))
+
+(defun gnus-summary-sort-by-score (&optional reverse)
+  "Sort the summary buffer by score.
+Argument REVERSE means reverse order."
+  (interactive "P")
+  (gnus-summary-sort 'score reverse))
+
+(defun gnus-summary-sort-by-lines (&optional reverse)
+  "Sort the summary buffer by article length.
+Argument REVERSE means reverse order."
+  (interactive "P")
+  (gnus-summary-sort 'lines reverse))
+
+(defun gnus-summary-sort (predicate reverse)
+  "Sort summary buffer by PREDICATE.  REVERSE means reverse order."
+  (gnus-set-global-variables)
+  (let* ((thread (intern (format "gnus-thread-sort-by-%s" predicate)))
+	 (article (intern (format "gnus-article-sort-by-%s" predicate)))
+	 (gnus-thread-sort-functions
+	  (list
+	   (if (not reverse)
+	       thread
+	     `(lambda (t1 t2)
+		(,thread t2 t1)))))
+	 (gnus-article-sort-functions
+	  (list
+	   (if (not reverse)
+	       article
+	     `(lambda (t1 t2)
+		(,article t2 t1)))))
+	 (buffer-read-only)
+	 (gnus-summary-prepare-hook nil))
+    ;; We do the sorting by regenerating the threads.
+    (gnus-summary-prepare)
+    ;; Hide subthreads if needed.
+    (when (and gnus-show-threads gnus-thread-hide-subtree)
+      (gnus-summary-hide-all-threads))))
+
+;; Summary saving commands.
+
+(defun gnus-summary-save-article (&optional n not-saved)
+  "Save the current article using the default saver function.
+If N is a positive number, save the N next articles.
+If N is a negative number, save the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+save those articles instead.
+The variable `gnus-default-article-saver' specifies the saver function."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let* ((articles (gnus-summary-work-articles n))
+	 (save-buffer (save-excursion
+			(nnheader-set-temp-buffer " *Gnus Save*")))
+	 (num (length articles))
+	 header article file)
+    (while articles
+      (setq header (gnus-summary-article-header
+		    (setq article (pop articles))))
+      (if (not (vectorp header))
+	  ;; This is a pseudo-article.
+	  (if (assq 'name header)
+	      (gnus-copy-file (cdr (assq 'name header)))
+	    (gnus-message 1 "Article %d is unsaveable" article))
+	;; This is a real article.
+	(save-window-excursion
+	  (gnus-summary-select-article t nil nil article))
+	(save-excursion
+	  (set-buffer save-buffer)
+	  (erase-buffer)
+	  (insert-buffer-substring gnus-original-article-buffer))
+	(setq file (gnus-article-save save-buffer file num))
+	(gnus-summary-remove-process-mark article)
+	(unless not-saved
+	  (gnus-summary-set-saved-mark article))))
+    (gnus-kill-buffer save-buffer)
+    (gnus-summary-position-point)
+    (gnus-set-mode-line 'summary)
+    n))
+
+(defun gnus-summary-pipe-output (&optional arg)
+  "Pipe the current article to a subprocess.
+If N is a positive number, pipe the N next articles.
+If N is a negative number, pipe the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+pipe those articles instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((gnus-default-article-saver 'gnus-summary-save-in-pipe))
+    (gnus-summary-save-article arg t))
+  (gnus-configure-windows 'pipe))
+
+(defun gnus-summary-save-article-mail (&optional arg)
+  "Append the current article to an mail file.
+If N is a positive number, save the N next articles.
+If N is a negative number, save the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+save those articles instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((gnus-default-article-saver 'gnus-summary-save-in-mail))
+    (gnus-summary-save-article arg)))
+
+(defun gnus-summary-save-article-rmail (&optional arg)
+  "Append the current article to an rmail file.
+If N is a positive number, save the N next articles.
+If N is a negative number, save the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+save those articles instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((gnus-default-article-saver 'gnus-summary-save-in-rmail))
+    (gnus-summary-save-article arg)))
+
+(defun gnus-summary-save-article-file (&optional arg)
+  "Append the current article to a file.
+If N is a positive number, save the N next articles.
+If N is a negative number, save the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+save those articles instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((gnus-default-article-saver 'gnus-summary-save-in-file))
+    (gnus-summary-save-article arg)))
+
+(defun gnus-summary-write-article-file (&optional arg)
+  "Write the current article to a file, deleting the previous file.
+If N is a positive number, save the N next articles.
+If N is a negative number, save the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+save those articles instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((gnus-default-article-saver 'gnus-summary-write-to-file))
+    (gnus-summary-save-article arg)))
+
+(defun gnus-summary-save-article-body-file (&optional arg)
+  "Append the current article body to a file.
+If N is a positive number, save the N next articles.
+If N is a negative number, save the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+save those articles instead."
+  (interactive "P")
+  (gnus-set-global-variables)
+  (let ((gnus-default-article-saver 'gnus-summary-save-body-in-file))
+    (gnus-summary-save-article arg)))
+
+(defun gnus-summary-pipe-message (program)
+  "Pipe the current article through PROGRAM."
+  (interactive "sProgram: ")
+  (gnus-set-global-variables)
+  (gnus-summary-select-article)
+  (let ((mail-header-separator "")
+        (art-buf (get-buffer gnus-article-buffer)))
+    (gnus-eval-in-buffer-window gnus-article-buffer
+      (save-restriction
+        (widen)
+        (let ((start (window-start))
+              buffer-read-only)
+          (message-pipe-buffer-body program)
+          (set-window-start (get-buffer-window (current-buffer)) start))))))
+
+(defun gnus-get-split-value (methods)
+  "Return a value based on the split METHODS."
+  (let (split-name method result match)
+    (when methods
+      (save-excursion
+	(set-buffer gnus-original-article-buffer)
+	(save-restriction
+	  (nnheader-narrow-to-headers)
+	  (while methods
+	    (goto-char (point-min))
+	    (setq method (pop methods))
+	    (setq match (car method))
+	    (when (cond
+		   ((stringp match)
+		    ;; Regular expression.
+		    (ignore-errors
+		      (re-search-forward match nil t)))
+		   ((gnus-functionp match)
+		    ;; Function.
+		    (save-restriction
+		      (widen)
+		      (setq result (funcall match gnus-newsgroup-name))))
+		   ((consp match)
+		    ;; Form.
+		    (save-restriction
+		      (widen)
+		      (setq result (eval match)))))
+	      (setq split-name (append (cdr method) split-name))
+	      (cond ((stringp result)
+		     (push (expand-file-name
+			    result gnus-article-save-directory)
+			   split-name))
+		    ((consp result)
+		     (setq split-name (append result split-name)))))))))
+    split-name))
+
+(defun gnus-valid-move-group-p (group)
+  (and (boundp group)
+       (symbol-name group)
+       (memq 'respool
+	     (assoc (symbol-name
+		     (car (gnus-find-method-for-group
+			   (symbol-name group))))
+		    gnus-valid-select-methods))))
+
+(defun gnus-read-move-group-name (prompt default articles prefix)
+  "Read a group name."
+  (let* ((split-name (gnus-get-split-value gnus-move-split-methods))
+	 (minibuffer-confirm-incomplete nil) ; XEmacs
+	 (prom
+	  (format "%s %s to:"
+		  prompt
+		  (if (> (length articles) 1)
+		      (format "these %d articles" (length articles))
+		    "this article")))
+	 (to-newsgroup
+	  (cond
+	   ((null split-name)
+	    (gnus-completing-read default prom
+				  gnus-active-hashtb
+				  'gnus-valid-move-group-p
+				  nil prefix
+				  'gnus-group-history))
+	   ((= 1 (length split-name))
+	    (gnus-completing-read (car split-name) prom
+				  gnus-active-hashtb
+				  'gnus-valid-move-group-p
+				  nil nil
+				  'gnus-group-history))
+	   (t
+	    (gnus-completing-read nil prom
+				  (mapcar (lambda (el) (list el))
+					  (nreverse split-name))
+				  nil nil nil
+				  'gnus-group-history)))))
+    (when to-newsgroup
+      (if (or (string= to-newsgroup "")
+	      (string= to-newsgroup prefix))
+	  (setq to-newsgroup default))
+      (unless to-newsgroup
+	(error "No group name entered"))
+      (or (gnus-active to-newsgroup)
+	  (gnus-activate-group to-newsgroup)
+	  (if (gnus-y-or-n-p (format "No such group: %s.  Create it? "
+				     to-newsgroup))
+	      (or (and (gnus-request-create-group
+			to-newsgroup (gnus-group-name-to-method to-newsgroup))
+		       (gnus-activate-group to-newsgroup nil nil
+					    (gnus-group-name-to-method
+					     to-newsgroup)))
+		  (error "Couldn't create group %s" to-newsgroup)))
+	  (error "No such group: %s" to-newsgroup)))
+    to-newsgroup))
+
+;; Summary extract commands
+
+(defun gnus-summary-insert-pseudos (pslist &optional not-view)
+  (let ((buffer-read-only nil)
+	(article (gnus-summary-article-number))
+	after-article b e)
+    (unless (gnus-summary-goto-subject article)
+      (error "No such article: %d" article))
+    (gnus-summary-position-point)
+    ;; If all commands are to be bunched up on one line, we collect
+    ;; them here.
+    (unless gnus-view-pseudos-separately
+      (let ((ps (setq pslist (sort pslist 'gnus-pseudos<)))
+	    files action)
+	(while ps
+	  (setq action (cdr (assq 'action (car ps))))
+	  (setq files (list (cdr (assq 'name (car ps)))))
+	  (while (and ps (cdr ps)
+		      (string= (or action "1")
+			       (or (cdr (assq 'action (cadr ps))) "2")))
+	    (push (cdr (assq 'name (cadr ps))) files)
+	    (setcdr ps (cddr ps)))
+	  (when files
+	    (when (not (string-match "%s" action))
+	      (push " " files))
+	    (push " " files)
+	    (when (assq 'execute (car ps))
+	      (setcdr (assq 'execute (car ps))
+		      (funcall (if (string-match "%s" action)
+				   'format 'concat)
+			       action
+			       (mapconcat
+				(lambda (f)
+				  (if (equal f " ")
+				      f
+				    (gnus-quote-arg-for-sh-or-csh f)))
+				files " ")))))
+	  (setq ps (cdr ps)))))
+    (if (and gnus-view-pseudos (not not-view))
+	(while pslist
+	  (when (assq 'execute (car pslist))
+	    (gnus-execute-command (cdr (assq 'execute (car pslist)))
+				  (eq gnus-view-pseudos 'not-confirm)))
+	  (setq pslist (cdr pslist)))
+      (save-excursion
+	(while pslist
+	  (setq after-article (or (cdr (assq 'article (car pslist)))
+				  (gnus-summary-article-number)))
+	  (gnus-summary-goto-subject after-article)
+	  (forward-line 1)
+	  (setq b (point))
+	  (insert "    " (file-name-nondirectory
+			  (cdr (assq 'name (car pslist))))
+		  ": " (or (cdr (assq 'execute (car pslist))) "") "\n")
+	  (setq e (point))
+	  (forward-line -1)		; back to `b'
+	  (gnus-add-text-properties
+	   b (1- e) (list 'gnus-number gnus-reffed-article-number
+			  gnus-mouse-face-prop gnus-mouse-face))
+	  (gnus-data-enter
+	   after-article gnus-reffed-article-number
+	   gnus-unread-mark b (car pslist) 0 (- e b))
+	  (push gnus-reffed-article-number gnus-newsgroup-unreads)
+	  (setq gnus-reffed-article-number (1- gnus-reffed-article-number))
+	  (setq pslist (cdr pslist)))))))
+
+(defun gnus-pseudos< (p1 p2)
+  (let ((c1 (cdr (assq 'action p1)))
+	(c2 (cdr (assq 'action p2))))
+    (and c1 c2 (string< c1 c2))))
+
+(defun gnus-request-pseudo-article (props)
+  (cond ((assq 'execute props)
+	 (gnus-execute-command (cdr (assq 'execute props)))))
+  (let ((gnus-current-article (gnus-summary-article-number)))
+    (run-hooks 'gnus-mark-article-hook)))
+
+(defun gnus-execute-command (command &optional automatic)
+  (save-excursion
+    (gnus-article-setup-buffer)
+    (set-buffer gnus-article-buffer)
+    (setq buffer-read-only nil)
+    (let ((command (if automatic command (read-string "Command: " command))))
+      (erase-buffer)
+      (insert "$ " command "\n\n")
+      (if gnus-view-pseudo-asynchronously
+	  (start-process "gnus-execute" (current-buffer) shell-file-name
+			 shell-command-switch command)
+	(call-process shell-file-name nil t nil
+		      shell-command-switch command)))))
+
+;; Summary kill commands.
+
+(defun gnus-summary-edit-global-kill (article)
+  "Edit the \"global\" kill file."
+  (interactive (list (gnus-summary-article-number)))
+  (gnus-set-global-variables)
+  (gnus-group-edit-global-kill article))
+
+(defun gnus-summary-edit-local-kill ()
+  "Edit a local kill file applied to the current newsgroup."
+  (interactive)
+  (gnus-set-global-variables)
+  (setq gnus-current-headers (gnus-summary-article-header))
+  (gnus-set-global-variables)
+  (gnus-group-edit-local-kill
+   (gnus-summary-article-number) gnus-newsgroup-name))
+
+;;; Header reading.
+
+(defun gnus-read-header (id &optional header)
+  "Read the headers of article ID and enter them into the Gnus system."
+  (let ((group gnus-newsgroup-name)
+	(gnus-override-method
+	 (and (gnus-news-group-p gnus-newsgroup-name)
+	      gnus-refer-article-method))
+	where)
+    ;; First we check to see whether the header in question is already
+    ;; fetched.
+    (if (stringp id)
+	;; This is a Message-ID.
+	(setq header (or header (gnus-id-to-header id)))
+      ;; This is an article number.
+      (setq header (or header (gnus-summary-article-header id))))
+    (if (and header
+	     (not (gnus-summary-article-sparse-p (mail-header-number header))))
+	;; We have found the header.
+	header
+      ;; We have to really fetch the header to this article.
+      (save-excursion
+	(set-buffer nntp-server-buffer)
+	(when (setq where (gnus-request-head id group))
+	  (nnheader-fold-continuation-lines)
+	  (goto-char (point-max))
+	  (insert ".\n")
+	  (goto-char (point-min))
+	  (insert "211 ")
+	  (princ (cond
+		  ((numberp id) id)
+		  ((cdr where) (cdr where))
+		  (header (mail-header-number header))
+		  (t gnus-reffed-article-number))
+		 (current-buffer))
+	  (insert " Article retrieved.\n"))
+	(if (or (not where)
+		(not (setq header (car (gnus-get-newsgroup-headers nil t)))))
+	    ()				; Malformed head.
+	  (unless (gnus-summary-article-sparse-p (mail-header-number header))
+	    (when (and (stringp id)
+		       (not (string= (gnus-group-real-name group)
+				     (car where))))
+	      ;; If we fetched by Message-ID and the article came
+	      ;; from a different group, we fudge some bogus article
+	      ;; numbers for this article.
+	      (mail-header-set-number header gnus-reffed-article-number))
+	    (save-excursion
+	      (set-buffer gnus-summary-buffer)
+	      (decf gnus-reffed-article-number)
+	      (gnus-remove-header (mail-header-number header))
+	      (push header gnus-newsgroup-headers)
+	      (setq gnus-current-headers header)
+	      (push (mail-header-number header) gnus-newsgroup-limit)))
+	  header)))))
+
+(defun gnus-remove-header (number)
+  "Remove header NUMBER from `gnus-newsgroup-headers'."
+  (if (and gnus-newsgroup-headers
+	   (= number (mail-header-number (car gnus-newsgroup-headers))))
+      (pop gnus-newsgroup-headers)
+    (let ((headers gnus-newsgroup-headers))
+      (while (and (cdr headers)
+		  (not (= number (mail-header-number (cadr headers)))))
+	(pop headers))
+      (when (cdr headers)
+	(setcdr headers (cddr headers))))))
+
+;;;
+;;; summary highlights
+;;;
+
+(defun gnus-highlight-selected-summary ()
+  ;; Added by Per Abrahamsen <amanda@iesd.auc.dk>.
+  ;; Highlight selected article in summary buffer
+  (when gnus-summary-selected-face
+    (save-excursion
+      (let* ((beg (progn (beginning-of-line) (point)))
+	     (end (progn (end-of-line) (point)))
+	     ;; Fix by Mike Dugan <dugan@bucrf16.bu.edu>.
+	     (from (if (get-text-property beg gnus-mouse-face-prop)
+		       beg
+		     (or (next-single-property-change
+			  beg gnus-mouse-face-prop nil end)
+			 beg)))
+	     (to
+	      (if (= from end)
+		  (- from 2)
+		(or (next-single-property-change
+		     from gnus-mouse-face-prop nil end)
+		    end))))
+	;; If no mouse-face prop on line we will have to = from = end,
+	;; so we highlight the entire line instead.
+	(when (= (+ to 2) from)
+	  (setq from beg)
+	  (setq to end))
+	(if gnus-newsgroup-selected-overlay
+	    ;; Move old overlay.
+	    (gnus-move-overlay
+	     gnus-newsgroup-selected-overlay from to (current-buffer))
+	  ;; Create new overlay.
+	  (gnus-overlay-put
+	   (setq gnus-newsgroup-selected-overlay (gnus-make-overlay from to))
+	   'face gnus-summary-selected-face))))))
+
+;; New implementation by Christian Limpach <Christian.Limpach@nice.ch>.
+(defun gnus-summary-highlight-line ()
+  "Highlight current line according to `gnus-summary-highlight'."
+  (let* ((list gnus-summary-highlight)
+	 (p (point))
+	 (end (progn (end-of-line) (point)))
+	 ;; now find out where the line starts and leave point there.
+	 (beg (progn (beginning-of-line) (point)))
+	 (article (gnus-summary-article-number))
+	 (score (or (cdr (assq (or article gnus-current-article)
+			       gnus-newsgroup-scored))
+		    gnus-summary-default-score 0))
+	 (mark (or (gnus-summary-article-mark) gnus-unread-mark))
+	 (inhibit-read-only t))
+    ;; Eval the cars of the lists until we find a match.
+    (let ((default gnus-summary-default-score))
+      (while (and list
+		  (not (eval (caar list))))
+	(setq list (cdr list))))
+    (let ((face (cdar list)))
+      (unless (eq face (get-text-property beg 'face))
+	(gnus-put-text-property
+	 beg end 'face
+	 (setq face (if (boundp face) (symbol-value face) face)))
+	(when gnus-summary-highlight-line-function
+	  (funcall gnus-summary-highlight-line-function article face))))
+    (goto-char p)))
+
+(defun gnus-update-read-articles (group unread)
+  "Update the list of read articles in GROUP."
+  (let* ((active (or gnus-newsgroup-active (gnus-active group)))
+	 (entry (gnus-gethash group gnus-newsrc-hashtb))
+	 (info (nth 2 entry))
+	 (prev 1)
+	 (unread (sort (copy-sequence unread) '<))
+	 read)
+    (if (or (not info) (not active))
+	;; There is no info on this group if it was, in fact,
+	;; killed.  Gnus stores no information on killed groups, so
+	;; there's nothing to be done.
+	;; One could store the information somewhere temporarily,
+	;; perhaps...  Hmmm...
+	()
+      ;; Remove any negative articles numbers.
+      (while (and unread (< (car unread) 0))
+	(setq unread (cdr unread)))
+      ;; Remove any expired article numbers
+      (while (and unread (< (car unread) (car active)))
+	(setq unread (cdr unread)))
+      ;; Compute the ranges of read articles by looking at the list of
+      ;; unread articles.
+      (while unread
+	(when (/= (car unread) prev)
+	  (push (if (= prev (1- (car unread))) prev
+		  (cons prev (1- (car unread))))
+		read))
+	(setq prev (1+ (car unread)))
+	(setq unread (cdr unread)))
+      (when (<= prev (cdr active))
+	(push (cons prev (cdr active)) read))
+      (save-excursion
+	(set-buffer gnus-group-buffer)
+	(gnus-undo-register
+	  `(progn
+	     (gnus-info-set-marks ',info ',(gnus-info-marks info) t)
+	     (gnus-info-set-read ',info ',(gnus-info-read info))
+	     (gnus-get-unread-articles-in-group ',info (gnus-active ,group))
+	     (gnus-group-update-group ,group t))))
+      ;; Enter this list into the group info.
+      (gnus-info-set-read
+       info (if (> (length read) 1) (nreverse read) read))
+      ;; Set the number of unread articles in gnus-newsrc-hashtb.
+      (gnus-get-unread-articles-in-group info (gnus-active group))
+      t)))
+
+(defun gnus-offer-save-summaries ()
+  "Offer to save all active summary buffers."
+  (save-excursion
+    (let ((buflist (buffer-list))
+	  buffers bufname)
+      ;; Go through all buffers and find all summaries.
+      (while buflist
+	(and (setq bufname (buffer-name (car buflist)))
+	     (string-match "Summary" bufname)
+	     (save-excursion
+	       (set-buffer bufname)
+	       ;; We check that this is, indeed, a summary buffer.
+	       (and (eq major-mode 'gnus-summary-mode)
+		    ;; Also make sure this isn't bogus.
+		    gnus-newsgroup-prepared
+		    ;; Also make sure that this isn't a dead summary buffer.
+		    (not gnus-dead-summary-mode)))
+	     (push bufname buffers))
+	(setq buflist (cdr buflist)))
+      ;; Go through all these summary buffers and offer to save them.
+      (when buffers
+	(map-y-or-n-p
+	 "Update summary buffer %s? "
+	 (lambda (buf) (switch-to-buffer buf) (gnus-summary-exit))
+	 buffers)))))
+
+(provide 'gnus-sum)
+
+(run-hooks 'gnus-sum-load-hook)
+
+;;; gnus-sum.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-topic.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,1397 @@
+;;; gnus-topic.el --- a folding minor mode for Gnus group buffers
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Ilja Weis <kult@uni-paderborn.de>
+;;	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-group)
+(require 'gnus-start)
+
+(defgroup gnus-topic nil
+  "Group topics."
+  :group 'gnus-group)
+
+(defvar gnus-topic-mode nil
+  "Minor mode for Gnus group buffers.")
+
+(defcustom gnus-topic-mode-hook nil
+  "Hook run in topic mode buffers."
+  :type 'hook
+  :group 'gnus-topic)
+
+(defcustom gnus-topic-line-format "%i[ %(%{%n%}%) -- %A ]%v\n"
+  "Format of topic lines.
+It works along the same lines as a normal formatting string,
+with some simple extensions.
+
+%i  Indentation based on topic level.
+%n  Topic name.
+%v  Nothing if the topic is visible, \"...\" otherwise.
+%g  Number of groups in the topic.
+%a  Number of unread articles in the groups in the topic.
+%A  Number of unread articles in the groups in the topic and its subtopics.
+"
+  :type 'string
+  :group 'gnus-topic)
+
+(defcustom gnus-topic-indent-level 2
+  "*How much each subtopic should be indented."
+  :type 'integer
+  :group 'gnus-topic)
+
+(defcustom gnus-topic-display-empty-topics t
+  "*If non-nil, display the topic lines even of topics that have no unread articles."
+  :type 'boolean
+  :group 'gnus-topic)
+
+;; Internal variables.
+
+(defvar gnus-topic-active-topology nil)
+(defvar gnus-topic-active-alist nil)
+
+(defvar gnus-topology-checked-p nil
+  "Whether the topology has been checked in this session.")
+
+(defvar gnus-topic-killed-topics nil)
+(defvar gnus-topic-inhibit-change-level nil)
+(defvar gnus-topic-tallied-groups nil)
+
+(defconst gnus-topic-line-format-alist
+  `((?n name ?s)
+    (?v visible ?s)
+    (?i indentation ?s)
+    (?g number-of-groups ?d)
+    (?a (gnus-topic-articles-in-topic entries) ?d)
+    (?A total-number-of-articles ?d)
+    (?l level ?d)))
+
+(defvar gnus-topic-line-format-spec nil)
+
+;;; Utility functions
+
+(defun gnus-group-topic-name ()
+  "The name of the topic on the current line."
+  (let ((topic (get-text-property (gnus-point-at-bol) 'gnus-topic)))
+    (and topic (symbol-name topic))))
+
+(defun gnus-group-topic-level ()
+  "The level of the topic on the current line."
+  (get-text-property (gnus-point-at-bol) 'gnus-topic-level))
+
+(defun gnus-group-topic-unread ()
+  "The number of unread articles in topic on the current line."
+  (get-text-property (gnus-point-at-bol) 'gnus-topic-unread))
+
+(defun gnus-topic-unread (topic)
+  "Return the number of unread articles in TOPIC."
+  (or (save-excursion
+	(and (gnus-topic-goto-topic topic)
+	     (gnus-group-topic-unread)))
+      0))
+
+(defun gnus-group-topic-p ()
+  "Return non-nil if the current line is a topic."
+  (gnus-group-topic-name))
+
+(defun gnus-topic-visible-p ()
+  "Return non-nil if the current topic is visible."
+  (get-text-property (gnus-point-at-bol) 'gnus-topic-visible))
+
+(defun gnus-topic-articles-in-topic (entries)
+  (let ((total 0)
+	number)
+    (while entries
+      (when (numberp (setq number (car (pop entries))))
+	(incf total number)))
+    total))
+
+(defun gnus-group-topic (group)
+  "Return the topic GROUP is a member of."
+  (let ((alist gnus-topic-alist)
+	out)
+    (while alist
+      (when (member group (cdar alist))
+	(setq out (caar alist)
+	      alist nil))
+      (setq alist (cdr alist)))
+    out))
+
+(defun gnus-group-parent-topic (group)
+  "Return the topic GROUP is member of by looking at the group buffer."
+  (save-excursion
+    (set-buffer gnus-group-buffer)
+    (if (gnus-group-goto-group group)
+	(gnus-current-topic)
+      (gnus-group-topic group))))
+
+(defun gnus-topic-goto-topic (topic)
+  "Go to TOPIC."
+  (when topic
+    (gnus-goto-char (text-property-any (point-min) (point-max)
+				       'gnus-topic (intern topic)))))
+
+(defun gnus-current-topic ()
+  "Return the name of the current topic."
+  (let ((result
+	 (or (get-text-property (point) 'gnus-topic)
+	     (save-excursion
+	       (and (gnus-goto-char (previous-single-property-change
+				     (point) 'gnus-topic))
+		    (get-text-property (max (1- (point)) (point-min))
+				       'gnus-topic))))))
+    (when result
+      (symbol-name result))))
+
+(defun gnus-current-topics ()
+  "Return a list of all current topics, lowest in hierarchy first."
+  (let ((topic (gnus-current-topic))
+	topics)
+    (while topic
+      (push topic topics)
+      (setq topic (gnus-topic-parent-topic topic)))
+    (nreverse topics)))
+
+(defun gnus-group-active-topic-p ()
+  "Say whether the current topic comes from the active topics."
+  (save-excursion
+    (beginning-of-line)
+    (get-text-property (point) 'gnus-active)))
+
+(defun gnus-topic-find-groups (topic &optional level all)
+  "Return entries for all visible groups in TOPIC."
+  (let ((groups (cdr (assoc topic gnus-topic-alist)))
+        info clevel unread group lowest params visible-groups entry active)
+    (setq lowest (or lowest 1))
+    (setq level (or level 7))
+    ;; We go through the newsrc to look for matches.
+    (while groups
+      (when (setq group (pop groups))
+	(setq entry (gnus-gethash group gnus-newsrc-hashtb)
+	      info (nth 2 entry)
+	      params (gnus-info-params info)
+	      active (gnus-active group)
+	      unread (or (car entry)
+			 (and (not (equal group "dummy.group"))
+			      active
+			      (- (1+ (cdr active)) (car active))))
+	      clevel (or (gnus-info-level info)
+			 (if (member group gnus-zombie-list) 8 9))))
+      (and
+       unread				; nil means that the group is dead.
+       (<= clevel level)
+       (>= clevel lowest)		; Is inside the level we want.
+       (or all
+	   (if (eq unread t)
+	       gnus-group-list-inactive-groups
+	     (> unread 0))
+	   (and gnus-list-groups-with-ticked-articles
+		(cdr (assq 'tick (gnus-info-marks info))))
+					; Has right readedness.
+	   ;; Check for permanent visibility.
+	   (and gnus-permanently-visible-groups
+		(string-match gnus-permanently-visible-groups group))
+	   (memq 'visible params)
+	   (cdr (assq 'visible params)))
+       ;; Add this group to the list of visible groups.
+       (push (or entry group) visible-groups)))
+    (nreverse visible-groups)))
+
+(defun gnus-topic-previous-topic (topic)
+  "Return the previous topic on the same level as TOPIC."
+  (let ((top (cddr (gnus-topic-find-topology
+		    (gnus-topic-parent-topic topic)))))
+    (unless (equal topic (caaar top))
+      (while (and top (not (equal (caaadr top) topic)))
+	(setq top (cdr top)))
+      (caaar top))))
+
+(defun gnus-topic-parent-topic (topic &optional topology)
+  "Return the parent of TOPIC."
+  (unless topology
+    (setq topology gnus-topic-topology))
+  (let ((parent (car (pop topology)))
+	result found)
+    (while (and topology
+		(not (setq found (equal (caaar topology) topic)))
+		(not (setq result (gnus-topic-parent-topic
+				   topic (car topology)))))
+      (setq topology (cdr topology)))
+    (or result (and found parent))))
+
+(defun gnus-topic-next-topic (topic &optional previous)
+  "Return the next sibling of TOPIC."
+  (let ((parentt (cddr (gnus-topic-find-topology
+			(gnus-topic-parent-topic topic))))
+	prev)
+    (while (and parentt
+		(not (equal (caaar parentt) topic)))
+      (setq prev (caaar parentt)
+	    parentt (cdr parentt)))
+    (if previous
+	prev
+      (caaadr parentt))))
+
+(defun gnus-topic-forward-topic (num)
+  "Go to the next topic on the same level as the current one."
+  (let* ((topic (gnus-current-topic))
+	 (way (if (< num 0) 'gnus-topic-previous-topic
+		'gnus-topic-next-topic))
+	 (num (abs num)))
+    (while (and (not (zerop num))
+		(setq topic (funcall way topic)))
+      (when (gnus-topic-goto-topic topic)
+	(decf num)))
+    (unless (zerop num)
+      (goto-char (point-max)))
+    num))
+
+(defun gnus-topic-find-topology (topic &optional topology level remove)
+  "Return the topology of TOPIC."
+  (unless topology
+    (setq topology gnus-topic-topology)
+    (setq level 0))
+  (let ((top topology)
+	result)
+    (if (equal (caar topology) topic)
+	(progn
+	  (when remove
+	    (delq topology remove))
+	  (cons level topology))
+      (setq topology (cdr topology))
+      (while (and topology
+		  (not (setq result (gnus-topic-find-topology
+				     topic (car topology) (1+ level)
+				     (and remove top)))))
+	(setq topology (cdr topology)))
+      result)))
+
+(defvar gnus-tmp-topics nil)
+(defun gnus-topic-list (&optional topology)
+  "Return a list of all topics in the topology."
+  (unless topology
+    (setq topology gnus-topic-topology
+	  gnus-tmp-topics nil))
+  (push (caar topology) gnus-tmp-topics)
+  (mapcar 'gnus-topic-list (cdr topology))
+  gnus-tmp-topics)
+
+;;; Topic parameter jazz
+
+(defun gnus-topic-parameters (topic)
+  "Return the parameters for TOPIC."
+  (let ((top (gnus-topic-find-topology topic)))
+    (when top
+      (nth 3 (cadr top)))))
+
+(defun gnus-topic-set-parameters (topic parameters)
+  "Set the topic parameters of TOPIC to PARAMETERS."
+  (let ((top (gnus-topic-find-topology topic)))
+    (unless top
+      (error "No such topic: %s" topic))
+    ;; We may have to extend if there is no parameters here
+    ;; to begin with.
+    (unless (nthcdr 2 (cadr top))
+      (nconc (cadr top) (list nil)))
+    (unless (nthcdr 3 (cadr top))
+      (nconc (cadr top) (list nil)))
+    (setcar (nthcdr 3 (cadr top)) parameters)
+    (gnus-dribble-enter
+     (format "(gnus-topic-set-parameters %S '%S)" topic parameters))))
+
+(defun gnus-group-topic-parameters (group)
+  "Compute the group parameters for GROUP taking into account inheritance from topics."
+  (let ((params-list (list (gnus-group-get-parameter group)))
+	topics params param out)
+    (save-excursion
+      (gnus-group-goto-group group)
+      (setq topics (gnus-current-topics))
+      (while topics
+	(push (gnus-topic-parameters (pop topics)) params-list))
+      ;; We probably have lots of nil elements here, so
+      ;; we remove them.  Probably faster than doing this "properly".
+      (setq params-list (delq nil params-list))
+      ;; Now we have all the parameters, so we go through them
+      ;; and do inheritance in the obvious way.
+      (while (setq params (pop params-list))
+	(while (setq param (pop params))
+	  (when (atom param)
+	    (setq param (cons param t)))
+	  ;; Override any old versions of this param.
+	  (setq out (delq (assq (car param) out) out))
+	  (push param out)))
+      ;; Return the resulting parameter list.
+      out)))
+
+;;; General utility functions
+
+(defun gnus-topic-enter-dribble ()
+  (gnus-dribble-enter
+   (format "(setq gnus-topic-topology '%S)" gnus-topic-topology)))
+
+;;; Generating group buffers
+
+(defun gnus-group-prepare-topics (level &optional all lowest regexp list-topic topic-level)
+  "List all newsgroups with unread articles of level LEVEL or lower, and
+use the `gnus-group-topics' to sort the groups.
+If ALL is non-nil, list groups that have no unread articles.
+If LOWEST is non-nil, list all newsgroups of level LOWEST or higher."
+  (set-buffer gnus-group-buffer)
+  (let ((buffer-read-only nil)
+        (lowest (or lowest 1)))
+
+    (setq gnus-topic-tallied-groups nil)
+
+    (when (or (not gnus-topic-alist)
+	      (not gnus-topology-checked-p))
+      (gnus-topic-check-topology))
+
+    (unless list-topic
+      (erase-buffer))
+
+    ;; List dead groups?
+    (when (and (>= level gnus-level-zombie) (<= lowest gnus-level-zombie))
+      (gnus-group-prepare-flat-list-dead
+       (setq gnus-zombie-list (sort gnus-zombie-list 'string<))
+       gnus-level-zombie ?Z
+       regexp))
+
+    (when (and (>= level gnus-level-killed) (<= lowest gnus-level-killed))
+      (gnus-group-prepare-flat-list-dead
+       (setq gnus-killed-list (sort gnus-killed-list 'string<))
+       gnus-level-killed ?K
+       regexp))
+
+    ;; Use topics.
+    (prog1
+	(when (< lowest gnus-level-zombie)
+	  (if list-topic
+	      (let ((top (gnus-topic-find-topology list-topic)))
+		(gnus-topic-prepare-topic (cdr top) (car top)
+					  (or topic-level level) all))
+	    (gnus-topic-prepare-topic gnus-topic-topology 0
+				      (or topic-level level) all)))
+
+      (gnus-group-set-mode-line)
+      (setq gnus-group-list-mode (cons level all))
+      (run-hooks 'gnus-group-prepare-hook))))
+
+(defun gnus-topic-prepare-topic (topicl level &optional list-level all silent)
+  "Insert TOPIC into the group buffer.
+If SILENT, don't insert anything.  Return the number of unread
+articles in the topic and its subtopics."
+  (let* ((type (pop topicl))
+	 (entries (gnus-topic-find-groups (car type) list-level all))
+	 (visiblep (and (eq (nth 1 type) 'visible) (not silent)))
+	 (gnus-group-indentation
+	  (make-string (* gnus-topic-indent-level level) ? ))
+	 (beg (progn (beginning-of-line) (point)))
+	 (topicl (reverse topicl))
+	 (all-entries entries)
+	 (point-max (point-max))
+	 (unread 0)
+	 (topic (car type))
+	 info entry end active tick)
+    ;; Insert any sub-topics.
+    (while topicl
+      (incf unread
+	    (gnus-topic-prepare-topic
+	     (pop topicl) (1+ level) list-level all
+	     (not visiblep))))
+    (setq end (point))
+    (goto-char beg)
+    ;; Insert all the groups that belong in this topic.
+    (while (setq entry (pop entries))
+      (when visiblep
+	(if (stringp entry)
+	    ;; Dead groups.
+	    (gnus-group-insert-group-line
+	     entry (if (member entry gnus-zombie-list) 8 9)
+	     nil (- (1+ (cdr (setq active (gnus-active entry))))
+		    (car active))
+	     nil)
+	  ;; Living groups.
+	  (when (setq info (nth 2 entry))
+	    (gnus-group-insert-group-line
+	     (gnus-info-group info)
+	     (gnus-info-level info) (gnus-info-marks info)
+	     (car entry) (gnus-info-method info)))))
+      (when (and (listp entry)
+		 (numberp (car entry))
+		 (not (member (gnus-info-group (setq info (nth 2 entry)))
+			      gnus-topic-tallied-groups)))
+	(push (gnus-info-group info) gnus-topic-tallied-groups)
+	(incf unread (car entry)))
+      (when (listp entry)
+	(setq tick t)))
+    (goto-char beg)
+    ;; Insert the topic line.
+    (when (and (not silent)
+	       (or gnus-topic-display-empty-topics ;We want empty topics
+		   (not (zerop unread))	;Non-empty
+		   tick			;Ticked articles
+		   (/= point-max (point-max)))) ;Unactivated groups
+      (gnus-extent-start-open (point))
+      (gnus-topic-insert-topic-line
+       (car type) visiblep
+       (not (eq (nth 2 type) 'hidden))
+       level all-entries unread))
+    (goto-char end)
+    unread))
+
+(defun gnus-topic-remove-topic (&optional insert total-remove hide in-level)
+  "Remove the current topic."
+  (let ((topic (gnus-group-topic-name))
+	(level (gnus-group-topic-level))
+	(beg (progn (beginning-of-line) (point)))
+	buffer-read-only)
+    (when topic
+      (while (and (zerop (forward-line 1))
+		  (> (or (gnus-group-topic-level) (1+ level)) level)))
+      (delete-region beg (point))
+      ;; Do the change in this rather odd manner because it has been
+      ;; reported that some topics share parts of some lists, for some
+      ;; reason.  I have been unable to determine why this is the
+      ;; case, but this hack seems to take care of things.
+      (let ((data (cadr (gnus-topic-find-topology topic))))
+	(setcdr data
+		(list (if insert 'visible 'invisible)
+		      (if hide 'hide nil)
+		      (cadddr data))))
+      (if total-remove
+	  (setq gnus-topic-alist
+		(delq (assoc topic gnus-topic-alist) gnus-topic-alist))
+	(gnus-topic-insert-topic topic in-level)))))
+
+(defun gnus-topic-insert-topic (topic &optional level)
+  "Insert TOPIC."
+  (gnus-group-prepare-topics
+   (car gnus-group-list-mode) (cdr gnus-group-list-mode)
+   nil nil topic level))
+
+(defun gnus-topic-fold (&optional insert)
+  "Remove/insert the current topic."
+  (let ((topic (gnus-group-topic-name)))
+    (when topic
+      (save-excursion
+	(if (not (gnus-group-active-topic-p))
+	    (gnus-topic-remove-topic
+	     (or insert (not (gnus-topic-visible-p))))
+	  (let ((gnus-topic-topology gnus-topic-active-topology)
+		(gnus-topic-alist gnus-topic-active-alist)
+		(gnus-group-list-mode (cons 5 t)))
+	    (gnus-topic-remove-topic
+	     (or insert (not (gnus-topic-visible-p))) nil nil 9)
+	    (gnus-topic-enter-dribble)))))))
+
+(defun gnus-topic-insert-topic-line (name visiblep shownp level entries
+					  &optional unread)
+  (let* ((visible (if visiblep "" "..."))
+	 (indentation (make-string (* gnus-topic-indent-level level) ? ))
+	 (total-number-of-articles unread)
+	 (number-of-groups (length entries))
+	 (active-topic (eq gnus-topic-alist gnus-topic-active-alist)))
+    (beginning-of-line)
+    ;; Insert the text.
+    (gnus-add-text-properties
+     (point)
+     (prog1 (1+ (point))
+       (eval gnus-topic-line-format-spec)
+       (gnus-topic-remove-excess-properties)1)
+     (list 'gnus-topic (intern name)
+	   'gnus-topic-level level
+	   'gnus-topic-unread unread
+	   'gnus-active active-topic
+	   'gnus-topic-visible visiblep))))
+
+(defun gnus-topic-update-topics-containing-group (group)
+  "Update all topics that have GROUP as a member."
+  (when (and (eq major-mode 'gnus-group-mode)
+	     gnus-topic-mode)
+    (save-excursion
+      (let ((alist gnus-topic-alist))
+	;; This is probably not entirely correct.  If a topic
+	;; isn't shown, then it's not updated.  But the updating
+	;; should be performed in any case, since the topic's
+	;; parent should be updated.  Pfft.
+	(while alist
+	  (when (and (member group (cdar alist))
+		     (gnus-topic-goto-topic (caar alist)))
+	    (gnus-topic-update-topic-line (caar alist)))
+	  (pop alist))))))
+
+(defun gnus-topic-update-topic ()
+  "Update all parent topics to the current group."
+  (when (and (eq major-mode 'gnus-group-mode)
+	     gnus-topic-mode)
+    (let ((group (gnus-group-group-name))
+	  (buffer-read-only nil))
+      (when (and group
+		 (gnus-get-info group)
+		 (gnus-topic-goto-topic (gnus-current-topic)))
+	(gnus-topic-update-topic-line (gnus-group-topic-name))
+	(gnus-group-goto-group group)
+	(gnus-group-position-point)))))
+
+(defun gnus-topic-goto-missing-group (group)
+  "Place point where GROUP is supposed to be inserted."
+  (let* ((topic (gnus-group-topic group))
+	 (groups (cdr (assoc topic gnus-topic-alist)))
+	 (g (cdr (member group groups)))
+	 (unfound t))
+    ;; Try to jump to a visible group.
+    (while (and g (not (gnus-group-goto-group (car g) t)))
+      (pop g))
+    ;; It wasn't visible, so we try to see where to insert it.
+    (when (not g)
+      (setq g (cdr (member group (reverse groups))))
+      (while (and g unfound)
+	(when (gnus-group-goto-group (pop g) t)
+	  (forward-line 1)
+	  (setq unfound nil)))
+      (when (and unfound
+		 topic
+		 (not (gnus-topic-goto-missing-topic topic)))
+	(gnus-topic-insert-topic-line
+	 topic t t (car (gnus-topic-find-topology topic)) nil 0)))))
+
+(defun gnus-topic-goto-missing-topic (topic)
+  (if (gnus-topic-goto-topic topic)
+      (forward-line 1)
+    ;; Topic not displayed.
+    (let* ((top (gnus-topic-find-topology
+		 (gnus-topic-parent-topic topic)))
+	   (tp (reverse (cddr top))))
+      (while (not (equal (caaar tp) topic))
+	(setq tp (cdr tp)))
+      (pop tp)
+      (while (and tp
+		  (not (gnus-topic-goto-topic (caaar tp))))
+	(pop tp))
+      (if tp
+	  (gnus-topic-forward-topic 1)
+	(gnus-topic-goto-missing-topic (caadr top))))
+    nil))
+
+(defun gnus-topic-update-topic-line (topic-name &optional reads)
+  (let* ((top (gnus-topic-find-topology topic-name))
+	 (type (cadr top))
+	 (children (cddr top))
+	 (entries (gnus-topic-find-groups
+		   (car type) (car gnus-group-list-mode)
+		   (cdr gnus-group-list-mode)))
+	 (parent (gnus-topic-parent-topic topic-name))
+	 (all-entries entries)
+	 (unread 0)
+	 old-unread entry)
+    (when (gnus-topic-goto-topic (car type))
+      ;; Tally all the groups that belong in this topic.
+      (if reads
+	  (setq unread (- (gnus-group-topic-unread) reads))
+	(while children
+	  (incf unread (gnus-topic-unread (caar (pop children)))))
+	(while (setq entry (pop entries))
+	  (when (numberp (car entry))
+	    (incf unread (car entry)))))
+      (setq old-unread (gnus-group-topic-unread))
+      ;; Insert the topic line.
+      (gnus-topic-insert-topic-line
+       (car type) (gnus-topic-visible-p)
+       (not (eq (nth 2 type) 'hidden))
+       (gnus-group-topic-level) all-entries unread)
+      (gnus-delete-line))
+    (when parent
+      (forward-line -1)
+      (gnus-topic-update-topic-line
+       parent (- old-unread (gnus-group-topic-unread))))
+    unread))
+
+(defun gnus-topic-group-indentation ()
+  (make-string
+   (* gnus-topic-indent-level
+      (or (save-excursion
+	    (forward-line -1)
+	    (gnus-topic-goto-topic (gnus-current-topic))
+	    (gnus-group-topic-level))
+	  0))
+   ? ))
+
+;;; Initialization
+
+(gnus-add-shutdown 'gnus-topic-close 'gnus)
+
+(defun gnus-topic-close ()
+  (setq gnus-topic-active-topology nil
+	gnus-topic-active-alist nil
+	gnus-topic-killed-topics nil
+	gnus-topic-tallied-groups nil
+	gnus-topology-checked-p nil))
+
+(defun gnus-topic-check-topology ()
+  ;; The first time we set the topology to whatever we have
+  ;; gotten here, which can be rather random.
+  (unless gnus-topic-alist
+    (gnus-topic-init-alist))
+
+  (setq gnus-topology-checked-p t)
+  ;; Go through the topic alist and make sure that all topics
+  ;; are in the topic topology.
+  (let ((topics (gnus-topic-list))
+	(alist gnus-topic-alist)
+	changed)
+    (while alist
+      (unless (member (caar alist) topics)
+	(nconc gnus-topic-topology
+	       (list (list (list (caar alist) 'visible))))
+	(setq changed t))
+      (setq alist (cdr alist)))
+    (when changed
+      (gnus-topic-enter-dribble))
+    ;; Conversely, go through the topology and make sure that all
+    ;; topologies have alists.
+    (while topics
+      (unless (assoc (car topics) gnus-topic-alist)
+	(push (list (car topics)) gnus-topic-alist))
+      (pop topics)))
+  ;; Go through all living groups and make sure that
+  ;; they belong to some topic.
+  (let* ((tgroups (apply 'append (mapcar (lambda (entry) (cdr entry))
+					 gnus-topic-alist)))
+	 (entry (assoc (caar gnus-topic-topology) gnus-topic-alist))
+	 (newsrc (cdr gnus-newsrc-alist))
+	 group)
+    (while newsrc
+      (unless (member (setq group (gnus-info-group (pop newsrc))) tgroups)
+	(setcdr entry (cons group (cdr entry))))))
+  ;; Go through all topics and make sure they contain only living groups.
+  (let ((alist gnus-topic-alist)
+	topic)
+    (while (setq topic (pop alist))
+      (while (cdr topic)
+	(if (gnus-gethash (cadr topic) gnus-newsrc-hashtb)
+	    (setq topic (cdr topic))
+	  (setcdr topic (cddr topic)))))))
+
+(defun gnus-topic-init-alist ()
+  "Initialize the topic structures."
+  (setq gnus-topic-topology
+	(cons (list "Gnus" 'visible)
+	      (mapcar (lambda (topic)
+			(list (list (car topic) 'visible)))
+		      '(("misc")))))
+  (setq gnus-topic-alist
+	(list (cons "misc"
+		    (mapcar (lambda (info) (gnus-info-group info))
+			    (cdr gnus-newsrc-alist)))
+	      (list "Gnus")))
+  (gnus-topic-enter-dribble))
+
+;;; Maintenance
+
+(defun gnus-topic-clean-alist ()
+  "Remove bogus groups from the topic alist."
+  (let ((topic-alist gnus-topic-alist)
+	result topic)
+    (unless gnus-killed-hashtb
+      (gnus-make-hashtable-from-killed))
+    (while (setq topic (pop topic-alist))
+      (let ((topic-name (pop topic))
+	    group filtered-topic)
+	(while (setq group (pop topic))
+	  (when (and (or (gnus-gethash group gnus-active-hashtb)
+			 (gnus-info-method (gnus-get-info group)))
+		     (not (gnus-gethash group gnus-killed-hashtb)))
+	    (push group filtered-topic)))
+	(push (cons topic-name (nreverse filtered-topic)) result)))
+    (setq gnus-topic-alist (nreverse result))))
+
+(defun gnus-topic-change-level (group level oldlevel)
+  "Run when changing levels to enter/remove groups from topics."
+  (save-excursion
+    (set-buffer gnus-group-buffer)
+    (when (and gnus-topic-mode
+	       gnus-topic-alist
+	       (not gnus-topic-inhibit-change-level))
+      ;; Remove the group from the topics.
+      (when (and (< oldlevel gnus-level-zombie)
+		 (>= level gnus-level-zombie))
+	(let (alist)
+	  (forward-line -1)
+	  (when (setq alist (assoc (gnus-current-topic) gnus-topic-alist))
+	    (setcdr alist (gnus-delete-first group (cdr alist))))))
+      ;; If the group is subscribed we enter it into the topics.
+      (when (and (< level gnus-level-zombie)
+		 (>= oldlevel gnus-level-zombie))
+	(let* ((prev (gnus-group-group-name))
+	       (gnus-topic-inhibit-change-level t)
+	       (gnus-group-indentation
+		(make-string
+		 (* gnus-topic-indent-level
+		    (or (save-excursion
+			  (gnus-topic-goto-topic (gnus-current-topic))
+			  (gnus-group-topic-level))
+			0))
+		 ? ))
+	       (yanked (list group))
+	       alist talist end)
+	  ;; Then we enter the yanked groups into the topics they belong
+	  ;; to.
+	  (when (setq alist (assoc (save-excursion
+				     (forward-line -1)
+				     (or
+				      (gnus-current-topic)
+				      (caar gnus-topic-topology)))
+				   gnus-topic-alist))
+	    (setq talist alist)
+	    (when (stringp yanked)
+	      (setq yanked (list yanked)))
+	    (if (not prev)
+		(nconc alist yanked)
+	      (if (not (cdr alist))
+		  (setcdr alist (nconc yanked (cdr alist)))
+		(while (and (not end) (cdr alist))
+		  (when (equal (cadr alist) prev)
+		    (setcdr alist (nconc yanked (cdr alist)))
+		    (setq end t))
+		  (setq alist (cdr alist)))
+		(unless end
+		  (nconc talist yanked))))))
+	(gnus-topic-update-topic)))))
+
+(defun gnus-topic-goto-next-group (group props)
+  "Go to group or the next group after group."
+  (if (not group)
+      (if (not (memq 'gnus-topic props))
+	  (goto-char (point-max))
+	(gnus-topic-goto-topic (symbol-name (cadr (memq 'gnus-topic props)))))
+    (if (gnus-group-goto-group group)
+	t
+      ;; The group is no longer visible.
+      (let* ((list (assoc (gnus-group-topic group) gnus-topic-alist))
+	     (after (cdr (member group (cdr list)))))
+	;; First try to put point on a group after the current one.
+	(while (and after
+		    (not (gnus-group-goto-group (car after))))
+	  (setq after (cdr after)))
+	;; Then try to put point on a group before point.
+	(unless after
+	  (setq after (cdr (member group (reverse (cdr list)))))
+	  (while (and after
+		      (not (gnus-group-goto-group (car after))))
+	    (setq after (cdr after))))
+	;; Finally, just put point on the topic.
+	(if (not (car list))
+	    (goto-char (point-min))
+	  (unless after
+	    (gnus-topic-goto-topic (car list))
+	    (setq after nil)))
+	t))))
+
+;;; Topic-active functions
+
+(defun gnus-topic-grok-active (&optional force)
+  "Parse all active groups and create topic structures for them."
+  ;; First we make sure that we have really read the active file.
+  (when (or force
+	    (not gnus-topic-active-alist))
+    (let (groups)
+      ;; Get a list of all groups available.
+      (mapatoms (lambda (g) (when (symbol-value g)
+			      (push (symbol-name g) groups)))
+		gnus-active-hashtb)
+      (setq groups (sort groups 'string<))
+      ;; Init the variables.
+      (setq gnus-topic-active-topology (list (list "" 'visible)))
+      (setq gnus-topic-active-alist nil)
+      ;; Descend the top-level hierarchy.
+      (gnus-topic-grok-active-1 gnus-topic-active-topology groups)
+      ;; Set the top-level topic names to something nice.
+      (setcar (car gnus-topic-active-topology) "Gnus active")
+      (setcar (car gnus-topic-active-alist) "Gnus active"))))
+
+(defun gnus-topic-grok-active-1 (topology groups)
+  (let* ((name (caar topology))
+	 (prefix (concat "^" (regexp-quote name)))
+	 tgroups ntopology group)
+    (while (and groups
+		(string-match prefix (setq group (car groups))))
+      (if (not (string-match "\\." group (match-end 0)))
+	  ;; There are no further hierarchies here, so we just
+	  ;; enter this group into the list belonging to this
+	  ;; topic.
+	  (push (pop groups) tgroups)
+	;; New sub-hierarchy, so we add it to the topology.
+	(nconc topology (list (setq ntopology
+				    (list (list (substring
+						 group 0 (match-end 0))
+						'invisible)))))
+	;; Descend the hierarchy.
+	(setq groups (gnus-topic-grok-active-1 ntopology groups))))
+    ;; We remove the trailing "." from the topic name.
+    (setq name
+	  (if (string-match "\\.$" name)
+	      (substring name 0 (match-beginning 0))
+	    name))
+    ;; Add this topic and its groups to the topic alist.
+    (push (cons name (nreverse tgroups)) gnus-topic-active-alist)
+    (setcar (car topology) name)
+    ;; We return the rest of the groups that didn't belong
+    ;; to this topic.
+    groups))
+
+;;; Topic mode, commands and keymap.
+
+(defvar gnus-topic-mode-map nil)
+(defvar gnus-group-topic-map nil)
+
+(unless gnus-topic-mode-map
+  (setq gnus-topic-mode-map (make-sparse-keymap))
+
+  ;; Override certain group mode keys.
+  (gnus-define-keys gnus-topic-mode-map
+    "=" gnus-topic-select-group
+    "\r" gnus-topic-select-group
+    " " gnus-topic-read-group
+    "\C-k" gnus-topic-kill-group
+    "\C-y" gnus-topic-yank-group
+    "\M-g" gnus-topic-get-new-news-this-topic
+    "AT" gnus-topic-list-active
+    "Gp" gnus-topic-edit-parameters
+    "#" gnus-topic-mark-topic
+    "\M-#" gnus-topic-unmark-topic
+    gnus-mouse-2 gnus-mouse-pick-topic)
+
+  ;; Define a new submap.
+  (gnus-define-keys (gnus-group-topic-map "T" gnus-group-mode-map)
+    "#" gnus-topic-mark-topic
+    "\M-#" gnus-topic-unmark-topic
+    "n" gnus-topic-create-topic
+    "m" gnus-topic-move-group
+    "D" gnus-topic-remove-group
+    "c" gnus-topic-copy-group
+    "h" gnus-topic-hide-topic
+    "s" gnus-topic-show-topic
+    "M" gnus-topic-move-matching
+    "C" gnus-topic-copy-matching
+    "\C-i" gnus-topic-indent
+    [tab] gnus-topic-indent
+    "r" gnus-topic-rename
+    "\177" gnus-topic-delete)
+
+  (gnus-define-keys (gnus-topic-sort-map "S" gnus-group-topic-map)
+    "s" gnus-topic-sort-groups
+    "a" gnus-topic-sort-groups-by-alphabet
+    "u" gnus-topic-sort-groups-by-unread
+    "l" gnus-topic-sort-groups-by-level
+    "v" gnus-topic-sort-groups-by-score
+    "r" gnus-topic-sort-groups-by-rank
+    "m" gnus-topic-sort-groups-by-method))
+
+(defun gnus-topic-make-menu-bar ()
+  (unless (boundp 'gnus-topic-menu)
+    (easy-menu-define
+     gnus-topic-menu gnus-topic-mode-map ""
+     '("Topics"
+       ["Toggle topics" gnus-topic-mode t]
+       ("Groups"
+	["Copy" gnus-topic-copy-group t]
+	["Move" gnus-topic-move-group t]
+	["Remove" gnus-topic-remove-group t]
+	["Copy matching" gnus-topic-copy-matching t]
+	["Move matching" gnus-topic-move-matching t])
+       ("Topics"
+	["Show" gnus-topic-show-topic t]
+	["Hide" gnus-topic-hide-topic t]
+	["Delete" gnus-topic-delete t]
+	["Rename" gnus-topic-rename t]
+	["Create" gnus-topic-create-topic t]
+	["Mark" gnus-topic-mark-topic t]
+	["Indent" gnus-topic-indent t])
+       ["List active" gnus-topic-list-active t]))))
+
+(defun gnus-topic-mode (&optional arg redisplay)
+  "Minor mode for topicsifying Gnus group buffers."
+  (interactive (list current-prefix-arg t))
+  (when (eq major-mode 'gnus-group-mode)
+    (make-local-variable 'gnus-topic-mode)
+    (setq gnus-topic-mode
+	  (if (null arg) (not gnus-topic-mode)
+	    (> (prefix-numeric-value arg) 0)))
+    ;; Infest Gnus with topics.
+    (when gnus-topic-mode
+      (when (gnus-visual-p 'topic-menu 'menu)
+	(gnus-topic-make-menu-bar))
+      (setq gnus-topic-line-format-spec
+	    (gnus-parse-format gnus-topic-line-format
+			       gnus-topic-line-format-alist t))
+      (unless (assq 'gnus-topic-mode minor-mode-alist)
+	(push '(gnus-topic-mode " Topic") minor-mode-alist))
+      (unless (assq 'gnus-topic-mode minor-mode-map-alist)
+	(push (cons 'gnus-topic-mode gnus-topic-mode-map)
+	      minor-mode-map-alist))
+      (add-hook 'gnus-summary-exit-hook 'gnus-topic-update-topic)
+      (add-hook 'gnus-group-catchup-group-hook 'gnus-topic-update-topic)
+      (set (make-local-variable 'gnus-group-prepare-function)
+	   'gnus-group-prepare-topics)
+      (set (make-local-variable 'gnus-group-get-parameter-function)
+	   'gnus-group-topic-parameters)
+      (set (make-local-variable 'gnus-group-goto-next-group-function)
+	   'gnus-topic-goto-next-group)
+      (set (make-local-variable 'gnus-group-indentation-function)
+	   'gnus-topic-group-indentation)
+      (set (make-local-variable 'gnus-group-update-group-function)
+	   'gnus-topic-update-topics-containing-group)
+      (set (make-local-variable 'gnus-group-sort-alist-function)
+	   'gnus-group-sort-topic)
+      (setq gnus-group-change-level-function 'gnus-topic-change-level)
+      (setq gnus-goto-missing-group-function 'gnus-topic-goto-missing-group)
+      (make-local-hook 'gnus-check-bogus-groups-hook)
+      (add-hook 'gnus-check-bogus-groups-hook 'gnus-topic-clean-alist)
+      (setq gnus-topology-checked-p nil)
+      ;; We check the topology.
+      (when gnus-newsrc-alist
+	(gnus-topic-check-topology))
+      (run-hooks 'gnus-topic-mode-hook))
+    ;; Remove topic infestation.
+    (unless gnus-topic-mode
+      (remove-hook 'gnus-summary-exit-hook 'gnus-topic-update-topic)
+      (remove-hook 'gnus-group-change-level-function
+		   'gnus-topic-change-level)
+      (remove-hook 'gnus-check-bogus-groups-hook 'gnus-topic-clean-alist)
+      (setq gnus-group-prepare-function 'gnus-group-prepare-flat)
+      (setq gnus-group-sort-alist-function 'gnus-group-sort-flat))
+    (when redisplay
+      (gnus-group-list-groups))))
+
+(defun gnus-topic-select-group (&optional all)
+  "Select this newsgroup.
+No article is selected automatically.
+If ALL is non-nil, already read articles become readable.
+If ALL is a number, fetch this number of articles.
+
+If performed over a topic line, toggle folding the topic."
+  (interactive "P")
+  (if (gnus-group-topic-p)
+      (let ((gnus-group-list-mode
+	     (if all (cons (if (numberp all) all 7) t) gnus-group-list-mode)))
+	(gnus-topic-fold all))
+    (gnus-group-select-group all)))
+
+(defun gnus-mouse-pick-topic (e)
+  "Select the group or topic under the mouse pointer."
+  (interactive "e")
+  (mouse-set-point e)
+  (gnus-topic-read-group nil))
+
+(defun gnus-topic-read-group (&optional all no-article group)
+  "Read news in this newsgroup.
+If the prefix argument ALL is non-nil, already read articles become
+readable.  IF ALL is a number, fetch this number of articles.  If the
+optional argument NO-ARTICLE is non-nil, no article will be
+auto-selected upon group entry.  If GROUP is non-nil, fetch that
+group.
+
+If performed over a topic line, toggle folding the topic."
+  (interactive "P")
+  (if (gnus-group-topic-p)
+      (let ((gnus-group-list-mode
+	     (if all (cons (if (numberp all) all 7) t) gnus-group-list-mode)))
+	(gnus-topic-fold all))
+    (gnus-group-read-group all no-article group)))
+
+(defun gnus-topic-create-topic (topic parent &optional previous full-topic)
+  (interactive
+   (list
+    (read-string "New topic: ")
+    (gnus-current-topic)))
+  ;; Check whether this topic already exists.
+  (when (gnus-topic-find-topology topic)
+    (error "Topic already exists"))
+  (unless parent
+    (setq parent (caar gnus-topic-topology)))
+  (let ((top (cdr (gnus-topic-find-topology parent)))
+	(full-topic (or full-topic `((,topic visible)))))
+    (unless top
+      (error "No such parent topic: %s" parent))
+    (if previous
+	(progn
+	  (while (and (cdr top)
+		      (not (equal (caaadr top) previous)))
+	    (setq top (cdr top)))
+	  (setcdr top (cons full-topic (cdr top))))
+      (nconc top (list full-topic)))
+    (unless (assoc topic gnus-topic-alist)
+      (push (list topic) gnus-topic-alist)))
+  (gnus-topic-enter-dribble)
+  (gnus-group-list-groups)
+  (gnus-topic-goto-topic topic))
+
+(defun gnus-topic-move-group (n topic &optional copyp)
+  "Move the next N groups to TOPIC.
+If COPYP, copy the groups instead."
+  (interactive
+   (list current-prefix-arg
+	 (completing-read "Move to topic: " gnus-topic-alist nil t)))
+  (let ((groups (gnus-group-process-prefix n))
+	(topicl (assoc topic gnus-topic-alist))
+	(start-group (progn (forward-line 1) (gnus-group-group-name)))
+	(start-topic (gnus-group-topic-name))
+	entry)
+    (mapcar
+     (lambda (g)
+       (gnus-group-remove-mark g)
+       (when (and
+	      (setq entry (assoc (gnus-current-topic) gnus-topic-alist))
+	      (not copyp))
+	 (setcdr entry (gnus-delete-first g (cdr entry))))
+       (nconc topicl (list g)))
+     groups)
+    (gnus-topic-enter-dribble)
+    (if start-group
+	(gnus-group-goto-group start-group)
+      (gnus-topic-goto-topic start-topic))
+    (gnus-group-list-groups)))
+
+(defun gnus-topic-remove-group (&optional arg)
+  "Remove the current group from the topic."
+  (interactive "P")
+  (gnus-group-iterate arg
+    (lambda (group)
+      (let ((topicl (assoc (gnus-current-topic) gnus-topic-alist))
+	    (buffer-read-only nil))
+	(when (and topicl group)
+	  (gnus-delete-line)
+	  (gnus-delete-first group topicl))
+	(gnus-topic-update-topic)
+	(gnus-group-position-point)))))
+
+(defun gnus-topic-copy-group (n topic)
+  "Copy the current group to a topic."
+  (interactive
+   (list current-prefix-arg
+	 (completing-read "Copy to topic: " gnus-topic-alist nil t)))
+  (gnus-topic-move-group n topic t))
+
+(defun gnus-topic-kill-group (&optional n discard)
+  "Kill the next N groups."
+  (interactive "P")
+  (if (gnus-group-topic-p)
+      (let ((topic (gnus-group-topic-name)))
+	(push (cons
+	       (gnus-topic-find-topology topic)
+	       (assoc topic gnus-topic-alist))
+	      gnus-topic-killed-topics)
+	(gnus-topic-remove-topic nil t)
+	(gnus-topic-find-topology topic nil nil gnus-topic-topology)
+	(gnus-topic-enter-dribble))
+    (gnus-group-kill-group n discard)
+    (gnus-topic-update-topic)))
+
+(defun gnus-topic-yank-group (&optional arg)
+  "Yank the last topic."
+  (interactive "p")
+  (if gnus-topic-killed-topics
+      (let* ((previous
+	      (or (gnus-group-topic-name)
+		  (gnus-topic-next-topic (gnus-current-topic))))
+	     (data (pop gnus-topic-killed-topics))
+	     (alist (cdr data))
+	     (item (cdar data)))
+	(push alist gnus-topic-alist)
+	(gnus-topic-create-topic
+	 (caar item) (gnus-topic-parent-topic previous) previous
+	 item)
+	(gnus-topic-enter-dribble)
+	(gnus-topic-goto-topic (caar item)))
+    (let* ((prev (gnus-group-group-name))
+	   (gnus-topic-inhibit-change-level t)
+	   (gnus-group-indentation
+	    (make-string
+	     (* gnus-topic-indent-level
+		(or (save-excursion
+		      (gnus-topic-goto-topic (gnus-current-topic))
+		      (gnus-group-topic-level))
+		    0))
+	     ? ))
+	   yanked alist)
+      ;; We first yank the groups the normal way...
+      (setq yanked (gnus-group-yank-group arg))
+      ;; Then we enter the yanked groups into the topics they belong
+      ;; to.
+      (setq alist (assoc (save-excursion
+			   (forward-line -1)
+			   (gnus-current-topic))
+			 gnus-topic-alist))
+      (when (stringp yanked)
+	(setq yanked (list yanked)))
+      (if (not prev)
+	  (nconc alist yanked)
+	(if (not (cdr alist))
+	    (setcdr alist (nconc yanked (cdr alist)))
+	  (while (cdr alist)
+	    (when (equal (cadr alist) prev)
+	      (setcdr alist (nconc yanked (cdr alist)))
+	      (setq alist nil))
+	    (setq alist (cdr alist))))))
+    (gnus-topic-update-topic)))
+
+(defun gnus-topic-hide-topic ()
+  "Hide the current topic."
+  (interactive)
+  (when (gnus-current-topic)
+    (gnus-topic-goto-topic (gnus-current-topic))
+    (gnus-topic-remove-topic nil nil 'hidden)))
+
+(defun gnus-topic-show-topic ()
+  "Show the hidden topic."
+  (interactive)
+  (when (gnus-group-topic-p)
+    (gnus-topic-remove-topic t nil 'shown)))
+
+(defun gnus-topic-mark-topic (topic &optional unmark)
+  "Mark all groups in the topic with the process mark."
+  (interactive (list (gnus-group-topic-name)))
+  (if (not topic)
+      (call-interactively 'gnus-group-mark-group)
+    (save-excursion
+      (let ((groups (gnus-topic-find-groups topic 9 t)))
+	(while groups
+	  (funcall (if unmark 'gnus-group-remove-mark 'gnus-group-set-mark)
+		   (gnus-info-group (nth 2 (pop groups)))))))))
+
+(defun gnus-topic-unmark-topic (topic &optional unmark)
+  "Remove the process mark from all groups in the topic."
+  (interactive (list (gnus-group-topic-name)))
+  (if (not topic)
+      (call-interactively 'gnus-group-unmark-group)
+    (gnus-topic-mark-topic topic t)))
+
+(defun gnus-topic-get-new-news-this-topic (&optional n)
+  "Check for new news in the current topic."
+  (interactive "P")
+  (if (not (gnus-group-topic-p))
+      (gnus-group-get-new-news-this-group n)
+    (gnus-topic-mark-topic (gnus-group-topic-name))
+    (gnus-group-get-new-news-this-group)))
+
+(defun gnus-topic-move-matching (regexp topic &optional copyp)
+  "Move all groups that match REGEXP to some topic."
+  (interactive
+   (let (topic)
+     (nreverse
+      (list
+       (setq topic (completing-read "Move to topic: " gnus-topic-alist nil t))
+       (read-string (format "Move to %s (regexp): " topic))))))
+  (gnus-group-mark-regexp regexp)
+  (gnus-topic-move-group nil topic copyp))
+
+(defun gnus-topic-copy-matching (regexp topic &optional copyp)
+  "Copy all groups that match REGEXP to some topic."
+  (interactive
+   (let (topic)
+     (nreverse
+      (list
+       (setq topic (completing-read "Copy to topic: " gnus-topic-alist nil t))
+       (read-string (format "Copy to %s (regexp): " topic))))))
+  (gnus-topic-move-matching regexp topic t))
+
+(defun gnus-topic-delete (topic)
+  "Delete a topic."
+  (interactive (list (gnus-group-topic-name)))
+  (unless topic
+    (error "No topic to be deleted"))
+  (let ((entry (assoc topic gnus-topic-alist))
+	(buffer-read-only nil))
+    (when (cdr entry)
+      (error "Topic not empty"))
+    ;; Delete if visible.
+    (when (gnus-topic-goto-topic topic)
+      (gnus-delete-line))
+    ;; Remove from alist.
+    (setq gnus-topic-alist (delq entry gnus-topic-alist))
+    ;; Remove from topology.
+    (gnus-topic-find-topology topic nil nil 'delete)))
+
+(defun gnus-topic-rename (old-name new-name)
+  "Rename a topic."
+  (interactive
+   (let ((topic (gnus-current-topic)))
+     (list topic
+	   (read-string (format "Rename %s to: " topic)))))
+  (let ((top (gnus-topic-find-topology old-name))
+	(entry (assoc old-name gnus-topic-alist)))
+    (when top
+      (setcar (cadr top) new-name))
+    (when entry
+      (setcar entry new-name))
+    (forward-line -1)
+    (gnus-dribble-touch)
+    (gnus-group-list-groups)))
+
+(defun gnus-topic-indent (&optional unindent)
+  "Indent a topic -- make it a sub-topic of the previous topic.
+If UNINDENT, remove an indentation."
+  (interactive "P")
+  (if unindent
+      (gnus-topic-unindent)
+    (let* ((topic (gnus-current-topic))
+	   (parent (gnus-topic-previous-topic topic))
+	   (buffer-read-only nil))
+      (unless parent
+	(error "Nothing to indent %s into" topic))
+      (when topic
+	(gnus-topic-goto-topic topic)
+	(gnus-topic-kill-group)
+	(push (cdar gnus-topic-killed-topics) gnus-topic-alist)
+	(gnus-topic-create-topic
+	 topic parent nil (cdaar gnus-topic-killed-topics))
+	(pop gnus-topic-killed-topics)
+	(or (gnus-topic-goto-topic topic)
+	    (gnus-topic-goto-topic parent))))))
+
+(defun gnus-topic-unindent ()
+  "Unindent a topic."
+  (interactive)
+  (let* ((topic (gnus-current-topic))
+	 (parent (gnus-topic-parent-topic topic))
+	 (grandparent (gnus-topic-parent-topic parent)))
+    (unless grandparent
+      (error "Nothing to indent %s into" topic))
+    (when topic
+      (gnus-topic-goto-topic topic)
+      (gnus-topic-kill-group)
+      (push (cdar gnus-topic-killed-topics) gnus-topic-alist)
+      (gnus-topic-create-topic
+       topic grandparent (gnus-topic-next-topic parent)
+       (cdaar gnus-topic-killed-topics))
+      (pop gnus-topic-killed-topics)
+      (gnus-topic-goto-topic topic))))
+
+(defun gnus-topic-list-active (&optional force)
+  "List all groups that Gnus knows about in a topicsified fashion.
+If FORCE, always re-read the active file."
+  (interactive "P")
+  (when force
+    (gnus-get-killed-groups))
+  (gnus-topic-grok-active force)
+  (let ((gnus-topic-topology gnus-topic-active-topology)
+	(gnus-topic-alist gnus-topic-active-alist)
+	gnus-killed-list gnus-zombie-list)
+    (gnus-group-list-groups 9 nil 1)))
+
+;;; Topic sorting functions
+
+(defun gnus-topic-edit-parameters (group)
+  "Edit the group parameters of GROUP.
+If performed on a topic, edit the topic parameters instead."
+  (interactive (list (gnus-group-group-name)))
+  (if group
+      (gnus-group-edit-group-parameters group)
+    (if (not (gnus-group-topic-p))
+	(error "Nothing to edit on the current line.")
+      (let ((topic (gnus-group-topic-name)))
+	(gnus-edit-form
+	 (gnus-topic-parameters topic)
+	 (format "Editing the topic parameters for `%s'."
+		 (or group topic))
+	 `(lambda (form)
+	    (gnus-topic-set-parameters ,topic form)))))))
+
+(defun gnus-group-sort-topic (func reverse)
+  "Sort groups in the topics according to FUNC and REVERSE."
+  (let ((alist gnus-topic-alist))
+    (while alist
+      ;; !!!Sometimes nil elements sneak into the alist,
+      ;; for some reason or other.
+      (setcar alist (delq nil (car alist)))
+      (setcar alist (delete "dummy.group" (car alist)))
+      (gnus-topic-sort-topic (pop alist) func reverse))))
+
+(defun gnus-topic-sort-topic (topic func reverse)
+  ;; Each topic only lists the name of the group, while
+  ;; the sort predicates expect group infos as inputs.
+  ;; So we first transform the group names into infos,
+  ;; then sort, and then transform back into group names.
+  (setcdr
+   topic
+   (mapcar
+    (lambda (info) (gnus-info-group info))
+    (sort
+     (mapcar
+      (lambda (group) (gnus-get-info group))
+      (cdr topic))
+     func)))
+  ;; Do the reversal, if necessary.
+  (when reverse
+    (setcdr topic (nreverse (cdr topic)))))
+
+(defun gnus-topic-sort-groups (func &optional reverse)
+  "Sort the current topic according to FUNC.
+If REVERSE, reverse the sorting order."
+  (interactive (list gnus-group-sort-function current-prefix-arg))
+  (let ((topic (assoc (gnus-current-topic) gnus-topic-alist)))
+    (gnus-topic-sort-topic
+     topic (gnus-make-sort-function func) reverse)
+    (gnus-group-list-groups)))
+
+(defun gnus-topic-sort-groups-by-alphabet (&optional reverse)
+  "Sort the current topic alphabetically by group name.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-topic-sort-groups 'gnus-group-sort-by-alphabet reverse))
+
+(defun gnus-topic-sort-groups-by-unread (&optional reverse)
+  "Sort the current topic by number of unread articles.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-topic-sort-groups 'gnus-group-sort-by-unread reverse))
+
+(defun gnus-topic-sort-groups-by-level (&optional reverse)
+  "Sort the current topic by group level.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-topic-sort-groups 'gnus-group-sort-by-level reverse))
+
+(defun gnus-topic-sort-groups-by-score (&optional reverse)
+  "Sort the current topic by group score.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-topic-sort-groups 'gnus-group-sort-by-score reverse))
+
+(defun gnus-topic-sort-groups-by-rank (&optional reverse)
+  "Sort the current topic by group rank.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-topic-sort-groups 'gnus-group-sort-by-rank reverse))
+
+(defun gnus-topic-sort-groups-by-method (&optional reverse)
+  "Sort the current topic alphabetically by backend name.
+If REVERSE, sort in reverse order."
+  (interactive "P")
+  (gnus-topic-sort-groups 'gnus-group-sort-by-method reverse))
+
+(provide 'gnus-topic)
+
+;;; gnus-topic.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-undo.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,173 @@
+;;; gnus-undo.el --- minor mode for undoing in Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; This package allows arbitrary undoing in Gnus buffers.  As all the
+;; Gnus buffers aren't very text-oriented (what is in the buffers is
+;; just some random representation of the actual data), normal Emacs
+;; undoing doesn't work at all for Gnus.
+;;
+;; This package works by letting Gnus register functions for reversing
+;; actions, and then calling these functions when the user pushes the
+;; `undo' key.  As with normal `undo', there it is possible to set
+;; undo boundaries and so on.
+;;
+;; Internally, the undo sequence is represented by the
+;; `gnus-undo-actions' list, where each element is a list of functions
+;; to be called, in sequence, to undo some action.  (An "action" is a
+;; collection of functions.)
+;;
+;; For instance, a function for killing a group will call
+;; `gnus-undo-register' with a function that un-kills the group.  This
+;; package will put that function into an action.
+
+;;; Code:
+
+(require 'gnus-util)
+(require 'gnus)
+
+(defvar gnus-undo-mode nil
+  "Minor mode for undoing in Gnus buffers.")
+
+(defvar gnus-undo-mode-hook nil
+  "Hook called in all `gnus-undo-mode' buffers.")
+
+;;; Internal variables.
+
+(defvar gnus-undo-actions nil)
+(defvar gnus-undo-boundary t)
+(defvar gnus-undo-last nil)
+(defvar gnus-undo-boundary-inhibit nil)
+
+;;; Minor mode definition.
+
+(defvar gnus-undo-mode-map nil)
+
+(unless gnus-undo-mode-map
+  (setq gnus-undo-mode-map (make-sparse-keymap))
+
+  (gnus-define-keys gnus-undo-mode-map
+   "\M-\C-_"     gnus-undo
+   "\C-_"        gnus-undo
+   "\C-xu"       gnus-undo
+   [(control /)] gnus-undo    ; many people are used to type `C-/' on
+			      ; X terminals and get `C-_'.
+   ))
+
+(defun gnus-undo-make-menu-bar ()
+  (when nil
+  (define-key-after (current-local-map) [menu-bar file gnus-undo]
+    (cons "Undo" 'gnus-undo-actions)
+    [menu-bar file whatever])))
+
+(defun gnus-undo-mode (&optional arg)
+  "Minor mode for providing `undo' in Gnus buffers.
+
+\\{gnus-undo-mode-map}"
+  (interactive "P")
+  (set (make-local-variable 'gnus-undo-mode)
+       (if (null arg) (not gnus-undo-mode)
+	 (> (prefix-numeric-value arg) 0)))
+  (set (make-local-variable 'gnus-undo-actions) nil)
+  (set (make-local-variable 'gnus-undo-boundary) t)
+  (when gnus-undo-mode
+    ;; Set up the menu.
+    (when (gnus-visual-p 'undo-menu 'menu)
+      (gnus-undo-make-menu-bar))
+    ;; Don't display anything in the mode line -- too annoying.
+    ;;(unless (assq 'gnus-undo-mode minor-mode-alist)
+    ;;  (push '(gnus-undo-mode " Undo") minor-mode-alist))
+    (unless (assq 'gnus-undo-mode minor-mode-map-alist)
+      (push (cons 'gnus-undo-mode gnus-undo-mode-map)
+	    minor-mode-map-alist))
+    (make-local-hook 'post-command-hook)
+    (add-hook 'post-command-hook 'gnus-undo-boundary nil t)
+    (add-hook 'gnus-summary-exit-hook 'gnus-undo-boundary)
+    (run-hooks 'gnus-undo-mode-hook)))
+
+;;; Interface functions.
+
+(defun gnus-disable-undo (&optional buffer)
+  "Disable undoing in the current buffer."
+  (interactive)
+  (save-excursion
+    (when buffer
+      (set-buffer buffer))
+    (gnus-undo-mode -1)))
+
+(defun gnus-undo-boundary ()
+  "Set Gnus undo boundary."
+  (if gnus-undo-boundary-inhibit
+      (setq gnus-undo-boundary-inhibit nil)
+    (setq gnus-undo-boundary t)))
+
+(defun gnus-undo-register (form)
+  "Register FORMS as something to be performed to undo a change.
+FORMS may use backtick quote syntax."
+  (when gnus-undo-mode
+    (gnus-undo-register-1
+     `(lambda ()
+	,form))))
+
+(put 'gnus-undo-register 'lisp-indent-function 0)
+(put 'gnus-undo-register 'edebug-form-spec '(body))
+
+(defun gnus-undo-register-1 (function)
+  "Register FUNCTION as something to be performed to undo a change."
+  (when gnus-undo-mode
+    (cond
+     ;; We are on a boundary, so we create a new action.
+     (gnus-undo-boundary
+      (push (list function) gnus-undo-actions)
+      (setq gnus-undo-boundary nil))
+     ;; Prepend the function to an old action.
+     (gnus-undo-actions
+      (setcar gnus-undo-actions (cons function (car gnus-undo-actions))))
+     ;; Initialize list.
+     (t
+      (setq gnus-undo-actions (list (list function)))))
+    (setq gnus-undo-boundary-inhibit t)))
+
+(defun gnus-undo (n)
+  "Undo some previous changes in Gnus buffers.
+Repeat this command to undo more changes.
+A numeric argument serves as a repeat count."
+  (interactive "p")
+  (unless gnus-undo-mode
+    (error "Undoing is not enabled in this buffer"))
+  (message "%s" last-command)
+  (when (or (not (eq last-command 'gnus-undo))
+	    (not gnus-undo-last))
+    (setq gnus-undo-last gnus-undo-actions))
+  (let ((action (pop gnus-undo-last)))
+    (unless action
+      (error "Nothing further to undo"))
+    (setq gnus-undo-actions (delq action gnus-undo-actions))
+    (setq gnus-undo-boundary t)
+    (while action
+      (funcall (pop action)))))
+
+(provide 'gnus-undo)
+
+;;; gnus-undo.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-util.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,829 @@
+;;; gnus-util.el --- utility functions for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; Nothing in this file depends on any other parts of Gnus -- all
+;; functions and macros in this file are utility functions that are
+;; used by Gnus and may be used by any other package without loading
+;; Gnus first.
+
+;;; Code:
+
+(require 'custom)
+(require 'cl)
+(require 'nnheader)
+(require 'timezone)
+(require 'message)
+
+(eval-and-compile
+  (autoload 'nnmail-date-to-time "nnmail"))
+
+(defun gnus-boundp (variable)
+  "Return non-nil if VARIABLE is bound and non-nil."
+  (and (boundp variable)
+       (symbol-value variable)))
+
+(defmacro gnus-eval-in-buffer-window (buffer &rest forms)
+  "Pop to BUFFER, evaluate FORMS, and then return to the original window."
+  (let ((tempvar (make-symbol "GnusStartBufferWindow"))
+        (w (make-symbol "w"))
+        (buf (make-symbol "buf")))
+    `(let* ((,tempvar (selected-window))
+            (,buf ,buffer)
+            (,w (get-buffer-window ,buf 'visible)))
+       (unwind-protect
+           (progn
+             (if ,w
+                 (progn
+                   (select-window ,w)
+                   (set-buffer (window-buffer ,w)))
+               (pop-to-buffer ,buf))
+             ,@forms)
+         (select-window ,tempvar)))))
+
+(put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
+(put 'gnus-eval-in-buffer-window 'edebug-form-spec '(form body))
+
+(defmacro gnus-intern-safe (string hashtable)
+  "Set hash value.  Arguments are STRING, VALUE, and HASHTABLE."
+  `(let ((symbol (intern ,string ,hashtable)))
+     (or (boundp symbol)
+	 (set symbol nil))
+     symbol))
+
+;; modified by MORIOKA Tomohiko <morioka@jaist.ac.jp>
+;;   function `substring' might cut on a middle of multi-octet
+;;   character.
+(defun gnus-truncate-string (str width)
+  (substring str 0 width))
+
+;; Added by Geoffrey T. Dairiki <dairiki@u.washington.edu>.  A safe way
+;; to limit the length of a string.  This function is necessary since
+;; `(substr "abc" 0 30)' pukes with "Args out of range".
+(defsubst gnus-limit-string (str width)
+  (if (> (length str) width)
+      (substring str 0 width)
+    str))
+
+(defsubst gnus-functionp (form)
+  "Return non-nil if FORM is funcallable."
+  (or (and (symbolp form) (fboundp form))
+      (and (listp form) (eq (car form) 'lambda))
+      (compiled-function-p form)))
+
+(defsubst gnus-goto-char (point)
+  (and point (goto-char point)))
+
+(defmacro gnus-buffer-exists-p (buffer)
+  `(let ((buffer ,buffer))
+     (when buffer
+       (funcall (if (stringp buffer) 'get-buffer 'buffer-name)
+		buffer))))
+
+(defmacro gnus-kill-buffer (buffer)
+  `(let ((buf ,buffer))
+     (when (gnus-buffer-exists-p buf)
+       (kill-buffer buf))))
+
+(if (fboundp 'point-at-bol)
+    (fset 'gnus-point-at-bol 'point-at-bol)
+  (defun gnus-point-at-bol ()
+    "Return point at the beginning of the line."
+    (let ((p (point)))
+      (beginning-of-line)
+      (prog1
+	  (point)
+	(goto-char p)))))
+
+(if (fboundp 'point-at-eol)
+    (fset 'gnus-point-at-eol 'point-at-eol)
+  (defun gnus-point-at-eol ()
+    "Return point at the end of the line."
+    (let ((p (point)))
+      (end-of-line)
+      (prog1
+	  (point)
+	(goto-char p)))))
+
+(defun gnus-delete-first (elt list)
+  "Delete by side effect the first occurrence of ELT as a member of LIST."
+  (if (equal (car list) elt)
+      (cdr list)
+    (let ((total list))
+      (while (and (cdr list)
+		  (not (equal (cadr list) elt)))
+	(setq list (cdr list)))
+      (when (cdr list)
+	(setcdr list (cddr list)))
+      total)))
+
+;; Delete the current line (and the next N lines).
+(defmacro gnus-delete-line (&optional n)
+  `(delete-region (progn (beginning-of-line) (point))
+		  (progn (forward-line ,(or n 1)) (point))))
+
+(defun gnus-byte-code (func)
+  "Return a form that can be `eval'ed based on FUNC."
+  (let ((fval (symbol-function func)))
+    (if (compiled-function-p fval)
+	(let ((flist (append fval nil)))
+	  (setcar flist 'byte-code)
+	  flist)
+      (cons 'progn (cddr fval)))))
+
+(defun gnus-extract-address-components (from)
+  (let (name address)
+    ;; First find the address - the thing with the @ in it.  This may
+    ;; not be accurate in mail addresses, but does the trick most of
+    ;; the time in news messages.
+    (when (string-match "\\b[^@ \t<>]+[!@][^@ \t<>]+\\b" from)
+      (setq address (substring from (match-beginning 0) (match-end 0))))
+    ;; Then we check whether the "name <address>" format is used.
+    (and address
+	 ;; Fix by MORIOKA Tomohiko <morioka@jaist.ac.jp>
+	 ;; Linear white space is not required.
+	 (string-match (concat "[ \t]*<" (regexp-quote address) ">") from)
+	 (and (setq name (substring from 0 (match-beginning 0)))
+	      ;; Strip any quotes from the name.
+	      (string-match "\".*\"" name)
+	      (setq name (substring name 1 (1- (match-end 0))))))
+    ;; If not, then "address (name)" is used.
+    (or name
+	(and (string-match "(.+)" from)
+	     (setq name (substring from (1+ (match-beginning 0))
+				   (1- (match-end 0)))))
+	(and (string-match "()" from)
+	     (setq name address))
+	;; Fix by MORIOKA Tomohiko <morioka@jaist.ac.jp>.
+	;; XOVER might not support folded From headers.
+	(and (string-match "(.*" from)
+	     (setq name (substring from (1+ (match-beginning 0))
+				   (match-end 0)))))
+    ;; Fix by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>.
+    (list (or name from) (or address from))))
+
+(defun gnus-fetch-field (field)
+  "Return the value of the header FIELD of current article."
+  (save-excursion
+    (save-restriction
+      (let ((case-fold-search t)
+	    (inhibit-point-motion-hooks t))
+	(nnheader-narrow-to-headers)
+	(message-fetch-field field)))))
+
+(defun gnus-goto-colon ()
+  (beginning-of-line)
+  (search-forward ":" (gnus-point-at-eol) t))
+
+(defun gnus-remove-text-with-property (prop)
+  "Delete all text in the current buffer with text property PROP."
+  (save-excursion
+    (goto-char (point-min))
+    (while (not (eobp))
+      (while (get-text-property (point) prop)
+	(delete-char 1))
+      (goto-char (next-single-property-change (point) prop nil (point-max))))))
+
+(defun gnus-newsgroup-directory-form (newsgroup)
+  "Make hierarchical directory name from NEWSGROUP name."
+  (let ((newsgroup (gnus-newsgroup-savable-name newsgroup))
+	(len (length newsgroup))
+	idx)
+    ;; If this is a foreign group, we don't want to translate the
+    ;; entire name.
+    (if (setq idx (string-match ":" newsgroup))
+	(aset newsgroup idx ?/)
+      (setq idx 0))
+    ;; Replace all occurrences of `.' with `/'.
+    (while (< idx len)
+      (when (= (aref newsgroup idx) ?.)
+	(aset newsgroup idx ?/))
+      (setq idx (1+ idx)))
+    newsgroup))
+
+(defun gnus-newsgroup-savable-name (group)
+  ;; Replace any slashes in a group name (eg. an ange-ftp nndoc group)
+  ;; with dots.
+  (nnheader-replace-chars-in-string group ?/ ?.))
+
+(defun gnus-string> (s1 s2)
+  (not (or (string< s1 s2)
+	   (string= s1 s2))))
+
+;;; Time functions.
+
+(defun gnus-days-between (date1 date2)
+  ;; Return the number of days between date1 and date2.
+  (- (gnus-day-number date1) (gnus-day-number date2)))
+
+(defun gnus-day-number (date)
+  (let ((dat (mapcar (lambda (s) (and s (string-to-int s)) )
+		     (timezone-parse-date date))))
+    (timezone-absolute-from-gregorian
+     (nth 1 dat) (nth 2 dat) (car dat))))
+
+(defun gnus-time-to-day (time)
+  "Convert TIME to day number."
+  (let ((tim (decode-time time)))
+    (timezone-absolute-from-gregorian
+     (nth 4 tim) (nth 3 tim) (nth 5 tim))))
+
+(defun gnus-encode-date (date)
+  "Convert DATE to internal time."
+  (let* ((parse (timezone-parse-date date))
+	 (date (mapcar (lambda (d) (and d (string-to-int d))) parse))
+	 (time (mapcar 'string-to-int (timezone-parse-time (aref parse 3)))))
+    (encode-time (caddr time) (cadr time) (car time)
+		 (caddr date) (cadr date) (car date) (nth 4 date))))
+
+(defun gnus-time-minus (t1 t2)
+  "Subtract two internal times."
+  (let ((borrow (< (cadr t1) (cadr t2))))
+    (list (- (car t1) (car t2) (if borrow 1 0))
+	  (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2)))))
+
+(defun gnus-time-less (t1 t2)
+  "Say whether time T1 is less than time T2."
+  (or (< (car t1) (car t2))
+      (and (= (car t1) (car t2))
+	   (< (nth 1 t1) (nth 1 t2)))))
+
+(defun gnus-file-newer-than (file date)
+  (let ((fdate (nth 5 (file-attributes file))))
+    (or (> (car fdate) (car date))
+	(and (= (car fdate) (car date))
+	     (> (nth 1 fdate) (nth 1 date))))))
+
+;;; Keymap macros.
+
+(defmacro gnus-local-set-keys (&rest plist)
+  "Set the keys in PLIST in the current keymap."
+  `(gnus-define-keys-1 (current-local-map) ',plist))
+
+(defmacro gnus-define-keys (keymap &rest plist)
+  "Define all keys in PLIST in KEYMAP."
+  `(gnus-define-keys-1 (quote ,keymap) (quote ,plist)))
+
+(defmacro gnus-define-keys-safe (keymap &rest plist)
+  "Define all keys in PLIST in KEYMAP without overwriting previous definitions."
+  `(gnus-define-keys-1 (quote ,keymap) (quote ,plist) t))
+
+(put 'gnus-define-keys 'lisp-indent-function 1)
+(put 'gnus-define-keys-safe 'lisp-indent-function 1)
+(put 'gnus-local-set-keys 'lisp-indent-function 1)
+
+(defmacro gnus-define-keymap (keymap &rest plist)
+  "Define all keys in PLIST in KEYMAP."
+  `(gnus-define-keys-1 ,keymap (quote ,plist)))
+
+(put 'gnus-define-keymap 'lisp-indent-function 1)
+
+(defun gnus-define-keys-1 (keymap plist &optional safe)
+  (when (null keymap)
+    (error "Can't set keys in a null keymap"))
+  (cond ((symbolp keymap)
+	 (setq keymap (symbol-value keymap)))
+	((keymapp keymap))
+	((listp keymap)
+	 (set (car keymap) nil)
+	 (define-prefix-command (car keymap))
+	 (define-key (symbol-value (caddr keymap)) (cadr keymap) (car keymap))
+	 (setq keymap (symbol-value (car keymap)))))
+  (let (key)
+    (while plist
+      (when (symbolp (setq key (pop plist)))
+	(setq key (symbol-value key)))
+      (if (or (not safe)
+	      (eq (lookup-key keymap key) 'undefined))
+	  (define-key keymap key (pop plist))
+	(pop plist)))))
+
+(defun gnus-completing-read (default prompt &rest args)
+  ;; Like `completing-read', except that DEFAULT is the default argument.
+  (let* ((prompt (if default
+		     (concat prompt " (default " default ") ")
+		   (concat prompt " ")))
+	 (answer (apply 'completing-read prompt args)))
+    (if (or (null answer) (zerop (length answer)))
+	default
+      answer)))
+
+;; Two silly functions to ensure that all `y-or-n-p' questions clear
+;; the echo area.
+(defun gnus-y-or-n-p (prompt)
+  (prog1
+      (y-or-n-p prompt)
+    (message "")))
+
+(defun gnus-yes-or-no-p (prompt)
+  (prog1
+      (yes-or-no-p prompt)
+    (message "")))
+
+;; I suspect there's a better way, but I haven't taken the time to do
+;; it yet.  -erik selberg@cs.washington.edu
+(defun gnus-dd-mmm (messy-date)
+  "Return a string like DD-MMM from a big messy string"
+  (let ((datevec (ignore-errors (timezone-parse-date messy-date))))
+    (if (not datevec)
+	"??-???"
+      (format "%2s-%s"
+	      (condition-case ()
+		  ;; Make sure leading zeroes are stripped.
+		  (number-to-string (string-to-number (aref datevec 2)))
+		(error "??"))
+	      (capitalize
+	       (or (car
+		    (nth (1- (string-to-number (aref datevec 1)))
+			 timezone-months-assoc))
+		   "???"))))))
+
+(defmacro gnus-date-get-time (date)
+  "Convert DATE string to Emacs time.
+Cache the result as a text property stored in DATE."
+  ;; Either return the cached value...
+  `(let ((d ,date))
+     (if (equal "" d)
+	 '(0 0)
+       (or (get-text-property 0 'gnus-time d)
+	   ;; or compute the value...
+	   (let ((time (nnmail-date-to-time d)))
+	     ;; and store it back in the string.
+	     (put-text-property 0 1 'gnus-time time d)
+	     time)))))
+
+(defsubst gnus-time-iso8601 (time)
+  "Return a string of TIME in YYMMDDTHHMMSS format."
+  (format-time-string "%Y%m%dT%H%M%S" time))
+
+(defun gnus-date-iso8601 (header)
+  "Convert the date field in HEADER to YYMMDDTHHMMSS"
+  (condition-case ()
+      (gnus-time-iso8601 (gnus-date-get-time (mail-header-date header)))
+    (error "")))
+
+(defun gnus-mode-string-quote (string)
+  "Quote all \"%\"'s in STRING."
+  (save-excursion
+    (gnus-set-work-buffer)
+    (insert string)
+    (goto-char (point-min))
+    (while (search-forward "%" nil t)
+      (insert "%"))
+    (buffer-string)))
+
+;; Make a hash table (default and minimum size is 256).
+;; Optional argument HASHSIZE specifies the table size.
+(defun gnus-make-hashtable (&optional hashsize)
+  (make-vector (if hashsize (max (gnus-create-hash-size hashsize) 256) 256) 0))
+
+;; Make a number that is suitable for hashing; bigger than MIN and
+;; equal to some 2^x.  Many machines (such as sparcs) do not have a
+;; hardware modulo operation, so they implement it in software.  On
+;; many sparcs over 50% of the time to intern is spent in the modulo.
+;; Yes, it's slower than actually computing the hash from the string!
+;; So we use powers of 2 so people can optimize the modulo to a mask.
+(defun gnus-create-hash-size (min)
+  (let ((i 1))
+    (while (< i min)
+      (setq i (* 2 i)))
+    i))
+
+(defcustom gnus-verbose 7
+  "*Integer that says how verbose Gnus should be.
+The higher the number, the more messages Gnus will flash to say what
+it's doing.  At zero, Gnus will be totally mute; at five, Gnus will
+display most important messages; and at ten, Gnus will keep on
+jabbering all the time."
+  :group 'gnus-start
+  :type 'integer)
+
+;; Show message if message has a lower level than `gnus-verbose'.
+;; Guideline for numbers:
+;; 1 - error messages, 3 - non-serious error messages, 5 - messages
+;; for things that take a long time, 7 - not very important messages
+;; on stuff, 9 - messages inside loops.
+(defun gnus-message (level &rest args)
+  (if (<= level gnus-verbose)
+      (apply 'message args)
+    ;; We have to do this format thingy here even if the result isn't
+    ;; shown - the return value has to be the same as the return value
+    ;; from `message'.
+    (apply 'format args)))
+
+(defun gnus-error (level &rest args)
+  "Beep an error if LEVEL is equal to or less than `gnus-verbose'."
+  (when (<= (floor level) gnus-verbose)
+    (apply 'message args)
+    (ding)
+    (let (duration)
+      (when (and (floatp level)
+		 (not (zerop (setq duration (* 10 (- level (floor level)))))))
+	(sit-for duration))))
+  nil)
+
+(defun gnus-split-references (references)
+  "Return a list of Message-IDs in REFERENCES."
+  (let ((beg 0)
+	ids)
+    (while (string-match "<[^>]+>" references beg)
+      (push (substring references (match-beginning 0) (setq beg (match-end 0)))
+	    ids))
+    (nreverse ids)))
+
+(defun gnus-parent-id (references &optional n)
+  "Return the last Message-ID in REFERENCES.
+If N, return the Nth ancestor instead."
+  (when references
+    (let ((ids (inline (gnus-split-references references))))
+      (car (last ids (or n 1))))))
+
+(defsubst gnus-buffer-live-p (buffer)
+  "Say whether BUFFER is alive or not."
+  (and buffer
+       (get-buffer buffer)
+       (buffer-name (get-buffer buffer))))
+
+(defun gnus-horizontal-recenter ()
+  "Recenter the current buffer horizontally."
+  (if (< (current-column) (/ (window-width) 2))
+      (set-window-hscroll (get-buffer-window (current-buffer) t) 0)
+    (let* ((orig (point))
+	   (end (window-end (get-buffer-window (current-buffer) t)))
+	   (max 0))
+      ;; Find the longest line currently displayed in the window.
+      (goto-char (window-start))
+      (while (and (not (eobp))
+		  (< (point) end))
+	(end-of-line)
+	(setq max (max max (current-column)))
+	(forward-line 1))
+      (goto-char orig)
+      ;; Scroll horizontally to center (sort of) the point.
+      (if (> max (window-width))
+	  (set-window-hscroll
+	   (get-buffer-window (current-buffer) t)
+	   (min (- (current-column) (/ (window-width) 3))
+		(+ 2 (- max (window-width)))))
+	(set-window-hscroll (get-buffer-window (current-buffer) t) 0))
+      max)))
+
+(defun gnus-read-event-char ()
+  "Get the next event."
+  (let ((event (read-event)))
+    ;; should be gnus-characterp, but this can't be called in XEmacs anyway
+    (cons (and (numberp event) event) event)))
+
+(defun gnus-sortable-date (date)
+  "Make sortable string by string-lessp from DATE.
+Timezone package is used."
+  (condition-case ()
+      (progn
+	(setq date (inline (timezone-fix-time
+			    date nil
+			    (aref (inline (timezone-parse-date date)) 4))))
+	(inline
+	  (timezone-make-sortable-date
+	   (aref date 0) (aref date 1) (aref date 2)
+	   (inline
+	     (timezone-make-time-string
+	      (aref date 3) (aref date 4) (aref date 5))))))
+    (error "")))
+
+(defun gnus-copy-file (file &optional to)
+  "Copy FILE to TO."
+  (interactive
+   (list (read-file-name "Copy file: " default-directory)
+	 (read-file-name "Copy file to: " default-directory)))
+  (unless to
+    (setq to (read-file-name "Copy file to: " default-directory)))
+  (when (file-directory-p to)
+    (setq to (concat (file-name-as-directory to)
+		     (file-name-nondirectory file))))
+  (copy-file file to))
+
+(defun gnus-kill-all-overlays ()
+  "Delete all overlays in the current buffer."
+  (unless gnus-xemacs
+    (let* ((overlayss (overlay-lists))
+	   (buffer-read-only nil)
+	   (overlays (nconc (car overlayss) (cdr overlayss))))
+      (while overlays
+	(delete-overlay (pop overlays))))))
+
+(defvar gnus-work-buffer " *gnus work*")
+
+(defun gnus-set-work-buffer ()
+  "Put point in the empty Gnus work buffer."
+  (if (get-buffer gnus-work-buffer)
+      (progn
+	(set-buffer gnus-work-buffer)
+	(erase-buffer))
+    (set-buffer (get-buffer-create gnus-work-buffer))
+    (kill-all-local-variables)
+    (buffer-disable-undo (current-buffer))))
+
+(defmacro gnus-group-real-name (group)
+  "Find the real name of a foreign newsgroup."
+  `(let ((gname ,group))
+     (if (string-match "^[^:]+:" gname)
+	 (substring gname (match-end 0))
+       gname)))
+
+(defun gnus-make-sort-function (funs)
+  "Return a composite sort condition based on the functions in FUNC."
+  (cond
+   ((not (listp funs)) funs)
+   ((null funs) funs)
+   ((cdr funs)
+    `(lambda (t1 t2)
+       ,(gnus-make-sort-function-1 (reverse funs))))
+   (t
+    (car funs))))
+
+(defun gnus-make-sort-function-1 (funs)
+  "Return a composite sort condition based on the functions in FUNC."
+  (if (cdr funs)
+      `(or (,(car funs) t1 t2)
+	   (and (not (,(car funs) t2 t1))
+		,(gnus-make-sort-function-1 (cdr funs))))
+    `(,(car funs) t1 t2)))
+
+(defun gnus-turn-off-edit-menu (type)
+  "Turn off edit menu in `gnus-TYPE-mode-map'."
+  (define-key (symbol-value (intern (format "gnus-%s-mode-map" type)))
+    [menu-bar edit] 'undefined))
+
+(defun gnus-prin1 (form)
+  "Use `prin1' on FORM in the current buffer.
+Bind `print-quoted' to t while printing."
+  (let ((print-quoted t)
+	print-level print-length)
+    (prin1 form (current-buffer))))
+
+(defun gnus-prin1-to-string (form)
+  "The same as `prin1', but but `print-quoted' to t."
+  (let ((print-quoted t))
+    (prin1-to-string form)))
+
+(defun gnus-make-directory (directory)
+  "Make DIRECTORY (and all its parents) if it doesn't exist."
+  (when (and directory
+	     (not (file-exists-p directory)))
+    (make-directory directory t))
+  t)
+
+(defun gnus-write-buffer (file)
+  "Write the current buffer's contents to FILE."
+  ;; Make sure the directory exists.
+  (gnus-make-directory (file-name-directory file))
+  ;; Write the buffer.
+  (write-region (point-min) (point-max) file nil 'quietly))
+
+(defmacro gnus-delete-assq (key list)
+  `(let ((listval (eval ,list)))
+     (setq ,list (delq (assq ,key listval) listval))))
+
+(defmacro gnus-delete-assoc (key list)
+  `(let ((listval ,list))
+     (setq ,list (delq (assoc ,key listval) listval))))
+
+(defun gnus-delete-file (file)
+  "Delete FILE if it exists."
+  (when (file-exists-p file)
+    (delete-file file)))
+
+(defun gnus-strip-whitespace (string)
+  "Return STRING stripped of all whitespace."
+  (while (string-match "[\r\n\t ]+" string)
+    (setq string (replace-match "" t t string)))
+  string)
+
+(defun gnus-put-text-property-excluding-newlines (beg end prop val)
+  "The same as `put-text-property', but don't put this prop on any newlines in the region."
+  (save-match-data
+    (save-excursion
+      (save-restriction
+	(goto-char beg)
+	(while (re-search-forward "[ \t]*\n" end 'move)
+	  (put-text-property beg (match-beginning 0) prop val)
+	  (setq beg (point)))
+	(put-text-property beg (point) prop val)))))
+
+;;; Protected and atomic operations.  dmoore@ucsd.edu 21.11.1996
+;;; The primary idea here is to try to protect internal datastructures
+;;; from becoming corrupted when the user hits C-g, or if a hook or
+;;; similar blows up.  Often in Gnus multiple tables/lists need to be
+;;; updated at the same time, or information can be lost.
+
+(defvar gnus-atomic-be-safe t
+  "If t, certain operations will be protected from interruption by C-g.")
+
+(defmacro gnus-atomic-progn (&rest forms)
+  "Evaluate FORMS atomically, which means to protect the evaluation
+from being interrupted by the user.  An error from the forms themselves
+will return without finishing the operation.  Since interrupts from
+the user are disabled, it is recommended that only the most minimal
+operations are performed by FORMS.  If you wish to assign many
+complicated values atomically, compute the results into temporary
+variables and then do only the assignment atomically."
+  `(let ((inhibit-quit gnus-atomic-be-safe))
+     ,@forms))
+
+(put 'gnus-atomic-progn 'lisp-indent-function 0)
+
+(defmacro gnus-atomic-progn-assign (protect &rest forms)
+  "Evaluate FORMS, but insure that the variables listed in PROTECT
+are not changed if anything in FORMS signals an error or otherwise
+non-locally exits.  The variables listed in PROTECT are updated atomically.
+It is safe to use gnus-atomic-progn-assign with long computations.
+
+Note that if any of the symbols in PROTECT were unbound, they will be
+set to nil on a sucessful assignment.  In case of an error or other
+non-local exit, it will still be unbound."
+  (let* ((temp-sym-map (mapcar (lambda (x) (list (make-symbol
+						  (concat (symbol-name x)
+							  "-tmp"))
+						 x))
+			       protect))
+	 (sym-temp-map (mapcar (lambda (x) (list (cadr x) (car x)))
+			       temp-sym-map))
+	 (temp-sym-let (mapcar (lambda (x) (list (car x)
+						 `(and (boundp ',(cadr x))
+						       ,(cadr x))))
+			       temp-sym-map))
+	 (sym-temp-let sym-temp-map)
+	 (temp-sym-assign (apply 'append temp-sym-map))
+	 (sym-temp-assign (apply 'append sym-temp-map))
+	 (result (make-symbol "result-tmp")))
+    `(let (,@temp-sym-let
+	   ,result)
+       (let ,sym-temp-let
+	 (setq ,result (progn ,@forms))
+	 (setq ,@temp-sym-assign))
+       (let ((inhibit-quit gnus-atomic-be-safe))
+	 (setq ,@sym-temp-assign))
+       ,result)))
+
+(put 'gnus-atomic-progn-assign 'lisp-indent-function 1)
+;(put 'gnus-atomic-progn-assign 'edebug-form-spec '(sexp body))
+
+(defmacro gnus-atomic-setq (&rest pairs)
+  "Similar to setq, except that the real symbols are only assigned when
+there are no errors.  And when the real symbols are assigned, they are
+done so atomically.  If other variables might be changed via side-effect,
+see gnus-atomic-progn-assign.  It is safe to use gnus-atomic-setq
+with potentially long computations."
+  (let ((tpairs pairs)
+	syms)
+    (while tpairs
+      (push (car tpairs) syms)
+      (setq tpairs (cddr tpairs)))
+    `(gnus-atomic-progn-assign ,syms
+       (setq ,@pairs))))
+
+;(put 'gnus-atomic-setq 'edebug-form-spec '(body))
+
+
+;;; Functions for saving to babyl/mail files.
+
+(defvar rmail-default-rmail-file)
+(defun gnus-output-to-rmail (filename &optional ask)
+  "Append the current article to an Rmail file named FILENAME."
+  (require 'rmail)
+  ;; Most of these codes are borrowed from rmailout.el.
+  (setq filename (expand-file-name filename))
+  (setq rmail-default-rmail-file filename)
+  (let ((artbuf (current-buffer))
+	(tmpbuf (get-buffer-create " *Gnus-output*")))
+    (save-excursion
+      (or (get-file-buffer filename)
+	  (file-exists-p filename)
+	  (if (or (not ask)
+		  (gnus-yes-or-no-p
+		   (concat "\"" filename "\" does not exist, create it? ")))
+	      (let ((file-buffer (create-file-buffer filename)))
+		(save-excursion
+		  (set-buffer file-buffer)
+		  (rmail-insert-rmail-file-header)
+		  (let ((require-final-newline nil))
+		    (gnus-write-buffer filename)))
+		(kill-buffer file-buffer))
+	    (error "Output file does not exist")))
+      (set-buffer tmpbuf)
+      (erase-buffer)
+      (insert-buffer-substring artbuf)
+      (gnus-convert-article-to-rmail)
+      ;; Decide whether to append to a file or to an Emacs buffer.
+      (let ((outbuf (get-file-buffer filename)))
+	(if (not outbuf)
+	    (append-to-file (point-min) (point-max) filename)
+	  ;; File has been visited, in buffer OUTBUF.
+	  (set-buffer outbuf)
+	  (let ((buffer-read-only nil)
+		(msg (and (boundp 'rmail-current-message)
+			  (symbol-value 'rmail-current-message))))
+	    ;; If MSG is non-nil, buffer is in RMAIL mode.
+	    (when msg
+	      (widen)
+	      (narrow-to-region (point-max) (point-max)))
+	    (insert-buffer-substring tmpbuf)
+	    (when msg
+	      (goto-char (point-min))
+	      (widen)
+	      (search-backward "\^_")
+	      (narrow-to-region (point) (point-max))
+	      (goto-char (1+ (point-min)))
+	      (rmail-count-new-messages t)
+	      (rmail-show-message msg))))))
+    (kill-buffer tmpbuf)))
+
+(defun gnus-output-to-mail (filename &optional ask)
+  "Append the current article to a mail file named FILENAME."
+  (setq filename (expand-file-name filename))
+  (let ((artbuf (current-buffer))
+	(tmpbuf (get-buffer-create " *Gnus-output*")))
+    (save-excursion
+      ;; Create the file, if it doesn't exist.
+      (when (and (not (get-file-buffer filename))
+		 (not (file-exists-p filename)))
+	(if (or (not ask)
+		(gnus-y-or-n-p
+		 (concat "\"" filename "\" does not exist, create it? ")))
+	    (let ((file-buffer (create-file-buffer filename)))
+	      (save-excursion
+		(set-buffer file-buffer)
+		(let ((require-final-newline nil))
+		  (gnus-write-buffer filename)))
+	      (kill-buffer file-buffer))
+	  (error "Output file does not exist")))
+      (set-buffer tmpbuf)
+      (erase-buffer)
+      (insert-buffer-substring artbuf)
+      (goto-char (point-min))
+      (if (looking-at "From ")
+	  (forward-line 1)
+	(insert "From nobody " (current-time-string) "\n"))
+      (let (case-fold-search)
+	(while (re-search-forward "^From " nil t)
+	  (beginning-of-line)
+	  (insert ">")))
+      ;; Decide whether to append to a file or to an Emacs buffer.
+      (let ((outbuf (get-file-buffer filename)))
+	(if (not outbuf)
+	    (let ((buffer-read-only nil))
+	      (save-excursion
+		(goto-char (point-max))
+		(forward-char -2)
+		(unless (looking-at "\n\n")
+		  (goto-char (point-max))
+		  (unless (bolp)
+		    (insert "\n"))
+		  (insert "\n"))
+		(goto-char (point-max))
+		(append-to-file (point-min) (point-max) filename)))
+	  ;; File has been visited, in buffer OUTBUF.
+	  (set-buffer outbuf)
+	  (let ((buffer-read-only nil))
+	    (goto-char (point-max))
+	    (unless (eobp)
+	      (insert "\n"))
+	    (insert "\n")
+	    (insert-buffer-substring tmpbuf)))))
+    (kill-buffer tmpbuf)))
+
+(defun gnus-convert-article-to-rmail ()
+  "Convert article in current buffer to Rmail message format."
+  (let ((buffer-read-only nil))
+    ;; Convert article directly into Babyl format.
+    (goto-char (point-min))
+    (insert "\^L\n0, unseen,,\n*** EOOH ***\n")
+    (while (search-forward "\n\^_" nil t) ;single char
+      (replace-match "\n^_" t t))	;2 chars: "^" and "_"
+    (goto-char (point-max))
+    (insert "\^_")))
+
+(provide 'gnus-util)
+
+;;; gnus-util.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-uu.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,2063 @@
+;;; gnus-uu.el --- extract (uu)encoded files in Gnus
+;; Copyright (C) 1985,86,87,93,94,95,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Created: 2 Oct 1993
+;; Keyword: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+(require 'gnus-art)
+(require 'message)
+(require 'gnus-msg)
+
+(defgroup gnus-extract nil
+  "Extracting encoded files."
+  :prefix "gnus-uu-"
+  :group 'gnus)
+
+(defgroup gnus-extract-view nil
+  "Viewwing extracted files."
+  :group 'gnus-extract)
+
+(defgroup gnus-extract-archive nil
+  "Extracting encoded archives."
+  :group 'gnus-extract)
+
+(defgroup gnus-extract-post nil
+  "Extracting encoded archives."
+  :prefix "gnus-uu-post"
+  :group 'gnus-extract)
+
+;; Default viewing action rules
+
+(defcustom gnus-uu-default-view-rules
+  '(("\\.te?xt$\\|\\.doc$\\|read.*me\\|\\.c?$\\|\\.h$\\|\\.bat$\\|\\.asm$\\|makefile" "cat %s | sed s/\r//g")
+    ("\\.pas$" "cat %s | sed s/\r//g")
+    ("\\.[1-9]$" "groff -mandoc -Tascii %s | sed s/\b.//g")
+    ("\\.\\(jpe?g\\|gif\\|tiff?\\|p[pgb]m\\|xwd\\|xbm\\|pcx\\)$" "xv")
+    ("\\.tga$" "tgatoppm %s | xv -")
+    ("\\.\\(wav\\|aiff\\|hcom\\|u[blw]\\|s[bfw]\\|voc\\|smp\\)$"
+     "sox -v .5 %s -t .au -u - > /dev/audio")
+    ("\\.au$" "cat %s > /dev/audio")
+    ("\\.midi?$" "playmidi -f")
+    ("\\.mod$" "str32")
+    ("\\.ps$" "ghostview")
+    ("\\.dvi$" "xdvi")
+    ("\\.html$" "xmosaic")
+    ("\\.mpe?g$" "mpeg_play")
+    ("\\.\\(flc\\|fli\\|rle\\|iff\\|pfx\\|avi\\|sme\\|rpza\\|dl\\|qt\\|rsrc\\|mov\\)$" "xanim")
+    ("\\.\\(tar\\|arj\\|zip\\|zoo\\|arc\\|gz\\|Z\\|lzh\\|ar\\|lha\\)$"
+     "gnus-uu-archive"))
+  "Default actions to be taken when the user asks to view a file.
+To change the behaviour, you can either edit this variable or set
+`gnus-uu-user-view-rules' to something useful.
+
+For example:
+
+To make gnus-uu use 'xli' to display JPEG and GIF files, put the
+following in your .emacs file:
+
+  (setq gnus-uu-user-view-rules '((\"jpg$\\\\|gif$\" \"xli\")))
+
+Both these variables are lists of lists with two string elements.  The
+first string is a regular expression.  If the file name matches this
+regular expression, the command in the second string is executed with
+the file as an argument.
+
+If the command string contains \"%s\", the file name will be inserted
+at that point in the command string.  If there's no \"%s\" in the
+command string, the file name will be appended to the command string
+before executing.
+
+There are several user variables to tailor the behaviour of gnus-uu to
+your needs.  First we have `gnus-uu-user-view-rules', which is the
+variable gnus-uu first consults when trying to decide how to view a
+file.  If this variable contains no matches, gnus-uu examines the
+default rule variable provided in this package.  If gnus-uu finds no
+match here, it uses `gnus-uu-user-view-rules-end' to try to make a
+match."
+  :group 'gnus-extract-view
+  :type '(repeat (group regexp (string :tag "Command"))))
+
+(defcustom gnus-uu-user-view-rules nil
+  "What actions are to be taken to view a file.
+See the documentation on the `gnus-uu-default-view-rules' variable for
+details."
+  :group 'gnus-extract-view
+  :type '(repeat (group regexp (string :tag "Command"))))
+
+(defcustom gnus-uu-user-view-rules-end
+  '(("" "file"))
+  "What actions are to be taken if no rule matched the file name.
+See the documentation on the `gnus-uu-default-view-rules' variable for
+details."
+  :group 'gnus-extract-view
+  :type '(repeat (group regexp (string :tag "Command"))))
+
+;; Default unpacking commands
+
+(defcustom gnus-uu-default-archive-rules
+  '(("\\.tar$" "tar xf")
+    ("\\.zip$" "unzip -o")
+    ("\\.ar$" "ar x")
+    ("\\.arj$" "unarj x")
+    ("\\.zoo$" "zoo -e")
+    ("\\.\\(lzh\\|lha\\)$" "lha x")
+    ("\\.Z$" "uncompress")
+    ("\\.gz$" "gunzip")
+    ("\\.arc$" "arc -x"))
+  "See `gnus-uu-user-archive-rules'."
+  :group 'gnus-extract-archive
+  :type '(repeat (group regexp (string :tag "Command"))))
+
+(defvar gnus-uu-destructive-archivers
+  (list "uncompress" "gunzip"))
+
+(defcustom gnus-uu-user-archive-rules nil
+  "A list that can be set to override the default archive unpacking commands.
+To use, for instance, 'untar' to unpack tar files and 'zip -x' to
+unpack zip files, say the following:
+  (setq gnus-uu-user-archive-rules
+    '((\"\\\\.tar$\" \"untar\")
+      (\"\\\\.zip$\" \"zip -x\")))"
+  :group 'gnus-extract-archive
+  :type '(repeat (group regexp (string :tag "Command"))))
+
+(defcustom gnus-uu-ignore-files-by-name nil
+  "*A regular expression saying what files should not be viewed based on name.
+If, for instance, you want gnus-uu to ignore all .au and .wav files,
+you could say something like
+
+  (setq gnus-uu-ignore-files-by-name \"\\\\.au$\\\\|\\\\.wav$\")
+
+Note that this variable can be used in conjunction with the
+`gnus-uu-ignore-files-by-type' variable."
+  :group 'gnus-extract
+  :type '(choice (const :tag "off" nil)
+		 (regexp :format "%v")))
+
+(defcustom gnus-uu-ignore-files-by-type nil
+  "*A regular expression saying what files that shouldn't be viewed, based on MIME file type.
+If, for instance, you want gnus-uu to ignore all audio files and all mpegs,
+you could say something like
+
+  (setq gnus-uu-ignore-files-by-type \"audio/\\\\|video/mpeg\")
+
+Note that this variable can be used in conjunction with the
+`gnus-uu-ignore-files-by-name' variable."
+  :group 'gnus-extract
+  :type '(choice (const :tag "off" nil)
+		 (regexp :format "%v")))
+
+;; Pseudo-MIME support
+
+(defconst gnus-uu-ext-to-mime-list
+  '(("\\.gif$" "image/gif")
+    ("\\.jpe?g$" "image/jpeg")
+    ("\\.tiff?$" "image/tiff")
+    ("\\.xwd$" "image/xwd")
+    ("\\.pbm$" "image/pbm")
+    ("\\.pgm$" "image/pgm")
+    ("\\.ppm$" "image/ppm")
+    ("\\.xbm$" "image/xbm")
+    ("\\.pcx$" "image/pcx")
+    ("\\.tga$" "image/tga")
+    ("\\.ps$" "image/postscript")
+    ("\\.fli$" "video/fli")
+    ("\\.wav$" "audio/wav")
+    ("\\.aiff$" "audio/aiff")
+    ("\\.hcom$" "audio/hcom")
+    ("\\.voc$" "audio/voc")
+    ("\\.smp$" "audio/smp")
+    ("\\.mod$" "audio/mod")
+    ("\\.dvi$" "image/dvi")
+    ("\\.mpe?g$" "video/mpeg")
+    ("\\.au$" "audio/basic")
+    ("\\.\\(te?xt\\|doc\\|c\\|h\\)$" "text/plain")
+    ("\\.\\(c\\|h\\)$" "text/source")
+    ("read.*me" "text/plain")
+    ("\\.html$" "text/html")
+    ("\\.bat$" "text/bat")
+    ("\\.[1-6]$" "text/man")
+    ("\\.flc$" "video/flc")
+    ("\\.rle$" "video/rle")
+    ("\\.pfx$" "video/pfx")
+    ("\\.avi$" "video/avi")
+    ("\\.sme$" "video/sme")
+    ("\\.rpza$" "video/prza")
+    ("\\.dl$" "video/dl")
+    ("\\.qt$" "video/qt")
+    ("\\.rsrc$" "video/rsrc")
+    ("\\..*$" "unknown/unknown")))
+
+;; Various variables users may set
+
+(defcustom gnus-uu-tmp-dir "/tmp/"
+  "*Variable saying where gnus-uu is to do its work.
+Default is \"/tmp/\"."
+  :group 'gnus-extract
+  :type 'directory)
+
+(defcustom gnus-uu-do-not-unpack-archives nil
+  "*Non-nil means that gnus-uu won't peek inside archives looking for files to display.
+Default is nil."
+  :group 'gnus-extract-archive
+  :type 'boolean)
+
+(defcustom gnus-uu-ignore-default-view-rules nil
+  "*Non-nil means that gnus-uu will ignore the default viewing rules.
+Only the user viewing rules will be consulted.  Default is nil."
+  :group 'gnus-extract-view
+  :type 'boolean)
+
+(defcustom gnus-uu-grabbed-file-functions nil
+  "Functions run on each file after successful decoding.
+They will be called with the name of the file as the argument.
+Likely functions you can use in this list are `gnus-uu-grab-view'
+and `gnus-uu-grab-move'."
+  :group 'gnus-extract
+  :options '(gnus-uu-grab-view gnus-uu-grab-move)
+  :type 'hook)
+
+(defcustom gnus-uu-ignore-default-archive-rules nil
+  "*Non-nil means that gnus-uu will ignore the default archive unpacking commands.
+Only the user unpacking commands will be consulted.  Default is nil."
+  :group 'gnus-extract-archive
+  :type 'boolean)
+
+(defcustom gnus-uu-kill-carriage-return t
+  "*Non-nil means that gnus-uu will strip all carriage returns from articles.
+Default is t."
+  :group 'gnus-extract
+  :type 'boolean)
+
+(defcustom gnus-uu-view-with-metamail nil
+  "*Non-nil means that files will be viewed with metamail.
+The gnus-uu viewing functions will be ignored and gnus-uu will try
+to guess at a content-type based on file name suffixes.  Default
+it nil."
+  :group 'gnus-extract
+  :type 'boolean)
+
+(defcustom gnus-uu-unmark-articles-not-decoded nil
+  "*Non-nil means that gnus-uu will mark articles that were unsuccessfully decoded as unread.
+Default is nil."
+  :group 'gnus-extract
+  :type 'boolean)
+
+(defcustom gnus-uu-correct-stripped-uucode nil
+  "*Non-nil means that gnus-uu will *try* to fix uuencoded files that have had trailing spaces deleted.
+Default is nil."
+  :group 'gnus-extract
+  :type 'boolean)
+
+(defcustom gnus-uu-save-in-digest nil
+  "*Non-nil means that gnus-uu, when asked to save without decoding, will save in digests.
+If this variable is nil, gnus-uu will just save everything in a
+file without any embellishments.  The digesting almost conforms to RFC1153 -
+no easy way to specify any meaningful volume and issue numbers were found,
+so I simply dropped them."
+  :group 'gnus-extract
+  :type 'boolean)
+
+(defcustom gnus-uu-digest-headers
+  '("^Date:" "^From:" "^To:" "^Cc:" "^Subject:" "^Message-ID:" "^Keywords:"
+    "^Summary:" "^References:")
+  "List of regexps to match headers included in digested messages.
+The headers will be included in the sequence they are matched."
+  :group 'gnus-extract
+  :type '(repeat regexp))
+
+(defcustom gnus-uu-save-separate-articles nil
+  "*Non-nil means that gnus-uu will save articles in separate files."
+  :group 'gnus-extract
+  :type 'boolean)
+
+(defcustom gnus-uu-be-dangerous 'ask
+  "*Specifies what to do if unusual situations arise during decoding.
+If nil, be as conservative as possible.  If t, ignore things that
+didn't work, and overwrite existing files.  Otherwise, ask each time."
+  :group 'gnus-extract
+  :type '(choice (const :tag "conservative" nil)
+		 (const :tag "ask" ask)
+		 (const :tag "liberal" t)))
+
+;; Internal variables
+
+(defvar gnus-uu-saved-article-name nil)
+
+(defconst gnus-uu-begin-string "^begin[ \t]+[0-7][0-7][0-7][ \t]+\\(.*\\)$")
+(defconst gnus-uu-end-string "^end[ \t]*$")
+
+(defconst gnus-uu-body-line "^M")
+(let ((i 61))
+  (while (> (setq i (1- i)) 0)
+    (setq gnus-uu-body-line (concat gnus-uu-body-line "[^a-z]")))
+  (setq gnus-uu-body-line (concat gnus-uu-body-line ".?$")))
+
+;"^M.............................................................?$"
+
+(defconst gnus-uu-shar-begin-string "^#! */bin/sh")
+
+(defvar gnus-uu-shar-file-name nil)
+(defconst gnus-uu-shar-name-marker "begin [0-7][0-7][0-7][ \t]+\\(\\(\\w\\|\\.\\)*\\b\\)")
+
+(defconst gnus-uu-postscript-begin-string "^%!PS-")
+(defconst gnus-uu-postscript-end-string "^%%EOF$")
+
+(defvar gnus-uu-file-name nil)
+(defconst gnus-uu-uudecode-process nil)
+(defvar gnus-uu-binhex-article-name nil)
+
+(defvar gnus-uu-work-dir nil)
+
+(defconst gnus-uu-output-buffer-name " *Gnus UU Output*")
+
+(defvar gnus-uu-default-dir gnus-article-save-directory)
+(defvar gnus-uu-digest-from-subject nil)
+
+;; Keymaps
+
+(gnus-define-keys (gnus-uu-mark-map "P" gnus-summary-mark-map)
+  "p" gnus-summary-mark-as-processable
+  "u" gnus-summary-unmark-as-processable
+  "U" gnus-summary-unmark-all-processable
+  "v" gnus-uu-mark-over
+  "s" gnus-uu-mark-series
+  "r" gnus-uu-mark-region
+  "R" gnus-uu-mark-by-regexp
+  "t" gnus-uu-mark-thread
+  "T" gnus-uu-unmark-thread
+  "a" gnus-uu-mark-all
+  "b" gnus-uu-mark-buffer
+  "S" gnus-uu-mark-sparse
+  "k" gnus-summary-kill-process-mark
+  "y" gnus-summary-yank-process-mark
+  "w" gnus-summary-save-process-mark
+  "i" gnus-uu-invert-processable)
+
+(gnus-define-keys (gnus-uu-extract-map "X" gnus-summary-mode-map)
+  ;;"x" gnus-uu-extract-any
+  ;;"m" gnus-uu-extract-mime
+  "u" gnus-uu-decode-uu
+  "U" gnus-uu-decode-uu-and-save
+  "s" gnus-uu-decode-unshar
+  "S" gnus-uu-decode-unshar-and-save
+  "o" gnus-uu-decode-save
+  "O" gnus-uu-decode-save
+  "b" gnus-uu-decode-binhex
+  "B" gnus-uu-decode-binhex
+  "p" gnus-uu-decode-postscript
+  "P" gnus-uu-decode-postscript-and-save)
+
+(gnus-define-keys
+ (gnus-uu-extract-view-map "v" gnus-uu-extract-map)
+ "u" gnus-uu-decode-uu-view
+ "U" gnus-uu-decode-uu-and-save-view
+ "s" gnus-uu-decode-unshar-view
+ "S" gnus-uu-decode-unshar-and-save-view
+ "o" gnus-uu-decode-save-view
+ "O" gnus-uu-decode-save-view
+ "b" gnus-uu-decode-binhex-view
+ "B" gnus-uu-decode-binhex-view
+ "p" gnus-uu-decode-postscript-view
+ "P" gnus-uu-decode-postscript-and-save-view)
+
+
+;; Commands.
+
+(defun gnus-uu-decode-uu (&optional n)
+  "Uudecodes the current article."
+  (interactive "P")
+  (gnus-uu-decode-with-method 'gnus-uu-uustrip-article n))
+
+(defun gnus-uu-decode-uu-and-save (n dir)
+  "Decodes and saves the resulting file."
+  (interactive
+   (list current-prefix-arg
+	 (file-name-as-directory
+	  (read-file-name "Uudecode and save in dir: "
+			  gnus-uu-default-dir
+			  gnus-uu-default-dir t))))
+  (gnus-uu-decode-with-method 'gnus-uu-uustrip-article n dir nil nil t))
+
+(defun gnus-uu-decode-unshar (&optional n)
+  "Unshars the current article."
+  (interactive "P")
+  (gnus-uu-decode-with-method 'gnus-uu-unshar-article n nil nil 'scan t))
+
+(defun gnus-uu-decode-unshar-and-save (n dir)
+  "Unshars and saves the current article."
+  (interactive
+   (list current-prefix-arg
+	 (file-name-as-directory
+	  (read-file-name "Unshar and save in dir: "
+			  gnus-uu-default-dir
+			  gnus-uu-default-dir t))))
+  (gnus-uu-decode-with-method 'gnus-uu-unshar-article n dir nil 'scan t))
+
+(defun gnus-uu-decode-save (n file)
+  "Saves the current article."
+  (interactive
+   (list current-prefix-arg
+	 (read-file-name
+	  (if gnus-uu-save-separate-articles
+	      "Save articles is dir: "
+	    "Save articles in file: ")
+	  gnus-uu-default-dir
+	  gnus-uu-default-dir)))
+  (setq gnus-uu-saved-article-name file)
+  (gnus-uu-decode-with-method 'gnus-uu-save-article n nil t))
+
+(defun gnus-uu-decode-binhex (n dir)
+  "Unbinhexes the current article."
+  (interactive
+   (list current-prefix-arg
+	 (file-name-as-directory
+	  (read-file-name "Unbinhex and save in dir: "
+			  gnus-uu-default-dir
+			  gnus-uu-default-dir))))
+  (setq gnus-uu-binhex-article-name
+	(make-temp-name (concat gnus-uu-work-dir "binhex")))
+  (gnus-uu-decode-with-method 'gnus-uu-binhex-article n dir))
+
+(defun gnus-uu-decode-uu-view (&optional n)
+  "Uudecodes and views the current article."
+  (interactive "P")
+  (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
+    (gnus-uu-decode-uu n)))
+
+(defun gnus-uu-decode-uu-and-save-view (n dir)
+  "Decodes, views and saves the resulting file."
+  (interactive
+   (list current-prefix-arg
+	 (read-file-name "Uudecode, view and save in dir: "
+			 gnus-uu-default-dir
+			 gnus-uu-default-dir t)))
+  (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
+    (gnus-uu-decode-uu-and-save n dir)))
+
+(defun gnus-uu-decode-unshar-view (&optional n)
+  "Unshars and views the current article."
+  (interactive "P")
+  (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
+    (gnus-uu-decode-unshar n)))
+
+(defun gnus-uu-decode-unshar-and-save-view (n dir)
+  "Unshars and saves the current article."
+  (interactive
+   (list current-prefix-arg
+	 (read-file-name "Unshar, view and save in dir: "
+			 gnus-uu-default-dir
+			 gnus-uu-default-dir t)))
+  (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
+    (gnus-uu-decode-unshar-and-save n dir)))
+
+(defun gnus-uu-decode-save-view (n file)
+  "Saves and views the current article."
+  (interactive
+   (list current-prefix-arg
+	 (read-file-name  (if gnus-uu-save-separate-articles
+			      "Save articles is dir: "
+			    "Save articles in file: ")
+			  gnus-uu-default-dir gnus-uu-default-dir)))
+  (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
+    (gnus-uu-decode-save n file)))
+
+(defun gnus-uu-decode-binhex-view (n file)
+  "Unbinhexes and views the current article."
+  (interactive
+   (list current-prefix-arg
+	 (read-file-name "Unbinhex, view and save in dir: "
+			 gnus-uu-default-dir gnus-uu-default-dir)))
+  (setq gnus-uu-binhex-article-name
+	(make-temp-name (concat gnus-uu-work-dir "binhex")))
+  (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
+    (gnus-uu-decode-binhex n file)))
+
+
+;; Digest and forward articles
+
+(defun gnus-uu-digest-mail-forward (&optional n post)
+  "Digests and forwards all articles in this series."
+  (interactive "P")
+  (let ((gnus-uu-save-in-digest t)
+	(file (make-temp-name (nnheader-concat gnus-uu-tmp-dir "forward")))
+	buf subject from newsgroups)
+    (gnus-setup-message 'forward
+      (setq gnus-uu-digest-from-subject nil)
+      (gnus-uu-decode-save n file)
+      (setq buf (switch-to-buffer (get-buffer-create " *gnus-uu-forward*")))
+      (gnus-add-current-to-buffer-list)
+      (erase-buffer)
+      (insert-file file)
+      (let ((fs gnus-uu-digest-from-subject))
+	(when fs
+	  (setq from (caar fs)
+		subject (gnus-simplify-subject-fuzzy (cdar fs))
+		fs (cdr fs))
+	  (while (and fs (or from subject))
+	    (when from
+	      (unless (string= from (caar fs))
+		(setq from nil)))
+	    (when subject
+	      (unless (string= (gnus-simplify-subject-fuzzy (cdar fs))
+			       subject)
+		(setq subject nil)))
+	    (setq fs (cdr fs))))
+	(unless subject
+	  (setq subject "Digested Articles"))
+	(unless from
+	  (setq from
+		(if (gnus-news-group-p gnus-newsgroup-name)
+		    gnus-newsgroup-name
+		  "Various"))))
+      (goto-char (point-min))
+      (when (re-search-forward "^Subject: ")
+	(delete-region (point) (gnus-point-at-eol))
+	(insert subject))
+      (goto-char (point-min))
+      (when (re-search-forward "^From: ")
+	(delete-region (point) (gnus-point-at-eol))
+	(insert from))
+      (message-forward post))
+    (delete-file file)
+    (kill-buffer buf)
+    (setq gnus-uu-digest-from-subject nil)))
+
+(defun gnus-uu-digest-post-forward (&optional n)
+  "Digest and forward to a newsgroup."
+  (interactive "P")
+  (gnus-uu-digest-mail-forward n t))
+
+;; Process marking.
+
+(defun gnus-uu-mark-by-regexp (regexp &optional unmark)
+  "Ask for a regular expression and set the process mark on all articles that match."
+  (interactive (list (read-from-minibuffer "Mark (regexp): ")))
+  (gnus-set-global-variables)
+  (let ((articles (gnus-uu-find-articles-matching regexp)))
+    (while articles
+      (if unmark
+	  (gnus-summary-remove-process-mark (pop articles))
+	(gnus-summary-set-process-mark (pop articles))))
+    (message ""))
+  (gnus-summary-position-point))
+
+(defun gnus-uu-unmark-by-regexp (regexp &optional unmark)
+  "Ask for a regular expression and remove the process mark on all articles that match."
+  (interactive (list (read-from-minibuffer "Mark (regexp): ")))
+  (gnus-uu-mark-by-regexp regexp t))
+
+(defun gnus-uu-mark-series ()
+  "Mark the current series with the process mark."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((articles (gnus-uu-find-articles-matching)))
+    (while articles
+      (gnus-summary-set-process-mark (car articles))
+      (setq articles (cdr articles)))
+    (message ""))
+  (gnus-summary-position-point))
+
+(defun gnus-uu-mark-region (beg end &optional unmark)
+  "Set the process mark on all articles between point and mark."
+  (interactive "r")
+  (gnus-set-global-variables)
+  (save-excursion
+    (goto-char beg)
+    (while (< (point) end)
+      (if unmark
+	  (gnus-summary-remove-process-mark (gnus-summary-article-number))
+	(gnus-summary-set-process-mark (gnus-summary-article-number)))
+      (forward-line 1)))
+  (gnus-summary-position-point))
+
+(defun gnus-uu-unmark-region (beg end)
+  "Remove the process mark from all articles between point and mark."
+  (interactive "r")
+  (gnus-uu-mark-region beg end t))
+
+(defun gnus-uu-mark-buffer ()
+  "Set the process mark on all articles in the buffer."
+  (interactive)
+  (gnus-uu-mark-region (point-min) (point-max)))
+
+(defun gnus-uu-unmark-buffer ()
+  "Remove the process mark on all articles in the buffer."
+  (interactive)
+  (gnus-uu-mark-region (point-min) (point-max) t))
+
+(defun gnus-uu-mark-thread ()
+  "Marks all articles downwards in this thread."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((level (gnus-summary-thread-level)))
+    (while (and (gnus-summary-set-process-mark (gnus-summary-article-number))
+		(zerop (gnus-summary-next-subject 1))
+		(> (gnus-summary-thread-level) level))))
+  (gnus-summary-position-point))
+
+(defun gnus-uu-unmark-thread ()
+  "Unmarks all articles downwards in this thread."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((level (gnus-summary-thread-level)))
+    (while (and (gnus-summary-remove-process-mark
+		 (gnus-summary-article-number))
+		(zerop (gnus-summary-next-subject 1))
+		(> (gnus-summary-thread-level) level))))
+  (gnus-summary-position-point))
+
+(defun gnus-uu-invert-processable ()
+  "Invert the list of process-marked articles."
+  (let ((data gnus-newsgroup-data)
+	d number)
+    (save-excursion
+      (while data
+	(if (memq (setq number (gnus-data-number (pop data)))
+		  gnus-newsgroup-processable)
+	    (gnus-summary-remove-process-mark number)
+	  (gnus-summary-set-process-mark number)))))
+  (gnus-summary-position-point))
+
+(defun gnus-uu-mark-over (&optional score)
+  "Mark all articles with a score over SCORE (the prefix.)"
+  (interactive "P")
+  (let ((score (gnus-score-default score))
+	(data gnus-newsgroup-data))
+    (save-excursion
+      (while data
+	(when (> (or (cdr (assq (gnus-data-number (car data))
+				gnus-newsgroup-scored))
+		     gnus-summary-default-score 0)
+		 score)
+	  (gnus-summary-set-process-mark (caar data)))
+	(setq data (cdr data))))
+    (gnus-summary-position-point)))
+
+(defun gnus-uu-mark-sparse ()
+  "Mark all series that have some articles marked."
+  (interactive)
+  (gnus-set-global-variables)
+  (let ((marked (nreverse gnus-newsgroup-processable))
+	subject articles total headers)
+    (unless marked
+      (error "No articles marked with the process mark"))
+    (setq gnus-newsgroup-processable nil)
+    (save-excursion
+      (while marked
+	(and (vectorp (setq headers
+			    (gnus-summary-article-header (car marked))))
+	     (setq subject (mail-header-subject headers)
+		   articles (gnus-uu-find-articles-matching
+			     (gnus-uu-reginize-string subject))
+		   total (nconc total articles)))
+	(while articles
+	  (gnus-summary-set-process-mark (car articles))
+	  (setcdr marked (delq (car articles) (cdr marked)))
+	  (setq articles (cdr articles)))
+	(setq marked (cdr marked)))
+      (setq gnus-newsgroup-processable (nreverse total)))
+    (gnus-summary-position-point)))
+
+(defun gnus-uu-mark-all ()
+  "Mark all articles in \"series\" order."
+  (interactive)
+  (gnus-set-global-variables)
+  (setq gnus-newsgroup-processable nil)
+  (save-excursion
+    (let ((data gnus-newsgroup-data)
+	  number)
+      (while data
+	(when (and (not (memq (setq number (gnus-data-number (car data)))
+			      gnus-newsgroup-processable))
+		   (vectorp (gnus-data-header (car data))))
+	  (gnus-summary-goto-subject number)
+	  (gnus-uu-mark-series))
+	(setq data (cdr data)))))
+  (gnus-summary-position-point))
+
+;; All PostScript functions written by Erik Selberg <speed@cs.washington.edu>.
+
+(defun gnus-uu-decode-postscript (&optional n)
+  "Gets postscript of the current article."
+  (interactive "P")
+  (gnus-uu-decode-with-method 'gnus-uu-decode-postscript-article n))
+
+(defun gnus-uu-decode-postscript-view (&optional n)
+  "Gets and views the current article."
+  (interactive "P")
+  (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
+    (gnus-uu-decode-postscript n)))
+
+(defun gnus-uu-decode-postscript-and-save (n dir)
+  "Extracts postscript and saves the current article."
+  (interactive
+   (list current-prefix-arg
+	 (file-name-as-directory
+	  (read-file-name "Save in dir: "
+			  gnus-uu-default-dir
+			  gnus-uu-default-dir t))))
+  (gnus-uu-decode-with-method 'gnus-uu-decode-postscript-article
+			      n dir nil nil t))
+
+(defun gnus-uu-decode-postscript-and-save-view (n dir)
+  "Decodes, views and saves the resulting file."
+  (interactive
+   (list current-prefix-arg
+	 (read-file-name "Where do you want to save the file(s)? "
+			 gnus-uu-default-dir
+			 gnus-uu-default-dir t)))
+  (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
+    (gnus-uu-decode-postscript-and-save n dir)))
+
+
+;; Internal functions.
+
+(defun gnus-uu-decode-with-method (method n &optional save not-insert
+					  scan cdir)
+  (gnus-uu-initialize scan)
+  (when save
+    (setq gnus-uu-default-dir save))
+  ;; Create the directory we save to.
+  (when (and scan cdir save
+	     (not (file-exists-p save)))
+    (make-directory save t))
+  (let ((articles (gnus-uu-get-list-of-articles n))
+	files)
+    (setq files (gnus-uu-grab-articles articles method t))
+    (let ((gnus-current-article (car articles)))
+      (when scan
+	(setq files (gnus-uu-scan-directory gnus-uu-work-dir))))
+    (when save
+      (gnus-uu-save-files files save))
+    (when (eq gnus-uu-do-not-unpack-archives nil)
+      (setq files (gnus-uu-unpack-files files)))
+    (setq files (nreverse (gnus-uu-get-actions files)))
+    (or not-insert (not gnus-insert-pseudo-articles)
+	(gnus-summary-insert-pseudos files save))))
+
+(defun gnus-uu-scan-directory (dir &optional rec)
+  "Return a list of all files under DIR."
+  (let ((files (directory-files dir t))
+	out file)
+    (while (setq file (pop files))
+      (unless (member (file-name-nondirectory file) '("." ".."))
+	(push (list (cons 'name file)
+		    (cons 'article gnus-current-article))
+	      out)
+	(when (file-directory-p file)
+	  (setq out (nconc (gnus-uu-scan-directory file t) out)))))
+    (if rec
+	out
+      (nreverse out))))
+
+(defun gnus-uu-save-files (files dir)
+  "Save FILES in DIR."
+  (let ((len (length files))
+	(reg (concat "^" (regexp-quote gnus-uu-work-dir)))
+	to-file file fromdir)
+    (while (setq file (cdr (assq 'name (pop files))))
+      (when (file-exists-p file)
+	(string-match reg file)
+	(setq fromdir (substring file (match-end 0)))
+	(if (file-directory-p file)
+	    (gnus-make-directory (concat dir fromdir))
+	  (setq to-file (concat dir fromdir))
+	  (when (or (not (file-exists-p to-file))
+		    (eq gnus-uu-be-dangerous t)
+		    (and gnus-uu-be-dangerous
+			 (gnus-y-or-n-p (format "%s exists; overwrite? "
+						to-file))))
+	    (copy-file file to-file t t)))))
+    (gnus-message 5 "Saved %d file%s" len (if (= len 1) "" "s"))))
+
+;; Functions for saving and possibly digesting articles without
+;; any decoding.
+
+;; Function called by gnus-uu-grab-articles to treat each article.
+(defun gnus-uu-save-article (buffer in-state)
+  (cond
+   (gnus-uu-save-separate-articles
+    (save-excursion
+      (set-buffer buffer)
+      (gnus-write-buffer
+       (concat gnus-uu-saved-article-name gnus-current-article))
+      (cond ((eq in-state 'first) (list gnus-uu-saved-article-name 'begin))
+	    ((eq in-state 'first-and-last) (list gnus-uu-saved-article-name
+						 'begin 'end))
+	    ((eq in-state 'last) (list 'end))
+	    (t (list 'middle)))))
+   ((not gnus-uu-save-in-digest)
+    (save-excursion
+      (set-buffer buffer)
+      (write-region (point-min) (point-max) gnus-uu-saved-article-name t)
+      (cond ((eq in-state 'first) (list gnus-uu-saved-article-name 'begin))
+	    ((eq in-state 'first-and-last) (list gnus-uu-saved-article-name
+						 'begin 'end))
+	    ((eq in-state 'last) (list 'end))
+	    (t (list 'middle)))))
+   (t
+    (let ((header (gnus-summary-article-header)))
+      (push (cons (mail-header-from header)
+		  (mail-header-subject header))
+	    gnus-uu-digest-from-subject))
+    (let ((name (file-name-nondirectory gnus-uu-saved-article-name))
+	  (delim (concat "^" (make-string 30 ?-) "$"))
+	  beg subj headers headline sorthead body end-string state)
+      (if (or (eq in-state 'first)
+	      (eq in-state 'first-and-last))
+	  (progn
+	    (setq state (list 'begin))
+	    (save-excursion (set-buffer (get-buffer-create "*gnus-uu-body*"))
+			    (erase-buffer))
+	    (save-excursion
+	      (set-buffer (get-buffer-create "*gnus-uu-pre*"))
+	      (erase-buffer)
+	      (insert (format
+		       "Date: %s\nFrom: %s\nSubject: %s Digest\n\nTopics:\n"
+		       (current-time-string) name name))))
+	(when (not (eq in-state 'end))
+	  (setq state (list 'middle))))
+      (save-excursion
+	(set-buffer (get-buffer "*gnus-uu-body*"))
+	(goto-char (setq beg (point-max)))
+	(save-excursion
+	  (save-restriction
+	    (set-buffer buffer)
+	    (let (buffer-read-only)
+	      (gnus-set-text-properties (point-min) (point-max) nil)
+	      ;; These two are necessary for XEmacs 19.12 fascism.
+	      (put-text-property (point-min) (point-max) 'invisible nil)
+	      (put-text-property (point-min) (point-max) 'intangible nil))
+	    (goto-char (point-min))
+	    (re-search-forward "\n\n")
+	    ;; Quote all 30-dash lines.
+	    (save-excursion
+	      (while (re-search-forward delim nil t)
+		(beginning-of-line)
+		(delete-char 1)
+		(insert " ")))
+	    (setq body (buffer-substring (1- (point)) (point-max)))
+	    (narrow-to-region (point-min) (point))
+	    (if (not (setq headers gnus-uu-digest-headers))
+		(setq sorthead (buffer-substring (point-min) (point-max)))
+	      (while headers
+		(setq headline (car headers))
+		(setq headers (cdr headers))
+		(goto-char (point-min))
+		(while (re-search-forward headline nil t)
+		  (setq sorthead
+			(concat sorthead
+				(buffer-substring
+				 (match-beginning 0)
+				 (or (and (re-search-forward "^[^ \t]" nil t)
+					  (1- (point)))
+				     (progn (forward-line 1) (point)))))))))
+	    (widen)))
+	(insert sorthead) (goto-char (point-max))
+	(insert body) (goto-char (point-max))
+	(insert (concat "\n" (make-string 30 ?-) "\n\n"))
+	(goto-char beg)
+	(when (re-search-forward "^Subject: \\(.*\\)$" nil t)
+	  (setq subj (buffer-substring (match-beginning 1) (match-end 1)))
+	  (save-excursion
+	    (set-buffer (get-buffer "*gnus-uu-pre*"))
+	    (insert (format "   %s\n" subj)))))
+      (when (or (eq in-state 'last)
+		(eq in-state 'first-and-last))
+	(save-excursion
+	  (set-buffer (get-buffer "*gnus-uu-pre*"))
+	  (insert (format "\n\n%s\n\n" (make-string 70 ?-)))
+	  (gnus-write-buffer gnus-uu-saved-article-name))
+	(save-excursion
+	  (set-buffer (get-buffer "*gnus-uu-body*"))
+	  (goto-char (point-max))
+	  (insert
+	   (concat (setq end-string (format "End of %s Digest" name))
+		   "\n"))
+	  (insert (concat (make-string (length end-string) ?*) "\n"))
+	  (write-region
+	   (point-min) (point-max) gnus-uu-saved-article-name t))
+	(kill-buffer (get-buffer "*gnus-uu-pre*"))
+	(kill-buffer (get-buffer "*gnus-uu-body*"))
+	(push 'end state))
+      (if (memq 'begin state)
+	  (cons gnus-uu-saved-article-name state)
+	state)))))
+
+;; Binhex treatment - not very advanced.
+
+(defconst gnus-uu-binhex-body-line
+  "^[^:]...............................................................$")
+(defconst gnus-uu-binhex-begin-line
+  "^:...............................................................$")
+(defconst gnus-uu-binhex-end-line
+  ":$")
+
+(defun gnus-uu-binhex-article (buffer in-state)
+  (let (state start-char)
+    (save-excursion
+      (set-buffer buffer)
+      (widen)
+      (goto-char (point-min))
+      (when (not (re-search-forward gnus-uu-binhex-begin-line nil t))
+	(when (not (re-search-forward gnus-uu-binhex-body-line nil t))
+	  (setq state (list 'wrong-type))))
+
+      (if (memq 'wrong-type state)
+	  ()
+	(beginning-of-line)
+	(setq start-char (point))
+	(if (looking-at gnus-uu-binhex-begin-line)
+	    (progn
+	      (setq state (list 'begin))
+	      (write-region 1 1 gnus-uu-binhex-article-name))
+	  (setq state (list 'middle)))
+	(goto-char (point-max))
+	(re-search-backward (concat gnus-uu-binhex-body-line "\\|"
+				    gnus-uu-binhex-end-line)
+			    nil t)
+	(when (looking-at gnus-uu-binhex-end-line)
+	  (setq state (if (memq 'begin state)
+			  (cons 'end state)
+			(list 'end))))
+	(beginning-of-line)
+	(forward-line 1)
+	(when (file-exists-p gnus-uu-binhex-article-name)
+	  (append-to-file start-char (point) gnus-uu-binhex-article-name))))
+    (if (memq 'begin state)
+	(cons gnus-uu-binhex-article-name state)
+      state)))
+
+;; PostScript
+
+(defun gnus-uu-decode-postscript-article (process-buffer in-state)
+  (let ((state (list 'ok))
+	start-char end-char file-name)
+    (save-excursion
+      (set-buffer process-buffer)
+      (goto-char (point-min))
+      (if (not (re-search-forward gnus-uu-postscript-begin-string nil t))
+	  (setq state (list 'wrong-type))
+	(beginning-of-line)
+	(setq start-char (point))
+	(if (not (re-search-forward gnus-uu-postscript-end-string nil t))
+	    (setq state (list 'wrong-type))
+	  (setq end-char (point))
+	  (set-buffer (get-buffer-create gnus-uu-output-buffer-name))
+	  (insert-buffer-substring process-buffer start-char end-char)
+	  (setq file-name (concat gnus-uu-work-dir
+				  (cdr gnus-article-current) ".ps"))
+	  (write-region (point-min) (point-max) file-name)
+	  (setq state (list file-name 'begin 'end)))))
+    state))
+
+
+;; Find actions.
+
+(defun gnus-uu-get-actions (files)
+  (let ((ofiles files)
+	action name)
+    (while files
+      (setq name (cdr (assq 'name (car files))))
+      (and
+       (setq action (gnus-uu-get-action name))
+       (setcar files (nconc (list (if (string= action "gnus-uu-archive")
+				      (cons 'action "file")
+				    (cons 'action action))
+				  (cons 'execute (gnus-uu-command
+						  action name)))
+			    (car files))))
+      (setq files (cdr files)))
+    ofiles))
+
+(defun gnus-uu-get-action (file-name)
+  (let (action)
+    (setq action
+	  (gnus-uu-choose-action
+	   file-name
+	   (append
+	    gnus-uu-user-view-rules
+	    (if gnus-uu-ignore-default-view-rules
+		nil
+	      gnus-uu-default-view-rules)
+	    gnus-uu-user-view-rules-end)))
+    (when (and (not (string= (or action "") "gnus-uu-archive"))
+	       gnus-uu-view-with-metamail)
+      (when (setq action
+		  (gnus-uu-choose-action file-name gnus-uu-ext-to-mime-list))
+	(setq action (format "metamail -d -b -c \"%s\"" action))))
+    action))
+
+
+;; Functions for treating subjects and collecting series.
+
+(defun gnus-uu-reginize-string (string)
+  ;; Takes a string and puts a \ in front of every special character;
+  ;; ignores any leading "version numbers" thingies that they use in
+  ;; the comp.binaries groups, and either replaces anything that looks
+  ;; like "2/3" with "[0-9]+/[0-9]+" or, if it can't find something
+  ;; like that, replaces the last two numbers with "[0-9]+".  This, in
+  ;; my experience, should get most postings of a series.
+  (let ((count 2)
+	(vernum "v[0-9]+[a-z][0-9]+:")
+	beg)
+    (save-excursion
+      (set-buffer (get-buffer-create gnus-uu-output-buffer-name))
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (insert (regexp-quote string))
+      (setq beg 1)
+
+      (setq case-fold-search nil)
+      (goto-char (point-min))
+      (when (looking-at vernum)
+	(replace-match vernum t t)
+	(setq beg (length vernum)))
+
+      (goto-char beg)
+      (if (re-search-forward "[ \t]*[0-9]+/[0-9]+" nil t)
+	  (replace-match " [0-9]+/[0-9]+")
+
+	(goto-char beg)
+	(if (re-search-forward "[0-9]+[ \t]*of[ \t]*[0-9]+" nil t)
+	    (replace-match "[0-9]+ of [0-9]+")
+
+	  (end-of-line)
+          (if (re-search-backward "\\([^0-9]\\)[0-9]+\\([^0-9]+\\)[0-9]+"
+                                  nil t)
+              (replace-match "\\1[0-9]+\\2[0-9]+" t nil nil nil))))
+
+      (goto-char beg)
+      (while (re-search-forward "[ \t]+" nil t)
+	(replace-match "[ \t]*" t t))
+
+      (buffer-substring 1 (point-max)))))
+
+(defun gnus-uu-get-list-of-articles (n)
+  ;; If N is non-nil, the article numbers of the N next articles
+  ;; will be returned.
+  ;; If any articles have been marked as processable, they will be
+  ;; returned.
+  ;; Failing that, articles that have subjects that are part of the
+  ;; same "series" as the current will be returned.
+  (let (articles)
+    (cond
+     (n
+      (setq n (prefix-numeric-value n))
+      (let ((backward (< n 0))
+	    (n (abs n)))
+	(save-excursion
+	  (while (and (> n 0)
+		      (push (gnus-summary-article-number)
+			    articles)
+		      (gnus-summary-search-forward nil nil backward))
+	    (setq n (1- n))))
+	(nreverse articles)))
+     (gnus-newsgroup-processable
+      (reverse gnus-newsgroup-processable))
+     (t
+      (gnus-uu-find-articles-matching)))))
+
+(defun gnus-uu-string< (l1 l2)
+  (string< (car l1) (car l2)))
+
+(defun gnus-uu-find-articles-matching
+  (&optional subject only-unread do-not-translate)
+  ;; Finds all articles that matches the regexp SUBJECT.  If it is
+  ;; nil, the current article name will be used.  If ONLY-UNREAD is
+  ;; non-nil, only unread articles are chosen.  If DO-NOT-TRANSLATE is
+  ;; non-nil, article names are not equalized before sorting.
+  (let ((subject (or subject
+		     (gnus-uu-reginize-string (gnus-summary-article-subject))))
+	list-of-subjects)
+    (save-excursion
+      (if (not subject)
+	  ()
+	;; Collect all subjects matching subject.
+	(let ((case-fold-search t)
+	      (data gnus-newsgroup-data)
+	      subj mark d)
+	  (while data
+	    (setq d (pop data))
+	    (and (not (gnus-data-pseudo-p d))
+		 (or (not only-unread)
+		     (= (setq mark (gnus-data-mark d))
+			gnus-unread-mark)
+		     (= mark gnus-ticked-mark)
+		     (= mark gnus-dormant-mark))
+		 (setq subj (mail-header-subject (gnus-data-header d)))
+		 (string-match subject subj)
+		 (push (cons subj (gnus-data-number d))
+		       list-of-subjects))))
+
+	;; Expand numbers, sort, and return the list of article
+	;; numbers.
+	(mapcar (lambda (sub) (cdr sub))
+		(sort (gnus-uu-expand-numbers
+		       list-of-subjects
+		       (not do-not-translate))
+		      'gnus-uu-string<))))))
+
+(defun gnus-uu-expand-numbers (string-list &optional translate)
+  ;; Takes a list of strings and "expands" all numbers in all the
+  ;; strings.  That is, this function makes all numbers equal length by
+  ;; prepending lots of zeroes before each number.  This is to ease later
+  ;; sorting to find out what sequence the articles are supposed to be
+  ;; decoded in.  Returns the list of expanded strings.
+  (let ((out-list string-list)
+	string)
+    (save-excursion
+      (set-buffer (get-buffer-create gnus-uu-output-buffer-name))
+      (buffer-disable-undo (current-buffer))
+      (while string-list
+	(erase-buffer)
+	(insert (caar string-list))
+	;; Translate multiple spaces to one space.
+	(goto-char (point-min))
+	(while (re-search-forward "[ \t]+" nil t)
+	  (replace-match " "))
+	;; Translate all characters to "a".
+	(goto-char (point-min))
+	(when translate
+	  (while (re-search-forward "[A-Za-z]" nil t)
+	    (replace-match "a" t t)))
+	;; Expand numbers.
+	(goto-char (point-min))
+	(while (re-search-forward "[0-9]+" nil t)
+	  (replace-match
+	   (format "%06d"
+		   (string-to-int (buffer-substring
+				   (match-beginning 0) (match-end 0))))))
+	(setq string (buffer-substring 1 (point-max)))
+	(setcar (car string-list) string)
+	(setq string-list (cdr string-list))))
+    out-list))
+
+
+;; `gnus-uu-grab-articles' is the general multi-article treatment
+;; function.  It takes a list of articles to be grabbed and a function
+;; to apply to each article.
+;;
+;; The function to be called should take two parameters.  The first
+;; parameter is the article buffer.  The function should leave the
+;; result, if any, in this buffer.  Most treatment functions will just
+;; generate files...
+;;
+;; The second parameter is the state of the list of articles, and can
+;; have four values: `first', `middle', `last' and `first-and-last'.
+;;
+;; The function should return a list.  The list may contain the
+;; following symbols:
+;; `error' if an error occurred
+;; `begin' if the beginning of an encoded file has been received
+;;   If the list returned contains a `begin', the first element of
+;;   the list *must* be a string with the file name of the decoded
+;;   file.
+;; `end' if the end of an encoded file has been received
+;; `middle' if the article was a body part of an encoded file
+;; `wrong-type' if the article was not a part of an encoded file
+;; `ok', which can be used everything is ok
+
+(defvar gnus-uu-has-been-grabbed nil)
+
+(defun gnus-uu-unmark-list-of-grabbed (&optional dont-unmark-last-article)
+  (let (art)
+    (if (not (and gnus-uu-has-been-grabbed
+		  gnus-uu-unmark-articles-not-decoded))
+	()
+      (when dont-unmark-last-article
+	(setq art (car gnus-uu-has-been-grabbed))
+	(setq gnus-uu-has-been-grabbed (cdr gnus-uu-has-been-grabbed)))
+      (while gnus-uu-has-been-grabbed
+	(gnus-summary-tick-article (car gnus-uu-has-been-grabbed) t)
+	(setq gnus-uu-has-been-grabbed (cdr gnus-uu-has-been-grabbed)))
+      (when dont-unmark-last-article
+	(setq gnus-uu-has-been-grabbed (list art))))))
+
+;; This function takes a list of articles and a function to apply to
+;; each article grabbed.
+;;
+;; This function returns a list of files decoded if the grabbing and
+;; the process-function has been successful and nil otherwise.
+(defun gnus-uu-grab-articles (articles process-function
+				       &optional sloppy limit no-errors)
+  (let ((state 'first)
+	has-been-begin article result-file result-files process-state
+	gnus-summary-display-article-function
+	gnus-article-display-hook gnus-article-prepare-hook
+	article-series files)
+
+    (while (and articles
+		(not (memq 'error process-state))
+		(or sloppy
+		    (not (memq 'end process-state))))
+
+      (setq article (pop articles))
+      (push article article-series)
+
+      (unless articles
+	(if (eq state 'first)
+	    (setq state 'first-and-last)
+	  (setq state 'last)))
+
+      (let ((part (gnus-uu-part-number article)))
+	(gnus-message 6 "Getting article %d%s..."
+		      article (if (string= part "") "" (concat ", " part))))
+      (gnus-summary-display-article article)
+
+      ;; Push the article to the processing function.
+      (save-excursion
+	(set-buffer gnus-original-article-buffer)
+	(let ((buffer-read-only nil))
+	  (save-excursion
+	    (set-buffer gnus-summary-buffer)
+	    (setq process-state
+		  (funcall process-function
+			   gnus-original-article-buffer state)))))
+
+      (gnus-summary-remove-process-mark article)
+
+      ;; If this is the beginning of a decoded file, we push it
+      ;; on to a list.
+      (when (or (memq 'begin process-state)
+		(and (or (eq state 'first)
+			 (eq state 'first-and-last))
+		     (memq 'ok process-state)))
+	(when has-been-begin
+	  ;; If there is a `result-file' here, that means that the
+	  ;; file was unsuccessfully decoded, so we delete it.
+	  (when (and result-file
+		     (file-exists-p result-file)
+		     (not gnus-uu-be-dangerous)
+		     (or (eq gnus-uu-be-dangerous t)
+			 (gnus-y-or-n-p
+			  (format "Delete unsuccessfully decoded file %s"
+				  result-file))))
+	    (delete-file result-file)))
+	(when (memq 'begin process-state)
+	  (setq result-file (car process-state)))
+	(setq has-been-begin t))
+
+      ;; Check whether we have decoded one complete file.
+      (when (memq 'end process-state)
+	(setq article-series nil)
+	(setq has-been-begin nil)
+	(if (stringp result-file)
+	    (setq files (list result-file))
+	  (setq files result-file))
+	(setq result-file (car files))
+	(while files
+	  (push (list (cons 'name (pop files))
+		      (cons 'article article))
+		result-files))
+	;; Allow user-defined functions to be run on this file.
+	(when gnus-uu-grabbed-file-functions
+	  (let ((funcs gnus-uu-grabbed-file-functions))
+	    (unless (listp funcs)
+	      (setq funcs (list funcs)))
+	    (while funcs
+	      (funcall (pop funcs) result-file))))
+	(setq result-file nil)
+	;; Check whether we have decoded enough articles.
+	(and limit (= (length result-files) limit)
+	     (setq articles nil)))
+
+      ;; If this is the last article to be decoded, and
+      ;; we still haven't reached the end, then we delete
+      ;; the partially decoded file.
+      (and (or (eq state 'last) (eq state 'first-and-last))
+	   (not (memq 'end process-state))
+	   result-file
+	   (file-exists-p result-file)
+	   (not gnus-uu-be-dangerous)
+	   (or (eq gnus-uu-be-dangerous t)
+	       (gnus-y-or-n-p (format "Delete incomplete file %s? " result-file)))
+	   (delete-file result-file))
+
+      ;; If this was a file of the wrong sort, then
+      (when (and (or (memq 'wrong-type process-state)
+		     (memq 'error process-state))
+		 gnus-uu-unmark-articles-not-decoded)
+	(gnus-summary-tick-article article t))
+
+      ;; Set the new series state.
+      (if (and (not has-been-begin)
+	       (not sloppy)
+	       (or (memq 'end process-state)
+		   (memq 'middle process-state)))
+	  (progn
+	    (setq process-state (list 'error))
+	    (gnus-message 2 "No begin part at the beginning")
+	    (sleep-for 2))
+	(setq state 'middle)))
+
+    ;; When there are no result-files, then something must be wrong.
+    (if result-files
+	(message "")
+      (cond
+       ((not has-been-begin)
+	(gnus-message 2 "Wrong type file"))
+       ((memq 'error process-state)
+	(gnus-message 2 "An error occurred during decoding"))
+       ((not (or (memq 'ok process-state)
+		 (memq 'end process-state)))
+	(gnus-message 2 "End of articles reached before end of file")))
+      ;; Make unsuccessfully decoded articles unread.
+      (when gnus-uu-unmark-articles-not-decoded
+	(while article-series
+	  (gnus-summary-tick-article (pop article-series) t))))
+
+    result-files))
+
+(defun gnus-uu-grab-view (file)
+  "View FILE using the gnus-uu methods."
+  (let ((action (gnus-uu-get-action file)))
+    (gnus-execute-command
+     (if (string-match "%" action)
+	 (format action file)
+       (concat action " " file))
+     (eq gnus-view-pseudos 'not-confirm))))
+
+(defun gnus-uu-grab-move (file)
+  "Move FILE to somewhere."
+  (when gnus-uu-default-dir
+    (let ((to-file (concat (file-name-as-directory gnus-uu-default-dir)
+			   (file-name-nondirectory file))))
+      (rename-file file to-file)
+      (unless (file-exists-p file)
+	(make-symbolic-link to-file file)))))
+
+(defun gnus-uu-part-number (article)
+  (let* ((header (gnus-summary-article-header article))
+	 (subject (and header (mail-header-subject header))))
+    (if (and subject
+	     (string-match "[0-9]+ */[0-9]+\\|[0-9]+ * of *[0-9]+" subject))
+	(match-string 0 subject)
+      "")))
+
+(defun gnus-uu-uudecode-sentinel (process event)
+  (delete-process (get-process process)))
+
+(defun gnus-uu-uustrip-article (process-buffer in-state)
+  ;; Uudecodes a file asynchronously.
+  (save-excursion
+    (set-buffer process-buffer)
+    (let ((state (list 'wrong-type))
+	  process-connection-type case-fold-search buffer-read-only
+	  files start-char)
+      (goto-char (point-min))
+
+      ;; Deal with ^M at the end of the lines.
+      (when gnus-uu-kill-carriage-return
+	(save-excursion
+	  (while (search-forward "\r" nil t)
+	    (delete-backward-char 1))))
+
+      (while (or (re-search-forward gnus-uu-begin-string nil t)
+		 (re-search-forward gnus-uu-body-line nil t))
+	(setq state (list 'ok))
+	;; Ok, we are at the first uucoded line.
+	(beginning-of-line)
+	(setq start-char (point))
+
+	(if (not (looking-at gnus-uu-begin-string))
+	    (setq state (list 'middle))
+	  ;; This is the beginning of an uuencoded article.
+	  ;; We replace certain characters that could make things messy.
+	  (setq gnus-uu-file-name
+		(let ((nnheader-file-name-translation-alist
+		       '((?/ . ?,) (? . ?_) (?* . ?_) (?$ . ?_))))
+		  (nnheader-translate-file-chars (match-string 1))))
+          (replace-match (concat "begin 644 " gnus-uu-file-name) t t)
+
+	  ;; Remove any non gnus-uu-body-line right after start.
+	  (forward-line 1)
+	  (while (and (not (eobp))
+		      (not (looking-at gnus-uu-body-line)))
+	    (gnus-delete-line))
+
+	  ;; If a process is running, we kill it.
+	  (when (and gnus-uu-uudecode-process
+		     (memq (process-status gnus-uu-uudecode-process)
+			   '(run stop)))
+	    (delete-process gnus-uu-uudecode-process)
+	    (gnus-uu-unmark-list-of-grabbed t))
+
+	  ;; Start a new uudecoding process.
+	  (let ((cdir default-directory))
+	    (unwind-protect
+		(progn
+		  (cd gnus-uu-work-dir)
+		  (setq gnus-uu-uudecode-process
+			(start-process
+			 "*uudecode*"
+			 (get-buffer-create gnus-uu-output-buffer-name)
+			 shell-file-name shell-command-switch
+			 (format "cd %s %s uudecode" gnus-uu-work-dir
+				 gnus-shell-command-separator))))
+	      (cd cdir)))
+	  (set-process-sentinel
+	   gnus-uu-uudecode-process 'gnus-uu-uudecode-sentinel)
+	  (setq state (list 'begin))
+	  (push (concat gnus-uu-work-dir gnus-uu-file-name) files))
+
+	;; We look for the end of the thing to be decoded.
+	(if (re-search-forward gnus-uu-end-string nil t)
+	    (push 'end state)
+	  (goto-char (point-max))
+	  (re-search-backward gnus-uu-body-line nil t))
+
+	(forward-line 1)
+
+	(when gnus-uu-uudecode-process
+	  (when (memq (process-status gnus-uu-uudecode-process) '(run stop))
+	    ;; Try to correct mishandled uucode.
+	    (when gnus-uu-correct-stripped-uucode
+	      (gnus-uu-check-correct-stripped-uucode start-char (point)))
+
+	    ;; Send the text to the process.
+	    (condition-case nil
+		(process-send-region
+		 gnus-uu-uudecode-process start-char (point))
+	      (error
+	       (progn
+		 (delete-process gnus-uu-uudecode-process)
+		 (gnus-message 2 "gnus-uu: Couldn't uudecode")
+		 (setq state (list 'wrong-type)))))
+
+	    (if (memq 'end state)
+		(progn
+		  ;; Send an EOF, just in case.
+		  (ignore-errors
+		    (process-send-eof gnus-uu-uudecode-process))
+		  (while (memq (process-status gnus-uu-uudecode-process)
+			       '(open run))
+		    (accept-process-output gnus-uu-uudecode-process 1)))
+	      (when (or (not gnus-uu-uudecode-process)
+			(not (memq (process-status gnus-uu-uudecode-process)
+				   '(run stop))))
+		(setq state (list 'wrong-type)))))))
+
+      (if (memq 'begin state)
+	  (cons (if (= (length files) 1) (car files) files) state)
+	state))))
+
+;; This function is used by `gnus-uu-grab-articles' to treat
+;; a shared article.
+(defun gnus-uu-unshar-article (process-buffer in-state)
+  (let ((state (list 'ok))
+	start-char)
+    (save-excursion
+      (set-buffer process-buffer)
+      (goto-char (point-min))
+      (if (not (re-search-forward gnus-uu-shar-begin-string nil t))
+	  (setq state (list 'wrong-type))
+	(beginning-of-line)
+	(setq start-char (point))
+	(call-process-region
+	 start-char (point-max) shell-file-name nil
+	 (get-buffer-create gnus-uu-output-buffer-name) nil
+	 shell-command-switch
+	 (concat "cd " gnus-uu-work-dir " "
+		 gnus-shell-command-separator  " sh"))))
+    state))
+
+;; Returns the name of what the shar file is going to unpack.
+(defun gnus-uu-find-name-in-shar ()
+  (let ((oldpoint (point))
+	res)
+    (goto-char (point-min))
+    (when (re-search-forward gnus-uu-shar-name-marker nil t)
+      (setq res (buffer-substring (match-beginning 1) (match-end 1))))
+    (goto-char oldpoint)
+    res))
+
+;; `gnus-uu-choose-action' chooses what action to perform given the name
+;; and `gnus-uu-file-action-list'.  Returns either nil if no action is
+;; found, or the name of the command to run if such a rule is found.
+(defun gnus-uu-choose-action (file-name file-action-list &optional no-ignore)
+  (let ((action-list (copy-sequence file-action-list))
+	(case-fold-search t)
+	rule action)
+    (and
+     (unless no-ignore
+       (and (not
+	     (and gnus-uu-ignore-files-by-name
+		  (string-match gnus-uu-ignore-files-by-name file-name)))
+	    (not
+	     (and gnus-uu-ignore-files-by-type
+		  (string-match gnus-uu-ignore-files-by-type
+				(or (gnus-uu-choose-action
+				     file-name gnus-uu-ext-to-mime-list t)
+				    ""))))))
+     (while (not (or (eq action-list ()) action))
+       (setq rule (car action-list))
+       (setq action-list (cdr action-list))
+       (when (string-match (car rule) file-name)
+	 (setq action (cadr rule)))))
+    action))
+
+(defun gnus-uu-treat-archive (file-path)
+  ;; Unpacks an archive.  Returns t if unpacking is successful.
+  (let ((did-unpack t)
+	action command dir)
+    (setq action (gnus-uu-choose-action
+		  file-path (append gnus-uu-user-archive-rules
+				    (if gnus-uu-ignore-default-archive-rules
+					nil
+				      gnus-uu-default-archive-rules))))
+
+    (when (not action)
+      (error "No unpackers for the file %s" file-path))
+
+    (string-match "/[^/]*$" file-path)
+    (setq dir (substring file-path 0 (match-beginning 0)))
+
+    (when (member action gnus-uu-destructive-archivers)
+      (copy-file file-path (concat file-path "~") t))
+
+    (setq command (format "cd %s ; %s" dir (gnus-uu-command action file-path)))
+
+    (save-excursion
+      (set-buffer (get-buffer-create gnus-uu-output-buffer-name))
+      (erase-buffer))
+
+    (gnus-message 5 "Unpacking: %s..." (gnus-uu-command action file-path))
+
+    (if (= 0 (call-process shell-file-name nil
+			   (get-buffer-create gnus-uu-output-buffer-name)
+			   nil shell-command-switch command))
+	(message "")
+      (gnus-message 2 "Error during unpacking of archive")
+      (setq did-unpack nil))
+
+    (when (member action gnus-uu-destructive-archivers)
+      (rename-file (concat file-path "~") file-path t))
+
+    did-unpack))
+
+(defun gnus-uu-dir-files (dir)
+  (let ((dirs (directory-files dir t "[^/][^\\.][^\\.]?$"))
+	files file)
+    (while dirs
+      (if (file-directory-p (setq file (car dirs)))
+	  (setq files (append files (gnus-uu-dir-files file)))
+	(push file files))
+      (setq dirs (cdr dirs)))
+    files))
+
+(defun gnus-uu-unpack-files (files &optional ignore)
+  ;; Go through FILES and look for files to unpack.
+  (let* ((totfiles (gnus-uu-ls-r gnus-uu-work-dir))
+	 (ofiles files)
+	 file did-unpack)
+    (while files
+      (setq file (cdr (assq 'name (car files))))
+      (when (and (not (member file ignore))
+		 (equal (gnus-uu-get-action (file-name-nondirectory file))
+			"gnus-uu-archive"))
+	(push file did-unpack)
+	(unless (gnus-uu-treat-archive file)
+	  (gnus-message 2 "Error during unpacking of %s" file))
+	(let* ((newfiles (gnus-uu-ls-r gnus-uu-work-dir))
+	       (nfiles newfiles))
+	  (while nfiles
+	    (unless (member (car nfiles) totfiles)
+	      (push (list (cons 'name (car nfiles))
+			  (cons 'original file))
+		    ofiles))
+	    (setq nfiles (cdr nfiles)))
+	  (setq totfiles newfiles)))
+      (setq files (cdr files)))
+    (if did-unpack
+	(gnus-uu-unpack-files ofiles (append did-unpack ignore))
+      ofiles)))
+
+(defun gnus-uu-ls-r (dir)
+  (let* ((files (gnus-uu-directory-files dir t))
+	 (ofiles files))
+    (while files
+      (when (file-directory-p (car files))
+	(setq ofiles (delete (car files) ofiles))
+	(setq ofiles (append ofiles (gnus-uu-ls-r (car files)))))
+      (setq files (cdr files)))
+    ofiles))
+
+;; Various stuff
+
+(defun gnus-uu-directory-files (dir &optional full)
+  (let (files out file)
+    (setq files (directory-files dir full))
+    (while files
+      (setq file (car files))
+      (setq files (cdr files))
+      (unless (member (file-name-nondirectory file) '("." ".."))
+	(push file out)))
+    (setq out (nreverse out))
+    out))
+
+(defun gnus-uu-check-correct-stripped-uucode (start end)
+  (save-excursion
+    (let (found beg length)
+      (if (not gnus-uu-correct-stripped-uucode)
+	  ()
+	(goto-char start)
+
+	(if (re-search-forward " \\|`" end t)
+	    (progn
+	      (goto-char start)
+	      (while (not (eobp))
+		(progn
+		  (when (looking-at "\n")
+		    (replace-match ""))
+		  (forward-line 1))))
+
+	  (while (not (eobp))
+	    (if (looking-at (concat gnus-uu-begin-string "\\|"
+				    gnus-uu-end-string))
+		()
+	      (when (not found)
+		(beginning-of-line)
+		(setq beg (point))
+		(end-of-line)
+		(setq length (- (point) beg)))
+	      (setq found t)
+	      (beginning-of-line)
+	      (setq beg (point))
+	      (end-of-line)
+	      (when (not (= length (- (point) beg)))
+		(insert (make-string (- length (- (point) beg)) ? ))))
+	    (forward-line 1)))))))
+
+(defvar gnus-uu-tmp-alist nil)
+
+(defun gnus-uu-initialize (&optional scan)
+  (let (entry)
+    (if (and (not scan)
+	     (when (setq entry (assoc gnus-newsgroup-name gnus-uu-tmp-alist))
+	       (if (file-exists-p (cdr entry))
+		   (setq gnus-uu-work-dir (cdr entry))
+		 (setq gnus-uu-tmp-alist (delq entry gnus-uu-tmp-alist))
+		 nil)))
+	t
+      (setq gnus-uu-tmp-dir (file-name-as-directory
+			     (expand-file-name gnus-uu-tmp-dir)))
+      (if (not (file-directory-p gnus-uu-tmp-dir))
+	  (error "Temp directory %s doesn't exist" gnus-uu-tmp-dir)
+	(when (not (file-writable-p gnus-uu-tmp-dir))
+	  (error "Temp directory %s can't be written to"
+		 gnus-uu-tmp-dir)))
+
+      (setq gnus-uu-work-dir
+	    (make-temp-name (concat gnus-uu-tmp-dir "gnus")))
+      (gnus-make-directory gnus-uu-work-dir)
+      (set-file-modes gnus-uu-work-dir 448)
+      (setq gnus-uu-work-dir (file-name-as-directory gnus-uu-work-dir))
+      (push (cons gnus-newsgroup-name gnus-uu-work-dir)
+	    gnus-uu-tmp-alist))))
+
+
+;; Kills the temporary uu buffers, kills any processes, etc.
+(defun gnus-uu-clean-up ()
+  (let (buf)
+    (and gnus-uu-uudecode-process
+	 (memq (process-status (or gnus-uu-uudecode-process "nevair"))
+	       '(stop run))
+	 (delete-process gnus-uu-uudecode-process))
+    (when (setq buf (get-buffer gnus-uu-output-buffer-name))
+      (kill-buffer buf))))
+
+(defun gnus-quote-arg-for-sh-or-csh (arg)
+  (let ((pos 0) new-pos accum)
+    ;; *** bug: we don't handle newline characters properly
+    (while (setq new-pos (string-match "[!`\"$\\& \t{}]" arg pos))
+      (push (substring arg pos new-pos) accum)
+      (push "\\" accum)
+      (push (list (aref arg new-pos)) accum)
+      (setq pos (1+ new-pos)))
+    (if (= pos 0)
+        arg
+      (apply 'concat (nconc (nreverse accum) (list (substring arg pos)))))))
+
+;; Inputs an action and a filename and returns a full command, making sure
+;; that the filename will be treated as a single argument when the shell
+;; executes the command.
+(defun gnus-uu-command (action file)
+  (let ((quoted-file (gnus-quote-arg-for-sh-or-csh file)))
+    (if (string-match "%s" action)
+	(format action quoted-file)
+      (concat action " " quoted-file))))
+
+(defun gnus-uu-delete-work-dir (&optional dir)
+  "Delete recursively all files and directories under `gnus-uu-work-dir'."
+  (if dir
+      (gnus-message 7 "Deleting directory %s..." dir)
+    (setq dir gnus-uu-work-dir))
+  (when (and dir
+	     (file-exists-p dir))
+    (let ((files (directory-files dir t nil t))
+	  file)
+      (while (setq file (pop files))
+	(unless (member (file-name-nondirectory file) '("." ".."))
+	  (if (file-directory-p file)
+	      (gnus-uu-delete-work-dir file)
+	    (gnus-message 9 "Deleting file %s..." file)
+	    (delete-file file))))
+      (delete-directory dir)))
+  (gnus-message 7 ""))
+
+;; Initializing
+
+(add-hook 'gnus-exit-group-hook 'gnus-uu-clean-up)
+(add-hook 'gnus-exit-group-hook	'gnus-uu-delete-work-dir)
+
+
+
+;;;
+;;; uuencoded posting
+;;;
+
+;; Any function that is to be used as and encoding method will take two
+;; parameters: PATH-NAME and FILE-NAME.  (E.g. "/home/gaga/spiral.jpg"
+;; and "spiral.jpg", respectively.) The function should return nil if
+;; the encoding wasn't successful.
+(defcustom gnus-uu-post-encode-method 'gnus-uu-post-encode-uuencode
+  "Function used for encoding binary files.
+There are three functions supplied with gnus-uu for encoding files:
+`gnus-uu-post-encode-uuencode', which does straight uuencoding;
+`gnus-uu-post-encode-mime', which encodes with base64 and adds MIME
+headers; and `gnus-uu-post-encode-mime-uuencode', which encodes with
+uuencode and adds MIME headers."
+  :group 'gnus-extract-post
+  :type '(radio (function-item gnus-uu-post-encode-uuencode)
+		(function-item gnus-uu-post-encode-mime)
+		(function-item gnus-uu-post-encode-mime-uuencode)
+		(function :tag "Other")))
+
+(defcustom gnus-uu-post-include-before-composing nil
+  "Non-nil means that gnus-uu will ask for a file to encode before you compose the article.
+If this variable is t, you can either include an encoded file with
+\\[gnus-uu-post-insert-binary-in-article] or have one included for you when you post the article."
+  :group 'gnus-extract-post
+  :type 'boolean)
+
+(defcustom gnus-uu-post-length 990
+  "Maximum length of an article.
+The encoded file will be split into how many articles it takes to
+post the entire file."
+  :group 'gnus-extract-post
+  :type 'integer)
+
+(defcustom gnus-uu-post-threaded nil
+  "Non-nil means that gnus-uu will post the encoded file in a thread.
+This may not be smart, as no other decoder I have seen are able to
+follow threads when collecting uuencoded articles.  (Well, I have seen
+one package that does that - gnus-uu, but somehow, I don't think that
+counts...) Default is nil."
+  :group 'gnus-extract-post
+  :type 'boolean)
+
+(defcustom gnus-uu-post-separate-description t
+  "Non-nil means that the description will be posted in a separate article.
+The first article will typically be numbered (0/x).  If this variable
+is nil, the description the user enters will be included at the
+beginning of the first article, which will be numbered (1/x).  Default
+is t."
+  :group 'gnus-extract-post
+  :type 'boolean)
+
+(defvar gnus-uu-post-binary-separator "--binary follows this line--")
+(defvar gnus-uu-post-message-id nil)
+(defvar gnus-uu-post-inserted-file-name nil)
+(defvar gnus-uu-winconf-post-news nil)
+
+(defun gnus-uu-post-news ()
+  "Compose an article and post an encoded file."
+  (interactive)
+  (setq gnus-uu-post-inserted-file-name nil)
+  (setq gnus-uu-winconf-post-news (current-window-configuration))
+
+  (gnus-summary-post-news)
+
+  (use-local-map (copy-keymap (current-local-map)))
+  (local-set-key "\C-c\C-c" 'gnus-summary-edit-article-done)
+  (local-set-key "\C-c\C-c" 'gnus-uu-post-news-inews)
+  (local-set-key "\C-c\C-s" 'gnus-uu-post-news-inews)
+  (local-set-key "\C-c\C-i" 'gnus-uu-post-insert-binary-in-article)
+
+  (when gnus-uu-post-include-before-composing
+    (save-excursion (setq gnus-uu-post-inserted-file-name
+			  (gnus-uu-post-insert-binary)))))
+
+(defun gnus-uu-post-insert-binary-in-article ()
+  "Inserts an encoded file in the buffer.
+The user will be asked for a file name."
+  (interactive)
+  (save-excursion
+    (setq gnus-uu-post-inserted-file-name (gnus-uu-post-insert-binary))))
+
+;; Encodes with uuencode and substitutes all spaces with backticks.
+(defun gnus-uu-post-encode-uuencode (path file-name)
+  (when (gnus-uu-post-encode-file "uuencode" path file-name)
+    (goto-char (point-min))
+    (forward-line 1)
+    (while (re-search-forward " " nil t)
+      (replace-match "`"))
+    t))
+
+;; Encodes with uuencode and adds MIME headers.
+(defun gnus-uu-post-encode-mime-uuencode (path file-name)
+  (when (gnus-uu-post-encode-uuencode path file-name)
+    (gnus-uu-post-make-mime file-name "x-uue")
+    t))
+
+;; Encodes with base64 and adds MIME headers
+(defun gnus-uu-post-encode-mime (path file-name)
+  (when (gnus-uu-post-encode-file "mmencode" path file-name)
+    (gnus-uu-post-make-mime file-name "base64")
+    t))
+
+;; Adds MIME headers.
+(defun gnus-uu-post-make-mime (file-name encoding)
+  (goto-char (point-min))
+  (insert (format "Content-Type: %s; name=\"%s\"\n"
+		  (gnus-uu-choose-action file-name gnus-uu-ext-to-mime-list)
+		  file-name))
+  (insert (format "Content-Transfer-Encoding: %s\n\n" encoding))
+  (save-restriction
+    (set-buffer gnus-message-buffer)
+    (goto-char (point-min))
+    (re-search-forward (concat "^" (regexp-quote mail-header-separator) "$"))
+    (forward-line -1)
+    (narrow-to-region 1 (point))
+    (unless (mail-fetch-field "mime-version")
+      (widen)
+      (insert "MIME-Version: 1.0\n"))
+    (widen)))
+
+;; Encodes a file PATH with COMMAND, leaving the result in the
+;; current buffer.
+(defun gnus-uu-post-encode-file (command path file-name)
+  (= 0 (call-process shell-file-name nil t nil shell-command-switch
+		     (format "%s %s %s" command path file-name))))
+
+(defun gnus-uu-post-news-inews ()
+  "Posts the composed news article and encoded file.
+If no file has been included, the user will be asked for a file."
+  (interactive)
+
+  (let (file-name)
+
+    (if gnus-uu-post-inserted-file-name
+	(setq file-name gnus-uu-post-inserted-file-name)
+      (setq file-name (gnus-uu-post-insert-binary)))
+
+    (if gnus-uu-post-threaded
+	(let ((message-required-news-headers
+	       (if (memq 'Message-ID message-required-news-headers)
+		   message-required-news-headers
+		 (cons 'Message-ID message-required-news-headers)))
+	      gnus-inews-article-hook)
+
+	  (setq gnus-inews-article-hook (if (listp gnus-inews-article-hook)
+					    gnus-inews-article-hook
+					  (list gnus-inews-article-hook)))
+	  (push
+	   '(lambda ()
+	      (save-excursion
+		(goto-char (point-min))
+		(if (re-search-forward "^Message-ID: \\(.*\\)$" nil t)
+		    (setq gnus-uu-post-message-id
+			  (buffer-substring
+			   (match-beginning 1) (match-end 1)))
+		  (setq gnus-uu-post-message-id nil))))
+	   gnus-inews-article-hook)
+	  (gnus-uu-post-encoded file-name t))
+      (gnus-uu-post-encoded file-name nil)))
+  (setq gnus-uu-post-inserted-file-name nil)
+  (when gnus-uu-winconf-post-news
+    (set-window-configuration gnus-uu-winconf-post-news)))
+
+;; Asks for a file to encode, encodes it and inserts the result in
+;; the current buffer.  Returns the file name the user gave.
+(defun gnus-uu-post-insert-binary ()
+  (let ((uuencode-buffer-name "*uuencode buffer*")
+	file-path uubuf file-name)
+
+    (setq file-path (read-file-name
+		     "What file do you want to encode? "))
+    (when (not (file-exists-p file-path))
+      (error "%s: No such file" file-path))
+
+    (goto-char (point-max))
+    (insert (format "\n%s\n" gnus-uu-post-binary-separator))
+
+    (when (string-match "^~/" file-path)
+      (setq file-path (concat "$HOME" (substring file-path 1))))
+    (if (string-match "/[^/]*$" file-path)
+	(setq file-name (substring file-path (1+ (match-beginning 0))))
+      (setq file-name file-path))
+
+    (unwind-protect
+	(if (save-excursion
+	      (set-buffer (setq uubuf
+				(get-buffer-create uuencode-buffer-name)))
+	      (erase-buffer)
+	      (funcall gnus-uu-post-encode-method file-path file-name))
+	    (insert-buffer-substring uubuf)
+	  (error "Encoding unsuccessful"))
+      (kill-buffer uubuf))
+    file-name))
+
+;; Posts the article and all of the encoded file.
+(defun gnus-uu-post-encoded (file-name &optional threaded)
+  (let ((send-buffer-name "*uuencode send buffer*")
+	(encoded-buffer-name "*encoded buffer*")
+	(top-string "[ cut here %s (%s %d/%d) %s gnus-uu ]")
+	(separator (concat mail-header-separator "\n\n"))
+	uubuf length parts header i end beg
+	beg-line minlen buf post-buf whole-len beg-binary end-binary)
+
+    (setq post-buf (current-buffer))
+
+    (goto-char (point-min))
+    (when (not (re-search-forward
+		(if gnus-uu-post-separate-description
+		    (concat "^" (regexp-quote gnus-uu-post-binary-separator)
+			    "$")
+		  (concat "^" (regexp-quote mail-header-separator) "$"))
+		nil t))
+      (error "Internal error: No binary/header separator"))
+    (beginning-of-line)
+    (forward-line 1)
+    (setq beg-binary (point))
+    (setq end-binary (point-max))
+
+    (save-excursion
+      (set-buffer (setq uubuf (get-buffer-create encoded-buffer-name)))
+      (erase-buffer)
+      (insert-buffer-substring post-buf beg-binary end-binary)
+      (goto-char (point-min))
+      (setq length (count-lines 1 (point-max)))
+      (setq parts (/ length gnus-uu-post-length))
+      (when (not (< (% length gnus-uu-post-length) 4))
+	(setq parts (1+ parts))))
+
+    (when gnus-uu-post-separate-description
+      (forward-line -1))
+    (kill-region (point) (point-max))
+
+    (goto-char (point-min))
+    (re-search-forward
+     (concat "^" (regexp-quote mail-header-separator) "$") nil t)
+    (beginning-of-line)
+    (setq header (buffer-substring 1 (point)))
+
+    (goto-char (point-min))
+    (if (not gnus-uu-post-separate-description)
+	()
+      (when (and (not threaded) (re-search-forward "^Subject: " nil t))
+	(end-of-line)
+	(insert (format " (0/%d)" parts)))
+      (message-send))
+
+    (save-excursion
+      (setq i 1)
+      (setq beg 1)
+      (while (not (> i parts))
+	(set-buffer (get-buffer-create send-buffer-name))
+	(erase-buffer)
+	(insert header)
+	(when (and threaded gnus-uu-post-message-id)
+	  (insert (format "References: %s\n" gnus-uu-post-message-id)))
+	(insert separator)
+	(setq whole-len
+	      (- 62 (length (format top-string "" file-name i parts ""))))
+	(when (> 1 (setq minlen (/ whole-len 2)))
+	  (setq minlen 1))
+	(setq
+	 beg-line
+	 (format top-string
+		 (make-string minlen ?-)
+		 file-name i parts
+		 (make-string
+		  (if (= 0 (% whole-len 2)) (1- minlen) minlen) ?-)))
+
+	(goto-char (point-min))
+	(if (not (re-search-forward "^Subject: " nil t))
+	    ()
+	  (if (not threaded)
+	      (progn
+		(end-of-line)
+		(insert (format " (%d/%d)" i parts)))
+	    (when (or (and (= i 2) gnus-uu-post-separate-description)
+		      (and (= i 1) (not gnus-uu-post-separate-description)))
+	      (replace-match "Subject: Re: "))))
+
+	(goto-char (point-max))
+	(save-excursion
+	  (set-buffer uubuf)
+	  (goto-char beg)
+	  (if (= i parts)
+	      (goto-char (point-max))
+	    (forward-line gnus-uu-post-length))
+	  (when (and (= (1+ i) parts) (< (count-lines (point) (point-max)) 4))
+	    (forward-line -4))
+	  (setq end (point)))
+	(insert-buffer-substring uubuf beg end)
+	(insert beg-line)
+	(insert "\n")
+	(setq beg end)
+	(setq i (1+ i))
+	(goto-char (point-min))
+	(re-search-forward
+	 (concat "^" (regexp-quote mail-header-separator) "$") nil t)
+	(beginning-of-line)
+	(forward-line 2)
+	(when (re-search-forward
+	       (concat "^" (regexp-quote gnus-uu-post-binary-separator) "$")
+	       nil t)
+	  (replace-match "")
+	  (forward-line 1))
+	(insert beg-line)
+	(insert "\n")
+	(let (message-sent-message-via)
+	  (message-send))))
+
+    (when (setq buf (get-buffer send-buffer-name))
+      (kill-buffer buf))
+    (when (setq buf (get-buffer encoded-buffer-name))
+      (kill-buffer buf))
+
+    (when (not gnus-uu-post-separate-description)
+      (set-buffer-modified-p nil)
+      (when (fboundp 'bury-buffer)
+	(bury-buffer)))))
+
+(provide 'gnus-uu)
+
+;; gnus-uu.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-vm.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,107 @@
+;;; gnus-vm.el --- vm interface for Gnus
+;; Copyright (C) 1994,95,96,97 Free Software Foundation, Inc.
+
+;; Author: Per Persson <pp@gnu.ai.mit.edu>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; Major contributors:
+;;	Christian Limpach <Christian.Limpach@nice.ch>
+;; Some code stolen from:
+;;	Rick Sladkey <jrs@world.std.com>
+
+;;; Code:
+
+(require 'sendmail)
+(require 'message)
+(require 'gnus)
+(require 'gnus-msg)
+
+(eval-when-compile
+  (autoload 'vm-mode "vm")
+  (autoload 'vm-save-message "vm")
+  (autoload 'vm-forward-message "vm")
+  (autoload 'vm-reply "vm")
+  (autoload 'vm-mail "vm"))
+
+(defvar gnus-vm-inhibit-window-system nil
+  "Inhibit loading `win-vm' if using a window-system.
+Has to be set before gnus-vm is loaded.")
+
+(or gnus-vm-inhibit-window-system
+    (condition-case nil
+	(when window-system
+	  (require 'win-vm))
+      (error nil)))
+
+(when (not (featurep 'vm))
+  (load "vm"))
+
+(defun gnus-vm-make-folder (&optional buffer)
+  (let ((article (or buffer (current-buffer)))
+	(tmp-folder (generate-new-buffer " *tmp-folder*"))
+	(start (point-min))
+	(end (point-max)))
+    (set-buffer tmp-folder)
+    (insert-buffer-substring article start end)
+    (goto-char (point-min))
+    (if (looking-at "^\\(From [^ ]+ \\).*$")
+	(replace-match (concat "\\1" (current-time-string)))
+      (insert "From " gnus-newsgroup-name " "
+	      (current-time-string) "\n"))
+    (while (re-search-forward "\n\nFrom " nil t)
+      (replace-match "\n\n>From "))
+    ;; insert a newline, otherwise the last line gets lost
+    (goto-char (point-max))
+    (insert "\n")
+    (vm-mode)
+    tmp-folder))
+
+(defun gnus-summary-save-article-vm (&optional arg)
+  "Append the current article to a vm folder.
+If N is a positive number, save the N next articles.
+If N is a negative number, save the N previous articles.
+If N is nil and any articles have been marked with the process mark,
+save those articles instead."
+  (interactive "P")
+  (let ((gnus-default-article-saver 'gnus-summary-save-in-vm))
+    (gnus-summary-save-article arg)))
+
+(defun gnus-summary-save-in-vm (&optional folder)
+  (interactive)
+  (setq folder
+	(cond ((eq folder 'default) default-name)
+	      (folder folder)
+	      (t (gnus-read-save-file-name
+		  "Save %s in VM folder:" folder
+		  gnus-mail-save-name gnus-newsgroup-name
+		  gnus-current-headers 'gnus-newsgroup-last-mail))))
+  (gnus-eval-in-buffer-window gnus-original-article-buffer
+    (save-excursion
+      (save-restriction
+	(widen)
+	(let ((vm-folder (gnus-vm-make-folder)))
+	  (vm-save-message folder)
+	  (kill-buffer vm-folder))))))
+
+(provide 'gnus-vm)
+
+;;; gnus-vm.el ends here.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus-win.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,550 @@
+;;; gnus-win.el --- window configuration functions for Gnus
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'gnus)
+
+(defgroup gnus-windows nil
+  "Window configuration."
+  :group 'gnus)
+
+(defcustom gnus-use-full-window t
+  "*If non-nil, use the entire Emacs screen."
+  :group 'gnus-windows
+  :type 'boolean)
+
+(defvar gnus-window-configuration nil
+  "Obsolete variable.  See `gnus-buffer-configuration'.")
+
+(defcustom gnus-window-min-width 2
+  "*Minimum width of Gnus buffers."
+  :group 'gnus-windows
+  :type 'integer)
+
+(defcustom gnus-window-min-height 1
+  "*Minimum height of Gnus buffers."
+  :group 'gnus-windows
+  :type 'integer)
+
+(defcustom gnus-always-force-window-configuration nil
+  "*If non-nil, always force the Gnus window configurations."
+  :group 'gnus-windows
+  :type 'boolean)
+
+(defvar gnus-buffer-configuration
+  '((group
+     (vertical 1.0
+	       (group 1.0 point)
+	       (if gnus-carpal '(group-carpal 4))))
+    (summary
+     (vertical 1.0
+	       (summary 1.0 point)
+	       (if gnus-carpal '(summary-carpal 4))))
+    (article
+     (cond
+      ((and gnus-use-picons
+	    (eq gnus-picons-display-where 'picons))
+       '(frame 1.0
+	       (vertical 1.0
+			 (summary 0.25 point)
+			 (if gnus-carpal '(summary-carpal 4))
+			 (article 1.0))
+	       (vertical ((height . 5) (width . 15)
+			  (user-position . t)
+			  (left . -1) (top . 1))
+			 (picons 1.0))))
+      (gnus-use-trees
+       '(vertical 1.0
+		  (summary 0.25 point)
+		  (tree 0.25)
+		  (article 1.0)))
+      (t
+       '(vertical 1.0
+		 (summary 0.25 point)
+		 (if gnus-carpal '(summary-carpal 4))
+		 (article 1.0)))))
+    (server
+     (vertical 1.0
+	       (server 1.0 point)
+	       (if gnus-carpal '(server-carpal 2))))
+    (browse
+     (vertical 1.0
+	       (browse 1.0 point)
+	       (if gnus-carpal '(browse-carpal 2))))
+    (message
+     (vertical 1.0
+	       (message 1.0 point)))
+    (pick
+     (vertical 1.0
+	       (article 1.0 point)))
+    (info
+     (vertical 1.0
+	       (info 1.0 point)))
+    (summary-faq
+     (vertical 1.0
+	       (summary 0.25)
+	       (faq 1.0 point)))
+    (edit-article
+     (vertical 1.0
+	       (article 1.0 point)))
+    (edit-form
+     (vertical 1.0
+	       (group 0.5)
+	       (edit-form 1.0 point)))
+    (edit-score
+     (vertical 1.0
+	       (summary 0.25)
+	       (edit-score 1.0 point)))
+    (post
+     (vertical 1.0
+	       (post 1.0 point)))
+    (reply
+     (vertical 1.0
+	       (article-copy 0.5)
+	       (message 1.0 point)))
+    (forward
+     (vertical 1.0
+	       (message 1.0 point)))
+    (reply-yank
+     (vertical 1.0
+	       (message 1.0 point)))
+    (mail-bounce
+     (vertical 1.0
+	       (article 0.5)
+	       (message 1.0 point)))
+    (draft
+     (vertical 1.0
+	       (draft 1.0 point)))
+    (pipe
+     (vertical 1.0
+	       (summary 0.25 point)
+	       (if gnus-carpal '(summary-carpal 4))
+	       ("*Shell Command Output*" 1.0)))
+    (bug
+     (vertical 1.0
+	       ("*Gnus Help Bug*" 0.5)
+	       ("*Gnus Bug*" 1.0 point)))
+    (score-trace
+     (vertical 1.0
+	       (summary 0.5 point)
+	       ("*Score Trace*" 1.0)))
+    (score-words
+     (vertical 1.0
+	       (summary 0.5 point)
+	       ("*Score Words*" 1.0)))
+    (compose-bounce
+     (vertical 1.0
+	       (article 0.5)
+	       (message 1.0 point))))
+  "Window configuration for all possible Gnus buffers.
+See the Gnus manual for an explanation of the syntax used.")
+
+(defvar gnus-window-to-buffer
+  '((group . gnus-group-buffer)
+    (summary . gnus-summary-buffer)
+    (article . gnus-article-buffer)
+    (server . gnus-server-buffer)
+    (browse . "*Gnus Browse Server*")
+    (edit-group . gnus-group-edit-buffer)
+    (edit-form . gnus-edit-form-buffer)
+    (edit-server . gnus-server-edit-buffer)
+    (group-carpal . gnus-carpal-group-buffer)
+    (summary-carpal . gnus-carpal-summary-buffer)
+    (server-carpal . gnus-carpal-server-buffer)
+    (browse-carpal . gnus-carpal-browse-buffer)
+    (edit-score . gnus-score-edit-buffer)
+    (message . gnus-message-buffer)
+    (mail . gnus-message-buffer)
+    (post-news . gnus-message-buffer)
+    (faq . gnus-faq-buffer)
+    (picons . "*Picons*")
+    (tree . gnus-tree-buffer)
+    (info . gnus-info-buffer)
+    (article-copy . gnus-article-copy)
+    (draft . gnus-draft-buffer))
+  "Mapping from short symbols to buffer names or buffer variables.")
+
+;;; Internal variables.
+
+(defvar gnus-current-window-configuration nil
+  "The most recently set window configuration.")
+
+(defvar gnus-created-frames nil)
+
+(defun gnus-kill-gnus-frames ()
+  "Kill all frames Gnus has created."
+  (while gnus-created-frames
+    (when (frame-live-p (car gnus-created-frames))
+      ;; We slap a condition-case around this `delete-frame' to ensure
+      ;; against errors if we try do delete the single frame that's left.
+      (ignore-errors
+	(delete-frame (car gnus-created-frames))))
+    (pop gnus-created-frames)))
+
+(defun gnus-window-configuration-element (list)
+  (while (and list
+	      (not (assq (car list) gnus-window-configuration)))
+    (pop list))
+  (cadr (assq (car list) gnus-window-configuration)))
+
+(defun gnus-windows-old-to-new (setting)
+  ;; First we take care of the really, really old Gnus 3 actions.
+  (when (symbolp setting)
+    (setq setting
+	  ;; Take care of ooold GNUS 3.x values.
+	  (cond ((eq setting 'SelectArticle) 'article)
+		((memq setting '(SelectNewsgroup SelectSubject ExpandSubject))
+		 'summary)
+		((memq setting '(ExitNewsgroup)) 'group)
+		(t setting))))
+  (if (or (listp setting)
+	  (not (and gnus-window-configuration
+		    (memq setting '(group summary article)))))
+      setting
+    (let* ((elem
+	    (cond
+	     ((eq setting 'group)
+	      (gnus-window-configuration-element
+	       '(group newsgroups ExitNewsgroup)))
+	     ((eq setting 'summary)
+	      (gnus-window-configuration-element
+	       '(summary SelectNewsgroup SelectSubject ExpandSubject)))
+	     ((eq setting 'article)
+	      (gnus-window-configuration-element
+	       '(article SelectArticle)))))
+	   (total (apply '+ elem))
+	   (types '(group summary article))
+	   (pbuf (if (eq setting 'newsgroups) 'group 'summary))
+	   (i 0)
+	   perc out)
+      (while (< i 3)
+	(or (not (numberp (nth i elem)))
+	    (zerop (nth i elem))
+	    (progn
+	      (setq perc (if (= i 2)
+			     1.0
+			   (/ (float (nth i elem)) total)))
+	      (push (if (eq pbuf (nth i types))
+			(list (nth i types) perc 'point)
+		      (list (nth i types) perc))
+		    out)))
+	(incf i))
+      `(vertical 1.0 ,@(nreverse out)))))
+
+;;;###autoload
+(defun gnus-add-configuration (conf)
+  "Add the window configuration CONF to `gnus-buffer-configuration'."
+  (setq gnus-buffer-configuration
+	(cons conf (delq (assq (car conf) gnus-buffer-configuration)
+			 gnus-buffer-configuration))))
+
+(defvar gnus-frame-list nil)
+
+(defun gnus-configure-frame (split &optional window)
+  "Split WINDOW according to SPLIT."
+  (unless window
+    (setq window (get-buffer-window (current-buffer))))
+  (select-window window)
+  ;; This might be an old-stylee buffer config.
+  (when (vectorp split)
+    (setq split (append split nil)))
+  (when (or (consp (car split))
+	    (vectorp (car split)))
+    (push 1.0 split)
+    (push 'vertical split))
+  ;; The SPLIT might be something that is to be evaled to
+  ;; return a new SPLIT.
+  (while (and (not (assq (car split) gnus-window-to-buffer))
+	      (gnus-functionp (car split)))
+    (setq split (eval split)))
+  (let* ((type (car split))
+	 (subs (cddr split))
+	 (len (if (eq type 'horizontal) (window-width) (window-height)))
+	 (total 0)
+	 (window-min-width (or gnus-window-min-width window-min-width))
+	 (window-min-height (or gnus-window-min-height window-min-height))
+	 s result new-win rest comp-subs size sub)
+    (cond
+     ;; Nothing to do here.
+     ((null split))
+     ;; Don't switch buffers.
+     ((null type)
+      (and (memq 'point split) window))
+     ;; This is a buffer to be selected.
+     ((not (memq type '(frame horizontal vertical)))
+      (let ((buffer (cond ((stringp type) type)
+			  (t (cdr (assq type gnus-window-to-buffer)))))
+	    buf)
+	(unless buffer
+	  (error "Illegal buffer type: %s" type))
+	(unless (setq buf (get-buffer (if (symbolp buffer)
+					  (symbol-value buffer) buffer)))
+	  (setq buf (get-buffer-create (if (symbolp buffer)
+					   (symbol-value buffer) buffer))))
+	(switch-to-buffer buf)
+	;; We return the window if it has the `point' spec.
+	(and (memq 'point split) window)))
+     ;; This is a frame split.
+     ((eq type 'frame)
+      (unless gnus-frame-list
+	(setq gnus-frame-list (list (window-frame
+				     (get-buffer-window (current-buffer))))))
+      (let ((i 0)
+	    params frame fresult)
+	(while (< i (length subs))
+	  ;; Frame parameter is gotten from the sub-split.
+	  (setq params (cadr (elt subs i)))
+	  ;; It should be a list.
+	  (unless (listp params)
+	    (setq params nil))
+	  ;; Create a new frame?
+	  (unless (setq frame (elt gnus-frame-list i))
+	    (nconc gnus-frame-list (list (setq frame (make-frame params))))
+	    (push frame gnus-created-frames))
+	  ;; Is the old frame still alive?
+	  (unless (frame-live-p frame)
+	    (setcar (nthcdr i gnus-frame-list)
+		    (setq frame (make-frame params))))
+	  ;; Select the frame in question and do more splits there.
+	  (select-frame frame)
+	  (setq fresult (or (gnus-configure-frame (elt subs i)) fresult))
+	  (incf i))
+	;; Select the frame that has the selected buffer.
+	(when fresult
+	  (select-frame (window-frame fresult)))))
+     ;; This is a normal split.
+     (t
+      (when (> (length subs) 0)
+	;; First we have to compute the sizes of all new windows.
+	(while subs
+	  (setq sub (append (pop subs) nil))
+	  (while (and (not (assq (car sub) gnus-window-to-buffer))
+		      (gnus-functionp (car sub)))
+	    (setq sub (eval sub)))
+	  (when sub
+	    (push sub comp-subs)
+	    (setq size (cadar comp-subs))
+	    (cond ((equal size 1.0)
+		   (setq rest (car comp-subs))
+		   (setq s 0))
+		  ((floatp size)
+		   (setq s (floor (* size len))))
+		  ((integerp size)
+		   (setq s size))
+		  (t
+		   (error "Illegal size: %s" size)))
+	    ;; Try to make sure that we are inside the safe limits.
+	    (cond ((zerop s))
+		  ((eq type 'horizontal)
+		   (setq s (max s window-min-width)))
+		  ((eq type 'vertical)
+		   (setq s (max s window-min-height))))
+	    (setcar (cdar comp-subs) s)
+	    (incf total s)))
+	;; Take care of the "1.0" spec.
+	(if rest
+	    (setcar (cdr rest) (- len total))
+	  (error "No 1.0 specs in %s" split))
+	;; The we do the actual splitting in a nice recursive
+	;; fashion.
+	(setq comp-subs (nreverse comp-subs))
+	(while comp-subs
+	  (if (null (cdr comp-subs))
+	      (setq new-win window)
+	    (setq new-win
+		  (split-window window (cadar comp-subs)
+				(eq type 'horizontal))))
+	  (setq result (or (gnus-configure-frame
+			    (car comp-subs) window)
+			   result))
+	  (select-window new-win)
+	  (setq window new-win)
+	  (setq comp-subs (cdr comp-subs))))
+      ;; Return the proper window, if any.
+      (when result
+	(select-window result))))))
+
+(defvar gnus-frame-split-p nil)
+
+(defun gnus-configure-windows (setting &optional force)
+  (setq gnus-current-window-configuration setting)
+  (setq force (or force gnus-always-force-window-configuration))
+  (setq setting (gnus-windows-old-to-new setting))
+  (let ((split (if (symbolp setting)
+		   (cadr (assq setting gnus-buffer-configuration))
+		 setting))
+	all-visible)
+
+    (setq gnus-frame-split-p nil)
+
+    (unless split
+      (error "No such setting: %s" setting))
+
+    (if (and (setq all-visible (gnus-all-windows-visible-p split))
+	     (not force))
+	;; All the windows mentioned are already visible, so we just
+	;; put point in the assigned buffer, and do not touch the
+	;; winconf.
+	(select-window all-visible)
+
+      ;; Either remove all windows or just remove all Gnus windows.
+      (let ((frame (selected-frame)))
+	(unwind-protect
+	    (if gnus-use-full-window
+		;; We want to remove all other windows.
+		(if (not gnus-frame-split-p)
+		    ;; This is not a `frame' split, so we ignore the
+		    ;; other frames.
+		    (delete-other-windows)
+		  ;; This is a `frame' split, so we delete all windows
+		  ;; on all frames.
+		  (gnus-delete-windows-in-gnusey-frames))
+	      ;; Just remove some windows.
+	      (gnus-remove-some-windows)
+	      (switch-to-buffer nntp-server-buffer))
+	  (select-frame frame)))
+
+      (switch-to-buffer nntp-server-buffer)
+      (gnus-configure-frame split (get-buffer-window (current-buffer))))))
+
+(defun gnus-delete-windows-in-gnusey-frames ()
+  "Do a `delete-other-windows' in all frames that have Gnus windows."
+  (let ((buffers
+	 (mapcar
+	  (lambda (elem)
+	    (if (symbolp (cdr elem))
+		(when (and (boundp (cdr elem))
+			   (symbol-value (cdr elem)))
+		  (get-buffer (symbol-value (cdr elem))))
+	      (when (cdr elem)
+		(get-buffer (cdr elem)))))
+	  gnus-window-to-buffer)))
+    (mapcar
+     (lambda (frame)
+       (unless (eq (cdr (assq 'minibuffer
+			      (frame-parameters frame)))
+		   'only)
+	 (select-frame frame)
+	 (let (do-delete)
+	   (walk-windows
+	    (lambda (window)
+	      (when (memq (window-buffer window) buffers)
+		(setq do-delete t))))
+	   (when do-delete
+	     (delete-other-windows)))))
+     (frame-list))))
+
+(defun gnus-all-windows-visible-p (split)
+  "Say whether all buffers in SPLIT are currently visible.
+In particular, the value returned will be the window that
+should have point."
+  (let ((stack (list split))
+	(all-visible t)
+	type buffer win buf)
+    (while (and (setq split (pop stack))
+		all-visible)
+      ;; Be backwards compatible.
+      (when (vectorp split)
+	(setq split (append split nil)))
+      (when (or (consp (car split))
+		(vectorp (car split)))
+	(push 1.0 split)
+	(push 'vertical split))
+      ;; The SPLIT might be something that is to be evaled to
+      ;; return a new SPLIT.
+      (while (and (not (assq (car split) gnus-window-to-buffer))
+		  (gnus-functionp (car split)))
+	(setq split (eval split)))
+
+      (setq type (elt split 0))
+      (cond
+       ;; Nothing here.
+       ((null split) t)
+       ;; A buffer.
+       ((not (memq type '(horizontal vertical frame)))
+	(setq buffer (cond ((stringp type) type)
+			   (t (cdr (assq type gnus-window-to-buffer)))))
+	(unless buffer
+	  (error "Illegal buffer type: %s" type))
+	(when (setq buf (get-buffer (if (symbolp buffer)
+					(symbol-value buffer)
+				      buffer)))
+	  (setq win (get-buffer-window buf t)))
+	(if win
+	    (when (memq 'point split)
+	      (setq all-visible win))
+	  (setq all-visible nil)))
+       (t
+	(when (eq type 'frame)
+	  (setq gnus-frame-split-p t))
+	(setq stack (append (cddr split) stack)))))
+    (unless (eq all-visible t)
+      all-visible)))
+
+(defun gnus-window-top-edge (&optional window)
+  (nth 1 (window-edges window)))
+
+(defun gnus-remove-some-windows ()
+  (let ((buffers gnus-window-to-buffer)
+	buf bufs lowest-buf lowest)
+    (save-excursion
+      ;; Remove windows on all known Gnus buffers.
+      (while buffers
+	(setq buf (cdar buffers))
+	(when (symbolp buf)
+	  (setq buf (and (boundp buf) (symbol-value buf))))
+	(and buf
+	     (get-buffer-window buf)
+	     (progn
+	       (push buf bufs)
+	       (pop-to-buffer buf)
+	       (when (or (not lowest)
+			 (< (gnus-window-top-edge) lowest))
+		 (setq lowest (gnus-window-top-edge))
+		 (setq lowest-buf buf))))
+	(setq buffers (cdr buffers)))
+      ;; Remove windows on *all* summary buffers.
+      (walk-windows
+       (lambda (win)
+	 (let ((buf (window-buffer win)))
+	   (when (string-match	"^\\*Summary" (buffer-name buf))
+	     (push buf bufs)
+	     (pop-to-buffer buf)
+	     (when (or (not lowest)
+		       (< (gnus-window-top-edge) lowest))
+	       (setq lowest-buf buf)
+	       (setq lowest (gnus-window-top-edge)))))))
+      (when lowest-buf
+	(pop-to-buffer lowest-buf)
+	(switch-to-buffer nntp-server-buffer))
+      (while bufs
+	(when (not (eq (car bufs) lowest-buf))
+	  (delete-windows-on (car bufs)))
+	(setq bufs (cdr bufs))))))
+
+(provide 'gnus-win)
+
+;;; gnus-win.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/gnus.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,2569 @@
+;;; gnus.el --- a newsreader for GNU Emacs
+;; Copyright (C) 1987,88,89,90,93,94,95,96,97 Free Software Foundation, Inc.
+
+;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;;	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(eval '(run-hooks 'gnus-load-hook))
+
+(require 'custom)
+(require 'gnus-load)
+(require 'message)
+
+(defgroup gnus nil
+  "The coffee-brewing, all singing, all dancing, kitchen sink newsreader."
+  :group 'news
+  :group 'mail)
+
+(defgroup gnus-start nil
+  "Starting your favorite newsreader."
+  :group 'gnus)
+
+(defgroup gnus-start-server nil
+  "Server options at startup."
+  :group 'gnus-start)
+
+;; These belong to gnus-group.el.
+(defgroup gnus-group nil
+  "Group buffers."
+  :link '(custom-manual "(gnus)The Group Buffer")
+  :group 'gnus)
+
+(defgroup gnus-group-foreign nil
+  "Foreign groups."
+  :link '(custom-manual "(gnus)Foreign Groups")
+  :group 'gnus-group)
+
+(defgroup gnus-group-new nil
+  "Automatic subscription of new groups."
+  :group 'gnus-group)
+
+(defgroup gnus-group-levels nil
+  "Group levels."
+  :link '(custom-manual "(gnus)Group Levels")
+  :group 'gnus-group)
+
+(defgroup gnus-group-select nil
+  "Selecting a Group."
+  :link '(custom-manual "(gnus)Selecting a Group")
+  :group 'gnus-group)
+
+(defgroup gnus-group-listing nil
+  "Showing slices of the group list."
+  :link '(custom-manual "(gnus)Listing Groups")
+  :group 'gnus-group)
+
+(defgroup gnus-group-visual nil
+  "Sorting the group buffer."
+  :link '(custom-manual "(gnus)Group Buffer Format")
+  :group 'gnus-group
+  :group 'gnus-visual)
+
+(defgroup gnus-group-various nil
+  "Various group options."
+  :link '(custom-manual "(gnus)Scanning New Messages")
+  :group 'gnus-group)
+
+;; These belong to gnus-sum.el.
+(defgroup gnus-summary nil
+  "Summary buffers."
+  :link '(custom-manual "(gnus)The Summary Buffer")
+  :group 'gnus)
+
+(defgroup gnus-summary-exit nil
+  "Leaving summary buffers."
+  :link '(custom-manual "(gnus)Exiting the Summary Buffer")
+  :group 'gnus-summary)
+
+(defgroup gnus-summary-marks nil
+  "Marks used in summary buffers."
+  :link '(custom-manual "(gnus)Marking Articles")
+  :group 'gnus-summary)
+
+(defgroup gnus-thread nil
+  "Ordering articles according to replies."
+  :link '(custom-manual "(gnus)Threading")
+  :group 'gnus-summary)
+
+(defgroup gnus-summary-format nil
+  "Formatting of the summary buffer."
+  :link '(custom-manual "(gnus)Summary Buffer Format")
+  :group 'gnus-summary)
+
+(defgroup gnus-summary-choose nil
+  "Choosing Articles."
+  :link '(custom-manual "(gnus)Choosing Articles")
+  :group 'gnus-summary)
+
+(defgroup gnus-summary-maneuvering nil
+  "Summary movement commands."
+  :link '(custom-manual "(gnus)Summary Maneuvering")
+  :group 'gnus-summary)
+
+(defgroup gnus-summary-mail nil
+  "Mail group commands."
+  :link '(custom-manual "(gnus)Mail Group Commands")
+  :group 'gnus-summary)
+
+(defgroup gnus-summary-sort nil
+  "Sorting the summary buffer."
+  :link '(custom-manual "(gnus)Sorting")
+  :group 'gnus-summary)
+
+(defgroup gnus-summary-visual nil
+  "Highlighting and menus in the summary buffer."
+  :link '(custom-manual "(gnus)Summary Highlighting")
+  :group 'gnus-visual
+  :group 'gnus-summary)
+
+(defgroup gnus-summary-various nil
+  "Various summary buffer options."
+  :link '(custom-manual "(gnus)Various Summary Stuff")
+  :group 'gnus-summary)
+
+;; Belongs to gnus-uu.el
+(defgroup gnus-extract-view nil
+  "Viewing extracted files."
+  :link '(custom-manual "(gnus)Viewing Files")
+  :group 'gnus-extract)
+
+;; Belongs to gnus-score.el
+(defgroup gnus-score nil
+  "Score and kill file handling."
+  :group 'gnus)
+
+(defgroup gnus-score-kill nil
+  "Kill files."
+  :group 'gnus-score)
+
+(defgroup gnus-score-adapt nil
+  "Adaptive score files."
+  :group 'gnus-score)
+
+(defgroup gnus-score-default nil
+  "Default values for score files."
+  :group 'gnus-score)
+
+(defgroup gnus-score-expire nil
+  "Expiring score rules."
+  :group 'gnus-score)
+
+(defgroup gnus-score-decay nil
+  "Decaying score rules."
+  :group 'gnus-score)
+
+(defgroup gnus-score-files nil
+  "Score and kill file names."
+  :group 'gnus-score
+  :group 'gnus-files)
+
+(defgroup gnus-score-various nil
+  "Various scoring and killing options."
+  :group 'gnus-score)
+
+;; Other
+(defgroup gnus-visual nil
+  "Options controling the visual fluff."
+  :group 'gnus
+  :group 'faces)
+
+(defgroup gnus-files nil
+  "Files used by Gnus."
+  :group 'gnus)
+
+(defgroup gnus-dribble-file nil
+  "Auto save file."
+  :link '(custom-manual "(gnus)Auto Save")
+  :group 'gnus-files)
+
+(defgroup gnus-newsrc nil
+  "Storing Gnus state."
+  :group 'gnus-files)
+
+(defgroup gnus-server nil
+  "Options related to newsservers and other servers used by Gnus."
+  :group 'gnus)
+
+(defgroup gnus-message '((message custom-group))
+  "Composing replies and followups in Gnus."
+  :group 'gnus)
+
+(defgroup gnus-meta nil
+  "Meta variables controling major portions of Gnus.
+In general, modifying these variables does not take affect until Gnus
+is restarted, and sometimes reloaded."
+  :group 'gnus)
+
+(defgroup gnus-various nil
+  "Other Gnus options."
+  :link '(custom-manual "(gnus)Various Various")
+  :group 'gnus)
+
+(defgroup gnus-exit nil
+  "Exiting gnus."
+  :link '(custom-manual "(gnus)Exiting Gnus")
+  :group 'gnus)
+
+(defconst gnus-version-number "5.4.45"
+  "Version number for this version of Gnus.")
+
+(defconst gnus-version (format "Gnus v%s" gnus-version-number)
+  "Version string for this version of Gnus.")
+
+(defcustom gnus-inhibit-startup-message nil
+  "If non-nil, the startup message will not be displayed.
+This variable is used before `.gnus.el' is loaded, so it should
+be set in `.emacs' instead."
+  :group 'gnus-start
+  :type 'boolean)
+
+(defcustom gnus-play-startup-jingle nil
+  "If non-nil, play the Gnus jingle at startup."
+  :group 'gnus-start
+  :type 'boolean)
+
+;;; Kludges to help the transition from the old `custom.el'.
+
+(unless (featurep 'gnus-xmas)
+  (defalias 'gnus-make-overlay 'make-overlay)
+  (defalias 'gnus-overlay-put 'overlay-put)
+  (defalias 'gnus-move-overlay 'move-overlay)
+  (defalias 'gnus-overlay-end 'overlay-end)
+  (defalias 'gnus-extent-detached-p 'ignore)
+  (defalias 'gnus-extent-start-open 'ignore)
+  (defalias 'gnus-set-text-properties 'set-text-properties)
+  (defalias 'gnus-group-remove-excess-properties 'ignore)
+  (defalias 'gnus-topic-remove-excess-properties 'ignore)
+  (defalias 'gnus-appt-select-lowest-window 'appt-select-lowest-window)
+  (defalias 'gnus-mail-strip-quoted-names 'mail-strip-quoted-names)
+  (defalias 'gnus-character-to-event 'identity)
+  (defalias 'gnus-add-text-properties 'add-text-properties)
+  (defalias 'gnus-put-text-property 'put-text-property)
+  (defalias 'gnus-mode-line-buffer-identification 'identity)
+  (defalias 'gnus-characterp 'numberp)
+  (defalias 'gnus-key-press-event-p 'numberp))
+
+;; The XEmacs people think this is evil, so it must go.
+(defun custom-face-lookup (&optional fg bg stipple bold italic underline)
+  "Lookup or create a face with specified attributes."
+  (let ((name (intern (format "custom-face-%s-%s-%s-%S-%S-%S"
+			      (or fg "default")
+			      (or bg "default")
+			      (or stipple "default")
+			      bold italic underline))))
+    (if (and (custom-facep name)
+	     (fboundp 'make-face))
+	()
+      (copy-face 'default name)
+      (when (and fg
+		 (not (string-equal fg "default")))
+	(ignore-errors
+	  (set-face-foreground name fg)))
+      (when (and bg
+		 (not (string-equal bg "default")))
+	(ignore-errors
+	  (set-face-background name bg)))
+      (when (and stipple
+		 (not (string-equal stipple "default"))
+		 (not (eq stipple 'custom:asis))
+		 (fboundp 'set-face-stipple))
+	(set-face-stipple name stipple))
+      (when (and bold
+		 (not (eq bold 'custom:asis)))
+	(ignore-errors
+	  (make-face-bold name)))
+      (when (and italic
+		 (not (eq italic 'custom:asis)))
+	(ignore-errors
+	  (make-face-italic name)))
+      (when (and underline
+		 (not (eq underline 'custom:asis)))
+	(ignore-errors
+	  (set-face-underline-p name t))))
+    name))
+
+;; We define these group faces here to avoid the display
+;; update forced when creating new faces.
+
+(defface gnus-group-news-1-face
+  '((((class color)
+      (background dark))
+     (:foreground "PaleTurquoise" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "ForestGreen" :bold t))
+    (t
+     ()))
+  "Level 1 newsgroup face.")
+
+(defface gnus-group-news-1-empty-face
+  '((((class color)
+      (background dark))
+     (:foreground "PaleTurquoise"))
+    (((class color)
+      (background light))
+     (:foreground "ForestGreen"))
+    (t
+     ()))
+  "Level 1 empty newsgroup face.")
+
+(defface gnus-group-news-2-face
+  '((((class color)
+      (background dark))
+     (:foreground "turquoise" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "CadetBlue4" :bold t))
+    (t
+     ()))
+  "Level 2 newsgroup face.")
+
+(defface gnus-group-news-2-empty-face
+  '((((class color)
+      (background dark))
+     (:foreground "turquoise"))
+    (((class color)
+      (background light))
+     (:foreground "CadetBlue4"))
+    (t
+     ()))
+  "Level 2 empty newsgroup face.")
+
+(defface gnus-group-news-3-face
+  '((((class color)
+      (background dark))
+     (:bold t))
+    (((class color)
+      (background light))
+     (:bold t))
+    (t
+     ()))
+  "Level 3 newsgroup face.")
+
+(defface gnus-group-news-3-empty-face
+  '((((class color)
+      (background dark))
+     ())
+    (((class color)
+      (background light))
+     ())
+    (t
+     ()))
+  "Level 3 empty newsgroup face.")
+
+(defface gnus-group-news-low-face
+  '((((class color)
+      (background dark))
+     (:foreground "DarkTurquoise" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "DarkGreen" :bold t))
+    (t
+     ()))
+  "Low level newsgroup face.")
+
+(defface gnus-group-news-low-empty-face
+  '((((class color)
+      (background dark))
+     (:foreground "DarkTurquoise"))
+    (((class color)
+      (background light))
+     (:foreground "DarkGreen"))
+    (t
+     ()))
+  "Low level empty newsgroup face.")
+
+(defface gnus-group-mail-1-face
+  '((((class color)
+      (background dark))
+     (:foreground "aquamarine1" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "DeepPink3" :bold t))
+    (t
+     (:bold t)))
+  "Level 1 mailgroup face.")
+
+(defface gnus-group-mail-1-empty-face
+  '((((class color)
+      (background dark))
+     (:foreground "aquamarine1"))
+    (((class color)
+      (background light))
+     (:foreground "DeepPink3"))
+    (t
+     (:italic t :bold t)))
+  "Level 1 empty mailgroup face.")
+
+(defface gnus-group-mail-2-face
+  '((((class color)
+      (background dark))
+     (:foreground "aquamarine2" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "HotPink3" :bold t))
+    (t
+     (:bold t)))
+  "Level 2 mailgroup face.")
+
+(defface gnus-group-mail-2-empty-face
+  '((((class color)
+      (background dark))
+     (:foreground "aquamarine2"))
+    (((class color)
+      (background light))
+     (:foreground "HotPink3"))
+    (t
+     (:bold t)))
+  "Level 2 empty mailgroup face.")
+
+(defface gnus-group-mail-3-face
+  '((((class color)
+      (background dark))
+     (:foreground "aquamarine3" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "magenta4" :bold t))
+    (t
+     (:bold t)))
+  "Level 3 mailgroup face.")
+
+(defface gnus-group-mail-3-empty-face
+  '((((class color)
+      (background dark))
+     (:foreground "aquamarine3"))
+    (((class color)
+      (background light))
+     (:foreground "magenta4"))
+    (t
+     ()))
+  "Level 3 empty mailgroup face.")
+
+(defface gnus-group-mail-low-face
+  '((((class color)
+      (background dark))
+     (:foreground "aquamarine4" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "DeepPink4" :bold t))
+    (t
+     (:bold t)))
+  "Low level mailgroup face.")
+
+(defface gnus-group-mail-low-empty-face
+  '((((class color)
+      (background dark))
+     (:foreground "aquamarine4"))
+    (((class color)
+      (background light))
+     (:foreground "DeepPink4"))
+    (t
+     (:bold t)))
+  "Low level empty mailgroup face.")
+
+;; Summary mode faces.
+
+(defface gnus-summary-selected-face '((t
+				       (:underline t)))
+  "Face used for selected articles.")
+
+(defface gnus-summary-cancelled-face
+  '((((class color))
+     (:foreground "yellow" :background "black")))
+  "Face used for cancelled articles.")
+
+(defface gnus-summary-high-ticked-face
+  '((((class color)
+      (background dark))
+     (:foreground "pink" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "firebrick" :bold t))
+    (t
+     (:bold t)))
+  "Face used for high interest ticked articles.")
+
+(defface gnus-summary-low-ticked-face
+  '((((class color)
+      (background dark))
+     (:foreground "pink" :italic t))
+    (((class color)
+      (background light))
+     (:foreground "firebrick" :italic t))
+    (t
+     (:italic t)))
+  "Face used for low interest ticked articles.")
+
+(defface gnus-summary-normal-ticked-face
+  '((((class color)
+      (background dark))
+     (:foreground "pink"))
+    (((class color)
+      (background light))
+     (:foreground "firebrick"))
+    (t
+     ()))
+  "Face used for normal interest ticked articles.")
+
+(defface gnus-summary-high-ancient-face
+  '((((class color)
+      (background dark))
+     (:foreground "SkyBlue" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "RoyalBlue" :bold t))
+    (t
+     (:bold t)))
+  "Face used for high interest ancient articles.")
+
+(defface gnus-summary-low-ancient-face
+  '((((class color)
+      (background dark))
+     (:foreground "SkyBlue" :italic t))
+    (((class color)
+      (background light))
+     (:foreground "RoyalBlue" :italic t))
+    (t
+     (:italic t)))
+  "Face used for low interest ancient articles.")
+
+(defface gnus-summary-normal-ancient-face
+  '((((class color)
+      (background dark))
+     (:foreground "SkyBlue"))
+    (((class color)
+      (background light))
+     (:foreground "RoyalBlue"))
+    (t
+     ()))
+  "Face used for normal interest ancient articles.")
+
+(defface gnus-summary-high-unread-face
+  '((t
+     (:bold t)))
+  "Face used for high interest unread articles.")
+
+(defface gnus-summary-low-unread-face
+  '((t
+     (:italic t)))
+  "Face used for low interest unread articles.")
+
+(defface gnus-summary-normal-unread-face
+  '((t
+     ()))
+  "Face used for normal interest unread articles.")
+
+(defface gnus-summary-high-read-face
+  '((((class color)
+      (background dark))
+     (:foreground "PaleGreen"
+		  :bold t))
+    (((class color)
+      (background light))
+     (:foreground "DarkGreen"
+		  :bold t))
+    (t
+     (:bold t)))
+  "Face used for high interest read articles.")
+
+(defface gnus-summary-low-read-face
+  '((((class color)
+      (background dark))
+     (:foreground "PaleGreen"
+		  :italic t))
+    (((class color)
+      (background light))
+     (:foreground "DarkGreen"
+		  :italic t))
+    (t
+     (:italic t)))
+  "Face used for low interest read articles.")
+
+(defface gnus-summary-normal-read-face
+  '((((class color)
+      (background dark))
+     (:foreground "PaleGreen"))
+    (((class color)
+      (background light))
+     (:foreground "DarkGreen"))
+    (t
+     ()))
+  "Face used for normal interest read articles.")
+
+
+;;; Splash screen.
+
+(defvar gnus-group-buffer "*Group*")
+
+(eval-and-compile
+  (autoload 'gnus-play-jingle "gnus-audio"))
+
+(defface gnus-splash-face
+  '((((class color)
+      (background dark))
+     (:foreground "red"))
+    (((class color)
+      (background light))
+     (:foreground "red"))
+    (t
+     ()))
+  "Level 1 newsgroup face.")
+
+(defun gnus-splash ()
+  (save-excursion
+    (switch-to-buffer gnus-group-buffer)
+    (let ((buffer-read-only nil))
+      (erase-buffer)
+      (unless gnus-inhibit-startup-message
+	(gnus-group-startup-message)
+	(sit-for 0)
+	(when gnus-play-startup-jingle
+	  (gnus-play-jingle))))))
+
+(defun gnus-indent-rigidly (start end arg)
+  "Indent rigidly using only spaces and no tabs."
+  (save-excursion
+    (save-restriction
+      (narrow-to-region start end)
+      (indent-rigidly start end arg)
+      ;; We translate tabs into spaces -- not everybody uses
+      ;; an 8-character tab.
+      (goto-char (point-min))
+      (while (search-forward "\t" nil t)
+	(replace-match "        " t t)))))
+
+(defvar gnus-simple-splash nil)
+
+(defun gnus-group-startup-message (&optional x y)
+  "Insert startup message in current buffer."
+  ;; Insert the message.
+  (erase-buffer)
+  (insert
+   (format "              %s
+          _    ___ _             _
+          _ ___ __ ___  __    _ ___
+          __   _     ___    __  ___
+              _           ___     _
+             _  _ __             _
+             ___   __            _
+                   __           _
+                    _      _   _
+                   _      _    _
+                      _  _    _
+                  __  ___
+                 _   _ _     _
+                _   _
+              _    _
+             _    _
+            _
+          __
+
+"
+           ""))
+  ;; And then hack it.
+  (gnus-indent-rigidly (point-min) (point-max)
+		       (/ (max (- (window-width) (or x 46)) 0) 2))
+  (goto-char (point-min))
+  (forward-line 1)
+  (let* ((pheight (count-lines (point-min) (point-max)))
+	 (wheight (window-height))
+	 (rest (- wheight pheight)))
+    (insert (make-string (max 0 (* 2 (/ rest 3))) ?\n)))
+  ;; Fontify some.
+  (put-text-property (point-min) (point-max) 'face 'gnus-splash-face)
+  (goto-char (point-min))
+  (setq mode-line-buffer-identification (concat " " gnus-version))
+  (setq gnus-simple-splash t)
+  (set-buffer-modified-p t))
+
+(eval-when (load)
+  (let ((command (format "%s" this-command)))
+    (when (and (string-match "gnus" command)
+	       (not (string-match "gnus-other-frame" command)))
+      (gnus-splash))))
+
+;;; Do the rest.
+
+(require 'custom)
+(require 'gnus-util)
+(require 'nnheader)
+
+(defcustom gnus-home-directory "~/"
+  "Directory variable that specifies the \"home\" directory.
+All other Gnus path variables are initialized from this variable."
+  :group 'gnus-files
+  :type 'directory)
+
+(defcustom gnus-directory (or (getenv "SAVEDIR")
+			      (nnheader-concat gnus-home-directory "News/"))
+  "Directory variable from which all other Gnus file variables are derived."
+  :group 'gnus-files
+  :type 'directory)
+
+(defcustom gnus-default-directory nil
+  "*Default directory for all Gnus buffers."
+  :group 'gnus-files
+  :type '(choice (const :tag "current" nil)
+		 directory))
+
+;; Site dependent variables.  These variables should be defined in
+;; paths.el.
+
+(defvar gnus-default-nntp-server nil
+  "Specify a default NNTP server.
+This variable should be defined in paths.el, and should never be set
+by the user.
+If you want to change servers, you should use `gnus-select-method'.
+See the documentation to that variable.")
+
+;; Don't touch this variable.
+(defvar gnus-nntp-service "nntp"
+  "NNTP service name (\"nntp\" or 119).
+This is an obsolete variable, which is scarcely used.  If you use an
+nntp server for your newsgroup and want to change the port number
+used to 899, you would say something along these lines:
+
+ (setq gnus-select-method '(nntp \"my.nntp.server\" (nntp-port-number 899)))")
+
+(defcustom gnus-nntpserver-file "/etc/nntpserver"
+  "A file with only the name of the nntp server in it."
+  :group 'gnus-files
+  :group 'gnus-server
+  :type 'file)
+
+;; This function is used to check both the environment variable
+;; NNTPSERVER and the /etc/nntpserver file to see whether one can find
+;; an nntp server name default.
+(defun gnus-getenv-nntpserver ()
+  (or (getenv "NNTPSERVER")
+      (and (file-readable-p gnus-nntpserver-file)
+	   (save-excursion
+	     (set-buffer (get-buffer-create " *gnus nntp*"))
+	     (buffer-disable-undo (current-buffer))
+	     (insert-file-contents gnus-nntpserver-file)
+	     (let ((name (buffer-string)))
+	       (prog1
+		   (if (string-match "^[ \t\n]*$" name)
+		       nil
+		     name)
+		 (kill-buffer (current-buffer))))))))
+
+(defcustom gnus-select-method
+  (ignore-errors
+    (nconc
+     (list 'nntp (or (ignore-errors
+		       (gnus-getenv-nntpserver))
+		     (when (and gnus-default-nntp-server
+				(not (string= gnus-default-nntp-server "")))
+		       gnus-default-nntp-server)
+		     (system-name)))
+     (if (or (null gnus-nntp-service)
+	     (equal gnus-nntp-service "nntp"))
+	 nil
+       (list gnus-nntp-service))))
+  "Default method for selecting a newsgroup.
+This variable should be a list, where the first element is how the
+news is to be fetched, the second is the address.
+
+For instance, if you want to get your news via NNTP from
+\"flab.flab.edu\", you could say:
+
+\(setq gnus-select-method '(nntp \"flab.flab.edu\"))
+
+If you want to use your local spool, say:
+
+\(setq gnus-select-method (list 'nnspool (system-name)))
+
+If you use this variable, you must set `gnus-nntp-server' to nil.
+
+There is a lot more to know about select methods and virtual servers -
+see the manual for details."
+  :group 'gnus-server
+  :type 'gnus-select-method)
+
+(defcustom gnus-message-archive-method
+  `(nnfolder
+    "archive"
+    (nnfolder-directory ,(nnheader-concat message-directory "archive"))
+    (nnfolder-active-file
+     ,(nnheader-concat message-directory "archive/active"))
+    (nnfolder-get-new-mail nil)
+    (nnfolder-inhibit-expiry t))
+  "Method used for archiving messages you've sent.
+This should be a mail method.
+
+It's probably not a very effective to change this variable once you've
+run Gnus once.  After doing that, you must edit this server from the
+server buffer."
+  :group 'gnus-server
+  :group 'gnus-message
+  :type 'gnus-select-method)
+
+(defcustom gnus-message-archive-group nil
+  "*Name of the group in which to save the messages you've written.
+This can either be a string; a list of strings; or an alist
+of regexps/functions/forms to be evaluated to return a string (or a list
+of strings).  The functions are called with the name of the current
+group (or nil) as a parameter.
+
+If you want to save your mail in one group and the news articles you
+write in another group, you could say something like:
+
+ \(setq gnus-message-archive-group
+        '((if (message-news-p)
+              \"misc-news\"
+            \"misc-mail\")))
+
+Normally the group names returned by this variable should be
+unprefixed -- which implicitly means \"store on the archive server\".
+However, you may wish to store the message on some other server.  In
+that case, just return a fully prefixed name of the group --
+\"nnml+private:mail.misc\", for instance."
+  :group 'gnus-message
+  :type '(choice (const :tag "none" nil)
+		 string))
+
+(defcustom gnus-secondary-servers nil
+  "List of NNTP servers that the user can choose between interactively.
+To make Gnus query you for a server, you have to give `gnus' a
+non-numeric prefix - `C-u M-x gnus', in short."
+  :group 'gnus-server
+  :type '(repeat string))
+
+(defcustom gnus-nntp-server nil
+  "*The name of the host running the NNTP server.
+This variable is semi-obsolete.	 Use the `gnus-select-method'
+variable instead."
+  :group 'gnus-server
+  :type '(choice (const :tag "disable" nil)
+		 string))
+
+(defcustom gnus-secondary-select-methods nil
+  "A list of secondary methods that will be used for reading news.
+This is a list where each element is a complete select method (see
+`gnus-select-method').
+
+If, for instance, you want to read your mail with the nnml backend,
+you could set this variable:
+
+\(setq gnus-secondary-select-methods '((nnml \"\")))"
+:group 'gnus-server
+:type '(repeat gnus-select-method))
+
+(defvar gnus-backup-default-subscribed-newsgroups
+  '("news.announce.newusers" "news.groups.questions" "gnu.emacs.gnus")
+  "Default default new newsgroups the first time Gnus is run.
+Should be set in paths.el, and shouldn't be touched by the user.")
+
+(defcustom gnus-local-domain nil
+  "Local domain name without a host name.
+The DOMAINNAME environment variable is used instead if it is defined.
+If the `system-name' function returns the full Internet name, there is
+no need to set this variable."
+  :group 'gnus-message
+  :type '(choice (const :tag "default" nil)
+		 string))
+
+(defvar gnus-local-organization nil
+  "String with a description of what organization (if any) the user belongs to.
+Obsolete variable; use `message-user-organization' instead.")
+
+;; Customization variables
+
+(defcustom gnus-refer-article-method nil
+  "Preferred method for fetching an article by Message-ID.
+If you are reading news from the local spool (with nnspool), fetching
+articles by Message-ID is painfully slow.  By setting this method to an
+nntp method, you might get acceptable results.
+
+The value of this variable must be a valid select method as discussed
+in the documentation of `gnus-select-method'."
+  :group 'gnus-server
+  :type '(choice (const :tag "default" nil)
+		 gnus-select-method))
+
+(defcustom gnus-group-faq-directory
+  '("/ftp@mirrors.aol.com:/pub/rtfm/usenet/"
+    "/ftp@sunsite.auc.dk:/pub/usenet/"
+    "/ftp@sunsite.doc.ic.ac.uk:/pub/usenet/news-faqs/"
+    "/ftp@src.doc.ic.ac.uk:/usenet/news-FAQS/"
+    "/ftp@ftp.seas.gwu.edu:/pub/rtfm/"
+    "/ftp@rtfm.mit.edu:/pub/usenet/"
+    "/ftp@ftp.uni-paderborn.de:/pub/FAQ/"
+    "/ftp@ftp.sunet.se:/pub/usenet/"
+    "/ftp@nctuccca.edu.tw:/USENET/FAQ/"
+    "/ftp@hwarang.postech.ac.kr:/pub/usenet/"
+    "/ftp@ftp.hk.super.net:/mirror/faqs/")
+  "Directory where the group FAQs are stored.
+This will most commonly be on a remote machine, and the file will be
+fetched by ange-ftp.
+
+This variable can also be a list of directories.  In that case, the
+first element in the list will be used by default.  The others can
+be used when being prompted for a site.
+
+Note that Gnus uses an aol machine as the default directory.  If this
+feels fundamentally unclean, just think of it as a way to finally get
+something of value back from them.
+
+If the default site is too slow, try one of these:
+
+   North America: mirrors.aol.com		 /pub/rtfm/usenet
+		  ftp.seas.gwu.edu		 /pub/rtfm
+		  rtfm.mit.edu			 /pub/usenet
+   Europe:	  ftp.uni-paderborn.de		 /pub/FAQ
+                  src.doc.ic.ac.uk               /usenet/news-FAQS
+		  ftp.sunet.se			 /pub/usenet
+	          sunsite.auc.dk                 /pub/usenet
+   Asia:	  nctuccca.edu.tw		 /USENET/FAQ
+		  hwarang.postech.ac.kr		 /pub/usenet
+		  ftp.hk.super.net		 /mirror/faqs"
+  :group 'gnus-group-various
+  :type '(choice directory
+		 (repeat directory)))
+
+(defcustom gnus-use-cross-reference t
+  "*Non-nil means that cross referenced articles will be marked as read.
+If nil, ignore cross references.  If t, mark articles as read in
+subscribed newsgroups.	If neither t nor nil, mark as read in all
+newsgroups."
+  :group 'gnus-server
+  :type '(choice (const :tag "off" nil)
+		 (const :tag "subscribed" t)
+		 (sexp :format "all"
+		       :value always)))
+
+(defcustom gnus-process-mark ?#
+  "*Process mark."
+  :group 'gnus-group-visual
+  :group 'gnus-summary-marks
+  :type 'character)
+
+(defcustom gnus-asynchronous nil
+  "*If non-nil, Gnus will supply backends with data needed for async article fetching."
+  :group 'gnus-asynchronous
+  :type 'boolean)
+
+(defcustom gnus-large-newsgroup 200
+  "*The number of articles which indicates a large newsgroup.
+If the number of articles in a newsgroup is greater than this value,
+confirmation is required for selecting the newsgroup."
+  :group 'gnus-group-select
+  :type 'integer)
+
+(defcustom gnus-use-long-file-name (not (memq system-type '(usg-unix-v xenix)))
+  "*Non-nil means that the default name of a file to save articles in is the group name.
+If it's nil, the directory form of the group name is used instead.
+
+If this variable is a list, and the list contains the element
+`not-score', long file names will not be used for score files; if it
+contains the element `not-save', long file names will not be used for
+saving; and if it contains the element `not-kill', long file names
+will not be used for kill files.
+
+Note that the default for this variable varies according to what system
+type you're using.  On `usg-unix-v' and `xenix' this variable defaults
+to nil while on all other systems it defaults to t."
+  :group 'gnus-start
+  :type 'boolean)
+
+(defcustom gnus-kill-files-directory gnus-directory
+  "*Name of the directory where kill files will be stored (default \"~/News\")."
+  :group 'gnus-score-files
+  :group 'gnus-score-kill
+  :type 'directory)
+
+(defcustom gnus-save-score nil
+  "*If non-nil, save group scoring info."
+  :group 'gnus-score-various
+  :group 'gnus-start
+  :type 'boolean)
+
+(defcustom gnus-use-undo t
+  "*If non-nil, allow undoing in Gnus group mode buffers."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-use-adaptive-scoring nil
+  "*If non-nil, use some adaptive scoring scheme.
+If a list, then the values `word' and `line' are meaningful.  The
+former will perform adaption on individual words in the subject
+header while `line' will perform adaption on several headers."
+  :group 'gnus-meta
+  :group 'gnus-score-adapt
+  :type '(set (const word) (const line)))
+
+(defcustom gnus-use-cache 'passive
+  "*If nil, Gnus will ignore the article cache.
+If `passive', it will allow entering (and reading) articles
+explicitly entered into the cache.  If anything else, use the
+cache to the full extent of the law."
+  :group 'gnus-meta
+  :group 'gnus-cache
+  :type '(choice (const :tag "off" nil)
+		 (const :tag "passive" passive)
+		 (const :tag "active" t)))
+
+(defcustom gnus-use-trees nil
+  "*If non-nil, display a thread tree buffer."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-use-grouplens nil
+  "*If non-nil, use GroupLens ratings."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-keep-backlog nil
+  "*If non-nil, Gnus will keep read articles for later re-retrieval.
+If it is a number N, then Gnus will only keep the last N articles
+read.  If it is neither nil nor a number, Gnus will keep all read
+articles.  This is not a good idea."
+  :group 'gnus-meta
+  :type '(choice (const :tag "off" nil)
+		 integer
+		 (sexp :format "all"
+		       :value t)))
+
+(defcustom gnus-use-nocem nil
+  "*If non-nil, Gnus will read NoCeM cancel messages."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-suppress-duplicates nil
+  "*If non-nil, Gnus will mark duplicate copies of the same article as read."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-use-demon nil
+  "If non-nil, Gnus might use some demons."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-use-scoring t
+  "*If non-nil, enable scoring."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-use-picons nil
+  "*If non-nil, display picons."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-summary-prepare-exit-hook
+  '(gnus-summary-expire-articles)
+  "A hook called when preparing to exit from the summary buffer.
+It calls `gnus-summary-expire-articles' by default."
+  :group 'gnus-summary-exit
+  :type 'hook)
+
+(defcustom gnus-novice-user t
+  "*Non-nil means that you are a usenet novice.
+If non-nil, verbose messages may be displayed and confirmations may be
+required."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-expert-user nil
+  "*Non-nil means that you will never be asked for confirmation about anything.
+And that means *anything*."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-interactive-catchup t
+  "*If non-nil, require your confirmation when catching up a group."
+  :group 'gnus-group-select
+  :type 'boolean)
+
+(defcustom gnus-interactive-exit t
+  "*If non-nil, require your confirmation when exiting Gnus."
+  :group 'gnus-exit
+  :type 'boolean)
+
+(defcustom gnus-extract-address-components 'gnus-extract-address-components
+  "*Function for extracting address components from a From header.
+Two pre-defined function exist: `gnus-extract-address-components',
+which is the default, quite fast, and too simplistic solution, and
+`mail-extract-address-components', which works much better, but is
+slower."
+  :group 'gnus-summary-format
+  :type '(radio (function-item gnus-extract-address-components)
+		(function-item mail-extract-address-components)
+		(function :tag "Other")))
+
+(defcustom gnus-carpal nil
+  "*If non-nil, display clickable icons."
+  :group 'gnus-meta
+  :type 'boolean)
+
+(defcustom gnus-shell-command-separator ";"
+  "String used to separate to shell commands."
+  :group 'gnus-files
+  :type 'string)
+
+(defcustom gnus-valid-select-methods
+  '(("nntp" post address prompt-address physical-address)
+    ("nnspool" post address)
+    ("nnvirtual" post-mail virtual prompt-address)
+    ("nnmbox" mail respool address)
+    ("nnml" mail respool address)
+    ("nnmh" mail respool address)
+    ("nndir" post-mail prompt-address physical-address)
+    ("nneething" none address prompt-address physical-address)
+    ("nndoc" none address prompt-address)
+    ("nnbabyl" mail address respool)
+    ("nnkiboze" post virtual)
+    ("nnsoup" post-mail address)
+    ("nndraft" post-mail)
+    ("nnfolder" mail respool address)
+    ("nngateway" none address prompt-address physical-address)
+    ("nnweb" none))
+  "An alist of valid select methods.
+The first element of each list lists should be a string with the name
+of the select method.  The other elements may be the category of
+this method (i. e., `post', `mail', `none' or whatever) or other
+properties that this method has (like being respoolable).
+If you implement a new select method, all you should have to change is
+this variable.	I think."
+  :group 'gnus-server
+  :type '(repeat (group (string :tag "Name")
+			(radio-button-choice (const :format "%v " post)
+					     (const :format "%v " mail)
+					     (const :format "%v " none)
+					     (const post-mail))
+			(checklist :inline t
+				   (const :format "%v " address)
+				   (const :format "%v " prompt-address)
+				   (const :format "%v " virtual)
+				   (const respool)))))
+
+(define-widget 'gnus-select-method 'list
+  "Widget for entering a select method."
+  :args `((choice :tag "Method"
+		  ,@(mapcar (lambda (entry)
+			      (list 'const :format "%v\n"
+				    (intern (car entry))))
+			    gnus-valid-select-methods))
+	  (string :tag "Address")
+	  (editable-list  :inline t
+			  (list :format "%v"
+				variable
+				(sexp :tag "Value")))))
+
+(defcustom gnus-updated-mode-lines '(group article summary tree)
+  "List of buffers that should update their mode lines.
+The list may contain the symbols `group', `article', `tree' and
+`summary'.  If the corresponding symbol is present, Gnus will keep
+that mode line updated with information that may be pertinent.
+If this variable is nil, screen refresh may be quicker."
+  :group 'gnus-various
+  :type '(set (const group)
+	      (const article)
+	      (const summary)
+	      (const tree)))
+
+;; Added by Keinonen Kari <kk85613@cs.tut.fi>.
+(defcustom gnus-mode-non-string-length nil
+  "*Max length of mode-line non-string contents.
+If this is nil, Gnus will take space as is needed, leaving the rest
+of the modeline intact.  Note that the default of nil is unlikely
+to be desirable; see the manual for further details."
+  :group 'gnus-various
+  :type '(choice (const nil)
+		 integer))
+
+(defcustom gnus-auto-expirable-newsgroups nil
+  "*Groups in which to automatically mark read articles as expirable.
+If non-nil, this should be a regexp that should match all groups in
+which to perform auto-expiry.  This only makes sense for mail groups."
+  :group 'nnmail-expire
+  :type '(choice (const nil)
+		 regexp))
+
+(defcustom gnus-total-expirable-newsgroups nil
+  "*Groups in which to perform expiry of all read articles.
+Use with extreme caution.  All groups that match this regexp will be
+expiring - which means that all read articles will be deleted after
+\(say) one week.	 (This only goes for mail groups and the like, of
+course.)"
+  :group 'nnmail-expire
+  :type '(choice (const nil)
+		 regexp))
+
+(defcustom gnus-group-uncollapsed-levels 1
+  "Number of group name elements to leave alone when making a short group name."
+  :group 'gnus-group-visual
+  :type 'integer)
+
+(defcustom gnus-group-use-permanent-levels nil
+  "*If non-nil, once you set a level, Gnus will use this level."
+  :group 'gnus-group-levels
+  :type 'boolean)
+
+;; Hooks.
+
+(defcustom gnus-load-hook nil
+  "A hook run while Gnus is loaded."
+  :group 'gnus-start
+  :type 'hook)
+
+(defcustom gnus-apply-kill-hook '(gnus-apply-kill-file)
+  "A hook called to apply kill files to a group.
+This hook is intended to apply a kill file to the selected newsgroup.
+The function `gnus-apply-kill-file' is called by default.
+
+Since a general kill file is too heavy to use only for a few
+newsgroups, I recommend you to use a lighter hook function.  For
+example, if you'd like to apply a kill file to articles which contains
+a string `rmgroup' in subject in newsgroup `control', you can use the
+following hook:
+
+ (setq gnus-apply-kill-hook
+      (list
+	(lambda ()
+	  (cond ((string-match \"control\" gnus-newsgroup-name)
+		 (gnus-kill \"Subject\" \"rmgroup\")
+		 (gnus-expunge \"X\"))))))"
+  :group 'gnus-score-kill
+  :options '(gnus-apply-kill-file)
+  :type 'hook)
+
+(defcustom gnus-group-change-level-function nil
+  "Function run when a group level is changed.
+It is called with three parameters -- GROUP, LEVEL and OLDLEVEL."
+  :group 'gnus-group-level
+  :type 'function)
+
+;;; Face thingies.
+
+(defcustom gnus-visual
+  '(summary-highlight group-highlight article-highlight
+		      mouse-face
+		      summary-menu group-menu article-menu
+		      tree-highlight menu highlight
+		      browse-menu server-menu
+		      page-marker tree-menu binary-menu pick-menu
+		      grouplens-menu)
+  "Enable visual features.
+If `visual' is disabled, there will be no menus and few faces.  Most of
+the visual customization options below will be ignored.  Gnus will use
+less space and be faster as a result.
+
+This variable can also be a list of visual elements to switch on.  For
+instance, to switch off all visual things except menus, you can say:
+
+   (setq gnus-visual '(menu))
+
+Valid elements include `summary-highlight', `group-highlight',
+`article-highlight', `mouse-face', `summary-menu', `group-menu',
+`article-menu', `tree-highlight', `menu', `highlight', `browse-menu',
+`server-menu', `page-marker', `tree-menu', `binary-menu', `pick-menu',
+and `grouplens-menu'."
+  :group 'gnus-meta
+  :group 'gnus-visual
+  :type '(set (const summary-highlight)
+	      (const group-highlight)
+	      (const article-highlight)
+	      (const mouse-face)
+	      (const summary-menu)
+	      (const group-menu)
+	      (const article-menu)
+	      (const tree-highlight)
+	      (const menu)
+	      (const highlight)
+	      (const browse-menu)
+	      (const server-menu)
+	      (const page-marker)
+	      (const tree-menu)
+	      (const binary-menu)
+	      (const pick-menu)
+	      (const grouplens-menu)))
+
+(defcustom gnus-mouse-face
+  (condition-case ()
+      (if (gnus-visual-p 'mouse-face 'highlight)
+	  (if (boundp 'gnus-mouse-face)
+	      (or gnus-mouse-face 'highlight)
+	    'highlight)
+	'default)
+    (error 'highlight))
+  "Face used for group or summary buffer mouse highlighting.
+The line beneath the mouse pointer will be highlighted with this
+face."
+  :group 'gnus-visual
+  :type 'face)
+
+(defcustom gnus-article-display-hook
+  (if (and (string-match "XEmacs" emacs-version)
+	   (featurep 'xface))
+      '(gnus-article-hide-headers-if-wanted
+	gnus-article-hide-boring-headers
+	gnus-article-treat-overstrike
+	gnus-article-maybe-highlight
+	gnus-article-display-x-face)
+    '(gnus-article-hide-headers-if-wanted
+      gnus-article-hide-boring-headers
+      gnus-article-treat-overstrike
+      gnus-article-maybe-highlight))
+  "Controls how the article buffer will look.
+
+If you leave the list empty, the article will appear exactly as it is
+stored on the disk.  The list entries will hide or highlight various
+parts of the article, making it easier to find the information you
+want."
+  :group 'gnus-article-highlight
+  :group 'gnus-visual
+  :type 'hook
+  :options '(gnus-article-add-buttons
+	     gnus-article-add-buttons-to-head
+	     gnus-article-emphasize
+	     gnus-article-fill-cited-article
+	     gnus-article-remove-cr
+	     gnus-article-de-quoted-unreadable
+	     gnus-article-display-x-face
+	     gnus-summary-stop-page-breaking
+	     ;; gnus-summary-caesar-message
+	     ;; gnus-summary-verbose-headers
+	     gnus-summary-toggle-mime
+	     gnus-article-hide
+	     gnus-article-hide-headers
+	     gnus-article-hide-boring-headers
+	     gnus-article-hide-signature
+	     gnus-article-hide-citation
+	     gnus-article-hide-pgp
+	     gnus-article-hide-pem
+	     gnus-article-highlight
+	     gnus-article-highlight-headers
+	     gnus-article-highlight-citation
+	     gnus-article-highlight-signature
+	     gnus-article-date-ut
+	     gnus-article-date-local
+	     gnus-article-date-lapsed
+	     gnus-article-date-original
+	     gnus-article-remove-trailing-blank-lines
+	     gnus-article-strip-leading-blank-lines
+	     gnus-article-strip-multiple-blank-lines
+	     gnus-article-strip-blank-lines
+	     gnus-article-treat-overstrike))
+
+(defcustom gnus-article-save-directory gnus-directory
+  "*Name of the directory articles will be saved in (default \"~/News\")."
+  :group 'gnus-article-saving
+  :type 'directory)
+
+
+;;; Internal variables
+
+(defvar gnus-group-get-parameter-function 'gnus-group-get-parameter)
+(defvar gnus-original-article-buffer " *Original Article*")
+(defvar gnus-newsgroup-name nil)
+
+(defvar gnus-current-select-method nil
+  "The current method for selecting a newsgroup.")
+
+(defvar gnus-tree-buffer "*Tree*"
+  "Buffer where Gnus thread trees are displayed.")
+
+;; Dummy variable.
+(defvar gnus-use-generic-from nil)
+
+;; Variable holding the user answers to all method prompts.
+(defvar gnus-method-history nil)
+(defvar gnus-group-history nil)
+
+;; Variable holding the user answers to all mail method prompts.
+(defvar gnus-mail-method-history nil)
+
+;; Variable holding the user answers to all group prompts.
+(defvar gnus-group-history nil)
+
+(defvar gnus-server-alist nil
+  "List of available servers.")
+
+(defvar gnus-predefined-server-alist
+  `(("cache"
+     (nnspool "cache"
+	      (nnspool-spool-directory "~/News/cache/")
+	      (nnspool-nov-directory "~/News/cache/")
+	      (nnspool-active-file "~/News/cache/active"))))
+  "List of predefined (convenience) servers.")
+
+(defvar gnus-topic-indentation "") ;; Obsolete variable.
+
+(defconst gnus-article-mark-lists
+  '((marked . tick) (replied . reply)
+    (expirable . expire) (killed . killed)
+    (bookmarks . bookmark) (dormant . dormant)
+    (scored . score) (saved . save)
+    (cached . cache)))
+
+(defvar gnus-headers-retrieved-by nil)
+(defvar gnus-article-reply nil)
+(defvar gnus-override-method nil)
+(defvar gnus-article-check-size nil)
+(defvar gnus-opened-servers nil)
+
+(defvar gnus-current-kill-article nil)
+
+(defvar gnus-have-read-active-file nil)
+
+(defconst gnus-maintainer
+  "bugs@gnus.org (The Gnus Bugfixing Girls + Boys)"
+  "The mail address of the Gnus maintainers.")
+
+(defvar gnus-info-nodes
+  '((gnus-group-mode "(gnus)The Group Buffer")
+    (gnus-summary-mode "(gnus)The Summary Buffer")
+    (gnus-article-mode "(gnus)The Article Buffer")
+    (mime/viewer-mode "(gnus)The Article Buffer")
+    (gnus-server-mode "(gnus)The Server Buffer")
+    (gnus-browse-mode "(gnus)Browse Foreign Server")
+    (gnus-tree-mode "(gnus)Tree Display"))
+  "Alist of major modes and related Info nodes.")
+
+(defvar gnus-group-buffer "*Group*")
+(defvar gnus-summary-buffer "*Summary*")
+(defvar gnus-article-buffer "*Article*")
+(defvar gnus-server-buffer "*Server*")
+
+(defvar gnus-buffer-list nil
+  "Gnus buffers that should be killed on exit.")
+
+(defvar gnus-slave nil
+  "Whether this Gnus is a slave or not.")
+
+(defvar gnus-batch-mode nil
+  "Whether this Gnus is running in batch mode or not.")
+
+(defvar gnus-variable-list
+  '(gnus-newsrc-options gnus-newsrc-options-n
+    gnus-newsrc-last-checked-date
+    gnus-newsrc-alist gnus-server-alist
+    gnus-killed-list gnus-zombie-list
+    gnus-topic-topology gnus-topic-alist
+    gnus-format-specs)
+  "Gnus variables saved in the quick startup file.")
+
+(defvar gnus-newsrc-alist nil
+  "Assoc list of read articles.
+gnus-newsrc-hashtb should be kept so that both hold the same information.")
+
+(defvar gnus-newsrc-hashtb nil
+  "Hashtable of gnus-newsrc-alist.")
+
+(defvar gnus-killed-list nil
+  "List of killed newsgroups.")
+
+(defvar gnus-killed-hashtb nil
+  "Hash table equivalent of gnus-killed-list.")
+
+(defvar gnus-zombie-list nil
+  "List of almost dead newsgroups.")
+
+(defvar gnus-description-hashtb nil
+  "Descriptions of newsgroups.")
+
+(defvar gnus-list-of-killed-groups nil
+  "List of newsgroups that have recently been killed by the user.")
+
+(defvar gnus-active-hashtb nil
+  "Hashtable of active articles.")
+
+(defvar gnus-moderated-hashtb nil
+  "Hashtable of moderated newsgroups.")
+
+;; Save window configuration.
+(defvar gnus-prev-winconf nil)
+
+(defvar gnus-reffed-article-number nil)
+
+;;; Let the byte-compiler know that we know about this variable.
+(defvar rmail-default-rmail-file)
+
+(defvar gnus-dead-summary nil)
+
+;;; End of variables.
+
+;; Define some autoload functions Gnus might use.
+(eval-and-compile
+
+  ;; This little mapcar goes through the list below and marks the
+  ;; symbols in question as autoloaded functions.
+  (mapcar
+   (lambda (package)
+     (let ((interactive (nth 1 (memq ':interactive package))))
+       (mapcar
+	(lambda (function)
+	  (let (keymap)
+	    (when (consp function)
+	      (setq keymap (car (memq 'keymap function)))
+	      (setq function (car function)))
+	    (autoload function (car package) nil interactive keymap)))
+	(if (eq (nth 1 package) ':interactive)
+	    (cdddr package)
+	  (cdr package)))))
+   '(("metamail" metamail-buffer)
+     ("info" Info-goto-node)
+     ("hexl" hexl-hex-string-to-integer)
+     ("pp" pp pp-to-string pp-eval-expression)
+     ("ps-print" ps-print-preprint)
+     ("mail-extr" mail-extract-address-components)
+     ("message" :interactive t
+      message-send-and-exit message-yank-original)
+     ("nnmail" nnmail-split-fancy nnmail-article-group nnmail-date-to-time)
+     ("nnvirtual" nnvirtual-catchup-group nnvirtual-convert-headers)
+     ("timezone" timezone-make-date-arpa-standard timezone-fix-time
+      timezone-make-sortable-date timezone-make-time-string)
+     ("rmailout" rmail-output)
+     ("rmail" rmail-insert-rmail-file-header rmail-count-new-messages
+      rmail-show-message)
+     ("gnus-audio" :interactive t gnus-audio-play)
+     ("gnus-xmas" gnus-xmas-splash)
+     ("gnus-soup" :interactive t
+      gnus-group-brew-soup gnus-brew-soup gnus-soup-add-article
+      gnus-soup-send-replies gnus-soup-save-areas gnus-soup-pack-packet)
+     ("nnsoup" nnsoup-pack-replies)
+     ("score-mode" :interactive t gnus-score-mode)
+     ("gnus-mh" gnus-summary-save-article-folder
+      gnus-Folder-save-name gnus-folder-save-name)
+     ("gnus-mh" :interactive t gnus-summary-save-in-folder)
+     ("gnus-demon" gnus-demon-add-nocem gnus-demon-add-scanmail
+      gnus-demon-add-rescan gnus-demon-add-scan-timestamps
+      gnus-demon-add-disconnection gnus-demon-add-handler
+      gnus-demon-remove-handler)
+     ("gnus-demon" :interactive t
+      gnus-demon-init gnus-demon-cancel)
+     ("gnus-salt" gnus-highlight-selected-tree gnus-possibly-generate-tree
+      gnus-tree-open gnus-tree-close gnus-carpal-setup-buffer)
+     ("gnus-nocem" gnus-nocem-scan-groups gnus-nocem-close
+      gnus-nocem-unwanted-article-p)
+     ("gnus-srvr" gnus-enter-server-buffer gnus-server-set-info)
+     ("gnus-srvr" gnus-browse-foreign-server)
+     ("gnus-cite" :interactive t
+      gnus-article-highlight-citation gnus-article-hide-citation-maybe
+      gnus-article-hide-citation gnus-article-fill-cited-article
+      gnus-article-hide-citation-in-followups)
+     ("gnus-kill" gnus-kill gnus-apply-kill-file-internal
+      gnus-kill-file-edit-file gnus-kill-file-raise-followups-to-author
+      gnus-execute gnus-expunge)
+     ("gnus-cache" gnus-cache-possibly-enter-article gnus-cache-save-buffers
+      gnus-cache-possibly-remove-articles gnus-cache-request-article
+      gnus-cache-retrieve-headers gnus-cache-possibly-alter-active
+      gnus-cache-enter-remove-article gnus-cached-article-p
+      gnus-cache-open gnus-cache-close gnus-cache-update-article)
+      ("gnus-cache" :interactive t gnus-jog-cache gnus-cache-enter-article
+       gnus-cache-remove-article gnus-summary-insert-cached-articles)
+      ("gnus-score" :interactive t
+       gnus-summary-increase-score gnus-summary-set-score
+       gnus-summary-raise-thread gnus-summary-raise-same-subject
+       gnus-summary-raise-score gnus-summary-raise-same-subject-and-select
+       gnus-summary-lower-thread gnus-summary-lower-same-subject
+       gnus-summary-lower-score gnus-summary-lower-same-subject-and-select
+       gnus-summary-current-score gnus-score-default
+       gnus-score-flush-cache gnus-score-close
+       gnus-possibly-score-headers gnus-score-followup-article
+       gnus-score-followup-thread)
+      ("gnus-score"
+       (gnus-summary-score-map keymap) gnus-score-save gnus-score-headers
+      gnus-current-score-file-nondirectory gnus-score-adaptive
+      gnus-score-find-trace gnus-score-file-name)
+     ("gnus-cus" :interactive t gnus-group-customize gnus-score-customize)
+     ("gnus-topic" :interactive t gnus-topic-mode)
+     ("gnus-topic" gnus-topic-remove-group gnus-topic-set-parameters)
+     ("gnus-salt" :interactive t gnus-pick-mode gnus-binary-mode)
+     ("gnus-uu" (gnus-uu-extract-map keymap) (gnus-uu-mark-map keymap))
+     ("gnus-uu" :interactive t
+      gnus-uu-digest-mail-forward gnus-uu-digest-post-forward
+      gnus-uu-mark-series gnus-uu-mark-region gnus-uu-mark-buffer
+      gnus-uu-mark-by-regexp gnus-uu-mark-all
+      gnus-uu-mark-sparse gnus-uu-mark-thread gnus-uu-decode-uu
+      gnus-uu-decode-uu-and-save gnus-uu-decode-unshar
+      gnus-uu-decode-unshar-and-save gnus-uu-decode-save
+      gnus-uu-decode-binhex gnus-uu-decode-uu-view
+      gnus-uu-decode-uu-and-save-view gnus-uu-decode-unshar-view
+      gnus-uu-decode-unshar-and-save-view gnus-uu-decode-save-view
+      gnus-uu-decode-binhex-view)
+     ("gnus-uu" gnus-uu-delete-work-dir gnus-quote-arg-for-sh-or-csh)
+     ("gnus-msg" (gnus-summary-send-map keymap)
+      gnus-article-mail gnus-copy-article-buffer gnus-extended-version)
+     ("gnus-msg" :interactive t
+      gnus-group-post-news gnus-group-mail gnus-summary-post-news
+      gnus-summary-followup gnus-summary-followup-with-original
+      gnus-summary-cancel-article gnus-summary-supersede-article
+      gnus-post-news gnus-summary-reply gnus-summary-reply-with-original
+      gnus-summary-mail-forward gnus-summary-mail-other-window
+      gnus-summary-resend-message gnus-summary-resend-bounced-mail
+      gnus-bug)
+     ("gnus-picon" :interactive t gnus-article-display-picons
+      gnus-group-display-picons gnus-picons-article-display-x-face
+      gnus-picons-display-x-face)
+     ("gnus-gl" bbb-login bbb-logout bbb-grouplens-group-p
+      gnus-grouplens-mode)
+     ("smiley" :interactive t gnus-smiley-display)
+     ("gnus-win" gnus-configure-windows gnus-add-configuration)
+     ("gnus-sum" gnus-summary-insert-line gnus-summary-read-group
+      gnus-list-of-unread-articles gnus-list-of-read-articles
+      gnus-offer-save-summaries gnus-make-thread-indent-array
+      gnus-summary-exit gnus-update-read-articles)
+     ("gnus-group" gnus-group-insert-group-line gnus-group-quit
+      gnus-group-list-groups gnus-group-first-unread-group
+      gnus-group-set-mode-line gnus-group-set-info gnus-group-save-newsrc
+      gnus-group-setup-buffer gnus-group-get-new-news
+      gnus-group-make-help-group gnus-group-update-group)
+     ("gnus-bcklg" gnus-backlog-request-article gnus-backlog-enter-article
+      gnus-backlog-remove-article)
+     ("gnus-art" gnus-article-read-summary-keys gnus-article-save
+      gnus-article-prepare gnus-article-set-window-start
+      gnus-article-next-page gnus-article-prev-page
+      gnus-request-article-this-buffer gnus-article-mode
+      gnus-article-setup-buffer gnus-narrow-to-page
+      gnus-article-delete-invisible-text)
+     ("gnus-art" :interactive t
+      gnus-article-hide-headers gnus-article-hide-boring-headers
+      gnus-article-treat-overstrike gnus-article-word-wrap
+      gnus-article-remove-cr gnus-article-remove-trailing-blank-lines
+      gnus-article-display-x-face gnus-article-de-quoted-unreadable
+      gnus-article-mime-decode-quoted-printable gnus-article-hide-pgp
+      gnus-article-hide-pem gnus-article-hide-signature
+      gnus-article-strip-leading-blank-lines gnus-article-date-local
+      gnus-article-date-original gnus-article-date-lapsed
+      gnus-article-show-all-headers
+      gnus-article-edit-mode gnus-article-edit-article
+      gnus-article-edit-done gnus-decode-rfc1522 article-decode-rfc1522)
+     ("gnus-int" gnus-request-type)
+     ("gnus-start" gnus-newsrc-parse-options gnus-1 gnus-no-server-1
+      gnus-dribble-enter)
+     ("gnus-dup" gnus-dup-suppress-articles gnus-dup-unsuppress-article
+      gnus-dup-enter-articles)
+     ("gnus-range" gnus-copy-sequence)
+     ("gnus-eform" gnus-edit-form)
+     ("gnus-move" :interactive t
+      gnus-group-move-group-to-server gnus-change-server)
+     ("gnus-logic" gnus-score-advanced)
+     ("gnus-undo" gnus-undo-mode gnus-undo-register)
+     ("gnus-async" gnus-async-request-fetched-article gnus-async-prefetch-next
+      gnus-async-prefetch-article gnus-async-prefetch-remove-group
+      gnus-async-halt-prefetch)
+     ("gnus-vm" :interactive t gnus-summary-save-in-vm
+      gnus-summary-save-article-vm))))
+
+;;; gnus-sum.el thingies
+
+
+(defcustom gnus-summary-line-format "%U\%R\%z\%I\%(%[%4L: %-20,20n%]%) %s\n"
+  "*The format specification of the lines in the summary buffer.
+
+It works along the same lines as a normal formatting string,
+with some simple extensions.
+
+%N   Article number, left padded with spaces (string)
+%S   Subject (string)
+%s   Subject if it is at the root of a thread, and \"\" otherwise (string)
+%n   Name of the poster (string)
+%a   Extracted name of the poster (string)
+%A   Extracted address of the poster (string)
+%F   Contents of the From: header (string)
+%x   Contents of the Xref: header (string)
+%D   Date of the article (string)
+%d   Date of the article (string) in DD-MMM format
+%M   Message-id of the article (string)
+%r   References of the article (string)
+%c   Number of characters in the article (integer)
+%L   Number of lines in the article (integer)
+%I   Indentation based on thread level (a string of spaces)
+%T   A string with two possible values: 80 spaces if the article
+     is on thread level two or larger and 0 spaces on level one
+%R   \"A\" if this article has been replied to, \" \" otherwise (character)
+%U   Status of this article (character, \"R\", \"K\", \"-\" or \" \")
+%[   Opening bracket (character, \"[\" or \"<\")
+%]   Closing bracket (character, \"]\" or \">\")
+%>   Spaces of length thread-level (string)
+%<   Spaces of length (- 20 thread-level) (string)
+%i   Article score (number)
+%z   Article zcore (character)
+%t   Number of articles under the current thread (number).
+%e   Whether the thread is empty or not (character).
+%l   GroupLens score (string).
+%V   Total thread score (number).
+%P   The line number (number).
+%u   User defined specifier.  The next character in the format string should
+     be a letter.  Gnus will call the function gnus-user-format-function-X,
+     where X is the letter following %u.  The function will be passed the
+     current header as argument.  The function should return a string, which
+     will be inserted into the summary just like information from any other
+     summary specifier.
+
+Text between %( and %) will be highlighted with `gnus-mouse-face'
+when the mouse point is placed inside the area.	 There can only be one
+such area.
+
+The %U (status), %R (replied) and %z (zcore) specs have to be handled
+with care.  For reasons of efficiency, Gnus will compute what column
+these characters will end up in, and \"hard-code\" that.  This means that
+it is illegal to have these specs after a variable-length spec.	 Well,
+you might not be arrested, but your summary buffer will look strange,
+which is bad enough.
+
+The smart choice is to have these specs as for to the left as
+possible.
+
+This restriction may disappear in later versions of Gnus."
+  :type 'string
+  :group 'gnus-summary-format)
+
+;;;
+;;; Skeleton keymaps
+;;;
+
+(defun gnus-suppress-keymap (keymap)
+  (suppress-keymap keymap)
+  (let ((keys `([delete] "\177" "\M-u"))) ;gnus-mouse-2
+    (while keys
+      (define-key keymap (pop keys) 'undefined))))
+
+(defvar gnus-article-mode-map
+  (let ((keymap (make-keymap)))
+    (gnus-suppress-keymap keymap)
+    keymap))
+(defvar gnus-summary-mode-map
+  (let ((keymap (make-keymap)))
+    (gnus-suppress-keymap keymap)
+    keymap))
+(defvar gnus-group-mode-map
+  (let ((keymap (make-keymap)))
+    (gnus-suppress-keymap keymap)
+    keymap))
+
+
+
+;; Fix by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>.
+;; If you want the cursor to go somewhere else, set these two
+;; functions in some startup hook to whatever you want.
+(defalias 'gnus-summary-position-point 'gnus-goto-colon)
+(defalias 'gnus-group-position-point 'gnus-goto-colon)
+
+;;; Various macros and substs.
+
+(defun gnus-header-from (header)
+  (mail-header-from header))
+
+(defmacro gnus-gethash (string hashtable)
+  "Get hash value of STRING in HASHTABLE."
+  `(symbol-value (intern-soft ,string ,hashtable)))
+
+(defmacro gnus-sethash (string value hashtable)
+  "Set hash value.  Arguments are STRING, VALUE, and HASHTABLE."
+  `(set (intern ,string ,hashtable) ,value))
+(put 'gnus-sethash 'edebug-form-spec '(form form form))
+
+(defmacro gnus-group-unread (group)
+  "Get the currently computed number of unread articles in GROUP."
+  `(car (gnus-gethash ,group gnus-newsrc-hashtb)))
+
+(defmacro gnus-group-entry (group)
+  "Get the newsrc entry for GROUP."
+  `(gnus-gethash ,group gnus-newsrc-hashtb))
+
+(defmacro gnus-active (group)
+  "Get active info on GROUP."
+  `(gnus-gethash ,group gnus-active-hashtb))
+
+(defmacro gnus-set-active (group active)
+  "Set GROUP's active info."
+  `(gnus-sethash ,group ,active gnus-active-hashtb))
+
+(defun gnus-alive-p ()
+  "Say whether Gnus is running or not."
+  (and gnus-group-buffer
+       (get-buffer gnus-group-buffer)
+       (save-excursion
+	 (set-buffer gnus-group-buffer)
+	 (eq major-mode 'gnus-group-mode))))
+
+;; Info access macros.
+
+(defmacro gnus-info-group (info)
+  `(nth 0 ,info))
+(defmacro gnus-info-rank (info)
+  `(nth 1 ,info))
+(defmacro gnus-info-read (info)
+  `(nth 2 ,info))
+(defmacro gnus-info-marks (info)
+  `(nth 3 ,info))
+(defmacro gnus-info-method (info)
+  `(nth 4 ,info))
+(defmacro gnus-info-params (info)
+  `(nth 5 ,info))
+
+(defmacro gnus-info-level (info)
+  `(let ((rank (gnus-info-rank ,info)))
+     (if (consp rank)
+	 (car rank)
+       rank)))
+(defmacro gnus-info-score (info)
+  `(let ((rank (gnus-info-rank ,info)))
+     (or (and (consp rank) (cdr rank)) 0)))
+
+(defmacro gnus-info-set-group (info group)
+  `(setcar ,info ,group))
+(defmacro gnus-info-set-rank (info rank)
+  `(setcar (nthcdr 1 ,info) ,rank))
+(defmacro gnus-info-set-read (info read)
+  `(setcar (nthcdr 2 ,info) ,read))
+(defmacro gnus-info-set-marks (info marks &optional extend)
+  (if extend
+      `(gnus-info-set-entry ,info ,marks 3)
+    `(setcar (nthcdr 3 ,info) ,marks)))
+(defmacro gnus-info-set-method (info method &optional extend)
+  (if extend
+      `(gnus-info-set-entry ,info ,method 4)
+    `(setcar (nthcdr 4 ,info) ,method)))
+(defmacro gnus-info-set-params (info params &optional extend)
+  (if extend
+      `(gnus-info-set-entry ,info ,params 5)
+    `(setcar (nthcdr 5 ,info) ,params)))
+
+(defun gnus-info-set-entry (info entry number)
+  ;; Extend the info until we have enough elements.
+  (while (<= (length info) number)
+    (nconc info (list nil)))
+  ;; Set the entry.
+  (setcar (nthcdr number info) entry))
+
+(defmacro gnus-info-set-level (info level)
+  `(let ((rank (cdr ,info)))
+     (if (consp (car rank))
+	 (setcar (car rank) ,level)
+       (setcar rank ,level))))
+(defmacro gnus-info-set-score (info score)
+  `(let ((rank (cdr ,info)))
+     (if (consp (car rank))
+	 (setcdr (car rank) ,score)
+       (setcar rank (cons (car rank) ,score)))))
+
+(defmacro gnus-get-info (group)
+  `(nth 2 (gnus-gethash ,group gnus-newsrc-hashtb)))
+
+;; Byte-compiler warning.
+(defvar gnus-visual)
+;; Find out whether the gnus-visual TYPE is wanted.
+(defun gnus-visual-p (&optional type class)
+  (and gnus-visual			; Has to be non-nil, at least.
+       (if (not type)			; We don't care about type.
+	   gnus-visual
+	 (if (listp gnus-visual)	; It's a list, so we check it.
+	     (or (memq type gnus-visual)
+		 (memq class gnus-visual))
+	   t))))
+
+;;; Load the compatability functions.
+
+(require 'gnus-ems)
+
+
+;;;
+;;; Shutdown
+;;;
+
+(defvar gnus-shutdown-alist nil)
+
+(defun gnus-add-shutdown (function &rest symbols)
+  "Run FUNCTION whenever one of SYMBOLS is shut down."
+  (push (cons function symbols) gnus-shutdown-alist))
+
+(defun gnus-shutdown (symbol)
+  "Shut down everything that waits for SYMBOL."
+  (let ((alist gnus-shutdown-alist)
+	entry)
+    (while (setq entry (pop alist))
+      (when (memq symbol (cdr entry))
+	(funcall (car entry))))))
+
+
+;;;
+;;; Gnus Utility Functions
+;;;
+
+;; Add the current buffer to the list of buffers to be killed on exit.
+(defun gnus-add-current-to-buffer-list ()
+  (or (memq (current-buffer) gnus-buffer-list)
+      (push (current-buffer) gnus-buffer-list)))
+
+(defun gnus-version (&optional arg)
+  "Version number of this version of Gnus.
+If ARG, insert string at point."
+  (interactive "P")
+  (let ((methods gnus-valid-select-methods)
+	(mess gnus-version)
+	meth)
+    ;; Go through all the legal select methods and add their version
+    ;; numbers to the total version string.  Only the backends that are
+    ;; currently in use will have their message numbers taken into
+    ;; consideration.
+    (while methods
+      (setq meth (intern (concat (caar methods) "-version")))
+      (and (boundp meth)
+	   (stringp (symbol-value meth))
+	   (setq mess (concat mess "; " (symbol-value meth))))
+      (setq methods (cdr methods)))
+    (if arg
+	(insert (message mess))
+      (message mess))))
+
+(defun gnus-continuum-version (version)
+  "Return VERSION as a floating point number."
+  (when (or (string-match "^\\([^ ]+\\)? ?Gnus v?\\([0-9.]+\\)$" version)
+	    (string-match "^\\(.?\\)gnus-\\([0-9.]+\\)$" version))
+    (let* ((alpha (and (match-beginning 1) (match-string 1 version)))
+	   (number (match-string 2 version))
+	   major minor least)
+      (string-match "\\([0-9]\\)\\.\\([0-9]+\\)\\.?\\([0-9]+\\)?" number)
+      (setq major (string-to-number (match-string 1 number)))
+      (setq minor (string-to-number (match-string 2 number)))
+      (setq least (if (match-beginning 3)
+		      (string-to-number (match-string 3 number))
+		    0))
+      (string-to-number
+       (if (zerop major)
+	   (format "%s00%02d%02d"
+		   (cond
+		    ((member alpha '("(ding)" "d")) "4.99")
+		    ((member alpha '("September" "s")) "5.01")
+		    ((member alpha '("Red" "r")) "5.03"))
+		   minor least)
+	 (format "%d.%02d%02d" major minor least))))))
+
+(defun gnus-info-find-node ()
+  "Find Info documentation of Gnus."
+  (interactive)
+  ;; Enlarge info window if needed.
+  (let (gnus-info-buffer)
+    (Info-goto-node (cadr (assq major-mode gnus-info-nodes)))
+    (setq gnus-info-buffer (current-buffer))
+    (gnus-configure-windows 'info)))
+
+;;; More various functions.
+
+(defun gnus-group-read-only-p (&optional group)
+  "Check whether GROUP supports editing or not.
+If GROUP is nil, `gnus-newsgroup-name' will be checked instead.	 Note
+that that variable is buffer-local to the summary buffers."
+  (let ((group (or group gnus-newsgroup-name)))
+    (not (gnus-check-backend-function 'request-replace-article group))))
+
+(defun gnus-group-total-expirable-p (group)
+  "Check whether GROUP is total-expirable or not."
+  (let ((params (gnus-group-find-parameter group))
+	val)
+    (cond
+     ((memq 'total-expire params)
+      t)
+     ((setq val (assq 'total-expire params)) ; (auto-expire . t)
+      (cdr val))
+     (gnus-total-expirable-newsgroups	; Check var.
+      (string-match gnus-total-expirable-newsgroups group)))))
+
+(defun gnus-group-auto-expirable-p (group)
+  "Check whether GROUP is total-expirable or not."
+  (let ((params (gnus-group-find-parameter group))
+	val)
+    (cond
+     ((memq 'auto-expire params)
+      t)
+     ((setq val (assq 'auto-expire params)) ; (auto-expire . t)
+      (cdr val))
+     (gnus-auto-expirable-newsgroups	; Check var.
+      (string-match gnus-auto-expirable-newsgroups group)))))
+
+(defun gnus-virtual-group-p (group)
+  "Say whether GROUP is virtual or not."
+  (memq 'virtual (assoc (symbol-name (car (gnus-find-method-for-group group)))
+			gnus-valid-select-methods)))
+
+(defun gnus-news-group-p (group &optional article)
+  "Return non-nil if GROUP (and ARTICLE) come from a news server."
+  (or (gnus-member-of-valid 'post group) ; Ordinary news group.
+      (and (gnus-member-of-valid 'post-mail group) ; Combined group.
+	   (eq (gnus-request-type group article) 'news))))
+
+;; Returns a list of writable groups.
+(defun gnus-writable-groups ()
+  (let ((alist gnus-newsrc-alist)
+	groups group)
+    (while (setq group (car (pop alist)))
+      (unless (gnus-group-read-only-p group)
+	(push group groups)))
+    (nreverse groups)))
+
+;; Check whether to use long file names.
+(defun gnus-use-long-file-name (symbol)
+  ;; The variable has to be set...
+  (and gnus-use-long-file-name
+       ;; If it isn't a list, then we return t.
+       (or (not (listp gnus-use-long-file-name))
+	   ;; If it is a list, and the list contains `symbol', we
+	   ;; return nil.
+	   (not (memq symbol gnus-use-long-file-name)))))
+
+;; Generate a unique new group name.
+(defun gnus-generate-new-group-name (leaf)
+  (let ((name leaf)
+	(num 0))
+    (while (gnus-gethash name gnus-newsrc-hashtb)
+      (setq name (concat leaf "<" (int-to-string (setq num (1+ num))) ">")))
+    name))
+
+(defun gnus-ephemeral-group-p (group)
+  "Say whether GROUP is ephemeral or not."
+  (gnus-group-get-parameter group 'quit-config))
+
+(defun gnus-group-quit-config (group)
+  "Return the quit-config of GROUP."
+  (gnus-group-get-parameter group 'quit-config))
+
+(defun gnus-kill-ephemeral-group (group)
+  "Remove ephemeral GROUP from relevant structures."
+  (gnus-sethash group nil gnus-newsrc-hashtb))
+
+(defun gnus-simplify-mode-line ()
+  "Make mode lines a bit simpler."
+  (setq mode-line-modified "-- ")
+  (when (listp mode-line-format)
+    (make-local-variable 'mode-line-format)
+    (setq mode-line-format (copy-sequence mode-line-format))
+    (when (equal (nth 3 mode-line-format) "   ")
+      (setcar (nthcdr 3 mode-line-format) " "))))
+
+;;; Servers and groups.
+
+(defsubst gnus-server-add-address (method)
+  (let ((method-name (symbol-name (car method))))
+    (if (and (memq 'address (assoc method-name gnus-valid-select-methods))
+	     (not (assq (intern (concat method-name "-address")) method))
+	     (memq 'physical-address (assq (car method)
+					   gnus-valid-select-methods)))
+	(append method (list (list (intern (concat method-name "-address"))
+				   (nth 1 method))))
+      method)))
+
+(defsubst gnus-server-get-method (group method)
+  ;; Input either a server name, and extended server name, or a
+  ;; select method, and return a select method.
+  (cond ((stringp method)
+	 (gnus-server-to-method method))
+	((equal method gnus-select-method)
+	 gnus-select-method)
+	((and (stringp (car method)) group)
+	 (gnus-server-extend-method group method))
+	((and method (not group)
+	      (equal (cadr method) ""))
+	 method)
+	(t
+	 (gnus-server-add-address method))))
+
+(defun gnus-server-to-method (server)
+  "Map virtual server names to select methods."
+  (or
+   ;; Is this a method, perhaps?
+   (and server (listp server) server)
+   ;; Perhaps this is the native server?
+   (and (equal server "native") gnus-select-method)
+   ;; It should be in the server alist.
+   (cdr (assoc server gnus-server-alist))
+   ;; It could be in the predefined server alist.
+   (cdr (assoc server gnus-predefined-server-alist))
+   ;; If not, we look through all the opened server
+   ;; to see whether we can find it there.
+   (let ((opened gnus-opened-servers))
+     (while (and opened
+		 (not (equal server (format "%s:%s" (caaar opened)
+					    (cadaar opened)))))
+       (pop opened))
+     (caar opened))))
+
+(defmacro gnus-method-equal (ss1 ss2)
+  "Say whether two servers are equal."
+  `(let ((s1 ,ss1)
+	 (s2 ,ss2))
+     (or (equal s1 s2)
+	 (and (= (length s1) (length s2))
+	      (progn
+		(while (and s1 (member (car s1) s2))
+		  (setq s1 (cdr s1)))
+		(null s1))))))
+
+(defun gnus-server-equal (m1 m2)
+  "Say whether two methods are equal."
+  (let ((m1 (cond ((null m1) gnus-select-method)
+		  ((stringp m1) (gnus-server-to-method m1))
+		  (t m1)))
+	(m2 (cond ((null m2) gnus-select-method)
+		  ((stringp m2) (gnus-server-to-method m2))
+		  (t m2))))
+    (gnus-method-equal m1 m2)))
+
+(defun gnus-servers-using-backend (backend)
+  "Return a list of known servers using BACKEND."
+  (let ((opened gnus-opened-servers)
+	out)
+    (while opened
+      (when (eq backend (caaar opened))
+	(push (caar opened) out))
+      (pop opened))
+    out))
+
+(defun gnus-archive-server-wanted-p ()
+  "Say whether the user wants to use the archive server."
+  (cond
+   ((or (not gnus-message-archive-method)
+	(not gnus-message-archive-group))
+    nil)
+   ((and gnus-message-archive-method gnus-message-archive-group)
+    t)
+   (t
+    (let ((active (cadr (assq 'nnfolder-active-file
+			      gnus-message-archive-method))))
+      (and active
+	   (file-exists-p active))))))
+
+(defun gnus-group-prefixed-name (group method)
+  "Return the whole name from GROUP and METHOD."
+  (and (stringp method) (setq method (gnus-server-to-method method)))
+  (if (not method)
+      group
+    (concat (format "%s" (car method))
+	    (when (and
+		   (or (assoc (format "%s" (car method))
+			      (gnus-methods-using 'address))
+		       (gnus-server-equal method gnus-message-archive-method))
+		   (nth 1 method)
+		   (not (string= (nth 1 method) "")))
+	      (concat "+" (nth 1 method)))
+	    ":" group)))
+
+(defun gnus-group-real-prefix (group)
+  "Return the prefix of the current group name."
+  (if (string-match "^[^:]+:" group)
+      (substring group 0 (match-end 0))
+    ""))
+
+(defun gnus-group-method (group)
+  "Return the server or method used for selecting GROUP.
+You should probably use `gnus-find-method-for-group' instead."
+  (let ((prefix (gnus-group-real-prefix group)))
+    (if (equal prefix "")
+	gnus-select-method
+      (let ((servers gnus-opened-servers)
+	    (server "")
+	    backend possible found)
+	(if (string-match "^[^\\+]+\\+" prefix)
+	    (setq backend (intern (substring prefix 0 (1- (match-end 0))))
+		  server (substring prefix (match-end 0) (1- (length prefix))))
+	  (setq backend (intern (substring prefix 0 (1- (length prefix))))))
+	(while servers
+	  (when (eq (caaar servers) backend)
+	    (setq possible (caar servers))
+	    (when (equal (cadaar servers) server)
+	      (setq found (caar servers))))
+	  (pop servers))
+	(or (car (rassoc found gnus-server-alist))
+	    found
+	    (car (rassoc possible gnus-server-alist))
+	    possible
+	    (list backend server))))))
+
+(defsubst gnus-secondary-method-p (method)
+  "Return whether METHOD is a secondary select method."
+  (let ((methods gnus-secondary-select-methods)
+	(gmethod (gnus-server-get-method nil method)))
+    (while (and methods
+		(not (equal (gnus-server-get-method nil (car methods))
+			    gmethod)))
+      (setq methods (cdr methods)))
+    methods))
+
+(defun gnus-group-foreign-p (group)
+  "Say whether a group is foreign or not."
+  (and (not (gnus-group-native-p group))
+       (not (gnus-group-secondary-p group))))
+
+(defun gnus-group-native-p (group)
+  "Say whether the group is native or not."
+  (not (string-match ":" group)))
+
+(defun gnus-group-secondary-p (group)
+  "Say whether the group is secondary or not."
+  (gnus-secondary-method-p (gnus-find-method-for-group group)))
+
+(defun gnus-group-find-parameter (group &optional symbol)
+  "Return the group parameters for GROUP.
+If SYMBOL, return the value of that symbol in the group parameters."
+  (save-excursion
+    (set-buffer gnus-group-buffer)
+    (let ((parameters (funcall gnus-group-get-parameter-function group)))
+      (if symbol
+	  (gnus-group-parameter-value parameters symbol)
+	parameters))))
+
+(defun gnus-group-get-parameter (group &optional symbol)
+  "Return the group parameters for GROUP.
+If SYMBOL, return the value of that symbol in the group parameters."
+  (let ((params (gnus-info-params (gnus-get-info group))))
+    (if symbol
+	(gnus-group-parameter-value params symbol)
+      params)))
+
+(defun gnus-group-parameter-value (params symbol)
+  "Return the value of SYMBOL in group PARAMS."
+  (or (car (memq symbol params))	; It's either a simple symbol
+      (cdr (assq symbol params))))	; or a cons.
+
+(defun gnus-group-add-parameter (group param)
+  "Add parameter PARAM to GROUP."
+  (let ((info (gnus-get-info group)))
+    (when info
+      (gnus-group-remove-parameter group (if (consp param) (car param) param))
+      ;; Cons the new param to the old one and update.
+      (gnus-group-set-info (cons param (gnus-info-params info))
+			   group 'params))))
+
+(defun gnus-group-set-parameter (group name value)
+  "Set parameter NAME to VALUE in GROUP."
+  (let ((info (gnus-get-info group)))
+    (when info
+      (gnus-group-remove-parameter group name)
+      (let ((old-params (gnus-info-params info))
+	    (new-params (list (cons name value))))
+	(while old-params
+	  (when (or (not (listp (car old-params)))
+		    (not (eq (caar old-params) name)))
+	    (setq new-params (append new-params (list (car old-params)))))
+	  (setq old-params (cdr old-params)))
+	(gnus-group-set-info new-params group 'params)))))
+
+(defun gnus-group-remove-parameter (group name)
+  "Remove parameter NAME from GROUP."
+  (let ((info (gnus-get-info group)))
+    (when info
+      (let ((params (gnus-info-params info)))
+	(when params
+	  (setq params (delq name params))
+	  (while (assq name params)
+	    (setq params (delq (assq name params) params)))
+	  (gnus-info-set-params info params))))))
+
+(defun gnus-group-add-score (group &optional score)
+  "Add SCORE to the GROUP score.
+If SCORE is nil, add 1 to the score of GROUP."
+  (let ((info (gnus-get-info group)))
+    (when info
+      (gnus-info-set-score info (+ (gnus-info-score info) (or score 1))))))
+
+;; Function written by Stainless Steel Rat <ratinox@peorth.gweep.net>
+(defun gnus-short-group-name (group &optional levels)
+  "Collapse GROUP name LEVELS.
+Select methods are stripped and any remote host name is stripped down to
+just the host name."
+  (let* ((name "") (foreign "") (depth -1) (skip 1)
+	 (levels (or levels
+		     (progn
+		       (while (string-match "\\." group skip)
+			 (setq skip (match-end 0)
+			       depth (+ depth 1)))
+		       depth))))
+    ;; separate foreign select method from group name and collapse.
+    ;; if method contains a server, collapse to non-domain server name,
+    ;; otherwise collapse to select method
+    (when (string-match ":" group)
+      (cond ((string-match "+" group)
+	     (let* ((plus (string-match "+" group))
+		    (colon (string-match ":" group (or plus 0)))
+		    (dot (string-match "\\." group)))
+	       (setq foreign (concat
+			      (substring group (+ 1 plus)
+					 (cond ((null dot) colon)
+					       ((< colon dot) colon)
+					       ((< dot colon) dot)))
+			      ":")
+		     group (substring group (+ 1 colon)))))
+	    (t
+	     (let* ((colon (string-match ":" group)))
+	       (setq foreign (concat (substring group 0 (+ 1 colon)))
+		     group (substring group (+ 1 colon)))))))
+    ;; collapse group name leaving LEVELS uncollapsed elements
+    (while group
+      (if (and (string-match "\\." group) (> levels 0))
+	  (setq name (concat name (substring group 0 1))
+		group (substring group (match-end 0))
+		levels (- levels 1)
+		name (concat name "."))
+	(setq name (concat foreign name group)
+	      group nil)))
+    name))
+
+(defun gnus-narrow-to-body ()
+  "Narrow to the body of an article."
+  (narrow-to-region
+   (progn
+     (goto-char (point-min))
+     (or (search-forward "\n\n" nil t)
+	 (point-max)))
+   (point-max)))
+
+
+;;;
+;;; Kill file handling.
+;;;
+
+(defun gnus-apply-kill-file ()
+  "Apply a kill file to the current newsgroup.
+Returns the number of articles marked as read."
+  (if (or (file-exists-p (gnus-newsgroup-kill-file nil))
+	  (file-exists-p (gnus-newsgroup-kill-file gnus-newsgroup-name)))
+      (gnus-apply-kill-file-internal)
+    0))
+
+(defun gnus-kill-save-kill-buffer ()
+  (let ((file (gnus-newsgroup-kill-file gnus-newsgroup-name)))
+    (when (get-file-buffer file)
+      (save-excursion
+	(set-buffer (get-file-buffer file))
+	(when (buffer-modified-p)
+	  (save-buffer))
+	(kill-buffer (current-buffer))))))
+
+(defcustom gnus-kill-file-name "KILL"
+  "Suffix of the kill files."
+  :group 'gnus-score-kill
+  :group 'gnus-score-files
+  :type 'string)
+
+(defun gnus-newsgroup-kill-file (newsgroup)
+  "Return the name of a kill file name for NEWSGROUP.
+If NEWSGROUP is nil, return the global kill file name instead."
+  (cond
+   ;; The global KILL file is placed at top of the directory.
+   ((or (null newsgroup)
+	(string-equal newsgroup ""))
+    (expand-file-name gnus-kill-file-name
+		      gnus-kill-files-directory))
+   ;; Append ".KILL" to newsgroup name.
+   ((gnus-use-long-file-name 'not-kill)
+    (expand-file-name (concat (gnus-newsgroup-savable-name newsgroup)
+			      "." gnus-kill-file-name)
+		      gnus-kill-files-directory))
+   ;; Place "KILL" under the hierarchical directory.
+   (t
+    (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
+			      "/" gnus-kill-file-name)
+		      gnus-kill-files-directory))))
+
+;;; Server things.
+
+(defun gnus-member-of-valid (symbol group)
+  "Find out if GROUP has SYMBOL as part of its \"valid\" spec."
+  (memq symbol (assoc
+		(symbol-name (car (gnus-find-method-for-group group)))
+		gnus-valid-select-methods)))
+
+(defun gnus-method-option-p (method option)
+  "Return non-nil if select METHOD has OPTION as a parameter."
+  (when (stringp method)
+    (setq method (gnus-server-to-method method)))
+  (memq option (assoc (format "%s" (car method))
+		      gnus-valid-select-methods)))
+
+(defun gnus-similar-server-opened (method)
+  (let ((opened gnus-opened-servers))
+    (while (and method opened)
+      (when (and (equal (cadr method) (cadaar opened))
+		 (not (equal method (caar opened))))
+	(setq method nil))
+      (pop opened))
+    (not method)))
+
+(defun gnus-server-extend-method (group method)
+  ;; This function "extends" a virtual server.	If the server is
+  ;; "hello", and the select method is ("hello" (my-var "something"))
+  ;; in the group "alt.alt", this will result in a new virtual server
+  ;; called "hello+alt.alt".
+  (if (or (not (inline (gnus-similar-server-opened method)))
+	  (not (cddr method)))
+      method
+    `(,(car method) ,(concat (cadr method) "+" group)
+      (,(intern (format "%s-address" (car method))) ,(cadr method))
+      ,@(cddr method))))
+
+(defun gnus-server-status (method)
+  "Return the status of METHOD."
+  (nth 1 (assoc method gnus-opened-servers)))
+
+(defun gnus-group-name-to-method (group)
+  "Guess a select method based on GROUP."
+  (if (string-match ":" group)
+      (let ((server (substring group 0 (match-beginning 0))))
+	(if (string-match "\\+" server)
+	    (list (intern (substring server 0 (match-beginning 0)))
+		  (substring server (match-end 0)))
+	  (list (intern server) "")))
+    gnus-select-method))
+
+(defun gnus-find-method-for-group (group &optional info)
+  "Find the select method that GROUP uses."
+  (or gnus-override-method
+      (and (not group)
+	   gnus-select-method)
+      (let ((info (or info (gnus-get-info group)))
+	    method)
+	(if (or (not info)
+		(not (setq method (gnus-info-method info)))
+		(equal method "native"))
+	    gnus-select-method
+	  (setq method
+		(cond ((stringp method)
+		       (inline (gnus-server-to-method method)))
+		      ((stringp (cadr method))
+		       (inline (gnus-server-extend-method group method)))
+		      (t
+		       method)))
+	  (cond ((equal (cadr method) "")
+		 method)
+		((null (cadr method))
+		 (list (car method) ""))
+		(t
+		 (gnus-server-add-address method)))))))
+
+(defsubst gnus-check-backend-function (func group)
+  "Check whether GROUP supports function FUNC.
+GROUP can either be a string (a group name) or a select method."
+  (ignore-errors
+    (let ((method (if (stringp group)
+		      (car (gnus-find-method-for-group group))
+		    group)))
+      (unless (featurep method)
+	(require method))
+      (fboundp (intern (format "%s-%s" method func))))))
+
+(defun gnus-methods-using (feature)
+  "Find all methods that have FEATURE."
+  (let ((valids gnus-valid-select-methods)
+	outs)
+    (while valids
+      (when (memq feature (car valids))
+	(push (car valids) outs))
+      (setq valids (cdr valids)))
+    outs))
+
+(defun gnus-read-group (prompt &optional default)
+  "Prompt the user for a group name.
+Disallow illegal group names."
+  (let ((prefix "")
+	group)
+    (while (not group)
+      (when (string-match
+	     "[: `'\"/]\\|^$"
+	     (setq group (read-string (concat prefix prompt)
+				      (cons (or default "") 0)
+				      'gnus-group-history)))
+	(setq prefix (format "Illegal group name: \"%s\".  " group)
+	      group nil)))
+    group))
+
+(defun gnus-read-method (prompt)
+  "Prompt the user for a method.
+Allow completion over sensible values."
+  (let ((method
+	 (completing-read
+	  prompt (append gnus-valid-select-methods gnus-predefined-server-alist
+			 gnus-server-alist)
+	  nil t nil 'gnus-method-history)))
+    (cond
+     ((equal method "")
+      (setq method gnus-select-method))
+     ((assoc method gnus-valid-select-methods)
+      (list (intern method)
+	    (if (memq 'prompt-address
+		      (assoc method gnus-valid-select-methods))
+		(read-string "Address: ")
+	      "")))
+     ((assoc method gnus-server-alist)
+      method)
+     (t
+      (list (intern method) "")))))
+
+;;; User-level commands.
+
+;;;###autoload
+(defun gnus-slave-no-server (&optional arg)
+  "Read network news as a slave, without connecting to local server"
+  (interactive "P")
+  (gnus-no-server arg t))
+
+;;;###autoload
+(defun gnus-no-server (&optional arg slave)
+  "Read network news.
+If ARG is a positive number, Gnus will use that as the
+startup level.	If ARG is nil, Gnus will be started at level 2.
+If ARG is non-nil and not a positive number, Gnus will
+prompt the user for the name of an NNTP server to use.
+As opposed to `gnus', this command will not connect to the local server."
+  (interactive "P")
+  (gnus-no-server-1 arg slave))
+
+;;;###autoload
+(defun gnus-slave (&optional arg)
+  "Read news as a slave."
+  (interactive "P")
+  (gnus arg nil 'slave))
+
+;;;###autoload
+(defun gnus-other-frame (&optional arg)
+  "Pop up a frame to read news."
+  (interactive "P")
+  (let ((window (get-buffer-window gnus-group-buffer)))
+    (cond (window
+	   (select-frame (window-frame window)))
+	  ((= (length (frame-list)) 1)
+	   (select-frame (make-frame)))
+	  (t
+	   (other-frame 1))))
+  (gnus arg))
+
+;;;###autoload
+(defun gnus (&optional arg dont-connect slave)
+  "Read network news.
+If ARG is non-nil and a positive number, Gnus will use that as the
+startup level.	If ARG is non-nil and not a positive number, Gnus will
+prompt the user for the name of an NNTP server to use."
+  (interactive "P")
+  (gnus-1 arg dont-connect slave))
+
+;; Allow redefinition of Gnus functions.
+
+(gnus-ems-redefine)
+
+(provide 'gnus)
+
+;;; gnus.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/message.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,3615 @@
+;;; message.el --- composing mail and news messages
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: mail, news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; This mode provides mail-sending facilities from within Emacs.  It
+;; consists mainly of large chunks of code from the sendmail.el,
+;; gnus-msg.el and rnewspost.el files.
+
+;;; Code:
+
+(require 'cl)
+(require 'mailheader)
+(require 'rmail)
+(require 'nnheader)
+(require 'timezone)
+(require 'easymenu)
+(require 'custom)
+(if (string-match "XEmacs\\|Lucid" emacs-version)
+    (require 'mail-abbrevs)
+  (require 'mailabbrev))
+
+(defgroup message '((user-mail-address custom-variable)
+		    (user-full-name custom-variable))
+  "Mail and news message composing."
+  :link '(custom-manual "(message)Top")
+  :group 'mail
+  :group 'news)
+
+(put 'user-mail-address 'custom-type 'string)
+(put 'user-full-name 'custom-type 'string)
+
+(defgroup message-various nil
+  "Various Message Variables"
+  :link '(custom-manual "(message)Various Message Variables")
+  :group 'message)
+
+(defgroup message-buffers nil
+  "Message Buffers"
+  :link '(custom-manual "(message)Message Buffers")
+  :group 'message)
+
+(defgroup message-sending nil
+  "Message Sending"
+  :link '(custom-manual "(message)Sending Variables")
+  :group 'message)
+
+(defgroup message-interface nil
+  "Message Interface"
+  :link '(custom-manual "(message)Interface")
+  :group 'message)
+
+(defgroup message-forwarding nil
+  "Message Forwarding"
+  :link '(custom-manual "(message)Forwarding")
+  :group 'message-interface)
+
+(defgroup message-insertion nil
+  "Message Insertion"
+  :link '(custom-manual "(message)Insertion")
+  :group 'message)
+
+(defgroup message-headers nil
+  "Message Headers"
+  :link '(custom-manual "(message)Message Headers")
+  :group 'message)
+
+(defgroup message-news nil
+  "Composing News Messages"
+  :group 'message)
+
+(defgroup message-mail nil
+  "Composing Mail Messages"
+  :group 'message)
+
+(defgroup message-faces nil
+  "Faces used for message composing."
+  :group 'message
+  :group 'faces)
+
+(defcustom message-directory "~/Mail/"
+  "*Directory from which all other mail file variables are derived."
+  :group 'message-various
+  :type 'directory)
+
+(defcustom message-max-buffers 10
+  "*How many buffers to keep before starting to kill them off."
+  :group 'message-buffers
+  :type 'integer)
+
+(defcustom message-send-rename-function nil
+  "Function called to rename the buffer after sending it."
+  :group 'message-buffers
+  :type 'function)
+
+(defcustom message-fcc-handler-function 'message-output
+  "*A function called to save outgoing articles.
+This function will be called with the name of the file to store the
+article in.  The default function is `message-output' which saves in Unix
+mailbox format."
+  :type '(radio (function-item message-output)
+		(function :tag "Other"))
+  :group 'message-sending)
+
+(defcustom message-courtesy-message
+  "The following message is a courtesy copy of an article\nthat has been posted to %s as well.\n\n"
+  "*This is inserted at the start of a mailed copy of a posted message.
+If the string contains the format spec \"%s\", the Newsgroups
+the article has been posted to will be inserted there.
+If this variable is nil, no such courtesy message will be added."
+  :group 'message-sending
+  :type 'string)
+
+(defcustom message-ignored-bounced-headers "^\\(Received\\|Return-Path\\):"
+  "*Regexp that matches headers to be removed in resent bounced mail."
+  :group 'message-interface
+  :type 'regexp)
+
+;;;###autoload
+(defcustom message-from-style 'default
+  "*Specifies how \"From\" headers look.
+
+If `nil', they contain just the return address like:
+	king@grassland.com
+If `parens', they look like:
+	king@grassland.com (Elvis Parsley)
+If `angles', they look like:
+	Elvis Parsley <king@grassland.com>
+
+Otherwise, most addresses look like `angles', but they look like
+`parens' if `angles' would need quoting and `parens' would not."
+  :type '(choice (const :tag "simple" nil)
+		 (const parens)
+		 (const angles)
+		 (const default))
+  :group 'message-headers)
+
+(defcustom message-syntax-checks nil
+  ;; Guess this one shouldn't be easy to customize...
+  "Controls what syntax checks should not be performed on outgoing posts.
+To disable checking of long signatures, for instance, add
+ `(signature . disabled)' to this list.
+
+Don't touch this variable unless you really know what you're doing.
+
+Checks include subject-cmsg multiple-headers sendsys message-id from
+long-lines control-chars size new-text redirected-followup signature
+approved sender empty empty-headers message-id from subject
+shorten-followup-to existing-newsgroups."
+  :group 'message-news)
+
+(defcustom message-required-news-headers
+  '(From Newsgroups Subject Date Message-ID
+	 (optional . Organization) Lines
+	 (optional . X-Newsreader))
+  "Headers to be generated or prompted for when posting an article.
+RFC977 and RFC1036 require From, Date, Newsgroups, Subject,
+Message-ID.  Organization, Lines, In-Reply-To, Expires, and
+X-Newsreader are optional.  If don't you want message to insert some
+header, remove it from this list."
+  :group 'message-news
+  :group 'message-headers
+  :type '(repeat sexp))
+
+(defcustom message-required-mail-headers
+  '(From Subject Date (optional . In-Reply-To) Message-ID Lines
+	 (optional . X-Mailer))
+  "Headers to be generated or prompted for when mailing a message.
+RFC822 required that From, Date, To, Subject and Message-ID be
+included.  Organization, Lines and X-Mailer are optional."
+  :group 'message-mail
+  :group 'message-headers
+  :type '(repeat sexp))
+
+(defcustom message-deletable-headers '(Message-ID Date Lines)
+  "Headers to be deleted if they already exist and were generated by message previously."
+  :group 'message-headers
+  :type 'sexp)
+
+(defcustom message-ignored-news-headers
+  "^NNTP-Posting-Host:\\|^Xref:\\|^Bcc:\\|^Gcc:\\|^Fcc:\\|^Resent-Fcc:"
+  "*Regexp of headers to be removed unconditionally before posting."
+  :group 'message-news
+  :group 'message-headers
+  :type 'regexp)
+
+(defcustom message-ignored-mail-headers "^Gcc:\\|^Fcc:\\|^Resent-Fcc:"
+  "*Regexp of headers to be removed unconditionally before mailing."
+  :group 'message-mail
+  :group 'message-headers
+  :type 'regexp)
+
+(defcustom message-ignored-supersedes-headers "^Path:\\|^Date\\|^NNTP-Posting-Host:\\|^Xref:\\|^Lines:\\|^Received:\\|^X-From-Line:\\|Return-Path:\\|^Supersedes:"
+  "*Header lines matching this regexp will be deleted before posting.
+It's best to delete old Path and Date headers before posting to avoid
+any confusion."
+  :group 'message-interface
+  :type 'regexp)
+
+;;;###autoload
+(defcustom message-signature-separator "^-- *$"
+  "Regexp matching the signature separator."
+  :type 'regexp
+  :group 'message-various)
+
+(defcustom message-elide-elipsis "\n[...]\n\n"
+  "*The string which is inserted for elided text.")
+
+(defcustom message-interactive nil
+  "Non-nil means when sending a message wait for and display errors.
+nil means let mailer mail back a message to report errors."
+  :group 'message-sending
+  :group 'message-mail
+  :type 'boolean)
+
+(defcustom message-generate-new-buffers t
+  "*Non-nil means that a new message buffer will be created whenever `mail-setup' is called.
+If this is a function, call that function with three parameters:  The type,
+the to address and the group name.  (Any of these may be nil.)  The function
+should return the new buffer name."
+  :group 'message-buffers
+  :type '(choice (const :tag "off" nil)
+		 (const :tag "on" t)
+		 (function fun)))
+
+(defcustom message-kill-buffer-on-exit nil
+  "*Non-nil means that the message buffer will be killed after sending a message."
+  :group 'message-buffers
+  :type 'boolean)
+
+(defvar gnus-local-organization)
+(defcustom message-user-organization
+  (or (and (boundp 'gnus-local-organization)
+	   (stringp gnus-local-organization)
+	   gnus-local-organization)
+      (getenv "ORGANIZATION")
+      t)
+  "*String to be used as an Organization header.
+If t, use `message-user-organization-file'."
+  :group 'message-headers
+  :type '(choice string
+		 (const :tag "consult file" t)))
+
+;;;###autoload
+(defcustom message-user-organization-file "/usr/lib/news/organization"
+  "*Local news organization file."
+  :type 'file
+  :group 'message-headers)
+
+(defcustom message-autosave-directory "~/"
+  ; (concat (file-name-as-directory message-directory) "drafts/")
+  "*Directory where message autosaves buffers.
+If nil, message won't autosave."
+  :group 'message-buffers
+  :type 'directory)
+
+(defcustom message-forward-start-separator
+  "------- Start of forwarded message -------\n"
+  "*Delimiter inserted before forwarded messages."
+  :group 'message-forwarding
+  :type 'string)
+
+(defcustom message-forward-end-separator
+  "------- End of forwarded message -------\n"
+  "*Delimiter inserted after forwarded messages."
+  :group 'message-forwarding
+  :type 'string)
+
+(defcustom message-signature-before-forwarded-message t
+  "*If non-nil, put the signature before any included forwarded message."
+  :group 'message-forwarding
+  :type 'boolean)
+
+(defcustom message-included-forward-headers
+  "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-\\|^Message-ID:\\|^References:"
+  "*Regexp matching headers to be included in forwarded messages."
+  :group 'message-forwarding
+  :type 'regexp)
+
+(defcustom message-ignored-resent-headers "^Return-receipt"
+  "*All headers that match this regexp will be deleted when resending a message."
+  :group 'message-interface
+  :type 'regexp)
+
+(defcustom message-ignored-cited-headers "."
+  "*Delete these headers from the messages you yank."
+  :group 'message-insertion
+  :type 'regexp)
+
+(defcustom message-cancel-message "I am canceling my own article."
+  "Message to be inserted in the cancel message."
+  :group 'message-interface
+  :type 'string)
+
+;; Useful to set in site-init.el
+;;;###autoload
+(defcustom message-send-mail-function 'message-send-mail-with-sendmail
+  "Function to call to send the current buffer as mail.
+The headers should be delimited by a line whose contents match the
+variable `mail-header-separator'.
+
+Legal values include `message-send-mail-with-sendmail' (the default),
+`message-send-mail-with-mh' and `message-send-mail-with-qmail'."
+  :type '(radio (function-item message-send-mail-with-sendmail)
+		(function-item message-send-mail-with-mh)
+		(function-item message-send-mail-with-qmail)
+		(function :tag "Other"))
+  :group 'message-sending
+  :group 'message-mail)
+
+(defcustom message-send-news-function 'message-send-news
+  "Function to call to send the current buffer as news.
+The headers should be delimited by a line whose contents match the
+variable `mail-header-separator'."
+  :group 'message-sending
+  :group 'message-news
+  :type 'function)
+
+(defcustom message-reply-to-function nil
+  "Function that should return a list of headers.
+This function should pick out addresses from the To, Cc, and From headers
+and respond with new To and Cc headers."
+  :group 'message-interface
+  :type 'function)
+
+(defcustom message-wide-reply-to-function nil
+  "Function that should return a list of headers.
+This function should pick out addresses from the To, Cc, and From headers
+and respond with new To and Cc headers."
+  :group 'message-interface
+  :type 'function)
+
+(defcustom message-followup-to-function nil
+  "Function that should return a list of headers.
+This function should pick out addresses from the To, Cc, and From headers
+and respond with new To and Cc headers."
+  :group 'message-interface
+  :type 'function)
+
+(defcustom message-use-followup-to 'ask
+  "*Specifies what to do with Followup-To header.
+If nil, always ignore the header.  If it is t, use its value, but
+query before using the \"poster\" value.  If it is the symbol `ask',
+always query the user whether to use the value.  If it is the symbol
+`use', always use the value."
+  :group 'message-interface
+  :type '(choice (const :tag "ignore" nil)
+		 (const use)
+		 (const ask)))
+
+;; stuff relating to broken sendmail in MMDF
+(defcustom message-sendmail-f-is-evil nil
+  "*Non-nil means that \"-f username\" should not be added to the sendmail
+command line, because it is even more evil than leaving it out."
+  :group 'message-sending
+  :type 'boolean)
+
+;; qmail-related stuff
+(defcustom message-qmail-inject-program "/var/qmail/bin/qmail-inject"
+  "Location of the qmail-inject program."
+  :group 'message-sending
+  :type 'file)
+
+(defcustom message-qmail-inject-args nil
+  "Arguments passed to qmail-inject programs.
+This should be a list of strings, one string for each argument.
+
+For e.g., if you wish to set the envelope sender address so that bounces
+go to the right place or to deal with listserv's usage of that address, you
+might set this variable to '(\"-f\" \"you@some.where\")."
+  :group 'message-sending
+  :type '(repeat string))
+
+(defvar gnus-post-method)
+(defvar gnus-select-method)
+(defcustom message-post-method
+  (cond ((and (boundp 'gnus-post-method)
+	      gnus-post-method)
+	 gnus-post-method)
+	((boundp 'gnus-select-method)
+	 gnus-select-method)
+	(t '(nnspool "")))
+  "Method used to post news."
+  :group 'message-news
+  :group 'message-sending
+  ;; This should be the `gnus-select-method' widget, but that might
+  ;; create a dependence to `gnus.el'.
+  :type 'sexp)
+
+(defcustom message-generate-headers-first nil
+  "*If non-nil, generate all possible headers before composing."
+  :group 'message-headers
+  :type 'boolean)
+
+(defcustom message-setup-hook nil
+  "Normal hook, run each time a new outgoing message is initialized.
+The function `message-setup' runs this hook."
+  :group 'message-various
+  :type 'hook)
+
+(defcustom message-signature-setup-hook nil
+  "Normal hook, run each time a new outgoing message is initialized.
+It is run after the headers have been inserted and before
+the signature is inserted."
+  :group 'message-various
+  :type 'hook)
+
+(defcustom message-mode-hook nil
+  "Hook run in message mode buffers."
+  :group 'message-various
+  :type 'hook)
+
+(defcustom message-header-hook nil
+  "Hook run in a message mode buffer narrowed to the headers."
+  :group 'message-various
+  :type 'hook)
+
+(defcustom message-header-setup-hook nil
+  "Hook called narrowed to the headers when setting up a message
+buffer."
+  :group 'message-various
+  :type 'hook)
+
+;;;###autoload
+(defcustom message-citation-line-function 'message-insert-citation-line
+  "*Function called to insert the \"Whomever writes:\" line."
+  :type 'function
+  :group 'message-insertion)
+
+;;;###autoload
+(defcustom message-yank-prefix "> "
+  "*Prefix inserted on the lines of yanked messages.
+nil means use indentation."
+  :type 'string
+  :group 'message-insertion)
+
+(defcustom message-indentation-spaces 3
+  "*Number of spaces to insert at the beginning of each cited line.
+Used by `message-yank-original' via `message-yank-cite'."
+  :group 'message-insertion
+  :type 'integer)
+
+;;;###autoload
+(defcustom message-cite-function
+  (if (and (boundp 'mail-citation-hook)
+	   mail-citation-hook)
+      mail-citation-hook
+    'message-cite-original)
+  "*Function for citing an original message."
+  :type '(radio (function-item message-cite-original)
+		(function-item sc-cite-original)
+		(function :tag "Other"))
+  :group 'message-insertion)
+
+;;;###autoload
+(defcustom message-indent-citation-function 'message-indent-citation
+  "*Function for modifying a citation just inserted in the mail buffer.
+This can also be a list of functions.  Each function can find the
+citation between (point) and (mark t).  And each function should leave
+point and mark around the citation text as modified."
+  :type 'function
+  :group 'message-insertion)
+
+(defvar message-abbrevs-loaded nil)
+
+;;;###autoload
+(defcustom message-signature t
+  "*String to be inserted at the end of the message buffer.
+If t, the `message-signature-file' file will be inserted instead.
+If a function, the result from the function will be used instead.
+If a form, the result from the form will be used instead."
+  :type 'sexp
+  :group 'message-insertion)
+
+;;;###autoload
+(defcustom message-signature-file "~/.signature"
+  "*File containing the text inserted at end of message buffer."
+  :type 'file
+  :group 'message-insertion)
+
+(defcustom message-distribution-function nil
+  "*Function called to return a Distribution header."
+  :group 'message-news
+  :group 'message-headers
+  :type 'function)
+
+(defcustom message-expires 14
+  "Number of days before your article expires."
+  :group 'message-news
+  :group 'message-headers
+  :link '(custom-manual "(message)News Headers")
+  :type 'integer)
+
+(defcustom message-user-path nil
+  "If nil, use the NNTP server name in the Path header.
+If stringp, use this; if non-nil, use no host name (user name only)."
+  :group 'message-news
+  :group 'message-headers
+  :link '(custom-manual "(message)News Headers")
+  :type '(choice (const :tag "nntp" nil)
+		 (string :tag "name")
+		 (sexp :tag "none" :format "%t" t)))
+
+(defvar message-reply-buffer nil)
+(defvar message-reply-headers nil)
+(defvar message-newsreader nil)
+(defvar message-mailer nil)
+(defvar message-sent-message-via nil)
+(defvar message-checksum nil)
+(defvar message-send-actions nil
+  "A list of actions to be performed upon successful sending of a message.")
+(defvar message-exit-actions nil
+  "A list of actions to be performed upon exiting after sending a message.")
+(defvar message-kill-actions nil
+  "A list of actions to be performed before killing a message buffer.")
+(defvar message-postpone-actions nil
+  "A list of actions to be performed after postponing a message.")
+
+(defcustom message-default-headers ""
+  "*A string containing header lines to be inserted in outgoing messages.
+It is inserted before you edit the message, so you can edit or delete
+these lines."
+  :group 'message-headers
+  :type 'string)
+
+(defcustom message-default-mail-headers ""
+  "*A string of header lines to be inserted in outgoing mails."
+  :group 'message-headers
+  :group 'message-mail
+  :type 'string)
+
+(defcustom message-default-news-headers ""
+  "*A string of header lines to be inserted in outgoing news
+articles."
+  :group 'message-headers
+  :group 'message-news
+  :type 'string)
+
+;; Note: could use /usr/ucb/mail instead of sendmail;
+;; options -t, and -v if not interactive.
+(defcustom message-mailer-swallows-blank-line
+  (if (and (string-match "sparc-sun-sunos\\(\\'\\|[^5]\\)"
+			 system-configuration)
+	   (file-readable-p "/etc/sendmail.cf")
+	   (let ((buffer (get-buffer-create " *temp*")))
+	     (unwind-protect
+		 (save-excursion
+		   (set-buffer buffer)
+		   (insert-file-contents "/etc/sendmail.cf")
+		   (goto-char (point-min))
+		   (let ((case-fold-search nil))
+		     (re-search-forward "^OR\\>" nil t)))
+	       (kill-buffer buffer))))
+      ;; According to RFC822, "The field-name must be composed of printable
+      ;; ASCII characters (i. e., characters that have decimal values between
+      ;; 33 and 126, except colon)", i. e., any chars except ctl chars,
+      ;; space, or colon.
+      '(looking-at "[ \t]\\|[][!\"#$%&'()*+,-./0-9;<=>?@A-Z\\\\^_`a-z{|}~]+:"))
+  "Set this non-nil if the system's mailer runs the header and body together.
+\(This problem exists on Sunos 4 when sendmail is run in remote mode.)
+The value should be an expression to test whether the problem will
+actually occur."
+  :group 'message-sending
+  :type 'sexp)
+
+(ignore-errors
+  (define-mail-user-agent 'message-user-agent
+    'message-mail 'message-send-and-exit
+    'message-kill-buffer 'message-send-hook))
+
+(defvar message-mh-deletable-headers '(Message-ID Date Lines Sender)
+  "If non-nil, delete the deletable headers before feeding to mh.")
+
+;;; Internal variables.
+;;; Well, not really internal.
+
+(defvar message-mode-syntax-table
+  (let ((table (copy-syntax-table text-mode-syntax-table)))
+    (modify-syntax-entry ?% ". " table)
+    table)
+  "Syntax table used while in Message mode.")
+
+(defvar message-mode-abbrev-table text-mode-abbrev-table
+  "Abbrev table used in Message mode buffers.
+Defaults to `text-mode-abbrev-table'.")
+(defgroup message-headers nil
+  "Message headers."
+  :link '(custom-manual "(message)Variables")
+  :group 'message)
+
+(defface message-header-to-face
+  '((((class color)
+      (background dark))
+     (:foreground "green2" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "MidnightBlue" :bold t))
+    (t
+     (:bold t :italic t)))
+  "Face used for displaying From headers."
+  :group 'message-faces)
+
+(defface message-header-cc-face
+  '((((class color)
+      (background dark))
+     (:foreground "green4" :bold t))
+    (((class color)
+      (background light))
+     (:foreground "MidnightBlue"))
+    (t
+     (:bold t)))
+  "Face used for displaying Cc headers."
+  :group 'message-faces)
+
+(defface message-header-subject-face
+  '((((class color)
+      (background dark))
+     (:foreground "green3"))
+    (((class color)
+      (background light))
+     (:foreground "navy blue" :bold t))
+    (t
+     (:bold t)))
+  "Face used for displaying subject headers."
+  :group 'message-faces)
+
+(defface message-header-newsgroups-face
+  '((((class color)
+      (background dark))
+     (:foreground "yellow" :bold t :italic t))
+    (((class color)
+      (background light))
+     (:foreground "blue4" :bold t :italic t))
+    (t
+     (:bold t :italic t)))
+  "Face used for displaying newsgroups headers."
+  :group 'message-faces)
+
+(defface message-header-other-face
+  '((((class color)
+      (background dark))
+     (:foreground "red4"))
+    (((class color)
+      (background light))
+     (:foreground "steel blue"))
+    (t
+     (:bold t :italic t)))
+  "Face used for displaying newsgroups headers."
+  :group 'message-faces)
+
+(defface message-header-name-face
+  '((((class color)
+      (background dark))
+     (:foreground "DarkGreen"))
+    (((class color)
+      (background light))
+     (:foreground "cornflower blue"))
+    (t
+     (:bold t)))
+  "Face used for displaying header names."
+  :group 'message-faces)
+
+(defface message-header-xheader-face
+  '((((class color)
+      (background dark))
+     (:foreground "blue"))
+    (((class color)
+      (background light))
+     (:foreground "blue"))
+    (t
+     (:bold t)))
+  "Face used for displaying X-Header headers."
+  :group 'message-faces)
+
+(defface message-separator-face
+  '((((class color)
+      (background dark))
+     (:foreground "blue4"))
+    (((class color)
+      (background light))
+     (:foreground "brown"))
+    (t
+     (:bold t)))
+  "Face used for displaying the separator."
+  :group 'message-faces)
+
+(defface message-cited-text-face
+  '((((class color)
+      (background dark))
+     (:foreground "red"))
+    (((class color)
+      (background light))
+     (:foreground "red"))
+    (t
+     (:bold t)))
+  "Face used for displaying cited text names."
+  :group 'message-faces)
+
+(defvar message-font-lock-keywords
+  (let* ((cite-prefix "A-Za-z")
+	 (cite-suffix (concat cite-prefix "0-9_.@-"))
+	 (content "[ \t]*\\(.+\\(\n[ \t].*\\)*\\)"))
+    `((,(concat "^\\(To:\\)" content)
+       (1 'message-header-name-face)
+       (2 'message-header-to-face nil t))
+      (,(concat "^\\(^[GBF]?[Cc][Cc]:\\|^Reply-To:\\)" content)
+       (1 'message-header-name-face)
+       (2 'message-header-cc-face nil t))
+      (,(concat "^\\(Subject:\\)" content)
+       (1 'message-header-name-face)
+       (2 'message-header-subject-face nil t))
+      (,(concat "^\\(Newsgroups:\\|Followup-to:\\)" content)
+       (1 'message-header-name-face)
+       (2 'message-header-newsgroups-face nil t))
+      (,(concat "^\\([^: \n\t]+:\\)" content)
+       (1 'message-header-name-face)
+       (2 'message-header-other-face nil t))
+      (,(concat "^\\(X-[A-Za-z0-9-]+\\|In-Reply-To\\):" content)
+       (1 'message-header-name-face)
+       (2 'message-header-name-face))
+      (,(concat "^\\(" (regexp-quote mail-header-separator) "\\)$")
+       1 'message-separator-face)
+      (,(concat "^[ \t]*"
+		"\\([" cite-prefix "]+[" cite-suffix "]*\\)?"
+		"[>|}].*")
+       (0 'message-cited-text-face))))
+  "Additional expressions to highlight in Message mode.")
+
+(defvar message-face-alist
+  '((bold . bold-region)
+    (underline . underline-region)
+    (default . (lambda (b e)
+		 (unbold-region b e)
+		 (ununderline-region b e))))
+  "Alist of mail and news faces for facemenu.
+The cdr of ech entry is a function for applying the face to a region.")
+
+(defcustom message-send-hook nil
+  "Hook run before sending messages."
+  :group 'message-various
+  :options '(ispell-message)
+  :type 'hook)
+
+(defcustom message-send-mail-hook nil
+  "Hook run before sending mail messages."
+  :group 'message-various
+  :type 'hook)
+
+(defcustom message-send-news-hook nil
+  "Hook run before sending news messages."
+  :group 'message-various
+  :type 'hook)
+
+(defcustom message-sent-hook nil
+  "Hook run after sending messages."
+  :group 'message-various
+  :type 'hook)
+
+;;; Internal variables.
+
+(defvar message-buffer-list nil)
+(defvar message-this-is-news nil)
+(defvar message-this-is-mail nil)
+
+;; Byte-compiler warning
+(defvar gnus-active-hashtb)
+(defvar gnus-read-active-file)
+
+;;; Regexp matching the delimiter of messages in UNIX mail format
+;;; (UNIX From lines), minus the initial ^.
+(defvar message-unix-mail-delimiter
+  (let ((time-zone-regexp
+	 (concat "\\([A-Z]?[A-Z]?[A-Z][A-Z]\\( DST\\)?"
+		 "\\|[-+]?[0-9][0-9][0-9][0-9]"
+		 "\\|"
+		 "\\) *")))
+    (concat
+     "From "
+
+     ;; Username, perhaps with a quoted section that can contain spaces.
+     "\\("
+     "[^ \n]*"
+     "\\(\\|\".*\"[^ \n]*\\)"
+     "\\|<[^<>\n]+>"
+     "\\)  ?"
+
+     ;; The time the message was sent.
+     "\\([^ \n]*\\) *"			; day of the week
+     "\\([^ ]*\\) *"			; month
+     "\\([0-9]*\\) *"			; day of month
+     "\\([0-9:]*\\) *"			; time of day
+
+     ;; Perhaps a time zone, specified by an abbreviation, or by a
+     ;; numeric offset.
+     time-zone-regexp
+
+     ;; The year.
+     " [0-9][0-9]\\([0-9]*\\) *"
+
+     ;; On some systems the time zone can appear after the year, too.
+     time-zone-regexp
+
+     ;; Old uucp cruft.
+     "\\(remote from .*\\)?"
+
+     "\n")))
+
+(defvar message-unsent-separator
+  (concat "^ *---+ +Unsent message follows +---+ *$\\|"
+	  "^ *---+ +Returned message +---+ *$\\|"
+	  "^Start of returned message$\\|"
+	  "^ *---+ +Original message +---+ *$\\|"
+	  "^ *--+ +begin message +--+ *$\\|"
+	  "^ *---+ +Original message follows +---+ *$\\|"
+	  "^|? *---+ +Message text follows: +---+ *|?$")
+  "A regexp that matches the separator before the text of a failed message.")
+
+(defvar message-header-format-alist
+  `((Newsgroups)
+    (To . message-fill-address)
+    (Cc . message-fill-address)
+    (Subject)
+    (In-Reply-To)
+    (Fcc)
+    (Bcc)
+    (Date)
+    (Organization)
+    (Distribution)
+    (Lines)
+    (Expires)
+    (Message-ID)
+    (References)
+    (X-Mailer)
+    (X-Newsreader))
+  "Alist used for formatting headers.")
+
+(eval-and-compile
+  (autoload 'message-setup-toolbar "messagexmas")
+  (autoload 'mh-send-letter "mh-comp")
+  (autoload 'gnus-point-at-eol "gnus-util")
+  (autoload 'gnus-point-at-bol "gnus-util")
+  (autoload 'gnus-output-to-mail "gnus-util")
+  (autoload 'gnus-output-to-rmail "gnus-util")
+  (autoload 'mail-abbrev-in-expansion-header-p "mailabbrev"))
+
+
+
+;;;
+;;; Utility functions.
+;;;
+
+(defmacro message-y-or-n-p (question show &rest text)
+  "Ask QUESTION, displaying the rest of the arguments in a temp. buffer if SHOW"
+  `(message-talkative-question 'y-or-n-p ,question ,show ,@text))
+
+;; Delete the current line (and the next N lines.);
+(defmacro message-delete-line (&optional n)
+  `(delete-region (progn (beginning-of-line) (point))
+		  (progn (forward-line ,(or n 1)) (point))))
+
+(defun message-tokenize-header (header &optional separator)
+  "Split HEADER into a list of header elements.
+\",\" is used as the separator."
+  (if (not header)
+      nil
+    (let ((regexp (format "[%s]+" (or separator ",")))
+	  (beg 1)
+	  (first t)
+	  quoted elems paren)
+      (save-excursion
+	(message-set-work-buffer)
+	(insert header)
+	(goto-char (point-min))
+	(while (not (eobp))
+	  (if first
+	      (setq first nil)
+	    (forward-char 1))
+	  (cond ((and (> (point) beg)
+		      (or (eobp)
+			  (and (looking-at regexp)
+			       (not quoted)
+			       (not paren))))
+		 (push (buffer-substring beg (point)) elems)
+		 (setq beg (match-end 0)))
+		((= (following-char) ?\")
+		 (setq quoted (not quoted)))
+		((and (= (following-char) ?\()
+		      (not quoted))
+		 (setq paren t))
+		((and (= (following-char) ?\))
+		      (not quoted))
+		 (setq paren nil))))
+	(nreverse elems)))))
+
+(defun message-mail-file-mbox-p (file)
+  "Say whether FILE looks like a Unix mbox file."
+  (when (and (file-exists-p file)
+	     (file-readable-p file)
+	     (file-regular-p file))
+    (nnheader-temp-write nil
+      (nnheader-insert-file-contents file)
+      (goto-char (point-min))
+      (looking-at message-unix-mail-delimiter))))
+
+(defun message-fetch-field (header &optional not-all)
+  "The same as `mail-fetch-field', only remove all newlines."
+  (let ((value (mail-fetch-field header nil (not not-all))))
+    (when value
+      (nnheader-replace-chars-in-string value ?\n ? ))))
+
+(defun message-add-header (&rest headers)
+  "Add the HEADERS to the message header, skipping those already present."
+  (while headers
+    (let (hclean)
+      (unless (string-match "^\\([^:]+\\):[ \t]*[^ \t]" (car headers))
+	(error "Invalid header `%s'" (car headers)))
+      (setq hclean (match-string 1 (car headers)))
+    (save-restriction
+      (message-narrow-to-headers)
+      (unless (re-search-forward (concat "^" (regexp-quote hclean) ":") nil t)
+	(insert (car headers) ?\n))))
+    (setq headers (cdr headers))))
+
+(defun message-fetch-reply-field (header)
+  "Fetch FIELD from the message we're replying to."
+  (when (and message-reply-buffer
+	     (buffer-name message-reply-buffer))
+    (save-excursion
+      (set-buffer message-reply-buffer)
+      (message-fetch-field header))))
+
+(defun message-set-work-buffer ()
+  (if (get-buffer " *message work*")
+      (progn
+	(set-buffer " *message work*")
+	(erase-buffer))
+    (set-buffer (get-buffer-create " *message work*"))
+    (kill-all-local-variables)
+    (buffer-disable-undo (current-buffer))))
+
+(defun message-functionp (form)
+  "Return non-nil if FORM is funcallable."
+  (or (and (symbolp form) (fboundp form))
+      (and (listp form) (eq (car form) 'lambda))
+      (compiled-function-p form)))
+
+(defun message-strip-subject-re (subject)
+  "Remove \"Re:\" from subject lines."
+  (if (string-match "^[Rr][Ee]: *" subject)
+      (substring subject (match-end 0))
+    subject))
+
+(defun message-remove-header (header &optional is-regexp first reverse)
+  "Remove HEADER in the narrowed buffer.
+If REGEXP, HEADER is a regular expression.
+If FIRST, only remove the first instance of the header.
+Return the number of headers removed."
+  (goto-char (point-min))
+  (let ((regexp (if is-regexp header (concat "^" header ":")))
+	(number 0)
+	(case-fold-search t)
+	last)
+    (while (and (not (eobp))
+		(not last))
+      (if (if reverse
+	      (not (looking-at regexp))
+	    (looking-at regexp))
+	  (progn
+	    (incf number)
+	    (when first
+	      (setq last t))
+	    (delete-region
+	     (point)
+	     ;; There might be a continuation header, so we have to search
+	     ;; until we find a new non-continuation line.
+	     (progn
+	       (forward-line 1)
+	       (if (re-search-forward "^[^ \t]" nil t)
+		   (goto-char (match-beginning 0))
+		 (point-max)))))
+	(forward-line 1)
+	(if (re-search-forward "^[^ \t]" nil t)
+	    (goto-char (match-beginning 0))
+	  (point-max))))
+    number))
+
+(defun message-narrow-to-headers ()
+  "Narrow the buffer to the head of the message."
+  (widen)
+  (narrow-to-region
+   (goto-char (point-min))
+   (if (re-search-forward
+	(concat "^" (regexp-quote mail-header-separator) "\n") nil t)
+       (match-beginning 0)
+     (point-max)))
+  (goto-char (point-min)))
+
+(defun message-narrow-to-head ()
+  "Narrow the buffer to the head of the message."
+  (widen)
+  (narrow-to-region
+   (goto-char (point-min))
+   (if (search-forward "\n\n" nil 1)
+       (1- (point))
+     (point-max)))
+  (goto-char (point-min)))
+
+(defun message-news-p ()
+  "Say whether the current buffer contains a news message."
+  (or message-this-is-news
+      (save-excursion
+	(save-restriction
+	  (message-narrow-to-headers)
+	  (message-fetch-field "newsgroups")))))
+
+(defun message-mail-p ()
+  "Say whether the current buffer contains a mail message."
+  (or message-this-is-mail
+      (save-excursion
+	(save-restriction
+	  (message-narrow-to-headers)
+	  (or (message-fetch-field "to")
+	      (message-fetch-field "cc")
+	      (message-fetch-field "bcc"))))))
+
+(defun message-next-header ()
+  "Go to the beginning of the next header."
+  (beginning-of-line)
+  (or (eobp) (forward-char 1))
+  (not (if (re-search-forward "^[^ \t]" nil t)
+	   (beginning-of-line)
+	 (goto-char (point-max)))))
+
+(defun message-sort-headers-1 ()
+  "Sort the buffer as headers using `message-rank' text props."
+  (goto-char (point-min))
+  (sort-subr
+   nil 'message-next-header
+   (lambda ()
+     (message-next-header)
+     (unless (bobp)
+       (forward-char -1)))
+   (lambda ()
+     (or (get-text-property (point) 'message-rank)
+	 10000))))
+
+(defun message-sort-headers ()
+  "Sort the headers of the current message according to `message-header-format-alist'."
+  (interactive)
+  (save-excursion
+    (save-restriction
+      (let ((max (1+ (length message-header-format-alist)))
+	    rank)
+	(message-narrow-to-headers)
+	(while (re-search-forward "^[^ \n]+:" nil t)
+	  (put-text-property
+	   (match-beginning 0) (1+ (match-beginning 0))
+	   'message-rank
+	   (if (setq rank (length (memq (assq (intern (buffer-substring
+						       (match-beginning 0)
+						       (1- (match-end 0))))
+					      message-header-format-alist)
+					message-header-format-alist)))
+	       (- max rank)
+	     (1+ max)))))
+      (message-sort-headers-1))))
+
+
+
+;;;
+;;; Message mode
+;;;
+
+;;; Set up keymap.
+
+(defvar message-mode-map nil)
+
+(unless message-mode-map
+  (setq message-mode-map (copy-keymap text-mode-map))
+  (define-key message-mode-map "\C-c?" 'describe-mode)
+
+  (define-key message-mode-map "\C-c\C-f\C-t" 'message-goto-to)
+  (define-key message-mode-map "\C-c\C-f\C-b" 'message-goto-bcc)
+  (define-key message-mode-map "\C-c\C-f\C-w" 'message-goto-fcc)
+  (define-key message-mode-map "\C-c\C-f\C-c" 'message-goto-cc)
+  (define-key message-mode-map "\C-c\C-f\C-s" 'message-goto-subject)
+  (define-key message-mode-map "\C-c\C-f\C-r" 'message-goto-reply-to)
+  (define-key message-mode-map "\C-c\C-f\C-n" 'message-goto-newsgroups)
+  (define-key message-mode-map "\C-c\C-f\C-d" 'message-goto-distribution)
+  (define-key message-mode-map "\C-c\C-f\C-f" 'message-goto-followup-to)
+  (define-key message-mode-map "\C-c\C-f\C-k" 'message-goto-keywords)
+  (define-key message-mode-map "\C-c\C-f\C-u" 'message-goto-summary)
+  (define-key message-mode-map "\C-c\C-b" 'message-goto-body)
+  (define-key message-mode-map "\C-c\C-i" 'message-goto-signature)
+
+  (define-key message-mode-map "\C-c\C-t" 'message-insert-to)
+  (define-key message-mode-map "\C-c\C-n" 'message-insert-newsgroups)
+
+  (define-key message-mode-map "\C-c\C-y" 'message-yank-original)
+  (define-key message-mode-map "\C-c\C-q" 'message-fill-yanked-message)
+  (define-key message-mode-map "\C-c\C-w" 'message-insert-signature)
+  (define-key message-mode-map "\C-c\C-r" 'message-caesar-buffer-body)
+  (define-key message-mode-map "\C-c\C-o" 'message-sort-headers)
+  (define-key message-mode-map "\C-c\M-r" 'message-rename-buffer)
+
+  (define-key message-mode-map "\C-c\C-c" 'message-send-and-exit)
+  (define-key message-mode-map "\C-c\C-s" 'message-send)
+  (define-key message-mode-map "\C-c\C-k" 'message-kill-buffer)
+  (define-key message-mode-map "\C-c\C-d" 'message-dont-send)
+
+  (define-key message-mode-map "\C-c\C-e" 'message-elide-region)
+
+  (define-key message-mode-map "\t" 'message-tab))
+
+(easy-menu-define
+ message-mode-menu message-mode-map "Message Menu."
+ '("Message"
+   ["Sort Headers" message-sort-headers t]
+   ["Yank Original" message-yank-original t]
+   ["Fill Yanked Message" message-fill-yanked-message t]
+   ["Insert Signature" message-insert-signature t]
+   ["Caesar (rot13) Message" message-caesar-buffer-body t]
+   ["Caesar (rot13) Region" message-caesar-region (mark t)]
+   ["Elide Region" message-elide-region (mark t)]
+   ["Rename buffer" message-rename-buffer t]
+   ["Spellcheck" ispell-message t]
+   "----"
+   ["Send Message" message-send-and-exit t]
+   ["Abort Message" message-dont-send t]))
+
+(easy-menu-define
+ message-mode-field-menu message-mode-map ""
+ '("Field"
+   ["Fetch To" message-insert-to t]
+   ["Fetch Newsgroups" message-insert-newsgroups t]
+   "----"
+   ["To" message-goto-to t]
+   ["Subject" message-goto-subject t]
+   ["Cc" message-goto-cc t]
+   ["Reply-To" message-goto-reply-to t]
+   ["Summary" message-goto-summary t]
+   ["Keywords" message-goto-keywords t]
+   ["Newsgroups" message-goto-newsgroups t]
+   ["Followup-To" message-goto-followup-to t]
+   ["Distribution" message-goto-distribution t]
+   ["Body" message-goto-body t]
+   ["Signature" message-goto-signature t]))
+
+(defvar facemenu-add-face-function)
+(defvar facemenu-remove-face-function)
+
+;;;###autoload
+(defun message-mode ()
+  "Major mode for editing mail and news to be sent.
+Like Text Mode but with these additional commands:
+C-c C-s  message-send (send the message)    C-c C-c  message-send-and-exit
+C-c C-f  move to a header field (and create it if there isn't):
+	 C-c C-f C-t  move to To	C-c C-f C-s  move to Subject
+	 C-c C-f C-c  move to Cc	C-c C-f C-b  move to Bcc
+	 C-c C-f C-w  move to Fcc	C-c C-f C-r  move to Reply-To
+	 C-c C-f C-u  move to Summary	C-c C-f C-n  move to Newsgroups
+	 C-c C-f C-k  move to Keywords	C-c C-f C-d  move to Distribution
+	 C-c C-f C-f  move to Followup-To
+C-c C-t  message-insert-to (add a To header to a news followup)
+C-c C-n  message-insert-newsgroups (add a Newsgroup header to a news reply)
+C-c C-b  message-goto-body (move to beginning of message text).
+C-c C-i  message-goto-signature (move to the beginning of the signature).
+C-c C-w  message-insert-signature (insert `message-signature-file' file).
+C-c C-y  message-yank-original (insert current message, if any).
+C-c C-q  message-fill-yanked-message (fill what was yanked).
+C-c C-e  message-elide-region (elide the text between point and mark).
+C-c C-r  message-caesar-buffer-body (rot13 the message body)."
+  (interactive)
+  (kill-all-local-variables)
+  (make-local-variable 'message-reply-buffer)
+  (setq message-reply-buffer nil)
+  (make-local-variable 'message-send-actions) 
+  (make-local-variable 'message-exit-actions) 
+  (make-local-variable 'message-kill-actions)
+  (make-local-variable 'message-postpone-actions)
+  (set-syntax-table message-mode-syntax-table)
+  (use-local-map message-mode-map)
+  (setq local-abbrev-table message-mode-abbrev-table)
+  (setq major-mode 'message-mode)
+  (setq mode-name "Message")
+  (setq buffer-offer-save t)
+  (make-local-variable 'font-lock-defaults)
+  (setq font-lock-defaults '(message-font-lock-keywords t))
+  (make-local-variable 'facemenu-add-face-function)
+  (make-local-variable 'facemenu-remove-face-function)
+  (setq facemenu-add-face-function
+	(lambda (face end)
+	  (let ((face-fun (cdr (assq face message-face-alist))))
+	    (if face-fun
+		(funcall face-fun (point) end)
+	      (error "Face %s not configured for %s mode" face mode-name)))
+	  "")
+	facemenu-remove-face-function t)
+  (make-local-variable 'paragraph-separate)
+  (make-local-variable 'paragraph-start)
+  (setq paragraph-start (concat (regexp-quote mail-header-separator)
+				"$\\|[ \t]*[-_][-_][-_]+$\\|"
+				"-- $\\|"
+				paragraph-start))
+  (setq paragraph-separate (concat (regexp-quote mail-header-separator)
+				   "$\\|[ \t]*[-_][-_][-_]+$\\|"
+				   "-- $\\|"
+				   paragraph-separate))
+  (make-local-variable 'message-reply-headers)
+  (setq message-reply-headers nil)
+  (make-local-variable 'message-newsreader)
+  (make-local-variable 'message-mailer)
+  (make-local-variable 'message-post-method)
+  (make-local-variable 'message-sent-message-via)
+  (setq message-sent-message-via nil)
+  (make-local-variable 'message-checksum)
+  (setq message-checksum nil)
+  ;;(when (fboundp 'mail-hist-define-keys)
+  ;;  (mail-hist-define-keys))
+  (when (string-match "XEmacs\\|Lucid" emacs-version)
+    (message-setup-toolbar))
+  (easy-menu-add message-mode-menu message-mode-map)
+  (easy-menu-add message-mode-field-menu message-mode-map)
+  ;; Allow mail alias things.
+  (if (fboundp 'mail-abbrevs-setup)
+      (mail-abbrevs-setup)
+    (funcall (intern "mail-aliases-setup")))
+  (run-hooks 'text-mode-hook 'message-mode-hook))
+
+
+
+;;;
+;;; Message mode commands
+;;;
+
+;;; Movement commands
+
+(defun message-goto-to ()
+  "Move point to the To header."
+  (interactive)
+  (message-position-on-field "To"))
+
+(defun message-goto-subject ()
+  "Move point to the Subject header."
+  (interactive)
+  (message-position-on-field "Subject"))
+
+(defun message-goto-cc ()
+  "Move point to the Cc header."
+  (interactive)
+  (message-position-on-field "Cc" "To"))
+
+(defun message-goto-bcc ()
+  "Move point to the Bcc  header."
+  (interactive)
+  (message-position-on-field "Bcc" "Cc" "To"))
+
+(defun message-goto-fcc ()
+  "Move point to the Fcc header."
+  (interactive)
+  (message-position-on-field "Fcc" "To" "Newsgroups"))
+
+(defun message-goto-reply-to ()
+  "Move point to the Reply-To header."
+  (interactive)
+  (message-position-on-field "Reply-To" "Subject"))
+
+(defun message-goto-newsgroups ()
+  "Move point to the Newsgroups header."
+  (interactive)
+  (message-position-on-field "Newsgroups"))
+
+(defun message-goto-distribution ()
+  "Move point to the Distribution header."
+  (interactive)
+  (message-position-on-field "Distribution"))
+
+(defun message-goto-followup-to ()
+  "Move point to the Followup-To header."
+  (interactive)
+  (message-position-on-field "Followup-To" "Newsgroups"))
+
+(defun message-goto-keywords ()
+  "Move point to the Keywords header."
+  (interactive)
+  (message-position-on-field "Keywords" "Subject"))
+
+(defun message-goto-summary ()
+  "Move point to the Summary header."
+  (interactive)
+  (message-position-on-field "Summary" "Subject"))
+
+(defun message-goto-body ()
+  "Move point to the beginning of the message body."
+  (interactive)
+  (if (looking-at "[ \t]*\n") (expand-abbrev))
+  (goto-char (point-min))
+  (search-forward (concat "\n" mail-header-separator "\n") nil t))
+
+(defun message-goto-signature ()
+  "Move point to the beginning of the message signature."
+  (interactive)
+  (goto-char (point-min))
+  (if (re-search-forward message-signature-separator nil t)
+      (forward-line 1)
+    (goto-char (point-max))))
+
+
+
+(defun message-insert-to ()
+  "Insert a To header that points to the author of the article being replied to."
+  (interactive)
+  (let ((co (message-fetch-reply-field "mail-copies-to")))
+    (when (and co
+	       (equal (downcase co) "never"))
+      (error "The user has requested not to have copies sent via mail")))
+  (when (and (message-position-on-field "To")
+	     (mail-fetch-field "to")
+	     (not (string-match "\\` *\\'" (mail-fetch-field "to"))))
+    (insert ", "))
+  (insert (or (message-fetch-reply-field "reply-to")
+	      (message-fetch-reply-field "from") "")))
+
+(defun message-insert-newsgroups ()
+  "Insert the Newsgroups header from the article being replied to."
+  (interactive)
+  (when (and (message-position-on-field "Newsgroups")
+	     (mail-fetch-field "newsgroups")
+	     (not (string-match "\\` *\\'" (mail-fetch-field "newsgroups"))))
+    (insert ","))
+  (insert (or (message-fetch-reply-field "newsgroups") "")))
+
+
+
+;;; Various commands
+
+(defun message-insert-signature (&optional force)
+  "Insert a signature.  See documentation for the `message-signature' variable."
+  (interactive (list 0))
+  (let* ((signature
+	  (cond
+	   ((and (null message-signature)
+		 (eq force 0))
+	    (save-excursion
+	      (goto-char (point-max))
+	      (not (re-search-backward
+		    message-signature-separator nil t))))
+	   ((and (null message-signature)
+		 force)
+	    t)
+	   ((message-functionp message-signature)
+	    (funcall message-signature))
+	   ((listp message-signature)
+	    (eval message-signature))
+	   (t message-signature)))
+	 (signature
+	  (cond ((stringp signature)
+		 signature)
+		((and (eq t signature)
+		      message-signature-file
+		      (file-exists-p message-signature-file))
+		 signature))))
+    (when signature
+      (goto-char (point-max))
+      ;; Insert the signature.
+      (unless (bolp)
+	(insert "\n"))
+      (insert "\n-- \n")
+      (if (eq signature t)
+	  (insert-file-contents message-signature-file)
+	(insert signature))
+      (goto-char (point-max))
+      (or (bolp) (insert "\n")))))
+
+(defun message-elide-region (b e)
+  "Elide the text between point and mark.  An ellipsis (from
+message-elide-elipsis) will be inserted where the text was killed."
+  (interactive "r")
+  (kill-region b e)
+  (unless (bolp)
+    (insert "\n"))
+  (insert message-elide-elipsis))
+
+(defvar message-caesar-translation-table nil)
+
+(defun message-caesar-region (b e &optional n)
+  "Caesar rotation of region by N, default 13, for decrypting netnews."
+  (interactive
+   (list
+    (min (point) (or (mark t) (point)))
+    (max (point) (or (mark t) (point)))
+    (when current-prefix-arg
+      (prefix-numeric-value current-prefix-arg))))
+
+  (setq n (if (numberp n) (mod n 26) 13)) ;canonize N
+  (unless (or (zerop n)			; no action needed for a rot of 0
+	      (= b e))			; no region to rotate
+    ;; We build the table, if necessary.
+    (when (or (not message-caesar-translation-table)
+	      (/= (aref message-caesar-translation-table ?a) (+ ?a n)))
+	(setq message-caesar-translation-table
+	      (message-make-caesar-translation-table n)))
+    ;; Then we translate the region.  Do it this way to retain
+    ;; text properties.
+    (while (< b e)
+      (subst-char-in-region
+       b (1+ b) (char-after b)
+       (aref message-caesar-translation-table (char-after b)))
+      (incf b))))
+
+(defun message-make-caesar-translation-table (n)
+  "Create a rot table with offset N."
+  (let ((i -1)
+	(table (make-string 256 0)))
+    (while (< (incf i) 256)
+      (aset table i i))
+    (concat
+     (substring table 0 ?A)
+     (substring table (+ ?A n) (+ ?A n (- 26 n)))
+     (substring table ?A (+ ?A n))
+     (substring table (+ ?A 26) ?a)
+     (substring table (+ ?a n) (+ ?a n (- 26 n)))
+     (substring table ?a (+ ?a n))
+     (substring table (+ ?a 26) 255))))
+
+(defun message-caesar-buffer-body (&optional rotnum)
+  "Caesar rotates all letters in the current buffer by 13 places.
+Used to encode/decode possibly offensive messages (commonly in net.jokes).
+With prefix arg, specifies the number of places to rotate each letter forward.
+Mail and USENET news headers are not rotated."
+  (interactive (if current-prefix-arg
+		   (list (prefix-numeric-value current-prefix-arg))
+		 (list nil)))
+  (save-excursion
+    (save-restriction
+      (when (message-goto-body)
+	(narrow-to-region (point) (point-max)))
+      (message-caesar-region (point-min) (point-max) rotnum))))
+
+(defun message-pipe-buffer-body (program)
+  "Pipe the message body in the current buffer through PROGRAM."
+  (save-excursion
+    (save-restriction
+      (when (message-goto-body)
+        (narrow-to-region (point) (point-max)))
+      (let ((body (buffer-substring (point-min) (point-max))))
+        (unless (equal 0 (call-process-region
+                           (point-min) (point-max) program t t))
+            (insert body)
+            (message "%s failed." program))))))
+
+(defun message-rename-buffer (&optional enter-string)
+  "Rename the *message* buffer to \"*message* RECIPIENT\".
+If the function is run with a prefix, it will ask for a new buffer
+name, rather than giving an automatic name."
+  (interactive "Pbuffer name: ")
+  (save-excursion
+    (save-restriction
+      (goto-char (point-min))
+      (narrow-to-region (point)
+			(search-forward mail-header-separator nil 'end))
+      (let* ((mail-to (or
+		       (if (message-news-p) (message-fetch-field "Newsgroups")
+			 (message-fetch-field "To"))
+		       ""))
+	     (mail-trimmed-to
+	      (if (string-match "," mail-to)
+		  (concat (substring mail-to 0 (match-beginning 0)) ", ...")
+		mail-to))
+	     (name-default (concat "*message* " mail-trimmed-to))
+	     (name (if enter-string
+		       (read-string "New buffer name: " name-default)
+		     name-default))
+	     (default-directory
+	       (file-name-as-directory message-autosave-directory)))
+	(rename-buffer name t)))))
+
+(defun message-fill-yanked-message (&optional justifyp)
+  "Fill the paragraphs of a message yanked into this one.
+Numeric argument means justify as well."
+  (interactive "P")
+  (save-excursion
+    (goto-char (point-min))
+    (search-forward (concat "\n" mail-header-separator "\n") nil t)
+    (let ((fill-prefix message-yank-prefix))
+      (fill-individual-paragraphs (point) (point-max) justifyp t))))
+
+(defun message-indent-citation ()
+  "Modify text just inserted from a message to be cited.
+The inserted text should be the region.
+When this function returns, the region is again around the modified text.
+
+Normally, indent each nonblank line `message-indentation-spaces' spaces.
+However, if `message-yank-prefix' is non-nil, insert that prefix on each line."
+  (let ((start (point)))
+    ;; Remove unwanted headers.
+    (when message-ignored-cited-headers
+      (let (all-removed)
+	(save-restriction
+	  (narrow-to-region
+	   (goto-char start)
+	   (if (search-forward "\n\n" nil t)
+	       (1- (point))
+	     (point)))
+	  (message-remove-header message-ignored-cited-headers t)
+	  (when (= (point-min) (point-max))
+	    (setq all-removed t))
+	  (goto-char (point-max)))
+	(if all-removed
+	    (goto-char start)
+	  (forward-line 1))))
+    ;; Delete blank lines at the start of the buffer.
+    (while (and (point-min)
+		(eolp)
+		(not (eobp)))
+      (message-delete-line))
+    ;; Delete blank lines at the end of the buffer.
+    (goto-char (point-max))
+    (unless (eolp)
+      (insert "\n"))
+    (while (and (zerop (forward-line -1))
+		(looking-at "$"))
+      (message-delete-line))
+    ;; Do the indentation.
+    (if (null message-yank-prefix)
+	(indent-rigidly start (mark t) message-indentation-spaces)
+      (save-excursion
+	(goto-char start)
+	(while (< (point) (mark t))
+	  (insert message-yank-prefix)
+	  (forward-line 1))))
+    (goto-char start)))
+
+(defun message-yank-original (&optional arg)
+  "Insert the message being replied to, if any.
+Puts point before the text and mark after.
+Normally indents each nonblank line ARG spaces (default 3).  However,
+if `message-yank-prefix' is non-nil, insert that prefix on each line.
+
+This function uses `message-cite-function' to do the actual citing.
+
+Just \\[universal-argument] as argument means don't indent, insert no
+prefix, and don't delete any headers."
+  (interactive "P")
+  (let ((modified (buffer-modified-p)))
+    (when (and message-reply-buffer
+	       message-cite-function)
+      (delete-windows-on message-reply-buffer t)
+      (insert-buffer message-reply-buffer)
+      (funcall message-cite-function)
+      (message-exchange-point-and-mark)
+      (unless (bolp)
+	(insert ?\n))
+      (unless modified
+	(setq message-checksum (cons (message-checksum) (buffer-size)))))))
+
+(defun message-cite-original ()
+  "Cite function in the standard Message manner."
+  (let ((start (point))
+	(functions
+	 (when message-indent-citation-function
+	   (if (listp message-indent-citation-function)
+	       message-indent-citation-function
+	     (list message-indent-citation-function)))))
+    (goto-char start)
+    (while functions
+      (funcall (pop functions)))
+    (when message-citation-line-function
+      (unless (bolp)
+	(insert "\n"))
+      (funcall message-citation-line-function))))
+
+(defun message-insert-citation-line ()
+  "Function that inserts a simple citation line."
+  (when message-reply-headers
+    (insert (mail-header-from message-reply-headers) " writes:\n\n")))
+
+(defun message-position-on-field (header &rest afters)
+  (let ((case-fold-search t))
+    (save-restriction
+      (narrow-to-region
+       (goto-char (point-min))
+       (progn
+	 (re-search-forward
+	  (concat "^" (regexp-quote mail-header-separator) "$"))
+	 (match-beginning 0)))
+      (goto-char (point-min))
+      (if (re-search-forward (concat "^" (regexp-quote header) ":") nil t)
+	  (progn
+	    (re-search-forward "^[^ \t]" nil 'move)
+	    (beginning-of-line)
+	    (skip-chars-backward "\n")
+	    t)
+	(while (and afters
+		    (not (re-search-forward
+			  (concat "^" (regexp-quote (car afters)) ":")
+			  nil t)))
+	  (pop afters))
+	(when afters
+	  (re-search-forward "^[^ \t]" nil 'move)
+	  (beginning-of-line))
+	(insert header ": \n")
+	(forward-char -1)
+	nil))))
+
+(defun message-remove-signature ()
+  "Remove the signature from the text between point and mark.
+The text will also be indented the normal way."
+  (save-excursion
+    (let ((start (point))
+	  mark)
+      (if (not (re-search-forward message-signature-separator (mark t) t))
+	  ;; No signature here, so we just indent the cited text.
+	  (message-indent-citation)
+	;; Find the last non-empty line.
+	(forward-line -1)
+	(while (looking-at "[ \t]*$")
+	  (forward-line -1))
+	(forward-line 1)
+	(setq mark (set-marker (make-marker) (point)))
+	(goto-char start)
+	(message-indent-citation)
+	;; Enable undoing the deletion.
+	(undo-boundary)
+	(delete-region mark (mark t))
+	(set-marker mark nil)))))
+
+
+
+;;;
+;;; Sending messages
+;;;
+
+(defun message-send-and-exit (&optional arg)
+  "Send message like `message-send', then, if no errors, exit from mail buffer."
+  (interactive "P")
+  (let ((buf (current-buffer))
+	(actions message-exit-actions))
+    (when (and (message-send arg)
+	       (buffer-name buf))
+      (if message-kill-buffer-on-exit
+	  (kill-buffer buf)
+	(bury-buffer buf)
+	(when (eq buf (current-buffer))
+	  (message-bury buf)))
+      (message-do-actions actions))))
+
+(defun message-dont-send ()
+  "Don't send the message you have been editing."
+  (interactive)
+  (let ((actions message-postpone-actions))
+    (message-bury (current-buffer))
+    (message-do-actions actions)))
+
+(defun message-kill-buffer ()
+  "Kill the current buffer."
+  (interactive)
+  (when (or (not (buffer-modified-p))
+	    (yes-or-no-p "Message modified; kill anyway? "))
+    (let ((actions message-kill-actions))
+      (kill-buffer (current-buffer))
+      (message-do-actions actions))))
+
+(defun message-bury (buffer)
+  "Bury this mail buffer."
+  (let ((newbuf (other-buffer buffer)))
+    (bury-buffer buffer)
+    (if (and (fboundp 'frame-parameters)
+	     (cdr (assq 'dedicated (frame-parameters)))
+	     (not (null (delq (selected-frame) (visible-frame-list)))))
+	(delete-frame (selected-frame))
+      (switch-to-buffer newbuf))))
+
+(defun message-send (&optional arg)
+  "Send the message in the current buffer.
+If `message-interactive' is non-nil, wait for success indication
+or error messages, and inform user.
+Otherwise any failure is reported in a message back to
+the user from the mailer."
+  (interactive "P")
+  (when (if buffer-file-name
+	    (y-or-n-p (format "Send buffer contents as %s message? "
+			      (if (message-mail-p)
+				  (if (message-news-p) "mail and news" "mail")
+				"news")))
+	  (or (buffer-modified-p)
+	      (y-or-n-p "No changes in the buffer; really send? ")))
+    ;; Make it possible to undo the coming changes.
+    (undo-boundary)
+    (let ((inhibit-read-only t))
+      (put-text-property (point-min) (point-max) 'read-only nil))
+    (message-fix-before-sending)
+    (run-hooks 'message-send-hook)
+    (message "Sending...")
+    (when (and (or (not (message-news-p))
+		   (and (or (not (memq 'news message-sent-message-via))
+			    (y-or-n-p
+			     "Already sent message via news; resend? "))
+			(funcall message-send-news-function arg)))
+	       (or (not (message-mail-p))
+		   (and (or (not (memq 'mail message-sent-message-via))
+			    (y-or-n-p
+			     "Already sent message via mail; resend? "))
+			(message-send-mail arg))))
+      (message-do-fcc)
+      ;;(when (fboundp 'mail-hist-put-headers-into-history)
+      ;; (mail-hist-put-headers-into-history))
+      (run-hooks 'message-sent-hook)
+      (message "Sending...done")
+      ;; If buffer has no file, mark it as unmodified and delete autosave.
+      (unless buffer-file-name
+	(set-buffer-modified-p nil)
+	(delete-auto-save-file-if-necessary t))
+      ;; Delete other mail buffers and stuff.
+      (message-do-send-housekeeping)
+      (message-do-actions message-send-actions)
+      ;; Return success.
+      t)))
+
+(defun message-fix-before-sending ()
+  "Do various things to make the message nice before sending it."
+  ;; Make sure there's a newline at the end of the message.
+  (goto-char (point-max))
+  (unless (bolp)
+    (insert "\n")))
+
+(defun message-add-action (action &rest types)
+  "Add ACTION to be performed when doing an exit of type TYPES."
+  (let (var)
+    (while types
+      (set (setq var (intern (format "message-%s-actions" (pop types))))
+	   (nconc (symbol-value var) (list action))))))
+
+(defun message-do-actions (actions)
+  "Perform all actions in ACTIONS."
+  ;; Now perform actions on successful sending.
+  (while actions
+    (ignore-errors
+      (cond
+       ;; A simple function.
+       ((message-functionp (car actions))
+	(funcall (car actions)))
+       ;; Something to be evaled.
+       (t
+	(eval (car actions)))))
+    (pop actions)))
+
+(defun message-send-mail (&optional arg)
+  (require 'mail-utils)
+  (let ((tembuf (message-generate-new-buffer-clone-locals " message temp"))
+	(case-fold-search nil)
+	(news (message-news-p))
+	(mailbuf (current-buffer)))
+    (save-restriction
+      (message-narrow-to-headers)
+      ;; Insert some headers.
+      (let ((message-deletable-headers
+	     (if news nil message-deletable-headers)))
+	(message-generate-headers message-required-mail-headers))
+      ;; Let the user do all of the above.
+      (run-hooks 'message-header-hook))
+    (unwind-protect
+	(save-excursion
+	  (set-buffer tembuf)
+	  (erase-buffer)
+	  ;; Avoid copying text props.
+	  (insert (format
+		   "%s" (save-excursion
+			  (set-buffer mailbuf)
+			  (buffer-string))))
+	  ;; Remove some headers.
+	  (save-restriction
+	    (message-narrow-to-headers)
+	    ;; Remove some headers.
+	    (message-remove-header message-ignored-mail-headers t))
+	  (goto-char (point-max))
+	  ;; require one newline at the end.
+	  (or (= (preceding-char) ?\n)
+	      (insert ?\n))
+	  (when (and news
+		     (or (message-fetch-field "cc")
+			 (message-fetch-field "to")))
+	    (message-insert-courtesy-copy))
+	  (funcall message-send-mail-function))
+      (kill-buffer tembuf))
+    (set-buffer mailbuf)
+    (push 'mail message-sent-message-via)))
+
+(defun message-send-mail-with-sendmail ()
+  "Send off the prepared buffer with sendmail."
+  (let ((errbuf (if message-interactive
+		    (generate-new-buffer " sendmail errors")
+		  0))
+	resend-to-addresses delimline)
+    (let ((case-fold-search t))
+      (save-restriction
+	(message-narrow-to-headers)
+	(setq resend-to-addresses (message-fetch-field "resent-to")))
+      ;; Change header-delimiter to be what sendmail expects.
+      (goto-char (point-min))
+      (re-search-forward
+       (concat "^" (regexp-quote mail-header-separator) "\n"))
+      (replace-match "\n")
+      (backward-char 1)
+      (setq delimline (point-marker))
+      (run-hooks 'message-send-mail-hook)
+      ;; Insert an extra newline if we need it to work around
+      ;; Sun's bug that swallows newlines.
+      (goto-char (1+ delimline))
+      (when (eval message-mailer-swallows-blank-line)
+	(newline))
+      (when message-interactive
+	(save-excursion
+	  (set-buffer errbuf)
+	  (erase-buffer))))
+    (let ((default-directory "/"))
+      (apply 'call-process-region
+	     (append (list (point-min) (point-max)
+			   (if (boundp 'sendmail-program)
+			       sendmail-program
+			     "/usr/lib/sendmail")
+			   nil errbuf nil "-oi")
+		     ;; Always specify who from,
+		     ;; since some systems have broken sendmails.
+		     ;; But some systems are more broken with -f, so
+		     ;; we'll let users override this.
+		     (if (null message-sendmail-f-is-evil)
+			 (list "-f" (user-login-name)))
+		     ;; These mean "report errors by mail"
+		     ;; and "deliver in background".
+		     (if (null message-interactive) '("-oem" "-odb"))
+		     ;; Get the addresses from the message
+		     ;; unless this is a resend.
+		     ;; We must not do that for a resend
+		     ;; because we would find the original addresses.
+		     ;; For a resend, include the specific addresses.
+		     (if resend-to-addresses
+			 (list resend-to-addresses)
+		       '("-t")))))
+    (when message-interactive
+      (save-excursion
+	(set-buffer errbuf)
+	(goto-char (point-min))
+	(while (re-search-forward "\n\n* *" nil t)
+	  (replace-match "; "))
+	(if (not (zerop (buffer-size)))
+	    (error "Sending...failed to %s"
+		   (buffer-substring (point-min) (point-max)))))
+      (when (bufferp errbuf)
+	(kill-buffer errbuf)))))
+
+(defun message-send-mail-with-qmail ()
+  "Pass the prepared message buffer to qmail-inject.
+Refer to the documentation for the variable `message-send-mail-function'
+to find out how to use this."
+  ;; replace the header delimiter with a blank line
+  (goto-char (point-min))
+  (re-search-forward
+   (concat "^" (regexp-quote mail-header-separator) "\n"))
+  (replace-match "\n")
+  (run-hooks 'message-send-mail-hook)
+  ;; send the message
+  (case
+      (apply
+       'call-process-region 1 (point-max) message-qmail-inject-program
+       nil nil nil
+       ;; qmail-inject's default behaviour is to look for addresses on the
+       ;; command line; if there're none, it scans the headers.
+       ;; yes, it does The Right Thing w.r.t. Resent-To and it's kin.
+       ;;
+       ;; in general, ALL of qmail-inject's defaults are perfect for simply
+       ;; reading a formatted (i. e., at least a To: or Resent-To header)
+       ;; message from stdin.
+       ;;
+       ;; qmail also has the advantage of not having been raped by
+       ;; various vendors, so we don't have to allow for that, either --
+       ;; compare this with message-send-mail-with-sendmail and weep
+       ;; for sendmail's lost innocence.
+       ;;
+       ;; all this is way cool coz it lets us keep the arguments entirely
+       ;; free for -inject-arguments -- a big win for the user and for us
+       ;; since we don't have to play that double-guessing game and the user
+       ;; gets full control (no gestapo'ish -f's, for instance).  --sj
+       message-qmail-inject-args)
+    ;; qmail-inject doesn't say anything on it's stdout/stderr,
+    ;; we have to look at the retval instead
+    (0 nil)
+    (1   (error "qmail-inject reported permanent failure."))
+    (111 (error "qmail-inject reported transient failure."))
+    ;; should never happen
+    (t   (error "qmail-inject reported unknown failure."))))
+
+(defun message-send-mail-with-mh ()
+  "Send the prepared message buffer with mh."
+  (let ((mh-previous-window-config nil)
+	(name (make-temp-name
+	       (concat (file-name-as-directory
+			(expand-file-name message-autosave-directory))
+		       "msg."))))
+    (setq buffer-file-name name)
+    ;; MH wants to generate these headers itself.
+    (when message-mh-deletable-headers
+      (let ((headers message-mh-deletable-headers))
+	(while headers
+	  (goto-char (point-min))
+	  (and (re-search-forward
+		(concat "^" (symbol-name (car headers)) ": *") nil t)
+	       (message-delete-line))
+	  (pop headers))))
+    (run-hooks 'message-send-mail-hook)
+    ;; Pass it on to mh.
+    (mh-send-letter)))
+
+(defun message-send-news (&optional arg)
+  (let ((tembuf (message-generate-new-buffer-clone-locals " *message temp*"))
+	(case-fold-search nil)
+	(method (if (message-functionp message-post-method)
+		    (funcall message-post-method arg)
+		  message-post-method))
+	(messbuf (current-buffer))
+	(message-syntax-checks
+	 (if arg
+	     (cons '(existing-newsgroups . disabled)
+		   message-syntax-checks)
+	   message-syntax-checks))
+	result)
+    (save-restriction
+      (message-narrow-to-headers)
+      ;; Insert some headers.
+      (message-generate-headers message-required-news-headers)
+      ;; Let the user do all of the above.
+      (run-hooks 'message-header-hook))
+    (message-cleanup-headers)
+    (if (not (message-check-news-syntax))
+	(progn
+	  ;;(message "Posting not performed")
+	  nil)
+      (unwind-protect
+	  (save-excursion
+	    (set-buffer tembuf)
+	    (buffer-disable-undo (current-buffer))
+	    (erase-buffer)
+	    ;; Avoid copying text props.
+	    (insert (format
+		     "%s" (save-excursion
+			    (set-buffer messbuf)
+			    (buffer-string))))
+	    ;; Remove some headers.
+	    (save-restriction
+	      (message-narrow-to-headers)
+	      ;; Remove some headers.
+	      (message-remove-header message-ignored-news-headers t))
+	    (goto-char (point-max))
+	    ;; require one newline at the end.
+	    (or (= (preceding-char) ?\n)
+		(insert ?\n))
+	    (let ((case-fold-search t))
+	      ;; Remove the delimiter.
+	      (goto-char (point-min))
+	      (re-search-forward
+	       (concat "^" (regexp-quote mail-header-separator) "\n"))
+	      (replace-match "\n")
+	      (backward-char 1))
+	    (run-hooks 'message-send-news-hook)
+	    (require (car method))
+	    (funcall (intern (format "%s-open-server" (car method)))
+		     (cadr method) (cddr method))
+	    (setq result
+		  (funcall (intern (format "%s-request-post" (car method))))))
+	(kill-buffer tembuf))
+      (set-buffer messbuf)
+      (if result
+	  (push 'news message-sent-message-via)
+	(message "Couldn't send message via news: %s"
+		 (nnheader-get-report (car method)))
+	nil))))
+
+;;;
+;;; Header generation & syntax checking.
+;;;
+
+(defmacro message-check (type &rest forms)
+  "Eval FORMS if TYPE is to be checked."
+  `(or (message-check-element ,type)
+       (save-excursion
+	 ,@forms)))
+
+(put 'message-check 'lisp-indent-function 1)
+(put 'message-check 'edebug-form-spec '(form body))
+
+(defun message-check-element (type)
+  "Returns non-nil if this type is not to be checked."
+  (if (eq message-syntax-checks 'dont-check-for-anything-just-trust-me)
+      t
+    (let ((able (assq type message-syntax-checks)))
+      (and (consp able)
+	   (eq (cdr able) 'disabled)))))
+
+(defun message-check-news-syntax ()
+  "Check the syntax of the message."
+  (save-excursion
+    (save-restriction
+      (widen)
+      (and
+       ;; We narrow to the headers and check them first.
+       (save-excursion
+	 (save-restriction
+	   (message-narrow-to-headers)
+	   (message-check-news-header-syntax)))
+       ;; Check the body.
+       (message-check-news-body-syntax)))))
+
+(defun message-check-news-header-syntax ()
+  (and
+   ;; Check the Subject header.
+   (message-check 'subject
+     (let* ((case-fold-search t)
+	    (subject (message-fetch-field "subject")))
+       (or
+	(and subject
+	     (not (string-match "\\`[ \t]*\\'" subject)))
+	(ignore
+	 (message
+	  "The subject field is empty or missing.  Posting is denied.")))))
+   ;; Check for commands in Subject.
+   (message-check 'subject-cmsg
+     (if (string-match "^cmsg " (message-fetch-field "subject"))
+	 (y-or-n-p
+	  "The control code \"cmsg\" is in the subject.  Really post? ")
+       t))
+   ;; Check for multiple identical headers.
+   (message-check 'multiple-headers
+     (let (found)
+       (while (and (not found)
+		   (re-search-forward "^[^ \t:]+: " nil t))
+	 (save-excursion
+	   (or (re-search-forward
+		(concat "^"
+			(regexp-quote
+			 (setq found
+			       (buffer-substring
+				(match-beginning 0) (- (match-end 0) 2))))
+			":")
+		nil t)
+	       (setq found nil))))
+       (if found
+	   (y-or-n-p (format "Multiple %s headers.  Really post? " found))
+	 t)))
+   ;; Check for Version and Sendsys.
+   (message-check 'sendsys
+     (if (re-search-forward "^Sendsys:\\|^Version:" nil t)
+	 (y-or-n-p
+	  (format "The article contains a %s command.  Really post? "
+		  (buffer-substring (match-beginning 0)
+				    (1- (match-end 0)))))
+       t))
+   ;; See whether we can shorten Followup-To.
+   (message-check 'shorten-followup-to
+     (let ((newsgroups (message-fetch-field "newsgroups"))
+	   (followup-to (message-fetch-field "followup-to"))
+	   to)
+       (when (and newsgroups
+		  (string-match "," newsgroups)
+		  (not followup-to)
+		  (not
+		   (zerop
+		    (length
+		     (setq to (completing-read
+			       "Followups to: (default all groups) "
+			       (mapcar (lambda (g) (list g))
+				       (cons "poster"
+					     (message-tokenize-header
+					      newsgroups)))))))))
+	 (goto-char (point-min))
+	 (insert "Followup-To: " to "\n"))
+       t))
+   ;; Check "Shoot me".
+   (message-check 'shoot
+     (if (re-search-forward
+	  "Message-ID.*.i-did-not-set--mail-host-address--so-shoot-me" nil t)
+	 (y-or-n-p "You appear to have a misconfigured system.  Really post? ")
+       t))
+   ;; Check for Approved.
+   (message-check 'approved
+     (if (re-search-forward "^Approved:" nil t)
+	 (y-or-n-p "The article contains an Approved header.  Really post? ")
+       t))
+   ;; Check the Message-ID header.
+   (message-check 'message-id
+     (let* ((case-fold-search t)
+	    (message-id (message-fetch-field "message-id" t)))
+       (or (not message-id)
+	   (and (string-match "@" message-id)
+		(string-match "@[^\\.]*\\." message-id))
+	   (y-or-n-p
+	    (format "The Message-ID looks strange: \"%s\".  Really post? "
+		    message-id)))))
+   ;; Check the Newsgroups & Followup-To headers.
+   (message-check 'existing-newsgroups
+     (let* ((case-fold-search t)
+	    (newsgroups (message-fetch-field "newsgroups"))
+	    (followup-to (message-fetch-field "followup-to"))
+	    (groups (message-tokenize-header
+		     (if followup-to
+			 (concat newsgroups "," followup-to)
+		       newsgroups)))
+	    (hashtb (and (boundp 'gnus-active-hashtb)
+			 gnus-active-hashtb))
+	    errors)
+       (if (or (not hashtb)
+	       (not (boundp 'gnus-read-active-file))
+	       (not gnus-read-active-file)
+	       (eq gnus-read-active-file 'some))
+	   t
+	 (while groups
+	   (when (and (not (boundp (intern (car groups) hashtb)))
+		      (not (equal (car groups) "poster")))
+	     (push (car groups) errors))
+	   (pop groups))
+	 (if (not errors)
+	     t
+	   (y-or-n-p
+	    (format
+	     "Really post to %s unknown group%s: %s "
+	     (if (= (length errors) 1) "this" "these")
+	     (if (= (length errors) 1) "" "s")
+	     (mapconcat 'identity errors ", ")))))))
+   ;; Check the Newsgroups & Followup-To headers for syntax errors.
+   (message-check 'valid-newsgroups
+     (let ((case-fold-search t)
+	   (headers '("Newsgroups" "Followup-To"))
+	   header error)
+       (while (and headers (not error))
+	 (when (setq header (mail-fetch-field (car headers)))
+	   (if (or
+		(not
+		 (string-match
+		  "\\`\\([-+_&.a-zA-Z0-9]+\\)?\\(,[-+_&.a-zA-Z0-9]+\\)*\\'"
+		  header))
+		(memq
+		 nil (mapcar
+		      (lambda (g)
+			(not (string-match "\\.\\'\\|\\.\\." g)))
+		      (message-tokenize-header header ","))))
+	       (setq error t)))
+	 (unless error
+	   (pop headers)))
+       (if (not error)
+	   t
+	 (y-or-n-p
+	  (format "The %s header looks odd: \"%s\".  Really post? "
+		  (car headers) header)))))
+   ;; Check the From header.
+   (message-check 'from
+     (let* ((case-fold-search t)
+	    (from (message-fetch-field "from"))
+	    (ad (nth 1 (mail-extract-address-components from))))
+       (cond
+	((not from)
+	 (message "There is no From line.  Posting is denied.")
+	 nil)
+	((or (not (string-match "@[^\\.]*\\." ad)) ;larsi@ifi
+	     (string-match "\\.\\." ad) ;larsi@ifi..uio
+	     (string-match "@\\." ad)	;larsi@.ifi.uio
+	     (string-match "\\.$" ad)	;larsi@ifi.uio.
+	     (not (string-match "^[^@]+@[^@]+$" ad)) ;larsi.ifi.uio
+	     (string-match "(.*).*(.*)" from)) ;(lars) (lars)
+	 (message
+	  "Denied posting -- the From looks strange: \"%s\"." from)
+	 nil)
+	(t t))))))
+
+(defun message-check-news-body-syntax ()
+  (and
+   ;; Check for long lines.
+   (message-check 'long-lines
+     (goto-char (point-min))
+     (re-search-forward
+      (concat "^" (regexp-quote mail-header-separator) "$"))
+     (while (and
+	     (progn
+	       (end-of-line)
+	       (< (current-column) 80))
+	     (zerop (forward-line 1))))
+     (or (bolp)
+	 (eobp)
+	 (y-or-n-p
+	  "You have lines longer than 79 characters.  Really post? ")))
+   ;; Check whether the article is empty.
+   (message-check 'empty
+     (goto-char (point-min))
+     (re-search-forward
+      (concat "^" (regexp-quote mail-header-separator) "$"))
+     (forward-line 1)
+     (let ((b (point)))
+       (goto-char (point-max))
+       (re-search-backward message-signature-separator nil t)
+       (beginning-of-line)
+       (or (re-search-backward "[^ \n\t]" b t)
+	   (y-or-n-p "Empty article.  Really post? "))))
+   ;; Check for control characters.
+   (message-check 'control-chars
+     (if (re-search-forward "[\000-\007\013\015-\037\200-\237]" nil t)
+	 (y-or-n-p
+	  "The article contains control characters.  Really post? ")
+       t))
+   ;; Check excessive size.
+   (message-check 'size
+     (if (> (buffer-size) 60000)
+	 (y-or-n-p
+	  (format "The article is %d octets long.  Really post? "
+		  (buffer-size)))
+       t))
+   ;; Check whether any new text has been added.
+   (message-check 'new-text
+     (or
+      (not message-checksum)
+      (not (and (eq (message-checksum) (car message-checksum))
+		(eq (buffer-size) (cdr message-checksum))))
+      (y-or-n-p
+       "It looks like no new text has been added.  Really post? ")))
+   ;; Check the length of the signature.
+   (message-check 'signature
+     (goto-char (point-max))
+     (if (or (not (re-search-backward message-signature-separator nil t))
+	     (search-forward message-forward-end-separator nil t))
+	 t
+       (if (> (count-lines (point) (point-max)) 5)
+	   (y-or-n-p
+	    (format
+	     "Your .sig is %d lines; it should be max 4.  Really post? "
+	     (1- (count-lines (point) (point-max)))))
+	 t)))))
+
+(defun message-checksum ()
+  "Return a \"checksum\" for the current buffer."
+  (let ((sum 0))
+    (save-excursion
+      (goto-char (point-min))
+      (re-search-forward
+       (concat "^" (regexp-quote mail-header-separator) "$"))
+      (while (not (eobp))
+	(when (not (looking-at "[ \t\n]"))
+	  (setq sum (logxor (ash sum 1) (following-char))))
+	(forward-char 1)))
+    sum))
+
+(defun message-do-fcc ()
+  "Process Fcc headers in the current buffer."
+  (let ((case-fold-search t)
+	(buf (current-buffer))
+	list file)
+    (save-excursion
+      (set-buffer (get-buffer-create " *message temp*"))
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (insert-buffer-substring buf)
+      (save-restriction
+	(message-narrow-to-headers)
+	(while (setq file (message-fetch-field "fcc"))
+	  (push file list)
+	  (message-remove-header "fcc" nil t)))
+      (goto-char (point-min))
+      (re-search-forward (concat "^" (regexp-quote mail-header-separator) "$"))
+      (replace-match "" t t)
+      ;; Process FCC operations.
+      (while list
+	(setq file (pop list))
+	(if (string-match "^[ \t]*|[ \t]*\\(.*\\)[ \t]*$" file)
+	    ;; Pipe the article to the program in question.
+	    (call-process-region (point-min) (point-max) shell-file-name
+				 nil nil nil shell-command-switch
+				 (match-string 1 file))
+	  ;; Save the article.
+	  (setq file (expand-file-name file))
+	  (unless (file-exists-p (file-name-directory file))
+	    (make-directory (file-name-directory file) t))
+	  (if (and message-fcc-handler-function
+		   (not (eq message-fcc-handler-function 'rmail-output)))
+	      (funcall message-fcc-handler-function file)
+	    (if (and (file-readable-p file) (mail-file-babyl-p file))
+		(rmail-output file 1 nil t)
+	      (let ((mail-use-rfc822 t))
+		(rmail-output file 1 t t))))))
+
+      (kill-buffer (current-buffer)))))
+
+(defun message-output (filename)
+  "Append this article to Unix/babyl mail file.."
+  (if (and (file-readable-p filename)
+	   (mail-file-babyl-p filename))
+      (gnus-output-to-rmail filename t)
+    (gnus-output-to-mail filename t)))
+
+(defun message-cleanup-headers ()
+  "Do various automatic cleanups of the headers."
+  ;; Remove empty lines in the header.
+  (save-restriction
+    (message-narrow-to-headers)
+    (while (re-search-forward "^[ \t]*\n" nil t)
+      (replace-match "" t t)))
+
+  ;; Correct Newsgroups and Followup-To headers: change sequence of
+  ;; spaces to comma and eliminate spaces around commas.  Eliminate
+  ;; embedded line breaks.
+  (goto-char (point-min))
+  (while (re-search-forward "^\\(Newsgroups\\|Followup-To\\): +" nil t)
+    (save-restriction
+      (narrow-to-region
+       (point)
+       (if (re-search-forward "^[^ \t]" nil t)
+	   (match-beginning 0)
+	 (forward-line 1)
+	 (point)))
+      (goto-char (point-min))
+      (while (re-search-forward "\n[ \t]+" nil t)
+	(replace-match " " t t))	;No line breaks (too confusing)
+      (goto-char (point-min))
+      (while (re-search-forward "[ \t\n]*,[ \t\n]*\\|[ \t]+" nil t)
+	(replace-match "," t t))
+      (goto-char (point-min))
+      ;; Remove trailing commas.
+      (when (re-search-forward ",+$" nil t)
+	(replace-match "" t t)))))
+
+(defun message-make-date ()
+  "Make a valid data header."
+  (let ((now (current-time)))
+    (timezone-make-date-arpa-standard
+     (current-time-string now) (current-time-zone now))))
+
+(defun message-make-message-id ()
+  "Make a unique Message-ID."
+  (concat "<" (message-unique-id)
+	  (let ((psubject (save-excursion (message-fetch-field "subject"))))
+	    (if (and message-reply-headers
+		     (mail-header-references message-reply-headers)
+		     (mail-header-subject message-reply-headers)
+		     psubject
+		     (mail-header-subject message-reply-headers)
+		     (not (string=
+			   (message-strip-subject-re
+			    (mail-header-subject message-reply-headers))
+			   (message-strip-subject-re psubject))))
+		"_-_" ""))
+	  "@" (message-make-fqdn) ">"))
+
+(defvar message-unique-id-char nil)
+
+;; If you ever change this function, make sure the new version
+;; cannot generate IDs that the old version could.
+;; You might for example insert a "." somewhere (not next to another dot
+;; or string boundary), or modify the "fsf" string.
+(defun message-unique-id ()
+  ;; Don't use microseconds from (current-time), they may be unsupported.
+  ;; Instead we use this randomly inited counter.
+  (setq message-unique-id-char
+	(% (1+ (or message-unique-id-char (logand (random t) (1- (lsh 1 20)))))
+	   ;; (current-time) returns 16-bit ints,
+	   ;; and 2^16*25 just fits into 4 digits i base 36.
+	   (* 25 25)))
+  (let ((tm (current-time)))
+    (concat
+     (if (memq system-type '(ms-dos emx vax-vms))
+	 (let ((user (downcase (user-login-name))))
+	   (while (string-match "[^a-z0-9_]" user)
+	     (aset user (match-beginning 0) ?_))
+	   user)
+       (message-number-base36 (user-uid) -1))
+     (message-number-base36 (+ (car   tm)
+			       (lsh (% message-unique-id-char 25) 16)) 4)
+     (message-number-base36 (+ (nth 1 tm)
+			       (lsh (/ message-unique-id-char 25) 16)) 4)
+     ;; Append the newsreader name, because while the generated
+     ;; ID is unique to this newsreader, other newsreaders might
+     ;; otherwise generate the same ID via another algorithm.
+     ".fsf")))
+
+(defun message-number-base36 (num len)
+  (if (if (< len 0)
+	  (<= num 0)
+	(= len 0))
+      ""
+    (concat (message-number-base36 (/ num 36) (1- len))
+	    (char-to-string (aref "zyxwvutsrqponmlkjihgfedcba9876543210"
+				  (% num 36))))))
+
+(defun message-make-organization ()
+  "Make an Organization header."
+  (let* ((organization
+	  (or (getenv "ORGANIZATION")
+	      (when message-user-organization
+		(if (message-functionp message-user-organization)
+		    (funcall message-user-organization)
+		  message-user-organization)))))
+    (save-excursion
+      (message-set-work-buffer)
+      (cond ((stringp organization)
+	     (insert organization))
+	    ((and (eq t organization)
+		  message-user-organization-file
+		  (file-exists-p message-user-organization-file))
+	     (insert-file-contents message-user-organization-file)))
+      (goto-char (point-min))
+      (while (re-search-forward "[\t\n]+" nil t)
+	(replace-match "" t t))
+      (unless (zerop (buffer-size))
+	(buffer-string)))))
+
+(defun message-make-lines ()
+  "Count the number of lines and return numeric string."
+  (save-excursion
+    (save-restriction
+      (widen)
+      (goto-char (point-min))
+      (re-search-forward
+       (concat "^" (regexp-quote mail-header-separator) "$"))
+      (forward-line 1)
+      (int-to-string (count-lines (point) (point-max))))))
+
+(defun message-make-in-reply-to ()
+  "Return the In-Reply-To header for this message."
+  (when message-reply-headers
+    (let ((from (mail-header-from message-reply-headers))
+	  (date (mail-header-date message-reply-headers)))
+      (when from
+	(let ((stop-pos
+	       (string-match "  *at \\|  *@ \\| *(\\| *<" from)))
+	  (concat (if stop-pos (substring from 0 stop-pos) from)
+		  "'s message of "
+		  (if (or (not date) (string= date ""))
+		      "(unknown date)" date)))))))
+
+(defun message-make-distribution ()
+  "Make a Distribution header."
+  (let ((orig-distribution (message-fetch-reply-field "distribution")))
+    (cond ((message-functionp message-distribution-function)
+	   (funcall message-distribution-function))
+	  (t orig-distribution))))
+
+(defun message-make-expires ()
+  "Return an Expires header based on `message-expires'."
+  (let ((current (current-time))
+	(future (* 1.0 message-expires 60 60 24)))
+    ;; Add the future to current.
+    (setcar current (+ (car current) (round (/ future (expt 2 16)))))
+    (setcar (cdr current) (+ (nth 1 current) (% (round future) (expt 2 16))))
+    ;; Return the date in the future in UT.
+    (timezone-make-date-arpa-standard
+     (current-time-string current) (current-time-zone current) '(0 "UT"))))
+
+(defun message-make-path ()
+  "Return uucp path."
+  (let ((login-name (user-login-name)))
+    (cond ((null message-user-path)
+	   (concat (system-name) "!" login-name))
+	  ((stringp message-user-path)
+	   ;; Support GENERICPATH.  Suggested by vixie@decwrl.dec.com.
+	   (concat message-user-path "!" login-name))
+	  (t login-name))))
+
+(defun message-make-from ()
+  "Make a From header."
+  (let* ((style message-from-style)
+	 (login (message-make-address))
+	 (fullname
+	  (or (and (boundp 'user-full-name)
+		   user-full-name)
+	      (user-full-name))))
+    (when (string= fullname "&")
+      (setq fullname (user-login-name)))
+    (save-excursion
+      (message-set-work-buffer)
+      (cond
+       ((or (null style)
+	    (equal fullname ""))
+	(insert login))
+       ((or (eq style 'angles)
+	    (and (not (eq style 'parens))
+		 ;; Use angles if no quoting is needed, or if parens would
+		 ;; need quoting too.
+		 (or (not (string-match "[^- !#-'*+/-9=?A-Z^-~]" fullname))
+		     (let ((tmp (concat fullname nil)))
+		       (while (string-match "([^()]*)" tmp)
+			 (aset tmp (match-beginning 0) ?-)
+			 (aset tmp (1- (match-end 0)) ?-))
+		       (string-match "[\\()]" tmp)))))
+	(insert fullname)
+	(goto-char (point-min))
+	;; Look for a character that cannot appear unquoted
+	;; according to RFC 822.
+	(when (re-search-forward "[^- !#-'*+/-9=?A-Z^-~]" nil 1)
+	  ;; Quote fullname, escaping specials.
+	  (goto-char (point-min))
+	  (insert "\"")
+	  (while (re-search-forward "[\"\\]" nil 1)
+	    (replace-match "\\\\\\&" t))
+	  (insert "\""))
+	(insert " <" login ">"))
+       (t				; 'parens or default
+	(insert login " (")
+	(let ((fullname-start (point)))
+	  (insert fullname)
+	  (goto-char fullname-start)
+	  ;; RFC 822 says \ and nonmatching parentheses
+	  ;; must be escaped in comments.
+	  ;; Escape every instance of ()\ ...
+	  (while (re-search-forward "[()\\]" nil 1)
+	    (replace-match "\\\\\\&" t))
+	  ;; ... then undo escaping of matching parentheses,
+	  ;; including matching nested parentheses.
+	  (goto-char fullname-start)
+	  (while (re-search-forward
+		  "\\(\\=\\|[^\\]\\(\\\\\\\\\\)*\\)\\\\(\\(\\([^\\]\\|\\\\\\\\\\)*\\)\\\\)"
+		  nil 1)
+	    (replace-match "\\1(\\3)" t)
+	    (goto-char fullname-start)))
+	(insert ")")))
+      (buffer-string))))
+
+(defun message-make-sender ()
+  "Return the \"real\" user address.
+This function tries to ignore all user modifications, and
+give as trustworthy answer as possible."
+  (concat (user-login-name) "@" (system-name)))
+
+(defun message-make-address ()
+  "Make the address of the user."
+  (or (message-user-mail-address)
+      (concat (user-login-name) "@" (message-make-domain))))
+
+(defun message-user-mail-address ()
+  "Return the pertinent part of `user-mail-address'."
+  (when user-mail-address
+    (if (string-match " " user-mail-address)
+	(nth 1 (mail-extract-address-components user-mail-address))
+      user-mail-address)))
+
+(defun message-make-fqdn ()
+  "Return user's fully qualified domain name."
+  (let ((system-name (system-name))
+	(user-mail (message-user-mail-address)))
+    (cond
+     ((string-match "[^.]\\.[^.]" system-name)
+      ;; `system-name' returned the right result.
+      system-name)
+     ;; Try `mail-host-address'.
+     ((and (boundp 'mail-host-address)
+	   (stringp mail-host-address)
+	   (string-match "\\." mail-host-address))
+      mail-host-address)
+     ;; We try `user-mail-address' as a backup.
+     ((and (string-match "\\." user-mail)
+	   (string-match "@\\(.*\\)\\'" user-mail))
+      (match-string 1 user-mail))
+     ;; Default to this bogus thing.
+     (t
+      (concat system-name ".i-did-not-set--mail-host-address--so-shoot-me")))))
+
+(defun message-make-host-name ()
+  "Return the name of the host."
+  (let ((fqdn (message-make-fqdn)))
+    (string-match "^[^.]+\\." fqdn)
+    (substring fqdn 0 (1- (match-end 0)))))
+
+(defun message-make-domain ()
+  "Return the domain name."
+  (or mail-host-address
+      (message-make-fqdn)))
+
+(defun message-generate-headers (headers)
+  "Prepare article HEADERS.
+Headers already prepared in the buffer are not modified."
+  (save-restriction
+    (message-narrow-to-headers)
+    (let* ((Date (message-make-date))
+	   (Message-ID (message-make-message-id))
+	   (Organization (message-make-organization))
+	   (From (message-make-from))
+	   (Path (message-make-path))
+	   (Subject nil)
+	   (Newsgroups nil)
+	   (In-Reply-To (message-make-in-reply-to))
+	   (To nil)
+	   (Distribution (message-make-distribution))
+	   (Lines (message-make-lines))
+	   (X-Newsreader message-newsreader)
+	   (X-Mailer (and (not (message-fetch-field "X-Newsreader"))
+			  message-mailer))
+	   (Expires (message-make-expires))
+	   (case-fold-search t)
+	   header value elem)
+      ;; First we remove any old generated headers.
+      (let ((headers message-deletable-headers))
+	(while headers
+	  (goto-char (point-min))
+	  (and (re-search-forward
+		(concat "^" (symbol-name (car headers)) ": *") nil t)
+	       (get-text-property (1+ (match-beginning 0)) 'message-deletable)
+	       (message-delete-line))
+	  (pop headers)))
+      ;; Go through all the required headers and see if they are in the
+      ;; articles already.  If they are not, or are empty, they are
+      ;; inserted automatically - except for Subject, Newsgroups and
+      ;; Distribution.
+      (while headers
+	(goto-char (point-min))
+	(setq elem (pop headers))
+	(if (consp elem)
+	    (if (eq (car elem) 'optional)
+		(setq header (cdr elem))
+	      (setq header (car elem)))
+	  (setq header elem))
+	(when (or (not (re-search-forward
+			(concat "^" (downcase (symbol-name header)) ":")
+			nil t))
+		  (progn
+		    ;; The header was found.  We insert a space after the
+		    ;; colon, if there is none.
+		    (if (/= (following-char) ? ) (insert " ") (forward-char 1))
+		    ;; Find out whether the header is empty...
+		    (looking-at "[ \t]*$")))
+	  ;; So we find out what value we should insert.
+	  (setq value
+		(cond
+		 ((and (consp elem) (eq (car elem) 'optional))
+		  ;; This is an optional header.  If the cdr of this
+		  ;; is something that is nil, then we do not insert
+		  ;; this header.
+		  (setq header (cdr elem))
+		  (or (and (fboundp (cdr elem)) (funcall (cdr elem)))
+		      (and (boundp (cdr elem)) (symbol-value (cdr elem)))))
+		 ((consp elem)
+		  ;; The element is a cons.  Either the cdr is a
+		  ;; string to be inserted verbatim, or it is a
+		  ;; function, and we insert the value returned from
+		  ;; this function.
+		  (or (and (stringp (cdr elem)) (cdr elem))
+		      (and (fboundp (cdr elem)) (funcall (cdr elem)))))
+		 ((and (boundp header) (symbol-value header))
+		  ;; The element is a symbol.  We insert the value
+		  ;; of this symbol, if any.
+		  (symbol-value header))
+		 (t
+		  ;; We couldn't generate a value for this header,
+		  ;; so we just ask the user.
+		  (read-from-minibuffer
+		   (format "Empty header for %s; enter value: " header)))))
+	  ;; Finally insert the header.
+	  (when (and value
+		     (not (equal value "")))
+	    (save-excursion
+	      (if (bolp)
+		  (progn
+		    ;; This header didn't exist, so we insert it.
+		    (goto-char (point-max))
+		    (insert (symbol-name header) ": " value "\n")
+		    (forward-line -1))
+		;; The value of this header was empty, so we clear
+		;; totally and insert the new value.
+		(delete-region (point) (gnus-point-at-eol))
+		(insert value))
+	      ;; Add the deletable property to the headers that require it.
+	      (and (memq header message-deletable-headers)
+		   (progn (beginning-of-line) (looking-at "[^:]+: "))
+		   (add-text-properties
+		    (point) (match-end 0)
+		    '(message-deletable t face italic) (current-buffer)))))))
+      ;; Insert new Sender if the From is strange.
+      (let ((from (message-fetch-field "from"))
+	    (sender (message-fetch-field "sender"))
+	    (secure-sender (message-make-sender)))
+	(when (and from
+		   (not (message-check-element 'sender))
+		   (not (string=
+			 (downcase
+			  (cadr (mail-extract-address-components from)))
+			 (downcase secure-sender)))
+		   (or (null sender)
+		       (not
+			(string=
+			 (downcase
+			  (cadr (mail-extract-address-components sender)))
+			 (downcase secure-sender)))))
+	  (goto-char (point-min))
+	  ;; Rename any old Sender headers to Original-Sender.
+	  (when (re-search-forward "^\\(Original-\\)*Sender:" nil t)
+	    (beginning-of-line)
+	    (insert "Original-")
+	    (beginning-of-line))
+	  (when (or (message-news-p)
+		    (string-match "^[^@]@.+\\..+" secure-sender))
+	    (insert "Sender: " secure-sender "\n")))))))
+
+(defun message-insert-courtesy-copy ()
+  "Insert a courtesy message in mail copies of combined messages."
+  (let (newsgroups)
+    (save-excursion
+      (save-restriction
+	(message-narrow-to-headers)
+	(when (setq newsgroups (message-fetch-field "newsgroups"))
+	  (goto-char (point-max))
+	  (insert "Posted-To: " newsgroups "\n")))
+      (forward-line 1)
+      (when message-courtesy-message
+	(cond
+	 ((string-match "%s" message-courtesy-message)
+	  (insert (format message-courtesy-message newsgroups)))
+	 (t
+	  (insert message-courtesy-message)))))))
+
+;;;
+;;; Setting up a message buffer
+;;;
+
+(defun message-fill-address (header value)
+  (save-restriction
+    (narrow-to-region (point) (point))
+    (insert (capitalize (symbol-name header))
+	    ": "
+	    (if (consp value) (car value) value)
+	    "\n")
+    (narrow-to-region (point-min) (1- (point-max)))
+    (let (quoted last)
+      (goto-char (point-min))
+      (while (not (eobp))
+	(skip-chars-forward "^,\"" (point-max))
+	(if (or (= (following-char) ?,)
+		(eobp))
+	    (when (not quoted)
+	      (if (and (> (current-column) 78)
+		       last)
+		  (progn
+		    (save-excursion
+		      (goto-char last)
+		      (insert "\n\t"))
+		    (setq last (1+ (point))))
+		(setq last (1+ (point)))))
+	  (setq quoted (not quoted)))
+	(unless (eobp)
+	  (forward-char 1))))
+    (goto-char (point-max))
+    (widen)
+    (forward-line 1)))
+
+(defun message-fill-header (header value)
+  (let ((begin (point))
+	(fill-column 78)
+	(fill-prefix "\t"))
+    (insert (capitalize (symbol-name header))
+	    ": "
+	    (if (consp value) (car value) value)
+	    "\n")
+    (save-restriction
+      (narrow-to-region begin (point))
+      (fill-region-as-paragraph begin (point))
+      ;; Tapdance around looong Message-IDs.
+      (forward-line -1)
+      (when (looking-at "[ \t]*$")
+	(message-delete-line))
+      (goto-char begin)
+      (re-search-forward ":" nil t)
+      (when (looking-at "\n[ \t]+")
+	(replace-match " " t t))
+      (goto-char (point-max)))))
+
+(defun message-position-point ()
+  "Move point to where the user probably wants to find it."
+  (message-narrow-to-headers)
+  (cond
+   ((re-search-forward "^[^:]+:[ \t]*$" nil t)
+    (search-backward ":" )
+    (widen)
+    (forward-char 1)
+    (if (= (following-char) ? )
+	(forward-char 1)
+      (insert " ")))
+   (t
+    (goto-char (point-max))
+    (widen)
+    (forward-line 1)
+    (unless (looking-at "$")
+      (forward-line 2)))
+   (sit-for 0)))
+
+(defun message-buffer-name (type &optional to group)
+  "Return a new (unique) buffer name based on TYPE and TO."
+  (cond
+   ;; Check whether `message-generate-new-buffers' is a function,
+   ;; and if so, call it.
+   ((message-functionp message-generate-new-buffers)
+    (funcall message-generate-new-buffers type to group))
+   ;; Generate a new buffer name The Message Way.
+   (message-generate-new-buffers
+    (generate-new-buffer-name
+     (concat "*" type
+	     (if to
+		 (concat " to "
+			 (or (car (mail-extract-address-components to))
+			     to) "")
+	       "")
+	     (if (and group (not (string= group ""))) (concat " on " group) "")
+	     "*")))
+   ;; Use standard name.
+   (t
+    (format "*%s message*" type))))
+
+(defun message-pop-to-buffer (name)
+  "Pop to buffer NAME, and warn if it already exists and is modified."
+  (let ((buffer (get-buffer name)))
+    (if (and buffer
+	     (buffer-name buffer))
+	(progn
+	  (set-buffer (pop-to-buffer buffer))
+	  (when (and (buffer-modified-p)
+		     (not (y-or-n-p
+			   "Message already being composed; erase? ")))
+	    (error "Message being composed")))
+      (set-buffer (pop-to-buffer name))))
+  (erase-buffer)
+  (message-mode))
+
+(defun message-do-send-housekeeping ()
+  "Kill old message buffers."
+  ;; We might have sent this buffer already.  Delete it from the
+  ;; list of buffers.
+  (setq message-buffer-list (delq (current-buffer) message-buffer-list))
+  (while (and message-max-buffers
+              message-buffer-list
+	      (>= (length message-buffer-list) message-max-buffers))
+    ;; Kill the oldest buffer -- unless it has been changed.
+    (let ((buffer (pop message-buffer-list)))
+      (when (and (buffer-name buffer)
+		 (not (buffer-modified-p buffer)))
+	(kill-buffer buffer))))
+  ;; Rename the buffer.
+  (if message-send-rename-function
+      (funcall message-send-rename-function)
+    (when (string-match "\\`\\*" (buffer-name))
+      (rename-buffer
+       (concat "*sent " (substring (buffer-name) (match-end 0))) t)))
+  ;; Push the current buffer onto the list.
+  (when message-max-buffers
+    (setq message-buffer-list
+	  (nconc message-buffer-list (list (current-buffer))))))
+
+(defvar mc-modes-alist)
+(defun message-setup (headers &optional replybuffer actions)
+  (when (and (boundp 'mc-modes-alist)
+	     (not (assq 'message-mode mc-modes-alist)))
+    (push '(message-mode (encrypt . mc-encrypt-message)
+			 (sign . mc-sign-message))
+	  mc-modes-alist))
+  (when actions
+    (setq message-send-actions actions))
+  (setq message-reply-buffer replybuffer)
+  (goto-char (point-min))
+  ;; Insert all the headers.
+  (mail-header-format
+   (let ((h headers)
+	 (alist message-header-format-alist))
+     (while h
+       (unless (assq (caar h) message-header-format-alist)
+	 (push (list (caar h)) alist))
+       (pop h))
+     alist)
+   headers)
+  (delete-region (point) (progn (forward-line -1) (point)))
+  (when message-default-headers
+    (insert message-default-headers))
+  (put-text-property
+   (point)
+   (progn
+     (insert mail-header-separator "\n")
+     (1- (point)))
+   'read-only nil)
+  (forward-line -1)
+  (when (message-news-p)
+    (when message-default-news-headers
+      (insert message-default-news-headers))
+    (when message-generate-headers-first
+      (message-generate-headers
+       (delq 'Lines
+	     (delq 'Subject
+		   (copy-sequence message-required-news-headers))))))
+  (when (message-mail-p)
+    (when message-default-mail-headers
+      (insert message-default-mail-headers))
+    (when message-generate-headers-first
+      (message-generate-headers
+       (delq 'Lines
+	     (delq 'Subject
+		   (copy-sequence message-required-mail-headers))))))
+  (run-hooks 'message-signature-setup-hook)
+  (message-insert-signature)
+  (message-set-auto-save-file-name)
+  (save-restriction
+    (message-narrow-to-headers)
+    (run-hooks 'message-header-setup-hook))
+  (set-buffer-modified-p nil)
+  (run-hooks 'message-setup-hook)
+  (message-position-point)
+  (undo-boundary))
+
+(defun message-set-auto-save-file-name ()
+  "Associate the message buffer with a file in the drafts directory."
+  (when message-autosave-directory
+    (unless (file-exists-p message-autosave-directory)
+      (make-directory message-autosave-directory t))
+    (let ((name (make-temp-name
+		 (expand-file-name
+		  (concat (file-name-as-directory message-autosave-directory)
+			  "msg.")))))
+      (setq buffer-auto-save-file-name
+	    (save-excursion
+	      (prog1
+		  (progn
+		    (set-buffer (get-buffer-create " *draft tmp*"))
+		    (setq buffer-file-name name)
+		    (make-auto-save-file-name))
+		(kill-buffer (current-buffer)))))
+      (clear-visited-file-modtime))))
+
+
+
+;;;
+;;; Commands for interfacing with message
+;;;
+
+;;;###autoload
+(defun message-mail (&optional to subject
+			       other-headers continue switch-function
+			       yank-action send-actions)
+  "Start editing a mail message to be sent."
+  (interactive)
+  (let ((message-this-is-mail t))
+    (message-pop-to-buffer (message-buffer-name "mail" to))
+    (message-setup
+     (nconc
+      `((To . ,(or to "")) (Subject . ,(or subject "")))
+      (when other-headers other-headers)))))
+
+;;;###autoload
+(defun message-news (&optional newsgroups subject)
+  "Start editing a news article to be sent."
+  (interactive)
+  (let ((message-this-is-news t))
+    (message-pop-to-buffer (message-buffer-name "news" nil newsgroups))
+    (message-setup `((Newsgroups . ,(or newsgroups ""))
+		     (Subject . ,(or subject ""))))))
+
+;;;###autoload
+(defun message-reply (&optional to-address wide ignore-reply-to)
+  "Start editing a reply to the article in the current buffer."
+  (interactive)
+  (let ((cur (current-buffer))
+	from subject date reply-to to cc
+	references message-id follow-to
+	(inhibit-point-motion-hooks t)
+	mct never-mct gnus-warning)
+    (save-restriction
+      (message-narrow-to-head)
+      ;; Allow customizations to have their say.
+      (if (not wide)
+	  ;; This is a regular reply.
+	  (if (message-functionp message-reply-to-function)
+	      (setq follow-to (funcall message-reply-to-function)))
+	;; This is a followup.
+	(if (message-functionp message-wide-reply-to-function)
+	    (save-excursion
+	      (setq follow-to
+		    (funcall message-wide-reply-to-function)))))
+      ;; Find all relevant headers we need.
+      (setq from (message-fetch-field "from")
+	    date (message-fetch-field "date")
+	    subject (or (message-fetch-field "subject") "none")
+	    to (message-fetch-field "to")
+	    cc (message-fetch-field "cc")
+	    mct (message-fetch-field "mail-copies-to")
+	    reply-to (unless ignore-reply-to (message-fetch-field "reply-to"))
+	    references (message-fetch-field "references")
+	    message-id (message-fetch-field "message-id" t))
+      ;; Remove any (buggy) Re:'s that are present and make a
+      ;; proper one.
+      (when (string-match "^[ \t]*[Rr][Ee]:[ \t]*" subject)
+	(setq subject (substring subject (match-end 0))))
+      (setq subject (concat "Re: " subject))
+
+      (when (and (setq gnus-warning (message-fetch-field "gnus-warning"))
+		 (string-match "<[^>]+>" gnus-warning))
+	(setq message-id (match-string 0 gnus-warning)))
+
+      ;; Handle special values of Mail-Copies-To.
+      (when mct
+	(cond ((equal (downcase mct) "never")
+	       (setq never-mct t)
+	       (setq mct nil))
+	      ((equal (downcase mct) "always")
+	       (setq mct (or reply-to from)))))
+
+      (unless follow-to
+	(if (or (not wide)
+		to-address)
+	    (setq follow-to (list (cons 'To (or to-address reply-to from))))
+	  (let (ccalist)
+	    (save-excursion
+	      (message-set-work-buffer)
+	      (unless never-mct
+		(insert (or reply-to from "")))
+	      (insert (if to (concat (if (bolp) "" ", ") to "") ""))
+	      (insert (if mct (concat (if (bolp) "" ", ") mct) ""))
+	      (insert (if cc (concat (if (bolp) "" ", ") cc) ""))
+	      (goto-char (point-min))
+	      (while (re-search-forward "[ \t]+" nil t)
+		(replace-match " " t t))
+	      ;; Remove addresses that match `rmail-dont-reply-to-names'.
+	      (insert (prog1 (rmail-dont-reply-to (buffer-string))
+			(erase-buffer)))
+	      (goto-char (point-min))
+	      ;; Perhaps Mail-Copies-To: never removed the only address?
+	      (when (eobp)
+		(insert (or reply-to from "")))
+	      (setq ccalist
+		    (mapcar
+		     (lambda (addr)
+		       (cons (mail-strip-quoted-names addr) addr))
+		     (message-tokenize-header (buffer-string))))
+	      (let ((s ccalist))
+		(while s
+		  (setq ccalist (delq (assoc (car (pop s)) s) ccalist)))))
+	    (setq follow-to (list (cons 'To (cdr (pop ccalist)))))
+	    (when ccalist
+	      (let ((ccs (cons 'Cc (mapconcat
+				    (lambda (addr) (cdr addr)) ccalist ", "))))
+		(when (string-match "^ +" (cdr ccs))
+		  (setcdr ccs (substring (cdr ccs) (match-end 0))))
+		(push ccs follow-to))))))
+      (widen))
+
+    (message-pop-to-buffer (message-buffer-name
+			    (if wide "wide reply" "reply") from
+			    (if wide to-address nil)))
+
+    (setq message-reply-headers
+	  (vector 0 subject from date message-id references 0 0 ""))
+
+    (message-setup
+     `((Subject . ,subject)
+       ,@follow-to
+       ,@(if (or references message-id)
+	     `((References . ,(concat (or references "") (and references " ")
+				      (or message-id ""))))
+	   nil))
+     cur)))
+
+;;;###autoload
+(defun message-wide-reply (&optional to-address ignore-reply-to)
+  "Make a \"wide\" reply to the message in the current buffer."
+  (interactive)
+  (message-reply to-address t ignore-reply-to))
+
+;;;###autoload
+(defun message-followup (&optional to-newsgroups)
+  "Follow up to the message in the current buffer.
+If TO-NEWSGROUPS, use that as the new Newsgroups line."
+  (interactive)
+  (let ((cur (current-buffer))
+	from subject date reply-to mct
+	references message-id follow-to
+	(inhibit-point-motion-hooks t)
+	(message-this-is-news t)
+	followup-to distribution newsgroups gnus-warning posted-to)
+    (save-restriction
+      (narrow-to-region
+       (goto-char (point-min))
+       (if (search-forward "\n\n" nil t)
+	   (1- (point))
+	 (point-max)))
+      (when (message-functionp message-followup-to-function)
+	(setq follow-to
+	      (funcall message-followup-to-function)))
+      (setq from (message-fetch-field "from")
+	    date (message-fetch-field "date")
+	    subject (or (message-fetch-field "subject") "none")
+	    references (message-fetch-field "references")
+	    message-id (message-fetch-field "message-id" t)
+	    followup-to (message-fetch-field "followup-to")
+	    newsgroups (message-fetch-field "newsgroups")
+	    posted-to (message-fetch-field "posted-to")
+	    reply-to (message-fetch-field "reply-to")
+	    distribution (message-fetch-field "distribution")
+	    mct (message-fetch-field "mail-copies-to"))
+      (when (and (setq gnus-warning (message-fetch-field "gnus-warning"))
+		 (string-match "<[^>]+>" gnus-warning))
+	(setq message-id (match-string 0 gnus-warning)))
+      ;; Remove bogus distribution.
+      (when (and (stringp distribution)
+		 (let ((case-fold-search t))
+		   (string-match "world" distribution)))
+	(setq distribution nil))
+      ;; Remove any (buggy) Re:'s that are present and make a
+      ;; proper one.
+      (when (string-match "^[ \t]*[Rr][Ee]:[ \t]*" subject)
+	(setq subject (substring subject (match-end 0))))
+      (setq subject (concat "Re: " subject))
+      (widen))
+
+    (message-pop-to-buffer (message-buffer-name "followup" from newsgroups))
+
+    (message-setup
+     `((Subject . ,subject)
+       ,@(cond
+	  (to-newsgroups
+	   (list (cons 'Newsgroups to-newsgroups)))
+	  (follow-to follow-to)
+	  ((and followup-to message-use-followup-to)
+	   (list
+	    (cond
+	     ((equal (downcase followup-to) "poster")
+	      (if (or (eq message-use-followup-to 'use)
+		      (message-y-or-n-p "Obey Followup-To: poster? " t "\
+You should normally obey the Followup-To: header.
+
+`Followup-To: poster' sends your response via e-mail instead of news.
+
+A typical situation where `Followup-To: poster' is used is when the poster
+does not read the newsgroup, so he wouldn't see any replies sent to it."))
+		  (progn
+		    (setq message-this-is-news nil)
+		    (cons 'To (or reply-to from "")))
+		(cons 'Newsgroups newsgroups)))
+	     (t
+	      (if (or (equal followup-to newsgroups)
+		      (not (eq message-use-followup-to 'ask))
+		      (message-y-or-n-p
+		       (concat "Obey Followup-To: " followup-to "? ") t "\
+You should normally obey the Followup-To: header.
+
+	`Followup-To: " followup-to "'
+directs your response to " (if (string-match "," followup-to)
+			       "the specified newsgroups"
+			     "that newsgroup only") ".
+
+If a message is posted to several newsgroups, Followup-To is often
+used to direct the following discussion to one newsgroup only,
+because discussions that are spread over several newsgroup tend to
+be fragmented and very difficult to follow.
+
+Also, some source/announcement newsgroups are not indented for discussion;
+responses here are directed to other newsgroups."))
+		  (cons 'Newsgroups followup-to)
+		(cons 'Newsgroups newsgroups))))))
+	  (posted-to
+	   `((Newsgroups . ,posted-to)))
+	  (t
+	   `((Newsgroups . ,newsgroups))))
+       ,@(and distribution (list (cons 'Distribution distribution)))
+       ,@(if (or references message-id)
+	     `((References . ,(concat (or references "") (and references " ")
+				      (or message-id "")))))
+       ,@(when (and mct
+		    (not (equal (downcase mct) "never")))
+	   (list (cons 'Cc (if (equal (downcase mct) "always")
+			       (or reply-to from "")
+			     mct)))))
+
+     cur)
+
+    (setq message-reply-headers
+	  (vector 0 subject from date message-id references 0 0 ""))))
+
+
+;;;###autoload
+(defun message-cancel-news ()
+  "Cancel an article you posted."
+  (interactive)
+  (unless (message-news-p)
+    (error "This is not a news article; canceling is impossible"))
+  (when (yes-or-no-p "Do you really want to cancel this article? ")
+    (let (from newsgroups message-id distribution buf)
+      (save-excursion
+	;; Get header info. from original article.
+	(save-restriction
+	  (message-narrow-to-head)
+	  (setq from (message-fetch-field "from")
+		newsgroups (message-fetch-field "newsgroups")
+		message-id (message-fetch-field "message-id" t)
+		distribution (message-fetch-field "distribution")))
+	;; Make sure that this article was written by the user.
+	(unless (string-equal
+		 (downcase (cadr (mail-extract-address-components from)))
+		 (downcase (message-make-address)))
+	  (error "This article is not yours"))
+	;; Make control message.
+	(setq buf (set-buffer (get-buffer-create " *message cancel*")))
+	(buffer-disable-undo (current-buffer))
+	(erase-buffer)
+	(insert "Newsgroups: " newsgroups "\n"
+		"From: " (message-make-from) "\n"
+		"Subject: cmsg cancel " message-id "\n"
+		"Control: cancel " message-id "\n"
+		(if distribution
+		    (concat "Distribution: " distribution "\n")
+		  "")
+		mail-header-separator "\n"
+		message-cancel-message)
+	(message "Canceling your article...")
+	(let ((message-syntax-checks 'dont-check-for-anything-just-trust-me))
+	  (funcall message-send-news-function))
+	(message "Canceling your article...done")
+	(kill-buffer buf)))))
+
+;;;###autoload
+(defun message-supersede ()
+  "Start composing a message to supersede the current message.
+This is done simply by taking the old article and adding a Supersedes
+header line with the old Message-ID."
+  (interactive)
+  (let ((cur (current-buffer)))
+    ;; Check whether the user owns the article that is to be superseded.
+    (unless (string-equal
+	     (downcase (cadr (mail-extract-address-components
+			      (message-fetch-field "from"))))
+	     (downcase (message-make-address)))
+      (error "This article is not yours"))
+    ;; Get a normal message buffer.
+    (message-pop-to-buffer (message-buffer-name "supersede"))
+    (insert-buffer-substring cur)
+    (message-narrow-to-head)
+    ;; Remove unwanted headers.
+    (when message-ignored-supersedes-headers
+      (message-remove-header message-ignored-supersedes-headers t))
+    (goto-char (point-min))
+    (if (not (re-search-forward "^Message-ID: " nil t))
+	(error "No Message-ID in this article")
+      (replace-match "Supersedes: " t t))
+    (goto-char (point-max))
+    (insert mail-header-separator)
+    (widen)
+    (forward-line 1)))
+
+;;;###autoload
+(defun message-recover ()
+  "Reread contents of current buffer from its last auto-save file."
+  (interactive)
+  (let ((file-name (make-auto-save-file-name)))
+    (cond ((save-window-excursion
+	     (if (not (eq system-type 'vax-vms))
+		 (with-output-to-temp-buffer "*Directory*"
+		   (buffer-disable-undo standard-output)
+		   (let ((default-directory "/"))
+		     (call-process
+		      "ls" nil standard-output nil "-l" file-name))))
+	     (yes-or-no-p (format "Recover auto save file %s? " file-name)))
+	   (let ((buffer-read-only nil))
+	     (erase-buffer)
+	     (insert-file-contents file-name nil)))
+	  (t (error "message-recover cancelled")))))
+
+;;; Forwarding messages.
+
+(defun message-make-forward-subject ()
+  "Return a Subject header suitable for the message in the current buffer."
+  (save-excursion
+    (save-restriction
+      (current-buffer)
+      (message-narrow-to-head)
+      (concat "[" (or (message-fetch-field
+		       (if (message-news-p) "newsgroups" "from"))
+		      "(nowhere)")
+	      "] " (or (message-fetch-field "Subject") "")))))
+
+;;;###autoload
+(defun message-forward (&optional news)
+  "Forward the current message via mail.
+Optional NEWS will use news to forward instead of mail."
+  (interactive "P")
+  (let ((cur (current-buffer))
+	(subject (message-make-forward-subject))
+	art-beg)
+    (if news (message-news nil subject) (message-mail nil subject))
+    ;; Put point where we want it before inserting the forwarded
+    ;; message.
+    (if message-signature-before-forwarded-message
+	(goto-char (point-max))
+      (message-goto-body))
+    ;; Make sure we're at the start of the line.
+    (unless (eolp)
+      (insert "\n"))
+    ;; Narrow to the area we are to insert.
+    (narrow-to-region (point) (point))
+    ;; Insert the separators and the forwarded buffer.
+    (insert message-forward-start-separator)
+    (setq art-beg (point))
+    (insert-buffer-substring cur)
+    (goto-char (point-max))
+    (insert message-forward-end-separator)
+    (set-text-properties (point-min) (point-max) nil)
+    ;; Remove all unwanted headers.
+    (goto-char art-beg)
+    (narrow-to-region (point) (if (search-forward "\n\n" nil t)
+				  (1- (point))
+				(point)))
+    (goto-char (point-min))
+    (message-remove-header message-included-forward-headers t nil t)
+    (widen)
+    (message-position-point)))
+
+;;;###autoload
+(defun message-resend (address)
+  "Resend the current article to ADDRESS."
+  (interactive "sResend message to: ")
+  (message "Resending message to %s..." address)
+  (save-excursion
+    (let ((cur (current-buffer))
+	  beg)
+      ;; We first set up a normal mail buffer.
+      (set-buffer (get-buffer-create " *message resend*"))
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (message-setup `((To . ,address)))
+      ;; Insert our usual headers.
+      (message-generate-headers '(From Date To))
+      (message-narrow-to-headers)
+      ;; Rename them all to "Resent-*".
+      (while (re-search-forward "^[A-Za-z]" nil t)
+	(forward-char -1)
+	(insert "Resent-"))
+      (widen)
+      (forward-line)
+      (delete-region (point) (point-max))
+      (setq beg (point))
+      ;; Insert the message to be resent.
+      (insert-buffer-substring cur)
+      (goto-char (point-min))
+      (search-forward "\n\n")
+      (forward-char -1)
+      (save-restriction
+	(narrow-to-region beg (point))
+	(message-remove-header message-ignored-resent-headers t)
+	(goto-char (point-max)))
+      (insert mail-header-separator)
+      ;; Rename all old ("Also-")Resent headers.
+      (while (re-search-backward "^\\(Also-\\)?Resent-" beg t)
+	(beginning-of-line)
+	(insert "Also-"))
+      ;; Quote any "From " lines at the beginning.
+      (goto-char beg)
+      (when (looking-at "From ")
+	(replace-match "X-From-Line: "))
+      ;; Send it.
+      (message-send-mail)
+      (kill-buffer (current-buffer)))
+    (message "Resending message to %s...done" address)))
+
+;;;###autoload
+(defun message-bounce ()
+  "Re-mail the current message.
+This only makes sense if the current message is a bounce message than
+contains some mail you have written which has been bounced back to
+you."
+  (interactive)
+  (let ((cur (current-buffer))
+	boundary)
+    (message-pop-to-buffer (message-buffer-name "bounce"))
+    (insert-buffer-substring cur)
+    (undo-boundary)
+    (message-narrow-to-head)
+    (if (and (message-fetch-field "Mime-Version")
+	     (setq boundary (message-fetch-field "Content-Type")))
+	(if (string-match "boundary=\"\\([^\"]+\\)\"" boundary)
+	    (setq boundary (concat (match-string 1 boundary) " *\n"
+				   "Content-Type: message/rfc822"))
+	  (setq boundary nil)))
+    (widen)
+    (goto-char (point-min))
+    (search-forward "\n\n" nil t)
+    (or (and boundary
+	     (re-search-forward boundary nil t)
+	     (forward-line 2))
+	(and (re-search-forward message-unsent-separator nil t)
+	     (forward-line 1))
+	(re-search-forward "^Return-Path:.*\n" nil t))
+    ;; We remove everything before the bounced mail.
+    (delete-region
+     (point-min)
+     (if (re-search-forward "^[^ \n\t]+:" nil t)
+	 (match-beginning 0)
+       (point)))
+    (save-restriction
+      (message-narrow-to-head)
+      (message-remove-header message-ignored-bounced-headers t)
+      (goto-char (point-max))
+      (insert mail-header-separator))
+    (message-position-point)))
+
+;;;
+;;; Interactive entry points for new message buffers.
+;;;
+
+;;;###autoload
+(defun message-mail-other-window (&optional to subject)
+  "Like `message-mail' command, but display mail buffer in another window."
+  (interactive)
+  (let ((pop-up-windows t)
+	(special-display-buffer-names nil)
+	(special-display-regexps nil)
+	(same-window-buffer-names nil)
+	(same-window-regexps nil))
+    (message-pop-to-buffer (message-buffer-name "mail" to)))
+  (message-setup `((To . ,(or to "")) (Subject . ,(or subject "")))))
+
+;;;###autoload
+(defun message-mail-other-frame (&optional to subject)
+  "Like `message-mail' command, but display mail buffer in another frame."
+  (interactive)
+  (let ((pop-up-frames t)
+	(special-display-buffer-names nil)
+	(special-display-regexps nil)
+	(same-window-buffer-names nil)
+	(same-window-regexps nil))
+    (message-pop-to-buffer (message-buffer-name "mail" to)))
+  (message-setup `((To . ,(or to "")) (Subject . ,(or subject "")))))
+
+;;;###autoload
+(defun message-news-other-window (&optional newsgroups subject)
+  "Start editing a news article to be sent."
+  (interactive)
+  (let ((pop-up-windows t)
+	(special-display-buffer-names nil)
+	(special-display-regexps nil)
+	(same-window-buffer-names nil)
+	(same-window-regexps nil))
+    (message-pop-to-buffer (message-buffer-name "news" nil newsgroups)))
+  (message-setup `((Newsgroups . ,(or newsgroups ""))
+		   (Subject . ,(or subject "")))))
+
+;;;###autoload
+(defun message-news-other-frame (&optional newsgroups subject)
+  "Start editing a news article to be sent."
+  (interactive)
+  (let ((pop-up-frames t)
+	(special-display-buffer-names nil)
+	(special-display-regexps nil)
+	(same-window-buffer-names nil)
+	(same-window-regexps nil))
+    (message-pop-to-buffer (message-buffer-name "news" nil newsgroups)))
+  (message-setup `((Newsgroups . ,(or newsgroups ""))
+		   (Subject . ,(or subject "")))))
+
+;;; underline.el
+
+;; This code should be moved to underline.el (from which it is stolen).
+
+;;;###autoload
+(defun bold-region (start end)
+  "Bold all nonblank characters in the region.
+Works by overstriking characters.
+Called from program, takes two arguments START and END
+which specify the range to operate on."
+  (interactive "r")
+  (save-excursion
+    (let ((end1 (make-marker)))
+      (move-marker end1 (max start end))
+      (goto-char (min start end))
+      (while (< (point) end1)
+	(or (looking-at "[_\^@- ]")
+	    (insert (following-char) "\b"))
+	(forward-char 1)))))
+
+;;;###autoload
+(defun unbold-region (start end)
+  "Remove all boldness (overstruck characters) in the region.
+Called from program, takes two arguments START and END
+which specify the range to operate on."
+  (interactive "r")
+  (save-excursion
+    (let ((end1 (make-marker)))
+      (move-marker end1 (max start end))
+      (goto-char (min start end))
+      (while (re-search-forward "\b" end1 t)
+	(if (eq (following-char) (char-after (- (point) 2)))
+	    (delete-char -2))))))
+
+(defalias 'message-exchange-point-and-mark 'exchange-point-and-mark)
+
+;; Support for toolbar
+(when (string-match "XEmacs\\|Lucid" emacs-version)
+  (require 'messagexmas))
+
+;;; Group name completion.
+
+(defvar message-newgroups-header-regexp
+  "^\\(Newsgroups\\|Followup-To\\|Posted-To\\|Gcc\\):"
+  "Regexp that match headers that lists groups.")
+
+(defun message-tab ()
+  "Expand group names in Newsgroups and Followup-To headers.
+Do a `tab-to-tab-stop' if not in those headers."
+  (interactive)
+  (if (let ((mail-abbrev-mode-regexp message-newgroups-header-regexp))
+	(mail-abbrev-in-expansion-header-p))
+      (message-expand-group)
+    (tab-to-tab-stop)))
+
+(defvar gnus-active-hashtb)
+(defun message-expand-group ()
+  (let* ((b (save-excursion
+	      (save-restriction
+		(narrow-to-region
+		 (save-excursion
+		   (beginning-of-line)
+		   (skip-chars-forward "^:")
+		   (1+ (point)))
+		 (point))
+		(skip-chars-backward "^, \t\n") (point))))
+	 (completion-ignore-case t)
+	 (string (buffer-substring b (point)))
+	 (hashtb (and (boundp 'gnus-active-hashtb) gnus-active-hashtb))
+	 (completions (all-completions string hashtb))
+	 (cur (current-buffer))
+	 comp)
+    (delete-region b (point))
+    (cond
+     ((= (length completions) 1)
+      (if (string= (car completions) string)
+	  (progn
+	    (insert string)
+	    (message "Only matching group"))
+	(insert (car completions))))
+     ((and (setq comp (try-completion string hashtb))
+	   (not (string= comp string)))
+      (insert comp))
+     (t
+      (insert string)
+      (if (not comp)
+	  (message "No matching groups")
+	(pop-to-buffer "*Completions*")
+	(buffer-disable-undo (current-buffer))
+	(let ((buffer-read-only nil))
+	  (erase-buffer)
+	  (let ((standard-output (current-buffer)))
+	    (display-completion-list (sort completions 'string<)))
+	  (goto-char (point-min))
+	  (pop-to-buffer cur)))))))
+
+;;; Help stuff.
+
+(defun message-talkative-question (ask question show &rest text)
+  "Call FUNCTION with argument QUESTION, displaying the rest of the arguments in a temporary buffer if SHOW.
+The following arguments may contain lists of values."
+  (if (and show
+	   (setq text (message-flatten-list text)))
+      (save-window-excursion
+	(save-excursion
+	  (with-output-to-temp-buffer " *MESSAGE information message*"
+	    (set-buffer " *MESSAGE information message*")
+	    (mapcar 'princ text)
+	    (goto-char (point-min))))
+	(funcall ask question))
+    (funcall ask question)))
+
+(defun message-flatten-list (list)
+  "Return a new, flat list that contains all elements of LIST.
+
+\(message-flatten-list '(1 (2 3 (4 5 (6))) 7))
+=> (1 2 3 4 5 6 7)"
+  (cond ((consp list)
+	 (apply 'append (mapcar 'message-flatten-list list)))
+	(list
+	 (list list))))
+
+(defun message-generate-new-buffer-clone-locals (name &optional varstr)
+  "Create and return a buffer with a name based on NAME using generate-new-buffer.
+Then clone the local variables and values from the old buffer to the
+new one, cloning only the locals having a substring matching the
+regexp varstr."
+  (let ((oldlocals (buffer-local-variables)))
+    (save-excursion
+      (set-buffer (generate-new-buffer name))
+      (mapcar (lambda (dude)
+		(when (and (car dude)
+			   (or (not varstr)
+			       (string-match varstr (symbol-name (car dude)))))
+		  (ignore-errors
+		    (set (make-local-variable (car dude))
+			 (cdr dude)))))
+	      oldlocals)
+      (current-buffer))))
+
+(run-hooks 'message-load-hook)
+
+(provide 'message)
+
+;;; message.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/messcompat.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,86 @@
+;;; messcompat.el --- making message mode compatible with mail mode
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: mail, news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; This file tries to provide backward compatability with sendmail.el
+;; for Message mode.  It should be used by simply adding
+;;
+;; (require 'messcompat)
+;;
+;; to the .emacs file.  Loading it after Message mode has been
+;; loaded will have no effect.
+
+;;; Code:
+
+(require 'sendmail)
+
+(defvar message-from-style mail-from-style
+  "*Specifies how \"From\" headers look.
+
+If `nil', they contain just the return address like:
+	king@grassland.com
+If `parens', they look like:
+	king@grassland.com (Elvis Parsley)
+If `angles', they look like:
+	Elvis Parsley <king@grassland.com>
+
+Otherwise, most addresses look like `angles', but they look like
+`parens' if `angles' would need quoting and `parens' would not.")
+
+(defvar message-interactive mail-interactive
+  "Non-nil means when sending a message wait for and display errors.
+nil means let mailer mail back a message to report errors.")
+
+(defvar message-setup-hook mail-setup-hook
+  "Normal hook, run each time a new outgoing message is initialized.
+The function `message-setup' runs this hook.")
+
+(defvar message-mode-hook mail-mode-hook
+  "Hook run in message mode buffers.")
+
+(defvar message-indentation-spaces mail-indentation-spaces
+  "*Number of spaces to insert at the beginning of each cited line.
+Used by `message-yank-original' via `message-yank-cite'.")
+
+(defvar message-signature mail-signature
+  "*String to be inserted at the end of the message buffer.
+If t, the `message-signature-file' file will be inserted instead.
+If a function, the result from the function will be used instead.
+If a form, the result from the form will be used instead.")
+
+;;;###autoload
+(defvar message-signature-file mail-signature-file
+  "*File containing the text inserted at end of message. buffer.")
+
+(defvar message-default-headers mail-default-headers
+  "*A string containing header lines to be inserted in outgoing messages.
+It is inserted before you edit the message, so you can edit or delete
+these lines.")
+
+(defvar message-send-hook mail-send-hook
+  "Hook run before sending messages.")
+
+(provide 'messcompat)
+
+;;; messcompat.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnbabyl.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,650 @@
+;;; nnbabyl.el --- rmail mbox access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; 	Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; For an overview of what the interface functions do, please see the
+;; Gnus sources.
+
+;;; Code:
+
+(require 'nnheader)
+(require 'rmail)
+(require 'nnmail)
+(require 'nnoo)
+(eval-when-compile (require 'cl))
+
+(nnoo-declare nnbabyl)
+
+(defvoo nnbabyl-mbox-file (expand-file-name "~/RMAIL")
+  "The name of the rmail box file in the users home directory.")
+
+(defvoo nnbabyl-active-file (expand-file-name "~/.rmail-active")
+  "The name of the active file for the rmail box.")
+
+(defvoo nnbabyl-get-new-mail t
+  "If non-nil, nnbabyl will check the incoming mail file and split the mail.")
+
+(defvoo nnbabyl-prepare-save-mail-hook nil
+  "Hook run narrowed to an article before saving.")
+
+
+
+(defvar nnbabyl-mail-delimiter "\^_")
+
+(defconst nnbabyl-version "nnbabyl 1.0"
+  "nnbabyl version.")
+
+(defvoo nnbabyl-mbox-buffer nil)
+(defvoo nnbabyl-current-group nil)
+(defvoo nnbabyl-status-string "")
+(defvoo nnbabyl-group-alist nil)
+(defvoo nnbabyl-active-timestamp nil)
+
+(defvoo nnbabyl-previous-buffer-mode nil)
+
+(eval-and-compile
+  (autoload 'gnus-set-text-properties "gnus-ems"))
+
+
+
+;;; Interface functions
+
+(nnoo-define-basics nnbabyl)
+
+(deffoo nnbabyl-retrieve-headers (articles &optional group server fetch-old)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (let ((number (length articles))
+	  (count 0)
+	  (delim (concat "^" nnbabyl-mail-delimiter))
+	  article art-string start stop)
+      (nnbabyl-possibly-change-newsgroup group server)
+      (while (setq article (pop articles))
+	(setq art-string (nnbabyl-article-string article))
+	(set-buffer nnbabyl-mbox-buffer)
+	(end-of-line)
+	(when (or (search-forward art-string nil t)
+		  (search-backward art-string nil t))
+	  (unless (re-search-backward delim nil t)
+	    (goto-char (point-min)))
+	  (while (and (not (looking-at ".+:"))
+		      (zerop (forward-line 1))))
+	  (setq start (point))
+	  (search-forward "\n\n" nil t)
+	  (setq stop (1- (point)))
+	  (set-buffer nntp-server-buffer)
+	  (insert "221 ")
+	  (princ article (current-buffer))
+	  (insert " Article retrieved.\n")
+	  (insert-buffer-substring nnbabyl-mbox-buffer start stop)
+	  (goto-char (point-max))
+	  (insert ".\n"))
+	(and (numberp nnmail-large-newsgroup)
+	     (> number nnmail-large-newsgroup)
+	     (zerop (% (incf count) 20))
+	     (nnheader-message 5 "nnbabyl: Receiving headers... %d%%"
+			       (/ (* count 100) number))))
+
+      (and (numberp nnmail-large-newsgroup)
+	   (> number nnmail-large-newsgroup)
+	   (nnheader-message 5 "nnbabyl: Receiving headers...done"))
+
+      (set-buffer nntp-server-buffer)
+      (nnheader-fold-continuation-lines)
+      'headers)))
+
+(deffoo nnbabyl-open-server (server &optional defs)
+  (nnoo-change-server 'nnbabyl server defs)
+  (nnbabyl-create-mbox)
+  (cond
+   ((not (file-exists-p nnbabyl-mbox-file))
+    (nnbabyl-close-server)
+    (nnheader-report 'nnbabyl "No such file: %s" nnbabyl-mbox-file))
+   ((file-directory-p nnbabyl-mbox-file)
+    (nnbabyl-close-server)
+    (nnheader-report 'nnbabyl "Not a regular file: %s" nnbabyl-mbox-file))
+   (t
+    (nnheader-report 'nnbabyl "Opened server %s using mbox %s" server
+		     nnbabyl-mbox-file)
+    t)))
+
+(deffoo nnbabyl-close-server (&optional server)
+  ;; Restore buffer mode.
+  (when (and (nnbabyl-server-opened)
+	     nnbabyl-previous-buffer-mode)
+    (save-excursion
+      (set-buffer nnbabyl-mbox-buffer)
+      (narrow-to-region
+       (caar nnbabyl-previous-buffer-mode)
+       (cdar nnbabyl-previous-buffer-mode))
+      (funcall (cdr nnbabyl-previous-buffer-mode))))
+  (nnoo-close-server 'nnbabyl server)
+  (setq nnbabyl-mbox-buffer nil)
+  t)
+
+(deffoo nnbabyl-server-opened (&optional server)
+  (and (nnoo-current-server-p 'nnbabyl server)
+       nnbabyl-mbox-buffer
+       (buffer-name nnbabyl-mbox-buffer)
+       nntp-server-buffer
+       (buffer-name nntp-server-buffer)))
+
+(deffoo nnbabyl-request-article (article &optional newsgroup server buffer)
+  (nnbabyl-possibly-change-newsgroup newsgroup server)
+  (save-excursion
+    (set-buffer nnbabyl-mbox-buffer)
+    (goto-char (point-min))
+    (when (search-forward (nnbabyl-article-string article) nil t)
+      (let (start stop summary-line)
+	(unless (re-search-backward (concat "^" nnbabyl-mail-delimiter) nil t)
+	  (goto-char (point-min))
+	  (end-of-line))
+	(while (and (not (looking-at ".+:"))
+		    (zerop (forward-line 1))))
+	(setq start (point))
+	(or (when (re-search-forward
+		   (concat "^" nnbabyl-mail-delimiter) nil t)
+	      (beginning-of-line)
+	      t)
+	    (goto-char (point-max)))
+	(setq stop (point))
+	(let ((nntp-server-buffer (or buffer nntp-server-buffer)))
+	  (set-buffer nntp-server-buffer)
+	  (erase-buffer)
+	  (insert-buffer-substring nnbabyl-mbox-buffer start stop)
+	  (goto-char (point-min))
+	  ;; If there is an EOOH header, then we have to remove some
+	  ;; duplicated headers.
+	  (setq summary-line (looking-at "Summary-line:"))
+	  (when (search-forward "\n*** EOOH ***" nil t)
+	    (if summary-line
+		;; The headers to be deleted are located before the
+		;; EOOH line...
+		(delete-region (point-min) (progn (forward-line 1)
+						  (point)))
+	      ;; ...or after.
+	      (delete-region (progn (beginning-of-line) (point))
+			     (or (search-forward "\n\n" nil t)
+				 (point)))))
+	  (if (numberp article)
+	      (cons nnbabyl-current-group article)
+	    (nnbabyl-article-group-number)))))))
+
+(deffoo nnbabyl-request-group (group &optional server dont-check)
+  (let ((active (cadr (assoc group nnbabyl-group-alist))))
+    (save-excursion
+      (cond
+       ((or (null active)
+	    (null (nnbabyl-possibly-change-newsgroup group server)))
+	(nnheader-report 'nnbabyl "No such group: %s" group))
+       (dont-check
+	(nnheader-report 'nnbabyl "Selected group %s" group)
+	(nnheader-insert ""))
+       (t
+	(nnheader-report 'nnbabyl "Selected group %s" group)
+	(nnheader-insert "211 %d %d %d %s\n"
+			 (1+ (- (cdr active) (car active)))
+			 (car active) (cdr active) group))))))
+
+(deffoo nnbabyl-request-scan (&optional group server)
+  (nnbabyl-possibly-change-newsgroup group server)
+  (nnbabyl-read-mbox)
+  (nnmail-get-new-mail
+   'nnbabyl
+   (lambda ()
+     (save-excursion
+       (set-buffer nnbabyl-mbox-buffer)
+       (save-buffer)))
+   (file-name-directory nnbabyl-mbox-file)
+   group
+   (lambda ()
+     (save-excursion
+       (let ((in-buf (current-buffer)))
+	 (goto-char (point-min))
+	 (while (search-forward "\n\^_\n" nil t)
+	   (delete-char -1))
+	 (set-buffer nnbabyl-mbox-buffer)
+	 (goto-char (point-max))
+	 (search-backward "\n\^_" nil t)
+	 (goto-char (match-end 0))
+	 (insert-buffer-substring in-buf)))
+     (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file))))
+
+(deffoo nnbabyl-close-group (group &optional server)
+  t)
+
+(deffoo nnbabyl-request-create-group (group &optional server args)
+  (nnmail-activate 'nnbabyl)
+  (unless (assoc group nnbabyl-group-alist)
+    (push (list group (cons 1 0))
+				    nnbabyl-group-alist)
+    (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file))
+  t)
+
+(deffoo nnbabyl-request-list (&optional server)
+  (save-excursion
+    (nnmail-find-file nnbabyl-active-file)
+    (setq nnbabyl-group-alist (nnmail-get-active))
+    t))
+
+(deffoo nnbabyl-request-newgroups (date &optional server)
+  (nnbabyl-request-list server))
+
+(deffoo nnbabyl-request-list-newsgroups (&optional server)
+  (nnheader-report 'nnbabyl "nnbabyl: LIST NEWSGROUPS is not implemented."))
+
+(deffoo nnbabyl-request-expire-articles
+  (articles newsgroup &optional server force)
+  (nnbabyl-possibly-change-newsgroup newsgroup server)
+  (let* ((is-old t)
+	 rest)
+    (nnmail-activate 'nnbabyl)
+
+    (save-excursion
+      (set-buffer nnbabyl-mbox-buffer)
+      (gnus-set-text-properties (point-min) (point-max) nil)
+      (while (and articles is-old)
+	(goto-char (point-min))
+	(when (search-forward (nnbabyl-article-string (car articles)) nil t)
+	  (if (setq is-old
+		    (nnmail-expired-article-p
+		     newsgroup
+		     (buffer-substring
+		      (point) (progn (end-of-line) (point))) force))
+	      (progn
+		(nnheader-message 5 "Deleting article %d in %s..."
+				  (car articles) newsgroup)
+		(nnbabyl-delete-mail))
+	    (push (car articles) rest)))
+	(setq articles (cdr articles)))
+      (save-buffer)
+      ;; Find the lowest active article in this group.
+      (let ((active (nth 1 (assoc newsgroup nnbabyl-group-alist))))
+	(goto-char (point-min))
+	(while (and (not (search-forward
+			  (nnbabyl-article-string (car active)) nil t))
+		    (<= (car active) (cdr active)))
+	  (setcar active (1+ (car active)))
+	  (goto-char (point-min))))
+      (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
+      (nconc rest articles))))
+
+(deffoo nnbabyl-request-move-article
+  (article group server accept-form &optional last)
+  (let ((buf (get-buffer-create " *nnbabyl move*"))
+	result)
+    (and
+     (nnbabyl-request-article article group server)
+     (save-excursion
+       (set-buffer buf)
+       (insert-buffer-substring nntp-server-buffer)
+       (goto-char (point-min))
+       (while (re-search-forward
+	       "^X-Gnus-Newsgroup:"
+	       (save-excursion (search-forward "\n\n" nil t) (point)) t)
+	 (delete-region (progn (beginning-of-line) (point))
+			(progn (forward-line 1) (point))))
+       (setq result (eval accept-form))
+       (kill-buffer (current-buffer))
+       result)
+     (save-excursion
+       (nnbabyl-possibly-change-newsgroup group server)
+       (set-buffer nnbabyl-mbox-buffer)
+       (goto-char (point-min))
+       (if (search-forward (nnbabyl-article-string article) nil t)
+	   (nnbabyl-delete-mail))
+       (and last (save-buffer))))
+    result))
+
+(deffoo nnbabyl-request-accept-article (group &optional server last)
+  (nnbabyl-possibly-change-newsgroup group server)
+  (nnmail-check-syntax)
+  (let ((buf (current-buffer))
+	result beg)
+    (and
+     (nnmail-activate 'nnbabyl)
+     (save-excursion
+       (goto-char (point-min))
+       (search-forward "\n\n" nil t)
+       (forward-line -1)
+       (save-excursion
+	 (while (re-search-backward "^X-Gnus-Newsgroup: " beg t)
+	   (delete-region (point) (progn (forward-line 1) (point)))))
+       (when nnmail-cache-accepted-message-ids
+	 (nnmail-cache-insert (nnmail-fetch-field "message-id")))
+       (setq result
+	     (if (stringp group)
+		 (list (cons group (nnbabyl-active-number group)))
+	       (nnmail-article-group 'nnbabyl-active-number)))
+       (if (and (null result)
+		(yes-or-no-p "Moved to `junk' group; delete article? "))
+	   (setq result 'junk)
+	 (setq result (car (nnbabyl-save-mail result))))
+       (set-buffer nnbabyl-mbox-buffer)
+       (goto-char (point-max))
+       (search-backward "\n\^_")
+       (goto-char (match-end 0))
+       (insert-buffer-substring buf)
+       (when last
+	 (when nnmail-cache-accepted-message-ids
+	   (nnmail-cache-insert (nnmail-fetch-field "message-id")))
+	 (save-buffer)
+	 (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file))
+       result))))
+
+(deffoo nnbabyl-request-replace-article (article group buffer)
+  (nnbabyl-possibly-change-newsgroup group)
+  (save-excursion
+    (set-buffer nnbabyl-mbox-buffer)
+    (goto-char (point-min))
+    (if (not (search-forward (nnbabyl-article-string article) nil t))
+	nil
+      (nnbabyl-delete-mail t t)
+      (insert-buffer-substring buffer)
+      (save-buffer)
+      t)))
+
+(deffoo nnbabyl-request-delete-group (group &optional force server)
+  (nnbabyl-possibly-change-newsgroup group server)
+  ;; Delete all articles in GROUP.
+  (if (not force)
+      ()				; Don't delete the articles.
+    (save-excursion
+      (set-buffer nnbabyl-mbox-buffer)
+      (goto-char (point-min))
+      ;; Delete all articles in this group.
+      (let ((ident (concat "\nX-Gnus-Newsgroup: " nnbabyl-current-group ":"))
+	    found)
+	(while (search-forward ident nil t)
+	  (setq found t)
+	  (nnbabyl-delete-mail))
+	(when found
+	  (save-buffer)))))
+  ;; Remove the group from all structures.
+  (setq nnbabyl-group-alist
+	(delq (assoc group nnbabyl-group-alist) nnbabyl-group-alist)
+	nnbabyl-current-group nil)
+  ;; Save the active file.
+  (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
+  t)
+
+(deffoo nnbabyl-request-rename-group (group new-name &optional server)
+  (nnbabyl-possibly-change-newsgroup group server)
+  (save-excursion
+    (set-buffer nnbabyl-mbox-buffer)
+    (goto-char (point-min))
+    (let ((ident (concat "\nX-Gnus-Newsgroup: " nnbabyl-current-group ":"))
+	  (new-ident (concat "\nX-Gnus-Newsgroup: " new-name ":"))
+	  found)
+      (while (search-forward ident nil t)
+	(replace-match new-ident t t)
+	(setq found t))
+      (when found
+	(save-buffer))))
+  (let ((entry (assoc group nnbabyl-group-alist)))
+    (and entry (setcar entry new-name))
+    (setq nnbabyl-current-group nil)
+    ;; Save the new group alist.
+    (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
+    t))
+
+
+;;; Internal functions.
+
+;; If FORCE, delete article no matter how many X-Gnus-Newsgroup
+;; headers there are.  If LEAVE-DELIM, don't delete the Unix mbox
+;; delimiter line.
+(defun nnbabyl-delete-mail (&optional force leave-delim)
+  ;; Delete the current X-Gnus-Newsgroup line.
+  (unless force
+    (delete-region
+     (progn (beginning-of-line) (point))
+     (progn (forward-line 1) (point))))
+  ;; Beginning of the article.
+  (save-excursion
+    (save-restriction
+      (widen)
+      (narrow-to-region
+       (save-excursion
+	(unless (re-search-backward (concat "^" nnbabyl-mail-delimiter) nil t)
+	  (goto-char (point-min))
+	  (end-of-line))
+	 (if leave-delim (progn (forward-line 1) (point))
+	   (match-beginning 0)))
+       (progn
+	 (forward-line 1)
+	 (or (and (re-search-forward (concat "^" nnbabyl-mail-delimiter)
+				     nil t)
+		  (match-beginning 0))
+	     (point-max))))
+      (goto-char (point-min))
+      ;; Only delete the article if no other groups owns it as well.
+      (when (or force (not (re-search-forward "^X-Gnus-Newsgroup: " nil t)))
+	(delete-region (point-min) (point-max))))))
+
+(defun nnbabyl-possibly-change-newsgroup (newsgroup &optional server)
+  (when (and server
+	     (not (nnbabyl-server-opened server)))
+    (nnbabyl-open-server server))
+  (when (or (not nnbabyl-mbox-buffer)
+	    (not (buffer-name nnbabyl-mbox-buffer)))
+    (save-excursion (nnbabyl-read-mbox)))
+  (unless nnbabyl-group-alist
+    (nnmail-activate 'nnbabyl))
+  (if newsgroup
+      (if (assoc newsgroup nnbabyl-group-alist)
+	  (setq nnbabyl-current-group newsgroup)
+	(nnheader-report 'nnbabyl "No such group in file"))
+    t))
+
+(defun nnbabyl-article-string (article)
+  (if (numberp article)
+      (concat "\nX-Gnus-Newsgroup: " nnbabyl-current-group ":"
+	      (int-to-string article) " ")
+    (concat "\nMessage-ID: " article)))
+
+(defun nnbabyl-article-group-number ()
+  (save-excursion
+    (goto-char (point-min))
+    (when (re-search-forward "^X-Gnus-Newsgroup: +\\([^:]+\\):\\([0-9]+\\) "
+			     nil t)
+      (cons (buffer-substring (match-beginning 1) (match-end 1))
+	    (string-to-int
+	     (buffer-substring (match-beginning 2) (match-end 2)))))))
+
+(defun nnbabyl-insert-lines ()
+  "Insert how many lines and chars there are in the body of the mail."
+  (let (lines chars)
+    (save-excursion
+      (goto-char (point-min))
+      (when (search-forward "\n\n" nil t)
+	;; There may be an EOOH line here...
+	(when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
+	  (search-forward "\n\n" nil t))
+	(setq chars (- (point-max) (point))
+	      lines (max (- (count-lines (point) (point-max)) 1) 0))
+	;; Move back to the end of the headers.
+	(goto-char (point-min))
+	(search-forward "\n\n" nil t)
+	(forward-char -1)
+	(save-excursion
+	  (when (re-search-backward "^Lines: " nil t)
+	    (delete-region (point) (progn (forward-line 1) (point)))))
+	(insert (format "Lines: %d\n" lines))
+	chars))))
+
+(defun nnbabyl-save-mail (group-art)
+  ;; Called narrowed to an article.
+  (nnbabyl-insert-lines)
+  (nnmail-insert-xref group-art)
+  (nnbabyl-insert-newsgroup-line group-art)
+  (run-hooks 'nnbabyl-prepare-save-mail-hook)
+  group-art)
+
+(defun nnbabyl-insert-newsgroup-line (group-art)
+  (save-excursion
+    (goto-char (point-min))
+    (while (looking-at "From ")
+      (replace-match "Mail-from: From " t t)
+      (forward-line 1))
+    ;; If there is a C-l at the beginning of the narrowed region, this
+    ;; isn't really a "save", but rather a "scan".
+    (goto-char (point-min))
+    (unless (looking-at "\^L")
+      (save-excursion
+	(insert "\^L\n0, unseen,,\n*** EOOH ***\n")
+	(goto-char (point-max))
+	(insert "\^_\n")))
+    (when (search-forward "\n\n" nil t)
+      (forward-char -1)
+      (while group-art
+	(insert (format "X-Gnus-Newsgroup: %s:%d   %s\n"
+			(caar group-art) (cdar group-art)
+			(current-time-string)))
+	(setq group-art (cdr group-art))))
+    t))
+
+(defun nnbabyl-active-number (group)
+  ;; Find the next article number in GROUP.
+  (let ((active (cadr (assoc group nnbabyl-group-alist))))
+    (if active
+	(setcdr active (1+ (cdr active)))
+      ;; This group is new, so we create a new entry for it.
+      ;; This might be a bit naughty... creating groups on the drop of
+      ;; a hat, but I don't know...
+      (push (list group (setq active (cons 1 1)))
+	    nnbabyl-group-alist))
+    (cdr active)))
+
+(defun nnbabyl-create-mbox ()
+  (unless (file-exists-p nnbabyl-mbox-file)
+    ;; Create a new, empty RMAIL mbox file.
+    (save-excursion
+      (set-buffer (setq nnbabyl-mbox-buffer
+			(create-file-buffer nnbabyl-mbox-file)))
+      (setq buffer-file-name nnbabyl-mbox-file)
+      (insert "BABYL OPTIONS:\n\n\^_")
+      (nnmail-write-region
+       (point-min) (point-max) nnbabyl-mbox-file t 'nomesg))))
+
+(defun nnbabyl-read-mbox ()
+  (nnmail-activate 'nnbabyl)
+  (nnbabyl-create-mbox)
+
+  (unless (and nnbabyl-mbox-buffer
+	   (buffer-name nnbabyl-mbox-buffer)
+	   (save-excursion
+	     (set-buffer nnbabyl-mbox-buffer)
+	     (= (buffer-size) (nnheader-file-size nnbabyl-mbox-file))))
+    ;; This buffer has changed since we read it last.  Possibly.
+    (save-excursion
+      (let ((delim (concat "^" nnbabyl-mail-delimiter))
+	    (alist nnbabyl-group-alist)
+	    start end number)
+	(set-buffer (setq nnbabyl-mbox-buffer
+			  (nnheader-find-file-noselect
+			   nnbabyl-mbox-file nil 'raw)))
+	;; Save previous buffer mode.
+	(setq nnbabyl-previous-buffer-mode
+	      (cons (cons (point-min) (point-max))
+		    major-mode))
+
+	(buffer-disable-undo (current-buffer))
+	(widen)
+	(setq buffer-read-only nil)
+	(fundamental-mode)
+
+	;; Go through the group alist and compare against
+	;; the rmail file.
+	(while alist
+	  (goto-char (point-max))
+	  (when (and (re-search-backward
+		      (format "^X-Gnus-Newsgroup: %s:\\([0-9]+\\) "
+			      (caar alist))
+		      nil t)
+		     (> (setq number
+			      (string-to-number
+			       (buffer-substring
+				(match-beginning 1) (match-end 1))))
+			(cdadar alist)))
+	    (setcdr (cadar alist) number))
+	  (setq alist (cdr alist)))
+
+	;; We go through the mbox and make sure that each and
+	;; every mail belongs to some group or other.
+	(goto-char (point-min))
+	(if (looking-at "\^L")
+	    (setq start (point))
+	  (re-search-forward delim nil t)
+	  (setq start (match-end 0)))
+	(while (re-search-forward delim nil t)
+	  (setq end (match-end 0))
+	  (unless (search-backward "\nX-Gnus-Newsgroup: " start t)
+	    (goto-char end)
+	    (save-excursion
+	      (save-restriction
+		(narrow-to-region (goto-char start) end)
+		(nnbabyl-save-mail
+		 (nnmail-article-group 'nnbabyl-active-number))
+		(setq end (point-max)))))
+	  (goto-char (setq start end)))
+	(when (buffer-modified-p (current-buffer))
+	  (save-buffer))
+	(nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)))))
+
+(defun nnbabyl-remove-incoming-delims ()
+  (goto-char (point-min))
+  (while (search-forward "\^_" nil t)
+    (replace-match "?" t t)))
+
+(defun nnbabyl-check-mbox ()
+  "Go through the nnbabyl mbox and make sure that no article numbers are reused."
+  (interactive)
+  (let ((idents (make-vector 1000 0))
+	id)
+    (save-excursion
+      (when (or (not nnbabyl-mbox-buffer)
+		(not (buffer-name nnbabyl-mbox-buffer)))
+	(nnbabyl-read-mbox))
+      (set-buffer nnbabyl-mbox-buffer)
+      (goto-char (point-min))
+      (while (re-search-forward "^X-Gnus-Newsgroup: \\([^ ]+\\) "  nil t)
+	(if (intern-soft (setq id (match-string 1)) idents)
+	    (progn
+	      (delete-region (progn (beginning-of-line) (point))
+			     (progn (forward-line 1) (point)))
+	      (nnheader-message 7 "Moving %s..." id)
+	      (nnbabyl-save-mail
+	       (nnmail-article-group 'nnbabyl-active-number)))
+	  (intern id idents)))
+      (when (buffer-modified-p (current-buffer))
+	(save-buffer))
+      (nnmail-save-active nnbabyl-group-alist nnbabyl-active-file)
+      (message ""))))
+
+(provide 'nnbabyl)
+
+;;; nnbabyl.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nndir.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,99 @@
+;;; nndir.el --- single directory newsgroup access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'nnmh)
+(require 'nnml)
+(require 'nnoo)
+(eval-when-compile (require 'cl))
+
+(nnoo-declare nndir
+  nnml nnmh)
+
+(defvoo nndir-directory nil
+  "Where nndir will look for groups."
+  nnml-current-directory nnmh-current-directory)
+
+(defvoo nndir-nov-is-evil nil
+  "*Non-nil means that nndir will never retrieve NOV headers."
+  nnml-nov-is-evil)
+
+
+
+(defvoo nndir-current-group "" nil nnml-current-group nnmh-current-group)
+(defvoo nndir-top-directory nil nil nnml-directory nnmh-directory)
+(defvoo nndir-get-new-mail nil nil nnml-get-new-mail nnmh-get-new-mail)
+
+(defvoo nndir-status-string "" nil nnmh-status-string)
+(defconst nndir-version "nndir 1.0")
+
+
+
+;;; Interface functions.
+
+(nnoo-define-basics nndir)
+
+(deffoo nndir-open-server (server &optional defs)
+  (setq nndir-directory
+	(or (cadr (assq 'nndir-directory defs))
+	    server))
+  (unless (assq 'nndir-directory defs)
+    (push `(nndir-directory ,server) defs))
+  (push `(nndir-current-group
+	  ,(file-name-nondirectory (directory-file-name nndir-directory)))
+	defs)
+  (push `(nndir-top-directory
+	  ,(file-name-directory (directory-file-name nndir-directory)))
+	defs)
+  (nnoo-change-server 'nndir server defs)
+  (let (err)
+    (cond
+     ((not (condition-case arg
+	       (file-exists-p nndir-directory)
+	     (ftp-error (setq err (format "%s" arg)))))
+      (nndir-close-server)
+      (nnheader-report
+       'nndir (or err "No such file or directory: %s" nndir-directory)))
+     ((not (file-directory-p (file-truename nndir-directory)))
+      (nndir-close-server)
+      (nnheader-report 'nndir "Not a directory: %s" nndir-directory))
+     (t
+      (nnheader-report 'nndir "Opened server %s using directory %s"
+		       server nndir-directory)
+      t))))
+
+(nnoo-map-functions nndir
+  (nnml-retrieve-headers 0 nndir-current-group 0 0)
+  (nnmh-request-article 0 nndir-current-group 0 0)
+  (nnmh-request-group nndir-current-group 0 0)
+  (nnml-close-group nndir-current-group 0)
+  (nnmh-request-list (nnoo-current-server 'nndir) nndir-directory)
+  (nnmh-request-newsgroups (nnoo-current-server 'nndir) nndir-directory))
+
+(provide 'nndir)
+
+;;; nndir.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nndoc.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,628 @@
+;;; nndoc.el --- single file access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; 	Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'message)
+(require 'nnmail)
+(require 'nnoo)
+(eval-when-compile (require 'cl))
+
+(nnoo-declare nndoc)
+
+(defvoo nndoc-article-type 'guess
+  "*Type of the file.
+One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward',
+`rfc934', `rfc822-forward', `mime-digest', `standard-digest',
+`slack-digest', `clari-briefs' or `guess'.")
+
+(defvoo nndoc-post-type 'mail
+  "*Whether the nndoc group is `mail' or `post'.")
+
+(defvar nndoc-type-alist
+  `((mmdf
+     (article-begin .  "^\^A\^A\^A\^A\n")
+     (body-end .  "^\^A\^A\^A\^A\n"))
+    (news
+     (article-begin . "^Path:"))
+    (rnews
+     (article-begin . "^#! *rnews +\\([0-9]+\\) *\n")
+     (body-end-function . nndoc-rnews-body-end))
+    (mbox
+     (article-begin-function . nndoc-mbox-article-begin)
+     (body-end-function . nndoc-mbox-body-end))
+    (babyl
+     (article-begin . "\^_\^L *\n")
+     (body-end . "\^_")
+     (body-begin-function . nndoc-babyl-body-begin)
+     (head-begin-function . nndoc-babyl-head-begin))
+    (forward
+     (article-begin . "^-+ Start of forwarded message -+\n+")
+     (body-end . "^-+ End of forwarded message -+$")
+     (prepare-body-function . nndoc-unquote-dashes))
+    (rfc934
+     (article-begin . "^--.*\n+")
+     (body-end . "^--.*$")
+     (prepare-body-function . nndoc-unquote-dashes))
+    (clari-briefs
+     (article-begin . "^ \\*")
+     (body-end . "^\t------*[ \t]^*\n^ \\*")
+     (body-begin . "^\t")
+     (head-end . "^\t")
+     (generate-head-function . nndoc-generate-clari-briefs-head)
+     (article-transform-function . nndoc-transform-clari-briefs))
+    (mime-digest
+     (article-begin . "")
+     (head-end . "^ ?$")
+     (body-end . "")
+     (file-end . "")
+     (subtype digest guess))
+    (standard-digest
+     (first-article . ,(concat "^" (make-string 70 ?-) "\n\n+"))
+     (article-begin . ,(concat "^\n" (make-string 30 ?-) "\n\n+"))
+     (prepare-body-function . nndoc-unquote-dashes)
+     (body-end-function . nndoc-digest-body-end)
+     (head-end . "^ ?$")
+     (body-begin . "^ ?\n")
+     (file-end . "^End of .*digest.*[0-9].*\n\\*\\*\\|^End of.*Digest *$")
+     (subtype digest guess))
+    (slack-digest
+     (article-begin . "^------------------------------*[\n \t]+")
+     (head-end . "^ ?$")
+     (body-end-function . nndoc-digest-body-end)
+     (body-begin . "^ ?$")
+     (file-end . "^End of")
+     (prepare-body-function . nndoc-unquote-dashes)
+     (subtype digest guess))
+    (lanl-gov-announce
+     (article-begin . "^\\\\\\\\\n")
+     (head-begin . "^Paper.*:")
+     (head-end   . "\\(^\\\\\\\\.*\n\\|-----------------\\)")
+     (body-begin . "")
+     (body-end   . "-------------------------------------------------")
+     (file-end   . "^Title: Recent Seminal")
+     (generate-head-function . nndoc-generate-lanl-gov-head)
+     (article-transform-function . nndoc-transform-lanl-gov-announce)
+     (subtype preprints guess))
+    (rfc822-forward
+     (article-begin . "^\n")
+     (body-end-function . nndoc-rfc822-forward-body-end-function))
+    (guess
+     (guess . t)
+     (subtype nil))
+    (digest
+     (guess . t)
+     (subtype nil))
+    (preprints
+     (guess . t)
+     (subtype nil))))
+
+
+
+(defvoo nndoc-file-begin nil)
+(defvoo nndoc-first-article nil)
+(defvoo nndoc-article-end nil)
+(defvoo nndoc-article-begin nil)
+(defvoo nndoc-head-begin nil)
+(defvoo nndoc-head-end nil)
+(defvoo nndoc-file-end nil)
+(defvoo nndoc-body-begin nil)
+(defvoo nndoc-body-end-function nil)
+(defvoo nndoc-body-begin-function nil)
+(defvoo nndoc-head-begin-function nil)
+(defvoo nndoc-body-end nil)
+(defvoo nndoc-dissection-alist nil)
+(defvoo nndoc-prepare-body-function nil)
+(defvoo nndoc-generate-head-function nil)
+(defvoo nndoc-article-transform-function nil)
+(defvoo nndoc-article-begin-function nil)
+
+(defvoo nndoc-status-string "")
+(defvoo nndoc-group-alist nil)
+(defvoo nndoc-current-buffer nil
+  "Current nndoc news buffer.")
+(defvoo nndoc-address nil)
+
+(defconst nndoc-version "nndoc 1.0"
+  "nndoc version.")
+
+
+
+;;; Interface functions
+
+(nnoo-define-basics nndoc)
+
+(deffoo nndoc-retrieve-headers (articles &optional newsgroup server fetch-old)
+  (when (nndoc-possibly-change-buffer newsgroup server)
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)
+      (let (article entry)
+	(if (stringp (car articles))
+	    'headers
+	  (while articles
+	    (when (setq entry (cdr (assq (setq article (pop articles))
+					 nndoc-dissection-alist)))
+	      (insert (format "221 %d Article retrieved.\n" article))
+	      (if nndoc-generate-head-function
+		  (funcall nndoc-generate-head-function article)
+		(insert-buffer-substring
+		 nndoc-current-buffer (car entry) (nth 1 entry)))
+	      (goto-char (point-max))
+	      (unless (= (char-after (1- (point))) ?\n)
+		(insert "\n"))
+	      (insert (format "Lines: %d\n" (nth 4 entry)))
+	      (insert ".\n")))
+
+	  (nnheader-fold-continuation-lines)
+	  'headers)))))
+
+(deffoo nndoc-request-article (article &optional newsgroup server buffer)
+  (nndoc-possibly-change-buffer newsgroup server)
+  (save-excursion
+    (let ((buffer (or buffer nntp-server-buffer))
+	  (entry (cdr (assq article nndoc-dissection-alist)))
+	  beg)
+      (set-buffer buffer)
+      (erase-buffer)
+      (when entry
+	(if (stringp article)
+	    nil
+	  (insert-buffer-substring
+	   nndoc-current-buffer (car entry) (nth 1 entry))
+	  (insert "\n")
+	  (setq beg (point))
+	  (insert-buffer-substring
+	   nndoc-current-buffer (nth 2 entry) (nth 3 entry))
+	  (goto-char beg)
+	  (when nndoc-prepare-body-function
+	    (funcall nndoc-prepare-body-function))
+	  (when nndoc-article-transform-function
+	    (funcall nndoc-article-transform-function article))
+	  t)))))
+
+(deffoo nndoc-request-group (group &optional server dont-check)
+  "Select news GROUP."
+  (let (number)
+    (cond
+     ((not (nndoc-possibly-change-buffer group server))
+      (nnheader-report 'nndoc "No such file or buffer: %s"
+		       nndoc-address))
+     (dont-check
+      (nnheader-report 'nndoc "Selected group %s" group)
+      t)
+     ((zerop (setq number (length nndoc-dissection-alist)))
+      (nndoc-close-group group)
+      (nnheader-report 'nndoc "No articles in group %s" group))
+     (t
+      (nnheader-insert "211 %d %d %d %s\n" number 1 number group)))))
+
+(deffoo nndoc-request-type (group &optional article)
+  (cond ((not article) 'unknown)
+        (nndoc-post-type nndoc-post-type)
+        (t 'unknown)))
+
+(deffoo nndoc-close-group (group &optional server)
+  (nndoc-possibly-change-buffer group server)
+  (and nndoc-current-buffer
+       (buffer-name nndoc-current-buffer)
+       (kill-buffer nndoc-current-buffer))
+  (setq nndoc-group-alist (delq (assoc group nndoc-group-alist)
+				nndoc-group-alist))
+  (setq nndoc-current-buffer nil)
+  (nnoo-close-server 'nndoc server)
+  (setq nndoc-dissection-alist nil)
+  t)
+
+(deffoo nndoc-request-list (&optional server)
+  nil)
+
+(deffoo nndoc-request-newgroups (date &optional server)
+  nil)
+
+(deffoo nndoc-request-list-newsgroups (&optional server)
+  nil)
+
+
+;;; Internal functions.
+
+(defun nndoc-possibly-change-buffer (group source)
+  (let (buf)
+    (cond
+     ;; The current buffer is this group's buffer.
+     ((and nndoc-current-buffer
+	   (buffer-name nndoc-current-buffer)
+	   (eq nndoc-current-buffer
+	       (setq buf (cdr (assoc group nndoc-group-alist))))))
+     ;; We change buffers by taking an old from the group alist.
+     ;; `source' is either a string (a file name) or a buffer object.
+     (buf
+      (setq nndoc-current-buffer buf))
+     ;; It's a totally new group.
+     ((or (and (bufferp nndoc-address)
+	       (buffer-name nndoc-address))
+	  (and (stringp nndoc-address)
+	       (file-exists-p nndoc-address)
+	       (not (file-directory-p nndoc-address))))
+      (push (cons group (setq nndoc-current-buffer
+			      (get-buffer-create
+			       (concat " *nndoc " group "*"))))
+	    nndoc-group-alist)
+      (setq nndoc-dissection-alist nil)
+      (save-excursion
+	(set-buffer nndoc-current-buffer)
+	(buffer-disable-undo (current-buffer))
+	(erase-buffer)
+	(if (stringp nndoc-address)
+	    (nnheader-insert-file-contents nndoc-address)
+	  (insert-buffer-substring nndoc-address)))))
+    ;; Initialize the nndoc structures according to this new document.
+    (when (and nndoc-current-buffer
+	       (not nndoc-dissection-alist))
+      (save-excursion
+	(set-buffer nndoc-current-buffer)
+	(nndoc-set-delims)
+	(nndoc-dissect-buffer)))
+    (unless nndoc-current-buffer
+      (nndoc-close-server))
+    ;; Return whether we managed to select a file.
+    nndoc-current-buffer))
+
+;;;
+;;; Deciding what document type we have
+;;;
+
+(defun nndoc-set-delims ()
+  "Set the nndoc delimiter variables according to the type of the document."
+  (let ((vars '(nndoc-file-begin
+		nndoc-first-article
+		nndoc-article-end nndoc-head-begin nndoc-head-end
+		nndoc-file-end nndoc-article-begin
+		nndoc-body-begin nndoc-body-end-function nndoc-body-end
+		nndoc-prepare-body-function nndoc-article-transform-function
+		nndoc-generate-head-function nndoc-body-begin-function
+		nndoc-head-begin-function)))
+    (while vars
+      (set (pop vars) nil)))
+  (let (defs)
+    ;; Guess away until we find the real file type.
+    (while (assq 'guess (setq defs (cdr (assq nndoc-article-type
+					      nndoc-type-alist))))
+      (setq nndoc-article-type (nndoc-guess-type nndoc-article-type)))
+    ;; Set the nndoc variables.
+    (while defs
+      (set (intern (format "nndoc-%s" (caar defs)))
+	   (cdr (pop defs))))))
+
+(defun nndoc-guess-type (subtype)
+  (let ((alist nndoc-type-alist)
+	results result entry)
+    (while (and (not result)
+		(setq entry (pop alist)))
+      (when (memq subtype (or (cdr (assq 'subtype entry)) '(guess)))
+	(goto-char (point-min))
+	(when (numberp (setq result (funcall (intern
+					      (format "nndoc-%s-type-p"
+						      (car entry))))))
+	  (push (cons result entry) results)
+	  (setq result nil))))
+    (unless (or result results)
+      (error "Document is not of any recognized type"))
+    (if result
+	(car entry)
+      (cadar (sort results (lambda (r1 r2) (< (car r1) (car r2))))))))
+
+;;;
+;;; Built-in type predicates and functions
+;;;
+
+(defun nndoc-mbox-type-p ()
+  (when (looking-at message-unix-mail-delimiter)
+    t))
+
+(defun nndoc-mbox-article-begin ()
+  (when (re-search-forward (concat "^" message-unix-mail-delimiter) nil t)
+    (goto-char (match-beginning 0))))
+
+(defun nndoc-mbox-body-end ()
+  (let ((beg (point))
+	len end)
+    (when
+	(save-excursion
+	  (and (re-search-backward
+		(concat "^" message-unix-mail-delimiter) nil t)
+	       (setq end (point))
+	       (search-forward "\n\n" beg t)
+	       (re-search-backward
+		"^Content-Length:[ \t]*\\([0-9]+\\) *$" end t)
+	       (setq len (string-to-int (match-string 1)))
+	       (search-forward "\n\n" beg t)
+	       (unless (= (setq len (+ (point) len)) (point-max))
+		 (and (< len (point-max))
+		      (goto-char len)
+		      (looking-at message-unix-mail-delimiter)))))
+      (goto-char len))))
+
+(defun nndoc-mmdf-type-p ()
+  (when (looking-at "\^A\^A\^A\^A$")
+    t))
+
+(defun nndoc-news-type-p ()
+  (when (looking-at "^Path:.*\n")
+    t))
+
+(defun nndoc-rnews-type-p ()
+  (when (looking-at "#! *rnews")
+    t))
+
+(defun nndoc-rnews-body-end ()
+  (and (re-search-backward nndoc-article-begin nil t)
+       (forward-line 1)
+       (goto-char (+ (point) (string-to-int (match-string 1))))))
+
+(defun nndoc-babyl-type-p ()
+  (when (re-search-forward "\^_\^L *\n" nil t)
+    t))
+
+(defun nndoc-babyl-body-begin ()
+  (re-search-forward "^\n" nil t)
+  (when (looking-at "\*\*\* EOOH \*\*\*")
+    (let ((next (or (save-excursion
+		      (re-search-forward nndoc-article-begin nil t))
+		    (point-max))))
+      (unless (re-search-forward "^\n" next t)
+	(goto-char next)
+	(forward-line -1)
+	(insert "\n")
+	(forward-line -1)))))
+
+(defun nndoc-babyl-head-begin ()
+  (when (re-search-forward "^[0-9].*\n" nil t)
+    (when (looking-at "\*\*\* EOOH \*\*\*")
+      (forward-line 1))
+    t))
+
+(defun nndoc-forward-type-p ()
+  (when (and (re-search-forward "^-+ Start of forwarded message -+\n+" nil t)
+	     (not (re-search-forward "^Subject:.*digest" nil t))
+	     (not (re-search-backward "^From:" nil t 2))
+	     (not (re-search-forward "^From:" nil t 2)))
+    t))
+
+(defun nndoc-rfc934-type-p ()
+  (when (and (re-search-forward "^-+ Start of forwarded.*\n+" nil t)
+	     (not (re-search-forward "^Subject:.*digest" nil t))
+	     (not (re-search-backward "^From:" nil t 2))
+	     (not (re-search-forward "^From:" nil t 2)))
+    t))
+
+(defun nndoc-rfc822-forward-type-p ()
+  (save-restriction
+    (message-narrow-to-head)
+    (when (re-search-forward "^Content-Type: *message/rfc822" nil t)
+      t)))
+
+(defun nndoc-rfc822-forward-body-end-function ()
+  (goto-char (point-max)))
+
+(defun nndoc-clari-briefs-type-p ()
+  (when (let ((case-fold-search nil))
+	  (re-search-forward "^\t[^a-z]+ ([^a-z]+) --" nil t))
+    t))
+
+(defun nndoc-transform-clari-briefs (article)
+  (goto-char (point-min))
+  (when (looking-at " *\\*\\(.*\\)\n")
+    (replace-match "" t t))
+  (nndoc-generate-clari-briefs-head article))
+
+(defun nndoc-generate-clari-briefs-head (article)
+  (let ((entry (cdr (assq article nndoc-dissection-alist)))
+	subject from)
+    (save-excursion
+      (set-buffer nndoc-current-buffer)
+      (save-restriction
+	(narrow-to-region (car entry) (nth 3 entry))
+	(goto-char (point-min))
+	(when (looking-at " *\\*\\(.*\\)$")
+	  (setq subject (match-string 1))
+	  (when (string-match "[ \t]+$" subject)
+	    (setq subject (substring subject 0 (match-beginning 0)))))
+	(when
+	    (let ((case-fold-search nil))
+	      (re-search-forward
+	       "^\t\\([^a-z]+\\(,[^(]+\\)? ([^a-z]+)\\) --" nil t))
+	  (setq from (match-string 1)))))
+    (insert "From: " "clari@clari.net (" (or from "unknown") ")"
+	    "\nSubject: " (or subject "(no subject)") "\n")))
+
+(defun nndoc-mime-digest-type-p ()
+  (let ((case-fold-search t)
+	boundary-id b-delimiter entry)
+    (when (and
+	   (re-search-forward
+	    (concat "^Content-Type: *multipart/digest;[ \t\n]*[ \t]"
+		    "boundary=\"\\([^\"\n]*[^\" \t\n]\\)\"")
+	    nil t)
+	   (match-beginning 1))
+      (setq boundary-id (match-string 1)
+	    b-delimiter (concat "\n--" boundary-id "[\n \t]+"))
+      (setq entry (assq 'mime-digest nndoc-type-alist))
+      (setcdr entry
+	      (list
+	       (cons 'head-end "^ ?$")
+	       (cons 'body-begin "^ ?\n")
+	       (cons 'article-begin b-delimiter)
+	       (cons 'body-end-function 'nndoc-digest-body-end)
+	       (cons 'file-end (concat "\n--" boundary-id "--[ \t]*$"))))
+      t)))
+
+(defun nndoc-standard-digest-type-p ()
+  (when (and (re-search-forward (concat "^" (make-string 70 ?-) "\n\n") nil t)
+	     (re-search-forward
+	      (concat "\n\n" (make-string 30 ?-) "\n\n") nil t))
+    t))
+
+(defun nndoc-digest-body-end ()
+  (and (re-search-forward nndoc-article-begin nil t)
+       (goto-char (match-beginning 0))))
+
+(defun nndoc-slack-digest-type-p ()
+  0)
+
+(defun nndoc-lanl-gov-announce-type-p ()
+  (when (let ((case-fold-search nil))
+	  (re-search-forward "^\\\\\\\\\nPaper: [a-z-]+/[0-9]+" nil t))
+    t))
+
+(defun nndoc-transform-lanl-gov-announce (article)
+  (goto-char (point-max))
+  (when (re-search-backward "^\\\\\\\\ +(\\([^ ]*\\) , *\\([^ ]*\\))" nil t)
+    (replace-match "\n\nGet it at \\1 (\\2)" t nil))
+  ;;  (when (re-search-backward "^\\\\\\\\$" nil t)
+  ;;    (replace-match "" t t))
+  )
+
+(defun nndoc-generate-lanl-gov-head (article)
+  (let ((entry (cdr (assq article nndoc-dissection-alist)))
+ 	(e-mail "no address given")
+ 	subject from)
+    (save-excursion
+      (set-buffer nndoc-current-buffer)
+      (save-restriction
+ 	(narrow-to-region (car entry) (nth 1 entry))
+ 	(goto-char (point-min))
+ 	(when (looking-at "^Paper.*: \\([a-z-]+/[0-9]+\\)")
+ 	  (setq subject (concat " (" (match-string 1) ")"))
+ 	  (when (re-search-forward "^From: \\([^ ]+\\)" nil t)
+ 	    (setq e-mail (match-string 1)))
+ 	  (when (re-search-forward "^Title: \\([^\f]*\\)\nAuthors?: \\(.*\\)"
+ 				   nil t)
+ 	    (setq subject (concat (match-string 1) subject))
+ 	    (setq from (concat (match-string 2) " <" e-mail ">"))))
+ 	))
+    (while (and from (string-match "(\[^)\]*)" from))
+      (setq from (replace-match "" t t from)))
+    (insert "From: "  (or from "unknown")
+ 	    "\nSubject: " (or subject "(no subject)") "\n")))
+
+
+
+;;;
+;;; Functions for dissecting the documents
+;;;
+
+(defun nndoc-search (regexp)
+  (prog1
+      (re-search-forward regexp nil t)
+    (beginning-of-line)))
+
+(defun nndoc-dissect-buffer ()
+  "Go through the document and partition it into heads/bodies/articles."
+  (let ((i 0)
+	(first t)
+	head-begin head-end body-begin body-end)
+    (setq nndoc-dissection-alist nil)
+    (save-excursion
+      (set-buffer nndoc-current-buffer)
+      (goto-char (point-min))
+      ;; Find the beginning of the file.
+      (when nndoc-file-begin
+	(nndoc-search nndoc-file-begin))
+      ;; Go through the file.
+      (while (if (and first nndoc-first-article)
+		 (nndoc-search nndoc-first-article)
+	       (nndoc-article-begin))
+	(setq first nil)
+	(cond (nndoc-head-begin-function
+	       (funcall nndoc-head-begin-function))
+	      (nndoc-head-begin
+	       (nndoc-search nndoc-head-begin)))
+ 	(if (or (>= (point) (point-max))
+		(and nndoc-file-end
+		     (looking-at nndoc-file-end)))
+	    (goto-char (point-max))
+	  (setq head-begin (point))
+	  (nndoc-search (or nndoc-head-end "^$"))
+	  (setq head-end (point))
+	  (if nndoc-body-begin-function
+	      (funcall nndoc-body-begin-function)
+	    (nndoc-search (or nndoc-body-begin "^\n")))
+	  (setq body-begin (point))
+	  (or (and nndoc-body-end-function
+		   (funcall nndoc-body-end-function))
+	      (and nndoc-body-end
+		   (nndoc-search nndoc-body-end))
+	      (nndoc-article-begin)
+	      (progn
+		(goto-char (point-max))
+		(when nndoc-file-end
+		  (and (re-search-backward nndoc-file-end nil t)
+		       (beginning-of-line)))))
+	  (setq body-end (point))
+	  (push (list (incf i) head-begin head-end body-begin body-end
+		      (count-lines body-begin body-end))
+		nndoc-dissection-alist))))))
+
+(defun nndoc-article-begin ()
+  (if nndoc-article-begin-function
+      (funcall nndoc-article-begin-function)
+    (ignore-errors
+      (nndoc-search nndoc-article-begin))))
+
+(defun nndoc-unquote-dashes ()
+  "Unquote quoted non-separators in digests."
+  (while (re-search-forward "^- -"nil t)
+    (replace-match "-" t t)))
+
+;;;###autoload
+(defun nndoc-add-type (definition &optional position)
+  "Add document DEFINITION to the list of nndoc document definitions.
+If POSITION is nil or `last', the definition will be added
+as the last checked definition, if t or `first', add as the
+first definition, and if any other symbol, add after that
+symbol in the alist."
+  ;; First remove any old instances.
+  (setq nndoc-type-alist
+	(delq (assq (car definition) nndoc-type-alist)
+	      nndoc-type-alist))
+  ;; Then enter the new definition in the proper place.
+  (cond
+   ((or (null position) (eq position 'last))
+    (setq nndoc-type-alist (nconc nndoc-type-alist (list definition))))
+   ((or (eq position t) (eq position 'first))
+    (push definition nndoc-type-alist))
+   (t
+    (let ((list (memq (assq position nndoc-type-alist)
+		      nndoc-type-alist)))
+      (unless list
+	(error "No such position: %s" position))
+      (setcdr list (cons definition (cdr list)))))))
+
+(provide 'nndoc)
+
+;;; nndoc.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nndraft.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,248 @@
+;;; nndraft.el --- draft article access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'nnmh)
+(require 'nnoo)
+(eval-and-compile (require 'cl))
+
+(nnoo-declare nndraft)
+
+(eval-and-compile
+  (autoload 'mail-send-and-exit "sendmail"))
+
+(defvoo nndraft-directory nil
+  "Where nndraft will store its directory.")
+
+
+
+(defconst nndraft-version "nndraft 1.0")
+(defvoo nndraft-status-string "")
+
+
+
+;;; Interface functions.
+
+(nnoo-define-basics nndraft)
+
+(deffoo nndraft-retrieve-headers (articles &optional group server fetch-old)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (let* ((buf (get-buffer-create " *draft headers*"))
+	   article)
+      (set-buffer buf)
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      ;; We don't support fetching by Message-ID.
+      (if (stringp (car articles))
+	  'headers
+	(while articles
+	  (set-buffer buf)
+	  (when (nndraft-request-article
+		 (setq article (pop articles)) group server (current-buffer))
+	    (goto-char (point-min))
+	    (if (search-forward "\n\n" nil t)
+		(forward-line -1)
+	      (goto-char (point-max)))
+	    (delete-region (point) (point-max))
+	    (set-buffer nntp-server-buffer)
+	    (goto-char (point-max))
+	    (insert (format "221 %d Article retrieved.\n" article))
+	    (insert-buffer-substring buf)
+	    (insert ".\n")))
+
+	(nnheader-fold-continuation-lines)
+	'headers))))
+
+(deffoo nndraft-open-server (server &optional defs)
+  (nnoo-change-server 'nndraft server defs)
+  (unless (assq 'nndraft-directory defs)
+    (setq nndraft-directory server))
+  (cond
+   ((not (file-exists-p nndraft-directory))
+    (nndraft-close-server)
+    (nnheader-report 'nndraft "No such file or directory: %s"
+		     nndraft-directory))
+   ((not (file-directory-p (file-truename nndraft-directory)))
+    (nndraft-close-server)
+    (nnheader-report 'nndraft "Not a directory: %s" nndraft-directory))
+   (t
+    (nnheader-report 'nndraft "Opened server %s using directory %s"
+		     server nndraft-directory)
+    t)))
+
+(deffoo nndraft-request-article (id &optional group server buffer)
+  (when (numberp id)
+    ;; We get the newest file of the auto-saved file and the
+    ;; "real" file.
+    (let* ((file (nndraft-article-filename id))
+	   (auto (nndraft-auto-save-file-name file))
+	   (newest (if (file-newer-than-file-p file auto) file auto))
+	   (nntp-server-buffer (or buffer nntp-server-buffer)))
+      (when (and (file-exists-p newest)
+		 (nnmail-find-file newest))
+	(save-excursion
+	  (set-buffer nntp-server-buffer)
+	  (goto-char (point-min))
+	  ;; If there's a mail header separator in this file,
+	  ;; we remove it.
+	  (when (re-search-forward
+		 (concat "^" mail-header-separator "$") nil t)
+	    (replace-match "" t t)))
+	t))))
+
+(deffoo nndraft-request-restore-buffer (article &optional group server)
+  "Request a new buffer that is restored to the state of ARTICLE."
+  (let ((file (nndraft-article-filename article ".state"))
+	nndraft-point nndraft-mode nndraft-buffer-name)
+    (when (file-exists-p file)
+      (load file t t t)
+      (when nndraft-buffer-name
+	(set-buffer (get-buffer-create
+		     (generate-new-buffer-name nndraft-buffer-name)))
+	(nndraft-request-article article group server (current-buffer))
+	(funcall nndraft-mode)
+	(let ((gnus-verbose-backends nil))
+	  (nndraft-request-expire-articles (list article) group server t))
+	(goto-char nndraft-point))
+      nndraft-buffer-name)))
+
+(deffoo nndraft-request-update-info (group info &optional server)
+  (setcar (cddr info) nil)
+  (when (nth 3 info)
+    (setcar (nthcdr 3 info) nil))
+  t)
+
+(deffoo nndraft-request-associate-buffer (group)
+  "Associate the current buffer with some article in the draft group."
+  (let* ((gnus-verbose-backends nil)
+	 (article (cdr (nndraft-request-accept-article
+			group (nnoo-current-server 'nndraft) t 'noinsert)))
+	 (file (nndraft-article-filename article)))
+    (setq buffer-file-name file)
+    (setq buffer-auto-save-file-name (make-auto-save-file-name))
+    (clear-visited-file-modtime)
+    article))
+
+(deffoo nndraft-request-group (group &optional server dont-check)
+  (prog1
+      (nndraft-execute-nnmh-command
+       `(nnmh-request-group group "" ,dont-check))
+    (nnheader-report 'nndraft nnmh-status-string)))
+
+(deffoo nndraft-request-list (&optional server dir)
+  (nndraft-execute-nnmh-command
+   `(nnmh-request-list nil ,dir)))
+
+(deffoo nndraft-request-newgroups (date &optional server)
+  (nndraft-execute-nnmh-command
+   `(nnmh-request-newgroups ,date ,server)))
+
+(deffoo nndraft-request-expire-articles
+  (articles group &optional server force)
+  (let ((res (nndraft-execute-nnmh-command
+	      `(nnmh-request-expire-articles
+		',articles group ,server ,force)))
+	article)
+    ;; Delete all the "state" files of articles that have been expired.
+    (while articles
+      (unless (memq (setq article (pop articles)) res)
+	(let ((file (nndraft-article-filename article ".state"))
+	      (auto (nndraft-auto-save-file-name
+		     (nndraft-article-filename article))))
+	  (when (file-exists-p file)
+	    (funcall nnmail-delete-file-function file))
+	  (when (file-exists-p auto)
+	    (funcall nnmail-delete-file-function auto)))))
+    res))
+
+(deffoo nndraft-request-accept-article (group &optional server last noinsert)
+  (let* ((point (point))
+	 (mode major-mode)
+	 (name (buffer-name))
+	 (gnus-verbose-backends nil)
+	 (gart (nndraft-execute-nnmh-command
+		`(nnmh-request-accept-article group ,server ,last noinsert)))
+	 (state
+	  (nndraft-article-filename (cdr gart) ".state")))
+    ;; Write the "state" file.
+    (save-excursion
+      (nnheader-set-temp-buffer " *draft state*")
+      (insert (format "%S\n" `(setq nndraft-mode (quote ,mode)
+				    nndraft-point ,point
+				    nndraft-buffer-name ,name)))
+      (write-region (point-min) (point-max) state nil 'silent)
+      (kill-buffer (current-buffer)))
+    gart))
+
+(deffoo nndraft-close-group (group &optional server)
+  t)
+
+(deffoo nndraft-request-create-group (group &optional server args)
+  (if (file-exists-p nndraft-directory)
+      (if (file-directory-p nndraft-directory)
+	  t
+	nil)
+    (condition-case ()
+	(progn
+	  (gnus-make-directory nndraft-directory)
+	  t)
+      (file-error nil))))
+
+
+;;; Low-Level Interface
+
+(defun nndraft-execute-nnmh-command (command)
+  (let ((dir (expand-file-name nndraft-directory)))
+    (when (string-match "/$" dir)
+      (setq dir (substring dir 0 (match-beginning 0))))
+    (string-match "/[^/]+$" dir)
+    (let ((group (substring dir (1+ (match-beginning 0))))
+          (nnmh-directory (substring dir 0 (1+ (match-beginning 0))))
+	  (nnmail-keep-last-article nil)
+	  (nnmh-get-new-mail nil))
+      (eval command))))
+
+(defun nndraft-article-filename (article &rest args)
+  (apply 'concat
+	 (file-name-as-directory nndraft-directory)
+	 (int-to-string article)
+	 args))
+
+(defun nndraft-auto-save-file-name (file)
+  (save-excursion
+    (prog1
+	(progn
+	  (set-buffer (get-buffer-create " *draft tmp*"))
+	  (setq buffer-file-name file)
+	  (make-auto-save-file-name))
+      (kill-buffer (current-buffer)))))
+
+(provide 'nndraft)
+
+;;; nndraft.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nneething.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,350 @@
+;;; nneething.el --- random file access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; 	Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'nnmail)
+(require 'nnoo)
+(require 'gnus-util)
+(require 'cl)
+
+(nnoo-declare nneething)
+
+(defvoo nneething-map-file-directory "~/.nneething/"
+  "Where nneething stores the map files.")
+
+(defvoo nneething-map-file ".nneething"
+  "Name of the map files.")
+
+(defvoo nneething-exclude-files nil
+  "Regexp saying what files to exclude from the group.
+If this variable is nil, no files will be excluded.")
+
+
+
+;;; Internal variables.
+
+(defconst nneething-version "nneething 1.0"
+  "nneething version.")
+
+(defvoo nneething-current-directory nil
+  "Current news group directory.")
+
+(defvoo nneething-status-string "")
+
+(defvoo nneething-message-id-number 0)
+(defvoo nneething-work-buffer " *nneething work*")
+
+(defvoo nneething-group nil)
+(defvoo nneething-map nil)
+(defvoo nneething-read-only nil)
+(defvoo nneething-active nil)
+
+
+
+;;; Interface functions.
+
+(nnoo-define-basics nneething)
+
+(deffoo nneething-retrieve-headers (articles &optional group server fetch-old)
+  (nneething-possibly-change-directory group)
+
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (let* ((number (length articles))
+	   (count 0)
+	   (large (and (numberp nnmail-large-newsgroup)
+		       (> number nnmail-large-newsgroup)))
+	   article file)
+
+      (if (stringp (car articles))
+	  'headers
+
+	(while (setq article (pop articles))
+	  (setq file (nneething-file-name article))
+
+	  (when (and (file-exists-p file)
+		     (or (file-directory-p file)
+			 (not (zerop (nnheader-file-size file)))))
+	    (insert (format "221 %d Article retrieved.\n" article))
+	    (nneething-insert-head file)
+	    (insert ".\n"))
+
+	  (incf count)
+
+	  (and large
+	       (zerop (% count 20))
+	       (message "nneething: Receiving headers... %d%%"
+			(/ (* count 100) number))))
+
+	(when large
+	  (message "nneething: Receiving headers...done"))
+
+	(nnheader-fold-continuation-lines)
+	'headers))))
+
+(deffoo nneething-request-article (id &optional group server buffer)
+  (nneething-possibly-change-directory group)
+  (let ((file (unless (stringp id)
+		(nneething-file-name id)))
+	(nntp-server-buffer (or buffer nntp-server-buffer)))
+    (and (stringp file)			; We did not request by Message-ID.
+	 (file-exists-p file)		; The file exists.
+	 (not (file-directory-p file))	; It's not a dir.
+	 (save-excursion
+	   (nnmail-find-file file)	; Insert the file in the nntp buf.
+	   (unless (nnheader-article-p)	; Either it's a real article...
+	     (goto-char (point-min))
+	     (nneething-make-head file (current-buffer)) ; ... or we fake some headers.
+	     (insert "\n"))
+	   t))))
+
+(deffoo nneething-request-group (group &optional server dont-check)
+  (nneething-possibly-change-directory group server)
+  (unless dont-check
+    (nneething-create-mapping)
+    (if (> (car nneething-active) (cdr nneething-active))
+	(nnheader-insert "211 0 1 0 %s\n" group)
+      (nnheader-insert
+       "211 %d %d %d %s\n"
+       (- (1+ (cdr nneething-active)) (car nneething-active))
+       (car nneething-active) (cdr nneething-active)
+       group)))
+  t)
+
+(deffoo nneething-request-list (&optional server dir)
+  (nnheader-report 'nneething "LIST is not implemented."))
+
+(deffoo nneething-request-newgroups (date &optional server)
+  (nnheader-report 'nneething "NEWSGROUPS is not implemented."))
+
+(deffoo nneething-request-type (group &optional article)
+  'unknown)
+
+(deffoo nneething-close-group (group &optional server)
+  (setq nneething-current-directory nil)
+  t)
+
+(deffoo nneething-open-server (server &optional defs)
+  (nnheader-init-server-buffer)
+  (if (nneething-server-opened server)
+      t
+    (unless (assq 'nneething-directory defs)
+      (setq defs (append defs (list (list 'nneething-directory server)))))
+    (nnoo-change-server 'nneething server defs)))
+
+
+;;; Internal functions.
+
+(defun nneething-possibly-change-directory (group &optional server)
+  (when (and server
+	     (not (nneething-server-opened server)))
+    (nneething-open-server server))
+  (when (and group
+	     (not (equal nneething-group group)))
+    (setq nneething-group group)
+    (setq nneething-map nil)
+    (setq nneething-active (cons 1 0))
+    (nneething-create-mapping)))
+
+(defun nneething-map-file ()
+  ;; We make sure that the .nneething directory exists.
+  (gnus-make-directory nneething-map-file-directory)
+  ;; We store it in a special directory under the user's home dir.
+  (concat (file-name-as-directory nneething-map-file-directory)
+	  nneething-group nneething-map-file))
+
+(defun nneething-create-mapping ()
+  ;; Read nneething-active and nneething-map.
+  (when (file-exists-p nneething-directory)
+    (let ((map-file (nneething-map-file))
+	  (files (directory-files nneething-directory))
+	  touched map-files)
+      (when (file-exists-p map-file)
+	(ignore-errors
+	  (load map-file nil t t)))
+      (unless nneething-active
+	(setq nneething-active (cons 1 0)))
+      ;; Old nneething had a different map format.
+      (when (and (cdar nneething-map)
+		 (atom (cdar nneething-map)))
+	(setq nneething-map
+	      (mapcar (lambda (n)
+			(list (cdr n) (car n)
+			      (nth 5 (file-attributes
+				      (nneething-file-name (car n))))))
+		      nneething-map)))
+      ;; Remove files matching the exclusion regexp.
+      (when nneething-exclude-files
+	(let ((f files)
+	      prev)
+	  (while f
+	    (if (string-match nneething-exclude-files (car f))
+		(if prev (setcdr prev (cdr f))
+		  (setq files (cdr files)))
+	      (setq prev f))
+	    (setq f (cdr f)))))
+      ;; Remove deleted files from the map.
+      (let ((map nneething-map)
+	    prev)
+	(while map
+	  (if (and (member (cadar map) files)
+		   ;; We also remove files that have changed mod times.
+		   (equal (nth 5 (file-attributes
+				  (nneething-file-name (cadar map))))
+			  (caddar map)))
+	      (progn
+		(push (cadar map) map-files)
+		(setq prev map))
+	    (setq touched t)
+	    (if prev
+		(setcdr prev (cdr map))
+	      (setq nneething-map (cdr nneething-map))))
+	  (setq map (cdr map))))
+      ;; Find all new files and enter them into the map.
+      (while files
+	(unless (member (car files) map-files)
+	  ;; This file is not in the map, so we enter it.
+	  (setq touched t)
+	  (setcdr nneething-active (1+ (cdr nneething-active)))
+	  (push (list (cdr nneething-active) (car files)
+		      (nth 5 (file-attributes
+			      (nneething-file-name (car files)))))
+		nneething-map))
+	(setq files (cdr files)))
+      (when (and touched
+		 (not nneething-read-only))
+	(nnheader-temp-write map-file
+	  (insert "(setq nneething-map '")
+	  (gnus-prin1 nneething-map)
+	  (insert ")\n(setq nneething-active '")
+	  (gnus-prin1 nneething-active)
+	  (insert ")\n"))))))
+
+(defun nneething-insert-head (file)
+  "Insert the head of FILE."
+  (when (nneething-get-head file)
+    (insert-buffer-substring nneething-work-buffer)
+    (goto-char (point-max))))
+
+(defun nneething-make-head (file &optional buffer)
+  "Create a head by looking at the file attributes of FILE."
+  (let ((atts (file-attributes file)))
+    (insert
+     "Subject: " (file-name-nondirectory file) "\n"
+     "Message-ID: <nneething-"
+     (int-to-string (incf nneething-message-id-number))
+     "@" (system-name) ">\n"
+     (if (equal '(0 0) (nth 5 atts)) ""
+       (concat "Date: " (current-time-string (nth 5 atts)) "\n"))
+     (or (when buffer
+	   (save-excursion
+	     (set-buffer buffer)
+	     (when (re-search-forward "<[a-zA-Z0-9_]@[-a-zA-Z0-9_]>" 1000 t)
+	       (concat "From: " (match-string 0) "\n"))))
+	 (nneething-from-line (nth 2 atts) file))
+     (if (> (string-to-int (int-to-string (nth 7 atts))) 0)
+	 (concat "Chars: " (int-to-string (nth 7 atts)) "\n")
+       "")
+     (if buffer
+	 (save-excursion
+	   (set-buffer buffer)
+	   (concat "Lines: " (int-to-string
+			      (count-lines (point-min) (point-max)))
+		   "\n"))
+       "")
+     )))
+
+(defun nneething-from-line (uid &optional file)
+  "Return a From header based of UID."
+  (let* ((login (condition-case nil
+		    (user-login-name uid)
+		  (error
+		   (cond ((= uid (user-uid)) (user-login-name))
+			 ((zerop uid) "root")
+			 (t (int-to-string uid))))))
+	 (name (condition-case nil
+		   (user-full-name uid)
+		 (error
+		  (cond ((= uid (user-uid)) (user-full-name))
+			((zerop uid) "Ms. Root")))))
+	 (host (if  (string-match "\\`/[^/@]*@\\([^:/]+\\):" file)
+		   (prog1
+		       (substring file
+				  (match-beginning 1)
+				  (match-end 1))
+		     (when (string-match "/\\(users\\|home\\)/\\([^/]+\\)/" file)
+		       (setq login (substring file
+					      (match-beginning 2)
+					      (match-end 2))
+			     name nil)))
+		 (system-name))))
+    (concat "From: " login "@" host
+	    (if name (concat " (" name ")") "") "\n")))
+
+(defun nneething-get-head (file)
+  "Either find the head in FILE or make a head for FILE."
+  (save-excursion
+    (set-buffer (get-buffer-create nneething-work-buffer))
+    (setq case-fold-search nil)
+    (buffer-disable-undo (current-buffer))
+    (erase-buffer)
+    (cond
+     ((not (file-exists-p file))
+      ;; The file do not exist.
+      nil)
+     ((or (file-directory-p file)
+	  (file-symlink-p file))
+      ;; It's a dir, so we fudge a head.
+      (nneething-make-head file) t)
+     (t
+      ;; We examine the file.
+      (nnheader-insert-head file)
+      (if (nnheader-article-p)
+	  (delete-region
+	   (progn
+	     (goto-char (point-min))
+	     (or (and (search-forward "\n\n" nil t)
+		      (1- (point)))
+		 (point-max)))
+	   (point-max))
+	(goto-char (point-min))
+	(nneething-make-head file (current-buffer))
+	(delete-region (point) (point-max)))
+      t))))
+
+(defun nneething-file-name (article)
+  "Return the file name of ARTICLE."
+  (concat (file-name-as-directory nneething-directory)
+	  (if (numberp article)
+	      (cadr (assq article nneething-map))
+	    article)))
+
+(provide 'nneething)
+
+;;; nneething.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnfolder.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,768 @@
+;;; nnfolder.el --- mail folder access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Scott Byer <byer@mv.us.adobe.com>
+;;	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; 	Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Keywords: mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'message)
+(require 'nnmail)
+(require 'nnoo)
+(require 'cl)
+(require 'gnus-util)
+
+(nnoo-declare nnfolder)
+
+(defvoo nnfolder-directory (expand-file-name message-directory)
+  "The name of the nnfolder directory.")
+
+(defvoo nnfolder-active-file
+  (nnheader-concat nnfolder-directory "active")
+  "The name of the active file.")
+
+;; I renamed this variable to something more in keeping with the general GNU
+;; style. -SLB
+
+(defvoo nnfolder-ignore-active-file nil
+  "If non-nil, causes nnfolder to do some extra work in order to determine
+the true active ranges of an mbox file.  Note that the active file is still
+saved, but it's values are not used.  This costs some extra time when
+scanning an mbox when opening it.")
+
+(defvoo nnfolder-distrust-mbox nil
+  "If non-nil, causes nnfolder to not trust the user with respect to
+inserting unaccounted for mail in the middle of an mbox file.  This can greatly
+slow down scans, which now must scan the entire file for unmarked messages.
+When nil, scans occur forward from the last marked message, a huge
+time saver for large mailboxes.")
+
+(defvoo nnfolder-newsgroups-file
+  (concat (file-name-as-directory nnfolder-directory) "newsgroups")
+  "Mail newsgroups description file.")
+
+(defvoo nnfolder-get-new-mail t
+  "If non-nil, nnfolder will check the incoming mail file and split the mail.")
+
+(defvoo nnfolder-prepare-save-mail-hook nil
+  "Hook run narrowed to an article before saving.")
+
+(defvoo nnfolder-save-buffer-hook nil
+  "Hook run before saving the nnfolder mbox buffer.")
+
+(defvoo nnfolder-inhibit-expiry nil
+  "If non-nil, inhibit expiry.")
+
+
+
+(defconst nnfolder-version "nnfolder 1.0"
+  "nnfolder version.")
+
+(defconst nnfolder-article-marker "X-Gnus-Article-Number: "
+  "String used to demarcate what the article number for a message is.")
+
+(defvoo nnfolder-current-group nil)
+(defvoo nnfolder-current-buffer nil)
+(defvoo nnfolder-status-string "")
+(defvoo nnfolder-group-alist nil)
+(defvoo nnfolder-buffer-alist nil)
+(defvoo nnfolder-scantime-alist nil)
+(defvoo nnfolder-active-timestamp nil)
+
+
+
+;;; Interface functions
+
+(nnoo-define-basics nnfolder)
+
+(deffoo nnfolder-retrieve-headers (articles &optional group server fetch-old)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (let (article art-string start stop)
+      (nnfolder-possibly-change-group group server)
+      (when nnfolder-current-buffer
+	(set-buffer nnfolder-current-buffer)
+	(goto-char (point-min))
+	(if (stringp (car articles))
+	    'headers
+	  (while articles
+	    (setq article (car articles))
+	    (setq art-string (nnfolder-article-string article))
+	    (set-buffer nnfolder-current-buffer)
+	    (when (or (search-forward art-string nil t)
+		      ;; Don't search the whole file twice!  Also, articles
+		      ;; probably have some locality by number, so searching
+		      ;; backwards will be faster.  Especially if we're at the
+		      ;; beginning of the buffer :-). -SLB
+		      (search-backward art-string nil t))
+	      (nnmail-search-unix-mail-delim-backward)
+	      (setq start (point))
+	      (search-forward "\n\n" nil t)
+	      (setq stop (1- (point)))
+	      (set-buffer nntp-server-buffer)
+	      (insert (format "221 %d Article retrieved.\n" article))
+	      (insert-buffer-substring nnfolder-current-buffer start stop)
+	      (goto-char (point-max))
+	      (insert ".\n"))
+	    (setq articles (cdr articles)))
+
+	  (set-buffer nntp-server-buffer)
+	  (nnheader-fold-continuation-lines)
+	  'headers)))))
+
+(deffoo nnfolder-open-server (server &optional defs)
+  (nnoo-change-server 'nnfolder server defs)
+  (nnmail-activate 'nnfolder t)
+  (gnus-make-directory nnfolder-directory)
+  (cond
+   ((not (file-exists-p nnfolder-directory))
+    (nnfolder-close-server)
+    (nnheader-report 'nnfolder "Couldn't create directory: %s"
+		     nnfolder-directory))
+   ((not (file-directory-p (file-truename nnfolder-directory)))
+    (nnfolder-close-server)
+    (nnheader-report 'nnfolder "Not a directory: %s" nnfolder-directory))
+   (t
+    (nnmail-activate 'nnfolder)
+    (nnheader-report 'nnfolder "Opened server %s using directory %s"
+		     server nnfolder-directory)
+    t)))
+
+(deffoo nnfolder-request-close ()
+  (let ((alist nnfolder-buffer-alist))
+    (while alist
+      (nnfolder-close-group (caar alist) nil t)
+      (setq alist (cdr alist))))
+  (nnoo-close-server 'nnfolder)
+  (setq nnfolder-buffer-alist nil
+	nnfolder-group-alist nil))
+
+(deffoo nnfolder-request-article (article &optional group server buffer)
+  (nnfolder-possibly-change-group group server)
+  (save-excursion
+    (set-buffer nnfolder-current-buffer)
+    (goto-char (point-min))
+    (when (search-forward (nnfolder-article-string article) nil t)
+      (let (start stop)
+	(nnmail-search-unix-mail-delim-backward)
+	(setq start (point))
+	(forward-line 1)
+	(unless (and (nnmail-search-unix-mail-delim)
+		     (forward-line -1))
+	  (goto-char (point-max)))
+	(setq stop (point))
+	(let ((nntp-server-buffer (or buffer nntp-server-buffer)))
+	  (set-buffer nntp-server-buffer)
+	  (erase-buffer)
+	  (insert-buffer-substring nnfolder-current-buffer start stop)
+	  (goto-char (point-min))
+	  (while (looking-at "From ")
+	    (delete-char 5)
+	    (insert "X-From-Line: ")
+	    (forward-line 1))
+	  (if (numberp article)
+	      (cons nnfolder-current-group article)
+	    (goto-char (point-min))
+	    (search-forward (concat "\n" nnfolder-article-marker))
+	    (cons nnfolder-current-group
+		  (string-to-int
+		   (buffer-substring
+		    (point) (progn (end-of-line) (point)))))))))))
+
+(deffoo nnfolder-request-group (group &optional server dont-check)
+  (nnfolder-possibly-change-group group server t)
+  (save-excursion
+    (if (not (assoc group nnfolder-group-alist))
+	(nnheader-report 'nnfolder "No such group: %s" group)
+      (if dont-check
+	  (progn
+	    (nnheader-report 'nnfolder "Selected group %s" group)
+	    t)
+	(let* ((active (assoc group nnfolder-group-alist))
+	       (group (car active))
+	       (range (cadr active)))
+	  (cond
+	   ((null active)
+	    (nnheader-report 'nnfolder "No such group: %s" group))
+	   ((null nnfolder-current-group)
+	    (nnheader-report 'nnfolder "Empty group: %s" group))
+	   (t
+	    (nnheader-report 'nnfolder "Selected group %s" group)
+	    (nnheader-insert "211 %d %d %d %s\n"
+			     (1+ (- (cdr range) (car range)))
+			     (car range) (cdr range) group))))))))
+
+(deffoo nnfolder-request-scan (&optional group server)
+  (nnfolder-possibly-change-group nil server)
+  (when nnfolder-get-new-mail
+    (nnfolder-possibly-change-group group server)
+    (nnmail-get-new-mail
+     'nnfolder
+     (lambda ()
+       (let ((bufs nnfolder-buffer-alist))
+	 (save-excursion
+	   (while bufs
+	     (if (not (gnus-buffer-live-p (nth 1 (car bufs))))
+		 (setq nnfolder-buffer-alist
+		       (delq (car bufs) nnfolder-buffer-alist))
+	       (set-buffer (nth 1 (car bufs)))
+	       (nnfolder-save-buffer)
+	       (kill-buffer (current-buffer)))
+	     (setq bufs (cdr bufs))))))
+     nnfolder-directory
+     group)))
+
+;; Don't close the buffer if we're not shutting down the server.  This way,
+;; we can keep the buffer in the group buffer cache, and not have to grovel
+;; over the buffer again unless we add new mail to it or modify it in some
+;; way.
+
+(deffoo nnfolder-close-group (group &optional server force)
+  ;; Make sure we _had_ the group open.
+  (when (or (assoc group nnfolder-buffer-alist)
+	    (equal group nnfolder-current-group))
+    (let ((inf (assoc group nnfolder-buffer-alist)))
+      (when inf
+	(when (and nnfolder-current-group
+		   nnfolder-current-buffer)
+	  (push (list nnfolder-current-group nnfolder-current-buffer)
+		nnfolder-buffer-alist))
+	(setq nnfolder-buffer-alist
+	      (delq inf nnfolder-buffer-alist))
+	(setq nnfolder-current-buffer (cadr inf)
+	      nnfolder-current-group (car inf))))
+    (when (and nnfolder-current-buffer
+	       (buffer-name nnfolder-current-buffer))
+      (save-excursion
+	(set-buffer nnfolder-current-buffer)
+	;; If the buffer was modified, write the file out now.
+	(nnfolder-save-buffer)
+	;; If we're shutting the server down, we need to kill the
+	;; buffer and remove it from the open buffer list.  Or, of
+	;; course, if we're trying to minimize our space impact.
+	(kill-buffer (current-buffer))
+	(setq nnfolder-buffer-alist (delq (assoc group nnfolder-buffer-alist)
+					  nnfolder-buffer-alist)))))
+  (setq nnfolder-current-group nil
+	nnfolder-current-buffer nil)
+  t)
+
+(deffoo nnfolder-request-create-group (group &optional server args)
+  (nnfolder-possibly-change-group nil server)
+  (nnmail-activate 'nnfolder)
+  (when group
+    (unless (assoc group nnfolder-group-alist)
+      (push (list group (cons 1 0)) nnfolder-group-alist)
+      (nnmail-save-active nnfolder-group-alist nnfolder-active-file)))
+  t)
+
+(deffoo nnfolder-request-list (&optional server)
+  (nnfolder-possibly-change-group nil server)
+  (save-excursion
+    (nnmail-find-file nnfolder-active-file)
+    (setq nnfolder-group-alist (nnmail-get-active))
+    t))
+
+(deffoo nnfolder-request-newgroups (date &optional server)
+  (nnfolder-possibly-change-group nil server)
+  (nnfolder-request-list server))
+
+(deffoo nnfolder-request-list-newsgroups (&optional server)
+  (nnfolder-possibly-change-group nil server)
+  (save-excursion
+    (nnmail-find-file nnfolder-newsgroups-file)))
+
+(deffoo nnfolder-request-expire-articles
+  (articles newsgroup &optional server force)
+  (nnfolder-possibly-change-group newsgroup server)
+  (let* ((is-old t)
+	 rest)
+    (nnmail-activate 'nnfolder)
+
+    (save-excursion
+      (set-buffer nnfolder-current-buffer)
+      (while (and articles is-old)
+	(goto-char (point-min))
+	(when (search-forward (nnfolder-article-string (car articles)) nil t)
+	  (if (setq is-old
+		    (nnmail-expired-article-p
+		     newsgroup
+		     (buffer-substring
+		      (point) (progn (end-of-line) (point)))
+		     force nnfolder-inhibit-expiry))
+	      (progn
+		(nnheader-message 5 "Deleting article %d..."
+				  (car articles) newsgroup)
+		(nnfolder-delete-mail))
+	    (push (car articles) rest)))
+	(setq articles (cdr articles)))
+      (unless nnfolder-inhibit-expiry
+	(nnheader-message 5 "Deleting articles...done"))
+      (nnfolder-save-buffer)
+      (nnfolder-adjust-min-active newsgroup)
+      (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
+      (nconc rest articles))))
+
+(deffoo nnfolder-request-move-article
+  (article group server accept-form &optional last)
+  (let ((buf (get-buffer-create " *nnfolder move*"))
+	result)
+    (and
+     (nnfolder-request-article article group server)
+     (save-excursion
+       (set-buffer buf)
+       (buffer-disable-undo (current-buffer))
+       (erase-buffer)
+       (insert-buffer-substring nntp-server-buffer)
+       (goto-char (point-min))
+       (while (re-search-forward
+	       (concat "^" nnfolder-article-marker)
+	       (save-excursion (search-forward "\n\n" nil t) (point)) t)
+	 (delete-region (progn (beginning-of-line) (point))
+			(progn (forward-line 1) (point))))
+       (setq result (eval accept-form))
+       (kill-buffer buf)
+       result)
+     (save-excursion
+       (nnfolder-possibly-change-group group server)
+       (set-buffer nnfolder-current-buffer)
+       (goto-char (point-min))
+       (when (search-forward (nnfolder-article-string article) nil t)
+	 (nnfolder-delete-mail))
+       (when last
+	 (nnfolder-save-buffer)
+	 (nnfolder-adjust-min-active group)
+	 (nnmail-save-active nnfolder-group-alist nnfolder-active-file))))
+    result))
+
+(deffoo nnfolder-request-accept-article (group &optional server last)
+  (nnfolder-possibly-change-group group server)
+  (nnmail-check-syntax)
+  (let ((buf (current-buffer))
+	result art-group)
+    (goto-char (point-min))
+    (when (looking-at "X-From-Line: ")
+      (replace-match "From "))
+    (and
+     (nnfolder-request-list)
+     (save-excursion
+       (set-buffer buf)
+       (goto-char (point-min))
+       (search-forward "\n\n" nil t)
+       (forward-line -1)
+       (while (re-search-backward (concat "^" nnfolder-article-marker) nil t)
+	 (delete-region (point) (progn (forward-line 1) (point))))
+       (when nnmail-cache-accepted-message-ids
+	 (nnmail-cache-insert (nnmail-fetch-field "message-id")))
+       (setq result (if (stringp group)
+			(list (cons group (nnfolder-active-number group)))
+		      (setq art-group
+			    (nnmail-article-group 'nnfolder-active-number))))
+       (if (and (null result)
+		(yes-or-no-p "Moved to `junk' group; delete article? "))
+	   (setq result 'junk)
+	 (setq result
+	       (car (nnfolder-save-mail result)))))
+     (when last
+       (save-excursion
+	 (nnfolder-possibly-change-folder (or (caar art-group) group))
+	 (nnfolder-save-buffer)
+	 (when nnmail-cache-accepted-message-ids
+	   (nnmail-cache-close)))))
+    (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
+    (unless result
+      (nnheader-report 'nnfolder "Couldn't store article"))
+    result))
+
+(deffoo nnfolder-request-replace-article (article group buffer)
+  (nnfolder-possibly-change-group group)
+  (save-excursion
+    (set-buffer nnfolder-current-buffer)
+    (goto-char (point-min))
+    (if (not (search-forward (nnfolder-article-string article) nil t))
+	nil
+      (nnfolder-delete-mail t t)
+      (insert-buffer-substring buffer)
+      (nnfolder-save-buffer)
+      t)))
+
+(deffoo nnfolder-request-delete-group (group &optional force server)
+  (nnfolder-close-group group server t)
+  ;; Delete all articles in GROUP.
+  (if (not force)
+      ()				; Don't delete the articles.
+    ;; Delete the file that holds the group.
+    (ignore-errors
+      (delete-file (nnfolder-group-pathname group))))
+  ;; Remove the group from all structures.
+  (setq nnfolder-group-alist
+	(delq (assoc group nnfolder-group-alist) nnfolder-group-alist)
+	nnfolder-current-group nil
+	nnfolder-current-buffer nil)
+  ;; Save the active file.
+  (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
+  t)
+
+(deffoo nnfolder-request-rename-group (group new-name &optional server)
+  (nnfolder-possibly-change-group group server)
+  (save-excursion
+    (set-buffer nnfolder-current-buffer)
+    (and (file-writable-p buffer-file-name)
+	 (ignore-errors
+	   (rename-file
+	    buffer-file-name
+	    (nnfolder-group-pathname new-name))
+	   t)
+	 ;; That went ok, so we change the internal structures.
+	 (let ((entry (assoc group nnfolder-group-alist)))
+	   (and entry (setcar entry new-name))
+	   (setq nnfolder-current-buffer nil
+		 nnfolder-current-group nil)
+	   ;; Save the new group alist.
+	   (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
+	   ;; We kill the buffer instead of renaming it and stuff.
+	   (kill-buffer (current-buffer))
+	   t))))
+
+
+;;; Internal functions.
+
+(defun nnfolder-adjust-min-active (group)
+  ;; Find the lowest active article in this group.
+  (let* ((active (cadr (assoc group nnfolder-group-alist)))
+	 (marker (concat "\n" nnfolder-article-marker))
+	 (number "[0-9]+")
+	 (activemin (cdr active)))
+    (save-excursion
+      (set-buffer nnfolder-current-buffer)
+      (goto-char (point-min))
+      (while (and (search-forward marker nil t)
+		  (re-search-forward number nil t))
+	(setq activemin (min activemin
+			     (string-to-number (buffer-substring
+						(match-beginning 0)
+						(match-end 0))))))
+      (setcar active activemin))))
+
+(defun nnfolder-article-string (article)
+  (if (numberp article)
+      (concat "\n" nnfolder-article-marker (int-to-string article) " ")
+    (concat "\nMessage-ID: " article)))
+
+(defun nnfolder-delete-mail (&optional force leave-delim)
+  "Delete the message that point is in."
+  (save-excursion
+    (delete-region
+     (save-excursion
+       (nnmail-search-unix-mail-delim-backward)
+       (if leave-delim (progn (forward-line 1) (point))
+	 (point)))
+     (progn
+       (forward-line 1)
+       (if (nnmail-search-unix-mail-delim)
+	   (if (and (not (bobp)) leave-delim)
+	       (progn (forward-line -2) (point))
+	     (point))
+	 (point-max))))))
+
+(defun nnfolder-possibly-change-group (group &optional server dont-check)
+  ;; Change servers.
+  (when (and server
+	     (not (nnfolder-server-opened server)))
+    (nnfolder-open-server server))
+  (unless (gnus-buffer-live-p nnfolder-current-buffer)
+    (setq nnfolder-current-buffer nil
+	  nnfolder-current-group nil))
+  ;; Change group.
+  (when (and group
+	     (not (equal group nnfolder-current-group)))
+    (nnmail-activate 'nnfolder)
+    (when (and (not (assoc group nnfolder-group-alist))
+	       (not (file-exists-p
+		     (nnfolder-group-pathname group))))
+      ;; The group doesn't exist, so we create a new entry for it.
+      (push (list group (cons 1 0)) nnfolder-group-alist)
+      (nnmail-save-active nnfolder-group-alist nnfolder-active-file))
+
+    (if dont-check
+	(setq nnfolder-current-group group)
+      (let (inf file)
+	;; If we have to change groups, see if we don't already have the
+	;; folder in memory.  If we do, verify the modtime and destroy
+	;; the folder if needed so we can rescan it.
+	(when (setq inf (assoc group nnfolder-buffer-alist))
+	  (setq nnfolder-current-buffer (nth 1 inf)))
+
+	;; If the buffer is not live, make sure it isn't in the alist.  If it
+	;; is live, verify that nobody else has touched the file since last
+	;; time.
+	(when (and nnfolder-current-buffer
+		   (not (gnus-buffer-live-p nnfolder-current-buffer)))
+	  (setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist)
+		nnfolder-current-buffer nil))
+
+	(setq nnfolder-current-group group)
+
+	(when (or (not nnfolder-current-buffer)
+		  (not (verify-visited-file-modtime nnfolder-current-buffer)))
+	  (save-excursion
+	    (setq file (nnfolder-group-pathname group))
+	    ;; See whether we need to create the new file.
+	    (unless (file-exists-p file)
+	      (gnus-make-directory (file-name-directory file))
+	      (nnmail-write-region 1 1 file t 'nomesg))
+	    (when (setq nnfolder-current-buffer (nnfolder-read-folder group))
+	      (set-buffer nnfolder-current-buffer)
+	      (push (list group nnfolder-current-buffer)
+		    nnfolder-buffer-alist))))))))
+
+(defun nnfolder-save-mail (group-art-list)
+  "Called narrowed to an article."
+  (let* (save-list group-art)
+    (goto-char (point-min))
+    ;; The From line may have been quoted by movemail.
+    (when (looking-at (concat ">" message-unix-mail-delimiter))
+      (delete-char 1))
+    ;; This might come from somewhere else.
+    (unless (looking-at message-unix-mail-delimiter)
+      (insert "From nobody " (current-time-string) "\n")
+      (goto-char (point-min)))
+    ;; Quote all "From " lines in the article.
+    (forward-line 1)
+    (let (case-fold-search)
+      (while (re-search-forward "^From " nil t)
+	(beginning-of-line)
+	(insert "> ")))
+    (setq save-list group-art-list)
+    (nnmail-insert-lines)
+    (nnmail-insert-xref group-art-list)
+    (run-hooks 'nnmail-prepare-save-mail-hook)
+    (run-hooks 'nnfolder-prepare-save-mail-hook)
+
+    ;; Insert the mail into each of the destination groups.
+    (while (setq group-art (pop group-art-list))
+      ;; Kill any previous newsgroup markers.
+      (goto-char (point-min))
+      (search-forward "\n\n" nil t)
+      (forward-line -1)
+      (while (search-backward (concat "\n" nnfolder-article-marker) nil t)
+	(delete-region (1+ (point)) (progn (forward-line 2) (point))))
+
+      ;; Insert the new newsgroup marker.
+      (nnfolder-insert-newsgroup-line group-art)
+
+      (save-excursion
+	(let ((beg (point-min))
+	      (end (point-max))
+	      (obuf (current-buffer)))
+	  (nnfolder-possibly-change-folder (car group-art))
+	  (let ((buffer-read-only nil))
+	    (goto-char (point-max))
+	    (unless (eolp)
+	      (insert "\n"))
+	    (unless (bobp)
+	      (insert "\n"))
+	    (insert-buffer-substring obuf beg end)))))
+
+    ;; Did we save it anywhere?
+    save-list))
+
+(defun nnfolder-insert-newsgroup-line (group-art)
+  (save-excursion
+    (goto-char (point-min))
+    (when (search-forward "\n\n" nil t)
+      (forward-char -1)
+      (insert (format (concat nnfolder-article-marker "%d   %s\n")
+		      (cdr group-art) (current-time-string))))))
+
+(defun nnfolder-active-number (group)
+  ;; Find the next article number in GROUP.
+  (let ((active (cadr (assoc group nnfolder-group-alist))))
+    (if active
+	(setcdr active (1+ (cdr active)))
+      ;; This group is new, so we create a new entry for it.
+      ;; This might be a bit naughty... creating groups on the drop of
+      ;; a hat, but I don't know...
+      (push (list group (setq active (cons 1 1)))
+	    nnfolder-group-alist))
+    (cdr active)))
+
+(defun nnfolder-possibly-change-folder (group)
+  (let ((inf (assoc group nnfolder-buffer-alist)))
+    (if (and inf
+	     (gnus-buffer-live-p (cadr inf)))
+	(set-buffer (cadr inf))
+      (when inf
+	(setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist)))
+      (when nnfolder-group-alist
+	(nnmail-save-active nnfolder-group-alist nnfolder-active-file))
+      (push (list group (nnfolder-read-folder group))
+	    nnfolder-buffer-alist))))
+
+;; This method has a problem if you've accidentally let the active list get
+;; out of sync with the files.  This could happen, say, if you've
+;; accidentally gotten new mail with something other than Gnus (but why
+;; would _that_ ever happen? :-).  In that case, we will be in the middle of
+;; processing the file, ready to add new X-Gnus article number markers, and
+;; we'll run across a message with no ID yet - the active list _may_not_ be
+;; ready for us yet.
+
+;; To handle this, I'm modifying this routine to maintain the maximum ID seen
+;; so far, and when we hit a message with no ID, we will _manually_ scan the
+;; rest of the message looking for any more, possibly higher IDs.  We'll
+;; assume the maximum that we find is the highest active.  Note that this
+;; shouldn't cost us much extra time at all, but will be a lot less
+;; vulnerable to glitches between the mbox and the active file.
+
+(defun nnfolder-read-folder (group)
+  (let* ((file (nnfolder-group-pathname group))
+	 (buffer (set-buffer (nnheader-find-file-noselect file))))
+    (if (equal (cadr (assoc group nnfolder-scantime-alist))
+	       (nth 5 (file-attributes file)))
+	;; This looks up-to-date, so we don't do any scanning.
+	buffer
+      ;; Parse the damn thing.
+      (save-excursion
+	(nnmail-activate 'nnfolder)
+	;; Read in the file.
+	(let ((delim (concat "^" message-unix-mail-delimiter))
+	      (marker (concat "\n" nnfolder-article-marker))
+	      (number "[0-9]+")
+	      (active (or (cadr (assoc group nnfolder-group-alist))
+			  (cons 1 0)))
+	      (scantime (assoc group nnfolder-scantime-alist))
+	      (minid (lsh -1 -1))
+	      maxid start end newscantime
+	      buffer-read-only)
+	  (buffer-disable-undo (current-buffer))
+	  (setq maxid (cdr active))
+	  (goto-char (point-min))
+
+	  ;; Anytime the active number is 1 or 0, it is suspect.  In that
+	  ;; case, search the file manually to find the active number.  Or,
+	  ;; of course, if we're being paranoid.  (This would also be the
+	  ;; place to build other lists from the header markers, such as
+	  ;; expunge lists, etc., if we ever desired to abandon the active
+	  ;; file entirely for mboxes.)
+	  (when (or nnfolder-ignore-active-file
+		    (< maxid 2))
+	    (while (and (search-forward marker nil t)
+			(re-search-forward number nil t))
+	      (let ((newnum (string-to-number (match-string 0))))
+		(setq maxid (max maxid newnum))
+		(setq minid (min minid newnum))))
+	    (setcar active (max 1 (min minid maxid)))
+	    (setcdr active (max maxid (cdr active)))
+	    (goto-char (point-min)))
+
+	  ;; As long as we trust that the user will only insert unmarked mail
+	  ;; at the end, go to the end and search backwards for the last
+	  ;; marker.  Find the start of that message, and begin to search for
+	  ;; unmarked messages from there.
+	  (when (not (or nnfolder-distrust-mbox
+			 (< maxid 2)))
+	    (goto-char (point-max))
+	    (unless (re-search-backward marker nil t)
+	      (goto-char (point-min)))
+	    (when (nnmail-search-unix-mail-delim)
+	      (goto-char (point-min))))
+
+	  ;; Keep track of the active number on our own, and insert it back
+	  ;; into the active list when we're done.  Also, prime the pump to
+	  ;; cut down on the number of searches we do.
+	  (unless (nnmail-search-unix-mail-delim)
+	    (goto-char (point-max)))
+	  (setq end (point-marker))
+	  (while (not (= end (point-max)))
+	    (setq start (marker-position end))
+	    (goto-char end)
+	    ;; There may be more than one "From " line, so we skip past
+	    ;; them.
+	    (while (looking-at delim)
+	      (forward-line 1))
+	    (set-marker end (if (nnmail-search-unix-mail-delim)
+				(point)
+			      (point-max)))
+	    (goto-char start)
+	    (when (not (search-forward marker end t))
+	      (narrow-to-region start end)
+	      (nnmail-insert-lines)
+	      (nnfolder-insert-newsgroup-line
+	       (cons nil (nnfolder-active-number nnfolder-current-group)))
+	      (widen)))
+
+	  (set-marker end nil)
+	  ;; Make absolutely sure that the active list reflects reality!
+	  (nnmail-save-active nnfolder-group-alist nnfolder-active-file)
+	  ;; Set the scantime for this group.
+	  (setq newscantime (visited-file-modtime))
+	  (if scantime
+	      (setcdr scantime (list newscantime))
+	    (push (list nnfolder-current-group newscantime)
+		  nnfolder-scantime-alist))
+	  (current-buffer))))))
+
+;;;###autoload
+(defun nnfolder-generate-active-file ()
+  "Look for mbox folders in the nnfolder directory and make them into groups."
+  (interactive)
+  (nnmail-activate 'nnfolder)
+  (let ((files (directory-files nnfolder-directory))
+        file)
+    (while (setq file (pop files))
+      (when (and (not (backup-file-name-p file))
+                 (message-mail-file-mbox-p
+		  (nnheader-concat nnfolder-directory file)))
+        (let ((oldgroup (assoc file nnfolder-group-alist)))
+          (if oldgroup
+              (nnheader-message 5 "Refreshing group %s..." file)
+            (nnheader-message 5 "Adding group %s..." file))
+          (setq nnfolder-group-alist (remove oldgroup nnfolder-group-alist))
+          (push (list file (cons 1 0)) nnfolder-group-alist)
+          (nnfolder-possibly-change-folder file)
+          (nnfolder-possibly-change-group file)
+          (nnfolder-close-group file))))
+    (message "")))
+
+(defun nnfolder-group-pathname (group)
+  "Make pathname for GROUP."
+  (let ((dir (file-name-as-directory (expand-file-name nnfolder-directory))))
+    ;; If this file exists, we use it directly.
+    (if (or nnmail-use-long-file-names
+	    (file-exists-p (concat dir group)))
+	(concat dir group)
+      ;; If not, we translate dots into slashes.
+      (concat dir (nnheader-replace-chars-in-string group ?. ?/)))))
+
+(defun nnfolder-save-buffer ()
+  "Save the buffer."
+  (when (buffer-modified-p)
+    (run-hooks 'nnfolder-save-buffer-hook)
+    (save-buffer)))
+
+(provide 'nnfolder)
+
+;;; nnfolder.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nngateway.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,80 @@
+;;; nngateway.el --- posting news via mail gateways
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnoo)
+(require 'message)
+
+(nnoo-declare nngateway)
+
+(defvoo nngateway-address nil
+  "Address of the mail-to-news gateway.")
+
+(defvoo nngateway-header-transformation 'nngateway-simple-header-transformation
+  "Function to be called to rewrite the news headers into mail headers.
+It is called narrowed to the headers to be transformed with one
+parameter -- the gateway address.")
+
+;;; Interface functions
+
+(nnoo-define-basics nngateway)
+
+(deffoo nngateway-open-server (server &optional defs)
+  (if (nngateway-server-opened server)
+      t
+    (unless (assq 'nngateway-address defs)
+      (setq defs (append defs (list (list 'nngateway-address server)))))
+    (nnoo-change-server 'nngateway server defs)))
+
+(deffoo nngateway-request-post (&optional server)
+  (when (or (nngateway-server-opened server)
+	    (nngateway-open-server server))
+    ;; Rewrite the header.
+    (let ((buf (current-buffer)))
+      (nnheader-temp-write nil
+	(insert-buffer-substring buf)
+	(message-narrow-to-head)
+	(funcall nngateway-header-transformation nngateway-address)
+	(widen)
+	(let (message-required-mail-headers)
+	  (message-send-mail))))))
+
+;;; Internal functions
+
+(defun nngateway-simple-header-transformation (gateway)
+  "Transform the headers to use GATEWAY."
+  (let ((newsgroups (mail-fetch-field "newsgroups")))
+    (message-remove-header "to")
+    (message-remove-header "cc")
+    (goto-char (point-min))
+    (insert "To: " (nnheader-replace-chars-in-string newsgroups ?. ?-)
+	    "@" gateway "\n")))
+
+(nnoo-define-skeleton nngateway)
+
+(provide 'nngateway)
+
+;;; nngateway.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnheader.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,820 @@
+;;; nnheader.el --- header access macros for Gnus and its backends
+;; Copyright (C) 1987,88,89,90,93,94,95,96,97 Free Software Foundation, Inc.
+
+;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; 	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; These macros may look very much like the ones in GNUS 4.1.  They
+;; are, in a way, but you should note that the indices they use have
+;; been changed from the internal GNUS format to the NOV format.  The
+;; makes it possible to read headers from XOVER much faster.
+;;
+;; The format of a header is now:
+;; [number subject from date id references chars lines xref]
+;;
+;; (That last entry is defined as "misc" in the NOV format, but Gnus
+;; uses it for xrefs.)
+
+;;; Code:
+
+(require 'mail-utils)
+
+(defvar nnheader-max-head-length 4096
+  "*Max length of the head of articles.")
+
+(defvar nnheader-head-chop-length 2048
+  "*Length of each read operation when trying to fetch HEAD headers.")
+
+(defvar nnheader-file-name-translation-alist nil
+  "*Alist that says how to translate characters in file names.
+For instance, if \":\" is illegal as a file character in file names
+on your system, you could say something like:
+
+\(setq nnheader-file-name-translation-alist '((?: . ?_)))")
+
+(eval-and-compile
+ (autoload 'nnmail-message-id "nnmail")
+ (autoload 'mail-position-on-field "sendmail")
+ (autoload 'message-remove-header "message")
+ (autoload 'cancel-function-timers "timers")
+ (autoload 'gnus-point-at-eol "gnus-util"))
+
+;;; Header access macros.
+
+(defmacro mail-header-number (header)
+  "Return article number in HEADER."
+  `(aref ,header 0))
+
+(defmacro mail-header-set-number (header number)
+  "Set article number of HEADER to NUMBER."
+  `(aset ,header 0 ,number))
+
+(defmacro mail-header-subject (header)
+  "Return subject string in HEADER."
+  `(aref ,header 1))
+
+(defmacro mail-header-set-subject (header subject)
+  "Set article subject of HEADER to SUBJECT."
+  `(aset ,header 1 ,subject))
+
+(defmacro mail-header-from (header)
+  "Return author string in HEADER."
+  `(aref ,header 2))
+
+(defmacro mail-header-set-from (header from)
+  "Set article author of HEADER to FROM."
+  `(aset ,header 2 ,from))
+
+(defmacro mail-header-date (header)
+  "Return date in HEADER."
+  `(aref ,header 3))
+
+(defmacro mail-header-set-date (header date)
+  "Set article date of HEADER to DATE."
+  `(aset ,header 3 ,date))
+
+(defalias 'mail-header-message-id 'mail-header-id)
+(defmacro mail-header-id (header)
+  "Return Id in HEADER."
+  `(aref ,header 4))
+
+(defalias 'mail-header-set-message-id 'mail-header-set-id)
+(defmacro mail-header-set-id (header id)
+  "Set article Id of HEADER to ID."
+  `(aset ,header 4 ,id))
+
+(defmacro mail-header-references (header)
+  "Return references in HEADER."
+  `(aref ,header 5))
+
+(defmacro mail-header-set-references (header ref)
+  "Set article references of HEADER to REF."
+  `(aset ,header 5 ,ref))
+
+(defmacro mail-header-chars (header)
+  "Return number of chars of article in HEADER."
+  `(aref ,header 6))
+
+(defmacro mail-header-set-chars (header chars)
+  "Set number of chars in article of HEADER to CHARS."
+  `(aset ,header 6 ,chars))
+
+(defmacro mail-header-lines (header)
+  "Return lines in HEADER."
+  `(aref ,header 7))
+
+(defmacro mail-header-set-lines (header lines)
+  "Set article lines of HEADER to LINES."
+  `(aset ,header 7 ,lines))
+
+(defmacro mail-header-xref (header)
+  "Return xref string in HEADER."
+  `(aref ,header 8))
+
+(defmacro mail-header-set-xref (header xref)
+  "Set article xref of HEADER to xref."
+  `(aset ,header 8 ,xref))
+
+(defun make-mail-header (&optional init)
+  "Create a new mail header structure initialized with INIT."
+  (make-vector 9 init))
+
+(defun make-full-mail-header (&optional number subject from date id
+					references chars lines xref)
+  "Create a new mail header structure initialized with the parameters given."
+  (vector number subject from date id references chars lines xref))
+
+;; fake message-ids: generation and detection
+
+(defvar nnheader-fake-message-id 1)
+
+(defsubst nnheader-generate-fake-message-id ()
+  (concat "fake+none+" (int-to-string (incf nnheader-fake-message-id))))
+
+(defsubst nnheader-fake-message-id-p (id)
+  (save-match-data			; regular message-id's are <.*>
+    (string-match "\\`fake\\+none\\+[0-9]+\\'" id)))
+
+;; Parsing headers and NOV lines.
+
+(defsubst nnheader-header-value ()
+  (buffer-substring (match-end 0) (gnus-point-at-eol)))
+
+(defun nnheader-parse-head (&optional naked)
+  (let ((case-fold-search t)
+	(cur (current-buffer))
+	(buffer-read-only nil)
+	in-reply-to lines p)
+    (goto-char (point-min))
+    (when naked
+      (insert "\n"))
+    ;; Search to the beginning of the next header.  Error messages
+    ;; do not begin with 2 or 3.
+    (prog1
+	(when (or naked (re-search-forward "^[23][0-9]+ " nil t))
+	  ;; This implementation of this function, with nine
+	  ;; search-forwards instead of the one re-search-forward and
+	  ;; a case (which basically was the old function) is actually
+	  ;; about twice as fast, even though it looks messier.	 You
+	  ;; can't have everything, I guess.  Speed and elegance
+	  ;; don't always go hand in hand.
+	  (vector
+	   ;; Number.
+	   (if naked
+	       (progn
+		 (setq p (point-min))
+		 0)
+	     (prog1
+		 (read cur)
+	       (end-of-line)
+	       (setq p (point))
+	       (narrow-to-region (point)
+				 (or (and (search-forward "\n.\n" nil t)
+					  (- (point) 2))
+				     (point)))))
+	   ;; Subject.
+	   (progn
+	     (goto-char p)
+	     (if (search-forward "\nsubject: " nil t)
+		 (nnheader-header-value) "(none)"))
+	   ;; From.
+	   (progn
+	     (goto-char p)
+	     (if (search-forward "\nfrom: " nil t)
+		 (nnheader-header-value) "(nobody)"))
+	   ;; Date.
+	   (progn
+	     (goto-char p)
+	     (if (search-forward "\ndate: " nil t)
+		 (nnheader-header-value) ""))
+	   ;; Message-ID.
+	   (progn
+	     (goto-char p)
+	     (if (search-forward "\nmessage-id:" nil t)
+		 (buffer-substring
+		  (1- (or (search-forward "<" nil t) (point)))
+		  (or (search-forward ">" nil t) (point)))
+	       ;; If there was no message-id, we just fake one to make
+	       ;; subsequent routines simpler.
+	       (nnheader-generate-fake-message-id)))
+	   ;; References.
+	   (progn
+	     (goto-char p)
+	     (if (search-forward "\nreferences: " nil t)
+		 (nnheader-header-value)
+	       ;; Get the references from the in-reply-to header if there
+	       ;; were no references and the in-reply-to header looks
+	       ;; promising.
+	       (if (and (search-forward "\nin-reply-to: " nil t)
+			(setq in-reply-to (nnheader-header-value))
+			(string-match "<[^>]+>" in-reply-to))
+		   (substring in-reply-to (match-beginning 0)
+			      (match-end 0))
+		 "")))
+	   ;; Chars.
+	   0
+	   ;; Lines.
+	   (progn
+	     (goto-char p)
+	     (if (search-forward "\nlines: " nil t)
+		 (if (numberp (setq lines (read cur)))
+		     lines 0)
+	       0))
+	   ;; Xref.
+	   (progn
+	     (goto-char p)
+	     (and (search-forward "\nxref: " nil t)
+		  (nnheader-header-value)))))
+      (when naked
+	(goto-char (point-min))
+	(delete-char 1)))))
+
+(defmacro nnheader-nov-skip-field ()
+  '(search-forward "\t" eol 'move))
+
+(defmacro nnheader-nov-field ()
+  '(buffer-substring (point) (if (nnheader-nov-skip-field) (1- (point)) eol)))
+
+(defmacro nnheader-nov-read-integer ()
+  '(prog1
+       (if (= (following-char) ?\t)
+	   0
+	 (let ((num (ignore-errors (read (current-buffer)))))
+	   (if (numberp num) num 0)))
+     (or (eobp) (forward-char 1))))
+
+;; (defvar nnheader-none-counter 0)
+
+(defun nnheader-parse-nov ()
+  (let ((eol (gnus-point-at-eol)))
+    (vector
+     (nnheader-nov-read-integer)	; number
+     (nnheader-nov-field)		; subject
+     (nnheader-nov-field)		; from
+     (nnheader-nov-field)		; date
+     (or (nnheader-nov-field)
+	 (nnheader-generate-fake-message-id)) ; id
+     (nnheader-nov-field)		; refs
+     (nnheader-nov-read-integer)	; chars
+     (nnheader-nov-read-integer)	; lines
+     (if (= (following-char) ?\n)
+	 nil
+       (nnheader-nov-field))		; misc
+     )))
+
+(defun nnheader-insert-nov (header)
+  (princ (mail-header-number header) (current-buffer))
+  (insert
+   "\t"
+   (or (mail-header-subject header) "(none)") "\t"
+   (or (mail-header-from header) "(nobody)") "\t"
+   (or (mail-header-date header) "") "\t"
+   (or (mail-header-id header)
+       (nnmail-message-id))
+   "\t"
+   (or (mail-header-references header) "") "\t")
+  (princ (or (mail-header-chars header) 0) (current-buffer))
+  (insert "\t")
+  (princ (or (mail-header-lines header) 0) (current-buffer))
+  (insert "\t")
+  (when (mail-header-xref header)
+    (insert "Xref: " (mail-header-xref header) "\t"))
+  (insert "\n"))
+
+(defun nnheader-insert-article-line (article)
+  (goto-char (point-min))
+  (insert "220 ")
+  (princ article (current-buffer))
+  (insert " Article retrieved.\n")
+  (search-forward "\n\n" nil 'move)
+  (delete-region (point) (point-max))
+  (forward-char -1)
+  (insert "."))
+
+(defun nnheader-nov-delete-outside-range (beg end)
+  "Delete all NOV lines that lie outside the BEG to END range."
+  ;; First we find the first wanted line.
+  (nnheader-find-nov-line beg)
+  (delete-region (point-min) (point))
+  ;; Then we find the last wanted line.
+  (when (nnheader-find-nov-line end)
+    (forward-line 1))
+  (delete-region (point) (point-max)))
+
+(defun nnheader-find-nov-line (article)
+  "Put point at the NOV line that start with ARTICLE.
+If ARTICLE doesn't exist, put point where that line
+would have been.  The function will return non-nil if
+the line could be found."
+  ;; This function basically does a binary search.
+  (let ((max (point-max))
+	(min (goto-char (point-min)))
+	(cur (current-buffer))
+	(prev (point-min))
+	num found)
+    (while (not found)
+      (goto-char (/ (+ max min) 2))
+      (beginning-of-line)
+      (if (or (= (point) prev)
+	      (eobp))
+	  (setq found t)
+	(setq prev (point))
+	(cond ((> (setq num (read cur)) article)
+	       (setq max (point)))
+	      ((< num article)
+	       (setq min (point)))
+	      (t
+	       (setq found 'yes)))))
+    ;; We may be at the first line.
+    (when (and (not num)
+	       (not (eobp)))
+      (setq num (read cur)))
+    ;; Now we may have found the article we're looking for, or we
+    ;; may be somewhere near it.
+    (when (and (not (eq found 'yes))
+	       (not (eq num article)))
+      (setq found (point))
+      (while (and (< (point) max)
+		  (or (not (numberp num))
+		      (< num article)))
+	(forward-line 1)
+	(setq found (point))
+	(or (eobp)
+	    (= (setq num (read cur)) article)))
+      (unless (eq num article)
+	(goto-char found)))
+    (beginning-of-line)
+    (eq num article)))
+
+;; Various cruft the backends and Gnus need to communicate.
+
+(defvar nntp-server-buffer nil)
+(defvar gnus-verbose-backends 7
+  "*A number that says how talkative the Gnus backends should be.")
+(defvar gnus-nov-is-evil nil
+  "If non-nil, Gnus backends will never output headers in the NOV format.")
+(defvar news-reply-yank-from nil)
+(defvar news-reply-yank-message-id nil)
+
+(defvar nnheader-callback-function nil)
+
+(defun nnheader-init-server-buffer ()
+  "Initialize the Gnus-backend communication buffer."
+  (save-excursion
+    (unless (gnus-buffer-live-p nntp-server-buffer)
+      (setq nntp-server-buffer (get-buffer-create " *nntpd*")))
+    (set-buffer nntp-server-buffer)
+    (buffer-disable-undo (current-buffer))
+    (erase-buffer)
+    (kill-all-local-variables)
+    (setq case-fold-search t)		;Should ignore case.
+    t))
+
+;;; Various functions the backends use.
+
+(defun nnheader-file-error (file)
+  "Return a string that says what is wrong with FILE."
+  (format
+   (cond
+    ((not (file-exists-p file))
+     "%s does not exist")
+    ((file-directory-p file)
+     "%s is a directory")
+    ((not (file-readable-p file))
+     "%s is not readable"))
+   file))
+
+(defun nnheader-insert-head (file)
+  "Insert the head of the article."
+  (when (file-exists-p file)
+    (if (eq nnheader-max-head-length t)
+	;; Just read the entire file.
+	(nnheader-insert-file-contents file)
+      ;; Read 1K blocks until we find a separator.
+      (let ((beg 0)
+	    format-alist)
+	(while (and (eq nnheader-head-chop-length
+			(nth 1 (nnheader-insert-file-contents
+				file nil beg
+				(incf beg nnheader-head-chop-length))))
+		    (prog1 (not (search-forward "\n\n" nil t))
+		      (goto-char (point-max)))
+		    (or (null nnheader-max-head-length)
+			(< beg nnheader-max-head-length))))))
+    t))
+
+(defun nnheader-article-p ()
+  "Say whether the current buffer looks like an article."
+  (goto-char (point-min))
+  (if (not (search-forward "\n\n" nil t))
+      nil
+    (narrow-to-region (point-min) (1- (point)))
+    (goto-char (point-min))
+    (while (looking-at "[A-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n")
+      (goto-char (match-end 0)))
+    (prog1
+	(eobp)
+      (widen))))
+
+(defun nnheader-insert-references (references message-id)
+  "Insert a References header based on REFERENCES and MESSAGE-ID."
+  (if (and (not references) (not message-id))
+      ()				; This is illegal, but not all articles have Message-IDs.
+    (mail-position-on-field "References")
+    (let ((begin (save-excursion (beginning-of-line) (point)))
+	  (fill-column 78)
+	  (fill-prefix "\t"))
+      (when references
+	(insert references))
+      (when (and references message-id)
+	(insert " "))
+      (when message-id
+	(insert message-id))
+      ;; Fold long References lines to conform to RFC1036 (sort of).
+      ;; The region must end with a newline to fill the region
+      ;; without inserting extra newline.
+      (fill-region-as-paragraph begin (1+ (point))))))
+
+(defun nnheader-replace-header (header new-value)
+  "Remove HEADER and insert the NEW-VALUE."
+  (save-excursion
+    (save-restriction
+      (nnheader-narrow-to-headers)
+      (prog1
+	  (message-remove-header header)
+	(goto-char (point-max))
+	(insert header ": " new-value "\n")))))
+
+(defun nnheader-narrow-to-headers ()
+  "Narrow to the head of an article."
+  (widen)
+  (narrow-to-region
+   (goto-char (point-min))
+   (if (search-forward "\n\n" nil t)
+       (1- (point))
+     (point-max)))
+  (goto-char (point-min)))
+
+(defun nnheader-set-temp-buffer (name &optional noerase)
+  "Set-buffer to an empty (possibly new) buffer called NAME with undo disabled."
+  (set-buffer (get-buffer-create name))
+  (buffer-disable-undo (current-buffer))
+  (unless noerase
+    (erase-buffer))
+  (current-buffer))
+
+(defmacro nnheader-temp-write (file &rest forms)
+  "Create a new buffer, evaluate FORMS there, and write the buffer to FILE.
+Return the value of FORMS.
+If FILE is nil, just evaluate FORMS and don't save anything.
+If FILE is t, return the buffer contents as a string."
+  (let ((temp-file (make-symbol "temp-file"))
+	(temp-buffer (make-symbol "temp-buffer"))
+	(temp-results (make-symbol "temp-results")))
+    `(save-excursion
+       (let* ((,temp-file ,file)
+	      (default-major-mode 'fundamental-mode)
+	      (,temp-buffer
+	       (set-buffer
+		(get-buffer-create
+		 (generate-new-buffer-name " *nnheader temp*"))))
+	      ,temp-results)
+	 (unwind-protect
+	     (progn
+	       (setq ,temp-results (progn ,@forms))
+	       (cond
+		;; Don't save anything.
+		((null ,temp-file)
+		 ,temp-results)
+		;; Return the buffer contents.
+		((eq ,temp-file t)
+		 (set-buffer ,temp-buffer)
+		 (buffer-string))
+		;; Save a file.
+		(t
+		 (set-buffer ,temp-buffer)
+		 ;; Make sure the directory where this file is
+		 ;; to be saved exists.
+		 (when (not (file-directory-p
+			     (file-name-directory ,temp-file)))
+		   (make-directory (file-name-directory ,temp-file) t))
+		 ;; Save the file.
+		 (write-region (point-min) (point-max)
+			       ,temp-file nil 'nomesg)
+		 ,temp-results)))
+	   ;; Kill the buffer.
+	   (when (buffer-name ,temp-buffer)
+	     (kill-buffer ,temp-buffer)))))))
+
+(put 'nnheader-temp-write 'lisp-indent-function 1)
+(put 'nnheader-temp-write 'edebug-form-spec '(form body))
+
+(defvar jka-compr-compression-info-list)
+(defvar nnheader-numerical-files
+  (if (boundp 'jka-compr-compression-info-list)
+      (concat "\\([0-9]+\\)\\("
+	      (mapconcat (lambda (i) (aref i 0))
+			 jka-compr-compression-info-list "\\|")
+	      "\\)?")
+    "[0-9]+$")
+  "Regexp that match numerical files.")
+
+(defvar nnheader-numerical-short-files (concat "^" nnheader-numerical-files)
+  "Regexp that matches numerical file names.")
+
+(defvar nnheader-numerical-full-files (concat "/" nnheader-numerical-files)
+  "Regexp that matches numerical full file paths.")
+
+(defsubst nnheader-file-to-number (file)
+  "Take a file name and return the article number."
+  (if (not (boundp 'jka-compr-compression-info-list))
+      (string-to-int file)
+    (string-match nnheader-numerical-short-files file)
+    (string-to-int (match-string 0 file))))
+
+(defun nnheader-directory-files-safe (&rest args)
+  ;; It has been reported numerous times that `directory-files'
+  ;; fails with an alarming frequency on NFS mounted file systems.
+  ;; This function executes that function twice and returns
+  ;; the longest result.
+  (let ((first (apply 'directory-files args))
+	(second (apply 'directory-files args)))
+    (if (> (length first) (length second))
+	first
+      second)))
+
+(defun nnheader-directory-articles (dir)
+  "Return a list of all article files in a directory."
+  (mapcar 'nnheader-file-to-number
+	  (nnheader-directory-files-safe
+	   dir nil nnheader-numerical-short-files t)))
+
+(defun nnheader-article-to-file-alist (dir)
+  "Return an alist of article/file pairs in DIR."
+  (mapcar (lambda (file) (cons (nnheader-file-to-number file) file))
+	  (nnheader-directory-files-safe
+	   dir nil nnheader-numerical-short-files t)))
+
+(defun nnheader-fold-continuation-lines ()
+  "Fold continuation lines in the current buffer."
+  (nnheader-replace-regexp "\\(\r?\n[ \t]+\\)+" " "))
+
+(defun nnheader-translate-file-chars (file)
+  (if (null nnheader-file-name-translation-alist)
+      ;; No translation is necessary.
+      file
+    ;; We translate -- but only the file name.  We leave the directory
+    ;; alone.
+    (let* ((i 0)
+	   trans leaf path len)
+      (if (string-match "/[^/]+\\'" file)
+	  ;; This is needed on NT's and stuff.
+	  (setq leaf (substring file (1+ (match-beginning 0)))
+		path (substring file 0 (1+ (match-beginning 0))))
+	;; Fall back on this.
+	(setq leaf (file-name-nondirectory file)
+	      path (file-name-directory file)))
+      (setq len (length leaf))
+      (while (< i len)
+	(when (setq trans (cdr (assq (aref leaf i)
+				     nnheader-file-name-translation-alist)))
+	  (aset leaf i trans))
+	(incf i))
+      (concat path leaf))))
+
+(defun nnheader-report (backend &rest args)
+  "Report an error from the BACKEND.
+The first string in ARGS can be a format string."
+  (set (intern (format "%s-status-string" backend))
+       (if (< (length args) 2)
+	   (car args)
+	 (apply 'format args)))
+  nil)
+
+(defun nnheader-get-report (backend)
+  "Get the most recent report from BACKEND."
+  (condition-case ()
+      (message "%s" (symbol-value (intern (format "%s-status-string"
+						  backend))))
+    (error (message ""))))
+
+(defun nnheader-insert (format &rest args)
+  "Clear the communication buffer and insert FORMAT and ARGS into the buffer.
+If FORMAT isn't a format string, it and all ARGS will be inserted
+without formatting."
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (if (string-match "%" format)
+	(insert (apply 'format format args))
+      (apply 'insert format args))
+    t))
+
+(defun nnheader-replace-chars-in-string (string from to)
+  "Replace characters in STRING from FROM to TO."
+  (let ((string (substring string 0))	;Copy string.
+	(len (length string))
+	(idx 0))
+    ;; Replace all occurrences of FROM with TO.
+    (while (< idx len)
+      (when (= (aref string idx) from)
+	(aset string idx to))
+      (setq idx (1+ idx)))
+    string))
+
+(defun nnheader-file-to-group (file &optional top)
+  "Return a group name based on FILE and TOP."
+  (nnheader-replace-chars-in-string
+   (if (not top)
+       file
+     (condition-case ()
+	 (substring (expand-file-name file)
+		    (length
+		     (expand-file-name
+		      (file-name-as-directory top))))
+       (error "")))
+   ?/ ?.))
+
+(defun nnheader-message (level &rest args)
+  "Message if the Gnus backends are talkative."
+  (if (or (not (numberp gnus-verbose-backends))
+	  (<= level gnus-verbose-backends))
+      (apply 'message args)
+    (apply 'format args)))
+
+(defun nnheader-be-verbose (level)
+  "Return whether the backends should be verbose on LEVEL."
+  (or (not (numberp gnus-verbose-backends))
+      (<= level gnus-verbose-backends)))
+
+(defun nnheader-group-pathname (group dir &optional file)
+  "Make pathname for GROUP."
+  (concat
+   (let ((dir (file-name-as-directory (expand-file-name dir))))
+     ;; If this directory exists, we use it directly.
+     (if (file-directory-p (concat dir group))
+	 (concat dir group "/")
+       ;; If not, we translate dots into slashes.
+       (concat dir (nnheader-replace-chars-in-string group ?. ?/) "/")))
+   (cond ((null file) "")
+	 ((numberp file) (int-to-string file))
+	 (t file))))
+
+(defun nnheader-functionp (form)
+  "Return non-nil if FORM is funcallable."
+  (or (and (symbolp form) (fboundp form))
+      (and (listp form) (eq (car form) 'lambda))))
+
+(defun nnheader-concat (dir &rest files)
+  "Concat DIR as directory to FILE."
+  (apply 'concat (file-name-as-directory dir) files))
+
+(defun nnheader-ms-strip-cr ()
+  "Strip ^M from the end of all lines."
+  (save-excursion
+    (goto-char (point-min))
+    (while (re-search-forward "\r$" nil t)
+      (delete-backward-char 1))))
+
+(defun nnheader-file-size (file)
+  "Return the file size of FILE or 0."
+  (or (nth 7 (file-attributes file)) 0))
+
+(defun nnheader-find-etc-directory (package &optional file)
+  "Go through the path and find the \".../etc/PACKAGE\" directory.
+If FILE, find the \".../etc/PACKAGE\" file instead."
+  (let ((path load-path)
+	dir result)
+    ;; We try to find the dir by looking at the load path,
+    ;; stripping away the last component and adding "etc/".
+    (while path
+      (if (and (car path)
+	       (file-exists-p
+		(setq dir (concat
+			   (file-name-directory
+			    (directory-file-name (car path)))
+			   "etc/" package
+			   (if file "" "/"))))
+	       (or file (file-directory-p dir)))
+	  (setq result dir
+		path nil)
+	(setq path (cdr path))))
+    result))
+
+(defvar ange-ftp-path-format)
+(defvar efs-path-regexp)
+(defun nnheader-re-read-dir (path)
+  "Re-read directory PATH if PATH is on a remote system."
+  (if (and (fboundp 'efs-re-read-dir) (boundp 'efs-path-regexp))
+      (when (string-match efs-path-regexp path)
+	(efs-re-read-dir path))
+    (when (and (fboundp 'ange-ftp-re-read-dir) (boundp 'ange-ftp-path-format))
+      (when (string-match (car ange-ftp-path-format) path)
+	(ange-ftp-re-read-dir path)))))
+
+(defun nnheader-insert-file-contents (filename &optional visit beg end replace)
+  "Like `insert-file-contents', q.v., but only reads in the file.
+A buffer may be modified in several ways after reading into the buffer due
+to advanced Emacs features, such as file-name-handlers, format decoding,
+find-file-hooks, etc.
+  This function ensures that none of these modifications will take place."
+  (let ((format-alist nil)
+	(auto-mode-alist (nnheader-auto-mode-alist))
+	(default-major-mode 'fundamental-mode)
+        (after-insert-file-functions nil))
+    (insert-file-contents filename visit beg end replace)))
+
+(defun nnheader-find-file-noselect (&rest args)
+  (let ((format-alist nil)
+	(auto-mode-alist (nnheader-auto-mode-alist))
+	(default-major-mode 'fundamental-mode)
+	(enable-local-variables nil)
+        (after-insert-file-functions nil))
+    (apply 'find-file-noselect args)))
+
+(defun nnheader-auto-mode-alist ()
+  "Return an `auto-mode-alist' with only the .gz (etc) thingies."
+  (let ((alist auto-mode-alist)
+	out)
+    (while alist
+      (when (listp (cdar alist))
+	(push (car alist) out))
+      (pop alist))
+    (nreverse out)))
+
+(defun nnheader-directory-regular-files (dir)
+  "Return a list of all regular files in DIR."
+  (let ((files (directory-files dir t))
+	out)
+    (while files
+      (when (file-regular-p (car files))
+	(push (car files) out))
+      (pop files))
+    (nreverse out)))
+
+(defmacro nnheader-skeleton-replace (from &optional to regexp)
+  `(let ((new (generate-new-buffer " *nnheader replace*"))
+	 (cur (current-buffer))
+	 (start (point-min)))
+     (set-buffer new)
+     (buffer-disable-undo (current-buffer))
+     (set-buffer cur)
+     (goto-char (point-min))
+     (while (,(if regexp 're-search-forward 'search-forward)
+	     ,from nil t)
+       (insert-buffer-substring
+	cur start (prog1 (match-beginning 0) (set-buffer new)))
+       (goto-char (point-max))
+       ,(when to `(insert ,to))
+       (set-buffer cur)
+       (setq start (point)))
+     (insert-buffer-substring
+      cur start (prog1 (point-max) (set-buffer new)))
+     (copy-to-buffer cur (point-min) (point-max))
+     (kill-buffer (current-buffer))
+     (set-buffer cur)))
+
+(defun nnheader-replace-string (from to)
+  "Do a fast replacement of FROM to TO from point to point-max."
+  (nnheader-skeleton-replace from to))
+
+(defun nnheader-replace-regexp (from to)
+  "Do a fast regexp replacement of FROM to TO from point to point-max."
+  (nnheader-skeleton-replace from to t))
+
+(defun nnheader-strip-cr ()
+  "Strip all \r's from the current buffer."
+  (nnheader-skeleton-replace "\r"))
+
+(fset 'nnheader-run-at-time 'run-at-time)
+(fset 'nnheader-cancel-timer 'cancel-timer)
+(fset 'nnheader-cancel-function-timers 'cancel-function-timers)
+
+(when (string-match "XEmacs\\|Lucid" emacs-version)
+  (require 'nnheaderxm))
+
+(run-hooks 'nnheader-load-hook)
+
+(provide 'nnheader)
+
+;;; nnheader.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnkiboze.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,349 @@
+;;; nnkiboze.el --- select virtual news access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; The other access methods (nntp, nnspool, etc) are general news
+;; access methods.  This module relies on Gnus and can't be used
+;; separately.
+
+;;; Code:
+
+(require 'nntp)
+(require 'nnheader)
+(require 'gnus)
+(require 'gnus-score)
+(require 'nnoo)
+(eval-when-compile (require 'cl))
+
+(nnoo-declare nnkiboze)
+(defvoo nnkiboze-directory (nnheader-concat gnus-directory "kiboze/")
+  "nnkiboze will put its files in this directory.")
+
+(defvoo nnkiboze-level 9
+  "The maximum level to be searched for articles.")
+
+(defvoo nnkiboze-remove-read-articles t
+  "If non-nil, nnkiboze will remove read articles from the kiboze group.")
+
+(defvoo nnkiboze-ephemeral nil
+  "If non-nil, don't store any data anywhere.")
+
+(defvoo nnkiboze-scores nil
+  "Score rules for generating the nnkiboze group.")
+
+(defvoo nnkiboze-regexp nil
+  "Regexp for matching component groups.")
+
+
+
+(defconst nnkiboze-version "nnkiboze 1.0")
+
+(defvoo nnkiboze-current-group nil)
+(defvoo nnkiboze-status-string "")
+
+(defvoo nnkiboze-headers nil)
+
+
+
+;;; Interface functions.
+
+(nnoo-define-basics nnkiboze)
+
+(deffoo nnkiboze-retrieve-headers (articles &optional group server fetch-old)
+  (nnkiboze-possibly-change-group group)
+  (unless gnus-nov-is-evil
+    (if (stringp (car articles))
+	'headers
+      (let ((nov (nnkiboze-nov-file-name)))
+	(when (file-exists-p nov)
+	  (save-excursion
+	    (set-buffer nntp-server-buffer)
+	    (erase-buffer)
+	    (nnheader-insert-file-contents nov)
+	    (nnheader-nov-delete-outside-range
+	     (car articles) (car (last articles)))
+	    'nov))))))
+
+(deffoo nnkiboze-request-article (article &optional newsgroup server buffer)
+  (nnkiboze-possibly-change-group newsgroup)
+  (if (not (numberp article))
+      ;; This is a real kludge.  It might not work at times, but it
+      ;; does no harm I think.  The only alternative is to offer no
+      ;; article fetching by message-id at all.
+      (nntp-request-article article newsgroup gnus-nntp-server buffer)
+    (let* ((header (gnus-summary-article-header article))
+	   (xref (mail-header-xref header)))
+      (unless xref
+	(error "nnkiboze: No xref"))
+      (unless (string-match " \\([^ ]+\\):\\([0-9]+\\)" xref)
+	(error "nnkiboze: Malformed xref"))
+      (gnus-request-article (string-to-int (match-string 2 xref))
+			    (match-string 1 xref)
+			    buffer))))
+
+(deffoo nnkiboze-request-scan (&optional group server)
+  (nnkiboze-generate-group (concat "nnkiboze:" group)))
+
+(deffoo nnkiboze-request-group (group &optional server dont-check)
+  "Make GROUP the current newsgroup."
+  (nnkiboze-possibly-change-group group)
+  (if dont-check
+      t
+    (let ((nov-file (nnkiboze-nov-file-name))
+	  beg end total)
+      (save-excursion
+	(set-buffer nntp-server-buffer)
+	(erase-buffer)
+	(if (not (file-exists-p nov-file))
+	    (nnheader-report 'nnkiboze "Can't select group %s" group)
+	  (nnheader-insert-file-contents nov-file)
+	  (if (zerop (buffer-size))
+	      (nnheader-insert "211 0 0 0 %s\n" group)
+	    (goto-char (point-min))
+	    (when (looking-at "[0-9]+")
+	      (setq beg (read (current-buffer))))
+	    (goto-char (point-max))
+	    (when (re-search-backward "^[0-9]" nil t)
+	      (setq end (read (current-buffer))))
+	    (setq total (count-lines (point-min) (point-max)))
+	    (nnheader-insert "211 %d %d %d %s\n" total beg end group)))))))
+
+(deffoo nnkiboze-close-group (group &optional server)
+  (nnkiboze-possibly-change-group group)
+  ;; Remove NOV lines of articles that are marked as read.
+  (when (and (file-exists-p (nnkiboze-nov-file-name))
+	     nnkiboze-remove-read-articles)
+    (nnheader-temp-write (nnkiboze-nov-file-name)
+      (let ((cur (current-buffer)))
+	(nnheader-insert-file-contents (nnkiboze-nov-file-name))
+	(goto-char (point-min))
+	(while (not (eobp))
+	  (if (not (gnus-article-read-p (read cur)))
+	      (forward-line 1)
+	    (gnus-delete-line))))))
+  (setq nnkiboze-current-group nil))
+
+(deffoo nnkiboze-open-server (server &optional defs)
+  (unless (assq 'nnkiboze-regexp defs)
+    (push `(nnkiboze-regexp ,server)
+	  defs))
+  (nnoo-change-server 'nnkiboze server defs))
+
+(deffoo nnkiboze-request-delete-group (group &optional force server)
+  (nnkiboze-possibly-change-group group)
+  (when force
+     (let ((files (list (nnkiboze-nov-file-name)
+			(concat nnkiboze-directory group ".newsrc")
+			(nnkiboze-score-file group))))
+       (while files
+	 (and (file-exists-p (car files))
+	      (file-writable-p (car files))
+	      (delete-file (car files)))
+	 (setq files (cdr files)))))
+  (setq nnkiboze-current-group nil))
+
+(nnoo-define-skeleton nnkiboze)
+
+
+;;; Internal functions.
+
+(defun nnkiboze-possibly-change-group (group)
+  (setq nnkiboze-current-group group))
+
+(defun nnkiboze-prefixed-name (group)
+  (gnus-group-prefixed-name group '(nnkiboze "")))
+
+;;;###autoload
+(defun nnkiboze-generate-groups ()
+  "Usage: emacs -batch -l nnkiboze -f nnkiboze-generate-groups
+Finds out what articles are to be part of the nnkiboze groups."
+  (interactive)
+  (let ((nnmail-spool-file nil)
+	(gnus-use-dribble-file nil)
+	(gnus-read-active-file t)
+	(gnus-expert-user t))
+    (gnus))
+  (let* ((gnus-newsrc-alist (gnus-copy-sequence gnus-newsrc-alist))
+	 (newsrc (cdr gnus-newsrc-alist))
+	 gnus-newsrc-hashtb info)
+    (gnus-make-hashtable-from-newsrc-alist)
+    ;; We have copied all the newsrc alist info over to local copies
+    ;; so that we can mess all we want with these lists.
+    (while (setq info (pop newsrc))
+      (when (string-match "nnkiboze" (gnus-info-group info))
+	;; For each kiboze group, we call this function to generate
+	;; it.
+	(nnkiboze-generate-group (gnus-info-group info))))))
+
+(defun nnkiboze-score-file (group)
+  (list (expand-file-name
+	 (concat (file-name-as-directory gnus-kill-files-directory)
+		 (nnheader-translate-file-chars
+		  (concat (nnkiboze-prefixed-name nnkiboze-current-group)
+			  "." gnus-score-file-suffix))))))
+
+(defun nnkiboze-generate-group (group)
+  (let* ((info (nth 2 (gnus-gethash group gnus-newsrc-hashtb)))
+	 (newsrc-file (concat nnkiboze-directory group ".newsrc"))
+	 (nov-file (concat nnkiboze-directory group ".nov"))
+	 method nnkiboze-newsrc gname newsrc active
+	 ginfo lowest glevel orig-info nov-buffer
+	 ;; Bind various things to nil to make group entry faster.
+	 (gnus-expert-user t)
+	 (gnus-large-newsgroup nil)
+	 (gnus-score-find-score-files-function 'nnkiboze-score-file)
+	 (gnus-verbose (min gnus-verbose 3))
+ 	 gnus-select-group-hook gnus-summary-prepare-hook
+	 gnus-thread-sort-functions gnus-show-threads
+	 gnus-visual gnus-suppress-duplicates)
+    (unless info
+      (error "No such group: %s" group))
+    ;; Load the kiboze newsrc file for this group.
+    (when (file-exists-p newsrc-file)
+      (load newsrc-file))
+    (nnheader-temp-write nov-file
+      (when (file-exists-p nov-file)
+	(insert-file-contents nov-file))
+      (setq nov-buffer (current-buffer))
+      ;; Go through the active hashtb and add new all groups that match the
+      ;; kiboze regexp.
+      (mapatoms
+       (lambda (group)
+	 (and (string-match nnkiboze-regexp
+			    (setq gname (symbol-name group))) ; Match
+	      (not (assoc gname nnkiboze-newsrc)) ; It isn't registered
+	      (numberp (car (symbol-value group))) ; It is active
+	      (or (> nnkiboze-level 7)
+		  (and (setq glevel (nth 1 (nth 2 (gnus-gethash
+						   gname gnus-newsrc-hashtb))))
+		       (>= nnkiboze-level glevel)))
+	      (not (string-match "^nnkiboze:" gname)) ; Exclude kibozes
+	      (push (cons gname (1- (car (symbol-value group))))
+		    nnkiboze-newsrc)))
+       gnus-active-hashtb)
+      ;; `newsrc' is set to the list of groups that possibly are
+      ;; component groups to this kiboze group.  This list has elements
+      ;; on the form `(GROUP . NUMBER)', where NUMBER is the highest
+      ;; number that has been kibozed in GROUP in this kiboze group.
+      (setq newsrc nnkiboze-newsrc)
+      (while newsrc
+	(if (not (setq active (gnus-gethash
+			       (caar newsrc) gnus-active-hashtb)))
+	    ;; This group isn't active after all, so we remove it from
+	    ;; the list of component groups.
+	    (setq nnkiboze-newsrc (delq (car newsrc) nnkiboze-newsrc))
+	  (setq lowest (cdar newsrc))
+	  ;; Ok, we have a valid component group, so we jump to it.
+	  (switch-to-buffer gnus-group-buffer)
+	  (gnus-group-jump-to-group (caar newsrc))
+	  (gnus-message 3 "nnkiboze: Checking %s..." (caar newsrc))
+	  (setq ginfo (gnus-get-info (gnus-group-group-name))
+		orig-info (gnus-copy-sequence ginfo))
+	  (unwind-protect
+	      (progn
+		;; We set all list of article marks to nil.  Since we operate
+		;; on copies of the real lists, we can destroy anything we
+		;; want here.
+		(when (nth 3 ginfo)
+		  (setcar (nthcdr 3 ginfo) nil))
+		;; We set the list of read articles to be what we expect for
+		;; this kiboze group -- either nil or `(1 . LOWEST)'.
+		(when ginfo
+		  (setcar (nthcdr 2 ginfo)
+			  (and (not (= lowest 1)) (cons 1 lowest))))
+		(when (and (or (not ginfo)
+			       (> (length (gnus-list-of-unread-articles
+					   (car ginfo)))
+				  0))
+			   (progn
+			     (gnus-group-select-group nil)
+			     (eq major-mode 'gnus-summary-mode)))
+		  ;; We are now in the group where we want to be.
+		  (setq method (gnus-find-method-for-group
+				gnus-newsgroup-name))
+		  (when (eq method gnus-select-method)
+		    (setq method nil))
+		  ;; We go through the list of scored articles.
+		  (while gnus-newsgroup-scored
+		    (when (> (caar gnus-newsgroup-scored) lowest)
+		      ;; If it has a good score, then we enter this article
+		      ;; into the kiboze group.
+		      (nnkiboze-enter-nov
+		       nov-buffer
+		       (gnus-summary-article-header
+			(caar gnus-newsgroup-scored))
+		       gnus-newsgroup-name))
+		    (setq gnus-newsgroup-scored (cdr gnus-newsgroup-scored)))
+		  ;; That's it.  We exit this group.
+		  (gnus-summary-exit-no-update)))
+	    ;; Restore the proper info.
+	    (when ginfo
+	      (setcdr ginfo (cdr orig-info)))))
+	(setcdr (car newsrc) (car active))
+	(gnus-message 3 "nnkiboze: Checking %s...done" (caar newsrc))
+	(setq newsrc (cdr newsrc))))
+    ;; We save the kiboze newsrc for this group.
+    (nnheader-temp-write newsrc-file
+      (insert "(setq nnkiboze-newsrc '")
+      (gnus-prin1 nnkiboze-newsrc)
+      (insert ")\n"))
+    t))
+
+(defun nnkiboze-enter-nov (buffer header group)
+  (save-excursion
+    (set-buffer buffer)
+    (goto-char (point-max))
+    (let ((xref (mail-header-xref header))
+	  (prefix (gnus-group-real-prefix group))
+	  (oheader (copy-sequence header))
+	  (first t)
+	  article)
+      (if (zerop (forward-line -1))
+	  (progn
+	    (setq article (1+ (read (current-buffer))))
+	    (forward-line 1))
+	(setq article 1))
+      (mail-header-set-number oheader article)
+      (nnheader-insert-nov oheader)
+      (search-backward "\t" nil t 2)
+      (if (re-search-forward " [^ ]+:[0-9]+" nil t)
+	  (goto-char (match-beginning 0))
+	(forward-char 1))
+      ;; The first Xref has to be the group this article
+      ;; really came for - this is the article nnkiboze
+      ;; will request when it is asked for the article.
+      (insert group ":"
+	      (int-to-string (mail-header-number header)) " ")
+      (while (re-search-forward " [^ ]+:[0-9]+" nil t)
+	(goto-char (1+ (match-beginning 0)))
+	(insert prefix)))))
+
+(defun nnkiboze-nov-file-name ()
+  (concat (file-name-as-directory nnkiboze-directory)
+	  (nnheader-translate-file-chars
+	   (concat (nnkiboze-prefixed-name nnkiboze-current-group) ".nov"))))
+
+(provide 'nnkiboze)
+
+;;; nnkiboze.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnmail.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,1705 @@
+;;; nnmail.el --- mail support functions for the Gnus mail backends
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'timezone)
+(require 'message)
+(require 'cl)
+(require 'custom)
+
+(eval-and-compile
+  (autoload 'gnus-error "gnus-util"))
+
+(defgroup nnmail nil
+  "Reading mail with Gnus."
+  :group 'gnus)
+
+(defgroup nnmail-retrieve nil
+  "Retrieving new mail."
+  :group 'nnmail)
+
+(defgroup nnmail-prepare nil
+  "Preparing (or mangling) new mail after retrival."
+  :group 'nnmail)
+
+(defgroup nnmail-duplicate nil
+  "Handling of duplicate mail messages."
+  :group 'nnmail)
+
+(defgroup nnmail-split nil
+  "Organizing the incomming mail in folders."
+  :group 'nnmail)
+
+(defgroup nnmail-files nil
+  "Mail files."
+  :group 'gnus-files
+  :group 'nnmail)
+
+(defgroup nnmail-expire nil
+  "Expiring old mail."
+  :group 'nnmail)
+
+(defgroup nnmail-procmail nil
+  "Interfacing with procmail and other mail agents."
+  :group 'nnmail)
+
+(defgroup nnmail-various nil
+  "Various mail options."
+  :group 'nnmail)
+
+(defcustom nnmail-split-methods
+  '(("mail.misc" ""))
+  "Incoming mail will be split according to this variable.
+
+If you'd like, for instance, one mail group for mail from the
+\"4ad-l\" mailing list, one group for junk mail and one for everything
+else, you could do something like this:
+
+ (setq nnmail-split-methods
+       '((\"mail.4ad\" \"From:.*4ad\")
+         (\"mail.junk\" \"From:.*Lars\\\\|Subject:.*buy\")
+         (\"mail.misc\" \"\")))
+
+As you can see, this variable is a list of lists, where the first
+element in each \"rule\" is the name of the group (which, by the way,
+does not have to be called anything beginning with \"mail\",
+\"yonka.zow\" is a fine, fine name), and the second is a regexp that
+nnmail will try to match on the header to find a fit.
+
+The second element can also be a function.  In that case, it will be
+called narrowed to the headers with the first element of the rule as
+the argument.  It should return a non-nil value if it thinks that the
+mail belongs in that group.
+
+The last element should always have \"\" as the regexp.
+
+This variable can also have a function as its value."
+  :group 'nnmail-split
+  :type '(choice (repeat :tag "Alist" (group (string :tag "Name") regexp))
+		 (function-item nnmail-split-fancy)
+		 (function :tag "Other")))
+
+;; Suggested by Erik Selberg <speed@cs.washington.edu>.
+(defcustom nnmail-crosspost t
+  "If non-nil, do crossposting if several split methods match the mail.
+If nil, the first match found will be used."
+  :group 'nnmail-split
+  :type 'boolean)
+
+;; Added by gord@enci.ucalgary.ca (Gordon Matzigkeit).
+(defcustom nnmail-keep-last-article nil
+  "If non-nil, nnmail will never delete the last expired article in a directory.
+You may need to set this variable if other programs are putting
+new mail into folder numbers that Gnus has marked as expired."
+  :group 'nnmail-procmail
+  :group 'nnmail-various
+  :type 'boolean)
+
+(defcustom nnmail-use-long-file-names nil
+  "If non-nil the mail backends will use long file and directory names.
+If nil, groups like \"mail.misc\" will end up in directories like
+\"mail/misc/\"."
+  :group 'nnmail-files
+  :type 'boolean)
+
+(defcustom nnmail-default-file-modes 384
+  "Set the mode bits of all new mail files to this integer."
+  :group 'nnmail-files
+  :type 'integer)
+
+(defcustom nnmail-expiry-wait 7
+  "*Expirable articles that are older than this will be expired.
+This variable can either be a number (which will be interpreted as a
+number of days) -- this doesn't have to be an integer.  This variable
+can also be `immediate' and `never'."
+  :group 'nnmail-expire
+  :type '(choice (const immediate)
+		 (integer :tag "days")
+		 (const never)))
+
+(defcustom nnmail-expiry-wait-function nil
+  "Variable that holds function to specify how old articles should be before they are expired.
+  The function will be called with the name of the group that the
+expiry is to be performed in, and it should return an integer that
+says how many days an article can be stored before it is considered
+\"old\".  It can also return the values `never' and `immediate'.
+
+Eg.:
+
+\(setq nnmail-expiry-wait-function
+      (lambda (newsgroup)
+        (cond ((string-match \"private\" newsgroup) 31)
+              ((string-match \"junk\" newsgroup) 1)
+	      ((string-match \"important\" newsgroup) 'never)
+	      (t 7))))"
+  :group 'nnmail-expire
+  :type '(choice (const :tag "nnmail-expiry-wait" nil)
+		 (function :format "%v" nnmail-)))
+
+(defcustom nnmail-cache-accepted-message-ids nil
+  "If non-nil, put Message-IDs of Gcc'd articles into the duplicate cache."
+  :group 'nnmail
+  :type 'boolean)
+
+(defcustom nnmail-spool-file
+  (or (getenv "MAIL")
+      (concat "/usr/spool/mail/" (user-login-name)))
+  "Where the mail backends will look for incoming mail.
+This variable is \"/usr/spool/mail/$user\" by default.
+If this variable is nil, no mail backends will read incoming mail.
+If this variable is a list, all files mentioned in this list will be
+used as incoming mailboxes.
+If this variable is a directory (i. e., it's name ends with a \"/\"),
+treat all files in that directory as incoming spool files."
+  :group 'nnmail-files
+  :type 'file)
+
+(defcustom nnmail-crash-box "~/.gnus-crash-box"
+  "File where Gnus will store mail while processing it."
+  :group 'nnmail-files
+  :type 'file)
+
+(defcustom nnmail-use-procmail nil
+  "*If non-nil, the mail backends will look in `nnmail-procmail-directory' for spool files.
+The file(s) in `nnmail-spool-file' will also be read."
+  :group 'nnmail-procmail
+  :type 'boolean)
+
+(defcustom nnmail-procmail-directory "~/incoming/"
+  "*When using procmail (and the like), incoming mail is put in this directory.
+The Gnus mail backends will read the mail from this directory."
+  :group 'nnmail-procmail
+  :type 'directory)
+
+(defcustom nnmail-procmail-suffix "\\.spool"
+  "*Suffix of files created by procmail (and the like).
+This variable might be a suffix-regexp to match the suffixes of
+several files - eg. \".spool[0-9]*\"."
+  :group 'nnmail-procmail
+  :type 'regexp)
+
+(defcustom nnmail-resplit-incoming nil
+  "*If non-nil, re-split incoming procmail sorted mail."
+  :group 'nnmail-procmail
+  :type 'boolean)
+
+(defcustom nnmail-delete-file-function 'delete-file
+  "Function called to delete files in some mail backends."
+  :group 'nnmail-files
+  :type 'function)
+
+(defcustom nnmail-crosspost-link-function
+  (if (string-match "windows-nt\\|emx" (format "%s" system-type))
+      'copy-file
+    'add-name-to-file)
+  "Function called to create a copy of a file.
+This is `add-name-to-file' by default, which means that crossposts
+will use hard links.  If your file system doesn't allow hard
+links, you could set this variable to `copy-file' instead."
+  :group 'nnmail-files
+  :type '(radio (function-item add-name-to-file)
+		(function-item copy-file)
+		(function :tag "Other")))
+
+(defcustom nnmail-movemail-program "movemail"
+  "*A command to be executed to move mail from the inbox.
+The default is \"movemail\".
+
+This can also be a function.  In that case, the function will be
+called with two parameters -- the name of the INBOX file, and the file
+to be moved to."
+  :group 'nnmail-files
+  :group 'nnmail-retrieve
+  :type 'string)
+
+(defcustom nnmail-pop-password-required nil
+  "*Non-nil if a password is required when reading mail using POP."
+  :group 'nnmail-retrieve
+  :type 'boolean)
+
+(defcustom nnmail-read-incoming-hook
+  (if (eq system-type 'windows-nt)
+      '(nnheader-ms-strip-cr)
+    nil)
+  "Hook that will be run after the incoming mail has been transferred.
+The incoming mail is moved from `nnmail-spool-file' (which normally is
+something like \"/usr/spool/mail/$user\") to the user's home
+directory.  This hook is called after the incoming mail box has been
+emptied, and can be used to call any mail box programs you have
+running (\"xwatch\", etc.)
+
+Eg.
+
+\(add-hook 'nnmail-read-incoming-hook
+	   (lambda ()
+	     (start-process \"mailsend\" nil
+			    \"/local/bin/mailsend\" \"read\" \"mbox\")))
+
+If you have xwatch running, this will alert it that mail has been
+read.
+
+If you use `display-time', you could use something like this:
+
+\(add-hook 'nnmail-read-incoming-hook
+	  (lambda ()
+	    ;; Update the displayed time, since that will clear out
+	    ;; the flag that says you have mail.
+	    (when (eq (process-status \"display-time\") 'run)
+	      (display-time-filter display-time-process \"\"))))"
+  :group 'nnmail-prepare
+  :type 'hook)
+
+;; Suggested by Erik Selberg <speed@cs.washington.edu>.
+(defcustom nnmail-prepare-incoming-hook nil
+  "Hook called before treating incoming mail.
+The hook is run in a buffer with all the new, incoming mail."
+  :group 'nnmail-prepare
+  :type 'hook)
+
+(defcustom nnmail-prepare-incoming-header-hook nil
+  "Hook called narrowed to the headers of each message.
+This can be used to remove excessive spaces (and stuff like
+that) from the headers before splitting and saving the messages."
+  :group 'nnmail-prepare
+  :type 'hook)
+
+(defcustom nnmail-prepare-incoming-message-hook nil
+  "Hook called narrowed to each message."
+  :group 'nnmail-prepare
+  :type 'hook)
+
+(defcustom nnmail-list-identifiers nil
+  "Regexp that matches list identifiers to be removed.
+This can also be a list of regexps."
+  :group 'nnmail-prepare
+  :type '(choice (const :tag "none" nil)
+		 regexp
+		 (repeat regexp)))
+
+(defcustom nnmail-pre-get-new-mail-hook nil
+  "Hook called just before starting to handle new incoming mail."
+  :group 'nnmail-retrieve
+  :type 'hook)
+
+(defcustom nnmail-post-get-new-mail-hook nil
+  "Hook called just after finishing handling new incoming mail."
+  :group 'nnmail-retrieve
+  :type 'hook)
+
+(defcustom nnmail-split-hook nil
+  "Hook called before deciding where to split an article.
+The functions in this hook are free to modify the buffer
+contents in any way they choose -- the buffer contents are
+discarded after running the split process."
+  :group 'nnmail-split
+  :type 'hook)
+
+;; Suggested by Mejia Pablo J <pjm9806@usl.edu>.
+(defcustom nnmail-tmp-directory nil
+  "*If non-nil, use this directory for temporary storage.
+Used when reading incoming mail."
+  :group 'nnmail-files
+  :group 'nnmail-retrieve
+  :type '(choice (const :tag "default" nil)
+		 (directory :format "%v")))
+
+(defcustom nnmail-large-newsgroup 50
+  "*The number of the articles which indicates a large newsgroup.
+If the number of the articles is greater than the value, verbose
+messages will be shown to indicate the current status."
+  :group 'nnmail-various
+  :type 'integer)
+
+(defcustom nnmail-split-fancy "mail.misc"
+  "Incoming mail can be split according to this fancy variable.
+To enable this, set `nnmail-split-methods' to `nnmail-split-fancy'.
+
+The format is this variable is SPLIT, where SPLIT can be one of
+the following:
+
+GROUP: Mail will be stored in GROUP (a string).
+
+\(FIELD VALUE SPLIT): If the message field FIELD (a regexp) contains
+  VALUE (a regexp), store the messages as specified by SPLIT.
+
+\(| SPLIT...): Process each SPLIT expression until one of them matches.
+  A SPLIT expression is said to match if it will cause the mail
+  message to be stored in one or more groups.
+
+\(& SPLIT...): Process each SPLIT expression.
+
+\(: FUNCTION optional args): Call FUNCTION with the optional args, in
+  the buffer containing the message headers.  The return value FUNCTION
+  should be a split, which is then recursively processed.
+
+FIELD must match a complete field name.  VALUE must match a complete
+word according to the `nnmail-split-fancy-syntax-table' syntax table.
+You can use \".*\" in the regexps to match partial field names or words.
+
+FIELD and VALUE can also be lisp symbols, in that case they are expanded
+as specified in `nnmail-split-abbrev-alist'.
+
+GROUP can contain \\& and \\N which will substitute from matching
+\\(\\) patterns in the previous VALUE.
+
+Example:
+
+\(setq nnmail-split-methods 'nnmail-split-fancy
+      nnmail-split-fancy
+      ;; Messages from the mailer daemon are not crossposted to any of
+      ;; the ordinary groups.  Warnings are put in a separate group
+      ;; from real errors.
+      '(| (\"from\" mail (| (\"subject\" \"warn.*\" \"mail.warning\")
+			  \"mail.misc\"))
+	  ;; Non-error messages are crossposted to all relevant
+	  ;; groups, but we don't crosspost between the group for the
+	  ;; (ding) list and the group for other (ding) related mail.
+	  (& (| (any \"ding@ifi\\\\.uio\\\\.no\" \"ding.list\")
+		(\"subject\" \"ding\" \"ding.misc\"))
+	     ;; Other mailing lists...
+	     (any \"procmail@informatik\\\\.rwth-aachen\\\\.de\" \"procmail.list\")
+	     (any \"SmartList@informatik\\\\.rwth-aachen\\\\.de\" \"SmartList.list\")
+	     ;; People...
+	     (any \"larsi@ifi\\\\.uio\\\\.no\" \"people.Lars Magne Ingebrigtsen\"))
+	  ;; Unmatched mail goes to the catch all group.
+	  \"misc.misc\"))"
+  :group 'nnmail-split
+  ;; Sigh!
+  :type 'sexp)
+
+(defcustom nnmail-split-abbrev-alist
+  '((any . "from\\|to\\|cc\\|sender\\|apparently-to\\|resent-from\\|resent-to\\|resent-cc")
+    (mail . "mailer-daemon\\|postmaster\\|uucp")
+    (to . "to\\|cc\\|apparently-to\\|resent-to\\|resent-cc")
+    (from . "from\\|sender\\|resent-from"))
+  "Alist of abbreviations allowed in `nnmail-split-fancy'."
+  :group 'nnmail-split
+  :type '(repeat (cons :format "%v" symbol regexp)))
+
+(defcustom nnmail-delete-incoming t
+  "*If non-nil, the mail backends will delete incoming files after
+splitting."
+  :group 'nnmail-retrieve
+  :type 'boolean)
+
+(defcustom nnmail-message-id-cache-length 1000
+  "*The approximate number of Message-IDs nnmail will keep in its cache.
+If this variable is nil, no checking on duplicate messages will be
+performed."
+  :group 'nnmail-duplicate
+  :type '(choice (const :tag "disable" nil)
+		 (integer :format "%v")))
+
+(defcustom nnmail-message-id-cache-file "~/.nnmail-cache"
+  "*The file name of the nnmail Message-ID cache."
+  :group 'nnmail-duplicate
+  :group 'nnmail-files
+  :type 'file)
+
+(defcustom nnmail-treat-duplicates 'warn
+  "*If non-nil, nnmail keep a cache of Message-IDs to discover mail duplicates.
+Three values are legal: nil, which means that nnmail is not to keep a
+Message-ID cache; `warn', which means that nnmail should insert extra
+headers to warn the user about the duplication (this is the default);
+and `delete', which means that nnmail will delete duplicated mails.
+
+This variable can also be a function.  It will be called from a buffer
+narrowed to the article in question with the Message-ID as a
+parameter.  It should return nil, `warn' or `delete'."
+  :group 'nnmail-duplicate
+  :type '(choice (const :tag "off" nil)
+		 (const warn)
+		 (const delete)))
+
+;;; Internal variables.
+
+(defvar nnmail-split-history nil
+  "List of group/article elements that say where the previous split put messages.")
+
+(defvar nnmail-pop-password nil
+  "*Password to use when reading mail from a POP server, if required.")
+
+(defvar nnmail-split-fancy-syntax-table nil
+  "Syntax table used by `nnmail-split-fancy'.")
+(unless (syntax-table-p nnmail-split-fancy-syntax-table)
+  (setq nnmail-split-fancy-syntax-table
+	(copy-syntax-table (standard-syntax-table)))
+  ;; support the %-hack
+  (modify-syntax-entry ?\% "." nnmail-split-fancy-syntax-table))
+
+(defvar nnmail-prepare-save-mail-hook nil
+  "Hook called before saving mail.")
+
+(defvar nnmail-moved-inboxes nil
+  "List of inboxes that have been moved.")
+
+(defvar nnmail-internal-password nil)
+
+
+
+(defconst nnmail-version "nnmail 1.0"
+  "nnmail version.")
+
+
+
+(defun nnmail-request-post (&optional server)
+  (mail-send-and-exit nil))
+
+(defun nnmail-find-file (file)
+  "Insert FILE in server buffer safely."
+  (set-buffer nntp-server-buffer)
+  (erase-buffer)
+  (let ((format-alist nil)
+        (after-insert-file-functions nil))
+    (condition-case ()
+	(progn (insert-file-contents file) t)
+      (file-error nil))))
+
+(defun nnmail-group-pathname (group dir &optional file)
+  "Make pathname for GROUP."
+  (concat
+   (let ((dir (file-name-as-directory (expand-file-name dir))))
+     ;; If this directory exists, we use it directly.
+     (if (or nnmail-use-long-file-names
+	     (file-directory-p (concat dir group)))
+	 (concat dir group "/")
+       ;; If not, we translate dots into slashes.
+       (concat dir (nnheader-replace-chars-in-string group ?. ?/) "/")))
+   (or file "")))
+
+(defun nnmail-date-to-time (date)
+  "Convert DATE into time."
+  (condition-case ()
+      (let* ((d1 (timezone-parse-date date))
+	     (t1 (timezone-parse-time (aref d1 3))))
+	(apply 'encode-time
+	       (mapcar (lambda (el)
+			 (and el (string-to-number el)))
+		       (list
+			(aref t1 2) (aref t1 1) (aref t1 0)
+			(aref d1 2) (aref d1 1) (aref d1 0)
+			(number-to-string
+			 (* 60 (timezone-zone-to-minute (aref d1 4))))))))
+    ;; If we get an error, then we just return a 0 time.
+    (error (list 0 0))))
+
+(defun nnmail-time-less (t1 t2)
+  "Say whether time T1 is less than time T2."
+  (or (< (car t1) (car t2))
+      (and (= (car t1) (car t2))
+	   (< (nth 1 t1) (nth 1 t2)))))
+
+(defun nnmail-days-to-time (days)
+  "Convert DAYS into time."
+  (let* ((seconds (* 1.0 days 60 60 24))
+	 (rest (expt 2 16))
+	 (ms (condition-case nil (round (/ seconds rest))
+	       (range-error (expt 2 16)))))
+    (list ms (condition-case nil (round (- seconds (* ms rest)))
+	       (range-error (expt 2 16))))))
+
+(defun nnmail-time-since (time)
+  "Return the time since TIME, which is either an internal time or a date."
+  (when (stringp time)
+    ;; Convert date strings to internal time.
+    (setq time (nnmail-date-to-time time)))
+  (let* ((current (current-time))
+	 (rest (when (< (nth 1 current) (nth 1 time))
+		 (expt 2 16))))
+    (list (- (+ (car current) (if rest -1 0)) (car time))
+	  (- (+ (or rest 0) (nth 1 current)) (nth 1 time)))))
+
+;; Function rewritten from rmail.el.
+(defun nnmail-move-inbox (inbox)
+  "Move INBOX to `nnmail-crash-box'."
+  (if (not (file-writable-p nnmail-crash-box))
+      (gnus-error 1 "Can't write to crash box %s.  Not moving mail."
+		  nnmail-crash-box)
+    ;; If the crash box exists and is empty, we delete it.
+    (when (and (file-exists-p nnmail-crash-box)
+	       (zerop (nnheader-file-size (file-truename nnmail-crash-box))))
+      (delete-file nnmail-crash-box))
+    (let ((inbox (file-truename (expand-file-name inbox)))
+	  (tofile (file-truename (expand-file-name nnmail-crash-box)))
+	  movemail popmail errors result)
+      (if (setq popmail (string-match
+			 "^po:" (file-name-nondirectory inbox)))
+	  (setq inbox (file-name-nondirectory inbox))
+	(setq movemail t)
+	;; On some systems, /usr/spool/mail/foo is a directory
+	;; and the actual inbox is /usr/spool/mail/foo/foo.
+	(when (file-directory-p inbox)
+	  (setq inbox (expand-file-name (user-login-name) inbox))))
+      (if (member inbox nnmail-moved-inboxes)
+	  ;; We don't try to move an already moved inbox.
+	  nil
+	(if popmail
+	    (progn
+	      (when (and nnmail-pop-password
+			 (not nnmail-internal-password))
+		(setq nnmail-internal-password nnmail-pop-password))
+	      (when (and nnmail-pop-password-required
+			 (not nnmail-internal-password))
+		(setq nnmail-internal-password
+		      (nnmail-read-passwd
+		       (format "Password for %s: "
+			       (substring inbox (+ popmail 3))))))
+	      (message "Getting mail from post office ..."))
+	  (when (or (and (file-exists-p tofile)
+			 (/= 0 (nnheader-file-size tofile)))
+		    (and (file-exists-p inbox)
+			 (/= 0 (nnheader-file-size inbox))))
+	    (message "Getting mail from %s..." inbox)))
+	;; Set TOFILE if have not already done so, and
+	;; rename or copy the file INBOX to TOFILE if and as appropriate.
+	(cond
+	 ((file-exists-p tofile)
+	  ;; The crash box exists already.
+	  t)
+	 ((and (not popmail)
+	       (not (file-exists-p inbox)))
+	  ;; There is no inbox.
+	  (setq tofile nil))
+	 (t
+	  ;; If getting from mail spool directory, use movemail to move
+	  ;; rather than just renaming, so as to interlock with the
+	  ;; mailer.
+	  (unwind-protect
+	      (save-excursion
+		(setq errors (generate-new-buffer " *nnmail loss*"))
+		(buffer-disable-undo errors)
+		(let ((default-directory "/"))
+		  (if (nnheader-functionp nnmail-movemail-program)
+		      (condition-case err
+			  (progn
+			    (funcall nnmail-movemail-program inbox tofile)
+			    (setq result 0))
+			(error
+			 (save-excursion
+			   (set-buffer errors)
+			   (insert (prin1-to-string err))
+			   (setq result 255))))
+		    (setq result
+			  (apply
+			   'call-process
+			   (append
+			    (list
+			     (expand-file-name
+			      nnmail-movemail-program exec-directory)
+			     nil errors nil inbox tofile)
+			    (when nnmail-internal-password
+			      (list nnmail-internal-password)))))))
+		(if (and (not (buffer-modified-p errors))
+			 (zerop result))
+		    ;; No output => movemail won
+		    (progn
+		      (unless popmail
+			(when (file-exists-p tofile)
+			  (set-file-modes tofile nnmail-default-file-modes)))
+		      (push inbox nnmail-moved-inboxes))
+		  (set-buffer errors)
+		  ;; There may be a warning about older revisions.  We
+		  ;; ignore those.
+		  (goto-char (point-min))
+		  (if (search-forward "older revision" nil t)
+		      (progn
+			(unless popmail
+			  (when (file-exists-p tofile)
+			    (set-file-modes tofile nnmail-default-file-modes)))
+			(push inbox nnmail-moved-inboxes))
+		    ;; Probably a real error.
+		    (subst-char-in-region (point-min) (point-max) ?\n ?\  )
+		    (goto-char (point-max))
+		    (skip-chars-backward " \t")
+		    (delete-region (point) (point-max))
+		    (goto-char (point-min))
+		    (when (looking-at "movemail: ")
+		      (delete-region (point-min) (match-end 0)))
+		    (unless (yes-or-no-p
+			     (format "movemail: %s (%d return).  Continue? "
+				     (buffer-string) result))
+		      (error "%s" (buffer-string)))
+		    (setq tofile nil)))))))
+	(message "Getting mail from %s...done" inbox)
+	(and errors
+	     (buffer-name errors)
+	     (kill-buffer errors))
+	tofile))))
+
+(defun nnmail-get-active ()
+  "Returns an assoc of group names and active ranges.
+nn*-request-list should have been called before calling this function."
+  (let (group-assoc)
+    ;; Go through all groups from the active list.
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (goto-char (point-min))
+      (while (re-search-forward
+	      "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+\\)" nil t)
+	;; We create an alist with `(GROUP (LOW . HIGH))' elements.
+	(push (list (match-string 1)
+		    (cons (string-to-int (match-string 3))
+			  (string-to-int (match-string 2))))
+	      group-assoc)))
+    group-assoc))
+
+(defun nnmail-save-active (group-assoc file-name)
+  "Save GROUP-ASSOC in ACTIVE-FILE."
+  (when file-name
+    (nnheader-temp-write file-name
+      (nnmail-generate-active group-assoc))))
+
+(defun nnmail-generate-active (alist)
+  "Generate an active file from group-alist ALIST."
+  (erase-buffer)
+  (let (group)
+    (while (setq group (pop alist))
+      (insert (format "%s %d %d y\n" (car group) (cdadr group)
+		      (caadr group))))))
+
+(defun nnmail-get-split-group (file group)
+  "Find out whether this FILE is to be split into GROUP only.
+If GROUP is non-nil and we are using procmail, return the group name
+only when the file is the correct procmail file.  When GROUP is nil,
+return nil if FILE is a spool file or the procmail group for which it
+is a spool.  If not using procmail, return GROUP."
+  (if (or (eq nnmail-spool-file 'procmail)
+	  nnmail-use-procmail)
+      (if (string-match (concat "^" (expand-file-name
+				     (file-name-as-directory
+				      nnmail-procmail-directory))
+				"\\([^/]*\\)" nnmail-procmail-suffix "$")
+			(expand-file-name file))
+	  (let ((procmail-group (substring (expand-file-name file)
+					   (match-beginning 1)
+					   (match-end 1))))
+	    (if group
+		(if (string-equal group procmail-group)
+		    group
+		  nil)
+	      procmail-group))
+	nil)
+    group))
+
+(defun nnmail-process-babyl-mail-format (func artnum-func)
+  (let ((case-fold-search t)
+	start message-id content-length do-search end)
+    (goto-char (point-min))
+    (while (not (eobp))
+      (re-search-forward
+       "\n0, *unseen,+\n\\(\\*\\*\\* EOOH \\*\\*\\*\n\\)?" nil t)
+      (goto-char (match-end 0))
+      (delete-region (match-beginning 0) (match-end 0))
+      (narrow-to-region
+       (setq start (point))
+       (progn
+	 ;; Skip all the headers in case there are more "From "s...
+	 (or (search-forward "\n\n" nil t)
+	     (search-forward-regexp "^[^:]*\\( .*\\|\\)$" nil t)
+	     (search-forward ""))
+	 (point)))
+      ;; Unquote the ">From " line, if any.
+      (goto-char (point-min))
+      (when (looking-at ">From ")
+	(replace-match "X-From-Line: ") )
+      (run-hooks 'nnmail-prepare-incoming-header-hook)
+      (goto-char (point-max))
+      ;; Find the Message-ID header.
+      (save-excursion
+	(if (re-search-backward
+	     "^Message-ID[ \t]*:[ \n\t]*\\(<[^>]*>\\)" nil t)
+	    (setq message-id (buffer-substring (match-beginning 1)
+					       (match-end 1)))
+	  ;; There is no Message-ID here, so we create one.
+	  (save-excursion
+	    (when (re-search-backward "^Message-ID[ \t]*:" nil t)
+	      (beginning-of-line)
+	      (insert "Original-")))
+	  (forward-line -1)
+	  (insert "Message-ID: " (setq message-id (nnmail-message-id))
+		  "\n")))
+      ;; Look for a Content-Length header.
+      (if (not (save-excursion
+		 (and (re-search-backward
+		       "^Content-Length:[ \t]*\\([0-9]+\\)" start t)
+		      (setq content-length (string-to-int
+					    (buffer-substring
+					     (match-beginning 1)
+					     (match-end 1))))
+		      ;; We destroy the header, since none of
+		      ;; the backends ever use it, and we do not
+		      ;; want to confuse other mailers by having
+		      ;; a (possibly) faulty header.
+		      (progn (insert "X-") t))))
+	  (setq do-search t)
+	(widen)
+	(if (or (= (+ (point) content-length) (point-max))
+		(save-excursion
+		  (goto-char (+ (point) content-length))
+		  (looking-at "")))
+	    (progn
+	      (goto-char (+ (point) content-length))
+	      (setq do-search nil))
+	  (setq do-search t)))
+      (widen)
+      ;; Go to the beginning of the next article - or to the end
+      ;; of the buffer.
+      (when do-search
+	(if (re-search-forward "^" nil t)
+	    (goto-char (match-beginning 0))
+	  (goto-char (1- (point-max)))))
+      (delete-char 1)			; delete ^_
+      (save-excursion
+	(save-restriction
+	  (narrow-to-region start (point))
+	  (goto-char (point-min))
+	  (nnmail-check-duplication message-id func artnum-func)
+	  (setq end (point-max))))
+      (goto-char end))))
+
+(defsubst nnmail-search-unix-mail-delim ()
+  "Put point at the beginning of the next Unix mbox message."
+  ;; Algorithm used to find the the next article in the
+  ;; brain-dead Unix mbox format:
+  ;;
+  ;; 1) Search for "^From ".
+  ;; 2) If we find it, then see whether the previous
+  ;;    line is blank and the next line looks like a header.
+  ;; Then it's possible that this is a mail delim, and we use it.
+  (let ((case-fold-search nil)
+	found)
+    (while (not found)
+      (if (not (re-search-forward "^From " nil t))
+	  (setq found 'no)
+	(save-excursion
+	  (beginning-of-line)
+	  (when (and (or (bobp)
+			 (save-excursion
+			   (forward-line -1)
+			   (= (following-char) ?\n)))
+		     (save-excursion
+		       (forward-line 1)
+		       (while (looking-at ">From ")
+			 (forward-line 1))
+		       (looking-at "[^ \n\t:]+[ \n\t]*:")))
+	    (setq found 'yes)))))
+    (beginning-of-line)
+    (eq found 'yes)))
+
+(defun nnmail-search-unix-mail-delim-backward ()
+  "Put point at the beginning of the current Unix mbox message."
+  ;; Algorithm used to find the the next article in the
+  ;; brain-dead Unix mbox format:
+  ;;
+  ;; 1) Search for "^From ".
+  ;; 2) If we find it, then see whether the previous
+  ;;    line is blank and the next line looks like a header.
+  ;; Then it's possible that this is a mail delim, and we use it.
+  (let ((case-fold-search nil)
+	found)
+    (while (not found)
+      (if (not (re-search-backward "^From " nil t))
+	  (setq found 'no)
+	(save-excursion
+	  (beginning-of-line)
+	  (when (and (or (bobp)
+			 (save-excursion
+			   (forward-line -1)
+			   (= (following-char) ?\n)))
+		     (save-excursion
+		       (forward-line 1)
+		       (while (looking-at ">From ")
+			 (forward-line 1))
+		       (looking-at "[^ \n\t:]+[ \n\t]*:")))
+	    (setq found 'yes)))))
+    (beginning-of-line)
+    (eq found 'yes)))
+
+(defun nnmail-process-unix-mail-format (func artnum-func)
+  (let ((case-fold-search t)
+	start message-id content-length end skip head-end)
+    (goto-char (point-min))
+    (if (not (and (re-search-forward "^From " nil t)
+		  (goto-char (match-beginning 0))))
+	;; Possibly wrong format?
+	(error "Error, unknown mail format! (Possibly corrupted.)")
+      ;; Carry on until the bitter end.
+      (while (not (eobp))
+	(setq start (point)
+	      end nil)
+	;; Find the end of the head.
+	(narrow-to-region
+	 start
+	 (if (search-forward "\n\n" nil t)
+	     (1- (point))
+	   ;; This will never happen, but just to be on the safe side --
+	   ;; if there is no head-body delimiter, we search a bit manually.
+	   (while (and (looking-at "From \\|[^ \t]+:")
+		       (not (eobp)))
+	     (forward-line 1))
+	   (point)))
+	;; Find the Message-ID header.
+	(goto-char (point-min))
+	(if (re-search-forward "^Message-ID[ \t]*:[ \n\t]*\\(<[^>]+>\\)" nil t)
+	    (setq message-id (match-string 1))
+	  (save-excursion
+	    (when (re-search-forward "^Message-ID[ \t]*:" nil t)
+	      (beginning-of-line)
+	      (insert "Original-")))
+	  ;; There is no Message-ID here, so we create one.
+	  (forward-line 1)
+	  (insert "Message-ID: " (setq message-id (nnmail-message-id)) "\n"))
+	;; Look for a Content-Length header.
+	(goto-char (point-min))
+	(if (not (re-search-forward
+		  "^Content-Length:[ \t]*\\([0-9]+\\)" nil t))
+	    (setq content-length nil)
+	  (setq content-length (string-to-int (match-string 1)))
+	  ;; We destroy the header, since none of the backends ever
+	  ;; use it, and we do not want to confuse other mailers by
+	  ;; having a (possibly) faulty header.
+	  (beginning-of-line)
+	  (insert "X-"))
+	(run-hooks 'nnmail-prepare-incoming-header-hook)
+	;; Find the end of this article.
+	(goto-char (point-max))
+	(widen)
+	(setq head-end (point))
+	;; We try the Content-Length value.  The idea: skip over the header
+	;; separator, then check what happens content-length bytes into the
+	;; message body.  This should be either the end ot the buffer, the
+	;; message separator or a blank line followed by the separator.
+	;; The blank line should probably be deleted.  If neither of the
+	;; three is met, the content-length header is probably invalid.
+	(when content-length
+	  (forward-line 1)
+	  (setq skip (+ (point) content-length))
+	  (goto-char skip)
+	  (cond ((or (= skip (point-max))
+		     (= (1+ skip) (point-max)))
+		 (setq end (point-max)))
+		((looking-at "From ")
+		 (setq end skip))
+		((looking-at "[ \t]*\n\\(From \\)")
+		 (setq end (match-beginning 1)))
+		(t (setq end nil))))
+	(if end
+	    (goto-char end)
+	  ;; No Content-Length, so we find the beginning of the next
+	  ;; article or the end of the buffer.
+	  (goto-char head-end)
+	  (or (nnmail-search-unix-mail-delim)
+	      (goto-char (point-max))))
+	;; Allow the backend to save the article.
+	(save-excursion
+	  (save-restriction
+	    (narrow-to-region start (point))
+	    (goto-char (point-min))
+	    (nnmail-check-duplication message-id func artnum-func)
+	    (setq end (point-max))))
+	(goto-char end)))))
+
+(defun nnmail-process-mmdf-mail-format (func artnum-func)
+  (let ((delim "^\^A\^A\^A\^A$")
+	(case-fold-search t)
+	start message-id end)
+    (goto-char (point-min))
+    (if (not (and (re-search-forward delim nil t)
+		  (forward-line 1)))
+	;; Possibly wrong format?
+	(error "Error, unknown mail format! (Possibly corrupted.)")
+      ;; Carry on until the bitter end.
+      (while (not (eobp))
+	(setq start (point))
+	;; Find the end of the head.
+	(narrow-to-region
+	 start
+	 (if (search-forward "\n\n" nil t)
+	     (1- (point))
+	   ;; This will never happen, but just to be on the safe side --
+	   ;; if there is no head-body delimiter, we search a bit manually.
+	   (while (and (looking-at "From \\|[^ \t]+:")
+		       (not (eobp)))
+	     (forward-line 1))
+	   (point)))
+	;; Find the Message-ID header.
+	(goto-char (point-min))
+	(if (re-search-forward "^Message-ID[ \t]*:[ \n\t]*\\(<[^>]+>\\)" nil t)
+	    (setq message-id (match-string 1))
+	  ;; There is no Message-ID here, so we create one.
+	  (save-excursion
+	    (when (re-search-backward "^Message-ID[ \t]*:" nil t)
+	      (beginning-of-line)
+	      (insert "Original-")))
+	  (forward-line 1)
+	  (insert "Message-ID: " (setq message-id (nnmail-message-id)) "\n"))
+	(run-hooks 'nnmail-prepare-incoming-header-hook)
+	;; Find the end of this article.
+	(goto-char (point-max))
+	(widen)
+	(if (re-search-forward delim nil t)
+	    (beginning-of-line)
+	  (goto-char (point-max)))
+	;; Allow the backend to save the article.
+	(save-excursion
+	  (save-restriction
+	    (narrow-to-region start (point))
+	    (goto-char (point-min))
+	    (nnmail-check-duplication message-id func artnum-func)
+	    (setq end (point-max))))
+	(goto-char end)
+	(forward-line 2)))))
+
+(defun nnmail-split-incoming (incoming func &optional exit-func
+				       group artnum-func)
+  "Go through the entire INCOMING file and pick out each individual mail.
+FUNC will be called with the buffer narrowed to each mail."
+  (let (;; If this is a group-specific split, we bind the split
+	;; methods to just this group.
+	(nnmail-split-methods (if (and group
+				       (or (eq nnmail-spool-file 'procmail)
+					   nnmail-use-procmail)
+				       (not nnmail-resplit-incoming))
+				  (list (list group ""))
+				nnmail-split-methods)))
+    (save-excursion
+      ;; Insert the incoming file.
+      (set-buffer (get-buffer-create " *nnmail incoming*"))
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (nnheader-insert-file-contents incoming)
+      (unless (zerop (buffer-size))
+	(goto-char (point-min))
+	(save-excursion (run-hooks 'nnmail-prepare-incoming-hook))
+	;; Handle both babyl, MMDF and unix mail formats, since movemail will
+	;; use the former when fetching from a mailbox, the latter when
+	;; fetching from a file.
+	(cond ((or (looking-at "\^L")
+		   (looking-at "BABYL OPTIONS:"))
+	       (nnmail-process-babyl-mail-format func artnum-func))
+	      ((looking-at "\^A\^A\^A\^A")
+	       (nnmail-process-mmdf-mail-format func artnum-func))
+	      (t
+	       (nnmail-process-unix-mail-format func artnum-func))))
+      (when exit-func
+	(funcall exit-func))
+      (kill-buffer (current-buffer)))))
+
+;; Mail crossposts suggested by Brian Edmonds <edmonds@cs.ubc.ca>.
+(defun nnmail-article-group (func)
+  "Look at the headers and return an alist of groups that match.
+FUNC will be called with the group name to determine the article number."
+  (let ((methods nnmail-split-methods)
+	(obuf (current-buffer))
+	(beg (point-min))
+	end group-art method)
+    (if (and (sequencep methods) (= (length methods) 1))
+	;; If there is only just one group to put everything in, we
+	;; just return a list with just this one method in.
+	(setq group-art
+	      (list (cons (caar methods) (funcall func (caar methods)))))
+      ;; We do actual comparison.
+      (save-excursion
+	;; Find headers.
+	(goto-char beg)
+	(setq end (if (search-forward "\n\n" nil t) (point) (point-max)))
+	(set-buffer nntp-server-buffer)
+	(erase-buffer)
+	;; Copy the headers into the work buffer.
+	(insert-buffer-substring obuf beg end)
+	;; Fold continuation lines.
+	(goto-char (point-min))
+	(while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
+	  (replace-match " " t t))
+	;; Allow washing.
+	(run-hooks 'nnmail-split-hook)
+	(if (and (symbolp nnmail-split-methods)
+		 (fboundp nnmail-split-methods))
+	    (let ((split
+		   (condition-case nil
+		       (or (funcall nnmail-split-methods)
+			   '("bogus"))
+		     (error
+		      (message
+		       "Error in `nnmail-split-methods'; using `bogus' mail group")
+		      (sit-for 1)
+		      '("bogus")))))
+	      (unless (equal split '(junk))
+		;; `nnmail-split-methods' is a function, so we just call
+		;; this function here and use the result.
+		(setq group-art
+		      (mapcar
+		       (lambda (group) (cons group (funcall func group)))
+		       split))))
+	  ;; Go through the split methods to find a match.
+	  (while (and methods (or nnmail-crosspost (not group-art)))
+	    (goto-char (point-max))
+	    (setq method (pop methods))
+	    (if (or methods
+		    (not (equal "" (nth 1 method))))
+		(when (and
+		       (ignore-errors
+			 (if (stringp (nth 1 method))
+			     (re-search-backward (cadr method) nil t)
+			   ;; Function to say whether this is a match.
+			   (funcall (nth 1 method) (car method))))
+		       ;; Don't enter the article into the same
+		       ;; group twice.
+		       (not (assoc (car method) group-art)))
+		  (push (cons (car method) (funcall func (car method)))
+			group-art))
+	      ;; This is the final group, which is used as a
+	      ;; catch-all.
+	      (unless group-art
+		(setq group-art
+		      (list (cons (car method)
+				  (funcall func (car method)))))))))
+	;; See whether the split methods returned `junk'.
+	(if (equal group-art '(junk))
+	    nil
+	  (nreverse (delq 'junk group-art)))))))
+
+(defun nnmail-insert-lines ()
+  "Insert how many lines there are in the body of the mail.
+Return the number of characters in the body."
+  (let (lines chars)
+    (save-excursion
+      (goto-char (point-min))
+      (when (search-forward "\n\n" nil t)
+	(setq chars (- (point-max) (point)))
+	(setq lines (count-lines (point) (point-max)))
+	(forward-char -1)
+	(save-excursion
+	  (when (re-search-backward "^Lines: " nil t)
+	    (delete-region (point) (progn (forward-line 1) (point)))))
+	(beginning-of-line)
+	(insert (format "Lines: %d\n" (max lines 0)))
+	chars))))
+
+(defun nnmail-insert-xref (group-alist)
+  "Insert an Xref line based on the (group . article) alist."
+  (save-excursion
+    (goto-char (point-min))
+    (when (search-forward "\n\n" nil t)
+      (forward-char -1)
+      (when (re-search-backward "^Xref: " nil t)
+	(delete-region (match-beginning 0)
+		       (progn (forward-line 1) (point))))
+      (insert (format "Xref: %s" (system-name)))
+      (while group-alist
+	(insert (format " %s:%d" (caar group-alist) (cdar group-alist)))
+	(setq group-alist (cdr group-alist)))
+      (insert "\n"))))
+
+;;; Message washing functions
+
+(defun nnmail-remove-leading-whitespace ()
+  "Remove excessive whitespace from all headers."
+  (goto-char (point-min))
+  (while (re-search-forward "^\\([^ :]+: \\) +" nil t)
+    (replace-match "\\1" t)))
+
+(defun nnmail-remove-list-identifiers ()
+  "Remove list identifiers from Subject headers."
+  (let ((regexp (if (stringp nnmail-list-identifiers) nnmail-list-identifiers
+		  (mapconcat 'identity nnmail-list-identifiers "\\|"))))
+    (when regexp
+      (goto-char (point-min))
+      (when (re-search-forward
+	     (concat "^Subject: +\\(Re: +\\)?\\(" regexp "\\) *")
+	     nil t)
+	(delete-region (match-beginning 2) (match-end 0))))))
+
+(defun nnmail-remove-tabs ()
+  "Translate TAB characters into SPACE characters."
+  (subst-char-in-region (point-min) (point-max) ?\t ?  t))
+
+;;; Utility functions
+
+;; Written by byer@mv.us.adobe.com (Scott Byer).
+(defun nnmail-make-complex-temp-name (prefix)
+  (let ((newname (make-temp-name prefix))
+	(newprefix prefix))
+    (while (file-exists-p newname)
+      (setq newprefix (concat newprefix "x"))
+      (setq newname (make-temp-name newprefix)))
+    newname))
+
+;; Written by Per Abrahamsen <amanda@iesd.auc.dk>.
+
+(defun nnmail-split-fancy ()
+  "Fancy splitting method.
+See the documentation for the variable `nnmail-split-fancy' for documentation."
+  (let ((syntab (syntax-table)))
+    (unwind-protect
+	(progn
+	  (set-syntax-table nnmail-split-fancy-syntax-table)
+	  (nnmail-split-it nnmail-split-fancy))
+      (set-syntax-table syntab))))
+
+(defvar nnmail-split-cache nil)
+;; Alist of split expressions their equivalent regexps.
+
+(defun nnmail-split-it (split)
+  ;; Return a list of groups matching SPLIT.
+  (cond
+   ;; nil split
+   ((null split)
+    nil)
+
+   ;; A group name.  Do the \& and \N subs into the string.
+   ((stringp split)
+    (list (nnmail-expand-newtext split)))
+
+   ;; Junk the message.
+   ((eq split 'junk)
+    (list 'junk))
+
+   ;; Builtin & operation.
+   ((eq (car split) '&)
+    (apply 'nconc (mapcar 'nnmail-split-it (cdr split))))
+
+   ;; Builtin | operation.
+   ((eq (car split) '|)
+    (let (done)
+      (while (and (not done) (cdr split))
+	(setq split (cdr split)
+	      done (nnmail-split-it (car split))))
+      done))
+
+   ;; Builtin : operation.
+   ((eq (car split) ':)
+    (nnmail-split-it (eval (cdr split))))
+
+   ;; Check the cache for the regexp for this split.
+   ;; FIX FIX FIX could avoid calling assq twice here
+   ((assq split nnmail-split-cache)
+    (goto-char (point-max))
+    ;; FIX FIX FIX problem with re-search-backward is that if you have
+    ;; a split: (from "foo-\\(bar\\|baz\\)@gnus.org "mail.foo.\\1")
+    ;; and someone mails a message with 'To: foo-bar@gnus.org' and
+    ;; 'CC: foo-baz@gnus.org', we'll pick 'mail.foo.baz' as the group
+    ;; if the cc line is a later header, even though the other choice
+    ;; is probably better.  Also, this routine won't do a crosspost
+    ;; when there are two different matches.
+    ;; I guess you could just make this more determined, and it could
+    ;; look for still more matches prior to this one, and recurse
+    ;; on each of the multiple matches hit.  Of course, then you'd
+    ;; want to make sure that nnmail-article-group or nnmail-split-fancy
+    ;; removed duplicates, since there might be more of those.
+    ;; I guess we could also remove duplicates in the & split case, since
+    ;; that's the only thing that can introduce them.
+    (when (re-search-backward (cdr (assq split nnmail-split-cache)) nil t)
+      ;; Someone might want to do a \N sub on this match, so get the
+      ;; correct match positions.
+      (goto-char (match-end 0))
+      (let ((value (nth 1 split)))
+	(re-search-backward (if (symbolp value)
+				(cdr (assq value nnmail-split-abbrev-alist))
+			      value)
+			    (match-end 1)))
+      (nnmail-split-it (nth 2 split))))
+
+   ;; Not in cache, compute a regexp for the field/value pair.
+   (t
+    (let* ((field (nth 0 split))
+	   (value (nth 1 split))
+	   (regexp (concat "^\\(\\("
+			   (if (symbolp field)
+			       (cdr (assq field nnmail-split-abbrev-alist))
+			     field)
+			   "\\):.*\\)\\<\\("
+			   (if (symbolp value)
+			       (cdr (assq value nnmail-split-abbrev-alist))
+			     value)
+			   "\\)\\>")))
+      (push (cons split regexp) nnmail-split-cache)
+      ;; Now that it's in the cache, just call nnmail-split-it again
+      ;; on the same split, which will find it immediately in the cache.
+      (nnmail-split-it split)))))
+
+(defun nnmail-expand-newtext (newtext)
+  (let ((len (length newtext))
+	(pos 0)
+	c expanded beg N did-expand)
+    (while (< pos len)
+      (setq beg pos)
+      (while (and (< pos len)
+		  (not (= (aref newtext pos) ?\\)))
+	(setq pos (1+ pos)))
+      (unless (= beg pos)
+	(push (substring newtext beg pos) expanded))
+      (when (< pos len)
+	;; we hit a \, expand it.
+	(setq did-expand t)
+	(setq pos (1+ pos))
+	(setq c (aref newtext pos))
+	(if (not (or (= c ?\&)
+		     (and (>= c ?1)
+			  (<= c ?9))))
+	    ;; \ followed by some character we don't expand
+	    (push (char-to-string c) expanded)
+	  ;; \& or \N
+	  (if (= c ?\&)
+	      (setq N 0)
+	    (setq N (- c ?0)))
+	  (when (match-beginning N)
+	    (push (buffer-substring (match-beginning N) (match-end N))
+		  expanded))))
+      (setq pos (1+ pos)))
+    (if did-expand
+	(apply 'concat (nreverse expanded))
+      newtext)))
+
+;; Get a list of spool files to read.
+(defun nnmail-get-spool-files (&optional group)
+  (if (null nnmail-spool-file)
+      ;; No spool file whatsoever.
+      nil
+    (let* ((procmails
+	    ;; If procmail is used to get incoming mail, the files
+	    ;; are stored in this directory.
+	    (and (file-exists-p nnmail-procmail-directory)
+		 (or (eq nnmail-spool-file 'procmail)
+		     nnmail-use-procmail)
+		 (directory-files
+		  nnmail-procmail-directory
+		  t (concat (if group (concat "^" group) "")
+			    nnmail-procmail-suffix "$"))))
+	   (p procmails)
+	   (crash (when (and (file-exists-p nnmail-crash-box)
+			     (> (nnheader-file-size
+				 (file-truename nnmail-crash-box))
+				0))
+		    (list nnmail-crash-box))))
+      ;; Remove any directories that inadvertently match the procmail
+      ;; suffix, which might happen if the suffix is "".
+      (while p
+	(when (file-directory-p (car p))
+	  (setq procmails (delete (car p) procmails)))
+	(setq p (cdr p)))
+      ;; Return the list of spools.
+      (append
+       crash
+       (cond ((and group
+		   (or (eq nnmail-spool-file 'procmail)
+		       nnmail-use-procmail)
+		   procmails)
+	      procmails)
+	     ((and group
+		   (eq nnmail-spool-file 'procmail))
+	      nil)
+	     ((listp nnmail-spool-file)
+	      (nconc
+	       (apply
+		'nconc
+		(mapcar
+		 (lambda (file)
+		   (if (and (not (string-match "^po:" file))
+			    (file-directory-p file))
+		       (nnheader-directory-regular-files file)
+		     (list file)))
+		 nnmail-spool-file))
+	       procmails))
+	     ((stringp nnmail-spool-file)
+	      (if (and (not (string-match "^po:" nnmail-spool-file))
+		       (file-directory-p nnmail-spool-file))
+		  (nconc
+		   (nnheader-directory-regular-files nnmail-spool-file)
+		   procmails)
+		(cons nnmail-spool-file procmails)))
+	     ((eq nnmail-spool-file 'pop)
+	      (cons (format "po:%s" (user-login-name)) procmails))
+	     (t
+	      procmails))))))
+
+;; Activate a backend only if it isn't already activated.
+;; If FORCE, re-read the active file even if the backend is
+;; already activated.
+(defun nnmail-activate (backend &optional force)
+  (let (file timestamp file-time)
+    (if (or (not (symbol-value (intern (format "%s-group-alist" backend))))
+	    force
+	    (and (setq file (ignore-errors
+			      (symbol-value (intern (format "%s-active-file"
+							    backend)))))
+		 (setq file-time (nth 5 (file-attributes file)))
+		 (or (not
+		      (setq timestamp
+			    (condition-case ()
+				(symbol-value (intern
+					       (format "%s-active-timestamp"
+						       backend)))
+			      (error 'none))))
+		     (not (consp timestamp))
+		     (equal timestamp '(0 0))
+		     (> (nth 0 file-time) (nth 0 timestamp))
+		     (and (= (nth 0 file-time) (nth 0 timestamp))
+			  (> (nth 1 file-time) (nth 1 timestamp))))))
+	(save-excursion
+	  (or (eq timestamp 'none)
+	      (set (intern (format "%s-active-timestamp" backend))
+		   file-time))
+	  (funcall (intern (format "%s-request-list" backend)))))
+    t))
+
+(defun nnmail-message-id ()
+  (concat "<" (message-unique-id) "@totally-fudged-out-message-id>"))
+
+;;;
+;;; nnmail duplicate handling
+;;;
+
+(defvar nnmail-cache-buffer nil)
+
+(defun nnmail-cache-open ()
+  (if (or (not nnmail-treat-duplicates)
+	  (and nnmail-cache-buffer
+	       (buffer-name nnmail-cache-buffer)))
+      ()				; The buffer is open.
+    (save-excursion
+      (set-buffer
+       (setq nnmail-cache-buffer
+	     (get-buffer-create " *nnmail message-id cache*")))
+      (buffer-disable-undo (current-buffer))
+      (when (file-exists-p nnmail-message-id-cache-file)
+	(nnheader-insert-file-contents nnmail-message-id-cache-file))
+      (set-buffer-modified-p nil)
+      (current-buffer))))
+
+(defun nnmail-cache-close ()
+  (when (and nnmail-cache-buffer
+	     nnmail-treat-duplicates
+	     (buffer-name nnmail-cache-buffer)
+	     (buffer-modified-p nnmail-cache-buffer))
+    (save-excursion
+      (set-buffer nnmail-cache-buffer)
+      ;; Weed out the excess number of Message-IDs.
+      (goto-char (point-max))
+      (when (search-backward "\n" nil t nnmail-message-id-cache-length)
+	(progn
+	  (beginning-of-line)
+	  (delete-region (point-min) (point))))
+      ;; Save the buffer.
+      (or (file-exists-p (file-name-directory nnmail-message-id-cache-file))
+	  (make-directory (file-name-directory nnmail-message-id-cache-file)
+			  t))
+      (nnmail-write-region (point-min) (point-max)
+			   nnmail-message-id-cache-file nil 'silent)
+      (set-buffer-modified-p nil)
+      (setq nnmail-cache-buffer nil)
+      (kill-buffer (current-buffer)))))
+
+(defun nnmail-cache-insert (id)
+  (when nnmail-treat-duplicates
+    (unless (gnus-buffer-live-p nnmail-cache-buffer)
+      (nnmail-cache-open))
+    (save-excursion
+      (set-buffer nnmail-cache-buffer)
+      (goto-char (point-max))
+      (insert id "\n"))))
+
+(defun nnmail-cache-id-exists-p (id)
+  (when nnmail-treat-duplicates
+    (save-excursion
+      (set-buffer nnmail-cache-buffer)
+      (goto-char (point-max))
+      (search-backward id nil t))))
+
+(defun nnmail-fetch-field (header)
+  (save-excursion
+    (save-restriction
+      (message-narrow-to-head)
+      (message-fetch-field header))))
+
+(defun nnmail-check-duplication (message-id func artnum-func)
+  (run-hooks 'nnmail-prepare-incoming-message-hook)
+  ;; If this is a duplicate message, then we do not save it.
+  (let* ((duplication (nnmail-cache-id-exists-p message-id))
+	 (case-fold-search t)
+	 (action (when duplication
+		   (cond
+		    ((memq nnmail-treat-duplicates '(warn delete))
+		     nnmail-treat-duplicates)
+		    ((nnheader-functionp nnmail-treat-duplicates)
+		     (funcall nnmail-treat-duplicates message-id))
+		    (t
+		     nnmail-treat-duplicates))))
+	 group-art)
+    ;; Let the backend save the article (or not).
+    (cond
+     ((not duplication)
+      (nnmail-cache-insert message-id)
+      (funcall func (setq group-art
+			  (nreverse (nnmail-article-group artnum-func)))))
+     ((eq action 'delete)
+      (setq group-art nil))
+     ((eq action 'warn)
+      ;; We insert a warning.
+      (let ((case-fold-search t))
+	(goto-char (point-min))
+	(re-search-forward "^message-id[ \t]*:" nil t)
+	(beginning-of-line)
+	(insert
+	 "Gnus-Warning: This is a duplicate of message " message-id "\n")
+	(funcall func (setq group-art
+			    (nreverse (nnmail-article-group artnum-func))))))
+     (t
+      (funcall func (setq group-art
+			  (nreverse (nnmail-article-group artnum-func))))))
+    ;; Add the group-art list to the history list.
+    (if group-art
+	(push group-art nnmail-split-history)
+      (delete-region (point-min) (point-max)))))
+
+;;; Get new mail.
+
+(defun nnmail-get-value (&rest args)
+  (let ((sym (intern (apply 'format args))))
+    (when (boundp sym)
+      (symbol-value sym))))
+
+(defun nnmail-get-new-mail (method exit-func temp
+				   &optional group spool-func)
+  "Read new incoming mail."
+  ;; Nix out the previous split history.
+  (unless group
+    (setq nnmail-split-history nil))
+  (let* ((spools (nnmail-get-spool-files group))
+	 (group-in group)
+	 incoming incomings spool)
+    (when (and (nnmail-get-value "%s-get-new-mail" method)
+	       nnmail-spool-file)
+      ;; We first activate all the groups.
+      (nnmail-activate method)
+      ;; Allow the user to hook.
+      (run-hooks 'nnmail-pre-get-new-mail-hook)
+      ;; Open the message-id cache.
+      (nnmail-cache-open)
+      ;; The we go through all the existing spool files and split the
+      ;; mail from each.
+      (while spools
+	(setq spool (pop spools))
+	;; We read each spool file if either the spool is a POP-mail
+	;; spool, or the file exists.  We can't check for the
+	;; existence of POPped mail.
+	(when (or (string-match "^po:" spool)
+		  (and (file-exists-p (file-truename spool))
+		       (> (nnheader-file-size (file-truename spool)) 0)))
+	  (nnheader-message 3 "%s: Reading incoming mail..." method)
+	  (when (and (nnmail-move-inbox spool)
+		     (file-exists-p nnmail-crash-box))
+	    ;; There is new mail.  We first find out if all this mail
+	    ;; is supposed to go to some specific group.
+	    (setq group (nnmail-get-split-group spool group-in))
+	    ;; We split the mail
+	    (nnmail-split-incoming
+	     nnmail-crash-box (intern (format "%s-save-mail" method))
+	     spool-func group (intern (format "%s-active-number" method)))
+	    ;; Check whether the inbox is to be moved to the special tmp dir.
+	    (setq incoming
+		  (nnmail-make-complex-temp-name
+		   (expand-file-name
+		    (if nnmail-tmp-directory
+			(concat
+			 (file-name-as-directory nnmail-tmp-directory)
+			 (file-name-nondirectory
+			  (concat (file-name-as-directory temp) "Incoming")))
+		      (concat (file-name-as-directory temp) "Incoming")))))
+	    (rename-file nnmail-crash-box incoming t)
+	    (push incoming incomings))))
+      ;; If we did indeed read any incoming spools, we save all info.
+      (when incomings
+	(nnmail-save-active
+	 (nnmail-get-value "%s-group-alist" method)
+	 (nnmail-get-value "%s-active-file" method))
+	(when exit-func
+	  (funcall exit-func))
+	(run-hooks 'nnmail-read-incoming-hook)
+	(nnheader-message 3 "%s: Reading incoming mail...done" method))
+      ;; Close the message-id cache.
+      (nnmail-cache-close)
+      ;; Allow the user to hook.
+      (run-hooks 'nnmail-post-get-new-mail-hook)
+      ;; Delete all the temporary files.
+      (while incomings
+	(setq incoming (pop incomings))
+	(and nnmail-delete-incoming
+	     (file-exists-p incoming)
+	     (file-writable-p incoming)
+	     (delete-file incoming))))))
+
+(defun nnmail-expired-article-p (group time force &optional inhibit)
+  "Say whether an article that is TIME old in GROUP should be expired."
+  (if force
+      t
+    (let ((days (or (and nnmail-expiry-wait-function
+			 (funcall nnmail-expiry-wait-function group))
+		    nnmail-expiry-wait)))
+      (cond ((or (eq days 'never)
+		 (and (not force)
+		      inhibit))
+	     ;; This isn't an expirable group.
+	     nil)
+	    ((eq days 'immediate)
+	     ;; We expire all articles on sight.
+	     t)
+	    ((equal time '(0 0))
+	     ;; This is an ange-ftp group, and we don't have any dates.
+	     nil)
+	    ((numberp days)
+	     (setq days (nnmail-days-to-time days))
+	     ;; Compare the time with the current time.
+	     (nnmail-time-less days (nnmail-time-since time)))))))
+
+(defvar nnmail-read-passwd nil)
+(defun nnmail-read-passwd (prompt &rest args)
+  "Read a password using PROMPT.
+If ARGS, PROMPT is used as an argument to `format'."
+  (let ((prompt
+	 (if args
+	     (apply 'format prompt args)
+	   prompt)))
+    (unless nnmail-read-passwd
+      (if (load "passwd" t)
+	  (setq nnmail-read-passwd 'read-passwd)
+	(unless (fboundp 'ange-ftp-read-passwd)
+	  (autoload 'ange-ftp-read-passwd "ange-ftp"))
+	(setq nnmail-read-passwd 'ange-ftp-read-passwd)))
+    (funcall nnmail-read-passwd prompt)))
+
+(defun nnmail-check-syntax ()
+  "Check (and modify) the syntax of the message in the current buffer."
+  (save-restriction
+    (message-narrow-to-head)
+    (let ((case-fold-search t))
+      (unless (re-search-forward "^Message-ID[ \t]*:" nil t)
+	(insert "Message-ID: " (nnmail-message-id) "\n")))))
+
+(defun nnmail-write-region (start end filename &optional append visit lockname)
+  "Do a `write-region', and then set the file modes."
+  (write-region start end filename append visit lockname)
+  (set-file-modes filename nnmail-default-file-modes))
+
+;;;
+;;; Status functions
+;;;
+
+(defun nnmail-replace-status (name value)
+  "Make status NAME and VALUE part of the current status line."
+  (save-restriction
+    (message-narrow-to-head)
+    (let ((status (nnmail-decode-status)))
+      (setq status (delq (member name status) status))
+      (when value
+	(push (cons name value) status))
+      (message-remove-header "status")
+      (goto-char (point-max))
+      (insert "Status: " (nnmail-encode-status status) "\n"))))
+
+(defun nnmail-decode-status ()
+  "Return a status-value alist from STATUS."
+  (goto-char (point-min))
+  (when (re-search-forward "^Status: " nil t)
+    (let (name value status)
+      (save-restriction
+	;; Narrow to the status.
+	(narrow-to-region
+	 (point)
+	 (if (re-search-forward "^[^ \t]" nil t)
+	     (1- (point))
+	   (point-max)))
+	;; Go through all elements and add them to the list.
+	(goto-char (point-min))
+	(while (re-search-forward "[^ \t=]+" nil t)
+	  (setq name (match-string 0))
+	  (if (not (= (following-char) ?=))
+	      ;; Implied "yes".
+	      (setq value "yes")
+	    (forward-char 1)
+	    (if (not (= (following-char) ?\"))
+		(if (not (looking-at "[^ \t]"))
+		    ;; Implied "no".
+		    (setq value "no")
+		  ;; Unquoted value.
+		  (setq value (match-string 0))
+		  (goto-char (match-end 0)))
+	      ;; Quoted value.
+	      (setq value (read (current-buffer)))))
+	  (push (cons name value) status)))
+      status)))
+
+(defun nnmail-encode-status (status)
+  "Return a status string from STATUS."
+  (mapconcat
+   (lambda (elem)
+     (concat
+      (car elem) "="
+      (if (string-match "[ \t]" (cdr elem))
+	  (prin1-to-string (cdr elem))
+	(cdr elem))))
+   status " "))
+
+(defun nnmail-split-history ()
+  "Generate an overview of where the last mail split put articles."
+  (interactive)
+  (unless nnmail-split-history
+    (error "No current split history"))
+  (with-output-to-temp-buffer "*nnmail split history*"
+    (let ((history nnmail-split-history)
+	  elem)
+      (while (setq elem (pop history))
+	(princ (mapconcat (lambda (ga)
+			    (concat (car ga) ":" (int-to-string (cdr ga))))
+			  elem
+			  ", "))
+	(princ "\n")))))
+
+(defun nnmail-new-mail-p (group)
+  "Say whether GROUP has new mail."
+  (let ((his nnmail-split-history)
+	found)
+    (while his
+      (when (assoc group (pop his))
+	(setq found t
+	      his nil)))
+    found))
+
+(eval-and-compile
+  (autoload 'pop3-movemail "pop3"))
+
+(defun nnmail-pop3-movemail (inbox crashbox)
+  "Function to move mail from INBOX on a pop3 server to file CRASHBOX."
+  (let ((pop3-maildrop
+         (substring inbox (match-end (string-match "^po:" inbox)))))
+    (pop3-movemail crashbox)))
+
+(run-hooks 'nnmail-load-hook)
+
+(provide 'nnmail)
+
+;;; nnmail.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnmbox.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,552 @@
+;;; nnmbox.el --- mail mbox access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; 	Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; For an overview of what the interface functions do, please see the
+;; Gnus sources.
+
+;;; Code:
+
+(require 'nnheader)
+(require 'message)
+(require 'nnmail)
+(require 'nnoo)
+(eval-when-compile (require 'cl))
+
+(nnoo-declare nnmbox)
+
+(defvoo nnmbox-mbox-file (expand-file-name "~/mbox")
+  "The name of the mail box file in the user's home directory.")
+
+(defvoo nnmbox-active-file (expand-file-name "~/.mbox-active")
+  "The name of the active file for the mail box.")
+
+(defvoo nnmbox-get-new-mail t
+  "If non-nil, nnmbox will check the incoming mail file and split the mail.")
+
+(defvoo nnmbox-prepare-save-mail-hook nil
+  "Hook run narrowed to an article before saving.")
+
+
+
+(defconst nnmbox-version "nnmbox 1.0"
+  "nnmbox version.")
+
+(defvoo nnmbox-current-group nil
+  "Current nnmbox news group directory.")
+
+(defconst nnmbox-mbox-buffer nil)
+
+(defvoo nnmbox-status-string "")
+
+(defvoo nnmbox-group-alist nil)
+(defvoo nnmbox-active-timestamp nil)
+
+
+
+;;; Interface functions
+
+(nnoo-define-basics nnmbox)
+
+(deffoo nnmbox-retrieve-headers (sequence &optional newsgroup server fetch-old)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (let ((number (length sequence))
+	  (count 0)
+	  article art-string start stop)
+      (nnmbox-possibly-change-newsgroup newsgroup server)
+      (while sequence
+	(setq article (car sequence))
+	(setq art-string (nnmbox-article-string article))
+	(set-buffer nnmbox-mbox-buffer)
+	(when (or (search-forward art-string nil t)
+		  (progn (goto-char (point-min))
+			 (search-forward art-string nil t)))
+	  (setq start
+		(save-excursion
+		  (re-search-backward
+		   (concat "^" message-unix-mail-delimiter) nil t)
+		  (point)))
+	  (search-forward "\n\n" nil t)
+	  (setq stop (1- (point)))
+	  (set-buffer nntp-server-buffer)
+	  (insert (format "221 %d Article retrieved.\n" article))
+	  (insert-buffer-substring nnmbox-mbox-buffer start stop)
+	  (goto-char (point-max))
+	  (insert ".\n"))
+	(setq sequence (cdr sequence))
+	(setq count (1+ count))
+	(and (numberp nnmail-large-newsgroup)
+	     (> number nnmail-large-newsgroup)
+	     (zerop (% count 20))
+	     (nnheader-message 5 "nnmbox: Receiving headers... %d%%"
+			       (/ (* count 100) number))))
+
+      (and (numberp nnmail-large-newsgroup)
+	   (> number nnmail-large-newsgroup)
+	   (nnheader-message 5 "nnmbox: Receiving headers...done"))
+
+      (set-buffer nntp-server-buffer)
+      (nnheader-fold-continuation-lines)
+      'headers)))
+
+(deffoo nnmbox-open-server (server &optional defs)
+  (nnoo-change-server 'nnmbox server defs)
+  (nnmbox-create-mbox)
+  (cond
+   ((not (file-exists-p nnmbox-mbox-file))
+    (nnmbox-close-server)
+    (nnheader-report 'nnmbox "No such file: %s" nnmbox-mbox-file))
+   ((file-directory-p nnmbox-mbox-file)
+    (nnmbox-close-server)
+    (nnheader-report 'nnmbox "Not a regular file: %s" nnmbox-mbox-file))
+   (t
+    (nnheader-report 'nnmbox "Opened server %s using mbox %s" server
+		     nnmbox-mbox-file)
+    t)))
+
+(deffoo nnmbox-close-server (&optional server)
+  (when (and nnmbox-mbox-buffer
+	     (buffer-name nnmbox-mbox-buffer))
+    (kill-buffer nnmbox-mbox-buffer))
+  (nnoo-close-server 'nnmbox server)
+  t)
+
+(deffoo nnmbox-server-opened (&optional server)
+  (and (nnoo-current-server-p 'nnmbox server)
+       nnmbox-mbox-buffer
+       (buffer-name nnmbox-mbox-buffer)
+       nntp-server-buffer
+       (buffer-name nntp-server-buffer)))
+
+(deffoo nnmbox-request-article (article &optional newsgroup server buffer)
+  (nnmbox-possibly-change-newsgroup newsgroup server)
+  (save-excursion
+    (set-buffer nnmbox-mbox-buffer)
+    (goto-char (point-min))
+    (when (search-forward (nnmbox-article-string article) nil t)
+      (let (start stop)
+	(re-search-backward (concat "^" message-unix-mail-delimiter) nil t)
+	(setq start (point))
+	(forward-line 1)
+	(or (and (re-search-forward
+		  (concat "^" message-unix-mail-delimiter) nil t)
+		 (forward-line -1))
+	    (goto-char (point-max)))
+	(setq stop (point))
+	(let ((nntp-server-buffer (or buffer nntp-server-buffer)))
+	  (set-buffer nntp-server-buffer)
+	  (erase-buffer)
+	  (insert-buffer-substring nnmbox-mbox-buffer start stop)
+	  (goto-char (point-min))
+	  (while (looking-at "From ")
+	    (delete-char 5)
+	    (insert "X-From-Line: ")
+	    (forward-line 1))
+	  (if (numberp article)
+	      (cons nnmbox-current-group article)
+	    (nnmbox-article-group-number)))))))
+
+(deffoo nnmbox-request-group (group &optional server dont-check)
+  (let ((active (cadr (assoc group nnmbox-group-alist))))
+    (cond
+     ((or (null active)
+	  (null (nnmbox-possibly-change-newsgroup group server)))
+      (nnheader-report 'nnmbox "No such group: %s" group))
+     (dont-check
+      (nnheader-report 'nnmbox "Selected group %s" group)
+      (nnheader-insert ""))
+     (t
+      (nnheader-report 'nnmbox "Selected group %s" group)
+      (nnheader-insert "211 %d %d %d %s\n"
+		       (1+ (- (cdr active) (car active)))
+		       (car active) (cdr active) group)))))
+
+(deffoo nnmbox-request-scan (&optional group server)
+  (nnmbox-possibly-change-newsgroup group server)
+  (nnmbox-read-mbox)
+  (nnmail-get-new-mail
+   'nnmbox
+   (lambda ()
+     (save-excursion
+       (set-buffer nnmbox-mbox-buffer)
+       (save-buffer)))
+   (file-name-directory nnmbox-mbox-file)
+   group
+   (lambda ()
+     (save-excursion
+       (let ((in-buf (current-buffer)))
+	 (set-buffer nnmbox-mbox-buffer)
+	 (goto-char (point-max))
+	 (insert-buffer-substring in-buf)))
+     (nnmail-save-active nnmbox-group-alist nnmbox-active-file))))
+
+(deffoo nnmbox-close-group (group &optional server)
+  t)
+
+(deffoo nnmbox-request-list (&optional server)
+  (save-excursion
+    (nnmail-find-file nnmbox-active-file)
+    (setq nnmbox-group-alist (nnmail-get-active))
+    t))
+
+(deffoo nnmbox-request-newgroups (date &optional server)
+  (nnmbox-request-list server))
+
+(deffoo nnmbox-request-list-newsgroups (&optional server)
+  (nnheader-report 'nnmbox "LIST NEWSGROUPS is not implemented."))
+
+(deffoo nnmbox-request-expire-articles
+  (articles newsgroup &optional server force)
+  (nnmbox-possibly-change-newsgroup newsgroup server)
+  (let* ((is-old t)
+	 rest)
+    (nnmail-activate 'nnmbox)
+
+    (save-excursion
+      (set-buffer nnmbox-mbox-buffer)
+      (while (and articles is-old)
+	(goto-char (point-min))
+	(when (search-forward (nnmbox-article-string (car articles)) nil t)
+	  (if (setq is-old
+		    (nnmail-expired-article-p
+		     newsgroup
+		     (buffer-substring
+		      (point) (progn (end-of-line) (point))) force))
+	      (progn
+		(nnheader-message 5 "Deleting article %d in %s..."
+				  (car articles) newsgroup)
+		(nnmbox-delete-mail))
+	    (push (car articles) rest)))
+	(setq articles (cdr articles)))
+      (save-buffer)
+      ;; Find the lowest active article in this group.
+      (let ((active (nth 1 (assoc newsgroup nnmbox-group-alist))))
+	(goto-char (point-min))
+	(while (and (not (search-forward
+			  (nnmbox-article-string (car active)) nil t))
+		    (<= (car active) (cdr active)))
+	  (setcar active (1+ (car active)))
+	  (goto-char (point-min))))
+      (nnmail-save-active nnmbox-group-alist nnmbox-active-file)
+      (nconc rest articles))))
+
+(deffoo nnmbox-request-move-article
+  (article group server accept-form &optional last)
+  (let ((buf (get-buffer-create " *nnmbox move*"))
+	result)
+    (and
+     (nnmbox-request-article article group server)
+     (save-excursion
+       (set-buffer buf)
+       (buffer-disable-undo (current-buffer))
+       (erase-buffer)
+       (insert-buffer-substring nntp-server-buffer)
+       (goto-char (point-min))
+       (while (re-search-forward
+	       "^X-Gnus-Newsgroup:"
+	       (save-excursion (search-forward "\n\n" nil t) (point)) t)
+	 (delete-region (progn (beginning-of-line) (point))
+			(progn (forward-line 1) (point))))
+       (setq result (eval accept-form))
+       (kill-buffer buf)
+       result)
+     (save-excursion
+       (nnmbox-possibly-change-newsgroup group server)
+       (set-buffer nnmbox-mbox-buffer)
+       (goto-char (point-min))
+       (when (search-forward (nnmbox-article-string article) nil t)
+	 (nnmbox-delete-mail))
+       (and last (save-buffer))))
+    result))
+
+(deffoo nnmbox-request-accept-article (group &optional server last)
+  (nnmbox-possibly-change-newsgroup group server)
+  (nnmail-check-syntax)
+  (let ((buf (current-buffer))
+	result)
+    (goto-char (point-min))
+    ;; The From line may have been quoted by movemail.
+    (when (looking-at (concat ">" message-unix-mail-delimiter))
+      (delete-char 1))
+    (if (looking-at "X-From-Line: ")
+	(replace-match "From ")
+      (insert "From nobody " (current-time-string) "\n"))
+    (and
+     (nnmail-activate 'nnmbox)
+     (progn
+       (set-buffer buf)
+       (goto-char (point-min))
+       (search-forward "\n\n" nil t)
+       (forward-line -1)
+       (while (re-search-backward "^X-Gnus-Newsgroup: " nil t)
+	 (delete-region (point) (progn (forward-line 1) (point))))
+       (when nnmail-cache-accepted-message-ids
+	 (nnmail-cache-insert (nnmail-fetch-field "message-id")))
+       (setq result (if (stringp group)
+			(list (cons group (nnmbox-active-number group)))
+		      (nnmail-article-group 'nnmbox-active-number)))
+       (if (and (null result)
+		(yes-or-no-p "Moved to `junk' group; delete article? "))
+	   (setq result 'junk)
+	 (setq result (car (nnmbox-save-mail result)))))
+     (save-excursion
+       (set-buffer nnmbox-mbox-buffer)
+       (goto-char (point-max))
+       (insert-buffer-substring buf)
+       (when last
+	 (when nnmail-cache-accepted-message-ids
+	   (nnmail-cache-close))
+	 (nnmail-save-active nnmbox-group-alist nnmbox-active-file)
+	 (save-buffer))))
+    result))
+
+(deffoo nnmbox-request-replace-article (article group buffer)
+  (nnmbox-possibly-change-newsgroup group)
+  (save-excursion
+    (set-buffer nnmbox-mbox-buffer)
+    (goto-char (point-min))
+    (if (not (search-forward (nnmbox-article-string article) nil t))
+	nil
+      (nnmbox-delete-mail t t)
+      (insert-buffer-substring buffer)
+      (save-buffer)
+      t)))
+
+(deffoo nnmbox-request-delete-group (group &optional force server)
+  (nnmbox-possibly-change-newsgroup group server)
+  ;; Delete all articles in GROUP.
+  (if (not force)
+      ()				; Don't delete the articles.
+    (save-excursion
+      (set-buffer nnmbox-mbox-buffer)
+      (goto-char (point-min))
+      ;; Delete all articles in this group.
+      (let ((ident (concat "\nX-Gnus-Newsgroup: " nnmbox-current-group ":"))
+	    found)
+	(while (search-forward ident nil t)
+	  (setq found t)
+	  (nnmbox-delete-mail))
+	(when found
+	  (save-buffer)))))
+  ;; Remove the group from all structures.
+  (setq nnmbox-group-alist
+	(delq (assoc group nnmbox-group-alist) nnmbox-group-alist)
+	nnmbox-current-group nil)
+  ;; Save the active file.
+  (nnmail-save-active nnmbox-group-alist nnmbox-active-file)
+  t)
+
+(deffoo nnmbox-request-rename-group (group new-name &optional server)
+  (nnmbox-possibly-change-newsgroup group server)
+  (save-excursion
+    (set-buffer nnmbox-mbox-buffer)
+    (goto-char (point-min))
+    (let ((ident (concat "\nX-Gnus-Newsgroup: " nnmbox-current-group ":"))
+	  (new-ident (concat "\nX-Gnus-Newsgroup: " new-name ":"))
+	  found)
+      (while (search-forward ident nil t)
+	(replace-match new-ident t t)
+	(setq found t))
+      (when found
+	(save-buffer))))
+  (let ((entry (assoc group nnmbox-group-alist)))
+    (when entry
+      (setcar entry new-name))
+    (setq nnmbox-current-group nil)
+    ;; Save the new group alist.
+    (nnmail-save-active nnmbox-group-alist nnmbox-active-file)
+    t))
+
+
+;;; Internal functions.
+
+;; If FORCE, delete article no matter how many X-Gnus-Newsgroup
+;; headers there are.  If LEAVE-DELIM, don't delete the Unix mbox
+;; delimiter line.
+(defun nnmbox-delete-mail (&optional force leave-delim)
+  ;; Delete the current X-Gnus-Newsgroup line.
+  (or force
+      (delete-region
+       (progn (beginning-of-line) (point))
+       (progn (forward-line 1) (point))))
+  ;; Beginning of the article.
+  (save-excursion
+    (save-restriction
+      (narrow-to-region
+       (save-excursion
+	 (re-search-backward (concat "^" message-unix-mail-delimiter) nil t)
+	 (if leave-delim (progn (forward-line 1) (point))
+	   (match-beginning 0)))
+       (progn
+	 (forward-line 1)
+	 (or (and (re-search-forward (concat "^" message-unix-mail-delimiter)
+				     nil t)
+		  (if (and (not (bobp)) leave-delim)
+		      (progn (forward-line -2) (point))
+		    (match-beginning 0)))
+	     (point-max))))
+      (goto-char (point-min))
+      ;; Only delete the article if no other groups owns it as well.
+      (when (or force (not (re-search-forward "^X-Gnus-Newsgroup: " nil t)))
+	(delete-region (point-min) (point-max))))))
+
+(defun nnmbox-possibly-change-newsgroup (newsgroup &optional server)
+  (when (and server
+	     (not (nnmbox-server-opened server)))
+    (nnmbox-open-server server))
+  (when (or (not nnmbox-mbox-buffer)
+	    (not (buffer-name nnmbox-mbox-buffer)))
+    (save-excursion
+      (set-buffer (setq nnmbox-mbox-buffer
+			(nnheader-find-file-noselect
+			 nnmbox-mbox-file nil 'raw)))
+      (buffer-disable-undo (current-buffer))))
+  (when (not nnmbox-group-alist)
+    (nnmail-activate 'nnmbox))
+  (if newsgroup
+      (when (assoc newsgroup nnmbox-group-alist)
+	(setq nnmbox-current-group newsgroup))
+    t))
+
+(defun nnmbox-article-string (article)
+  (if (numberp article)
+      (concat "\nX-Gnus-Newsgroup: " nnmbox-current-group ":"
+	      (int-to-string article) " ")
+    (concat "\nMessage-ID: " article)))
+
+(defun nnmbox-article-group-number ()
+  (save-excursion
+    (goto-char (point-min))
+    (when (re-search-forward "^X-Gnus-Newsgroup: +\\([^:]+\\):\\([0-9]+\\) "
+			     nil t)
+      (cons (buffer-substring (match-beginning 1) (match-end 1))
+	    (string-to-int
+	     (buffer-substring (match-beginning 2) (match-end 2)))))))
+
+(defun nnmbox-save-mail (group-art)
+  "Called narrowed to an article."
+  (let ((delim (concat "^" message-unix-mail-delimiter)))
+    (goto-char (point-min))
+    ;; This might come from somewhere else.
+    (unless (looking-at delim)
+      (insert "From nobody " (current-time-string) "\n")
+      (goto-char (point-min)))
+    ;; Quote all "From " lines in the article.
+    (forward-line 1)
+    (while (re-search-forward delim nil t)
+      (beginning-of-line)
+      (insert "> "))
+    (nnmail-insert-lines)
+    (nnmail-insert-xref group-art)
+    (nnmbox-insert-newsgroup-line group-art)
+    (run-hooks 'nnmail-prepare-save-mail-hook)
+    (run-hooks 'nnmbox-prepare-save-mail-hook)
+    group-art))
+
+(defun nnmbox-insert-newsgroup-line (group-art)
+  (save-excursion
+    (goto-char (point-min))
+    (when (search-forward "\n\n" nil t)
+      (forward-char -1)
+      (while group-art
+	(insert (format "X-Gnus-Newsgroup: %s:%d   %s\n"
+			(caar group-art) (cdar group-art)
+			(current-time-string)))
+	(setq group-art (cdr group-art))))
+    t))
+
+(defun nnmbox-active-number (group)
+  ;; Find the next article number in GROUP.
+  (let ((active (cadr (assoc group nnmbox-group-alist))))
+    (if active
+	(setcdr active (1+ (cdr active)))
+      ;; This group is new, so we create a new entry for it.
+      ;; This might be a bit naughty... creating groups on the drop of
+      ;; a hat, but I don't know...
+      (push (list group (setq active (cons 1 1)))
+	    nnmbox-group-alist))
+    (cdr active)))
+
+(defun nnmbox-create-mbox ()
+  (when (not (file-exists-p nnmbox-mbox-file))
+    (nnmail-write-region 1 1 nnmbox-mbox-file t 'nomesg)))
+
+(defun nnmbox-read-mbox ()
+  (nnmail-activate 'nnmbox)
+  (nnmbox-create-mbox)
+  (if (and nnmbox-mbox-buffer
+	   (buffer-name nnmbox-mbox-buffer)
+	   (save-excursion
+	     (set-buffer nnmbox-mbox-buffer)
+	     (= (buffer-size) (nnheader-file-size nnmbox-mbox-file))))
+      ()
+    (save-excursion
+      (let ((delim (concat "^" message-unix-mail-delimiter))
+	    (alist nnmbox-group-alist)
+	    start end number)
+	(set-buffer (setq nnmbox-mbox-buffer
+			  (nnheader-find-file-noselect
+			   nnmbox-mbox-file nil 'raw)))
+	(buffer-disable-undo (current-buffer))
+
+	;; Go through the group alist and compare against
+	;; the mbox file.
+	(while alist
+	  (goto-char (point-max))
+	  (when (and (re-search-backward
+		      (format "^X-Gnus-Newsgroup: %s:\\([0-9]+\\) "
+			      (caar alist)) nil t)
+		     (>= (setq number
+			       (string-to-number
+				(buffer-substring
+				 (match-beginning 1) (match-end 1))))
+			 (cdadar alist)))
+	    (setcdr (cadar alist) (1+ number)))
+	  (setq alist (cdr alist)))
+
+	(goto-char (point-min))
+	(while (re-search-forward delim nil t)
+	  (setq start (match-beginning 0))
+	  (when (not (search-forward "\nX-Gnus-Newsgroup: "
+				     (save-excursion
+				       (setq end
+					     (or
+					      (and
+					       (re-search-forward delim nil t)
+					       (match-beginning 0))
+					      (point-max))))
+				     t))
+	    (save-excursion
+	      (save-restriction
+		(narrow-to-region start end)
+		(nnmbox-save-mail
+		 (nnmail-article-group 'nnmbox-active-number)))))
+	  (goto-char end))))))
+
+(provide 'nnmbox)
+
+;;; nnmbox.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnmh.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,547 @@
+;;; nnmh.el --- mhspool access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; 	Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; Based on nnspool.el by Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>.
+;; For an overview of what the interface functions do, please see the
+;; Gnus sources.
+
+;;; Code:
+
+(require 'nnheader)
+(require 'nnmail)
+(require 'gnus-start)
+(require 'nnoo)
+(require 'cl)
+
+(nnoo-declare nnmh)
+
+(defvoo nnmh-directory message-directory
+  "*Mail spool directory.")
+
+(defvoo nnmh-get-new-mail t
+  "*If non-nil, nnmh will check the incoming mail file and split the mail.")
+
+(defvoo nnmh-prepare-save-mail-hook nil
+  "*Hook run narrowed to an article before saving.")
+
+(defvoo nnmh-be-safe nil
+  "*If non-nil, nnmh will check all articles to make sure whether they are new or not.")
+
+
+
+(defconst nnmh-version "nnmh 1.0"
+  "nnmh version.")
+
+(defvoo nnmh-current-directory nil
+  "Current news group directory.")
+
+(defvoo nnmh-status-string "")
+(defvoo nnmh-group-alist nil)
+
+
+
+;;; Interface functions.
+
+(nnoo-define-basics nnmh)
+
+(deffoo nnmh-retrieve-headers (articles &optional newsgroup server fetch-old)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (let* ((file nil)
+	   (number (length articles))
+	   (large (and (numberp nnmail-large-newsgroup)
+		       (> number nnmail-large-newsgroup)))
+	   (count 0)
+	   beg article)
+      (nnmh-possibly-change-directory newsgroup server)
+      ;; We don't support fetching by Message-ID.
+      (if (stringp (car articles))
+	  'headers
+	(while articles
+	  (when (and (file-exists-p
+		      (setq file (concat (file-name-as-directory
+					  nnmh-current-directory)
+					 (int-to-string
+					  (setq article (pop articles))))))
+		     (not (file-directory-p file)))
+	    (insert (format "221 %d Article retrieved.\n" article))
+	    (setq beg (point))
+	    (nnheader-insert-head file)
+	    (goto-char beg)
+	    (if (search-forward "\n\n" nil t)
+		(forward-char -1)
+	      (goto-char (point-max))
+	      (insert "\n\n"))
+	    (insert ".\n")
+	    (delete-region (point) (point-max)))
+	  (setq count (1+ count))
+
+	  (and large
+	       (zerop (% count 20))
+	       (message "nnmh: Receiving headers... %d%%"
+			(/ (* count 100) number))))
+
+	(when large
+	  (message "nnmh: Receiving headers...done"))
+
+	(nnheader-fold-continuation-lines)
+	'headers))))
+
+(deffoo nnmh-open-server (server &optional defs)
+  (nnoo-change-server 'nnmh server defs)
+  (when (not (file-exists-p nnmh-directory))
+    (condition-case ()
+	(make-directory nnmh-directory t)
+      (error t)))
+  (cond
+   ((not (file-exists-p nnmh-directory))
+    (nnmh-close-server)
+    (nnheader-report 'nnmh "Couldn't create directory: %s" nnmh-directory))
+   ((not (file-directory-p (file-truename nnmh-directory)))
+    (nnmh-close-server)
+    (nnheader-report 'nnmh "Not a directory: %s" nnmh-directory))
+   (t
+    (nnheader-report 'nnmh "Opened server %s using directory %s"
+		     server nnmh-directory)
+    t)))
+
+(deffoo nnmh-request-article (id &optional newsgroup server buffer)
+  (nnmh-possibly-change-directory newsgroup server)
+  (let ((file (if (stringp id)
+		  nil
+		(concat nnmh-current-directory (int-to-string id))))
+	(nntp-server-buffer (or buffer nntp-server-buffer)))
+    (and (stringp file)
+	 (file-exists-p file)
+	 (not (file-directory-p file))
+	 (save-excursion (nnmail-find-file file))
+	 (string-to-int (file-name-nondirectory file)))))
+
+(deffoo nnmh-request-group (group &optional server dont-check)
+  (let ((pathname (nnmail-group-pathname group nnmh-directory))
+	dir)
+    (cond
+     ((not (file-directory-p pathname))
+      (nnheader-report
+       'nnmh "Can't select group (no such directory): %s" group))
+     (t
+      (setq nnmh-current-directory pathname)
+      (and nnmh-get-new-mail
+	   nnmh-be-safe
+	   (nnmh-update-gnus-unreads group))
+      (cond
+       (dont-check
+	(nnheader-report 'nnmh "Selected group %s" group)
+	t)
+       (t
+	;; Re-scan the directory if it's on a foreign system.
+	(nnheader-re-read-dir pathname)
+	(setq dir
+	      (sort
+	       (mapcar (lambda (name) (string-to-int name))
+		       (directory-files pathname nil "^[0-9]+$" t))
+	       '<))
+	  (cond
+	   (dir
+	    (nnheader-report 'nnmh "Selected group %s" group)
+	    (nnheader-insert
+	     "211 %d %d %d %s\n" (length dir) (car dir)
+	     (progn (while (cdr dir) (setq dir (cdr dir))) (car dir))
+	     group))
+	   (t
+	    (nnheader-report 'nnmh "Empty group %s" group)
+	    (nnheader-insert (format "211 0 1 0 %s\n" group))))))))))
+
+(deffoo nnmh-request-scan (&optional group server)
+  (nnmail-get-new-mail 'nnmh nil nnmh-directory group))
+
+(deffoo nnmh-request-list (&optional server dir)
+  (nnheader-insert "")
+  (let ((nnmh-toplev
+	 (or dir (file-truename (file-name-as-directory nnmh-directory)))))
+    (nnmh-request-list-1 nnmh-toplev))
+  (setq nnmh-group-alist (nnmail-get-active))
+  t)
+
+(defvar nnmh-toplev)
+(defun nnmh-request-list-1 (dir)
+  (setq dir (expand-file-name dir))
+  ;; Recurse down all directories.
+  (let ((dirs (and (file-readable-p dir)
+		   (> (nth 1 (file-attributes (file-chase-links dir))) 2)
+		   (directory-files dir t nil t)))
+	dir)
+    ;; Recurse down directories.
+    (while (setq dir (pop dirs))
+      (when (and (not (member (file-name-nondirectory dir) '("." "..")))
+		 (file-directory-p dir)
+		 (file-readable-p dir))
+	(nnmh-request-list-1 dir))))
+  ;; For each directory, generate an active file line.
+  (unless (string= (expand-file-name nnmh-toplev) dir)
+    (let ((files (mapcar
+		  (lambda (name) (string-to-int name))
+		  (directory-files dir nil "^[0-9]+$" t))))
+      (when files
+	(save-excursion
+	  (set-buffer nntp-server-buffer)
+	  (goto-char (point-max))
+	  (insert
+	   (format
+	    "%s %d %d y\n"
+	    (progn
+	      (string-match
+	       (regexp-quote
+		(file-truename (file-name-as-directory
+				(expand-file-name nnmh-toplev))))
+	       dir)
+	      (nnheader-replace-chars-in-string
+	       (substring dir (match-end 0)) ?/ ?.))
+	    (apply 'max files)
+	    (apply 'min files)))))))
+  t)
+
+(deffoo nnmh-request-newgroups (date &optional server)
+  (nnmh-request-list server))
+
+(deffoo nnmh-request-expire-articles (articles newsgroup
+					       &optional server force)
+  (nnmh-possibly-change-directory newsgroup server)
+  (let* ((active-articles
+	  (mapcar
+	   (function
+	    (lambda (name)
+	      (string-to-int name)))
+	   (directory-files nnmh-current-directory nil "^[0-9]+$" t)))
+	 (is-old t)
+	 article rest mod-time)
+    (nnmail-activate 'nnmh)
+
+    (while (and articles is-old)
+      (setq article (concat nnmh-current-directory
+			    (int-to-string (car articles))))
+      (when (setq mod-time (nth 5 (file-attributes article)))
+	(if (and (nnmh-deletable-article-p newsgroup (car articles))
+		 (setq is-old
+		       (nnmail-expired-article-p newsgroup mod-time force)))
+	    (progn
+	      (nnheader-message 5 "Deleting article %s in %s..."
+				article newsgroup)
+	      (condition-case ()
+		  (funcall nnmail-delete-file-function article)
+		(file-error
+		 (nnheader-message 1 "Couldn't delete article %s in %s"
+				   article newsgroup)
+		 (push (car articles) rest))))
+	  (push (car articles) rest)))
+      (setq articles (cdr articles)))
+    (message "")
+    (nconc rest articles)))
+
+(deffoo nnmh-close-group (group &optional server)
+  t)
+
+(deffoo nnmh-request-move-article
+  (article group server accept-form &optional last)
+  (let ((buf (get-buffer-create " *nnmh move*"))
+	result)
+    (and
+     (nnmh-deletable-article-p group article)
+     (nnmh-request-article article group server)
+     (save-excursion
+       (set-buffer buf)
+       (erase-buffer)
+       (insert-buffer-substring nntp-server-buffer)
+       (setq result (eval accept-form))
+       (kill-buffer (current-buffer))
+       result)
+     (progn
+       (nnmh-possibly-change-directory group server)
+       (condition-case ()
+	   (funcall nnmail-delete-file-function
+		    (concat nnmh-current-directory (int-to-string article)))
+	 (file-error nil))))
+    result))
+
+(deffoo nnmh-request-accept-article (group &optional server last noinsert)
+  (nnmh-possibly-change-directory group server)
+  (nnmail-check-syntax)
+  (when nnmail-cache-accepted-message-ids
+    (nnmail-cache-insert (nnmail-fetch-field "message-id")))
+  (prog1
+      (if (stringp group)
+	  (and
+	   (nnmail-activate 'nnmh)
+	   (car (nnmh-save-mail
+		 (list (cons group (nnmh-active-number group)))
+		 noinsert)))
+	(and
+	 (nnmail-activate 'nnmh)
+	 (let ((res (nnmail-article-group 'nnmh-active-number)))
+	   (if (and (null res)
+		    (yes-or-no-p "Moved to `junk' group; delete article? "))
+	       'junk
+	     (car (nnmh-save-mail res noinsert))))))
+    (when (and last nnmail-cache-accepted-message-ids)
+      (nnmail-cache-close))))
+
+(deffoo nnmh-request-replace-article (article group buffer)
+  (nnmh-possibly-change-directory group)
+  (save-excursion
+    (set-buffer buffer)
+    (nnmh-possibly-create-directory group)
+    (ignore-errors
+      (nnmail-write-region
+       (point-min) (point-max)
+       (concat nnmh-current-directory (int-to-string article))
+       nil (if (nnheader-be-verbose 5) nil 'nomesg))
+      t)))
+
+(deffoo nnmh-request-create-group (group &optional server args)
+  (nnmail-activate 'nnmh)
+  (unless (assoc group nnmh-group-alist)
+    (let (active)
+      (push (list group (setq active (cons 1 0)))
+	    nnmh-group-alist)
+      (nnmh-possibly-create-directory group)
+      (nnmh-possibly-change-directory group server)
+      (let ((articles (mapcar
+		       (lambda (file)
+			 (string-to-int file))
+		       (directory-files
+			nnmh-current-directory nil "^[0-9]+$"))))
+	(when articles
+	  (setcar active (apply 'min articles))
+	  (setcdr active (apply 'max articles))))))
+  t)
+
+(deffoo nnmh-request-delete-group (group &optional force server)
+  (nnmh-possibly-change-directory group server)
+  ;; Delete all articles in GROUP.
+  (if (not force)
+      ()				; Don't delete the articles.
+    (let ((articles (directory-files nnmh-current-directory t "^[0-9]+$")))
+      (while articles
+	(when (file-writable-p (car articles))
+	  (nnheader-message 5 "Deleting article %s in %s..."
+			    (car articles) group)
+	  (funcall nnmail-delete-file-function (car articles)))
+	(setq articles (cdr articles))))
+    ;; Try to delete the directory itself.
+    (ignore-errors
+      (delete-directory nnmh-current-directory)))
+  ;; Remove the group from all structures.
+  (setq nnmh-group-alist
+	(delq (assoc group nnmh-group-alist) nnmh-group-alist)
+	nnmh-current-directory nil)
+  t)
+
+(deffoo nnmh-request-rename-group (group new-name &optional server)
+  (nnmh-possibly-change-directory group server)
+  (let ((new-dir (nnmail-group-pathname new-name nnmh-directory))
+	(old-dir (nnmail-group-pathname group nnmh-directory)))
+    (when (ignore-errors
+	    (make-directory new-dir t)
+	    t)
+      ;; We move the articles file by file instead of renaming
+      ;; the directory -- there may be subgroups in this group.
+      ;; One might be more clever, I guess.
+      (let ((files (nnheader-article-to-file-alist old-dir)))
+	(while files
+	  (rename-file
+	   (concat old-dir (cdar files))
+	   (concat new-dir (cdar files)))
+	  (pop files)))
+      (when (<= (length (directory-files old-dir)) 2)
+	(ignore-errors
+	  (delete-directory old-dir)))
+      ;; That went ok, so we change the internal structures.
+      (let ((entry (assoc group nnmh-group-alist)))
+	(when entry
+	  (setcar entry new-name))
+	(setq nnmh-current-directory nil)
+	t))))
+
+(nnoo-define-skeleton nnmh)
+
+
+;;; Internal functions.
+
+(defun nnmh-possibly-change-directory (newsgroup &optional server)
+  (when (and server
+	     (not (nnmh-server-opened server)))
+    (nnmh-open-server server))
+  (when newsgroup
+    (let ((pathname (nnmail-group-pathname newsgroup nnmh-directory)))
+      (if (file-directory-p pathname)
+	  (setq nnmh-current-directory pathname)
+	(error "No such newsgroup: %s" newsgroup)))))
+
+(defun nnmh-possibly-create-directory (group)
+  (let (dir dirs)
+    (setq dir (nnmail-group-pathname group nnmh-directory))
+    (while (not (file-directory-p dir))
+      (push dir dirs)
+      (setq dir (file-name-directory (directory-file-name dir))))
+    (while dirs
+      (when (make-directory (directory-file-name (car dirs)))
+	(error "Could not create directory %s" (car dirs)))
+      (nnheader-message 5 "Creating mail directory %s" (car dirs))
+      (setq dirs (cdr dirs)))))
+
+(defun nnmh-save-mail (group-art &optional noinsert)
+  "Called narrowed to an article."
+  (unless noinsert
+    (nnmail-insert-lines)
+    (nnmail-insert-xref group-art))
+  (run-hooks 'nnmail-prepare-save-mail-hook)
+  (run-hooks 'nnmh-prepare-save-mail-hook)
+  (goto-char (point-min))
+  (while (looking-at "From ")
+    (replace-match "X-From-Line: ")
+    (forward-line 1))
+  ;; We save the article in all the newsgroups it belongs in.
+  (let ((ga group-art)
+	first)
+    (while ga
+      (nnmh-possibly-create-directory (caar ga))
+      (let ((file (concat (nnmail-group-pathname
+			   (caar ga) nnmh-directory)
+			  (int-to-string (cdar ga)))))
+	(if first
+	    ;; It was already saved, so we just make a hard link.
+	    (funcall nnmail-crosspost-link-function first file t)
+	  ;; Save the article.
+	  (nnmail-write-region (point-min) (point-max) file nil nil)
+	  (setq first file)))
+      (setq ga (cdr ga))))
+  group-art)
+
+(defun nnmh-active-number (group)
+  "Compute the next article number in GROUP."
+  (let ((active (cadr (assoc group nnmh-group-alist)))
+	(dir (nnmail-group-pathname group nnmh-directory)))
+    (unless active
+      ;; The group wasn't known to nnmh, so we just create an active
+      ;; entry for it.
+      (setq active (cons 1 0))
+      (push (list group active) nnmh-group-alist)
+      (unless (file-exists-p dir)
+	(make-directory dir))
+      ;; Find the highest number in the group.
+      (let ((files (sort
+		    (mapcar
+		     (lambda (f)
+		       (string-to-int f))
+		     (directory-files dir nil "^[0-9]+$"))
+		    '>)))
+	(when files
+	  (setcdr active (car files)))))
+    (setcdr active (1+ (cdr active)))
+    (while (file-exists-p
+	    (concat (nnmail-group-pathname group nnmh-directory)
+		    (int-to-string (cdr active))))
+      (setcdr active (1+ (cdr active))))
+    (cdr active)))
+
+(defun nnmh-update-gnus-unreads (group)
+  ;; Go through the .nnmh-articles file and compare with the actual
+  ;; articles in this folder.  The articles that are "new" will be
+  ;; marked as unread by Gnus.
+  (let* ((dir nnmh-current-directory)
+	 (files (sort (mapcar (function (lambda (name) (string-to-int name)))
+			      (directory-files nnmh-current-directory
+					       nil "^[0-9]+$" t))
+		      '<))
+	 (nnmh-file (concat dir ".nnmh-articles"))
+	 new articles)
+    ;; Load the .nnmh-articles file.
+    (when (file-exists-p nnmh-file)
+      (setq articles
+	    (let (nnmh-newsgroup-articles)
+	      (ignore-errors (load nnmh-file nil t t))
+	      nnmh-newsgroup-articles)))
+    ;; Add all new articles to the `new' list.
+    (let ((art files))
+      (while art
+	(unless (assq (car art) articles)
+	  (push (car art) new))
+	(setq art (cdr art))))
+    ;; Remove all deleted articles.
+    (let ((art articles))
+      (while art
+	(unless (memq (caar art) files)
+	  (setq articles (delq (car art) articles)))
+	(setq art (cdr art))))
+    ;; Check whether the articles really are the ones that Gnus thinks
+    ;; they are by looking at the time-stamps.
+    (let ((arts articles)
+	  art)
+      (while (setq art (pop arts))
+	(when (not (equal
+		    (nth 5 (file-attributes
+			    (concat dir (int-to-string (car art)))))
+		    (cdr art)))
+	  (setq articles (delq art articles))
+	  (push (car art) new))))
+    ;; Go through all the new articles and add them, and their
+    ;; time-stamps, to the list.
+    (setq articles
+	  (nconc articles
+		 (mapcar
+		  (lambda (art)
+		    (cons art
+			  (nth 5 (file-attributes
+				  (concat dir (int-to-string art))))))
+		  new)))
+    ;; Make Gnus mark all new articles as unread.
+    (when new
+      (gnus-make-articles-unread
+       (gnus-group-prefixed-name group (list 'nnmh ""))
+       (setq new (sort new '<))))
+    ;; Sort the article list with highest numbers first.
+    (setq articles (sort articles (lambda (art1 art2)
+				    (> (car art1) (car art2)))))
+    ;; Finally write this list back to the .nnmh-articles file.
+    (nnheader-temp-write nnmh-file
+      (insert ";; Gnus article active file for " group "\n\n")
+      (insert "(setq nnmh-newsgroup-articles '")
+      (gnus-prin1 articles)
+      (insert ")\n"))))
+
+(defun nnmh-deletable-article-p (group article)
+  "Say whether ARTICLE in GROUP can be deleted."
+  (let ((path (concat nnmh-current-directory (int-to-string article))))
+    ;; Writable.
+    (and (file-writable-p path)
+	 ;; We can never delete the last article in the group.
+	 (not (eq (cdr (nth 1 (assoc group nnmh-group-alist)))
+		  article)))))
+
+(provide 'nnmh)
+
+;;; nnmh.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnml.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,793 @@
+;;; nnml.el --- mail spool access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; 	Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; Based on nnspool.el by Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>.
+;; For an overview of what the interface functions do, please see the
+;; Gnus sources.
+
+;;; Code:
+
+(require 'nnheader)
+(require 'nnmail)
+(require 'nnoo)
+(require 'cl)
+
+(nnoo-declare nnml)
+
+(defvoo nnml-directory message-directory
+  "Mail spool directory.")
+
+(defvoo nnml-active-file
+  (concat (file-name-as-directory nnml-directory) "active")
+  "Mail active file.")
+
+(defvoo nnml-newsgroups-file
+  (concat (file-name-as-directory nnml-directory) "newsgroups")
+  "Mail newsgroups description file.")
+
+(defvoo nnml-get-new-mail t
+  "If non-nil, nnml will check the incoming mail file and split the mail.")
+
+(defvoo nnml-nov-is-evil nil
+  "If non-nil, Gnus will never generate and use nov databases for mail groups.
+Using nov databases will speed up header fetching considerably.
+This variable shouldn't be flipped much.  If you have, for some reason,
+set this to t, and want to set it to nil again, you should always run
+the `nnml-generate-nov-databases' command.  The function will go
+through all nnml directories and generate nov databases for them
+all.  This may very well take some time.")
+
+(defvoo nnml-prepare-save-mail-hook nil
+  "Hook run narrowed to an article before saving.")
+
+(defvoo nnml-inhibit-expiry nil
+  "If non-nil, inhibit expiry.")
+
+
+
+
+(defconst nnml-version "nnml 1.0"
+  "nnml version.")
+
+(defvoo nnml-nov-file-name ".overview")
+
+(defvoo nnml-current-directory nil)
+(defvoo nnml-current-group nil)
+(defvoo nnml-status-string "")
+(defvoo nnml-nov-buffer-alist nil)
+(defvoo nnml-group-alist nil)
+(defvoo nnml-active-timestamp nil)
+(defvoo nnml-article-file-alist nil)
+
+(defvoo nnml-generate-active-function 'nnml-generate-active-info)
+
+
+
+;;; Interface functions.
+
+(nnoo-define-basics nnml)
+
+(deffoo nnml-retrieve-headers (sequence &optional group server fetch-old)
+  (when (nnml-possibly-change-directory group server)
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)
+      (let ((file nil)
+	    (number (length sequence))
+	    (count 0)
+	    beg article)
+	(if (stringp (car sequence))
+	    'headers
+	  (if (nnml-retrieve-headers-with-nov sequence fetch-old)
+	      'nov
+	    (while sequence
+	      (setq article (car sequence))
+	      (setq file (nnml-article-to-file article))
+	      (when (and file
+			 (file-exists-p file)
+			 (not (file-directory-p file)))
+		(insert (format "221 %d Article retrieved.\n" article))
+		(setq beg (point))
+		(nnheader-insert-head file)
+		(goto-char beg)
+		(if (search-forward "\n\n" nil t)
+		    (forward-char -1)
+		  (goto-char (point-max))
+		  (insert "\n\n"))
+		(insert ".\n")
+		(delete-region (point) (point-max)))
+	      (setq sequence (cdr sequence))
+	      (setq count (1+ count))
+	      (and (numberp nnmail-large-newsgroup)
+		   (> number nnmail-large-newsgroup)
+		   (zerop (% count 20))
+		   (nnheader-message 6 "nnml: Receiving headers... %d%%"
+				     (/ (* count 100) number))))
+
+	    (and (numberp nnmail-large-newsgroup)
+		 (> number nnmail-large-newsgroup)
+		 (nnheader-message 6 "nnml: Receiving headers...done"))
+
+	    (nnheader-fold-continuation-lines)
+	    'headers))))))
+
+(deffoo nnml-open-server (server &optional defs)
+  (nnoo-change-server 'nnml server defs)
+  (when (not (file-exists-p nnml-directory))
+    (condition-case ()
+	(make-directory nnml-directory t)
+      (error)))
+  (cond
+   ((not (file-exists-p nnml-directory))
+    (nnml-close-server)
+    (nnheader-report 'nnml "Couldn't create directory: %s" nnml-directory))
+   ((not (file-directory-p (file-truename nnml-directory)))
+    (nnml-close-server)
+    (nnheader-report 'nnml "Not a directory: %s" nnml-directory))
+   (t
+    (nnheader-report 'nnml "Opened server %s using directory %s"
+		     server nnml-directory)
+    t)))
+
+(defun nnml-request-regenerate (server)
+  (nnml-possibly-change-directory nil server)
+  (nnml-generate-nov-databases)
+  t)
+
+(deffoo nnml-request-article (id &optional group server buffer)
+  (nnml-possibly-change-directory group server)
+  (let* ((nntp-server-buffer (or buffer nntp-server-buffer))
+	 path gpath group-num)
+    (if (stringp id)
+	(when (and (setq group-num (nnml-find-group-number id))
+		   (cdr
+		    (assq (cdr group-num)
+			  (nnheader-article-to-file-alist
+			   (setq gpath
+				 (nnmail-group-pathname
+				  (car group-num)
+				  nnml-directory))))))
+	  (setq path (concat gpath (int-to-string (cdr group-num)))))
+      (setq path (nnml-article-to-file id)))
+    (cond
+     ((not path)
+      (nnheader-report 'nnml "No such article: %s" id))
+     ((not (file-exists-p path))
+      (nnheader-report 'nnml "No such file: %s" path))
+     ((file-directory-p path)
+      (nnheader-report 'nnml "File is a directory: %s" path))
+     ((not (save-excursion (nnmail-find-file path)))
+      (nnheader-report 'nnml "Couldn't read file: %s" path))
+     (t
+      (nnheader-report 'nnml "Article %s retrieved" id)
+      ;; We return the article number.
+      (cons (if group-num (car group-num) group)
+	    (string-to-int (file-name-nondirectory path)))))))
+
+(deffoo nnml-request-group (group &optional server dont-check)
+  (cond
+   ((not (nnml-possibly-change-directory group server))
+    (nnheader-report 'nnml "Invalid group (no such directory)"))
+   ((not (file-exists-p nnml-current-directory))
+    (nnheader-report 'nnml "Directory %s does not exist"
+		     nnml-current-directory))
+   ((not (file-directory-p nnml-current-directory))
+    (nnheader-report 'nnml "%s is not a directory" nnml-current-directory))
+   (dont-check
+    (nnheader-report 'nnml "Group %s selected" group)
+    t)
+   (t
+    (nnheader-re-read-dir nnml-current-directory)
+    (nnmail-activate 'nnml)
+    (let ((active (nth 1 (assoc group nnml-group-alist))))
+      (if (not active)
+	  (nnheader-report 'nnml "No such group: %s" group)
+	(nnheader-report 'nnml "Selected group %s" group)
+	(nnheader-insert "211 %d %d %d %s\n"
+			 (max (1+ (- (cdr active) (car active))) 0)
+			 (car active) (cdr active) group))))))
+
+(deffoo nnml-request-scan (&optional group server)
+  (setq nnml-article-file-alist nil)
+  (nnml-possibly-change-directory group server)
+  (nnmail-get-new-mail 'nnml 'nnml-save-nov nnml-directory group))
+
+(deffoo nnml-close-group (group &optional server)
+  (setq nnml-article-file-alist nil)
+  t)
+
+(deffoo nnml-request-create-group (group &optional server args)
+  (nnmail-activate 'nnml)
+  (unless (assoc group nnml-group-alist)
+    (let (active)
+      (push (list group (setq active (cons 1 0)))
+	    nnml-group-alist)
+      (nnml-possibly-create-directory group)
+      (nnml-possibly-change-directory group server)
+      (let ((articles (nnheader-directory-articles nnml-current-directory)))
+	(when articles
+	  (setcar active (apply 'min articles))
+	  (setcdr active (apply 'max articles))))
+      (nnmail-save-active nnml-group-alist nnml-active-file)))
+  t)
+
+(deffoo nnml-request-list (&optional server)
+  (save-excursion
+    (nnmail-find-file nnml-active-file)
+    (setq nnml-group-alist (nnmail-get-active))
+    t))
+
+(deffoo nnml-request-newgroups (date &optional server)
+  (nnml-request-list server))
+
+(deffoo nnml-request-list-newsgroups (&optional server)
+  (save-excursion
+    (nnmail-find-file nnml-newsgroups-file)))
+
+(deffoo nnml-request-expire-articles (articles group
+					       &optional server force)
+  (nnml-possibly-change-directory group server)
+  (let* ((active-articles
+	  (nnheader-directory-articles nnml-current-directory))
+	 (is-old t)
+	 article rest mod-time number)
+    (nnmail-activate 'nnml)
+
+    (while (and articles is-old)
+      (when (setq article (nnml-article-to-file (setq number (pop articles))))
+	(when (setq mod-time (nth 5 (file-attributes article)))
+	  (if (and (nnml-deletable-article-p group number)
+		   (setq is-old
+			 (nnmail-expired-article-p group mod-time force
+						   nnml-inhibit-expiry)))
+	      (progn
+		(nnheader-message 5 "Deleting article %s in %s"
+				  article group)
+		(condition-case ()
+		    (funcall nnmail-delete-file-function article)
+		  (file-error
+		   (push number rest)))
+		(setq active-articles (delq number active-articles))
+		(nnml-nov-delete-article group number))
+	    (push number rest)))))
+    (let ((active (nth 1 (assoc group nnml-group-alist))))
+      (when active
+	(setcar active (or (and active-articles
+				(apply 'min active-articles))
+			   (1+ (cdr active)))))
+      (nnmail-save-active nnml-group-alist nnml-active-file))
+    (nnml-save-nov)
+    (nconc rest articles)))
+
+(deffoo nnml-request-move-article
+  (article group server accept-form &optional last)
+  (let ((buf (get-buffer-create " *nnml move*"))
+	result)
+    (nnml-possibly-change-directory group server)
+    (nnml-update-file-alist)
+    (and
+     (nnml-deletable-article-p group article)
+     (nnml-request-article article group server)
+     (save-excursion
+       (set-buffer buf)
+       (insert-buffer-substring nntp-server-buffer)
+       (setq result (eval accept-form))
+       (kill-buffer (current-buffer))
+       result)
+     (progn
+       (nnml-possibly-change-directory group server)
+       (condition-case ()
+	   (funcall nnmail-delete-file-function
+		    (nnml-article-to-file  article))
+	 (file-error nil))
+       (nnml-nov-delete-article group article)
+       (when last
+	 (nnml-save-nov)
+	 (nnmail-save-active nnml-group-alist nnml-active-file))))
+    result))
+
+(deffoo nnml-request-accept-article (group &optional server last)
+  (nnml-possibly-change-directory group server)
+  (nnmail-check-syntax)
+  (let (result)
+    (when nnmail-cache-accepted-message-ids
+      (nnmail-cache-insert (nnmail-fetch-field "message-id")))
+    (if (stringp group)
+	(and
+	 (nnmail-activate 'nnml)
+	 (setq result (car (nnml-save-mail
+			    (list (cons group (nnml-active-number group))))))
+	 (progn
+	   (nnmail-save-active nnml-group-alist nnml-active-file)
+	   (and last (nnml-save-nov))))
+      (and
+       (nnmail-activate 'nnml)
+       (if (and (not (setq result (nnmail-article-group 'nnml-active-number)))
+		(yes-or-no-p "Moved to `junk' group; delete article? "))
+	   (setq result 'junk)
+	 (setq result (car (nnml-save-mail result))))
+       (when last
+	 (nnmail-save-active nnml-group-alist nnml-active-file)
+	 (when nnmail-cache-accepted-message-ids
+	   (nnmail-cache-close))
+	 (nnml-save-nov))))
+    result))
+
+(deffoo nnml-request-replace-article (article group buffer)
+  (nnml-possibly-change-directory group)
+  (save-excursion
+    (set-buffer buffer)
+    (nnml-possibly-create-directory group)
+    (let ((chars (nnmail-insert-lines))
+	  (art (concat (int-to-string article) "\t"))
+	  headers)
+      (when (condition-case ()
+		(progn
+		  (nnmail-write-region
+		   (point-min) (point-max)
+		   (or (nnml-article-to-file article)
+		       (concat nnml-current-directory
+			       (int-to-string article)))
+		   nil (if (nnheader-be-verbose 5) nil 'nomesg))
+		  t)
+	      (error nil))
+	(setq headers (nnml-parse-head chars article))
+	;; Replace the NOV line in the NOV file.
+	(save-excursion
+	  (set-buffer (nnml-open-nov group))
+	  (goto-char (point-min))
+	  (if (or (looking-at art)
+		  (search-forward (concat "\n" art) nil t))
+	      ;; Delete the old NOV line.
+	      (delete-region (progn (beginning-of-line) (point))
+			     (progn (forward-line 1) (point)))
+	    ;; The line isn't here, so we have to find out where
+	    ;; we should insert it.  (This situation should never
+	    ;; occur, but one likes to make sure...)
+	    (while (and (looking-at "[0-9]+\t")
+			(< (string-to-int
+			    (buffer-substring
+			     (match-beginning 0) (match-end 0)))
+			   article)
+			(zerop (forward-line 1)))))
+	  (beginning-of-line)
+	  (nnheader-insert-nov headers)
+	  (nnml-save-nov)
+	  t)))))
+
+(deffoo nnml-request-delete-group (group &optional force server)
+  (nnml-possibly-change-directory group server)
+  (when force
+    ;; Delete all articles in GROUP.
+    (let ((articles
+	   (directory-files
+	    nnml-current-directory t
+	    (concat nnheader-numerical-short-files
+		    "\\|" (regexp-quote nnml-nov-file-name) "$")))
+	  article)
+      (while articles
+	(setq article (pop articles))
+	(when (file-writable-p article)
+	  (nnheader-message 5 "Deleting article %s in %s..." article group)
+	  (funcall nnmail-delete-file-function article))))
+    ;; Try to delete the directory itself.
+    (condition-case ()
+	(delete-directory nnml-current-directory)
+      (error nil)))
+  ;; Remove the group from all structures.
+  (setq nnml-group-alist
+	(delq (assoc group nnml-group-alist) nnml-group-alist)
+	nnml-current-group nil
+	nnml-current-directory nil)
+  ;; Save the active file.
+  (nnmail-save-active nnml-group-alist nnml-active-file)
+  t)
+
+(deffoo nnml-request-rename-group (group new-name &optional server)
+  (nnml-possibly-change-directory group server)
+  (let ((new-dir (nnmail-group-pathname new-name nnml-directory))
+	(old-dir (nnmail-group-pathname group nnml-directory)))
+    (when (condition-case ()
+	      (progn
+		(make-directory new-dir t)
+		t)
+	    (error nil))
+      ;; We move the articles file by file instead of renaming
+      ;; the directory -- there may be subgroups in this group.
+      ;; One might be more clever, I guess.
+      (let ((files (nnheader-article-to-file-alist old-dir)))
+	(while files
+	  (rename-file
+	   (concat old-dir (cdar files))
+	   (concat new-dir (cdar files)))
+	  (pop files)))
+      ;; Move .overview file.
+      (let ((overview (concat old-dir nnml-nov-file-name)))
+	(when (file-exists-p overview)
+	  (rename-file overview (concat new-dir nnml-nov-file-name))))
+      (when (<= (length (directory-files old-dir)) 2)
+	(condition-case ()
+	    (delete-directory old-dir)
+	  (error nil)))
+      ;; That went ok, so we change the internal structures.
+      (let ((entry (assoc group nnml-group-alist)))
+	(when entry
+	  (setcar entry new-name))
+	(setq nnml-current-directory nil
+	      nnml-current-group nil)
+	;; Save the new group alist.
+	(nnmail-save-active nnml-group-alist nnml-active-file)
+	t))))
+
+(deffoo nnml-set-status (article name value &optional group server)
+  (nnml-possibly-change-directory group server)
+  (let ((file (nnml-article-to-file article)))
+    (cond
+     ((not (file-exists-p file))
+      (nnheader-report 'nnml "File %s does not exist" file))
+     (t
+      (nnheader-temp-write file
+	(nnheader-insert-file-contents file)
+	(nnmail-replace-status name value))
+      t))))
+
+
+;;; Internal functions.
+
+(defun nnml-article-to-file (article)
+  (nnml-update-file-alist)
+  (let (file)
+    (when (setq file (cdr (assq article nnml-article-file-alist)))
+      (concat nnml-current-directory file))))
+
+(defun nnml-deletable-article-p (group article)
+  "Say whether ARTICLE in GROUP can be deleted."
+  (let (path)
+    (when (setq path (nnml-article-to-file article))
+      (when (file-writable-p path)
+	(or (not nnmail-keep-last-article)
+	    (not (eq (cdr (nth 1 (assoc group nnml-group-alist)))
+		     article)))))))
+
+;; Find an article number in the current group given the Message-ID.
+(defun nnml-find-group-number (id)
+  (save-excursion
+    (set-buffer (get-buffer-create " *nnml id*"))
+    (buffer-disable-undo (current-buffer))
+    (let ((alist nnml-group-alist)
+	  number)
+      ;; We want to look through all .overview files, but we want to
+      ;; start with the one in the current directory.  It seems most
+      ;; likely that the article we are looking for is in that group.
+      (if (setq number (nnml-find-id nnml-current-group id))
+	  (cons nnml-current-group number)
+	;; It wasn't there, so we look through the other groups as well.
+	(while (and (not number)
+		    alist)
+	  (or (string= (caar alist) nnml-current-group)
+	      (setq number (nnml-find-id (caar alist) id)))
+	  (or number
+	      (setq alist (cdr alist))))
+	(and number
+	     (cons (caar alist) number))))))
+
+(defun nnml-find-id (group id)
+  (erase-buffer)
+  (let ((nov (concat (nnmail-group-pathname group nnml-directory)
+		     nnml-nov-file-name))
+	number found)
+    (when (file-exists-p nov)
+      (nnheader-insert-file-contents nov)
+      (while (and (not found)
+		  (search-forward id nil t)) ; We find the ID.
+	;; And the id is in the fourth field.
+	(if (not (and (search-backward "\t" nil t 4)
+		      (not (search-backward"\t" (gnus-point-at-bol) t))))
+	    (forward-line 1)
+	  (beginning-of-line)
+	  (setq found t)
+	  ;; We return the article number.
+	  (setq number
+		(condition-case ()
+		    (read (current-buffer))
+		  (error nil)))))
+      number)))
+
+(defun nnml-retrieve-headers-with-nov (articles &optional fetch-old)
+  (if (or gnus-nov-is-evil nnml-nov-is-evil)
+      nil
+    (let ((nov (concat nnml-current-directory nnml-nov-file-name)))
+      (when (file-exists-p nov)
+	(save-excursion
+	  (set-buffer nntp-server-buffer)
+	  (erase-buffer)
+	  (nnheader-insert-file-contents nov)
+	  (if (and fetch-old
+		   (not (numberp fetch-old)))
+	      t				; Don't remove anything.
+	    (nnheader-nov-delete-outside-range
+	     (if fetch-old (max 1 (- (car articles) fetch-old))
+	       (car articles))
+	     (car (last articles)))
+	    t))))))
+
+(defun nnml-possibly-change-directory (group &optional server)
+  (when (and server
+	     (not (nnml-server-opened server)))
+    (nnml-open-server server))
+  (if (not group)
+      t
+    (let ((pathname (nnmail-group-pathname group nnml-directory)))
+      (when (not (equal pathname nnml-current-directory))
+	(setq nnml-current-directory pathname
+	      nnml-current-group group
+	      nnml-article-file-alist nil))
+      (file-exists-p nnml-current-directory))))
+
+(defun nnml-possibly-create-directory (group)
+  (let (dir dirs)
+    (setq dir (nnmail-group-pathname group nnml-directory))
+    (while (not (file-directory-p dir))
+      (push dir dirs)
+      (setq dir (file-name-directory (directory-file-name dir))))
+    (while dirs
+      (make-directory (directory-file-name (car dirs)))
+      (nnheader-message 5 "Creating mail directory %s" (car dirs))
+      (setq dirs (cdr dirs)))))
+
+(defun nnml-save-mail (group-art)
+  "Called narrowed to an article."
+  (let (chars headers)
+    (setq chars (nnmail-insert-lines))
+    (nnmail-insert-xref group-art)
+    (run-hooks 'nnmail-prepare-save-mail-hook)
+    (run-hooks 'nnml-prepare-save-mail-hook)
+    (goto-char (point-min))
+    (while (looking-at "From ")
+      (replace-match "X-From-Line: ")
+      (forward-line 1))
+    ;; We save the article in all the groups it belongs in.
+    (let ((ga group-art)
+	  first)
+      (while ga
+	(nnml-possibly-create-directory (caar ga))
+	(let ((file (concat (nnmail-group-pathname
+			     (caar ga) nnml-directory)
+			    (int-to-string (cdar ga)))))
+	  (if first
+	      ;; It was already saved, so we just make a hard link.
+	      (funcall nnmail-crosspost-link-function first file t)
+	    ;; Save the article.
+	    (nnmail-write-region (point-min) (point-max) file nil
+				 (if (nnheader-be-verbose 5) nil 'nomesg))
+	    (setq first file)))
+	(setq ga (cdr ga))))
+    ;; Generate a nov line for this article.  We generate the nov
+    ;; line after saving, because nov generation destroys the
+    ;; header.
+    (setq headers (nnml-parse-head chars))
+    ;; Output the nov line to all nov databases that should have it.
+    (let ((ga group-art))
+      (while ga
+	(nnml-add-nov (caar ga) (cdar ga) headers)
+	(setq ga (cdr ga))))
+    group-art))
+
+(defun nnml-active-number (group)
+  "Compute the next article number in GROUP."
+  (let ((active (cadr (assoc group nnml-group-alist))))
+    ;; The group wasn't known to nnml, so we just create an active
+    ;; entry for it.
+    (unless active
+      ;; Perhaps the active file was corrupt?  See whether
+      ;; there are any articles in this group.
+      (nnml-possibly-create-directory group)
+      (nnml-possibly-change-directory group)
+      (unless nnml-article-file-alist
+	(setq nnml-article-file-alist
+	      (sort
+	       (nnheader-article-to-file-alist nnml-current-directory)
+	       (lambda (a1 a2) (< (car a1) (car a2))))))
+      (setq active
+	    (if nnml-article-file-alist
+		(cons (caar nnml-article-file-alist)
+		      (caar (last nnml-article-file-alist)))
+	      (cons 1 0)))
+      (push (list group active) nnml-group-alist))
+    (setcdr active (1+ (cdr active)))
+    (while (file-exists-p
+	    (concat (nnmail-group-pathname group nnml-directory)
+		    (int-to-string (cdr active))))
+      (setcdr active (1+ (cdr active))))
+    (cdr active)))
+
+(defun nnml-add-nov (group article headers)
+  "Add a nov line for the GROUP base."
+  (save-excursion
+    (set-buffer (nnml-open-nov group))
+    (goto-char (point-max))
+    (mail-header-set-number headers article)
+    (nnheader-insert-nov headers)))
+
+(defsubst nnml-header-value ()
+  (buffer-substring (match-end 0) (progn (end-of-line) (point))))
+
+(defun nnml-parse-head (chars &optional number)
+  "Parse the head of the current buffer."
+  (save-excursion
+    (save-restriction
+      (goto-char (point-min))
+      (narrow-to-region
+       (point)
+       (1- (or (search-forward "\n\n" nil t) (point-max))))
+      ;; Fold continuation lines.
+      (goto-char (point-min))
+      (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
+	(replace-match " " t t))
+      ;; Remove any tabs; they are too confusing.
+      (subst-char-in-region (point-min) (point-max) ?\t ? )
+      (let ((headers (nnheader-parse-head t)))
+	(mail-header-set-chars headers chars)
+	(mail-header-set-number headers number)
+	headers))))
+
+(defun nnml-open-nov (group)
+  (or (cdr (assoc group nnml-nov-buffer-alist))
+      (let ((buffer (nnheader-find-file-noselect
+		     (concat (nnmail-group-pathname group nnml-directory)
+			     nnml-nov-file-name))))
+	(save-excursion
+	  (set-buffer buffer)
+	  (buffer-disable-undo (current-buffer)))
+	(push (cons group buffer) nnml-nov-buffer-alist)
+	buffer)))
+
+(defun nnml-save-nov ()
+  (save-excursion
+    (while nnml-nov-buffer-alist
+      (when (buffer-name (cdar nnml-nov-buffer-alist))
+	(set-buffer (cdar nnml-nov-buffer-alist))
+	(when (buffer-modified-p)
+	  (nnmail-write-region 1 (point-max) (buffer-file-name) nil 'nomesg))
+	(set-buffer-modified-p nil)
+	(kill-buffer (current-buffer)))
+      (setq nnml-nov-buffer-alist (cdr nnml-nov-buffer-alist)))))
+
+;;;###autoload
+(defun nnml-generate-nov-databases ()
+  "Generate NOV databases in all nnml directories."
+  (interactive)
+  ;; Read the active file to make sure we don't re-use articles
+  ;; numbers in empty groups.
+  (nnmail-activate 'nnml)
+  (nnml-open-server (or (nnoo-current-server 'nnml) ""))
+  (setq nnml-directory (expand-file-name nnml-directory))
+  ;; Recurse down the directories.
+  (nnml-generate-nov-databases-1 nnml-directory nil t)
+  ;; Save the active file.
+  (nnmail-save-active nnml-group-alist nnml-active-file))
+
+(defun nnml-generate-nov-databases-1 (dir &optional seen no-active)
+  "Regenerate the NOV database in DIR."
+  (interactive "DRegenerate NOV in: ")
+  (setq dir (file-name-as-directory dir))
+  ;; Only scan this sub-tree if we haven't been here yet.
+  (unless (member (file-truename dir) seen)
+    (push (file-truename dir) seen)
+    ;; We descend recursively
+    (let ((dirs (directory-files dir t nil t))
+	  dir)
+      (while (setq dir (pop dirs))
+	(when (and (not (member (file-name-nondirectory dir) '("." "..")))
+		   (file-directory-p dir))
+	  (nnml-generate-nov-databases-1 dir seen))))
+    ;; Do this directory.
+    (let ((files (sort (nnheader-article-to-file-alist dir)
+		       (lambda (a b) (< (car a) (car b))))))
+      (when files
+	(funcall nnml-generate-active-function dir)
+	;; Generate the nov file.
+	(nnml-generate-nov-file dir files)
+	(unless no-active
+	  (nnmail-save-active nnml-group-alist nnml-active-file))))))
+
+(defvar files)
+(defun nnml-generate-active-info (dir)
+  ;; Update the active info for this group.
+  (let ((group (nnheader-file-to-group
+		(directory-file-name dir) nnml-directory)))
+    (setq nnml-group-alist
+	  (delq (assoc group nnml-group-alist) nnml-group-alist))
+    (push (list group
+		(cons (caar files)
+		      (let ((f files))
+			(while (cdr f) (setq f (cdr f)))
+			(caar f))))
+	  nnml-group-alist)))
+
+(defun nnml-generate-nov-file (dir files)
+  (let* ((dir (file-name-as-directory dir))
+	 (nov (concat dir nnml-nov-file-name))
+	 (nov-buffer (get-buffer-create " *nov*"))
+	 chars file headers)
+    (save-excursion
+      ;; Init the nov buffer.
+      (set-buffer nov-buffer)
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (set-buffer nntp-server-buffer)
+      ;; Delete the old NOV file.
+      (when (file-exists-p nov)
+	(funcall nnmail-delete-file-function nov))
+      (while files
+	(unless (file-directory-p (setq file (concat dir (cdar files))))
+	  (erase-buffer)
+	  (nnheader-insert-file-contents file)
+	  (narrow-to-region
+	   (goto-char (point-min))
+	   (progn
+	     (search-forward "\n\n" nil t)
+	     (setq chars (- (point-max) (point)))
+	     (max 1 (1- (point)))))
+	  (when (and (not (= 0 chars))	; none of them empty files...
+		     (not (= (point-min) (point-max))))
+	    (goto-char (point-min))
+	    (setq headers (nnml-parse-head chars (caar files)))
+	    (save-excursion
+	      (set-buffer nov-buffer)
+	      (goto-char (point-max))
+	      (nnheader-insert-nov headers)))
+	  (widen))
+	(setq files (cdr files)))
+      (save-excursion
+	(set-buffer nov-buffer)
+	(nnmail-write-region 1 (point-max) nov nil 'nomesg)
+	(kill-buffer (current-buffer))))))
+
+(defun nnml-nov-delete-article (group article)
+  (save-excursion
+    (set-buffer (nnml-open-nov group))
+    (when (nnheader-find-nov-line article)
+      (delete-region (point) (progn (forward-line 1) (point)))
+      (when (bobp)
+	(let ((active (cadr (assoc group nnml-group-alist)))
+	      num)
+	  (when active
+	    (if (eobp)
+		(setf (car active) (1+ (cdr active)))
+	      (when (and (setq num (ignore-errors (read (current-buffer))))
+			 (numberp num))
+		(setf (car active) num)))))))
+    t))
+
+(defun nnml-update-file-alist ()
+  (unless nnml-article-file-alist
+    (setq nnml-article-file-alist
+	  (nnheader-article-to-file-alist nnml-current-directory))))
+
+(provide 'nnml)
+
+;;; nnml.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnoo.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,279 @@
+;;; nnoo.el --- OO Gnus Backends
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'cl)
+
+(defvar nnoo-definition-alist nil)
+(defvar nnoo-state-alist nil)
+
+(defmacro defvoo (var init &optional doc &rest map)
+  "The same as `defvar', only takes list of variables to MAP to."
+  `(prog1
+       ,(if doc
+	    `(defvar ,var ,init ,doc)
+	  `(defvar ,var ,init))
+     (nnoo-define ',var ',map)))
+(put 'defvoo 'lisp-indent-function 2)
+(put 'defvoo 'edebug-form-spec '(var init &optional doc &rest map))
+
+(defmacro deffoo (func args &rest forms)
+  "The same as `defun', only register FUNC."
+  `(prog1
+       (defun ,func ,args ,@forms)
+     (nnoo-register-function ',func)))
+(put 'deffoo 'lisp-indent-function 2)
+(put 'deffoo 'edebug-form-spec '(&define name lambda-list def-body))
+
+(defun nnoo-register-function (func)
+  (let ((funcs (nthcdr 3 (assoc (nnoo-backend func)
+				nnoo-definition-alist))))
+    (unless funcs
+      (error "%s belongs to a backend that hasn't been declared" func))
+    (setcar funcs (cons func (car funcs)))))
+
+(defmacro nnoo-declare (backend &rest parents)
+  `(eval-and-compile
+     (push (list ',backend
+		 (mapcar (lambda (p) (list p)) ',parents)
+		 nil nil)
+	   nnoo-definition-alist)
+     (push (list ',backend "*internal-non-initialized-backend*")
+	   nnoo-state-alist)))
+(put 'nnoo-declare 'lisp-indent-function 1)
+
+(defun nnoo-parents (backend)
+  (nth 1 (assoc backend nnoo-definition-alist)))
+
+(defun nnoo-variables (backend)
+  (nth 2 (assoc backend nnoo-definition-alist)))
+
+(defun nnoo-functions (backend)
+  (nth 3 (assoc backend nnoo-definition-alist)))
+
+(defmacro nnoo-import (backend &rest imports)
+  `(nnoo-import-1 ',backend ',imports))
+(put 'nnoo-import 'lisp-indent-function 1)
+
+(defun nnoo-import-1 (backend imports)
+  (let ((call-function
+	 (if (symbolp (car imports)) (pop imports) 'nnoo-parent-function))
+	imp functions function)
+    (while (setq imp (pop imports))
+      (setq functions
+	    (or (cdr imp)
+		(nnoo-functions (car imp))))
+      (while functions
+	(unless (fboundp (setq function
+			       (nnoo-symbol backend (nnoo-rest-symbol
+						     (car functions)))))
+	  (eval `(deffoo ,function (&rest args)
+		   (,call-function ',backend ',(car functions) args))))
+	(pop functions)))))
+
+(defun nnoo-parent-function (backend function args)
+  (let* ((pbackend (nnoo-backend function)))
+    (nnoo-change-server pbackend (nnoo-current-server backend)
+			(cdr (assq pbackend (nnoo-parents backend))))
+    (apply function args)))
+
+(defun nnoo-execute (backend function &rest args)
+  "Execute FUNCTION on behalf of BACKEND."
+  (let* ((pbackend (nnoo-backend function)))
+    (nnoo-change-server pbackend (nnoo-current-server backend)
+			(cdr (assq pbackend (nnoo-parents backend))))
+    (apply function args)))
+
+(defmacro nnoo-map-functions (backend &rest maps)
+  `(nnoo-map-functions-1 ',backend ',maps))
+(put 'nnoo-map-functions 'lisp-indent-function 1)
+
+(defun nnoo-map-functions-1 (backend maps)
+  (let (m margs i)
+    (while (setq m (pop maps))
+      (setq i 0
+	    margs nil)
+      (while (< i (length (cdr m)))
+	(if (numberp (nth i (cdr m)))
+	    (push `(nth ,i args) margs)
+	  (push (nth i (cdr m)) margs))
+	(incf i))
+      (eval `(deffoo ,(nnoo-symbol backend (nnoo-rest-symbol (car m)))
+		 (&rest args)
+	       (nnoo-parent-function ',backend ',(car m)
+				     ,(cons 'list (nreverse margs))))))))
+
+(defun nnoo-backend (symbol)
+  (string-match "^[^-]+-" (symbol-name symbol))
+  (intern (substring (symbol-name symbol) 0 (1- (match-end 0)))))
+
+(defun nnoo-rest-symbol (symbol)
+  (string-match "^[^-]+-" (symbol-name symbol))
+  (intern (substring (symbol-name symbol) (match-end 0))))
+
+(defun nnoo-symbol (backend symbol)
+  (intern (format "%s-%s" backend symbol)))
+
+(defun nnoo-define (var map)
+  (let* ((backend (nnoo-backend var))
+	 (def (assq backend nnoo-definition-alist))
+	 (parents (nth 1 def)))
+    (unless def
+      (error "%s belongs to a backend that hasn't been declared." var))
+    (setcar (nthcdr 2 def)
+	    (delq (assq var (nth 2 def)) (nth 2 def)))
+    (setcar (nthcdr 2 def)
+	    (cons (cons var (symbol-value var))
+		  (nth 2 def)))
+    (while map
+      (nconc (assq (nnoo-backend (car map)) parents)
+	     (list (list (pop map) var))))))
+
+(defun nnoo-change-server (backend server defs)
+  (let* ((bstate (cdr (assq backend nnoo-state-alist)))
+	 (current (car bstate))
+	 (parents (nnoo-parents backend))
+	 (bvariables (nnoo-variables backend))
+	 state def)
+    (unless bstate
+      (push (setq bstate (list backend nil))
+	    nnoo-state-alist)
+      (pop bstate))
+    (if (equal server current)
+	t
+      (nnoo-push-server backend current)
+      (setq state (or (cdr (assoc server (cddr bstate)))
+		      (nnoo-variables backend)))
+      (while state
+	(set (caar state) (cdar state))
+	(pop state))
+      (setcar bstate server)
+      (unless (cdr (assoc server (cddr bstate)))
+	(while (setq def (pop defs))
+	  (unless (assq (car def) bvariables)
+	    (nconc bvariables
+ 		   (list (cons (car def) (and (boundp (car def))
+ 					      (symbol-value (car def)))))))
+	  (set (car def) (cadr def))))
+      (while parents
+	(nnoo-change-server
+	 (caar parents) server
+	 (mapcar (lambda (def) (list (car def) (symbol-value (cadr def))))
+		 (cdar parents)))
+	(pop parents))))
+  t)
+
+(defun nnoo-push-server (backend current)
+  (let ((bstate (assq backend nnoo-state-alist))
+	(defs (nnoo-variables backend)))
+    ;; Remove the old definition.
+    (setcdr (cdr bstate) (delq (assoc current (cddr bstate)) (cddr bstate)))
+    ;; If this is the first time we push the server (i. e., this is
+    ;; the nil server), then we update the default values of
+    ;; all the variables to reflect the current values.
+    (when (equal current "*internal-non-initialized-backend*")
+      (let ((defaults (nnoo-variables backend))
+	    def)
+	(while (setq def (pop defaults))
+	  (setcdr def (symbol-value (car def))))))
+    (let (state)
+      (while defs
+	(push (cons (caar defs) (symbol-value (caar defs)))
+	      state)
+	(pop defs))
+      (nconc bstate (list (cons current state))))))
+
+(defsubst nnoo-current-server-p (backend server)
+  (equal (nnoo-current-server backend) server))
+
+(defun nnoo-current-server (backend)
+  (nth 1 (assq backend nnoo-state-alist)))
+
+(defun nnoo-close-server (backend &optional server)
+  (unless server
+    (setq server (nnoo-current-server backend)))
+  (when server
+    (let* ((bstate (cdr (assq backend nnoo-state-alist)))
+	   (defs (assoc server (cdr bstate))))
+      (when bstate
+	(setcar bstate nil)
+	(setcdr bstate (delq defs (cdr bstate)))
+	(pop defs)
+	(while defs
+	  (set (car (pop defs)) nil)))))
+  t)
+
+(defun nnoo-close (backend)
+  (setq nnoo-state-alist
+	(delq (assq backend nnoo-state-alist)
+	      nnoo-state-alist))
+  t)
+
+(defun nnoo-status-message (backend server)
+  (nnheader-get-report backend))
+
+(defun nnoo-server-opened (backend server)
+  (and (nnoo-current-server-p backend server)
+       nntp-server-buffer
+       (buffer-name nntp-server-buffer)))
+
+(defmacro nnoo-define-basics (backend)
+  "Define `close-server', `server-opened' and `status-message'."
+  `(eval-and-compile
+     (nnoo-define-basics-1 ',backend)))
+
+(defun nnoo-define-basics-1 (backend)
+  (let ((functions '(close-server server-opened status-message)))
+    (while functions
+      (eval `(deffoo ,(nnoo-symbol backend (car functions))
+		 (&optional server)
+	       (,(nnoo-symbol 'nnoo (pop functions)) ',backend server)))))
+  (eval `(deffoo ,(nnoo-symbol backend 'open-server)
+	     (server &optional defs)
+	   (nnoo-change-server ',backend server defs))))
+
+(defmacro nnoo-define-skeleton (backend)
+  "Define all required backend functions for BACKEND.
+All functions will return nil and report an error."
+  `(eval-and-compile
+     (nnoo-define-skeleton-1 ',backend)))
+
+(defun nnoo-define-skeleton-1 (backend)
+  (let ((functions '(retrieve-headers
+		     request-close request-article
+		     request-group close-group
+		     request-list request-post request-list-newsgroups))
+	function fun)
+    (while (setq function (pop functions))
+      (when (not (fboundp (setq fun (nnoo-symbol backend function))))
+	(eval `(deffoo ,fun
+		   (&rest args)
+		 (nnheader-report ',backend ,(format "%s-%s not implemented"
+						     backend function))))))))
+(provide 'nnoo)
+
+;;; nnoo.el ends here.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnsoup.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,804 @@
+;;; nnsoup.el --- SOUP access for Gnus
+;; Copyright (C) 1995,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; 	Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'nnmail)
+(require 'gnus-soup)
+(require 'gnus-msg)
+(require 'nnoo)
+(eval-when-compile (require 'cl))
+
+(nnoo-declare nnsoup)
+
+(defvoo nnsoup-directory "~/SOUP/"
+  "*SOUP packet directory.")
+
+(defvoo nnsoup-tmp-directory "/tmp/"
+  "*Where nnsoup will store temporary files.")
+
+(defvoo nnsoup-replies-directory (concat nnsoup-directory "replies/")
+  "*Directory where outgoing packets will be composed.")
+
+(defvoo nnsoup-replies-format-type ?n
+  "*Format of the replies packages.")
+
+(defvoo nnsoup-replies-index-type ?n
+  "*Index type of the replies packages.")
+
+(defvoo nnsoup-active-file (concat nnsoup-directory "active")
+  "Active file.")
+
+(defvoo nnsoup-packer "tar cf - %s | gzip > $HOME/Soupin%d.tgz"
+  "Format string command for packing a SOUP packet.
+The SOUP files will be inserted where the %s is in the string.
+This string MUST contain both %s and %d.  The file number will be
+inserted where %d appears.")
+
+(defvoo nnsoup-unpacker "gunzip -c %s | tar xvf -"
+  "*Format string command for unpacking a SOUP packet.
+The SOUP packet file name will be inserted at the %s.")
+
+(defvoo nnsoup-packet-directory "~/"
+  "*Where nnsoup will look for incoming packets.")
+
+(defvoo nnsoup-packet-regexp "Soupout"
+  "*Regular expression matching SOUP packets in `nnsoup-packet-directory'.")
+
+
+
+(defconst nnsoup-version "nnsoup 0.0"
+  "nnsoup version.")
+
+(defvoo nnsoup-status-string "")
+(defvoo nnsoup-group-alist nil)
+(defvoo nnsoup-current-prefix 0)
+(defvoo nnsoup-replies-list nil)
+(defvoo nnsoup-buffers nil)
+(defvoo nnsoup-current-group nil)
+(defvoo nnsoup-group-alist-touched nil)
+(defvoo nnsoup-article-alist nil)
+
+
+
+;;; Interface functions.
+
+(nnoo-define-basics nnsoup)
+
+(deffoo nnsoup-retrieve-headers (sequence &optional group server fetch-old)
+  (nnsoup-possibly-change-group group)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (let ((areas (cddr (assoc nnsoup-current-group nnsoup-group-alist)))
+	  (articles sequence)
+	  (use-nov t)
+	  useful-areas this-area-seq msg-buf)
+      (if (stringp (car sequence))
+	  ;; We don't support fetching by Message-ID.
+	  'headers
+	;; We go through all the areas and find which files the
+	;; articles in SEQUENCE come from.
+	(while (and areas sequence)
+	  ;; Peel off areas that are below sequence.
+	  (while (and areas (< (cdaar areas) (car sequence)))
+	    (setq areas (cdr areas)))
+	  (when areas
+	    ;; This is a useful area.
+	    (push (car areas) useful-areas)
+	    (setq this-area-seq nil)
+	    ;; We take note whether this MSG has a corresponding IDX
+	    ;; for later use.
+	    (when (or (= (gnus-soup-encoding-index
+			  (gnus-soup-area-encoding (nth 1 (car areas)))) ?n)
+		      (not (file-exists-p
+			    (nnsoup-file
+			     (gnus-soup-area-prefix (nth 1 (car areas)))))))
+	      (setq use-nov nil))
+	    ;; We assign the portion of `sequence' that is relevant to
+	    ;; this MSG packet to this packet.
+	    (while (and sequence (<= (car sequence) (cdaar areas)))
+	      (push (car sequence) this-area-seq)
+	      (setq sequence (cdr sequence)))
+	    (setcar useful-areas (cons (nreverse this-area-seq)
+				       (car useful-areas)))))
+
+	;; We now have a list of article numbers and corresponding
+	;; areas.
+	(setq useful-areas (nreverse useful-areas))
+
+	;; Two different approaches depending on whether all the MSG
+	;; files have corresponding IDX files.  If they all do, we
+	;; simply return the relevant IDX files and let Gnus sort out
+	;; what lines are relevant.  If some of the IDX files are
+	;; missing, we must return HEADs for all the articles.
+	(if use-nov
+	    ;; We have IDX files for all areas.
+	    (progn
+	      (while useful-areas
+		(goto-char (point-max))
+		(let ((b (point))
+		      (number (car (nth 1 (car useful-areas))))
+		      (index-buffer (nnsoup-index-buffer
+				     (gnus-soup-area-prefix
+				      (nth 2 (car useful-areas))))))
+		  (when index-buffer
+		    (insert-buffer-substring index-buffer)
+		    (goto-char b)
+		    ;; We have to remove the index number entires and
+		    ;; insert article numbers instead.
+		    (while (looking-at "[0-9]+")
+		      (replace-match (int-to-string number) t t)
+		      (incf number)
+		      (forward-line 1))))
+		(setq useful-areas (cdr useful-areas)))
+	      'nov)
+	  ;; We insert HEADs.
+	  (while useful-areas
+	    (setq articles (caar useful-areas)
+		  useful-areas (cdr useful-areas))
+	    (while articles
+	      (when (setq msg-buf
+			  (nnsoup-narrow-to-article
+			   (car articles) (cdar useful-areas) 'head))
+		(goto-char (point-max))
+		(insert (format "221 %d Article retrieved.\n" (car articles)))
+		(insert-buffer-substring msg-buf)
+		(goto-char (point-max))
+		(insert ".\n"))
+	      (setq articles (cdr articles))))
+
+	  (nnheader-fold-continuation-lines)
+	  'headers)))))
+
+(deffoo nnsoup-open-server (server &optional defs)
+  (nnoo-change-server 'nnsoup server defs)
+  (when (not (file-exists-p nnsoup-directory))
+    (condition-case ()
+	(make-directory nnsoup-directory t)
+      (error t)))
+  (cond
+   ((not (file-exists-p nnsoup-directory))
+    (nnsoup-close-server)
+    (nnheader-report 'nnsoup "Couldn't create directory: %s" nnsoup-directory))
+   ((not (file-directory-p (file-truename nnsoup-directory)))
+    (nnsoup-close-server)
+    (nnheader-report 'nnsoup "Not a directory: %s" nnsoup-directory))
+   (t
+    (nnsoup-read-active-file)
+    (nnheader-report 'nnsoup "Opened server %s using directory %s"
+		     server nnsoup-directory)
+    t)))
+
+(deffoo nnsoup-request-close ()
+  (nnsoup-write-active-file)
+  (nnsoup-write-replies)
+  (gnus-soup-save-areas)
+  ;; Kill all nnsoup buffers.
+  (let (buffer)
+    (while nnsoup-buffers
+      (setq buffer (cdr (pop nnsoup-buffers)))
+      (and buffer
+	   (buffer-name buffer)
+	   (kill-buffer buffer))))
+  (setq nnsoup-group-alist nil
+	nnsoup-group-alist-touched nil
+	nnsoup-current-group nil
+	nnsoup-replies-list nil)
+  (nnoo-close-server 'nnoo)
+  t)
+
+(deffoo nnsoup-request-article (id &optional newsgroup server buffer)
+  (nnsoup-possibly-change-group newsgroup)
+  (let (buf)
+    (save-excursion
+      (set-buffer (or buffer nntp-server-buffer))
+      (erase-buffer)
+      (when (and (not (stringp id))
+		 (setq buf (nnsoup-narrow-to-article id)))
+	(insert-buffer-substring buf)
+	t))))
+
+(deffoo nnsoup-request-group (group &optional server dont-check)
+  (nnsoup-possibly-change-group group)
+  (if dont-check
+      t
+    (let ((active (cadr (assoc group nnsoup-group-alist))))
+      (if (not active)
+	  (nnheader-report 'nnsoup "No such group: %s" group)
+	(nnheader-insert
+	 "211 %d %d %d %s\n"
+	 (max (1+ (- (cdr active) (car active))) 0)
+	 (car active) (cdr active) group)))))
+
+(deffoo nnsoup-request-type (group &optional article)
+  (nnsoup-possibly-change-group group)
+  ;; Try to guess the type based on the first articl ein the group.
+  (when (not article)
+    (setq article
+	  (cdaar (cddr (assoc group nnsoup-group-alist)))))
+  (if (not article)
+      'unknown
+    (let ((kind (gnus-soup-encoding-kind
+		 (gnus-soup-area-encoding
+		  (nth 1 (nnsoup-article-to-area
+			  article nnsoup-current-group))))))
+      (cond ((= kind ?m) 'mail)
+	    ((= kind ?n) 'news)
+	    (t 'unknown)))))
+
+(deffoo nnsoup-close-group (group &optional server)
+  ;; Kill all nnsoup buffers.
+  (let ((buffers nnsoup-buffers)
+	elem)
+    (while buffers
+      (when (equal (car (setq elem (pop buffers))) group)
+	(setq nnsoup-buffers (delq elem nnsoup-buffers))
+	(and (cdr elem) (buffer-name (cdr elem))
+	     (kill-buffer (cdr elem))))))
+  t)
+
+(deffoo nnsoup-request-list (&optional server)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (unless nnsoup-group-alist
+      (nnsoup-read-active-file))
+    (let ((alist nnsoup-group-alist)
+	  (standard-output (current-buffer))
+	  entry)
+      (while (setq entry (pop alist))
+	(insert (car entry) " ")
+	(princ (cdadr entry))
+	(insert " ")
+	(princ (caadr entry))
+	(insert " y\n"))
+      t)))
+
+(deffoo nnsoup-request-scan (group &optional server)
+  (nnsoup-unpack-packets))
+
+(deffoo nnsoup-request-newgroups (date &optional server)
+  (nnsoup-request-list))
+
+(deffoo nnsoup-request-list-newsgroups (&optional server)
+  nil)
+
+(deffoo nnsoup-request-post (&optional server)
+  (nnsoup-store-reply "news")
+  t)
+
+(deffoo nnsoup-request-mail (&optional server)
+  (nnsoup-store-reply "mail")
+  t)
+
+(deffoo nnsoup-request-expire-articles (articles group &optional server force)
+  (nnsoup-possibly-change-group group)
+  (let* ((total-infolist (assoc group nnsoup-group-alist))
+	 (active (cadr total-infolist))
+	 (infolist (cddr total-infolist))
+	 info range-list mod-time prefix)
+    (while infolist
+      (setq info (pop infolist)
+	    range-list (gnus-uncompress-range (car info))
+	    prefix (gnus-soup-area-prefix (nth 1 info)))
+      (when ;; All the articles in this file are marked for expiry.
+	  (and (or (setq mod-time (nth 5 (file-attributes
+					  (nnsoup-file prefix))))
+		   (setq mod-time (nth 5 (file-attributes
+					  (nnsoup-file prefix t)))))
+	       (gnus-sublist-p articles range-list)
+	       ;; This file is old enough.
+	       (nnmail-expired-article-p group mod-time force))
+	;; Ok, we delete this file.
+	(when (ignore-errors
+		(nnheader-message
+		 5 "Deleting %s in group %s..." (nnsoup-file prefix)
+		 group)
+		(when (file-exists-p (nnsoup-file prefix))
+		  (delete-file (nnsoup-file prefix)))
+		(nnheader-message
+		 5 "Deleting %s in group %s..." (nnsoup-file prefix t)
+		 group)
+		(when (file-exists-p (nnsoup-file prefix t))
+		  (delete-file (nnsoup-file prefix t)))
+		t)
+	  (setcdr (cdr total-infolist) (delq info (cddr total-infolist)))
+	  (setq articles (gnus-sorted-complement articles range-list))))
+      (when (not mod-time)
+	(setcdr (cdr total-infolist) (delq info (cddr total-infolist)))))
+    (if (cddr total-infolist)
+	(setcar active (caaadr (cdr total-infolist)))
+      (setcar active (1+ (cdr active))))
+    (nnsoup-write-active-file t)
+    ;; Return the articles that weren't expired.
+    articles))
+
+
+;;; Internal functions
+
+(defun nnsoup-possibly-change-group (group &optional force)
+  (when (and group
+	     (not (equal nnsoup-current-group group)))
+    (setq nnsoup-article-alist nil)
+    (setq nnsoup-current-group group))
+  t)
+
+(defun nnsoup-read-active-file ()
+  (setq nnsoup-group-alist nil)
+  (when (file-exists-p nnsoup-active-file)
+    (ignore-errors
+      (load nnsoup-active-file t t t))
+    ;; Be backwards compatible.
+    (when (and nnsoup-group-alist
+	       (not (atom (caadar nnsoup-group-alist))))
+      (let ((alist nnsoup-group-alist)
+	    entry e min max)
+	(while (setq e (cdr (setq entry (pop alist))))
+	  (setq min (caaar e))
+	  (while (cdr e)
+	    (setq e (cdr e)))
+	  (setq max (cdaar e))
+	  (setcdr entry (cons (cons min max) (cdr entry)))))
+      (setq nnsoup-group-alist-touched t))
+    nnsoup-group-alist))
+
+(defun nnsoup-write-active-file (&optional force)
+  (when (and nnsoup-group-alist
+	     (or force
+		 nnsoup-group-alist-touched))
+    (setq nnsoup-group-alist-touched nil)
+    (nnheader-temp-write nnsoup-active-file
+      (gnus-prin1 `(setq nnsoup-group-alist ',nnsoup-group-alist))
+      (insert "\n")
+      (gnus-prin1 `(setq nnsoup-current-prefix ,nnsoup-current-prefix))
+      (insert "\n"))))
+
+(defun nnsoup-next-prefix ()
+  "Return the next free prefix."
+  (let (prefix)
+    (while (or (file-exists-p
+		(nnsoup-file (setq prefix (int-to-string
+					   nnsoup-current-prefix))))
+	       (file-exists-p (nnsoup-file prefix t)))
+      (incf nnsoup-current-prefix))
+    (incf nnsoup-current-prefix)
+    prefix))
+
+(defun nnsoup-file-name (dir file)
+  "Return the full path of FILE (in any case) in DIR."
+  (let* ((case-fold-search t)
+	 (files (directory-files dir t))
+	 (regexp (concat (regexp-quote file) "$")))
+    (car (delq nil
+	       (mapcar
+		(lambda (file)
+		  (if (string-match regexp file)
+		      file
+		    nil))
+		files)))))
+
+(defun nnsoup-read-areas ()
+  (let ((areas-file (nnsoup-file-name nnsoup-tmp-directory "areas")))
+    (when areas-file
+      (save-excursion
+	(set-buffer nntp-server-buffer)
+	(let ((areas (gnus-soup-parse-areas areas-file))
+	      entry number area lnum cur-prefix file)
+	  ;; Go through all areas in the new AREAS file.
+	  (while (setq area (pop areas))
+	    ;; Change the name to the permanent name and move the files.
+	    (setq cur-prefix (nnsoup-next-prefix))
+	    (message "Incorporating file %s..." cur-prefix)
+	    (when (file-exists-p
+		   (setq file (concat nnsoup-tmp-directory
+				      (gnus-soup-area-prefix area) ".IDX")))
+	      (rename-file file (nnsoup-file cur-prefix)))
+	    (when (file-exists-p
+		   (setq file (concat nnsoup-tmp-directory
+				      (gnus-soup-area-prefix area) ".MSG")))
+	      (rename-file file (nnsoup-file cur-prefix t))
+	      (gnus-soup-set-area-prefix area cur-prefix)
+	      ;; Find the number of new articles in this area.
+	      (setq number (nnsoup-number-of-articles area))
+	      (if (not (setq entry (assoc (gnus-soup-area-name area)
+					  nnsoup-group-alist)))
+		  ;; If this is a new area (group), we just add this info to
+		  ;; the group alist.
+		  (push (list (gnus-soup-area-name area)
+			      (cons 1 number)
+			      (list (cons 1 number) area))
+			nnsoup-group-alist)
+		;; There are already articles in this group, so we add this
+		;; info to the end of the entry.
+		(nconc entry (list (list (cons (1+ (setq lnum (cdadr entry)))
+					       (+ lnum number))
+					 area)))
+		(setcdr (cadr entry) (+ lnum number))))))
+	(nnsoup-write-active-file t)
+	(delete-file areas-file)))))
+
+(defun nnsoup-number-of-articles (area)
+  (save-excursion
+    (cond
+     ;; If the number is in the area info, we just return it.
+     ((gnus-soup-area-number area)
+      (gnus-soup-area-number area))
+     ;; If there is an index file, we just count the lines.
+     ((/= (gnus-soup-encoding-index (gnus-soup-area-encoding area)) ?n)
+      (set-buffer (nnsoup-index-buffer (gnus-soup-area-prefix area)))
+      (count-lines (point-min) (point-max)))
+     ;; We do it the hard way - re-searching through the message
+     ;; buffer.
+     (t
+      (set-buffer (nnsoup-message-buffer (gnus-soup-area-prefix area)))
+      (unless (assoc (gnus-soup-area-prefix area) nnsoup-article-alist)
+	(nnsoup-dissect-buffer area))
+      (length (cdr (assoc (gnus-soup-area-prefix area)
+			  nnsoup-article-alist)))))))
+
+(defun nnsoup-dissect-buffer (area)
+  (let ((mbox-delim (concat "^" message-unix-mail-delimiter))
+	(format (gnus-soup-encoding-format (gnus-soup-area-encoding area)))
+	(i 0)
+	alist len)
+    (goto-char (point-min))
+    (cond
+     ;; rnews batch format
+     ((= format ?n)
+      (while (looking-at "^#! *rnews \\(+[0-9]+\\) *$")
+	(forward-line 1)
+	(push (list
+	       (incf i) (point)
+	       (progn
+		 (forward-char (string-to-number (match-string 1)))
+		 (point)))
+	      alist)))
+     ;; Unix mbox format
+     ((= format ?m)
+      (while (looking-at mbox-delim)
+	(forward-line 1)
+	(push (list
+	       (incf i) (point)
+	       (progn
+		 (if (re-search-forward mbox-delim nil t)
+		     (beginning-of-line)
+		   (goto-char (point-max)))
+		 (point)))
+	      alist)))
+     ;; MMDF format
+     ((= format ?M)
+      (while (looking-at "\^A\^A\^A\^A\n")
+	(forward-line 1)
+	(push (list
+	       (incf i) (point)
+	       (progn
+		 (if (search-forward "\n\^A\^A\^A\^A\n" nil t)
+		     (beginning-of-line)
+		   (goto-char (point-max)))
+		 (point)))
+	      alist)))
+     ;; Binary format
+     ((or (= format ?B) (= format ?b))
+      (while (not (eobp))
+	(setq len (+ (* (char-after (point)) (expt 2.0 24))
+		     (* (char-after (+ (point) 1)) (expt 2 16))
+		     (* (char-after (+ (point) 2)) (expt 2 8))
+		     (char-after (+ (point) 3))))
+	(push (list
+	       (incf i) (+ (point) 4)
+	       (progn
+		 (forward-char (floor (+ len 4)))
+		 (point)))
+	      alist)))
+     (t
+      (error "Unknown format: %c" format)))
+    (push (cons (gnus-soup-area-prefix area) alist) nnsoup-article-alist)))
+
+(defun nnsoup-index-buffer (prefix &optional message)
+  (let* ((file (concat prefix (if message ".MSG" ".IDX")))
+	 (buffer-name (concat " *nnsoup " file "*")))
+    (or (get-buffer buffer-name)	; File already loaded.
+	(when (file-exists-p (concat nnsoup-directory file))
+	  (save-excursion		; Load the file.
+	    (set-buffer (get-buffer-create buffer-name))
+	    (buffer-disable-undo (current-buffer))
+	    (push (cons nnsoup-current-group (current-buffer)) nnsoup-buffers)
+	    (nnheader-insert-file-contents (concat nnsoup-directory file))
+	    (current-buffer))))))
+
+(defun nnsoup-file (prefix &optional message)
+  (expand-file-name
+   (concat nnsoup-directory prefix (if message ".MSG" ".IDX"))))
+
+(defun nnsoup-message-buffer (prefix)
+  (nnsoup-index-buffer prefix 'msg))
+
+(defun nnsoup-unpack-packets ()
+  "Unpack all packets in `nnsoup-packet-directory'."
+  (let ((packets (directory-files
+		  nnsoup-packet-directory t nnsoup-packet-regexp))
+	packet)
+    (while (setq packet (pop packets))
+      (message "nnsoup: unpacking %s..." packet)
+      (if (not (gnus-soup-unpack-packet
+		nnsoup-tmp-directory nnsoup-unpacker packet))
+	  (message "Couldn't unpack %s" packet)
+	(delete-file packet)
+	(nnsoup-read-areas)
+	(message "Unpacking...done")))))
+
+(defun nnsoup-narrow-to-article (article &optional area head)
+  (let* ((area (or area (nnsoup-article-to-area article nnsoup-current-group)))
+	 (prefix (and area (gnus-soup-area-prefix (nth 1 area))))
+	 (msg-buf (and prefix (nnsoup-index-buffer prefix 'msg)))
+	 beg end)
+    (when area
+      (save-excursion
+	(cond
+	 ;; There is no MSG file.
+	 ((null msg-buf)
+	  nil)
+	 ;; We use the index file to find out where the article
+	 ;; begins and ends.
+	 ((and (= (gnus-soup-encoding-index
+		   (gnus-soup-area-encoding (nth 1 area)))
+		  ?c)
+	       (file-exists-p (nnsoup-file prefix)))
+	  (set-buffer (nnsoup-index-buffer prefix))
+	  (widen)
+	  (goto-char (point-min))
+	  (forward-line (- article (caar area)))
+	  (setq beg (read (current-buffer)))
+	  (forward-line 1)
+	  (if (looking-at "[0-9]+")
+	      (progn
+		(setq end (read (current-buffer)))
+		(set-buffer msg-buf)
+		(widen)
+		(let ((format (gnus-soup-encoding-format
+			       (gnus-soup-area-encoding (nth 1 area)))))
+		  (goto-char end)
+		  (when (or (= format ?n) (= format ?m))
+		    (setq end (progn (forward-line -1) (point))))))
+	    (set-buffer msg-buf))
+	  (widen)
+	  (narrow-to-region beg (or end (point-max))))
+	 (t
+	  (set-buffer msg-buf)
+	  (widen)
+	  (unless (assoc (gnus-soup-area-prefix (nth 1 area))
+			 nnsoup-article-alist)
+	    (nnsoup-dissect-buffer (nth 1 area)))
+	  (let ((entry (assq article (cdr (assoc (gnus-soup-area-prefix
+						  (nth 1 area))
+						 nnsoup-article-alist)))))
+	    (when entry
+	      (narrow-to-region (cadr entry) (caddr entry))))))
+	(goto-char (point-min))
+	(if (not head)
+	    ()
+	  (narrow-to-region
+	   (point-min)
+	   (if (search-forward "\n\n" nil t)
+	       (1- (point))
+	     (point-max))))
+	msg-buf))))
+
+;;;###autoload
+(defun nnsoup-pack-replies ()
+  "Make an outbound package of SOUP replies."
+  (interactive)
+  (unless (file-exists-p nnsoup-replies-directory)
+    (message "No such directory: %s" nnsoup-replies-directory))
+  ;; Write all data buffers.
+  (gnus-soup-save-areas)
+  ;; Write the active file.
+  (nnsoup-write-active-file)
+  ;; Write the REPLIES file.
+  (nnsoup-write-replies)
+  ;; Check whether there is anything here.
+  (when (null (directory-files nnsoup-replies-directory nil "\\.MSG$"))
+    (error "No files to pack."))
+  ;; Pack all these files into a SOUP packet.
+  (gnus-soup-pack nnsoup-replies-directory nnsoup-packer))
+
+(defun nnsoup-write-replies ()
+  "Write the REPLIES file."
+  (when nnsoup-replies-list
+    (gnus-soup-write-replies nnsoup-replies-directory nnsoup-replies-list)
+    (setq nnsoup-replies-list nil)))
+
+(defun nnsoup-article-to-area (article group)
+  "Return the area that ARTICLE in GROUP is located in."
+  (let ((areas (cddr (assoc group nnsoup-group-alist))))
+    (while (and areas (< (cdaar areas) article))
+      (setq areas (cdr areas)))
+    (and areas (car areas))))
+
+(defvar nnsoup-old-functions
+  (list message-send-mail-function message-send-news-function))
+
+;;;###autoload
+(defun nnsoup-set-variables ()
+  "Use the SOUP methods for posting news and mailing mail."
+  (interactive)
+  (setq message-send-news-function 'nnsoup-request-post)
+  (setq message-send-mail-function 'nnsoup-request-mail))
+
+;;;###autoload
+(defun nnsoup-revert-variables ()
+  "Revert posting and mailing methods to the standard Emacs methods."
+  (interactive)
+  (setq message-send-mail-function (car nnsoup-old-functions))
+  (setq message-send-news-function (cadr nnsoup-old-functions)))
+
+(defun nnsoup-store-reply (kind)
+  ;; Mostly stolen from `message.el'.
+  (require 'mail-utils)
+  (let ((tembuf (generate-new-buffer " message temp"))
+	(case-fold-search nil)
+	delimline
+	(mailbuf (current-buffer)))
+    (unwind-protect
+	(save-excursion
+	  (save-restriction
+	    (message-narrow-to-headers)
+	    (if (equal kind "mail")
+		(message-generate-headers message-required-mail-headers)
+	      (message-generate-headers message-required-news-headers)))
+	  (set-buffer tembuf)
+	  (erase-buffer)
+	  (insert-buffer-substring mailbuf)
+	  ;; Remove some headers.
+	  (save-restriction
+	    (message-narrow-to-headers)
+	    ;; Remove some headers.
+	    (message-remove-header message-ignored-mail-headers t))
+	  (goto-char (point-max))
+	  ;; require one newline at the end.
+	  (or (= (preceding-char) ?\n)
+	      (insert ?\n))
+	  (let ((case-fold-search t))
+	    ;; Change header-delimiter to be what sendmail expects.
+	    (goto-char (point-min))
+	    (re-search-forward
+	     (concat "^" (regexp-quote mail-header-separator) "\n"))
+	    (replace-match "\n")
+	    (backward-char 1)
+	    (setq delimline (point-marker))
+	    ;; Insert an extra newline if we need it to work around
+	    ;; Sun's bug that swallows newlines.
+	    (goto-char (1+ delimline))
+	    (when (eval message-mailer-swallows-blank-line)
+	      (newline))
+	    (let ((msg-buf
+		   (gnus-soup-store
+		    nnsoup-replies-directory
+		    (nnsoup-kind-to-prefix kind) nil nnsoup-replies-format-type
+		    nnsoup-replies-index-type))
+		  (num 0))
+	      (when (and msg-buf (bufferp msg-buf))
+		(save-excursion
+		  (set-buffer msg-buf)
+		  (goto-char (point-min))
+		  (while (re-search-forward "^#! *rnews" nil t)
+		    (incf num)))
+		(message "Stored %d messages" num)))
+	    (nnsoup-write-replies)
+	    (kill-buffer tembuf))))))
+
+(defun nnsoup-kind-to-prefix (kind)
+  (unless nnsoup-replies-list
+    (setq nnsoup-replies-list
+	  (gnus-soup-parse-replies
+	   (concat nnsoup-replies-directory "REPLIES"))))
+  (let ((replies nnsoup-replies-list))
+    (while (and replies
+		(not (string= kind (gnus-soup-reply-kind (car replies)))))
+      (setq replies (cdr replies)))
+    (if replies
+	(gnus-soup-reply-prefix (car replies))
+      (push (vector (gnus-soup-unique-prefix nnsoup-replies-directory)
+		    kind
+		    (format "%c%c%c"
+			    nnsoup-replies-format-type
+			    nnsoup-replies-index-type
+			    (if (string= kind "news")
+				?n ?m)))
+	    nnsoup-replies-list)
+      (gnus-soup-reply-prefix (car nnsoup-replies-list)))))
+
+(defun nnsoup-make-active ()
+  "(Re-)create the SOUP active file."
+  (interactive)
+  (let ((files (sort (directory-files nnsoup-directory t "IDX$")
+		     (lambda (f1 f2)
+		       (< (progn (string-match "/\\([0-9]+\\)\\." f1)
+				 (string-to-int (match-string 1 f1)))
+			  (progn (string-match "/\\([0-9]+\\)\\." f2)
+				 (string-to-int (match-string 1 f2)))))))
+	active group lines ident elem min)
+    (set-buffer (get-buffer-create " *nnsoup work*"))
+    (buffer-disable-undo (current-buffer))
+    (while files
+      (message "Doing %s..." (car files))
+      (erase-buffer)
+      (nnheader-insert-file-contents (car files))
+      (goto-char (point-min))
+      (if (not (re-search-forward "^[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t *\\(Xref: \\)? *[^ ]* \\([^ ]+\\):[0-9]" nil t))
+	  (setq group "unknown")
+	(setq group (match-string 2)))
+      (setq lines (count-lines (point-min) (point-max)))
+      (setq ident (progn (string-match
+			  "/\\([0-9]+\\)\\." (car files))
+			 (substring
+			  (car files) (match-beginning 1)
+			  (match-end 1))))
+      (if (not (setq elem (assoc group active)))
+	  (push (list group (cons 1 lines)
+		      (list (cons 1 lines)
+			    (vector ident group "ncm" "" lines)))
+		active)
+	(nconc elem
+	       (list
+		(list (cons (1+ (setq min (cdadr elem)))
+			    (+ min lines))
+		      (vector ident group "ncm" "" lines))))
+	(setcdr (cadr elem) (+ min lines)))
+      (setq files (cdr files)))
+    (message "")
+    (setq nnsoup-group-alist active)
+    (nnsoup-write-active-file t)))
+
+(defun nnsoup-delete-unreferenced-message-files ()
+  "Delete any *.MSG and *.IDX files that aren't known by nnsoup."
+  (interactive)
+  (let* ((known (apply 'nconc (mapcar
+			       (lambda (ga)
+				 (mapcar
+				  (lambda (area)
+				    (gnus-soup-area-prefix (cadr area)))
+				  (cddr ga)))
+			       nnsoup-group-alist)))
+	 (regexp "\\.MSG$\\|\\.IDX$")
+	 (files (directory-files nnsoup-directory nil regexp))
+	 non-files file)
+    ;; Find all files that aren't known by nnsoup.
+    (while (setq file (pop files))
+      (string-match regexp file)
+      (unless (member (substring file 0 (match-beginning 0)) known)
+	(push file non-files)))
+    ;; Sort and delete the files.
+    (setq non-files (sort non-files 'string<))
+    (map-y-or-n-p "Delete file %s? "
+		  (lambda (file) (delete-file (concat nnsoup-directory file)))
+		  non-files)))
+
+(provide 'nnsoup)
+
+;;; nnsoup.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnspool.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,463 @@
+;;; nnspool.el --- spool access for GNU Emacs
+;; Copyright (C) 1988,89,90,93,94,95,96,97 Free Software Foundation, Inc.
+
+;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; 	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'nntp)
+(require 'timezone)
+(require 'nnoo)
+(eval-when-compile (require 'cl))
+
+(nnoo-declare nnspool)
+
+(defvoo nnspool-inews-program news-inews-program
+  "Program to post news.
+This is most commonly `inews' or `injnews'.")
+
+(defvoo nnspool-inews-switches '("-h" "-S")
+  "Switches for nnspool-request-post to pass to `inews' for posting news.
+If you are using Cnews, you probably should set this variable to nil.")
+
+(defvoo nnspool-spool-directory (file-name-as-directory news-path)
+  "Local news spool directory.")
+
+(defvoo nnspool-nov-directory (concat nnspool-spool-directory "over.view/")
+  "Local news nov directory.")
+
+(defvoo nnspool-lib-dir "/usr/lib/news/"
+  "Where the local news library files are stored.")
+
+(defvoo nnspool-active-file (concat nnspool-lib-dir "active")
+  "Local news active file.")
+
+(defvoo nnspool-newsgroups-file (concat nnspool-lib-dir "newsgroups")
+  "Local news newsgroups file.")
+
+(defvoo nnspool-distributions-file (concat nnspool-lib-dir "distribs.pat")
+  "Local news distributions file.")
+
+(defvoo nnspool-history-file (concat nnspool-lib-dir "history")
+  "Local news history file.")
+
+(defvoo nnspool-active-times-file (concat nnspool-lib-dir "active.times")
+  "Local news active date file.")
+
+(defvoo nnspool-large-newsgroup 50
+  "The number of the articles which indicates a large newsgroup.
+If the number of the articles is greater than the value, verbose
+messages will be shown to indicate the current status.")
+
+(defvoo nnspool-nov-is-evil nil
+  "Non-nil means that nnspool will never return NOV lines instead of headers.")
+
+(defconst nnspool-sift-nov-with-sed nil
+  "If non-nil, use sed to get the relevant portion from the overview file.
+If nil, nnspool will load the entire file into a buffer and process it
+there.")
+
+(defvoo nnspool-rejected-article-hook nil
+  "*A hook that will be run when an article has been rejected by the server.")
+
+
+
+(defconst nnspool-version "nnspool 2.0"
+  "Version numbers of this version of NNSPOOL.")
+
+(defvoo nnspool-current-directory nil
+  "Current news group directory.")
+
+(defvoo nnspool-current-group nil)
+(defvoo nnspool-status-string "")
+
+
+;;; Interface functions.
+
+(nnoo-define-basics nnspool)
+
+(deffoo nnspool-retrieve-headers (articles &optional group server fetch-old)
+  "Retrieve the headers of ARTICLES."
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (when (nnspool-possibly-change-directory group)
+      (let* ((number (length articles))
+	     (count 0)
+	     (default-directory nnspool-current-directory)
+	     (do-message (and (numberp nnspool-large-newsgroup)
+			      (> number nnspool-large-newsgroup)))
+	     file beg article ag)
+	(if (and (numberp (car articles))
+		 (nnspool-retrieve-headers-with-nov articles fetch-old))
+	    ;; We successfully retrieved the NOV headers.
+	    'nov
+	  ;; No NOV headers here, so we do it the hard way.
+	  (while (setq article (pop articles))
+	    (if (stringp article)
+		;; This is a Message-ID.
+		(setq ag (nnspool-find-id article)
+		      file (and ag (nnspool-article-pathname
+				    (car ag) (cdr ag)))
+		      article (cdr ag))
+	      ;; This is an article in the current group.
+	      (setq file (int-to-string article)))
+	    ;; Insert the head of the article.
+	    (when (and file
+		       (file-exists-p file))
+	      (insert "221 ")
+	      (princ article (current-buffer))
+	      (insert " Article retrieved.\n")
+	      (setq beg (point))
+	      (inline (nnheader-insert-head file))
+	      (goto-char beg)
+	      (search-forward "\n\n" nil t)
+	      (forward-char -1)
+	      (insert ".\n")
+	      (delete-region (point) (point-max)))
+
+	    (and do-message
+		 (zerop (% (incf count) 20))
+		 (message "nnspool: Receiving headers... %d%%"
+			  (/ (* count 100) number))))
+
+	  (when do-message
+	    (message "nnspool: Receiving headers...done"))
+
+	  ;; Fold continuation lines.
+	  (nnheader-fold-continuation-lines)
+	  'headers)))))
+
+(deffoo nnspool-open-server (server &optional defs)
+  (nnoo-change-server 'nnspool server defs)
+  (cond
+   ((not (file-exists-p nnspool-spool-directory))
+    (nnspool-close-server)
+    (nnheader-report 'nnspool "Spool directory doesn't exist: %s"
+		     nnspool-spool-directory))
+   ((not (file-directory-p
+	  (directory-file-name
+	   (file-truename nnspool-spool-directory))))
+    (nnspool-close-server)
+    (nnheader-report 'nnspool "Not a directory: %s" nnspool-spool-directory))
+   ((not (file-exists-p nnspool-active-file))
+    (nnheader-report 'nnspool "The active file doesn't exist: %s"
+		     nnspool-active-file))
+   (t
+    (nnheader-report 'nnspool "Opened server %s using directory %s"
+		     server nnspool-spool-directory)
+    t)))
+
+(deffoo nnspool-request-article (id &optional group server buffer)
+  "Select article by message ID (or number)."
+  (nnspool-possibly-change-directory group)
+  (let ((nntp-server-buffer (or buffer nntp-server-buffer))
+	file ag)
+    (if (stringp id)
+	;; This is a Message-ID.
+	(when (setq ag (nnspool-find-id id))
+	  (setq file (nnspool-article-pathname (car ag) (cdr ag))))
+      (setq file (nnspool-article-pathname nnspool-current-group id)))
+    (and file
+	 (file-exists-p file)
+	 (not (file-directory-p file))
+	 (save-excursion (nnspool-find-file file))
+	 ;; We return the article number and group name.
+	 (if (numberp id)
+	     (cons nnspool-current-group id)
+	   ag))))
+
+(deffoo nnspool-request-body (id &optional group server)
+  "Select article body by message ID (or number)."
+  (nnspool-possibly-change-directory group)
+  (let ((res (nnspool-request-article id)))
+    (when res
+      (save-excursion
+	(set-buffer nntp-server-buffer)
+	(goto-char (point-min))
+	(when (search-forward "\n\n" nil t)
+	  (delete-region (point-min) (point)))
+	res))))
+
+(deffoo nnspool-request-head (id &optional group server)
+  "Select article head by message ID (or number)."
+  (nnspool-possibly-change-directory group)
+  (let ((res (nnspool-request-article id)))
+    (when res
+      (save-excursion
+	(set-buffer nntp-server-buffer)
+	(goto-char (point-min))
+	(when (search-forward "\n\n" nil t)
+	  (delete-region (1- (point)) (point-max)))
+	(nnheader-fold-continuation-lines)))
+    res))
+
+(deffoo nnspool-request-group (group &optional server dont-check)
+  "Select news GROUP."
+  (let ((pathname (nnspool-article-pathname group))
+	dir)
+    (if (not (file-directory-p pathname))
+	(nnheader-report
+	 'nnspool "Invalid group name (no such directory): %s" group)
+      (setq nnspool-current-directory pathname)
+      (nnheader-report 'nnspool "Selected group %s" group)
+      (if dont-check
+	  (progn
+	    (nnheader-report 'nnspool "Selected group %s" group)
+	    t)
+	;; Yes, completely empty spool directories *are* possible.
+	;; Fix by Sudish Joseph <joseph@cis.ohio-state.edu>
+	(when (setq dir (directory-files pathname nil "^[0-9]+$" t))
+	  (setq dir
+		(sort (mapcar (lambda (name) (string-to-int name)) dir) '<)))
+	(if dir
+	    (nnheader-insert
+	     "211 %d %d %d %s\n" (length dir) (car dir)
+	     (progn (while (cdr dir) (setq dir (cdr dir))) (car dir))
+	     group)
+	  (nnheader-report 'nnspool "Empty group %s" group)
+	  (nnheader-insert "211 0 0 0 %s\n" group))))))
+
+(deffoo nnspool-request-type (group &optional article)
+  'news)
+
+(deffoo nnspool-close-group (group &optional server)
+  t)
+
+(deffoo nnspool-request-list (&optional server)
+  "List active newsgroups."
+  (save-excursion
+    (or (nnspool-find-file nnspool-active-file)
+	(nnheader-report 'nnspool (nnheader-file-error nnspool-active-file)))))
+
+(deffoo nnspool-request-list-newsgroups (&optional server)
+  "List newsgroups (defined in NNTP2)."
+  (save-excursion
+    (or (nnspool-find-file nnspool-newsgroups-file)
+	(nnheader-report 'nnspool (nnheader-file-error
+				   nnspool-newsgroups-file)))))
+
+(deffoo nnspool-request-list-distributions (&optional server)
+  "List distributions (defined in NNTP2)."
+  (save-excursion
+    (or (nnspool-find-file nnspool-distributions-file)
+	(nnheader-report 'nnspool (nnheader-file-error
+				   nnspool-distributions-file)))))
+
+;; Suggested by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>.
+(deffoo nnspool-request-newgroups (date &optional server)
+  "List groups created after DATE."
+  (if (nnspool-find-file nnspool-active-times-file)
+      (save-excursion
+	;; Find the last valid line.
+	(goto-char (point-max))
+	(while (and (not (looking-at
+			  "\\([^ ]+\\) +\\([0-9]+\\)[0-9][0-9][0-9] "))
+		    (zerop (forward-line -1))))
+	(let ((seconds (nnspool-seconds-since-epoch date))
+	      groups)
+	  ;; Go through lines and add the latest groups to a list.
+	  (while (and (looking-at "\\([^ ]+\\) +[0-9]+ ")
+		      (progn
+			;; We insert a .0 to make the list reader
+			;; interpret the number as a float.  It is far
+			;; too big to be stored in a lisp integer.
+			(goto-char (1- (match-end 0)))
+			(insert ".0")
+			(> (progn
+			     (goto-char (match-end 1))
+			     (read (current-buffer)))
+			   seconds))
+		      (push (buffer-substring
+					  (match-beginning 1) (match-end 1))
+					 groups)
+		      (zerop (forward-line -1))))
+	  (erase-buffer)
+	  (while groups
+	    (insert (car groups) " 0 0 y\n")
+	    (setq groups (cdr groups))))
+	t)
+    nil))
+
+(deffoo nnspool-request-post (&optional server)
+  "Post a new news in current buffer."
+  (save-excursion
+    (let* ((process-connection-type nil) ; t bugs out on Solaris
+	   (inews-buffer (generate-new-buffer " *nnspool post*"))
+	   (proc
+	    (condition-case err
+		(apply 'start-process "*nnspool inews*" inews-buffer
+		       nnspool-inews-program nnspool-inews-switches)
+	      (error
+	       (nnheader-report 'nnspool "inews error: %S" err)))))
+      (if (not proc)
+	  ;; The inews program failed.
+	  ()
+	(nnheader-report 'nnspool "")
+	(set-process-sentinel proc 'nnspool-inews-sentinel)
+	(process-send-region proc (point-min) (point-max))
+	;; We slap a condition-case around this, because the process may
+	;; have exited already...
+	(ignore-errors
+	  (process-send-eof proc))
+	t))))
+
+
+
+;;; Internal functions.
+
+(defun nnspool-inews-sentinel (proc status)
+  (save-excursion
+    (set-buffer (process-buffer proc))
+    (goto-char (point-min))
+    (if (or (zerop (buffer-size))
+	    (search-forward "spooled" nil t))
+	(kill-buffer (current-buffer))
+      ;; Make status message by folding lines.
+      (while (re-search-forward "[ \t\n]+" nil t)
+	(replace-match " " t t))
+      (nnheader-report 'nnspool "%s" (buffer-string))
+      (message "nnspool: %s" nnspool-status-string)
+      (ding)
+      (run-hooks 'nnspool-rejected-article-hook))))
+
+(defun nnspool-retrieve-headers-with-nov (articles &optional fetch-old)
+  (if (or gnus-nov-is-evil nnspool-nov-is-evil)
+      nil
+    (let ((nov (nnheader-group-pathname
+		nnspool-current-group nnspool-nov-directory ".overview"))
+	  (arts articles)
+	  last)
+      (if (not (file-exists-p nov))
+	  ()
+	(save-excursion
+	  (set-buffer nntp-server-buffer)
+	  (erase-buffer)
+	  (if nnspool-sift-nov-with-sed
+	      (nnspool-sift-nov-with-sed articles nov)
+	    (nnheader-insert-file-contents nov)
+	    (if (and fetch-old
+		     (not (numberp fetch-old)))
+		t			; We want all the headers.
+	      (ignore-errors
+		;; Delete unwanted NOV lines.
+		(nnheader-nov-delete-outside-range
+		 (if fetch-old (max 1 (- (car articles) fetch-old))
+		   (car articles))
+		 (car (last articles)))
+		;; If the buffer is empty, this wasn't very successful.
+		(unless (zerop (buffer-size))
+		  ;; We check what the last article number was.
+		  ;; The NOV file may be out of sync with the articles
+		  ;; in the group.
+		  (forward-line -1)
+		  (setq last (read (current-buffer)))
+		  (if (= last (car articles))
+		      ;; Yup, it's all there.
+		      t
+		    ;; Perhaps not.  We try to find the missing articles.
+		    (while (and arts
+				(<= last (car arts)))
+		      (pop arts))
+		    ;; The articles in `arts' are missing from the buffer.
+		    (while arts
+		      (nnspool-insert-nov-head (pop arts)))
+		    t))))))))))
+
+(defun nnspool-insert-nov-head (article)
+  "Read the head of ARTICLE, convert to NOV headers, and insert."
+  (save-excursion
+    (let ((cur (current-buffer))
+	  buf)
+      (setq buf (nnheader-set-temp-buffer " *nnspool head*"))
+      (when (nnheader-insert-head
+	     (nnspool-article-pathname nnspool-current-group article))
+	(nnheader-insert-article-line article)
+	(let ((headers (nnheader-parse-head)))
+	  (set-buffer cur)
+	  (goto-char (point-max))
+	  (nnheader-insert-nov headers)))
+      (kill-buffer buf))))
+
+(defun nnspool-sift-nov-with-sed (articles file)
+  (let ((first (car articles))
+	(last (progn (while (cdr articles) (setq articles (cdr articles)))
+		     (car articles))))
+    (call-process "awk" nil t nil
+		  (format "BEGIN {firstmsg=%d; lastmsg=%d;}\n $1 >= firstmsg && $1 <= lastmsg {print;}"
+			  (1- first) (1+ last))
+		  file)))
+
+;; Fixed by fdc@cliwe.ping.de (Frank D. Cringle).
+;; Find out what group an article identified by a Message-ID is in.
+(defun nnspool-find-id (id)
+  (save-excursion
+    (set-buffer (get-buffer-create " *nnspool work*"))
+    (buffer-disable-undo (current-buffer))
+    (erase-buffer)
+    (ignore-errors
+      (call-process "grep" nil t nil (regexp-quote id) nnspool-history-file))
+    (goto-char (point-min))
+    (prog1
+	(when (looking-at "<[^>]+>[ \t]+[-0-9~]+[ \t]+\\([^ /\t\n]+\\)/\\([0-9]+\\)[ \t\n]")
+	  (cons (match-string 1) (string-to-int (match-string 2))))
+      (kill-buffer (current-buffer)))))
+
+(defun nnspool-find-file (file)
+  "Insert FILE in server buffer safely."
+  (set-buffer nntp-server-buffer)
+  (erase-buffer)
+  (condition-case ()
+      (progn (nnheader-insert-file-contents file) t)
+    (file-error nil)))
+
+(defun nnspool-possibly-change-directory (group)
+  (if (not group)
+      t
+    (let ((pathname (nnspool-article-pathname group)))
+      (if (file-directory-p pathname)
+	  (setq nnspool-current-directory pathname
+		nnspool-current-group group)
+	(nnheader-report 'nnspool "No such newsgroup: %s" group)))))
+
+(defun nnspool-article-pathname (group &optional article)
+  "Find the path for GROUP."
+  (nnheader-group-pathname group nnspool-spool-directory article))
+
+(defun nnspool-seconds-since-epoch (date)
+  (let* ((tdate (mapcar (lambda (ti) (and ti (string-to-int ti)))
+			(timezone-parse-date date)))
+	 (ttime (mapcar (lambda (ti) (and ti (string-to-int ti)))
+			(timezone-parse-time
+			 (aref (timezone-parse-date date) 3))))
+	 (unix (encode-time (nth 2 ttime) (nth 1 ttime) (nth 0 ttime)
+			    (nth 2 tdate) (nth 1 tdate) (nth 0 tdate)
+			    (nth 4 tdate))))
+    (+ (* (car unix) 65536.0)
+       (cadr unix))))
+
+(provide 'nnspool)
+
+;;; nnspool.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nntp.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,1138 @@
+;;; nntp.el --- nntp access for Gnus
+;;; Copyright (C) 1987,88,89,90,92,93,94,95,96,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'nnheader)
+(require 'nnoo)
+(require 'gnus-util)
+
+(nnoo-declare nntp)
+
+(eval-and-compile
+  (unless (fboundp 'open-network-stream)
+    (require 'tcp)))
+
+(eval-when-compile (require 'cl))
+
+(defvoo nntp-address nil
+  "Address of the physical nntp server.")
+
+(defvoo nntp-port-number "nntp"
+  "Port number on the physical nntp server.")
+
+(defvoo nntp-server-opened-hook '(nntp-send-mode-reader)
+  "*Hook used for sending commands to the server at startup.
+The default value is `nntp-send-mode-reader', which makes an innd
+server spawn an nnrpd server.  Another useful function to put in this
+hook might be `nntp-send-authinfo', which will prompt for a password
+to allow posting from the server.  Note that this is only necessary to
+do on servers that use strict access control.")
+
+(defvoo nntp-authinfo-function 'nntp-send-authinfo
+  "Function used to send AUTHINFO to the server.")
+
+(defvoo nntp-server-action-alist
+  '(("nntpd 1\\.5\\.11t"
+     (remove-hook 'nntp-server-opened-hook 'nntp-send-mode-reader))
+    ("NNRP server Netscape"
+     (setq nntp-server-list-active-group nil)))
+  "Alist of regexps to match on server types and actions to be taken.
+For instance, if you want Gnus to beep every time you connect
+to innd, you could say something like:
+
+\(setq nntp-server-action-alist
+       '((\"innd\" (ding))))
+
+You probably don't want to do that, though.")
+
+(defvoo nntp-open-connection-function 'nntp-open-network-stream
+  "*Function used for connecting to a remote system.
+It will be called with the buffer to output in.
+
+Two pre-made functions are `nntp-open-network-stream', which is the
+default, and simply connects to some port or other on the remote
+system (see nntp-port-number).  The other are `nntp-open-rlogin', which
+does an rlogin on the remote system, and then does a telnet to the
+NNTP server available there (see nntp-rlogin-parameters) and `nntp-open-telnet' which
+telnets to a remote system, logs in and does the same")
+
+(defvoo nntp-rlogin-parameters '("telnet" "-8" "${NNTPSERVER:=news}" "nntp")
+  "*Parameters to `nntp-open-login'.
+That function may be used as `nntp-open-connection-function'.  In that
+case, this list will be used as the parameter list given to rsh.")
+
+(defvoo nntp-rlogin-user-name nil
+  "*User name on remote system when using the rlogin connect method.")
+
+(defvoo nntp-telnet-parameters '("exec" "telnet" "-8" "${NNTPSERVER:=news}" "nntp")
+  "*Parameters to `nntp-open-telnet'.
+That function may be used as `nntp-open-connection-function'.  In that
+case, this list will be executed as a command after logging in
+via telnet.")
+
+(defvoo nntp-telnet-user-name nil
+  "User name to log in via telnet with.")
+
+(defvoo nntp-telnet-passwd nil
+  "Password to use to log in via telnet with.")
+
+(defvoo nntp-end-of-line "\r\n"
+  "String to use on the end of lines when talking to the NNTP server.
+This is \"\\r\\n\" by default, but should be \"\\n\" when
+using rlogin or telnet to communicate with the server.")
+
+(defvoo nntp-large-newsgroup 50
+  "*The number of the articles which indicates a large newsgroup.
+If the number of the articles is greater than the value, verbose
+messages will be shown to indicate the current status.")
+
+(defvoo nntp-maximum-request 400
+  "*The maximum number of the requests sent to the NNTP server at one time.
+If Emacs hangs up while retrieving headers, set the variable to a
+lower value.")
+
+(defvoo nntp-nov-is-evil nil
+  "*If non-nil, nntp will never attempt to use XOVER when talking to the server.")
+
+(defvoo nntp-xover-commands '("XOVER" "XOVERVIEW")
+  "*List of strings that are used as commands to fetch NOV lines from a server.
+The strings are tried in turn until a positive response is gotten.  If
+none of the commands are successful, nntp will just grab headers one
+by one.")
+
+(defvoo nntp-nov-gap 20
+  "*Maximum allowed gap between two articles.
+If the gap between two consecutive articles is bigger than this
+variable, split the XOVER request into two requests.")
+
+(defvoo nntp-connection-timeout nil
+  "*Number of seconds to wait before an nntp connection times out.
+If this variable is nil, which is the default, no timers are set.")
+
+(defvoo nntp-prepare-server-hook nil
+  "*Hook run before a server is opened.
+If can be used to set up a server remotely, for instance.  Say you
+have an account at the machine \"other.machine\".  This machine has
+access to an NNTP server that you can't access locally.  You could
+then use this hook to rsh to the remote machine and start a proxy NNTP
+server there that you can connect to.  See also `nntp-open-connection-function'")
+
+(defvoo nntp-warn-about-losing-connection t
+  "*If non-nil, beep when a server closes connection.")
+
+
+
+;;; Internal variables.
+
+(defvar nntp-have-messaged nil)
+
+(defvar nntp-process-wait-for nil)
+(defvar nntp-process-to-buffer nil)
+(defvar nntp-process-callback nil)
+(defvar nntp-process-decode nil)
+(defvar nntp-process-start-point nil)
+(defvar nntp-inside-change-function nil)
+
+(defvar nntp-connection-list nil)
+
+(defvoo nntp-server-type nil)
+(defvoo nntp-connection-alist nil)
+(defvoo nntp-status-string "")
+(defconst nntp-version "nntp 5.0")
+(defvoo nntp-inhibit-erase nil)
+(defvoo nntp-inhibit-output nil)
+
+(defvoo nntp-server-xover 'try)
+(defvoo nntp-server-list-active-group 'try)
+
+(eval-and-compile
+  (autoload 'nnmail-read-passwd "nnmail"))
+
+
+
+;;; Internal functions.
+
+(defsubst nntp-send-string (process string)
+  "Send STRING to PROCESS."
+  (process-send-string process (concat string nntp-end-of-line)))
+
+(defsubst nntp-wait-for (process wait-for buffer &optional decode discard)
+  "Wait for WAIT-FOR to arrive from PROCESS."
+  (save-excursion
+    (set-buffer (process-buffer process))
+    (goto-char (point-min))
+    (while (or (not (memq (following-char) '(?2 ?3 ?4 ?5)))
+	       (looking-at "480"))
+      (when (looking-at "480")
+	(erase-buffer)
+	(funcall nntp-authinfo-function))
+      (nntp-accept-process-output process)
+      (goto-char (point-min)))
+    (prog1
+	(if (looking-at "[45]")
+	    (progn
+	      (nntp-snarf-error-message)
+	      nil)
+	  (goto-char (point-max))
+	  (let ((limit (point-min)))
+	    (while (not (re-search-backward wait-for limit t))
+	      ;; We assume that whatever we wait for is less than 1000
+	      ;; characters long.
+	      (setq limit (max (- (point-max) 1000) (point-min)))
+	      (nntp-accept-process-output process)
+	      (goto-char (point-max))))
+	  (nntp-decode-text (not decode))
+	  (unless discard
+	    (save-excursion
+	      (set-buffer buffer)
+	      (goto-char (point-max))
+	      (insert-buffer-substring (process-buffer process))
+	      ;; Nix out "nntp reading...." message.
+	      (when nntp-have-messaged
+		(setq nntp-have-messaged nil)
+		(message ""))
+	      t)))
+      (unless discard
+	(erase-buffer)))))
+
+(defsubst nntp-find-connection (buffer)
+  "Find the connection delivering to BUFFER."
+  (let ((alist nntp-connection-alist)
+	(buffer (if (stringp buffer) (get-buffer buffer) buffer))
+	process entry)
+    (while (setq entry (pop alist))
+      (when (eq buffer (cadr entry))
+	(setq process (car entry)
+	      alist nil)))
+    (when process
+      (if (memq (process-status process) '(open run))
+	  process
+	(when (buffer-name (process-buffer process))
+	  (kill-buffer (process-buffer process)))
+	(setq nntp-connection-alist (delq entry nntp-connection-alist))
+	nil))))
+
+(defsubst nntp-find-connection-entry (buffer)
+  "Return the entry for the connection to BUFFER."
+  (assq (nntp-find-connection buffer) nntp-connection-alist))
+
+(defun nntp-find-connection-buffer (buffer)
+  "Return the process connection buffer tied to BUFFER."
+  (let ((process (nntp-find-connection buffer)))
+    (when process
+      (process-buffer process))))
+
+(defsubst nntp-retrieve-data (command address port buffer
+				   &optional wait-for callback decode)
+  "Use COMMAND to retrieve data into BUFFER from PORT on ADDRESS."
+  (let ((process (or (nntp-find-connection buffer)
+		     (nntp-open-connection buffer))))
+    (if (not process)
+	(nnheader-report 'nntp "Couldn't open connection to %s" address)
+      (unless (or nntp-inhibit-erase nnheader-callback-function)
+	(save-excursion
+	  (set-buffer (process-buffer process))
+	  (erase-buffer)))
+      (when command
+	(nntp-send-string process command))
+      (cond
+       ((eq callback 'ignore)
+	t)
+       ((and callback wait-for)
+	(save-excursion
+	  (set-buffer (process-buffer process))
+	  (unless nntp-inside-change-function
+	    (erase-buffer))
+	  (setq nntp-process-decode decode
+		nntp-process-to-buffer buffer
+		nntp-process-wait-for wait-for
+		nntp-process-callback callback
+		nntp-process-start-point (point-max)
+		after-change-functions
+		(list 'nntp-after-change-function-callback)))
+	t)
+       (wait-for
+	(nntp-wait-for process wait-for buffer decode))
+       (t t)))))
+
+(defsubst nntp-send-command (wait-for &rest strings)
+  "Send STRINGS to server and wait until WAIT-FOR returns."
+  (when (and (not nnheader-callback-function)
+	     (not nntp-inhibit-output))
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)))
+  (nntp-retrieve-data
+   (mapconcat 'identity strings " ")
+   nntp-address nntp-port-number nntp-server-buffer
+   wait-for nnheader-callback-function))
+
+(defun nntp-send-command-nodelete (wait-for &rest strings)
+  "Send STRINGS to server and wait until WAIT-FOR returns."
+  (nntp-retrieve-data
+   (mapconcat 'identity strings " ")
+   nntp-address nntp-port-number nntp-server-buffer
+   wait-for nnheader-callback-function))
+
+(defun nntp-send-command-and-decode (wait-for &rest strings)
+  "Send STRINGS to server and wait until WAIT-FOR returns."
+  (when (and (not nnheader-callback-function)
+	     (not nntp-inhibit-output))
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)))
+  (nntp-retrieve-data
+   (mapconcat 'identity strings " ")
+   nntp-address nntp-port-number nntp-server-buffer
+   wait-for nnheader-callback-function t))
+
+(defun nntp-send-buffer (wait-for)
+  "Send the current buffer to server and wait until WAIT-FOR returns."
+  (when (and (not nnheader-callback-function)
+	     (not nntp-inhibit-output))
+    (save-excursion
+      (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
+      (erase-buffer)))
+  (nntp-encode-text)
+  (process-send-region (nntp-find-connection nntp-server-buffer)
+		       (point-min) (point-max))
+  (nntp-retrieve-data
+   nil nntp-address nntp-port-number nntp-server-buffer
+   wait-for nnheader-callback-function))
+
+
+
+;;; Interface functions.
+
+(nnoo-define-basics nntp)
+
+(deffoo nntp-retrieve-headers (articles &optional group server fetch-old)
+  "Retrieve the headers of ARTICLES."
+  (nntp-possibly-change-group group server)
+  (save-excursion
+    (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
+    (erase-buffer)
+    (if (and (not gnus-nov-is-evil)
+	     (not nntp-nov-is-evil)
+	     (nntp-retrieve-headers-with-xover articles fetch-old))
+	;; We successfully retrieved the headers via XOVER.
+        'nov
+      ;; XOVER didn't work, so we do it the hard, slow and inefficient
+      ;; way.
+      (let ((number (length articles))
+	    (count 0)
+	    (received 0)
+	    (last-point (point-min))
+	    (buf (nntp-find-connection-buffer nntp-server-buffer))
+	    (nntp-inhibit-erase t))
+	;; Send HEAD command.
+	(while articles
+	  (nntp-send-command
+	   nil
+	   "HEAD" (if (numberp (car articles))
+		      (int-to-string (car articles))
+		    ;; `articles' is either a list of article numbers
+		    ;; or a list of article IDs.
+		    (car articles)))
+	  (setq articles (cdr articles)
+		count (1+ count))
+	  ;; Every 400 header requests we have to read the stream in
+	  ;; order to avoid deadlocks.
+	  (when (or (null articles)	;All requests have been sent.
+		    (zerop (% count nntp-maximum-request)))
+	    (nntp-accept-response)
+	    (while (progn
+		     (progn
+		       (set-buffer buf)
+		       (goto-char last-point))
+		     ;; Count replies.
+		     (while (re-search-forward "^[0-9]" nil t)
+		       (incf received))
+		     (setq last-point (point))
+		     (< received count))
+	      ;; If number of headers is greater than 100, give
+	      ;;  informative messages.
+	      (and (numberp nntp-large-newsgroup)
+		   (> number nntp-large-newsgroup)
+		   (zerop (% received 20))
+		   (nnheader-message 6 "NNTP: Receiving headers... %d%%"
+				     (/ (* received 100) number)))
+	      (nntp-accept-response))))
+	;; Wait for text of last command.
+	(goto-char (point-max))
+	(re-search-backward "^[0-9]" nil t)
+	(when (looking-at "^[23]")
+	  (while (progn
+		   (goto-char (point-max))
+		   (forward-line -1)
+		   (not (looking-at "^\\.\r?\n")))
+	    (nntp-accept-response)))
+	(and (numberp nntp-large-newsgroup)
+	     (> number nntp-large-newsgroup)
+	     (nnheader-message 6 "NNTP: Receiving headers...done"))
+
+	;; Now all of replies are received.  Fold continuation lines.
+	(nnheader-fold-continuation-lines)
+	;; Remove all "\r"'s.
+	(nnheader-strip-cr)
+	(copy-to-buffer nntp-server-buffer (point-min) (point-max))
+	'headers))))
+
+(deffoo nntp-retrieve-groups (groups &optional server)
+  "Retrieve group info on GROUPS."
+  (nntp-possibly-change-group nil server)
+  (save-excursion
+    (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
+    ;; The first time this is run, this variable is `try'.  So we
+    ;; try.
+    (when (eq nntp-server-list-active-group 'try)
+      (nntp-try-list-active (car groups)))
+    (erase-buffer)
+    (let ((count 0)
+	  (received 0)
+	  (last-point (point-min))
+	  (nntp-inhibit-erase t)
+	  (command (if nntp-server-list-active-group "LIST ACTIVE" "GROUP")))
+      (while groups
+	;; Send the command to the server.
+	(nntp-send-command nil command (pop groups))
+	(incf count)
+	;; Every 400 requests we have to read the stream in
+	;; order to avoid deadlocks.
+	(when (or (null groups)		;All requests have been sent.
+		  (zerop (% count nntp-maximum-request)))
+	  (nntp-accept-response)
+	  (while (progn
+		   (goto-char last-point)
+		   ;; Count replies.
+		   (while (re-search-forward "^[0-9]" nil t)
+		     (incf received))
+		   (setq last-point (point))
+		   (< received count))
+	    (nntp-accept-response))))
+
+      ;; Wait for the reply from the final command.
+      (goto-char (point-max))
+      (re-search-backward "^[0-9]" nil t)
+      (when (looking-at "^[23]")
+	(while (progn
+		 (goto-char (point-max))
+		 (if (not nntp-server-list-active-group)
+		     (not (re-search-backward "\r?\n" (- (point) 3) t))
+		   (not (re-search-backward "^\\.\r?\n" (- (point) 4) t))))
+	  (nntp-accept-response)))
+
+      ;; Now all replies are received.  We remove CRs.
+      (goto-char (point-min))
+      (while (search-forward "\r" nil t)
+	(replace-match "" t t))
+
+      (if (not nntp-server-list-active-group)
+	  (progn
+	    (copy-to-buffer nntp-server-buffer (point-min) (point-max))
+	    'group)
+	;; We have read active entries, so we just delete the
+	;; superfluous gunk.
+	(goto-char (point-min))
+	(while (re-search-forward "^[.2-5]" nil t)
+	  (delete-region (match-beginning 0)
+			 (progn (forward-line 1) (point))))
+	(copy-to-buffer nntp-server-buffer (point-min) (point-max))
+	'active))))
+
+(deffoo nntp-retrieve-articles (articles &optional group server)
+  (nntp-possibly-change-group group server)
+  (save-excursion
+    (let ((number (length articles))
+	  (count 0)
+	  (received 0)
+	  (last-point (point-min))
+	  (buf (nntp-find-connection-buffer nntp-server-buffer))
+	  (nntp-inhibit-erase t)
+	  (map (apply 'vector articles))
+	  (point 1)
+	  article alist)
+      (set-buffer buf)
+      (erase-buffer)
+      ;; Send HEAD command.
+      (while (setq article (pop articles))
+	(nntp-send-command
+	 nil
+	 "ARTICLE" (if (numberp article)
+		       (int-to-string article)
+		     ;; `articles' is either a list of article numbers
+		     ;; or a list of article IDs.
+		     article))
+	(incf count)
+	;; Every 400 requests we have to read the stream in
+	;; order to avoid deadlocks.
+	(when (or (null articles)	;All requests have been sent.
+		  (zerop (% count nntp-maximum-request)))
+	  (nntp-accept-response)
+	  (while (progn
+		   (progn
+		     (set-buffer buf)
+		     (goto-char last-point))
+		   ;; Count replies.
+		   (while (nntp-next-result-arrived-p)
+		     (aset map received (cons (aref map received) (point)))
+		     (incf received))
+		   (setq last-point (point))
+		   (< received count))
+	    ;; If number of headers is greater than 100, give
+	    ;;  informative messages.
+	    (and (numberp nntp-large-newsgroup)
+		 (> number nntp-large-newsgroup)
+		 (zerop (% received 20))
+		 (nnheader-message 6 "NNTP: Receiving articles... %d%%"
+				   (/ (* received 100) number)))
+	    (nntp-accept-response))))
+      (and (numberp nntp-large-newsgroup)
+	   (> number nntp-large-newsgroup)
+	   (nnheader-message 6 "NNTP: Receiving headers...done"))
+
+      ;; Now we have all the responses.  We go through the results,
+      ;; washes it and copies it over to the server buffer.
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)
+      (mapcar
+       (lambda (entry)
+	 (narrow-to-region
+	  (setq point (goto-char (point-max)))
+	  (progn
+	    (insert-buffer-substring buf last-point (cdr entry))
+	    (point-max)))
+	 (nntp-decode-text)
+	 (widen)
+	 (cons (car entry) point))
+       map))))
+
+(defun nntp-next-result-arrived-p ()
+  (let ((point (point)))
+    (cond
+     ((looking-at "2")
+      (if (re-search-forward "\n.\r?\n" nil t)
+	  t
+	(goto-char point)
+	nil))
+     ((looking-at "[34]")
+      (forward-line 1)
+      t)
+     (t
+      nil))))
+
+(defun nntp-try-list-active (group)
+  (nntp-list-active-group group)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (goto-char (point-min))
+    (cond ((or (eobp)
+	       (looking-at "5[0-9]+"))
+	   (setq nntp-server-list-active-group nil))
+	  (t
+	   (setq nntp-server-list-active-group t)))))
+
+(deffoo nntp-list-active-group (group &optional server)
+  "Return the active info on GROUP (which can be a regexp."
+  (nntp-possibly-change-group nil server)
+  (nntp-send-command "^.*\r?\n" "LIST ACTIVE" group))
+
+(deffoo nntp-request-article (article &optional group server buffer command)
+  (nntp-possibly-change-group group server)
+  (when (nntp-send-command-and-decode
+	 "\r?\n\\.\r?\n" "ARTICLE"
+	 (if (numberp article) (int-to-string article) article))
+    (when (and buffer
+	       (not (equal buffer nntp-server-buffer)))
+      (save-excursion
+	(set-buffer nntp-server-buffer)
+	(copy-to-buffer buffer (point-min) (point-max))
+	(nntp-find-group-and-number)))
+    (nntp-find-group-and-number)))
+
+(deffoo nntp-request-head (article &optional group server)
+  (nntp-possibly-change-group group server)
+  (when (nntp-send-command-and-decode
+	 "\r?\n\\.\r?\n" "HEAD"
+	 (if (numberp article) (int-to-string article) article))
+    (nntp-find-group-and-number)))
+
+(deffoo nntp-request-body (article &optional group server)
+  (nntp-possibly-change-group group server)
+  (nntp-send-command-and-decode
+   "\r?\n\\.\r?\n" "BODY"
+   (if (numberp article) (int-to-string article) article)))
+
+(deffoo nntp-request-group (group &optional server dont-check)
+  (nntp-possibly-change-group nil server)
+  (when (nntp-send-command "^2.*\n" "GROUP" group)
+    (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
+      (setcar (cddr entry) group))))
+
+(deffoo nntp-close-group (group &optional server)
+  t)
+
+(deffoo nntp-server-opened (&optional server)
+  "Say whether a connection to SERVER has been opened."
+  (and (nnoo-current-server-p 'nntp server)
+       nntp-server-buffer
+       (gnus-buffer-live-p nntp-server-buffer)
+       (nntp-find-connection nntp-server-buffer)))
+
+(deffoo nntp-open-server (server &optional defs connectionless)
+  (nnheader-init-server-buffer)
+  (if (nntp-server-opened server)
+      t
+    (when (or (stringp (car defs))
+	      (numberp (car defs)))
+      (setq defs (cons (list 'nntp-port-number (car defs)) (cdr defs))))
+    (unless (assq 'nntp-address defs)
+      (setq defs (append defs (list (list 'nntp-address server)))))
+    (nnoo-change-server 'nntp server defs)
+    (unless connectionless
+      (or (nntp-find-connection nntp-server-buffer)
+	  (nntp-open-connection nntp-server-buffer)))))
+
+(deffoo nntp-close-server (&optional server)
+  (nntp-possibly-change-group nil server t)
+  (let (process)
+    (while (setq process (car (pop nntp-connection-alist)))
+      (when (memq (process-status process) '(open run))
+	(set-process-sentinel process nil)
+	(nntp-send-string process "QUIT"))
+      (when (buffer-name (process-buffer process))
+	(kill-buffer (process-buffer process))))
+    (nnoo-close-server 'nntp)))
+
+(deffoo nntp-request-close ()
+  (let (process)
+    (while (setq process (pop nntp-connection-list))
+      (when (memq (process-status process) '(open run))
+	(set-process-sentinel process nil)
+	(ignore-errors
+	  (nntp-send-string process "QUIT")))
+      (when (buffer-name (process-buffer process))
+	(kill-buffer (process-buffer process))))))
+
+(deffoo nntp-request-list (&optional server)
+  (nntp-possibly-change-group nil server)
+  (nntp-send-command-and-decode "\r?\n\\.\r?\n" "LIST"))
+
+(deffoo nntp-request-list-newsgroups (&optional server)
+  (nntp-possibly-change-group nil server)
+  (nntp-send-command "\r?\n\\.\r?\n" "LIST NEWSGROUPS"))
+
+(deffoo nntp-request-newgroups (date &optional server)
+  (nntp-possibly-change-group nil server)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (let* ((date (timezone-parse-date date))
+	   (time-string
+	    (format "%s%02d%02d %s%s%s"
+		    (substring (aref date 0) 2) (string-to-int (aref date 1))
+		    (string-to-int (aref date 2)) (substring (aref date 3) 0 2)
+		    (substring
+		     (aref date 3) 3 5) (substring (aref date 3) 6 8))))
+      (prog1
+	  (nntp-send-command "^\\.\r?\n" "NEWGROUPS" time-string)
+	(nntp-decode-text)))))
+
+(deffoo nntp-request-post (&optional server)
+  (nntp-possibly-change-group nil server)
+  (when (nntp-send-command "^[23].*\r?\n" "POST")
+    (nntp-send-buffer "^[23].*\n")))
+
+(deffoo nntp-request-type (group article)
+  'news)
+
+(deffoo nntp-asynchronous-p ()
+  t)
+
+;;; Hooky functions.
+
+(defun nntp-send-mode-reader ()
+  "Send the MODE READER command to the nntp server.
+This function is supposed to be called from `nntp-server-opened-hook'.
+It will make innd servers spawn an nnrpd process to allow actual article
+reading."
+  (nntp-send-command "^.*\r?\n" "MODE READER"))
+
+(defun nntp-send-nosy-authinfo ()
+  "Send the AUTHINFO to the nntp server.
+This function is supposed to be called from `nntp-server-opened-hook'.
+It will prompt for a password."
+  (nntp-send-command
+   "^.*\r?\n" "AUTHINFO USER"
+   (read-string (format "NNTP (%s) user name: " nntp-address)))
+  (nntp-send-command
+   "^.*\r?\n" "AUTHINFO PASS"
+   (nnmail-read-passwd "NNTP (%s) password: " nntp-address)))
+
+(defun nntp-send-authinfo ()
+  "Send the AUTHINFO to the nntp server.
+This function is supposed to be called from `nntp-server-opened-hook'.
+It will prompt for a password."
+  (nntp-send-command "^.*\r?\n" "AUTHINFO USER" (user-login-name))
+  (nntp-send-command
+   "^.*\r?\n" "AUTHINFO PASS"
+   (nnmail-read-passwd (format "NNTP (%s) password: " nntp-address))))
+
+(defun nntp-send-authinfo-from-file ()
+  "Send the AUTHINFO to the nntp server.
+This function is supposed to be called from `nntp-server-opened-hook'."
+  (when (file-exists-p "~/.nntp-authinfo")
+    (nnheader-temp-write nil
+      (insert-file-contents "~/.nntp-authinfo")
+      (goto-char (point-min))
+      (nntp-send-command "^.*\r?\n" "AUTHINFO USER" (user-login-name))
+      (nntp-send-command
+       "^.*\r?\n" "AUTHINFO PASS"
+       (buffer-substring (point) (progn (end-of-line) (point)))))))
+
+;;; Internal functions.
+
+(defun nntp-make-process-buffer (buffer)
+  "Create a new, fresh buffer usable for nntp process connections."
+  (save-excursion
+    (set-buffer
+     (generate-new-buffer
+      (format " *server %s %s %s*"
+	      nntp-address nntp-port-number
+	      (buffer-name (get-buffer buffer)))))
+    (buffer-disable-undo (current-buffer))
+    (set (make-local-variable 'after-change-functions) nil)
+    (set (make-local-variable 'nntp-process-wait-for) nil)
+    (set (make-local-variable 'nntp-process-callback) nil)
+    (set (make-local-variable 'nntp-process-to-buffer) nil)
+    (set (make-local-variable 'nntp-process-start-point) nil)
+    (set (make-local-variable 'nntp-process-decode) nil)
+    (current-buffer)))
+
+(defun nntp-open-connection (buffer)
+  "Open a connection to PORT on ADDRESS delivering output to BUFFER."
+  (run-hooks 'nntp-prepare-server-hook)
+  (let* ((pbuffer (nntp-make-process-buffer buffer))
+	 (process
+	  (condition-case ()
+	      (funcall nntp-open-connection-function pbuffer)
+	    (error nil)
+	    (quit nil))))
+    (when process
+      (process-kill-without-query process)
+      (nntp-wait-for process "^.*\n" buffer nil t)
+      (if (memq (process-status process) '(open run))
+	  (prog1
+	      (caar (push (list process buffer nil) nntp-connection-alist))
+	    (push process nntp-connection-list)
+	    (save-excursion
+	      (set-buffer pbuffer)
+	      (nntp-read-server-type)
+	      (erase-buffer)
+	      (set-buffer nntp-server-buffer)
+	      (let ((nnheader-callback-function nil))
+		(run-hooks 'nntp-server-opened-hook))))
+	(when (buffer-name (process-buffer process))
+	  (kill-buffer (process-buffer process)))
+	nil))))
+
+(defun nntp-open-network-stream (buffer)
+  (open-network-stream "nntpd" buffer nntp-address nntp-port-number))
+
+(defun nntp-read-server-type ()
+  "Find out what the name of the server we have connected to is."
+  ;; Wait for the status string to arrive.
+  (setq nntp-server-type (buffer-string))
+  (let ((alist nntp-server-action-alist)
+	(case-fold-search t)
+	entry)
+    ;; Run server-specific commands.
+    (while alist
+      (setq entry (pop alist))
+      (when (string-match (car entry) nntp-server-type)
+	(if (and (listp (cadr entry))
+		 (not (eq 'lambda (caadr entry))))
+	    (eval (cadr entry))
+	  (funcall (cadr entry)))))))
+
+(defun nntp-after-change-function-callback (beg end len)
+  (when nntp-process-callback
+    (save-match-data
+      (if (and (= beg (point-min))
+	       (memq (char-after beg) '(?4 ?5)))
+	  ;; Report back error messages.
+	  (save-excursion
+	    (goto-char beg)
+	    (if (looking-at "480")
+		(funcall nntp-authinfo-function)
+	      (nntp-snarf-error-message)
+	      (funcall nntp-process-callback nil)))
+	(goto-char end)
+	(when (and (> (point) nntp-process-start-point)
+		   (re-search-backward nntp-process-wait-for
+				       nntp-process-start-point t))
+	  (when (buffer-name (get-buffer nntp-process-to-buffer))
+	    (let ((cur (current-buffer))
+		  (start nntp-process-start-point))
+	      (save-excursion
+		(set-buffer (get-buffer nntp-process-to-buffer))
+		(goto-char (point-max))
+		(let ((b (point)))
+		  (insert-buffer-substring cur start)
+		  (narrow-to-region b (point-max))
+		  (nntp-decode-text)
+		  (widen)))))
+	  (goto-char end)
+	  (let ((callback nntp-process-callback)
+		(nntp-inside-change-function t))
+	    (setq nntp-process-callback nil)
+	    (save-excursion
+	      (funcall callback (buffer-name
+				 (get-buffer nntp-process-to-buffer))))))))))
+
+(defun nntp-snarf-error-message ()
+  "Save the error message in the current buffer."
+  (let ((message (buffer-string)))
+    (while (string-match "[\r\n]+" message)
+      (setq message (replace-match " " t t message)))
+    (nnheader-report 'nntp message)
+    message))
+
+(defun nntp-accept-process-output (process)
+  "Wait for output from PROCESS and message some dots."
+  (save-excursion
+    (set-buffer (or (nntp-find-connection-buffer nntp-server-buffer)
+		    nntp-server-buffer))
+    (let ((len (/ (point-max) 1024))
+	  message-log-max)
+      (unless (< len 10)
+	(setq nntp-have-messaged t)
+	(nnheader-message 7 "nntp read: %dk" len)))
+    (accept-process-output process 1)))
+
+(defun nntp-accept-response ()
+  "Wait for output from the process that outputs to BUFFER."
+  (nntp-accept-process-output (nntp-find-connection nntp-server-buffer)))
+
+(defun nntp-possibly-change-group (group server &optional connectionless)
+  (let ((nnheader-callback-function nil))
+    (when server
+      (or (nntp-server-opened server)
+	  (nntp-open-server server nil connectionless)))
+
+    (unless connectionless
+      (or (nntp-find-connection nntp-server-buffer)
+	  (nntp-open-connection nntp-server-buffer))))
+
+  (when group
+    (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
+      (when (not (equal group (caddr entry)))
+	(save-excursion
+	  (set-buffer (process-buffer (car entry)))
+	  (erase-buffer)
+	  (nntp-send-string (car entry) (concat "GROUP " group))
+	  (nntp-wait-for-string "^2.*\n")
+	  (setcar (cddr entry) group)
+	  (erase-buffer))))))
+
+(defun nntp-decode-text (&optional cr-only)
+  "Decode the text in the current buffer."
+  (goto-char (point-min))
+  (while (search-forward "\r" nil t)
+    (delete-char -1))
+  (unless cr-only
+    ;; Remove trailing ".\n" end-of-transfer marker.
+    (goto-char (point-max))
+    (forward-line -1)
+    (when (looking-at ".\n")
+      (delete-char 2))
+    ;; Delete status line.
+    (goto-char (point-min))
+    (delete-region (point) (progn (forward-line 1) (point)))
+    ;; Remove "." -> ".." encoding.
+    (while (search-forward "\n.." nil t)
+      (delete-char -1))))
+
+(defun nntp-encode-text ()
+  "Encode the text in the current buffer."
+  (save-excursion
+    ;; Replace "." at beginning of line with "..".
+    (goto-char (point-min))
+    (while (re-search-forward "^\\." nil t)
+      (insert "."))
+    (goto-char (point-max))
+    ;; Insert newline at the end of the buffer.
+    (unless (bolp)
+      (insert "\n"))
+    ;; Insert `.' at end of buffer (end of text mark).
+    (goto-char (point-max))
+    (insert "." nntp-end-of-line)))
+
+(defun nntp-retrieve-headers-with-xover (articles &optional fetch-old)
+  (set-buffer nntp-server-buffer)
+  (erase-buffer)
+  (cond
+
+   ;; This server does not talk NOV.
+   ((not nntp-server-xover)
+    nil)
+
+   ;; We don't care about gaps.
+   ((or (not nntp-nov-gap)
+	fetch-old)
+    (nntp-send-xover-command
+     (if fetch-old
+	 (if (numberp fetch-old)
+	     (max 1 (- (car articles) fetch-old))
+	   1)
+       (car articles))
+     (car (last articles)) 'wait)
+
+    (goto-char (point-min))
+    (when (looking-at "[1-5][0-9][0-9] ")
+      (delete-region (point) (progn (forward-line 1) (point))))
+    (while (search-forward "\r" nil t)
+      (replace-match "" t t))
+    (goto-char (point-max))
+    (forward-line -1)
+    (when (looking-at "\\.")
+      (delete-region (point) (progn (forward-line 1) (point)))))
+
+   ;; We do it the hard way.  For each gap, an XOVER command is sent
+   ;; to the server.  We do not wait for a reply from the server, we
+   ;; just send them off as fast as we can.  That means that we have
+   ;; to count the number of responses we get back to find out when we
+   ;; have gotten all we asked for.
+   ((numberp nntp-nov-gap)
+    (let ((count 0)
+	  (received 0)
+	  (last-point (point-min))
+	  (buf nntp-server-buffer)
+	  ;;(process-buffer (nntp-find-connection (current-buffer))))
+	  first)
+      ;; We have to check `nntp-server-xover'.  If it gets set to nil,
+      ;; that means that the server does not understand XOVER, but we
+      ;; won't know that until we try.
+      (while (and nntp-server-xover articles)
+	(setq first (car articles))
+	;; Search forward until we find a gap, or until we run out of
+	;; articles.
+	(while (and (cdr articles)
+		    (< (- (nth 1 articles) (car articles)) nntp-nov-gap))
+	  (setq articles (cdr articles)))
+
+	(when (nntp-send-xover-command first (car articles))
+	  (setq articles (cdr articles)
+		count (1+ count))
+
+	  ;; Every 400 requests we have to read the stream in
+	  ;; order to avoid deadlocks.
+	  (when (or (null articles)	;All requests have been sent.
+		    (zerop (% count nntp-maximum-request)))
+	    (accept-process-output)
+	    ;; On some Emacs versions the preceding function has
+	    ;; a tendency to change the buffer.  Perhaps.  It's
+	    ;; quite difficult to reproduce, because it only
+	    ;; seems to happen once in a blue moon.
+	    (set-buffer buf)
+	    (while (progn
+		     (goto-char last-point)
+		     ;; Count replies.
+		     (while (re-search-forward "^[0-9][0-9][0-9] " nil t)
+		       (setq received (1+ received)))
+		     (setq last-point (point))
+		     (< received count))
+	      (accept-process-output)
+	      (set-buffer buf)))))
+
+      (when nntp-server-xover
+	;; Wait for the reply from the final command.
+	(goto-char (point-max))
+	(re-search-backward "^[0-9][0-9][0-9] " nil t)
+	(when (looking-at "^[23]")
+	  (while (progn
+		   (goto-char (point-max))
+		   (forward-line -1)
+		   (not (looking-at "^\\.\r?\n")))
+	    (nntp-accept-response)))
+
+	;; We remove any "." lines and status lines.
+	(goto-char (point-min))
+	(while (search-forward "\r" nil t)
+	  (delete-char -1))
+	(goto-char (point-min))
+	(delete-matching-lines "^\\.$\\|^[1-5][0-9][0-9] ")
+	;;(copy-to-buffer nntp-server-buffer (point-min) (point-max))
+	t))))
+
+  nntp-server-xover)
+
+(defun nntp-send-xover-command (beg end &optional wait-for-reply)
+  "Send the XOVER command to the server."
+  (let ((range (format "%d-%d" beg end))
+	(nntp-inhibit-erase t))
+    (if (stringp nntp-server-xover)
+	;; If `nntp-server-xover' is a string, then we just send this
+	;; command.
+	(if wait-for-reply
+	    (nntp-send-command-nodelete
+	     "\r?\n\\.\r?\n" nntp-server-xover range)
+	  ;; We do not wait for the reply.
+	  (nntp-send-command-nodelete "\r?\n\\.\r?\n" nntp-server-xover range))
+      (let ((commands nntp-xover-commands))
+	;; `nntp-xover-commands' is a list of possible XOVER commands.
+	;; We try them all until we get at positive response.
+	(while (and commands (eq nntp-server-xover 'try))
+	  (nntp-send-command-nodelete "\r?\n\\.\r?\n" (car commands) range)
+	  (save-excursion
+	    (set-buffer nntp-server-buffer)
+	    (goto-char (point-min))
+	    (and (looking-at "[23]")	; No error message.
+		 ;; We also have to look at the lines.  Some buggy
+		 ;; servers give back simple lines with just the
+		 ;; article number.  How... helpful.
+		 (progn
+		   (forward-line 1)
+		   (looking-at "[0-9]+\t...")) ; More text after number.
+		 (setq nntp-server-xover (car commands))))
+	  (setq commands (cdr commands)))
+	;; If none of the commands worked, we disable XOVER.
+	(when (eq nntp-server-xover 'try)
+	  (save-excursion
+	    (set-buffer nntp-server-buffer)
+	    (erase-buffer)
+	    (setq nntp-server-xover nil)))
+	nntp-server-xover))))
+
+;;; Alternative connection methods.
+
+(defun nntp-wait-for-string (regexp)
+  "Wait until string arrives in the buffer."
+  (let ((buf (current-buffer)))
+    (goto-char (point-min))
+    (while (not (re-search-forward regexp nil t))
+      (accept-process-output (nntp-find-connection nntp-server-buffer))
+      (set-buffer buf)
+      (goto-char (point-min)))))
+
+(defun nntp-open-telnet (buffer)
+  (save-excursion
+    (set-buffer buffer)
+    (erase-buffer)
+    (let ((proc (start-process
+		 "nntpd" buffer "telnet" "-8"))
+	  (case-fold-search t))
+      (when (memq (process-status proc) '(open run))
+	(process-send-string proc "set escape \^X\n")
+	(process-send-string proc (concat "open " nntp-address "\n"))
+	(nntp-wait-for-string "^\r*.?login:")
+	(process-send-string
+	 proc (concat
+	       (or nntp-telnet-user-name
+		   (setq nntp-telnet-user-name (read-string "login: ")))
+	       "\n"))
+	(nntp-wait-for-string "^\r*.?password:")
+	(process-send-string
+	 proc (concat
+	       (or nntp-telnet-passwd
+		   (setq nntp-telnet-passwd
+			 (nnmail-read-passwd "Password: ")))
+	       "\n"))
+	(erase-buffer)
+	(nntp-wait-for-string "bash\\|\$ *\r?$\\|> *\r?")
+	(process-send-string
+	 proc (concat (mapconcat 'identity nntp-telnet-parameters " ") "\n"))
+	(nntp-wait-for-string "^\r*200")
+	(beginning-of-line)
+	(delete-region (point-min) (point))
+	(process-send-string proc "\^]")
+	(nntp-wait-for-string "^telnet")
+	(process-send-string proc "mode character\n")
+	(accept-process-output proc 1)
+	(sit-for 1)
+	(goto-char (point-min))
+	(forward-line 1)
+	(delete-region (point) (point-max)))
+      proc)))
+
+(defun nntp-open-rlogin (buffer)
+  "Open a connection to SERVER using rsh."
+  (let ((proc (if nntp-rlogin-user-name
+		  (start-process
+		   "nntpd" buffer "rsh"
+		   nntp-address "-l" nntp-rlogin-user-name
+		   (mapconcat 'identity
+			      nntp-rlogin-parameters " "))
+		(start-process
+		 "nntpd" buffer "rsh" nntp-address
+		 (mapconcat 'identity
+			    nntp-rlogin-parameters " ")))))
+    (set-buffer buffer)
+    (nntp-wait-for-string "^\r*200")
+    (beginning-of-line)
+    (delete-region (point-min) (point))
+    proc))
+
+(defun nntp-find-group-and-number ()
+  (save-excursion
+    (save-restriction
+      (set-buffer nntp-server-buffer)
+      (narrow-to-region (goto-char (point-min))
+			(or (search-forward "\n\n" nil t) (point-max)))
+      (goto-char (point-min))
+      ;; We first find the number by looking at the status line.
+      (let ((number (and (looking-at "2[0-9][0-9] +\\([0-9]+\\) ")
+			 (string-to-int
+			  (buffer-substring (match-beginning 1)
+					    (match-end 1)))))
+	    group newsgroups xref)
+	(and number (zerop number) (setq number nil))
+	;; Then we find the group name.
+	(setq group
+	      (cond
+	       ;; If there is only one group in the Newsgroups header,
+	       ;; then it seems quite likely that this article comes
+	       ;; from that group, I'd say.
+	       ((and (setq newsgroups (mail-fetch-field "newsgroups"))
+		     (not (string-match "," newsgroups)))
+		newsgroups)
+	       ;; If there is more than one group in the Newsgroups
+	       ;; header, then the Xref header should be filled out.
+	       ;; We hazard a guess that the group that has this
+	       ;; article number in the Xref header is the one we are
+	       ;; looking for.  This might very well be wrong if this
+	       ;; article happens to have the same number in several
+	       ;; groups, but that's life.
+	       ((and (setq xref (mail-fetch-field "xref"))
+		     number
+		     (string-match (format "\\([^ :]+\\):%d" number) xref))
+		(substring xref (match-beginning 1) (match-end 1)))
+	       (t "")))
+	(when (string-match "\r" group)
+	  (setq group (substring group 0 (match-beginning 0))))
+	(cons group number)))))
+
+(provide 'nntp)
+
+;;; nntp.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnvirtual.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,766 @@
+;;; nnvirtual.el --- virtual newsgroups access for Gnus
+;; Copyright (C) 1994,95,96,97 Free Software Foundation, Inc.
+
+;; Author: David Moore <dmoore@ucsd.edu>
+;;	Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; 	Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; The other access methods (nntp, nnspool, etc) are general news
+;; access methods.  This module relies on Gnus and can not be used
+;; separately.
+
+;;; Code:
+
+(require 'nntp)
+(require 'nnheader)
+(require 'gnus)
+(require 'nnoo)
+(require 'gnus-util)
+(require 'gnus-start)
+(require 'gnus-sum)
+(eval-when-compile (require 'cl))
+
+(nnoo-declare nnvirtual)
+
+(defvoo nnvirtual-always-rescan nil
+  "*If non-nil, always scan groups for unread articles when entering a group.
+If this variable is nil (which is the default) and you read articles
+in a component group after the virtual group has been activated, the
+read articles from the component group will show up when you enter the
+virtual group.")
+
+(defvoo nnvirtual-component-regexp nil
+  "*Regexp to match component groups.")
+
+(defvoo nnvirtual-component-groups nil
+  "Component group in this nnvirtual group.")
+
+
+
+(defconst nnvirtual-version "nnvirtual 1.1")
+
+(defvoo nnvirtual-current-group nil)
+
+(defvoo nnvirtual-mapping-table nil
+  "Table of rules on how to map between component group and article number
+to virtual article number.")
+
+(defvoo nnvirtual-mapping-offsets nil
+  "Table indexed by component group to an offset to be applied to article numbers in that group.")
+
+(defvoo nnvirtual-mapping-len 0
+  "Number of articles in this virtual group.")
+
+(defvoo nnvirtual-mapping-reads nil
+  "Compressed sequence of read articles on the virtual group as computed from the unread status of individual component groups.")
+
+(defvoo nnvirtual-mapping-marks nil
+  "Compressed marks alist for the virtual group as computed from the marks of individual component groups.")
+
+(defvoo nnvirtual-info-installed nil
+  "T if we have already installed the group info for this group, and shouldn't blast over it again.")
+
+(defvoo nnvirtual-status-string "")
+
+(eval-and-compile
+  (autoload 'gnus-cache-articles-in-group "gnus-cache"))
+
+
+
+;;; Interface functions.
+
+(nnoo-define-basics nnvirtual)
+
+
+(deffoo nnvirtual-retrieve-headers (articles &optional newsgroup
+					     server fetch-old)
+  (when (nnvirtual-possibly-change-server server)
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)
+      (if (stringp (car articles))
+	  'headers
+	(let ((vbuf (nnheader-set-temp-buffer
+		     (get-buffer-create " *virtual headers*")))
+	      (carticles (nnvirtual-partition-sequence articles))
+	      (system-name (system-name))
+	      cgroup carticle article result prefix)
+	  (while carticles
+	    (setq cgroup (caar carticles))
+	    (setq articles (cdar carticles))
+	    (pop carticles)
+	    (when (and articles
+		       (gnus-check-server
+			(gnus-find-method-for-group cgroup) t)
+		       (gnus-request-group cgroup t)
+		       (setq prefix (gnus-group-real-prefix cgroup))
+		       ;; FIX FIX FIX we want to check the cache!
+		       ;; This is probably evil if people have set
+		       ;; gnus-use-cache to nil themselves, but I
+		       ;; have no way of finding the true value of it.
+		       (let ((gnus-use-cache t))
+			 (setq result (gnus-retrieve-headers
+				       articles cgroup nil))))
+	    (set-buffer nntp-server-buffer)
+	    ;; If we got HEAD headers, we convert them into NOV
+	    ;; headers.  This is slow, inefficient and, come to think
+	    ;; of it, downright evil.  So sue me.  I couldn't be
+	    ;; bothered to write a header parse routine that could
+	    ;; parse a mixed HEAD/NOV buffer.
+	    (when (eq result 'headers)
+	      (nnvirtual-convert-headers))
+	    (goto-char (point-min))
+	    (while (not (eobp))
+	      (delete-region (point)
+			     (progn
+			       (setq carticle (read nntp-server-buffer))
+			       (point)))
+
+	      ;; We remove this article from the articles list, if
+	      ;; anything is left in the articles list after going through
+	      ;; the entire buffer, then those articles have been
+	      ;; expired or canceled, so we appropriately update the
+	      ;; component group below.  They should be coming up
+	      ;; generally in order, so this shouldn't be slow.
+	      (setq articles (delq carticle articles))
+
+	      (setq article (nnvirtual-reverse-map-article cgroup carticle))
+	      (if (null article)
+		  ;; This line has no reverse mapping, that means it
+		  ;; was an extra article reference returned by nntp.
+		  (progn
+		    (beginning-of-line)
+		    (delete-region (point) (progn (forward-line 1) (point))))
+		;; Otherwise insert the virtual article number,
+		;; and clean up the xrefs.
+		(princ article nntp-server-buffer)
+		(nnvirtual-update-xref-header cgroup carticle
+					      prefix system-name)
+		(forward-line 1))
+	      )
+
+	    (set-buffer vbuf)
+	    (goto-char (point-max))
+	    (insert-buffer-substring nntp-server-buffer))
+	    ;; Anything left in articles is expired or canceled.
+	    ;; Could be smart and not tell it about articles already known?
+	    (when articles
+	      (gnus-group-make-articles-read cgroup articles))
+	    )
+
+	  ;; The headers are ready for reading, so they are inserted into
+	  ;; the nntp-server-buffer, which is where Gnus expects to find
+	  ;; them.
+	  (prog1
+	      (save-excursion
+		(set-buffer nntp-server-buffer)
+		(erase-buffer)
+		(insert-buffer-substring vbuf)
+		;; FIX FIX FIX, we should be able to sort faster than
+		;; this if needed, since each cgroup is sorted, we just
+		;; need to merge
+		(sort-numeric-fields 1 (point-min) (point-max))
+		'nov)
+	    (kill-buffer vbuf)))))))
+
+
+(defvoo nnvirtual-last-accessed-component-group nil)
+
+(deffoo nnvirtual-request-article (article &optional group server buffer)
+  (when (nnvirtual-possibly-change-server server)
+    (if (stringp article)
+	;; This is a fetch by Message-ID.
+	(cond
+	 ((not nnvirtual-last-accessed-component-group)
+	  (nnheader-report
+	   'nnvirtual "Don't know what server to request from"))
+	 (t
+	  (save-excursion
+	    (when buffer
+	      (set-buffer buffer))
+	    (let ((method (gnus-find-method-for-group
+			   nnvirtual-last-accessed-component-group)))
+	      (funcall (gnus-get-function method 'request-article)
+		       article nil (nth 1 method) buffer)))))
+      ;; This is a fetch by number.
+      (let* ((amap (nnvirtual-map-article article))
+	     (cgroup (car amap)))
+	(cond
+	 ((not amap)
+	  (nnheader-report 'nnvirtual "No such article: %s" article))
+	 ((not (gnus-check-group cgroup))
+	  (nnheader-report
+	   'nnvirtual "Can't open server where %s exists" cgroup))
+	 ((not (gnus-request-group cgroup t))
+	  (nnheader-report 'nnvirtual "Can't open component group %s" cgroup))
+	 (t
+	  (setq nnvirtual-last-accessed-component-group cgroup)
+	  (if buffer
+	      (save-excursion
+		(set-buffer buffer)
+		(gnus-request-article-this-buffer (cdr amap) cgroup))
+	    (gnus-request-article (cdr amap) cgroup))))))))
+
+
+(deffoo nnvirtual-open-server (server &optional defs)
+  (unless (assq 'nnvirtual-component-regexp defs)
+    (push `(nnvirtual-component-regexp ,server)
+	  defs))
+  (nnoo-change-server 'nnvirtual server defs)
+  (if nnvirtual-component-groups
+      t
+    (setq nnvirtual-mapping-table nil
+	  nnvirtual-mapping-offsets nil
+	  nnvirtual-mapping-len 0
+	  nnvirtual-mapping-reads nil
+	  nnvirtual-mapping-marks nil
+	  nnvirtual-info-installed nil)
+    (when nnvirtual-component-regexp
+      ;; Go through the newsrc alist and find all component groups.
+      (let ((newsrc (cdr gnus-newsrc-alist))
+	    group)
+	(while (setq group (car (pop newsrc)))
+	  (when (string-match nnvirtual-component-regexp group) ; Match
+	    ;; Add this group to the list of component groups.
+	    (setq nnvirtual-component-groups
+		  (cons group (delete group nnvirtual-component-groups)))))))
+    (if (not nnvirtual-component-groups)
+	(nnheader-report 'nnvirtual "No component groups: %s" server)
+      t)))
+
+
+(deffoo nnvirtual-request-group (group &optional server dont-check)
+  (nnvirtual-possibly-change-server server)
+  (setq nnvirtual-component-groups
+	(delete (nnvirtual-current-group) nnvirtual-component-groups))
+  (cond
+   ((null nnvirtual-component-groups)
+    (setq nnvirtual-current-group nil)
+    (nnheader-report 'nnvirtual "No component groups in %s" group))
+   (t
+    (when (or (not dont-check)
+	      nnvirtual-always-rescan)
+      (nnvirtual-create-mapping))
+    (setq nnvirtual-current-group group)
+    (nnheader-insert "211 %d 1 %d %s\n"
+		     nnvirtual-mapping-len nnvirtual-mapping-len group))))
+
+
+(deffoo nnvirtual-request-type (group &optional article)
+  (if (not article)
+      'unknown
+    (let ((mart (nnvirtual-map-article article)))
+      (when mart
+	(gnus-request-type (car mart) (cdr mart))))))
+
+(deffoo nnvirtual-request-update-mark (group article mark)
+  (let* ((nart (nnvirtual-map-article article))
+	 (cgroup (car nart))
+	 ;; The component group might be a virtual group.
+	 (nmark (gnus-request-update-mark cgroup (cdr nart) mark)))
+    (when (and nart
+	       (= mark nmark)
+	       (gnus-group-auto-expirable-p cgroup))
+      (setq mark gnus-expirable-mark)))
+  mark)
+
+
+(deffoo nnvirtual-close-group (group &optional server)
+  (when (and (nnvirtual-possibly-change-server server)
+	     (not (gnus-ephemeral-group-p (nnvirtual-current-group))))
+    (nnvirtual-update-read-and-marked t t))
+  t)
+
+
+(deffoo nnvirtual-request-list (&optional server)
+  (nnheader-report 'nnvirtual "LIST is not implemented."))
+
+
+(deffoo nnvirtual-request-newgroups (date &optional server)
+  (nnheader-report 'nnvirtual "NEWGROUPS is not supported."))
+
+
+(deffoo nnvirtual-request-list-newsgroups (&optional server)
+  (nnheader-report 'nnvirtual "LIST NEWSGROUPS is not implemented."))
+
+
+(deffoo nnvirtual-request-update-info (group info &optional server)
+  (when (and (nnvirtual-possibly-change-server server)
+	     (not nnvirtual-info-installed))
+    ;; Install the precomputed lists atomically, so the virtual group
+    ;; is not left in a half-way state in case of C-g.
+    (gnus-atomic-progn
+      (setcar (cddr info) nnvirtual-mapping-reads)
+      (if (nthcdr 3 info)
+	  (setcar (nthcdr 3 info) nnvirtual-mapping-marks)
+	(when nnvirtual-mapping-marks
+	  (setcdr (nthcdr 2 info) (list nnvirtual-mapping-marks))))
+      (setq nnvirtual-info-installed t))
+    t))
+
+
+(deffoo nnvirtual-catchup-group (group &optional server all)
+  (when (and (nnvirtual-possibly-change-server server)
+	     (not (gnus-ephemeral-group-p (nnvirtual-current-group))))
+    ;; copy over existing marks first, in case they set anything
+    (nnvirtual-update-read-and-marked nil nil)
+    ;; do a catchup on all component groups
+    (let ((gnus-group-marked (copy-sequence nnvirtual-component-groups))
+	  (gnus-expert-user t))
+      ;; Make sure all groups are activated.
+      (mapcar
+       (lambda (g)
+	 (when (not (numberp (car (gnus-gethash g gnus-newsrc-hashtb))))
+	   (gnus-activate-group g)))
+       nnvirtual-component-groups)
+      (save-excursion
+	(set-buffer gnus-group-buffer)
+	(gnus-group-catchup-current nil all)))))
+
+
+(deffoo nnvirtual-find-group-art (group article)
+  "Return the real group and article for virtual GROUP and ARTICLE."
+  (nnvirtual-map-article article))
+
+
+;;; Internal functions.
+
+(defun nnvirtual-convert-headers ()
+  "Convert HEAD headers into NOV headers."
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (let* ((dependencies (make-vector 100 0))
+	   (headers (gnus-get-newsgroup-headers dependencies))
+	   header)
+      (erase-buffer)
+      (while (setq header (pop headers))
+	(nnheader-insert-nov header)))))
+
+
+(defun nnvirtual-update-xref-header (group article prefix system-name)
+  "Edit current NOV header in current buffer to have an xref to the component group, and also server prefix any existing xref lines."
+  ;; Move to beginning of Xref field, creating a slot if needed.
+  (beginning-of-line)
+  (looking-at
+   "[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t[^\t]*\t")
+  (goto-char (match-end 0))
+  (unless (search-forward "\t" (gnus-point-at-eol) 'move)
+    (insert "\t"))
+
+  ;; Remove any spaces at the beginning of the Xref field.
+  (while (= (char-after (1- (point))) ? )
+    (forward-char -1)
+    (delete-char 1))
+
+  (insert "Xref: " system-name " " group ":")
+  (princ article (current-buffer))
+
+  ;; If there were existing xref lines, clean them up to have the correct
+  ;; component server prefix.
+  (let ((xref-end (save-excursion
+		    (search-forward "\t" (gnus-point-at-eol) 'move)
+		    (point)))
+	(len (length prefix)))
+    (unless (= (point) xref-end)
+      (insert " ")
+      (when (not (string= "" prefix))
+	(while (re-search-forward "[^ ]+:[0-9]+" xref-end t)
+	  (save-excursion
+	    (goto-char (match-beginning 0))
+	    (insert prefix))
+	  (setq xref-end (+ xref-end len)))
+	)))
+
+  ;; Ensure a trailing \t.
+  (end-of-line)
+  (or (= (char-after (1- (point))) ?\t)
+      (insert ?\t)))
+
+
+(defun nnvirtual-possibly-change-server (server)
+  (or (not server)
+      (nnoo-current-server-p 'nnvirtual server)
+      (nnvirtual-open-server server)))
+
+
+(defun nnvirtual-update-read-and-marked (read-p update-p)
+  "Copy marks from the virtual group to the component groups.
+If READ-P is not nil, update the (un)read status of the components.
+If UPDATE-P is not nil, call gnus-group-update-group on the components."
+  (when nnvirtual-current-group
+    (let ((unreads (and read-p
+			(nnvirtual-partition-sequence
+			 (gnus-list-of-unread-articles
+			  (nnvirtual-current-group)))))
+	  (type-marks (mapcar (lambda (ml)
+				(cons (car ml)
+				      (nnvirtual-partition-sequence (cdr ml))))
+			      (gnus-info-marks (gnus-get-info
+						(nnvirtual-current-group)))))
+	  mark type groups carticles info entry)
+
+      ;; Ok, atomically move all of the (un)read info, clear any old
+      ;; marks, and move all of the current marks.  This way if someone
+      ;; hits C-g, you won't leave the component groups in a half-way state.
+      (gnus-atomic-progn
+	;; move (un)read
+	(let ((gnus-newsgroup-active nil)) ;workaround guns-update-read-articles
+	  (while (setq entry (pop unreads))
+	    (gnus-update-read-articles (car entry) (cdr entry))))
+
+	;; clear all existing marks on the component groups
+	(setq groups nnvirtual-component-groups)
+	(while groups
+	  (when (and (setq info (gnus-get-info (pop groups)))
+		     (gnus-info-marks info))
+	    (gnus-info-set-marks info nil)))
+
+	;; Ok, currently type-marks is an assq list with keys of a mark type,
+	;; with data of an assq list with keys of component group names
+	;; and the articles which correspond to that key/group pair.
+	(while (setq mark (pop type-marks))
+	  (setq type (car mark))
+	  (setq groups (cdr mark))
+	  (while (setq carticles (pop groups))
+	    (gnus-add-marked-articles (car carticles) type (cdr carticles)
+				      nil t))))
+
+      ;; possibly update the display, it is really slow
+      (when update-p
+	(setq groups nnvirtual-component-groups)
+	(while groups
+	  (gnus-group-update-group (pop groups) t))))))
+
+
+(defun nnvirtual-current-group ()
+  "Return the prefixed name of the current nnvirtual group."
+  (concat "nnvirtual:" nnvirtual-current-group))
+
+
+
+;;; This is currently O(kn^2) to merge n lists of length k.
+;;; You could do it in O(knlogn), but we have a small n, and the
+;;; overhead of the other approach is probably greater.
+(defun nnvirtual-merge-sorted-lists (&rest lists)
+  "Merge many sorted lists of numbers."
+  (if (null (cdr lists))
+      (car lists)
+    (apply 'nnvirtual-merge-sorted-lists
+	   (merge 'list (car lists) (cadr lists) '<)
+	   (cddr lists))))
+
+
+
+;;; We map between virtual articles and real articles in a manner
+;;; which keeps the size of the virtual active list the same as
+;;; the sum of the component active lists.
+;;; To achieve fair mixing of the groups, the last article in
+;;; each of N component groups will be in the the last N articles
+;;; in the virtual group.
+
+;;; If you have 3 components A, B and C, with articles 1-8, 1-5, and 6-7
+;;; resprectively, then the virtual article numbers look like:
+;;;
+;;;  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
+;;;  A1 A2 A3 A4 B1 A5 B2 A6 B3 A7 B4 C6 A8 B5 C7
+
+;;; To compute these mappings we generate a couple tables and then
+;;; do some fast operations on them.  Tables for the example above:
+;;;
+;;; Offsets - [(A 0) (B -3) (C -1)]
+;;;
+;;;               a  b  c  d  e
+;;; Mapping - ([  3  0  1  3  0 ]
+;;;            [  6  3  2  9  3 ]
+;;;            [  8  6  3 15  9 ])
+;;;
+;;; (note column 'e' is different in real algorithm, which is slightly
+;;;  different than described here, but this gives you the methodology.)
+;;;
+;;; The basic idea is this, when going from component->virtual, apply
+;;; the appropriate offset to the article number.  Then search the first
+;;; column of the table for a row where 'a' is less than or equal to the
+;;; modified number.  You can see that only group A can therefore go to
+;;; the first row, groups A and B to the second, and all to the last.
+;;; The third column of the table is telling us the number of groups
+;;; which might be able to reach that row (it might increase by more than
+;;; 1 if several groups have the same size).
+;;; Then column 'b' provides an additional offset you apply when you have
+;;; found the correct row.  You then multiply by 'c' and add on the groups
+;;; _position_ in the offset table.  The basic idea here is that on
+;;; any given row we are going to map back and forth using X'=X*c+Y and
+;;; X=(X'/c), Y=(X' mod c).  Then once you've done this transformation,
+;;; you apply a final offset from column 'e' to give the virtual article.
+;;;
+;;; Going the other direction, you instead search on column 'd' instead
+;;; of 'a', and apply everything in reverse order.
+
+;;; Convert component -> virtual:
+;;; set num = num - Offset(group)
+;;; find first row in Mapping where num <= 'a'
+;;; num = (num-'b')*c + Position(group) + 'e'
+
+;;; Convert virtual -> component:
+;;; find first row in Mapping where num <= 'd'
+;;; num = num - 'e'
+;;; group_pos = num mod 'c'
+;;; num = (num / 'c') + 'b' + Offset(group_pos)
+
+;;; Easy no? :)
+;;;
+;;; Well actually, you need to keep column e offset smaller by the 'c'
+;;; column for that line, and always add 1 more when going from
+;;; component -> virtual.  Otherwise you run into a problem with
+;;; unique reverse mapping.
+
+(defun nnvirtual-map-article (article)
+  "Return a cons of the component group and article corresponding to the given virtual ARTICLE."
+  (let ((table nnvirtual-mapping-table)
+	entry group-pos)
+    (while (and table
+		(> article (aref (car table) 3)))
+      (setq table (cdr table)))
+    (when (and table
+	       (> article 0))
+      (setq entry (car table))
+      (setq article (- article (aref entry 4) 1))
+      (setq group-pos (mod article (aref entry 2)))
+      (cons (car (aref nnvirtual-mapping-offsets group-pos))
+	    (+ (/ article (aref entry 2))
+	       (aref entry 1)
+	       (cdr (aref nnvirtual-mapping-offsets group-pos)))
+	    ))
+      ))
+
+
+
+(defun nnvirtual-reverse-map-article (group article)
+  "Return the virtual article number corresponding to the given component GROUP and ARTICLE."
+  (let ((table nnvirtual-mapping-table)
+	(group-pos 0)
+	entry)
+    (while (not (string= group (car (aref nnvirtual-mapping-offsets
+					  group-pos))))
+      (setq group-pos (1+ group-pos)))
+    (setq article (- article (cdr (aref nnvirtual-mapping-offsets
+					group-pos))))
+    (while (and table
+		(> article (aref (car table) 0)))
+      (setq table (cdr table)))
+    (setq entry (car table))
+    (when (and entry
+	       (> article 0)
+	       (< group-pos (aref entry 2))) ; article not out of range below
+      (+ (aref entry 4)
+	 group-pos
+	 (* (- article (aref entry 1))
+	    (aref entry 2))
+	 1))
+    ))
+
+
+(defsubst nnvirtual-reverse-map-sequence (group articles)
+  "Return list of virtual article numbers for all ARTICLES in GROUP.
+The ARTICLES should be sorted, and can be a compressed sequence.
+If any of the article numbers has no corresponding virtual article,
+then it is left out of the result."
+  (when (numberp (cdr-safe articles))
+    (setq articles (list articles)))
+  (let (result a i j new-a)
+    (while (setq a (pop articles))
+      (if (atom a)
+	  (setq i a
+		j a)
+	(setq i (car a)
+	      j (cdr a)))
+      (while (<= i j)
+	;; If this is slow, you can optimize by moving article checking
+	;; into here.  You don't have to recompute the group-pos,
+	;; nor scan the table every time.
+	(when (setq new-a (nnvirtual-reverse-map-article group i))
+	  (push new-a result))
+	(setq i (1+ i))))
+    (nreverse result)))
+
+
+(defun nnvirtual-partition-sequence (articles)
+  "Return an association list of component article numbers.
+These are indexed by elements of nnvirtual-component-groups, based on
+the sequence ARTICLES of virtual article numbers.  ARTICLES should be
+sorted, and can be a compressed sequence. If any of the article
+numbers has no corresponding component article, then it is left out of
+the result."
+  (when (numberp (cdr-safe articles))
+    (setq articles (list articles)))
+  (let ((carticles (mapcar (lambda (g) (list g))
+			   nnvirtual-component-groups))
+	a i j article entry)
+    (while (setq a (pop articles))
+      (if (atom a)
+	  (setq i a
+		j a)
+	(setq i (car a)
+	      j (cdr a)))
+      (while (<= i j)
+	(when (setq article (nnvirtual-map-article i))
+	  (setq entry (assoc (car article) carticles))
+	  (setcdr entry (cons (cdr article) (cdr entry))))
+	(setq i (1+ i))))
+    (mapc (lambda (x) (setcdr x (nreverse (cdr x))))
+	  carticles)
+    carticles))
+
+
+(defun nnvirtual-create-mapping ()
+  "Build the tables necessary to map between component (group, article) to virtual article.
+Generate the set of read messages and marks for the virtual group
+based on the marks on the component groups."
+  (let ((cnt 0)
+	(tot 0)
+	(M 0)
+	(i 0)
+	actives all-unreads all-marks
+	active min max size unreads marks
+	next-M next-tot
+	reads beg)
+    ;; Ok, we loop over all component groups and collect a lot of
+    ;; information:
+    ;; Into actives we place (g size max), where size is max-min+1.
+    ;; Into all-unreads we put (g unreads).
+    ;; Into all-marks we put (g marks).
+    ;; We also increment cnt and tot here, and compute M (max of sizes).
+    (mapc (lambda (g)
+	    (setq active (gnus-activate-group g)
+		  min (car active)
+		  max (cdr active))
+	    (when (and active (>= max min) (not (zerop max)))
+	      ;; store active information
+	      (push (list g (- max min -1) max) actives)
+	      ;; collect unread/mark info for later
+	      (setq unreads (gnus-list-of-unread-articles g))
+	      (setq marks (gnus-info-marks (gnus-get-info g)))
+	      (when gnus-use-cache
+		(push (cons 'cache
+			    (gnus-cache-articles-in-group g))
+		      marks))
+	      (push (cons g unreads) all-unreads)
+	      (push (cons g marks) all-marks)
+	      ;; count groups, total #articles, and max size
+	      (setq size (- max min -1))
+	      (setq cnt (1+ cnt)
+		    tot (+ tot size)
+		    M (max M size))))
+	  nnvirtual-component-groups)
+
+    ;; Number of articles in the virtual group.
+    (setq nnvirtual-mapping-len tot)
+
+
+    ;; We want the actives list sorted by size, to build the tables.
+    (setq actives (sort actives (lambda (g1 g2) (< (nth 1 g1) (nth 1 g2)))))
+
+    ;; Build the offset table.  Largest sized groups are at the front.
+    (setq nnvirtual-mapping-offsets
+	  (vconcat
+	   (nreverse
+	    (mapcar (lambda (entry)
+		      (cons (nth 0 entry)
+			    (- (nth 2 entry) M)))
+		    actives))))
+
+    ;; Build the mapping table.
+    (setq nnvirtual-mapping-table nil)
+    (setq actives (mapcar (lambda (entry) (nth 1 entry)) actives))
+    (while actives
+      (setq size (car actives))
+      (setq next-M (- M size))
+      (setq next-tot (- tot (* cnt size)))
+      ;; make current row in table
+      (push (vector M next-M cnt tot (- next-tot cnt))
+	    nnvirtual-mapping-table)
+      ;; update M and tot
+      (setq M next-M)
+      (setq tot next-tot)
+      ;; subtract the current size from all entries.
+      (setq actives (mapcar (lambda (x) (- x size)) actives))
+      ;; remove anything that went to 0.
+      (while (and actives
+		  (= (car actives) 0))
+	(pop actives)
+	(setq cnt (- cnt 1))))
+
+
+    ;; Now that the mapping tables are generated, we can convert
+    ;; and combine the separate component unreads and marks lists
+    ;; into single lists of virtual article numbers.
+    (setq unreads (apply 'nnvirtual-merge-sorted-lists
+			 (mapcar (lambda (x)
+				   (nnvirtual-reverse-map-sequence
+				    (car x) (cdr x)))
+				 all-unreads)))
+    (setq marks (mapcar
+		 (lambda (type)
+		   (cons (cdr type)
+			 (gnus-compress-sequence
+			  (apply
+			   'nnvirtual-merge-sorted-lists
+			   (mapcar (lambda (x)
+				     (nnvirtual-reverse-map-sequence
+				      (car x)
+				      (cdr (assq (cdr type) (cdr x)))))
+				   all-marks)))))
+		 gnus-article-mark-lists))
+
+    ;; Remove any empty marks lists, and store.
+    (setq nnvirtual-mapping-marks (delete-if-not 'cdr marks))
+
+    ;; We need to convert the unreads to reads.  We compress the
+    ;; sequence as we go, otherwise it could be huge.
+    (while (and (<= (incf i) nnvirtual-mapping-len)
+		unreads)
+      (if (= i (car unreads))
+	  (setq unreads (cdr unreads))
+	;; try to get a range.
+	(setq beg i)
+	(while (and (<= (incf i) nnvirtual-mapping-len)
+		    (not (= i (car unreads)))))
+	(setq i (- i 1))
+	(if (= i beg)
+	    (push i reads)
+	  (push (cons beg i) reads))
+	))
+    (when (<= i nnvirtual-mapping-len)
+      (if (= i nnvirtual-mapping-len)
+	  (push i reads)
+	(push (cons i nnvirtual-mapping-len) reads)))
+
+    ;; Store the reads list for later use.
+    (setq nnvirtual-mapping-reads (nreverse reads))
+
+    ;; Throw flag to show we changed the info.
+    (setq nnvirtual-info-installed nil)
+    ))
+
+(provide 'nnvirtual)
+
+;;; nnvirtual.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/nnweb.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,689 @@
+;;; nnweb.el --- retrieving articles via web search engines
+;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; Note: You need to have `url' and `w3' installed for this
+;; backend to work.
+
+;;; Code:
+
+(require 'nnoo)
+(require 'message)
+(require 'gnus-util)
+(require 'gnus)
+(require 'w3)
+(require 'url)
+(require 'nnmail)
+(ignore-errors
+  (require 'w3-forms))
+
+(nnoo-declare nnweb)
+
+(defvoo nnweb-directory (nnheader-concat gnus-directory "nnweb/")
+  "Where nnweb will save its files.")
+
+(defvoo nnweb-type 'dejanews
+  "What search engine type is being used.")
+
+(defvar nnweb-type-definition
+  '((dejanews
+     (article . nnweb-dejanews-wash-article)
+     (map . nnweb-dejanews-create-mapping)
+     (search . nnweb-dejanews-search)
+     (address . "http://xp9.dejanews.com/dnquery.xp")
+     (identifier . nnweb-dejanews-identity))
+    (reference
+     (article . nnweb-reference-wash-article)
+     (map . nnweb-reference-create-mapping)
+     (search . nnweb-reference-search)
+     (address . "http://www.reference.com/cgi-bin/pn/go")
+     (identifier . identity))
+    (altavista
+     (article . nnweb-altavista-wash-article)
+     (map . nnweb-altavista-create-mapping)
+     (search . nnweb-altavista-search)
+     (address . "http://www.altavista.digital.com/cgi-bin/query")
+     (id . "/cgi-bin/news?id@%s")
+     (identifier . identity)))
+  "Type-definition alist.")
+
+(defvoo nnweb-search nil
+  "Search string to feed to DejaNews.")
+
+(defvoo nnweb-max-hits 100
+  "Maximum number of hits to display.")
+
+(defvoo nnweb-ephemeral-p nil
+  "Whether this nnweb server is ephemeral.")
+
+;;; Internal variables
+
+(defvoo nnweb-articles nil)
+(defvoo nnweb-buffer nil)
+(defvoo nnweb-group-alist nil)
+(defvoo nnweb-group nil)
+(defvoo nnweb-hashtb nil)
+
+;;; Interface functions
+
+(nnoo-define-basics nnweb)
+
+(deffoo nnweb-retrieve-headers (articles &optional group server fetch-old)
+  (nnweb-possibly-change-server group server)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (let (article header)
+      (while (setq article (pop articles))
+	(when (setq header (cadr (assq article nnweb-articles)))
+	  (nnheader-insert-nov header)))
+      'nov)))
+
+(deffoo nnweb-request-scan (&optional group server)
+  (nnweb-possibly-change-server group server)
+  (setq nnweb-hashtb (gnus-make-hashtable 4095))
+  (funcall (nnweb-definition 'map))
+  (unless nnweb-ephemeral-p
+    (nnweb-write-active)
+    (nnweb-write-overview group)))
+
+(deffoo nnweb-request-group (group &optional server dont-check)
+  (nnweb-possibly-change-server nil server)
+  (when (and group
+	     (not (equal group nnweb-group))
+	     (not nnweb-ephemeral-p))
+    (let ((info (assoc group nnweb-group-alist)))
+      (setq nnweb-group group)
+      (setq nnweb-type (nth 2 info))
+      (setq nnweb-search (nth 3 info))
+      (unless dont-check
+	(nnweb-read-overview group))))
+  (cond
+   ((not nnweb-articles)
+    (nnheader-report 'nnweb "No matching articles"))
+   (t
+    (let ((active (if nnweb-ephemeral-p
+		      (cons (caar nnweb-articles)
+			    (caar (last nnweb-articles)))
+		    (cadr (assoc group nnweb-group-alist)))))
+      (nnheader-report 'nnweb "Opened group %s" group)
+      (nnheader-insert
+       "211 %d %d %d %s\n" (length nnweb-articles)
+       (car active) (cdr active) group)))))
+
+(deffoo nnweb-close-group (group &optional server)
+  (nnweb-possibly-change-server group server)
+  (when (gnus-buffer-live-p nnweb-buffer)
+    (save-excursion
+      (set-buffer nnweb-buffer)
+      (set-buffer-modified-p nil)
+      (kill-buffer nnweb-buffer)))
+  t)
+
+(deffoo nnweb-request-article (article &optional group server buffer)
+  (nnweb-possibly-change-server group server)
+  (save-excursion
+    (set-buffer (or buffer nntp-server-buffer))
+    (let* ((header (cadr (assq article nnweb-articles)))
+	   (url (and header (mail-header-xref header))))
+      (when (or (and url
+		     (nnweb-fetch-url url))
+		(and (stringp article)
+		     (nnweb-definition 'id t)
+		     (let ((fetch (nnweb-definition 'id))
+			   art)
+		       (when (string-match "^<\\(.*\\)>$" article)
+			 (setq art (match-string 1 article)))
+		       (and fetch
+			    art
+			    (nnweb-fetch-url
+			     (format fetch article))))))
+	(unless nnheader-callback-function
+	  (funcall (nnweb-definition 'article))
+	  (nnweb-decode-entities))
+	(nnheader-report 'nnweb "Fetched article %s" article)
+	t))))
+
+(deffoo nnweb-close-server (&optional server)
+  (when (and (nnweb-server-opened server)
+	     (gnus-buffer-live-p nnweb-buffer))
+    (save-excursion
+      (set-buffer nnweb-buffer)
+      (set-buffer-modified-p nil)
+      (kill-buffer nnweb-buffer)))
+  (nnoo-close-server 'nnweb server))
+
+(deffoo nnweb-request-list (&optional server)
+  (nnweb-possibly-change-server nil server)
+  (save-excursion
+    (set-buffer nntp-server-buffer)
+    (nnmail-generate-active nnweb-group-alist)
+    t))
+
+(deffoo nnweb-request-update-info (group info &optional server)
+  (nnweb-possibly-change-server group server)
+  ;;(setcar (cddr info) nil)
+  )
+
+(deffoo nnweb-asynchronous-p ()
+  t)
+
+(deffoo nnweb-request-create-group (group &optional server args)
+  (nnweb-possibly-change-server nil server)
+  (nnweb-request-delete-group group)
+  (push `(,group ,(cons 1 0) ,@args) nnweb-group-alist)
+  (nnweb-write-active)
+  t)
+
+(deffoo nnweb-request-delete-group (group &optional force server)
+  (nnweb-possibly-change-server group server)
+  (gnus-delete-assoc group nnweb-group-alist)
+  (gnus-delete-file (nnweb-overview-file group))
+  t)
+
+(nnoo-define-skeleton nnweb)
+
+;;; Internal functions
+
+(defun nnweb-read-overview (group)
+  "Read the overview of GROUP and build the map."
+  (when (file-exists-p (nnweb-overview-file group))
+    (nnheader-temp-write nil
+      (nnheader-insert-file-contents (nnweb-overview-file group))
+      (goto-char (point-min))
+      (let (header)
+	(while (not (eobp))
+	  (setq header (nnheader-parse-nov))
+	  (forward-line 1)
+	  (push (list (mail-header-number header)
+		      header (mail-header-xref header))
+		nnweb-articles)
+	  (nnweb-set-hashtb header (car nnweb-articles)))))))
+
+(defun nnweb-write-overview (group)
+  "Write the overview file for GROUP."
+  (nnheader-temp-write (nnweb-overview-file group)
+    (let ((articles nnweb-articles))
+      (while articles
+	(nnheader-insert-nov (cadr (pop articles)))))))
+
+(defun nnweb-set-hashtb (header data)
+  (gnus-sethash (nnweb-identifier (mail-header-xref header))
+		data nnweb-hashtb))
+
+(defun nnweb-get-hashtb (url)
+  (gnus-gethash (nnweb-identifier url) nnweb-hashtb))
+
+(defun nnweb-identifier (ident)
+  (funcall (nnweb-definition 'identifier) ident))
+
+(defun nnweb-overview-file (group)
+  "Return the name of the overview file of GROUP."
+  (nnheader-concat nnweb-directory group ".overview"))
+
+(defun nnweb-write-active ()
+  "Save the active file."
+  (nnheader-temp-write (nnheader-concat nnweb-directory "active")
+    (prin1 `(setq nnweb-group-alist ',nnweb-group-alist) (current-buffer))))
+
+(defun nnweb-read-active ()
+  "Read the active file."
+  (load (nnheader-concat nnweb-directory "active") t t t))
+
+(defun nnweb-definition (type &optional noerror)
+  "Return the definition of TYPE."
+  (let ((def (cdr (assq type (assq nnweb-type nnweb-type-definition)))))
+    (when (and (not def)
+	       (not noerror))
+      (error "Undefined definition %s" type))
+    def))
+
+(defun nnweb-possibly-change-server (&optional group server)
+  (nnweb-init server)
+  (when server
+    (unless (nnweb-server-opened server)
+      (nnweb-open-server server)))
+  (unless nnweb-group-alist
+    (nnweb-read-active))
+  (when group
+    (when (and (not nnweb-ephemeral-p)
+	       (not (equal group nnweb-group)))
+      (nnweb-request-group group nil t))))
+
+(defun nnweb-init (server)
+  "Initialize buffers and such."
+  (unless (gnus-buffer-live-p nnweb-buffer)
+    (setq nnweb-buffer
+	  (save-excursion
+	    (nnheader-set-temp-buffer
+	     (format " *nnweb %s %s %s*" nnweb-type nnweb-search server))))))
+
+(defun nnweb-fetch-url (url)
+  (save-excursion
+    (if (not nnheader-callback-function)
+	(let ((buf (current-buffer)))
+	  (save-excursion
+	    (set-buffer nnweb-buffer)
+	    (erase-buffer)
+	    (prog1
+		(url-insert-file-contents url)
+	      (copy-to-buffer buf (point-min) (point-max)))))
+      (nnweb-url-retrieve-asynch
+       url 'nnweb-callback (current-buffer) nnheader-callback-function)
+      t)))
+
+(defun nnweb-callback (buffer callback)
+  (when (gnus-buffer-live-p url-working-buffer)
+    (save-excursion
+      (set-buffer url-working-buffer)
+      (funcall (nnweb-definition 'article))
+      (nnweb-decode-entities)
+      (set-buffer buffer)
+      (goto-char (point-max))
+      (insert-buffer-substring url-working-buffer))
+    (funcall callback t)
+    (gnus-kill-buffer url-working-buffer)))
+
+(defun nnweb-url-retrieve-asynch (url callback &rest data)
+  (let ((url-request-method "GET")
+	(old-asynch url-be-asynchronous)
+	(url-request-data nil)
+	(url-request-extra-headers nil)
+	(url-working-buffer (generate-new-buffer-name " *nnweb*")))
+    (setq-default url-be-asynchronous t)
+    (save-excursion
+      (set-buffer (get-buffer-create url-working-buffer))
+      (setq url-current-callback-data data
+	    url-be-asynchronous t
+	    url-current-callback-func callback)
+      (url-retrieve url))
+    (setq-default url-be-asynchronous old-asynch)))
+
+(defun nnweb-encode-www-form-urlencoded (pairs)
+  "Return PAIRS encoded for forms."
+  (mapconcat
+   (function
+    (lambda (data)
+      (concat (w3-form-encode-xwfu (car data)) "="
+	      (w3-form-encode-xwfu (cdr data)))))
+   pairs "&"))
+
+(defun nnweb-fetch-form (url pairs)
+  (let ((url-request-data (nnweb-encode-www-form-urlencoded pairs))
+	(url-request-method "POST")
+	(url-request-extra-headers
+	 '(("Content-type" . "application/x-www-form-urlencoded"))))
+    (url-insert-file-contents url)
+    (setq buffer-file-name nil))
+  t)
+
+(defun nnweb-decode-entities ()
+  (goto-char (point-min))
+  (while (re-search-forward "&\\([a-z]+\\);" nil t)
+    (replace-match (char-to-string (or (cdr (assq (intern (match-string 1))
+						  w3-html-entities ))
+				       ?#))
+		   t t)))
+
+(defun nnweb-remove-markup ()
+  (goto-char (point-min))
+  (while (search-forward "<!--" nil t)
+    (delete-region (match-beginning 0)
+		   (or (search-forward "-->" nil t)
+		       (point-max))))
+  (goto-char (point-min))
+  (while (re-search-forward "<[^>]+>" nil t)
+    (replace-match "" t t)))
+
+;;;
+;;; DejaNews functions.
+;;;
+
+(defun nnweb-dejanews-create-mapping ()
+  "Perform the search and create an number-to-url alist."
+  (save-excursion
+    (set-buffer nnweb-buffer)
+    (erase-buffer)
+    (when (funcall (nnweb-definition 'search) nnweb-search)
+      (let ((i 0)
+	    (more t)
+	    (case-fold-search t)
+	    (active (or (cadr (assoc nnweb-group nnweb-group-alist))
+			(cons 1 0)))
+	    Subject Score Date Newsgroup Author
+	    map url)
+	(while more
+	  ;; Go through all the article hits on this page.
+	  (goto-char (point-min))
+	  (nnweb-decode-entities)
+	  (goto-char (point-min))
+	  (while (re-search-forward "^ +[0-9]+\\." nil t)
+	    (narrow-to-region
+	     (point)
+	     (cond ((re-search-forward "^ +[0-9]+\\." nil t)
+		    (match-beginning 0))
+		   ((search-forward "\n\n" nil t)
+		    (point))
+		   (t
+		    (point-max))))
+	    (goto-char (point-min))
+	    (when (looking-at ".*HREF=\"\\([^\"]+\\)\"")
+	      (setq url (match-string 1)))
+	    (nnweb-remove-markup)
+	    (goto-char (point-min))
+	    (while (search-forward "\t" nil t)
+	      (replace-match " "))
+	    (goto-char (point-min))
+	    (while (re-search-forward "^ +\\([^:]+\\): +\\(.*\\)$" nil t)
+	      (set (intern (match-string 1)) (match-string 2)))
+	    (widen)
+	    (when (string-match "#[0-9]+/[0-9]+ *$" Subject)
+	      (setq Subject (substring Subject 0 (match-beginning 0))))
+	    (incf i)
+	    (unless (nnweb-get-hashtb url)
+	      (push
+	       (list
+		(incf (cdr active))
+		(make-full-mail-header
+		 (cdr active) (concat  "(" Newsgroup ") " Subject) Author Date
+		 (concat "<" (nnweb-identifier url) "@dejanews>")
+		 nil 0 (string-to-int Score) url))
+	       map)
+	      (nnweb-set-hashtb (cadar map) (car map))))
+	  ;; See whether there is a "Get next 20 hits" button here.
+	  (if (or (not (re-search-forward
+			"HREF=\"\\([^\"]+\\)\">Get next" nil t))
+		  (>= i nnweb-max-hits))
+	      (setq more nil)
+	    ;; Yup -- fetch it.
+	    (setq more (match-string 1))
+	    (erase-buffer)
+	    (url-insert-file-contents more)))
+	;; Return the articles in the right order.
+	(setq nnweb-articles
+	      (sort (nconc nnweb-articles map)
+		    (lambda (s1 s2) (< (car s1) (car s2)))))))))
+
+(defun nnweb-dejanews-wash-article ()
+  (let ((case-fold-search t))
+    (goto-char (point-min))
+    (re-search-forward "<PRE>" nil t)
+    (delete-region (point-min) (point))
+    (re-search-forward "</PRE>" nil t)
+    (delete-region (point) (point-max))
+    (nnweb-remove-markup)
+    (goto-char (point-min))
+    (while (and (looking-at " *$")
+		(not (eobp)))
+      (gnus-delete-line))
+    (while (looking-at "\\(^[^ ]+:\\) *")
+      (replace-match "\\1 " t)
+      (forward-line 1))
+    (when (re-search-forward "\n\n+" nil t)
+      (replace-match "\n" t t))))
+
+(defun nnweb-dejanews-search (search)
+  (nnweb-fetch-form
+   (nnweb-definition 'address)
+   `(("query" . ,search)
+     ("defaultOp" . "AND")
+     ("svcclass" . "dncurrent")
+     ("maxhits" . "100")
+     ("format" . "verbose")
+     ("threaded" . "0")
+     ("showsort" . "score")
+     ("agesign" . "1")
+     ("ageweight" . "1")))
+  t)
+
+(defun nnweb-dejanews-identity (url)
+  "Return an unique identifier based on URL."
+  (if (string-match "recnum=\\([0-9]+\\)" url)
+      (match-string 1 url)
+    url))
+
+;;;
+;;; InReference
+;;;
+
+(defun nnweb-reference-create-mapping ()
+  "Perform the search and create an number-to-url alist."
+  (save-excursion
+    (set-buffer nnweb-buffer)
+    (erase-buffer)
+    (when (funcall (nnweb-definition 'search) nnweb-search)
+      (let ((i 0)
+	    (more t)
+	    (case-fold-search t)
+	    (active (or (cadr (assoc nnweb-group nnweb-group-alist))
+			(cons 1 0)))
+	    Subject Score Date Newsgroups From Message-ID
+	    map url)
+	(while more
+	  ;; Go through all the article hits on this page.
+	  (goto-char (point-min))
+	  (search-forward "</pre><hr>" nil t)
+	  (delete-region (point-min) (point))
+					;(nnweb-decode-entities)
+	  (goto-char (point-min))
+	  (while (re-search-forward "^ +[0-9]+\\." nil t)
+	    (narrow-to-region
+	     (point)
+	     (if (re-search-forward "^$" nil t)
+		 (match-beginning 0)
+	       (point-max)))
+	    (goto-char (point-min))
+	    (when (looking-at ".*href=\"\\([^\"]+\\)\"")
+	      (setq url (match-string 1)))
+	    (nnweb-remove-markup)
+	    (goto-char (point-min))
+	    (while (search-forward "\t" nil t)
+	      (replace-match " "))
+	    (goto-char (point-min))
+	    (while (re-search-forward "^\\([^:]+\\): \\(.*\\)$" nil t)
+	      (set (intern (match-string 1)) (match-string 2)))
+	    (widen)
+	    (search-forward "</pre>" nil t)
+	    (incf i)
+	    (unless (nnweb-get-hashtb url)
+	      (push
+	       (list
+		(incf (cdr active))
+		(make-full-mail-header
+		 (cdr active) (concat  "(" Newsgroups ") " Subject) From Date
+		 Message-ID
+		 nil 0 (string-to-int Score) url))
+	       map)
+	      (nnweb-set-hashtb (cadar map) (car map))))
+	  (setq more nil))
+	;; Return the articles in the right order.
+	(setq nnweb-articles
+	      (sort (nconc nnweb-articles map)
+		    (lambda (s1 s2) (< (car s1) (car s2)))))))))
+
+(defun nnweb-reference-wash-article ()
+  (let ((case-fold-search t))
+    (goto-char (point-min))
+    (re-search-forward "^</center><hr>" nil t)
+    (delete-region (point-min) (point))
+    (search-forward "<pre>" nil t)
+    (forward-line -1)
+    (let ((body (point-marker)))
+      (search-forward "</pre>" nil t)
+      (delete-region (point) (point-max))
+      (nnweb-remove-markup)
+      (goto-char (point-min))
+      (while (looking-at " *$")
+	(gnus-delete-line))
+      (narrow-to-region (point-min) body)
+      (while (and (re-search-forward "^$" nil t)
+		  (not (eobp)))
+	(gnus-delete-line))
+      (goto-char (point-min))
+      (while (looking-at "\\(^[^ ]+:\\) *")
+	(replace-match "\\1 " t)
+	(forward-line 1))
+      (goto-char (point-min))
+      (when (re-search-forward "^References:" nil t)
+	(narrow-to-region
+	 (point) (if (re-search-forward "^$\\|^[^:]+:" nil t)
+		     (match-beginning 0)
+		   (point-max)))
+	(goto-char (point-min))
+	(while (not (eobp))
+	  (unless (looking-at "References")
+	    (insert "\t")
+	    (forward-line 1)))
+	(goto-char (point-min))
+	(while (search-forward "," nil t)
+	  (replace-match " " t t)))
+      (widen)
+      (set-marker body nil))))
+
+(defun nnweb-reference-search (search)
+  (prog1
+      (url-insert-file-contents
+       (concat
+	(nnweb-definition 'address)
+	"?"
+	(nnweb-encode-www-form-urlencoded
+	 `(("search" . "advanced")
+	   ("querytext" . ,search)
+	   ("subj" . "")
+	   ("name" . "")
+	   ("login" . "")
+	   ("host" . "")
+	   ("organization" . "")
+	   ("groups" . "")
+	   ("keywords" . "")
+	   ("choice" . "Search")
+	   ("startmonth" . "Jul")
+	   ("startday" . "25")
+	   ("startyear" . "1996")
+	   ("endmonth" . "Aug")
+	   ("endday" . "24")
+	   ("endyear" . "1996")
+	   ("mode" . "Quick")
+	   ("verbosity" . "Verbose")
+	   ("ranking" . "Relevance")
+	   ("first" . "1")
+	   ("last" . "25")
+	   ("score" . "50")))))
+    (setq buffer-file-name nil))
+  t)
+
+;;;
+;;; Alta Vista
+;;;
+
+(defun nnweb-altavista-create-mapping ()
+  "Perform the search and create an number-to-url alist."
+  (save-excursion
+    (set-buffer nnweb-buffer)
+    (erase-buffer)
+    (let ((part 0))
+      (when (funcall (nnweb-definition 'search) nnweb-search part)
+	(let ((i 0)
+	      (more t)
+	      (case-fold-search t)
+	      (active (or (cadr (assoc nnweb-group nnweb-group-alist))
+			  (cons 1 0)))
+	      subject date from id group
+	      map url)
+	  (while more
+	    ;; Go through all the article hits on this page.
+	    (goto-char (point-min))
+	    (search-forward "<dt>" nil t)
+	    (delete-region (point-min) (match-beginning 0))
+	    (goto-char (point-min))
+	    (while (search-forward "<dt>" nil t)
+	      (replace-match "\n<blubb>"))
+	    (nnweb-decode-entities)
+	    (goto-char (point-min))
+	    (while (re-search-forward "<blubb>.*href=\"\\([^\"]+\\)\"><strong>\\([^>]*\\)</strong></a><dd>\\([^-]+\\)- <b>\\([^<]+\\)<.*href=\"news:\\([^\"]+\\)\">.*\">\\(.+\\)</a><P>"
+				      nil t)
+	      (setq url (match-string 1)
+		    subject (match-string 2)
+		    date (match-string 3)
+		    group (match-string 4)
+		    id (concat "<" (match-string 5) ">")
+		    from (match-string 6))
+	      (incf i)
+	      (unless (nnweb-get-hashtb url)
+		(push
+		 (list
+		  (incf (cdr active))
+		  (make-full-mail-header
+		   (cdr active) (concat  "(" group ") " subject) from date
+		   id nil 0 0 url))
+		 map)
+		(nnweb-set-hashtb (cadar map) (car map))))
+	    ;; See if we want more.
+	    (when (or (not nnweb-articles)
+		      (>= i nnweb-max-hits)
+		      (not (funcall (nnweb-definition 'search)
+				    nnweb-search (incf part))))
+	      (setq more nil)))
+	  ;; Return the articles in the right order.
+	  (setq nnweb-articles
+		(sort (nconc nnweb-articles map)
+		      (lambda (s1 s2) (< (car s1) (car s2))))))))))
+
+(defun nnweb-altavista-wash-article ()
+  (goto-char (point-min))
+  (let ((case-fold-search t))
+    (when (re-search-forward "^<strong>" nil t)
+      (delete-region (point-min) (match-beginning 0)))
+    (goto-char (point-min))
+    (while (looking-at "<strong>\\([^ ]+\\) +</strong> +\\(.*\\)$")
+      (replace-match "\\1: \\2" t)
+      (forward-line 1))
+    (when (re-search-backward "^References:" nil t)
+      (narrow-to-region (point) (progn (forward-line 1) (point)))
+      (goto-char (point-min))
+      (while (re-search-forward "<A.*\\?id@\\([^\"]+\\)\">[0-9]+</A>" nil t)
+	(replace-match "&lt;\\1&gt; " t)))
+    (widen)
+    (nnweb-remove-markup)))
+
+(defun nnweb-altavista-search (search &optional part)
+  (prog1
+      (url-insert-file-contents
+       (concat
+	(nnweb-definition 'address)
+	"?"
+	(nnweb-encode-www-form-urlencoded
+	 `(("pg" . "aq")
+	   ("what" . "news")
+	   ,@(when part `(("stq" . ,(int-to-string (* part 30)))))
+	   ("fmt" . "d")
+	   ("q" . ,search)
+	   ("r" . "")
+	   ("d0" . "")
+	   ("d1" . "")))))
+    (setq buffer-file-name nil)))
+
+(provide 'nnweb)
+
+;;; nnweb.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/parse-time.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,199 @@
+;;; parse-time.el --- Parsing time strings
+
+;; Copyright (C) 1996 by Free Software Foundation, Inc.
+
+;; Author: Erik Naggum <erik@arcana.naggum.no>
+;; Keywords: util
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; With the introduction of the `encode-time', `decode-time', and
+;; `format-time-string' functions, dealing with time became simpler in
+;; Emacs.  However, parsing time strings is still largely a matter of
+;; heuristics and no common interface has been designed.
+
+;; `parse-time-string' parses a time in a string and returns a list of 9
+;; values, just like `decode-time', where unspecified elements in the
+;; string are returned as nil.  `encode-time' may be applied on these
+;; valuse to obtain an internal time value.
+
+;;; Code:
+
+(require 'cl)				;and ah ain't kiddin' 'bout it
+
+(put 'parse-time-syntax 'char-table-extra-slots 0)
+
+(defvar parse-time-syntax (make-char-table 'parse-time-syntax))
+(defvar parse-time-digits (make-char-table 'parse-time-syntax))
+
+;; Byte-compiler warnings
+(defvar elt)
+(defvar val)
+
+(unless (aref parse-time-digits ?0)
+  (loop for i from ?0 to ?9
+	do (set-char-table-range parse-time-digits i (- i ?0))))
+
+(unless (aref parse-time-syntax ?0)
+  (loop for i from ?0 to ?9
+	do (set-char-table-range parse-time-syntax i ?0))
+  (loop for i from ?A to ?Z
+	do (set-char-table-range parse-time-syntax i ?A))
+  (loop for i from ?a to ?z
+	do (set-char-table-range parse-time-syntax i ?a))
+  (set-char-table-range parse-time-syntax ?+ 1)
+  (set-char-table-range parse-time-syntax ?- -1)
+  (set-char-table-range parse-time-syntax ?: ?d)
+  )
+
+(defsubst digit-char-p (char)
+  (aref parse-time-digits char))
+
+(defsubst parse-time-string-chars (char)
+  (aref parse-time-syntax char))
+
+(put 'parse-error 'error-conditions '(parse-error error))
+(put 'parse-error 'error-message "Parsing error")
+
+(defsubst parse-integer (string &optional start end)
+  "[CL] Parse and return the integer in STRING, or nil if none."
+  (let ((integer 0)
+	(digit 0)
+	(index (or start 0))
+	(end (or end (length string))))
+    (when (< index end)
+      (let ((sign (aref string index)))
+	(if (or (eq sign ?+) (eq sign ?-))
+	    (setq sign (parse-time-string-chars sign)
+		  index (1+ index))
+	  (setq sign 1))
+	(while (and (< index end)
+		    (setq digit (digit-char-p (aref string index))))
+	  (setq integer (+ (* integer 10) digit)
+		index (1+ index)))
+	(if (/= index end)
+	    (signal 'parse-error `("not an integer" ,(substring string (or start 0) end)))
+	  (* sign integer))))))
+
+(defun parse-time-tokenize (string)
+  "Tokenize STRING into substrings."
+  (let ((start nil)
+	(end (length string))
+	(all-digits nil)
+	(list ())
+	(index 0)
+	(c nil))
+    (while (< index end)
+      (while (and (< index end)		;skip invalid characters
+		  (not (setq c (parse-time-string-chars (aref string index)))))
+	(incf index))
+      (setq start index all-digits (eq c ?0))
+      (while (and (< (incf index) end)	;scan valid characters
+		  (setq c (parse-time-string-chars (aref string index))))
+	(setq all-digits (and all-digits (eq c ?0))))
+      (if (<= index end)
+	  (push (if all-digits (parse-integer string start index)
+		  (substring string start index))
+		list)))
+    (nreverse list)))
+
+(defvar parse-time-months '(("Jan" . 1) ("Feb" . 2) ("Mar" . 3)
+			    ("Apr" . 4) ("May" . 5) ("Jun" . 6)
+			    ("Jul" . 7) ("Aug" . 8) ("Sep" . 9)
+			    ("Oct" . 10) ("Nov" . 11) ("Dec" . 12)))
+(defvar parse-time-weekdays '(("Sun" . 0) ("Mon" . 1) ("Tue" . 2)
+			      ("Wed" . 3) ("Thu" . 4) ("Fri" . 5) ("Sat" . 6)))
+(defvar parse-time-zoneinfo `(("Z" 0) ("UT" 0) ("GMT" 0)
+			      ("PST" ,(* -8 3600)) ("PDT" ,(* -7 3600) t)
+			      ("MST" ,(* -7 3600)) ("MDT" ,(* -6 3600) t)
+			      ("CST" ,(* -6 3600)) ("CDT" ,(* -5 3600) t)
+			      ("EST" ,(* -5 3600)) ("EDT" ,(* -4 3600) t))
+  "(zoneinfo seconds-off daylight-savings-time-p)")
+
+(defvar parse-time-rules
+  `(((6) parse-time-weekdays)
+    ((3) (1 31))
+    ((4) parse-time-months)
+    ((5) (1970 2038))
+    ((2 1 0)
+     ,#'(lambda () (and (stringp elt)
+			(= (length elt) 8)
+			(= (aref elt 2) ?:)
+			(= (aref elt 5) ?:)))
+     [0 2] [3 5] [6 8])
+    ((8 7) parse-time-zoneinfo
+     ,#'(lambda () (car val))
+     ,#'(lambda () (cadr val)))
+    ((8)
+     ,#'(lambda ()
+	  (and (stringp elt)
+	       (= 5 (length elt))
+	       (or (= (aref elt 0) ?+) (= (aref elt 0) ?-))))
+     ,#'(lambda () (* 60 (+ (parse-integer elt 3 5)
+			    (* 60 (parse-integer elt 1 3)))
+		      (if (= (aref elt 0) ?-) -1 1))))
+    ((5 4 3)
+     ,#'(lambda () (and (stringp elt) (= (length elt) 10) (= (aref elt 4) ?-) (= (aref elt 7) ?-)))
+     [0 4] [5 7] [8 10])
+    ((2 1)
+     ,#'(lambda () (and (stringp elt) (= (length elt) 5) (= (aref elt 2) ?:)))
+     [0 2] [3 5])
+    ((5) (70 99) ,#'(lambda () (+ 1900 elt))))
+  "(slots predicate extractor...)")
+
+(defun parse-time-string (string)
+  "Parse the time-string STRING into (SEC MIN HOUR DAY MON YEAR DOW DST TZ).
+The values are identical to those of `decode-time', but any values that are
+unknown are returned as nil."
+  (let ((time (list nil nil nil nil nil nil nil nil nil nil))
+	(temp (parse-time-tokenize string)))
+    (while temp
+      (let ((elt (pop temp))
+	    (rules parse-time-rules)
+	    (exit nil))
+	(while (and (not (null rules)) (not exit))
+	  (let* ((rule (pop rules))
+		 (slots (pop rule))
+		 (predicate (pop rule))
+		 (val))
+	    (if (and (not (nth (car slots) time)) ;not already set
+		     (setq val (cond ((and (consp predicate)
+					   (not (eq (car predicate) 'lambda)))
+				      (and (numberp elt)
+					   (<= (car predicate) elt)
+					   (<= elt (cadr predicate))
+					   elt))
+				     ((symbolp predicate)
+				      (cdr (assoc elt (symbol-value predicate))))
+				     ((funcall predicate)))))
+		(progn
+		  (setq exit t)
+		  (while slots
+		    (let ((new-val (and rule
+					(let ((this (pop rule)))
+					  (if (vectorp this)
+					      (parse-integer elt (aref this 0) (aref this 1))
+					    (funcall this))))))
+		      (rplaca (nthcdr (pop slots) time) (or new-val val))))))))))
+    time))
+
+(provide 'parse-time)
+
+;;; parse-time.el ends here
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/pop3.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,443 @@
+;;; pop3.el --- Post Office Protocol (RFC 1460) interface
+
+;; Copyright (C) 1996, Free Software Foundation, Inc.
+
+;; Author: Richard L. Pieri <ratinox@peorth.gweep.net>
+;; Keywords: mail, pop3
+;; Version: 1.3e
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; Most of the standard Post Office Protocol version 3 (RFC 1460) commands
+;; are implemented.  The LIST command has not been implemented due to lack
+;; of actual usefulness.
+;; The optional POP3 command TOP has not been implemented.
+
+;; This program was inspired by Kyle E. Jones's vm-pop program.
+
+;;; Code:
+
+(require 'mail-utils)
+(provide 'pop3)
+
+(defconst pop3-version "1.3c")
+
+(defvar pop3-maildrop (or user-login-name (getenv "LOGNAME") (getenv "USER") nil)
+  "*POP3 maildrop.")
+(defvar pop3-mailhost (or (getenv "MAILHOST") nil)
+  "*POP3 mailhost.")
+(defvar pop3-port 110
+  "*POP3 port.")
+
+(defvar pop3-password-required t
+  "*Non-nil if a password is required when connecting to POP server.")
+(defvar pop3-password nil
+  "*Password to use when connecting to POP server.")
+
+(defvar pop3-authentication-scheme 'pass
+  "*POP3 authentication scheme.
+Defaults to 'pass, for the standard USER/PASS authentication.  Other valid
+values are 'apop.")
+
+(defvar pop3-timestamp nil
+  "Timestamp returned when initially connected to the POP server.
+Used for APOP authentication.")
+
+(defvar pop3-read-point nil)
+(defvar pop3-debug nil)
+
+(defun pop3-movemail (&optional crashbox)
+  "Transfer contents of a maildrop to the specified CRASHBOX."
+  (or crashbox (setq crashbox (expand-file-name "~/.crashbox")))
+  (let* ((process (pop3-open-server pop3-mailhost pop3-port))
+	 (crashbuf (get-buffer-create " *pop3-retr*"))
+	 (n 1)
+	 message-count)
+    ;; for debugging only
+    (if pop3-debug (switch-to-buffer (process-buffer process)))
+    (cond ((equal 'apop pop3-authentication-scheme)
+	   (pop3-apop process pop3-maildrop))
+	  ((equal 'pass pop3-authentication-scheme)
+	   (pop3-user process pop3-maildrop)
+	   (pop3-pass process))
+	  (t (error "Invalid POP3 authentication scheme.")))
+    (setq message-count (car (pop3-stat process)))
+    (while (<= n message-count)
+      (message (format "Retrieving message %d of %d from %s..."
+		       n message-count pop3-mailhost))
+      (pop3-retr process n crashbuf)
+      (save-excursion
+	(set-buffer crashbuf)
+	(append-to-file (point-min) (point-max) crashbox)
+	(set-buffer (process-buffer process))
+	(while (> (buffer-size) 5000)
+	  (goto-char (point-min))
+	  (forward-line 50)
+	  (delete-region (point-min) (point))))
+      (pop3-dele process n)
+      (setq n (+ 1 n))
+      (if pop3-debug (sit-for 1) (sit-for 0.1))
+      )
+    (pop3-quit process)
+    (kill-buffer crashbuf)
+    )
+  )
+
+(defun pop3-open-server (mailhost port)
+  "Open TCP connection to MAILHOST.
+Returns the process associated with the connection."
+  (let ((process-buffer
+	 (get-buffer-create (format "trace of POP session to %s" mailhost)))
+	(process))
+    (save-excursion
+      (set-buffer process-buffer)
+      (erase-buffer))
+    (setq process
+	  (open-network-stream "POP" process-buffer mailhost port))
+    (setq pop3-read-point (point-min))
+    (let ((response (pop3-read-response process t)))
+      (setq pop3-timestamp
+	    (substring response (or (string-match "<" response) 0)
+		       (+ 1 (or (string-match ">" response) -1)))))
+    process
+    ))
+
+;; Support functions
+
+(defun pop3-process-filter (process output)
+  (save-excursion
+    (set-buffer (process-buffer process))
+    (goto-char (point-max))
+    (insert output)))
+
+(defun pop3-send-command (process command)
+    (set-buffer (process-buffer process))
+    (goto-char (point-max))
+;;    (if (= (aref command 0) ?P)
+;;	(insert "PASS <omitted>\r\n")
+;;      (insert command "\r\n"))
+    (setq pop3-read-point (point))
+    (goto-char (point-max))
+    (process-send-string process command)
+    (process-send-string process "\r\n")
+    )
+
+(defun pop3-read-response (process &optional return)
+  "Read the response from the server.
+Return the response string if optional second argument is non-nil."
+  (let ((case-fold-search nil)
+	match-end)
+    (save-excursion
+      (set-buffer (process-buffer process))
+      (goto-char pop3-read-point)
+      (while (not (search-forward "\r\n" nil t))
+	(accept-process-output process)
+	(goto-char pop3-read-point))
+      (setq match-end (point))
+      (goto-char pop3-read-point)
+      (if (looking-at "-ERR")
+	  (error (buffer-substring (point) (- match-end 2)))
+	(if (not (looking-at "+OK"))
+	    (progn (setq pop3-read-point match-end) nil)
+	  (setq pop3-read-point match-end)
+	  (if return
+	      (buffer-substring (point) match-end)
+	    t)
+	  )))))
+
+(defun pop3-string-to-list (string &optional regexp)
+  "Chop up a string into a list."
+  (let ((list)
+	(regexp (or regexp " "))
+	(string (if (string-match "\r" string)
+		    (substring string 0 (match-beginning 0))
+		  string)))
+    (store-match-data nil)
+    (while string
+      (if (string-match regexp string)
+	  (setq list (cons (substring string 0 (- (match-end 0) 1)) list)
+		string (substring string (match-end 0)))
+	(setq list (cons string list)
+	      string nil)))
+    (nreverse list)))
+
+(defvar pop3-read-passwd nil)
+(defun pop3-read-passwd (prompt)
+  (if (not pop3-read-passwd)
+      (if (load "passwd" t)
+	  (setq pop3-read-passwd 'read-passwd)
+	(autoload 'ange-ftp-read-passwd "ange-ftp")
+	(setq pop3-read-passwd 'ange-ftp-read-passwd)))
+  (funcall pop3-read-passwd prompt))
+
+(defun pop3-clean-region (start end)
+  (setq end (set-marker (make-marker) end))
+  (save-excursion
+    (goto-char start)
+    (while (and (< (point) end) (search-forward "\r\n" end t))
+      (replace-match "\n" t t))
+    (goto-char start)
+    (while (and (< (point) end) (re-search-forward "^\\." end t))
+      (replace-match "" t t)
+      (forward-char)))
+  (set-marker end nil))
+
+(defun pop3-munge-message-separator (start end)
+  "Check to see if a message separator exists.  If not, generate one."
+  (save-excursion
+    (save-restriction
+      (narrow-to-region start end)
+      (goto-char (point-min))
+      (if (not (or (looking-at "From .?") ; Unix mail
+		   (looking-at "\001\001\001\001\n") ; MMDF
+		   (looking-at "BABYL OPTIONS:") ; Babyl
+		   ))
+	  (let ((from (mail-strip-quoted-names (mail-fetch-field "From")))
+		(date (pop3-string-to-list (mail-fetch-field "Date")))
+		(From_))
+	    ;; sample date formats I have seen
+	    ;; Date: Tue, 9 Jul 1996 09:04:21 -0400 (EDT)
+	    ;; Date: 08 Jul 1996 23:22:24 -0400
+	    ;; should be
+	    ;; Tue Jul 9 09:04:21 1996
+	    (setq date
+		  (cond ((string-match "[A-Z]" (nth 0 date))
+			 (format "%s %s %s %s %s"
+				 (nth 0 date) (nth 2 date) (nth 1 date)
+				 (nth 4 date) (nth 3 date)))
+			(t
+			 ;; this really needs to be better but I don't feel
+			 ;; like writing a date to day converter.
+			 (format "Sun %s %s %s %s"
+				 (nth 1 date) (nth 0 date)
+				 (nth 3 date) (nth 2 date)))
+			))
+	    (setq From_ (format "\nFrom %s  %s\n" from date))
+	    (while (string-match "," From_)
+	      (setq From_ (concat (substring From_ 0 (match-beginning 0))
+				  (substring From_ (match-end 0)))))
+	    (goto-char (point-min))
+	    (insert From_))))))
+
+;; The Command Set
+
+;; AUTHORIZATION STATE
+
+(defun pop3-user (process user)
+  "Send USER information to POP3 server."
+  (pop3-send-command process (format "USER %s" user))
+  (let ((response (pop3-read-response process t)))
+    (if (not (and response (string-match "+OK" response)))
+	(error (format "USER %s not valid." user)))))
+
+(defun pop3-pass (process)
+  "Send authentication information to the server."
+  (let ((pass pop3-password))
+    (if (and pop3-password-required (not pass))
+	(setq pass
+	      (pop3-read-passwd (format "Password for %s: " pop3-maildrop))))
+    (if pass
+	(progn
+	  (pop3-send-command process (format "PASS %s" pass))
+	  (let ((response (pop3-read-response process t)))
+	    (if (not (and response (string-match "+OK" response)))
+		(pop3-quit process)))))
+    ))
+
+(defun pop3-apop (process user)
+  "Send alternate authentication information to the server."
+  (if (not (fboundp 'md5)) (autoload 'md5 "md5"))
+  (let ((pass pop3-password))
+    (if (and pop3-password-required (not pass))
+	(setq pass
+	      (pop3-read-passwd (format "Password for %s: " pop3-maildrop))))
+    (if pass
+	(let ((hash (md5 (concat pop3-timestamp pass))))
+	  (pop3-send-command process (format "APOP %s %s" user hash))
+	  (let ((response (pop3-read-response process t)))
+	    (if (not (and response (string-match "+OK" response)))
+		(pop3-quit process)))))
+    ))
+
+;; TRANSACTION STATE
+
+(defun pop3-stat (process)
+  "Return the number of messages in the maildrop and the maildrop's size."
+  (pop3-send-command process "STAT")
+  (let ((response (pop3-read-response process t)))
+    (list (string-to-int (nth 1 (pop3-string-to-list response)))
+	  (string-to-int (nth 2 (pop3-string-to-list response))))
+    ))
+
+(defun pop3-list (process &optional msg)
+  "Scan listing of available messages.
+This function currently does nothing.")
+
+(defun pop3-retr (process msg crashbuf)
+  "Retrieve message-id MSG to buffer CRASHBUF."
+  (pop3-send-command process (format "RETR %s" msg))
+  (pop3-read-response process)
+  (let ((start pop3-read-point) end)
+    (save-excursion
+      (set-buffer (process-buffer process))
+      (while (not (re-search-forward "^\\.\r\n" nil t))
+	(accept-process-output process)
+	;; bill@att.com ... to save wear and tear on the heap
+	(if (> (buffer-size)  20000) (sleep-for 1))
+	(if (> (buffer-size)  50000) (sleep-for 1))
+	(if (> (buffer-size) 100000) (sleep-for 1))
+	(if (> (buffer-size) 200000) (sleep-for 1))
+	(if (> (buffer-size) 500000) (sleep-for 1))
+	;; bill@att.com
+	(goto-char start))
+      (setq pop3-read-point (point-marker))
+;; this code does not seem to work for some POP servers...
+;; and I cannot figure out why not.
+;      (goto-char (match-beginning 0))
+;      (backward-char 2)
+;      (if (not (looking-at "\r\n"))
+;	  (insert "\r\n"))
+;      (re-search-forward "\\.\r\n")
+      (goto-char (match-beginning 0))
+      (setq end (point-marker))
+      (pop3-clean-region start end)
+      (pop3-munge-message-separator start end)
+      (save-excursion
+	(set-buffer crashbuf)
+	(erase-buffer))
+      (copy-to-buffer crashbuf start end)
+      (delete-region start end)
+      )))
+
+(defun pop3-dele (process msg)
+  "Mark message-id MSG as deleted."
+  (pop3-send-command process (format "DELE %s" msg))
+  (pop3-read-response process))
+
+(defun pop3-noop (process msg)
+  "No-operation."
+  (pop3-send-command process "NOOP")
+  (pop3-read-response process))
+
+(defun pop3-last (process)
+  "Return highest accessed message-id number for the session."
+  (pop3-send-command process "LAST")
+  (let ((response (pop3-read-response process t)))
+    (string-to-int (nth 1 (pop3-string-to-list response)))
+    ))
+
+(defun pop3-rset (process)
+  "Remove all delete marks from current maildrop."
+  (pop3-send-command process "RSET")
+  (pop3-read-response process))
+
+;; UPDATE
+
+(defun pop3-quit (process)
+  "Close connection to POP3 server.
+Tell server to remove all messages marked as deleted, unlock the maildrop,
+and close the connection."
+  (pop3-send-command process "QUIT")
+  (pop3-read-response process t)
+  (if process
+      (save-excursion
+	(set-buffer (process-buffer process))
+	(goto-char (point-max))
+	(delete-process process))))
+
+;; Summary of POP3 (Post Office Protocol version 3) commands and responses
+
+;;; AUTHORIZATION STATE
+
+;; Initial TCP connection
+;; Arguments: none
+;; Restrictions: none
+;; Possible responses:
+;;  +OK [POP3 server ready]
+
+;; USER name
+;; Arguments: a server specific user-id (required)
+;; Restrictions: authorization state [after unsuccessful USER or PASS
+;; Possible responses:
+;;  +OK [valid user-id]
+;;  -ERR [invalid user-id]
+
+;; PASS string
+;; Arguments: a server/user-id specific password (required)
+;; Restrictions: authorization state, after successful USER
+;; Possible responses:
+;;  +OK [maildrop locked and ready]
+;;  -ERR [invalid password]
+;;  -ERR [unable to lock maildrop]
+
+;;; TRANSACTION STATE
+
+;; STAT
+;; Arguments: none
+;; Restrictions: transaction state
+;; Possible responses:
+;;  +OK nn mm [# of messages, size of maildrop]
+
+;; LIST [msg]
+;; Arguments: a message-id (optional)
+;; Restrictions: transaction state; msg must not be deleted
+;; Possible responses:
+;;  +OK [scan listing follows]
+;;  -ERR [no such message]
+
+;; RETR msg
+;; Arguments: a message-id (required)
+;; Restrictions: transaction state; msg must not be deleted
+;; Possible responses:
+;;  +OK [message contents follow]
+;;  -ERR [no such message]
+
+;; DELE msg
+;; Arguments: a message-id (required)
+;; Restrictions: transaction state; msg must not be deleted
+;; Possible responses:
+;;  +OK [message deleted]
+;;  -ERR [no such message]
+
+;; NOOP
+;; Arguments: none
+;; Restrictions: transaction state
+;; Possible responses:
+;;  +OK
+
+;; LAST
+;; Arguments: none
+;; Restrictions: transaction state
+;; Possible responses:
+;;  +OK nn [highest numbered message accessed]
+
+;; RSET
+;; Arguments: none
+;; Restrictions: transaction state
+;; Possible responses:
+;;  +OK [all delete marks removed]
+
+;;; UPDATE STATE
+
+;; QUIT
+;; Arguments: none
+;; Restrictions: none
+;; Possible responses:
+;;  +OK [TCP connection closed]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/gnus/score-mode.el	Wed Apr 16 22:13:18 1997 +0000
@@ -0,0 +1,109 @@
+;;; score-mode.el --- mode for editing Gnus score files
+;; Copyright (C) 1996 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Keywords: news, mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'easymenu)
+(require 'timezone)
+(eval-when-compile (require 'cl))
+
+(defvar gnus-score-mode-hook nil
+  "*Hook run in score mode buffers.")
+
+(defvar gnus-score-menu-hook nil
+  "*Hook run after creating the score mode menu.")
+
+(defvar gnus-score-edit-exit-function nil
+  "Function run on exit from the score buffer.")
+
+(defvar gnus-score-mode-map nil)
+(unless gnus-score-mode-map
+  (setq gnus-score-mode-map (copy-keymap emacs-lisp-mode-map))
+  (define-key gnus-score-mode-map "\C-c\C-c" 'gnus-score-edit-exit)
+  (define-key gnus-score-mode-map "\C-c\C-d" 'gnus-score-edit-insert-date)
+  (define-key gnus-score-mode-map "\C-c\C-p" 'gnus-score-pretty-print))
+
+;;;###autoload
+(defun gnus-score-mode ()
+  "Mode for editing Gnus score files.
+This mode is an extended emacs-lisp mode.
+
+\\{gnus-score-mode-map}"
+  (interactive)
+  (kill-all-local-variables)
+  (use-local-map gnus-score-mode-map)
+  (gnus-score-make-menu-bar)
+  (set-syntax-table emacs-lisp-mode-syntax-table)
+  (setq major-mode 'gnus-score-mode)
+  (setq mode-name "Score")
+  (lisp-mode-variables nil)
+  (make-local-variable 'gnus-score-edit-exit-function)
+  (run-hooks 'emacs-lisp-mode-hook 'gnus-score-mode-hook))
+
+(defun gnus-score-make-menu-bar ()
+  (unless (boundp 'gnus-score-menu)
+    (easy-menu-define
+     gnus-score-menu gnus-score-mode-map ""
+     '("Score"
+       ["Exit" gnus-score-edit-exit t]
+       ["Insert date" gnus-score-edit-insert-date t]
+       ["Format" gnus-score-pretty-print t]))
+    (run-hooks 'gnus-score-menu-hook)))
+
+(defun gnus-score-edit-insert-date ()
+  "Insert date in numerical format."
+  (interactive)
+  (princ (gnus-score-day-number (current-time)) (current-buffer)))
+
+(defun gnus-score-pretty-print ()
+  "Format the current score file."
+  (interactive)
+  (goto-char (point-min))
+  (let ((form (read (current-buffer))))
+    (erase-buffer)
+    (pp form (current-buffer)))
+  (goto-char (point-min)))
+
+(defun gnus-score-edit-exit ()
+  "Stop editing the score file."
+  (interactive)
+  (unless (file-exists-p (file-name-directory (buffer-file-name)))
+    (make-directory (file-name-directory (buffer-file-name)) t))
+  (save-buffer)
+  (bury-buffer (current-buffer))
+  (let ((buf (current-buffer)))
+    (when gnus-score-edit-exit-function
+      (funcall gnus-score-edit-exit-function))
+    (when (eq buf (current-buffer))
+      (switch-to-buffer (other-buffer (current-buffer))))))
+
+(defun gnus-score-day-number (time)
+  (let ((dat (decode-time time)))
+    (timezone-absolute-from-gregorian
+     (nth 4 dat) (nth 3 dat) (nth 5 dat))))
+
+(provide 'score-mode)
+
+;;; score-mode.el ends here