root/src/editor/editsearch.c

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

DEFINITIONS

This source file includes following definitions.
  1. edit_dialog_search_show
  2. edit_search_get_current_end_line_char
  3. edit_calculate_start_of_next_line
  4. edit_calculate_end_of_previous_line
  5. edit_calculate_start_of_previous_line
  6. edit_calculate_start_of_current_line
  7. edit_search_fix_search_start_if_selection
  8. edit_find
  9. edit_replace_cmd__conv_to_display
  10. edit_replace_cmd__conv_to_input
  11. edit_search_show_error
  12. edit_do_search
  13. edit_search
  14. edit_search_init
  15. edit_search_deinit
  16. edit_search_cmd_callback
  17. edit_search_update_callback
  18. edit_search_status_update_cb
  19. edit_search_cmd
  20. edit_dialog_replace_show
  21. edit_dialog_replace_prompt_show
  22. edit_replace_cmd

   1 /*
   2    Search & replace engine of MCEditor.
   3 
   4    Copyright (C) 2021-2025
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Andrew Borodin <aborodin@vmail.ru>, 2021-2022
   9 
  10    This file is part of the Midnight Commander.
  11 
  12    The Midnight Commander is free software: you can redistribute it
  13    and/or modify it under the terms of the GNU General Public License as
  14    published by the Free Software Foundation, either version 3 of the License,
  15    or (at your option) any later version.
  16 
  17    The Midnight Commander is distributed in the hope that it will be useful,
  18    but WITHOUT ANY WARRANTY; without even the implied warranty of
  19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20    GNU General Public License for more details.
  21 
  22    You should have received a copy of the GNU General Public License
  23    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  24  */
  25 
  26 #include <config.h>
  27 
  28 #include <assert.h>
  29 
  30 #include "lib/global.h"
  31 #include "lib/search.h"
  32 #include "lib/mcconfig.h"  // mc_config_history_get_recent_item()
  33 #ifdef HAVE_CHARSET
  34 #include "lib/charsets.h"  // cp_source
  35 #endif
  36 #include "lib/util.h"
  37 #include "lib/widget.h"
  38 #include "lib/skin.h"  // BOOK_MARK_FOUND_COLOR
  39 
  40 #include "src/history.h"  // MC_HISTORY_SHARED_SEARCH
  41 #include "src/setup.h"    // verbose
  42 
  43 #include "edit-impl.h"
  44 #include "editwidget.h"
  45 
  46 #include "editsearch.h"
  47 
  48 /*** global variables ****************************************************************************/
  49 
  50 edit_search_options_t edit_search_options = {
  51     .type = MC_SEARCH_T_NORMAL,
  52     .case_sens = FALSE,
  53     .backwards = FALSE,
  54     .only_in_selection = FALSE,
  55     .whole_words = FALSE,
  56     .all_codepages = FALSE,
  57 };
  58 
  59 /*** file scope macro definitions ****************************************************************/
  60 
  61 /*** file scope type declarations ****************************************************************/
  62 
  63 /*** forward declarations (file scope functions) *************************************************/
  64 
  65 MC_MOCKABLE void edit_dialog_replace_show (WEdit *edit, const char *search_default,
  66                                            const char *replace_default, char **search_text,
  67                                            char **replace_text);
  68 
  69 MC_MOCKABLE int edit_dialog_replace_prompt_show (WEdit *edit, char *from_text, char *to_text,
  70                                                  int xpos, int ypos);
  71 
  72 /*** file scope variables ************************************************************************/
  73 
  74 /* --------------------------------------------------------------------------------------------- */
  75 /*** file scope functions ************************************************************************/
  76 /* --------------------------------------------------------------------------------------------- */
  77 
  78 static gboolean
  79 edit_dialog_search_show (WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
  80 {
  81     char *search_text = NULL;
  82     size_t num_of_types = 0;
  83     gchar **list_of_types;
  84     int dialog_result;
  85 
  86     list_of_types = mc_search_get_types_strings_array (&num_of_types);
  87 
  88     {
  89         quick_widget_t quick_widgets[] = {
  90             // clang-format off
  91             QUICK_LABELED_INPUT (N_ ("Enter search string:"), input_label_above, INPUT_LAST_TEXT,
  92                                  MC_HISTORY_SHARED_SEARCH, &search_text, NULL, FALSE, FALSE,
  93                                  INPUT_COMPLETE_NONE),
  94             QUICK_SEPARATOR (TRUE),
  95             QUICK_START_COLUMNS,
  96                 QUICK_RADIO (num_of_types, (const char **) list_of_types,
  97                              (int *) &edit_search_options.type, NULL),
  98             QUICK_NEXT_COLUMN,
  99                 QUICK_CHECKBOX (N_ ("Cas&e sensitive"), &edit_search_options.case_sens, NULL),
 100                 QUICK_CHECKBOX (N_ ("&Backwards"), &edit_search_options.backwards, NULL),
 101                 QUICK_CHECKBOX (N_ ("In se&lection"), &edit_search_options.only_in_selection, NULL),
 102                 QUICK_CHECKBOX (N_ ("&Whole words"), &edit_search_options.whole_words, NULL),
 103 #ifdef HAVE_CHARSET
 104                 QUICK_CHECKBOX (N_ ("&All charsets"), &edit_search_options.all_codepages, NULL),
 105 #endif
 106             QUICK_STOP_COLUMNS,
 107             QUICK_START_BUTTONS (TRUE, TRUE),
 108                 QUICK_BUTTON (N_ ("&OK"), B_ENTER, NULL, NULL),
 109                 QUICK_BUTTON (N_ ("&Find all"), B_USER, NULL, NULL),
 110                 QUICK_BUTTON (N_ ("&Cancel"), B_CANCEL, NULL, NULL),
 111             QUICK_END,
 112             // clang-format on
 113         };
 114 
 115         WRect r = { -1, -1, 0, 58 };
 116 
 117         quick_dialog_t qdlg = {
 118             .rect = r,
 119             .title = N_ ("Search"),
 120             .help = "[Input Line Keys]",
 121             .widgets = quick_widgets,
 122             .callback = NULL,
 123             .mouse_callback = NULL,
 124         };
 125 
 126         dialog_result = quick_dialog (&qdlg);
 127     }
 128 
 129     g_strfreev (list_of_types);
 130 
 131     if (dialog_result == B_CANCEL || search_text == NULL || search_text[0] == '\0')
 132     {
 133         g_free (search_text);
 134         return FALSE;
 135     }
 136 
 137     if (dialog_result == B_USER)
 138         search_create_bookmark = TRUE;
 139 
 140 #ifdef HAVE_CHARSET
 141     {
 142         GString *tmp;
 143 
 144         tmp = str_convert_to_input (search_text);
 145         g_free (search_text);
 146         if (tmp != NULL)
 147             search_text = g_string_free (tmp, FALSE);
 148         else
 149             search_text = g_strdup ("");
 150     }
 151 #endif
 152 
 153     edit_search_deinit (edit);
 154     edit->last_search_string = search_text;
 155 
 156     return edit_search_init (edit, edit->last_search_string);
 157 }
 158 
 159 /* --------------------------------------------------------------------------------------------- */
 160 
 161 /**
 162  * Get EOL symbol for searching.
 163  *
 164  * @param edit editor object
 165  * @return EOL symbol
 166  */
 167 
 168 static inline char
 169 edit_search_get_current_end_line_char (const WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 170 {
 171     switch (edit->lb)
 172     {
 173     case LB_MAC:
 174         return '\r';
 175     default:
 176         return '\n';
 177     }
 178 }
 179 
 180 /* --------------------------------------------------------------------------------------------- */
 181 /**
 182  * Calculating the start position of next line.
 183  *
 184  * @param buf               editor buffer object
 185  * @param current_pos       current position
 186  * @param max_pos           max position
 187  * @param end_string_symbol end of line symbol
 188  * @return start position of next line
 189  */
 190 
 191 static off_t
 192 edit_calculate_start_of_next_line (const edit_buffer_t *buf, off_t current_pos, off_t max_pos,
     /* [previous][next][first][last][top][bottom][index][help]  */
 193                                    char end_string_symbol)
 194 {
 195     off_t i;
 196 
 197     for (i = current_pos; i < max_pos; i++)
 198     {
 199         current_pos++;
 200         if (edit_buffer_get_byte (buf, i) == end_string_symbol)
 201             break;
 202     }
 203 
 204     return current_pos;
 205 }
 206 
 207 /* --------------------------------------------------------------------------------------------- */
 208 /**
 209  * Calculating the end position of previous line.
 210  *
 211  * @param buf               editor buffer object
 212  * @param current_pos       current position
 213  * @param end_string_symbol end of line symbol
 214  * @return end position of previous line
 215  */
 216 
 217 static off_t
 218 edit_calculate_end_of_previous_line (const edit_buffer_t *buf, off_t current_pos,
     /* [previous][next][first][last][top][bottom][index][help]  */
 219                                      char end_string_symbol)
 220 {
 221     off_t i;
 222 
 223     for (i = current_pos - 1; i >= 0; i--)
 224         if (edit_buffer_get_byte (buf, i) == end_string_symbol)
 225             break;
 226 
 227     return i;
 228 }
 229 
 230 /* --------------------------------------------------------------------------------------------- */
 231 /**
 232  * Calculating the start position of previous line.
 233  *
 234  * @param buf               editor buffer object
 235  * @param current_pos       current position
 236  * @param end_string_symbol end of line symbol
 237  * @return start position of previous line
 238  */
 239 
 240 static inline off_t
 241 edit_calculate_start_of_previous_line (const edit_buffer_t *buf, off_t current_pos,
     /* [previous][next][first][last][top][bottom][index][help]  */
 242                                        char end_string_symbol)
 243 {
 244     current_pos = edit_calculate_end_of_previous_line (buf, current_pos, end_string_symbol);
 245     current_pos = edit_calculate_end_of_previous_line (buf, current_pos, end_string_symbol);
 246 
 247     return (current_pos + 1);
 248 }
 249 
 250 /* --------------------------------------------------------------------------------------------- */
 251 /**
 252  * Calculating the start position of current line.
 253  *
 254  * @param buf               editor buffer object
 255  * @param current_pos       current position
 256  * @param end_string_symbol end of line symbol
 257  * @return start position of current line
 258  */
 259 
 260 static inline off_t
 261 edit_calculate_start_of_current_line (const edit_buffer_t *buf, off_t current_pos,
     /* [previous][next][first][last][top][bottom][index][help]  */
 262                                       char end_string_symbol)
 263 {
 264     current_pos = edit_calculate_end_of_previous_line (buf, current_pos, end_string_symbol);
 265 
 266     return (current_pos + 1);
 267 }
 268 
 269 /* --------------------------------------------------------------------------------------------- */
 270 /**
 271  * Fixing (if needed) search start position if 'only in selection' option present.
 272  *
 273  * @param edit editor object
 274  */
 275 
 276 static void
 277 edit_search_fix_search_start_if_selection (WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 278 {
 279     off_t start_mark = 0;
 280     off_t end_mark = 0;
 281 
 282     if (!edit_search_options.only_in_selection)
 283         return;
 284 
 285     if (!eval_marks (edit, &start_mark, &end_mark))
 286         return;
 287 
 288     if (edit_search_options.backwards)
 289     {
 290         if (edit->search_start > end_mark || edit->search_start <= start_mark)
 291             edit->search_start = end_mark;
 292     }
 293     else
 294     {
 295         if (edit->search_start < start_mark || edit->search_start >= end_mark)
 296             edit->search_start = start_mark;
 297     }
 298 }
 299 
 300 /* --------------------------------------------------------------------------------------------- */
 301 
 302 static gboolean
 303 edit_find (edit_search_status_msg_t *esm, gsize *len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 304 {
 305     WEdit *edit = esm->edit;
 306     edit_buffer_t *buf = &edit->buffer;
 307     off_t search_start = edit->search_start;
 308     off_t search_end;
 309     off_t start_mark = 0;
 310     off_t end_mark = buf->size;
 311     gboolean start_from_next_line = FALSE;
 312     char end_string_symbol;
 313 
 314     end_string_symbol = edit_search_get_current_end_line_char (edit);
 315 
 316     // prepare for search
 317     if (edit_search_options.only_in_selection)
 318     {
 319         if (!eval_marks (edit, &start_mark, &end_mark))
 320         {
 321             mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _ (STR_E_NOTFOUND));
 322             return FALSE;
 323         }
 324 
 325         // fix the start and the end of search block positions
 326         if (!edit_search_options.backwards && (edit->search_line_type & MC_SEARCH_LINE_BEGIN) != 0
 327             && search_start != 0)
 328         {
 329             const off_t bol =
 330                 edit_calculate_start_of_current_line (buf, search_start, end_string_symbol);
 331 
 332             if (search_start != bol)
 333             {
 334                 start_mark = edit_calculate_start_of_next_line (buf, start_mark, buf->size,
 335                                                                 end_string_symbol);
 336                 start_from_next_line = TRUE;
 337             }
 338         }
 339 
 340         if ((edit->search_line_type & MC_SEARCH_LINE_END) != 0
 341             && (end_mark - 1 != buf->size
 342                 || edit_buffer_get_byte (buf, end_mark) != end_string_symbol))
 343             end_mark = edit_calculate_end_of_previous_line (buf, end_mark, end_string_symbol);
 344 
 345         if (start_mark >= end_mark)
 346         {
 347             mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _ (STR_E_NOTFOUND));
 348             return FALSE;
 349         }
 350     }
 351     else if (edit_search_options.backwards)
 352         end_mark = MAX (1, buf->curs1) - 1;
 353     else if ((edit->search_line_type & MC_SEARCH_LINE_BEGIN) != 0)
 354     {
 355         // regex forward search
 356         const off_t bol =
 357             edit_calculate_start_of_current_line (buf, search_start, end_string_symbol);
 358 
 359         start_from_next_line = search_start != bol;
 360     }
 361 
 362     // search
 363     if (edit_search_options.backwards)
 364     {
 365         // backward search
 366         search_end = end_mark;
 367 
 368         if ((edit->search_line_type & MC_SEARCH_LINE_BEGIN) != 0)
 369             search_start =
 370                 edit_calculate_start_of_current_line (buf, search_start, end_string_symbol);
 371 
 372         while (search_start >= start_mark)
 373         {
 374             gboolean ok;
 375 
 376             if (search_end > (off_t) (search_start + edit->search->original.str->len)
 377                 && mc_search_is_fixed_search_str (edit->search))
 378                 search_end = search_start + edit->search->original.str->len;
 379 
 380             ok = mc_search_run (edit->search, (void *) esm, search_start, search_end, len);
 381 
 382             if (ok && edit->search->normal_offset == search_start)
 383                 return TRUE;
 384 
 385             /* We abort the search in case of a pattern error, or if the user aborts
 386                the search. In other words: in all cases except "string not found". */
 387             if (!ok && edit->search->error != MC_SEARCH_E_NOTFOUND)
 388                 return FALSE;
 389 
 390             if ((edit->search_line_type & MC_SEARCH_LINE_BEGIN) != 0)
 391                 search_start =
 392                     edit_calculate_start_of_previous_line (buf, search_start, end_string_symbol);
 393             else
 394                 search_start--;
 395         }
 396 
 397         mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _ (STR_E_NOTFOUND));
 398         return FALSE;
 399     }
 400 
 401     // forward search
 402 
 403     // correct end_mark if cursor is in column 0: move end_mark to the end of previous line
 404     if (end_mark == edit_calculate_start_of_current_line (buf, end_mark, end_string_symbol))
 405         end_mark = edit_calculate_end_of_previous_line (buf, end_mark, end_string_symbol);
 406 
 407     if (start_from_next_line)
 408         search_start =
 409             edit_calculate_start_of_next_line (buf, search_start, end_mark, end_string_symbol);
 410 
 411     return mc_search_run (edit->search, (void *) esm, search_start, end_mark, len);
 412 }
 413 
 414 /* --------------------------------------------------------------------------------------------- */
 415 
 416 static char *
 417 edit_replace_cmd__conv_to_display (const char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 418 {
 419 #ifdef HAVE_CHARSET
 420     GString *tmp;
 421 
 422     tmp = str_convert_to_display (str);
 423     if (tmp != NULL)
 424     {
 425         if (tmp->len != 0)
 426             return g_string_free (tmp, FALSE);
 427         g_string_free (tmp, TRUE);
 428     }
 429 #endif
 430     return g_strdup (str);
 431 }
 432 
 433 /* --------------------------------------------------------------------------------------------- */
 434 
 435 static char *
 436 edit_replace_cmd__conv_to_input (char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 437 {
 438 #ifdef HAVE_CHARSET
 439     GString *tmp;
 440 
 441     tmp = str_convert_to_input (str);
 442     if (tmp != NULL)
 443     {
 444         if (tmp->len != 0)
 445             return g_string_free (tmp, FALSE);
 446         g_string_free (tmp, TRUE);
 447     }
 448 #endif
 449     return g_strdup (str);
 450 }
 451 
 452 /* --------------------------------------------------------------------------------------------- */
 453 
 454 static void
 455 edit_search_show_error (const WEdit *edit, const char *title)
     /* [previous][next][first][last][top][bottom][index][help]  */
 456 {
 457     if (edit->search->error == MC_SEARCH_E_NOTFOUND)
 458         message (D_NORMAL, title, "%s", _ (STR_E_NOTFOUND));
 459     else if (edit->search->error_str != NULL)
 460         message (D_NORMAL, title, "%s", edit->search->error_str);
 461 }
 462 
 463 /* --------------------------------------------------------------------------------------------- */
 464 
 465 static void
 466 edit_do_search (WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 467 {
 468     edit_search_status_msg_t esm;
 469     gsize len = 0;
 470 
 471     // This shouldn't happen
 472     assert (edit->search != NULL);
 473 
 474     edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
 475 
 476     esm.first = TRUE;
 477     esm.edit = edit;
 478     esm.offset = edit->search_start;
 479 
 480     status_msg_init (STATUS_MSG (&esm), _ ("Search"), 1.0, simple_status_msg_init_cb,
 481                      edit_search_status_update_cb, NULL);
 482 
 483     if (search_create_bookmark)
 484     {
 485         gboolean found = FALSE;
 486         long l = 0, l_last = -1;
 487         long q = 0;
 488 
 489         search_create_bookmark = FALSE;
 490         book_mark_flush (edit, -1);
 491 
 492         while (mc_search_run (edit->search, (void *) &esm, q, edit->buffer.size, &len))
 493         {
 494             if (!found)
 495                 edit->search_start = edit->search->normal_offset;
 496             found = TRUE;
 497 
 498             l += edit_buffer_count_lines (&edit->buffer, q, edit->search->normal_offset);
 499             if (l != l_last)
 500                 book_mark_insert (edit, l, BOOK_MARK_FOUND_COLOR);
 501             l_last = l;
 502             q = edit->search->normal_offset + 1;
 503         }
 504 
 505         if (!found)
 506             message (D_NORMAL, _ ("Search"), "%s", _ (STR_E_NOTFOUND));
 507         else
 508             edit_cursor_move (edit, edit->search_start - edit->buffer.curs1);
 509     }
 510     else
 511     {
 512         if (edit_search_options.backwards)
 513         {
 514             if (edit->found_len != 0 && edit->search_start == edit->found_start + 1)
 515                 edit->search_start--;
 516         }
 517         else
 518         {
 519             if (edit->found_len != 0 && edit->search_start == edit->found_start - 1)
 520                 edit->search_start++;
 521         }
 522 
 523         if (edit_find (&esm, &len))
 524         {
 525             edit->found_start = edit->search->normal_offset;
 526             edit->found_len = len;
 527 
 528             edit->over_col = 0;
 529             edit_cursor_move (edit, edit->found_start - edit->buffer.curs1);
 530             edit_scroll_screen_over_cursor (edit);
 531 
 532             if (edit_search_options.backwards)
 533                 edit->search_start = edit->found_start - 1;
 534             else
 535                 edit->search_start = edit->found_start + edit->found_len;
 536         }
 537         else
 538         {
 539             edit->search_start = edit->buffer.curs1;
 540             edit_search_show_error (edit, _ ("Search"));
 541         }
 542     }
 543 
 544     status_msg_deinit (STATUS_MSG (&esm));
 545 
 546     edit->force |= REDRAW_COMPLETELY;
 547     edit_scroll_screen_over_cursor (edit);
 548 }
 549 
 550 /* --------------------------------------------------------------------------------------------- */
 551 
 552 static void
 553 edit_search (WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 554 {
 555     if (edit_dialog_search_show (edit))
 556         edit_do_search (edit);
 557 }
 558 
 559 /* --------------------------------------------------------------------------------------------- */
 560 /*** public functions ****************************************************************************/
 561 /* --------------------------------------------------------------------------------------------- */
 562 
 563 gboolean
 564 edit_search_init (WEdit *edit, const char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 565 {
 566 #ifdef HAVE_CHARSET
 567     edit->search = mc_search_new (str, cp_source);
 568 #else
 569     edit->search = mc_search_new (str, NULL);
 570 #endif
 571 
 572     if (edit->search == NULL)
 573         return FALSE;
 574 
 575     edit->search->search_type = edit_search_options.type;
 576 #ifdef HAVE_CHARSET
 577     edit->search->is_all_charsets = edit_search_options.all_codepages;
 578 #endif
 579     edit->search->is_case_sensitive = edit_search_options.case_sens;
 580     edit->search->whole_words = edit_search_options.whole_words;
 581     edit->search->search_fn = edit_search_cmd_callback;
 582     edit->search->update_fn = edit_search_update_callback;
 583 
 584     edit->search_line_type = mc_search_get_line_type (edit->search);
 585 
 586     edit_search_fix_search_start_if_selection (edit);
 587 
 588     return TRUE;
 589 }
 590 
 591 /* --------------------------------------------------------------------------------------------- */
 592 
 593 void
 594 edit_search_deinit (WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 595 {
 596     mc_search_free (edit->search);
 597     g_free (edit->last_search_string);
 598 }
 599 
 600 /* --------------------------------------------------------------------------------------------- */
 601 
 602 mc_search_cbret_t
 603 edit_search_cmd_callback (const void *user_data, off_t char_offset, int *current_char)
     /* [previous][next][first][last][top][bottom][index][help]  */
 604 {
 605     WEdit *edit = ((const edit_search_status_msg_t *) user_data)->edit;
 606 
 607     *current_char = edit_buffer_get_byte (&edit->buffer, char_offset);
 608 
 609     return MC_SEARCH_CB_OK;
 610 }
 611 
 612 /* --------------------------------------------------------------------------------------------- */
 613 
 614 mc_search_cbret_t
 615 edit_search_update_callback (const void *user_data, off_t char_offset)
     /* [previous][next][first][last][top][bottom][index][help]  */
 616 {
 617     status_msg_t *sm = STATUS_MSG (user_data);
 618 
 619     ((edit_search_status_msg_t *) sm)->offset = char_offset;
 620 
 621     return (sm->update (sm) == B_CANCEL ? MC_SEARCH_CB_ABORT : MC_SEARCH_CB_OK);
 622 }
 623 
 624 /* --------------------------------------------------------------------------------------------- */
 625 
 626 int
 627 edit_search_status_update_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
 628 {
 629     simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
 630     edit_search_status_msg_t *esm = (edit_search_status_msg_t *) sm;
 631     Widget *wd = WIDGET (sm->dlg);
 632 
 633     if (verbose)
 634         label_set_textv (ssm->label, _ ("Searching %s: %3d%%"), esm->edit->last_search_string,
 635                          edit_buffer_calc_percent (&esm->edit->buffer, esm->offset));
 636     else
 637         label_set_textv (ssm->label, _ ("Searching %s"), esm->edit->last_search_string);
 638 
 639     if (esm->first)
 640     {
 641         Widget *lw = WIDGET (ssm->label);
 642         WRect r;
 643 
 644         r = wd->rect;
 645         r.cols = MAX (r.cols, lw->rect.cols + 6);
 646         widget_set_size_rect (wd, &r);
 647         r = lw->rect;
 648         r.x = wd->rect.x + (wd->rect.cols - r.cols) / 2;
 649         widget_set_size_rect (lw, &r);
 650         esm->first = FALSE;
 651     }
 652 
 653     return status_msg_common_update (sm);
 654 }
 655 
 656 /* --------------------------------------------------------------------------------------------- */
 657 
 658 void
 659 edit_search_cmd (WEdit *edit, gboolean again)
     /* [previous][next][first][last][top][bottom][index][help]  */
 660 {
 661     if (!again)
 662         edit_search (edit);
 663     else if (edit->last_search_string != NULL)
 664         edit_do_search (edit);
 665     else
 666     {
 667         // find last search string in history
 668         char *s;
 669 
 670         s = mc_config_history_get_recent_item (MC_HISTORY_SHARED_SEARCH);
 671         if (s != NULL)
 672         {
 673             edit->last_search_string = s;
 674 
 675             if (edit_search_init (edit, edit->last_search_string))
 676             {
 677                 edit_do_search (edit);
 678                 return;
 679             }
 680 
 681             // found, but cannot init search
 682             MC_PTR_FREE (edit->last_search_string);
 683         }
 684 
 685         // if not... then ask for an expression
 686         edit_search (edit);
 687     }
 688 }
 689 
 690 /* --------------------------------------------------------------------------------------------- */
 691 
 692 void
 693 edit_dialog_replace_show (WEdit *edit, const char *search_default, const char *replace_default,
     /* [previous][next][first][last][top][bottom][index][help]  */
 694                           /*@out@ */ char **search_text, /*@out@ */ char **replace_text)
 695 {
 696     size_t num_of_types = 0;
 697     gchar **list_of_types;
 698 
 699     if ((search_default == NULL) || (*search_default == '\0'))
 700         search_default = INPUT_LAST_TEXT;
 701 
 702     list_of_types = mc_search_get_types_strings_array (&num_of_types);
 703 
 704     {
 705         quick_widget_t quick_widgets[] = {
 706             // clang-format off
 707             QUICK_LABELED_INPUT (N_ ("Enter search string:"), input_label_above, search_default,
 708                                  MC_HISTORY_SHARED_SEARCH, search_text, NULL, FALSE, FALSE,
 709                                  INPUT_COMPLETE_NONE),
 710             QUICK_LABELED_INPUT (N_ ("Enter replacement string:"), input_label_above,
 711                                  replace_default, "replace", replace_text, NULL, FALSE, FALSE,
 712                                  INPUT_COMPLETE_NONE),
 713             QUICK_SEPARATOR (TRUE),
 714             QUICK_START_COLUMNS,
 715                 QUICK_RADIO (num_of_types, (const char **) list_of_types,
 716                              (int *) &edit_search_options.type, NULL),
 717             QUICK_NEXT_COLUMN,
 718                 QUICK_CHECKBOX (N_ ("Cas&e sensitive"), &edit_search_options.case_sens, NULL),
 719                 QUICK_CHECKBOX (N_ ("&Backwards"), &edit_search_options.backwards, NULL),
 720                 QUICK_CHECKBOX (N_ ("In se&lection"), &edit_search_options.only_in_selection, NULL),
 721                 QUICK_CHECKBOX (N_ ("&Whole words"), &edit_search_options.whole_words, NULL),
 722 #ifdef HAVE_CHARSET
 723                 QUICK_CHECKBOX (N_ ("&All charsets"), &edit_search_options.all_codepages, NULL),
 724 #endif
 725             QUICK_STOP_COLUMNS,
 726             QUICK_BUTTONS_OK_CANCEL,
 727             QUICK_END,
 728             // clang-format on
 729         };
 730 
 731         WRect r = { -1, -1, 0, 58 };
 732 
 733         quick_dialog_t qdlg = {
 734             .rect = r,
 735             .title = N_ ("Replace"),
 736             .help = "[Input Line Keys]",
 737             .widgets = quick_widgets,
 738             .callback = NULL,
 739             .mouse_callback = NULL,
 740         };
 741 
 742         if (quick_dialog (&qdlg) != B_CANCEL)
 743             edit->replace_mode = 0;
 744         else
 745         {
 746             *replace_text = NULL;
 747             *search_text = NULL;
 748         }
 749     }
 750 
 751     g_strfreev (list_of_types);
 752 }
 753 
 754 /* --------------------------------------------------------------------------------------------- */
 755 
 756 int
 757 edit_dialog_replace_prompt_show (WEdit *edit, char *from_text, char *to_text, int xpos, int ypos)
     /* [previous][next][first][last][top][bottom][index][help]  */
 758 {
 759     Widget *w = WIDGET (edit);
 760 
 761     // dialog size
 762     int dlg_height = 10;
 763     int dlg_width;
 764 
 765     char tmp[BUF_MEDIUM];
 766     char *repl_from, *repl_to;
 767     int retval;
 768 
 769     if (xpos == -1)
 770         xpos = w->rect.x + edit_options.line_state_width + 1;
 771     if (ypos == -1)
 772         ypos = w->rect.y + w->rect.lines / 2;
 773     // Sometimes menu can hide replaced text. I don't like it
 774     if ((edit->curs_row >= ypos - 1) && (edit->curs_row <= ypos + dlg_height - 1))
 775         ypos -= dlg_height;
 776 
 777     dlg_width = WIDGET (w->owner)->rect.cols - xpos - 1;
 778 
 779     g_snprintf (tmp, sizeof (tmp), "\"%s\"", from_text);
 780     repl_from = g_strdup (str_trunc (tmp, dlg_width - 7));
 781 
 782     g_snprintf (tmp, sizeof (tmp), "\"%s\"", to_text);
 783     repl_to = g_strdup (str_trunc (tmp, dlg_width - 7));
 784 
 785     {
 786         quick_widget_t quick_widgets[] = {
 787             // clang-format off
 788             QUICK_LABEL (repl_from, NULL),
 789             QUICK_LABEL (N_ ("Replace with:"), NULL),
 790             QUICK_LABEL (repl_to, NULL),
 791             QUICK_START_BUTTONS (TRUE, TRUE),
 792                 QUICK_BUTTON (N_ ("&Replace"), B_ENTER, NULL, NULL),
 793                 QUICK_BUTTON (N_ ("A&ll"), B_REPLACE_ALL, NULL, NULL),
 794                 QUICK_BUTTON (N_ ("&Skip"), B_SKIP_REPLACE, NULL, NULL),
 795                 QUICK_BUTTON (N_ ("&Cancel"), B_CANCEL, NULL, NULL),
 796             QUICK_END,
 797             // clang-format on
 798         };
 799 
 800         WRect r = { ypos, xpos, 0, -1 };
 801 
 802         quick_dialog_t qdlg = {
 803             .rect = r,
 804             .title = N_ ("Confirm replace"),
 805             .help = NULL,
 806             .widgets = quick_widgets,
 807             .callback = NULL,
 808             .mouse_callback = NULL,
 809         };
 810 
 811         retval = quick_dialog (&qdlg);
 812     }
 813 
 814     g_free (repl_from);
 815     g_free (repl_to);
 816 
 817     return retval;
 818 }
 819 
 820 /* --------------------------------------------------------------------------------------------- */
 821 /** call with edit = 0 before shutdown to close memory leaks */
 822 
 823 void
 824 edit_replace_cmd (WEdit *edit, gboolean again)
     /* [previous][next][first][last][top][bottom][index][help]  */
 825 {
 826     // 1 = search string, 2 = replace with
 827     static char *saved1 = NULL;  // saved default[123]
 828     static char *saved2 = NULL;
 829     char *input1 = NULL;  // user input from the dialog
 830     char *input2 = NULL;
 831     GString *input2_str = NULL;
 832     char *disp1 = NULL;
 833     char *disp2 = NULL;
 834     long times_replaced = 0;
 835     gboolean once_found = FALSE;
 836     edit_search_status_msg_t esm;
 837 
 838     if (edit == NULL)
 839     {
 840         MC_PTR_FREE (saved1);
 841         MC_PTR_FREE (saved2);
 842         return;
 843     }
 844 
 845     edit->force |= REDRAW_COMPLETELY;
 846 
 847     if (again && saved1 == NULL && saved2 == NULL)
 848         again = FALSE;
 849 
 850     if (again)
 851     {
 852         input1 = g_strdup (saved1 != NULL ? saved1 : "");
 853         input2 = g_strdup (saved2 != NULL ? saved2 : "");
 854     }
 855     else
 856     {
 857         char *tmp_inp1, *tmp_inp2;
 858 
 859         disp1 = edit_replace_cmd__conv_to_display (saved1 != NULL ? saved1 : "");
 860         disp2 = edit_replace_cmd__conv_to_display (saved2 != NULL ? saved2 : "");
 861 
 862         edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
 863 
 864         edit_dialog_replace_show (edit, disp1, disp2, &input1, &input2);
 865 
 866         g_free (disp1);
 867         g_free (disp2);
 868 
 869         if (input1 == NULL || *input1 == '\0')
 870         {
 871             edit->force = REDRAW_COMPLETELY;
 872             goto cleanup;
 873         }
 874 
 875         tmp_inp1 = input1;
 876         tmp_inp2 = input2;
 877         input1 = edit_replace_cmd__conv_to_input (input1);
 878         input2 = edit_replace_cmd__conv_to_input (input2);
 879         g_free (tmp_inp1);
 880         g_free (tmp_inp2);
 881 
 882         g_free (saved1);
 883         saved1 = g_strdup (input1);
 884         g_free (saved2);
 885         saved2 = g_strdup (input2);
 886 
 887         mc_search_free (edit->search);
 888         edit->search = NULL;
 889     }
 890 
 891     input2_str = g_string_new_take (input2);
 892     input2 = NULL;
 893 
 894     if (edit->search == NULL && !edit_search_init (edit, input1))
 895     {
 896         edit->search_start = edit->buffer.curs1;
 897         goto cleanup;
 898     }
 899 
 900     if (edit_search_options.backwards)
 901     {
 902         if (edit->found_len != 0 && edit->search_start == edit->found_start + 1)
 903             edit->search_start--;
 904     }
 905     else
 906     {
 907         if (edit->found_len != 0 && edit->search_start == edit->found_start - 1)
 908             edit->search_start++;
 909     }
 910 
 911     esm.first = TRUE;
 912     esm.edit = edit;
 913     esm.offset = edit->search_start;
 914 
 915     status_msg_init (STATUS_MSG (&esm), _ ("Search"), 1.0, simple_status_msg_init_cb,
 916                      edit_search_status_update_cb, NULL);
 917 
 918     do
 919     {
 920         gsize len = 0;
 921 
 922         if (!edit_find (&esm, &len))
 923         {
 924             if (!(edit->search->error == MC_SEARCH_E_OK
 925                   || (once_found && edit->search->error == MC_SEARCH_E_NOTFOUND)))
 926                 edit_search_show_error (edit, _ ("Search"));
 927             break;
 928         }
 929 
 930         once_found = TRUE;
 931 
 932         edit->search_start = edit->search->normal_offset;
 933         // returns negative on not found or error in pattern
 934 
 935         if (edit->search_start >= 0 && edit->search_start < edit->buffer.size)
 936         {
 937             gsize i;
 938             GString *repl_str;
 939 
 940             edit->found_start = edit->search_start;
 941             edit->found_len = len;
 942 
 943             edit_cursor_move (edit, edit->found_start - edit->buffer.curs1);
 944             edit_scroll_screen_over_cursor (edit);
 945 
 946             if (edit->replace_mode == 0)
 947             {
 948                 long l;
 949                 int prompt;
 950 
 951                 l = edit->curs_row - WIDGET (edit)->rect.lines / 3;
 952                 if (l > 0)
 953                     edit_scroll_downward (edit, l);
 954                 if (l < 0)
 955                     edit_scroll_upward (edit, -l);
 956 
 957                 edit_scroll_screen_over_cursor (edit);
 958                 edit->force |= REDRAW_PAGE;
 959                 edit_render_keypress (edit);
 960 
 961                 // so that undo stops at each query
 962                 edit_push_key_press (edit);
 963                 // and prompt 2/3 down
 964                 disp1 = edit_replace_cmd__conv_to_display (saved1);
 965                 disp2 = edit_replace_cmd__conv_to_display (saved2);
 966                 prompt = edit_dialog_replace_prompt_show (edit, disp1, disp2, -1, -1);
 967                 g_free (disp1);
 968                 g_free (disp2);
 969 
 970                 if (prompt == B_REPLACE_ALL)
 971                     edit->replace_mode = 1;
 972                 else if (prompt == B_SKIP_REPLACE)
 973                 {
 974                     if (edit_search_options.backwards)
 975                         edit->search_start--;
 976                     else
 977                         edit->search_start++;
 978                     continue;  // loop
 979                 }
 980                 else if (prompt == B_CANCEL)
 981                 {
 982                     edit->replace_mode = -1;
 983                     break;  // loop
 984                 }
 985             }
 986 
 987             repl_str = mc_search_prepare_replace_str (edit->search, input2_str);
 988 
 989             if (edit->search->error != MC_SEARCH_E_OK)
 990             {
 991                 edit_search_show_error (edit, _ ("Replace"));
 992                 if (repl_str != NULL)
 993                     g_string_free (repl_str, TRUE);
 994                 break;
 995             }
 996 
 997             // delete then insert new
 998             for (i = 0; i < len; i++)
 999                 edit_delete (edit, TRUE);
1000 
1001             for (i = 0; i < repl_str->len; i++)
1002                 edit_insert (edit, repl_str->str[i]);
1003 
1004             edit->found_len = repl_str->len;
1005             g_string_free (repl_str, TRUE);
1006             times_replaced++;
1007 
1008             // so that we don't find the same string again
1009             if (edit_search_options.backwards)
1010                 edit->search_start--;
1011             else
1012             {
1013                 edit->search_start += edit->found_len + (len == 0 ? 1 : 0);
1014 
1015                 if (edit->search_start >= edit->buffer.size)
1016                     break;
1017             }
1018 
1019             edit_scroll_screen_over_cursor (edit);
1020         }
1021         else
1022         {
1023             // try and find from right here for next search
1024             edit->search_start = edit->buffer.curs1;
1025             edit_update_curs_col (edit);
1026 
1027             edit->force |= REDRAW_PAGE;
1028             edit_render_keypress (edit);
1029 
1030             if (times_replaced == 0)
1031                 query_dialog (_ ("Replace"), _ (STR_E_NOTFOUND), D_NORMAL, 1, _ ("&OK"));
1032             break;
1033         }
1034     }
1035     while (edit->replace_mode >= 0);
1036 
1037     status_msg_deinit (STATUS_MSG (&esm));
1038     edit_scroll_screen_over_cursor (edit);
1039     edit->force |= REDRAW_COMPLETELY;
1040     edit_render_keypress (edit);
1041 
1042     if (edit->replace_mode == 1 && times_replaced != 0)
1043         message (D_NORMAL, _ ("Replace"), _ ("%ld replacements made"), times_replaced);
1044 
1045 cleanup:
1046     g_free (input1);
1047     g_free (input2);
1048     if (input2_str != NULL)
1049         g_string_free (input2_str, TRUE);
1050 }
1051 
1052 /* --------------------------------------------------------------------------------------------- */

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