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-2024
   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 <http://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 =
 222         view->mode_flags.nroff
 223         ? mcview__get_nroff_real_len (view, view->search->start_buffer,
 224                                       view->search->normal_offset - view->search->start_buffer) : 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 =
 231         view->mode_flags.nroff ? mcview__get_nroff_real_len (view, view->search_start - 1,
 232                                                              match_len) : 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 =
 267                         -(mcview__get_nroff_real_len (view, nroff->index - 1, 2) +
 268                           nroff->char_length + 1);
 269                 else
 270                     search_start = -2;
 271 
 272                 mcview_nroff_seq_free (&nroff);
 273             }
 274             else
 275             {
 276                 search_start = mcview__get_nroff_real_len (view, view->search_start + 1, 2);
 277             }
 278             search_start += view->search_start;
 279         }
 280     }
 281 
 282     if (mcview_search_options.backwards && search_start < 0)
 283         search_start = 0;
 284 
 285     /* Compute the percent steps */
 286     mcview_search_update_steps (view);
 287 
 288     view->update_activate = search_start;
 289 
 290     vsm.first = TRUE;
 291     vsm.view = view;
 292     vsm.offset = search_start;
 293 
 294     status_msg_init (STATUS_MSG (&vsm), _("Search"), 1.0, simple_status_msg_init_cb,
 295                      mcview_search_status_update_cb, NULL);
 296 
 297     do
 298     {
 299         off_t growbufsize;
 300 
 301         if (view->growbuf_in_use)
 302             growbufsize = mcview_growbuf_filesize (view);
 303         else
 304             growbufsize = view->search->original.str->len;
 305 
 306         if (mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
 307         {
 308             mcview_search_show_result (view, match_len);
 309             found = TRUE;
 310             break;
 311         }
 312 
 313         /* Search error is here.
 314          * MC_SEARCH_E_NOTFOUND: continue search
 315          * others: stop
 316          */
 317         if (view->search->error != MC_SEARCH_E_NOTFOUND)
 318             break;
 319 
 320         search_start = growbufsize - view->search->original.str->len;
 321     }
 322     while (search_start > 0 && mcview_may_still_grow (view));
 323 
 324     /* After mcview_may_still_grow (view) == FALSE, last chunk remains. Search there. */
 325     if (view->growbuf_in_use && !found && view->search->error == MC_SEARCH_E_NOTFOUND
 326         && !mcview_search_options.backwards
 327         && mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
 328     {
 329         mcview_search_show_result (view, match_len);
 330         found = TRUE;
 331     }
 332 
 333     status_msg_deinit (STATUS_MSG (&vsm));
 334 
 335     if (orig_search_start != 0 && (!found && view->search->error == MC_SEARCH_E_NOTFOUND)
 336         && !mcview_search_options.backwards)
 337     {
 338         view->search_start = orig_search_start;
 339         mcview_update (view);
 340 
 341         if (query_dialog
 342             (_("Search done"), _("Continue from beginning?"), D_NORMAL, 2, _("&Yes"),
 343              _("&No")) != 0)
 344             found = TRUE;
 345         else
 346         {
 347             /* continue search from beginning */
 348             view->update_activate = 0;
 349 
 350             vsm.first = TRUE;
 351             vsm.view = view;
 352             vsm.offset = 0;
 353 
 354             status_msg_init (STATUS_MSG (&vsm), _("Search"), 1.0, simple_status_msg_init_cb,
 355                              mcview_search_status_update_cb, NULL);
 356 
 357             /* search from file begin up to initial search start position */
 358             if (mcview_find (&vsm, 0, orig_search_start, &match_len))
 359             {
 360                 mcview_search_show_result (view, match_len);
 361                 found = TRUE;
 362             }
 363 
 364             status_msg_deinit (STATUS_MSG (&vsm));
 365         }
 366     }
 367 
 368     if (!found)
 369     {
 370         view->search_start = orig_search_start;
 371         mcview_update (view);
 372 
 373         if (view->search->error == MC_SEARCH_E_NOTFOUND)
 374             query_dialog (_("Search"), _(STR_E_NOTFOUND), D_NORMAL, 1, _("&Dismiss"));
 375         else if (view->search->error_str != NULL)
 376             query_dialog (_("Search"), view->search->error_str, D_NORMAL, 1, _("&Dismiss"));
 377     }
 378 
 379     view->dirty++;
 380 }
 381 
 382 /* --------------------------------------------------------------------------------------------- */
 383 /*** public functions ****************************************************************************/
 384 /* --------------------------------------------------------------------------------------------- */
 385 
 386 gboolean
 387 mcview_search_init (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 388 {
 389 #ifdef HAVE_CHARSET
 390     view->search = mc_search_new (view->last_search_string, cp_source);
 391 #else
 392     view->search = mc_search_new (view->last_search_string, NULL);
 393 #endif
 394 
 395     view->search_nroff_seq = mcview_nroff_seq_new (view);
 396 
 397     if (view->search == NULL)
 398         return FALSE;
 399 
 400     view->search->search_type = mcview_search_options.type;
 401 #ifdef HAVE_CHARSET
 402     view->search->is_all_charsets = mcview_search_options.all_codepages;
 403 #endif
 404     view->search->is_case_sensitive = mcview_search_options.case_sens;
 405     view->search->whole_words = mcview_search_options.whole_words;
 406     view->search->search_fn = mcview_search_cmd_callback;
 407     view->search->update_fn = mcview_search_update_cmd_callback;
 408 
 409     view->search_line_type = mc_search_get_line_type (view->search);
 410 
 411     return TRUE;
 412 }
 413 
 414 /* --------------------------------------------------------------------------------------------- */
 415 
 416 void
 417 mcview_search_deinit (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 418 {
 419     mc_search_free (view->search);
 420     g_free (view->last_search_string);
 421     mcview_nroff_seq_free (&view->search_nroff_seq);
 422 }
 423 
 424 /* --------------------------------------------------------------------------------------------- */
 425 
 426 mc_search_cbret_t
 427 mcview_search_cmd_callback (const void *user_data, gsize char_offset, int *current_char)
     /* [previous][next][first][last][top][bottom][index][help]  */
 428 {
 429     WView *view = ((const mcview_search_status_msg_t *) user_data)->view;
 430 
 431     /*    view_read_continue (view, &view->search_onechar_info); *//* AB:FIXME */
 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 = 1 + view->search_nroff_seq->char_length; /* real char length and 0x8 */
 462                 break;
 463             case NROFF_TYPE_UNDERLINE:
 464                 view->search_numNeedSkipChar = 2;       /* underline symbol and ox8 */
 465                 break;
 466             default:
 467                 break;
 468             }
 469         }
 470         return MC_SEARCH_CB_INVALID;
 471     }
 472 
 473     *current_char = search_cb_char_buffer[search_cb_char_curr_index];
 474     search_cb_char_curr_index++;
 475 
 476     return (*current_char != -1) ? MC_SEARCH_CB_OK : MC_SEARCH_CB_INVALID;
 477 }
 478 
 479 /* --------------------------------------------------------------------------------------------- */
 480 
 481 mc_search_cbret_t
 482 mcview_search_update_cmd_callback (const void *user_data, gsize char_offset)
     /* [previous][next][first][last][top][bottom][index][help]  */
 483 {
 484     status_msg_t *sm = STATUS_MSG (user_data);
 485     mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) user_data;
 486     WView *view = vsm->view;
 487     gboolean do_update = FALSE;
 488     mc_search_cbret_t result = MC_SEARCH_CB_OK;
 489 
 490     vsm->offset = (off_t) char_offset;
 491 
 492     if (mcview_search_options.backwards)
 493     {
 494         if (vsm->offset <= view->update_activate)
 495         {
 496             view->update_activate -= view->update_steps;
 497 
 498             do_update = TRUE;
 499         }
 500     }
 501     else
 502     {
 503         if (vsm->offset >= view->update_activate)
 504         {
 505             view->update_activate += view->update_steps;
 506 
 507             do_update = TRUE;
 508         }
 509     }
 510 
 511     if (do_update && sm->update (sm) == B_CANCEL)
 512         result = MC_SEARCH_CB_ABORT;
 513 
 514     /* may be in future return from this callback will change current position in searching block. */
 515 
 516     return result;
 517 }
 518 
 519 /* --------------------------------------------------------------------------------------------- */
 520 
 521 /* Both views */
 522 void
 523 mcview_search (WView *view, gboolean start_search)
     /* [previous][next][first][last][top][bottom][index][help]  */
 524 {
 525     off_t want_search_start = view->search_start;
 526 
 527     if (start_search)
 528     {
 529         if (mcview_dialog_search (view))
 530         {
 531             if (view->mode_flags.hex)
 532                 want_search_start = view->hex_cursor;
 533 
 534             mcview_do_search (view, want_search_start);
 535         }
 536     }
 537     else
 538     {
 539         if (view->mode_flags.hex)
 540         {
 541             if (!mcview_search_options.backwards)
 542                 want_search_start = view->hex_cursor + 1;
 543             else if (view->hex_cursor > 0)
 544                 want_search_start = view->hex_cursor - 1;
 545             else
 546                 want_search_start = 0;
 547         }
 548 
 549         mcview_do_search (view, want_search_start);
 550     }
 551 }
 552 
 553 /* --------------------------------------------------------------------------------------------- */

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