Manual pages: mcmcdiffmceditmcview

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

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