Manual pages: mcmcdiffmceditmcview

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

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