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