"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/search.c" (22 Dec 2021, 18868 Bytes) of package /linux/misc/tin-2.6.1.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.0_vs_2.6.1.

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