Article 2703 of comp.sources.unix:
Path: nntpd.lkg.dec.com!crl.dec.com!crl.dec.com!bloom-beacon.mit.edu!news.kei.com!news.mathworks.com!newsfeed.internetmci.com!in1.uu.net!news1.digital.com!vixie!vixie!not-for-mail
From: davison@borland.com (Wayne Davison)
Newsgroups: comp.sources.unix
Subject: v29i045: trn-3.6 - threaded newsreader based on RN, V3.6, Part08/14
Date: 29 Oct 1995 01:32:56 -0800
Organization: Vixie Enterprises
Lines: 3765
Sender: vixie@vix.com
Approved: vixie@gw.home.vix.com
Message-ID: <46vho8$t4t@gw.home.vix.com>
References: <1.814959141.29825@gw.home.vix.com>
NNTP-Posting-Host: 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 <max> 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 <max> 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 "<MISSING>";
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


