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"  // EDITOR_BOOKMARK_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 (_ ("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 (_ ("Cas&e sensitive"), &edit_search_options.case_sens, NULL),
  98                 QUICK_CHECKBOX (_ ("&Backwards"), &edit_search_options.backwards, NULL),
  99                 QUICK_CHECKBOX (_ ("In se&lection"), &edit_search_options.only_in_selection, NULL),
 100                 QUICK_CHECKBOX (_ ("&Whole words"), &edit_search_options.whole_words, NULL),
 101                 QUICK_CHECKBOX (_ ("&All charsets"), &edit_search_options.all_codepages, NULL),
 102             QUICK_STOP_COLUMNS,
 103             QUICK_START_BUTTONS (TRUE, TRUE),
 104                 QUICK_BUTTON (_ ("&OK"), B_ENTER, NULL, NULL),
 105                 QUICK_BUTTON (_ ("&Find all"), B_USER, NULL, NULL),
 106                 QUICK_BUTTON (_ ("&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 = _ ("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         // in case of backward search, cursor posision could be anywhere
 323         if (!edit_search_options.backwards && search_start >= end_mark)
 324         {
 325             mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _ (STR_E_NOTFOUND));
 326             return FALSE;
 327         }
 328     }
 329     else if (edit_search_options.backwards)
 330         end_mark = MAX (1, buf->curs1) - 1;
 331     else if ((edit->search_line_type & MC_SEARCH_LINE_BEGIN) != 0)
 332     {
 333         // regex forward search
 334         const off_t bol =
 335             edit_calculate_start_of_current_line (buf, search_start, end_string_symbol);
 336 
 337         start_from_next_line = search_start != bol;
 338     }
 339 
 340     // search
 341     if (edit_search_options.backwards)
 342     {
 343         // backward search
 344 
 345         off_t search_end = end_mark;
 346 
 347         if ((edit->search_line_type & MC_SEARCH_LINE_BEGIN) != 0)
 348             search_start =
 349                 edit_calculate_start_of_current_line (buf, search_start, end_string_symbol);
 350 
 351         while (search_start >= start_mark)
 352         {
 353             gboolean ok;
 354 
 355             if (search_end > (off_t) (search_start + edit->search->original.str->len)
 356                 && mc_search_is_fixed_search_str (edit->search))
 357                 search_end = search_start + edit->search->original.str->len;
 358 
 359             ok = mc_search_run (edit->search, (void *) esm, search_start, search_end, len);
 360 
 361             if (ok && edit->search->normal_offset == search_start)
 362                 return TRUE;
 363 
 364             /* We abort the search in case of a pattern error, or if the user aborts
 365                the search. In other words: in all cases except "string not found". */
 366             if (!ok && edit->search->error != MC_SEARCH_E_NOTFOUND)
 367                 return FALSE;
 368 
 369             if ((edit->search_line_type & MC_SEARCH_LINE_BEGIN) != 0)
 370                 search_start =
 371                     edit_calculate_start_of_previous_line (buf, search_start, end_string_symbol);
 372             else
 373                 search_start--;
 374         }
 375 
 376         mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _ (STR_E_NOTFOUND));
 377         return FALSE;
 378     }
 379 
 380     // forward search
 381 
 382     // correct end_mark if cursor is in column 0: move end_mark to the end of previous line
 383     if (edit_search_options.only_in_selection
 384         && end_mark == edit_calculate_start_of_current_line (buf, end_mark, end_string_symbol))
 385     {
 386         end_mark = edit_calculate_end_of_previous_line (buf, end_mark, end_string_symbol);
 387 
 388         // update bottom marker
 389         if (edit->mark2 >= 0 && edit->mark2 != edit->mark1)
 390         {
 391             if (edit->mark2 > edit->mark1)
 392                 edit->mark2 = end_mark;
 393             else
 394                 edit->mark1 = end_mark;
 395         }
 396     }
 397 
 398     if (start_from_next_line)
 399         search_start =
 400             edit_calculate_start_of_next_line (buf, search_start, end_mark, end_string_symbol);
 401 
 402     if (search_start >= end_mark)
 403     {
 404         mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _ (STR_E_NOTFOUND));
 405         return FALSE;
 406     }
 407 
 408     return mc_search_run (edit->search, (void *) esm, search_start, end_mark, len);
 409 }
 410 
 411 /* --------------------------------------------------------------------------------------------- */
 412 
 413 static char *
 414 edit_replace_cmd__conv_to_display (const char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 415 {
 416     GString *tmp;
 417 
 418     tmp = str_convert_to_display (str);
 419     if (tmp != NULL)
 420     {
 421         if (tmp->len != 0)
 422             return g_string_free (tmp, FALSE);
 423         g_string_free (tmp, TRUE);
 424     }
 425 
 426     return g_strdup (str);
 427 }
 428 
 429 /* --------------------------------------------------------------------------------------------- */
 430 
 431 static char *
 432 edit_replace_cmd__conv_to_input (char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 433 {
 434     GString *tmp;
 435 
 436     tmp = str_convert_to_input (str);
 437     if (tmp != NULL)
 438     {
 439         if (tmp->len != 0)
 440             return g_string_free (tmp, FALSE);
 441         g_string_free (tmp, TRUE);
 442     }
 443 
 444     return g_strdup (str);
 445 }
 446 
 447 /* --------------------------------------------------------------------------------------------- */
 448 
 449 static void
 450 edit_search_show_error (const WEdit *edit, const char *title)
     /* [previous][next][first][last][top][bottom][index][help]  */
 451 {
 452     if (edit->search->error == MC_SEARCH_E_NOTFOUND)
 453         message (D_NORMAL, title, "%s", _ (STR_E_NOTFOUND));
 454     else if (edit->search->error_str != NULL)
 455         message (D_NORMAL, title, "%s", edit->search->error_str);
 456 }
 457 
 458 /* --------------------------------------------------------------------------------------------- */
 459 
 460 static void
 461 edit_do_search (WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 462 {
 463     edit_search_status_msg_t esm;
 464     gsize len = 0;
 465 
 466     // This shouldn't happen
 467     assert (edit->search != NULL);
 468 
 469     edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
 470 
 471     esm.first = TRUE;
 472     esm.edit = edit;
 473     esm.offset = edit->search_start;
 474 
 475     status_msg_init (STATUS_MSG (&esm), _ ("Search"), 1.0, simple_status_msg_init_cb,
 476                      edit_search_status_update_cb, NULL);
 477 
 478     if (search_create_bookmark)
 479     {
 480         gboolean found = FALSE;
 481         long l = 0, l_last = -1;
 482         long q = 0;
 483 
 484         search_create_bookmark = FALSE;
 485         book_mark_flush (edit, -1);
 486 
 487         while (mc_search_run (edit->search, (void *) &esm, q, edit->buffer.size, &len))
 488         {
 489             if (!found)
 490                 edit->search_start = edit->search->normal_offset;
 491             found = TRUE;
 492 
 493             l += edit_buffer_count_lines (&edit->buffer, q, edit->search->normal_offset);
 494             if (l != l_last)
 495                 book_mark_insert (edit, l, EDITOR_BOOKMARK_FOUND_COLOR);
 496             l_last = l;
 497             q = edit->search->normal_offset + 1;
 498         }
 499 
 500         if (!found)
 501             message (D_NORMAL, _ ("Search"), "%s", _ (STR_E_NOTFOUND));
 502         else
 503             edit_cursor_move (edit, edit->search_start - edit->buffer.curs1);
 504     }
 505     else
 506     {
 507         if (edit_search_options.backwards)
 508         {
 509             if (edit->found_len != 0 && edit->search_start == edit->found_start + 1)
 510                 edit->search_start--;
 511         }
 512         else
 513         {
 514             if (edit->found_len != 0 && edit->search_start == edit->found_start - 1)
 515                 edit->search_start++;
 516         }
 517 
 518         if (edit_find (&esm, &len))
 519         {
 520             edit->found_start = edit->search->normal_offset;
 521             edit->found_len = len;
 522 
 523             edit->over_col = 0;
 524             edit_cursor_move (edit, edit->found_start - edit->buffer.curs1);
 525             edit_scroll_screen_over_cursor (edit);
 526 
 527             if (edit_search_options.backwards)
 528                 edit->search_start = edit->found_start - 1;
 529             else
 530                 edit->search_start = edit->found_start + edit->found_len;
 531         }
 532         else
 533         {
 534             edit->search_start = edit->buffer.curs1;
 535             edit_search_show_error (edit, _ ("Search"));
 536         }
 537     }
 538 
 539     status_msg_deinit (STATUS_MSG (&esm));
 540 
 541     edit->force |= REDRAW_COMPLETELY;
 542     edit_scroll_screen_over_cursor (edit);
 543 }
 544 
 545 /* --------------------------------------------------------------------------------------------- */
 546 
 547 static void
 548 edit_search (WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 549 {
 550     if (edit_dialog_search_show (edit))
 551         edit_do_search (edit);
 552 }
 553 
 554 /* --------------------------------------------------------------------------------------------- */
 555 /*** public functions ****************************************************************************/
 556 /* --------------------------------------------------------------------------------------------- */
 557 
 558 gboolean
 559 edit_search_init (WEdit *edit, const char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 560 {
 561     edit->search = mc_search_new (str, cp_source);
 562 
 563     if (edit->search == NULL)
 564         return FALSE;
 565 
 566     edit->search->search_type = edit_search_options.type;
 567     edit->search->is_all_charsets = edit_search_options.all_codepages;
 568     edit->search->is_case_sensitive = edit_search_options.case_sens;
 569     edit->search->whole_words = edit_search_options.whole_words;
 570     edit->search->search_fn = edit_search_cmd_callback;
 571     edit->search->update_fn = edit_search_update_callback;
 572 
 573     edit->search_line_type = mc_search_get_line_type (edit->search);
 574 
 575     edit_search_fix_search_start_if_selection (edit);
 576 
 577     return TRUE;
 578 }
 579 
 580 /* --------------------------------------------------------------------------------------------- */
 581 
 582 void
 583 edit_search_deinit (WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 584 {
 585     mc_search_free (edit->search);
 586     g_free (edit->last_search_string);
 587 }
 588 
 589 /* --------------------------------------------------------------------------------------------- */
 590 
 591 mc_search_cbret_t
 592 edit_search_cmd_callback (const void *user_data, off_t char_offset, int *current_char)
     /* [previous][next][first][last][top][bottom][index][help]  */
 593 {
 594     WEdit *edit = ((const edit_search_status_msg_t *) user_data)->edit;
 595 
 596     *current_char = edit_buffer_get_byte (&edit->buffer, char_offset);
 597 
 598     return MC_SEARCH_CB_OK;
 599 }
 600 
 601 /* --------------------------------------------------------------------------------------------- */
 602 
 603 mc_search_cbret_t
 604 edit_search_update_callback (const void *user_data, off_t char_offset)
     /* [previous][next][first][last][top][bottom][index][help]  */
 605 {
 606     status_msg_t *sm = STATUS_MSG (user_data);
 607 
 608     ((edit_search_status_msg_t *) sm)->offset = char_offset;
 609 
 610     return (sm->update (sm) == B_CANCEL ? MC_SEARCH_CB_ABORT : MC_SEARCH_CB_OK);
 611 }
 612 
 613 /* --------------------------------------------------------------------------------------------- */
 614 
 615 int
 616 edit_search_status_update_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
 617 {
 618     simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
 619     edit_search_status_msg_t *esm = (edit_search_status_msg_t *) sm;
 620     Widget *wd = WIDGET (sm->dlg);
 621 
 622     if (verbose)
 623         label_set_textv (ssm->label, _ ("Searching %s: %3d%%"), esm->edit->last_search_string,
 624                          edit_buffer_calc_percent (&esm->edit->buffer, esm->offset));
 625     else
 626         label_set_textv (ssm->label, _ ("Searching %s"), esm->edit->last_search_string);
 627 
 628     if (esm->first)
 629     {
 630         Widget *lw = WIDGET (ssm->label);
 631         WRect r;
 632 
 633         r = wd->rect;
 634         r.cols = MAX (r.cols, lw->rect.cols + 6);
 635         widget_set_size_rect (wd, &r);
 636         r = lw->rect;
 637         r.x = wd->rect.x + (wd->rect.cols - r.cols) / 2;
 638         widget_set_size_rect (lw, &r);
 639         esm->first = FALSE;
 640     }
 641 
 642     return status_msg_common_update (sm);
 643 }
 644 
 645 /* --------------------------------------------------------------------------------------------- */
 646 
 647 void
 648 edit_search_cmd (WEdit *edit, gboolean again)
     /* [previous][next][first][last][top][bottom][index][help]  */
 649 {
 650     if (!again)
 651         edit_search (edit);
 652     else if (edit->last_search_string != NULL)
 653         edit_do_search (edit);
 654     else
 655     {
 656         // find last search string in history
 657         char *s;
 658 
 659         s = mc_config_history_get_recent_item (MC_HISTORY_SHARED_SEARCH);
 660         if (s != NULL)
 661         {
 662             edit->last_search_string = s;
 663 
 664             if (edit_search_init (edit, edit->last_search_string))
 665             {
 666                 edit_do_search (edit);
 667                 return;
 668             }
 669 
 670             // found, but cannot init search
 671             MC_PTR_FREE (edit->last_search_string);
 672         }
 673 
 674         // if not... then ask for an expression
 675         edit_search (edit);
 676     }
 677 }
 678 
 679 /* --------------------------------------------------------------------------------------------- */
 680 
 681 void
 682 edit_dialog_replace_show (WEdit *edit, const char *search_default, const char *replace_default,
     /* [previous][next][first][last][top][bottom][index][help]  */
 683                           /*@out@ */ char **search_text, /*@out@ */ char **replace_text)
 684 {
 685     size_t num_of_types = 0;
 686     gchar **list_of_types;
 687 
 688     if ((search_default == NULL) || (*search_default == '\0'))
 689         search_default = INPUT_LAST_TEXT;
 690 
 691     list_of_types = mc_search_get_types_strings_array (&num_of_types);
 692 
 693     {
 694         quick_widget_t quick_widgets[] = {
 695             // clang-format off
 696             QUICK_LABELED_INPUT (_ ("Enter search string:"), input_label_above, search_default,
 697                                  MC_HISTORY_SHARED_SEARCH, search_text, NULL, FALSE, FALSE,
 698                                  INPUT_COMPLETE_NONE),
 699             QUICK_LABELED_INPUT (_ ("Enter replacement string:"), input_label_above,
 700                                  replace_default, "replace", replace_text, NULL, FALSE, FALSE,
 701                                  INPUT_COMPLETE_NONE),
 702             QUICK_SEPARATOR (TRUE),
 703             QUICK_START_COLUMNS,
 704                 QUICK_RADIO (num_of_types, (const char **) list_of_types,
 705                              (int *) &edit_search_options.type, NULL),
 706             QUICK_NEXT_COLUMN,
 707                 QUICK_CHECKBOX (_ ("Cas&e sensitive"), &edit_search_options.case_sens, NULL),
 708                 QUICK_CHECKBOX (_ ("&Backwards"), &edit_search_options.backwards, NULL),
 709                 QUICK_CHECKBOX (_ ("In se&lection"), &edit_search_options.only_in_selection, NULL),
 710                 QUICK_CHECKBOX (_ ("&Whole words"), &edit_search_options.whole_words, NULL),
 711                 QUICK_CHECKBOX (_ ("&All charsets"), &edit_search_options.all_codepages, NULL),
 712             QUICK_STOP_COLUMNS,
 713             QUICK_BUTTONS_OK_CANCEL,
 714             QUICK_END,
 715             // clang-format on
 716         };
 717 
 718         WRect r = { -1, -1, 0, 58 };
 719 
 720         quick_dialog_t qdlg = {
 721             .rect = r,
 722             .title = _ ("Replace"),
 723             .help = "[Input Line Keys]",
 724             .widgets = quick_widgets,
 725             .callback = NULL,
 726             .mouse_callback = NULL,
 727         };
 728 
 729         if (quick_dialog (&qdlg) != B_CANCEL)
 730             edit->replace_mode = 0;
 731         else
 732         {
 733             *replace_text = NULL;
 734             *search_text = NULL;
 735         }
 736     }
 737 
 738     g_strfreev (list_of_types);
 739 }
 740 
 741 /* --------------------------------------------------------------------------------------------- */
 742 
 743 int
 744 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]  */
 745 {
 746     Widget *w = WIDGET (edit);
 747 
 748     // dialog size
 749     int dlg_height = 10;
 750     int dlg_width;
 751 
 752     char tmp[BUF_MEDIUM];
 753     char *repl_from, *repl_to;
 754     int retval;
 755 
 756     if (xpos == -1)
 757         xpos = w->rect.x + edit_options.line_state_width + 1;
 758     if (ypos == -1)
 759         ypos = w->rect.y + w->rect.lines / 2;
 760     // Sometimes menu can hide replaced text. I don't like it
 761     if ((edit->curs_row >= ypos - 1) && (edit->curs_row <= ypos + dlg_height - 1))
 762         ypos -= dlg_height;
 763 
 764     dlg_width = WIDGET (w->owner)->rect.cols - xpos - 1;
 765 
 766     g_snprintf (tmp, sizeof (tmp), "\"%s\"", from_text);
 767     repl_from = g_strdup (str_trunc (tmp, dlg_width - 7));
 768 
 769     g_snprintf (tmp, sizeof (tmp), "\"%s\"", to_text);
 770     repl_to = g_strdup (str_trunc (tmp, dlg_width - 7));
 771 
 772     {
 773         quick_widget_t quick_widgets[] = {
 774             // clang-format off
 775             QUICK_LABEL (repl_from, NULL),
 776             QUICK_LABEL (_ ("Replace with:"), NULL),
 777             QUICK_LABEL (repl_to, NULL),
 778             QUICK_START_BUTTONS (TRUE, TRUE),
 779                 QUICK_BUTTON (_ ("&Replace"), B_ENTER, NULL, NULL),
 780                 QUICK_BUTTON (_ ("A&ll"), B_REPLACE_ALL, NULL, NULL),
 781                 QUICK_BUTTON (_ ("&Skip"), B_SKIP_REPLACE, NULL, NULL),
 782                 QUICK_BUTTON (_ ("&Cancel"), B_CANCEL, NULL, NULL),
 783             QUICK_END,
 784             // clang-format on
 785         };
 786 
 787         WRect r = { ypos, xpos, 0, -1 };
 788 
 789         quick_dialog_t qdlg = {
 790             .rect = r,
 791             .title = _ ("Confirm replace"),
 792             .help = NULL,
 793             .widgets = quick_widgets,
 794             .callback = NULL,
 795             .mouse_callback = NULL,
 796         };
 797 
 798         retval = quick_dialog (&qdlg);
 799     }
 800 
 801     g_free (repl_from);
 802     g_free (repl_to);
 803 
 804     return retval;
 805 }
 806 
 807 /* --------------------------------------------------------------------------------------------- */
 808 /** call with edit = 0 before shutdown to close memory leaks */
 809 
 810 void
 811 edit_replace_cmd (WEdit *edit, gboolean again)
     /* [previous][next][first][last][top][bottom][index][help]  */
 812 {
 813     // 1 = search string, 2 = replace with
 814     static char *saved1 = NULL;  // saved default[123]
 815     static char *saved2 = NULL;
 816     char *input1 = NULL;  // user input from the dialog
 817     char *input2 = NULL;
 818     GString *input2_str = NULL;
 819     char *disp1 = NULL;
 820     char *disp2 = NULL;
 821     long times_replaced = 0;
 822     gboolean once_found = FALSE;
 823     edit_search_status_msg_t esm;
 824 
 825     if (edit == NULL)
 826     {
 827         MC_PTR_FREE (saved1);
 828         MC_PTR_FREE (saved2);
 829         return;
 830     }
 831 
 832     edit->force |= REDRAW_COMPLETELY;
 833 
 834     if (again && saved1 == NULL && saved2 == NULL)
 835         again = FALSE;
 836 
 837     if (again)
 838     {
 839         input1 = g_strdup (saved1 != NULL ? saved1 : "");
 840         input2 = g_strdup (saved2 != NULL ? saved2 : "");
 841     }
 842     else
 843     {
 844         char *tmp_inp1, *tmp_inp2;
 845 
 846         disp1 = edit_replace_cmd__conv_to_display (saved1 != NULL ? saved1 : "");
 847         disp2 = edit_replace_cmd__conv_to_display (saved2 != NULL ? saved2 : "");
 848 
 849         edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
 850 
 851         edit_dialog_replace_show (edit, disp1, disp2, &input1, &input2);
 852 
 853         g_free (disp1);
 854         g_free (disp2);
 855 
 856         if (input1 == NULL || *input1 == '\0')
 857         {
 858             edit->force = REDRAW_COMPLETELY;
 859             goto cleanup;
 860         }
 861 
 862         tmp_inp1 = input1;
 863         tmp_inp2 = input2;
 864         input1 = edit_replace_cmd__conv_to_input (input1);
 865         input2 = edit_replace_cmd__conv_to_input (input2);
 866         g_free (tmp_inp1);
 867         g_free (tmp_inp2);
 868 
 869         g_free (saved1);
 870         saved1 = g_strdup (input1);
 871         g_free (saved2);
 872         saved2 = g_strdup (input2);
 873 
 874         mc_search_free (edit->search);
 875         edit->search = NULL;
 876     }
 877 
 878     input2_str = g_string_new_take (input2);
 879     input2 = NULL;
 880 
 881     if (edit->search == NULL && !edit_search_init (edit, input1))
 882     {
 883         edit->search_start = edit->buffer.curs1;
 884         goto cleanup;
 885     }
 886 
 887     if (edit_search_options.backwards)
 888     {
 889         if (edit->found_len != 0 && edit->search_start == edit->found_start + 1)
 890             edit->search_start--;
 891     }
 892     else
 893     {
 894         if (edit->found_len != 0 && edit->search_start == edit->found_start - 1)
 895             edit->search_start++;
 896     }
 897 
 898     esm.first = TRUE;
 899     esm.edit = edit;
 900     esm.offset = edit->search_start;
 901 
 902     status_msg_init (STATUS_MSG (&esm), _ ("Search"), 1.0, simple_status_msg_init_cb,
 903                      edit_search_status_update_cb, NULL);
 904 
 905     do
 906     {
 907         gsize len = 0;
 908 
 909         if (!edit_find (&esm, &len))
 910         {
 911             if (!(edit->search->error == MC_SEARCH_E_OK
 912                   || (once_found && edit->search->error == MC_SEARCH_E_NOTFOUND)))
 913                 edit_search_show_error (edit, _ ("Search"));
 914             break;
 915         }
 916 
 917         once_found = TRUE;
 918 
 919         edit->search_start = edit->search->normal_offset;
 920         // returns negative on not found or error in pattern
 921 
 922         if (edit->search_start >= 0 && edit->search_start < edit->buffer.size)
 923         {
 924             gsize i;
 925             GString *repl_str;
 926 
 927             edit->found_start = edit->search_start;
 928             edit->found_len = len;
 929 
 930             edit_cursor_move (edit, edit->found_start - edit->buffer.curs1);
 931             edit_scroll_screen_over_cursor (edit);
 932 
 933             if (edit->replace_mode == 0)
 934             {
 935                 long l;
 936                 int prompt;
 937 
 938                 l = edit->curs_row - WIDGET (edit)->rect.lines / 3;
 939                 if (l > 0)
 940                     edit_scroll_downward (edit, l);
 941                 if (l < 0)
 942                     edit_scroll_upward (edit, -l);
 943 
 944                 edit_scroll_screen_over_cursor (edit);
 945                 edit->force |= REDRAW_PAGE;
 946                 edit_render_keypress (edit);
 947 
 948                 // so that undo stops at each query
 949                 edit_push_key_press (edit);
 950                 // and prompt 2/3 down
 951                 disp1 = edit_replace_cmd__conv_to_display (saved1);
 952                 disp2 = edit_replace_cmd__conv_to_display (saved2);
 953                 prompt = edit_dialog_replace_prompt_show (edit, disp1, disp2, -1, -1);
 954                 g_free (disp1);
 955                 g_free (disp2);
 956 
 957                 if (prompt == B_REPLACE_ALL)
 958                     edit->replace_mode = 1;
 959                 else if (prompt == B_SKIP_REPLACE)
 960                 {
 961                     if (edit_search_options.backwards)
 962                         edit->search_start--;
 963                     else
 964                         edit->search_start++;
 965                     continue;  // loop
 966                 }
 967                 else if (prompt == B_CANCEL)
 968                 {
 969                     edit->replace_mode = -1;
 970                     break;  // loop
 971                 }
 972             }
 973 
 974             repl_str = mc_search_prepare_replace_str (edit->search, input2_str);
 975 
 976             if (edit->search->error != MC_SEARCH_E_OK)
 977             {
 978                 edit_search_show_error (edit, _ ("Replace"));
 979                 if (repl_str != NULL)
 980                     g_string_free (repl_str, TRUE);
 981                 break;
 982             }
 983 
 984             // delete then insert new
 985             for (i = 0; i < len; i++)
 986                 edit_delete (edit, TRUE);
 987 
 988             for (i = 0; i < repl_str->len; i++)
 989                 edit_insert (edit, repl_str->str[i]);
 990 
 991             edit->found_len = repl_str->len;
 992             g_string_free (repl_str, TRUE);
 993             times_replaced++;
 994 
 995             // so that we don't find the same string again
 996             if (edit_search_options.backwards)
 997                 edit->search_start--;
 998             else
 999             {
1000                 edit->search_start += edit->found_len + (len == 0 ? 1 : 0);
1001 
1002                 if (edit->search_start >= edit->buffer.size)
1003                     break;
1004             }
1005 
1006             edit_scroll_screen_over_cursor (edit);
1007         }
1008         else
1009         {
1010             // try and find from right here for next search
1011             edit->search_start = edit->buffer.curs1;
1012             edit_update_curs_col (edit);
1013 
1014             edit->force |= REDRAW_PAGE;
1015             edit_render_keypress (edit);
1016 
1017             if (times_replaced == 0)
1018                 query_dialog (_ ("Replace"), _ (STR_E_NOTFOUND), D_NORMAL, 1, _ ("&OK"));
1019             break;
1020         }
1021     }
1022     while (edit->replace_mode >= 0);
1023 
1024     status_msg_deinit (STATUS_MSG (&esm));
1025     edit_scroll_screen_over_cursor (edit);
1026     edit->force |= REDRAW_COMPLETELY;
1027     edit_render_keypress (edit);
1028 
1029     if (edit->replace_mode == 1 && times_replaced != 0)
1030         message (D_NORMAL, _ ("Replace"), _ ("%ld replacements made"), times_replaced);
1031 
1032 cleanup:
1033     g_free (input1);
1034     g_free (input2);
1035     if (input2_str != NULL)
1036         g_string_free (input2_str, TRUE);
1037 }
1038 
1039 /* --------------------------------------------------------------------------------------------- */

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