2 * search.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2006-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2011 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 * Find, Replace, Find in Files dialog related functions.
24 * Note that the basic text find functions are in document.c.
27 #include <gdk/gdkkeysyms.h>
35 #include "msgwindow.h"
36 #include "sciwrappers.h"
39 #include "encodings.h"
50 # include <sys/types.h>
51 # include <sys/wait.h>
57 # include "gnuregex.h"
63 GEANY_RESPONSE_FIND
= 1,
64 GEANY_RESPONSE_FIND_PREVIOUS
,
65 GEANY_RESPONSE_FIND_IN_FILE
,
66 GEANY_RESPONSE_FIND_IN_SESSION
,
68 GEANY_RESPONSE_REPLACE
,
69 GEANY_RESPONSE_REPLACE_AND_FIND
,
70 GEANY_RESPONSE_REPLACE_IN_SESSION
,
71 GEANY_RESPONSE_REPLACE_IN_FILE
,
72 GEANY_RESPONSE_REPLACE_IN_SEL
84 GeanySearchData search_data
;
85 GeanySearchPrefs search_prefs
;
91 gboolean fif_case_sensitive
;
92 gboolean fif_match_whole_word
;
93 gboolean fif_invert_results
;
94 gboolean fif_recursive
;
95 gboolean fif_use_extra_options
;
96 gchar
*fif_extra_options
;
100 gboolean find_escape_sequences
;
101 gboolean find_case_sensitive
;
102 gboolean find_match_whole_word
;
103 gboolean find_match_word_start
;
104 gboolean find_close_dialog
;
105 gboolean replace_regexp
;
106 gboolean replace_escape_sequences
;
107 gboolean replace_case_sensitive
;
108 gboolean replace_match_whole_word
;
109 gboolean replace_match_word_start
;
110 gboolean replace_search_backwards
;
111 gboolean replace_close_dialog
;
115 static StashGroup
*fif_prefs
= NULL
;
116 static StashGroup
*find_prefs
= NULL
;
117 static StashGroup
*replace_prefs
= NULL
;
124 gboolean all_expanded
;
125 gint position
[2]; /* x, y */
127 find_dlg
= {NULL
, NULL
, FALSE
, {0, 0}};
132 GtkWidget
*find_entry
;
133 GtkWidget
*replace_entry
;
134 gboolean all_expanded
;
135 gint position
[2]; /* x, y */
137 replace_dlg
= {NULL
, NULL
, NULL
, FALSE
, {0, 0}};
142 GtkWidget
*dir_combo
;
143 GtkWidget
*files_combo
;
144 GtkWidget
*search_combo
;
145 GtkWidget
*encoding_combo
;
146 GtkWidget
*files_mode_combo
;
147 gint position
[2]; /* x, y */
149 fif_dlg
= {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, {0, 0}};
152 static gboolean
search_read_io(GIOChannel
*source
, GIOCondition condition
, gpointer data
);
153 static gboolean
search_read_io_stderr(GIOChannel
*source
, GIOCondition condition
, gpointer data
);
155 static void search_close_pid(GPid child_pid
, gint status
, gpointer user_data
);
157 static gchar
**search_get_argv(const gchar
**argv_prefix
, const gchar
*dir
);
161 on_find_replace_checkbutton_toggled(GtkToggleButton
*togglebutton
, gpointer user_data
);
164 on_find_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
);
167 on_find_entry_activate(GtkEntry
*entry
, gpointer user_data
);
170 on_find_entry_activate_backward(GtkEntry
*entry
, gpointer user_data
);
173 on_replace_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
);
176 on_replace_entry_activate(GtkEntry
*entry
, gpointer user_data
);
179 on_find_in_files_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
);
182 search_find_in_files(const gchar
*utf8_search_text
, const gchar
*dir
, const gchar
*opts
,
186 static void init_prefs(void)
190 group
= stash_group_new("search");
191 configuration_add_pref_group(group
, TRUE
);
192 stash_group_add_toggle_button(group
, &search_prefs
.use_current_file_dir
,
193 "pref_search_current_file_dir", TRUE
, "check_fif_current_dir");
194 stash_group_add_boolean(group
, &find_dlg
.all_expanded
, "find_all_expanded", FALSE
);
195 stash_group_add_boolean(group
, &replace_dlg
.all_expanded
, "replace_all_expanded", FALSE
);
196 /* dialog positions */
197 stash_group_add_integer(group
, &find_dlg
.position
[0], "position_find_x", -1);
198 stash_group_add_integer(group
, &find_dlg
.position
[1], "position_find_y", -1);
199 stash_group_add_integer(group
, &replace_dlg
.position
[0], "position_replace_x", -1);
200 stash_group_add_integer(group
, &replace_dlg
.position
[1], "position_replace_y", -1);
201 stash_group_add_integer(group
, &fif_dlg
.position
[0], "position_fif_x", -1);
202 stash_group_add_integer(group
, &fif_dlg
.position
[1], "position_fif_y", -1);
204 memset(&settings
, '\0', sizeof(settings
));
206 group
= stash_group_new("search");
208 configuration_add_pref_group(group
, FALSE
);
209 stash_group_add_toggle_button(group
, &settings
.fif_regexp
,
210 "fif_regexp", FALSE
, "check_regexp");
211 stash_group_add_toggle_button(group
, &settings
.fif_case_sensitive
,
212 "fif_case_sensitive", TRUE
, "check_case");
213 stash_group_add_toggle_button(group
, &settings
.fif_match_whole_word
,
214 "fif_match_whole_word", FALSE
, "check_wholeword");
215 stash_group_add_toggle_button(group
, &settings
.fif_invert_results
,
216 "fif_invert_results", FALSE
, "check_invert");
217 stash_group_add_toggle_button(group
, &settings
.fif_recursive
,
218 "fif_recursive", FALSE
, "check_recursive");
219 stash_group_add_entry(group
, &settings
.fif_extra_options
,
220 "fif_extra_options", "", "entry_extra");
221 stash_group_add_toggle_button(group
, &settings
.fif_use_extra_options
,
222 "fif_use_extra_options", FALSE
, "check_extra");
223 stash_group_add_entry(group
, &settings
.fif_files
,
224 "fif_files", "", "entry_files");
225 stash_group_add_combo_box(group
, &settings
.fif_files_mode
,
226 "fif_files_mode", FILES_MODE_ALL
, "combo_files_mode");
228 group
= stash_group_new("search");
230 configuration_add_pref_group(group
, FALSE
);
231 stash_group_add_toggle_button(group
, &settings
.find_regexp
,
232 "find_regexp", FALSE
, "check_regexp");
233 stash_group_add_toggle_button(group
, &settings
.find_case_sensitive
,
234 "find_case_sensitive", FALSE
, "check_case");
235 stash_group_add_toggle_button(group
, &settings
.find_escape_sequences
,
236 "find_escape_sequences", FALSE
, "check_escape");
237 stash_group_add_toggle_button(group
, &settings
.find_match_whole_word
,
238 "find_match_whole_word", FALSE
, "check_word");
239 stash_group_add_toggle_button(group
, &settings
.find_match_word_start
,
240 "find_match_word_start", FALSE
, "check_wordstart");
241 stash_group_add_toggle_button(group
, &settings
.find_close_dialog
,
242 "find_close_dialog", TRUE
, "check_close");
244 group
= stash_group_new("search");
245 replace_prefs
= group
;
246 configuration_add_pref_group(group
, FALSE
);
247 stash_group_add_toggle_button(group
, &settings
.replace_regexp
,
248 "replace_regexp", FALSE
, "check_regexp");
249 stash_group_add_toggle_button(group
, &settings
.replace_case_sensitive
,
250 "replace_case_sensitive", FALSE
, "check_case");
251 stash_group_add_toggle_button(group
, &settings
.replace_escape_sequences
,
252 "replace_escape_sequences", FALSE
, "check_escape");
253 stash_group_add_toggle_button(group
, &settings
.replace_match_whole_word
,
254 "replace_match_whole_word", FALSE
, "check_word");
255 stash_group_add_toggle_button(group
, &settings
.replace_match_word_start
,
256 "replace_match_word_start", FALSE
, "check_wordstart");
257 stash_group_add_toggle_button(group
, &settings
.replace_search_backwards
,
258 "replace_search_backwards", FALSE
, "check_back");
259 stash_group_add_toggle_button(group
, &settings
.replace_close_dialog
,
260 "replace_close_dialog", TRUE
, "check_close");
264 void search_init(void)
266 search_data
.text
= NULL
;
267 search_data
.original_text
= NULL
;
272 #define FREE_WIDGET(wid) \
273 if (wid && GTK_IS_WIDGET(wid)) gtk_widget_destroy(wid);
275 void search_finalize(void)
277 FREE_WIDGET(find_dlg
.dialog
);
278 FREE_WIDGET(replace_dlg
.dialog
);
279 FREE_WIDGET(fif_dlg
.dialog
);
280 g_free(search_data
.text
);
281 g_free(search_data
.original_text
);
285 static GtkWidget
*add_find_checkboxes(GtkDialog
*dialog
)
287 GtkWidget
*checkbox1
, *checkbox2
, *check_regexp
, *check_back
, *checkbox5
,
288 *checkbox7
, *hbox
, *fbox
, *mbox
;
290 check_regexp
= gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
291 ui_hookup_widget(dialog
, check_regexp
, "check_regexp");
292 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp
), FALSE
);
293 gtk_widget_set_tooltip_text(check_regexp
, _("Use POSIX-like regular expressions. "
294 "For detailed information about using regular expressions, please read the documentation."));
295 g_signal_connect(check_regexp
, "toggled",
296 G_CALLBACK(on_find_replace_checkbutton_toggled
), dialog
);
298 if (dialog
!= GTK_DIALOG(find_dlg
.dialog
))
300 check_back
= gtk_check_button_new_with_mnemonic(_("Search _backwards"));
301 ui_hookup_widget(dialog
, check_back
, "check_back");
302 gtk_button_set_focus_on_click(GTK_BUTTON(check_back
), FALSE
);
305 { /* align the two checkboxes at the top of the hbox */
306 GtkSizeGroup
*label_size
;
307 check_back
= gtk_label_new(NULL
);
308 label_size
= gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL
);
309 gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size
), check_back
);
310 gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size
), check_regexp
);
311 g_object_unref(label_size
);
313 checkbox7
= gtk_check_button_new_with_mnemonic(_("Use _escape sequences"));
314 ui_hookup_widget(dialog
, checkbox7
, "check_escape");
315 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox7
), FALSE
);
316 gtk_widget_set_tooltip_text(checkbox7
,
317 _("Replace \\\\, \\t, \\n, \\r and \\uXXXX (Unicode chararacters) with the "
318 "corresponding control characters"));
320 /* Search features */
321 fbox
= gtk_vbox_new(FALSE
, 0);
322 gtk_container_add(GTK_CONTAINER(fbox
), check_regexp
);
323 gtk_container_add(GTK_CONTAINER(fbox
), checkbox7
);
324 gtk_container_add(GTK_CONTAINER(fbox
), check_back
);
326 checkbox1
= gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
327 ui_hookup_widget(dialog
, checkbox1
, "check_case");
328 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1
), FALSE
);
330 checkbox2
= gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
331 ui_hookup_widget(dialog
, checkbox2
, "check_word");
332 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2
), FALSE
);
334 checkbox5
= gtk_check_button_new_with_mnemonic(_("Match from s_tart of word"));
335 ui_hookup_widget(dialog
, checkbox5
, "check_wordstart");
336 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox5
), FALSE
);
338 /* Matching options */
339 mbox
= gtk_vbox_new(FALSE
, 0);
340 gtk_container_add(GTK_CONTAINER(mbox
), checkbox1
);
341 gtk_container_add(GTK_CONTAINER(mbox
), checkbox2
);
342 gtk_container_add(GTK_CONTAINER(mbox
), checkbox5
);
344 hbox
= gtk_hbox_new(TRUE
, 6);
345 gtk_container_add(GTK_CONTAINER(hbox
), fbox
);
346 gtk_container_add(GTK_CONTAINER(hbox
), mbox
);
351 static void send_find_dialog_response(GtkButton
*button
, gpointer user_data
)
353 gtk_dialog_response(GTK_DIALOG(find_dlg
.dialog
), GPOINTER_TO_INT(user_data
));
357 /* store text, clear search flags so we can use Search->Find Next/Previous */
358 static void setup_find_next(const gchar
*text
)
360 g_free(search_data
.text
);
361 g_free(search_data
.original_text
);
362 search_data
.text
= g_strdup(text
);
363 search_data
.original_text
= g_strdup(text
);
364 search_data
.flags
= 0;
365 search_data
.backwards
= FALSE
;
366 search_data
.search_bar
= FALSE
;
370 /* Search for next match of the current "selection".
371 * Optionally for X11 based systems, this will try to use the system-wide
373 * If it doesn't find a suitable search string it will try to use
374 * the current word instead, or just repeat the last search.
375 * Search flags are always zero.
377 void search_find_selection(GeanyDocument
*doc
, gboolean search_backwards
)
381 g_return_if_fail(doc
!= NULL
);
384 if (search_prefs
.find_selection_type
== GEANY_FIND_SEL_X
)
386 GtkClipboard
*clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
388 s
= gtk_clipboard_wait_for_text(clipboard
);
389 if (s
&& (strchr(s
,'\n') || strchr(s
, '\r')))
397 if (!s
&& sci_has_selection(doc
->editor
->sci
))
398 s
= sci_get_selection_contents(doc
->editor
->sci
);
400 if (!s
&& search_prefs
.find_selection_type
!= GEANY_FIND_SEL_AGAIN
)
402 /* get the current word */
403 s
= editor_get_default_selection(doc
->editor
, TRUE
, NULL
);
408 setup_find_next(s
); /* allow find next/prev */
410 if (document_find_text(doc
, s
, NULL
, 0, search_backwards
, FALSE
, NULL
) > -1)
411 editor_display_current_line(doc
->editor
, 0.3F
);
414 else if (search_prefs
.find_selection_type
== GEANY_FIND_SEL_AGAIN
)
416 /* Repeat last search (in case selection was lost) */
417 search_find_again(search_backwards
);
426 /* this will load a GTK rc style to set a monospace font for text fields(GtkEntry) in all
427 * search dialogs. This needs to be done only once.
428 * The monospace font should increase readibility of regular expressions containing spaces, points,
429 * commas and similar (#1907117). */
430 static void load_monospace_style(void)
432 static const gchar
*rcstyle
=
433 "style \"geany-monospace\"\n" \
435 " font_name=\"Monospace\"\n" \
437 "widget \"GeanyDialogSearch.*.GtkEntry\" style \"geany-monospace\"";
438 static gboolean load
= TRUE
;
442 gtk_rc_parse_string(rcstyle
);
448 static void on_expander_activated(GtkExpander
*exp
, gpointer data
)
450 gboolean
*setting
= data
;
452 *setting
= gtk_expander_get_expanded(exp
);
456 static void create_find_dialog(void)
458 GtkWidget
*label
, *entry
, *sbox
, *vbox
;
459 GtkWidget
*exp
, *bbox
, *button
, *check_close
;
461 load_monospace_style();
463 find_dlg
.dialog
= gtk_dialog_new_with_buttons(_("Find"),
464 GTK_WINDOW(main_widgets
.window
), GTK_DIALOG_DESTROY_WITH_PARENT
,
465 GTK_STOCK_CLOSE
, GTK_RESPONSE_CANCEL
, NULL
);
466 vbox
= ui_dialog_vbox_new(GTK_DIALOG(find_dlg
.dialog
));
467 gtk_widget_set_name(find_dlg
.dialog
, "GeanyDialogSearch");
468 gtk_box_set_spacing(GTK_BOX(vbox
), 9);
470 button
= ui_button_new_with_image(GTK_STOCK_GO_BACK
, _("_Previous"));
471 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg
.dialog
), button
,
472 GEANY_RESPONSE_FIND_PREVIOUS
);
473 ui_hookup_widget(find_dlg
.dialog
, button
, "btn_previous");
475 button
= ui_button_new_with_image(GTK_STOCK_GO_FORWARD
, _("_Next"));
476 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg
.dialog
), button
,
477 GEANY_RESPONSE_FIND
);
479 label
= gtk_label_new_with_mnemonic(_("_Search for:"));
480 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
482 entry
= gtk_combo_box_entry_new_text();
483 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry
))));
484 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), entry
);
485 gtk_entry_set_max_length(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry
))), 248);
486 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry
))), 50);
487 find_dlg
.entry
= GTK_BIN(entry
)->child
;
488 ui_hookup_widget(find_dlg
.dialog
, entry
, "entry");
490 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry
)), "activate",
491 G_CALLBACK(on_find_entry_activate
), NULL
);
492 ui_entry_add_activate_backward_signal(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry
))));
493 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry
)), "activate-backward",
494 G_CALLBACK(on_find_entry_activate_backward
), NULL
);
495 g_signal_connect(find_dlg
.dialog
, "response",
496 G_CALLBACK(on_find_dialog_response
), entry
);
497 g_signal_connect(find_dlg
.dialog
, "delete-event",
498 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
500 sbox
= gtk_hbox_new(FALSE
, 6);
501 gtk_box_pack_start(GTK_BOX(sbox
), label
, FALSE
, FALSE
, 0);
502 gtk_box_pack_start(GTK_BOX(sbox
), entry
, TRUE
, TRUE
, 0);
503 gtk_box_pack_start(GTK_BOX(vbox
), sbox
, TRUE
, FALSE
, 0);
505 gtk_container_add(GTK_CONTAINER(vbox
),
506 add_find_checkboxes(GTK_DIALOG(find_dlg
.dialog
)));
508 /* Now add the multiple match options */
509 exp
= gtk_expander_new_with_mnemonic(_("_Find All"));
510 gtk_expander_set_expanded(GTK_EXPANDER(exp
), find_dlg
.all_expanded
);
511 g_signal_connect_after(exp
, "activate",
512 G_CALLBACK(on_expander_activated
), &find_dlg
.all_expanded
);
514 bbox
= gtk_hbutton_box_new();
516 button
= gtk_button_new_with_mnemonic(_("_Mark"));
517 gtk_widget_set_tooltip_text(button
,
518 _("Mark all matches in the current document"));
519 gtk_container_add(GTK_CONTAINER(bbox
), button
);
520 g_signal_connect(button
, "clicked", G_CALLBACK(send_find_dialog_response
),
521 GINT_TO_POINTER(GEANY_RESPONSE_MARK
));
523 button
= gtk_button_new_with_mnemonic(_("In Sessi_on"));
524 gtk_container_add(GTK_CONTAINER(bbox
), button
);
525 g_signal_connect(button
, "clicked", G_CALLBACK(send_find_dialog_response
),
526 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_SESSION
));
528 button
= gtk_button_new_with_mnemonic(_("_In Document"));
529 gtk_container_add(GTK_CONTAINER(bbox
), button
);
530 g_signal_connect(button
, "clicked", G_CALLBACK(send_find_dialog_response
),
531 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_FILE
));
533 /* close window checkbox */
534 check_close
= gtk_check_button_new_with_mnemonic(_("Close _dialog"));
535 ui_hookup_widget(find_dlg
.dialog
, check_close
, "check_close");
536 gtk_button_set_focus_on_click(GTK_BUTTON(check_close
), FALSE
);
537 gtk_widget_set_tooltip_text(check_close
,
538 _("Disable this option to keep the dialog open"));
539 gtk_container_add(GTK_CONTAINER(bbox
), check_close
);
540 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox
), check_close
, TRUE
);
542 ui_hbutton_box_copy_layout(
543 GTK_BUTTON_BOX(GTK_DIALOG(find_dlg
.dialog
)->action_area
),
544 GTK_BUTTON_BOX(bbox
));
545 gtk_container_add(GTK_CONTAINER(exp
), bbox
);
546 gtk_container_add(GTK_CONTAINER(vbox
), exp
);
550 static void set_dialog_position(GtkWidget
*dialog
, gint
*position
)
552 if (position
[0] >= 0)
553 gtk_window_move(GTK_WINDOW(dialog
), position
[0], position
[1]);
557 void search_show_find_dialog(void)
559 GeanyDocument
*doc
= document_get_current();
562 g_return_if_fail(doc
!= NULL
);
564 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
566 if (find_dlg
.dialog
== NULL
)
568 create_find_dialog();
569 stash_group_display(find_prefs
, find_dlg
.dialog
);
571 gtk_entry_set_text(GTK_ENTRY(find_dlg
.entry
), sel
);
573 set_dialog_position(find_dlg
.dialog
, find_dlg
.position
);
574 gtk_widget_show_all(find_dlg
.dialog
);
578 /* only set selection if the dialog is not already visible */
579 if (! GTK_WIDGET_VISIBLE(find_dlg
.dialog
) && sel
)
580 gtk_entry_set_text(GTK_ENTRY(find_dlg
.entry
), sel
);
581 gtk_widget_grab_focus(find_dlg
.entry
);
582 set_dialog_position(find_dlg
.dialog
, find_dlg
.position
);
583 gtk_widget_show(find_dlg
.dialog
);
584 if (sel
!= NULL
) /* when we have a selection, reset the entry widget's background colour */
585 ui_set_search_entry_background(find_dlg
.entry
, TRUE
);
586 /* bring the dialog back in the foreground in case it is already open but the focus is away */
587 gtk_window_present(GTK_WINDOW(find_dlg
.dialog
));
594 static void send_replace_dialog_response(GtkButton
*button
, gpointer user_data
)
596 gtk_dialog_response(GTK_DIALOG(replace_dlg
.dialog
), GPOINTER_TO_INT(user_data
));
601 on_widget_key_pressed_set_focus(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
603 if (event
->keyval
== GDK_Tab
)
605 gtk_widget_grab_focus(GTK_WIDGET(user_data
));
612 static void create_replace_dialog(void)
614 GtkWidget
*label_find
, *label_replace
, *entry_find
, *entry_replace
,
615 *check_close
, *button
, *rbox
, *fbox
, *vbox
, *exp
, *bbox
;
616 GtkSizeGroup
*label_size
;
618 load_monospace_style();
620 replace_dlg
.dialog
= gtk_dialog_new_with_buttons(_("Replace"),
621 GTK_WINDOW(main_widgets
.window
), GTK_DIALOG_DESTROY_WITH_PARENT
,
622 GTK_STOCK_CLOSE
, GTK_RESPONSE_CANCEL
, NULL
);
623 vbox
= ui_dialog_vbox_new(GTK_DIALOG(replace_dlg
.dialog
));
624 gtk_box_set_spacing(GTK_BOX(vbox
), 9);
625 gtk_widget_set_name(replace_dlg
.dialog
, "GeanyDialogSearch");
627 button
= gtk_button_new_from_stock(GTK_STOCK_FIND
);
628 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg
.dialog
), button
,
629 GEANY_RESPONSE_FIND
);
630 button
= gtk_button_new_with_mnemonic(_("_Replace"));
631 gtk_button_set_image(GTK_BUTTON(button
),
632 gtk_image_new_from_stock(GTK_STOCK_FIND_AND_REPLACE
, GTK_ICON_SIZE_BUTTON
));
633 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg
.dialog
), button
,
634 GEANY_RESPONSE_REPLACE
);
635 button
= gtk_button_new_with_mnemonic(_("Replace & Fi_nd"));
636 gtk_button_set_image(GTK_BUTTON(button
),
637 gtk_image_new_from_stock(GTK_STOCK_FIND_AND_REPLACE
, GTK_ICON_SIZE_BUTTON
));
638 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg
.dialog
), button
,
639 GEANY_RESPONSE_REPLACE_AND_FIND
);
641 label_find
= gtk_label_new_with_mnemonic(_("_Search for:"));
642 gtk_misc_set_alignment(GTK_MISC(label_find
), 0, 0.5);
644 label_replace
= gtk_label_new_with_mnemonic(_("Replace wit_h:"));
645 gtk_misc_set_alignment(GTK_MISC(label_replace
), 0, 0.5);
647 entry_find
= gtk_combo_box_entry_new_text();
648 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find
))));
649 gtk_label_set_mnemonic_widget(GTK_LABEL(label_find
), entry_find
);
650 gtk_entry_set_max_length(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find
))), 248);
651 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find
))), 50);
652 ui_hookup_widget(replace_dlg
.dialog
, entry_find
, "entry_find");
653 replace_dlg
.find_entry
= GTK_BIN(entry_find
)->child
;
655 entry_replace
= gtk_combo_box_entry_new_text();
656 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace
))));
657 gtk_label_set_mnemonic_widget(GTK_LABEL(label_replace
), entry_replace
);
658 gtk_entry_set_max_length(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace
))), 248);
659 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace
))), 50);
660 ui_hookup_widget(replace_dlg
.dialog
, entry_replace
, "entry_replace");
661 replace_dlg
.replace_entry
= GTK_BIN(entry_replace
)->child
;
663 /* tab from find to the replace entry */
664 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_find
)),
665 "key-press-event", G_CALLBACK(on_widget_key_pressed_set_focus
),
666 gtk_bin_get_child(GTK_BIN(entry_replace
)));
667 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_replace
)), "activate",
668 G_CALLBACK(on_replace_entry_activate
), NULL
);
669 g_signal_connect(replace_dlg
.dialog
, "response",
670 G_CALLBACK(on_replace_dialog_response
), entry_replace
);
671 g_signal_connect(replace_dlg
.dialog
, "delete-event",
672 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
674 fbox
= gtk_hbox_new(FALSE
, 6);
675 gtk_box_pack_start(GTK_BOX(fbox
), label_find
, FALSE
, FALSE
, 0);
676 gtk_box_pack_start(GTK_BOX(fbox
), entry_find
, TRUE
, TRUE
, 0);
678 rbox
= gtk_hbox_new(FALSE
, 6);
679 gtk_box_pack_start(GTK_BOX(rbox
), label_replace
, FALSE
, FALSE
, 0);
680 gtk_box_pack_start(GTK_BOX(rbox
), entry_replace
, TRUE
, TRUE
, 0);
682 label_size
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
683 gtk_size_group_add_widget(label_size
, label_find
);
684 gtk_size_group_add_widget(label_size
, label_replace
);
685 g_object_unref(G_OBJECT(label_size
)); /* auto destroy the size group */
687 gtk_box_pack_start(GTK_BOX(vbox
), fbox
, TRUE
, FALSE
, 0);
688 gtk_box_pack_start(GTK_BOX(vbox
), rbox
, TRUE
, FALSE
, 0);
689 gtk_container_add(GTK_CONTAINER(vbox
),
690 add_find_checkboxes(GTK_DIALOG(replace_dlg
.dialog
)));
692 /* Now add the multiple replace options */
693 exp
= gtk_expander_new_with_mnemonic(_("Re_place All"));
694 gtk_expander_set_expanded(GTK_EXPANDER(exp
), replace_dlg
.all_expanded
);
695 g_signal_connect_after(exp
, "activate",
696 G_CALLBACK(on_expander_activated
), &replace_dlg
.all_expanded
);
698 bbox
= gtk_hbutton_box_new();
700 button
= gtk_button_new_with_mnemonic(_("In Sessi_on"));
701 gtk_container_add(GTK_CONTAINER(bbox
), button
);
702 g_signal_connect(button
, "clicked", G_CALLBACK(send_replace_dialog_response
),
703 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SESSION
));
705 button
= gtk_button_new_with_mnemonic(_("_In Document"));
706 gtk_container_add(GTK_CONTAINER(bbox
), button
);
707 g_signal_connect(button
, "clicked", G_CALLBACK(send_replace_dialog_response
),
708 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_FILE
));
710 button
= gtk_button_new_with_mnemonic(_("In Se_lection"));
711 gtk_widget_set_tooltip_text(button
,
712 _("Replace all matches found in the currently selected text"));
713 gtk_container_add(GTK_CONTAINER(bbox
), button
);
714 g_signal_connect(button
, "clicked", G_CALLBACK(send_replace_dialog_response
),
715 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SEL
));
717 /* close window checkbox */
718 check_close
= gtk_check_button_new_with_mnemonic(_("Close _dialog"));
719 ui_hookup_widget(replace_dlg
.dialog
, check_close
, "check_close");
720 gtk_button_set_focus_on_click(GTK_BUTTON(check_close
), FALSE
);
721 gtk_widget_set_tooltip_text(check_close
,
722 _("Disable this option to keep the dialog open"));
723 gtk_container_add(GTK_CONTAINER(bbox
), check_close
);
724 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox
), check_close
, TRUE
);
726 ui_hbutton_box_copy_layout(
727 GTK_BUTTON_BOX(GTK_DIALOG(replace_dlg
.dialog
)->action_area
),
728 GTK_BUTTON_BOX(bbox
));
729 gtk_container_add(GTK_CONTAINER(exp
), bbox
);
730 gtk_container_add(GTK_CONTAINER(vbox
), exp
);
734 void search_show_replace_dialog(void)
736 GeanyDocument
*doc
= document_get_current();
742 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
744 if (replace_dlg
.dialog
== NULL
)
746 create_replace_dialog();
747 stash_group_display(replace_prefs
, replace_dlg
.dialog
);
749 gtk_entry_set_text(GTK_ENTRY(replace_dlg
.find_entry
), sel
);
751 set_dialog_position(replace_dlg
.dialog
, replace_dlg
.position
);
752 gtk_widget_show_all(replace_dlg
.dialog
);
756 /* only set selection if the dialog is not already visible */
757 if (! GTK_WIDGET_VISIBLE(replace_dlg
.dialog
) && sel
)
758 gtk_entry_set_text(GTK_ENTRY(replace_dlg
.find_entry
), sel
);
759 if (sel
!= NULL
) /* when we have a selection, reset the entry widget's background colour */
760 ui_set_search_entry_background(replace_dlg
.find_entry
, TRUE
);
761 gtk_widget_grab_focus(replace_dlg
.find_entry
);
762 set_dialog_position(replace_dlg
.dialog
, replace_dlg
.position
);
763 gtk_widget_show(replace_dlg
.dialog
);
764 /* bring the dialog back in the foreground in case it is already open but the focus is away */
765 gtk_window_present(GTK_WINDOW(replace_dlg
.dialog
));
772 static void on_widget_toggled_set_sensitive(GtkToggleButton
*togglebutton
, gpointer user_data
)
774 /* disable extra option entry when checkbutton not checked */
775 gtk_widget_set_sensitive(GTK_WIDGET(user_data
),
776 gtk_toggle_button_get_active(togglebutton
));
780 static void update_file_patterns(GtkWidget
*mode_combo
, GtkWidget
*fcombo
)
785 entry
= gtk_bin_get_child(GTK_BIN(fcombo
));
787 selection
= gtk_combo_box_get_active(GTK_COMBO_BOX(mode_combo
));
789 if (selection
== FILES_MODE_ALL
)
791 gtk_entry_set_text(GTK_ENTRY(entry
), "");
792 gtk_widget_set_sensitive(fcombo
, FALSE
);
794 else if (selection
== FILES_MODE_CUSTOM
)
796 gtk_widget_set_sensitive(fcombo
, TRUE
);
798 else if (selection
== FILES_MODE_PROJECT
)
800 if (app
->project
&& NZV(app
->project
->file_patterns
))
804 patterns
= g_strjoinv(" ", app
->project
->file_patterns
);
805 gtk_entry_set_text(GTK_ENTRY(entry
), patterns
);
810 gtk_entry_set_text(GTK_ENTRY(entry
), "");
813 gtk_widget_set_sensitive(fcombo
, FALSE
);
818 /* creates the combo to choose which files include in the search */
819 static GtkWidget
*create_fif_file_mode_combo(void)
822 GtkCellRenderer
*renderer
;
827 store
= gtk_list_store_new(2, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
828 gtk_list_store_append(store
, &iter
);
829 gtk_list_store_set(store
, &iter
, 0, _("all"), 1, TRUE
, -1);
830 gtk_list_store_append(store
, &iter
);
831 gtk_list_store_set(store
, &iter
, 0, _("project"), 1, app
->project
!= NULL
, -1);
832 gtk_list_store_append(store
, &iter
);
833 gtk_list_store_set(store
, &iter
, 0, _("custom"), 1, TRUE
, -1);
835 combo
= gtk_combo_box_new_with_model(GTK_TREE_MODEL(store
));
836 g_object_unref(store
);
837 gtk_widget_set_tooltip_text(combo
, _("All: search all files in the directory\n"
838 "Project: use file patterns defined in the project settings\n"
839 "Custom: specify file patterns manually"));
841 renderer
= gtk_cell_renderer_text_new();
842 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo
), renderer
, TRUE
);
843 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo
), renderer
, "text", 0, "sensitive", 1, NULL
);
849 /* updates the sensitivity of the project combo item */
850 static void update_fif_file_mode_combo(void)
852 GtkTreeModel
*model
= gtk_combo_box_get_model(GTK_COMBO_BOX(fif_dlg
.files_mode_combo
));
855 /* "1" refers to the second list entry, project */
856 if (gtk_tree_model_get_iter_from_string(model
, &iter
, "1"))
857 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
, 1, app
->project
!= NULL
, -1);
861 static void create_fif_dialog(void)
863 GtkWidget
*dir_combo
, *combo
, *fcombo
, *e_combo
, *entry
;
864 GtkWidget
*label
, *label1
, *label2
, *label3
, *checkbox1
, *checkbox2
, *check_wholeword
,
865 *check_recursive
, *check_extra
, *entry_extra
, *check_regexp
, *combo_files_mode
;
866 GtkWidget
*dbox
, *sbox
, *lbox
, *rbox
, *hbox
, *vbox
, *ebox
;
867 GtkSizeGroup
*size_group
;
868 gchar
*encoding_string
;
871 load_monospace_style();
873 fif_dlg
.dialog
= gtk_dialog_new_with_buttons(
874 _("Find in Files"), GTK_WINDOW(main_widgets
.window
), GTK_DIALOG_DESTROY_WITH_PARENT
,
875 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
, NULL
);
876 vbox
= ui_dialog_vbox_new(GTK_DIALOG(fif_dlg
.dialog
));
877 gtk_box_set_spacing(GTK_BOX(vbox
), 9);
878 gtk_widget_set_name(fif_dlg
.dialog
, "GeanyDialogSearch");
880 gtk_dialog_add_button(GTK_DIALOG(fif_dlg
.dialog
), "gtk-find", GTK_RESPONSE_ACCEPT
);
881 gtk_dialog_set_default_response(GTK_DIALOG(fif_dlg
.dialog
),
882 GTK_RESPONSE_ACCEPT
);
884 label
= gtk_label_new_with_mnemonic(_("_Search for:"));
885 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
887 combo
= gtk_combo_box_entry_new_text();
888 entry
= gtk_bin_get_child(GTK_BIN(combo
));
889 ui_entry_add_clear_icon(GTK_ENTRY(entry
));
890 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), entry
);
891 gtk_entry_set_max_length(GTK_ENTRY(entry
), 248);
892 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 50);
893 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
894 fif_dlg
.search_combo
= combo
;
896 sbox
= gtk_hbox_new(FALSE
, 6);
897 gtk_box_pack_start(GTK_BOX(sbox
), label
, FALSE
, FALSE
, 0);
898 gtk_box_pack_start(GTK_BOX(sbox
), combo
, TRUE
, TRUE
, 0);
900 /* make labels same width */
901 size_group
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
902 gtk_size_group_add_widget(size_group
, label
);
904 label3
= gtk_label_new_with_mnemonic(_("Fi_les:"));
905 gtk_misc_set_alignment(GTK_MISC(label3
), 0, 0.5);
907 combo_files_mode
= create_fif_file_mode_combo();
908 gtk_label_set_mnemonic_widget(GTK_LABEL(label3
), combo_files_mode
);
909 ui_hookup_widget(fif_dlg
.dialog
, combo_files_mode
, "combo_files_mode");
910 fif_dlg
.files_mode_combo
= combo_files_mode
;
912 fcombo
= gtk_combo_box_entry_new_text();
913 entry
= gtk_bin_get_child(GTK_BIN(fcombo
));
914 ui_entry_add_clear_icon(GTK_ENTRY(entry
));
915 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
916 gtk_widget_set_tooltip_text(entry
, _("File patterns, e.g. *.c *.h"));
917 ui_hookup_widget(fif_dlg
.dialog
, entry
, "entry_files");
918 fif_dlg
.files_combo
= fcombo
;
920 /* update the entry when selection is changed */
921 g_signal_connect(combo_files_mode
, "changed", G_CALLBACK(update_file_patterns
), fcombo
);
923 hbox
= gtk_hbox_new(FALSE
, 6);
924 gtk_box_pack_start(GTK_BOX(hbox
), label3
, FALSE
, FALSE
, 0);
925 gtk_box_pack_start(GTK_BOX(hbox
), combo_files_mode
, FALSE
, FALSE
, 0);
926 gtk_box_pack_start(GTK_BOX(hbox
), fcombo
, TRUE
, TRUE
, 0);
928 label1
= gtk_label_new_with_mnemonic(_("_Directory:"));
929 gtk_misc_set_alignment(GTK_MISC(label1
), 0, 0.5);
931 dir_combo
= gtk_combo_box_entry_new_text();
932 entry
= gtk_bin_get_child(GTK_BIN(dir_combo
));
933 ui_entry_add_clear_icon(GTK_ENTRY(entry
));
934 gtk_label_set_mnemonic_widget(GTK_LABEL(label1
), entry
);
935 gtk_entry_set_max_length(GTK_ENTRY(entry
), 248);
936 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 50);
937 fif_dlg
.dir_combo
= dir_combo
;
939 /* tab from files to the dir entry */
940 g_signal_connect(gtk_bin_get_child(GTK_BIN(fcombo
)), "key-press-event",
941 G_CALLBACK(on_widget_key_pressed_set_focus
), entry
);
943 dbox
= ui_path_box_new(NULL
, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
,
945 gtk_box_pack_start(GTK_BOX(dbox
), label1
, FALSE
, FALSE
, 0);
947 label2
= gtk_label_new_with_mnemonic(_("E_ncoding:"));
948 gtk_misc_set_alignment(GTK_MISC(label2
), 0, 0.5);
950 e_combo
= gtk_combo_box_new_text();
951 for (i
= 0; i
< GEANY_ENCODINGS_MAX
; i
++)
953 encoding_string
= encodings_to_string(&encodings
[i
]);
954 gtk_combo_box_append_text(GTK_COMBO_BOX(e_combo
), encoding_string
);
955 g_free(encoding_string
);
957 gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(e_combo
), 3);
958 gtk_label_set_mnemonic_widget(GTK_LABEL(label2
), e_combo
);
959 fif_dlg
.encoding_combo
= e_combo
;
961 ebox
= gtk_hbox_new(FALSE
, 6);
962 gtk_box_pack_start(GTK_BOX(ebox
), label2
, FALSE
, FALSE
, 0);
963 gtk_box_pack_start(GTK_BOX(ebox
), e_combo
, TRUE
, TRUE
, 0);
965 gtk_size_group_add_widget(size_group
, label1
);
966 gtk_size_group_add_widget(size_group
, label2
);
967 gtk_size_group_add_widget(size_group
, label3
);
968 g_object_unref(G_OBJECT(size_group
)); /* auto destroy the size group */
970 gtk_box_pack_start(GTK_BOX(vbox
), sbox
, TRUE
, FALSE
, 0);
971 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, TRUE
, FALSE
, 0);
972 gtk_box_pack_start(GTK_BOX(vbox
), dbox
, TRUE
, FALSE
, 0);
973 gtk_box_pack_start(GTK_BOX(vbox
), ebox
, TRUE
, FALSE
, 0);
975 check_regexp
= gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
976 ui_hookup_widget(fif_dlg
.dialog
, check_regexp
, "check_regexp");
977 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp
), FALSE
);
978 gtk_widget_set_tooltip_text(check_regexp
, _("See grep's manual page for more information"));
980 check_recursive
= gtk_check_button_new_with_mnemonic(_("_Recurse in subfolders"));
981 ui_hookup_widget(fif_dlg
.dialog
, check_recursive
, "check_recursive");
982 gtk_button_set_focus_on_click(GTK_BUTTON(check_recursive
), FALSE
);
984 checkbox1
= gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
985 ui_hookup_widget(fif_dlg
.dialog
, checkbox1
, "check_case");
986 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1
), FALSE
);
987 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox1
), TRUE
);
989 check_wholeword
= gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
990 ui_hookup_widget(fif_dlg
.dialog
, check_wholeword
, "check_wholeword");
991 gtk_button_set_focus_on_click(GTK_BUTTON(check_wholeword
), FALSE
);
993 checkbox2
= gtk_check_button_new_with_mnemonic(_("_Invert search results"));
994 ui_hookup_widget(fif_dlg
.dialog
, checkbox2
, "check_invert");
995 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2
), FALSE
);
996 gtk_widget_set_tooltip_text(checkbox2
,
997 _("Invert the sense of matching, to select non-matching lines"));
999 lbox
= gtk_vbox_new(FALSE
, 0);
1000 gtk_container_add(GTK_CONTAINER(lbox
), check_regexp
);
1001 gtk_container_add(GTK_CONTAINER(lbox
), checkbox2
);
1002 gtk_container_add(GTK_CONTAINER(lbox
), check_recursive
);
1004 rbox
= gtk_vbox_new(FALSE
, 0);
1005 gtk_container_add(GTK_CONTAINER(rbox
), checkbox1
);
1006 gtk_container_add(GTK_CONTAINER(rbox
), check_wholeword
);
1007 gtk_container_add(GTK_CONTAINER(rbox
), gtk_label_new(NULL
));
1009 hbox
= gtk_hbox_new(FALSE
, 6);
1010 gtk_container_add(GTK_CONTAINER(hbox
), lbox
);
1011 gtk_container_add(GTK_CONTAINER(hbox
), rbox
);
1012 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
1014 check_extra
= gtk_check_button_new_with_mnemonic(_("E_xtra options:"));
1015 ui_hookup_widget(fif_dlg
.dialog
, check_extra
, "check_extra");
1016 gtk_button_set_focus_on_click(GTK_BUTTON(check_extra
), FALSE
);
1018 entry_extra
= gtk_entry_new();
1019 ui_entry_add_clear_icon(GTK_ENTRY(entry_extra
));
1020 gtk_widget_set_sensitive(entry_extra
, FALSE
);
1021 gtk_widget_set_tooltip_text(entry_extra
, _("Other options to pass to Grep"));
1022 ui_hookup_widget(fif_dlg
.dialog
, entry_extra
, "entry_extra");
1024 /* enable entry_extra when check_extra is checked */
1025 g_signal_connect(check_extra
, "toggled",
1026 G_CALLBACK(on_widget_toggled_set_sensitive
), entry_extra
);
1028 hbox
= gtk_hbox_new(FALSE
, 6);
1029 gtk_box_pack_start(GTK_BOX(hbox
), check_extra
, FALSE
, FALSE
, 0);
1030 gtk_box_pack_start(GTK_BOX(hbox
), entry_extra
, TRUE
, TRUE
, 0);
1031 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
1033 g_signal_connect(fif_dlg
.dialog
, "response",
1034 G_CALLBACK(on_find_in_files_dialog_response
), NULL
);
1035 g_signal_connect(fif_dlg
.dialog
, "delete-event",
1036 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
1040 /* dir is the directory to search in (UTF-8 encoding), maybe NULL to determine it the usual way
1041 * by using the current file's path */
1042 void search_show_find_in_files_dialog(const gchar
*dir
)
1044 GtkWidget
*entry
; /* for child GtkEntry of a GtkComboBoxEntry */
1045 GeanyDocument
*doc
= document_get_current();
1047 gchar
*cur_dir
= NULL
;
1048 GeanyEncodingIndex enc_idx
= GEANY_ENCODING_UTF_8
;
1050 if (fif_dlg
.dialog
== NULL
)
1052 create_fif_dialog();
1053 gtk_widget_show_all(fif_dlg
.dialog
);
1055 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
1057 stash_group_display(fif_prefs
, fif_dlg
.dialog
);
1059 /* only set selection if the dialog is not already visible, or has just been created */
1060 if (doc
&& ! sel
&& ! GTK_WIDGET_VISIBLE(fif_dlg
.dialog
))
1061 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
1063 entry
= GTK_BIN(fif_dlg
.search_combo
)->child
;
1065 gtk_entry_set_text(GTK_ENTRY(entry
), sel
);
1068 /* add project's base path directory to the dir list, we do this here once
1069 * (in create_fif_dialog() it would fail if a project is opened after dialog creation) */
1070 if (app
->project
!= NULL
&& NZV(app
->project
->base_path
))
1072 ui_combo_box_prepend_text_once(GTK_COMBO_BOX(fif_dlg
.dir_combo
),
1073 app
->project
->base_path
);
1076 entry
= GTK_BIN(fif_dlg
.dir_combo
)->child
;
1078 cur_dir
= g_strdup(dir
); /* custom directory argument passed */
1081 gboolean entry_empty
= ! NZV(gtk_entry_get_text(GTK_ENTRY(entry
)));
1083 if (search_prefs
.use_current_file_dir
|| entry_empty
)
1085 cur_dir
= utils_get_current_file_dir_utf8();
1087 /* use default_open_path if no directory could be determined
1088 * (e.g. when no files are open) */
1090 cur_dir
= g_strdup(utils_get_default_dir_utf8());
1092 cur_dir
= g_get_current_dir();
1097 gtk_entry_set_text(GTK_ENTRY(entry
), cur_dir
);
1101 update_fif_file_mode_combo();
1102 update_file_patterns(fif_dlg
.files_mode_combo
, fif_dlg
.files_combo
);
1104 /* set the encoding of the current file */
1106 enc_idx
= encodings_get_idx_from_charset(doc
->encoding
);
1107 gtk_combo_box_set_active(GTK_COMBO_BOX(fif_dlg
.encoding_combo
), enc_idx
);
1109 /* put the focus to the directory entry if it is empty */
1110 if (utils_str_equal(gtk_entry_get_text(GTK_ENTRY(entry
)), ""))
1111 gtk_widget_grab_focus(fif_dlg
.dir_combo
);
1113 gtk_widget_grab_focus(fif_dlg
.search_combo
);
1115 /* set dialog window position */
1116 set_dialog_position(fif_dlg
.dialog
, fif_dlg
.position
);
1118 gtk_widget_show(fif_dlg
.dialog
);
1119 /* bring the dialog back in the foreground in case it is already open but the focus is away */
1120 gtk_window_present(GTK_WINDOW(fif_dlg
.dialog
));
1125 on_find_replace_checkbutton_toggled(GtkToggleButton
*togglebutton
, gpointer user_data
)
1127 GtkWidget
*dialog
= GTK_WIDGET(user_data
);
1128 GtkToggleButton
*chk_regexp
= GTK_TOGGLE_BUTTON(
1129 ui_lookup_widget(dialog
, "check_regexp"));
1131 if (togglebutton
== chk_regexp
)
1133 gboolean regex_set
= gtk_toggle_button_get_active(chk_regexp
);
1134 GtkWidget
*check_word
= ui_lookup_widget(dialog
, "check_word");
1135 GtkWidget
*check_wordstart
= ui_lookup_widget(dialog
, "check_wordstart");
1136 GtkWidget
*check_escape
= ui_lookup_widget(dialog
, "check_escape");
1137 gboolean replace
= (dialog
!= find_dlg
.dialog
);
1138 const char *back_button
[2] = { "btn_previous" , "check_back" };
1140 /* hide options that don't apply to regex searches */
1141 gtk_widget_set_sensitive(check_escape
, ! regex_set
);
1142 gtk_widget_set_sensitive(ui_lookup_widget(dialog
, back_button
[replace
]), ! regex_set
);
1143 gtk_widget_set_sensitive(check_word
, ! regex_set
);
1144 gtk_widget_set_sensitive(check_wordstart
, ! regex_set
);
1149 /* Clears markers if text is null/empty.
1150 * @return Number of matches marked. */
1151 gint
search_mark_all(GeanyDocument
*doc
, const gchar
*search_text
, gint flags
)
1153 gint pos
, count
= 0;
1155 struct Sci_TextToFind ttf
;
1157 g_return_val_if_fail(doc
!= NULL
, 0);
1159 /* clear previous search indicators */
1160 editor_indicator_clear(doc
->editor
, GEANY_INDICATOR_SEARCH
);
1162 if (G_UNLIKELY(! NZV(search_text
)))
1166 ttf
.chrg
.cpMax
= sci_get_length(doc
->editor
->sci
);
1167 ttf
.lpstrText
= (gchar
*)search_text
;
1170 pos
= sci_find_text(doc
->editor
->sci
, flags
, &ttf
);
1171 if (pos
== -1) break;
1173 len
= ttf
.chrgText
.cpMax
- ttf
.chrgText
.cpMin
;
1175 editor_indicator_set_on_range(doc
->editor
, GEANY_INDICATOR_SEARCH
, pos
, pos
+ len
);
1177 ttf
.chrg
.cpMin
= ttf
.chrgText
.cpMax
;
1185 on_find_entry_activate(GtkEntry
*entry
, gpointer user_data
)
1187 on_find_dialog_response(NULL
, GEANY_RESPONSE_FIND
,
1188 ui_lookup_widget(GTK_WIDGET(entry
), "entry"));
1193 on_find_entry_activate_backward(GtkEntry
*entry
, gpointer user_data
)
1195 /* can't search backwards with a regexp */
1196 if (search_data
.flags
& SCFIND_REGEXP
)
1199 on_find_dialog_response(NULL
, GEANY_RESPONSE_FIND_PREVIOUS
,
1200 ui_lookup_widget(GTK_WIDGET(entry
), "entry"));
1204 #define int_search_flags(match_case, whole_word, regexp, word_start) \
1205 ((match_case ? SCFIND_MATCHCASE : 0) | \
1206 (whole_word ? SCFIND_WHOLEWORD : 0) | \
1207 (regexp ? SCFIND_REGEXP | SCFIND_POSIX : 0) | \
1208 (word_start ? SCFIND_WORDSTART : 0))
1212 on_find_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
)
1214 gtk_window_get_position(GTK_WINDOW(find_dlg
.dialog
),
1215 &find_dlg
.position
[0], &find_dlg
.position
[1]);
1217 stash_group_update(find_prefs
, find_dlg
.dialog
);
1219 if (response
== GTK_RESPONSE_CANCEL
|| response
== GTK_RESPONSE_DELETE_EVENT
)
1220 gtk_widget_hide(find_dlg
.dialog
);
1223 GeanyDocument
*doc
= document_get_current();
1224 gboolean check_close
= settings
.find_close_dialog
;
1229 search_data
.backwards
= FALSE
;
1230 search_data
.search_bar
= FALSE
;
1232 g_free(search_data
.text
);
1233 g_free(search_data
.original_text
);
1234 search_data
.text
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(user_data
)))));
1235 search_data
.original_text
= g_strdup(search_data
.text
);
1236 search_data
.flags
= int_search_flags(settings
.find_case_sensitive
,
1237 settings
.find_match_whole_word
, settings
.find_regexp
, settings
.find_match_word_start
);
1239 if (! NZV(search_data
.text
))
1243 gtk_widget_grab_focus(find_dlg
.entry
);
1246 if (settings
.find_escape_sequences
|| search_data
.flags
& SCFIND_REGEXP
)
1248 if (! utils_str_replace_escape(search_data
.text
, search_data
.flags
& SCFIND_REGEXP
))
1251 ui_combo_box_add_to_history(GTK_COMBO_BOX_ENTRY(user_data
), search_data
.original_text
, 0);
1255 case GEANY_RESPONSE_FIND
:
1256 case GEANY_RESPONSE_FIND_PREVIOUS
:
1258 gint result
= document_find_text(doc
, search_data
.text
, search_data
.original_text
, search_data
.flags
,
1259 (response
== GEANY_RESPONSE_FIND_PREVIOUS
), TRUE
, GTK_WIDGET(find_dlg
.dialog
));
1260 ui_set_search_entry_background(find_dlg
.entry
, (result
> -1));
1261 check_close
= FALSE
;
1262 if (search_prefs
.suppress_dialogs
)
1266 case GEANY_RESPONSE_FIND_IN_FILE
:
1267 search_find_usage(search_data
.text
, search_data
.original_text
, search_data
.flags
, FALSE
);
1270 case GEANY_RESPONSE_FIND_IN_SESSION
:
1271 search_find_usage(search_data
.text
, search_data
.original_text
, search_data
.flags
, TRUE
);
1274 case GEANY_RESPONSE_MARK
:
1276 gint count
= search_mark_all(doc
, search_data
.text
, search_data
.flags
);
1279 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), search_data
.original_text
);
1281 ui_set_statusbar(FALSE
,
1282 ngettext("Found %d match for \"%s\".",
1283 "Found %d matches for \"%s\".", count
),
1284 count
, search_data
.original_text
);
1289 gtk_widget_hide(find_dlg
.dialog
);
1295 on_replace_entry_activate(GtkEntry
*entry
, gpointer user_data
)
1297 on_replace_dialog_response(NULL
, GEANY_RESPONSE_REPLACE
, NULL
);
1301 static void replace_in_session(GeanyDocument
*doc
,
1302 gint search_flags_re
, gboolean search_replace_escape_re
,
1303 const gchar
*find
, const gchar
*replace
,
1304 const gchar
*original_find
, const gchar
*original_replace
)
1306 guint n
, page_count
, rep_count
= 0, file_count
= 0;
1308 /* replace in all documents following notebook tab order */
1309 page_count
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets
.notebook
));
1310 for (n
= 0; n
< page_count
; n
++)
1312 GeanyDocument
*tmp_doc
= document_get_from_page(n
);
1315 reps
= document_replace_all(tmp_doc
, find
, replace
, original_find
, original_replace
, search_flags_re
);
1320 if (file_count
== 0)
1323 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), original_find
);
1326 /* if only one file was changed, don't override that document's status message
1327 * so we don't have to translate 4 messages for ngettext */
1329 ui_set_statusbar(FALSE
, _("Replaced %u matches in %u documents."),
1330 rep_count
, file_count
);
1332 /* show which docs had replacements: */
1333 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow
.notebook
), MSG_STATUS
);
1335 ui_save_buttons_toggle(doc
->changed
); /* update save all */
1340 on_replace_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
)
1342 GeanyDocument
*doc
= document_get_current();
1343 gint search_flags_re
;
1344 gboolean search_backwards_re
, search_replace_escape_re
;
1345 gchar
*find
, *replace
, *original_find
= NULL
, *original_replace
= NULL
;
1347 gtk_window_get_position(GTK_WINDOW(replace_dlg
.dialog
),
1348 &replace_dlg
.position
[0], &replace_dlg
.position
[1]);
1350 stash_group_update(replace_prefs
, replace_dlg
.dialog
);
1352 if (response
== GTK_RESPONSE_CANCEL
|| response
== GTK_RESPONSE_DELETE_EVENT
)
1354 gtk_widget_hide(replace_dlg
.dialog
);
1358 search_backwards_re
= settings
.replace_search_backwards
;
1359 search_replace_escape_re
= settings
.replace_escape_sequences
;
1360 find
= g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg
.find_entry
)));
1361 replace
= g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg
.replace_entry
)));
1363 search_flags_re
= int_search_flags(settings
.replace_case_sensitive
,
1364 settings
.replace_match_whole_word
, settings
.replace_regexp
,
1365 settings
.replace_match_word_start
);
1367 if ((response
!= GEANY_RESPONSE_FIND
) && (search_flags_re
& SCFIND_MATCHCASE
)
1368 && (strcmp(find
, replace
) == 0))
1371 original_find
= g_strdup(find
);
1372 original_replace
= g_strdup(replace
);
1373 if (search_flags_re
& SCFIND_REGEXP
)
1375 if (! utils_str_replace_escape(find
, TRUE
) ||
1376 ! utils_str_replace_escape(replace
, TRUE
))
1379 else if (search_replace_escape_re
)
1381 if (! utils_str_replace_escape(find
, FALSE
) ||
1382 ! utils_str_replace_escape(replace
, FALSE
))
1386 ui_combo_box_add_to_history(GTK_COMBO_BOX_ENTRY(
1387 gtk_widget_get_parent(replace_dlg
.find_entry
)), original_find
, 0);
1388 ui_combo_box_add_to_history(GTK_COMBO_BOX_ENTRY(
1389 gtk_widget_get_parent(replace_dlg
.replace_entry
)), original_replace
, 0);
1393 case GEANY_RESPONSE_REPLACE_AND_FIND
:
1395 gint rep
= document_replace_text(doc
, find
, original_find
, replace
, search_flags_re
,
1396 search_backwards_re
);
1398 document_find_text(doc
, find
, original_find
, search_flags_re
, search_backwards_re
,
1402 case GEANY_RESPONSE_REPLACE
:
1403 document_replace_text(doc
, find
, original_find
, replace
, search_flags_re
,
1404 search_backwards_re
);
1407 case GEANY_RESPONSE_FIND
:
1409 gint result
= document_find_text(doc
, find
, original_find
, search_flags_re
,
1410 search_backwards_re
, TRUE
, GTK_WIDGET(dialog
));
1411 ui_set_search_entry_background(replace_dlg
.find_entry
, (result
> -1));
1414 case GEANY_RESPONSE_REPLACE_IN_FILE
:
1415 if (! document_replace_all(doc
, find
, replace
, original_find
, original_replace
, search_flags_re
))
1419 case GEANY_RESPONSE_REPLACE_IN_SESSION
:
1420 replace_in_session(doc
, search_flags_re
, search_replace_escape_re
, find
, replace
, original_find
, original_replace
);
1423 case GEANY_RESPONSE_REPLACE_IN_SEL
:
1424 document_replace_sel(doc
, find
, replace
, original_find
, original_replace
, search_flags_re
);
1429 case GEANY_RESPONSE_REPLACE_IN_SEL
:
1430 case GEANY_RESPONSE_REPLACE_IN_FILE
:
1431 case GEANY_RESPONSE_REPLACE_IN_SESSION
:
1432 if (settings
.replace_close_dialog
)
1433 gtk_widget_hide(replace_dlg
.dialog
);
1437 g_free(original_find
);
1438 g_free(original_replace
);
1443 gtk_widget_grab_focus(replace_dlg
.find_entry
);
1446 g_free(original_find
);
1447 g_free(original_replace
);
1451 static GString
*get_grep_options(void)
1453 GString
*gstr
= g_string_new("-nHI"); /* line numbers, filenames, ignore binaries */
1455 if (settings
.fif_invert_results
)
1456 g_string_append_c(gstr
, 'v');
1457 if (! settings
.fif_case_sensitive
)
1458 g_string_append_c(gstr
, 'i');
1459 if (settings
.fif_match_whole_word
)
1460 g_string_append_c(gstr
, 'w');
1461 if (settings
.fif_recursive
)
1462 g_string_append_c(gstr
, 'r');
1464 if (!settings
.fif_regexp
)
1465 g_string_append_c(gstr
, 'F');
1467 g_string_append_c(gstr
, 'E');
1469 if (settings
.fif_use_extra_options
)
1471 g_strstrip(settings
.fif_extra_options
);
1473 if (*settings
.fif_extra_options
!= 0)
1475 g_string_append_c(gstr
, ' ');
1476 g_string_append(gstr
, settings
.fif_extra_options
);
1479 g_strstrip(settings
.fif_files
);
1480 if (settings
.fif_files_mode
!= FILES_MODE_ALL
&& *settings
.fif_files
)
1484 /* put --include= before each pattern */
1485 tmp
= g_string_new(settings
.fif_files
);
1486 do {} while (utils_string_replace_all(tmp
, " ", " "));
1487 g_string_prepend_c(tmp
, ' ');
1488 utils_string_replace_all(tmp
, " ", " --include=");
1489 g_string_append(gstr
, tmp
->str
);
1490 g_string_free(tmp
, TRUE
);
1497 on_find_in_files_dialog_response(GtkDialog
*dialog
, gint response
,
1498 G_GNUC_UNUSED gpointer user_data
)
1500 gtk_window_get_position(GTK_WINDOW(fif_dlg
.dialog
), &fif_dlg
.position
[0], &fif_dlg
.position
[1]);
1502 stash_group_update(fif_prefs
, fif_dlg
.dialog
);
1504 if (response
== GTK_RESPONSE_ACCEPT
)
1506 GtkWidget
*search_combo
= fif_dlg
.search_combo
;
1507 const gchar
*search_text
=
1508 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(search_combo
))));
1509 GtkWidget
*dir_combo
= fif_dlg
.dir_combo
;
1510 const gchar
*utf8_dir
=
1511 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(dir_combo
))));
1512 GeanyEncodingIndex enc_idx
= gtk_combo_box_get_active(
1513 GTK_COMBO_BOX(fif_dlg
.encoding_combo
));
1515 if (G_UNLIKELY(! NZV(utf8_dir
)))
1516 ui_set_statusbar(FALSE
, _("Invalid directory for find in files."));
1517 else if (NZV(search_text
))
1520 GString
*opts
= get_grep_options();
1521 const gchar
*enc
= (enc_idx
== GEANY_ENCODING_UTF_8
) ? NULL
:
1522 encodings_get_charset_from_index(enc_idx
);
1524 locale_dir
= utils_get_locale_from_utf8(utf8_dir
);
1526 if (search_find_in_files(search_text
, locale_dir
, opts
->str
, enc
))
1528 ui_combo_box_add_to_history(GTK_COMBO_BOX_ENTRY(search_combo
), search_text
, 0);
1529 ui_combo_box_add_to_history(GTK_COMBO_BOX_ENTRY(fif_dlg
.files_combo
), NULL
, 0);
1530 ui_combo_box_add_to_history(GTK_COMBO_BOX_ENTRY(dir_combo
), utf8_dir
, 0);
1531 gtk_widget_hide(fif_dlg
.dialog
);
1534 g_string_free(opts
, TRUE
);
1537 ui_set_statusbar(FALSE
, _("No text to find."));
1540 gtk_widget_hide(fif_dlg
.dialog
);
1545 search_find_in_files(const gchar
*utf8_search_text
, const gchar
*dir
, const gchar
*opts
,
1548 gchar
**argv_prefix
, **argv
, **opts_argv
;
1549 gchar
*command_grep
;
1550 gchar
*search_text
= NULL
;
1551 guint opts_argv_len
, i
;
1555 GError
*error
= NULL
;
1556 gboolean ret
= FALSE
;
1557 gssize utf8_text_len
;
1559 if (! NZV(utf8_search_text
) || ! dir
) return TRUE
;
1561 command_grep
= g_find_program_in_path(tool_prefs
.grep_cmd
);
1562 if (command_grep
== NULL
)
1564 ui_set_statusbar(TRUE
, _("Cannot execute grep tool '%s';"
1565 " check the path setting in Preferences."), tool_prefs
.grep_cmd
);
1569 /* convert the search text in the preferred encoding (if the text is not valid UTF-8. assume
1570 * it is already in the preferred encoding) */
1571 utf8_text_len
= strlen(utf8_search_text
);
1572 if (enc
!= NULL
&& g_utf8_validate(utf8_search_text
, utf8_text_len
, NULL
))
1574 search_text
= g_convert(utf8_search_text
, utf8_text_len
, enc
, "UTF-8", NULL
, NULL
, NULL
);
1576 if (search_text
== NULL
)
1577 search_text
= g_strdup(utf8_search_text
);
1579 opts_argv
= g_strsplit(opts
, " ", -1);
1580 opts_argv_len
= g_strv_length(opts_argv
);
1582 /* set grep command and options */
1583 argv_prefix
= g_new0(gchar
*, 1 + opts_argv_len
+ 3 + 1); /* last +1 for recursive arg */
1585 argv_prefix
[0] = command_grep
;
1586 for (i
= 0; i
< opts_argv_len
; i
++)
1588 argv_prefix
[i
+ 1] = g_strdup(opts_argv
[i
]);
1590 g_strfreev(opts_argv
);
1592 i
++; /* correct for tool_prefs.grep_cmd */
1593 argv_prefix
[i
++] = g_strdup("--");
1594 argv_prefix
[i
++] = search_text
;
1596 /* finally add the arguments(files to be searched) */
1597 if (strstr(argv_prefix
[1], "r")) /* recursive option set */
1599 /* Use '.' so we get relative paths in the output */
1600 argv_prefix
[i
++] = g_strdup(".");
1601 argv_prefix
[i
++] = NULL
;
1606 argv_prefix
[i
++] = NULL
;
1607 argv
= search_get_argv((const gchar
**)argv_prefix
, dir
);
1608 g_strfreev(argv_prefix
);
1611 if (argv
== NULL
) /* no files */
1616 gtk_list_store_clear(msgwindow
.store_msg
);
1617 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow
.notebook
), MSG_MESSAGE
);
1619 if (! g_spawn_async_with_pipes(dir
, (gchar
**)argv
, NULL
, G_SPAWN_DO_NOT_REAP_CHILD
,
1620 NULL
, NULL
, &child_pid
,
1621 NULL
, &stdout_fd
, &stderr_fd
, &error
))
1623 geany_debug("%s: g_spawn_async_with_pipes() failed: %s", G_STRFUNC
, error
->message
);
1624 ui_set_statusbar(TRUE
, _("Process failed (%s)"), error
->message
);
1625 g_error_free(error
);
1630 gchar
*str
, *utf8_str
;
1632 ui_progress_bar_start(_("Searching..."));
1634 msgwin_set_messages_dir(dir
);
1635 /* we can pass 'enc' without strdup'ing it here because it's a global const string and
1636 * always exits longer than the lifetime of this function */
1637 utils_set_up_io_channel(stdout_fd
, G_IO_IN
| G_IO_PRI
| G_IO_ERR
| G_IO_HUP
| G_IO_NVAL
,
1638 TRUE
, search_read_io
, (gpointer
) enc
);
1639 utils_set_up_io_channel(stderr_fd
, G_IO_IN
| G_IO_PRI
| G_IO_ERR
| G_IO_HUP
| G_IO_NVAL
,
1640 TRUE
, search_read_io_stderr
, (gpointer
) enc
);
1641 g_child_watch_add(child_pid
, search_close_pid
, NULL
);
1643 str
= g_strdup_printf(_("%s %s -- %s (in directory: %s)"),
1644 tool_prefs
.grep_cmd
, opts
, utf8_search_text
, dir
);
1645 utf8_str
= utils_get_utf8_from_locale(str
);
1646 msgwin_msg_add_string(COLOR_BLUE
, -1, NULL
, utf8_str
);
1647 utils_free_pointers(2, str
, utf8_str
, NULL
);
1655 static gboolean
pattern_list_match(GSList
*patterns
, const gchar
*str
)
1659 foreach_slist(item
, patterns
)
1661 if (g_pattern_match_string(item
->data
, str
))
1668 /* Creates an argument vector of strings, copying argv_prefix[] values for
1669 * the first arguments, then followed by filenames found in dir.
1670 * Returns NULL if no files were found, otherwise returned vector should be fully freed. */
1671 static gchar
**search_get_argv(const gchar
**argv_prefix
, const gchar
*dir
)
1673 guint prefix_len
, list_len
, i
, j
;
1675 GSList
*list
, *item
, *patterns
= NULL
;
1676 GError
*error
= NULL
;
1678 g_return_val_if_fail(dir
!= NULL
, NULL
);
1680 prefix_len
= g_strv_length((gchar
**)argv_prefix
);
1681 list
= utils_get_file_list(dir
, &list_len
, &error
);
1684 ui_set_statusbar(TRUE
, _("Could not open directory (%s)"), error
->message
);
1685 g_error_free(error
);
1691 argv
= g_new(gchar
*, prefix_len
+ list_len
+ 1);
1693 for (i
= 0, j
= 0; i
< prefix_len
; i
++)
1695 if (g_str_has_prefix(argv_prefix
[i
], "--include="))
1697 const gchar
*pat
= &(argv_prefix
[i
][10]); /* the pattern part of the argument */
1699 patterns
= g_slist_prepend(patterns
, g_pattern_spec_new(pat
));
1702 argv
[j
++] = g_strdup(argv_prefix
[i
]);
1709 foreach_slist(item
, list
)
1711 if (pattern_list_match(patterns
, item
->data
))
1712 argv
[j
++] = item
->data
;
1716 foreach_slist(pat
, patterns
)
1717 g_pattern_spec_free(pat
->data
);
1718 g_slist_free(patterns
);
1722 foreach_slist(item
, list
)
1723 argv
[j
++] = item
->data
;
1732 static gboolean
read_fif_io(GIOChannel
*source
, GIOCondition condition
, gchar
*enc
, gint msg_color
)
1734 if (condition
& (G_IO_IN
| G_IO_PRI
))
1736 gchar
*msg
, *utf8_msg
;
1738 while (g_io_channel_read_line(source
, &msg
, NULL
, NULL
, NULL
) && msg
)
1743 /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */
1746 if (! g_utf8_validate(msg
, -1, NULL
))
1748 utf8_msg
= g_convert(msg
, -1, "UTF-8", enc
, NULL
, NULL
, NULL
);
1750 if (utf8_msg
== NULL
)
1756 msgwin_msg_add_string(msg_color
, -1, NULL
, utf8_msg
);
1758 if (utf8_msg
!= msg
)
1763 if (condition
& (G_IO_ERR
| G_IO_HUP
| G_IO_NVAL
))
1770 static gboolean
search_read_io(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
1772 return read_fif_io(source
, condition
, data
, COLOR_BLACK
);
1776 static gboolean
search_read_io_stderr(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
1778 return read_fif_io(source
, condition
, data
, COLOR_DARK_RED
);
1782 static void search_close_pid(GPid child_pid
, gint status
, gpointer user_data
)
1784 /* TODO: port this also to Windows API */
1786 const gchar
*msg
= _("Search failed.");
1787 gint exit_status
= 1;
1789 if (WIFEXITED(status
))
1791 exit_status
= WEXITSTATUS(status
);
1793 else if (WIFSIGNALED(status
))
1796 g_warning("Find in Files: The command failed unexpectedly (signal received).");
1799 switch (exit_status
)
1803 gint count
= gtk_tree_model_iter_n_children(
1804 GTK_TREE_MODEL(msgwindow
.store_msg
), NULL
) - 1;
1805 gchar
*text
= ngettext(
1806 "Search completed with %d match.",
1807 "Search completed with %d matches.", count
);
1809 msgwin_msg_add(COLOR_BLUE
, -1, NULL
, text
, count
);
1810 ui_set_statusbar(FALSE
, text
, count
);
1814 msg
= _("No matches found.");
1816 msgwin_msg_add_string(COLOR_BLUE
, -1, NULL
, msg
);
1817 ui_set_statusbar(FALSE
, "%s", msg
);
1823 g_spawn_close_pid(child_pid
);
1824 ui_progress_bar_stop();
1828 static gboolean
compile_regex(regex_t
*regex
, const gchar
*str
, gint sflags
)
1831 gint rflags
= REG_EXTENDED
| REG_NEWLINE
;
1833 if (~sflags
& SCFIND_MATCHCASE
)
1834 rflags
|= REG_ICASE
;
1835 if (sflags
& (SCFIND_WHOLEWORD
| SCFIND_WORDSTART
))
1837 geany_debug("%s: Unsupported regex flags found!", G_STRFUNC
);
1840 err
= regcomp(regex
, str
, rflags
);
1845 regerror(err
, regex
, buf
, sizeof buf
);
1846 ui_set_statusbar(FALSE
, _("Bad regex: %s"), buf
);
1853 /* groups that don't exist are handled OK as len = end - start = (-1) - (-1) = 0 */
1854 static gchar
*get_regex_match_string(const gchar
*text
, regmatch_t
*pmatch
, gint match_idx
)
1856 return g_strndup(&text
[pmatch
[match_idx
].rm_so
],
1857 pmatch
[match_idx
].rm_eo
- pmatch
[match_idx
].rm_so
);
1861 static regmatch_t regex_matches
[10];
1862 /* All matching text from regex_matches[0].rm_so to regex_matches[0].rm_eo */
1863 static gchar
*regex_match_text
= NULL
;
1865 static gint
find_regex(ScintillaObject
*sci
, guint pos
, regex_t
*regex
)
1870 g_return_val_if_fail(pos
<= (guint
)sci_get_length(sci
), FALSE
);
1872 if (sci_get_col_from_position(sci
, pos
) != 0)
1874 /* Warning: any SCI calls will invalidate 'text' after calling SCI_GETCHARACTERPOINTER */
1875 text
= (void*)scintilla_send_message(sci
, SCI_GETCHARACTERPOINTER
, 0, 0);
1878 if (regexec(regex
, text
, G_N_ELEMENTS(regex_matches
), regex_matches
, flags
) == 0)
1880 setptr(regex_match_text
, get_regex_match_string(text
, regex_matches
, 0));
1881 return regex_matches
[0].rm_so
+ pos
;
1883 setptr(regex_match_text
, NULL
);
1888 gint
search_find_next(ScintillaObject
*sci
, const gchar
*str
, gint flags
)
1894 if (~flags
& SCFIND_REGEXP
)
1895 return sci_search_next(sci
, flags
, str
);
1897 if (!compile_regex(®ex
, str
, flags
))
1900 pos
= sci_get_current_position(sci
);
1901 ret
= find_regex(sci
, pos
, ®ex
);
1903 sci_set_selection(sci
, ret
, regex_matches
[0].rm_eo
+ pos
);
1910 gint
search_replace_target(ScintillaObject
*sci
, const gchar
*replace_text
,
1918 return sci_replace_target(sci
, replace_text
, FALSE
);
1920 str
= g_string_new(replace_text
);
1923 gchar
*ptr
= &str
->str
[i
];
1933 /* backslash or unnecessary escape */
1934 if (c
== '\\' || !isdigit(c
))
1936 g_string_erase(str
, i
, 1);
1941 g_string_erase(str
, i
, 2);
1942 /* fix match offsets by subtracting index of whole match start from the string */
1943 grp
= get_regex_match_string(regex_match_text
- regex_matches
[0].rm_so
,
1944 regex_matches
, c
- '0');
1945 g_string_insert(str
, i
, grp
);
1949 ret
= sci_replace_target(sci
, str
->str
, FALSE
);
1950 g_string_free(str
, TRUE
);
1955 gint
search_find_text(ScintillaObject
*sci
, gint flags
, struct Sci_TextToFind
*ttf
)
1961 if (~flags
& SCFIND_REGEXP
)
1962 return sci_find_text(sci
, flags
, ttf
);
1964 if (!compile_regex(®ex
, ttf
->lpstrText
, flags
))
1967 pos
= ttf
->chrg
.cpMin
;
1968 ret
= find_regex(sci
, pos
, ®ex
);
1971 if (ret
>= 0 && ret
< ttf
->chrg
.cpMax
)
1973 ttf
->chrgText
.cpMin
= regex_matches
[0].rm_so
+ pos
;
1974 ttf
->chrgText
.cpMax
= regex_matches
[0].rm_eo
+ pos
;
1981 static gint
find_document_usage(GeanyDocument
*doc
, const gchar
*search_text
, gint flags
)
1983 gchar
*buffer
, *short_file_name
;
1984 struct Sci_TextToFind ttf
;
1986 gint prev_line
= -1;
1988 g_return_val_if_fail(doc
!= NULL
, 0);
1990 short_file_name
= g_path_get_basename(DOC_FILENAME(doc
));
1993 ttf
.chrg
.cpMax
= sci_get_length(doc
->editor
->sci
);
1994 ttf
.lpstrText
= (gchar
*)search_text
;
1997 gint pos
, line
, start
, find_len
;
1999 pos
= search_find_text(doc
->editor
->sci
, flags
, &ttf
);
2001 break; /* no more matches */
2002 find_len
= ttf
.chrgText
.cpMax
- ttf
.chrgText
.cpMin
;
2004 break; /* Ignore regex ^ or $ */
2007 line
= sci_get_line_from_position(doc
->editor
->sci
, pos
);
2008 if (line
!= prev_line
)
2010 buffer
= sci_get_line(doc
->editor
->sci
, line
);
2011 msgwin_msg_add(COLOR_BLACK
, line
+ 1, doc
,
2012 "%s:%d: %s", short_file_name
, line
+ 1, g_strstrip(buffer
));
2017 start
= ttf
.chrgText
.cpMax
+ 1;
2018 ttf
.chrg
.cpMin
= start
;
2020 g_free(short_file_name
);
2025 void search_find_usage(const gchar
*search_text
, const gchar
*original_search_text
,
2026 gint flags
, gboolean in_session
)
2031 doc
= document_get_current();
2032 g_return_if_fail(doc
!= NULL
);
2034 if (G_UNLIKELY(! NZV(search_text
)))
2040 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow
.notebook
), MSG_MESSAGE
);
2041 gtk_list_store_clear(msgwindow
.store_msg
);
2044 { /* use current document */
2045 count
= find_document_usage(doc
, search_text
, flags
);
2050 for (i
= 0; i
< documents_array
->len
; i
++)
2052 if (documents
[i
]->is_valid
)
2054 count
+= find_document_usage(documents
[i
], search_text
, flags
);
2059 if (count
== 0) /* no matches were found */
2061 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), original_search_text
);
2062 msgwin_msg_add(COLOR_BLUE
, -1, NULL
, _("No matches found for \"%s\"."), original_search_text
);
2066 ui_set_statusbar(FALSE
, ngettext(
2067 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count
),
2068 count
, original_search_text
);
2069 msgwin_msg_add(COLOR_BLUE
, -1, NULL
, ngettext(
2070 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count
),
2071 count
, original_search_text
);
2076 /* ttf is updated to include the last match position (ttf->chrg.cpMin) and
2077 * the new search range end (ttf->chrg.cpMax).
2078 * Note: Normally you would call sci_start/end_undo_action() around this call. */
2079 /* Warning: Scintilla recommends caching replacements to do all at once to avoid
2080 * performance issues with SCI_GETCHARACTERPOINTER. */
2081 guint
search_replace_range(ScintillaObject
*sci
, struct Sci_TextToFind
*ttf
,
2082 gint flags
, const gchar
*replace_text
)
2085 const gchar
*find_text
= ttf
->lpstrText
;
2086 gint start
= ttf
->chrg
.cpMin
;
2087 gint end
= ttf
->chrg
.cpMax
;
2089 g_return_val_if_fail(sci
!= NULL
&& find_text
!= NULL
&& replace_text
!= NULL
, 0);
2096 gint find_len
= 0, replace_len
= 0;
2098 search_pos
= search_find_text(sci
, flags
, ttf
);
2099 find_len
= ttf
->chrgText
.cpMax
- ttf
->chrgText
.cpMin
;
2100 if (search_pos
== -1)
2101 break; /* no more matches */
2102 if (find_len
== 0 && ! NZV(replace_text
))
2103 break; /* nothing to do */
2105 if (search_pos
+ find_len
> end
)
2106 break; /* found text is partly out of range */
2109 gint movepastEOL
= 0;
2111 sci_set_target_start(sci
, search_pos
);
2112 sci_set_target_end(sci
, search_pos
+ find_len
);
2116 gchar chNext
= sci_get_char_at(sci
, sci_get_target_end(sci
));
2118 if (chNext
== '\r' || chNext
== '\n')
2121 replace_len
= search_replace_target(sci
, replace_text
,
2122 flags
& SCFIND_REGEXP
);
2124 if (search_pos
== end
)
2125 break; /* Prevent hang when replacing regex $ */
2127 /* make the next search start after the replaced text */
2128 start
= search_pos
+ replace_len
+ movepastEOL
;
2130 start
= sci_get_position_after(sci
, start
); /* prevent '[ ]*' regex rematching part of replaced text */
2131 ttf
->chrg
.cpMin
= start
;
2132 end
+= replace_len
- find_len
; /* update end of range now text has changed */
2133 ttf
->chrg
.cpMax
= end
;
2140 void search_find_again(gboolean change_direction
)
2142 GeanyDocument
*doc
= document_get_current();
2144 g_return_if_fail(doc
!= NULL
);
2146 if (search_data
.text
)
2148 gboolean forward
= ! search_data
.backwards
;
2149 gint result
= document_find_text(doc
, search_data
.text
, search_data
.original_text
, search_data
.flags
,
2150 change_direction
? forward
: !forward
, FALSE
, NULL
);
2153 editor_display_current_line(doc
->editor
, 0.3F
);
2155 if (search_data
.search_bar
)
2156 ui_set_search_entry_background(
2157 toolbar_get_widget_child_by_name("SearchEntry"), (result
> -1));