home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume25 / trn / part07 / rt-rn.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-12-02  |  21.4 KB  |  991 lines

  1. /* $Id: rt-rn.c,v 4.4.3.1 1991/11/22 04:12:18 davison Trn $
  2. **
  3. ** $Log: rt-rn.c,v $
  4. ** Revision 4.4.3.1  1991/11/22  04:12:18  davison
  5. ** Trn Release 2.0
  6. ** 
  7. */
  8.  
  9. #include "EXTERN.h"
  10. #include "common.h"
  11. #include "term.h"
  12. #include "final.h"
  13. #include "util.h"
  14. #include "bits.h"
  15. #include "artio.h"
  16. #include "ng.h"
  17. #include "ngdata.h"
  18. #include "search.h"
  19. #include "artstate.h"
  20. #include "backpage.h"
  21.  
  22. #ifdef USETHREADS
  23.  
  24. #include "threads.h"
  25. #include "rthreads.h"
  26.  
  27. static void find_depth(), cache_tree(), display_tree();
  28. static char letter();
  29.  
  30. /* Find the article structure information based on article number.
  31. */
  32. void
  33. find_article(artnum)
  34. ART_NUM artnum;
  35. {
  36.     register PACKED_ARTICLE *article;
  37.     register int i;
  38.  
  39.     if (!p_articles) {
  40.     p_art = Nullart;
  41.     return;
  42.     }
  43.  
  44.     if (!p_art) {
  45.     p_art = p_articles;
  46.     }
  47.     /* Start looking for the article num from our last known spot in the array.
  48.     ** That way, if we already know where we are, we run into ourselves right
  49.     ** away.
  50.     */
  51.     for (article=p_art, i=p_art-p_articles; i < total.article; article++,i++) {
  52.     if (article->num == artnum) {
  53.         p_art = article;
  54.         return;
  55.     }
  56.     }
  57.     /* Didn't find it, so search the ones before our current position.
  58.     */
  59.     for (article = p_articles; article != p_art; article++) {
  60.     if (article->num == artnum) {
  61.         p_art = article;
  62.         return;
  63.     }
  64.     }
  65.     p_art = Nullart;
  66. }
  67.  
  68. static char tree_indent[] = {
  69.     ' ', 0,
  70.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  71.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  72.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  73.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  74.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  75.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  76.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  77.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  78.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  79.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  80.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  81.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  82.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0,
  83.     ' ', ' ', ' ', ' ', 0,   ' ', ' ', ' ', ' ', 0
  84. };
  85.  
  86. char letters[] = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?";
  87.  
  88. static PACKED_ARTICLE *tree_article;
  89.  
  90. static int max_depth, max_line = -1;
  91. static int first_depth, first_line;
  92. static int my_depth, my_line;
  93. static bool node_on_line;
  94. static int node_line_cnt;
  95.  
  96. static int line_num;
  97. static int header_indent;
  98.  
  99. static char *tree_lines[11];
  100. static char tree_buff[128], *str;
  101.  
  102. /* Prepare tree display for inclusion in the article header.
  103. */
  104. void
  105. init_tree()
  106. {
  107.     register PACKED_ARTICLE *article;
  108.  
  109.     while (max_line >= 0) {        /* free any previous tree data */
  110.     free(tree_lines[max_line--]);
  111.     }
  112.     tree_article = curr_p_art;
  113.  
  114.     if (!curr_p_art) {
  115.     return;
  116.     }
  117.     article = p_articles + p_roots[curr_p_art->root].articles;
  118.  
  119.     max_depth = max_line = my_depth = my_line = node_line_cnt = 0;
  120.     find_depth(article, 0);
  121.  
  122.     if (max_depth <= 5) {
  123.     first_depth = 0;
  124.     } else {
  125.     if (my_depth+2 > max_depth) {
  126.         first_depth = max_depth - 5;
  127.     } else if ((first_depth = my_depth - 3) < 0) {
  128.         first_depth = 0;
  129.     }
  130.     max_depth = first_depth + 5;
  131.     }
  132.     if (--max_line < max_tree_lines) {
  133.     first_line = 0;
  134.     } else {
  135.     if (my_line + max_tree_lines/2 > max_line) {
  136.         first_line = max_line - (max_tree_lines-1);
  137.     } else if ((first_line = my_line - (max_tree_lines-1)/2) < 0) {
  138.         first_line = 0;
  139.     }
  140.     max_line = first_line + max_tree_lines-1;
  141.     }
  142.  
  143.     str = tree_buff;        /* initialize first line's data */
  144.     *str++ = ' ';
  145.     node_on_line = FALSE;
  146.     line_num = 0;
  147.     /* cache our portion of the tree */
  148.     cache_tree(article, 0, tree_indent);
  149.  
  150.     max_depth = (max_depth-first_depth) * 5;    /* turn depth into char width */
  151.     max_line -= first_line;            /* turn max_line into count */
  152.     /* shorten tree if lower lines aren't visible */
  153.     if (node_line_cnt < max_line) {
  154.     max_line = node_line_cnt + 1;
  155.     }
  156. }
  157.  
  158. /* A recursive routine to find the maximum tree extents and where we are.
  159. */
  160. static void
  161. find_depth(article, depth)
  162. PACKED_ARTICLE *article;
  163. int depth;
  164. {
  165.     if (depth > max_depth) {
  166.     max_depth = depth;
  167.     }
  168.     for (;;) {
  169.     if (article == tree_article) {
  170.         my_depth = depth;
  171.         my_line = max_line;
  172.     }
  173.     if (article->child_cnt) {
  174.         find_depth(article+1, depth+1);
  175.     } else {
  176.         max_line++;
  177.     }
  178.     if (!article->siblings) {
  179.         break;
  180.     }
  181.     article += article->siblings;
  182.     }
  183. }
  184.  
  185. /* Place the tree display in a maximum of 11 lines x 6 nodes.
  186. */
  187. static void
  188. cache_tree(article, depth, cp)
  189. PACKED_ARTICLE *article;
  190. int depth;
  191. char *cp;
  192. {
  193.     int depth_mode;
  194.  
  195.     cp[1] = ' ';
  196.     if (depth >= first_depth && depth <= max_depth) {
  197.     cp += 5;
  198.     depth_mode = 1;
  199.     } else if (depth+1 == first_depth) {
  200.     depth_mode = 2;
  201.     } else {
  202.     cp = tree_indent;
  203.     depth_mode = 0;
  204.     }
  205.     for (;;) {
  206.     switch (depth_mode) {
  207.     case 1: {
  208.         char ch;
  209.  
  210.         *str++ = (article->flags & ROOT_ARTICLE)? ' ' : '-';
  211.         if (article == tree_article) {
  212.         *str++ = '*';
  213.         }
  214.         if (was_read(article->num)) {
  215.         *str++ = '(';
  216.         ch = ')';
  217.         } else {
  218.         *str++ = '[';
  219.         ch = ']';
  220.         }
  221.         if (article == recent_p_art && article != tree_article) {
  222.         *str++ = '@';
  223.         }
  224.         *str++ = letter(article);
  225.         *str++ = ch;
  226.         if (article->child_cnt) {
  227.         *str++ = (article->child_cnt == 1)? '-' : '+';
  228.         }
  229.         if (article->siblings) {
  230.         *cp = '|';
  231.         } else {
  232.         *cp = ' ';
  233.         }
  234.         node_on_line = TRUE;
  235.         break;
  236.     }
  237.     case 2:
  238.         *tree_buff = (!article->child_cnt)? ' ' :
  239.         (article->child_cnt == 1)? '-' : '+';
  240.         break;
  241.     default:
  242.         break;
  243.     }
  244.     if (article->child_cnt) {
  245.         cache_tree(article+1, depth+1, cp);
  246.         cp[1] = '\0';
  247.     } else {
  248.         if (!node_on_line && first_line == line_num) {
  249.         first_line++;
  250.         }
  251.         if (line_num >= first_line) {
  252.         if (str[-1] == ' ') {
  253.             str--;
  254.         }
  255.         *str = '\0';
  256.         tree_lines[line_num-first_line]
  257.             = safemalloc(str-tree_buff + 1);
  258.         strcpy(tree_lines[line_num - first_line], tree_buff);
  259.         if (node_on_line) {
  260.             node_line_cnt = line_num - first_line;
  261.         }
  262.         }
  263.         line_num++;
  264.         node_on_line = FALSE;
  265.     }
  266.     if (!article->siblings || line_num > max_line) {
  267.         break;
  268.     }
  269.     article += article->siblings;
  270.     if (!article->siblings) {
  271.         *cp = '\\';
  272.     }
  273.     if (!first_depth) {
  274.         tree_indent[5] = ' ';
  275.     }
  276.     strcpy(tree_buff, tree_indent+5);
  277.     str = tree_buff + strlen(tree_buff);
  278.     }
  279. }
  280.  
  281. /* Output a header line with possible tree display on the right hand side.
  282. ** Does automatic wrapping of lines that are too long.
  283. */
  284. int
  285. tree_puts(orig_line, header_line, use_underline)
  286. char *orig_line;
  287. ART_LINE header_line;
  288. int use_underline;
  289. {
  290.     char *buf;
  291.     register char *line, *cp, *end;
  292.     int pad_cnt, wrap_at;
  293.     ART_LINE start_line = header_line;
  294.     int i;
  295.     char ch;
  296.  
  297.     /* Make a modifiable copy of the line */
  298.     buf = safemalloc(strlen(orig_line) + 2);  /* yes, I mean "2" */
  299.     strcpy(buf, orig_line);
  300.     line = buf;
  301.  
  302.     /* Change any embedded control characters to spaces */
  303.     for (end = line; *end && *end != '\n'; end++) {
  304.     if ((unsigned char)*end < ' ') {
  305.         *end = ' ';
  306.     }
  307.     }
  308.     *end = '\0';
  309.  
  310.     if (!*line) {
  311.     strcpy(line, " ");
  312.     end = line+1;
  313.     }
  314.  
  315.     /* If this is the first subject line, output it with a preceeding [1] */
  316.     if (use_underline && curr_p_art && (unsigned char)*line > ' ') {
  317. #ifdef NOFIREWORKS
  318.     no_sofire();
  319. #endif
  320.     standout();
  321.     putchar('[');
  322.     putchar(letter(curr_p_art));
  323.     putchar(']');
  324.     un_standout();
  325.     putchar(' ');
  326.     header_indent = 4;
  327.     line += 9;
  328.     i = 0;
  329.     } else {
  330.     if (*line != ' ') {
  331.         /* A "normal" header line -- output keyword and set header_indent
  332.         ** _except_ for the first line, which is a non-standard header.
  333.         */
  334.         if (!header_line || !(cp = index(line, ':')) || *++cp != ' ') {
  335.         header_indent = 0;
  336.         } else {
  337.         *cp = '\0';
  338.         fputs(line, stdout);
  339.         putchar(' ');
  340.         header_indent = ++cp - line;
  341.         line = cp;
  342.         }
  343.         i = 0;
  344.     } else {
  345.         /* Skip whitespace of continuation lines and prepare to indent */
  346.         while (*++line == ' ') {
  347.         ;
  348.         }
  349.         i = header_indent;
  350.     }
  351.     }
  352.     for (; *line; i = header_indent) {
  353. #ifdef CLEAREOL
  354.     maybe_eol();
  355. #endif
  356.     if (i) {
  357.         putchar('+');
  358.         while (--i) {
  359.         putchar(' ');
  360.         }
  361.     }
  362.     /* If no (more) tree lines, wrap at COLS-1 */
  363.     if (max_line < 0 || header_line > max_line+1) {
  364.         wrap_at = COLS-1;
  365.     } else {
  366.         wrap_at = COLS - max_depth - 5 - 3;
  367.     }
  368.     /* Figure padding between header and tree output, wrapping long lines */
  369.     pad_cnt = wrap_at - (end - line + header_indent);
  370.     if (pad_cnt <= 0) {
  371.         cp = line + wrap_at - header_indent - 1;
  372.         pad_cnt = 1;
  373.         while (cp > line && *cp != ' ') {
  374.         if (*--cp == ',' || *cp == '.' || *cp == '-' || *cp == '!') {
  375.             cp++;
  376.             break;
  377.         }
  378.         pad_cnt++;
  379.         }
  380.         if (cp == line) {
  381.         cp += wrap_at - header_indent;
  382.         pad_cnt = 0;
  383.         }
  384.         ch = *cp;
  385.         *cp = '\0';
  386.         /* keep rn's backpager happy */
  387.         vwtary(artline, vrdary(artline - 1));
  388.         artline++;
  389.     } else {
  390.         cp = end;
  391.         ch = '\0';
  392.     }
  393.     if (use_underline) {
  394.         underprint(line);
  395.     } else {
  396.         fputs(line, stdout);
  397.     }
  398.     *cp = ch;
  399.     /* Skip whitespace in wrapped line */
  400.     while (*cp == ' ') {
  401.         cp++;
  402.     }
  403.     line = cp;
  404.     /* Check if we've got any tree lines to output */
  405.     if (wrap_at != COLS-1 && header_line <= max_line) {
  406.         char *cp1, *cp2;
  407.  
  408.         do {
  409.         putchar(' ');
  410.         } while (pad_cnt--);
  411.         /* Check string for the '*' flagging our current node
  412.         ** and the '@' flagging our prior node.
  413.         */
  414.         cp = tree_lines[header_line];
  415.         cp1 = index(cp, '*');
  416.         cp2 = index(cp, '@');
  417.         if (cp1 != Nullch) {
  418.         *cp1 = '\0';
  419.         }
  420.         if (cp2 != Nullch) {
  421.         *cp2 = '\0';
  422.         }
  423.         fputs(cp, stdout);
  424.         /* Handle standout output for '*' and '@' marked nodes, then
  425.         ** continue with the rest of the line.
  426.         */
  427.         while (cp1 || cp2) {
  428.         standout();
  429.         if (cp1 && (!cp2 || cp1 < cp2)) {
  430.             cp = cp1;
  431.             cp1 = Nullch;
  432.             *cp++ = '*';
  433.             putchar(*cp++);
  434.             putchar(*cp++);
  435.         } else {
  436.             cp = cp2;
  437.             cp2 = Nullch;
  438.             *cp++ = '@';
  439.         }
  440.         putchar(*cp++);
  441.         un_standout();
  442.         if (*cp) {
  443.             fputs(cp, stdout);
  444.         }
  445.         }/* while */
  446.     }/* if */
  447.     putchar('\n') FLUSH;
  448.     header_line++;
  449.     }/* for remainder of line */
  450.  
  451.     /* free allocated copy of line */
  452.     free(buf);
  453.  
  454.     /* return number of lines displayed */
  455.     return header_line - start_line;
  456. }
  457.  
  458. /* Output any parts of the tree that are left to display.  Called at the
  459. ** end of each header.
  460. */
  461. int
  462. finish_tree(last_line)
  463. ART_LINE last_line;
  464. {
  465.     ART_LINE start_line = last_line;
  466.  
  467.     while (last_line <= max_line) {
  468.     artline++;
  469.     last_line += tree_puts("+", last_line, 0);
  470.     vwtary(artline, artpos);    /* keep rn's backpager happy */
  471.     }
  472.     return last_line - start_line;
  473. }
  474.  
  475. /* Output the entire article tree for the user.
  476. */
  477. void
  478. entire_tree()
  479. {
  480.     int j, root;
  481.  
  482.     if (!ThreadedGroup) {
  483.     ThreadedGroup = use_data(TRUE);
  484.     find_article(art);
  485.     curr_p_art = p_art;
  486.     }
  487.     if (check_page_line()) {
  488.     return;
  489.     }
  490.     if (!p_art) {
  491. #ifdef VERBOSE
  492.     IF (verbose)
  493.         fputs("\nNo article tree to display.\n", stdout);
  494.     ELSE
  495. #endif
  496. #ifdef TERSE
  497.         fputs("\nNo tree.\n", stdout);
  498. #endif
  499.     } else {
  500.     root = p_art->root;
  501. #ifdef NOFIREWORKS
  502.     no_sofire();
  503. #endif
  504.     standout();
  505.     printf("T%ld:\n", (long)p_roots[root].root_num);
  506.     un_standout();
  507.     if (check_page_line()) {
  508.         return;
  509.     }
  510.     putchar('\n');
  511.     for (j = 0; j < p_roots[root].subject_cnt; j++) {
  512.         sprintf(buf, "[%c] %s\n", letters[j > 9+26+26 ? 9+26+26 : j],
  513.         subject_ptrs[root_subjects[root]+j]);
  514.         if (check_page_line()) {
  515.         return;
  516.         }
  517.         fputs(buf, stdout);
  518.     }
  519.     if (check_page_line()) {
  520.         return;
  521.     }
  522.     putchar('\n');
  523.     if (check_page_line()) {
  524.         return;
  525.     }
  526.     putchar(' ');
  527.     buf[3] = '\0';
  528.     display_tree(p_articles+p_roots[p_art->root].articles, tree_indent);
  529.  
  530.     if (check_page_line()) {
  531.         return;
  532.     }
  533.     putchar('\n');
  534.     }
  535. }
  536.  
  537. /* A recursive routine to output the entire article tree.
  538. */
  539. static void
  540. display_tree(article, cp)
  541. PACKED_ARTICLE *article;
  542. char *cp;
  543. {
  544.     if (cp - tree_indent > COLS || page_line < 0) {
  545.     return;
  546.     }
  547.     cp[1] = ' ';
  548.     cp += 5;
  549.     for (;;) {
  550.     putchar((article->flags & ROOT_ARTICLE)? ' ' : '-');
  551.     if (was_read(article->num)) {
  552.         buf[0] = '(';
  553.         buf[2] = ')';
  554.     } else {
  555.         buf[0] = '[';
  556.         buf[2] = ']';
  557.     }
  558.     buf[1] = letter(article);
  559.     if (article == curr_p_art) {
  560.         standout();
  561.         fputs(buf, stdout);
  562.         un_standout();
  563.     } else if (article == recent_p_art) {
  564.         putchar(buf[0]);
  565.         standout();
  566.         putchar(buf[1]);
  567.         un_standout();
  568.         putchar(buf[2]);
  569.     } else {
  570.         fputs(buf, stdout);
  571.     }
  572.  
  573.     if (article->siblings) {
  574.         *cp = '|';
  575.     } else {
  576.         *cp = ' ';
  577.     }
  578.     if (article->child_cnt) {
  579.         putchar((article->child_cnt == 1)? '-' : '+');
  580.         display_tree(article+1, cp);
  581.         cp[1] = '\0';
  582.     } else {
  583.         putchar('\n') FLUSH;
  584.     }
  585.     if (!article->siblings) {
  586.         break;
  587.     }
  588.     article += article->siblings;
  589.     if (!article->siblings) {
  590.         *cp = '\\';
  591.     }
  592.     tree_indent[5] = ' ';
  593.     if (check_page_line()) {
  594.         return;
  595.     }
  596.     fputs(tree_indent+5, stdout);
  597.     }
  598. }
  599.  
  600. int
  601. check_page_line()
  602. {
  603.     if (page_line < 0) {
  604.     return -1;
  605.     }
  606.     if (page_line >= LINES || int_count) {
  607.       register int cmd = -1;
  608.     if (int_count || (cmd = get_anything())) {
  609.         page_line = -1;        /* disable further printing */
  610.         if (cmd > 0) {
  611.         pushchar(cmd);
  612.         }
  613.         return cmd;
  614.     }
  615.     }
  616.     page_line++;
  617.     return 0;
  618. }
  619.  
  620. /* Calculate the subject letter representation.  "Place-holder" nodes
  621. ** are marked with a ' ', others get a letter in the sequence:
  622. **    ' ', '1'-'9', 'A'-'Z', 'a'-'z', '?'
  623. */
  624. static char
  625. letter(article)
  626. PACKED_ARTICLE *article;
  627. {
  628.     register int subj = article->subject;
  629.  
  630.     if (subj < 0) {
  631.     return ' ';
  632.     }
  633.     subj -= root_subjects[article->root];
  634.     if (subj < 9+26+26) {
  635.     return letters[subj];
  636.     }
  637.     return '?';
  638. }
  639.  
  640. /* Find the first unread article in the (possibly selected) root order.
  641. */
  642. void
  643. first_art()
  644. {
  645.     if (!ThreadedGroup) {
  646.     art = firstart;
  647.     return;
  648.     }
  649.     p_art = Nullart;
  650.     art = lastart+1;
  651.     follow_thread('n');
  652. }
  653.  
  654. /* Perform a command over all or a section of the article tree.  Most of
  655. ** the option letters match commands entered from article mode:
  656. **   n - find the next unread article after current article.
  657. **  ^N - find the next unread article with the same subject.
  658. **   N - goto the next article in the thread.
  659. **   j - junk the entire thread.
  660. **   J - junk the entire thread, chasing xrefs.
  661. **   k - junk all articles with this same subject, chasing xrefs.
  662. **   K - kill all this article's descendants, chasing xrefs (we know that
  663. **     the caller killed the current article on the way here).
  664. **   u - mark entire thread as "unread".
  665. **   U - mark this article and its descendants as "unread".
  666. **   x - go through the unread articles and just chase their xrefs.
  667. **   f - follow the thread (like 'n'), but don't attempt to find a new thread
  668. **     if we run off the end.
  669. */
  670. void
  671. follow_thread(cmd)
  672. char_int cmd;
  673. {
  674.     int curr_subj = -1, selected;
  675.     PACKED_ARTICLE *root_limit, *p_art_old = Nullart;
  676.     bool subthread_flag, chase_flag;
  677.  
  678.     reread = FALSE;
  679.  
  680.     if (cmd == 'N') {
  681.     reread = TRUE;
  682.     }
  683.     if (!p_art) {
  684.     if (ThreadedGroup && art > lastart) {
  685.         p_art = root_limit = p_articles;
  686.         goto follow_root;
  687.     }
  688.     art++;
  689.     return;
  690.     }
  691.     if (cmd == 'k' || cmd == Ctl('n')) {
  692.     if ((curr_subj = p_art->subject) == -1) {
  693.         return;
  694.     }
  695.     p_art_old = p_art;
  696.     }
  697.     selected = (selected_roots[p_art->root] & 1);
  698.     if (cmd == 'U' || cmd == 'K') {
  699.     subthread_flag = TRUE;
  700.     p_art_old = p_art;
  701.     } else {
  702.     subthread_flag = FALSE;
  703.     }
  704.     chase_flag = (!olden_days && (cmd == 'J' || cmd == 'k' || cmd == 'K'));
  705.  
  706.     /* Some commands encompass the entire thread */
  707.     if (cmd == 'k' || cmd == 'j' || cmd == 'J' || cmd == 'u' || cmd == 'x') {
  708.     p_art = p_articles + p_roots[p_art->root].articles;
  709.     art = p_art->num;
  710.     }
  711.     /* The current article is already marked as read for 'K' */
  712.     if (cmd == 'k' || cmd == 'j' || cmd == 'J') {
  713.     if (!was_read(art) && (curr_subj < 0 || curr_subj == p_art->subject)) {
  714.         set_read(art, selected, chase_flag);
  715.     }
  716.     cmd = 'K';
  717.     }
  718.     if (cmd == 'u') {
  719.     p_art_old = p_art;
  720.     cmd = 'U';
  721.     }
  722.     if (cmd == 'U') {
  723.     if (p_art->subject != -1) {
  724.         set_unread(art, selected);
  725.     }
  726.     root_article_cnts[p_art->root] = 1;
  727.     scan_all_roots = FALSE;
  728.     }
  729.     if (cmd == 'x') {
  730.     if (olden_days) {
  731.         return;
  732.     }
  733.     if ((p_art->flags & HAS_XREFS) && !was_read(art)) {
  734.         chase_xrefs(art, TRUE);
  735.     }
  736.     }
  737.   follow_again:
  738.     selected = (selected_roots[p_art->root] & 1);
  739.     root_limit = upper_limit(p_art, subthread_flag);
  740.     for (;;) {
  741.     if (++p_art == root_limit) {
  742.         break;
  743.     }
  744.     if (!(art = p_art->num)) {
  745.         continue;
  746.     }
  747.     if (cmd == 'K' || p_art->subject == -1) {
  748.         if (!was_read(art)
  749.          && (curr_subj < 0 || curr_subj == p_art->subject)) {
  750.         set_read(art, selected, chase_flag);
  751.         }
  752.     } else if (cmd == 'U') {
  753.         set_unread(art, selected);
  754.     } else if (cmd == 'x') {
  755.         if ((p_art->flags & HAS_XREFS) && !was_read(art)) {
  756.         chase_xrefs(art, TRUE);
  757.         }
  758.     } else if (!was_read(art)
  759.         && (curr_subj < 0 || curr_subj == p_art->subject)) {
  760.         return;
  761.     } else if (cmd == 'N') {
  762.         return;
  763.     }
  764.     }/* for */
  765.     if (p_art_old) {
  766.     p_art = p_art_old;
  767.     if (cmd == 'U' && p_art->subject != -1) {
  768.         art = p_art->num;
  769.         return;
  770.     }
  771.     p_art_old = Nullart;
  772.     cmd = 'n';
  773.     curr_subj = -1;
  774.     subthread_flag = FALSE;
  775.     goto follow_again;
  776.     }
  777.     if (cmd == 'f') {
  778.     p_art = Nullart;
  779.     art = lastart+1;
  780.     return;
  781.     }
  782.   follow_root:
  783.     if (root_limit != p_articles + total.article) {
  784.     register int r;
  785.  
  786.     for (r = p_art->root; r < total.root; r++) {
  787.         if (!selected_root_cnt || selected_roots[r]) {
  788.         p_art = p_articles + p_roots[r].articles;
  789.         art = p_art->num;
  790.         if (p_art->subject == -1 || (cmd != 'N' && was_read(art))) {
  791.             if (cmd != 'N') {
  792.             cmd = 'n';
  793.             }
  794.             curr_subj = -1;
  795.             subthread_flag = FALSE;
  796.             goto follow_again;
  797.         }
  798.         return;
  799.         }
  800.     }
  801.     }
  802.     if (!count_roots(FALSE) && unthreaded) {
  803.     /* No threaded articles left -- blow everything else away */
  804.     for (art = firstbit; art <= lastart; art++) {
  805.         oneless(art);
  806.     }
  807.     unthreaded = 0;
  808.     }
  809.     p_art = Nullart;
  810.     art = lastart+1;
  811.     if (cmd == 'N') {
  812.     forcelast = TRUE;
  813.     reread = FALSE;
  814.     }
  815. }
  816.  
  817. /* Go backward in the article tree.  Options match commands in article mode:
  818. **    p - previous unread article.
  819. **   ^P - previous unread article with same subject.
  820. **    P - previous article.
  821. */
  822. void
  823. backtrack_thread(cmd)
  824. char_int cmd;
  825. {
  826.     int curr_subj = -1, selected;
  827.     PACKED_ARTICLE *root_limit, *p_art_old = Nullart;
  828.  
  829.     if (art > lastart) {
  830.     p_art = p_articles + total.article - 1;
  831.     root_limit = Nullart;
  832.     goto backtrack_root;
  833.     }
  834.     if (!p_art) {
  835.     art--;
  836.     return;
  837.     }
  838.     if (cmd == Ctl('p')) {
  839.     if ((curr_subj = p_art->subject) == -1) {
  840.         return;
  841.     }
  842.     p_art_old = p_art;
  843.     }
  844.   backtrack_again:
  845.     selected = (selected_roots[p_art->root] & 1);
  846.     root_limit = p_articles + p_roots[p_art->root].articles;
  847.     for (;;) {
  848.     if (p_art-- == root_limit) {
  849.         break;
  850.     }
  851.     if (!(art = p_art->num)) {
  852.         continue;
  853.     }
  854.     if (p_art->subject == -1) {
  855.         set_read(art, selected, FALSE);
  856.     } else if (!was_read(art)
  857.         && (curr_subj < 0 || curr_subj == p_art->subject)) {
  858.         return;
  859.     } else if (cmd == 'P') {
  860.         reread = TRUE;
  861.         return;
  862.     }
  863.     }/* for */
  864.     if (p_art_old) {
  865.     p_art = p_art_old;
  866.     p_art_old = Nullart;
  867.     curr_subj = -1;
  868.     goto backtrack_again;
  869.     }
  870.   backtrack_root:
  871.     if (root_limit != p_articles) {
  872.     register int r;
  873.  
  874.     for (r = p_art->root; r >= 0; r--) {
  875.         if (!selected_root_cnt || selected_roots[r]) {
  876.         art = p_art->num;
  877.         if (cmd != 'P' && was_read(art)) {
  878.             goto backtrack_again;
  879.         }
  880.         return;
  881.         }
  882.         p_art = p_articles + p_roots[r].articles - 1;
  883.     }
  884.     }
  885.     p_art = Nullart;
  886.     art = absfirst-1;
  887. }
  888.  
  889. /* Find the next root (first if p_art == NULL).  If roots are selected,
  890. ** only choose from selected roots.
  891. */
  892. void
  893. next_root()
  894. {
  895.     register int r;
  896.  
  897.     reread = FALSE;
  898.  
  899.     if (p_art) {
  900.     r = p_art->root+1;
  901.     } else {
  902.     r = 0;
  903.     }
  904.     for (; r < total.root; r++) {
  905.     if (!selected_root_cnt || selected_roots[r]) {
  906.       try_again:
  907.         p_art = p_articles + p_roots[r].articles;
  908.         art = p_art->num;
  909.         if (p_art->subject == -1 || (!reread && was_read(art))) {
  910.         follow_thread(reread ? 'N' : 'f');
  911.         if (art == lastart+1) {
  912.             if (scan_all_roots || selected_root_cnt
  913.              || root_article_cnts[r]) {
  914.             reread = TRUE;
  915.             goto try_again;
  916.             }
  917.             continue;
  918.         }
  919.         }
  920.         return;
  921.     }
  922.     }
  923.     p_art = Nullart;
  924.     art = lastart+1;
  925.     forcelast = TRUE;
  926. }
  927.  
  928. /* Find previous root (or last if p_art == NULL).  If roots are selected,
  929. ** only choose from selected roots.
  930. */
  931. void
  932. prev_root()
  933. {
  934.     register int r;
  935.  
  936.     reread = FALSE;
  937.  
  938.     if (p_art) {
  939.     r = p_art->root - 1;
  940.     } else {
  941.     r = total.root - 1;
  942.     }
  943.     for (; r >= 0; r--) {
  944.     if (!selected_root_cnt || selected_roots[r]) {
  945.       try_again:
  946.         p_art = p_articles + p_roots[r].articles;
  947.         art = p_art->num;
  948.         if (p_art->subject == -1 || (!reread && was_read(art))) {
  949.         follow_thread(reread ? 'N' : 'f');
  950.         if (art == lastart+1) {
  951.             if (scan_all_roots || selected_root_cnt
  952.              || root_article_cnts[r]) {
  953.             reread = TRUE;
  954.             goto try_again;
  955.             }
  956.             continue;
  957.         }
  958.         }
  959.         return;
  960.     }
  961.     }
  962.     p_art = Nullart;
  963.     art = lastart+1;
  964.     forcelast = TRUE;
  965. }
  966.  
  967. /* Return a pointer value that we will equal when we've reached the end of
  968. ** the current (sub-)thread.
  969. */
  970. PACKED_ARTICLE *
  971. upper_limit(artp, subthread_flag)
  972. PACKED_ARTICLE *artp;
  973. bool_int subthread_flag;
  974. {
  975.     if (subthread_flag) {
  976.     for (;;) {
  977.         if (artp->siblings) {
  978.         return artp + artp->siblings;
  979.         }
  980.         if (!artp->parent) {
  981.         break;
  982.         }
  983.         artp += artp->parent;
  984.     }
  985.     }
  986.     return p_articles + (artp->root == total.root-1 ?
  987.     total.article : p_roots[artp->root+1].articles);
  988. }
  989.  
  990. #endif /* USETHREADS */
  991.