BSD 4_4_Lite1 development
authorCSRG <csrg@ucbvax.Berkeley.EDU>
Tue, 1 Jun 1993 04:48:58 +0000 (20:48 -0800)
committerCSRG <csrg@ucbvax.Berkeley.EDU>
Tue, 1 Jun 1993 04:48:58 +0000 (20:48 -0800)
Work on file usr/src/contrib/news/trn3/addng.c
Work on file usr/src/contrib/news/trn3/cache.h
Work on file usr/src/contrib/news/trn3/bits.h
Work on file usr/src/contrib/news/trn3/common.h
Work on file usr/src/contrib/news/trn3/hash.c
Work on file usr/src/contrib/news/trn3/rcstuff.h
Work on file usr/src/contrib/news/trn3/rt-select.c
Work on file usr/src/contrib/news/trn3/rthread.h
Work on file usr/src/contrib/news/trn3/rthread.c
Work on file usr/src/contrib/news/trn3/trn.1

Synthesized-from: CSRG/cd2/4.4BSD-Lite1

usr/src/contrib/news/trn3/addng.c [new file with mode: 0644]
usr/src/contrib/news/trn3/bits.h [new file with mode: 0644]
usr/src/contrib/news/trn3/cache.h [new file with mode: 0644]
usr/src/contrib/news/trn3/common.h [new file with mode: 0644]
usr/src/contrib/news/trn3/hash.c [new file with mode: 0644]
usr/src/contrib/news/trn3/rcstuff.h [new file with mode: 0644]
usr/src/contrib/news/trn3/rt-select.c [new file with mode: 0644]
usr/src/contrib/news/trn3/rthread.c [new file with mode: 0644]
usr/src/contrib/news/trn3/rthread.h [new file with mode: 0644]
usr/src/contrib/news/trn3/trn.1 [new file with mode: 0644]

