DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T u

⟦5535543a6⟧ TextFile

    Length: 34049 (0x8501)
    Types: TextFile
    Names: »utils.el«

Derivation

└─⟦a05ed705a⟧ Bits:30007078 DKUUG GNU 2/12/89
    └─⟦32c6b6024⟧ »./gnews-2.0-tar.Z« 
        └─⟦5d06bd818⟧ 
            └─⟦this⟧ »gnews-2.0/utils.el« 

TextFile

;;; utils.el: basic utilities for Gnews
;;; Copyright (C) 1987, 1988 by Matthew P Wiener; all rights reserved.

;;; Send bugs, complaints, suggestions etc to weemba@garnet.berkeley.edu

;; Gnews is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY.  No author or distributor accepts responsibility to
;; anyone for the consequences of using it or for whether it serves any
;; particular purpose or works at all, unless he says so in writing.
;; Refer to the Gnews General License for full details.

;; Everyone is granted permission to copy, modify and redistribute Gnews,
;; but only under the conditions described in the Gnews General License.
;; A copy of this license is supposed to have been given to you along with
;; Gnews so you can know your rights and responsibilities.  It should be
;; accessible with the key sequence "ESC l l" while in News mode.  Among 
;; other things, the copyright notice and this notice must be preserved on
;; all copies.
\f


;;; .gnewsrc.* handlers

(defun gnews-from-news-rc ()
  "Convert .newsrc file to .gnewsrc format"
  (gnews-string-as-buffer "" nil
    (insert-file (concat gnews-dot-dir ".newsrc"))
    (message ".newsrc to .gnewsrc conversion...")
    (goto-char 1)
    (if (looking-at "^options ")
	(progn
	  (setq gnews-rn-options (buffer-substring 1 (gnews-eol)))
	  (gnews-delete-line)))
    (while (re-search-forward "^\\([^:!\n ]+\\)$" nil t)
      (replace-match "\\1: 0"))
    (goto-char 1)
    (while (re-search-forward "^\\([^:!]*\\)\\(.\\) *\\(.*\\)$" nil t)
      (replace-match "   (\"\\1\" \\2 \\3)" t nil))
    (goto-char 1)
    (while (search-forward ":" nil t)
      (replace-match "t"))
    (goto-char 1)
    (while (search-forward "!" nil t)
      (replace-match "nil"))
    (goto-char 1)
    (while (search-forward "," nil t)
      (replace-match " "))
    (goto-char 1)
    (while (re-search-forward "\\([0-9]+\\)-\\([0-9]+\\)" nil t)
      (replace-match "(\\1 . \\2)"))
    (goto-char 1)
    (insert "(setq\t\t\t\t;\n group-roster\n '(\n")
    (goto-char (point-max))
    (insert "   ))\n")
    (eval-current-buffer)
    (write-file gnews-rc-file)
    (write-file gnews-rc-file-new))
  (message ".newsrc to .gnewsrc conversion...done"))

