|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T u
Length: 34049 (0x8501)
Types: TextFile
Names: »utils.el«
└─⟦a05ed705a⟧ Bits:30007078 DKUUG GNU 2/12/89
└─⟦32c6b6024⟧ »./gnews-2.0-tar.Z«
└─⟦5d06bd818⟧
└─⟦this⟧ »gnews-2.0/utils.el«
;;; 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))))