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

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