(defun gnews-to-news-rc (file)
  "Convert group-roster to .newsrc format, and write to FILE."
  (interactive (list (concat gnews-dot-dir ".newsrc")))
  (let ((max-lisp-eval-depth 3000))
    ;; Until gnews-replace is written non-recursively.
    (if (or (not (file-exists-p file))
	    (yes-or-no-p "Overwrite existing .newsrc? "))
	(gnews-string-as-buffer
	  (apply 'concat
		 (mapcar '(lambda (g)
			    (insert (car g) (if (gnadr g) ": " "! ")
				    (if (gnddr g)
					(gnews-replace "[()]" ""
					  (gnews-replace ")? (?" ","
					    (gnews-replace " \\. " "-"
					      (prin1-to-string (gnddr g)))))
				      "")
				    ?\n)
			    (let ((ll (count-lines 1 (point))))
			      (if (zerop (% ll 10))
				  (progn (message "%d groups ..." ll) ""))))
			 group-roster)) nil
	  (goto-char 1)
	  (if (boundp 'gnews-rn-options) (insert gnews-rn-options ?\n))
	  (write-region 1 (point-max) file nil 0)
	  (message "%s written" file))))
  (gnews-flush))

(defun group-roster-write (file)
  "Write out group-roster to FILE."
  (let ((b (current-buffer))
	(fb (find-file-noselect file))
	(gp (car group-roster))
	(r (cdr group-roster))
	version-control)
    (set-buffer fb)
    (erase-buffer)
    (insert "(setq\t\t\t\t; -*- Emacs-Lisp -*-\n group-roster\n '(")
    (while gp
      (insert "\n   ")
      (prin1 gp fb)
      (setq gp (car r) r (cdr r)))
    (insert "\n   ))\n")
    (write-region 1 (point-max) buffer-file-name nil 0)
    (set-buffer-modified-p nil)
    (switch-to-buffer b)
    (kill-buffer fb)
    (setq gnews-rc-dangle)))

(defun gnews-hook-write (file)
  "Write out hook-kill-all and gnews-abbrev to FILE."
  (gnews-string-as-buffer "" nil
    (insert "(setq\t\t\t\t; -*- Emacs-Lisp -*-\n hook-kill-all\n '(nil")
    (mapcar '(lambda (g)
	       (let ((gh (cdr (assoc g hook-kill-all))))
		 (if (car gh)
		     (progn
		       (insert "\n   (" (prin1-to-string g))
		       (mapcar '(lambda (x)
				  (insert "\n    " (prin1-to-string x)))
			       gh)
		       (insert "\n    )")))))
	    (mapcar 'car (cdr hook-kill-all)))
    (insert "\n   ))\n\f\n(setq\n gnews-abbrev\n '(")
    (mapcar '(lambda (x) (insert "\n   " (prin1-to-string x))) gnews-abbrev)
    (insert "\n   ))\n")
    (if (boundp 'gnews-rn-options)
	(insert "\f\n(setq gnews-rn-options \"" gnews-rn-options "\")\n"))
    (write-region 1 (point-max) file nil 0))
  (setq gnews-hook-dangle))

(defun gnews-time-write (file)
  "Write out roster-new to FILE."
  (gnews-string-as-buffer "" nil
    (insert "(setq\t\t\t\t; -*- Emacs-Lisp -*-\n roster-new\n '(("
	    (prin1-to-string (gnaar roster-new)) " . "
	    (prin1-to-string (gndar roster-new)) ")")
    (mapcar '(lambda (x) (insert "\n   " (prin1-to-string x)))
	    (cdr roster-new))
    (insert "\n   ))\n")
  (write-region 1 (point-max) file nil 0))
  (setq news-new-noted))

(defun roster-new-check ()
  (let ((buff (set-buffer (find-file-noselect gnews-list-file))))
    (setq roster-old (buffer-string)
	  roster-old-count (count-lines 1 (point-max)))
    (erase-buffer)
    (insert roster-string)
    (setq roster-new-count (count-lines 1 (point-max))
	  roster-new-p (and (< 0 roster-old-count)
			    (< roster-old-count roster-new-count)))
    (write-region 1 (point-max) buffer-file-name nil 0)
    (set-buffer-modified-p nil)
    (switch-to-buffer news-buffer)
    (kill-buffer buff)
    roster-new-p))

(defun gnews-load (file &optional mok nom nos)
  "Safely load FILE, with optional arguments as in load, which see.
On error, abort NNTP and throw the user into FILE.  [[On quit, abort
NNTP and get out of Gnews.]]"
  (condition-case q
      (load file mok nom nos)
    (error (if (processp nntp)
	       (progn (delete-process nntp)
		      (delete-process nntp-index)))
	   (bury-buffer news-buffer)
	   (find-file file)
	   (signal (car q) (cdr q)))
;;; (quit (nntp-exec t t "quit")		; doesn't work?
;;;	  (bury-buffer news-buffer))		; so why not?
    ))

(defun gnews-hook-load ()
  "Load the .gnewsrc.hook file."
  (if (file-exists-p gnews-hook-file)
      (gnews-load gnews-hook-file nil t t)
    (setq gnews-hook-dangle t
	  hook-kill-all (list nil)
	  gnews-abbrev (list nil)))
  ;; Some gnews-hook => hook-kill-all rename conversions.
  (if (boundp 'gnews-hook-list)
      (progn (setq hook-kill-all gnews-hook-list
		   gnews-hook-dangle t)
	     (makunbound 'gnews-hook-list)
	     (switch-to-buffer gnews-warn-buffer)
	     (insert "** WARNING **\n\n\
I have renamed numerous internal variables, so as to make most things
cleaner.  The variable that contains your hook-kills will be automatically
fixed--the fact that it is out-of-date is what triggered this warning in
the first place.

You will have to fix a few other internal variables that you might use
somewhere.  Fortunately, the command to do this has been written for you.
It is named `M-x gnews-rename', and is in `Help.el'.  You do not have to
run this immediately: if you don't certain of your customizations may
fail to take effect, nothing worse.

You use gnews-rename by quitting out of Gnews, then visiting your `.emacs'
file, or whatever file(s) contain your basic Gnews user-variable settings,
and then running `M-x gnews-rename'.  Also, you may need to run this while
visiting your `.gnewsrc.hook' file, if you have any non-Gnews-generated
hooks.

This message will be saved in `.gnewsrc.warn'.  Hit any key to continue.")
	     (read-char)
	     (write-region 1 (point-max) (concat gnews-rc-file ".warn"))
	     (switch-to-buffer news-buffer)))
  (if (and (boundp 'gnews-hook-kill-alist) (not hook-kill-alist))
      (setq hook-kill-alist gnews-hook-kill-alist)))

;;; I suppose a warning message is appropriate.  However, if the hook
;;; file is gone, I don't see what good this does.  And if it has been
;;; moved around, a copy will, perhaps, still be around, so for now, I
;;; do nothing.  As it is, the .hook file needs some sort of backup.

;;; Also, this currently is how a new .gnewsrc.hook is created.
\f


;;; article headers

(defun article-header-clean (art-flag)
  "Remove unsightly headers.  Non-nil ART-FLAG means update
article-field-list."
  (goto-char 1)
  (if art-flag
      (progn
	(setq article-field-list (list nil))
	(while (not (looking-at "^[ \t]*$"))	; fix those moronic
	  (if (looking-at "^[ \t]+")		; multiline headers
	      (delete-indentation))
	  (forward-line 1))))
  (goto-char 1)					; now we do business
  (let (field value intern-field)
    (while (re-search-forward "^\\([^:]*\\): *\\(.*\\)$" (article-min) t)
      (setq field (gnews-match 1)
	    value (gnews-match 2))
      (delete-region (match-beginning 1) (1+ (match-end 2)))
      (setq value (article-header-tweak field value))
      (if (or article-header-all-p
	      (and (string< "" value)
		   (setq intern-field (intern field))
		   (if (memq intern-field article-header-ignore)
		       (memq intern-field article-header-show)
		     t)))
	  (insert field ": " value "\n"))
      (if art-flag (nconc article-field-list (list (cons field value))))))
  (goto-char 1)
  (setq article-formfeed-p t)
  (run-hooks 'article-header-hook)
  (goto-char 1))

(defun article-header-tweak (field string)
  "Given header FIELD with value STRING, return a new value for the header.\n
This can be used for fine tweaking of header displays, eg, removing
extraneous \"Re:\"s, converting GMT time to local time, etc."
  string)

(defun article-field (&rest headers)
  "As a function call, return the first non-empty field associated
with the HEADERS, eg (article-field \"Reply-To\" \"From\") returns
the Reply-To: field if present, else the From: field, or failing
even that, a null string.\n
Interactively, insert the prompted-for field.\n
Note: this function is case-sensitive."
  (interactive (list (completing-read "Header: " article-field-list)))
  (if (interactive-p)
      (insert (article-field-intern (car headers)))
    (let ((f (article-field-intern (car headers))))
      (while (and headers f (zerop (length f)))
	(setq headers (cdr headers)
	      f (article-field-intern (car headers))))
      (or f ""))))

(defun article-field-intern (header)
  "Return the field associated with HEADER, or \"\" if non-existent.
This function refers to digest headers when in the Digest minor mode.\n
Compare with article-field-raw."
  (if (gnews-digest-p)
      (article-digest-field-raw header)
    (article-field-raw header)))

(defun article-field-raw (header)
  "Return the field associated with HEADER, or \"\" if non-existent.
This always refers to the headers of the raw article, and never to the
headers within a digest.\n
Compare with article-field-intern."
  (or (cdr (assoc header article-field-list)) ""))
\f


;;; Identifying the next newsgroup to proffer

(defun news-next-intern (flag gl)
  "Internal function for identifying the next newsgroup to proffer.  FLAG
means proffer from all subscribed-to newsgroups when non-nil, and from
those with new news only when FLAG is nil.\n
GL is the newsgroup roster to search through, in order."
  (setq news-default-command 'news-yes)
  (let ((g (car gl)) q rst)
    (group-warn-delete-window t)
    (condition-case q
	(progn
	  (while (and g
		      (not q)
		      (not (and (setq rst (news-restrictions g))
				(gnadr g)
				(or flag
				    (and (roster-string-new-news g)
					 group-not-bogus
					 (group-new-news g))))))
	    (setq news-next-message (if flag ""
				      (concat
					(if (< (length news-next-message) 20)
					    news-next-message "")
					(if rst "." ""))))
	    (message news-next-message)
	    (setq gl (cdr gl) g (car gl)))
	  (cond (q)
		((null g) (news-end))
		(t
		 (setq news-next-message "")
		 (group-set (car g))
		 (group-proffer flag g (group-get-info g)))))
      (quit (setq news-next-message ""
		  news-default-command 'news-next-unread
		  news-prompt-return news-prompt-next)
	    (group-set (car g))
	    (gnews-message "interrupted at %s--%s ? "
			   group-current news-prompt-next))))
  (gnews-flush))

(defun roster-string-new-news (g)
  "Check if roster-string gives quick new news information about
group entry G.  Return t if it looks like new news."
  (let* ((gp (car g))
	 (i (string-match (concat "^" (regexp-quote gp)
				  " \\([0-9]+\\) \\([0-9]+\\) \\([ymn]\\)")
			  roster-string))
	 (gg (gnddr g)))
    (and (setq group-not-bogus i)
	 (setq article-final (read (substring roster-string
					      (match-beginning 1)
					      (match-end 1)))
	       article-first (read (substring roster-string
					      (match-beginning 2)
					      (match-end 2))))
	 (setq group-not-empty (< 0 article-final))
	 (if gg (< (amark-cdr (car gg)) article-final) t))))

(defvar group-0-reset-warn nil
  "*If non-nil, warn about newsgroups reset to 0 articles.")

(defun group-new-news (g &optional nowarn)
  "Return determination of existence of unread news for group entry G.
Optional second argument NOWARN non-nil means don't display any warning
messages."
  (if (nntp-exec t t "group" (car g))
      (progn
	(if (string-match (concat " " (regexp-quote (car g)) "$")
			  nntp-info)
	    nil				; we got the correct nntp-info
	  (let ((nntp-exec-force t))	; timing error???--let's retry
	    (nntp-exec t t "group" (car g))))
	(if (string= nntp-info nntp-bogus-group)
	    nil				; no new news: it's bogus
	  (let* ((cc (read-from-string nntp-info 4))
		 (ii (read-from-string nntp-info (cdr cc)))
		 (ll (read-from-string nntp-info (cdr ii)))
		 (tt (if (and (gnddr g) (gnaddr g)) (car (reverse g)) 0)))
	    (setq article-first (car ii)
		  article-final (car ll))
	    (cond ((and (not nowarn)
			(< (1+ article-final) (amark-cdr tt))
			(or (< 0 article-final) group-0-reset-warn))
		   (with-output-to-temp-buffer gnews-warn-buffer
		     (use-local-map news-mode-map)
		     (if (boundp 'group-warning-options) nil
		       (setq group-warning-options
			     (append
			       (list (substring news-prompt-next 1 2))
			       (gnews-map '(lambda (dk f)
					     (if (eq (key-binding dk) f) dk
					       (gnews-subst-command-keys
						 (format "\\[%s]"
							 (symbol-name f)))))
				 '("u" "c" "m" "D")
				 '(news-unsubscribe news-catchup
						    news-mark news-delete)))))
		     (princ
		       (concat
			 "\nWarning: " (car g) " has been ?reset to "
			 article-final " from " (amark-cdr tt)
			 "\n\nYour options include:\n\n\t"
			 (nth 0 group-warning-options)
			 " (to skip to the next group)\n\t"
			 (nth 1 group-warning-options)
			 " (to unsubscribe the problem away)\n\t"
			 (nth 2 group-warning-options)
			 " (to mark everything read)\n\t"
			 (nth 3 group-warning-options)
			 " (to mark everything unread)\n\t"
			 (nth 4 group-warning-options)
			 " (to delete the newsgroup)\n")))
		   (gnews-message "what next [%s] ? "
				  (apply 'concat group-warning-options))
		   (setq group-warn-p t)
		   (group-set (car g) t))
		  (t
		   (and (< 0 article-final)
			(or (< article-first (amark-car tt))
			    (> article-final (amark-cdr tt)))))))))))

(defun group-get-info (g &optional quick)
  "Get the basic information about newsgroup .gnewsrc entry G.  Returns a
list containing subscription bit, G's amark and the count of unread
articles.  Returns nil if G is bogus.\n
If optional second argument QUICK is non-nil, an internal NNTP call will
not be done."
  (if (or quick (nntp-exec t t "group" (car g)))
      (let* ((s (gnadr g))
	     (m (gnddr g))
	     (z (amark-list-init article-first m))
	     (c (if (zerop article-final) 0
		  (apply '- article-final 0 (mapcar 'amark-size m)))))
	(list s m c))))

(defun group-warn-delete-window (delete)
  "Delete the warning buffer if DELETE is non-nil."
  (if delete
      (progn
	(delete-windows-on (get-buffer gnews-warn-buffer))
	(setq group-warn-p))))

(defun group-bogus-warn (group)
  "Give an error message when trying to enter a bogus newsgroup."
  (message "%s: <bogus>" group (ding)))
\f


;;; Newsgroup information getting/setting

(defun group-set (group &optional informed)
  "Set the current news GROUP.  Basic group-dependent variables are set.
The newsgroup is not actually entered.\n
Optional flag INFORMED non-nil means do not call group-new-news to set
certain basics.\n"
  (group-warn-delete-window (not group-warn-p))
  (if (string= group group-set-current) group	 ; already up-to-date
    (let ((g (assoc group group-roster)))
      (if (not informed) (group-new-news g))
      (gnews-map 'set
	'(amark amark article-count)
	(group-get-info g t))
      (setq group-bogus (string= nntp-info nntp-bogus-group))
      (if (article-exists-p article-first)
	  nil					 ; everything's OK
	(setq article-current article-first
	      article-first (article+1 t)	 ; skip over dead stuff
	      article-current article-first))
      (article-current-set (max (if (car amark)
				    (1+ (amark-cdr (car amark)))
				  0)
				article-first))
      (amark-list-init article-current amark)
      (setq article-same-subject (and article-same-subject-trigger
				      (<= article-same-subject-trigger
					  article-count))
	    amark-entry amark
	    group-previous (or group-current group-previous)
	    group-current group
	    group-checkpoint nil
	    group-mark-later-list (list nil)
	    article-digest-maybe nil
	    index-current nil
	    index-header-field nil
	    group-set-current group)
      (setcdr (cdr g) amark)
      group)))

(defun group-get (group &optional nosub dot)
  "Switch to named news GROUP.  Resubscribes unless optional second argu-
ment NOSUB is non-nil.  Goes to the first available article, or the end
of the newsgroup if the group is caught up in, unless third argument DOT
is non-nil, in which case no article setting is done."
  (setq group-current (group-set group)
	index-return-configuration (current-window-configuration))
  (if (not nosub)
      (let ((sub (cdr (assoc group group-roster))))
	(setcar sub (or (car sub) t))))
  (setq news-default-command 'news-yes)
  (if group-bogus
      (group-bogus-warn group)
    (group-mode)
    (hook-kill-set group)
    (setq group-pattern ""
	  group-pattern-field "Subject"
	  group-pattern-command nil
	  group-entry-command (if (rassq this-command gnews-abbrev-keymap)
				  group-entry-command this-command)
	  group-pattern-redo nil
	  group-last-save (concat gnews-news-dir
				  (if (eq gnews-slashify t)
				      (gnews-replace "\\." "/" group-current)
				    group-current)
				  (if gnews-slashify "/")))
    (if hook-kill-pre-ok			; a locking mechanism
	(progn
	  (setq index-perm-marks (list nil))
	  (mapcar 'hook-kill-do hook-kill-pre)
	  (setq hook-kill-pre-ok nil)))
    (if (or dot (boundp 'index-pop)) nil
      (while (and (<= article-current article-final)
		  (not (article-exists-p article-current)))
	(amark-list-insert article-current amark)
	(setq article-current (1+ article-current)))
      (if (<= article-current article-final)
	  (cond ((catch 'article-nil (article-get
				       article-current hook-kill-per t))
		 (article-junk-local)
		 (group-next-unread t)))
	(group-last)))))

(defun group-proffer (flag g gi)
  "Ask if user wishes to read newsgroup, requiring new news if FLAG is nil.
Returns with list of interesting data if yes.  G is list of newsgroup info
from the .gnewsrc file, and GI is the list of info from group-get-info."
  (if (or flag (< 0 (nth 2 gi)))
      (if (string= nntp-info nntp-bogus-group)
	  (gnews-message "%s <bogus> %s ? "
	    (setq group-bogus t
		  group-current (car g))
	    (setq news-prompt-return news-prompt-next))
	(gnews-message "%s {%d} %s ? "
	  (setq group-bogus nil
		news-seen t
		group-current (car g))
	  (setq group-proffer-count (nth 2 gi))
	  (setq news-prompt-return news-prompt-yes)))))

(defun group-proffer-must (group &optional gn)
  "Proffer to read news from GROUP.  Optional second argument GN is the
.gnewsrc entry for GROUP."
  (if (not gn) (setq gn (assoc group group-roster)))
  (group-set group)
  (group-proffer t gn (group-get-info gn)))

(defun group-proffer-new (group)
  "Ask if user wishes to read newsgroup GROUP not in .gnewsrc."
  (let* ((f (group-name-read
	      (format "place %s after: " group) group-roster 'news-all))
	 (g (assoc f group-roster))
	 (n (list group t))
	 (hd (reverse (memq g (reverse group-roster))))
	 (tl (cdr (memq g group-roster))))
    (setq news-seen t
	  group-roster (nconc hd (list n) tl))
    (group-roster-write gnews-rc-file-new)
    (group-get group)))

(defun gnews-mod-p (group)
  "Return non-nil if GROUP is moderated."
  (if (string-match (concat "^" group "[ \t]+[0-9]+[ \t]+[0-9]+[ \t]+"
			    "\\([ymn]\\)")
		    roster-string)
      (string-match (substring roster-string
			       (match-beginning 1)
			       (match-end 1))
		    "mn")))
\f


;;; newsgroup exiting internals

(defun group-amark-set (group om)
  "Set GROUP's amark to AMARK."
  (setcdr (cdr (assoc group group-roster)) (setq amark om)))

(defun group-quit-intern ()
  "The essential internals of newsgroup quitting."
  (if (cdr group-mark-later-list)	; things to mark for later
      (progn
	(group-mark (cdr group-mark-later-list))
	(group-amark-set group-current amark)
	(setq group-mark-later-list (list nil))))
  (setcdr (cdr (assoc group-current group-roster)) amark)
  (mapcar 'hook-kill-do hook-kill-post)
  (setq hook-kill-post nil
	hook-kill-pre-ok t)		; unlock the pre-hooks for re-use
  (if (boundp 'index-pop)
      (let ((iw (get-buffer-window index-buffer)))
	(if (and index-pop iw (not (one-window-p t)))
	    (delete-window iw))
	(if gnews-configuration-p
	    (set-window-configuration index-return-configuration))
	(set-buffer news-buffer)
	(setq buffer-read-only)
	(erase-buffer)
	(setq buffer-read-only t)
	(switch-to-buffer news-buffer)
	(bury-buffer index-buffer)
	(makunbound 'index-pop)))
  (if group-checkpoint (group-roster-write gnews-rc-file-new)))

(defun news-next-unread-maybe (&optional quit)
  "Run 'news-next-unread if the current newsgroup was entered with
one of the usual entry commands appropriate for .gnewsrc in-sequence
profferings; otherwise merely print the quit to top level prompt.\n
If optional argument QUIT is non-nil, run a group-quit first."
  (if quit (group-quit))
  (cond ((memq group-entry-command
	       '(gnews news-yes news-default news-next-unread news-index
		       news-catchup))
	 (news-next-unread))
	((memq group-entry-command
	       '(news-previous-unread))
	 (news-previous-unread))
	(t
	 (setq news-default-command 'news-next-unread)
	 (message "quit to top level--what next %s ? " news-prompt-next))))

(defun group-next-but-index-done (forward)
  "What to do when the index-buffer is filled and you go to the next
article in group-mode.  Argument FORWARD non-nil if going forward,
nil if going backward."
  (if (and (= (length amark) 1)
	   (= (amark-cdr (car amark)) article-final))
      (news-next-unread-maybe t)
    (save-excursion
      (set-buffer index-buffer)
      (goto-char (if forward 1 (point-max)))
      (if (funcall (if forward 're-search-forward 're-search-backward)
		   "^ *[0-9]+\\([ i]\\)" nil t)
	  (if (string= " " (gnews-match 1))
	      (cond ((catch 'article-nil (article-get (index-article) nil t))
		     (article-junk-local)
		     (group-next-but-index-done forward)))
	    (group-catchup nil 1))
	(news-next-unread-maybe t)))))

(defun group-next-but-no-wrap ()
  "What to do when no wrapping is wanted at the $-end of a newsgroup."
  (if (and (= (length amark) 1)
	   (= (amark-cdr (car amark)) article-final))
      (news-next-unread-maybe t)
    (group-catchup nil 1)))
\f


;;; initial article display

(defun article-display-init (&optional dotsokay rot13)
  "Display the article sitting in nntp-buffer.  If optional argument
DOTSOKAY is non-nil, don't play games with leading periods."
  (let (p q)
    (setq article-grab-point-old nil)
    (if (null article-grab-point)
	(progn (set-buffer nntp-buffer)
	       (setq article-grab-point (point-max))))
    (if article-subject-hilite
	(progn (set-buffer nntp-buffer)
	       (goto-char 1)
	       (re-search-forward "^Subject: ")
	       (setq p (match-end 0) q (gnews-eol))))
    (if article-formfeed
	(progn (goto-char 1)
	       (forward-paragraph 1)
	       (if (re-search-forward article-formfeed article-grab-point t)
		   (setq article-grab-point (match-end 0)))))
    (switch-to-buffer news-buffer)
    (setq buffer-read-only)
    (erase-buffer)
    (if article-subject-hilite
	(progn
	  (insert-buffer-substring nntp-buffer 1 p)
	  (setq buffer-read-only t)	; no flashing "*"s
	  (sit-for 0)
	  (setq inverse-video (not inverse-video)
		buffer-read-only)
	  (insert-buffer-substring nntp-buffer p q)
	  (setq buffer-read-only t)	; no flashing "*"s
	  (sit-for 0)
	  (setq inverse-video (not inverse-video)
		buffer-read-only)
	  (insert-buffer-substring nntp-buffer q article-grab-point))
      (insert-buffer-substring nntp-buffer 1 article-grab-point))
    (goto-char 1)
    (setq article-count-off-but-ok nil)
    (if dotsokay nil
      (setq article-grab-point (- article-grab-point (nntp-undot))))
    (setq buffer-read-only t)
    (goto-char 1)
    (set-buffer-modified-p nil)
    (if gnews-rot13-p
	(let ((min (article-min)) buffer-read-only)
	  (save-excursion
	    (goto-char min)
	    (gnews-rot13 min article-grab-point))))
    (message "")
    (article-%-clear)
    (let (article-subject-hilite)
      (article-mode))
    (article-%-compute)))

(defun article-effective-init-display ()
  "Set the effective article-display-count to nil if the current article
has article-big lines are more.\n
If non-nil, also make sure that the effective article-display-count is at
least two more the number of displayed headers."
  (let ((l (string-to-int (article-field "Lines"))))
    (cond ((= 0 l) nil)
	   (article-display-count
	    (if (< l article-big)
		(max article-display-count
		     (+ 2 (save-excursion
			    (goto-char 1)
			    (forward-paragraph 1)
			    (count-lines 1 (point))))))))))
\f


;;; basic article predicates

(defun article-min ()
  "Return smallest value of point in the body of the article."
  (save-excursion
    (goto-char 1)
    (forward-paragraph)
    (forward-line 1)
    (point)))

(defun article-max ()
  "Return largest value of point in the body of the article."
  (point-max))

(defun article-done ()
  "Returns t if the end of the article is visible, nil otherwise"
  (and (<= article-current article-final)
       (or (null article-%)
	   article-count-off-but-ok
	   (< 99 (read article-%)))
       (< 0 article-current)))

(defun article-forward-intern (junk &optional ff digest)
  "Get more of the article--if JUNK non-nil, mark as junkable.\n
If optional second argument FF is non-nil, it is treated as a
formfeed regexp, blocking display and scrolling at the next
formfeed, if present.  If FF is nil, the rest of the article
will be filled in.\n
If FF is a string and the third argument DIGEST is a string,
then always grab to the next formfeed even if it is not visible,
and use the string as a regexp that matches text after FF, to
confirm that FF was a genuine digest separator."
  (setq article-junkable (or article-junkable junk))
  (if (and article-grab-point
	   (or (not ff)
	       digest
	       (pos-visible-in-window-p article-grab-point)))
      (save-excursion
	(if article-grab-point
	    (setq article-grab-point-old article-grab-point))
	(setq buffer-read-only)
        (set-buffer nntp-buffer)
        (let* ((pmax (point-max))
	       (pm (if (and ff (not digest))
		       (save-excursion
			 (goto-char article-grab-point)
			 (forward-line (screen-height))
			 (point))
		     pmax))
	       (start (min article-grab-point pm))
	       (rsf (goto-char start))
	       (rsfp nil)
	       (end (if (and ff
			     (setq rsf (re-search-forward ff nil t))
			     (setq rsfp (point))
			     (or (not digest)
				 (while (not (or (eobp) (looking-at digest)))
				   (forward-line 1)
				   (setq rsf (re-search-forward ff nil t)
					 rsfp (point)))
				 rsf))
			rsfp pm))
	       string)
	  (setq article-formfeed-p (or article-formfeed-p rsf))
	  (set-buffer news-buffer)
	  (if (< start end)
	      (save-excursion
		(goto-char (setq news-start (point-max)))
		(if (not (and article-formfeed-p gnews-rot13-p))
		    (insert-buffer-substring nntp-buffer start end)
		  (set-buffer nntp-buffer)
		  (setq string (buffer-substring start end))
		  (set-buffer news-buffer)
		  (gnews-rot13-insert string))
		(setq article-grab-point (if ff end))))
	  (if (= end pmax) (setq article-grab-point)))
	(setq buffer-read-only t)
	(article-%-compute))))

(defun article-exists-p (j)
  "Return t if article J exists in the current newsgroup, and reset
the internal NNTP article number to J if it does."
  (nntp-exec t t "stat" j))

(defun article+1 (&optional raw)
  "Return the next available article number.  If it is necessary to skip
over non-existent articles, then also close up dead space in the current
amark.\n
If the optional argument RAW is non-nil, the amark will be ignored
when identifying the next available article."
  (let (i j sm0 nen)
    (if (and (< article-current article-final)
	     (or (setq nen (nntp-exec t t "next"))
		 (setq sm0 (string-match "<0>" nntp-info)))
	     (string< "" nntp-info))
	(progn
	  (setq i (car (read-from-string nntp-info 4))
		j (if raw i (amark-next-unread (1- i) amark)))
	  (amark-block-mark article-current i amark)
	  (cond (raw (if (or (not nen) sm0) (article+1 t) i))
		((< article-final j) j)
		((article-exists-p j) j)
		(t (article+1))))
      (1+ article-final))))
  
(defun article-1 (&optional raw)
  "Return the previous available article number.  If it is necessary to skip
over non-existent articles, then also close up dead space in the current
amark.\n
If optional argument RAW is non-nil, the amark will be ignored when
identifying the previous available article."
  (let (i j sm0 nel)
    (if (and (or (setq nel (nntp-exec t t "last"))
		 (setq sm0 (string-match "<0>" nntp-info)))
	     (string< "" nntp-info))
	(progn
	  (setq i (car (read-from-string nntp-info 4))
		j (if raw i (amark-previous-unread (1+ i) amark)))
	  (amark-block-mark article-current i amark)
	  (cond (raw (if (or (not nel) sm0) (article-1 t) i))
		((< j article-first) j)
		((article-exists-p j) j)
		(t (article-1))))
      article-first)))			; this is wrong, but what isn't ?

(defun article-current-set (art)
  "Set the current article number to ART."
  (setq article-previous article-current
	article-current art))
\f


;;; newsgroup roster generation

(defun roster-all ()
  "Return list of all newsgroups known to this site."
  (or roster
      (gnews-string-as-buffer roster-string nil
	(setq roster (list nil))
	(while (not (eobp))
	  (let* ((info (buffer-substring (gnews-bol) (gnews-eol)))
		 (sp (string-match " " info))
		 (gg (substring info 0 sp))
		 (ff (read-from-string info sp))
		 (ii (read-from-string info (cdr ff))))
	    (nconc roster (list (list gg (car ii) (car ff)))))
	  (forward-line 1))
	(setq roster (cdr roster)))))

(defun roster-string-set ()
  (save-excursion
    (nntp-exec t t "list")
    (set-buffer nntp-buffer)
    (setq roster-string (buffer-string))))

(defun roster-new-set (msg-y msg-n urgent)
  "Get the new groups waiting in the nntp-buffer, and append to
roster-new.  If new groups are found, display message MSG-Y (if
non-nil).  A %d, if present, is formatted as the number of new
newsgroups.  If no new groups are found, display message MSG-N
\(if non-nil\).  If third argument URGENT is non-nil, then the
yes message will be accompanied by a ding; moreover, the yes
message will be generated if there are still \"old\" new groups
that the user hasn't considered yet."
  (set-buffer nntp-buffer)
  (goto-char 1)
  (if (looking-at "^2") (forward-line 1))
  (if (eobp)
      (cond ((and urgent (cdr roster-new))
	     (ding)
	     (if msg-y (message msg-y (1- (length roster-new)))))
	    (msg-n
	     (message msg-n)))
    (while (not (eobp))
      (let ((gn (buffer-substring (gnews-bol) (gnews-eol))))
	(if (and (not (assoc gn roster-new))
		 (not (assoc gn group-roster)))
	    (nconc roster-new (list (list gn nil)))))
      (forward-line 1))
    (if urgent (ding))
    (if msg-y (message msg-y (1- (length roster-new))))))
\f


;;; rot13 function

(setq gnews-rot13-map (make-keymap))

(let ((i 0))
  (while (< i 128)
    (aset gnews-rot13-map i (cond ((< i ?A) i)
				  ((< i ?N) (+ i 13))
				  ((<= i ?Z) (- i 13))
				  ((< i ?a) i)
				  ((< i ?n) (+ i 13))
				  ((<= i ?z) (- i 13))
				  (t i)))
    (setq i (1+ i))))

(defun gnews-rot13-insert (str)
  "Insert the rot13 version of STR."
  (let ((i 0) (m (length str)))
    (while (< i m)
      (insert (aref gnews-rot13-map (aref str i)))
      (setq i (1+ i)))))

(defun gnews-rot13-string (str)
  "Return the rot13 version of STR."
  (mapconcat 'char-to-string
	     (mapcar '(lambda (c) (aref gnews-rot13-map c)) str)
	     ""))

(defun gnews-rot13 (beg end &optional adjust)
  "Caesar alphabet shift the region by 13, better known as \"rot13\".\n
In Lisp code, the region is given by arguments BEG and END."
  ;; Bug: what if beg is before the currently visible portion?
  (interactive "*r")
  (save-excursion
    (if (pos-visible-in-window-p end)
	(let ((str (buffer-substring beg end)))
	  (delete-region beg end)
	  (gnews-rot13-insert str))
      (let* ((mid (save-excursion
		    (move-to-window-line -1) (end-of-line) (point)))
	     (vis (buffer-substring beg mid))	; visible portion of region
	     (inv (buffer-substring mid end)))	; invisible portion of region
	(delete-region beg end)
	(gnews-rot13-insert vis)
	(sit-for 0)				; rot13 flash for the reader
	(gnews-rot13-insert inv))))
  (if adjust (recenter article-formfeed-top)))
\f


;;; history mechanism

(defvar article-history nil)

(defun article-history-append (art gp msgid)
  (if (and article-history (string= msgid (gnddr (car article-history))))
      nil
    (setq article-history (cons (cons art (cons gp msgid))
				article-history))))