diff lisp/mail/rnews.el @ 35:63b375f17a65

Initial revision
author Jim Blandy <jimb@redhat.com>
date Tue, 31 Oct 1989 15:59:53 +0000
parents
children 4cd7543be581
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/mail/rnews.el	Tue Oct 31 15:59:53 1989 +0000
@@ -0,0 +1,979 @@
+;;; USENET news reader for gnu emacs
+;; Copyright (C) 1985, 1986, 1987 Free Software Foundation, Inc.
+
+;; 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 1, 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.
+
+;; Created Sun Mar 10,1985 at 21:35:01 ads and sundar@hernes.ai.mit.edu
+;; Should do the point pdl stuff sometime
+;; finito except pdl.... Sat Mar 16,1985 at 06:43:44
+;; lets keep the summary stuff out until we get it working ..
+;;	sundar@hermes.ai.mit.edu Wed Apr 10,1985 at 16:32:06
+;; hack slash maim. mly@prep.ai.mit.edu Thu 18 Apr, 1985 06:11:14
+;; modified to correct reentrance bug, to not bother with groups that
+;;   received no new traffic since last read completely, to find out
+;;   what traffic a group has available much more quickly when
+;;   possible, to do some completing reads for group names - should
+;;   be much faster...
+;;	KING@KESTREL.arpa, Thu Mar 13 09:03:28 1986
+;; made news-{next,previous}-group skip groups with no new messages; and
+;; added checking for unsubscribed groups to news-add-news-group
+;;	tower@prep.ai.mit.edu Jul 18 1986
+;; bound rmail-output to C-o; and changed header-field commands binding to
+;; agree with the new C-c C-f usage in sendmail
+;; 	tower@prep Sep  3 1986
+;; added news-rotate-buffer-body
+;;	tower@prep Oct 17 1986
+;; made messages more user friendly, cleanuped news-inews
+;; move posting and mail code to new file rnewpost.el
+;;	tower@prep Oct 29 1986
+;; added caesar-region, rename news-caesar-buffer-body, hacked accordingly
+;;	tower@prep Nov 21 1986
+;; added (provide 'rnews)	tower@prep 22 Apr 87
+(provide 'rnews)
+(require 'mail-utils)
+
+(autoload 'rmail-output "rmailout"
+  "Append this message to Unix mail file named FILE-NAME."
+  t)
+
+(autoload 'news-reply "rnewspost"
+  "Compose and post a reply to the current article on USENET.
+While composing the reply, use \\[mail-yank-original] to yank the original
+message into it."
+  t)
+
+(autoload 'news-mail-other-window "rnewspost"
+  "Send mail in another window.
+While composing the message, use \\[mail-yank-original] to yank the
+original message into it."
+  t)
+
+(autoload 'news-post-news "rnewspost"
+  "Begin editing a new USENET news article to be posted."
+  t)
+
+(autoload 'news-mail-reply "rnewspost"
+  "Mail a reply to the author of the current article.
+While composing the reply, use \\[mail-yank-original] to yank the original
+message into it."
+  t)
+
+(defvar news-group-hook-alist nil
+  "Alist of (GROUP-REGEXP . HOOK) pairs.
+Just before displaying a message, each HOOK is called
+if its GROUP-REGEXP matches the current newsgroup name.")
+
+(defvar rmail-last-file (expand-file-name "~/mbox.news"))
+
+;Now in paths.el.
+;(defvar news-path "/usr/spool/news/"
+;  "The root directory below which all news files are stored.")
+
+(defvar news-startup-file "$HOME/.newsrc" "Contains ~/.newsrc")
+(defvar news-certification-file "$HOME/.news-dates" "Contains ~/.news-dates")
+
+;; random headers that we decide to ignore.
+(defvar news-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:"
+  "All random fields within the header of a message.")
+
+(defvar news-mode-map nil)
+(defvar news-read-first-time-p t)
+;; Contains the (dotified) news groups of which you are a member. 
+(defvar news-user-group-list nil)
+
+(defvar news-current-news-group nil)
+(defvar news-current-group-begin nil)
+(defvar news-current-group-end  nil)
+(defvar news-current-certifications nil
+   	"An assoc list of a group name and the time at which it is
+known that the group had no new traffic")
+(defvar news-current-certifiable nil
+	"The time when the directory we are now working on was written")
+
+(defvar news-message-filter nil
+  "User specifiable filter function that will be called during
+formatting of the news file")
+
+;(defvar news-mode-group-string "Starting-Up"
+;  "Mode line group name info is held in this variable")
+(defvar news-list-of-files nil
+  "Global variable in which we store the list of files
+associated with the current newsgroup")
+(defvar news-list-of-files-possibly-bogus nil
+  "variable indicating we only are guessing at which files are available.
+Not currently used.")
+
+;; association list in which we store lists of the form
+;; (pointified-group-name (first last old-last))
+(defvar news-group-article-assoc nil)
+  
+(defvar news-current-message-number 0 "Displayed Article Number")
+(defvar news-total-current-group 0 "Total no of messages in group")
+
+(defvar news-unsubscribe-groups ())
+(defvar news-point-pdl () "List of visited news messages.")
+(defvar news-no-jumps-p t)
+(defvar news-buffer () "Buffer into which news files are read.")
+
+(defmacro news-push (item ref)
+  (list 'setq ref (list 'cons item ref)))
+
+(defmacro news-cadr (x) (list 'car (list 'cdr x)))
+(defmacro news-cdar (x) (list 'cdr (list 'car x)))
+(defmacro news-caddr (x) (list 'car (list 'cdr (list 'cdr x))))
+(defmacro news-cadar (x) (list 'car (list 'cdr (list 'car x))))
+(defmacro news-caadr (x) (list 'car (list 'car (list 'cdr x))))
+(defmacro news-cdadr (x) (list 'cdr (list 'car (list 'cdr x))))
+
+(defmacro news-wins (pfx index)
+  (` (file-exists-p (concat (, pfx) "/" (int-to-string (, index))))))
+
+(defvar news-max-plausible-gap 2
+	"* In an rnews directory, the maximum possible gap size.
+A gap is a sequence of missing messages between two messages that exist.
+An empty file does not contribute to a gap -- it ends one.")
+
+(defun news-find-first-and-last (prefix base)
+  (and (news-wins prefix base)
+       (cons (news-find-first-or-last prefix base -1)
+	     (news-find-first-or-last prefix base 1))))
+
+(defmacro news-/ (a1 a2)
+;; a form of / that guarantees that (/ -1 2) = 0
+  (if (zerop (/ -1 2))
+      (` (/ (, a1) (, a2)))
+    (` (if (< (, a1) 0)
+	   (- (/ (- (, a1)) (, a2)))
+	 (/ (, a1) (, a2))))))
+
+(defun news-find-first-or-last (pfx base dirn)
+  ;; first use powers of two to find a plausible ceiling
+  (let ((original-dir dirn))
+    (while (news-wins pfx (+ base dirn))
+      (setq dirn (* dirn 2)))
+    (setq dirn (news-/ dirn 2))
+    ;; Then use a binary search to find the high water mark
+    (let ((offset (news-/ dirn 2)))
+      (while (/= offset 0)
+	(if (news-wins pfx (+ base dirn offset))
+	    (setq dirn (+ dirn offset)))
+	(setq offset (news-/ offset 2))))
+    ;; If this high-water mark is bogus, recurse.
+    (let ((offset (* news-max-plausible-gap original-dir)))
+      (while (and (/= offset 0) (not (news-wins pfx (+ base dirn offset))))
+	(setq offset (- offset original-dir)))
+      (if (= offset 0)
+	  (+ base dirn)
+	(news-find-first-or-last pfx (+ base dirn offset) original-dir)))))
+
+(defun rnews ()
+"Read USENET news for groups for which you are a member and add or
+delete groups.
+You can reply to articles posted and send articles to any group.
+
+Type \\[describe-mode] once reading news to get a list of rnews commands."
+  (interactive)
+  (let ((last-buffer (buffer-name)))
+    (make-local-variable 'rmail-last-file)
+    (switch-to-buffer (setq news-buffer (get-buffer-create "*news*")))
+    (news-mode)
+    (setq news-buffer-save last-buffer)
+    (setq buffer-read-only nil)
+    (erase-buffer)
+    (setq buffer-read-only t)
+    (set-buffer-modified-p t)
+    (sit-for 0)
+    (message "Getting new USENET news...")
+    (news-set-mode-line)
+    (news-get-certifications)
+    (news-get-new-news)))
+
+(defun news-group-certification (group)
+  (cdr-safe (assoc group news-current-certifications)))
+
+
+(defun news-set-current-certifiable ()
+  ;; Record the date that corresponds to the directory you are about to check
+  (let ((file (concat news-path
+		      (string-subst-char ?/ ?. news-current-news-group))))
+    (setq news-current-certifiable
+	  (nth 5 (file-attributes
+		  (or (file-symlink-p file) file))))))
+
+(defun news-get-certifications ()
+  ;; Read the certified-read file from last session
+  (save-excursion
+    (save-window-excursion
+      (setq news-current-certifications
+	    (car-safe
+	     (condition-case var
+		 (let*
+		     ((file (substitute-in-file-name news-certification-file))
+		      (buf (find-file-noselect file)))
+		   (and (file-exists-p file)
+			(progn
+			  (switch-to-buffer buf 'norecord)
+			  (unwind-protect
+			      (read-from-string (buffer-string))
+			    (kill-buffer buf)))))
+	       (error nil)))))))
+
+(defun news-write-certifications ()
+  ;; Write a certification file.
+  ;; This is an assoc list of group names with doubletons that represent
+  ;; mod times of the directory when group is read completely.
+  (save-excursion
+    (save-window-excursion
+      (with-output-to-temp-buffer
+	  "*CeRtIfIcAtIoNs*"
+	  (print news-current-certifications))
+      (let ((buf (get-buffer "*CeRtIfIcAtIoNs*")))
+	(switch-to-buffer buf)
+	(write-file (substitute-in-file-name news-certification-file))
+	(kill-buffer buf)))))
+
+(defun news-set-current-group-certification ()
+  (let ((cgc (assoc news-current-news-group news-current-certifications)))
+    (if cgc (setcdr cgc news-current-certifiable)
+      (news-push (cons news-current-news-group news-current-certifiable)
+		 news-current-certifications))))
+
+(defun news-set-minor-modes ()
+  "Creates a minor mode list that has group name, total articles,
+and attribute for current article."
+  (setq news-minor-modes (list (cons 'foo
+				     (concat news-current-message-number
+					     "/"
+					     news-total-current-group
+					     (news-get-attribute-string)))))
+  ;; Detect Emacs versions 18.16 and up, which display
+  ;; directly from news-minor-modes by using a list for mode-name.
+  (or (boundp 'minor-mode-alist)
+      (setq minor-modes news-minor-modes)))
+
+(defun news-set-message-counters ()
+  "Scan through current news-groups filelist to figure out how many messages
+are there. Set counters for use with minor mode display."
+    (if (null news-list-of-files)
+	(setq news-current-message-number 0)))
+
+(if news-mode-map
+    nil
+  (setq news-mode-map (make-keymap))
+  (suppress-keymap news-mode-map)
+  (define-key news-mode-map "." 'beginning-of-buffer)
+  (define-key news-mode-map " " 'scroll-up)
+  (define-key news-mode-map "\177" 'scroll-down)
+  (define-key news-mode-map "n" 'news-next-message)
+  (define-key news-mode-map "c" 'news-make-link-to-message)
+  (define-key news-mode-map "p" 'news-previous-message)
+  (define-key news-mode-map "j" 'news-goto-message)
+  (define-key news-mode-map "q" 'news-exit)
+  (define-key news-mode-map "e" 'news-exit)
+  (define-key news-mode-map "\ej" 'news-goto-news-group)
+  (define-key news-mode-map "\en" 'news-next-group)
+  (define-key news-mode-map "\ep" 'news-previous-group)
+  (define-key news-mode-map "l" 'news-list-news-groups)
+  (define-key news-mode-map "?" 'describe-mode)
+  (define-key news-mode-map "g" 'news-get-new-news)
+  (define-key news-mode-map "f" 'news-reply)
+  (define-key news-mode-map "m" 'news-mail-other-window)
+  (define-key news-mode-map "a" 'news-post-news)
+  (define-key news-mode-map "r" 'news-mail-reply)
+  (define-key news-mode-map "o" 'news-save-item-in-file)
+  (define-key news-mode-map "\C-o" 'rmail-output)
+  (define-key news-mode-map "t" 'news-show-all-headers)
+  (define-key news-mode-map "x" 'news-force-update)
+  (define-key news-mode-map "A" 'news-add-news-group)
+  (define-key news-mode-map "u" 'news-unsubscribe-current-group)
+  (define-key news-mode-map "U" 'news-unsubscribe-group)
+  (define-key news-mode-map "\C-c\C-r" 'news-caesar-buffer-body))
+
+(defun news-mode ()
+  "News Mode is used by M-x rnews for reading USENET Newsgroups articles.
+New readers can find additional help in newsgroup: news.announce.newusers .
+All normal editing commands are turned off.
+Instead, these commands are available:
+
+.	move point to front of this news article (same as Meta-<).
+Space	scroll to next screen of this news article.
+Delete  scroll down previous page of this news article.
+n	move to next news article, possibly next group.
+p	move to previous news article, possibly previous group.
+j	jump to news article specified by numeric position.
+M-j     jump to news group.
+M-n     goto next news group.
+M-p     goto previous news group.
+l       list all the news groups with current status.
+?       print this help message.
+C-c C-r caesar rotate all letters by 13 places in the article's body (rot13).
+g       get new USENET news.
+f       post a reply article to USENET.
+a       post an original news article.
+A       add a newsgroup. 
+o	save the current article in the named file (append if file exists).
+C-o	output this message to a Unix-format mail file (append it).
+c       \"copy\" (actually link) current or prefix-arg msg to file.
+	warning: target directory and message file must be on same device
+		(UNIX magic)
+t       show all the headers this news article originally had.
+q	quit reading news after updating .newsrc file.
+e	exit updating .newsrc file.
+m	mail a news article.  Same as C-x 4 m.
+x       update last message seen to be the current message.
+r	mail a reply to this news article.  Like m but initializes some fields.
+u       unsubscribe from current newsgroup.
+U       unsubscribe from specified newsgroup."
+  (interactive)
+  (kill-all-local-variables)
+  (make-local-variable 'news-read-first-time-p)
+  (setq news-read-first-time-p t)
+  (make-local-variable 'news-current-news-group)
+;  (setq news-current-news-group "??")
+  (make-local-variable 'news-current-group-begin)
+  (setq news-current-group-begin 0)
+  (make-local-variable 'news-current-message-number)
+  (setq news-current-message-number 0)
+  (make-local-variable 'news-total-current-group)
+  (make-local-variable 'news-buffer-save)
+  (make-local-variable 'version-control)
+  (setq version-control 'never)
+  (make-local-variable 'news-point-pdl)
+;  This breaks it.  I don't have time to figure out why. -- RMS
+;  (make-local-variable 'news-group-article-assoc)
+  (setq major-mode 'news-mode)
+  (if (boundp 'minor-mode-alist)
+      ;; Emacs versions 18.16 and up.
+      (setq mode-name '("NEWS" news-minor-modes))
+    ;; Earlier versions display minor-modes via a special mechanism.
+    (setq mode-name "NEWS"))
+  (news-set-mode-line)
+  (set-syntax-table text-mode-syntax-table)
+  (use-local-map news-mode-map)
+  (setq local-abbrev-table text-mode-abbrev-table)
+  (run-hooks 'news-mode-hook))
+
+(defun string-subst-char (new old string)
+  (let (index)
+    (setq old (regexp-quote (char-to-string old))
+	  string (substring string 0))
+    (while (setq index (string-match old string))
+      (aset string index new)))
+  string)
+
+;; update read message number
+(defmacro news-update-message-read (ngroup nno)
+  (list 'setcar
+	(list 'news-cdadr
+	      (list 'assoc ngroup 'news-group-article-assoc))
+	nno))
+
+(defun news-parse-range (number-string)
+  "Parse string representing range of numbers of he form <a>-<b>
+to a list (a . b)"
+  (let ((n (string-match "-" number-string)))
+    (if n
+	(cons (string-to-int (substring number-string 0 n))
+	      (string-to-int (substring number-string (1+ n))))
+      (setq n (string-to-int number-string))
+      (cons n n))))
+
+;(defun is-in (elt lis)
+;  (catch 'foo
+;    (while lis
+;      (if (equal (car lis) elt)
+;	  (throw 'foo t)
+;	(setq lis (cdr lis))))))
+
+(defun news-get-new-news ()
+  "Get new USENET news, if there is any for the current user."
+  (interactive)
+  (if (not (null news-user-group-list))
+      (news-update-newsrc-file))
+  (setq news-group-article-assoc ())
+  (setq news-user-group-list ())
+  (message "Looking up %s file..." news-startup-file)
+  (let ((file (substitute-in-file-name news-startup-file))
+	(temp-user-groups ()))
+    (save-excursion
+      (let ((newsrcbuf (find-file-noselect file))
+	    start end endofline tem)
+	(set-buffer newsrcbuf)
+	(goto-char 0)
+	(while (search-forward ": " nil t)
+	  (setq end (point))
+	  (beginning-of-line)
+	  (setq start (point))
+	  (end-of-line)
+	  (setq endofline (point))
+	  (setq tem (buffer-substring start (- end 2)))
+	  (let ((range (news-parse-range
+			(buffer-substring end endofline))))
+	    (if (assoc tem news-group-article-assoc)
+		(message "You are subscribed twice to %s; I ignore second"
+			 tem)	      
+	      (setq temp-user-groups (cons tem temp-user-groups)
+		    news-group-article-assoc
+		    (cons (list tem (list (car range)
+					  (cdr range)
+					  (cdr range)))
+			  news-group-article-assoc)))))
+	(kill-buffer newsrcbuf)))      
+    (setq temp-user-groups (nreverse temp-user-groups))
+    (message "Prefrobnicating...")
+    (switch-to-buffer news-buffer)
+    (setq news-user-group-list temp-user-groups)
+    (while (and temp-user-groups
+		(not (news-read-files-into-buffer
+		      (car temp-user-groups) nil)))
+      (setq temp-user-groups (cdr temp-user-groups)))
+    (if (null temp-user-groups)
+	(message "No news is good news.")
+      (message ""))))
+
+(defun news-list-news-groups ()
+  "Display all the news groups to which you belong."
+  (interactive)
+  (with-output-to-temp-buffer "*Newsgroups*"
+    (save-excursion
+      (set-buffer standard-output)
+      (insert
+	"News Group        Msg No.       News Group        Msg No.\n")
+      (insert
+	"-------------------------       -------------------------\n")
+      (let ((temp news-user-group-list)
+	    (flag nil))
+	(while temp
+	  (let ((item (assoc (car temp) news-group-article-assoc)))
+	    (insert (car item))
+	    (indent-to (if flag 52 20))
+	    (insert (int-to-string (news-cadr (news-cadr item))))
+	    (if flag
+		(insert "\n")
+	      (indent-to 33))
+	    (setq temp (cdr temp) flag (not flag))))))))
+
+;; Mode line hack
+(defun news-set-mode-line ()
+  "Set mode line string to something useful."
+  (setq mode-line-process
+	(concat " "
+		(if (integerp news-current-message-number)
+		    (int-to-string news-current-message-number)
+		 "??")
+		"/"
+		(if (integerp news-current-group-end)
+		    (int-to-string news-current-group-end)
+		  news-current-group-end)))
+  (setq mode-line-buffer-identification
+	(concat "NEWS: "
+		news-current-news-group
+		;; Enough spaces to pad group name to 17 positions.
+		(substring "                 "
+			   0 (max 0 (- 17 (length news-current-news-group))))))
+  (set-buffer-modified-p t)
+  (sit-for 0))
+
+(defun news-goto-news-group (gp)
+  "Takes a string and goes to that news group."
+  (interactive (list (completing-read "NewsGroup: "
+				      news-group-article-assoc)))
+  (message "Jumping to news group %s..." gp)
+  (news-select-news-group gp)
+  (message "Jumping to news group %s... done." gp))
+
+(defun news-select-news-group (gp)
+  (let ((grp (assoc gp news-group-article-assoc)))
+    (if (null grp)
+ 	(error "Group %s not subscribed to" gp)
+      (progn
+	(news-update-message-read news-current-news-group
+				  (news-cdar news-point-pdl))
+	(news-read-files-into-buffer  (car grp) nil)
+	(news-set-mode-line)))))
+
+(defun news-goto-message (arg)
+  "Goes to the article ARG in current newsgroup."
+  (interactive "p")
+  (if (null current-prefix-arg)
+      (setq arg (read-no-blanks-input "Go to article: " "")))
+  (news-select-message arg))
+
+(defun news-select-message (arg)
+  (if (stringp arg) (setq arg (string-to-int arg)))
+  (let ((file (concat news-path
+		      (string-subst-char ?/ ?. news-current-news-group)
+		      "/" arg)))
+    (if (file-exists-p file)
+	(let ((buffer-read-only ()))
+	  (if (= arg 
+		 (or (news-cadr (memq (news-cdar news-point-pdl) news-list-of-files))
+		     0))
+	      (setcdr (car news-point-pdl) arg))
+	  (setq news-current-message-number arg)
+	  (news-read-in-file file)
+	  (news-set-mode-line))
+      (error "Article %d nonexistent" arg))))
+
+(defun news-force-update ()
+  "updates the position of last article read in the current news group"
+  (interactive)
+  (setcdr (car news-point-pdl) news-current-message-number)
+  (message "Updated to %d" news-current-message-number))
+
+(defun news-next-message (arg)
+  "Move ARG messages forward within one newsgroup.
+Negative ARG moves backward.
+If ARG is 1 or -1, moves to next or previous newsgroup if at end."
+  (interactive "p")
+  (let ((no (+ arg news-current-message-number)))
+    (if (or (< no news-current-group-begin) 
+	    (> no news-current-group-end))
+	(cond ((= arg 1)
+	       (news-set-current-group-certification)
+	       (news-next-group))
+	      ((= arg -1)
+	       (news-previous-group))
+	      (t (error "Article out of range")))
+      (let ((plist (news-get-motion-lists
+		     news-current-message-number
+		     news-list-of-files)))
+	(if (< arg 0)
+	    (news-select-message (nth (1- (- arg)) (car (cdr plist))))
+	  (news-select-message (nth (1- arg) (car plist))))))))
+
+(defun news-previous-message (arg)
+  "Move ARG messages backward in current newsgroup.
+With no arg or arg of 1, move one message
+and move to previous newsgroup if at beginning.
+A negative ARG means move forward."
+  (interactive "p")
+  (news-next-message (- arg)))
+
+(defun news-move-to-group (arg)
+  "Given arg move forward or backward to a new newsgroup."
+  (let ((cg news-current-news-group))
+    (let ((plist (news-get-motion-lists cg news-user-group-list))
+	  ngrp)
+      (if (< arg 0)
+	  (or (setq ngrp (nth (1- (- arg)) (news-cadr plist)))
+	      (error "No previous news groups"))
+	(or (setq ngrp (nth arg (car plist)))
+	    (error "No more news groups")))
+      (news-select-news-group ngrp))))
+
+(defun news-next-group ()
+  "Moves to the next user group."
+  (interactive)
+;  (message "Moving to next group...")
+  (news-move-to-group 0)
+  (while (null news-list-of-files)
+    (news-move-to-group 0)))
+;  (message "Moving to next group... done.")
+
+(defun news-previous-group ()
+  "Moves to the previous user group."
+  (interactive)
+;  (message "Moving to previous group...")
+  (news-move-to-group -1)
+  (while (null news-list-of-files)
+    (news-move-to-group -1)))
+;  (message "Moving to previous group... done.")
+
+(defun news-get-motion-lists (arg listy)
+  "Given a msgnumber/group this will return a list of two lists;
+one for moving forward and one for moving backward."
+  (let ((temp listy)
+	(result ()))
+    (catch 'out
+      (while temp
+	(if (equal (car temp) arg)
+	    (throw 'out (cons (cdr temp) (list result)))
+	  (setq result (nconc (list (car temp)) result))
+	  (setq temp (cdr temp)))))))
+
+;; miscellaneous io routines
+(defun news-read-in-file (filename)
+  (erase-buffer)
+  (let ((start (point)))
+  (insert-file-contents filename)
+  (news-convert-format)
+  ;; Run each hook that applies to the current newsgroup.
+  (let ((hooks news-group-hook-alist))
+    (while hooks
+      (goto-char start)
+      (if (string-match (car (car hooks)) news-group-name)
+	  (funcall (cdr (car hooks))))
+      (setq hooks (cdr hooks))))
+  (goto-char start)
+  (forward-line 1)
+  (if (eobp)
+      (message "(Empty file?)")
+    (goto-char start))))
+
+(defun news-convert-format ()
+  (save-excursion
+    (save-restriction
+      (let* ((start (point))
+	     (end (condition-case ()
+		      (progn (search-forward "\n\n") (point))
+		    (error nil)))
+	     has-from has-date)
+       (cond (end
+	      (narrow-to-region start end)
+	      (goto-char start)
+	      (setq has-from (search-forward "\nFrom:" nil t))
+	      (cond ((and (not has-from) has-date)
+		     (goto-char start)
+		     (search-forward "\nDate:")
+		     (beginning-of-line)
+		     (kill-line) (kill-line)))
+	      (news-delete-headers start)
+	      (goto-char start)))))))
+
+(defun news-show-all-headers ()
+  "Redisplay current news item with all original headers"
+  (interactive)
+  (let (news-ignored-headers
+	(buffer-read-only ()))
+    (erase-buffer)
+    (news-set-mode-line)
+    (news-read-in-file
+     (concat news-path
+	     (string-subst-char ?/ ?. news-current-news-group)
+	     "/" (int-to-string news-current-message-number)))))
+
+(defun news-delete-headers (pos)
+  (goto-char pos)
+  (and (stringp news-ignored-headers)
+       (while (re-search-forward news-ignored-headers nil t)
+	 (beginning-of-line)
+	 (delete-region (point)
+			(progn (re-search-forward "\n[^ \t]")
+			       (forward-char -1)
+			       (point))))))
+
+(defun news-exit ()
+  "Quit news reading session and update the .newsrc file."
+  (interactive)
+  (if (y-or-n-p "Do you really wanna quit reading news ? ")
+      (progn (message "Updating %s..." news-startup-file)
+	     (news-update-newsrc-file)
+	     (news-write-certifications)
+	     (message "Updating %s... done" news-startup-file)
+	     (message "Now do some real work")
+	     (and (fboundp 'bury-buffer) (bury-buffer (current-buffer)))
+	     (switch-to-buffer news-buffer-save)
+	     (setq news-user-group-list ()))
+    (message "")))
+
+(defun news-update-newsrc-file ()
+  "Updates the .newsrc file in the users home dir."
+  (let ((newsrcbuf (find-file-noselect
+		     (substitute-in-file-name news-startup-file)))
+	(tem news-user-group-list)
+	group)
+    (save-excursion
+      (if (not (null news-current-news-group))
+	  (news-update-message-read news-current-news-group
+				    (news-cdar news-point-pdl)))
+      (set-buffer newsrcbuf)
+      (while tem
+	(setq group (assoc (car tem) news-group-article-assoc))
+	(if (= (news-cadr (news-cadr group)) (news-caddr (news-cadr group)))
+	    nil
+	  (goto-char 0)
+	  (if (search-forward (concat (car group) ": ") nil t)
+	      (kill-line nil)
+	    (insert (car group) ": \n") (backward-char 1))
+	  (insert (int-to-string (car (news-cadr group))) "-"
+		  (int-to-string (news-cadr (news-cadr group)))))
+	(setq tem (cdr tem)))
+     (while news-unsubscribe-groups
+       (setq group (assoc (car news-unsubscribe-groups)
+			  news-group-article-assoc))
+       (goto-char 0)
+       (if (search-forward (concat (car group) ": ") nil t)
+	   (progn
+	      (backward-char 2)
+	      (kill-line nil)
+	      (insert "! " (int-to-string (car (news-cadr group)))
+		      "-" (int-to-string (news-cadr (news-cadr group))))))
+       (setq news-unsubscribe-groups (cdr news-unsubscribe-groups)))
+     (save-buffer)
+     (kill-buffer (current-buffer)))))
+
+
+(defun news-unsubscribe-group (group)
+  "Removes you from newgroup GROUP."
+  (interactive (list (completing-read  "Unsubscribe from group: "
+				      news-group-article-assoc)))
+  (news-unsubscribe-internal group))
+
+(defun news-unsubscribe-current-group ()
+  "Removes you from the newsgroup you are now reading."
+  (interactive)
+  (if (y-or-n-p "Do you really want to unsubscribe from this group ? ")
+      (news-unsubscribe-internal news-current-news-group)))
+
+(defun news-unsubscribe-internal (group)
+  (let ((tem (assoc group news-group-article-assoc)))
+    (if tem
+	(progn
+	  (setq news-unsubscribe-groups (cons group news-unsubscribe-groups))
+	  (news-update-message-read group (news-cdar news-point-pdl))
+	  (if (equal group news-current-news-group)
+	      (news-next-group))
+	  (message ""))
+      (error "Not subscribed to group: %s" group))))
+
+(defun news-save-item-in-file (file)
+  "Save the current article that is being read by appending to a file."
+  (interactive "FSave item in file: ")
+  (append-to-file (point-min) (point-max) file))
+
+(defun news-get-pruned-list-of-files (gp-list end-file-no)
+  "Given a news group it finds all files in the news group.
+The arg must be in slashified format.
+Using ls was found to be too slow in a previous version."
+  (let
+      ((answer
+	(and
+	 (not (and end-file-no
+		   (equal (news-set-current-certifiable)
+		     (news-group-certification gp-list))
+		   (setq news-list-of-files nil
+			 news-list-of-files-possibly-bogus t)))
+	 (let* ((file-directory (concat news-path
+					(string-subst-char ?/ ?. gp-list)))
+		tem
+		(last-winner
+		 (and end-file-no
+		      (news-wins file-directory end-file-no)
+		      (news-find-first-or-last file-directory end-file-no 1))))
+	   (setq news-list-of-files-possibly-bogus t news-list-of-files nil)
+	   (if last-winner
+	       (progn
+		 (setq news-list-of-files-possibly-bogus t
+		       news-current-group-end last-winner)
+		 (while (> last-winner end-file-no)
+		   (news-push last-winner news-list-of-files)
+		   (setq last-winner (1- last-winner)))
+		 news-list-of-files)
+	     (if (or (not (file-directory-p file-directory))
+		     (not (file-readable-p file-directory)))
+		 nil
+	       (setq news-list-of-files
+		     (condition-case error
+			 (directory-files file-directory)
+		       (file-error
+			(if (string= (nth 2 error) "permission denied")
+			    (message "Newsgroup %s is read-protected"
+				     gp-list)
+			  (signal 'file-error (cdr error)))
+			nil)))
+	       (setq tem news-list-of-files)
+	       (while tem
+		 (if (or (not (string-match "^[0-9]*$" (car tem)))
+			 ;; dont get confused by directories that look like numbers
+			 (file-directory-p
+			  (concat file-directory "/" (car tem)))
+			 (<= (string-to-int (car tem)) end-file-no))
+		     (setq news-list-of-files
+			   (delq (car tem) news-list-of-files)))
+		 (setq tem (cdr tem)))
+	       (if (null news-list-of-files)
+		   (progn (setq news-current-group-end 0)
+			  nil)
+		 (setq news-list-of-files
+		       (mapcar 'string-to-int news-list-of-files))
+		 (setq news-list-of-files (sort news-list-of-files '<))
+		 (setq news-current-group-end
+		       (elt news-list-of-files
+			    (1- (length news-list-of-files))))
+		 news-list-of-files)))))))
+    (or answer (progn (news-set-current-group-certification) nil))))
+
+(defun news-read-files-into-buffer (group reversep)
+  (let* ((files-start-end (news-cadr (assoc group news-group-article-assoc)))
+	 (start-file-no (car files-start-end))
+	 (end-file-no (news-cadr files-start-end))
+	 (buffer-read-only nil))
+    (setq news-current-news-group group)
+    (setq news-current-message-number nil)
+    (setq news-current-group-end nil)
+    (news-set-mode-line)
+    (news-get-pruned-list-of-files group end-file-no)
+    (news-set-mode-line)
+    ;; @@ should be a lot smarter than this if we have to move
+    ;; @@ around correctly.
+    (setq news-point-pdl (list (cons (car files-start-end)
+				     (news-cadr files-start-end))))
+    (if (null news-list-of-files)
+	(progn (erase-buffer)
+	       (setq news-current-group-end end-file-no)
+	       (setq news-current-group-begin end-file-no)
+	       (setq news-current-message-number end-file-no)
+	       (news-set-mode-line)
+;	       (message "No new articles in " group " group.")
+	       nil)
+      (setq news-current-group-begin (car news-list-of-files))
+      (if reversep
+	  (setq news-current-message-number news-current-group-end)
+	(if (> (car news-list-of-files) end-file-no)
+	    (setcdr (car news-point-pdl) (car news-list-of-files)))
+	(setq news-current-message-number news-current-group-begin))
+      (news-set-message-counters)
+      (news-set-mode-line)
+      (news-read-in-file (concat news-path
+				 (string-subst-char ?/ ?. group)
+				 "/"
+				 (int-to-string
+				   news-current-message-number)))
+      (news-set-message-counters)
+      (news-set-mode-line)
+      t)))
+
+(defun news-add-news-group (gp)
+  "Resubscribe to or add a USENET news group named GROUP (a string)."
+; @@ (completing-read ...)
+; @@ could be based on news library file ../active (slightly facist)
+; @@ or (expensive to compute) all directories under the news spool directory
+  (interactive "sAdd news group: ")
+  (let ((file-dir (concat news-path (string-subst-char ?/ ?. gp))))
+    (save-excursion
+      (if (null (assoc gp news-group-article-assoc))
+	  (let ((newsrcbuf (find-file-noselect
+			    (substitute-in-file-name news-startup-file))))
+	    (if (file-directory-p file-dir)
+		(progn
+		  (switch-to-buffer newsrcbuf)
+		  (goto-char 0)
+		  (if (search-forward (concat gp "! ") nil t)
+		      (progn
+			(message "Re-subscribing to group %s." gp)
+			;;@@ news-unsubscribe-groups isn't being used
+			;;(setq news-unsubscribe-groups
+			;;    (delq gp news-unsubscribe-groups))
+			(backward-char 2)
+			(delete-char 1)
+			(insert ":"))
+		    (progn
+		      (message
+		       "Added %s to your list of newsgroups." gp)
+		      (end-of-buffer)
+		      (insert gp ": 1-1\n")))
+		  (search-backward gp nil t)
+		  (let (start end endofline tem)
+		    (search-forward ": " nil t)
+		    (setq end (point))
+		    (beginning-of-line)
+		    (setq start (point))
+		    (end-of-line)
+		    (setq endofline (point))
+		    (setq tem (buffer-substring start (- end 2)))
+		    (let ((range (news-parse-range
+				  (buffer-substring end endofline))))
+		      (setq news-group-article-assoc
+			    (cons (list tem (list (car range)
+						  (cdr range)
+						  (cdr range)))
+				  news-group-article-assoc))))
+		  (save-buffer)
+		  (kill-buffer (current-buffer)))
+	      (message "Newsgroup %s doesn't exist." gp)))
+	(message "Already subscribed to group %s." gp)))))
+
+(defun news-make-link-to-message (number newname)
+	"Forges a link to an rnews message numbered number (current if no arg)
+Good for hanging on to a message that might or might not be
+automatically deleted."
+  (interactive "P
+FName to link to message: ")
+  (add-name-to-file
+   (concat news-path
+	   (string-subst-char ?/ ?. news-current-news-group)
+	   "/" (if number
+		   (prefix-numeric-value number)
+		 news-current-message-number))
+   newname))
+
+;;; caesar-region written by phr@prep.ai.mit.edu  Nov 86
+;;; modified by tower@prep Nov 86
+(defun caesar-region (&optional n)
+  "Caesar rotation of region by N, default 13, for decrypting netnews."
+  (interactive (if current-prefix-arg	; Was there a prefix arg?
+		   (list (prefix-numeric-value current-prefix-arg))
+		 (list nil)))
+  (cond ((not (numberp n)) (setq n 13))
+	((< n 0) (setq n (- 26 (% (- n) 26))))
+	(t (setq n (% n 26))))		;canonicalize N
+  (if (not (zerop n))		; no action needed for a rot of 0
+      (progn
+	(if (or (not (boundp 'caesar-translate-table))
+		(/= (aref caesar-translate-table ?a) (+ ?a n)))
+	    (let ((i 0) (lower "abcdefghijklmnopqrstuvwxyz") upper)
+	      (message "Building caesar-translate-table...")
+	      (setq caesar-translate-table (make-vector 256 0))
+	      (while (< i 256)
+		(aset caesar-translate-table i i)
+		(setq i (1+ i)))
+	      (setq lower (concat lower lower) upper (upcase lower) i 0)
+	      (while (< i 26)
+		(aset caesar-translate-table (+ ?a i) (aref lower (+ i n)))
+		(aset caesar-translate-table (+ ?A i) (aref upper (+ i n)))
+		(setq i (1+ i)))
+	      (message "Building caesar-translate-table... done")))
+	(let ((from (region-beginning))
+	      (to (region-end))
+	      (i 0) str len)
+	  (setq str (buffer-substring from to))
+	  (setq len (length str))
+	  (while (< i len)
+	    (aset str i (aref caesar-translate-table (aref str i)))
+	    (setq i (1+ i)))
+	  (goto-char from)
+	  (kill-region from to)
+	  (insert str)))))
+
+;;; news-caesar-buffer-body written by paul@media-lab.mit.edu  Wed Oct 1, 1986
+;;; hacked further by tower@prep.ai.mit.edu
+(defun news-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	; Was there a prefix arg?
+		   (list (prefix-numeric-value current-prefix-arg))
+		 (list nil)))
+  (save-excursion
+    (let ((buffer-status buffer-read-only))
+      (setq buffer-read-only nil)
+      ;; setup the region
+      (set-mark (if (progn (goto-char (point-min))
+			    (search-forward
+			     (concat "\n"
+				     (if (equal major-mode 'news-mode)
+					 ""
+				       mail-header-separator)
+				     "\n") nil t))
+		     (point)
+		   (point-min)))
+      (goto-char (point-max))
+      (caesar-region rotnum)
+      (setq buffer-read-only buffer-status))))