home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / trn_12.zip / src / rt-rn.c < prev    next >
C/C++ Source or Header  |  1993-12-04  |  25KB  |  1,124 lines

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