"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/search.c" (9 Dec 2022, 19436 Bytes) of package /linux/misc/tin-2.6.2.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "search.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.6.1_vs_2.6.2.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : search.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2022-08-29
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1991-2023 Iain Lea <iain@bricbrac.de>, Rich Skrenta <skrenta@pbm.com>
   10  * All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  *
   16  * 1. Redistributions of source code must retain the above copyright notice,
   17  *    this list of conditions and the following disclaimer.
   18  *
   19  * 2. Redistributions in binary form must reproduce the above copyright
   20  *    notice, this list of conditions and the following disclaimer in the
   21  *    documentation and/or other materials provided with the distribution.
   22  *
   23  * 3. Neither the name of the copyright holder nor the names of its
   24  *    contributors may be used to endorse or promote products derived from
   25  *    this software without specific prior written permission.
   26  *
   27  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   37  * POSSIBILITY OF SUCH DAMAGE.
   38  */
   39 
   40 
   41 #ifndef TIN_H
   42 #   include "tin.h"
   43 #endif /* !TIN_H */
   44 
   45 
   46 /*
   47  * local prototypes
   48  */
   49 static char *get_search_pattern(t_bool *forward, t_bool repeat, const char *fwd_msg, const char *bwd_msg, char *def, int which_hist);
   50 static int author_search(int i, char *searchbuf);
   51 static int body_search(int i, char *searchbuf);
   52 static int subject_search(int i, char *searchbuf);
   53 static int search_group(t_bool forward, int current_art, char *searchbuff, int (*search_func) (int i, char *searchbuff));
   54 
   55 
   56 /*
   57  * Kludge to maintain some internal state for body search
   58  */
   59 int srch_lineno = -1;
   60 static int total_cnt = 0, curr_cnt = 0;
   61 
   62 /*
   63  * Used by article and body search - this saves passing around large numbers
   64  * of parameters all the time
   65  */
   66 static t_bool last_article_search_matched = FALSE;
   67 static REGEX_SIZE srch_offsets[2];
   68 static REGEX_NOFFSET srch_offsets_size = ARRAY_SIZE(srch_offsets);
   69 static struct regex_cache search_regex = REGEX_CACHE_INITIALIZER;
   70 
   71 /*
   72  * Used to copy the per regex_cache offsets to the global ones.
   73  */
   74 static void
   75 copy_offsets(
   76     REGEX_SIZE *dst,
   77     REGEX_NOFFSET dst_size,
   78     struct regex_cache *re)
   79 {
   80     REGEX_NOFFSET i;
   81     REGEX_NOFFSET limit = 2 * regex_get_ovector_count(re);
   82     REGEX_SIZE *ovector = regex_get_ovector_pointer(re);
   83 
   84     if (limit > dst_size)
   85         limit = dst_size;
   86 
   87     for (i = 0; i < limit; i++)
   88         dst[i] = ovector[i];
   89 }
   90 
   91 
   92 /*
   93  * Obtain the search pattern, save it in the default buffer.
   94  * Return NULL if no pattern could be found
   95  */
   96 static char *
   97 get_search_pattern(
   98     t_bool *forward,
   99     t_bool repeat,
  100     const char *fwd_msg,
  101     const char *bwd_msg,
  102     char *def,
  103     int which_hist)
  104 {
  105     static char tmpbuf[LEN];    /* Hold the last pattern used */
  106     static char last_pattern[LEN];  /* last search pattern used; for repeated search */
  107     static t_bool last_forward;
  108 
  109     if (repeat) {
  110         *forward = last_forward;
  111         my_strncpy(def, last_pattern, LEN);
  112     } else {
  113         snprintf(tmpbuf, sizeof(tmpbuf), (*forward ? fwd_msg : bwd_msg), def);
  114 
  115         if (!prompt_string_default(tmpbuf, def, _(txt_no_search_string), which_hist))
  116             return NULL;
  117 
  118         last_forward = *forward;
  119         my_strncpy(last_pattern, def, LEN);
  120 
  121         /* HIST_BODY_SEARCH doesn't exist, hence last_search is set directly in search_body() */
  122         if (which_hist == HIST_AUTHOR_SEARCH)
  123             last_search = *forward ? GLOBAL_SEARCH_AUTHOR_FORWARD : GLOBAL_SEARCH_AUTHOR_BACKWARD;
  124         else
  125             last_search = *forward ? GLOBAL_SEARCH_SUBJECT_FORWARD : GLOBAL_SEARCH_SUBJECT_BACKWARD;
  126     }
  127 
  128     wait_message(0, _(txt_searching));
  129 
  130     stow_cursor();
  131 
  132 #ifdef HAVE_UNICODE_NORMALIZATION
  133     /* normalize search pattern */
  134     if (IS_LOCAL_CHARSET("UTF-8")) {
  135         char *tmp;
  136 
  137         tmp = normalize(def);
  138         my_strncpy(def, tmp, LEN);
  139         free(tmp);
  140     }
  141 #endif /* HAVE_UNICODE_NORMALIZATION */
  142 
  143     if (tinrc.wildcard) {           /* ie, not wildmat() */
  144         strcpy(def, quote_wild_whitespace(def));
  145         return def;
  146     }
  147 
  148     /*
  149      * A gross hack to simulate substrings with wildmat()
  150      */
  151 /* TODO: somehow use REGEX_FMT here? */
  152     snprintf(tmpbuf, sizeof(tmpbuf), "*%s*", def);
  153     return tmpbuf;
  154 }
  155 
  156 
  157 /*
  158  * called by config.c
  159  */
  160 enum option_enum
  161 search_config(
  162     t_bool forward,
  163     t_bool repeat,
  164     enum option_enum current,
  165     enum option_enum last)
  166 {
  167     char *pattern, *buf;
  168     enum option_enum n = current;
  169     enum option_enum result = current;
  170 
  171     if (!(pattern = get_search_pattern(&forward, repeat, _(txt_search_forwards), _(txt_search_backwards), tinrc.default_search_config, HIST_CONFIG_SEARCH)))
  172         return result;
  173 
  174     if (tinrc.wildcard && !(compile_regex(pattern, &search_regex, REGEX_CASELESS)))
  175         return result;
  176 
  177     do {
  178         if (n == 0 && !forward)
  179             n = last;
  180         else {
  181             if (n == last && forward)
  182                 n = 0;
  183             else
  184                 n = (n + (enum option_enum) (forward ? 1 : -1));
  185         }
  186         /* search only visible options */
  187         if (option_is_visible(n)) {
  188 #ifdef HAVE_UNICODE_NORMALIZATION
  189             if (IS_LOCAL_CHARSET("UTF-8"))
  190                 buf = normalize(_(option_table[n].txt->opt));
  191             else
  192 #endif /* HAVE_UNICODE_NORMALIZATION */
  193                 buf = my_strdup(_(option_table[n].txt->opt));
  194 
  195             if (match_regex(buf, pattern, &search_regex, TRUE)) {
  196                 result = n;
  197                 free(buf);
  198                 break;
  199             }
  200             free(buf);
  201         }
  202     } while (n != current);
  203 
  204     clear_message();
  205     if (tinrc.wildcard) {
  206         regex_cache_destroy(&search_regex);
  207     }
  208     return result;
  209 }
  210 
  211 
  212 /*
  213  * called by save.c (search for attachment) and page.c (search for URL)
  214  */
  215 int
  216 generic_search(
  217     t_bool forward,
  218     t_bool repeat,
  219     int current,
  220     int last,
  221     int level)
  222 {
  223     char *pattern;
  224     char buf[BUFSIZ];
  225     const char *name, *charset;
  226     int n = current;
  227     int result = current;
  228     t_bool found = FALSE;
  229     t_part *part;
  230     t_url *urlptr;
  231     t_posted *phptr;
  232 
  233     if (!(pattern = get_search_pattern(&forward, repeat, _(txt_search_forwards), _(txt_search_backwards), tinrc.default_search_config, HIST_CONFIG_SEARCH)))
  234         return result;
  235 
  236     if (tinrc.wildcard && !(compile_regex(pattern, &search_regex, REGEX_CASELESS)))
  237         return result;
  238 
  239     do {
  240         if (n == 0 && !forward)
  241             n = last;
  242         else {
  243             if (n == last && forward)
  244                 n = 0;
  245             else
  246                 n += (forward ? 1 : -1);
  247         }
  248         switch (level) {
  249             case ATTACHMENT_LEVEL:
  250                 part = get_part(n);
  251                 if (!(name = get_filename(part->params))) {
  252                     if (!(name = part->description))
  253                         name = _(txt_attachment_no_name);
  254                 }
  255                 charset = get_param(part->params, "charset");
  256                 snprintf(buf, sizeof(buf), "%s %s/%s %s, %s", name, content_types[part->type], part->subtype, content_encodings[part->encoding], charset ? charset : "");
  257                 break;
  258 
  259             case POSTED_LEVEL:
  260                 phptr = find_post_hist(n);
  261                 snprintf(buf, sizeof(buf), "%s %s %s", phptr->date, phptr->group, phptr->subj);
  262                 break;
  263 
  264             case URL_LEVEL:
  265                 urlptr = find_url(n);
  266                 snprintf(buf, sizeof(buf), "%s", urlptr->url);
  267                 break;
  268 
  269             default:
  270                 buf[0] = '\0';
  271                 break;
  272         }
  273         if (match_regex(buf, pattern, &search_regex, TRUE)) {
  274             result = n;
  275             found = TRUE;
  276             break;
  277         }
  278     } while (n != current);
  279 
  280     clear_message();
  281     if (tinrc.wildcard) {
  282         regex_cache_destroy(&search_regex);
  283     }
  284     if (!found)
  285         info_message(_(txt_no_match));
  286 
  287     return result;
  288 }
  289 
  290 
  291 /*
  292  * Search active[] looking for a groupname
  293  * Called by select.c
  294  * Return index into active of matching groupname or -1
  295  */
  296 int
  297 search_active(
  298     t_bool forward,
  299     t_bool repeat)
  300 {
  301     char *buf;
  302     char *ptr;
  303     char buf2[LEN];
  304     int i;
  305 
  306     if (!selmenu.max) {
  307         info_message(_(txt_no_groups));
  308         return -1;
  309     }
  310 
  311     if (!(buf = get_search_pattern(&forward, repeat, _(txt_search_forwards), _(txt_search_backwards), tinrc.default_search_group, HIST_GROUP_SEARCH)))
  312         return -1;
  313 
  314     if (tinrc.wildcard && !(compile_regex(buf, &search_regex, REGEX_CASELESS)))
  315         return -1;
  316 
  317     i = selmenu.curr;
  318 
  319     do {
  320         if (forward) {
  321             if (++i >= selmenu.max)
  322                 i = 0;
  323         } else {
  324             if (--i < 0)
  325                 i = selmenu.max - 1;
  326         }
  327 
  328         /*
  329          * Get the group name & description into buf2
  330          */
  331         if (show_description && active[my_group[i]].description) {
  332             snprintf(buf2, sizeof(buf2), "%s %s", active[my_group[i]].name, active[my_group[i]].description);
  333             ptr = buf2;
  334         } else
  335             ptr = active[my_group[i]].name;
  336 
  337         if (match_regex(ptr, buf, &search_regex, TRUE)) {
  338             if (tinrc.wildcard) {
  339                 regex_cache_destroy(&search_regex);
  340             }
  341             return i;
  342         }
  343     } while (i != selmenu.curr);
  344 
  345     if (tinrc.wildcard) {
  346         regex_cache_destroy(&search_regex);
  347     }
  348     info_message(_(txt_no_match));
  349     return -1;
  350 }
  351 
  352 
  353 /*
  354  * Scan the body of an arts[i] for searchbuf
  355  * used only by search_body()
  356  * Returns: 1   String found
  357  *          0   Not found
  358  *          -1  User aborted search
  359  */
  360 static int
  361 body_search(
  362     int i,
  363     char *searchbuf)
  364 {
  365     static char msg[LEN];   /* show_progress needs a constant message buffer */
  366     char *line, *tmp;
  367     t_openartinfo artinfo;
  368 
  369     switch (art_open(TRUE, &arts[i], curr_group, &artinfo, FALSE, NULL)) {
  370         case ART_ABORT:                 /* User 'q'uit */
  371             art_close(&artinfo);
  372             return -1;
  373 
  374         case ART_UNAVAILABLE:           /* Treat as string not present */
  375             art_close(&artinfo);
  376             info_message(_(txt_no_match));
  377             return 0;
  378     }
  379 
  380     /*
  381      * Skip the header - is this right ?
  382      */
  383     for (i = 0; artinfo.cookl[i].flags & C_HEADER; ++i)
  384         ;
  385     if (fseek(artinfo.cooked, artinfo.cookl[i].offset, SEEK_SET) != 0) {
  386         art_close(&artinfo);
  387         return -1;
  388     }
  389 
  390     /*
  391      * Now search the body
  392      */
  393     snprintf(msg, sizeof(msg), _(txt_searching_body), ++curr_cnt, total_cnt);
  394     show_progress(msg, curr_cnt, total_cnt);
  395     while ((tmp = tin_fgets(artinfo.cooked, FALSE)) != NULL) {
  396 #ifdef HAVE_UNICODE_NORMALIZATION
  397         if (IS_LOCAL_CHARSET("UTF-8"))
  398             line = normalize(tmp);
  399         else
  400 #endif /* HAVE_UNICODE_NORMALIZATION */
  401             line = my_strdup(tmp);
  402 
  403         if (tinrc.wildcard) {
  404             if (match_regex_ex(line, (int) strlen(line), 0, 0, &search_regex) >= 0) {
  405                 copy_offsets(srch_offsets, srch_offsets_size, &search_regex);
  406                 srch_lineno = i;
  407                 art_close(&pgart);      /* Switch the pager over to matched art */
  408                 pgart = artinfo;
  409 #ifdef DEBUG
  410                 if (debug & DEBUG_MISC)
  411                     fprintf(stderr, "art_switch(%p = %p)\n", (void *) &pgart, (void *) &artinfo);
  412 #endif /* DEBUG */
  413                 free(line);
  414 
  415                 return 1;
  416             }
  417         } else {
  418             if (wildmatpos(line, searchbuf, TRUE, srch_offsets, srch_offsets_size)) {
  419                 srch_lineno = i;
  420                 art_close(&pgart);      /* Switch the pager over to matched art */
  421                 pgart = artinfo;
  422                 free(line);
  423                 return 1;
  424             }
  425         }
  426         i++;
  427         free(line);
  428     }
  429 
  430     if (tin_errno != 0) {           /* User abort */
  431         art_close(&artinfo);
  432         return -1;
  433     }
  434 
  435     art_close(&artinfo);
  436 /*  info_message(_(txt_no_match)); */
  437     return 0;
  438 }
  439 
  440 
  441 /*
  442  * Match searchbuff against the From: information in arts[i]
  443  * 1 = found, 0 = not found
  444  */
  445 static int
  446 author_search(
  447     int i,
  448     char *searchbuf)
  449 {
  450     char *buf, *tmp;
  451 
  452     if (arts[i].name == NULL)
  453         tmp = my_strdup(arts[i].from);
  454     else {
  455         size_t len = strlen(arts[i].from) + strlen(arts[i].name) + 4;
  456 
  457         tmp = my_malloc(len);
  458         snprintf(tmp, len, "%s <%s>", arts[i].name, arts[i].from);
  459     }
  460 
  461 #ifdef HAVE_UNICODE_NORMALIZATION
  462     if (IS_LOCAL_CHARSET("UTF-8")) {
  463         buf = normalize(tmp);
  464         free(tmp);
  465     } else
  466 #endif /* HAVE_UNICODE_NORMALIZATION */
  467         buf = tmp;
  468 
  469     if (match_regex(buf, searchbuf, &search_regex, TRUE)) {
  470         free(buf);
  471         return 1;
  472     }
  473 
  474     free(buf);
  475     return 0;
  476 }
  477 
  478 
  479 /*
  480  * Match searchbuff against the Subject: information in arts[i]
  481  * 1 = found, 0 = not found
  482  */
  483 static int
  484 subject_search(
  485     int i,
  486     char *searchbuf)
  487 {
  488     char *buf;
  489 
  490 #ifdef HAVE_UNICODE_NORMALIZATION
  491     if (IS_LOCAL_CHARSET("UTF-8"))
  492         buf = normalize(arts[i].subject);
  493     else
  494 #endif /* HAVE_UNICODE_NORMALIZATION */
  495         buf = my_strdup(arts[i].subject);
  496 
  497     if (match_regex(buf, searchbuf, &search_regex, TRUE)) {
  498         free(buf);
  499         return 1;
  500     }
  501 
  502     free(buf);
  503     return 0;
  504 }
  505 
  506 
  507 /*
  508  * Returns index into arts[] of matching article or -1
  509  */
  510 static int
  511 search_group(
  512     t_bool forward,
  513     int current_art,
  514     char *searchbuff,
  515     int (*search_func) (int i, char *searchbuff))
  516 {
  517     int i, ret, loop_cnt;
  518 
  519     if (grpmenu.curr < 0) {
  520         info_message(_(txt_no_arts));
  521         return -1;
  522     }
  523 
  524     /*
  525      * precompile if we're using full regex
  526      */
  527     if (tinrc.wildcard && !(compile_regex(searchbuff, &search_regex, REGEX_CASELESS)))
  528         return -1;
  529 
  530     i = current_art;
  531     loop_cnt = 1;
  532 
  533     do {
  534         if (forward) {
  535             if ((i = next_response(i)) < 0)
  536                 i = (int) base[0];
  537         } else {
  538             if ((i = prev_response(i)) < 0)
  539                 i = find_response(grpmenu.max - 1, num_of_responses(grpmenu.max - 1));
  540         }
  541 
  542         /* Only search displayed articles */
  543         if (curr_group->attribute->show_only_unread_arts && arts[i].status != ART_UNREAD)
  544             continue;
  545 
  546         ret = search_func(i, searchbuff);
  547         if (tinrc.wildcard && (ret == 1 || ret == -1)) {
  548             /* we will exit soon, clean up */
  549             regex_cache_destroy(&search_regex);
  550         }
  551         switch (ret) {
  552             case 1:                             /* Found */
  553                 clear_message();
  554                 return i;
  555 
  556             case -1:                            /* User abort */
  557                 return -1;
  558         }
  559 #ifdef HAVE_SELECT
  560         if (wait_for_input())   /* allow abort */
  561             return -1;
  562 #endif /* HAVE_SELECT */
  563         if (loop_cnt % (MODULO_COUNT_NUM * 20) == 0)
  564             show_progress(txt_searching, loop_cnt, top_art);
  565     } while (i != current_art && loop_cnt++ <= top_art);
  566 
  567     if (tinrc.wildcard) {
  568         regex_cache_destroy(&search_regex);
  569     }
  570     info_message(_(txt_no_match));
  571     return -1;
  572 }
  573 
  574 
  575 /*
  576  * Generic entry point to search for fields in arts[]
  577  * Returns index into arts[] of matching article or -1
  578  */
  579 int
  580 search(
  581     t_function func,
  582     int current_art,
  583     t_bool repeat)
  584 {
  585     char *buf;
  586     int (*search_func) (int i, char *searchbuff) = author_search;
  587     t_bool forward;
  588 
  589     if (func == GLOBAL_SEARCH_SUBJECT_FORWARD || func == GLOBAL_SEARCH_AUTHOR_FORWARD)
  590         forward = TRUE;
  591     else
  592         forward = FALSE;
  593 
  594     switch (func) {
  595         case GLOBAL_SEARCH_SUBJECT_FORWARD:
  596         case GLOBAL_SEARCH_SUBJECT_BACKWARD:
  597             if (!(buf = get_search_pattern(&forward, repeat, _(txt_search_forwards), _(txt_search_backwards), tinrc.default_search_subject, HIST_SUBJECT_SEARCH)))
  598                 return -1;
  599             search_func = subject_search;
  600             break;
  601 
  602         case GLOBAL_SEARCH_AUTHOR_FORWARD:
  603         case GLOBAL_SEARCH_AUTHOR_BACKWARD:
  604         default:
  605             if (!(buf = get_search_pattern(&forward, repeat, _(txt_author_search_forwards), _(txt_author_search_backwards), tinrc.default_search_author, HIST_AUTHOR_SEARCH)))
  606                 return -1;
  607             search_func = author_search;
  608             break;
  609     }
  610     return (search_group(forward, current_art, buf, search_func));
  611 }
  612 
  613 
  614 /*
  615  * page.c (search current article body)
  616  * Return line number that matches or -1
  617  * If using regex's return vector of character offsets
  618  */
  619 int
  620 search_article(
  621     t_bool forward,
  622     t_bool repeat,
  623     int start_line,
  624     int lines,
  625     t_lineinfo *line,
  626     int reveal_ctrl_l_lines,
  627     FILE *fp)
  628 {
  629     char *pattern, *ptr, *tmp;
  630     int i = start_line;
  631     REGEX_SIZE tmp_srch_offsets[2] = {0, 0};
  632     t_bool wrap = FALSE;
  633     t_bool match = FALSE;
  634 
  635     if (!(pattern = get_search_pattern(&forward, repeat, _(txt_search_forwards), _(txt_search_backwards), tinrc.default_search_art, HIST_ART_SEARCH)))
  636         return 0;
  637 
  638     if (tinrc.wildcard && !(compile_regex(pattern, &search_regex, REGEX_CASELESS)))
  639         return -1;
  640 
  641     srch_lineno = -1;
  642 
  643     forever {
  644         if (i == start_line && wrap)
  645             break;
  646 
  647         /*
  648          * TODO: consider not searching some line types?
  649          * 'B'ody search skips hdrs, '/' inside article does not.
  650          */
  651         if (fseek(fp, line[i].offset, SEEK_SET) != 0)
  652             return -1;
  653 
  654         /* Don't search beyond ^L if hiding is enabled */
  655         if ((line[i].flags & C_CTRLL) && i > reveal_ctrl_l_lines)
  656             break;
  657 
  658         if ((tmp = tin_fgets(fp, FALSE)) == NULL)
  659             return -1;
  660 
  661         if (!forward && last_article_search_matched) {
  662             tmp[srch_offsets[0]] = '\0';    /* ignore anything on this line after the beginning of the last match */
  663             srch_offsets[1] = 0;    /* start backwards search always at the beginning of the line */
  664         }
  665         last_article_search_matched = FALSE;
  666 
  667 #ifdef HAVE_UNICODE_NORMALIZATION
  668         if (IS_LOCAL_CHARSET("UTF-8"))
  669             ptr = normalize(tmp);
  670         else
  671 #endif /* HAVE_UNICODE_NORMALIZATION */
  672             ptr = my_strdup(tmp);
  673 
  674         if (tinrc.wildcard) {
  675             while (match_regex_ex(ptr, (int) strlen(ptr), srch_offsets[1], REGEX_NOTEMPTY, &search_regex) >= 0) {
  676                 copy_offsets(srch_offsets, srch_offsets_size, &search_regex);
  677                 match = TRUE;
  678                 if (forward)
  679                     break;
  680                 else {
  681                     tmp_srch_offsets[0] = srch_offsets[0];
  682                     tmp_srch_offsets[1] = srch_offsets[1];
  683                 }
  684             }
  685             if (match) {
  686                 if (!forward) {
  687                     srch_offsets[0] = tmp_srch_offsets[0];
  688                     srch_offsets[1] = tmp_srch_offsets[1];
  689                 }
  690                 srch_lineno = i;
  691                 regex_cache_destroy(&search_regex);
  692                 free(ptr);
  693                 last_article_search_matched = TRUE;
  694                 return i;
  695             }
  696         } else {
  697             if (wildmatpos(ptr, pattern, TRUE, srch_offsets, srch_offsets_size)) {
  698                 srch_lineno = i;
  699                 free(ptr);
  700                 last_article_search_matched = TRUE;
  701                 return i;
  702             }
  703         }
  704         free(ptr);
  705 
  706         if (forward) {
  707             if (i >= lines - 1) {
  708                 i = 0;
  709                 wrap = TRUE;
  710             } else
  711                 i++;
  712         } else {
  713             if (i <= 0) {
  714                 i = lines - 1;
  715                 wrap = TRUE;
  716             } else
  717                 i--;
  718         }
  719 
  720         /* search at the beginning of the line */
  721         srch_offsets[1] = 0;
  722     }
  723 
  724     info_message(_(txt_no_match));
  725     if (tinrc.wildcard) {
  726         regex_cache_destroy(&search_regex);
  727     }
  728     return -1;
  729 }
  730 
  731 
  732 /*
  733  * Search the bodies of all the articles in current group
  734  * Start the search at the current article
  735  * A match will replace the context of the article open in the pager
  736  * Save the line # that matched (and the start/end vector for regex)
  737  * for later retrieval
  738  * Return index in arts[] of article that matched or -1
  739  */
  740 int
  741 search_body(
  742     struct t_group *group,
  743     int current_art,
  744     t_bool repeat)
  745 {
  746     char *buf;
  747     int i;
  748     t_bool forward_fake = TRUE;
  749 
  750     if (!(buf = get_search_pattern(
  751             &forward_fake,              /* we pass a dummy var since body search has no `forward' */
  752             repeat,
  753             _(txt_search_body),
  754             _(txt_search_body),
  755             tinrc.default_search_art,
  756             HIST_ART_SEARCH
  757     ))) return -1;
  758 
  759     last_search = GLOBAL_SEARCH_BODY;   /* store last search type for repeated search */
  760     total_cnt = curr_cnt = 0;           /* Reset global counter of articles done */
  761 
  762     /*
  763      * Count up the articles to be processed for the progress meter
  764      */
  765     if (group->attribute->show_only_unread_arts) {
  766         for (i = 0; i < grpmenu.max; i++)
  767             total_cnt += new_responses(i);
  768     } else {
  769         for_each_art(i) {
  770             if (!IGNORE_ART(i))
  771                 total_cnt++;
  772         }
  773     }
  774 
  775     srch_lineno = -1;
  776     return search_group(1, current_art, buf, body_search);
  777 }
  778 
  779 
  780 /*
  781  * Return the saved line & start/end info from previous successful
  782  * regex search
  783  */
  784 int
  785 get_search_vectors(
  786     int *start,
  787     int *end)
  788 {
  789     int i = srch_lineno;
  790 
  791     *start = srch_offsets[0];
  792     *end = srch_offsets[1];
  793     srch_lineno = -1;           /* We can only retrieve this info once */
  794     return i;
  795 }
  796 
  797 
  798 /*
  799  * Reset offsets so that the next search starts at the beginning of the line.
  800  * This function is needed to access srch_offsets from within other modules.
  801  */
  802 void
  803 reset_srch_offsets(
  804     void)
  805 {
  806     srch_offsets[0] = srch_offsets[1] = 0;
  807     /*
  808      * bwd article search starts at the first line. This kludge avoids bwd
  809      * search to match in the first line first.
  810      */
  811     last_article_search_matched = FALSE;
  812 }