Newsgroups: comp.sources.unix From: davison@borland.com (Wayne Davison) Subject: v29i045: trn-3.6 - threaded newsreader based on RN, V3.6, Part08/14 References: <1.814959141.29825@gw.home.vix.com> Sender: unix-sources-moderator@gw.home.vix.com Approved: vixie@gw.home.vix.com Submitted-By: davison@borland.com (Wayne Davison) Posting-Number: Volume 29, Issue 45 Archive-Name: trn-3.6/part08 #!/bin/sh # This is `part08' (part 8 of a multipart archive). # Do not concatenate these parts, unpack them in order with `/bin/sh'. # File `trn-3.6/rt-ov.c' is being continued... # touch -am 1231235999 $$.touch >/dev/null 2>&1 if test ! -f 1231235999 && test -f $$.touch; then shar_touch=touch else shar_touch=: echo echo 'WARNING: not restoring timestamps. Consider getting and' echo "installing GNU \`touch', distributed in GNU File Utilities..." echo fi rm -f 1231235999 $$.touch # if test ! -r _sharseq.tmp; then echo 'Please unpack part 1 first!' exit 1 fi shar_sequence=`cat _sharseq.tmp` if test "$shar_sequence" != 8; then echo "Please unpack part $shar_sequence next!" exit 1 fi if test ! -f _sharnew.tmp; then echo 'x - still skipping trn-3.6/rt-ov.c' else echo 'x - continuing file trn-3.6/rt-ov.c' sed 's/^X//' << 'SHAR_EOF' >> 'trn-3.6/rt-ov.c' && X# else Xbool ov_files_have_xrefs = FALSE; /* set once per session */ X# endif X#endif X#ifndef USE_XOVER XFILE *ov_in; X#endif X X/* Process the data in the group's news-overview file. X*/ Xbool Xov_data(first, last, cheating) XART_NUM first, last; Xbool_int cheating; X{ X register ARTICLE *ap; X ART_NUM artnum, an; X char *line, *last_buf = buf; X MEM_SIZE last_buflen = LBUFLEN; X int cachemask; X bool success = TRUE; X#ifdef USE_XOVER X ART_NUM real_first = first, real_last = last; X X setspin(cheating? SPIN_BACKGROUND : SPIN_FOREGROUND); Xbeginning: X for (ap = article_ptr(first); first <= last && (ap->flags & AF_CACHED); ) X first++, ap++; X if (first > last) X goto exit; X if (last - first > OV_CHUNK_SIZE + OV_CHUNK_SIZE/2 - 1) X last = first + OV_CHUNK_SIZE - 1; X for (ap = article_ptr(last); last > first && (ap->flags & AF_CACHED); ) X last--, ap--; X sprintf(ser_line, "XOVER %ld-%ld", (long)first, (long)last); X nntp_command(ser_line); X if (nntp_check(FALSE) != NNTP_CLASS_OK) { X success = FALSE; X goto exit; X } X#ifdef VERBOSE X IF(verbose) X if (!ov_opened) X printf("\nGetting overview file."), fflush(stdout); X#endif X X#else /* !USE_XOVER */ X X if (!ov_opened) { X if ((ov_in = fopen(ov_name(ngname), "r")) == Nullfp) { X return FALSE; X } X#ifdef VERBOSE X IF(verbose) X printf("\nReading overview file."), fflush(stdout); X#endif X } X setspin(cheating? SPIN_BACKGROUND : SPIN_FOREGROUND); X#endif /* !USE_XOVER */ X X ov_opened = TRUE; X artnum = first-1; X for (;;) { X#ifdef USE_XOVER X line = nntp_get_a_line(last_buf, last_buflen, last_buf != buf); X if (!line || NNTP_LIST_END(line)) X break; X#else X line = get_a_line(last_buf, last_buflen, last_buf != buf, ov_in); X if (!line) X break; X#endif X last_buf = line; X last_buflen = buflen_last_line_got; X artnum = atol(line); X spin(100); X if (artnum < first) X continue; X if (artnum > last) { X artnum = last; X#ifdef USE_XOVER X continue; X#else X break; X#endif X } X if ((ap = ov_parse(line, artnum)) != Nullart) { X#ifndef OV_XREFS X if (ov_files_have_xrefs) { X if (!ap->xrefs) X ap->xrefs = nullstr; X } else if (ap->xrefs) { X register ART_UNREAD i; X register ARTICLE *ap2; X ap2 = article_ptr(first); X for (i = artnum-first; i; ap2++, i--) X ap2->xrefs = nullstr; X ov_files_have_xrefs = TRUE; X } X#endif X if (ThreadedGroup) { X if (valid_article(ap)) X thread_article(ap); X } else if (!(ap->flags & AF_CACHED)) X cache_article(ap); X check_poster(ap); X } X#ifdef USE_XOVER X if (int_count) { X int_count = 0; X success = FALSE; X } X#else X if (int_count) { X int_count = 0; X success = FALSE; X break; X } X if (cheating) { X if (input_pending()) { X success = FALSE; X break; X } X if (curr_artp != sentinel_artp) { X pushchar('\f' | 0200); X success = FALSE; X break; X } X } X#endif /* !USE_XOVER */ X } X cachemask = (ThreadedGroup? AF_THREADED : AF_CACHED); X#ifdef USE_XOVER X an = real_first; X#else X an = first; X#endif X for (ap = article_ptr(an); an <= artnum; an++, ap++) { X if (!(ap->flags & cachemask)) { X#ifdef USE_NNTP X onemissing(ap); X#else X (void) parseheader(an); X#endif X } X } X if (artnum > last_cached && artnum >= first) X last_cached = artnum; X#ifdef USE_XOVER X exit: X if (int_count || !success) { X int_count = 0; X success = FALSE; X } else if (cheating && curr_artp != sentinel_artp) { X pushchar('\f' | 0200); X success = FALSE; X } else if (last < real_last) { X if (!cheating || !input_pending()) { X first = last+1; X last = real_last; X goto beginning; X } X success = FALSE; X } X if (success && real_first <= first_cached) { X first_cached = real_first; X cached_all_in_range = TRUE; X } X#else X if (success && first <= first_cached) { X first_cached = first; X cached_all_in_range = TRUE; X } X if (!cheating) X fseek(ov_in, 0L, 0); /* rewind it for the cheating phase */ X#endif X setspin(SPIN_POP); X if (last_buf != buf) X free(last_buf); X return success; X} X Xstatic ARTICLE * Xov_parse(line, artnum) Xregister char *line; XART_NUM artnum; X{ X register int nf; X register ARTICLE *article; X char *fields[OV_OTHERS+1], *cp; X X article = article_ptr(artnum); X if (article->flags & AF_THREADED) X return Nullart; X X if (len_last_line_got > 0 && line[len_last_line_got-1] == '\n') X#ifdef USE_NNTP X if (len_last_line_got > 1 && line[len_last_line_got-2] == '\r') X line[len_last_line_got-2] = '\0'; X else X#endif X line[len_last_line_got-1] = '\0'; X X cp = line; X X for (nf = 0; ; nf++) { X fields[nf] = cp; X if (nf == OV_OTHERS) X break; X if (!(cp = index(cp, '\t'))) { X if (nf < OV_OTHERS-1) /* only "others" field is optional */ X return Nullart; /* skip this line */ X break; X } X *cp++ = '\0'; X } X X if (!article->subj) X set_subj_line(article, fields[OV_SUBJ], strlen(fields[OV_SUBJ])); X if (!article->msgid) X article->msgid = savestr(fields[OV_MSGID]); X if (!article->from) X article->from = savestr(fields[OV_FROM]); X if (!article->date) X article->date = parsedate(fields[OV_DATE]); X references = fields[OV_REFS]; X X#ifdef OV_XREFS X# ifdef OV_LAX_XREFS X if (!strncasecmp("xref: ", fields[OV_XREFS], 6)) X article->xrefs = savestr(fields[OV_XREFS]+6); X else X article->xrefs = savestr(fields[OV_XREFS]); X# else X article->xrefs = savestr(fields[OV_XREFS]); X# endif X#else X /* check the "others" field for an optional xrefs header */ X if (nf == OV_OTHERS && !article->xrefs) { X register char *fld; X cp = fields[OV_OTHERS]; X while (cp && *cp) { X fld = cp; X if ((cp = index(cp, '\t')) != Nullch) X *cp++ = '\0'; X if (!strncasecmp("xref: ", fld, 6)) { X article->xrefs = savestr(fld+6); X break; X } X } X } X#endif X return article; X} X X#ifndef USE_XOVER X/* Change a newsgroup name into the name of the overview data file. We X** subsitute any '.'s in the group name into '/'s, prepend the path, and X** append the '/.overview' or '.ov') on to the end. X*/ Xstatic char * Xov_name(group) Xchar *group; X{ X register char *cp; X X cp = strcpy(buf, overviewdir) + strlen(overviewdir); X *cp++ = '/'; X strcpy(cp, group); X while ((cp = index(cp, '.'))) X *cp = '/'; X strcat(buf, OV_FILE_NAME); X return buf; X} X#endif X Xvoid Xov_close() X{ X if (ov_opened) { X#ifndef USE_XOVER X (void) fclose(ov_in); X#endif X ov_opened = FALSE; X } X} SHAR_EOF echo 'File trn-3.6/rt-ov.c is complete' && $shar_touch -am 1121151794 'trn-3.6/rt-ov.c' && chmod 0644 'trn-3.6/rt-ov.c' || echo 'restore of trn-3.6/rt-ov.c failed' shar_count="`wc -c < 'trn-3.6/rt-ov.c'`" test 7697 -eq "$shar_count" || echo "trn-3.6/rt-ov.c: original size 7697, current size $shar_count" rm -f _sharnew.tmp fi # ============= trn-3.6/rt-process.c ============== if test -f 'trn-3.6/rt-process.c' && test X"$1" != X"-c"; then echo 'x - skipping trn-3.6/rt-process.c (file already exists)' rm -f _sharnew.tmp else > _sharnew.tmp echo 'x - extracting trn-3.6/rt-process.c (text)' sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/rt-process.c' && X/* $Id: rt-process.c,v 3.0 1992/12/14 00:14:13 davison Trn $ X*/ X/* The authors make no claims as to the fitness or correctness of this software X * for any use whatsoever, and it is provided as is. Any use of this software X * is at the user's own risk. X */ X X#include "EXTERN.h" X#include "common.h" X#include "intrp.h" X#include "trn.h" X#include "cache.h" X#include "bits.h" X#include "final.h" X#include "ng.h" X#include "ngdata.h" X#include "rcln.h" X#include "util.h" X#include "kfile.h" X#include "hash.h" X#include "rthread.h" X#include "rt-select.h" X#include "INTERN.h" X#include "rt-process.h" X Xextern HASHTABLE *msgid_hash; X Xstatic char *valid_message_id _((char*, char*)); Xstatic void link_child _((ARTICLE*)); Xstatic void unlink_child _((ARTICLE*)); X X/* This depends on art being set to the current article number. X*/ XARTICLE * Xallocate_article(artnum) XART_NUM artnum; X{ X register ARTICLE *article; X X /* create an new article */ X if (artnum >= absfirst) { X if (artnum > lastart) X grow_cache(artnum); X article = article_ptr(artnum); X } else { X article = (ARTICLE *)safemalloc(sizeof (ARTICLE)); X bzero((char*)article, sizeof (ARTICLE)); X article->flags |= AF_READ|AF_FAKE|AF_MISSING|AF_TMPMEM; X } X return article; X} X Xvoid Xfix_msgid(msgid) Xchar *msgid; X{ X register char *cp; X X if ((cp = index(msgid, '@')) != Nullch) { X while (*++cp) { X if (isupper(*cp)) { X *cp = tolower(*cp); /* lower-case domain portion */ X } X } X } X} X Xint Xmsgid_cmp(key, keylen, data) Xchar *key; Xint keylen; XHASHDATUM data; X{ X ARTICLE *article = (data.dat_ptr? (ARTICLE*)data.dat_ptr X : article_ptr(data.dat_len)); X /* We already know that the lengths are equal, just compare the strings */ X return bcmp(key, article->msgid, keylen); X} X XSUBJECT *fake_had_subj; /* the fake-turned-real article had this subject */ X Xbool Xvalid_article(article) XARTICLE *article; X{ X ARTICLE *ap, *fake_ap; X char *msgid = article->msgid; X HASHDATUM data; X X if (msgid) { X fix_msgid(msgid); X data = hashfetch(msgid_hash, msgid, strlen(msgid)); X if ((fake_ap = (ARTICLE*)data.dat_ptr) == Nullart) { X if (!data.dat_len) { X data.dat_len = article_num(article); X hashstorelast(data); X fake_had_subj = Nullsubj; X return TRUE; X } X if (data.dat_len == article_num(article)) { X fake_had_subj = Nullsubj; X return TRUE; X } X } X X /* Whenever we replace a fake art with a real one, it's a lot of work X ** cleaning up the references. Fortunately, this is not often. */ X if (fake_ap) { X article->parent = fake_ap->parent; X article->child1 = fake_ap->child1; X article->sibling = fake_ap->sibling; X fake_had_subj = fake_ap->subj; X if (fake_ap->autofl) { X article->autofl |= fake_ap->autofl; X localkf_changes = 2; X } X if (curr_artp == fake_ap) { X curr_artp = article; X curr_art = article_num(article); X } X if (recent_artp == fake_ap) { X recent_artp = article; X recent_art = article_num(article); X } X if ((ap = article->parent) != Nullart) { X if (ap->child1 == fake_ap) X ap->child1 = article; X else { X ap = ap->child1; X goto sibling_search; X } X } else if (fake_had_subj) { X register SUBJECT *sp = fake_had_subj; X if ((ap = sp->thread) == fake_ap) { X do { X sp->thread = article; X sp = sp->thread_link; X } while (sp != fake_had_subj); X } else { X sibling_search: X while (ap->sibling) { X if (ap->sibling == fake_ap) { X ap->sibling = article; X break; X } X ap = ap->sibling; X } X } X } X for (ap = article->child1; ap; ap = ap->sibling) X ap->parent = article; X clear_article(fake_ap); X free((char*)fake_ap); X data.dat_ptr = Nullch; X data.dat_len = article_num(article); X hashstorelast(data); X return TRUE; X } X } X /* Forget about the duplicate message-id or bogus article. */ X uncache_article(article,TRUE); X return FALSE; X} X X/* Take a message-id and see if we already know about it. If so, return X** the article, otherwise create a fake one. X*/ XARTICLE * Xget_article(msgid) Xchar *msgid; X{ X register ARTICLE *article; X HASHDATUM data; X X fix_msgid(msgid); X X data = hashfetch(msgid_hash, msgid, strlen(msgid)); X if (!(article = (ARTICLE *)data.dat_ptr)) { X if (data.dat_len) X article = article_ptr(data.dat_len); X else { X article = allocate_article(0); X data.dat_ptr = (char*)article; X article->msgid = savestr(msgid); X hashstorelast(data); X } X } X return article; X} X X/* Take all the data we've accumulated about the article and shove it into X** the article tree at the best place we can deduce. X*/ Xvoid Xthread_article(article) XARTICLE *article; X{ X register ARTICLE *ap, *last; X register char *cp, *end; X ARTICLE *kill_ap = ((article->autofl & AUTO_KILL)? article : Nullart); X int art_chain_autofl = article->autofl X | (article->subj->articles? article->subj->articles->autofl : 0); X int art_thread_autofl; X X /* We're definitely not a fake anymore */ X article->flags = (article->flags & ~AF_FAKE) | AF_THREADED; X X /* If the article was already part of an existing thread, unlink it X ** to try to put it in the best possible spot. X */ X if (fake_had_subj) { X ARTICLE *stopper; X if (fake_had_subj->thread != article->subj->thread) X merge_threads(fake_had_subj, article->subj); X /* Check for a real or shared-fake parent */ X ap = article->parent; X while (ap && (ap->flags&AF_FAKE) == AF_FAKE && !ap->child1->sibling) { X last = ap; X ap = ap->parent; X } X stopper = ap; X unlink_child(article); X /* We'll assume that this article has as good or better references X ** than the child that faked us initially. Free the fake reference- X ** chain and process our references as usual. X */ X for (ap = article->parent; ap != stopper; ap = last) { X unlink_child(ap); X last = ap->parent; X ap->date = 0; X ap->subj = 0; X ap->parent = 0; X /* don't free it until group exit since we probably re-use it */ X } X article->parent = Nullart; /* neaten up */ X article->sibling = Nullart; X } X X /* If we have references, process them from the right end one at a time X ** until we either run into somebody, or we run out of references. X */ X if (*references) { X last = article; X ap = Nullart; X end = references + strlen(references) - 1; X while ((cp = rindex(references, '<')) != Nullch) { X while (end >= cp && ((unsigned char)*end <= ' ' || *end == ',')) { X end--; X } X end[1] = '\0'; X /* Quit parsing references if this one is garbage. */ X if (!(end = valid_message_id(cp, end))) X break; X /* Dump all domains that end in '.', such as "..." & "1@DEL." */ X if (end[-1] == '.') X break; X ap = get_article(cp); X *cp = '\0'; X art_chain_autofl |= ap->autofl; X if (ap->autofl & AUTO_KILL) X kill_ap = ap; X X /* Check for duplicates on the reference line. Brand-new data has X ** no date. Data we just allocated earlier on this line has a X ** date but no subj. Special-case the article itself, since it X ** does have a subj. X */ X if ((ap->date && !ap->subj) || ap == article) { X if ((ap = last) == article) X ap = Nullart; X continue; X } X last->parent = ap; X link_child(last); X if (ap->subj) X break; X X ap->date = article->date; X last = ap; X end = cp-1; X } X if (!ap) X goto no_references; X X /* Check if we ran into anybody that was already linked. If so, we X ** just use their thread. X */ X if (ap->subj) { X /* See if this article spans the gap between what we thought X ** were two different threads. X */ X if (article->subj->thread != ap->subj->thread) X merge_threads(ap->subj, article->subj); X } else { X /* We didn't find anybody we knew, so either create a new thread X ** or use the article's thread if it was previously faked. X */ X ap->subj = article->subj; X link_child(ap); X } X /* Set the subj of faked articles we created as references. */ X for (ap = article->parent; ap && !ap->subj; ap = ap->parent) X ap->subj = article->subj; X X /* Make sure we didn't circularly link to a child article(!), by X ** ensuring that we run off the top before we run into ourself. X */ X while (ap && ap->parent != article) X ap = ap->parent; X if (ap) { X /* Ugh. Someone's tweaked reference line with an incorrect X ** article-order arrived first, and one of our children is X ** really one of our ancestors. Cut off the bogus child branch X ** right where we are and link it to the thread. X */ X unlink_child(ap); X ap->parent = Nullart; X link_child(ap); X } X } else { X no_references: X /* The article has no references. Either turn it into a new thread X ** or re-attach the fleshed-out article to its old thread. X */ X link_child(article); X } X if (!(article->flags & AF_CACHED)) X cache_article(article); X art_thread_autofl = art_chain_autofl; X if (sel_mode == SM_THREAD && article == article->subj->articles) { X SUBJECT *sp = article->subj->thread_link; X while (sp != article->subj) { X if (sp->articles) X art_thread_autofl |= sp->articles->autofl; X sp = sp->thread_link; X } X } X if (art_thread_autofl & AUTO_SELECTALL) { X if (sel_mode == SM_THREAD) X select_arts_thread(article, AUTO_SELECTALL); X else X select_arts_subject(article, AUTO_SELECTALL); X } else if (art_chain_autofl & AUTO_SELECT) X select_subthread(article, AUTO_SELECT); X if (art_thread_autofl & AUTO_KILLALL) { X if (sel_mode == SM_THREAD) X kill_arts_thread(article, KF_ALL|KF_KILLFILE); X else X kill_arts_subject(article, KF_ALL|KF_KILLFILE); X } X else if (kill_ap) X kill_subthread(kill_ap, KF_ALL|KF_KILLFILE); X} X X/* Check if the string we've found looks like a valid message-id reference. X*/ Xstatic char * Xvalid_message_id(start, end) Xregister char *start, *end; X{ X char *mid; X X if (start == end) X return 0; X X if (*end != '>') { X /* Compensate for space cadets who include the header in their X ** subsitution of all '>'s into another citation character. X */ X if (*end == '<' || *end == '-' || *end == '!' || *end == '%' X || *end == ')' || *end == '|' || *end == ':' || *end == '}' X || *end == '*' || *end == '+' || *end == '#' || *end == ']' X || *end == '@' || *end == '$') { X *end = '>'; X } X } else if (end[-1] == '>') { X *(end--) = '\0'; X } X /* Id must be "<...@...>" */ X if (*start != '<' || *end != '>' || (mid = index(start, '@')) == Nullch X || mid == start+1 || mid+1 == end) { X return 0; X } X return end; X} X X/* Remove an article from its parent/siblings. Leave parent pointer intact. X*/ Xstatic void Xunlink_child(child) Xregister ARTICLE *child; X{ X register ARTICLE *last; X X if (!(last = child->parent)) { X register SUBJECT *sp = child->subj; X if ((last = sp->thread) == child) { X do { X sp->thread = child->sibling; X sp = sp->thread_link; X } while (sp != child->subj); X } else X goto sibling_search; X } else { X if (last->child1 == child) X last->child1 = child->sibling; X else { X last = last->child1; X sibling_search: X while (last->sibling != child) X last = last->sibling; X last->sibling = child->sibling; X } X } X} X X/* Link an article to its parent article. If its parent pointer is zero, X** link it to its thread. Sorts siblings by date. X*/ Xstatic void Xlink_child(child) Xregister ARTICLE *child; X{ X register ARTICLE *ap; X X if (!(ap = child->parent)) { X register SUBJECT *sp = child->subj; X ap = sp->thread; X if (!ap || child->date < ap->date) { X do { X sp->thread = child; X sp = sp->thread_link; X } while (sp != child->subj); X child->sibling = ap; X } else X goto sibling_search; X } else { X ap = ap->child1; X if (!ap || child->date < ap->date) { X child->sibling = ap; X child->parent->child1 = child; X } else { X sibling_search: X while (ap->sibling && ap->sibling->date <= child->date) X ap = ap->sibling; X child->sibling = ap->sibling; X ap->sibling = child; X } X } X} X X/* Merge all of s2's thread into s1's thread. X*/ Xvoid Xmerge_threads(s1, s2) XSUBJECT *s1, *s2; X{ X register SUBJECT *sp; X register ARTICLE *t1, *t2; X X t1 = s1->thread; X t2 = s2->thread; X /* Change all of t2's thread pointers to a common lead article */ X sp = s2; X do { X sp->thread = t1; X sp = sp->thread_link; X } while (sp != s2); X X /* Join the two circular lists together */ X sp = s2->thread_link; X s2->thread_link = s1->thread_link; X s1->thread_link = sp; X X /* If thread mode is set, ensure the subjects are adjacent in the list. */ X /* Don't do this if the selector is active, because it gets messed up. */ X if (sel_mode == SM_THREAD && mode != 't') { X for (sp = s2; sp->prev && sp->prev->thread == t1; ) { X sp = sp->prev; X if (sp == s1) X goto artlink; X } X while (s2->next && s2->next->thread == t1) { X s2 = s2->next; X if (s2 == s1) X goto artlink; X } X /* Unlink the s2 chunk of subjects from the list */ X if (!sp->prev) X first_subject = s2->next; X else X sp->prev->next = s2->next; X if (!s2->next) X last_subject = sp->prev; X else X s2->next->prev = sp->prev; X /* Link the s2 chunk after s1 */ X sp->prev = s1; X s2->next = s1->next; X if (!s1->next) X last_subject = s2; X else X s1->next->prev = s2; X s1->next = sp; X } X X artlink: X /* Link each article that was attached to t2 to t1. */ X for (t1 = t2; t1; t1 = t2) { X t2 = t2->sibling; X link_child(t1); /* parent is null, thread is newly set */ X } X} SHAR_EOF $shar_touch -am 1118220194 'trn-3.6/rt-process.c' && chmod 0644 'trn-3.6/rt-process.c' || echo 'restore of trn-3.6/rt-process.c failed' shar_count="`wc -c < 'trn-3.6/rt-process.c'`" test 13440 -eq "$shar_count" || echo "trn-3.6/rt-process.c: original size 13440, current size $shar_count" rm -f _sharnew.tmp fi # ============= trn-3.6/rt-select.c ============== if test -f 'trn-3.6/rt-select.c' && test X"$1" != X"-c"; then echo 'x - skipping trn-3.6/rt-select.c (file already exists)' rm -f _sharnew.tmp else > _sharnew.tmp echo 'x - extracting trn-3.6/rt-select.c (text)' sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/rt-select.c' && X/* $Id: rt-select.c,v 3.0 1992/12/14 00:14:12 davison Trn $ X*/ X/* The authors make no claims as to the fitness or correctness of this software X * for any use whatsoever, and it is provided as is. Any use of this software X * is at the user's own risk. X */ X X#include "EXTERN.h" X#include "common.h" X#include "trn.h" X#include "term.h" X#include "final.h" X#include "util.h" X#include "help.h" X#include "cache.h" X#include "bits.h" X#include "artsrch.h" X#include "ng.h" X#include "ngdata.h" X#include "ngstuff.h" X#include "kfile.h" X#include "rthread.h" X#include "rt-page.h" X#include "rt-util.h" X#include "INTERN.h" X#include "rt-select.h" X X/* When display mode is 'l', each author gets a separate line; when 's', no X** authors are displayed. X*/ Xchar *display_mode = select_order; Xchar sel_disp_char[] = { ' ', '+', '-', '*' }; X Xstatic char sel_ret; Xstatic bool empty_ok; Xstatic bool disp_status_line; Xstatic bool clean_screen; X X/* CAA hacks: xterm mouse support */ Xstatic char *sel_mouse_save; X Xvoid Xsel_go_line(line) Xint line; X{ X int i; X X for (i = 0; i < sel_item_cnt; i++) { X if (sel_items[i].line > line) X break; X } X if (i > 0) X i--; X sel_item_index = i; X} X Xvoid Xsel_do_mouse(button,x,y) Xint button; /* 0: button1 1: button2 2: button3 3: release */ Xint x,y; X{ X switch (button) { X case 0: X case 1: X if (!y) X sel_mouse_save = "<"; X else if (y >= sel_last_line) X sel_mouse_save = (button == 0)? " " : ">"; X else { X sel_go_line(y); X if (button == 0) X sel_mouse_save = "."; X } X break; X case 2: X /* move forward or backwards a page: X * if cursor in top half: backwards X * if cursor in bottom half: forwards X */ X if (y<(LINES/2)) X sel_mouse_save = "<"; X else X sel_mouse_save = ">"; X break; X case 3: X /* do range stuff here later? */ X break; X } X} X Xvoid Xsel_mouse() X{ X int x,y; X int button; X X sel_mouse_save = Nullch; X read_tty(buf,1); X button = (int)(buf[0]); X read_tty(buf,1); X x = (int)(buf[0])-33; X read_tty(buf,1); X y = (int)(buf[0])-33; X sel_do_mouse(button&3,x,y); X X /* get the button-up event */ X while (1) { X getcmd(buf); X if (*buf == 3) /* got the button-up */ X break; X /* otherwise just eat any other keystrokes */ X } X X /* interpret the button-up event */ X read_tty(buf,1); X button = (int)(buf[0]); X read_tty(buf,1); X x = (int)(buf[0])-33; X read_tty(buf,1); X y = (int)(buf[0])-33; X sel_do_mouse(button&3,x,y); X if (sel_mouse_save) X pushstring(sel_mouse_save,0); X} X X/* Display a menu of threads/subjects/articles for the user to choose from. X** If "cmd" is '+' we display all the unread items and allow the user to mark X** them as selected and perform various commands upon them. If "cmd" is 'U' X** the list consists of previously-read items for the user to mark as unread. X*/ Xchar Xdo_selector(cmd) Xchar_int cmd; X{ X register int j; X int got_dash; X int ch, action; X char page_char, end_char; X char promptbuf[80]; X char oldmode = mode; X char *in_select; X X mode = 't'; X art = lastart+1; X sel_rereading = (cmd == 'U'); X clear_on_stop = TRUE; X empty_ok = FALSE; X X set_sel_mode(cmd); X xmouse_on(); X X if (!cache_range(sel_rereading? absfirst : firstart, lastart)) { X sel_ret = '+'; X goto sel_exit; X } X X start_selector: X /* Setup for selecting articles to read or set unread */ X if (sel_rereading) { X page_char = '>'; X end_char = 'Z'; X sel_page_app = Null(ARTICLE**); X sel_page_sp = Nullsubj; X sel_mask = AF_DELSEL; X } else { X page_char = page_select; X end_char = end_select; X if (curr_artp) { X sel_last_ap = curr_artp; X sel_last_sp = curr_artp->subj; X } X sel_mask = AF_SEL; X } X selected_only = TRUE; X count_subjects(cmd ? CS_UNSEL_STORE : CS_NORM); X X /* If nothing to display, we're done. */ X if (!article_count && !empty_ok) { X empty_complaint(); X sel_ret = '+'; X goto sel_exit; X } X init_pages(FILL_LAST_PAGE); X sel_item_index = 0; X *promptbuf = '\0'; X disp_status_line = FALSE; X if (added_articles) { X register long i = added_articles, j; X register ARTICLE *ap = article_ptr(lastart - i + 1); X for (j = 0; j < added_articles; j++, ap++) { X if (ap->flags & AF_READ) X i--; X } X if (i == added_articles) X sprintf(promptbuf, "** %ld new article%s arrived ** ", X (long)added_articles, added_articles == 1? nullstr : "s"); X else X sprintf(promptbuf, "** %ld of %ld new articles unread ** ", X i, (long)added_articles); X disp_status_line = TRUE; X } X added_articles = 0; X if (cmd && selected_count) { X sprintf(promptbuf+strlen(promptbuf), "%ld article%s selected.", X (long)selected_count, selected_count == 1? " is" : "s are"); X disp_status_line = TRUE; X } X cmd = 0; Xdisplay_selector: X /* Present a page of items to the user */ X display_page(); X X /* Check if there is really anything left to display. */ X if (!sel_item_cnt && !empty_ok) { X empty_complaint(); X sel_ret = 'q'; X goto sel_exit; X } X empty_ok = FALSE; X X if (sel_item_index >= sel_item_cnt) X sel_item_index = 0; X if (disp_status_line) { X printf("\n%s", promptbuf); X if (can_home) { X carriage_return(); X goto_line(sel_last_line+1, sel_last_line); X } else X putchar('\n'); X } Xreask_selector: X /* Prompt the user */ X#ifdef MAILCALL X setmail(FALSE); X#endif X if (sel_at_end) X sprintf(cmd_buf, "%s [%c%c] --", X (!sel_prior_arts? "All" : "Bot"), end_char, page_char); X else X sprintf(cmd_buf, "%s%ld%% [%c%c] --", X (!sel_prior_arts? "Top " : nullstr), X (long)((sel_prior_arts+sel_page_arts)*100 / sel_total_arts), X page_char, end_char); X interp(buf, sizeof buf, mailcall); X sprintf(promptbuf, "%s-- %s %s (%s%s order) -- %s", buf, X sel_exclusive? "SELECTED" : "Select", sel_mode_string, X sel_direction<0? "reverse " : nullstr, sel_sort_string, cmd_buf); X#ifdef CLEAREOL X if (erase_screen && can_home_clear) X clear_rest(); X#endif X standout(); X fputs(promptbuf, stdout); X un_standout(); X if (can_home) X carriage_return(); X sel_line = sel_last_line; Xposition_selector: X got_dash = 0; X if (can_home) X goto_line(sel_line, sel_items[sel_item_index].line); X sel_line = sel_items[sel_item_index].line; Xreinp_selector: X /* Grab some commands from the user */ X fflush(stdout); X eat_typeahead(); X spin_char = sel_chars[sel_item_index]; X cache_until_key(); X spin_char = ' '; X#ifdef CONDSUB X getcmd(buf); X if (*buf == ' ') X setdef(buf, sel_at_end? &end_char : &page_char); X ch = *buf; X#else X getcmd(cmd_buf); /* If no conditionals, don't allow macros */ X buf[0] = ch = *cmd_buf; X buf[1] = FINISHCMD; X#endif X if (errno) X ch = Ctl('l'); X if (disp_status_line) { X if (can_home) { X goto_line(sel_line, sel_last_line+1); X erase_eol(); X sel_line = sel_last_line+1; X } X disp_status_line = FALSE; X } X/* CAA: hack for mouse support */ X if (ch == Ctl('c')) { X sel_mouse(); X goto position_selector; X } X if (ch == '-') { X got_dash = 1; X if (!can_home) X putchar('-'), fflush(stdout); X goto reinp_selector; X } X in_select = index(sel_chars, ch); X if (in_select) { X j = in_select - sel_chars; X if (j >= sel_item_cnt) { X dingaling(); X goto position_selector; X } else if (got_dash) X ; X else if (sel_items[j].sel == 1) X action = (sel_rereading ? 'k' : '-'); X else X action = '+'; X } else if (ch == '*' && sel_mode == SM_ARTICLE) { X register ARTICLE *ap = (ARTICLE*)sel_items[sel_item_index].ptr; X if (sel_items[sel_item_index].sel) X deselect_subject(ap->subj); X else X select_subject(ap->subj, 0); X update_page(); X goto position_selector; X } else if (ch == 'y' || ch == '.' || ch == '*') { X j = sel_item_index; X if (sel_items[j].sel == 1) X action = (sel_rereading ? 'k' : '-'); X else X action = '+'; X } else if (ch == 'k' || ch == 'j' || ch == ',') { X j = sel_item_index; X action = 'k'; X } else if (ch == 'm' || ch == '\\') { X j = sel_item_index; X action = '-'; X } else if (ch == 'M') { X j = sel_item_index; X action = 'M'; X } else if (ch == '@') { X sel_item_index = 0; X j = sel_item_cnt-1; X got_dash = 1; X action = '@'; X } else if (ch == '[' || ch == 'p') { X if (--sel_item_index < 0) X sel_item_index = sel_item_cnt ? sel_item_cnt-1 : 0; X goto position_selector; X } else if (ch == ']' || ch == 'n') { X if (++sel_item_index >= sel_item_cnt) X sel_item_index = 0; X goto position_selector; X } else { X sel_ret = ch; X switch (sel_command(ch)) { X case DS_POS: X if (!clean_screen) X goto display_selector; X goto position_selector; X case DS_ASK: X if (!clean_screen) X goto display_selector; X goto reask_selector; X case DS_DISPLAY: X ds_display: X if (disp_status_line) X strcpy(promptbuf, buf); X goto display_selector; X case DS_UPDATE: X if (!clean_screen) X goto ds_display; X if (disp_status_line) { X printf("\n%s",buf); X if (can_home) { X carriage_return(); X up_line(); X erase_eol(); X } X } X update_page(); X if (can_home) { X goto_line(sel_line, sel_last_line); X sel_line = sel_last_line; X } X goto reask_selector; X case DS_RESTART: X goto start_selector; X case DS_QUIT: X sel_cleanup(); X if (!output_chase_phrase) X putchar('\n') FLUSH; X goto sel_exit; X case DS_STATUS: X disp_status_line = TRUE; X if (!clean_screen) { X strcpy(promptbuf, buf); X goto display_selector; X } X if (can_home) { X goto_line(sel_line, sel_last_line+1); X sel_line = sel_last_line+1; X } else X putchar('\n'); X X fputs(buf, stdout); X X if (can_home) X carriage_return(); X else X putchar('\n'); X goto position_selector; X } X } X X /* The user manipulated one of the letters -- handle it. */ X if (!got_dash) X sel_item_index = j; X else { X if (j < sel_item_index) { X ch = sel_item_index-1; X sel_item_index = j; X j = ch; X } X } X if (++j == sel_item_cnt) X j = 0; X do { X register int sel = sel_items[sel_item_index].sel; X register SUBJECT *sp = (SUBJECT*)sel_items[sel_item_index].ptr; X if (can_home) { X goto_line(sel_line, sel_items[sel_item_index].line); X sel_line = sel_items[sel_item_index].line; X } X if (action == '@') { X if (sel == 2) X ch = (sel_rereading ? '+' : ' '); X else if (sel_rereading) X ch = 'k'; X else if (sel == 1) X ch = '-'; X else X ch = '+'; X } else X ch = action; X switch (ch) { X case '+': X if (sel_mode == SM_THREAD) X select_thread(sp->thread, 0); X else if (sel_mode == SM_SUBJECT) X select_subject(sp, 0); X else X select_article((ARTICLE*)sp, 0); X output_sel(1); X break; X case '-': case 'k': case 'M': X { X bool sel_reread_save = sel_rereading; X if (ch == 'M') { X if (sel_mode == SM_ARTICLE) X delay_unmark((ARTICLE*)sp); X else { X register ARTICLE *ap; X if (sel_mode == SM_THREAD) { X for (ap = first_art(sp); ap; ap = next_art(ap)) X if (!(ap->flags & AF_READ) ^ sel_rereading) X delay_unmark(ap); X } else { X for (ap = sp->articles; ap; ap = ap->subj_next) X if (!(ap->flags & AF_READ) ^ sel_rereading) X delay_unmark(ap); X } X } X } X if (ch == '-') X sel_rereading = FALSE; X else X sel_rereading = TRUE; X if (sel_mode == SM_THREAD) X deselect_thread(sp->thread); X else if (sel_mode == SM_SUBJECT) X deselect_subject(sp); X else X deselect_article((ARTICLE*)sp); X sel_rereading = sel_reread_save; X output_sel(ch == '-'? 0 : 2); X break; X } X } X fflush(stdout); X if (++sel_item_index == sel_item_cnt) X sel_item_index = 0; X if (can_home) X carriage_return(); X } while (sel_item_index != j); X goto position_selector; X Xsel_exit: X if (sel_rereading) { X sel_rereading = 0; X sel_mask = AF_SEL; X } X if (sel_mode != SM_ARTICLE || sel_sort == SS_GROUPS X || sel_sort == SS_SUBJECT) { X if (artptr_list) { X free((char*)artptr_list); X artptr_list = sel_page_app = Null(ARTICLE**); X sort_subjects(); X } X artptr = Null(ARTICLE**); X#ifdef ARTSEARCH X if (!ThreadedGroup) X srchahead = -1; X#endif X } X#ifdef ARTSEARCH X else X srchahead = 0; X#endif X selected_only = (selected_count != 0); X if (sel_ret != '#') X count_subjects(sel_ret == '+'? CS_RESELECT : CS_UNSELECT); X clear_on_stop = FALSE; X mode = oldmode; X if (sel_ret == '+') { X art = curr_art; X artp = curr_artp; X } else X top_article(); X xmouse_off(); X return sel_ret; X} X Xstatic void Xsel_cleanup() X{ X if (sel_rereading) { X /* Turn selections into unread selected articles. Let X ** count_subjects() fix the counts after we're through. X */ X register SUBJECT *sp; X sel_last_ap = Nullart; X sel_last_sp = Nullsubj; X for (sp = first_subject; sp; sp = sp->next) X unkill_subject(sp); X } else { X if (sel_mode == SM_ARTICLE) { X register ARTICLE *ap; X register ART_NUM an; X for (an=absfirst, ap=article_ptr(an); an<=lastart; an++, ap++) { X if (ap->flags & AF_DEL) { X ap->flags &= ~AF_DEL; X set_read(ap); X } X } X } else { X register SUBJECT *sp; X for (sp = first_subject; sp; sp = sp->next) { X if (sp->flags & SF_DEL) { X sp->flags &= ~SF_DEL; X if (sel_mode == SM_THREAD) X kill_thread(sp->thread, KF_UNSELECTED); X else X kill_subject(sp, KF_UNSELECTED); X } X } X } X } X} X Xstatic int Xsel_command(ch) Xchar_int ch; X{ X if (can_home) X goto_line(sel_line, sel_last_line); X sel_line = sel_last_line; X clean_screen = TRUE; X do_command: X output_chase_phrase = TRUE; X switch (ch) { X case '>': X sel_item_index = 0; X if (next_page()) X return DS_DISPLAY; X return DS_POS; X case '<': X sel_item_index = 0; X if (prev_page()) X return DS_DISPLAY; X return DS_POS; X case '^': case Ctl('r'): X sel_item_index = 0; X if (first_page()) X return DS_DISPLAY; X return DS_POS; X case '$': X sel_item_index = 0; X if (last_page()) X return DS_DISPLAY; X return DS_POS; X case Ctl('l'): X return DS_DISPLAY; X case Ctl('^'): X erase_eol(); /* erase the prompt */ X#ifdef MAILCALL X setmail(TRUE); /* force a mail check */ X#endif X return DS_ASK; X case '#': X { X register SUBJECT *sp; X for (sp = first_subject; sp; sp = sp->next) X sp->flags &= ~SF_VISIT; X selected_count = 0; X sp = (SUBJECT*)sel_items[sel_item_index].ptr; X switch (sel_mode) { X case SM_THREAD: X deselect_thread(sp->thread); X select_thread(sp->thread, 0); X break; X case SM_SUBJECT: X deselect_subject(sp); X select_subject(sp, 0); X break; X case SM_ARTICLE: X deselect_article((ARTICLE*)sp); X select_article((ARTICLE*)sp, 0); X break; X } X return DS_QUIT; X } X case '\r': case '\n': X if (!selected_count) { X if (sel_rereading || sel_items[sel_item_index].sel != 2) { X register SUBJECT *sp = (SUBJECT*)sel_items[sel_item_index].ptr; X switch (sel_mode) { X case SM_THREAD: X select_thread(sp->thread, 0); X break; X case SM_SUBJECT: X select_subject(sp, 0); X break; X case SM_ARTICLE: X select_article((ARTICLE*)sp, 0); X break; X } X } X } X return DS_QUIT; X case 'Z': case '\t': X return DS_QUIT; X case 'q': case 'Q': X return DS_QUIT; X case Ctl('Q'): case '\033': case '+': X sel_ret = '+'; X return DS_QUIT; X case 'N': case 'P': X return DS_QUIT; X case 'L': X if (!*++display_mode) X display_mode = select_order; X return DS_DISPLAY; X case 'Y': X if (!dmcount) { X sprintf(buf,"No marked articles to yank back."); X return DS_STATUS; X } X yankback(); X sel_line++; X if (!sel_rereading) X sel_cleanup(); X disp_status_line = TRUE; X count_subjects(CS_NORM); X sel_page_sp = Nullsubj; X sel_page_app = Null(ARTICLE**); X init_pages(PRESERVE_PAGE); X return DS_DISPLAY; X case 'U': X sel_cleanup(); X sel_rereading = !sel_rereading; X sel_page_sp = Nullsubj; X sel_page_app = Null(ARTICLE**); X if (!cache_range(sel_rereading? absfirst : firstart, lastart)) X sel_rereading = !sel_rereading; X empty_ok = TRUE; X return DS_RESTART; X case '=': X if (!sel_rereading) X sel_cleanup(); X if (sel_mode == SM_ARTICLE) { X set_selector(sel_threadmode, sel_threadsort); X sel_page_sp = sel_page_app[0]->subj; X } else { X set_selector(SM_ARTICLE, sel_artsort); X sel_page_app = 0; X } X count_subjects(CS_NORM); X sel_item_index = 0; X init_pages(FILL_LAST_PAGE); X return DS_DISPLAY; X case 'S': X if (!sel_rereading) X sel_cleanup(); X erase_eol(); /* erase the prompt */ X reask_output: X in_char("Selector mode: Threads, Subjects, Articles?", 'o', "tsa"); X#ifdef VERIFY X printcmd(); X#endif X if (*buf == 'h') { X#ifdef VERBOSE X IF(verbose) X fputs("\n\ XType t or SP to display/select thread groups (threads the group, if needed).\n\ XType s to display/select subject groups.\n\ XType a to display/select individual articles.\n\ XType q to leave things as they are.\n\n\ X",stdout) FLUSH; X ELSE X#endif X#ifdef TERSE X fputs("\n\ Xt or SP selects thread groups (threads the group too).\n\ Xs selects subject groups.\n\ Xa selects individual articles.\n\ Xq does nothing.\n\n\ X",stdout) FLUSH; X#endif X clean_screen = FALSE; X goto reask_output; X } else if (*buf == 'q') { X if (can_home) { X carriage_return(); X erase_eol(); X } X return DS_ASK; X } X set_sel_mode(*buf); X count_subjects(CS_NORM); X init_pages(FILL_LAST_PAGE); X return DS_DISPLAY; X case 'O': X if (!sel_rereading) X sel_cleanup(); X erase_eol(); /* erase the prompt */ X reask_sort: X if (sel_mode == SM_ARTICLE) X in_char("Order by Date, Subject, Author, Number, subject-date Groups?", X 'q', "dsangDSANG"); X else X in_char("Order by Date, Subject, or Count?", 'q', "dscDSC"); X#ifdef VERIFY X printcmd(); X#endif X if (*buf == 'h') { X#ifdef VERBOSE X IF(verbose) { X fputs("\n\ XType d or SP to order the displayed items by date.\n\ XType s to order the items by subject.\n\ X",stdout) FLUSH; X if (sel_mode == SM_ARTICLE) X fputs("\ XType a to order the items by author.\n\ XType n to order the items by number.\n\ XType g to order the items in subject-groups by date.\n\ X",stdout) FLUSH; X else X fputs("\ XType c to order the items by article-count.\n\ X",stdout) FLUSH; X fputs("\ XTyping your selection in upper case it will reverse the selected order.\n\ XType q to leave things as they are.\n\n\ X",stdout) FLUSH; X } X ELSE X#endif X#ifdef TERSE X { X fputs("\n\ Xd or SP sorts by date.\n\ Xs sorts by subject.\n\ X",stdout) FLUSH; X if (sel_mode == SM_ARTICLE) X fputs("\ Xa sorts by author.\n\ Xg sorts in subject-groups by date.\n\ X",stdout) FLUSH; X else X fputs("\ Xc sorts by article-count.\n\ X",stdout) FLUSH; X fputs("\ XUpper case reverses the sort.\n\ Xq does nothing.\n\n\ X",stdout) FLUSH; X } X#endif X clean_screen = FALSE; X goto reask_sort; X } else if (*buf == 'q') { X if (can_home) { X carriage_return(); X erase_eol(); X } X return DS_ASK; X } X set_sel_sort(*buf); X count_subjects(CS_NORM); X sel_page_sp = Nullsubj; X sel_page_app = Null(ARTICLE**); X init_pages(FILL_LAST_PAGE); X return DS_DISPLAY; X case 'R': X if (!sel_rereading) X sel_cleanup(); X sel_direction *= -1; X count_subjects(CS_NORM); X sel_page_sp = Nullsubj; X sel_page_app = Null(ARTICLE**); X init_pages(FILL_LAST_PAGE); X return DS_DISPLAY; X case 'E': X if (!sel_rereading) X sel_cleanup(); X sel_exclusive = !sel_exclusive; X count_subjects(CS_NORM); X sel_page_sp = Nullsubj; X sel_page_app = Null(ARTICLE**); X init_pages(FILL_LAST_PAGE); X empty_ok = TRUE; X sel_item_index = 0; X return DS_DISPLAY; X case 'X': case 'D': case 'J': X if (!sel_rereading) { X if (sel_mode == SM_ARTICLE) { X register ARTICLE *ap, **app, **limit; X limit = artptr_list + artptr_list_size; X if (ch == 'D') X app = sel_page_app; X else X app = artptr_list; X for (;;) { X ap = *app; X if ((!(ap->flags & AF_SEL) ^ (ch == 'J')) X || (ap->flags & AF_DEL)) X if (!sel_exclusive || (ap->flags & AF_INCLUDED)) X set_read(ap); X app++; X if (app >= limit || (ch == 'D' && app == sel_next_app)) X break; X } X } else { X register SUBJECT *sp; X if (ch == 'D') X sp = sel_page_sp; X else X sp = first_subject; X for (;;) { X if (((!(sp->flags & SF_SEL) ^ (ch == 'J')) && sp->misc) X || (sp->flags & SF_DEL)) { X if (!sel_exclusive || (sp->flags & SF_INCLUDED)) X kill_subject(sp, ch=='J'? KF_ALL : KF_UNSELECTED); X } X sp = sp->next; X if (!sp || (ch == 'D' && sp == sel_next_sp)) X break; X } X } X count_subjects(CS_UNSELECT); X if (article_count X && (ch == 'J' || (ch == 'D' && !selected_count))) { X init_pages(FILL_LAST_PAGE); X sel_item_index = 0; X return DS_DISPLAY; X } X if (artptr_list && article_count) X sort_articles(); X return DS_QUIT; X } else if (ch == 'J') { X register SUBJECT *sp; X for (sp = first_subject; sp; sp = sp->next) X deselect_subject(sp); X selected_subj_cnt = selected_count = 0; X return DS_DISPLAY; X } X return DS_QUIT; X case 'T': X if (!ThreadedGroup) { X sprintf(buf,"Group is not threaded."); X return DS_STATUS; X } X /* FALL THROUGH */ X case 'A': X erase_eol(); /* erase the prompt */ X if (sel_mode == SM_ARTICLE) X artp = (ARTICLE*)sel_items[sel_item_index].ptr; X else { X register SUBJECT *sp = (SUBJECT*)sel_items[sel_item_index].ptr; X if (sel_mode == SM_THREAD) { X while (!sp->misc) X sp = sp->thread_link; X } X artp = sp->articles; X } X art = article_num(artp); X /* This call executes the action too */ X switch (ask_memorize(ch)) { X case 'j': case ',': X count_subjects(sel_rereading ? CS_NORM : CS_UNSELECT); X init_pages(PRESERVE_PAGE); X sprintf(buf,"Kill memorized."); X disp_status_line = TRUE; X return DS_DISPLAY; X case '.': X sprintf(buf,"Selection memorized."); X disp_status_line = TRUE; X return DS_DISPLAY; X case '+': X sprintf(buf,"Selection memorized."); X disp_status_line = TRUE; X return DS_UPDATE; X case 'c': case 'C': X sprintf(buf,"Auto-commands cleared."); X disp_status_line = TRUE; X return DS_DISPLAY; X case 'q': X return DS_DISPLAY; X case 'Q': X break; X } X if (can_home) { X carriage_return(); X erase_eol(); X } X return DS_ASK; X case Ctl('k'): X xmouse_off(); X edit_kfile(); X xmouse_on(); X return DS_DISPLAY; X case ':': X if (sel_mode == SM_ARTICLE) X artp = (ARTICLE*)sel_items[sel_item_index].ptr; X else { X register SUBJECT *sp = (SUBJECT*)sel_items[sel_item_index].ptr; X if (sel_mode == SM_THREAD) { X while (!sp->misc) X sp = sp->thread_link; X } X artp = sp->articles; X } X art = article_num(artp); X /* FALL THROUGH */ X case '/': case '&': case '!': X erase_eol(); /* erase the prompt */ X if (!finish_command(TRUE)) { /* get rest of command */ X if (clean_screen) X return DS_ASK; X goto extend_done; X } X if (ch == '&' || ch == '!') { X xmouse_off(); X one_command = TRUE; X perform(buf, FALSE); X one_command = FALSE; X putchar('\n') FLUSH; X clean_screen = FALSE; X xmouse_on(); X } else { X int sel_art_save = selected_count; X X if (ch == ':') { X clean_screen = (thread_perform() == 2) && clean_screen; X if (!sel_rereading) { X register SUBJECT *sp; X for (sp = first_subject; sp; sp = sp->next) { X if (sp->flags & SF_DEL) { X sp->flags = 0; X if (sel_mode == SM_THREAD) X kill_thread(sp->thread, KF_UNSELECTED); X else X kill_subject(sp, KF_UNSELECTED); X } X } X } X } else { X /* Force the search to begin at absfirst or firstart, X ** depending upon whether they specified the 'r' option. X */ X art = lastart+1; X page_line = 1; X switch (art_search(buf, sizeof buf, FALSE)) { X case SRCH_ERROR: X case SRCH_ABORT: X case SRCH_INTR: X fputs("\nInterrupted\n", stdout) FLUSH; X break; X case SRCH_DONE: X case SRCH_SUBJDONE: X fputs("Done\n", stdout) FLUSH; X break; X case SRCH_NOTFOUND: X fputs("\nNot found.\n", stdout) FLUSH; X break; X case SRCH_FOUND: X break; X } X clean_screen = FALSE; X } X /* Recount, in case something has changed. */ X count_subjects(sel_rereading ? CS_NORM : CS_UNSELECT); X init_pages(PRESERVE_PAGE); X sel_item_index = 0; X X sel_art_save -= selected_count; X if (sel_art_save) { X putchar('\n'); X if (sel_art_save < 0) { X fputs("S", stdout); X sel_art_save *= -1; X } else X fputs("Des", stdout); X printf("elected %d article%s.", X sel_art_save, sel_art_save == 1 ? nullstr : "s"); X clean_screen = FALSE; X } X if (!clean_screen) X putchar('\n') FLUSH; X }/* if !& else :/ */ X X if (clean_screen) { X carriage_return(); X up_line(); X erase_eol(); X return DS_ASK; X } X extend_done: X if ((ch = pause_getcmd())) { X got_cmd: X if (ch > 0) { X /* try to optimize the screen update for some commands. */ X if (!index(sel_chars, ch) X && (index("<+>^$!?&:;/hDEJLNOPqQRSUXYZ\n\r\t\033", ch) X || ch == Ctl('k'))) { X buf[0] = sel_ret = ch; X buf[1] = FINISHCMD; X goto do_command; X } X pushchar(ch | 0200); X } X } X return DS_DISPLAY; X case 'c': X erase_eol(); /* erase the prompt */ X if ((ch = ask_catchup()) == 'y' || ch == 'u') { X count_subjects(CS_UNSELECT); X if (ch != 'u' && article_count) { X sel_page_sp = Nullsubj; X sel_page_app = Null(ARTICLE**); X init_pages(FILL_LAST_PAGE); X return DS_DISPLAY; X } X sel_ret = 'Z'; X return DS_QUIT; X } X if (ch != 'N') X return DS_DISPLAY; X if (can_home) { X carriage_return(); X erase_eol(); X } X return DS_ASK; X case 'h': case '?': X putchar('\n'); X if ((ch = help_select()) || (ch = pause_getcmd())) X goto got_cmd; X return DS_DISPLAY; X default: X sprintf(buf,"Type ? for help."); X settle_down(); X if (clean_screen) X return DS_STATUS; X printf("\n%s\n",buf); X goto extend_done; X } X} X Xstatic void Xempty_complaint() X{ X clear_on_stop = FALSE; X putchar('\n'); X if (sel_rereading) { X#ifdef VERBOSE X IF (verbose) X fputs("\nNo articles to set unread.\n", stdout); X ELSE X#endif X#ifdef TERSE X fputs("\nNo articles.\n", stdout) FLUSH; X#endif X sel_rereading = 0; X sel_mask = AF_SEL; X } else { X#ifdef VERBOSE X IF (verbose) X fputs("\nNo unread articles to select.", stdout); X ELSE X#endif X#ifdef TERSE X fputs("\nNo unread articles.", stdout); X#endif X putchar('\n'); /* let "them" FLUSH */ X } X selected_only = FALSE; X art = curr_art; X artp = curr_artp; X} SHAR_EOF $shar_touch -am 1118220194 'trn-3.6/rt-select.c' && chmod 0644 'trn-3.6/rt-select.c' || echo 'restore of trn-3.6/rt-select.c failed' shar_count="`wc -c < 'trn-3.6/rt-select.c'`" test 26206 -eq "$shar_count" || echo "trn-3.6/rt-select.c: original size 26206, current size $shar_count" rm -f _sharnew.tmp fi # ============= trn-3.6/rt-util.c ============== if test -f 'trn-3.6/rt-util.c' && test X"$1" != X"-c"; then echo 'x - skipping trn-3.6/rt-util.c (file already exists)' rm -f _sharnew.tmp else > _sharnew.tmp echo 'x - extracting trn-3.6/rt-util.c (text)' sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/rt-util.c' && X/* $Id: rt-util.c,v 3.0 1992/12/14 00:14:12 davison Trn $ X*/ X/* The authors make no claims as to the fitness or correctness of this software X * for any use whatsoever, and it is provided as is. Any use of this software X * is at the user's own risk. X */ X X#include "EXTERN.h" X#include "common.h" X#include "cache.h" X#include "ngdata.h" X#include "artio.h" X#include "rthread.h" X#include "rt-select.h" X#include "term.h" X#include "nntp.h" X#include "INTERN.h" X#include "rt-util.h" X X/* Name-munging routines written by Ross Ridge. X** Enhanced by Wayne Davison. X*/ X X/* Extract the full-name part of an email address, returning NULL if not X** found. X*/ Xchar * Xextract_name(name) Xchar *name; X{ X char *s; X char *lparen, *rparen; X char *langle; X X while (isspace(*name)) { X name++; X } X X lparen = index(name, '('); X rparen = rindex(name, ')'); X langle = index(name, '<'); X if (!lparen && !langle) { X return NULL; X } else X if (langle && (!lparen || !rparen || lparen > langle || rparen < langle)) { X if (langle == name) { X return NULL; X } X *langle = '\0'; X } else { X name = lparen; X *name++ = '\0'; X while (isspace(*name)) { X name++; X } X if (name == rparen) { X return NULL; X } X if (rparen != NULL) { X *rparen = '\0'; X } X } X X if (*name == '"') { X name++; X while (isspace(*name)) { X name++; X } X if ((s = rindex(name, '"')) != NULL) { X *s = '\0'; X } X } X return name; X} X X/* If necessary, compress a net user's full name by playing games with X** initials and the middle name(s). If we start with "Ross Douglas Ridge" X** we try "Ross D Ridge", "Ross Ridge", "R D Ridge" and finally "R Ridge" X** before simply truncating the thing. We also turn "R. Douglas Ridge" X** into "Douglas Ridge" and "Ross Ridge D.D.S." into "Ross Ridge" as a X** first step of the compaction, if needed. X*/ Xchar * Xcompress_name(name, max) Xchar *name; Xint max; X{ X register char *s, *last, *mid, *d; X register int len, namelen, midlen; X int notlast; X Xtry_again: X /* First remove white space from both ends. */ X while (isspace(*name)) { X name++; X } X if ((len = strlen(name)) == 0) { X return name; X } X s = name + len - 1; X while (isspace(*s)) { X s--; X } X s[1] = '\0'; X if (s - name + 1 <= max) { X return name; X } X X /* Look for characters that likely mean the end of the name X ** and the start of some hopefully uninteresting additional info. X ** Spliting at a comma is somewhat questionalble, but since X ** "Ross Ridge, The Great HTMU" comes up much more often than X ** "Ridge, Ross" and since "R HTMU" is worse than "Ridge" we do X ** it anyways. X */ X for (d = name + 1; *d; d++) { X if (*d == ',' || *d == ';' || *d == '(' || *d == '@' X || (*d == '-' && (d[1] == '-' || d[1] == ' '))) { X *d-- = '\0'; X s = d; X break; X } X } X X /* Find the last name */ X do { X notlast = 0; X while (isspace(*s)) { X s--; X } X s[1] = '\0'; X len = s - name + 1; X if (len <= max) { X return name; X } X /* If the last name is an abbreviation it's not the one we want. */ X if (*s == '.') X notlast = 1; X while (!isspace(*s)) { X if (s == name) { /* only one name */ X name[max] = '\0'; X return name; X } X if (isdigit(*s)) { /* probably a phone number */ X notlast = 1; /* so chuck it */ X } X s--; X } X } while (notlast); X X last = s-- + 1; X X /* Look for a middle name */ X while (isspace(*s)) { /* get rid of any extra space */ X len--; X s--; X } X mid = name; X while (!isspace(*mid)) { X mid++; X } X namelen = mid - name + 1; X if (mid == s+1) { /* no middle name */ X mid = 0; X midlen = 0; X } else { X *mid++ = '\0'; X while (isspace(*mid)) { X len--; X mid++; X } X midlen = s - mid + 2; X /* If first name is an initial and middle isn't and it all fits X ** without the first initial, drop it. */ X if (len > max && mid != s) { X if (len - namelen <= max X && ((mid[1] != '.' && (!name[1] || (name[1] == '.' && !name[2]))) X || (*mid == '"' && *s == '"'))) { X len -= namelen; X name = mid; X namelen = midlen; X mid = 0; X } X else if (*mid == '"' && *s == '"') { X if (midlen > max) { X name = mid+1; X *s = '\0'; X goto try_again; X } X len = midlen; X last = mid; X namelen = 0; X mid = 0; X } X } X } X s[1] = '\0'; X if (mid && len > max) { X /* Turn middle names into intials */ X len -= s - mid + 2; X d = s = mid; X while (*s) { X if (isalpha(*s)) { X if (d != mid) { X *d++ = ' '; X } X *d++ = *s++; X } X while (*s && !isspace(*s)) { X s++; X } X while (isspace(*s)) { X s++; X } X } X if (d != mid) { X *d = '\0'; X midlen = d - mid + 1; X len += midlen; X } else { X mid = 0; X } X } X if (len > max) { X /* If the first name fits without the middle initials, drop them */ X if (mid && len - midlen <= max) { X len -= midlen; X mid = 0; X } else if (namelen > 0) { X /* Turn the first name into an initial */ X len -= namelen - 2; X name[1] = '\0'; X namelen = 2; X if (len > max) { X /* Dump the middle initials (if present) */ X if (mid) { X len -= midlen; X mid = 0; X } X if (len > max) { X /* Finally just truncate the last name */ X last[max - 2] = '\0'; X } X } X } else X namelen = 0; X } X X /* Paste the names back together */ X d = name + namelen; X if (namelen) X d[-1] = ' '; X if (mid) { X strcpy(d, mid); X d += midlen; X d[-1] = ' '; X } X strcpy(d, last); X return name; X} X X/* Compress an email address, trying to keep as much of the local part of X** the addresses as possible. The order of precence is @ ! %, but X** @ % ! may be better... X*/ Xstatic char * Xcompress_address(name, max) Xchar *name; Xint max; X{ X char *s, *at, *bang, *hack, *start; X int len; X X /* Remove white space from both ends. */ X while (isspace(*name)) { X name++; X } X if ((len = strlen(name)) == 0) { X return name; X } X s = name + len - 1; X while (isspace(*s)) { X s--; X } X s[1] = '\0'; X if (*name == '<') { X name++; X if (*s == '>') { X *s-- = '\0'; X } X } X if ((len = s - name + 1) <= max) { X return name; X } X X at = bang = hack = NULL; X for (s = name + 1; *s; s++) { X /* If there's whitespace in the middle then it's probably not X ** really an email address. */ X if (isspace(*s)) { X name[max] = '\0'; X return name; X } X switch (*s) { X case '@': X if (at == NULL) { X at = s; X } X break; X case '!': X if (at == NULL) { X bang = s; X hack = NULL; X } X break; X case '%': X if (at == NULL && hack == NULL) { X hack = s; X } X break; X } X } X if (at == NULL) { X at = name + len; X } X X if (hack != NULL) { X if (bang != NULL) { X if (at - bang - 1 >= max) { X start = bang + 1; X } else if (at - name >= max) { X start = at - max; X } else { X start = name; X } X } else { X start = name; X } X } else if (bang != NULL) { X if (at - name >= max) { X start = at - max; X } else { X start = name; X } X } else { X start = name; X } X if (len - (start - name) > max) { X start[max] = '\0'; X } X return start; X} X X/* Fit the author name in chars. Uses the comment portion if present X** and pads with spaces. X*/ Xchar * Xcompress_from(ap, size) XARTICLE *ap; Xint size; X{ X char *s, *t; X int len; X X for (t = cmd_buf, s = ap && ap->from? ap->from : nullstr; *s; ) { X if ((unsigned char)*s < ' ') X *t++ = ' ', s++; X else X *t++ = *s++; X } X *t = '\0'; X if ((s = extract_name(cmd_buf)) != NULL) X s = compress_name(s, size); X else X s = compress_address(cmd_buf, size); X len = strlen(s); X if (!len) { X strcpy(s,"NO NAME"); X len = 7; X } X while (len < size) X s[len++] = ' '; X s[size] = '\0'; X return s; X} X X#define EQ(x,y) ((isupper(x) ? tolower(x) : (x)) == (y)) X X/* Parse the subject to skip past any "Re[:^]"s at the start. X*/ Xchar * Xget_subject_start(str) Xregister char *str; X{ X while (*str && (unsigned char)*str <= ' ') X str++; X while (EQ(str[0], 'r') && EQ(str[1], 'e')) { /* check for Re: */ X register char *cp = str + 2; X if (*cp == '^') { /* allow Re^2: */ X while (*++cp <= '9' && *cp >= '0') X ; X } X if (*cp != ':') X break; X while (*++cp == ' ') X ; X str = cp; X } X return str; X} X X/* Output a subject in chars. Does intelligent trimming that tries to X** save the last two words on the line, excluding "(was: blah)" if needed. X*/ Xchar * Xcompress_subj(ap, max) XARTICLE *ap; Xint max; X{ X register char *cp; X register int len; X ARTICLE *first; X X if (!ap) X return ""; X X /* Put a preceeding '>' on subjects that are replies to other articles */ X cp = buf; X first = (ThreadedGroup? ap->subj->thread : ap->subj->articles); X if (ap != first || (ap->flags & AF_HAS_RE) X || !(!(ap->flags&AF_READ) ^ sel_rereading)) X *cp++ = '>'; X strcpy(cp, ap->subj->str + 4); X X /* Remove "(was: oldsubject)", because we already know the old subjects. X ** Also match "(Re: oldsubject)". Allow possible spaces after the ('s. X */ X for (cp = buf; (cp = index(cp+1, '(')) != Nullch;) { X while (*++cp == ' ') X ; X if (EQ(cp[0], 'w') && EQ(cp[1], 'a') && EQ(cp[2], 's') X && (cp[3] == ':' || cp[3] == ' ')) { X *--cp = '\0'; X break; X } X if (EQ(cp[0], 'r') && EQ(cp[1], 'e') X && ((cp[2]==':' && cp[3]==' ') || (cp[2]=='^' && cp[4]==':'))) { X *--cp = '\0'; X break; X } X } X len = strlen(buf); X if (!unbroken_subjects && len > max) { X char *last_word; X /* Try to include the last two words on the line while trimming */ X if ((last_word = rindex(buf, ' ')) != Nullch) { X char *next_to_last; X *last_word = '\0'; X if ((next_to_last = rindex(buf, ' ')) != Nullch) { X if (next_to_last-buf >= len - max + 3 + 10-1) X cp = next_to_last; X else X cp = last_word; X } else X cp = last_word; X *last_word = ' '; X if (cp-buf >= len - max + 3 + 10-1) { X sprintf(buf + max - (len-(cp-buf)+3), "...%s", cp + 1); X len = max; X } X } X } X if (len > max) X buf[max] = '\0'; X return buf; X} X X#ifndef HAS_STRCASECMP Xstatic unsigned char casemap[256] = { X 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, X 0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, X 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, X 0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, X 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27, X 0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, X 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37, X 0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, X 0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67, X 0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, X 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77, X 0x78,0x79,0x7A,0x7B,0x5C,0x5D,0x5E,0x5F, X 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67, X 0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, X 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77, X 0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F, X 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, X 0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, X 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97, X 0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, X 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7, X 0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, X 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7, X 0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, X 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7, X 0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, X 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7, X 0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, X 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7, X 0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, X 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7, X 0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF X}; X Xint Xstrcasecmp(s1, s2) Xregister char *s1, *s2; X{ X do { X if (casemap[(unsigned)*s1++] != casemap[(unsigned)*s2]) X return casemap[(unsigned)s1[-1]] - casemap[(unsigned)*s2]; X } while (*s2++ != '\0'); X return 0; X} X Xint Xstrncasecmp(s1, s2, len) Xregister char *s1, *s2; Xregister int len; X{ X while (len--) { X if (casemap[(unsigned)*s1++] != casemap[(unsigned)*s2]) X return casemap[(unsigned)s1[-1]] - casemap[(unsigned)*s2]; X if (*s2++ == '\0') X break; X } X return 0; X} X#endif X X/* Modified version of a spinner originally found in Clifford Adams' strn. */ X Xstatic char spinchars[] = {'|','/','-','\\'}; Xstatic int spin_place; /* represents place in spinchars array */ Xstatic int spin_count; /* counter for when to spin */ Xstatic int spin_level INIT(0); /* used to allow non-interfering nested spins */ Xstatic int spin_mode; Xstatic ART_NUM spin_art; Xstatic ART_POS spin_tell; X Xvoid Xsetspin(mode) Xint mode; X{ X switch (mode) { X case SPIN_FOREGROUND: X case SPIN_BACKGROUND: X if (!spin_level++) { X if ((spin_art = openart) != 0 && artfp) X spin_tell = tellart(); X spin_count = 1; /* not 0 to prevent immediate spin display */ X spin_place = 1; /* start with slash... */ X } X spin_mode = mode; X break; X case SPIN_POP: X if (--spin_level > 0) X break; X /* FALL THROUGH */ X case SPIN_OFF: X spin_level = 0; X if (spin_place > 1) { /* we have spun at least once */ X putchar(spin_char); /* get rid of spin character */ X backspace(); X fflush(stdout); X spin_place = 0; X } X if (spin_art) { X artopen(spin_art); X if (artfp) X seekart(spin_tell); /* do not screw up the pager */ X spin_art = 0; X } X spin_mode = SPIN_OFF; X break; X } X} X Xvoid Xspin(count) Xint count; /* modulus for the spin... */ X{ X if (!spin_level || (!bkgnd_spinner && spin_mode == SPIN_BACKGROUND)) X return; X if (!(spin_count++%count)) { X if (spin_mode == SPIN_FOREGROUND) X putchar('.'); X else { X putchar(spinchars[spin_place++%4]); X backspace(); X } X fflush(stdout); X } X} X Xbool Xinbackground() X{ X return spin_mode == SPIN_BACKGROUND; X} SHAR_EOF $shar_touch -am 1101184494 'trn-3.6/rt-util.c' && chmod 0644 'trn-3.6/rt-util.c' || echo 'restore of trn-3.6/rt-util.c failed' shar_count="`wc -c < 'trn-3.6/rt-util.c'`" test 13607 -eq "$shar_count" || echo "trn-3.6/rt-util.c: original size 13607, current size $shar_count" rm -f _sharnew.tmp fi # ============= trn-3.6/rt-wumpus.c ============== if test -f 'trn-3.6/rt-wumpus.c' && test X"$1" != X"-c"; then echo 'x - skipping trn-3.6/rt-wumpus.c (file already exists)' rm -f _sharnew.tmp else > _sharnew.tmp echo 'x - extracting trn-3.6/rt-wumpus.c (text)' sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/rt-wumpus.c' && X/* $Id: rt-wumpus.c,v 3.0 1992/12/14 00:14:00 davison Trn $ X*/ X/* The authors make no claims as to the fitness or correctness of this software X * for any use whatsoever, and it is provided as is. Any use of this software X * is at the user's own risk. X */ X X#include "EXTERN.h" X#include "common.h" X#include "cache.h" X#include "ng.h" X#include "head.h" X#include "util.h" X#include "term.h" X#include "final.h" X#include "ngdata.h" X#include "artio.h" X#include "backpage.h" X#include "rthread.h" X#include "rt-select.h" X#include "INTERN.h" X#include "rt-wumpus.h" X Xstatic char tree_indent[] = { X ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0, X ' ', ' ', ' ', ' ', 0, ' ', ' ', ' ', ' ', 0 X}; X Xchar letters[] = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+"; X Xstatic ARTICLE *tree_article; X Xstatic int max_depth, max_line = -1; Xstatic int first_depth, first_line; Xstatic int my_depth, my_line; Xstatic bool node_on_line; Xstatic int node_line_cnt; X Xstatic int line_num; Xstatic int header_indent; X Xstatic char *tree_lines[11]; Xstatic char tree_buff[128], *str; X X/* Prepare tree display for inclusion in the article header. X*/ Xvoid Xinit_tree() X{ X ARTICLE *thread; X SUBJECT *sp; X int num; X X while (max_line >= 0) /* free any previous tree data */ X free(tree_lines[max_line--]); X X if (!(tree_article = curr_artp) || !tree_article->subj) X return; X if (!(thread = tree_article->subj->thread)) X return; X /* Enumerate our subjects for display */ X sp = thread->subj; X num = 0; X do { X sp->misc = num++; X sp = sp->thread_link; X } while (sp != thread->subj); X X max_depth = max_line = my_depth = my_line = node_line_cnt = 0; X find_depth(thread, 0); X X if (max_depth <= 5) { X first_depth = 0; X } else { X if (my_depth+2 > max_depth) { X first_depth = max_depth - 5; X } else if ((first_depth = my_depth - 3) < 0) { X first_depth = 0; X } X max_depth = first_depth + 5; X } X if (--max_line < max_tree_lines) { X first_line = 0; X } else { X if (my_line + max_tree_lines/2 > max_line) { X first_line = max_line - (max_tree_lines-1); X } else if ((first_line = my_line - (max_tree_lines-1)/2) < 0) { X first_line = 0; X } X max_line = first_line + max_tree_lines-1; X } X X str = tree_buff; /* initialize first line's data */ X *str++ = ' '; X node_on_line = FALSE; X line_num = 0; X /* cache our portion of the tree */ X cache_tree(thread, 0, tree_indent); X X max_depth = (max_depth-first_depth) * 5; /* turn depth into char width */ X max_line -= first_line; /* turn max_line into count */ X /* shorten tree if lower lines aren't visible */ X if (node_line_cnt < max_line) { X max_line = node_line_cnt + 1; X } X} X X/* A recursive routine to find the maximum tree extents and where we are. X*/ Xstatic void Xfind_depth(article, depth) XARTICLE *article; Xint depth; X{ X if (depth > max_depth) { X max_depth = depth; X } X for (;;) { X if (article == tree_article) { X my_depth = depth; X my_line = max_line; X } X if (article->child1) { X find_depth(article->child1, depth+1); X } else { X max_line++; X } X if (!(article = article->sibling)) { X break; X } X } X} X X/* Place the tree display in a maximum of 11 lines x 6 nodes. X*/ Xstatic void Xcache_tree(ap, depth, cp) XARTICLE *ap; Xint depth; Xchar *cp; X{ X int depth_mode; X X cp[1] = ' '; X if (depth >= first_depth && depth <= max_depth) { X cp += 5; X depth_mode = 1; X } else if (depth+1 == first_depth) { X depth_mode = 2; X } else { X cp = tree_indent; X depth_mode = 0; X } X for (;;) { X switch (depth_mode) { X case 1: { X char ch; X X *str++ = ((ap->flags & AF_HAS_RE) || ap->parent) ? '-' : ' '; X if (ap == tree_article) X *str++ = '*'; X if (ap->flags & AF_READ) { X *str++ = '('; X ch = ')'; X } else if (!selected_only || (ap->flags & AF_SEL)) { X *str++ = '['; X ch = ']'; X } else { X *str++ = '<'; X ch = '>'; X } X if (ap == recent_artp && ap != tree_article) X *str++ = '@'; X *str++ = letter(ap); X *str++ = ch; X if (ap->child1) { X *str++ = (ap->child1->sibling? '+' : '-'); X } X if (ap->sibling) X *cp = '|'; X else X *cp = ' '; X node_on_line = TRUE; X break; X } X case 2: X *tree_buff = (!ap->child1)? ' ' : X (ap->child1->sibling)? '+' : '-'; X break; X default: X break; X } X if (ap->child1) { X cache_tree(ap->child1, depth+1, cp); X cp[1] = '\0'; X } else { X if (!node_on_line && first_line == line_num) { X first_line++; X } X if (line_num >= first_line) { X if (str[-1] == ' ') { X str--; X } X *str = '\0'; X tree_lines[line_num-first_line] X = safemalloc(str-tree_buff + 1); X strcpy(tree_lines[line_num - first_line], tree_buff); X if (node_on_line) { X node_line_cnt = line_num - first_line; X } X } X line_num++; X node_on_line = FALSE; X } X if (!(ap = ap->sibling) || line_num > max_line) X break; X if (!ap->sibling) X *cp = '\\'; X if (!first_depth) X tree_indent[5] = ' '; X strcpy(tree_buff, tree_indent+5); X str = tree_buff + strlen(tree_buff); X } X} X X/* Output a header line with possible tree display on the right hand side. X** Does automatic wrapping of lines that are too long. X*/ Xint Xtree_puts(orig_line, header_line, use_underline) Xchar *orig_line; XART_LINE header_line; Xint use_underline; X{ X char *buf; X register char *line, *cp, *end; X int pad_cnt, wrap_at; X ART_LINE start_line = header_line; X int i; X char ch; X X /* Make a modifiable copy of the line */ X buf = safemalloc(strlen(orig_line) + 2); /* yes, I mean "2" */ X strcpy(buf, orig_line); X line = buf; X X /* Change any embedded control characters to spaces */ X for (end = line; *end && *end != '\n'; end++) { X if ((unsigned char)*end < ' ') { X *end = ' '; X } X } X *end = '\0'; X X if (!*line) { X strcpy(line, " "); X end = line+1; X } X X /* If this is the first subject line, output it with a preceeding [1] */ X if (ThreadedGroup && use_underline && (unsigned char)*line > ' ') { X#ifdef NOFIREWORKS X no_sofire(); X#endif X standout(); X putchar('['); X putchar(letter(curr_artp)); X putchar(']'); X un_standout(); X putchar(' '); X header_indent = 4; X line += 9; X i = 0; X } else { X if (*line != ' ') { X /* A "normal" header line -- output keyword and set header_indent X ** _except_ for the first line, which is a non-standard header. X */ X if (!header_line || !(cp = index(line, ':')) || *++cp != ' ') { X header_indent = 0; X } else { X *cp = '\0'; X fputs(line, stdout); X putchar(' '); X header_indent = ++cp - line; X line = cp; X if (!*line) { X *--line = ' '; X } X } X i = 0; X } else { X /* Skip whitespace of continuation lines and prepare to indent */ X while (*++line == ' ') { X ; X } X i = header_indent; X } X } X for ( ; *line; i = header_indent) { X#ifdef CLEAREOL X maybe_eol(); X#endif X if (i) { X putchar('+'); X while (--i) { X putchar(' '); X } X } X /* If no (more) tree lines, wrap at COLS-1 */ X if (max_line < 0 || header_line > max_line+1) { X wrap_at = COLS-1; X } else { X wrap_at = COLS - max_depth - 5 - 3; X } X /* Figure padding between header and tree output, wrapping long lines */ X pad_cnt = wrap_at - (end - line + header_indent); X if (pad_cnt <= 0) { X cp = line + wrap_at - header_indent - 1; X pad_cnt = 1; X while (cp > line && *cp != ' ') { X if (*--cp == ',' || *cp == '.' || *cp == '-' || *cp == '!') { X cp++; X break; X } X pad_cnt++; X } X if (cp == line) { X cp += wrap_at - header_indent; X pad_cnt = 0; X } X ch = *cp; X *cp = '\0'; X /* keep rn's backpager happy */ X vwtary(artline, vrdary(artline - 1)); X artline++; X } else { X cp = end; X ch = '\0'; X } X if (use_underline) { X underprint(line); X } else { X fputs(line, stdout); X } X *cp = ch; X /* Skip whitespace in wrapped line */ X while (*cp == ' ') { X cp++; X } X line = cp; X /* Check if we've got any tree lines to output */ X if (wrap_at != COLS-1 && header_line <= max_line) { X char *cp1, *cp2; X X do { X putchar(' '); X } while (pad_cnt--); X /* Check string for the '*' flagging our current node X ** and the '@' flagging our prior node. X */ X cp = tree_lines[header_line]; X cp1 = index(cp, '*'); X cp2 = index(cp, '@'); X if (cp1 != Nullch) { X *cp1 = '\0'; X } X if (cp2 != Nullch) { X *cp2 = '\0'; X } X fputs(cp, stdout); X /* Handle standout output for '*' and '@' marked nodes, then X ** continue with the rest of the line. X */ X while (cp1 || cp2) { X standout(); X if (cp1 && (!cp2 || cp1 < cp2)) { X cp = cp1; X cp1 = Nullch; X *cp++ = '*'; X putchar(*cp++); X putchar(*cp++); X } else { X cp = cp2; X cp2 = Nullch; X *cp++ = '@'; X } X putchar(*cp++); X un_standout(); X if (*cp) { X fputs(cp, stdout); X } X }/* while */ X }/* if */ X putchar('\n') FLUSH; X header_line++; X }/* for remainder of line */ X X /* free allocated copy of line */ X free(buf); X X /* return number of lines displayed */ X return header_line - start_line; X} X X/* Output any parts of the tree that are left to display. Called at the X** end of each header. X*/ Xint Xfinish_tree(last_line) XART_LINE last_line; X{ X ART_LINE start_line = last_line; X X while (last_line <= max_line) { X artline++; X last_line += tree_puts("+", last_line, 0); X vwtary(artline, artpos); /* keep rn's backpager happy */ X } X return last_line - start_line; X} X X/* Output the entire article tree for the user. X*/ Xvoid Xentire_tree(ap) XARTICLE *ap; X{ X ARTICLE *thread; X SUBJECT *sp; X int num; X X if (!ap) { X#ifdef VERBOSE X IF (verbose) X fputs("\nNo article tree to display.\n", stdout) FLUSH; X ELSE X#endif X#ifdef TERSE X fputs("\nNo tree.\n", stdout) FLUSH; X#endif X return; X } X X if (!ThreadedGroup) { X ThreadedGroup = TRUE; X printf("Threading the group. "), fflush(stdout); X thread_open(); X if (!ThreadedGroup) { X printf("*failed*\n") FLUSH; X return; X } X count_subjects(CS_NORM); X putchar('\n') FLUSH; X } X if (!(ap->flags & AF_THREADED)) X parseheader(article_num(ap)); X if (check_page_line()) X return; X putchar('\n'); X thread = ap->subj->thread; X /* Enumerate our subjects for display */ X sp = thread->subj; X num = 0; X do { X if (check_page_line()) X return; X printf("[%c] %s\n",letters[num>9+26+26? 9+26+26:num],sp->str+4) FLUSH; X sp->misc = num++; X sp = sp->thread_link; X } while (sp != thread->subj); X if (check_page_line()) X return; X putchar('\n'); X if (check_page_line()) X return; X putchar(' '); X buf[3] = '\0'; X display_tree(thread, tree_indent); X X if (check_page_line()) X return; X putchar('\n'); X} X X/* A recursive routine to output the entire article tree. X*/ Xstatic void Xdisplay_tree(article, cp) XARTICLE *article; Xchar *cp; X{ X if (cp - tree_indent > COLS || page_line < 0) X return; X cp[1] = ' '; X cp += 5; X for (;;) { X putchar(((article->flags&AF_HAS_RE) || article->parent) ? '-' : ' '); X if (article->flags & AF_READ) { X buf[0] = '('; X buf[2] = ')'; X } else if (!selected_only || (article->flags & AF_SEL)) { X buf[0] = '['; X buf[2] = ']'; X } else { X buf[0] = '<'; X buf[2] = '>'; X } X buf[1] = letter(article); X if (article == curr_artp) { X standout(); X fputs(buf, stdout); X un_standout(); X } else if (article == recent_artp) { X putchar(buf[0]); X standout(); X putchar(buf[1]); X un_standout(); X putchar(buf[2]); X } else { X fputs(buf, stdout); X } X X if (article->sibling) { X *cp = '|'; X } else { X *cp = ' '; X } X if (article->child1) { X putchar((article->child1->sibling)? '+' : '-'); X display_tree(article->child1, cp); X cp[1] = '\0'; X } else { X putchar('\n') FLUSH; X } X if (!(article = article->sibling)) { X break; X } X if (!article->sibling) { X *cp = '\\'; X } X tree_indent[5] = ' '; X if (check_page_line()) { X return; X } X fputs(tree_indent+5, stdout); X } X} X Xstatic int Xcheck_page_line() X{ X if (page_line < 0) X return -1; X if (page_line >= LINES || int_count) { X register int cmd = -1; X if (int_count || (cmd = get_anything())) { X page_line = -1; /* disable further printing */ X if (cmd > 0) X pushchar(cmd); X return cmd; X } X } X page_line++; X return 0; X} X X/* Calculate the subject letter representation. "Place-holder" nodes X** are marked with a ' ', others get a letter in the sequence: X** ' ', '1'-'9', 'A'-'Z', 'a'-'z', '+' X*/ Xstatic char Xletter(ap) Xregister ARTICLE *ap; X{ X int subj = ap->subj->misc; X X if (!(ap->flags & AF_CACHED) X && (absfirst < first_cached || last_cached < lastart X || !cached_all_in_range)) X return '?'; X if (ap->flags & AF_MISSING) X return ' '; X return letters[subj > 9+26+26 ? 9+26+26 : subj]; X} SHAR_EOF $shar_touch -am 0711223693 'trn-3.6/rt-wumpus.c' && chmod 0644 'trn-3.6/rt-wumpus.c' || echo 'restore of trn-3.6/rt-wumpus.c failed' shar_count="`wc -c < 'trn-3.6/rt-wumpus.c'`" test 13386 -eq "$shar_count" || echo "trn-3.6/rt-wumpus.c: original size 13386, current size $shar_count" rm -f _sharnew.tmp fi # ============= trn-3.6/rthread.c ============== if test -f 'trn-3.6/rthread.c' && test X"$1" != X"-c"; then echo 'x - skipping trn-3.6/rthread.c (file already exists)' rm -f _sharnew.tmp else > _sharnew.tmp echo 'x - extracting trn-3.6/rthread.c (text)' sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/rthread.c' && X/* $Id: rthread.c,v 3.0 1992/12/14 00:14:13 davison Trn $ X*/ X/* The authors make no claims as to the fitness or correctness of this software X * for any use whatsoever, and it is provided as is. Any use of this software X * is at the user's own risk. X */ X X#include "EXTERN.h" X#include "common.h" X#include "intrp.h" X#include "trn.h" X#include "cache.h" X#include "bits.h" X#include "ng.h" X#include "rcln.h" X#include "search.h" X#include "artstate.h" X#include "rcstuff.h" X#include "ngdata.h" X#include "final.h" X#include "kfile.h" X#include "head.h" X#include "util.h" X#include "hash.h" X#include "nntp.h" X#include "rt-mt.h" X#include "rt-ov.h" X#include "rt-page.h" X#include "rt-process.h" X#include "rt-select.h" X#include "rt-util.h" X#include "rt-wumpus.h" X#include "INTERN.h" X#include "rthread.h" X XHASHTABLE *msgid_hash = 0; X Xvoid Xthread_init() X{ X if (try_ov > 0) X try_ov = ov_init()? 1 : -1; X if (try_mt > 0) X try_mt = mt_init()? 1 : -1; X} X X/* Generate the thread data we need for this group. We must call X** thread_close() before calling this again. X*/ Xvoid Xthread_open() X{ X if (!msgid_hash) X msgid_hash = hashcreate(201, msgid_cmp); /*TODO: pick a better size */ X if (ThreadedGroup) { X /* Parse input and use msgid_hash for quick article lookups. */ X /* If cached but not threaded articles exist, set up to thread them. */ X if (first_subject) { X first_cached = firstart; X last_cached = firstart - 1; X parsed_art = 0; X } X } X X if (sel_mode == SM_ARTICLE) X set_selector(sel_mode, sel_artsort); X else X set_selector(sel_threadmode, sel_threadsort); X X if (try_mt > 0 && !first_subject) X if (!mt_data()) X return; X if (try_ov > 0 && first_cached > last_cached) X if (thread_always) X (void) ov_data(absfirst, lastart, FALSE); X else if (firstart > lastart) { X /* If no unread articles, see if ov. exists as fast as possible */ X (void) ov_data(absfirst, absfirst, FALSE); X cached_all_in_range = FALSE; X } else X (void) ov_data(firstart, lastart, FALSE); X#ifdef USE_NNTP X if (!ov_opened) X setmissingbits(); X#endif X X#ifndef USE_NNTP X if (last_cached > lastart) { X toread[ng] += (ART_UNREAD)(last_cached-lastart); X /* ensure getngsize() knows the new maximum */ X ngmax[ng] = lastart = last_cached; X } X#endif X thread_grow(); /* thread any new articles not yet in the database */ X added_articles = 0; X sel_page_sp = 0; X sel_page_app = 0; X} X X/* Update the group's thread info. X*/ Xvoid Xthread_grow() X{ X added_articles += lastart - last_cached; X if (added_articles && thread_always) X cache_range(last_cached + 1, lastart); X count_subjects(CS_NORM); X if (artptr_list) X sort_articles(); X else X sort_subjects(); X} X Xstatic void Xkill_tmp_arts(data, extra) XHASHDATUM *data; Xint extra; X{ X register ARTICLE *ap = (ARTICLE*)data->dat_ptr; X X if (ap) { X clear_article(ap); X free((char*)ap); X } X} X Xvoid Xthread_close() X{ X curr_artp = artp = Nullart; X init_tree(); /* free any tree lines */ X X if (msgid_hash) { X hashwalk(msgid_hash, kill_tmp_arts, 0); X hashdestroy(msgid_hash); X msgid_hash = 0; X } X sel_page_sp = 0; X sel_page_app = 0; X sel_last_ap = 0; X sel_last_sp = 0; X selected_only = FALSE; X sel_exclusive = 0; X ov_close(); X} X Xvoid Xtop_article() X{ X art = lastart+1; X artp = Nullart; X inc_art(selected_only, FALSE); X if (art > lastart && last_cached < lastart) X art = firstart; X} X XARTICLE * Xfirst_art(sp) Xregister SUBJECT *sp; X{ X register ARTICLE *ap = (ThreadedGroup? sp->thread : sp->articles); X if (ap && (ap->flags & AF_MISSING)) X ap = next_art(ap); X return ap; X} X XARTICLE * Xlast_art(sp) Xregister SUBJECT *sp; X{ X register ARTICLE *ap; X X if (!ThreadedGroup) { X ap = sp->articles; X while (ap->subj_next) X ap = ap->subj_next; X return ap; X } X X ap = sp->thread; X if (ap) { X for (;;) { X if (ap->sibling) X ap = ap->sibling; X else if (ap->child1) X ap = ap->child1; X else X break; X } X if (ap->flags & AF_MISSING) X ap = prev_art(ap); X } X return ap; X} X X/* Bump art/artp to the next article, wrapping from thread to thread. X** If sel_flag is TRUE, only stops at selected articles. X** If rereading is FALSE, only stops at unread articles. X*/ Xvoid Xinc_art(sel_flag, rereading) Xbool_int sel_flag, rereading; X{ X register ARTICLE *ap = artp; X int subj_mask = (rereading? 0 : SF_VISIT); X X /* Use the explicit article-order if it exists */ X if (artptr_list) { X ARTICLE **limit = artptr_list + artptr_list_size; X if (!ap) X artptr = artptr_list-1; X else if (!artptr || *artptr != ap) { X for (artptr = artptr_list; artptr < limit; artptr++) { X if (*artptr == ap) X break; X } X } X do { X if (++artptr >= limit) X break; X ap = *artptr; X } while ((!rereading && (ap->flags & AF_READ)) X || (sel_flag && !(ap->flags & AF_SEL))); X if (artptr < limit) { X artp = *artptr; X art = article_num(artp); X } else { X artp = Nullart; X art = lastart+1; X artptr = artptr_list; X } X return; X } X X /* Use subject- or thread-order when possible */ X if (ThreadedGroup || srchahead) { X register SUBJECT *sp; X if (ap) X sp = ap->subj; X else X sp = next_subj(Nullsubj, subj_mask); X if (!sp) X goto num_inc; X do { X if (ap) X ap = next_art(ap); X else X ap = first_art(sp); X while (!ap) { X sp = next_subj(sp, subj_mask); X if (!sp) X break; X ap = first_art(sp); X } X } while (ap && ((!rereading && (ap->flags & AF_READ)) X || (sel_flag && !(ap->flags & AF_SEL)))); X if ((artp = ap) != Nullart) X art = article_num(ap); X else { X if (art <= last_cached) X art = last_cached+1; X else X art++; X if (art <= lastart) X artp = article_ptr(art); X else X art = lastart+1; X } X return; X } X X /* Otherwise, just increment through the art numbers */ X num_inc: X if (!ap) { X art = firstart-1; X ap = article_ptr(art); X } X do { X if (++art > lastart) { X ap = Nullart; X break; X } X ap++; X } while ((!rereading && (ap->flags & AF_READ)) X || (sel_flag && !(ap->flags & AF_SEL)) X || (ap->flags & AF_MISSING)); X artp = ap; X} X X/* Bump art/artp to the previous article, wrapping from thread to thread. X** If sel_flag is TRUE, only stops at selected articles. X** If rereading is FALSE, only stops at unread articles. X*/ Xvoid Xdec_art(sel_flag, rereading) Xbool_int sel_flag, rereading; X{ X register ARTICLE *ap = artp; X int subj_mask = (rereading? 0 : SF_VISIT); X X /* Use the explicit article-order if it exists */ X if (artptr_list) { X ARTICLE **limit = artptr_list + artptr_list_size; X if (!ap) X artptr = limit; X else if (!artptr || *artptr != ap) { X for (artptr = artptr_list; artptr < limit; artptr++) { X if (*artptr == ap) X break; X } X } X do { X if (artptr == artptr_list) X break; X ap = *--artptr; X } while ((!rereading && (ap->flags & AF_READ)) X || (sel_flag && !(ap->flags & AF_SEL))); X artp = *artptr; X art = article_num(artp); X return; X } X X /* Use subject- or thread-order when possible */ X if (ThreadedGroup || srchahead) { X register SUBJECT *sp; X if (ap) X sp = ap->subj; X else X sp = prev_subj(Nullsubj, subj_mask); X if (!sp) X goto num_dec; X do { X if (ap) X ap = prev_art(ap); X else X ap = last_art(sp); X while (!ap) { X sp = prev_subj(sp, subj_mask); X if (!sp) X break; X ap = last_art(sp); X } X } while (ap && ((!rereading && (ap->flags & AF_READ)) X || (sel_flag && !(ap->flags & AF_SEL)))); X if ((artp = ap) != Nullart) X art = article_num(ap); X else X art = absfirst-1; X return; X } X X /* Otherwise, just decrement through the art numbers */ X num_dec: X ap = article_ptr(art); X do { X if (--art < absfirst) { X ap = Nullart; X break; X } X ap--; X } while ((!rereading && (ap->flags & AF_READ)) X || (sel_flag && !(ap->flags & AF_SEL)) X || (ap->flags & AF_MISSING)); X artp = ap; X} X X/* Bump the param to the next article in depth-first order. X*/ XARTICLE * Xbump_art(ap) Xregister ARTICLE *ap; X{ X if (ap->child1) X return ap->child1; X while (!ap->sibling) { X if (!(ap = ap->parent)) X return Nullart; X } X return ap->sibling; X} X X/* Bump the param to the next REAL article. Uses subject order in a X** non-threaded group; honors the breadth_first flag in a threaded one. X*/ XARTICLE * Xnext_art(ap) Xregister ARTICLE *ap; X{ Xtry_again: X if (!ThreadedGroup) { X ap = ap->subj_next; X goto done; X } X if (breadth_first) { X if (ap->sibling) { X ap = ap->sibling; X goto done; X } X if (ap->parent) X ap = ap->parent->child1; X else X ap = ap->subj->thread; X } X do { X if (ap->child1) { X ap = ap->child1; X goto done; X } X while (!ap->sibling) { X if (!(ap = ap->parent)) X return Nullart; X } X ap = ap->sibling; X } while (breadth_first); Xdone: X if (ap && (ap->flags & AF_MISSING)) X goto try_again; X return ap; X} X X/* Bump the param to the previous REAL article. Uses subject order in a X** non-threaded group. X*/ XARTICLE * Xprev_art(ap) Xregister ARTICLE *ap; X{ X register ARTICLE *initial_ap; X Xtry_again: X initial_ap = ap; X if (!ThreadedGroup) { X if ((ap = ap->subj->articles) == initial_ap) X ap = Nullart; X else X while (ap->subj_next != initial_ap) X ap = ap->subj_next; X goto done; X } X ap = (ap->parent ? ap->parent->child1 : ap->subj->thread); X if (ap == initial_ap) { X ap = ap->parent; X goto done; X } X while (ap->sibling != initial_ap) X ap = ap->sibling; X while (ap->child1) { X ap = ap->child1; X while (ap->sibling) X ap = ap->sibling; X } Xdone: X if (ap && (ap->flags & AF_MISSING)) X goto try_again; X return ap; X} X X/* Find the next art/artp with the same subject as this one. Returns X** FALSE if no such article exists. X*/ Xbool Xnext_art_with_subj() X{ X register ARTICLE *ap = artp; X X if (!ap) X return FALSE; X X do { X ap = ap->subj_next; X if (!ap) { X if (!art) X art = firstart; X return FALSE; X } X } while ((ap->flags & (AF_READ|AF_MISSING)) X || (selected_only && !(ap->flags & AF_SEL))); X artp = ap; X art = article_num(ap); X#ifdef ARTSEARCH X srchahead = -1; X#endif X return TRUE; X} X X/* Find the previous art/artp with the same subject as this one. Returns X** FALSE if no such article exists. X*/ Xbool Xprev_art_with_subj() X{ X register ARTICLE *ap = artp, *ap2; X X if (!ap) X return FALSE; X X do { X ap2 = ap->subj->articles; X if (ap2 == ap) X ap = Nullart; X else { X while (ap2 && ap2->subj_next != ap) X ap2 = ap2->subj_next; X ap = ap2; X } X if (!ap) { X if (!art) X art = lastart; X return FALSE; X } X } while ((ap->flags & AF_MISSING) X || (selected_only && !(ap->flags & AF_SEL))); X artp = ap; X art = article_num(ap); SHAR_EOF : || echo 'restore of trn-3.6/rthread.c failed' fi echo 'End of archive part 8' echo 'File trn-3.6/rthread.c is continued in part 9' echo 9 > _sharseq.tmp exit 0