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