root/src/viewer/search.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. mcview_search_status_update_cb
  2. mcview_calculate_start_of_previous_line
  3. mcview_search_update_steps
  4. mcview_find
  5. mcview_search_show_result
  6. mcview_do_search
  7. mcview_search_init
  8. mcview_search_deinit
  9. mcview_search_cmd_callback
  10. mcview_search_update_cmd_callback
  11. mcview_search

   1 /*
   2    Internal file viewer for the Midnight Commander
   3    Function for search data
   4 
   5    Copyright (C) 1994-2025
   6    Free Software Foundation, Inc.
   7 
   8    Written by:
   9    Miguel de Icaza, 1994, 1995, 1998
  10    Janne Kukonlehto, 1994, 1995
  11    Jakub Jelinek, 1995
  12    Joseph M. Hinkle, 1996
  13    Norbert Warmuth, 1997
  14    Pavel Machek, 1998
  15    Roland Illig <roland.illig@gmx.de>, 2004, 2005
  16    Slava Zanko <slavazanko@google.com>, 2009
  17    Andrew Borodin <aborodin@vmail.ru>, 2009-2022
  18    Ilia Maslakov <il.smind@gmail.com>, 2009
  19 
  20    This file is part of the Midnight Commander.
  21 
  22    The Midnight Commander is free software: you can redistribute it
  23    and/or modify it under the terms of the GNU General Public License as
  24    published by the Free Software Foundation, either version 3 of the License,
  25    or (at your option) any later version.
  26 
  27    The Midnight Commander is distributed in the hope that it will be useful,
  28    but WITHOUT ANY WARRANTY; without even the implied warranty of
  29    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  30    GNU General Public License for more details.
  31 
  32    You should have received a copy of the GNU General Public License
  33    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  34  */
  35 
  36 #include <config.h>
  37 
  38 #include "lib/global.h"
  39 #include "lib/strutil.h"
  40 #ifdef HAVE_CHARSET
  41 #    include "lib/charsets.h"  // cp_source
  42 #endif
  43 #include "lib/widget.h"
  44 
  45 #include "src/setup.h"
  46 
  47 #include "internal.h"
  48 
  49 /*** global variables ****************************************************************************/
  50 
  51 mcview_search_options_t mcview_search_options = {
  52     .type = MC_SEARCH_T_NORMAL,
  53     .case_sens = FALSE,
  54     .backwards = FALSE,
  55     .whole_words = FALSE,
  56     .all_codepages = FALSE,
  57 };
  58 
  59 /*** file scope macro definitions ****************************************************************/
  60 
  61 /*** file scope type declarations ****************************************************************/
  62 
  63 typedef struct
  64 {
  65     simple_status_msg_t status_msg;  // base class
  66 
  67     gboolean first;
  68     WView *view;
  69     off_t offset;
  70 } mcview_search_status_msg_t;
  71 
  72 /*** forward declarations (file scope functions) *************************************************/
  73 
  74 /*** file scope variables ************************************************************************/
  75 
  76 static int search_cb_char_curr_index = -1;
  77 static char search_cb_char_buffer[6];
  78 
  79 /* --------------------------------------------------------------------------------------------- */
  80 /*** file scope functions ************************************************************************/
  81 /* --------------------------------------------------------------------------------------------- */
  82 
  83 static int
  84 mcview_search_status_update_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
  85 {
  86     simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
  87     mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) sm;
  88     Widget *wd = WIDGET (sm->dlg);
  89     int percent = -1;
  90 
  91     if (verbose)
  92         percent = mcview_calc_percent (vsm->view, vsm->offset);
  93 
  94     if (percent >= 0)
  95         label_set_textv (ssm->label, _ ("Searching %s: %3d%%"), vsm->view->last_search_string,
  96                          percent);
  97     else
  98         label_set_textv (ssm->label, _ ("Searching %s"), vsm->view->last_search_string);
  99 
 100     if (vsm->first)
 101     {
 102         Widget *lw = WIDGET (ssm->label);
 103         WRect r;
 104 
 105         r = wd->rect;
 106         r.cols = MAX (r.cols, lw->rect.cols + 6);
 107         widget_set_size_rect (wd, &r);
 108         r = lw->rect;
 109         r.x = wd->rect.x + (wd->rect.cols - r.cols) / 2;
 110         widget_set_size_rect (lw, &r);
 111         vsm->first = FALSE;
 112     }
 113 
 114     return status_msg_common_update (sm);
 115 }
 116 
 117 /* --------------------------------------------------------------------------------------------- */
 118 
 119 static inline off_t
 120 mcview_calculate_start_of_previous_line (WView *view, const off_t current_pos)
     /* [previous][next][first][last][top][bottom][index][help]  */
 121 {
 122     const off_t bol = mcview_bol (view, current_pos, 0);
 123 
 124     // Are we in the 1st line?
 125     if (bol == 0)
 126         return (-1);
 127 
 128     return mcview_bol (view, bol - 1, 0);
 129 }
 130 
 131 /* --------------------------------------------------------------------------------------------- */
 132 
 133 static void
 134 mcview_search_update_steps (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 135 {
 136     off_t filesize;
 137 
 138     filesize = mcview_get_filesize (view);
 139 
 140     if (filesize != 0)
 141         view->update_steps = filesize / 100;
 142     else  // viewing a data stream, not a file
 143         view->update_steps = 40000;
 144 
 145     // Do not update the percent display but every 20 kb
 146     if (view->update_steps < 20000)
 147         view->update_steps = 20000;
 148 
 149     // Make interrupt more responsive
 150     if (view->update_steps > 40000)
 151         view->update_steps = 40000;
 152 }
 153 
 154 /* --------------------------------------------------------------------------------------------- */
 155 
 156 static gboolean
 157 mcview_find (mcview_search_status_msg_t *ssm, off_t search_start, off_t search_end, gsize *len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 158 {
 159     WView *view = ssm->view;
 160 
 161     view->search_numNeedSkipChar = 0;
 162     search_cb_char_curr_index = -1;
 163 
 164     if (mcview_search_options.backwards)
 165     {
 166         search_end = mcview_get_filesize (view);
 167 
 168         if ((view->search_line_type & MC_SEARCH_LINE_BEGIN) != 0)
 169             search_start = mcview_bol (view, search_start, 0);
 170 
 171         while (search_start >= 0)
 172         {
 173             gboolean ok;
 174 
 175             view->search_nroff_seq->index = search_start;
 176             mcview_nroff_seq_info (view->search_nroff_seq);
 177 
 178             if (search_end > search_start + (off_t) view->search->original.str->len
 179                 && mc_search_is_fixed_search_str (view->search))
 180                 search_end = search_start + view->search->original.str->len;
 181 
 182             ok = mc_search_run (view->search, (void *) ssm, search_start, search_end, len);
 183             if (ok && view->search->normal_offset == search_start)
 184             {
 185                 if (view->mode_flags.nroff)
 186                     view->search->normal_offset++;
 187                 return TRUE;
 188             }
 189 
 190             /* We abort the search in case of a pattern error, or if the user aborts
 191                the search. In other words: in all cases except "string not found". */
 192             if (!ok && view->search->error != MC_SEARCH_E_NOTFOUND)
 193                 return FALSE;
 194 
 195             if ((view->search_line_type & MC_SEARCH_LINE_BEGIN) != 0)
 196                 search_start = mcview_calculate_start_of_previous_line (view, search_start);
 197             else
 198                 search_start--;
 199         }
 200 
 201         mc_search_set_error (view->search, MC_SEARCH_E_NOTFOUND, "%s", _ (STR_E_NOTFOUND));
 202         return FALSE;
 203     }
 204 
 205     if ((view->search_line_type & MC_SEARCH_LINE_BEGIN) != 0 && search_start != 0)
 206         search_start = mcview_eol (view, search_start);
 207 
 208     view->search_nroff_seq->index = search_start;
 209     mcview_nroff_seq_info (view->search_nroff_seq);
 210 
 211     return mc_search_run (view->search, (void *) ssm, search_start, search_end, len);
 212 }
 213 
 214 /* --------------------------------------------------------------------------------------------- */
 215 
 216 static void
 217 mcview_search_show_result (WView *view, size_t match_len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 218 {
 219     int nroff_len;
 220 
 221     nroff_len = view->mode_flags.nroff
 222         ? mcview__get_nroff_real_len (view, view->search->start_buffer,
 223                                       view->search->normal_offset - view->search->start_buffer)
 224         : 0;
 225     view->search_start = view->search->normal_offset + nroff_len;
 226 
 227     if (!view->mode_flags.hex)
 228         view->search_start++;
 229 
 230     nroff_len = view->mode_flags.nroff
 231         ? mcview__get_nroff_real_len (view, view->search_start - 1, match_len)
 232         : 0;
 233     view->search_end = view->search_start + match_len + nroff_len;
 234 
 235     mcview_moveto_match (view);
 236 }
 237 
 238 /* --------------------------------------------------------------------------------------------- */
 239 
 240 static void
 241 mcview_do_search (WView *view, off_t want_search_start)
     /* [previous][next][first][last][top][bottom][index][help]  */
 242 {
 243     mcview_search_status_msg_t vsm;
 244 
 245     off_t search_start = 0;
 246     off_t orig_search_start = view->search_start;
 247     gboolean found = FALSE;
 248 
 249     size_t match_len;
 250 
 251     view->search_start = want_search_start;
 252     // to avoid infinite search loop we need to increase or decrease start offset of search
 253 
 254     if (view->search_start != 0)
 255     {
 256         if (!view->mode_flags.nroff)
 257             search_start = view->search_start + (mcview_search_options.backwards ? -2 : 0);
 258         else
 259         {
 260             if (mcview_search_options.backwards)
 261             {
 262                 mcview_nroff_t *nroff;
 263 
 264                 nroff = mcview_nroff_seq_new_num (view, view->search_start);
 265                 if (mcview_nroff_seq_prev (nroff) != -1)
 266                     search_start = -(mcview__get_nroff_real_len (view, nroff->index - 1, 2)
 267                                      + nroff->char_length + 1);
 268                 else
 269                     search_start = -2;
 270 
 271                 mcview_nroff_seq_free (&nroff);
 272             }
 273             else
 274             {
 275                 search_start = mcview__get_nroff_real_len (view, view->search_start + 1, 2);
 276             }
 277             search_start += view->search_start;
 278         }
 279     }
 280 
 281     if (mcview_search_options.backwards && search_start < 0)
 282         search_start = 0;
 283 
 284     // Compute the percent steps
 285     mcview_search_update_steps (view);
 286 
 287     view->update_activate = search_start;
 288 
 289     vsm.first = TRUE;
 290     vsm.view = view;
 291     vsm.offset = search_start;
 292 
 293     status_msg_init (STATUS_MSG (&vsm), _ ("Search"), 1.0, simple_status_msg_init_cb,
 294                      mcview_search_status_update_cb, NULL);
 295 
 296     do
 297     {
 298         off_t growbufsize;
 299 
 300         if (view->growbuf_in_use)
 301             growbufsize = mcview_growbuf_filesize (view);
 302         else
 303             growbufsize = view->search->original.str->len;
 304 
 305         if (mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
 306         {
 307             mcview_search_show_result (view, match_len);
 308             found = TRUE;
 309             break;
 310         }
 311 
 312         /* Search error is here.
 313          * MC_SEARCH_E_NOTFOUND: continue search
 314          * others: stop
 315          */
 316         if (view->search->error != MC_SEARCH_E_NOTFOUND)
 317             break;
 318 
 319         search_start = growbufsize - view->search->original.str->len;
 320     }
 321     while (search_start > 0 && mcview_may_still_grow (view));
 322 
 323     // After mcview_may_still_grow (view) == FALSE, last chunk remains. Search there.
 324     if (view->growbuf_in_use && !found && view->search->error == MC_SEARCH_E_NOTFOUND
 325         && !mcview_search_options.backwards
 326         && mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
 327     {
 328         mcview_search_show_result (view, match_len);
 329         found = TRUE;
 330     }
 331 
 332     status_msg_deinit (STATUS_MSG (&vsm));
 333 
 334     if (orig_search_start != 0 && (!found && view->search->error == MC_SEARCH_E_NOTFOUND)
 335         && !mcview_search_options.backwards)
 336     {
 337         view->search_start = orig_search_start;
 338         mcview_update (view);
 339 
 340         if (query_dialog (_ ("Search done"), _ ("Continue from beginning?"), D_NORMAL, 2,
 341                           _ ("&Yes"), _ ("&No"))
 342             != 0)
 343             found = TRUE;
 344         else
 345         {
 346             // continue search from beginning
 347             view->update_activate = 0;
 348 
 349             vsm.first = TRUE;
 350             vsm.view = view;
 351             vsm.offset = 0;
 352 
 353             status_msg_init (STATUS_MSG (&vsm), _ ("Search"), 1.0, simple_status_msg_init_cb,
 354                              mcview_search_status_update_cb, NULL);
 355 
 356             // search from file begin up to initial search start position
 357             if (mcview_find (&vsm, 0, orig_search_start, &match_len))
 358             {
 359                 mcview_search_show_result (view, match_len);
 360                 found = TRUE;
 361             }
 362 
 363             status_msg_deinit (STATUS_MSG (&vsm));
 364         }
 365     }
 366 
 367     if (!found)
 368     {
 369         view->search_start = orig_search_start;
 370         mcview_update (view);
 371 
 372         if (view->search->error == MC_SEARCH_E_NOTFOUND)
 373             message (D_NORMAL, _ ("Search"), "%s", _ (STR_E_NOTFOUND));
 374         else if (view->search->error_str != NULL)
 375             message (D_NORMAL, _ ("Search"), "%s", view->search->error_str);
 376     }
 377 
 378     view->dirty++;
 379 }
 380 
 381 /* --------------------------------------------------------------------------------------------- */
 382 /*** public functions ****************************************************************************/
 383 /* --------------------------------------------------------------------------------------------- */
 384 
 385 gboolean
 386 mcview_search_init (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 387 {
 388 #ifdef HAVE_CHARSET
 389     view->search = mc_search_new (view->last_search_string, cp_source);
 390 #else
 391     view->search = mc_search_new (view->last_search_string, NULL);
 392 #endif
 393 
 394     view->search_nroff_seq = mcview_nroff_seq_new (view);
 395 
 396     if (view->search == NULL)
 397         return FALSE;
 398 
 399     view->search->search_type = mcview_search_options.type;
 400 #ifdef HAVE_CHARSET
 401     view->search->is_all_charsets = mcview_search_options.all_codepages;
 402 #endif
 403     view->search->is_case_sensitive = mcview_search_options.case_sens;
 404     view->search->whole_words = mcview_search_options.whole_words;
 405     view->search->search_fn = mcview_search_cmd_callback;
 406     view->search->update_fn = mcview_search_update_cmd_callback;
 407 
 408     view->search_line_type = mc_search_get_line_type (view->search);
 409 
 410     return TRUE;
 411 }
 412 
 413 /* --------------------------------------------------------------------------------------------- */
 414 
 415 void
 416 mcview_search_deinit (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 417 {
 418     mc_search_free (view->search);
 419     g_free (view->last_search_string);
 420     mcview_nroff_seq_free (&view->search_nroff_seq);
 421 }
 422 
 423 /* --------------------------------------------------------------------------------------------- */
 424 
 425 mc_search_cbret_t
 426 mcview_search_cmd_callback (const void *user_data, off_t char_offset, int *current_char)
     /* [previous][next][first][last][top][bottom][index][help]  */
 427 {
 428     WView *view = ((const mcview_search_status_msg_t *) user_data)->view;
 429 
 430     // AB: FIXME
 431     //    view_read_continue (view, &view->search_onechar_info);
 432     if (!view->mode_flags.nroff)
 433     {
 434         mcview_get_byte (view, char_offset, current_char);
 435         return MC_SEARCH_CB_OK;
 436     }
 437 
 438     if (view->search_numNeedSkipChar != 0)
 439     {
 440         view->search_numNeedSkipChar--;
 441         return MC_SEARCH_CB_SKIP;
 442     }
 443 
 444     if (search_cb_char_curr_index == -1
 445         || search_cb_char_curr_index >= view->search_nroff_seq->char_length)
 446     {
 447         if (search_cb_char_curr_index != -1)
 448             mcview_nroff_seq_next (view->search_nroff_seq);
 449 
 450         search_cb_char_curr_index = 0;
 451         if (view->search_nroff_seq->char_length > 1)
 452             g_unichar_to_utf8 (view->search_nroff_seq->current_char, search_cb_char_buffer);
 453         else
 454             search_cb_char_buffer[0] = (char) view->search_nroff_seq->current_char;
 455 
 456         if (view->search_nroff_seq->type != NROFF_TYPE_NONE)
 457         {
 458             switch (view->search_nroff_seq->type)
 459             {
 460             case NROFF_TYPE_BOLD:
 461                 view->search_numNeedSkipChar =
 462                     1 + view->search_nroff_seq->char_length;  // real char length and 0x8
 463                 break;
 464             case NROFF_TYPE_UNDERLINE:
 465                 view->search_numNeedSkipChar = 2;  // underline symbol and ox8
 466                 break;
 467             default:
 468                 break;
 469             }
 470         }
 471         return MC_SEARCH_CB_INVALID;
 472     }
 473 
 474     *current_char = search_cb_char_buffer[search_cb_char_curr_index];
 475     search_cb_char_curr_index++;
 476 
 477     return (*current_char != -1) ? MC_SEARCH_CB_OK : MC_SEARCH_CB_INVALID;
 478 }
 479 
 480 /* --------------------------------------------------------------------------------------------- */
 481 
 482 mc_search_cbret_t
 483 mcview_search_update_cmd_callback (const void *user_data, off_t char_offset)
     /* [previous][next][first][last][top][bottom][index][help]  */
 484 {
 485     status_msg_t *sm = STATUS_MSG (user_data);
 486     mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) user_data;
 487     WView *view = vsm->view;
 488     gboolean do_update = FALSE;
 489     mc_search_cbret_t result = MC_SEARCH_CB_OK;
 490 
 491     vsm->offset = char_offset;
 492 
 493     if (mcview_search_options.backwards)
 494     {
 495         if (vsm->offset <= view->update_activate)
 496         {
 497             view->update_activate -= view->update_steps;
 498 
 499             do_update = TRUE;
 500         }
 501     }
 502     else
 503     {
 504         if (vsm->offset >= view->update_activate)
 505         {
 506             view->update_activate += view->update_steps;
 507 
 508             do_update = TRUE;
 509         }
 510     }
 511 
 512     if (do_update && sm->update (sm) == B_CANCEL)
 513         result = MC_SEARCH_CB_ABORT;
 514 
 515     // may be in future return from this callback will change current position in searching block.
 516 
 517     return result;
 518 }
 519 
 520 /* --------------------------------------------------------------------------------------------- */
 521 
 522 /* Both views */
 523 void
 524 mcview_search (WView *view, gboolean start_search)
     /* [previous][next][first][last][top][bottom][index][help]  */
 525 {
 526     off_t want_search_start = view->search_start;
 527 
 528     if (start_search)
 529     {
 530         if (mcview_dialog_search (view))
 531         {
 532             if (view->mode_flags.hex)
 533                 want_search_start = view->hex_cursor;
 534 
 535             mcview_do_search (view, want_search_start);
 536         }
 537     }
 538     else
 539     {
 540         if (view->mode_flags.hex)
 541         {
 542             if (!mcview_search_options.backwards)
 543                 want_search_start = view->hex_cursor + 1;
 544             else if (view->hex_cursor > 0)
 545                 want_search_start = view->hex_cursor - 1;
 546             else
 547                 want_search_start = 0;
 548         }
 549 
 550         mcview_do_search (view, want_search_start);
 551     }
 552 }
 553 
 554 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */