"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.5/src/search.c" (1 Dec 2020, 18666 Bytes) of package /linux/misc/tin-2.4.5.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.4.4_vs_2.4.5.

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