diff --git a/usr/src/contrib/news/trn3/addng.c b/usr/src/contrib/news/trn3/addng.c
new file mode 100644 (file)
index 0000000..4a9ee5a
--- /dev/null
@@ -0,0 +1,293 @@
+/* $Id: addng.c,v 3.0 1992/02/01 03:09:32 davison Trn $
+ */
+/* This software is Copyright 1991 by Stan Barber. 
+ *
+ * Permission is hereby granted to copy, reproduce, redistribute or otherwise
+ * use this software as long as: there is no monetary profit gained
+ * specifically from the use or reproduction of this software, it is not
+ * sold, rented, traded or otherwise marketed, and this copyright notice is
+ * included prominently in any copy made. 
+ *
+ * The author make no claims as to the fitness or correctness of this software
+ * for any use whatsoever, and it is provided as is. Any use of this software
+ * is at the user's own risk. 
+ */
+
+#include "EXTERN.h"
+#include "common.h"
+#include "trn.h"
+#include "ngdata.h"
+#include "last.h"
+#include "util.h"
+#include "intrp.h"
+#include "only.h"
+#include "rcstuff.h"
+#include "nntp.h"
+#include "final.h"
+#include "INTERN.h"
+#include "addng.h"
+
+void
+addng_init()
+{
+    ;
+}
+
+#ifdef FINDNEWNG
+/* generate a list of new newsgroups from active file */
+
+bool
+newlist(munged,checkinlist)
+bool_int munged;                       /* are we scanning the whole file? */
+bool_int checkinlist;
+{
+    char *tmpname;
+    register char *s, *status;
+    register NG_NUM ngnum;
+#ifndef ACTIVE_TIMES
+    long birthof();
+#endif
+
+    tmpname = filexp(RNEWNAME);
+    tmpfp = fopen(tmpname,"w+");
+    if (tmpfp == Nullfp) {
+       printf(cantcreate,tmpname) FLUSH;
+       return FALSE;
+    }
+    UNLINK(tmpname);                   /* be nice to the world */
+
+    while (fgets(buf,LBUFLEN,actfp) != Nullch) {
+       /* Check if they want to break out of the new newsgroups search */
+       if (int_count) {
+           int_count = 0;
+           fclose(tmpfp);
+           return FALSE;
+       }
+       if (s = index(buf,' ')) {
+           status=s;
+           while (isdigit(*status) || isspace(*status)) status++;
+           *s++ = '\0';
+           if (strnEQ(buf,"to.",3) || *status == 'x' || *status == '=')
+               /* since = groups are refiling to another group, just
+                  ignore their existence */
+               continue;
+#ifdef ACTIVE_TIMES
+           if (inlist(buf) && ((ngnum = find_ng(buf)) == nextrcline
+                               || toread[ngnum] == TR_UNSUB)
+#else
+           if (checkinlist ?
+               (inlist(buf) && ((ngnum = find_ng(buf)) == nextrcline
+                                || toread[ngnum] == TR_UNSUB))
+             : (find_ng(buf) == nextrcline
+                && birthof(buf,(ART_NUM)atol(s)) > lasttime)
+#endif
+           ) {
+                                       /* if not in .newsrc and younger */
+                                       /* than the last time we checked */
+               fprintf(tmpfp,"%s\n",buf);
+                                       /* then remember said newsgroup */
+           }
+#ifdef FASTNEW
+           else {                      /* not really a new group */
+               if (!munged) {          /* did we assume not munged? */
+                   fclose(tmpfp);      /* then go back, knowing that */
+                   return TRUE;        /* active file was indeed munged */
+               }
+           }
+#endif
+       }
+#ifdef DEBUG
+       else
+           printf("Bad active record: %s\n",buf) FLUSH;
+#endif
+    }
+
+    /* we have successfully generated the list */
+
+    fseek(tmpfp,0L,0);                 /* rewind back to the beginning */
+    while (fgets(buf,LBUFLEN,tmpfp) != Nullch) {
+       buf[strlen(buf)-1] = '\0';
+       get_ng(buf,GNG_RELOC);          /* add newsgroup, maybe */
+    }
+    fclose(tmpfp);                     /* be nice to ourselves */
+    return FALSE;                      /* do not call us again */
+}
+
+#ifdef ACTIVE_TIMES
+#ifdef USE_NNTP
+
+bool
+find_new_groups()
+{
+    char *tmpname;
+    register char *s;
+    time_t server_time;
+    NG_NUM oldnext = nextrcline;       /* remember # lines in newsrc */
+
+    tmpname = filexp(RNEWNAME);
+    tmpfp = fopen(tmpname,"w+");
+    if (tmpfp == Nullfp) {
+       printf(cantcreate,tmpname) FLUSH;
+       return FALSE;
+    }
+    UNLINK(tmpname);                   /* be nice to the world */
+
+    server_time = nntp_time();
+    if (!nntp_newgroups(lastnewtime)) {
+       fclose(tmpfp);
+       printf("Can't get new groups from server:\n%s\n", ser_line);
+       return FALSE;
+    }
+
+    while (1) {
+       nntp_gets(ser_line, sizeof ser_line);
+#ifdef DEBUG
+       if (debug & DEB_NNTP)
+           printf("<%s\n", ser_line) FLUSH;
+#endif
+       if (ser_line[0] == '.')
+           break;
+       if ((s = index(ser_line, ' ')) != Nullch)
+           *s = '\0';
+       fprintf(tmpfp,"%s\n",ser_line);
+    }
+
+    /* we have successfully generated the list */
+
+    if (ftell(tmpfp)) {
+       fputs("\nFinding new newsgroups:\n",stdout) FLUSH;
+
+       fseek(tmpfp,0L,0);              /* rewind back to the beginning */
+       while (fgets(buf,LBUFLEN,tmpfp) != Nullch) {
+           buf[strlen(buf)-1] = '\0';
+           get_ng(buf,0);              /* add newsgroup, maybe */
+       }
+       lastnewtime = server_time;      /* remember when we found new groups */
+    }                                  /* (ends up back in .rnlast) */
+    fclose(tmpfp);                     /* be nice to ourselves */
+
+    return oldnext != nextrcline;
+}
+#else /* !USE_NNTP */
+
+bool
+find_new_groups()
+{
+    register char *s;
+    long lastone;
+    NG_NUM oldnext = nextrcline;       /* remember # lines in newsrc */
+
+    fstat(fileno(actfp),&filestat);    /* find active file size */
+    lastactsiz = filestat.st_size;     /* just to save it in .rnlast */
+
+    stat(ACTIVE_TIMES,&filestat);      /* did active.times file grow? */
+    if (filestat.st_size == lastnewsize)
+       return FALSE;
+    lastnewsize = filestat.st_size;
+
+    fputs("\nChecking for new newsgroups...\n",stdout) FLUSH;
+
+    s = filexp(ACTIVE_TIMES);
+    tmpfp = fopen(s,"r");
+    if (tmpfp == Nullfp) {
+       printf(cantopen,s) FLUSH;
+       return FALSE;
+    }
+    lastone = time(Null(time_t*)) - 24L * 60 * 60 - 1;
+    while (fgets(buf,LBUFLEN,tmpfp) != Nullch) {
+       if ((s = index(buf, ' ')) != Nullch)
+           if ((lastone = atol(s+1)) >= lastnewtime) {
+               char tmpbuf[LBUFLEN];
+               *s = '\0';
+               if (findact(tmpbuf, buf, s - buf, 0L) >= 0)
+                   get_ng(buf,0);      /* add newsgroup, maybe */
+           }
+    }
+    fclose(tmpfp);
+    lastnewtime = lastone+1;           /* remember time of last new group */
+                                       /* (ends up back in .rnlast) */
+    return oldnext != nextrcline;
+}
+#endif /* !USE_NNTP */
+#else /* not ACTIVE_TIMES */
+
+bool
+find_new_groups()
+{
+    long oldactsiz = lastactsiz;
+    NG_NUM oldnext = nextrcline;       /* remember # lines in newsrc */
+
+    fstat(fileno(actfp),&filestat);    /* did active file grow? */
+
+    if (filestat.st_size == lastactsiz)
+       return FALSE;
+    lastactsiz = filestat.st_size;     /* remember new size */
+
+#ifdef VERBOSE
+    IF(verbose)
+       fputs("\nChecking active file for new newsgroups...\n",stdout) FLUSH;
+    ELSE
+#endif
+#ifdef TERSE
+       fputs("\nNew newsgroups:\n",stdout) FLUSH;
+#endif
+
+#ifdef FASTNEW                         /* bad soft ptrs -> edited active */
+    if (!writesoft && oldactsiz) {     /* maybe just do tail of file? */
+       fseek(actfp,oldactsiz-NL_SIZE,0);
+       fgets(buf,LBUFLEN,actfp);
+       if (*buf == '\n' && !newlist(FALSE,FALSE))
+           goto bugout;
+    }
+#endif
+    fseek(actfp,0L,0);         /* rewind active file */
+    newlist(TRUE,FALSE);               /* sure hope they use hashing... */
+bugout:
+    return oldnext != nextrcline;
+}
+
+/* return creation time of newsgroup */
+
+long
+birthof(ngnam,ngsize)
+char *ngnam;
+ART_NUM ngsize;
+{
+#ifdef USE_NNTP                /* ngsize not used */
+    long tot;
+
+    if (!nntp_group(ngnam))
+       return 0;       /* not a real group */
+    (void) sscanf(ser_line,"%*d%ld",&tot);
+    if (tot > 0)
+       return time(Null(long *));
+    return 0;
+
+#else /* !USE_NNTP */
+    char tst[128];
+
+    sprintf(tst, ngsize ? "%s/%s/1" : "%s/%s" ,spool,getngdir(ngnam));
+    if (stat(tst,&filestat) < 0)
+       return (ngsize ? 0L : time(Null(long *)));
+    /* not there, assume something good */
+    return filestat.st_mtime;
+
+#endif /* !USE_NNTP */
+}
+#endif /* ACTIVE_TIMES */
+
+bool
+scanactive()
+{
+    NG_NUM oldnext = nextrcline;       /* remember # lines in newsrc */
+
+    fseek(actfp,0L,0);
+    newlist(TRUE,TRUE);
+    if (nextrcline != oldnext) {       /* did we add any new groups? */
+       return TRUE;
+    }
+    return FALSE;
+}
+
+#endif
+
diff --git a/usr/src/contrib/news/trn3/bits.h b/usr/src/contrib/news/trn3/bits.h
new file mode 100644 (file)
index 0000000..10082e6
--- /dev/null
@@ -0,0 +1,50 @@
+/* $Id: bits.h,v 3.0 1991/09/09 20:18:23 davison Trn $
+ */
+/* This software is Copyright 1991 by Stan Barber. 
+ *
+ * Permission is hereby granted to copy, reproduce, redistribute or otherwise
+ * use this software as long as: there is no monetary profit gained
+ * specifically from the use or reproduction of this software, it is not
+ * sold, rented, traded or otherwise marketed, and this copyright notice is
+ * included prominently in any copy made. 
+ *
+ * The author make no claims as to the fitness or correctness of this software
+ * for any use whatsoever, and it is provided as is. Any use of this software
+ * is at the user's own risk. 
+ */
+
+EXT char *found_bits INIT(Nullch);
+EXT ART_NUM found_min;
+
+/* if subscripting is faster than shifting on your machine, define this */
+#undef USESUBSCRIPT
+#ifdef USESUBSCRIPT
+EXT char powerof2[] INIT({1,2,4,8,16,32,64,128});
+#define pow2(x) powerof2[x]
+#else
+#define pow2(x) (1 << (x))
+#endif
+
+#define foundart(a) (found_bits[((a)-found_min) / BITSPERBYTE] \
+       |= pow2(((a)-found_min) % BITSPERBYTE))
+#define artismissing(a) (!(found_bits[((a)-found_min) / BITSPERBYTE] \
+       & pow2(((a)-found_min) % BITSPERBYTE)))
+
+EXT int dmcount INIT(0);
+
+void   bits_init _((void));
+void   rc_to_bits _((void));
+void   bits_to_rc _((void));
+void   setfoundbits _((void));
+void   setmissingbits _((void));
+void   onemore _((ARTICLE*));
+void   oneless _((ARTICLE*));
+void   onemissing _((ARTICLE*));
+void   unmark_as_read _((void));
+void   set_read _((ARTICLE*));
+void   set_unread _((ARTICLE*));
+void   delay_unmark _((ARTICLE*));
+void   mark_as_read _((void));
+void   check_first _((ART_NUM));
+void   yankback _((void));
+int    chase_xrefs _((ART_NUM,int));
diff --git a/usr/src/contrib/news/trn3/cache.h b/usr/src/contrib/news/trn3/cache.h
new file mode 100644 (file)
index 0000000..ddeea64
--- /dev/null
@@ -0,0 +1,143 @@
+/* $Id: cache.h,v 3.0 1991/09/09 20:18:23 davison Trn $
+ */
+/* This software is Copyright 1991 by Stan Barber. 
+ *
+ * Permission is hereby granted to copy, reproduce, redistribute or otherwise
+ * use this software as long as: there is no monetary profit gained
+ * specifically from the use or reproduction of this software, it is not
+ * sold, rented, traded or otherwise marketed, and this copyright notice is
+ * included prominently in any copy made. 
+ *
+ * The author make no claims as to the fitness or correctness of this software
+ * for any use whatsoever, and it is provided as is. Any use of this software
+ * is at the user's own risk. 
+ */
+
+/* Subjects get their own structure */
+
+typedef struct rt_subj {
+    struct rt_subj *next;
+    struct rt_subj *prev;
+    struct rt_art *articles;
+    struct rt_art *thread;
+    struct rt_subj *thread_link;
+    char *str;
+    time_t date;
+    short flags;
+    short misc;                /* used for temporary totals and subject numbers */
+} SUBJECT;
+
+/* subject flags */
+
+#define SF_SEL         0x0001
+#define SF_DEL         0x0002
+#define SF_DELSEL      0x0004
+#define SF_OLDSEL      0x0008
+#define SF_INCLUDED    0x0010
+
+#define SF_THREAD      0x0100
+#define SF_VISIT       0x0200
+#define SF_WASSELECTED  0x0400
+#define SF_AUTOSELECT  0x0800
+#define SF_SUBJTRUNCED 0x1000
+
+/* This is our article-caching structure */
+
+typedef struct rt_art {
+    time_t date;
+    SUBJECT *subj;
+    char *from;
+    char *msgid;
+    char *xrefs;
+    struct rt_art *parent;     /* parent article */
+    struct rt_art *child1;     /* first child of a chain */
+    struct rt_art *sibling;    /* our next sibling */
+    struct rt_art *subj_next;  /* next article in subject order */
+    short flags;               /* user-settable flags */
+    short padding;
+} ARTICLE;
+
+/* article flags */
+
+#define AF_SEL         0x0001
+#define AF_DEL         0x0002
+#define AF_DELSEL      0x0004
+#define AF_OLDSEL      0x0008
+#define AF_INCLUDED    0x0010
+
+#define AF_READ                0x0020
+#define AF_CACHED      0x0040
+#define AF_THREADED    0x0080
+#define AF_MISSING     0x0100
+#define AF_AUTOKILL    0x0200
+#define AF_AUTOKILLALL 0x0400
+#define AF_AUTOSELECT  0x0800
+#define AF_AUTOSELECTALL 0x1000
+#define AF_AUTOFLAGS    (AF_AUTOKILL|AF_AUTOKILLALL|AF_AUTOSELECT|AF_AUTOSELECTALL)
+
+/* If AF_MISSING is NOT set the last 3 bits have the following meaning: */
+#define AF_HAS_RE      0x2000
+#define AF_YANKBACK    0x4000
+#define AF_FROMTRUNCED 0x8000
+
+/* These flags only have meaning when combined with AF_MISSING */
+#define AF_TMPMEM      (0x2000|AF_MISSING)
+#define AF_FAKE                (0x4000|AF_MISSING)
+
+/* The following define is only valid as a flag to the select_article call */
+#define AF_ECHO        0x8000
+
+#define Nullart Null(ARTICLE*)
+#define Nullsubj Null(SUBJECT*)
+
+#define was_read(a)     (article_ptr(a)->flags & AF_READ)
+
+/* These must never use their args more than once in the definition */
+#define article_num(ap)      (((ap)-article_list)+absfirst)
+#define article_ptr(artnum)  (article_list+((artnum)-absfirst))
+
+EXT ARTICLE *article_list INIT(Nullart);
+EXT ARTICLE **artptr_list INIT(0);
+EXT ARTICLE **artptr;
+
+#ifdef ARTSEARCH
+EXT ART_NUM srchahead INIT(0);         /* are we in subject scan mode? */
+                               /* (if so, contains art # found or -1) */
+#endif
+
+EXT ART_NUM first_cached;
+EXT ART_NUM last_cached;
+EXT bool cached_all_in_range;
+EXT ARTICLE *sentinel_artp;
+
+EXT struct rt_subj *first_subject INIT(0);
+EXT struct rt_subj *last_subject INIT(0);
+
+EXT bool untrim_cache INIT(FALSE);
+
+#ifdef PENDING
+EXT ART_NUM subj_to_get;
+EXT ART_NUM xref_to_get;
+#endif
+
+void   cache_init _((void));
+void   build_cache _((void));
+void   grow_cache _((ART_NUM));
+void   close_cache _((void));
+void   cache_article _((ARTICLE*));
+void   check_poster _((ARTICLE*));
+void   uncache_article _((ARTICLE*,bool_int));
+char   *fetchcache _((ART_NUM,int));   /* return actual cache ptr */
+char   *get_cached_line _((ARTICLE*, int, bool_int));
+void   set_subj_line _((ARTICLE*, char*, int));
+void   set_cached_line _((ARTICLE*, int, char*));
+void   look_ahead _((void));
+void   cache_until_key _((void));
+bool   cache_subjects _((void));
+bool   cache_xrefs _((void));
+bool   cache_all_arts _((void));
+bool   cache_unread_arts _((void));
+bool   art_data _((ART_NUM,ART_NUM,bool_int,bool_int));
+bool   cache_range _((ART_NUM,ART_NUM));
+void   clear_article _((ARTICLE*));
+void   free_subject _((SUBJECT*));
diff --git a/usr/src/contrib/news/trn3/common.h b/usr/src/contrib/news/trn3/common.h
new file mode 100644 (file)
index 0000000..e5d8ac1
--- /dev/null
@@ -0,0 +1,861 @@
+/* $Id: common.h,v 3.0 1992/02/23 21:25:39 davison Trn $
+ */
+/* This software is Copyright 1991 by Stan Barber. 
+ *
+ * Permission is hereby granted to copy, reproduce, redistribute or otherwise
+ * use this software as long as: there is no monetary profit gained
+ * specifically from the use or reproduction of this software, it is not
+ * sold, rented, traded or otherwise marketed, and this copyright notice is
+ * included prominently in any copy made. 
+ *
+ * The author make no claims as to the fitness or correctness of this software
+ * for any use whatsoever, and it is provided as is. Any use of this software
+ * is at the user's own risk. 
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include "config.h"    /* generated by installation script */
+#ifndef isalnum
+#   define isalnum(c) (isalpha(c) || isdigit(c))
+#endif
+
+#include <errno.h>
+#include <signal.h>
+#ifdef I_SYS_IOCTL
+#include <sys/ioctl.h>
+#endif
+#ifdef I_VFORK
+#  include <vfork.h>
+#endif
+#include <fcntl.h>
+
+#ifdef I_TERMIO
+#   include <termio.h>
+#else
+# ifdef I_TERMIOS
+#   include <termios.h>
+# else
+#   include <sgtty.h>
+# endif
+#endif
+
+#ifdef HAS_GETPWENT
+#   include <pwd.h>
+#endif
+
+#ifdef I_PTEM
+#include <sys/stream.h>
+#include <sys/ptem.h>
+#endif
+
+#include <time.h>
+
+#define BITSPERBYTE 8
+#define LBUFLEN 1024   /* line buffer length */
+                       /* (don't worry, .newsrc lines can exceed this) */
+#define CBUFLEN 512    /* command buffer length */
+#define PUSHSIZE 256
+#define MAXFILENAME 512
+#define LONGKEY 15     /* longest keyword: currently "posting-version" */
+#define FINISHCMD 0177
+
+/* some handy defs */
+
+#define bool char
+#define bool_int int
+#define char_int int
+#ifndef TRUE
+#define TRUE (1)
+#endif
+#ifndef FALSE
+#define FALSE (0)
+#endif
+#define Null(t) ((t)0)
+#define Nullch Null(char*)
+#define Nullfp Null(FILE*)
+
+#define Ctl(ch) (ch & 037)
+
+#define strNE(s1,s2) (strcmp(s1,s2))
+#define strEQ(s1,s2) (!strcmp(s1,s2))
+#define strnNE(s1,s2,l) (strncmp(s1,s2,l))
+#define strnEQ(s1,s2,l) (!strncmp(s1,s2,l))
+
+/* Things we can figure out ourselves */
+
+#ifdef SIGTSTP
+#   define BERKELEY    /* include job control signals? */
+#endif
+
+#if defined(FIONREAD) || defined(HAS_RDCHK) || defined(O_NDELAY)
+#   define PENDING
+#endif
+
+#ifdef EUNICE
+#   define LINKART             /* add 1 level of possible indirection */
+#   define UNLINK(victim) while (!unlink(victim))
+#else
+#   define UNLINK(victim) unlink(victim)
+#endif
+
+/* Valid substitutions for strings marked with % comment are:
+ *     %a      Current article number
+ *     %A      Full name of current article (%P/%c/%a)
+ *             (if LINKART defined, is the name of the real article)
+ *     %b      Destination of a save command, a mailbox or command
+ *     %B      The byte offset to the beginning of the article for saves
+ *             with or without the header
+ *     %c      Current newsgroup, directory form
+ *     %C      Current newsgroup, dot form
+ *     %d      %P/%c
+ *     %D      Old Distribution: line
+ *     %e      Extract program
+ *     %E      Extract destination directory
+ *     %f      Old From: line or Reply-To: line
+ *     %F      Newsgroups to followup to from Newsgroups: and Followup-To:
+ *     %h      Name of header file to pass to mail or news poster
+ *     %H      Host name (yours)
+ *     %i      Old Message-I.D.: line, with <>
+ *     %I      Inclusion indicator
+ *     %l      News administrator login name
+ *     %L      Login name (yours)
+ *     %m      The current mode of trn.
+ *     %M      Number of articles marked with M
+ *     %n      Newsgroups from source article
+ *     %N      Full name (yours)
+ *     %o      Organization (yours)
+ *     %O      Original working directory (where you ran trn from)
+ *     %p      Your private news directory (-d switch)
+ *     %P      Public news spool directory (NEWSSPOOL)
+ *     %r      Last reference (parent article id)
+ *     %R      New references list
+ *     %s      Subject, with all Re's and (nf)'s stripped off
+ *     %S      Subject, with one Re stripped off
+ *     %t      New To: line derived from From: and Reply-To (Internet always)
+ *     %T      New To: line derived from Path:
+ *     %u      Number of unread articles
+ *     %U      Number of unread articles disregarding current article
+ *     %v      Number of unselected articles disregarding current article
+ *     %W      The thread directory root
+ *     %x      News library directory, usually /usr/lib/news
+ *     %X      Rn library directory, usually %x/rn
+ *     %z      Size of current article in bytes.
+ *     %Z      Number of selected threads.
+ *     %~      Home directory
+ *     %.      Directory containing . files
+ *     %#      count of articles saved in current command (from 1 to n)
+ *     %$      current process number
+ *     %{name} Environment variable "name".  %{name-default} form allowed.
+ *     %[name] Header line beginning with "Name: ", without "Name: " 
+ *     %"prompt"
+ *             Print prompt and insert what is typed.
+ *     %`command`
+ *             Insert output of command.
+ *     %(test_text=pattern?if_text:else_text)
+ *             Substitute if_text if test_text matches pattern, otherwise
+ *             substitute else_text.  Use != for negated match.
+ *             % substitutions are done on test_text, if_text, and else_text.
+ *             (Note: %() only works if CONDSUB defined.)
+ *     %digit  Substitute the text matched by the nth bracket in the last
+ *             pattern that had brackets.  %0 matches the last bracket
+ *             matched, in case you had alternatives.
+ *
+ *     Put ^ in the middle to capitalize the first letter: %^C = Rec.humor
+ *     Put _ in the middle to capitalize last component: %_c = net/Jokes
+ *     Put \ in the middle to quote regexp and % characters in the result
+ *     Put :FMT in the middle to format the result: %:-30.30t
+ *
+ *     ~ interpretation in filename expansion happens after % expansion, so
+ *     you could put ~%{NEWSLOGNAME-news} and it will expand correctly.
+ */
+
+/* *** System Dependent Stuff *** */
+
+/* NOTE: many of these are defined in the config.h file */
+
+/* name of organization */
+#ifndef ORGNAME
+#   define ORGNAME "ACME Widget Company, Widget Falls, Southern North Dakota"
+#endif
+
+#ifndef MBOXCHAR
+#   define MBOXCHAR 'F'        /* how to recognize a mailbox by 1st char */
+#endif
+
+#ifndef ROOTID
+#   define ROOTID 0        /* uid of superuser */
+#endif
+
+#ifdef NORMSIG
+#   define sigset signal
+#   define sigignore(sig) signal(sig,SIG_IGN)
+#endif
+
+#ifndef LOGDIRFIELD
+#   define LOGDIRFIELD 6               /* Which field (origin 1) is the */
+                                       /* login directory in /etc/passwd? */
+                                       /* (If it is not kept in passwd, */
+                                       /* but getpwnam() returns it, */
+                                       /* define the symbol HAS_GETPWENT) */
+#endif
+#ifndef GCOSFIELD
+#   define GCOSFIELD 5
+#endif
+
+#ifndef NEGCHAR
+#   define NEGCHAR '!'
+#endif
+
+/* Space conservation section */
+
+/* To save D space, cut down size of NGMAX and  VARYSIZE. */
+#define NGMAX 100      /* number of newsgroups allowed on command line */
+                       /* undefine ONLY symbol to disable "only" feature */
+#define VARYSIZE 256   /* this makes a block 1024 bytes long in DECville */
+                       /* (used by virtual array routines) */
+
+/* Undefine any of the following features to save both I and D space */
+/* In general, earlier ones are easier to get along without */
+#define CUSTOMLINES    /* include code for HIDELINE and PAGESTOP */
+#define WORDERASE      /* enable ^W to erase a word */
+#define MAILCALL       /* check periodically for mail */
+#define CLEAREOL       /* use clear to end-of-line instead of clear screen */
+#define NOFIREWORKS    /* keep whole screen from flashing on certain */
+                       /* terminals such as older Televideos */
+#define VERIFY         /* echo the command they just typed */
+#define HASHNG         /* hash newsgroup lines for fast lookup-- */
+                       /* linear search used if not defined */
+#define CONDSUB                /* allow %(cond?text:text) */
+#define BACKTICK       /* allow %`command` */
+#define PROMPTTTY      /* allow %"prompt" */
+#define ULSMARTS       /* catch _^H in text and do underlining */
+#define TERMMOD                /* allow terminal type modifier on switches */
+#define BAUDMOD                /* allow baudrate modifier on switches */
+#define GETLOGIN       /* use getlogin() routine as backup to environment */
+                       /* variables USER or LOGNAME */
+#define ORGFILE                /* if organization begins with /, look up in file */
+#define TILDENAME      /* allow ~logname expansion */
+#define SETENV         /* allow command line environment variable setting */
+#define MAKEDIR                /* use our makedir() instead of shell script */
+#define MEMHELP                /* keep help messages in memory */
+#define VERBOSE                /* compile in more informative messages */
+#define TERSE          /* compile in shorter messages */
+                       /* (Note: both VERBOSE and TERSE can be defined; -t
+                        * sets terse mode.  One or the other MUST be defined.
+                        */
+#define ROTATION       /* enable x, X and ^X commands to work */
+#define DELBOGUS       /* ask if bogus newsgroups should be deleted */
+#define RELOCATE       /* allow newsgroup rearranging */
+#define ESCSUBS                /* escape substitutions in multi-character commands */
+#undef MCHASE          /* unmark xrefed articles on m or M */
+#define MUNGHEADER     /* allow alternate header formatting via */
+                       /* environment variable ALTHEADER (not impl) */
+#define ASYNC_PARSE    /* allow parsing headers asyncronously to reading */
+                       /* used by MCHASE and MUNGHEADER */
+#define FINDNEWNG      /* check for new newsgroups on startup */
+#define FASTNEW                /* do optimizations on FINDNEWNG for faster startup */
+                       /* (this optimization can make occasional mistakes */
+                       /* if a group is removed and another group of the */
+                       /* same length is added, and if no softpointers are */
+                       /* affected by said change.) */
+#define INNERSEARCH    /* search command 'g' with article */
+#define CATCHUP                /* catchup command at newsgroup level */
+#define NGSEARCH       /* newsgroup pattern matching */
+#define ONLY           /* newsgroup restrictions by pattern */
+#define KILLFILES      /* automatic article killer files */
+#define ARTSEARCH      /* pattern searches among articles */
+                       /* /, ?, ^N, ^P, k, K */
+#define EDIT_DISTANCE  /* Allow -G to specify a fuzzy 'go' command */
+#undef METAMAIL        /* use metamail to process mime articles */
+
+/* some dependencies among options */
+
+#ifndef ARTSEARCH
+#   undef KILLFILES
+#   undef INNERSEARCH
+#endif
+
+#ifndef SETUIDGID
+#   define eaccess access
+#endif
+
+#ifdef ONLY                            /* idiot lint doesn't grok #if */
+#   define NGSORONLY
+#else
+#   ifdef NGSEARCH
+#      define NGSORONLY
+#   endif
+#endif
+
+#ifdef VERBOSE
+#   ifdef TERSE
+#      define IF(c) if (c)
+#      define ELSE else
+#   else
+#      define IF(c)
+#      define ELSE
+#   endif
+#else /* !VERBOSE */
+#   ifndef TERSE
+#      define TERSE
+#   endif
+#   define IF(c) "IF" outside of VERBOSE???
+#   define ELSE "ELSE" outside of VERBOSE???
+#endif
+
+#ifdef DEBUG
+#   define assert(ex) {if (!(ex)){fprintf(stderr,"Assertion failed: file %s, line %d\n", __FILE__, __LINE__);sig_catcher(0);}}
+#else
+#   define assert(ex) ;
+#endif
+
+/* If you're strapped for space use the help messages in shell scripts */
+/* if {NG,ART,PAGER,SUBS}HELP is undefined, help messages are in memory */
+#ifdef MEMHELP  /* undef MEMHELP above to get them all as sh scripts */
+#   undef NGHELP
+#   undef ARTHELP
+#   undef PAGERHELP
+#   undef SUBSHELP
+#else
+#   ifndef NGHELP                      /* % and ~ */
+#      define NGHELP "%X/ng.help"
+#   endif
+#   ifndef ARTHELP                     /* % and ~ */
+#      define ARTHELP "%X/art.help"
+#   endif
+#   ifndef PAGERHELP           /* % and ~ */
+#      define PAGERHELP "%X/pager.help"
+#   endif
+#   ifndef SUBSHELP            /* % and ~ */
+#      define SUBSHELP "%X/subs.help"
+#   endif
+#endif
+
+#define TCSIZE 512     /* capacity for termcap strings */
+
+#ifdef EDIT_DISTANCE
+#   define MIN_DIST 7  /* Maximum error count for acceptable match */
+#endif
+
+/* Additional ideas:
+ *     Make the do_newsgroup() routine a separate process.
+ *     Keep .newsrc on disk instead of in memory.
+ *     Overlays, if you have them.
+ *     Get a bigger machine.
+ */
+
+/* End of Space Conservation Section */
+
+/* More System Dependencies */
+
+/* news library */
+#ifndef NEWSLIB                /* ~ and %l only ("~%l" is permissable) */
+#   define NEWSLIB "/usr/lib/news"
+#endif
+
+/* path to private executables */
+#ifndef PRIVLIB                /* ~, %x and %l only */
+#   define PRIVLIB "%x/trn"
+#endif
+
+/* system-wide RNINIT switches */
+#ifndef GLOBINIT
+#   define GLOBINIT "%X/INIT"
+#endif
+
+/* where to find news files */
+#ifndef NEWSSPOOL              /* % and ~ */
+#   define NEWSSPOOL "/usr/spool/news"
+#endif
+
+#ifndef THREAD_DIR
+#   undef LONG_THREAD_NAMES
+#endif
+
+/* default characters to use in the selection menu */
+#ifndef SELECTCHARS
+#   define SELECTCHARS "abdefgijlorstuvwxyz1234567890BCFGHIKVW"
+#endif
+
+/* file containing list of active newsgroups and max article numbers */
+#ifndef ACTIVE                 /* % and ~ */
+#   define ACTIVE "%x/active"
+#endif
+#ifndef DBINIT
+#   define DBINIT "%W/db.init"
+#endif
+
+#ifdef USE_NNTP
+# ifndef ACTIVE_TIMES
+#   define APPEND_UNSUB
+# endif
+#else
+# ifdef USE_XTHREAD
+#   undef USE_XTHREAD
+# endif
+# ifdef USE_XOVER
+#   undef USE_XOVER
+# endif
+#endif
+
+/* location of history file */
+#ifndef ARTFILE                        /* % and ~ */
+#    define ARTFILE "%x/history"
+#endif
+
+/* command to setup a new .newsrc */
+#ifndef NEWSETUP               /* % and ~ */
+#   define NEWSETUP "newsetup"
+#endif
+
+/* command to display a list of un-subscribed-to newsgroups */
+#ifndef NEWSGROUPS             /* % and ~ */
+#   define NEWSGROUPS "newsgroups"
+#endif
+
+/* preferred shell for use in doshell routine */
+/*  ksh or sh would be okay here */
+#ifndef PREFSHELL
+#   define PREFSHELL "/bin/csh"
+#endif
+
+/* path to fastest starting shell */
+#ifndef SH
+#   define SH "/bin/sh"
+#endif
+
+/* default unshar'ing program */
+#ifndef UNSHAR
+#   define UNSHAR "/bin/sh"
+#endif
+
+#ifdef METAMAIL
+/* default MIME extraction program */
+#  ifndef MIMESTORE
+#    define MIMESTORE "/usr/local/bin/mh/mhn -store -auto -file "
+#  endif
+
+/* default MIME show program */
+#  ifndef MIMESHOW
+#    define MIMESHOW "metamail -e -p -m \"trn %s\" %A"
+#  endif
+#endif
+
+
+/* path to default editor */
+#ifndef DEFEDITOR
+#   define DEFEDITOR "/usr/ucb/vi"
+#endif
+
+/* location of macro file for trn and rn modes */
+#ifndef TRNMACRO
+#   define TRNMACRO "%./.trnmac"
+#endif
+#ifndef RNMACRO
+#   define RNMACRO "%./.rnmac"
+#endif
+
+/* location of full name */
+#ifndef FULLNAMEFILE
+#   ifndef PASSNAMES
+#      define FULLNAMEFILE "%./.fullname"
+#   endif
+#endif
+
+/* virtual array file name template */
+#ifndef VARYNAME               /* % and ~ */
+#   define VARYNAME "/tmp/rnvary.%$"
+#endif
+
+/* where to compile a new newsgroup list */
+#ifndef RNEWNAME
+#   define RNEWNAME "/tmp/rnew.%$"
+#endif
+
+/* file to pass header to followup article poster */
+#ifndef HEADNAME               /* % and ~ */
+#   define HEADNAME "%./.rnhead"
+/* or alternately #define HEADNAME "/tmp/rnhead.%$" */
+#endif
+
+#ifndef MAKEDIR
+/* shell script to make n-deep subdirectories */
+#   ifndef DIRMAKER            /* % and ~ */
+#      define DIRMAKER "%X/makedir"
+#   endif
+#endif
+
+/* location of newsrc file */
+#ifndef RCNAME         /* % and ~ */
+#   define RCNAME "%./.newsrc"
+#endif
+
+/* temporary newsrc file in case we crash while writing out */
+#ifndef RCTNAME                /* % and ~ */
+#   define RCTNAME "%./.newnewsrc"
+#endif
+
+/* newsrc file at the beginning of this session */
+#ifndef RCBNAME                /* % and ~ */
+#   define RCBNAME "%./.oldnewsrc"
+#endif
+
+/* if existent, contains process number of current or crashed trn */
+#ifndef LOCKNAME               /* % and ~ */
+#   define LOCKNAME "%./.rnlock"
+#endif
+
+/* information from last invocation of trn */
+#ifndef LASTNAME               /* % and ~ */
+#   define LASTNAME "%./.rnlast"
+#endif
+
+/* file with soft pointers into the active file */
+#ifndef SOFTNAME               /* % and ~ */
+#   define SOFTNAME "%./.rnsoft"
+#endif
+
+/* list of article numbers to mark as unread later (see M and Y cmmands) */
+#ifndef RNDELNAME              /* % and ~ */
+#   define RNDELNAME "%./.rndelay"
+#endif
+
+/* a motd-like file for trn */
+#ifndef NEWSNEWSNAME           /* % and ~ */
+#   define NEWSNEWSNAME "%X/newsnews"
+#endif
+
+/* command to send a reply */
+#ifndef MAILPOSTER             /* % and ~ */
+#   define MAILPOSTER "QUOTECHARS=%I Rnmail -h %h"
+#endif
+
+#ifdef INTERNET
+#   ifndef MAILHEADER          /* % */
+#      ifdef CONDSUB
+#          define MAILHEADER "To: %t\nSubject: Re: %S\n%(%{REPLYTO}=^$?:Reply-To: %{REPLYTO}\n)Newsgroups: %n\nIn-Reply-To: %i\n%(%[references]!=^$?References\\: %[references]\n)Organization: %o\nCc: \nBcc: \n\n"
+#      else
+#          define MAILHEADER "To: %t\nSubject: Re: %S\nNewsgroups: %n\nIn-Reply-To: %i\nReferences: %[references]\nCc: \nBcc: \n\n"
+#      endif
+#   endif
+#else
+#   ifndef MAILHEADER          /* % */
+#      ifdef CONDSUB
+#          define MAILHEADER "To: %T\nSubject: %(%i=^$?:Re: %S\nNewsgroups: %n\nIn-Reply-To: %i)\n%(%[references]!=^$?References\\: %[references]\n)Organization: %o\nCc: \nBcc: \n\n"
+#      else
+#          define MAILHEADER "To: %T\nSubject: Re: %S\nNewsgroups: %n\nIn-Reply-To: %i\nReferences: %[references]\nCc: \nBcc: \n\n"
+#      endif
+#   endif
+#endif
+
+#ifndef YOUSAID                        /* % */
+#   define YOUSAID "In article %i you write:"
+#endif
+
+/* command to submit a followup article */
+#ifndef NEWSPOSTER             /* % and ~ */
+#   define NEWSPOSTER "QUOTECHARS=%I Pnews -h %h"
+#endif
+
+#ifndef NEWSHEADER             /* % */
+#   ifdef CONDSUB
+#      define NEWSHEADER "%(%[followup-to]=^$?:X-ORIGINAL-NEWSGROUPS: %n\n)Newsgroups: %(%F=^$?%C:%F)\nSubject: %(%S=^$?%\"\n\nSubject: \":Re: %S)\nSummary: \nExpires: \n%(%R=^$?:References: %R\n)Sender: \nFollowup-To: \n%(%{REPLYTO}=^$?:Reply-To: %{REPLYTO}\n)Distribution: %(%i=^$?%\"Distribution: \":%D)\nOrganization: %o\nKeywords: %[keywords]\nCc: \n\n"
+#   else
+#      define NEWSHEADER "Newsgroups: %F\nSubject: Re: %S\nSummary: \nExpires: \nReferences: %R\nSender: \nFollowup-To: \nDistribution: %D\nOrganization: %o\nKeywords: %[keywords]\nCc: \n\n"
+#   endif
+#endif
+
+#ifndef ATTRIBUTION            /* % */
+#   define ATTRIBUTION "In article %i %f writes:"
+#endif
+
+#ifndef PIPESAVER              /* % */
+#   ifdef CONDSUB
+#      define PIPESAVER "%(%B=^0$?<%A:tail +%Bc %A |) %b"
+#   else
+#      define PIPESAVER "tail +%Bc %A | %b"
+#   endif
+#endif
+
+#ifndef EXSAVER
+#   define EXSAVER "tail +%Bc %A | %e"
+#endif
+
+#ifdef METAMAIL
+#  ifndef EXMIMESAVER
+#    define EXMIMESAVER "%e %A"
+#  endif
+#endif
+
+#ifndef NORMSAVER              /* % and ~ */
+#   define NORMSAVER "%X/norm.saver %A %P %c %a %B %C \"%b\""
+#endif
+
+#ifndef MBOXSAVER              /* % and ~ */
+#   ifndef ANCIENT_NEWS
+#      define MBOXSAVER "%X/mbox.saver %A %P %c %a %B %C \"%b\" \"From %t %`LANG= date`\""
+#   else
+#      ifdef CONDSUB
+#          define MBOXSAVER "%X/mbox.saver %A %P %c %a %B %C \"%b\" \"From %t %(%[date]=^\\(\\w*\\), \\(\\w*\\)-\\(\\w*\\)-\\(\\w*\\) \\([^ ]*\\)?%1 %3 %(%2=..?%2: %2) %5 19%4)\""
+                                       /* header munging with a vengeance */
+#      else
+#          define MBOXSAVER "%X/mbox.saver %A %P %c %a %B %C \"%b\" \"From %t %[posted]\""
+#      endif
+#   endif
+#endif
+
+#ifdef MKDIRS
+
+#   ifndef SAVEDIR                     /* % and ~ */
+#      define SAVEDIR "%p/%c"
+#   endif
+#   ifndef SAVENAME            /* % */
+#      define SAVENAME "%a"
+#   endif
+
+#else
+
+#   ifndef SAVEDIR                     /* % and ~ */
+#      define SAVEDIR "%p"
+#   endif
+#   ifndef SAVENAME            /* % */
+#      define SAVENAME "%^C"
+#   endif
+
+#endif
+
+#ifndef KILLGLOBAL             /* % and ~ */
+#   define KILLGLOBAL "%p/KILL"
+#endif
+
+#ifndef KILLLOCAL              /* % and ~ */
+#   define KILLLOCAL "%p/%c/KILL"
+#endif
+
+/* how to cancel an article */
+#ifndef CANCEL
+#   ifdef BNEWS
+#      define CANCEL "%x/inews -h < %h"
+#   else
+#      define CANCEL "inews -h < %h"
+#   endif
+#endif
+
+/* how to cancel an article, continued */
+#ifndef CANCELHEADER
+#   define CANCELHEADER "Newsgroups: %n\nSubject: cancel\nControl: cancel %i\nDistribution: %D\n\n%i was cancelled from within trn.\n"
+#endif
+
+/* how to supersede an article */
+#ifndef SUPERSEDEHEADER
+#   define SUPERSEDEHEADER "Newsgroups: %n\nSubject: %S\nSummary: %[summary]\nExpires: %[expires]\nReferences: %[references]\nSupersedes: %i\nSender: %[sender]\nFollowup-To: %[followup-to]\nDistribution: %D\nOrganization: %o\nKeywords: %[keywords]\n\n"
+#endif
+
+#ifndef LOCALTIMEFMT
+#   define LOCALTIMEFMT "%a %b %d %X %Z %Y"
+#endif
+
+/* where to find the mail file */
+#ifndef MAILFILE
+#   define MAILFILE "/usr/spool/mail/%L"
+#endif
+
+/* how to open binary format files */
+#ifndef FOPEN_RB
+#   define FOPEN_RB "r"
+#endif
+#ifndef FOPEN_WB
+#   define FOPEN_WB "w"
+#endif
+
+/* what to do with ansi prototypes -- '()' == ignore, 'x' == use */
+#ifndef _
+#   ifdef __STDC__
+#      define _(x) x
+#      ifndef CONST
+#          define CONST const
+#      endif
+#   else
+#      define _(x) ()
+#      ifndef CONST
+#          define CONST
+#      endif
+#   endif
+#endif
+
+/* how many characters is a newline in a text file? */
+#ifndef NL_SIZE
+#   define NL_SIZE 1
+#endif
+
+/* some important types */
+
+typedef int            NG_NUM;         /* newsgroup number */
+typedef long           ART_NUM;        /* article number */
+typedef long           ART_UNREAD;     /* could be short to save space */
+typedef long           ART_POS;        /* char position in article file */
+typedef int            ART_LINE;       /* line position in article file */
+typedef long           ACT_POS;        /* char position in active file */
+typedef unsigned int   MEM_SIZE;       /* for passing to malloc */
+
+
+/* *** end of the machine dependent stuff *** */
+
+/* GLOBAL THINGS */
+
+/* file statistics area */
+
+EXT struct stat filestat;
+
+/* various things of type char */
+
+char   *index();
+char   *rindex();
+char   *getenv();
+char   *strcat();
+char   *strcpy();
+char   *malloc();
+char   *realloc();
+
+EXT char buf[LBUFLEN+1];       /* general purpose line buffer */
+EXT char cmd_buf[CBUFLEN];     /* buffer for formatting system commands */
+EXT char *indstr INIT(">");    /* indent for old article embedded in followup */
+
+EXT char *cwd INIT(Nullch);            /* current working directory */
+EXT char *dfltcmd INIT(Nullch);        /* 1st char is default command */
+
+/* switches */
+
+#ifdef DEBUG
+    EXT int debug INIT(0);                             /* -D */
+#   define DEB_COREDUMPSOK 2
+#   define DEB_HEADER 4
+#   define DEB_INTRP 8
+#   define DEB_NNTP 16
+#   define DEB_INNERSRCH 32
+#   define DEB_FILEXP 64 
+#   define DEB_HASH 128
+#   define DEB_XREF_MARKER 256
+#   define DEB_CTLAREA_BITMAP 512
+#   define DEB_SOFT_POINTERS 1024
+#   define DEB_NEWSRC_LINE 2048
+#   define DEB_SEARCH_AHEAD 4096
+#   define DEB_CHECKPOINTING 8192
+#   define DEB_FEED_XREF 16384
+#endif
+
+#ifdef ARTSEARCH
+    EXT int scanon INIT(0);                            /* -S */
+#endif
+
+EXT bool use_threads INIT(THREAD_INIT);                        /* -x */
+EXT int max_tree_lines INIT(6);
+EXT char select_order[4] INIT("lms");
+EXT int select_on INIT(SELECT_INIT);                   /* -X */
+EXT char end_select INIT('Z');
+EXT char page_select INIT('>');
+
+EXT bool dont_filter_control INIT(FALSE);              /* -j */
+EXT bool mbox_always INIT(FALSE);                      /* -M */
+EXT bool norm_always INIT(FALSE);                      /* -N */
+EXT bool thread_always INIT(FALSE);                    /* -a */
+EXT bool auto_arrow_macros INIT(TRUE);                 /* -B */
+EXT bool breadth_first INIT(FALSE);                    /* -b */
+EXT bool bkgnd_spinner INIT(FALSE);                    /* -B */
+EXT bool novice_delays INIT(TRUE);                     /* +f */
+EXT int olden_days INIT(FALSE);                                /* -o */
+EXT bool auto_select_postings INIT(FALSE);             /* -p */
+EXT bool checkflag INIT(FALSE);                                /* -c */
+EXT bool suppress_cn INIT(FALSE);                      /* -s */
+EXT int countdown INIT(5);     /* how many lines to list before invoking -s */
+EXT bool muck_up_clear INIT(FALSE);                    /* -loco */
+EXT bool erase_screen INIT(FALSE);                     /* -e */
+EXT bool can_home INIT(FALSE);
+#ifdef CLEAREOL
+EXT bool can_home_clear INIT(FALSE);           /* fancy -e */
+#endif
+EXT bool findlast INIT(FALSE);                 /* -r */
+EXT bool typeahead INIT(FALSE);                        /* -T */
+#ifdef EDIT_DISTANCE
+EXT bool fuzzyGet INIT(FALSE);                 /* -G */
+#endif
+#ifdef VERBOSE
+#   ifdef TERSE
+       EXT bool verbose INIT(TRUE);                    /* +t */
+#   endif
+#endif
+EXT bool unbroken_subjects INIT(FALSE);                        /* -u */
+#ifdef VERIFY
+    EXT bool verify INIT(FALSE);                       /* -v */
+#endif
+    EXT bool quickstart INIT(FALSE);                   /* -q */
+
+#define NOMARKING 0
+#define STANDOUT 1
+#define UNDERLINE 2
+EXT int marking INIT(NOMARKING);                       /* -m */
+
+EXT ART_LINE initlines INIT(0);                                /* -i */
+EXT bool initlines_specified INIT(FALSE);
+#ifdef APPEND_UNSUB
+EXT bool append_unsub INIT(1);                         /* -I */
+#else
+EXT bool append_unsub INIT(0);
+#endif
+
+/* miscellania */
+
+int fseek();
+long atol(), ftell();
+EXT bool in_ng INIT(FALSE);            /* current state of trn */
+EXT char mode INIT('i');               /* current state of trn */
+
+EXT FILE *tmpfp INIT(Nullfp);  /* scratch fp used for .rnlock, .rnlast, etc. */
+
+EXT NG_NUM nextrcline INIT(0); /* 1st unused slot in rcline array */
+                       /* startup to avoid checking twice in a row */
+
+extern errno;
+/* Factored strings */
+
+EXT char nullstr[1] INIT("");
+EXT char sh[] INIT(SH);
+EXT char defeditor[] INIT(DEFEDITOR);
+EXT char hforhelp[] INIT("Type h for help.\n");
+#ifdef STRICTCR
+EXT char badcr[] INIT("\nUnnecessary CR ignored.\n");
+#endif
+EXT char readerr[] INIT("rn read error");
+EXT char unsubto[] INIT("\n\nUnsubscribed to newsgroup %s\n");
+EXT char cantopen[] INIT("Can't open %s\n");
+EXT char cantcreate[] INIT("Can't create %s\n");
+EXT char cantrecreate[] INIT("Can't recreate %s -- restoring older version.\n");
+
+#ifdef VERBOSE
+    EXT char nocd[] INIT("Can't chdir to directory %s\n");
+#else
+    EXT char nocd[] INIT("Can't find %s\n");
+#endif
+
+#ifdef METAMAIL
+EXT bool mime_article INIT(FALSE);
+#endif
+
+#ifdef NOLINEBUF
+#define FLUSH ,fflush(stdout)
+#else
+#define FLUSH
+#endif
+
+#ifdef lint
+#undef FLUSH
+#define FLUSH
+#undef putchar
+#define putchar(c)
+#endif
+
+#define advise(str) fputs(str,stdout)
+#define fatal_error(str) fputs(str,stderr), finalize(1)
diff --git a/usr/src/contrib/news/trn3/hash.c b/usr/src/contrib/news/trn3/hash.c
new file mode 100644 (file)
index 0000000..8e219e6
--- /dev/null
@@ -0,0 +1,282 @@
+/* $Id: hash.c,v 3.0 1992/12/14 00:14:13 davison Trn $
+*/
+/* This file is an altered version of a set of hash routines by
+** Geoffrey Collyer.  See the end of the file for his copyright.
+*/
+
+#include "EXTERN.h"
+#include "common.h"
+#include "util.h"
+#include "INTERN.h"
+#include "hash.h"
+
+/* tunable parameters */
+#define RETAIN 600             /* retain & recycle this many HASHENTs */
+
+static HASHENT *hereuse = NULL;
+static int reusables = 0;
+
+static HASHENT **hashfind _((HASHTABLE*,char*,int));
+static unsigned hash _((char*,int));
+static int default_cmp _((char*,int,HASHDATUM));
+static HASHENT *healloc _((void));
+static void hefree _((HASHENT*));
+
+HASHTABLE *
+hashcreate(size, cmpfunc)
+unsigned size;                 /* a crude guide to size */
+int (*cmpfunc)();
+{
+    register HASHTABLE *tbl;
+    /* allocate HASHTABLE and (HASHENT*) array together to reduce the
+    ** number of malloc calls. */
+    register struct alignalloc {
+       HASHTABLE ht;
+       HASHENT *hepa[1];       /* longer than it looks */
+    } *aap;
+
+    aap = (struct alignalloc*)
+       safemalloc(sizeof *aap + (size-1)*sizeof (HASHENT*));
+    bzero((char*)aap, sizeof *aap + (size-1)*sizeof (HASHENT*));
+    tbl = &aap->ht;
+    tbl->ht_size = (size == 0? 1: size);       /* size of 0 is nonsense */
+    tbl->ht_magic = HASHMAG;
+    tbl->ht_cmp = (cmpfunc == NULL? default_cmp: cmpfunc);
+    tbl->ht_addr = aap->hepa;
+    return tbl;
+}
+
+/* Free all the memory associated with tbl, erase the pointers to it, and
+** invalidate tbl to prevent further use via other pointers to it.
+*/
+void
+hashdestroy(tbl)
+register HASHTABLE *tbl;
+{
+    register unsigned idx;
+    register HASHENT *hp, *next;
+    register HASHENT **hepp;
+    register int tblsize;
+
+    if (tbl == NULL || BADTBL(tbl))
+       return;
+    tblsize = tbl->ht_size;
+    hepp = tbl->ht_addr;
+    for (idx = 0; idx < tblsize; idx++) {
+       for (hp = hepp[idx]; hp != NULL; hp = next) {
+           next = hp->he_next;
+           hp->he_next = NULL;
+           hefree(hp);
+       }
+       hepp[idx] = NULL;
+    }
+    tbl->ht_magic = 0;                 /* de-certify this table */
+    tbl->ht_addr = NULL;
+    free((char*)tbl);
+}
+
+void
+hashstore(tbl, key, keylen, data)
+register HASHTABLE *tbl;
+char *key;
+int keylen;
+HASHDATUM data;
+{
+    register HASHENT *hp;
+    register HASHENT **nextp;
+
+    nextp = hashfind(tbl, key, keylen);
+    hp = *nextp;
+    if (hp == NULL) {                  /* absent; allocate an entry */
+       hp = healloc();
+       hp->he_next = NULL;
+       hp->he_keylen = keylen;
+       *nextp = hp;                    /* append to hash chain */
+    }
+    hp->he_data = data;                /* supersede any old data for this key */
+}
+
+void
+hashdelete(tbl, key, keylen)
+register HASHTABLE *tbl;
+char *key;
+int keylen;
+{
+    register HASHENT *hp;
+    register HASHENT **nextp;
+
+    nextp = hashfind(tbl, key, keylen);
+    hp = *nextp;
+    if (hp == NULL)                    /* absent */
+       return;
+    *nextp = hp->he_next;              /* skip this entry */
+    hp->he_next = NULL;
+    hp->he_data.dat_ptr = NULL;
+    hefree(hp);
+}
+
+HASHENT **slast_nextp;
+int slast_keylen;
+
+HASHDATUM                              /* data corresponding to key */
+hashfetch(tbl, key, keylen)
+register HASHTABLE *tbl;
+char *key;
+int keylen;
+{
+    register HASHENT *hp;
+    register HASHENT **nextp;
+    static HASHDATUM errdatum = { NULL, 0 };
+
+    nextp = hashfind(tbl, key, keylen);
+    slast_nextp = nextp;
+    slast_keylen = keylen;
+    hp = *nextp;
+    if (hp == NULL)                    /* absent */
+       return errdatum;
+    else
+       return hp->he_data;
+}
+
+void
+hashstorelast(data)
+HASHDATUM data;
+{
+    register HASHENT *hp;
+
+    hp = *slast_nextp;
+    if (hp == NULL) {                  /* absent; allocate an entry */
+       hp = healloc();
+       hp->he_next = NULL;
+       hp->he_keylen = slast_keylen;
+       *slast_nextp = hp;              /* append to hash chain */
+    }
+    hp->he_data = data;                /* supersede any old data for this key */
+}
+
+/* Visit each entry by calling nodefunc at each, with key, data and extra as
+** arguments.
+*/
+void
+hashwalk(tbl, nodefunc, extra)
+HASHTABLE *tbl;
+register void (*nodefunc)();
+register int extra;
+{
+    register unsigned idx;
+    register HASHENT *hp;
+    register HASHENT **hepp;
+    register int tblsize;
+
+    if (BADTBL(tbl))
+       return;
+    hepp = tbl->ht_addr;
+    tblsize = tbl->ht_size;
+    for (idx = 0; idx < tblsize; idx++)
+       for (hp = hepp[idx]; hp != NULL; hp = hp->he_next)
+           (*nodefunc)(&hp->he_data, extra);
+}
+
+/* The returned value is the address of the pointer that refers to the
+** found object.  Said pointer may be NULL if the object was not found;
+** if so, this pointer should be updated with the address of the object
+** to be inserted, if insertion is desired.
+*/
+static HASHENT **
+hashfind(tbl, key, keylen)
+register HASHTABLE *tbl;
+char *key;
+register int keylen;
+{
+    register HASHENT *hp, *prevhp = NULL;
+    register HASHENT **hepp;
+    register unsigned size; 
+
+    if (BADTBL(tbl))
+       fatal_error("Hash table is invalid.");
+    size = tbl->ht_size;
+    hepp = &tbl->ht_addr[hash(key,keylen) % size];
+    for (hp = *hepp; hp != NULL; prevhp = hp, hp = hp->he_next) {
+       if (hp->he_keylen == keylen && !(*tbl->ht_cmp)(key, keylen, hp->he_data))
+           break;
+    }
+    /* assert: *(returned value) == hp */
+    return (prevhp == NULL? hepp: &prevhp->he_next);
+}
+
+static unsigned                                /* not yet taken modulus table size */
+hash(key, keylen)
+register char *key;
+register int keylen;
+{
+    register unsigned hash = 0;
+
+    while (keylen--)
+       hash += *key++;
+    return hash;
+}
+
+static int
+default_cmp(key, keylen, data)
+char *key;
+int keylen;
+HASHDATUM data;
+{
+    /* We already know that the lengths are equal, just compare the strings */
+    return bcmp(key, data.dat_ptr, keylen);
+}
+
+static HASHENT *
+healloc()                              /* allocate a hash entry */
+{
+    register HASHENT *hp;
+
+    if (hereuse == NULL)
+       return (HASHENT*)safemalloc(sizeof (HASHENT));
+    /* pull the first reusable one off the pile */
+    hp = hereuse;
+    hereuse = hereuse->he_next;
+    hp->he_next = NULL;                        /* prevent accidents */
+    reusables--;
+    return hp;
+}
+
+static void
+hefree(hp)                             /* free a hash entry */
+register HASHENT *hp;
+{
+    if (reusables >= RETAIN)           /* compost heap is full? */
+       free((char*)hp);                /* yup, just pitch this one */
+    else {                             /* no, just stash for reuse */
+       ++reusables;
+       hp->he_next = hereuse;
+       hereuse = hp;
+    }
+}
+
+/*
+ * Copyright (c) 1992 Geoffrey Collyer
+ * All rights reserved.
+ * Written by Geoffrey Collyer.
+ *
+ * This software is not subject to any license of the American Telephone
+ * and Telegraph Company, the Regents of the University of California, or
+ * the Free Software Foundation.
+ *
+ * Permission is granted to anyone to use this software for any purpose on
+ * any computer system, and to alter it and redistribute it freely, subject
+ * to the following restrictions:
+ *
+ * 1. The author is not responsible for the consequences of use of this
+ *    software, no matter how awful, even if they arise from flaws in it.
+ *
+ * 2. The origin of this software must not be misrepresented, either by
+ *    explicit claim or by omission.  Since few users ever read sources,
+ *    credits must appear in the documentation.
+ *
+ * 3. Altered versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.  Since few users
+ *    ever read sources, credits must appear in the documentation.
+ *
+ * 4. This notice may not be removed or altered.
+ */
diff --git a/usr/src/contrib/news/trn3/rcstuff.h b/usr/src/contrib/news/trn3/rcstuff.h
new file mode 100644 (file)
index 0000000..99cb195
--- /dev/null
@@ -0,0 +1,58 @@
+/* $Id: rcstuff.h,v 3.0 1992/02/01 03:09:32 davison Trn $
+ */
+/* This software is Copyright 1991 by Stan Barber. 
+ *
+ * Permission is hereby granted to copy, reproduce, redistribute or otherwise
+ * use this software as long as: there is no monetary profit gained
+ * specifically from the use or reproduction of this software, it is not
+ * sold, rented, traded or otherwise marketed, and this copyright notice is
+ * included prominently in any copy made. 
+ *
+ * The author make no claims as to the fitness or correctness of this software
+ * for any use whatsoever, and it is provided as is. Any use of this software
+ * is at the user's own risk. 
+ */
+
+EXT char **rcline INIT(NULL);/* pointers to lines of .newsrc */
+EXT ART_UNREAD *toread INIT(NULL);
+                       /* number of articles to be read in newsgroup */
+                       /* <0 => invalid or unsubscribed newsgroup */
+#define TR_ONE ((ART_UNREAD) 1)
+#define TR_NONE ((ART_UNREAD) 0)
+#define TR_UNSUB ((ART_UNREAD) -1)
+                       /* keep this one as -1, some tests use >= TR_UNSUB */
+#define TR_BOGUS ((ART_UNREAD) -2)
+#define TR_JUNK ((ART_UNREAD) -3)
+
+#define RCCHAR(ch) ((ch) == '0' ? ':' : (ch))
+
+#define ADDNEW_SUB ':'
+#define ADDNEW_UNSUB '!'
+
+#define GNG_RELOC      0x0001
+#define GNG_FUZZY      0x0002
+
+EXT char *rcchar INIT(NULL); /* holds the character : or ! while spot is \0 */
+EXT char *rcnums INIT(NULL); /* offset from rcline to numbers on line */
+EXT ACT_POS *softptr INIT(NULL);
+                       /* likely ptr to active file entry for newsgroup */
+EXT bool paranoid INIT(FALSE); /* did we detect some inconsistency in .newsrc? */
+EXT int maxrcline INIT(0);     /* current maximum # of lines in .newsrc */
+EXT int addnewbydefault INIT(0);
+
+bool   rcstuff_init _((void));
+void   abandon_ng _((NG_NUM));
+bool   get_ng _((char*,int)); /* return TRUE if newsgroup is found or added */
+NG_NUM add_newsgroup _((char*,char_int));
+#ifdef RELOCATE
+NG_NUM relocate_newsgroup _((NG_NUM,NG_NUM)); /* move newsgroup around */
+#endif
+void   list_newsgroups _((void));
+NG_NUM find_ng _((char*));     /* return index of newsgroup */
+void   cleanup_rc _((void));
+void   sethash _((NG_NUM));
+int    hash _((char*));
+void   newsrc_check _((void));
+void   checkpoint_rc _((void));
+void   write_rc _((void));
+void   get_old_rc _((void));
diff --git a/usr/src/contrib/news/trn3/rt-select.c b/usr/src/contrib/news/trn3/rt-select.c
new file mode 100644 (file)
index 0000000..dd89a60
--- /dev/null
@@ -0,0 +1,1002 @@
+/* $Id: rt-select.c,v 3.0 1992/12/14 00:14:12 davison Trn $
+*/
+
+#include "EXTERN.h"
+#include "common.h"
+#include "trn.h"
+#include "term.h"
+#include "final.h"
+#include "util.h"
+#include "help.h"
+#include "cache.h"
+#include "bits.h"
+#include "artsrch.h"
+#include "ng.h"
+#include "ngdata.h"
+#include "ngstuff.h"
+#include "kfile.h"
+#include "rthread.h"
+#include "rt-page.h"
+#include "rt-util.h"
+#include "INTERN.h"
+#include "rt-select.h"
+
+/* When display mode is 'l', each author gets a separate line; when 's', no
+** authors are displayed.
+*/
+char *display_mode = select_order;
+char sel_disp_char[] = { ' ', '+', '-', '*' };
+
+static char sel_ret;
+static bool empty_ok;
+static bool disp_status_line;
+static bool clean_screen;
+
+/* Display a menu of threads/subjects/articles for the user to choose from.
+** If "cmd" is '+' we display all the unread items and allow the user to mark
+** them as selected and perform various commands upon them.  If "cmd" is 'U'
+** the list consists of previously-read items for the user to mark as unread.
+*/
+char
+do_selector(cmd)
+char_int cmd;
+{
+    register int j;
+    int got_dash;
+    int ch, action;
+    char page_char, end_char;
+    char promptbuf[80];
+    char oldmode = mode;
+    char *in_select;
+
+    mode = 't';
+    sel_rereading = (cmd == 'U');
+    clear_on_stop = TRUE;
+    empty_ok = FALSE;
+
+    set_sel_mode(cmd);
+
+    if (!cache_range(sel_rereading? absfirst : firstart, lastart)) {
+       sel_ret = '+';
+       goto sel_exit;
+    }
+
+  start_selector:
+    /* Setup for selecting articles to read or set unread */
+    if (sel_rereading) {
+       page_char = '>';
+       end_char = 'Z';
+       sel_page_app = Null(ARTICLE**);
+       sel_page_sp = Nullsubj;
+       sel_mask = AF_DELSEL;
+    } else {
+       page_char = page_select;
+       end_char = end_select;
+       if (curr_artp) {
+           sel_last_ap = curr_artp;
+           sel_last_sp = curr_artp->subj;
+       }
+       sel_mask = AF_SEL;
+    }
+    selected_only = TRUE;
+    count_subjects(cmd ? CS_UNSEL_STORE : CS_NORM);
+
+    /* If nothing to display, we're done. */
+    if (!article_count && !empty_ok) {
+       empty_complaint();
+       sel_ret = '+';
+       goto sel_exit;
+    }
+    init_pages();
+    sel_item_index = 0;
+    *promptbuf = '\0';
+    disp_status_line = FALSE;
+    if (added_articles > 0) {
+       sprintf(promptbuf, "** %ld new article%s arrived **  ",
+               (long)added_articles, added_articles == 1? nullstr : "s");
+       disp_status_line = TRUE;
+    }
+    added_articles = 0;
+    if (cmd && selected_count) {
+       sprintf(promptbuf+strlen(promptbuf), "%ld article%s selected.",
+               (long)selected_count, selected_count == 1? " is" : "s are");
+       disp_status_line = TRUE;
+    }
+    cmd = 0;
+display_selector:
+    /* Present a page of items to the user */
+    display_page();
+
+    /* Check if there is really anything left to display. */
+    if (!sel_item_cnt && !empty_ok) { /*TODO: this may not be needed anymore */
+       empty_complaint();
+       sel_ret = '+';
+       goto sel_exit;
+    }
+    empty_ok = FALSE;
+
+    if (sel_item_index >= sel_item_cnt)
+       sel_item_index = 0;
+    if (disp_status_line) {
+       printf("\n%s", promptbuf);
+       if (can_home) {
+           carriage_return();
+           goto_line(sel_last_line+1, sel_last_line);
+       } else
+           putchar('\n');
+    }
+reask_selector:
+    /* Prompt the user */
+#ifdef MAILCALL
+    setmail(FALSE);
+#endif
+    if (sel_at_end)
+       sprintf(cmd_buf, "%s [%c%c] --",
+               (!sel_prior_arts? "All" : "Bot"), end_char, page_char);
+    else
+       sprintf(cmd_buf, "%s%ld%% [%c%c] --",
+               (!sel_prior_arts? "Top " : nullstr),
+               (long)((sel_prior_arts+sel_page_arts)*100 / sel_total_arts),
+               page_char, end_char);
+    sprintf(promptbuf, "%s-- %s %s (%s%s order) -- %s", mailcall,
+           sel_exclusive? "SELECTED" : "Select", sel_mode_string,
+           sel_direction<0? "reverse " : nullstr, sel_sort_string, cmd_buf);
+#ifdef CLEAREOL
+    if (erase_screen && can_home_clear)
+       clear_rest();
+#endif
+    standout();
+    fputs(promptbuf, stdout);
+    un_standout();
+    if (can_home)
+       carriage_return();
+    sel_line = sel_last_line;
+position_selector:
+    got_dash = 0;
+    if (can_home)
+       goto_line(sel_line, sel_items[sel_item_index].line);
+    sel_line = sel_items[sel_item_index].line;
+reinp_selector:
+    /* Grab some commands from the user */
+    fflush(stdout);
+    eat_typeahead();
+    spin_char = sel_chars[sel_item_index];
+    cache_until_key();
+    spin_char = ' ';
+#ifdef CONDSUB
+    getcmd(buf);
+    ch = *buf;
+#else
+    getcmd(cmd_buf);   /* If no conditionals, don't allow macros */ 
+    buf[0] = ch = *cmd_buf;
+    buf[1] = FINISHCMD;
+#endif
+    if (errno)
+       ch = Ctl('l');
+    if (disp_status_line) {
+       if (can_home) {
+           goto_line(sel_line, sel_last_line+1);
+           erase_eol();
+           sel_line = sel_last_line+1;
+       }
+       disp_status_line = FALSE;
+    }
+    if (ch == '-') {
+       got_dash = 1;
+       if (!can_home)
+           putchar('-'), fflush(stdout);
+       goto reinp_selector;
+    }
+    if (ch == ' ') {
+       if (sel_at_end)
+           ch = end_char;
+       else
+           ch = page_char;
+    }
+    in_select = index(sel_chars, ch);
+    if (in_select) {
+       j = in_select - sel_chars;
+       if (j >= sel_item_cnt) {
+           dingaling();
+           goto position_selector;
+       } else if (got_dash)
+           ;
+       else if (sel_items[j].sel == 1)
+           action = (sel_rereading ? 'k' : '-');
+       else
+           action = '+';
+    } else if (ch == '*' && sel_mode == SM_ARTICLE) {
+       register ARTICLE *ap = (ARTICLE*)sel_items[sel_item_index].ptr;
+       if (sel_items[sel_item_index].sel)
+           deselect_subject(ap->subj);
+       else
+           select_subject(ap->subj, 0);
+       update_page();
+       goto position_selector;
+    } else if (ch == 'y' || ch == '.' || ch == '*') {
+       j = sel_item_index;
+       if (sel_items[j].sel == 1)
+           action = (sel_rereading ? 'k' : '-');
+       else
+           action = '+';
+    } else if (ch == 'k' || ch == 'j' || ch == ',') {
+       j = sel_item_index;
+       action = 'k';
+    } else if (ch == 'm' || ch == '\\') {
+       j = sel_item_index;
+       action = '-';
+    } else if (ch == 'M') {
+       j = sel_item_index;
+       action = 'M';
+    } else if (ch == '@') {
+       sel_item_index = 0;
+       j = sel_item_cnt-1;
+       got_dash = 1;
+       action = '@';
+    } else if (ch == '[' || ch == 'p') {
+       if (--sel_item_index < 0)
+           sel_item_index = sel_item_cnt ? sel_item_cnt-1 : 0;
+       goto position_selector;
+    } else if (ch == ']' || ch == 'n') {
+       if (++sel_item_index >= sel_item_cnt)
+           sel_item_index = 0;
+       goto position_selector;
+    } else {
+       sel_ret = ch;
+       switch (sel_command(ch)) {
+       case DS_POS:
+           if (!clean_screen)
+               goto display_selector;
+           goto position_selector;
+       case DS_ASK:
+           if (!clean_screen)
+               goto display_selector;
+           goto reask_selector;
+       case DS_DISPLAY:
+       ds_display:
+           if (disp_status_line)
+               strcpy(promptbuf, buf);
+           goto display_selector;
+       case DS_UPDATE:
+           if (!clean_screen)
+               goto ds_display;
+           if (disp_status_line) {
+               printf("\n%s",buf);
+               if (can_home) {
+                   carriage_return();
+                   up_line();
+                   erase_eol();
+               }
+           }
+           update_page();
+           if (can_home) {
+               goto_line(sel_line, sel_last_line);
+               sel_line = sel_last_line;
+           }
+           goto reask_selector;
+       case DS_RESTART:
+           goto start_selector;
+       case DS_QUIT:
+           sel_cleanup();
+           if (!output_chase_phrase)
+               putchar('\n') FLUSH;
+           goto sel_exit;
+       case DS_STATUS:
+           disp_status_line = TRUE;
+           if (!clean_screen) {
+               strcpy(promptbuf, buf);
+               goto display_selector;
+           }
+           if (can_home) {
+               goto_line(sel_line, sel_last_line+1);
+               sel_line = sel_last_line+1;
+           } else
+               putchar('\n');
+
+           fputs(buf, stdout);
+
+           if (can_home)
+               carriage_return();
+           else
+               putchar('\n');
+           goto position_selector;
+       }
+    }
+
+    /* The user manipulated one of the letters -- handle it. */
+    if (!got_dash)
+       sel_item_index = j;
+    else {
+       if (j < sel_item_index) {
+           ch = sel_item_index-1;
+           sel_item_index = j;
+           j = ch;
+       }
+    }
+    if (++j == sel_item_cnt)
+       j = 0;
+    do {
+       register int sel = sel_items[sel_item_index].sel;
+       register SUBJECT *sp = (SUBJECT*)sel_items[sel_item_index].ptr;
+       if (can_home) {
+           goto_line(sel_line, sel_items[sel_item_index].line);
+           sel_line = sel_items[sel_item_index].line;
+       }
+       if (action == '@') {
+           if (sel == 2)
+               ch = (sel_rereading ? '+' : ' ');
+           else if (sel_rereading)
+               ch = 'k';
+           else if (sel == 1)
+               ch = '-';
+           else
+               ch = '+';
+       } else
+           ch = action;
+       switch (ch) {
+       case '+':
+           if (sel_mode == SM_THREAD)
+               select_thread(sp->thread, 0);
+           else if (sel_mode == SM_SUBJECT)
+               select_subject(sp, 0);
+           else
+               select_article((ARTICLE*)sp, 0);
+           output_sel(1);
+           break;
+       case '-':  case 'k':  case 'M':
+          {
+           bool sel_reread_save = sel_rereading;
+           if (ch == 'M') {
+               if (sel_mode == SM_ARTICLE)
+                   delay_unmark((ARTICLE*)sp);
+               else {
+                   register ARTICLE *ap;
+                   if (sel_mode == SM_THREAD) {
+                       for (ap = first_art(sp); ap; ap = next_art(ap))
+                           if (!(ap->flags & AF_READ) ^ sel_rereading)
+                               delay_unmark(ap);
+                   } else {
+                       for (ap = sp->articles; ap; ap = ap->subj_next)
+                           if (!(ap->flags & AF_READ) ^ sel_rereading)
+                               delay_unmark(ap);
+                   }
+               }
+           }
+           if (ch == '-')
+               sel_rereading = FALSE;
+           else
+               sel_rereading = TRUE;
+           if (sel_mode == SM_THREAD)
+               deselect_thread(sp->thread);
+           else if (sel_mode == SM_SUBJECT)
+               deselect_subject(sp);
+           else
+               deselect_article((ARTICLE*)sp);
+           sel_rereading = sel_reread_save;
+           output_sel(ch == '-'? 0 : 2);
+           break;
+          }
+       }
+       fflush(stdout);
+       if (++sel_item_index == sel_item_cnt)
+           sel_item_index = 0;
+       if (can_home)
+           carriage_return();
+    } while (sel_item_index != j);
+    goto position_selector;
+
+sel_exit:
+    if (sel_rereading) {
+       sel_rereading = 0;
+       sel_mask = AF_SEL;
+    }
+    if (sel_mode != SM_ARTICLE || sel_sort == SS_GROUPS
+     || sel_sort == SS_SUBJECT) {
+       if (artptr_list) {
+           free((char*)artptr_list);
+           artptr_list = sel_page_app = Null(ARTICLE**);
+           sort_subjects();
+       }
+       artptr = Null(ARTICLE**);
+#ifdef ARTSEARCH
+       if (!ThreadedGroup)
+           srchahead = -1;
+#endif
+    }
+#ifdef ARTSEARCH
+    else
+       srchahead = 0;
+#endif
+    selected_only = (selected_count || !article_count);
+    if (sel_ret != '#')
+       count_subjects(sel_ret == '+'? CS_RESELECT : CS_UNSELECT);
+    clear_on_stop = FALSE;
+    mode = oldmode;
+    if (sel_ret == '+') {
+       art = curr_art;
+       artp = curr_artp;
+    } else
+       top_article();
+    return sel_ret;
+}
+
+static void
+sel_cleanup()
+{
+    if (sel_rereading) {
+       /* Turn selections into unread selected articles.  Let
+       ** count_subjects() fix the counts after we're through.
+       */
+       register SUBJECT *sp;
+       sel_last_ap = Nullart;
+       sel_last_sp = Nullsubj;
+       for (sp = first_subject; sp; sp = sp->next)
+           unkill_subject(sp);
+    } else {
+       if (sel_mode == SM_ARTICLE) {
+           register ARTICLE *ap;
+           register ART_NUM an;
+           for (an=absfirst, ap=article_ptr(an); an<=lastart; an++, ap++) {
+               if (ap->flags & AF_DEL) {
+                   ap->flags &= ~AF_DEL;
+                   set_read(ap);
+               }
+           }
+       } else {
+           register SUBJECT *sp;
+           for (sp = first_subject; sp; sp = sp->next) {
+               if (sp->flags & SF_DEL) {
+                   sp->flags &= ~SF_DEL;
+                   if (sel_mode == SM_THREAD)
+                       kill_thread(sp->thread, KF_UNSELECTED);
+                   else
+                       kill_subject(sp, KF_UNSELECTED);
+               }
+           }
+       }
+    }
+}
+
+static int
+sel_command(ch)
+char_int ch;
+{
+    if (can_home)
+       goto_line(sel_line, sel_last_line);
+    sel_line = sel_last_line;
+    clean_screen = TRUE;
+  do_command:
+    output_chase_phrase = TRUE;
+    switch (ch) {
+    case '>':
+       sel_item_index = 0;
+       if (next_page())
+           return DS_DISPLAY;
+       return DS_POS;
+    case '<':
+       sel_item_index = 0;
+       if (prev_page())
+           return DS_DISPLAY;
+       return DS_POS;
+    case '^':  case Ctl('r'):
+       sel_item_index = 0;
+       if (first_page())
+           return DS_DISPLAY;
+       return DS_POS;
+    case '$':
+       sel_item_index = 0;
+       if (last_page())
+           return DS_DISPLAY;
+       return DS_POS;
+    case Ctl('l'):
+       return DS_DISPLAY;
+    case Ctl('f'):
+       erase_eol();            /* erase the prompt */
+#ifdef MAILCALL
+       setmail(TRUE);          /* force a mail check */
+#endif
+       return DS_ASK;
+    case '#':
+       {
+       register SUBJECT *sp;
+       for (sp = first_subject; sp; sp = sp->next)
+           sp->flags &= ~SF_VISIT;
+       selected_count = 0;
+       sp = (SUBJECT*)sel_items[sel_item_index].ptr;
+       switch (sel_mode) {
+       case SM_THREAD:
+           deselect_thread(sp->thread);
+           select_thread(sp->thread, 0);
+           break;
+       case SM_SUBJECT:
+           deselect_subject(sp);
+           select_subject(sp, 0);
+           break;
+       case SM_ARTICLE:
+           deselect_article((ARTICLE*)sp);
+           select_article((ARTICLE*)sp, 0);
+           break;
+       }
+       return DS_QUIT;
+       }
+    case '\r':  case '\n':
+       if (!selected_count) {
+           if (sel_rereading || sel_items[sel_item_index].sel != 2) {
+               register SUBJECT *sp = (SUBJECT*)sel_items[sel_item_index].ptr;
+               switch (sel_mode) {
+               case SM_THREAD:
+                   select_thread(sp->thread, 0);
+                   break;
+               case SM_SUBJECT:
+                   select_subject(sp, 0);
+                   break;
+               case SM_ARTICLE:
+                   select_article((ARTICLE*)sp, 0);
+                   break;
+               }
+           }
+       }
+       return DS_QUIT;
+    case 'Z':  case '\t':
+       return DS_QUIT;
+    case 'q':  case 'Q':
+       return DS_QUIT;
+    case Ctl('Q'):  case '\033':  case '+':
+       sel_ret = '+';
+       return DS_QUIT;
+    case 'N':  case 'P':
+       return DS_QUIT;
+    case 'L':
+       if (!*++display_mode)
+           display_mode = select_order;
+       return DS_DISPLAY;
+    case 'Y':
+       if (!dmcount) {
+           sprintf(buf,"No marked articles to yank back.");
+           return DS_STATUS;
+       }
+       yankback();
+       sel_line++;
+       if (!sel_rereading)
+           sel_cleanup();
+       disp_status_line = TRUE;
+       count_subjects(CS_NORM);
+       sel_page_sp = Nullsubj;
+       sel_page_app = Null(ARTICLE**);
+       init_pages();
+       return DS_DISPLAY;
+    case 'U':
+       sel_cleanup();
+       sel_rereading = !sel_rereading;
+       sel_page_sp = Nullsubj;
+       sel_page_app = Null(ARTICLE**);
+       if (!cache_range(sel_rereading? absfirst : firstart, lastart))
+           sel_rereading = !sel_rereading;
+       empty_ok = TRUE;
+       return DS_RESTART;
+    case '=':
+       if (!sel_rereading)
+           sel_cleanup();
+       if (sel_mode == SM_ARTICLE) {
+           set_selector(sel_threadmode, sel_threadsort);
+           sel_page_sp = sel_page_app[0]->subj;
+       } else {
+           set_selector(SM_ARTICLE, sel_artsort);
+           sel_page_app = 0;
+       }
+       count_subjects(CS_NORM);
+       sel_item_index = 0;
+       init_pages();
+       return DS_DISPLAY;
+    case 'S':
+       if (!sel_rereading)
+           sel_cleanup();
+       erase_eol();            /* erase the prompt */
+    reask_output:
+       in_char("Selector mode:  Threads, Subjects, Articles? [tsa] ", 'o');
+       setdef(buf,"t");
+#ifdef VERIFY
+       printcmd();
+#endif
+       if (*buf == 'h') {
+#ifdef VERBOSE
+           IF(verbose)
+               fputs("\n\
+Type t or SP to display/select thread groups (threads the group, if needed).\n\
+Type s to display/select subject groups.\n\
+Type a to display/select individual articles.\n\
+Type q to leave things as they are.\n\n\
+",stdout) FLUSH;
+           ELSE
+#endif
+#ifdef TERSE
+               fputs("\n\
+t or SP selects thread groups (threads the group too).\n\
+s selects subject groups.\n\
+a selects individual articles.\n\
+q does nothing.\n\n\
+",stdout) FLUSH;
+#endif
+           clean_screen = FALSE;
+           goto reask_output;
+       } else if (*buf == 'q') {
+           if (can_home) {
+               carriage_return();
+               erase_eol();
+           }
+           return DS_ASK;
+       }
+       set_sel_mode(*buf);
+       count_subjects(CS_NORM);
+       init_pages();
+       return DS_DISPLAY;
+    case 'O':
+       if (!sel_rereading)
+           sel_cleanup();
+       erase_eol();            /* erase the prompt */
+    reask_sort:
+       if (sel_mode == SM_ARTICLE)
+           in_char("Order by Date, Subject, Author, subject-date Groups? [dsagDSAG] ",
+                   'q');
+       else
+           in_char("Order by Date, Subject, or Count? [dscDSC] ", 'q');
+       setdef(buf,"d");
+#ifdef VERIFY
+       printcmd();
+#endif
+       if (*buf == 'h') {
+#ifdef VERBOSE
+           IF(verbose) {
+               fputs("\n\
+Type d or SP to order the displayed items by date.\n\
+Type s to order the items by subject.\n\
+",stdout) FLUSH;
+               if (sel_mode == SM_ARTICLE)
+                   fputs("\
+Type a to order the items by author.\n\
+Type g to order the items in subject-groups by date.\n\
+",stdout) FLUSH;
+               else
+                   fputs("\
+Type c to order the items by article-count.\n\
+",stdout) FLUSH;
+               fputs("\
+Typing your selection in upper case it will reverse the selected order.\n\
+Type q to leave things as they are.\n\n\
+",stdout) FLUSH;
+           }
+           ELSE
+#endif
+#ifdef TERSE
+           {
+               fputs("\n\
+d or SP sorts by date.\n\
+s sorts by subject.\n\
+",stdout) FLUSH;
+               if (sel_mode == SM_ARTICLE)
+                   fputs("\
+a sorts by author.\n\
+g sorts in subject-groups by date.\n\
+",stdout) FLUSH;
+               else
+                   fputs("\
+c sorts by article-count.\n\
+",stdout) FLUSH;
+               fputs("\
+Upper case reverses the sort.\n\
+q does nothing.\n\n\
+",stdout) FLUSH;
+           }
+#endif
+           clean_screen = FALSE;
+           goto reask_sort;
+       } else if (*buf == 'q') {
+           if (can_home) {
+               carriage_return();
+               erase_eol();
+           }
+           return DS_ASK;
+       }
+       set_sel_sort(*buf);
+       count_subjects(CS_NORM);
+#if 0
+       sel_last_sp = (SUBJECT*)sel_items[sel_item_index].ptr;
+       sel_last_ap = sel_page_app[sel_item_index];
+#else
+       sel_page_sp = Nullsubj;
+       sel_page_app = Null(ARTICLE**);
+#endif
+       init_pages();
+       return DS_DISPLAY;
+    case 'R':
+       if (!sel_rereading)
+           sel_cleanup();
+       sel_direction *= -1;
+       count_subjects(CS_NORM);
+#if 0
+       sel_last_sp = (SUBJECT*)sel_items[sel_item_index].ptr;
+       sel_last_ap = sel_page_app[sel_item_index];
+#else
+       sel_page_sp = Nullsubj;
+       sel_page_app = Null(ARTICLE**);
+#endif
+       init_pages();
+       return DS_DISPLAY;
+    case 'E':
+       if (!sel_rereading)
+           sel_cleanup();
+       sel_exclusive = !sel_exclusive;
+       count_subjects(CS_NORM);
+       sel_page_sp = Nullsubj;
+       sel_page_app = Null(ARTICLE**);
+       init_pages();
+       empty_ok = TRUE;
+       return DS_DISPLAY;
+    case 'X':  case 'D':  case 'J':
+       if (!sel_rereading) {
+           if (sel_mode == SM_ARTICLE) {
+               register ARTICLE *ap, **app, **limit;
+               limit = artptr_list + article_count;
+               if (ch == 'D')
+                   app = sel_page_app;
+               else
+                   app = artptr_list;
+               for (;;) {
+                   ap = *app;
+                   if ((!(ap->flags & AF_SEL) ^ (ch == 'J'))
+                    || (ap->flags & AF_DEL))
+                       if (!sel_exclusive || (ap->flags & AF_INCLUDED))
+                           set_read(ap);
+                   app++;
+                   if (app >= limit || (ch == 'D' && app == sel_next_app))
+                       break;
+               }
+           } else {
+               register SUBJECT *sp;
+               if (ch == 'D')
+                   sp = sel_page_sp;
+               else
+                   sp = first_subject;
+               for (;;) {
+                   if (((!(sp->flags & SF_SEL) ^ (ch == 'J')) && sp->misc)
+                    || (sp->flags & SF_DEL)) {
+                       if (!sel_exclusive || (sp->flags & SF_INCLUDED))
+                           kill_subject(sp, ch=='J'? KF_ALL : KF_UNSELECTED);
+                   }
+                   sp = sp->next;
+                   if (!sp || (ch == 'D' && sp == sel_next_sp))
+                       break;
+               }
+           }
+           count_subjects(CS_UNSELECT);
+           if (article_count
+            && (ch == 'J' || (ch == 'D' && !selected_count))) {
+               if (ch == 'D') {
+                   sel_page_sp = sel_next_sp;
+                   sel_page_app = sel_next_app;
+               }
+               init_pages();
+               sel_item_index = 0;
+               return DS_DISPLAY;
+           }
+           if (artptr_list && article_count)
+               sort_articles();
+           return DS_QUIT;
+       } else if (ch == 'J') {
+           register SUBJECT *sp;
+           for (sp = first_subject; sp; sp = sp->next)
+               deselect_subject(sp);
+           selected_subj_cnt = selected_count = 0;
+           return DS_DISPLAY;
+       }
+       sprintf(buf,"That command does not work in the set-unread selector.");
+       return DS_STATUS;
+    case 'T':
+       if (!ThreadedGroup) {
+           sprintf(buf,"Group is not threaded.");
+           return DS_STATUS;
+       }
+       /* FALL THROUGH */
+    case 'A':
+       erase_eol();            /* erase the prompt */
+       if (sel_mode == SM_ARTICLE)
+           artp = (ARTICLE*)sel_items[sel_item_index].ptr;
+       else
+           artp = ((SUBJECT*)sel_items[sel_item_index].ptr)->articles;
+       art = article_num(artp);
+       /* This call executes the action too */
+       switch (ask_memorize(ch)) {
+       case 'j':  case ',':
+           count_subjects(sel_rereading ? CS_NORM : CS_UNSELECT);
+           init_pages();
+           sprintf(buf,"Kill memorized.");
+           disp_status_line = TRUE;
+           return DS_DISPLAY;
+       case '.':
+           sprintf(buf,"Selection memorized.");
+           disp_status_line = TRUE;
+           return DS_DISPLAY;
+       case '+':
+           sprintf(buf,"Selection memorized.");
+           disp_status_line = TRUE;
+           return DS_UPDATE;
+       case 'c':  case 'C':
+           sprintf(buf,"Auto-commands cleared.");
+           disp_status_line = TRUE;
+           return DS_DISPLAY;
+       case 'q':
+           return DS_DISPLAY;
+       case 'Q':
+           break;
+       }
+       if (can_home) {
+           carriage_return();
+           erase_eol();
+       }
+       return DS_ASK;
+    case Ctl('k'):
+       edit_kfile();
+       return DS_DISPLAY;
+    case ':':  case '/':  case '&':  case '!':
+       erase_eol();            /* erase the prompt */
+       if (!finish_command(TRUE)) {    /* get rest of command */
+           if (clean_screen)
+               return DS_ASK;
+           goto extend_done;
+       }
+       if (ch == '&' || ch == '!') {
+           one_command = TRUE;
+           perform(buf, FALSE);
+           one_command = FALSE;
+           putchar('\n') FLUSH;
+           clean_screen = FALSE;
+       } else {
+           int sel_art_save = selected_count;
+
+           if (ch == ':') {
+               clean_screen = (use_selected() == 2) && clean_screen;
+               if (!sel_rereading) {
+                   register SUBJECT *sp;
+                   for (sp = first_subject; sp; sp = sp->next) {
+                       if (sp->flags & SF_DEL) {
+                           sp->flags = 0;
+                           if (sel_mode == SM_THREAD)
+                               kill_thread(sp->thread, KF_UNSELECTED);
+                           else
+                               kill_subject(sp, KF_UNSELECTED);
+                       }
+                   }
+               }
+           } else {
+               /* Force the search to begin at absfirst or firstart,
+               ** depending upon whether they specified the 'r' option.
+               */
+               art = lastart+1;
+               page_line = 1;
+               switch (art_search(buf, sizeof buf, FALSE)) {
+               case SRCH_ERROR:
+               case SRCH_ABORT:
+               case SRCH_INTR:
+                   fputs("\nInterrupted\n", stdout) FLUSH;
+                   break;
+               case SRCH_DONE:
+               case SRCH_SUBJDONE:
+                   fputs("Done\n", stdout) FLUSH;
+                   break;
+               case SRCH_NOTFOUND:
+                   fputs("\nNot found.\n", stdout) FLUSH;
+                   break;
+               case SRCH_FOUND:
+                   break;
+               }
+               clean_screen = FALSE;
+           }
+           /* Recount, in case something has changed. */
+           count_subjects(sel_rereading ? CS_NORM : CS_UNSELECT);
+           init_pages();
+           sel_item_index = 0;
+
+           sel_art_save -= selected_count;
+           if (sel_art_save) {
+               putchar('\n');
+               if (sel_art_save < 0) {
+                   fputs("S", stdout);
+                   sel_art_save *= -1;
+               } else
+                   fputs("Des", stdout);
+               printf("elected %d article%s.",
+                       sel_art_save, sel_art_save == 1 ? nullstr : "s");
+               clean_screen = FALSE;
+           }
+           if (!clean_screen)
+               putchar('\n') FLUSH;
+       }/* if !& else :/ */
+
+       if (clean_screen) {
+           carriage_return();
+           up_line();
+           erase_eol();
+           return DS_ASK;
+       }
+      extend_done:
+       if ((ch = pause_getcmd())) {
+         got_cmd:
+           if (ch > 0) {
+               /* try to optimize the screen update for some commands. */
+               if (!index(sel_chars, ch)
+                && (index("<+>^$!?&:/hDEJLNOPqQRSUXYZ\n\r\t\033", ch)
+                 || ch == Ctl('k'))) {
+                   buf[0] = sel_ret = ch;
+                   buf[1] = FINISHCMD;
+                   goto do_command;
+               }
+               pushchar(ch | 0200);
+           }
+       }
+       return DS_DISPLAY;
+    case 'c':
+       erase_eol();            /* erase the prompt */
+       if ((ch = ask_catchup()) == 'y' || ch == 'u') {
+           count_subjects(CS_UNSELECT);
+           if (ch != 'u' && article_count) {
+               sel_page_sp = Nullsubj;
+               sel_page_app = Null(ARTICLE**);
+               init_pages();
+               return DS_DISPLAY;
+           }
+           sel_ret = 'Z';
+           return DS_QUIT;
+       }
+       if (ch != 'N')
+           return DS_DISPLAY;
+       if (can_home) {
+           carriage_return();
+           erase_eol();
+       }
+       return DS_ASK;
+    case 'h':  case '?':
+       putchar('\n');
+       if ((ch = help_select()) || (ch = pause_getcmd()))
+           goto got_cmd;
+        return DS_DISPLAY;
+    default:
+       sprintf(buf,"Type ? for help.");
+       settle_down();
+       if (clean_screen)
+           return DS_STATUS;
+       printf("\n%s\n",buf);
+       goto extend_done;
+    }
+}
+
+static void
+empty_complaint()
+{
+    clear_on_stop = FALSE;
+    putchar('\n');
+    if (sel_rereading) {
+#ifdef VERBOSE
+       IF (verbose)
+           fputs("\nNo articles to set unread.\n", stdout);
+       ELSE
+#endif
+#ifdef TERSE
+           fputs("\nNo articles.\n", stdout) FLUSH;
+#endif
+       sel_rereading = 0;
+       sel_mask = AF_SEL;
+    } else {
+#ifdef VERBOSE
+       IF (verbose)
+           fputs("\nNo unread articles to select.", stdout);
+       ELSE
+#endif
+#ifdef TERSE
+           fputs("\nNo unread articles.", stdout);
+#endif
+       putchar('\n');  /* let "them" FLUSH */
+    }
+    selected_only = FALSE;
+    art = curr_art;
+    artp = curr_artp;
+}
diff --git a/usr/src/contrib/news/trn3/rthread.c b/usr/src/contrib/news/trn3/rthread.c
new file mode 100644 (file)
index 0000000..b5b07b5
--- /dev/null
@@ -0,0 +1,1524 @@
+/* $Id: rthread.c,v 3.0 1992/12/14 00:14:13 davison Trn $
+*/
+
+#include "EXTERN.h"
+#include "common.h"
+#include "intrp.h"
+#include "trn.h"
+#include "cache.h"
+#include "bits.h"
+#include "ng.h"
+#include "rcln.h"
+#include "search.h"
+#include "artstate.h"
+#include "rcstuff.h"
+#include "ngdata.h"
+#include "kfile.h"
+#include "head.h"
+#include "util.h"
+#include "hash.h"
+#include "nntp.h"
+#include "rt-mt.h"
+#include "rt-ov.h"
+#include "rt-page.h"
+#include "rt-process.h"
+#include "rt-select.h"
+#include "rt-wumpus.h"
+#include "INTERN.h"
+#include "rthread.h"
+
+HASHTABLE *msgid_hash = 0;
+
+bool try_ov = FALSE;
+bool try_mt = FALSE;
+
+void
+thread_init()
+{
+#ifdef USE_OV
+    try_ov = ov_init();
+#endif
+#ifdef USE_MT
+    try_mt = mt_init();
+#endif
+}
+
+/* Generate the thread data we need for this group.  We must call
+** thread_close() before calling this again.
+*/
+void
+thread_open()
+{
+    if (!msgid_hash)
+       msgid_hash = hashcreate(201, msgid_cmp); /*TODO: pick a better size */
+    if (ThreadedGroup) {
+       /* Parse input and use msgid_hash for quick article lookups. */
+       /* If cached but not threaded articles exist, set up to thread them. */
+       if (first_subject) {
+           first_cached = firstart;
+           last_cached = firstart - 1;
+           parsed_art = 0;
+       }
+    }
+
+    if (sel_mode == SM_ARTICLE)
+       set_selector(sel_mode, sel_artsort);
+    else
+       set_selector(sel_threadmode, sel_threadsort);
+
+#ifdef USE_MT
+    if (try_mt && !first_subject)
+       if (!mt_data())
+           return;
+#endif
+#ifdef USE_OV
+    if (try_ov && first_cached > last_cached)
+       if (thread_always)
+           (void) ov_data(absfirst, lastart, FALSE);
+       else if (firstart > lastart) {
+           /* If no unread articles, see if ov. exists as quick as possible */
+           (void) ov_data(absfirst, absfirst, FALSE);
+           first_cached = last_cached+1;
+       } else
+           (void) ov_data(firstart, lastart, FALSE);
+#endif
+#ifdef USE_NNTP
+    if (!ov_opened)
+       setmissingbits();
+#endif
+
+    if (last_cached > lastart) {
+       toread[ng] += (ART_UNREAD)(last_cached-lastart);
+       /* ensure getngsize() knows the new maximum */
+       ngmax[ng] = lastart = last_cached;
+    }
+    thread_grow();     /* thread any new articles not yet in the database */
+    added_articles = 0;
+    sel_page_sp = 0;
+    sel_page_app = 0;
+}
+
+/* Update the group's thread info.
+*/
+void
+thread_grow()
+{
+    added_articles = lastart - last_cached;
+    if (added_articles > 0 && thread_always)
+       cache_range(last_cached + 1, lastart);
+    count_subjects(CS_NORM);
+    if (artptr_list)
+       sort_articles();
+    else
+       sort_subjects();
+}
+
+static void
+kill_tmp_arts(data, extra)
+HASHDATUM *data;
+int extra;
+{
+    register ARTICLE *ap = (ARTICLE*)data->dat_ptr;
+
+    if (ap)
+       free((char*)ap);
+}
+
+void
+thread_close()
+{
+    curr_artp = artp = Nullart;
+    init_tree();                       /* free any tree lines */
+
+    if (msgid_hash) {
+       hashwalk(msgid_hash, kill_tmp_arts, 0);
+       hashdestroy(msgid_hash);
+       msgid_hash = 0;
+    }
+    sel_page_sp = 0;
+    sel_page_app = 0;
+    sel_last_ap = 0;
+    sel_last_sp = 0;
+    selected_only = FALSE;
+#ifdef USE_OV
+    ov_close();
+#endif
+}
+
+ARTICLE *
+find_article(artnum)
+ART_NUM artnum;
+{
+    if (artp && (artp->flags & AF_TMPMEM) == AF_TMPMEM && !artnum)
+       return artp;
+    if (artnum < absfirst || artnum > lastart)
+       return (artp = Nullart);
+    return (artp = article_ptr(artnum));
+}
+
+void
+top_article()
+{
+    art = lastart+1;
+    artp = Nullart;
+    inc_art(selected_only, FALSE);
+}
+
+ARTICLE *
+first_art(sp)
+register SUBJECT *sp;
+{
+    register ARTICLE *ap = (ThreadedGroup? sp->thread : sp->articles);
+    if (ap && (ap->flags & AF_MISSING))
+       ap = next_art(ap);
+    return ap;
+}
+
+ARTICLE *
+last_art(sp)
+register SUBJECT *sp;
+{
+    register ARTICLE *ap;
+
+    if (!ThreadedGroup) {
+       ap = sp->articles;
+       while (ap->subj_next)
+           ap = ap->subj_next;
+       return ap;
+    }
+
+    ap = sp->thread;
+    if (ap) {
+       for (;;) {
+           if (ap->sibling)
+               ap = ap->sibling;
+           else if (ap->child1)
+               ap = ap->child1;
+           else
+               break;
+       }
+       if (ap->flags & AF_MISSING)
+           ap = prev_art(ap);
+    }
+    return ap;
+}
+
+/* Bump art/artp to the next article, wrapping from thread to thread.
+** If sel_flag is TRUE, only stops at selected articles.
+** If rereading is FALSE, only stops at unread articles.
+*/
+void
+inc_art(sel_flag, rereading)
+bool_int sel_flag, rereading;
+{
+    register ARTICLE *ap = artp;
+    int subj_mask = (sel_mode == SM_THREAD? (SF_THREAD|SF_VISIT) : SF_VISIT);
+
+    /* Use the explicit article-order if it exists */
+    if (artptr_list) {
+       ARTICLE **limit = artptr_list + article_count;
+       if (!ap)
+           artptr = artptr_list-1;
+       else if (!artptr || *artptr != ap) {
+           for (artptr = artptr_list; artptr < limit; artptr++) {
+               if (*artptr == ap)
+                   break;
+           }
+       }
+       do {
+           if (++artptr >= limit)
+               break;
+           ap = *artptr;
+       } while ((!rereading && (ap->flags & AF_READ))
+             || (sel_flag && !(ap->flags & AF_SEL)));
+       if (artptr < limit) {
+           artp = *artptr;
+           art = article_num(artp);
+       } else {
+           artp = Nullart;
+           art = lastart+1;
+           artptr = artptr_list;
+       }
+       return;
+    }
+
+    /* Use subject- or thread-order when possible */
+    if (ThreadedGroup || srchahead) {
+       register SUBJECT *sp;
+       bool try_this_group_again = !rereading;
+       if (ap)
+           sp = ap->subj;
+       else
+           for (sp = first_subject; sp && (sp->flags&subj_mask) != subj_mask;)
+               sp = sp->next;
+       if (!sp)
+           goto num_inc;
+       do {
+           if (ap)
+               ap = next_art(ap);
+           else
+               ap = first_art(sp);
+           while (!ap) {
+               if (try_this_group_again)
+                   try_this_group_again = FALSE;
+               else {
+                   while ((sp = sp->next) != Nullsubj
+                       && (sp->flags & subj_mask) != subj_mask)
+                       ;
+                   if (!sp)
+                       break;
+               }
+               ap = first_art(sp);
+           }
+       } while (ap && ((!rereading && (ap->flags & AF_READ))
+                    || (sel_flag && !(ap->flags & AF_SEL))));
+       if ((artp = ap) != Nullart)
+           art = article_num(ap);
+       else
+           art = lastart+1;
+       return;
+    }
+
+    /* Otherwise, just increment through the art numbers */
+  num_inc:
+    if (!ap) {
+       art = firstart-1;
+       ap = article_ptr(art);
+    }
+    do {
+       if (++art > lastart) {
+           ap = Nullart;
+           break;
+       }
+       ap++;
+    } while ((!rereading && (ap->flags & AF_READ))
+         || (sel_flag && !(ap->flags & AF_SEL))
+         || (ap->flags & AF_MISSING));
+    artp = ap;
+}
+
+/* Bump art/artp to the previous article, wrapping from thread to thread.
+** If sel_flag is TRUE, only stops at selected articles.
+** If rereading is FALSE, only stops at unread articles.
+*/
+void
+dec_art(sel_flag, rereading)
+bool_int sel_flag, rereading;
+{
+    register ARTICLE *ap = artp;
+    int subj_mask = (sel_mode == SM_THREAD? (SF_THREAD|SF_VISIT) : SF_VISIT);
+
+    /* Use the explicit article-order if it exists */
+    if (artptr_list) {
+       ARTICLE **limit = artptr_list + article_count;
+       if (!ap)
+           artptr = limit;
+       else if (!artptr || *artptr != ap) {
+           for (artptr = artptr_list; artptr < limit; artptr++) {
+               if (*artptr == ap)
+                   break;
+           }
+       }
+       do {
+           if (artptr == artptr_list)
+               break;
+           ap = *--artptr;
+       } while ((!rereading && (ap->flags & AF_READ))
+             || (sel_flag && !(ap->flags & AF_SEL)));
+       artp = *artptr;
+       art = article_num(artp);
+       return;
+    }
+
+    /* Use subject- or thread-order when possible */
+    if (ThreadedGroup || srchahead) {
+       register SUBJECT *sp;
+       if (ap)
+           sp = ap->subj;
+       else
+           for (sp = last_subject; sp && (sp->flags&subj_mask) != subj_mask;)
+               sp = sp->prev;
+       if (!sp)
+           goto num_dec;
+       do {
+           if (ap)
+               ap = prev_art(ap);
+           else
+               ap = last_art(sp);
+           while (!ap) {
+               while ((sp = sp->prev) != Nullsubj
+                   && (sp->flags & subj_mask) != subj_mask)
+                   ;
+               if (!sp)
+                   break;
+               ap = last_art(sp);
+           }
+       } while (ap && ((!rereading && (ap->flags & AF_READ))
+                    || (sel_flag && !(ap->flags & AF_SEL))));
+       if ((artp = ap) != Nullart)
+           art = article_num(ap);
+       else
+           art = absfirst-1;
+       return;
+    }
+
+    /* Otherwise, just decrement through the art numbers */
+  num_dec:
+    ap = article_ptr(art);
+    do {
+       if (--art < absfirst) {
+           ap = Nullart;
+           break;
+       }
+       ap--;
+    } while ((!rereading && (ap->flags & AF_READ))
+         || (sel_flag && !(ap->flags & AF_SEL))
+         || (ap->flags & AF_MISSING));
+    artp = ap;
+}
+
+/* Bump the param to the next article in depth-first order.
+*/
+ARTICLE *
+bump_art(ap)
+register ARTICLE *ap;
+{
+    if (ap->child1)
+       return ap->child1;
+    while (!ap->sibling) {
+       if (!(ap = ap->parent))
+           return Nullart;
+    }
+    return ap->sibling;
+}
+
+/* Bump the param to the next REAL article.  Uses subject order in a
+** non-threaded group; honors the breadth_first flag in a threaded one.
+*/
+ARTICLE *
+next_art(ap)
+register ARTICLE *ap;
+{
+try_again:
+    if (!ThreadedGroup) {
+       ap = ap->subj_next;
+       goto done;
+    }
+    if (breadth_first) {
+       if (ap->sibling) {
+           ap = ap->sibling;
+           goto done;
+       }
+       if (ap->parent)
+           ap = ap->parent->child1;
+       else
+           ap = ap->subj->thread;
+    }
+    do {
+       if (ap->child1) {
+           ap = ap->child1;
+           goto done;
+       }
+       while (!ap->sibling) {
+           if (!(ap = ap->parent))
+               return Nullart;
+       }
+       ap = ap->sibling;
+    } while (breadth_first);
+done:
+    if (ap && (ap->flags & AF_MISSING))
+       goto try_again;
+    return ap;
+}
+
+/* Bump the param to the previous REAL article.  Uses subject order in a
+** non-threaded group.
+*/
+ARTICLE *
+prev_art(ap)
+register ARTICLE *ap;
+{
+    register ARTICLE *initial_ap;
+
+try_again:
+    initial_ap = ap;
+    if (!ThreadedGroup) {
+       if ((ap = ap->subj->articles) == initial_ap)
+           ap = Nullart;
+       else
+           while (ap->subj_next != initial_ap)
+               ap = ap->subj_next;
+       goto done;
+    }
+    ap = (ap->parent ? ap->parent->child1 : ap->subj->thread);
+    if (ap == initial_ap) {
+       ap = ap->parent;
+       goto done;
+    }
+    while (ap->sibling != initial_ap)
+       ap = ap->sibling;
+    while (ap->child1) {
+       ap = ap->child1;
+       while (ap->sibling)
+           ap = ap->sibling;
+    }
+done:
+    if (ap && (ap->flags & AF_MISSING))
+       goto try_again;
+    return ap;
+}
+
+/* Find the next art/artp with the same subject as this one.  Returns
+** FALSE if no such article exists.
+*/
+bool
+next_art_with_subj()
+{
+    register ARTICLE *ap = artp;
+    register SUBJECT *sp;
+    bool try_this_subj_again = TRUE;
+
+    if (!ap)
+       return FALSE;
+    sp = ap->subj;
+
+    do {
+       ap = ap->subj_next;
+       if (!ap) {
+           if (try_this_subj_again) {
+               ap = sp->articles;
+               try_this_subj_again = FALSE;
+           } else {
+               if (!art)
+                   art = firstart;
+               return FALSE;
+           }
+       }
+    } while ((ap->flags & (AF_READ|AF_MISSING))
+         || (selected_only && !(ap->flags & AF_SEL)));
+    artp = ap;
+    art = article_num(ap);
+#ifdef ARTSEARCH
+    srchahead = -1;
+#endif
+    return TRUE;
+}
+
+/* Find the previous art/artp with the same subject as this one.  Returns
+** FALSE if no such article exists.
+*/
+bool
+prev_art_with_subj()
+{
+    register ARTICLE *ap = artp, *ap2;
+    register SUBJECT *sp;
+    bool try_this_subj_again = TRUE;
+
+    if (!ap)
+       return FALSE;
+    sp = ap->subj;
+
+    do {
+       ap2 = ap->subj->articles;
+       if (ap2 == ap)
+           ap = Nullart;
+       else {
+           while (ap2 && ap2->subj_next != ap)
+               ap2 = ap2->subj_next;
+           ap = ap2;
+       }
+       if (!ap) {
+           if (try_this_subj_again) {
+               ap = sp->articles;
+               while (ap->subj_next)
+                   ap = ap->subj_next;
+               try_this_subj_again = FALSE;
+           } else {
+               if (!art)
+                   art = lastart;
+               return FALSE;
+           }
+       }
+    } while ((ap->flags & (AF_READ|AF_MISSING))
+         || (selected_only && !(ap->flags & AF_SEL)));
+    artp = ap;
+    art = article_num(ap);
+    return TRUE;
+}
+
+/* Select a single article.
+*/
+void
+select_article(ap, sel_flags)
+register ARTICLE *ap;
+int sel_flags;
+{
+    int desired_flags = (sel_rereading? AF_READ : 0);
+#ifdef VERBOSE
+    bool echo;
+
+    if (sel_flags & AF_ECHO) {
+       echo = TRUE;
+       sel_flags &= ~AF_ECHO;
+    } else
+       echo = FALSE;
+#else
+    sel_flags &= ~AF_ECHO;
+#endif
+    if (sel_flags & (AF_AUTOSELECT|AF_AUTOSELECTALL))
+       save_ids = TRUE;
+    if ((ap->flags & (AF_MISSING|AF_READ)) == desired_flags) {
+       if (!(ap->flags & sel_mask)) {
+           selected_count++;
+#ifdef VERBOSE
+           if (echo) {
+               IF(verbose)
+                   fputs("\tSelected",stdout);
+           }
+#endif
+       }
+       ap->flags = (ap->flags & ~AF_DEL) | sel_mask | sel_flags;
+    } else
+       ap->flags |= sel_flags;
+    if (!(ap->subj->flags & sel_mask))
+       selected_subj_cnt++;
+    ap->subj->flags = (ap->subj->flags&~SF_DEL)|sel_mask|sel_flags|SF_VISIT;
+    if (sel_mode == SM_THREAD) {
+       if ((ap = ap->subj->thread) != NULL)
+           ap->subj->flags |= SF_VISIT;
+    }
+    selected_only = (selected_only || selected_count != 0);
+}
+
+/* Select all the articles in a subject.
+*/
+void
+select_subject(subj, sel_flags)
+SUBJECT *subj;
+int sel_flags;
+{
+    register ARTICLE *ap;
+    int desired_flags = (sel_rereading? AF_READ : 0);
+    int old_count = selected_count;
+
+    if (sel_flags & (AF_AUTOSELECT|AF_AUTOSELECTALL)) {
+       save_ids = TRUE;
+       if (sel_flags & AF_AUTOSELECTALL)
+           subj->flags |= SF_AUTOSELECT;
+    }
+    for (ap = subj->articles; ap; ap = ap->subj_next) {
+       if ((ap->flags & (AF_MISSING|AF_READ|sel_mask)) == desired_flags) {
+           ap->flags |= sel_mask | sel_flags;
+           selected_count++;
+       } else
+           ap->flags |= sel_flags;
+    }
+    if (selected_count > old_count) {
+       if (!(subj->flags & sel_mask))
+           selected_subj_cnt++;
+       subj->flags = (subj->flags & ~SF_DEL)
+                   | sel_mask | SF_VISIT | SF_WASSELECTED;
+       if (sel_mode == SM_THREAD) {
+           if ((ap = subj->thread) != NULL)
+               ap->subj->flags |= SF_VISIT;
+       }
+       selected_only = TRUE;
+    } else
+       subj->flags |= SF_WASSELECTED;
+}
+
+/* Select all the articles in a thread.
+*/
+void
+select_thread(thread, sel_flags)
+register ARTICLE *thread;
+int sel_flags;
+{
+    register SUBJECT *sp;
+
+    sp = thread->subj;
+    do {
+       select_subject(sp, sel_flags);
+       sp = sp->thread_link;
+    } while (sp != thread->subj);
+}
+
+/* Select the subthread attached to this article.
+*/
+void
+select_subthread(ap, sel_flags)
+register ARTICLE *ap;
+int sel_flags;
+{
+    register ARTICLE *limit;
+    SUBJECT *subj;
+    int desired_flags = (sel_rereading? AF_READ : 0);
+    int old_count = selected_count;
+
+    if (!ap)
+       return;
+    subj = ap->subj;
+    for (limit = ap; limit; limit = limit->parent) {
+       if (limit->sibling) {
+           limit = limit->sibling;
+           break;
+       }
+    }
+
+    if (sel_flags & (AF_AUTOSELECT|AF_AUTOSELECTALL))
+       save_ids = TRUE;
+    for (; ap != limit; ap = bump_art(ap)) {
+       if ((ap->flags & (AF_MISSING|AF_READ|sel_mask)) == desired_flags) {
+           ap->flags |= sel_mask | sel_flags;
+           selected_count++;
+       } else
+           ap->flags |= sel_flags;
+    }
+    if (selected_count > old_count) {
+       if (!(subj->flags & sel_mask))
+           selected_subj_cnt++;
+       subj->flags = (subj->flags & ~SF_DEL) | sel_mask | SF_VISIT;
+       if (sel_mode == SM_THREAD) {
+           if ((ap = subj->thread) != NULL)
+               ap->subj->flags |= SF_VISIT;
+       }
+       selected_only = TRUE;
+    }
+}
+
+/* Deselect a single article.
+*/
+void
+deselect_article(ap)
+register ARTICLE *ap;
+{
+    if (ap->flags & sel_mask) {
+       ap->flags &= ~sel_mask;
+       if (!selected_count--)
+           selected_count = 0;
+#ifdef VERBOSE
+       if (mode != 't') {
+           IF(verbose)
+               fputs("\tDeselected",stdout);
+       }
+#endif
+    }
+    if (sel_rereading && sel_mode == SM_ARTICLE)
+       ap->flags |= AF_DEL;
+}
+
+/* Deselect all the articles in a subject.
+*/
+void
+deselect_subject(subj)
+SUBJECT *subj;
+{
+    register ARTICLE *ap;
+
+    for (ap = subj->articles; ap; ap = ap->subj_next) {
+       if (ap->flags & sel_mask) {
+           ap->flags &= ~sel_mask;
+           if (!selected_count--)
+               selected_count = 0;
+       }
+    }
+    if (subj->flags & sel_mask) {
+       subj->flags &= ~sel_mask;
+       selected_subj_cnt--;
+    }
+    subj->flags &= ~(SF_VISIT | SF_WASSELECTED);
+    if (sel_rereading)
+       subj->flags |= SF_DEL;
+    else
+       subj->flags &= ~SF_DEL;
+}
+
+/* Deselect all the articles in a thread.
+*/
+void
+deselect_thread(thread)
+register ARTICLE *thread;
+{
+    register SUBJECT *sp;
+
+    sp = thread->subj;
+    do {
+       deselect_subject(sp);
+       sp = sp->thread_link;
+    } while (sp != thread->subj);
+}
+
+/* Deselect everything.
+*/
+void
+deselect_all()
+{
+    register SUBJECT *sp;
+
+    for (sp = first_subject; sp; sp = sp->next)
+       deselect_subject(sp);
+    selected_count = selected_subj_cnt = 0;
+    sel_page_sp = 0;
+    sel_page_app = 0;
+    sel_last_ap = 0;
+    sel_last_sp = 0;
+    selected_only = FALSE;
+}
+
+/* Kill all unread articles attached to the given subject.
+*/
+void
+kill_subject(subj, kill_flags)
+SUBJECT *subj;
+int kill_flags;
+{
+    register ARTICLE *ap;
+    register int killmask = ((kill_flags&KF_ALL)? AF_READ:(AF_READ|sel_mask));
+
+    if (kill_flags & KF_KILLFILE) {
+       save_ids = TRUE;
+       kill_flags = AF_AUTOKILLALL;
+    } else
+       kill_flags = 0;
+    for (ap = subj->articles; ap; ap = ap->subj_next) {
+       if (!(ap->flags & killmask))
+           set_read(ap);
+       ap->flags |= kill_flags;
+    }
+    subj->flags &= ~(SF_VISIT | SF_WASSELECTED);
+}
+
+/* Kill all unread articles attached to the given thread.
+*/
+void
+kill_thread(thread, kill_flags)
+register ARTICLE *thread;
+int kill_flags;
+{
+    register SUBJECT *sp;
+
+    sp = thread->subj;
+    do {
+       kill_subject(sp, kill_flags);
+       sp = sp->thread_link;
+    } while (sp != thread->subj);
+}
+
+/* Kill the subthread attached to this article.
+*/
+void
+kill_subthread(ap, kill_flags)
+register ARTICLE *ap;
+int kill_flags;
+{
+    register ARTICLE *limit;
+
+    if (!ap)
+       return;
+    for (limit = ap; limit; limit = limit->parent) {
+       if (limit->sibling) {
+           limit = limit->sibling;
+           break;
+       }
+    }
+
+    if (kill_flags & KF_KILLFILE) {
+       save_ids = TRUE;
+       kill_flags = AF_AUTOKILL;
+    } else
+       kill_flags = 0;
+    for (; ap != limit; ap = bump_art(ap)) {
+       if (!(ap->flags & (AF_READ|AF_MISSING)))
+           set_read(ap);
+       ap->flags |= kill_flags;
+    }
+}
+
+/* Unkill all the articles attached to the given subject.
+*/
+void
+unkill_subject(subj)
+SUBJECT *subj;
+{
+    register ARTICLE *ap;
+
+    for (ap = subj->articles; ap; ap = ap->subj_next) {
+       if (sel_rereading) {
+           if ((ap->flags & (AF_DELSEL|AF_MISSING)) == AF_DELSEL) {
+               if (ap->flags & AF_READ)
+                   toread[ng]++;
+               ap->flags = (ap->flags & ~(AF_DELSEL|AF_READ)) | AF_SEL;
+           } else
+               ap->flags &= ~(AF_DEL|AF_DELSEL);
+       } else {
+           if ((ap->flags & (AF_READ|AF_MISSING)) == AF_READ)
+               onemore(ap);
+           if (selected_only && !(ap->flags & (AF_SEL|AF_READ))) {
+               ap->flags = (ap->flags & ~AF_DEL) | AF_SEL;
+               selected_count++;
+           }
+       }
+    }
+    if (!sel_rereading && selected_only && !(subj->flags & SF_SEL)) {
+       subj->flags |= SF_SEL | SF_VISIT | SF_WASSELECTED;
+       if (sel_mode == SM_THREAD) {
+           if ((ap = subj->thread) != NULL)
+               ap->subj->flags |= SF_VISIT;
+       }
+       selected_subj_cnt++;
+    }
+    subj->flags &= ~(SF_DEL|SF_DELSEL);
+}
+
+/* Unkill all the articles attached to the given thread.
+*/
+void
+unkill_thread(thread)
+register ARTICLE *thread;
+{
+    register SUBJECT *sp;
+
+    sp = thread->subj;
+    do {
+       unkill_subject(sp);
+       sp = sp->thread_link;
+    } while (sp != thread->subj);
+}
+
+/* Unkill the subthread attached to this article.
+*/
+void
+unkill_subthread(ap)
+register ARTICLE *ap;
+{
+    register ARTICLE *limit;
+    register SUBJECT *sp;
+
+    if (!ap)
+       return;
+    for (limit = ap; limit; limit = limit->parent) {
+       if (limit->sibling) {
+           limit = limit->sibling;
+           break;
+       }
+    }
+
+    sp = ap->subj;
+    for (; ap != limit; ap = bump_art(ap)) {
+       if ((ap->flags & (AF_READ|AF_MISSING)) == AF_READ)
+           onemore(ap);
+       if (selected_only && !(ap->flags & AF_SEL)) {
+           ap->flags |= AF_SEL;
+           selected_count++;
+       }
+    }
+    if (!(sp->flags & sel_mask))
+       selected_subj_cnt++;
+    sp->flags = (sp->flags & ~SF_DEL) | SF_SEL | SF_VISIT;
+    if (sel_mode == SM_THREAD) {
+       if ((ap = sp->thread) != NULL)
+           ap->subj->flags |= SF_VISIT;
+    }
+    selected_only = (selected_only || selected_count != 0);
+}
+
+/* Kill all unread articles attached to the given subject.
+*/
+void
+clear_subject(subj)
+SUBJECT *subj;
+{
+    register ARTICLE *ap;
+
+    for (ap = subj->articles; ap; ap = ap->subj_next) {
+       ap->flags &= ~AF_AUTOFLAGS;
+    }
+    subj->flags &= ~(SF_WASSELECTED | SF_AUTOSELECT);
+}
+
+/* Kill all unread articles attached to the given thread.
+*/
+void
+clear_thread(thread)
+register ARTICLE *thread;
+{
+    register SUBJECT *sp;
+
+    sp = thread->subj;
+    do {
+       clear_subject(sp);
+       sp = sp->thread_link;
+    } while (sp != thread->subj);
+}
+
+/* Kill the subthread attached to this article.
+*/
+void
+clear_subthread(ap)
+register ARTICLE *ap;
+{
+    register ARTICLE *limit;
+
+    if (!ap)
+       return;
+    for (limit = ap; limit; limit = limit->parent) {
+       if (limit->sibling) {
+           limit = limit->sibling;
+           break;
+       }
+    }
+
+    for (; ap != limit; ap = bump_art(ap)) {
+       ap->flags &= ~AF_AUTOFLAGS;
+    }
+}
+
+ARTICLE *
+subj_art(sp)
+SUBJECT *sp;
+{
+    register ARTICLE *ap = Nullart;
+    int art_mask = (selected_only? AF_SEL : 0);
+    bool TG_save = ThreadedGroup;
+
+    ThreadedGroup = (sel_mode == SM_THREAD);
+    ap = first_art(sp);
+    while (ap && (ap->flags & (art_mask|AF_READ)) != art_mask)
+       ap = next_art(ap);
+    if (!ap) {
+       reread = TRUE;
+       ap = first_art(sp);
+       if (art_mask) {
+           while (ap && !(ap->flags & AF_SEL))
+               ap = next_art(ap);
+           if (!ap)
+               ap = first_art(sp);
+       }
+    }
+    ThreadedGroup = TG_save;
+    return ap;
+}
+
+/* Find the next thread (first if art > lastart).  If articles are selected,
+** only choose from threads with selected articles.
+*/
+void
+next_subject()
+{
+    register SUBJECT *sp;
+    register ARTICLE *ap;
+
+    sp = ((ap = artp) ? ap->subj->next : first_subject);
+    for (; sp; sp = sp->next) {
+       if (sp->flags & SF_VISIT) {
+           if ((ap = subj_art(sp)) != Nullart) {
+               art = article_num(ap);
+               artp = ap;
+               return;
+           }
+           reread = FALSE;
+       }
+    }
+    artp = Nullart;
+    art = lastart+1;
+    forcelast = TRUE;
+}
+
+/* Find previous thread (or last if artp == NULL).  If articles are selected,
+** only choose from threads with selected articles.
+*/
+void
+prev_subject()
+{
+    register SUBJECT *sp;
+    register ARTICLE *ap;
+
+    sp = ((ap = artp) ? ap->subj->prev : last_subject);
+    for (; sp; sp = sp->prev) {
+       if (sp->flags & SF_VISIT) {
+           if ((ap = subj_art(sp)) != Nullart) {
+               art = article_num(ap);
+               artp = ap;
+               return;
+           }
+           reread = FALSE;
+       }
+    }
+    artp = Nullart;
+    art = lastart+1;
+    forcelast = TRUE;
+}
+
+/* Find artp's parent or oldest ancestor.  Returns FALSE if no such
+** article.  Sets art and artp otherwise.
+*/
+bool
+find_parent(keep_going)
+bool_int keep_going;
+{
+    register ARTICLE *ap = artp;
+
+    if (!ap->parent)
+       return FALSE;
+
+    do {
+       ap = ap->parent;
+    } while (keep_going && ap->parent);
+
+    if (((artp = ap)->flags & AF_TMPMEM) == AF_TMPMEM)
+       art = 0;
+    else
+       art = article_num(ap);
+    return TRUE;
+}
+
+/* Find artp's first child or youngest decendent.  Returns FALSE if no
+** such article.  Sets art and artp otherwise.
+*/
+bool
+find_leaf(keep_going)
+bool_int keep_going;
+{
+    register ARTICLE *ap = artp;
+
+    if (!ap->child1)
+       return FALSE;
+
+    do {
+       ap = ap->child1;
+    } while (keep_going && ap->child1);
+
+    if (((artp = ap)->flags & AF_TMPMEM) == AF_TMPMEM)
+       art = 0;
+    else
+       art = article_num(ap);
+    return TRUE;
+}
+
+static ARTICLE *first_sib(), *last_sib();
+
+/* Find the next "sibling" of artp, including cousins that are the
+** same distance down the thread as we are.  Returns FALSE if no such
+** article.  Sets art and artp otherwise.
+*/
+bool
+find_next_sib()
+{
+    ARTICLE *ta, *tb;
+    int ascent;
+
+    ascent = 0;
+    ta = artp;
+    for (;;) {
+       while (ta->sibling) {
+           ta = ta->sibling;
+           if (tb = first_sib(ta, ascent)) {
+               if (((artp = tb)->flags & AF_TMPMEM) == AF_TMPMEM)
+                   art = 0;
+               else
+                   art = article_num(tb);
+               return TRUE;
+           }
+       }
+       if (!(ta = ta->parent))
+           break;
+       ascent++;
+    }
+    return FALSE;
+}
+
+/* A recursive routine to find the first node at the proper depth.  This
+** article is at depth 0.
+*/
+static ARTICLE *
+first_sib(ta, depth)
+ARTICLE *ta;
+int depth;
+{
+    ARTICLE *tb;
+
+    if (!depth)
+       return ta;
+
+    for (;;) {
+       if (ta->child1 && (tb = first_sib(ta->child1, depth-1)))
+           return tb;
+
+       if (!ta->sibling)
+           return Nullart;
+
+       ta = ta->sibling;
+    }
+}
+
+/* Find the previous "sibling" of artp, including cousins that are
+** the same distance down the thread as we are.  Returns FALSE if no
+** such article.  Sets art and artp otherwise.
+*/
+bool
+find_prev_sib()
+{
+    ARTICLE *ta, *tb;
+    int ascent;
+
+    ascent = 0;
+    ta = artp;
+    for (;;) {
+       tb = ta;
+       if (ta->parent)
+           ta = ta->parent->child1;
+       else
+           ta = ta->subj->thread;
+       if (tb = last_sib(ta, ascent, tb)) {
+           if (((artp = tb)->flags & AF_TMPMEM) == AF_TMPMEM)
+               art = 0;
+           else
+               art = article_num(tb);
+           return TRUE;
+       }
+       if (!(ta = ta->parent))
+           break;
+       ascent++;
+    }
+    return FALSE;
+}
+
+/* A recursive routine to find the last node at the proper depth.  This
+** article is at depth 0.
+*/
+static ARTICLE *
+last_sib(ta, depth, limit)
+ARTICLE *ta;
+int depth;
+ARTICLE *limit;
+{
+    ARTICLE *tb, *tc;
+
+    if (ta == limit)
+       return Nullart;
+
+    if (ta->sibling) {
+       tc = ta->sibling;
+       if (tc != limit && (tb = last_sib(tc,depth,limit)))
+           return tb;
+    }
+    if (!depth)
+       return ta;
+    if (ta->child1)
+       return last_sib(ta->child1, depth-1, limit);
+    return Nullart;
+}
+
+/* Get each subject's article count; count total articles and selected
+** articles (use sel_rereading to determine whether to count read or
+** unread articles); deselect any subjects we find that are empty if
+** CS_UNSELECT or CS_UNSEL_STORE is specified.  If mode is CS_RESELECT
+** is specified, the selections from the last CS_UNSEL_STORE are
+** reselected.
+*/
+void
+count_subjects(mode)
+int mode;
+{
+    register int count, sel_count;
+    register ARTICLE *ap;
+    register SUBJECT *sp;
+    int desired_flags = (sel_rereading? AF_READ : 0);
+    time_t subjdate;
+
+    article_count = selected_count = selected_subj_cnt = 0;
+    if (last_cached >= lastart)
+       firstart = lastart+1;
+
+    for (sp = first_subject; sp; sp = sp->next)
+       sp->flags &= ~SF_VISIT;
+    for (sp = first_subject; sp; sp = sp->next) {
+       subjdate = 0;
+       count = sel_count = 0;
+       for (ap = sp->articles; ap; ap = ap->subj_next) {
+           if ((ap->flags & (AF_MISSING|AF_READ)) == desired_flags) {
+               count++;
+               if (ap->flags & sel_mask)
+                   sel_count++;
+               if (!subjdate)
+                   subjdate = ap->date;
+               if (article_num(ap) < firstart)
+                   firstart = article_num(ap);
+           }
+       }
+       if (mode == CS_UNSEL_STORE) {
+           if (sp->flags & SF_SEL)
+               sp->flags |= SF_OLDSEL;
+           else
+               sp->flags &= ~SF_OLDSEL;
+       } else if (mode == CS_RESELECT) {
+           if (sp->flags & SF_OLDSEL)
+               sp->flags |= SF_SEL;
+           else
+               sp->flags &= ~SF_SEL;
+       }
+       sp->misc = count;
+       if (subjdate)
+           sp->date = subjdate;
+       article_count += count;
+       if (sel_count) {
+           sp->flags = (sp->flags & ~(SF_SEL|SF_DEL)) | sel_mask;
+           selected_count += sel_count;
+           selected_subj_cnt++;
+       } else if (mode >= CS_UNSELECT)
+           sp->flags &= ~sel_mask;
+       else if (sp->flags & sel_mask) {
+           sp->flags &= ~SF_DEL;
+           selected_subj_cnt++;
+       }
+       if (count && (!selected_only || (sp->flags & sel_mask))) {
+           sp->flags |= SF_VISIT;
+           if (sel_mode == SM_THREAD) {
+               if ((ap = sp->thread) != NULL)
+                   ap->subj->flags |= SF_VISIT;
+           }
+       }
+    }
+    if (mode && !article_count && !selected_only) {
+       for (sp = first_subject; sp; sp = sp->next)
+           sp->flags |= SF_VISIT;
+    }
+}
+
+int
+subjorder_subject(spp1, spp2)
+register SUBJECT **spp1;
+register SUBJECT **spp2;
+{
+    return strCASEcmp((*spp1)->str+4, (*spp2)->str+4) * sel_direction;
+}
+
+int
+subjorder_date(spp1, spp2)
+register SUBJECT **spp1;
+register SUBJECT **spp2;
+{
+    return (int)((*spp1)->date - (*spp2)->date) * sel_direction;
+}
+
+int
+subjorder_count(spp1, spp2)
+register SUBJECT **spp1;
+register SUBJECT **spp2;
+{
+    int eq;
+    if ((eq = (int)((*spp1)->misc - (*spp2)->misc)) != 0)
+       return eq * sel_direction;
+    return (int)((*spp1)->date - (*spp2)->date) * sel_direction;
+}
+
+int
+threadorder_subject(spp1, spp2)
+SUBJECT **spp1;
+SUBJECT **spp2;
+{
+    register ARTICLE *t1 = (*spp1)->thread;
+    register ARTICLE *t2 = (*spp2)->thread;
+    if (t1 != t2 && t1 && t2)
+       return strCASEcmp(t1->subj->str+4, t2->subj->str+4) * sel_direction;
+    return (int)((*spp1)->date - (*spp2)->date) * sel_direction;
+}
+
+int
+threadorder_date(spp1, spp2)
+SUBJECT **spp1;
+SUBJECT **spp2;
+{
+    register ARTICLE *t1 = (*spp1)->thread;
+    register ARTICLE *t2 = (*spp2)->thread;
+    if (t1 != t2 && t1 && t2) {
+       register SUBJECT *sp1, *sp2;
+       int eq;
+       if (!(sp1 = t1->subj)->misc)
+           for (sp1=sp1->thread_link; sp1 != t1->subj; sp1=sp1->thread_link)
+               if (sp1->misc)
+                   break;
+       if (!(sp2 = t2->subj)->misc)
+           for (sp2=sp2->thread_link; sp2 != t2->subj; sp2=sp2->thread_link)
+               if (sp2->misc)
+                   break;
+       if ((eq = (int)(sp1->date - sp2->date) * sel_direction) != 0)
+           return eq;
+       return strCASEcmp(sp1->str+4, sp2->str+4) * sel_direction;
+    }
+    return (int)((*spp1)->date - (*spp2)->date) * sel_direction;
+}
+
+int
+threadorder_count(spp1, spp2)
+SUBJECT **spp1;
+SUBJECT **spp2;
+{
+    register int size1 = (*spp1)->misc;
+    register int size2 = (*spp2)->misc;
+    if ((*spp1)->thread != (*spp2)->thread) {
+       register SUBJECT *sp;
+       for (sp = (*spp1)->thread_link; sp != *spp1; sp = sp->thread_link)
+           size1 += sp->misc;
+       for (sp = (*spp2)->thread_link; sp != *spp2; sp = sp->thread_link)
+           size2 += sp->misc;
+    }
+    if (size1 != size2)
+       return (size1 - size2) * sel_direction;
+    return threadorder_date(spp1, spp2);
+}
+
+/* Sort the subjects according to the chosen order.
+*/
+void
+sort_subjects()
+{
+    register SUBJECT *sp;
+    register int i;
+    SUBJECT **lp, **subj_list;
+    int (*sort_procedure)();
+
+    /* If we don't have at least two subjects, we're done! */
+    if (!first_subject || !first_subject->next)
+       return;
+
+    switch (sel_sort) {
+    case SS_DATE:
+    case SS_AUTHOR:
+    case SS_GROUPS:
+       sort_procedure = (sel_mode == SM_THREAD?
+                         threadorder_date : subjorder_date);
+       break;
+    case SS_SUBJECT:
+       sort_procedure = (sel_mode == SM_THREAD?
+                         threadorder_subject : subjorder_subject);
+       break;
+    case SS_COUNT:
+       sort_procedure = (sel_mode == SM_THREAD?
+                         threadorder_count : subjorder_count);
+       break;
+    }
+
+    subj_list = (SUBJECT**)safemalloc(subject_count * sizeof (SUBJECT*));
+    for (lp = subj_list, sp = first_subject; sp; sp = sp->next)
+       *lp++ = sp;
+    assert(lp - subj_list == subject_count);
+
+    qsort(subj_list, subject_count, sizeof (SUBJECT*), sort_procedure);
+
+    first_subject = sp = subj_list[0];
+    sp->prev = Nullsubj;
+    for (i = subject_count, lp = subj_list; --i; lp++) {
+       lp[0]->next = lp[1];
+       lp[1]->prev = lp[0];
+       if (sel_mode == SM_THREAD) {
+           if (lp[0]->thread == lp[1]->thread)
+               lp[0]->thread_link = lp[1];
+           else {
+               lp[0]->thread_link = sp;
+               sp = lp[1];
+           }
+       }
+    }
+    last_subject = lp[0];
+    last_subject->next = Nullsubj;
+    if (sel_mode == SM_THREAD)
+       last_subject->thread_link = sp;
+    free((char*)subj_list);
+}
+
+int
+artorder_date(art1, art2)
+register ARTICLE **art1;
+register ARTICLE **art2;
+{
+    return (int)((*art1)->date - (*art2)->date) * sel_direction;
+}
+
+int
+artorder_subject(art1, art2)
+register ARTICLE **art1;
+register ARTICLE **art2;
+{
+    if ((*art1)->subj == (*art2)->subj)
+       return (int)((*art1)->date - (*art2)->date);
+    return strCASEcmp((*art1)->subj->str + 4, (*art2)->subj->str + 4)
+       * sel_direction;
+}
+
+int
+artorder_author(art1, art2)
+register ARTICLE **art1;
+register ARTICLE **art2;
+{
+    int eq;
+    if ((eq = strCASEcmp((*art1)->from, (*art2)->from)) != 0)
+       return eq * sel_direction;
+    return (int)((*art1)->date - (*art2)->date);
+}
+
+int
+artorder_groups(art1, art2)
+register ARTICLE **art1;
+register ARTICLE **art2;
+{
+    if ((*art1)->subj == (*art2)->subj)
+       return (int)((*art1)->date - (*art2)->date);
+    return (int)((*art1)->subj->date - (*art2)->subj->date) * sel_direction;
+}
+
+/* Sort the articles according to the chosen order.
+*/
+void
+sort_articles()
+{
+    int (*sort_procedure)();
+
+    build_artptrs();
+
+    /* If we don't have at least two articles, we're done! */
+    if (article_count <2)
+       return;
+
+    switch (sel_sort) {
+    case SS_DATE:
+    case SS_COUNT:
+       sort_procedure = artorder_date;
+       break;
+    case SS_SUBJECT:
+       sort_procedure = artorder_subject;
+       break;
+    case SS_AUTHOR:
+       sort_procedure = artorder_author;
+       break;
+    case SS_GROUPS:
+       sort_procedure = artorder_groups;
+       break;
+    }
+    if (sel_page_app)
+       sel_last_ap = *sel_page_app;
+    sel_page_app = 0;
+    qsort(artptr_list, article_count, sizeof (ARTICLE*), sort_procedure);
+}
+
+static long artptr_list_size = 0;
+
+static void
+build_artptrs()
+{
+    ARTICLE **app, *ap;
+    long count = article_count;
+    int desired_flags = (sel_rereading? AF_READ : 0);
+
+    if (!artptr_list || artptr_list_size != count) {
+       artptr_list = (ARTICLE**)saferealloc((char*)artptr_list,
+               (MEM_SIZE)count * sizeof (ARTICLE*));
+       artptr_list_size = count;
+    }
+    for (app = artptr_list, ap = article_list; count; ap++) {
+       if ((ap->flags & (AF_MISSING|AF_READ)) == desired_flags) {
+           *app++ = ap;
+           count--;
+       }
+    }
+}
diff --git a/usr/src/contrib/news/trn3/rthread.h b/usr/src/contrib/news/trn3/rthread.h
new file mode 100644 (file)
index 0000000..2a5e643
--- /dev/null
@@ -0,0 +1,82 @@
+/* $Id: rthread.h,v 3.0 1992/12/14 00:14:15 davison Trn $
+*/
+
+EXT ART_NUM article_count INIT(0);
+EXT int subject_count INIT(0);
+EXT bool output_chase_phrase;
+EXT char *references;
+EXT bool ov_opened INIT(FALSE);
+
+void thread_init _((void));
+void thread_open _((void));
+void thread_grow _((void));
+void thread_close _((void));
+
+void top_article _((void));
+ARTICLE *find_article _((ART_NUM));
+ARTICLE *first_art _((SUBJECT*));
+ARTICLE *last_art _((SUBJECT*));
+ARTICLE *bump_art _((ARTICLE*));
+ARTICLE *next_art _((ARTICLE*));
+ARTICLE *prev_art _((ARTICLE*));
+void inc_art _((bool_int,bool_int));
+void dec_art _((bool_int,bool_int));
+bool next_art_with_subj _((void));
+bool prev_art_with_subj _((void));
+
+void select_article _((ARTICLE*,int));
+void select_subject _((SUBJECT*,int));
+void select_thread _((ARTICLE*,int));
+void select_subthread _((ARTICLE*,int));
+void deselect_article _((ARTICLE*));
+void deselect_subject _((SUBJECT*));
+void deselect_thread _((ARTICLE*));
+void deselect_all _((void));
+void kill_subject _((SUBJECT*,int));
+void kill_thread _((ARTICLE*,int));
+void kill_subthread _((ARTICLE*,int));
+void unkill_subject _((SUBJECT*));
+void unkill_thread _((ARTICLE*));
+void unkill_subthread _((ARTICLE*));
+void clear_subject _((SUBJECT*));
+void clear_thread _((ARTICLE*));
+void clear_subthread _((ARTICLE*));
+#define KF_UNSELECTED  0
+#define KF_ALL         1
+#define KF_KILLFILE    2
+
+ARTICLE *subj_art _((SUBJECT*));
+void next_subject _((void));
+void prev_subject _((void));
+
+bool find_parent _((bool_int));
+bool find_leaf _((bool_int));
+bool find_prev_sib _((void));
+bool find_next_sib _((void));
+
+void sort_subjects _((void));
+void count_subjects _((int));
+#define CS_NORM        0
+#define CS_RESELECT    1
+#define CS_UNSELECT    2
+#define CS_UNSEL_STORE 3
+
+int subjorder_date _((SUBJECT**, SUBJECT**));
+int subjorder_str _((SUBJECT**, SUBJECT**));
+int threadorder_date _((SUBJECT**, SUBJECT**));
+int threadorder_str _((SUBJECT**, SUBJECT**));
+
+void sort_articles _((void));
+
+int artorder_date _((ARTICLE**, ARTICLE**));
+int artorder_str _((ARTICLE**, ARTICLE**));
+
+time_t parsedate _((char*));
+
+/* Stuff local to rthread.c. */
+
+#ifdef DOINIT
+
+static void build_artptrs _((void));
+
+#endif
diff --git a/usr/src/contrib/news/trn3/trn.1 b/usr/src/contrib/news/trn3/trn.1
new file mode 100644 (file)
index 0000000..b856f89
--- /dev/null
@@ -0,0 +1,2595 @@
+.\" $Id: trn.1,v 3.0 1992/12/14 00:14:10 davison Trn $
+.\" 
+.\" This software is Copyright 1991 by Stan Barber. 
+.\"
+.\" Permission is hereby granted to copy, reproduce, redistribute or otherwise
+.\" use this software as long as: there is no monetary profit gained
+.\" specifically from the use or reproduction of this software, it is not
+.\" sold, rented, traded or otherwise marketed, and this copyright notice is
+.\" included prominently in any copy made. 
+.\"
+.\" The author make no claims as to the fitness or correctness of this software
+.\" for any use whatsoever, and it is provided as is. Any use of this software
+.\" is at the user's own risk. 
+.\" 
+.de Sh
+.br
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp
+.if t .sp .5v
+.if n .sp
+..
+.de Ip
+.br
+.ie \\n.$>=3 .ne \\$3
+.el .ne 3
+.IP "\\$1" \\$2
+..
+.\" unbreakable dash.
+.tr \(*W-|\(bv\*(Tr
+.ie n \{\
+.ds -- \(*W-
+.if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+.if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+.ds L" ""
+.ds R" ""
+.ds L' '
+.ds R' '
+'br\}
+.el\{\
+.ds -- \(em\|
+.tr \*(Tr
+.ds L" ``
+.ds R" ''
+.ds L' `
+.ds R' '
+'br\}
+.TH TRN 1 LOCAL
+.UC 6
+.SH NAME
+trn - threaded read news program
+.SH SYNOPSIS
+.B trn [options] [newsgroups]
+.SH DESCRIPTION
+.I Trn
+is a threaded version of
+.I rn,
+which is a replacement for the readnews(1) program.
+Being \*(L"threaded\*(R" means that the articles are interconnected
+in reply order.
+Each discussion thread is a tree of articles where all the
+reply (child) articles branch off from their respective originating
+(parent) articles.
+A representation of this tree (or a portion of it) is displayed in the
+article header as you are reading news.
+This gives you a better feel for how all the articles are related, and
+even lets you see at a glance when an article has replies \*(-- a good
+thing to check before posting.
+In addition,
+.I trn
+has an thread selector that allows you to quickly browse through a
+list of subjects and choose the ones you find interesting.
+This thread selector can be sorted by in a variety of orders and switched
+into various display modes that allows you to pick all the subjects
+separately (threads can have multiple subjects) or even pick individual
+articles.
+Any items you don't select can be saved for reading later or
+marked as read with a single keystroke.
+.PP
+If you are already familiar with
+.I trn
+you may just want to read the
+.IR "WHAT'S NEW" 
+section.
+People upgrading from
+.I rn
+will probably want to pay attention to the sections on
+.IR "The Selector" ,
+.IR "The Tree Display" ,
+and the aforementioned
+.IR "WHAT'S NEW" .
+If you're impatient, just dive in and get started.
+All the regular commands will be familiar to an
+.I rn
+or
+.I trn
+user, and the on-line help will give you a quick run-down of what commands
+are available (just type \*(L'h\*(R' from any prompt).
+I'd also suggest using the command:
+.Sp
+    trn \-x \-X
+.Sp
+to make sure some of the best features are turned on.
+.Sh "Starting Trn"
+If no newsgroups are specified, all the newsgroups which have unread news
+will be presented to the user in the order in which they occur in the
+.I .newsrc
+file.
+At the prompt for each group you can choose to read it, skip it, move it,
+etc.
+If a list of newsgroups is provided on the command line,
+.I trn
+will start up in \*(L"add\*(R" mode, using the list as a set of patterns to
+add new newsgroups and restrict which newsgroups are displayed (see also
+the discussion of the \*(L'a\*(R' command on the newsgroup-selection level).
+.PP
+.I Trn
+operates on four levels: the newsgroup-selection level, the thread
+selector, the article-reading level, and the paging level.
+Each level has its own set of commands, and its own help menu.
+At the paging level (the bottom level)
+.I trn
+behaves much like the
+.IR more (1)
+program.
+At the article-reading level articles are presented to you in the
+order of their replies, with the subjects being ordered by the date
+of the oldest unread article (though there are commands for changing
+the default display order).
+In the thread selector you are presented with the subjects and
+(usually) authors associated with each discussion thread, and given
+a chance to choose which ones you wish to read now, save for later, or
+manipulate in some way.
+At the newsgroup-selection level (the top level), you may specify which
+newsgroup you want next, or read them in the default order, which is the
+order that the newsgroups occur in your
+.I .newsrc
+file.
+(You will therefore want to rearrange your
+.I .newsrc
+file to put the most interesting newsgroups first.
+This can be done with the \*(L'm\*(R' command on the Newsgroup Selection level.
+WARNING: invoking readnews/vnews (the old user interface) in any way (including
+as a news checker in your login sequence!) will cause your
+.I .newsrc
+to be disarranged again.)
+.PP
+On any level, at ANY prompt, help is available by typing an \*(L'h\*(R'.
+This gives you a summary of available commands and what they do.
+Remember this command, you'll need it.
+.PP
+Typing space to any question means to do the normal thing.
+You will know what that is because
+every prompt has a list of several plausible commands enclosed in
+square brackets.
+The first command in the list is the one which will be done if you type
+a space.
+(All input is done in cbreak mode, so carriage returns should not be typed
+to terminate anything except certain multi-character commands.
+Those commands will be obvious in the discussion below because they take an
+argument.)
+.PP
+Upon startup,
+.I trn
+will do several things:
+.Ip 1. 4
+It will look for your
+.I .newsrc
+file, which is your list of subscribed-to
+newsgroups.
+If
+.I trn
+doesn't find a
+.IR .newsrc ,
+it will create one.
+If it does find one, it will back it up under the name \*(L".\|oldnewsrc\*(R".
+.Ip 2. 4
+It will input your
+.I .newsrc
+file, listing out the first several newsgroups
+with unread news.
+.Ip 3. 4
+It will perform certain consistency checks on your
+.IR .newsrc .
+If your
+.I .newsrc
+is out of date in any of several ways,
+.I trn
+will warn you and patch it up for you, but you may have to wait a little
+longer for it to start up.
+.Ip 4. 4
+.I Trn
+will next check to see if any new newsgroups have been created, and give
+you the opportunity to add them to your
+.IR .newsrc .
+.Ip 5. 4
+.I Trn
+goes into the top prompt level \*(-- the newsgroup-selection level.
+.Sh "Newsgroup Selection Level"
+In this section the words \*(L"next\*(R" and \*(L"previous\*(R" refer to
+the ordering of the newsgroups in your
+.I .newsrc
+file.
+On the newsgroup-selection level, the prompt looks like this:
+.Sp
+====== 17 unread articles in talk.blurfl \*(-- read now? [ynq]
+.Sp
+unless the group is set for unthreaded reading, in which case the first
+six characters are \*(L"******\*(R".
+The following commands may be given at this level:
+.Ip \+ 8
+Enter this newsgroup through the selector.
+.Ip y 8
+Begin reading this newsgroup now.
+.Ip SP 8
+Enter the newsgroup by executing the default command listed in []'s.
+.Ip .command 8
+Do this newsgroup now, but execute
+.I command
+before displaying anything.
+The command will be interpreted as if typed on the article selection level.
+.Ip = 8
+Start this newsgroup, but list subjects before displaying articles.
+.Ip U 8
+Enter this newsgroup through the \*(L"Set unread\*(R" prompt.
+.Ip t 8
+Toggle the newsgroup between threaded and unthreaded reading.
+The default is threaded, and the current setting is stored in your .newsrc.
+.Ip n 8
+Go to the next newsgroup with unread news.
+.Ip N 8
+Go to the next newsgroup.
+.Ip p 8
+Go to the previous newsgroup with unread news.
+If there is none, stay at the current newsgroup.
+.Ip P 8
+Go to the previous newsgroup.
+.Ip \- 8
+Go to the previously displayed newsgroup (regardless of whether it is
+before or after the current one in the list).
+.Ip 1 8
+Go to the first newsgroup.
+.Ip ^ 8
+Go to the first newsgroup with unread news.
+.Ip $ 8
+Go to the end of the newsgroups list.
+.Ip "g newsgroup" 8
+Go to
+.IR newsgroup ,
+which can be the group's name or a zero-relative number of the groups in
+your .newsrc (see the \*(L'L\*(R' command to list your .newsrc).
+If it isn't currently subscribed to, you will be asked if you want to
+subscribe.
+.Ip "/pattern" 8
+Scan forward for a newsgroup matching
+.IR pattern .
+Patterns do globbing like filenames, i.\|e., use ? to match a single
+character, * to match any sequence of characters, and [] to specify a list
+of characters to match.
+(\*(L"all\*(R" may be used as a synonym for \*(L"*\*(R".)
+Unlike normal filename globbing, newsgroup-searching is not anchored to
+the front and back of the filename, i.\|e. \*(L"/ski\*(R" will find
+rec.skiing.
+You may use ^ or $ to anchor the front or back of the search:
+\*(L"/^test$\*(R" will find newsgroup test and nothing else
+If you want to include newsgroups with 0 unread articles, append /r.
+If the newsgroup is not found between the current newsgroup and the last
+newsgroup, the search will wrap around to the beginning.
+.Ip "?pattern" 8
+Same as /, but search backwards.
+.Ip u 8
+Unsubscribe from the current newsgroup.
+.Ip "l string" 8
+List newsgroups not subscribed to which contain the string specified.
+.Ip L 8 13v
+Lists the current state of the
+.IR .newsrc ,
+along with status information.
+.Sp
+.nf
+    \h'|0.5i'Status    \h'|2i'Meaning
+    \h'|0.5i'<number>  \h'|2i'Count of unread articles in newsgroup.
+    \h'|0.5i'READ      \h'|2i'No unread articles in newsgroup.
+    \h'|0.5i'UNSUB     \h'|2i'Unsubscribed newsgroup.
+    \h'|0.5i'BOGUS     \h'|2i'Bogus newsgroup.
+    \h'|0.5i'JUNK      \h'|2i'Ignored line in .newsrc
+\h'|2i'(e.\|g. readnews \*(L"options\*(R" line).
+.fi
+.Sp
+(A bogus newsgroup is one that is not in the list of active newsgroups
+in the active file, which on most systems is /usr/lib/news/active unless
+you use NNTP.)
+.Ip "m {name}" 8
+Move the named newsgroup somewhere else in the
+.IR .newsrc .
+If no name is given, the current newsgroup is moved.
+There are a number of ways to specify where you want the newsgroup \*(-- type
+h for help when it asks where you want to put it.
+.Ip c 8
+Catch up \*(-- mark all unread articles in this newsgroup as read.
+.Ip A 8
+Abandon the changes made to the current newsgroup since
+.I trn
+was started.
+Useful when you accidentally mark a group as read.
+.Ip "o {pattern}" 8
+Only display those newsgroups whose name matches
+.IR pattern .
+Patterns are the same as for the \*(L'/\*(R' command.
+Multiple patterns may be separated by spaces, just as on the
+command line.
+The restriction will remain in effect either until there are no articles
+left in the restricted set of newsgroups, or another restriction command
+is given.
+Since
+.I pattern
+is optional, \*(L'o\*(R' by itself will remove the
+restriction.
+.Ip "a pattern" 8
+Add unsubscribed newsgroups matching
+.IR pattern .
+If any matching newsgroups are found, you will be asked for each one whether
+you would like to add it.
+If you want to add all the newsgroups, you can
+type \*(L'Y\*(R' and they will be added the the end of the
+.I .newsrc
+file.
+If you don't want to subscribe, all
+the remaining groups can be ignored by typing \*(L'N\*(R'.
+After any new newsgroups have been added, the \*(L'a\*(R' command also
+restricts the current set of newsgroups just like the \*(L'o\*(R' command
+does.
+.Ip & 8
+Print out the current status of command-line switches and any newsgroup
+restrictions.
+.Ip "&switch {switch}" 8
+Set additional command-line switches.
+.Ip && 8
+Print out the current macro definitions.
+.Ip "&&keys commands" 8
+Define additional macros.
+.Ip !command 8
+Escape to a subshell.
+One exclamation mark (!) leaves you in your own news directory.
+A double exclamation mark (!!) leaves you in the spool
+directory for news, which is usually /usr/spool/news unless you're
+using NNTP to read news.
+The environment variable SHELL will be used if defined.
+If
+.I command
+is null, an interactive shell is started.
+.Ip v 8
+Print the current version number and information on where to send bug
+reports.
+.Ip q 8
+Quit.
+.Ip x 8
+Quit, restoring .newsrc to its state at startup of
+.IR trn .
+The .newsrc you would have had if you had exited with \*(L'q\*(R' will be
+called .newnewsrc, in case you didn't really want to type \*(L'x\*(R'.
+.Ip ^K 8
+Edit the global list of memorized commands (in the global KILL file) that
+you wish to be performed in every newsgroup as it is started up (that is, when
+it is selected at the newsgroup-selection level).
+This file contains commands (one per line) such as /subject/:j or /author/f:+
+to kill or select articles based on the indicated search criteria.
+There is also a local list of commands for each newsgroup that can contain
+kill/selection commands tailored for each specific group.
+Because of the overhead involved in searching for articles to kill, it is
+better if possible to use a local list rather than the global one.
+Local memorized commands are usually maintained by using the \*(L'A\*(R'
+or \*(L'T\*(R' commands from the article/pager level or in the selector.
+There is also a K search modifier that appends any search command you
+desire to add.
+It is also possible to manually edit the file with the \*(L'^K\*(R'
+command from anywhere inside a newsgroup.
+If either of the environment variables VISUAL or EDITOR is set, the
+specified editor will be invoked; otherwise a default editor
+is invoked on the KILL file.
+.Sh "The Selector"
+Most people who don't have all day to read news will want to enter a newsgroup
+by way of the selector.
+This is accomplished by using the \*(L'+\*(R'
+command at the newsgroup-selection or article/pager levels.
+In fact, this may be the default command for entering a newsgroup, depending
+on how your version of
+.I trn
+was configured and your use of the
+.B \-X
+option.
+.PP
+The selector displays a list of articles by their subjects and (usually)
+authors.
+The articles are grouped into threads by default (which may list multiple
+subjects per selectable item if the subject has changed during the discussion)
+and ordered by the date of their oldest unread article.
+Thread or subject groups are also shown with a count of the number of
+articles in each group.
+Each selectable item is preceded by a letter or number that can be typed
+to toggle its selection.
+Items that are selected are flagged with a \*(L'+\*(R' after their letter.
+Groups that have only some of their articles selected are flagged with
+a \*(L'*\*(R'.
+You can change the selector's mode (to pick each subject separately or
+pick individual articles), order the list by a variety of sort
+criteria, and switch the author display between its long, medium and short
+styles using the commands detailed below.
+.PP
+The following commands are available in the selector:
+.Ip "a-z,0-9,A-Z" 8
+Select/deselect the indicated item by its letter or number.
+There are quite a few letters omitted from the alpha characters to be
+typed as commands \*(-- see below.
+Also, the variable SELECTCHARS is available to customize which characters
+you want to be used as selection letters, overriding their command function.
+.Ip SP 8
+Perform the default command.
+This is usually > for most pages, and Z on the last page (although D and X
+are also quite popular).
+.Ip CR 8
+Begin reading.
+If no articles are selected, the current item is selected (unless you've
+marked it as killed).
+.Ip "Z,TAB" 8
+Begin reading.
+If no articles are selected, read all unread articles.
+.Ip "\*(L'.\*(R'" 8
+Toggle the current item's selection (the one under the cursor).
+.Ip * 8
+Same as \*(L'.\*(R' except that it affects all articles with the same
+subject (useful in the article selector).
+.Ip # 8
+Make an overriding selection that reads the current item only,
+temporarily ignoring all other selections.
+.Ip "k, \*(L',\*(R'" 8
+Mark the current item as killed.
+.Ip "m, \e" 8
+Unmark the current item.
+.Ip \- 8
+Set a range, as in a \- k.
+Repeats the last marking action: selection, deselection, killing,
+or unmarking.
+.Ip @ 8
+Toggle all visible selections.
+.Ip M 8
+Mark the current item's article(s) to return on newsgroup exit
+and kill the item.
+.Ip Y 8
+Yank back and select the marked-to-return articles, clearing their
+to-return status.
+.Ip E 8
+Exclude all unselected items from the selection list (narrow the display).
+Press it again to pick from all available items.
+.Ip "n, ]" 8
+Move down to the next item (try the down-arrow keypad key also).
+.Ip "p, [" 8
+Move up to the previous item (try the up-arrow keypad key also).
+.Ip < 8
+Go to previous page (try the left-arrow keypad key also).
+.Ip > 8
+Go to next page (try the right-arrow keypad key also).
+.Ip ^ 8
+Go to the first page.
+.Ip $ 8
+Go to the last page.
+.Ip S 8
+Set the items the selector displays: threads, subjects or articles.
+If the group is unthreaded setting this to threads will thread the
+group.
+.Ip = 8
+Switch between the article selector and the subject/thread selector.
+.Ip O 8
+Pick the order for the items: date, subject, author, item count
+(for thread/subject groups), and a subject-date grouping of
+individual articles.
+Typing the selection in lower-case will sort the articles in the
+default direction, while using upper-case will reverse the sort.
+There is a separate default sort order for the subject/thread selector
+and the article selector.
+See the
+.B \-O
+option to set your favorite selector mode and sort order as the default.
+.Ip R 8
+Reverse the current sort order.
+.Ip L 8
+Switch the selector's display between the long, medium and short
+display styles.
+See the
+.B \-x
+option to set your favorite style as the default.
+.Ip U 8
+Switch between selecting unread/read articles.
+.Ip X 8
+Mark all unselected articles as read and start reading.
+.Ip D 8
+Mark unselected articles on the current page as read and
+begin reading if articles are selected, otherwise go to the next page.
+.Ip J 8
+Mark all selected articles as read (useful after performing some action
+on them with the \*(L':\*(R' command).
+.Ip c 8
+Catch up \*(-- marks ALL articles as read without affecting their cross-posted
+counterparts.
+.Ip A 8
+Add a subject-search command to the memorized list (a.\|k.\|a. a KILL file)
+for this group.
+You are prompted to choose selection (+), junking (j),
+selection including all replies (.) or junking including all replies (,).
+If the thread has more than one subject the first subject is the one
+chosen for the memorized command.
+.Ip T 8
+Add a thread-oriented command to the memorized list for this group.
+You are prompted to choose selecting the thread (+), junking the
+thread (j), or clearing the auto-selection/junking for the thread (c).
+(Note: there are three other options (\*(L'.\*(R', \*(L',\*(R',
+and \*(L'C\*(R') on the
+article-reading level \*(-- look there for an explanation of their use.)
+.Ip ^K 8
+Edit the local list of memorized commands (a.\|k.\|a. a KILL file) for this
+newsgroup.
+A detailed description of memorized commands is found in the Article Selection
+section.
+.Ip ":command" 8
+Apply a command to all selected articles.
+If nothing is selected, apply the command to all the unread articles.
+.Sp
+Applicable commands include \*(L'+\*(R'/\*(L'-\*(R' (select/deselect
+an article), \*(L"++\*(R"/\*(L"--\*(R" (select/deselect a thread),
+\&\*(L"T+\*(R" (auto-select the entire thread), \*(L"Tj\*(R"
+(auto-junk the entire thread), \*(L't\*(R'
+(display article tree), \*(L"s dest\*(R" (save article to a destination),
+\*(L"e dir\*(R" (extract to directory), \*(L'E\*(R' (end partial uudecode),
+as well as: S, |, w, W, m, M, j, = and \*(L',\*(R'.
+.Ip "/pattern" 8
+Scan all articles for a subject containing
+.I pattern
+and select it.
+.Ip "/pattern/modifiers:command{:command}" 8
+Apply the commands listed to articles matching the search command (possibly
+with h, a, r, or K modifiers).
+The default action, if no command is specified, is to select the article's
+item in the selector (e.\|g. the entire thread (\*(L"++\*(R") in the thread
+selector).
+See the section on Regular Expressions and the description of pattern
+searching in the Article Selection section.
+.Sp
+One example: to scan all the unread articles looking for \*(L"topic\*(R"
+anywhere in the article and then select its group and save the articles to
+the files topic.1, topic.2, etc. use \*(L"/topic/a:++:s topic.%#\*(R".
+.Ip N 8
+Go to the next newsgroup with unread news.
+.Ip P 8
+Go to the previous newsgroup with unread news.
+.Ip & 8
+Display or set the current status of command-line switches.
+.Ip && 8
+Display or set the current macro definitions.
+.Ip !command 8
+Escape to a subshell.
+.Ip q 8
+Quit this group.
+.Ip "ESC,+" 8
+Quit the selector to the article level.
+Note: ESC won't work if trn has mapped your arrow keys with default macros
+and the first character that your arrow keys send is an ESC.
+.Ip Q 8
+Quit the current newsgroup and return to the newsgroup-selection prompt
+for this group.
+.Sh "Article-Reading Level"
+On the article-reading level,
+.I trn
+displays unread articles in thread sequence (reading each article and its
+replies before going on to another topic) unless threads are disabled for
+a particular group, in which case the default order is the order they
+arrived at your site (numeric sequence).
+In either case if you use the subject-search command (^N) you will switch
+to reading the articles in date order within each matching subject.
+(Making selections in the subject selector or using the
+.B \-S
+switch will automatically turn subject search mode on in an unthreaded group.)
+.Sp
+On the article-reading level you are
+.I not
+asked whether you want to read an article before the article is displayed;
+rather,
+.I trn
+simply displays the first page (or portion of a page, at low baud rates) of an
+article and asks if you want to continue.
+The normal article-reading prompt comes at the END of an article
+(although article-reading commands can also be given from within the middle
+of an article in addition to the pager level commands).
+The prompt at the end of an article looks like this:
+.Sp 
+End of article 248 (of 257) \*(-- what next? [npq]
+.Sp
+The following are the options at this point:
+.Ip n,SP 8
+Scan forward for next unread article.
+(Note: the \*(L'n\*(R' (next) command when typed at the end of an article
+does not mark the article as read, since an article is automatically marked
+as read after the last line of it is printed.
+It is therefore possible to type a sequence such as \*(L'mn\*(R' and leave
+the article marked as unread.
+The fact that an article is marked as read by typing n, N, ^N, e, s, S, |,
+w, or W within the MIDDLE of the article is in fact a special case.)
+.Ip N 8
+Go to the next article.
+.Ip ^N 8
+Find the next article with the same subject in date order.
+This also makes subject search mode (^N) the default command at the end
+of an article.
+.Ip p 8
+Scan backward for previous unread article.
+If there is none, stay at the current article.
+.Ip P 8
+Go to the previous article.
+.Ip \- 8
+Go to the previously displayed article (regardless of whether that article
+is before or after this article in the normal sequence).
+.Ip ^P 8
+Find the previous article with the same subject in date order.
+Makes subject search mode (^N) the default.
+.Ip _N 8
+Go to the next article in numeric sequence.
+.Ip _P 8
+Go to the previous article in numeric sequence.
+.Ip "<, >" 8
+Browse the previous/next selected thread/subject.
+If no selections have been made, all the threads that had unread news when
+you entered the newsgroup (or last left the selector) are treated
+as selected.
+Entering an empty newsgroup makes all the already-read threads available
+for browsing.
+.Ip "[, ]" 8
+Proceed to the left/right in the article tree.
+Visits already-read articles as well as empty nodes.
+Try using the left-/right-arrow keys also.
+.Ip "{, }" 8
+Go to the root/leaf of the article tree, even if the node is
+already read or empty.
+Proceeds to the very first/last node if you're already at a root/leaf in
+a multi-root thread.
+.Ip "(, )" 8
+Go to the previous/next sibling in the thread, including \*(L"cousin\*(R"
+siblings.
+Try using the up-/down-arrow keys also.
+.Ip t 8
+Display the entire article tree and all its associated subjects.
+If the group is not currently threaded, it will become threaded to
+process this command.
+.Ip ^R 8
+Restart the current article.
+.Ip v 8
+Restart the current article verbosely, displaying the entire header.
+.Ip ^L 8
+Refresh the screen.
+.Ip ^X 8
+Restart the current article, and decrypt as a rot13 message.
+.Ip X 8
+Refresh the screen, and decrypt as a rot13 message.
+.Ip b 8
+Back up one page.
+.Ip q 8
+Quit this newsgroup and go back to the newsgroup-selection level.
+.Ip ^ 8
+Go to the first unread article.
+.Ip $ 8
+Go to the last article (actually, one past the last article).
+.Ip "number" 8
+Go to the numbered article.
+.Ip "range{,range}:command{:command}" 8
+Apply a set of commands to a set of articles.
+A range consists of either <article number> or
+<article\ number>\-<article\ number>.
+A dot \*(L'.\*(R' represents the current article, and a dollar
+sign \*(L'$\*(R' represents the last article.
+.Sp
+Applicable commands include \*(L'm\*(R' (mark as unread), \*(L'M\*(R'
+(mark as read-until-exit), \*(L'j\*(R' (mark as read), \*(L"s dest\*(R"
+(save to a destination), \*(L"e dir\*(R" (extract to directory),
+\&\*(L"!command\*(R" (shell escape), \*(L"=\*(R" (print the subject),
+\&\*(L'+\*(R'/\*(L'-\*(R' (select/deselect the article),
+\&\*(L'T+\*(R' (auto-select the entire thread), \*(L'Tj\*(R'
+(auto-junk the entire thread), \*(L"++\*(R"/\*(L"--\*(R" (select/deselect
+the associated thread), \*(L'C\*(R' (cancel),
+as well as S, |, w, W, and t.
+.Ip ":command" 8
+Apply a command to all selected articles.
+If nothing is selected, nothing is done.
+For applicable commands, see the discussion above for the range command.
+.Ip j 8
+Junk the current article (i.\|e. mark it as read).
+If this command is used from within an article, you are left at the end of
+the article, unlike \*(L'n\*(R', which looks for the next article.
+.Ip m 8
+Mark the current article as still unread.
+(If you don't want to see this article for a while you're probably better
+off using M instead of m, otherwise this article might get picked again
+as the first available article sooner than you'd like.)
+.Ip M 8
+Mark the current article to return on newsgroup exit.
+Until then, the current article will be marked as read.
+This is useful for returning to an article in another session.
+.Ip Y 8
+Yank back the marked-to-return articles, clearing their to-return status.
+If you are reading selected articles, the yanked articles come back selected.
+.Ip /pattern 8
+Scan forward for article containing
+.I pattern
+in the subject.
+See the Regular Expressions section.
+Together with the escape substitution facility described later, it becomes
+easy to search for various attributes of the current article, such as
+subject, article ID, author name, etc.
+The previous pattern can be recalled with ESC.
+If
+.I pattern
+is omitted, the previous pattern is assumed.
+.Ip /pattern/f 8
+Scan forward for article containing
+.I pattern
+in the from line.
+.Ip /pattern/h 8
+Scan forward for article containing
+.I pattern
+in the header.
+.Ip /pattern/a 8
+Scan forward for article containing
+.I pattern
+anywhere in the article.
+.Ip /pattern/r 8
+Scan read articles also.
+.Ip /pattern/c 8
+Make search case sensitive.
+Ordinarily upper- and lower-case are considered the same.
+.Ip "/pattern/modifiers:command{:command}" 8
+Apply the commands listed to articles matching the search command (possibly
+with h, a, or r modifiers).
+Applicable commands include \*(L'm\*(R' (mark as unread), \*(L'M\*(R'
+(mark as read-until-exit), \*(L'j\*(R' (mark as read), \*(L"s dest\*(R"
+(save to a destination), \*(L"e dir\*(R" (extract to directory),
+\&\*(L"!command\*(R" (shell escape), \*(L"=\*(R" (print the subject),
+\&\*(L'+\*(R' (select the article), \*(L'-\*(R' deselect the article,
+\&\*(L'T+\*(R' (auto-select the entire thread), \*(L'Tj\*(R'
+(auto-junk the entire thread), \*(L"++\*(R" (select the associated thread),
+\&\*(L"--\*(R" deselect the associated thread), and \*(L'C\*(R' (cancel).
+If the first command is \*(L'm\*(R' or \*(L'M\*(R', modifier r is assumed.
+A K may be included in the modifiers (not the commands) to cause the
+entire command (sans K) to be saved to the local KILL file, where it will
+be applied to every article that shows up in the newsgroup.
+.Sp
+For example, to save all articles in a given newsgroup to the line printer
+and mark them read, use \*(L"/^/\||\|lpr:j\*(R".
+If you type \*(L"/^/K\||\|lpr:j\*(R", this will happen every time you enter the
+newsgroup.
+.Ip ?pattern 8
+Scan backward for article containing
+.I pattern
+in the subject.
+May be modified as the forward search is: ?pattern?modifiers[:commands].
+It is likely that you will want an r modifier when scanning backward.
+.Ip k 8
+Mark as read all articles with the same subject as the current article.
+(Note: there is no single character command to temporarily mark as read (M
+command) articles matching the current subject.
+That can be done with \*(L"/<ESC>s/M\*(R", however.)
+.Ip \, 8
+Mark the current article and all its replies as read.
+.Ip J 8
+Junk all the articles in the current thread, even if it contains multiple
+subjects.
+.Ip A 8
+Add a subject-search command to the memorized list for this group (in the
+KILL file).
+You are prompted to choose selection (+), junking (j),
+selection including all replies (.) or junking including all replies (,).
+.Ip K 8
+This is a synonym for the command \*(L"Aj\*(R" which adds a command to
+junk the current subject to the memorized commands for the group.
+See also the K modifier on searches above.
+.Ip T 8
+Add a thread-oriented command to the memorized list for this group.
+You are prompted to choose selection of entire thread (+), junking of
+entire thread (j), selection of an article and its replies (.), junking
+of an article and its replies (,), clearing the auto-selection/junking
+for this thread (c), or clearing the auto-selection/junking for an article
+and its replies (C).
+.Ip ^K 8
+Edit the local list of memorized commands (a.\|k.\|a. a KILL file) for this
+newsgroup.
+Each line of the KILL file is either a subject-affecting command of the
+form /pattern/x or a thread-affecting command of the form <message-id> Tx.
+The first line in the KILL file has the form \*(L"THRU <number>\*(R",
+which tells
+.I trn
+the maximum article number that the KILL file has been applied to.
+The THRU value is usually only used to keep header or article searches
+from happening multiple times.
+Subject and from-line searches are quite fast if the group has cached data
+around (e.\|g. a .\|thread or .\|overview file).
+If it doesn't, the THRU line is used to set a lower boundary on the
+search to keep the startup time as short as possible.
+If trn skipped some selections (or you're not sure), wait for the group to
+finish being cached (e.\|g. visiting the selector forces the caching of
+all unread articles), quit the group, and re-enter.
+.Sp
+To see only newgroup articles in the control newsgroup, for instance, you
+might include the line
+.Sp
+/newgroup/:+
+.Sp
+which selects all subjects containing \*(L"newgroup\*(R".
+You can add lines automatically via the A and T commands as well as the
+K search modifier, but editing is the only way to remove subject commands
+(thread commands die automatically as the thread dies).
+If either of the environment variables VISUAL or EDITOR is set, the
+specified editor will be invoked; otherwise a default editor (normally vi)
+is invoked on the KILL file.
+.Sp
+The KILL file may also contain switch-setting lines beginning with \*(L'&\*(R'.
+Additionally, any line beginning with \*(L'X\*(R' is executed on exit
+from the newsgroup rather than on entrance.
+This can be used to set switches back to a default value.
+One use for this capability is to set your save directory to a custom value
+upon entry to a newsgroup and set it back on exit using the \-ESAVEDIR option.
+See also the
+.B \-/
+option for another solution to multiple save directories without using KILL
+files.
+.Ip r 8
+Reply through net mail.
+The environment variables MAILPOSTER and MAILHEADER may be used to modify
+the mailing behavior of
+.I trn
+(see the environment section).
+If the current article does not exist (such as the \*(L"End of newsgroup\*(R"
+pseudo-article you can get to with a \*(L'$\*(R' command), invokes the mailer
+to nobody in particular.
+.Ip R 8
+Reply, including the current article in the header file generated.
+(See \*(L'F\*(R' command below).
+The YOUSAID environment variable controls the format of the attribution line.
+.Ip f 8
+Submit a follow-up article.
+If the current article does not exist (such as the \*(L"End of newsgroup\*(R"
+pseudo-article you can get to with a \*(L'$\*(R' command), posts an original
+(root) article.
+.Ip F 8
+Submit a follow-up article, and include the old article, with lines prefixed
+either by \*(L">\*(R" or by the argument to the
+.B \-F
+switch.
+.I Trn
+will attempt to provide an attribution line in front of the quoted article,
+generated from the From: line of the article.
+Unfortunately, the From: line doesn't always contain the right name; you
+should double check it against the signature and change it if necessary,
+or you may have to apologize for quoting the wrong person.
+The environment variables NEWSPOSTER, NEWSHEADER and ATTRIBUTION may be
+used to modify the posting behavior of
+.I trn
+(see environment section).
+.Ip C 8
+Cancel the current article, but only if you are the contributor or superuser.
+.Ip z 8
+Supersede the current article, but only if you are the contributor.
+.Ip Z 8
+Same as the \*(L'z\*(R' command, but you start with a copy of the
+original article to work with.
+.Ip c 8
+Catch up in this newsgroup; i.\|e., mark all articles as read.
+.Ip U 8
+Mark some or all articles as unread.
+You can choose to mark the current thread, sub-thread (the current article
+and its replies), all the articles, or start up the selector
+to choose specific articles to set unread.
+.Ip u 8
+Unsubscribe from this newsgroup.
+.Ip "s destination" 8
+Save to a filename or pipe using sh.
+If the first character of the destination is a vertical bar, the rest of
+the command is considered a shell command to which the article is passed
+through standard input.
+The command is subject to filename expansion.
+(See also the environment variable PIPESAVER.)
+If the destination does not begin with a vertical bar, the rest of the
+command is assumed to be a filename of some sort.
+An initial tilde \*(L'~\*(R' will be translated to the name of the home
+directory, and an initial environment variable substitution is also allowed.
+If only a directory name is specified, the environment variable SAVENAME
+is used to generate the actual name.
+If a non-absolute filename is specified, the environment variable
+SAVEDIR will be used to generate the actual directory.
+If nothing is specified, then obviously both variables will be used.
+Since the current directory for
+.I trn
+while doing a save command is your
+private news directory, typing \*(L"s ./filename\*(R" will force the file
+to your news directory.
+Save commands are also run through % interpretation, so that you can
+enter \*(L"s %O/filename\*(R" to save to the directory you were in when you ran
+.IR trn ,
+and \*(L"s %t\*(R" to save to a filename consisting of the Internet address
+of the sender.
+.Sp
+After generating the full pathname of the file to save to,
+.I trn
+determines if the file exists already, and if so, appends to it.
+.I trn
+will attempt to determine if an existing file is a mailbox or a normal file,
+and save the article in the same format.
+If the output file does not yet exist,
+.I trn
+will by default ask you which format you want, or you can make it skip the
+question with either the
+.B \-M
+or
+.B \-N
+switch.
+If the article is to be saved in mailbox format, the command to do so is
+generated from the environment variable MBOXSAVER.
+Otherwise, NORMSAVER is used.
+.Ip "S destination" 8
+Save to a filename or pipe using a preferred shell, such as csh.
+Which shell is used depends first on what you have the environment variable
+SHELL set to, and in the absence of that, on what your news administrator
+set for the preferred shell when he or she installed
+.IR trn .
+.Ip "| command" 8
+Shorthand for \*(L"s | command\*(R".
+.Ip "w destination" 8
+The same as \*(L"s destination\*(R", but saves without the header.
+.Ip "W destination" 8
+The same as \*(L"S destination\*(R", but saves without the header.
+.Ip "e directory" 8
+Extract a shell archive or uuencoded binary to the designated directory.
+The article is first scanned to try discover what type of data is
+encapsulated.
+If a \*(L"cut here\*(R" line is found, the first non-blank line after it
+must be either the start of a shar header, or the \*(L"begin\*(R"
+or \*(L"table\*(R" line of a uuencoded binary.
+The default for extracting shars is to send the data portion of the file
+to /bin/sh, but that can be overridden with the UNSHAR variable (see the
+ENVIRONMENT section).
+Uudecoding is done internally by a decoder that can handle the data
+being split up over multiple articles, and extracted one piece at a time.
+To decode a multi-article file, either execute the \*(L'e\*(R'
+command in each article in sequence, use an article range to execute the
+command, or use the \*(L":e\*(R" command to repeat the command for each of the
+currently selected articles.
+When the \*(L'e\*(R' command is not followed by any arguments, it will
+repeat the arguments from the last extraction.
+All directory specifications are relative to the value of SAVEDIR, so you
+can use the command \*(L"e .\*(R" to force an extraction to SAVEDIR itself.
+If a uudecoding is in progress (i.\|e. the last piece wasn't extracted yet)
+and you exit the group, the partial file will be removed.
+This also occurs if you start to extract a new uuencoded file before the
+previous one was finished.
+See also the \*(L'E\*(R' command for ending a multi-part uudecoding
+manually.
+.Sp
+There is one special case that is handled differently: if the first file in a
+recognizable shar file is a uuencoded binary that was packed with lines
+starting with an \*(L'X\*(R', we will not unshar the file but instead
+uudecode it.
+If this causes problems, you can override the default extraction method
+by following the directory with an explicit command to execute, as
+described below.
+.Ip "e directory|command" 8
+This form of the \*(L'e\*(R' command allows you to extract other data
+formats than shar or uuencoded files or to override the decisions made
+by the automatic extraction selection described above.
+In normal operation, all data following what we recognize as
+a \*(L"cut here\*(R" line will be sent to the specified command.
+Additionally, the distinctive beginning of a shell archive is also recognized
+without a preceding cut line.
+When the command is run, the default directory will be set to the
+specified directory, or the value
+of SAVEDIR if unspecified.
+Entering the \*(L'e\*(R' command without arguments will repeat your
+previous extract command.
+You can use the command \*(L"e dir|\*(R" to extract to a new directory
+using the previously-specified command.
+.Ip E 8
+This command ends any multi-part uuencoded file extraction that you began,
+but are unable (or unwilling) to complete.
+The partially extracted file is removed.
+.Ip & 8
+Print out the current status of command-line switches.
+.Ip "&switch {switch}" 8
+Set additional command-line switches.
+.Ip && 8
+Print out current macro definitions.
+.Ip "&&keys commands" 8
+Define an additional macro.
+.Ip !command 8
+Escape to a subshell.
+One exclamation mark (!) leaves you in your own news directory.
+A double exclamation mark (!!) leaves you in the spool
+directory of the current newsgroup.
+The environment variable SHELL will be used if defined.
+If
+.I command
+is null, an interactive shell is started.
+.Sp
+You can use escape key substitutions described later to get to many
+run-time values.
+The command is also run through % interpretation, in case it is being called
+from a range or search command.
+.Ip \+ 8
+Start the selector in the last-used mode.
+If the newsgroup is unthreaded and the default selector mode is threads,
+we temporarily switch to subject selection unless manually overridden.
+.Ip _a 8
+Start the selector in article mode.
+.Ip _s 8
+Start the selector in subject mode.
+.Ip _t 8
+Start the selector in thread mode.
+.Ip _T 8
+Start the selector in thread mode unless the group isn't threaded, in
+which case we settle for the subject selector.
+.Ip = 8
+List subjects of unread articles.
+.Ip # 8
+Print last article number.
+.Ip _+ 8
+Select the entire thread associated with the current article.
+.Ip _- 8
+Deselect the entire thread associated with the current article.
+.Sh "Pager Level"
+At the pager level (within an article), the prompt looks like this:
+.Sp
+\*(--MORE\*(--(17%)
+.Sp
+and a number of commands may be given:
+.Ip SP 8
+Display next page.
+.Ip x 8
+Display next page and decrypt as a rot13 message.
+.Ip d 8
+Display half a page more.
+.Ip CR 8
+Display one more line.
+.Ip q 8
+Go to the end of the current article (don't mark it either read or unread).
+Leaves you at the \*(L"What next?\*(R" prompt.
+.Ip j 8
+Junk the current article.
+Mark it read and go to the end of the article.
+.Ip ^L 8
+Refresh the screen.
+.Ip X 8
+Refresh the screen and decrypt as a rot13 message.
+.Ip b 8
+Back up one page.
+.Ip t 8
+Display the entire article tree, including its associated subjects, and
+continue reading.
+If the group is not currently threaded, it will be threaded first.
+.Ip gpattern 8
+Goto (search forward for)
+.I pattern
+within current article.
+Note that there is no space between the command and the pattern.
+If the pattern is found, the page containing the pattern will be displayed.
+Where on the page the line matching the pattern goes depends on the value
+of the
+.B \-g
+switch.
+By default the matched line goes at the top of the screen.
+.Ip G 8
+Search for g pattern again.
+.Ip ^G 8
+This is a special version of the \*(L'g\*(R' command that is for skipping
+articles in a digest.
+It is equivalent to setting \*(L"\-g4\*(R" and then executing the command
+\*(L"g^Subject:\*(R".
+.Ip TAB 8
+This is another special version of the \*(L'g\*(R' command that is for
+skipping inclusions of older articles.
+It is equivalent to setting \*(L"\-g4\*(R" and then executing the command
+\*(L"g^[^c]\*(R", where \fIc\fR is the first character of the last line
+on the screen.
+It searches for the first line that doesn't begin with the same character
+as the last line on the screen.
+.Ip !command 8
+Escape to a subshell.
+.PP
+The following commands skip the rest of the current article, then behave just
+as if typed to the \*(L"What next?\*(R" prompt at the end of the article.
+See the documentation at the article selection level for these commands.
+.Sp    
+    # $ & / = ? A c C f F k K T ^K J , m M r R ^R u U v Y ^
+.br
+    p P ^P - < > [ ] { } number
+.br
+    range{,range} command{:command}
+.Sp
+The following commands also skip to the end of the article, but have the
+additional effect of marking the current article as read:
+.Sp
+    n N ^N e s S | w W
+.Sp
+.Sh "Miscellaneous facts about commands"
+An \*(L'n\*(R' typed at either the \*(L"Last newsgroup\*(R" prompt or a
+\*(L"Last article\*(R" prompt will cycle back to the top of the newsgroup
+or article list, whereas a \*(L'q\*(R' will quit the level.
+(Note that \*(L'n\*(R' does not mean \*(L"no\*(R", but rather
+\*(L"next\*(R".)
+A space will of course do whatever is shown as the
+default, which will vary depending on whether
+.I trn
+thinks you have more articles or newsgroups to read.
+.PP
+The \*(L'b\*(R' (backup page) command may be repeated until the beginning of
+the article is reached.
+If
+.I trn
+is suspended (via a ^Z), then when the job is resumed, a refresh (^L) will
+automatically be done (Berkeley-type systems only).
+If you type a command such as \*(L'!\*(R' or \*(L's\*(R' which takes you
+from the middle of the article to the end, you can always get back into the
+middle by typing \*(L'^L\*(R'.
+.PP
+In multi-character commands such as \*(L'!\*(R', \*(L's\*(R', \*(L'/\*(R',
+etc, you can interpolate various run-time values by typing escape and a
+character.
+To find out what you can interpolate, type escape and \*(L'h\*(R', or check
+out the single character % substitutions for environment variables in the
+Interpretation and Interpolation section, which are the same.
+Additionally, typing a double escape will cause any % substitutions in the
+string already typed in to be expanded.
+.Sh "The Tree Display"
+When reading a threaded newsgroup,
+.I trn
+displays a character representation of the article tree in the upper right
+corner of the header.
+For example, consider the following display:
+.Sp
+.nf
+.in +4n
+(1)+-(1)--(\fI2\fP)--\fI[2]\fP
+\h'\w'(1)'u'|-(1)+-<3>
+\h'\w'(1)'u'|\h'\w'-(1)'u'\\-[1]
+\h'\w'(1)'u'\\-(1)+-[1]--[1]
+\h'\w'(1)+-(1)'u'\\-[1]
+.in -4n
+.fi
+.Sp
+This tree represents an initial article that has three direct replies
+(the second column with three (1)'s).
+Each reply has further replies branching off from them.
+In two cases the subject line was altered in the reply, as indicated
+by the increasing numbers.
+.PP
+The third subject is not selected for reading, as indicated by the <>'s.
+Note you can always forcefully visit an unselected article with \*(L'N\*(R'
+and \*(L'P\*(R' as well as the thread-navagation commands (which are
+typically macro'ed to the arrow keys on your keypad).
+.PP
+When there is only one subject associated with a thread, all the nodes
+are marked with the number 1.
+When the first subject change arrives, it is marked with the number 2,
+and so on.
+If you were to look at this thread in the thread selector, the three
+subjects associated with it would be listed in the same order as the
+ascending digits.
+In those rare cases where more than 9 subjects are associated with each
+thread, the nodes are marked with the letters A-Z, and then by a-z.
+.PP
+The articles that have already been read are enclosed in ()'s,
+Unread articles are displayed in []'s, and unread-but-unselected articles
+are displayed in <>'s.
+The currently displayed article has its entire node highlighted in the
+display.
+The previously displayed article has only its number highlighted.
+If the group has not been completely threaded yet, some articles will
+appear as (?) until trn can determine if the referenced article truly
+exists or not.
+If you visit such an article and wait for trn to finish threading the
+group, the screen will refresh as soon as the presence or absence of
+the article is determined.
+.Sh "Options"
+.I Trn
+has a nice set of options to allow you to tailor the interaction
+to your liking.
+(You might like to know that the author swears by
+\*(L"\-x6ms \-e \+m \-S -XX -N -B -p\*(R".)
+These options may be set on the command line, via the TRNINIT
+environment variable, via a file pointed to by the TRNINIT variable, or
+from within
+.I trn
+via the & command.
+Options may generally be unset by typing \*(L"+switch\*(R".
+Options include:
+.TP 5
+.B \-a
+causes trn to always thread the unread articles on entry to a group.
+Without this option trn may enter a group in a partially-threaded
+state and process the unthreaded articles in the background.
+The down side of this is that the tree display may not be complete when
+it is first displayed and you may start out at an odd position in the
+first thread's article tree.
+.TP 5
+.B \-A
+tells trn to attempt to create some default macros that will map your
+arrow keys to useful trn functions (this is the default).
+Use 
+.B +A
+to turn this behavior off.
+.TP 5
+.B \-b
+will force trn to read each thread in a breadth-first order, rather than
+depth-first.
+.TP 5
+.B \-B
+will turn on a spinner that twirls when trn is doing background
+article-processing.
+A gizmo for those interested in what's going on behind the scenes.
+.TP 5
+.B \-c
+checks for news without reading news.
+If a list of newsgroups is given on the command line, only those newsgroups
+will be checked; otherwise all subscribed-to newsgroups are checked.
+Whenever the
+.B \-c
+switch is specified, a non-zero exit status from
+.I trn
+means that there is unread news in one of the checked newsgroups.
+The
+.B \-c
+switch does not disable the printing of newsgroups with unread news;
+this is controlled by the
+.B \-s
+switch.
+(The
+.B \-c
+switch is not meaningful when given via the & command.)
+.TP 5
+.B \-C<number>
+tells
+.I trn
+how often to checkpoint the
+.IR .newsrc ,
+in articles read.
+Actually, this number says when to start thinking about doing a checkpoint
+if the situation is right.
+If a reasonable check-pointing situation doesn't arise within 10 more
+articles, the
+.I .newsrc
+is check-pointed willy-nilly.
+.TP 5
+.B \-d<directory name>
+sets the default save directory to something other than ~/News.
+The directory name will be globbed (via csh) if necessary (and if possible).
+Articles saved by
+.I trn
+may be placed in the save directory or in a subdirectory thereof depending
+on the command that you give and the state of the environment variables
+SAVEDIR and SAVENAME.
+Any KILL files (see the K command in the Article Selection section)
+also reside in this directory and its subdirectories, by default.
+In addition, shell escapes leave you in this directory.
+.TP 5
+.B \-D<flags>
+enables debugging output.
+See common.h for flag values.
+Warning: normally
+.I trn
+attempts to restore your
+.I .newsrc
+when an unexpected signal or internal error occurs.
+This is disabled when any debugging flags are set.
+.TP 5
+.B \-e
+causes each page within an article to be started at the top of the screen,
+not just the first page.
+(It is similar to the
+.B \-c
+switch of
+.IR more (1).)
+You never have to read scrolling text with this switch.
+This is helpful especially at certain baud rates because you can start reading
+the top of the next page without waiting for the whole page to be printed.
+It works nicely in conjunction with the
+.B \-m
+switch, especially if you use half-intensity for your highlight mode.
+See also the
+.B \-L
+switch.
+.TP 5
+.B \-E<name>=<val>
+sets the environment variable <name> to the value specified.
+Within
+.IR trn ,
+\*(L"&\-ESAVENAME=%t\*(R" is similar to \*(L"setenv SAVENAME '%t'\*(R" in
+.IR csh ,
+or \*(L"SAVENAME='%t'; export SAVENAME\*(R" in
+.IR sh .
+Any environment variables set with
+.B \-E
+will be inherited by subprocesses of
+.IR trn .
+.TP 5
+.B \-f
+will make trn avoid various sleep calls and the prompt after the processing
+of the memorized commands that are intended to allow you time to read a
+message before the screen clears.
+This allows the advanced user to cruise along a little faster at the
+expense of readability.
+The \-t (terse) option turns on -f by default, but you can override this
+by specifying \+f after the \-t option.
+.TP 5
+.B \-F<string>
+sets the prefix string for the \*(L'F\*(R' follow-up command to use in
+prefixing each line of the quoted article.
+For example, \*(L"\-F<tab>\*(R" inserts a tab on the front of each line
+(which will cause long lines to wrap around, unfortunately),
+\*(L"\-F>>>>\*(R" inserts \*(L">>>>\*(R" on every line, and
+\*(L"\-F\*(R" by itself causes nothing to be inserted, in case you want to
+reformat the text, for instance.
+The initial default prefix is \*(L">\*(R".
+.TP 5
+.B \-g<line>
+tells
+.I trn
+which line of the screen you want searched-for strings to show up on when
+you search with the \*(L'g\*(R' command within an article.
+The lines are numbered starting with 1.
+The initial default is \*(L"\-g1\*(R", meaning the first line of the screen.
+Setting the line to less than 1 or more than the number of lines on the screen
+will set it to the last line of the screen.
+.TP 5
+.B \-G
+selects the "fuzzy" processing on the go command when you don't type in a
+valid group name.
+With this option on trn will attempt to find the group you probably meant
+to type, but it can be a little slow about it, so it's not on by default.
+.TP 5
+.B \-h<string>
+hides (disables the printing of) all header lines beginning with
+.I string.
+For instance, \-hexp will disable the printing of the \*(L"Expires:\*(R" line.
+Case is insignificant.
+If <string> is null, all header lines except Subject are hidden, and you
+may then use
+.B +h
+to select those lines you want to see.
+You may wish to use the baud-rate switch modifier below to hide more lines
+at lower baud rates.
+.TP 5
+.B \-H<string>
+works just like
+.B \-h
+except that instead of setting the hiding flag for a header line, it sets
+the magic flag for that header line.
+Certain header lines have magic behavior that can be controlled this way.
+At present, the following actions are caused by the flag for the particular
+line:
+the Date line prints the date in local time if the group is threaded;
+the From line will only print the commented portion of the user name;
+the Newsgroups line will only print when there are multiple newsgroups;
+the Subject line will be underlined and (when threaded) the
+keyword \*(L'Subject:\*(R' is replaced by its subject number (e.\|g. [1]);
+and the Expires line will always be suppressed if there is nothing on it.
+In fact, all of these actions are the default, and you must use
+.B +H
+to undo them.
+.TP 5
+.B \-i=<number>
+specifies how long (in lines) to consider the initial page of an
+article \*(-- normally this is determined automatically depending on baud rate.
+(Note that an entire article header will always be printed regardless of the
+specified initial page length.
+If you are working at low baud rate and wish to reduce the size of the
+headers, you may hide certain header lines with the
+.B \(bsh
+switch.)
+.TP 5
+.B \-I
+tells trn to append all new, unsubscribed groups to the end of the .newsrc.
+.TP 5
+.B \-j
+forces trn to leave control characters unmolested in messages.
+.TP 5
+.B \-l
+disables the clearing of the screen at the beginning of each
+article, in case you have a bizarre terminal.
+.TP 5
+.B \-L
+tells
+.I trn
+to leave information on the screen as long as possible by not blanking
+the screen between pages, and by using clear to end-of-line.
+(The
+.IR more (1)
+program does this.)
+This feature works only if you have the requisite termcap
+capabilities.
+The switch has no effect unless the
+.B \-e
+switch is set.
+.TP 5
+.B \-m=<mode>
+enables the marking of the last line of the previous page
+printed, to help the user see where to continue reading.
+This is most helpful when less than a full page is going to be displayed.
+It may also be used in conjunction with the
+.B \-e
+switch, in which case the page is erased, and the first line (which is
+the last line of the previous page) is highlighted.
+If
+.B \-m=s
+is specified, the standout mode will be used, but if
+.B \-m=u
+is specified, underlining will be used.
+If neither
+.B =s
+or
+.B =u
+is specified, standout is the default.
+Use
+.B +m
+to disable highlighting.
+.TP 5
+.B \-M
+forces mailbox format in creating new save files.
+Ordinarily you are asked which format you want.
+.TP 5
+.B \-N
+forces normal (non-mailbox) format in creating new save files.
+Ordinarily you are asked which format you want.
+.TP 5
+.B \-o
+will act like old versions of trn and not junk cross-referenced articles
+when using thread commands to junk articles in the current group (such as
+the selector's \*(L'X\*(R' command).
+.TP 5
+.B \-O<mode>{<order>}
+specifies the selector's mode and (optionally) the sort order.
+The modes are \*(L'a\*(R'rticle, \*(L's\*(R'ubject, or \*(L't\*(R'hread.
+The orders are \*(L'd\*(R'ate, \*(L's\*(R'ubject, \*(L'a\*(R'uthor,
+article \*(L'c\*(R'ount per group, or subject-date \*(L'g\*(R'roups.
+The order can be capitalized to reverse the indicated order.
+For example, to choose the article selector in subject order specify
+\*(L"-Oas\*(R".
+.TP 5
+.B \-p
+tells trn to auto-select your postings and their replies as it encounters them
+in the various groups you read.
+.TP 5
+.B \-q
+bypasses the automatic check for new newsgroups when starting 
+.IR trn .
+.TP 5
+.B \-r
+causes
+.I trn
+to restart in the last newsgroup read during a previous session with
+.IR trn .
+It is equivalent to starting up normally and then getting to the newsgroup
+with a g command.
+.TP 5
+.B \-s
+with no argument suppresses the initial listing of newsgroups with unread
+news, whether
+.B \-c
+is specified or not.
+Thus
+.B \-c
+and
+.B \-s
+can be used together to test \*(L"silently\*(R" the status of news from
+within your
+.I .login
+file.
+If
+.B \-s
+is followed by a number, the initial listing is suppressed after that many
+lines have been listed.
+Presuming that you have your
+.I .newsrc
+sorted into order of interest,
+.B \-s5
+will tell you the 5 most interesting newsgroups that have unread news.
+This is also a nice feature to use in your
+.I .login
+file, since it not only tells you whether there is unread news, but also how
+important the unread news is, without having to wade through the entire
+list of unread newsgroups.
+If no 
+.B \-s
+switch is given 
+.B \-s5
+is assumed, so just putting \*(L"rn \-c\*(R"
+into your
+\&.login file is fine.
+.TP 5
+.B \-S<number>
+causes
+.I trn
+to enter subject search mode (^N) automatically whenever an unthreaded
+newsgroup is
+started up with <number> unread articles or more.
+Additionally, it causes any \*(L'n\*(R' typed while in subject search mode
+to be interpreted as \*(L'^N\*(R' instead.
+(To get back out of subject search mode, the best command is probably
+\&\*(L'^\*(R'.)
+If <number> is omitted, 3 is assumed.
+.TP 5
+.B \-t
+puts
+.I trn
+into terse mode.
+This is more cryptic but useful for low baud rates.
+(Note that your system administrator may have compiled
+.I trn
+with either verbose or terse messages only to save memory.)
+You may wish to use the baud-rate switch modifier below to enable terse mode
+only at lower baud rates.
+.TP 5
+.B \-T
+allows you to type ahead of rn.
+Ordinarily rn will eat typeahead to prevent your autorepeating space bar from
+doing a very frustrating thing when you accidentally hold it down.
+If you don't have a repeating space bar, or you are working at low baud
+rate, you can set this switch to prevent this behavior.
+You may wish to use the baud-rate switch modifier below to disable typeahead
+only at lower baud rates.
+.TP 5
+.B \-u
+sets the unbroken-subject-line mode in the selector, which simply truncates
+subjects that are too long instead of dumping the middle portion prior to
+the last two words of the subject.
+.TP 5
+.B \-v
+sets verification mode for commands.
+When set, the command being executed is displayed to give some feedback that
+the key has actually been typed.
+Useful when the system is heavily loaded and you give a command that takes
+a while to start up.
+.TP 5
+.B \-x{<number>}{<list>}
+Enable the extended (threaded) features of
+.I trn
+beyond the
+.I rn
+compatibility mode
+(this may be the default on your system, use +x if you yearn for the good
+ol' days).
+The <number> is the maximum number of article-tree lines (from 0 to 11)
+you want displayed in your header.
+Use the <list> to choose which thread selector styles you like
+(\*(L's\*(R'hort, \*(L'm\*(R'edium, or \*(L'l\*(R'ong), and in what order
+they are selected with the \*(L'L\*(R' command.
+For example, use
+.B \-xms
+to start with the medium display mode and only switch between it and
+the short mode.
+You can omit either or both of the parameters, in which case a default of
+.B \-x6lms
+is assumed.
+.TP 5
+.B \-X{<number>}{<commands>}
+If you like using the selector, you'll probably want to use this
+option to make the selector command (+) the default when a newsgroup
+is started up with at least <number> unread articles.
+(Your installer may have chosen to make -X1 the default on your system.)
+It is also used to select which commands you want to be the defaults while
+using the thread selector.
+For example,
+.B \-X2XD
+will make the thread selector the default command for entering a newsgroup
+with at least 2 unread articles, and set the default command for the LAST
+page of the thread selector to be the
+.B X
+command and the default command for all other pages to be the
+.B D
+command.
+Either or both parameters can be omitted, as well as the second default
+command (e.\|g.
+.B \-XX
+would change the default newsgroup entry to use the selector and the default
+command for the last page of the selector to be \*(L'X\*(R').
+The default is
+.B \-X1Z>
+if just
+.B \-X
+is specified.
+To set the default selector commands without having \*(L'+\*(R' be the
+default entry into a newsgroup, specify a high number, like 9999.
+.TP 5
+.B \-/
+sets SAVEDIR to \*(L"%p/%c\*(R" and SAVENAME to \*(L"%a\*(R", which means
+that by default articles are saved in a subdirectory of your private news
+directory corresponding to the name of the the current newsgroup, with the
+filename being the article number.
+.B +/
+sets SAVEDIR to \*(L"%p\*(R" and SAVENAME to \*(L"%^C\*(R", which by
+default saves articles directly to your private news directory, with the
+filename being the name of the current newsgroup, first letter capitalized.
+(Either
+.B +/
+or
+.B \-/
+may be default on your system, depending on the feelings of your news
+administrator when he, she or it installed
+.IR trn .)
+You may, of course, explicitly set SAVEDIR and SAVENAME to other
+values \*(-- see discussion in the environment section.
+.PP
+Any switch may be selectively applied according to the current baud-rate.
+Simply prefix the switch with +speed to apply the switch at that speed or
+greater, and \%\-speed to apply the switch at that speed or less.
+Examples: \%\-1200\-hposted suppresses the Posted line at 1200 baud or less;
+\%+9600\-m enables marking at 9600 baud or more.
+You can apply the modifier recursively to itself also: \%+300\-1200\-t sets
+terse mode from 300 to 1200 baud.
+.PP
+Similarly, switches may be selected based on terminal type:
+.Sp
+       \-=vt100+T              set +T on vt100
+.br
+       \-=tvi920\-ETERM=mytvi  get a special termcap entry
+.br
+       \-=tvi920\-ERNMACRO=%./.rnmac.tvi
+.br
+                               set up special key-mappings
+.br
+       +=paper\-v              set verify mode if not hardcopy
+.PP
+Some switch arguments, such as environment variable values, may require
+spaces in them.
+Such spaces should be quoted via ", ', or \e in the conventional fashion,
+even when passed via TRNINIT or the & command.
+.Sh "Regular Expressions"
+The patterns used in article searching are regular expressions such as
+those used by
+.IR ed (1).
+In addition, \ew matches an alphanumeric character and \eW a non-alphanumeric.
+Word boundaries may be matched by \eb, and non-boundaries by \eB.
+The bracketing construct \e(\ ...\ \e) may also be used, and \edigit matches
+the digit'th substring, where digit can range from 1 to 9.
+\e0 matches whatever the last bracket match matched.
+Up to 10 alternatives may given in a pattern, separated by \e|, with the
+caveat that \e(\ ...\ \e|\ ...\ \e) is illegal.
+.Sh "Interpretation and Interpolation"
+Many of the strings that
+.I trn
+handles are subject to interpretations of several types.
+Under filename expansion, an initial \*(L"~/\*(R" is translated to the name
+of your home directory, and \*(L"~name\*(R" is translated to the login
+directory for the user specified.
+Filename expansion will also expand an initial environment variable, and
+also does the backslash, caret and percent expansion mentioned below.
+.PP
+All interpreted strings go through backslash, caret and percent
+interpretation.
+The backslash escapes are the normal ones (such as \en, \et, \e033, etc.).
+The caret escapes indicate control codes (such as ^i, ^l, etc.).
+If you wish to pass through a backslash or a caret it must be escaped with
+a backslash.
+The special percent escapes are similar to printf percent escapes.
+These cause the substitution of various run-time values into the string.
+The following are currently recognized:
+.Ip %a 8
+Current article number.
+.Ip %A 8
+Full name of current article (%P/%c/%a).
+.Ip %b 8
+Destination of last save command, often a mailbox.
+.Ip %B 8
+The byte offset to the beginning of the part of the article to be saved,
+set by the save command.
+The \*(L's\*(R' and \*(L'S\*(R' commands set it to 0, and the \*(L'w\*(R'
+and \*(L'W\*(R' commands set it to the byte offset of the body of the article.
+.Ip %c 8
+Current newsgroup, directory form.
+.Ip %C 8
+Current newsgroup, dot form.
+.Ip %d 8
+Full name of newsgroup directory (%P/%c).
+.Ip %D 8
+\*(L"Distribution:\*(R" line from the current article.
+.Ip %e 8
+The last command executed to extract data from an article.
+.Ip %E 8
+The last directory where an extracted file went.
+.Ip %f 8
+\*(L"From:\*(R" line from the current article, or the \*(L"Reply-To:\*(R"
+line if there is one.
+This differs from %t in that comments (such as the full name) are not
+stripped out with %f.
+.Ip %F 8
+\*(L"Newsgroups:\*(R" line for a new article, constructed from
+\*(L"Newsgroups:\*(R" and \*(L"Followup-To:\*(R" lines of current article.
+.Ip %h 8
+Name of the header file to pass to the mail or news poster,
+containing all the information that the poster program needs in the
+form of a message header.
+It may also contain a copy of the current article.
+The format of the header file is controlled by the MAILHEADER and NEWSHEADER
+environment variables.
+.Ip %H 8
+Host name (your machine's name).
+.Ip %i 8
+\*(L"Message-I.D.:\*(R" line from the current article, with <> guaranteed.
+.Ip %I 8
+The reference indication mark (see the
+.B \-F
+switch.)
+.Ip %l 8
+The news administrator's login name, if any.
+.Ip %L 8
+Login name (yours).
+.Ip %m 8
+The current mode of
+.I trn,
+for use in conditional macros.
+.Sp
+.nf
+       i       Initializing.
+       n       Newsgroup-selection level.
+       f       end (Finis) of newsgroup-selection level.
+       t       the Thread/subject/article selector.
+       a       Article level (What next?).
+       e       End of the article level.
+       p       Pager level (MORE prompt).
+       u       Set-unread prompt.
+       d       selector moDe prompt.
+       o       selector Order prompt.
+       m       Memorize thread command prompt.
+       r       memoRize subject command prompt.
+       k       processing memorized (KILL file) commands.
+       A       Add this newsgroup?
+       B       aBandon confirmation.
+       C       Catchup confirmation.
+       D       Delete bogus newsgroups?
+       F       Is follow-up a new topic?
+       M       Use mailbox format?
+       R       Resubscribe to this newsgroup?
+.fi
+.Sp
+Note that yes/no questions are all upper-case modes.
+If, for example, you wanted to disallow defaults on all yes/no questions,
+you could define the following macro:
+.Sp
+.nf
+\e040  %(%m=[A-Z]?h: )
+.fi
+.Ip %M 8
+The number of articles marked to return via the \*(L'M\*(R' command.
+If the same article is Marked multiple times, \*(L"%M\*(R" counts it
+multiple times in the current implementation.
+.Ip %n 8
+\*(L"Newsgroups:\*(R" line from the current article.
+.Ip %N 8
+Full name (yours).
+.Ip %o 8
+Organization (yours).
+.Ip %O 8
+Original working directory (where you ran rn from).
+.Ip %p 8
+Your private news directory, normally ~/News.
+.Ip %P 8
+Public news spool directory, normally /usr/spool/news on systems that don't use NNTP.
+.Ip %r 8
+Last reference on references line of current article (parent article id).
+.Ip %R 8
+References list for a new article, constructed from the references and article
+ID of the current article.
+.Ip %s 8
+Subject, with all Re's and (nf)'s stripped off.
+.Ip %S 8
+Subject, with one \*(L"Re:\*(R" stripped off.
+.Ip %t 8
+\*(L"To:\*(R" line derived from the \*(L"From:\*(R" and \*(L"Reply-To:\*(R"
+lines of the current article.
+This always returns an Internet format address.
+.Ip %T 8
+\*(L"To:\*(R" line derived from the \*(L"Path:\*(R" line of the
+current article to produce a uucp path.
+.Ip %u 8
+The number of unread articles in the current newsgroup.
+.Ip %U 8
+The number of unread articles in the current newsgroup, not counting the
+the current article.
+When threads are selected, this count reflects only selected articles.
+.Ip %v 8
+The number of unselected articles, not counting the current article
+if it is unselected.
+.Ip %w 8
+The directory where mthreads keeps its tmp files.
+.Ip %W 8
+The directory where thread files are placed.
+.Ip %x 8
+The news library directory.
+.Ip %X 8
+The rn library directory.
+.Ip %z 8
+The length of the current article in bytes.
+.Ip %Z 8
+The number of selected threads.
+.Ip %~ 8
+Your home directory.
+.Ip %. 8
+The directory containing your dot files, which is your home directory unless
+the environment variable DOTDIR is defined when rn is invoked.
+.Ip %# 8
+The current count for a multi-file save, starting with 1.
+This value is incremented by one for each file saved or extracted within a
+single command.
+.Ip %$ 8
+Current process number.
+.Ip %/ 8
+Last search string.
+.Ip %% 8
+A percent sign.
+.Ip "%{name} or %{name\-default}" 8
+The environment variable \*(L"name\*(R".
+.Ip %[name] 8
+The value of header line \*(L"Name:\*(R" from the current article.
+The \*(L"Name:\ \*(R" is not included.
+For example \*(L"%D\*(R" and \*(L"%[distribution]\*(R" are equivalent.
+The name must be spelled out in full.
+.Ip %`command` 8
+Inserts the output of the command, with any embedded newlines translated
+to space.
+.Ip %""prompt"" 8
+Prints prompt on the terminal, then inputs one string, and inserts it.
+.Ip "%(test_text=pattern?then_text:else_text)" 8
+If
+.I test_text
+matches
+.IR pattern ,
+has the value
+.IR then_text ,
+otherwise
+.IR else_text .
+The \*(L":else_text\*(R" is optional, and if absent, interpolates the null string.
+The = may be replaced with != to negate the test.
+To quote any of the meta-characters
+(\*(L'=\*(R', \*(L'?\*(R', \*(L':\*(R', or \*(L')\*(R'),
+precede with a backslash.
+.Ip %digit 8
+The digits 1 through 9 interpolate the string matched by the nth bracket
+in the last pattern match that had brackets.
+If the last pattern had alternatives, you may not know the number of the
+bracket you want \*(-- %0 will give you the last bracket matched.
+.PP
+Modifiers: to capitalize the first letter, insert \*(L'^\*(R':
+\*(L"%^C\*(R" produces something like \*(L"Rec.humor\*(R".
+Inserting \*(L'_\*(R' causes the first letter following the last
+\&\*(L'/\*(R' to be capitalized: \*(L"%_c\*(R" produces \*(L"rec/Humor\*(R".
+.PP
+Inserting \*(L'\\\*(R' will insert a backslash before any characters that
+would be magic in a regular expression, including \*(L'%\*(R':
+\*(L"%\\C\*(R" produces \*(L"rec\\.humor\*(R".
+.PP
+Inserting \*(L":FMT\*(R" will format the result according to the printf-style
+FMT string: \*(L"%:-50.50s\*(R" left-justifies the subject into a 50
+character field.
+.SH ENVIRONMENT
+The following environment variables are paid attention to by
+.IR trn .
+In general the default values assumed for these variables by
+.I trn
+are reasonable, so if you are using
+.I trn
+for the first time, you can safely ignore this section.
+Note that the defaults below may not correspond precisely to the defaults
+on your system.
+To find the actual defaults you would need to look in config.h and common.h
+in the trn source directory, and the file INIT in the trn library directory.
+.PP
+Those variables marked (%) are subject to % interpolation, and those marked
+(~) are subject to both % interpolation and ~ interpretation.
+.Ip "ATTRIBUTION (%)" 8
+Gives the format of the attribution line in front of the quoted article
+included by an F command.
+.Sp
+Default: In article %i %f writes:
+.Ip "AUTOSUBSCRIBE" 8
+When
+.I trn
+is checking for new newsgroups and finds one
+matching one of the patterns in AUTOSUBSCRIBE, the new group is
+automatically added to the end of the .newsrc, subscribed.
+Newsgroups not matching this or AUTOUNSUBSCRIBE, below, are offered
+to the user.
+.Sp
+AUTOSUBSCRIBE is a comma separated list of newsgroup patterns ala
+\&\*(L'o\*(R', \*(L'/\*(R', etc.
+It can also include \*(L"but not\*(R" entries preceded by \*(L'!\*(R'.
+\*(L"a,b,!c,d\*(R" is read as \*(L"matching a or b, unless it also
+matches c; matching d regardless\*(R".
+Another way to look at it is \*(L"(((a or b) and not c) or d)\*(R".
+To automatically subscribe to all local
+groups but be choosy about non-local groups, one might say \*(L"*,!*.*\*(R".
+.Sp
+Default: (none)
+.Ip "AUTOUNSUBSCRIBE" 8
+AUTOUNSUBSCRIBE is very similar to AUTOSUBSCRIBE, above, but
+new newsgroups matching it are automatically added to the end
+of the .newsrc file, unsubscribed.
+If a newsgroup matches AUTOSUBSCRIBE, AUTOUNSUBSCRIBE is not consulted.
+.Sp
+Default: (none)
+.Ip "CANCEL (~)" 8
+The shell command used to cancel an article.
+.Sp
+Default: inews \-h < %h
+.Ip "CANCELHEADER (%)" 8 13v
+The format of the file to pass to the CANCEL command in order to cancel
+an article.
+.Sp
+Default:
+.br
+Newsgroups: %n
+.br
+Subject: cmsg cancel %i
+.br
+References: %R
+.br
+Reply-To: %L@%H (%N)
+.br
+Distribution: %D
+.br
+Organization: %o
+.sp 1
+%i cancelled from rn.
+.Ip DOTDIR 8
+Where to find your dot files, if they aren't in your home directory.
+Can be interpolated using \*(L"%.\*(R".
+.Sp
+Default: $HOME
+.Ip "EDITOR (~)" 8
+The name of your editor, if VISUAL is undefined.
+.Sp
+Default: whatever your news administrator compiled in, usually vi.
+.Ip "EXSAVER (%)" 8
+The shell command to execute in order to extract data to either /bin/sh
+or a user-specified command.
+.Sp
+Default: tail +%Bc %A | %e
+.Ip "FIRSTLINE (%)" 8
+Controls the format of the line displayed at the top of an article.
+Warning: this may go away.
+.Sp
+The default (ignoring the Marked to return display in unthreaded
+groups) is approximately:
+.Sp
+%C #%a%(%Z=^0$?%(%U!=^0$? (%U more\e)): (%U + %v more\e))
+.Ip HIDELINE 8
+If defined, contains a regular expression which matches article lines to
+be hidden, in order, for instance, to suppress quoted material.
+A recommended string for this purpose is \*(L"^>...\*(R", which \fIdoesn't\fR
+hide lines with only \*(L'>\*(R', to give some indication that quoted
+material is being skipped.
+If you want to hide more than one pattern, you can use \*(L"\||\|\*(R" to
+separate the alternatives.
+You can view the hidden lines by restarting the article with the \*(L'v\*(R'
+command.
+.Sp
+There is some overhead involved in matching each line of the article against
+a regular expression.
+You might wish to use a baud-rate modifier to enable this feature only at
+low baud rates.
+.Sp
+Default: undefined
+.Ip HOME 8
+Your home directory.
+Affects ~ interpretation, and the location of your
+dot files if DOTDIR is not defined.
+.Sp
+Default: $LOGDIR
+.Ip "KILLGLOBAL (~)" 8
+Where to find the KILL file to apply to every newsgroup.
+See the \*(L'^K\*(R' command at the newsgroup-selection level.
+.Sp
+Default: %p/KILL
+.Ip "KILLLOCAL (~)" 8
+Where to find the KILL file for the current newsgroup.
+See the commands \*(L'K\*(R' and \*(L'^K\*(R' at the article selection level,
+and the search modifier \*(L'K\*(R'.
+.Sp
+Default: %p/%c/KILL
+.Ip LOGDIR 8
+Your home directory if HOME is undefined.
+Affects ~ interpretation, and the location of your
+dot files if DOTDIR is not defined.
+.Sp
+Default: none.
+.Sp
+Explanation: you must have either $HOME or $LOGDIR.
+.Ip LOGNAME 8
+Your login name, if USER is undefined.
+May be interpolated using \*(L"%L\*(R".
+.Sp
+Default: value of getlogin().
+.Ip LOCALTIMEFMT 8
+The format used by strftime() to print the local time.
+The Date line is only displayed in local time if the group is threaded
+(see the \-H option for more information on Date).
+.Sp
+Default: %a %b %e %X %Z %Y
+.Sp
+which is the same format as the
+.IR date (1)
+command.
+.Ip "MAILCALL (~)" 8
+What to say when there is new mail.
+.Sp
+Default: (Mail)
+.Ip "MAILFILE (~)" 8
+Where to check for mail.
+.Sp
+Default: /usr/spool/mail/%L
+.Ip "MAILHEADER (%)" 8
+The format of the header file for replies.
+See also MAILPOSTER.
+.Sp
+Default:
+.Sp
+To: %t
+.br
+Subject: %(%i=^$?:Re: %S
+.br
+Newsgroups: %n
+.br
+In-Reply-To: %i)
+.br
+%(%[references]!=^$?References\\: %[references]
+.br
+)Organization: %o
+.br
+Cc: 
+.br
+Bcc: \en\en
+.Ip "MAILPOSTER (~)" 8
+The shell command to be used by the reply commands (r and R)
+in order to allow you to enter and deliver the response.
+.I trn
+will not itself call upon an editor for replies \*(-- this
+is a function of the program called by
+.IR trn .
+See also MAILHEADER.
+.Sp
+Default: QUOTECHARS=%I Rnmail \-h %h
+.Ip "MBOXSAVER (~)" 8
+The shell command to save an article in mailbox format.
+.Sp
+Default: %X/mbox.saver %A %P %c %a %B %C "%b" \e
+.br
+"From %t %`date`"
+.Sp
+Explanation: the first seven arguments are the same as for NORMSAVER.
+The eighth argument to the shell script is the new From line
+for the article, including the posting date,
+derived either directly from the Posted: line, or not-so-directly from
+the Date: line.
+Header munging at its finest.
+.Ip MODSTRING 8
+The string to insert in the group summary line, which heads each article,
+for a moderated group.
+See also NOPOSTRING.
+.Sp
+Default: " (moderated)"
+.Ip NAME 8
+Your full name.
+May be interpolated using \*(L"%N\*(R".
+.Sp
+Default: name from /etc/passwd, or ~/.fullname.
+.Ip "NEWSHEADER (%)" 8 16v
+The format of the header file for follow-ups.
+See also NEWSPOSTER.
+.Sp
+Default:
+.Sp
+%(%[followup-to]=^$?:X-ORIGINAL-NEWSGROUPS: %n
+.br
+)Newsgroups: %(%F=^$?%C:%F)
+.br
+Subject: %(%S=^$?%"\en\enSubject: ":Re: %S)
+.br
+Summary:
+.br
+Expires: 
+.br
+%(%R=^$?:References: %R
+.br
+)Sender: 
+.br
+Followup-To: 
+.br
+%(%{REPLYTO}=^$?:Reply-To: %{REPLYTO}
+.br
+)Distribution: %(%i=^$?%"Distribution: ":%D)
+.br
+Organization: %o
+.br
+Keywords: %[keywords]
+.br
+Cc: \en\en
+.Ip NEWSORG 8
+Either the name of your organization, or the name of a file containing the
+name of your organization.
+(For use at sites where the ORGANIZATION environmental variable is already
+in use.
+NEWSORG will override ORGANIZATION if both are present.)
+May be interpolated using \*(L"%o\*(R".
+.Sp
+Default: whatever your news administrator compiled in.
+.Ip "NEWSPOSTER (~)" 8
+The shell command to be used by the follow-up commands (f and F)
+in order to allow you to enter and post a follow-up news article.
+.I trn
+will not itself call upon an editor for follow-ups \*(-- this
+is a function of the program called by
+.IR trn .
+See also NEWSHEADER.
+.Sp
+Default: QUOTECHARS=%I Pnews \-h %h
+.Ip NEWSRC 8
+Your newsgroup subscription list.
+.Sp
+Default: $HOME/.newsrc
+.Ip NNTPSERVER 8
+The hostname of your NNTPSERVER.
+[This does not apply unless you are running the NNTP version of rn.]
+.Sp
+Default: the hostname listed in the server file, usually
+/usr/local/lib/rn/server.
+.Ip NOPOSTRING 8
+The string to insert in the group summary line, which heads each article,
+for a group to which local posting is not allowed.
+See also MODSTRING.
+.Sp
+Default: " (no posting)"
+.Ip "NORMSAVER (~)" 8
+The shell command to save an article in the normal (non-mailbox) format.
+.Sp
+Default: %X/norm.saver %A %P %c %a %B %C "%b"
+.Ip ORGANIZATION 8
+Either the name of your organization, or the name of a file containing the
+name of your organization.
+(If NEWSORG is set, it will override ORGANIZATION.)
+May be interpolated using \*(L"%o\*(R".
+.Sp
+Default: whatever your news administrator compiled in.
+.Ip PAGESTOP 8
+If defined, contains a regular expression which matches article lines to
+be treated as form-feeds.
+There are at least two things you might want to do with this.
+To cause page breaks between articles in a digest, you might define it
+as \*(L"^--------\*(R".
+To force a page break before a signature, you could define it
+as \*(L"^-- $\*(R".
+(Then, when you see \*(L"--\*(R" at the bottom of the page, you can skip
+the signature if you so desire by typing \*(L'n\*(R' instead of space.)
+To do both, you could use \*(L"^--\*(R".
+If you want to break on more than one pattern, you can use \*(L"\||\|\*(R" to
+separate the alternatives.
+.Sp
+There is some overhead involved in matching each line of the article against
+a regular expression.
+You might wish to use a baud-rate modifier to enable this feature only at
+low baud rates.
+.Sp
+Default: undefined
+.Ip "PIPESAVER (%)" 8
+The shell command to execute in order to accomplish a save to a pipe
+(\*(L"s\ |\ command\*(R" or \*(L"w\ |\ command\*(R").
+The command typed by the user is substituted in as %b.
+.Sp
+Default: %(%B=^0$?<%A:tail +%Bc %A |) %b
+.Sp
+Explanation: if %B is 0, the command is \*(L"<%A %b\*(R", otherwise
+the command is \*(L"tail +%Bc %A | %b\*(R".
+.Ip REPLYTO 8
+The value of the \*(L"Reply-To:\*(R" header, if needed.
+.Sp Default: \*(L" \*(R".
+.Ip RNINIT 8
+This variable is used when initializing trn in rn-compatibility mode
+(see the \-x switch) or when the TRNINIT variable isn't defined.
+See the TRNINIT variable for a description.
+.Ip "RNMACRO (~)" 8
+The name of the file containing macros and key mappings when running trn
+as rn.
+See also the TRNMACRO variable and the CUSTOM MACROS section.
+.Sp
+Default: %./.rnmac
+.Ip "SAVEDIR (~)" 8
+The name of the directory to save to, if the save command does not specify
+a directory name.
+.Sp
+Default:
+.br
+   If
+.B \-/
+is set: %p/%c
+.br
+   If
+.B +/
+is set: %p
+.Ip "SAVENAME (%)" 8
+The name of the file to save to, if the save command contains only a
+directory name.
+.Sp
+Default:
+.br
+   If
+.B \-/
+is set: %a
+.br
+   If
+.B +/
+is set: %^C
+.Ip "SELECTCHARS" 8
+The characters used by the thread selector to select the associated thread
+of discussion.
+You can specify up to 64 visible characters, including upper- and lower-case
+letters, numbers, and many punctuation characters.
+Selection characters override command characters in the selector, but are
+not excluded from macro expansion, so be careful.
+.br
+Default: abdefgijlorstuvwxyz1234567890BCFGHIKMVW
+.br
+(You'll notice various characters are omitted to allow them to be typed
+as commands in the selector.)
+.Ip SHELL 8
+The name of your preferred shell.
+It will be used by the \*(L'!\*(R', \*(L'S\*(R' and \*(L'W\*(R' commands.
+.Sp
+Default: whatever your news administrator compiled in.
+.Ip "SUBJLINE (%)" 8
+Controls the format of the lines displayed by the \*(L'=\*(R' command at
+the article selection level.
+.Sp
+Default: %s
+.Ip "SUPERSEDEHEADER (%)" 8 16v
+The format of the header file for a supersede article.
+.Sp
+Default:
+.Sp
+From: %L@%H (%N)
+.br
+Newsgroups: %n
+.br
+Subject: %S
+.br
+Distribution: %D
+.br
+Organization: %o
+.br
+Supersedes: %i
+.Ip TERM 8
+Determines which termcap entry to use, unless TERMCAP contains the entry.
+.Ip TERMCAP 8
+Holds either the name of your termcap file, or a termcap entry.
+.Sp
+Default: /etc/termcap, normally.
+.Ip TRNINIT 8
+Default values for switches may be passed to
+.I trn
+by placing them in the TRNINIT variable (or RNINIT if you're starting
+trn in rn-compatibility mode).
+Any switch that is set in this way may be overruled 
+on the command line, or via the \*(L'&\*(R' command from within
+.IR trn .
+Binary-valued switches that are set with \*(L"\-switch\*(R" may be unset
+using \*(L"+switch\*(R".
+.Sp
+If TRNINIT begins with a \*(L'/\*(R' it is assumed to be the name of a file
+containing switches.
+You can put comments in this file by preceding them with a \*(L'#\*(R'
+as long as this is the first character on a line or it follows some
+white-space (which delimits the switches in the file).
+If you want to set many environment variables but don't want to keep
+them all in your environment, or if the use of any of these variables
+conflicts with other programs, you can use this feature along with the
+.B \-E
+switch to set the environment variables upon startup.
+.Sp
+Default: \*(L" \*(R".
+.Ip "TRNMACRO (~)" 8
+The name of the file containing macros and key mappings.
+If the file is not found, the RNMACRO variable is used to look for your
+rn macros.
+For information on what to put into this file, see the CUSTOM MACROS section.
+.Sp
+Default: %./.trnmac
+.Ip "UNSHAR (~)" 8
+The shell command to execute in order to accomplish the unshar'ing of a
+shell archive.
+.Sp
+Default: /bin/sh
+.Ip USER 8
+Your login name.
+May be interpolated using \*(L"%L\*(R".
+.Sp
+Default: $LOGNAME
+.Ip "VISUAL (~)" 8
+The name of your editor.
+.Sp
+Default: $EDITOR
+.Ip "YOUSAID (%)" 8
+Gives the format of the attribution line in front of the quoted article
+included by an R command.
+.Sp
+Default: In article %i you write:
+.SH "AUTOMATIC MACROS"
+On startup
+.I trn
+attempts to build a set of macros that map your keypad arrow keys to
+useful functions.
+These default actions are mentioned in the prior description of each level's
+commands.
+If you don't like this (or trn gets it wrong), you can disable the automatic
+macros by using the
+.B \-A
+option.
+.SH "CUSTOM MACROS"
+When
+.I trn
+starts up it looks for a file containing macro definitions (see environment
+variables TRNMACRO and RNMACRO).
+Any sequence of commands may be bound to any sequence of keys, so you
+could re-map your entire keyboard if you desire.
+Blank lines or lines beginning with # in the macro file are considered
+comments; otherwise
+.I trn
+looks for two fields separated by white space.
+The first field gives the sequence of keystrokes that trigger the macro,
+and the second field gives the sequence of commands to execute.
+Both fields are subject to % interpolation, which will also translate
+backslash and caret sequences.
+(The keystroke field is interpreted at startup time, but the command field
+is interpreted at macro execution time so that you may refer to % values
+in a macro.)
+For example, if you want to reverse the roles of carriage return and
+space in
+.I trn
+.Sp
+^J     \e040
+.br
+^M     \e040
+.br
+\e040  ^J
+.Sp
+will do just that.
+By default, all characters in the command field are interpreted as the
+canonical
+.I trn
+characters, i.\|e. no macro expansion is done.
+Otherwise the above pair of macros would cause an infinite loop.
+To force macro expansion in the command field, enclose the
+macro call with ^( ... ^) thusly:
+.Sp
+@s     |mysavescript
+.br
+@w     w^(@s^)
+.Sp
+You can use the %() conditional construct to construct macros that work
+differently under different circumstances.
+In particular, the current mode (%m) of
+.I trn
+could be used to make a command that only works at a particular level.
+This is particularly vital for the selector which uses most of
+the lower-case letters to select the associated item in its display.
+For example,
+.Sp
+a      %(%m=t?a:s art.hold\en)
+.Sp
+will return the original letter (a) in the selector, and the command
+\*(L"s art.hold\en\*(R" everywhere else.
+.Sp
+%(%{TERM}=vt100?^[[O)  /^J
+.Sp
+will do the binding only if the terminal type is vt100,
+though if you have many of these it would be better to have separate
+files for each terminal.
+.Sp
+If you want to bind a macro to a function key that puts a common garbage character
+after the sequence (such as the carriage return on the end of Televideo 920
+function sequences), DO NOT put the carriage return
+into all the sequences or you will waste a CONSIDERABLE amount of internal
+storage.
+Instead of \*(L"^AF^M\*(R", put \*(L"^AF+1\*(R", which indicates to
+.I trn
+that it should gobble up one character after the F.
+.SH "WHAT'S NEW"
+Here's a quick run-down of
+.IR trn 's
+features and commands aimed at the knowledgeable
+.I rn
+or
+.I trn
+user.
+.PP
+The addition of true reference-line threading is one of the biggest
+improvements over rn.
+This threading allows you to read a discussion in reply order with
+an article's replies being attached to the article that inspired them.
+Threads will encompass multiple subjects whenever a reply to an article
+in the thread arrives with a different subject.
+This is usually done to better indicate the topic in the reply
+when it diverges from the original subject.
+.PP
+Another big improvement is the selector, which is bound
+to the \*(L'+\*(R' key.
+The selector displays a list of threads, subjects, or individual articles
+to allow you to select the topics that interest you by typing their
+associated letter.
+The difference between the thread and the subject selector is that the
+subject selector displays all subjects with a separate selection letter,
+even those tied together via their references.
+This can be quite useful if you select some threads and desire to weed
+out some extraneous discussions: you could switch the selector into
+exclusive mode (\*(L'E\*(R' shows only selected threads) and then into
+subject mode (\*(L'Ss\*(R') to separate the threads into their component
+subjects and deselect or kill the subjects you don't care about.
+You don't have to go to all this trouble using the selector if you prefer
+to just hit the \*(L'k\*(R' key when you start reading a subject you're not
+interested in.
+The selector can also switch between showing unread articles and
+articles that have already been read, allowing you to selectively re-read
+discussions (this is the \*(L'U\*(R' command in the selector).
+.PP
+Another threaded addition is the article-tree display in the
+upper-right corner of the header.
+Looking at the tree gives you a feel for how the articles you are
+reading relate to each other, allowing you to see at a glance when
+there are lots of replies and decide if you want to junk an uninteresting
+set of replies or perhaps tough it out.
+.PP
+The header display has also been modified to hide a few more lines by default
+(e.\|g. References), but, as always, you can override these with \-h.
+There is also some more \*(L"magic\*(R" in the header: the From header can
+be trimmed to be just the comment portion (if available), and the Date
+header is displayed in local time (by default).
+Use \-H and +H to turn header magic on and off.
+.PP
+Once you begin reading articles, use the regular movement commands (n, N,
+p, P, etc.) as you normally would.
+You'll find that these commands track the reply order shown in the tree
+display.
+Then try using ^N and ^P, which follow a subject in the order the articles
+were posted.
+Finally, check out the [, ], (, ), {, and } commands to move around in the
+article tree a bit more directly.
+The first four commands should also be bound to your keypad's arrow keys,
+making them easier to type.
+For example, typing \*(L'[\*(R' (left) takes you to your parent article,
+even if it was already read, which is very useful for tracking down the
+cited portion of the article in its original context.
+.PP
+There are additional kill commands for the entire thread (J) and the
+current article and all its replies (,).
+.PP
+The KILL files have been extended and the commands inside them are now
+referred to memorized commands, since they are often used for selection
+rather than killing of articles.
+There are new, easier ways to add memorized commands using the \*(L'A\*(R'dd
+and \*(L'T\*(R'hread commands.
+The \*(L'A\*(R' command is subject-oriented, while the \*(L'T\*(R' command
+is article-oriented (meaning they affect a specific set of articles rather
+than any article that happens to have a matching subject).
+They both prompt you for what kind of command you want to add,
+making both auto-killing and auto-selecting just as easy.
+.PP
+There is also an easy way to skip around among the various threads with
+the < and > commands.
+Use them if you want to skip a set of article and read them later instead
+of junking them.
+.PP
+Note: your news administrator has the option of turning thread processing
+off for individual groups, and thus it is possible for some groups to not
+have any pre-processed thread information available for use.
+When
+.I trn
+encounters such a group, it generates the thread information on the fly
+while entering the group.
+For really large groups (or really slow systems), this can take an
+appreciable amount of time.
+If you can't talk your news administrator into pre-threading the group,
+you can turn off the threading on a group-by-group basis using
+the \*(L't\*(R' command at the newsgroup-selection level.
+Groups turned off in this way are read in the
+.I rn
+style \*(-- articles arranged in arrival order unless you specify the
+\-S option, which reads the articles in date order by subject.
+.PP
+Take note of the \*(L"e dir\*(R" command, which is used to extract a shell
+archive or uuencoded file into the specified directory.
+It is even possible to extract other data formats if you specify the
+appropriate filter command (e.\|g. \*(L"e dir|cmd\*(R".
+.PP
+Also, if you plan to use macro definitions, it is good to keep in mind
+that the selector uses most of the lower-case letters for
+selection, and thus it is a good idea to explicitly set the mode(s) in
+which a macro applies.
+For example, if you want to press \*(L'f\*(R' from the article pager/selector
+to forward the current article to the user \*(L"smith\*(R", you could define:
+.Sp
+.nf
+       f       %(%m=[pa]?|mail smith\en:f)
+.fi
+.Sp
+This checks the current mode (%m) and if it is \*(L'p\*(R' or \*(L'a\*(R'
+it expands it to the string \*(L"|mail smith\en\*(R", otherwise it returns
+the letter \*(L'f\*(R'.
+In some cases, you may simply wish to exclude the selector from a
+macro with the conditional \*(L"%m!=t\*(R".
+.PP
+Finally, you'll probably want to use the new options,
+.B \-x
+and
+.B \-X
+to ensure that all the newest features are available for use.
+These options might be on by default, depending on how your administrator
+decided to install
+.IR trn .
+.SH AUTHORS
+Rn was created by Larry Wall <lwall@jpl-devvax.jpl.nasa.gov>
+.br
+and is now under the direction of Stan Barber <sob@bcm.tmc.edu>.
+.br
+Threaded version by Wayne Davison <davison@borland.com>
+.br
+(Mail all bug reports for trn to Wayne.)
+.br
+Regular expression routines are borrowed from emacs, by James Gosling.
+.SH FILES
+.Ip "%./.newsrc" 1.25i
+status of your news reading
+.Ip "%./.oldnewsrc" 1.25i
+backup copy of your
+.I .newsrc
+from start of session
+.Ip "%./.rnlock" 1.25i
+lock file so you don't screw up your
+.I .newsrc
+.Ip "%./.rnlast" 1.25i
+info from last run of rn
+.Ip "%./.rnsoft" 1.25i
+soft pointers into /usr/lib/news/active to speed startup, synchronous with
+.I .newsrc
+.Ip "%./.rnhead" 1.25i
+temporary header file to pass to a mailer or news poster
+.Ip "%./.[t]rnmac" 1.25i
+macro and keymap definitions
+.Ip "%p" 1.25i
+your news save directory, usually ~/News
+.Ip "%x/active" 1.25i
+the list of active newsgroups, usually /usr/lib/news/active on systems that don't use NNTP
+.Ip "%P" 1.25i
+the public news spool directory, usually /usr/spool/news on systems that don't use NNTP
+.Ip "%X/INIT" 1.25i
+system-wide default switches
+.SH SEE ALSO
+newsrc(5), more(1), readnews(1), Pnews(1), Rnmail(1)
+.SH DIAGNOSTICS
+Generally self-documenting, as they say.
+.SH BUGS
+The
+.B \-h
+switch can only hide header lines that
+.I trn
+knows about.
+.PP
+The \*(L'\-\*(R' command doesn't cross newsgroup boundaries, and only undoes
+the last article selection.
+.PP
+If you edit your
+.I .newsrc
+while
+.I trn
+is running,
+.I trn
+will happily wipe out your changes when it decides to
+write out the
+.I .newsrc
+file.
+.PP
+Marking of duplicate articles as read in cross-referenced newsgroups will
+not work unless the Xref patch is installed in inews.
+.PP
+If you get carried away with % or escape substitutions, you can overflow
+buffers.