2 * search.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2006-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2012 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 along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 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"
44 #include "gtkcompat.h"
51 # include <sys/types.h>
52 # include <sys/wait.h>
58 GEANY_RESPONSE_FIND
= 1,
59 GEANY_RESPONSE_FIND_PREVIOUS
,
60 GEANY_RESPONSE_FIND_IN_FILE
,
61 GEANY_RESPONSE_FIND_IN_SESSION
,
63 GEANY_RESPONSE_REPLACE
,
64 GEANY_RESPONSE_REPLACE_AND_FIND
,
65 GEANY_RESPONSE_REPLACE_IN_SESSION
,
66 GEANY_RESPONSE_REPLACE_IN_FILE
,
67 GEANY_RESPONSE_REPLACE_IN_SEL
79 GeanySearchData search_data
;
80 GeanySearchPrefs search_prefs
;
86 gboolean fif_case_sensitive
;
87 gboolean fif_match_whole_word
;
88 gboolean fif_invert_results
;
89 gboolean fif_recursive
;
90 gboolean fif_use_extra_options
;
91 gchar
*fif_extra_options
;
95 gboolean find_escape_sequences
;
96 gboolean find_case_sensitive
;
97 gboolean find_match_whole_word
;
98 gboolean find_match_word_start
;
99 gboolean find_close_dialog
;
100 gboolean replace_regexp
;
101 gboolean replace_escape_sequences
;
102 gboolean replace_case_sensitive
;
103 gboolean replace_match_whole_word
;
104 gboolean replace_match_word_start
;
105 gboolean replace_search_backwards
;
106 gboolean replace_close_dialog
;
110 static StashGroup
*fif_prefs
= NULL
;
111 static StashGroup
*find_prefs
= NULL
;
112 static StashGroup
*replace_prefs
= NULL
;
119 gboolean all_expanded
;
120 gint position
[2]; /* x, y */
122 find_dlg
= {NULL
, NULL
, FALSE
, {0, 0}};
127 GtkWidget
*find_entry
;
128 GtkWidget
*replace_entry
;
129 gboolean all_expanded
;
130 gint position
[2]; /* x, y */
132 replace_dlg
= {NULL
, NULL
, NULL
, FALSE
, {0, 0}};
137 GtkWidget
*dir_combo
;
138 GtkWidget
*files_combo
;
139 GtkWidget
*search_combo
;
140 GtkWidget
*encoding_combo
;
141 GtkWidget
*files_mode_combo
;
142 gint position
[2]; /* x, y */
144 fif_dlg
= {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, {0, 0}};
147 static gboolean
search_read_io(GIOChannel
*source
, GIOCondition condition
, gpointer data
);
148 static gboolean
search_read_io_stderr(GIOChannel
*source
, GIOCondition condition
, gpointer data
);
150 static void search_close_pid(GPid child_pid
, gint status
, gpointer user_data
);
152 static gchar
**search_get_argv(const gchar
**argv_prefix
, const gchar
*dir
);
154 static GRegex
*compile_regex(const gchar
*str
, gint sflags
);
158 on_find_replace_checkbutton_toggled(GtkToggleButton
*togglebutton
, gpointer user_data
);
161 on_find_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
);
164 on_find_entry_activate(GtkEntry
*entry
, gpointer user_data
);
167 on_find_entry_activate_backward(GtkEntry
*entry
, gpointer user_data
);
170 on_replace_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
);
173 on_replace_entry_activate(GtkEntry
*entry
, gpointer user_data
);
176 on_find_in_files_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
);
179 search_find_in_files(const gchar
*utf8_search_text
, const gchar
*dir
, const gchar
*opts
,
183 static void init_prefs(void)
187 group
= stash_group_new("search");
188 configuration_add_pref_group(group
, TRUE
);
189 stash_group_add_toggle_button(group
, &search_prefs
.always_wrap
,
190 "pref_search_hide_find_dialog", FALSE
, "check_always_wrap_search");
191 stash_group_add_toggle_button(group
, &search_prefs
.hide_find_dialog
,
192 "pref_search_always_wrap", FALSE
, "check_hide_find_dialog");
193 stash_group_add_toggle_button(group
, &search_prefs
.use_current_file_dir
,
194 "pref_search_current_file_dir", TRUE
, "check_fif_current_dir");
195 stash_group_add_boolean(group
, &find_dlg
.all_expanded
, "find_all_expanded", FALSE
);
196 stash_group_add_boolean(group
, &replace_dlg
.all_expanded
, "replace_all_expanded", FALSE
);
197 /* dialog positions */
198 stash_group_add_integer(group
, &find_dlg
.position
[0], "position_find_x", -1);
199 stash_group_add_integer(group
, &find_dlg
.position
[1], "position_find_y", -1);
200 stash_group_add_integer(group
, &replace_dlg
.position
[0], "position_replace_x", -1);
201 stash_group_add_integer(group
, &replace_dlg
.position
[1], "position_replace_y", -1);
202 stash_group_add_integer(group
, &fif_dlg
.position
[0], "position_fif_x", -1);
203 stash_group_add_integer(group
, &fif_dlg
.position
[1], "position_fif_y", -1);
205 memset(&settings
, '\0', sizeof(settings
));
207 group
= stash_group_new("search");
209 configuration_add_pref_group(group
, FALSE
);
210 stash_group_add_toggle_button(group
, &settings
.fif_regexp
,
211 "fif_regexp", FALSE
, "check_regexp");
212 stash_group_add_toggle_button(group
, &settings
.fif_case_sensitive
,
213 "fif_case_sensitive", TRUE
, "check_case");
214 stash_group_add_toggle_button(group
, &settings
.fif_match_whole_word
,
215 "fif_match_whole_word", FALSE
, "check_wholeword");
216 stash_group_add_toggle_button(group
, &settings
.fif_invert_results
,
217 "fif_invert_results", FALSE
, "check_invert");
218 stash_group_add_toggle_button(group
, &settings
.fif_recursive
,
219 "fif_recursive", FALSE
, "check_recursive");
220 stash_group_add_entry(group
, &settings
.fif_extra_options
,
221 "fif_extra_options", "", "entry_extra");
222 stash_group_add_toggle_button(group
, &settings
.fif_use_extra_options
,
223 "fif_use_extra_options", FALSE
, "check_extra");
224 stash_group_add_entry(group
, &settings
.fif_files
,
225 "fif_files", "", "entry_files");
226 stash_group_add_combo_box(group
, &settings
.fif_files_mode
,
227 "fif_files_mode", FILES_MODE_ALL
, "combo_files_mode");
229 group
= stash_group_new("search");
231 configuration_add_pref_group(group
, FALSE
);
232 stash_group_add_toggle_button(group
, &settings
.find_regexp
,
233 "find_regexp", FALSE
, "check_regexp");
234 stash_group_add_toggle_button(group
, &settings
.find_case_sensitive
,
235 "find_case_sensitive", FALSE
, "check_case");
236 stash_group_add_toggle_button(group
, &settings
.find_escape_sequences
,
237 "find_escape_sequences", FALSE
, "check_escape");
238 stash_group_add_toggle_button(group
, &settings
.find_match_whole_word
,
239 "find_match_whole_word", FALSE
, "check_word");
240 stash_group_add_toggle_button(group
, &settings
.find_match_word_start
,
241 "find_match_word_start", FALSE
, "check_wordstart");
242 stash_group_add_toggle_button(group
, &settings
.find_close_dialog
,
243 "find_close_dialog", TRUE
, "check_close");
245 group
= stash_group_new("search");
246 replace_prefs
= group
;
247 configuration_add_pref_group(group
, FALSE
);
248 stash_group_add_toggle_button(group
, &settings
.replace_regexp
,
249 "replace_regexp", FALSE
, "check_regexp");
250 stash_group_add_toggle_button(group
, &settings
.replace_case_sensitive
,
251 "replace_case_sensitive", FALSE
, "check_case");
252 stash_group_add_toggle_button(group
, &settings
.replace_escape_sequences
,
253 "replace_escape_sequences", FALSE
, "check_escape");
254 stash_group_add_toggle_button(group
, &settings
.replace_match_whole_word
,
255 "replace_match_whole_word", FALSE
, "check_word");
256 stash_group_add_toggle_button(group
, &settings
.replace_match_word_start
,
257 "replace_match_word_start", FALSE
, "check_wordstart");
258 stash_group_add_toggle_button(group
, &settings
.replace_search_backwards
,
259 "replace_search_backwards", FALSE
, "check_back");
260 stash_group_add_toggle_button(group
, &settings
.replace_close_dialog
,
261 "replace_close_dialog", TRUE
, "check_close");
265 void search_init(void)
267 search_data
.text
= NULL
;
268 search_data
.original_text
= NULL
;
273 #define FREE_WIDGET(wid) \
274 if (wid && GTK_IS_WIDGET(wid)) gtk_widget_destroy(wid);
276 void search_finalize(void)
278 FREE_WIDGET(find_dlg
.dialog
);
279 FREE_WIDGET(replace_dlg
.dialog
);
280 FREE_WIDGET(fif_dlg
.dialog
);
281 g_free(search_data
.text
);
282 g_free(search_data
.original_text
);
286 static void on_widget_toggled_set_insensitive(
287 GtkToggleButton
*togglebutton
, gpointer user_data
)
289 gtk_widget_set_sensitive(GTK_WIDGET(user_data
),
290 !gtk_toggle_button_get_active(togglebutton
));
294 static GtkWidget
*add_find_checkboxes(GtkDialog
*dialog
)
296 GtkWidget
*checkbox1
, *checkbox2
, *check_regexp
, *check_back
, *checkbox5
,
297 *checkbox7
, *hbox
, *fbox
, *mbox
;
299 check_regexp
= gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
300 ui_hookup_widget(dialog
, check_regexp
, "check_regexp");
301 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp
), FALSE
);
302 gtk_widget_set_tooltip_text(check_regexp
, _("Use POSIX-like regular expressions. "
303 "For detailed information about using regular expressions, please read the documentation."));
304 g_signal_connect(check_regexp
, "toggled",
305 G_CALLBACK(on_find_replace_checkbutton_toggled
), dialog
);
307 if (dialog
!= GTK_DIALOG(find_dlg
.dialog
))
309 check_back
= gtk_check_button_new_with_mnemonic(_("Search _backwards"));
310 ui_hookup_widget(dialog
, check_back
, "check_back");
311 gtk_button_set_focus_on_click(GTK_BUTTON(check_back
), FALSE
);
314 { /* align the two checkboxes at the top of the hbox */
315 GtkSizeGroup
*label_size
;
316 check_back
= gtk_label_new(NULL
);
317 label_size
= gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL
);
318 gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size
), check_back
);
319 gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size
), check_regexp
);
320 g_object_unref(label_size
);
322 checkbox7
= gtk_check_button_new_with_mnemonic(_("Use _escape sequences"));
323 ui_hookup_widget(dialog
, checkbox7
, "check_escape");
324 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox7
), FALSE
);
325 gtk_widget_set_tooltip_text(checkbox7
,
326 _("Replace \\\\, \\t, \\n, \\r and \\uXXXX (Unicode chararacters) with the "
327 "corresponding control characters"));
329 /* Search features */
330 fbox
= gtk_vbox_new(FALSE
, 0);
331 gtk_container_add(GTK_CONTAINER(fbox
), check_regexp
);
332 gtk_container_add(GTK_CONTAINER(fbox
), checkbox7
);
333 gtk_container_add(GTK_CONTAINER(fbox
), check_back
);
335 checkbox1
= gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
336 ui_hookup_widget(dialog
, checkbox1
, "check_case");
337 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1
), FALSE
);
339 checkbox2
= gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
340 ui_hookup_widget(dialog
, checkbox2
, "check_word");
341 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2
), FALSE
);
343 checkbox5
= gtk_check_button_new_with_mnemonic(_("Match from s_tart of word"));
344 ui_hookup_widget(dialog
, checkbox5
, "check_wordstart");
345 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox5
), FALSE
);
347 /* disable wordstart when wholeword is checked */
348 g_signal_connect(checkbox2
, "toggled",
349 G_CALLBACK(on_widget_toggled_set_insensitive
), checkbox5
);
351 /* Matching options */
352 mbox
= gtk_vbox_new(FALSE
, 0);
353 gtk_container_add(GTK_CONTAINER(mbox
), checkbox1
);
354 gtk_container_add(GTK_CONTAINER(mbox
), checkbox2
);
355 gtk_container_add(GTK_CONTAINER(mbox
), checkbox5
);
357 hbox
= gtk_hbox_new(TRUE
, 6);
358 gtk_container_add(GTK_CONTAINER(hbox
), fbox
);
359 gtk_container_add(GTK_CONTAINER(hbox
), mbox
);
364 static void send_find_dialog_response(GtkButton
*button
, gpointer user_data
)
366 gtk_dialog_response(GTK_DIALOG(find_dlg
.dialog
), GPOINTER_TO_INT(user_data
));
370 /* store text, clear search flags so we can use Search->Find Next/Previous */
371 static void setup_find_next(const gchar
*text
)
373 g_free(search_data
.text
);
374 g_free(search_data
.original_text
);
375 search_data
.text
= g_strdup(text
);
376 search_data
.original_text
= g_strdup(text
);
377 search_data
.flags
= 0;
378 search_data
.backwards
= FALSE
;
379 search_data
.search_bar
= FALSE
;
383 /* Search for next match of the current "selection".
384 * Optionally for X11 based systems, this will try to use the system-wide
386 * If it doesn't find a suitable search string it will try to use
387 * the current word instead, or just repeat the last search.
388 * Search flags are always zero.
390 void search_find_selection(GeanyDocument
*doc
, gboolean search_backwards
)
394 g_return_if_fail(doc
!= NULL
);
397 if (search_prefs
.find_selection_type
== GEANY_FIND_SEL_X
)
399 GtkClipboard
*clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
401 s
= gtk_clipboard_wait_for_text(clipboard
);
402 if (s
&& (strchr(s
,'\n') || strchr(s
, '\r')))
410 if (!s
&& sci_has_selection(doc
->editor
->sci
))
411 s
= sci_get_selection_contents(doc
->editor
->sci
);
413 if (!s
&& search_prefs
.find_selection_type
!= GEANY_FIND_SEL_AGAIN
)
415 /* get the current word */
416 s
= editor_get_default_selection(doc
->editor
, TRUE
, NULL
);
421 setup_find_next(s
); /* allow find next/prev */
423 if (document_find_text(doc
, s
, NULL
, 0, search_backwards
, NULL
, FALSE
, NULL
) > -1)
424 editor_display_current_line(doc
->editor
, 0.3F
);
427 else if (search_prefs
.find_selection_type
== GEANY_FIND_SEL_AGAIN
)
429 /* Repeat last search (in case selection was lost) */
430 search_find_again(search_backwards
);
439 static void on_expander_activated(GtkExpander
*exp
, gpointer data
)
441 gboolean
*setting
= data
;
443 *setting
= gtk_expander_get_expanded(exp
);
447 static void create_find_dialog(void)
449 GtkWidget
*label
, *entry
, *sbox
, *vbox
;
450 GtkWidget
*exp
, *bbox
, *button
, *check_close
;
452 find_dlg
.dialog
= gtk_dialog_new_with_buttons(_("Find"),
453 GTK_WINDOW(main_widgets
.window
), GTK_DIALOG_DESTROY_WITH_PARENT
,
454 GTK_STOCK_CLOSE
, GTK_RESPONSE_CANCEL
, NULL
);
455 vbox
= ui_dialog_vbox_new(GTK_DIALOG(find_dlg
.dialog
));
456 gtk_widget_set_name(find_dlg
.dialog
, "GeanyDialogSearch");
457 gtk_box_set_spacing(GTK_BOX(vbox
), 9);
459 button
= ui_button_new_with_image(GTK_STOCK_GO_BACK
, _("_Previous"));
460 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg
.dialog
), button
,
461 GEANY_RESPONSE_FIND_PREVIOUS
);
462 ui_hookup_widget(find_dlg
.dialog
, button
, "btn_previous");
464 button
= ui_button_new_with_image(GTK_STOCK_GO_FORWARD
, _("_Next"));
465 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg
.dialog
), button
,
466 GEANY_RESPONSE_FIND
);
468 label
= gtk_label_new_with_mnemonic(_("_Search for:"));
469 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
471 entry
= gtk_combo_box_text_new_with_entry();
472 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry
))));
473 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), entry
);
474 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry
))), 50);
475 find_dlg
.entry
= gtk_bin_get_child(GTK_BIN(entry
));
476 ui_hookup_widget(find_dlg
.dialog
, entry
, "entry");
478 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry
)), "activate",
479 G_CALLBACK(on_find_entry_activate
), NULL
);
480 ui_entry_add_activate_backward_signal(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry
))));
481 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry
)), "activate-backward",
482 G_CALLBACK(on_find_entry_activate_backward
), NULL
);
483 g_signal_connect(find_dlg
.dialog
, "response",
484 G_CALLBACK(on_find_dialog_response
), entry
);
485 g_signal_connect(find_dlg
.dialog
, "delete-event",
486 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
488 sbox
= gtk_hbox_new(FALSE
, 6);
489 gtk_box_pack_start(GTK_BOX(sbox
), label
, FALSE
, FALSE
, 0);
490 gtk_box_pack_start(GTK_BOX(sbox
), entry
, TRUE
, TRUE
, 0);
491 gtk_box_pack_start(GTK_BOX(vbox
), sbox
, TRUE
, FALSE
, 0);
493 gtk_container_add(GTK_CONTAINER(vbox
),
494 add_find_checkboxes(GTK_DIALOG(find_dlg
.dialog
)));
496 /* Now add the multiple match options */
497 exp
= gtk_expander_new_with_mnemonic(_("_Find All"));
498 gtk_expander_set_expanded(GTK_EXPANDER(exp
), find_dlg
.all_expanded
);
499 g_signal_connect_after(exp
, "activate",
500 G_CALLBACK(on_expander_activated
), &find_dlg
.all_expanded
);
502 bbox
= gtk_hbutton_box_new();
504 button
= gtk_button_new_with_mnemonic(_("_Mark"));
505 gtk_widget_set_tooltip_text(button
,
506 _("Mark all matches in the current document"));
507 gtk_container_add(GTK_CONTAINER(bbox
), button
);
508 g_signal_connect(button
, "clicked", G_CALLBACK(send_find_dialog_response
),
509 GINT_TO_POINTER(GEANY_RESPONSE_MARK
));
511 button
= gtk_button_new_with_mnemonic(_("In Sessi_on"));
512 gtk_container_add(GTK_CONTAINER(bbox
), button
);
513 g_signal_connect(button
, "clicked", G_CALLBACK(send_find_dialog_response
),
514 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_SESSION
));
516 button
= gtk_button_new_with_mnemonic(_("_In Document"));
517 gtk_container_add(GTK_CONTAINER(bbox
), button
);
518 g_signal_connect(button
, "clicked", G_CALLBACK(send_find_dialog_response
),
519 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_FILE
));
521 /* close window checkbox */
522 check_close
= gtk_check_button_new_with_mnemonic(_("Close _dialog"));
523 ui_hookup_widget(find_dlg
.dialog
, check_close
, "check_close");
524 gtk_button_set_focus_on_click(GTK_BUTTON(check_close
), FALSE
);
525 gtk_widget_set_tooltip_text(check_close
,
526 _("Disable this option to keep the dialog open"));
527 gtk_container_add(GTK_CONTAINER(bbox
), check_close
);
528 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox
), check_close
, TRUE
);
530 ui_hbutton_box_copy_layout(
531 GTK_BUTTON_BOX(gtk_dialog_get_action_area(GTK_DIALOG(find_dlg
.dialog
))),
532 GTK_BUTTON_BOX(bbox
));
533 gtk_container_add(GTK_CONTAINER(exp
), bbox
);
534 gtk_container_add(GTK_CONTAINER(vbox
), exp
);
538 static void set_dialog_position(GtkWidget
*dialog
, gint
*position
)
540 if (position
[0] >= 0)
541 gtk_window_move(GTK_WINDOW(dialog
), position
[0], position
[1]);
545 void search_show_find_dialog(void)
547 GeanyDocument
*doc
= document_get_current();
550 g_return_if_fail(doc
!= NULL
);
552 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
554 if (find_dlg
.dialog
== NULL
)
556 create_find_dialog();
557 stash_group_display(find_prefs
, find_dlg
.dialog
);
559 gtk_entry_set_text(GTK_ENTRY(find_dlg
.entry
), sel
);
561 set_dialog_position(find_dlg
.dialog
, find_dlg
.position
);
562 gtk_widget_show_all(find_dlg
.dialog
);
566 /* only set selection if the dialog is not already visible */
567 if (! gtk_widget_get_visible(find_dlg
.dialog
) && sel
)
568 gtk_entry_set_text(GTK_ENTRY(find_dlg
.entry
), sel
);
569 gtk_widget_grab_focus(find_dlg
.entry
);
570 set_dialog_position(find_dlg
.dialog
, find_dlg
.position
);
571 gtk_widget_show(find_dlg
.dialog
);
572 if (sel
!= NULL
) /* when we have a selection, reset the entry widget's background colour */
573 ui_set_search_entry_background(find_dlg
.entry
, TRUE
);
574 /* bring the dialog back in the foreground in case it is already open but the focus is away */
575 gtk_window_present(GTK_WINDOW(find_dlg
.dialog
));
582 static void send_replace_dialog_response(GtkButton
*button
, gpointer user_data
)
584 gtk_dialog_response(GTK_DIALOG(replace_dlg
.dialog
), GPOINTER_TO_INT(user_data
));
589 on_widget_key_pressed_set_focus(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
591 if (event
->keyval
== GDK_Tab
)
593 gtk_widget_grab_focus(GTK_WIDGET(user_data
));
600 static void create_replace_dialog(void)
602 GtkWidget
*label_find
, *label_replace
, *entry_find
, *entry_replace
,
603 *check_close
, *button
, *rbox
, *fbox
, *vbox
, *exp
, *bbox
;
604 GtkSizeGroup
*label_size
;
606 replace_dlg
.dialog
= gtk_dialog_new_with_buttons(_("Replace"),
607 GTK_WINDOW(main_widgets
.window
), GTK_DIALOG_DESTROY_WITH_PARENT
,
608 GTK_STOCK_CLOSE
, GTK_RESPONSE_CANCEL
, NULL
);
609 vbox
= ui_dialog_vbox_new(GTK_DIALOG(replace_dlg
.dialog
));
610 gtk_box_set_spacing(GTK_BOX(vbox
), 9);
611 gtk_widget_set_name(replace_dlg
.dialog
, "GeanyDialogSearch");
613 button
= gtk_button_new_from_stock(GTK_STOCK_FIND
);
614 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg
.dialog
), button
,
615 GEANY_RESPONSE_FIND
);
616 button
= gtk_button_new_with_mnemonic(_("_Replace"));
617 gtk_button_set_image(GTK_BUTTON(button
),
618 gtk_image_new_from_stock(GTK_STOCK_FIND_AND_REPLACE
, GTK_ICON_SIZE_BUTTON
));
619 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg
.dialog
), button
,
620 GEANY_RESPONSE_REPLACE
);
621 button
= gtk_button_new_with_mnemonic(_("Replace & Fi_nd"));
622 gtk_button_set_image(GTK_BUTTON(button
),
623 gtk_image_new_from_stock(GTK_STOCK_FIND_AND_REPLACE
, GTK_ICON_SIZE_BUTTON
));
624 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg
.dialog
), button
,
625 GEANY_RESPONSE_REPLACE_AND_FIND
);
627 label_find
= gtk_label_new_with_mnemonic(_("_Search for:"));
628 gtk_misc_set_alignment(GTK_MISC(label_find
), 0, 0.5);
630 label_replace
= gtk_label_new_with_mnemonic(_("Replace wit_h:"));
631 gtk_misc_set_alignment(GTK_MISC(label_replace
), 0, 0.5);
633 entry_find
= gtk_combo_box_text_new_with_entry();
634 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find
))));
635 gtk_label_set_mnemonic_widget(GTK_LABEL(label_find
), entry_find
);
636 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find
))), 50);
637 ui_hookup_widget(replace_dlg
.dialog
, entry_find
, "entry_find");
638 replace_dlg
.find_entry
= gtk_bin_get_child(GTK_BIN(entry_find
));
640 entry_replace
= gtk_combo_box_text_new_with_entry();
641 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace
))));
642 gtk_label_set_mnemonic_widget(GTK_LABEL(label_replace
), entry_replace
);
643 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace
))), 50);
644 ui_hookup_widget(replace_dlg
.dialog
, entry_replace
, "entry_replace");
645 replace_dlg
.replace_entry
= gtk_bin_get_child(GTK_BIN(entry_replace
));
647 /* tab from find to the replace entry */
648 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_find
)),
649 "key-press-event", G_CALLBACK(on_widget_key_pressed_set_focus
),
650 gtk_bin_get_child(GTK_BIN(entry_replace
)));
651 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_replace
)), "activate",
652 G_CALLBACK(on_replace_entry_activate
), NULL
);
653 g_signal_connect(replace_dlg
.dialog
, "response",
654 G_CALLBACK(on_replace_dialog_response
), entry_replace
);
655 g_signal_connect(replace_dlg
.dialog
, "delete-event",
656 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
658 fbox
= gtk_hbox_new(FALSE
, 6);
659 gtk_box_pack_start(GTK_BOX(fbox
), label_find
, FALSE
, FALSE
, 0);
660 gtk_box_pack_start(GTK_BOX(fbox
), entry_find
, TRUE
, TRUE
, 0);
662 rbox
= gtk_hbox_new(FALSE
, 6);
663 gtk_box_pack_start(GTK_BOX(rbox
), label_replace
, FALSE
, FALSE
, 0);
664 gtk_box_pack_start(GTK_BOX(rbox
), entry_replace
, TRUE
, TRUE
, 0);
666 label_size
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
667 gtk_size_group_add_widget(label_size
, label_find
);
668 gtk_size_group_add_widget(label_size
, label_replace
);
669 g_object_unref(G_OBJECT(label_size
)); /* auto destroy the size group */
671 gtk_box_pack_start(GTK_BOX(vbox
), fbox
, TRUE
, FALSE
, 0);
672 gtk_box_pack_start(GTK_BOX(vbox
), rbox
, TRUE
, FALSE
, 0);
673 gtk_container_add(GTK_CONTAINER(vbox
),
674 add_find_checkboxes(GTK_DIALOG(replace_dlg
.dialog
)));
676 /* Now add the multiple replace options */
677 exp
= gtk_expander_new_with_mnemonic(_("Re_place All"));
678 gtk_expander_set_expanded(GTK_EXPANDER(exp
), replace_dlg
.all_expanded
);
679 g_signal_connect_after(exp
, "activate",
680 G_CALLBACK(on_expander_activated
), &replace_dlg
.all_expanded
);
682 bbox
= gtk_hbutton_box_new();
684 button
= gtk_button_new_with_mnemonic(_("In Sessi_on"));
685 gtk_container_add(GTK_CONTAINER(bbox
), button
);
686 g_signal_connect(button
, "clicked", G_CALLBACK(send_replace_dialog_response
),
687 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SESSION
));
689 button
= gtk_button_new_with_mnemonic(_("_In Document"));
690 gtk_container_add(GTK_CONTAINER(bbox
), button
);
691 g_signal_connect(button
, "clicked", G_CALLBACK(send_replace_dialog_response
),
692 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_FILE
));
694 button
= gtk_button_new_with_mnemonic(_("In Se_lection"));
695 gtk_widget_set_tooltip_text(button
,
696 _("Replace all matches found in the currently selected text"));
697 gtk_container_add(GTK_CONTAINER(bbox
), button
);
698 g_signal_connect(button
, "clicked", G_CALLBACK(send_replace_dialog_response
),
699 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SEL
));
701 /* close window checkbox */
702 check_close
= gtk_check_button_new_with_mnemonic(_("Close _dialog"));
703 ui_hookup_widget(replace_dlg
.dialog
, check_close
, "check_close");
704 gtk_button_set_focus_on_click(GTK_BUTTON(check_close
), FALSE
);
705 gtk_widget_set_tooltip_text(check_close
,
706 _("Disable this option to keep the dialog open"));
707 gtk_container_add(GTK_CONTAINER(bbox
), check_close
);
708 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox
), check_close
, TRUE
);
710 ui_hbutton_box_copy_layout(
711 GTK_BUTTON_BOX(gtk_dialog_get_action_area(GTK_DIALOG(replace_dlg
.dialog
))),
712 GTK_BUTTON_BOX(bbox
));
713 gtk_container_add(GTK_CONTAINER(exp
), bbox
);
714 gtk_container_add(GTK_CONTAINER(vbox
), exp
);
718 void search_show_replace_dialog(void)
720 GeanyDocument
*doc
= document_get_current();
726 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
728 if (replace_dlg
.dialog
== NULL
)
730 create_replace_dialog();
731 stash_group_display(replace_prefs
, replace_dlg
.dialog
);
733 gtk_entry_set_text(GTK_ENTRY(replace_dlg
.find_entry
), sel
);
735 set_dialog_position(replace_dlg
.dialog
, replace_dlg
.position
);
736 gtk_widget_show_all(replace_dlg
.dialog
);
740 /* only set selection if the dialog is not already visible */
741 if (! gtk_widget_get_visible(replace_dlg
.dialog
) && sel
)
742 gtk_entry_set_text(GTK_ENTRY(replace_dlg
.find_entry
), sel
);
743 if (sel
!= NULL
) /* when we have a selection, reset the entry widget's background colour */
744 ui_set_search_entry_background(replace_dlg
.find_entry
, TRUE
);
745 gtk_widget_grab_focus(replace_dlg
.find_entry
);
746 set_dialog_position(replace_dlg
.dialog
, replace_dlg
.position
);
747 gtk_widget_show(replace_dlg
.dialog
);
748 /* bring the dialog back in the foreground in case it is already open but the focus is away */
749 gtk_window_present(GTK_WINDOW(replace_dlg
.dialog
));
756 static void on_widget_toggled_set_sensitive(GtkToggleButton
*togglebutton
, gpointer user_data
)
758 /* disable extra option entry when checkbutton not checked */
759 gtk_widget_set_sensitive(GTK_WIDGET(user_data
),
760 gtk_toggle_button_get_active(togglebutton
));
764 static void update_file_patterns(GtkWidget
*mode_combo
, GtkWidget
*fcombo
)
769 entry
= gtk_bin_get_child(GTK_BIN(fcombo
));
771 selection
= gtk_combo_box_get_active(GTK_COMBO_BOX(mode_combo
));
773 if (selection
== FILES_MODE_ALL
)
775 gtk_entry_set_text(GTK_ENTRY(entry
), "");
776 gtk_widget_set_sensitive(fcombo
, FALSE
);
778 else if (selection
== FILES_MODE_CUSTOM
)
780 gtk_widget_set_sensitive(fcombo
, TRUE
);
782 else if (selection
== FILES_MODE_PROJECT
)
784 if (app
->project
&& !EMPTY(app
->project
->file_patterns
))
788 patterns
= g_strjoinv(" ", app
->project
->file_patterns
);
789 gtk_entry_set_text(GTK_ENTRY(entry
), patterns
);
794 gtk_entry_set_text(GTK_ENTRY(entry
), "");
797 gtk_widget_set_sensitive(fcombo
, FALSE
);
802 /* creates the combo to choose which files include in the search */
803 static GtkWidget
*create_fif_file_mode_combo(void)
806 GtkCellRenderer
*renderer
;
811 store
= gtk_list_store_new(2, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
812 gtk_list_store_append(store
, &iter
);
813 gtk_list_store_set(store
, &iter
, 0, _("all"), 1, TRUE
, -1);
814 gtk_list_store_append(store
, &iter
);
815 gtk_list_store_set(store
, &iter
, 0, _("project"), 1, app
->project
!= NULL
, -1);
816 gtk_list_store_append(store
, &iter
);
817 gtk_list_store_set(store
, &iter
, 0, _("custom"), 1, TRUE
, -1);
819 combo
= gtk_combo_box_new_with_model(GTK_TREE_MODEL(store
));
820 g_object_unref(store
);
821 gtk_widget_set_tooltip_text(combo
, _("All: search all files in the directory\n"
822 "Project: use file patterns defined in the project settings\n"
823 "Custom: specify file patterns manually"));
825 renderer
= gtk_cell_renderer_text_new();
826 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo
), renderer
, TRUE
);
827 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo
), renderer
, "text", 0, "sensitive", 1, NULL
);
833 /* updates the sensitivity of the project combo item */
834 static void update_fif_file_mode_combo(void)
836 GtkTreeModel
*model
= gtk_combo_box_get_model(GTK_COMBO_BOX(fif_dlg
.files_mode_combo
));
839 /* "1" refers to the second list entry, project */
840 if (gtk_tree_model_get_iter_from_string(model
, &iter
, "1"))
841 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
, 1, app
->project
!= NULL
, -1);
845 static void create_fif_dialog(void)
847 GtkWidget
*dir_combo
, *combo
, *fcombo
, *e_combo
, *entry
;
848 GtkWidget
*label
, *label1
, *label2
, *label3
, *checkbox1
, *checkbox2
, *check_wholeword
,
849 *check_recursive
, *check_extra
, *entry_extra
, *check_regexp
, *combo_files_mode
;
850 GtkWidget
*dbox
, *sbox
, *lbox
, *rbox
, *hbox
, *vbox
, *ebox
;
851 GtkSizeGroup
*size_group
;
852 gchar
*encoding_string
;
855 fif_dlg
.dialog
= gtk_dialog_new_with_buttons(
856 _("Find in Files"), GTK_WINDOW(main_widgets
.window
), GTK_DIALOG_DESTROY_WITH_PARENT
,
857 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
, NULL
);
858 vbox
= ui_dialog_vbox_new(GTK_DIALOG(fif_dlg
.dialog
));
859 gtk_box_set_spacing(GTK_BOX(vbox
), 9);
860 gtk_widget_set_name(fif_dlg
.dialog
, "GeanyDialogSearch");
862 gtk_dialog_add_button(GTK_DIALOG(fif_dlg
.dialog
), GTK_STOCK_FIND
, GTK_RESPONSE_ACCEPT
);
863 gtk_dialog_set_default_response(GTK_DIALOG(fif_dlg
.dialog
),
864 GTK_RESPONSE_ACCEPT
);
866 label
= gtk_label_new_with_mnemonic(_("_Search for:"));
867 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
869 combo
= gtk_combo_box_text_new_with_entry();
870 entry
= gtk_bin_get_child(GTK_BIN(combo
));
871 ui_entry_add_clear_icon(GTK_ENTRY(entry
));
872 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), entry
);
873 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 50);
874 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
875 fif_dlg
.search_combo
= combo
;
877 sbox
= gtk_hbox_new(FALSE
, 6);
878 gtk_box_pack_start(GTK_BOX(sbox
), label
, FALSE
, FALSE
, 0);
879 gtk_box_pack_start(GTK_BOX(sbox
), combo
, TRUE
, TRUE
, 0);
881 /* make labels same width */
882 size_group
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
883 gtk_size_group_add_widget(size_group
, label
);
885 label3
= gtk_label_new_with_mnemonic(_("Fi_les:"));
886 gtk_misc_set_alignment(GTK_MISC(label3
), 0, 0.5);
888 combo_files_mode
= create_fif_file_mode_combo();
889 gtk_label_set_mnemonic_widget(GTK_LABEL(label3
), combo_files_mode
);
890 ui_hookup_widget(fif_dlg
.dialog
, combo_files_mode
, "combo_files_mode");
891 fif_dlg
.files_mode_combo
= combo_files_mode
;
893 fcombo
= gtk_combo_box_text_new_with_entry();
894 entry
= gtk_bin_get_child(GTK_BIN(fcombo
));
895 ui_entry_add_clear_icon(GTK_ENTRY(entry
));
896 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
897 gtk_widget_set_tooltip_text(entry
, _("File patterns, e.g. *.c *.h"));
898 ui_hookup_widget(fif_dlg
.dialog
, entry
, "entry_files");
899 fif_dlg
.files_combo
= fcombo
;
901 /* update the entry when selection is changed */
902 g_signal_connect(combo_files_mode
, "changed", G_CALLBACK(update_file_patterns
), fcombo
);
904 hbox
= gtk_hbox_new(FALSE
, 6);
905 gtk_box_pack_start(GTK_BOX(hbox
), label3
, FALSE
, FALSE
, 0);
906 gtk_box_pack_start(GTK_BOX(hbox
), combo_files_mode
, FALSE
, FALSE
, 0);
907 gtk_box_pack_start(GTK_BOX(hbox
), fcombo
, TRUE
, TRUE
, 0);
909 label1
= gtk_label_new_with_mnemonic(_("_Directory:"));
910 gtk_misc_set_alignment(GTK_MISC(label1
), 0, 0.5);
912 dir_combo
= gtk_combo_box_text_new_with_entry();
913 entry
= gtk_bin_get_child(GTK_BIN(dir_combo
));
914 ui_entry_add_clear_icon(GTK_ENTRY(entry
));
915 gtk_label_set_mnemonic_widget(GTK_LABEL(label1
), entry
);
916 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 50);
917 fif_dlg
.dir_combo
= dir_combo
;
919 /* tab from files to the dir entry */
920 g_signal_connect(gtk_bin_get_child(GTK_BIN(fcombo
)), "key-press-event",
921 G_CALLBACK(on_widget_key_pressed_set_focus
), entry
);
923 dbox
= ui_path_box_new(NULL
, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
,
925 gtk_box_pack_start(GTK_BOX(dbox
), label1
, FALSE
, FALSE
, 0);
927 label2
= gtk_label_new_with_mnemonic(_("E_ncoding:"));
928 gtk_misc_set_alignment(GTK_MISC(label2
), 0, 0.5);
930 e_combo
= gtk_combo_box_text_new();
931 for (i
= 0; i
< GEANY_ENCODINGS_MAX
; i
++)
933 encoding_string
= encodings_to_string(&encodings
[i
]);
934 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(e_combo
), encoding_string
);
935 g_free(encoding_string
);
937 gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(e_combo
), 3);
938 gtk_label_set_mnemonic_widget(GTK_LABEL(label2
), e_combo
);
939 fif_dlg
.encoding_combo
= e_combo
;
941 ebox
= gtk_hbox_new(FALSE
, 6);
942 gtk_box_pack_start(GTK_BOX(ebox
), label2
, FALSE
, FALSE
, 0);
943 gtk_box_pack_start(GTK_BOX(ebox
), e_combo
, TRUE
, TRUE
, 0);
945 gtk_size_group_add_widget(size_group
, label1
);
946 gtk_size_group_add_widget(size_group
, label2
);
947 gtk_size_group_add_widget(size_group
, label3
);
948 g_object_unref(G_OBJECT(size_group
)); /* auto destroy the size group */
950 gtk_box_pack_start(GTK_BOX(vbox
), sbox
, TRUE
, FALSE
, 0);
951 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, TRUE
, FALSE
, 0);
952 gtk_box_pack_start(GTK_BOX(vbox
), dbox
, TRUE
, FALSE
, 0);
953 gtk_box_pack_start(GTK_BOX(vbox
), ebox
, TRUE
, FALSE
, 0);
955 check_regexp
= gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
956 ui_hookup_widget(fif_dlg
.dialog
, check_regexp
, "check_regexp");
957 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp
), FALSE
);
958 gtk_widget_set_tooltip_text(check_regexp
, _("See grep's manual page for more information"));
960 check_recursive
= gtk_check_button_new_with_mnemonic(_("_Recurse in subfolders"));
961 ui_hookup_widget(fif_dlg
.dialog
, check_recursive
, "check_recursive");
962 gtk_button_set_focus_on_click(GTK_BUTTON(check_recursive
), FALSE
);
964 checkbox1
= gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
965 ui_hookup_widget(fif_dlg
.dialog
, checkbox1
, "check_case");
966 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1
), FALSE
);
967 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox1
), TRUE
);
969 check_wholeword
= gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
970 ui_hookup_widget(fif_dlg
.dialog
, check_wholeword
, "check_wholeword");
971 gtk_button_set_focus_on_click(GTK_BUTTON(check_wholeword
), FALSE
);
973 checkbox2
= gtk_check_button_new_with_mnemonic(_("_Invert search results"));
974 ui_hookup_widget(fif_dlg
.dialog
, checkbox2
, "check_invert");
975 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2
), FALSE
);
976 gtk_widget_set_tooltip_text(checkbox2
,
977 _("Invert the sense of matching, to select non-matching lines"));
979 lbox
= gtk_vbox_new(FALSE
, 0);
980 gtk_container_add(GTK_CONTAINER(lbox
), check_regexp
);
981 gtk_container_add(GTK_CONTAINER(lbox
), checkbox2
);
982 gtk_container_add(GTK_CONTAINER(lbox
), check_recursive
);
984 rbox
= gtk_vbox_new(FALSE
, 0);
985 gtk_container_add(GTK_CONTAINER(rbox
), checkbox1
);
986 gtk_container_add(GTK_CONTAINER(rbox
), check_wholeword
);
987 gtk_container_add(GTK_CONTAINER(rbox
), gtk_label_new(NULL
));
989 hbox
= gtk_hbox_new(FALSE
, 6);
990 gtk_container_add(GTK_CONTAINER(hbox
), lbox
);
991 gtk_container_add(GTK_CONTAINER(hbox
), rbox
);
992 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
994 check_extra
= gtk_check_button_new_with_mnemonic(_("E_xtra options:"));
995 ui_hookup_widget(fif_dlg
.dialog
, check_extra
, "check_extra");
996 gtk_button_set_focus_on_click(GTK_BUTTON(check_extra
), FALSE
);
998 entry_extra
= gtk_entry_new();
999 ui_entry_add_clear_icon(GTK_ENTRY(entry_extra
));
1000 gtk_widget_set_sensitive(entry_extra
, FALSE
);
1001 gtk_widget_set_tooltip_text(entry_extra
, _("Other options to pass to Grep"));
1002 ui_hookup_widget(fif_dlg
.dialog
, entry_extra
, "entry_extra");
1004 /* enable entry_extra when check_extra is checked */
1005 g_signal_connect(check_extra
, "toggled",
1006 G_CALLBACK(on_widget_toggled_set_sensitive
), entry_extra
);
1008 hbox
= gtk_hbox_new(FALSE
, 6);
1009 gtk_box_pack_start(GTK_BOX(hbox
), check_extra
, FALSE
, FALSE
, 0);
1010 gtk_box_pack_start(GTK_BOX(hbox
), entry_extra
, TRUE
, TRUE
, 0);
1011 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
1013 g_signal_connect(fif_dlg
.dialog
, "response",
1014 G_CALLBACK(on_find_in_files_dialog_response
), NULL
);
1015 g_signal_connect(fif_dlg
.dialog
, "delete-event",
1016 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
1021 * Shows the Find in Files dialog.
1023 * @param dir The directory to search in (UTF-8 encoding). May be @c NULL
1024 * to determine it the usual way by using the current document's path.
1026 * @since 0.14, plugin API 53
1028 void search_show_find_in_files_dialog(const gchar
*dir
)
1030 search_show_find_in_files_dialog_full(NULL
, dir
);
1034 void search_show_find_in_files_dialog_full(const gchar
*text
, const gchar
*dir
)
1036 GtkWidget
*entry
; /* for child GtkEntry of a GtkComboBoxEntry */
1037 GeanyDocument
*doc
= document_get_current();
1039 gchar
*cur_dir
= NULL
;
1040 GeanyEncodingIndex enc_idx
= GEANY_ENCODING_UTF_8
;
1042 if (fif_dlg
.dialog
== NULL
)
1044 create_fif_dialog();
1045 gtk_widget_show_all(fif_dlg
.dialog
);
1047 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
1049 stash_group_display(fif_prefs
, fif_dlg
.dialog
);
1053 /* only set selection if the dialog is not already visible, or has just been created */
1054 if (doc
&& ! sel
&& ! gtk_widget_get_visible(fif_dlg
.dialog
))
1055 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
1059 entry
= gtk_bin_get_child(GTK_BIN(fif_dlg
.search_combo
));
1061 gtk_entry_set_text(GTK_ENTRY(entry
), text
);
1064 /* add project's base path directory to the dir list, we do this here once
1065 * (in create_fif_dialog() it would fail if a project is opened after dialog creation) */
1066 if (app
->project
!= NULL
&& !EMPTY(app
->project
->base_path
))
1068 ui_combo_box_prepend_text_once(GTK_COMBO_BOX_TEXT(fif_dlg
.dir_combo
),
1069 app
->project
->base_path
);
1072 entry
= gtk_bin_get_child(GTK_BIN(fif_dlg
.dir_combo
));
1074 cur_dir
= g_strdup(dir
); /* custom directory argument passed */
1077 if (search_prefs
.use_current_file_dir
)
1079 static gchar
*last_cur_dir
= NULL
;
1080 static GeanyDocument
*last_doc
= NULL
;
1082 /* Only set the directory entry once for the current document */
1083 cur_dir
= utils_get_current_file_dir_utf8();
1084 if (doc
== last_doc
&& cur_dir
&& utils_str_equal(cur_dir
, last_cur_dir
))
1086 /* in case the user now wants the current directory, add it to history */
1087 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(fif_dlg
.dir_combo
), cur_dir
, 0);
1088 SETPTR(cur_dir
, NULL
);
1091 SETPTR(last_cur_dir
, g_strdup(cur_dir
));
1095 if (!cur_dir
&& EMPTY(gtk_entry_get_text(GTK_ENTRY(entry
))))
1097 /* use default_open_path if no directory could be determined
1098 * (e.g. when no files are open) */
1100 cur_dir
= g_strdup(utils_get_default_dir_utf8());
1102 cur_dir
= g_get_current_dir();
1107 gtk_entry_set_text(GTK_ENTRY(entry
), cur_dir
);
1111 update_fif_file_mode_combo();
1112 update_file_patterns(fif_dlg
.files_mode_combo
, fif_dlg
.files_combo
);
1114 /* set the encoding of the current file */
1116 enc_idx
= encodings_get_idx_from_charset(doc
->encoding
);
1117 gtk_combo_box_set_active(GTK_COMBO_BOX(fif_dlg
.encoding_combo
), enc_idx
);
1119 /* put the focus to the directory entry if it is empty */
1120 if (utils_str_equal(gtk_entry_get_text(GTK_ENTRY(entry
)), ""))
1121 gtk_widget_grab_focus(fif_dlg
.dir_combo
);
1123 gtk_widget_grab_focus(fif_dlg
.search_combo
);
1125 /* set dialog window position */
1126 set_dialog_position(fif_dlg
.dialog
, fif_dlg
.position
);
1128 gtk_widget_show(fif_dlg
.dialog
);
1129 /* bring the dialog back in the foreground in case it is already open but the focus is away */
1130 gtk_window_present(GTK_WINDOW(fif_dlg
.dialog
));
1135 on_find_replace_checkbutton_toggled(GtkToggleButton
*togglebutton
, gpointer user_data
)
1137 GtkWidget
*dialog
= GTK_WIDGET(user_data
);
1138 GtkToggleButton
*chk_regexp
= GTK_TOGGLE_BUTTON(
1139 ui_lookup_widget(dialog
, "check_regexp"));
1141 if (togglebutton
== chk_regexp
)
1143 gboolean regex_set
= gtk_toggle_button_get_active(chk_regexp
);
1144 GtkWidget
*check_word
= ui_lookup_widget(dialog
, "check_word");
1145 GtkWidget
*check_wordstart
= ui_lookup_widget(dialog
, "check_wordstart");
1146 GtkWidget
*check_escape
= ui_lookup_widget(dialog
, "check_escape");
1147 gboolean replace
= (dialog
!= find_dlg
.dialog
);
1148 const char *back_button
[2] = { "btn_previous" , "check_back" };
1150 /* hide options that don't apply to regex searches */
1151 gtk_widget_set_sensitive(check_escape
, ! regex_set
);
1152 gtk_widget_set_sensitive(ui_lookup_widget(dialog
, back_button
[replace
]), ! regex_set
);
1153 gtk_widget_set_sensitive(check_word
, ! regex_set
);
1154 gtk_widget_set_sensitive(check_wordstart
, ! regex_set
);
1159 static GeanyMatchInfo
*match_info_new(gint flags
, gint start
, gint end
)
1161 GeanyMatchInfo
*info
= g_slice_alloc(sizeof *info
);
1163 info
->flags
= flags
;
1164 info
->start
= start
;
1166 info
->match_text
= NULL
;
1171 void geany_match_info_free(GeanyMatchInfo
*info
)
1173 g_free(info
->match_text
);
1174 g_slice_free1(sizeof *info
, info
);
1178 /* find all in the given range.
1179 * Returns a list of allocated GeanyMatchInfo, should be freed using:
1181 * foreach_slist(node, matches)
1182 * geany_match_info_free(node->data);
1183 * g_slist_free(matches); */
1184 static GSList
*find_range(ScintillaObject
*sci
, gint flags
, struct Sci_TextToFind
*ttf
)
1186 GSList
*matches
= NULL
;
1187 GeanyMatchInfo
*info
;
1189 g_return_val_if_fail(sci
!= NULL
&& ttf
->lpstrText
!= NULL
, NULL
);
1190 if (! *ttf
->lpstrText
)
1193 while (search_find_text(sci
, flags
, ttf
, &info
) != -1)
1195 if (ttf
->chrgText
.cpMax
> ttf
->chrg
.cpMax
)
1197 /* found text is partially out of range */
1198 geany_match_info_free(info
);
1202 matches
= g_slist_prepend(matches
, info
);
1203 ttf
->chrg
.cpMin
= ttf
->chrgText
.cpMax
;
1205 /* avoid rematching with empty matches like "(?=[a-z])" or "^$".
1206 * note we cannot assume a match will always be empty or not and then break out, since
1207 * matches like "a?(?=b)" will sometimes be empty and sometimes not */
1208 if (ttf
->chrgText
.cpMax
== ttf
->chrgText
.cpMin
)
1212 return g_slist_reverse(matches
);
1216 /* Clears markers if text is null/empty.
1217 * @return Number of matches marked. */
1218 gint
search_mark_all(GeanyDocument
*doc
, const gchar
*search_text
, gint flags
)
1221 struct Sci_TextToFind ttf
;
1222 GSList
*match
, *matches
;
1224 g_return_val_if_fail(doc
!= NULL
, 0);
1226 /* clear previous search indicators */
1227 editor_indicator_clear(doc
->editor
, GEANY_INDICATOR_SEARCH
);
1229 if (G_UNLIKELY(EMPTY(search_text
)))
1233 ttf
.chrg
.cpMax
= sci_get_length(doc
->editor
->sci
);
1234 ttf
.lpstrText
= (gchar
*)search_text
;
1236 matches
= find_range(doc
->editor
->sci
, flags
, &ttf
);
1237 foreach_slist (match
, matches
)
1239 GeanyMatchInfo
*info
= match
->data
;
1241 if (info
->end
!= info
->start
)
1242 editor_indicator_set_on_range(doc
->editor
, GEANY_INDICATOR_SEARCH
, info
->start
, info
->end
);
1245 geany_match_info_free(info
);
1247 g_slist_free(matches
);
1254 on_find_entry_activate(GtkEntry
*entry
, gpointer user_data
)
1256 on_find_dialog_response(NULL
, GEANY_RESPONSE_FIND
,
1257 ui_lookup_widget(GTK_WIDGET(entry
), "entry"));
1262 on_find_entry_activate_backward(GtkEntry
*entry
, gpointer user_data
)
1264 /* can't search backwards with a regexp */
1265 if (search_data
.flags
& SCFIND_REGEXP
)
1268 on_find_dialog_response(NULL
, GEANY_RESPONSE_FIND_PREVIOUS
,
1269 ui_lookup_widget(GTK_WIDGET(entry
), "entry"));
1273 static gboolean
int_search_flags(gint match_case
, gint whole_word
, gint regexp
, gint word_start
)
1275 return (match_case
? SCFIND_MATCHCASE
: 0) |
1276 (regexp
? SCFIND_REGEXP
| SCFIND_POSIX
: 0) |
1277 (whole_word
? SCFIND_WHOLEWORD
: 0) |
1278 /* SCFIND_WORDSTART overrides SCFIND_WHOLEWORD, but we want the opposite */
1279 (word_start
&& !whole_word
? SCFIND_WORDSTART
: 0);
1284 on_find_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
)
1286 gtk_window_get_position(GTK_WINDOW(find_dlg
.dialog
),
1287 &find_dlg
.position
[0], &find_dlg
.position
[1]);
1289 stash_group_update(find_prefs
, find_dlg
.dialog
);
1291 if (response
== GTK_RESPONSE_CANCEL
|| response
== GTK_RESPONSE_DELETE_EVENT
)
1292 gtk_widget_hide(find_dlg
.dialog
);
1295 GeanyDocument
*doc
= document_get_current();
1296 gboolean check_close
= settings
.find_close_dialog
;
1301 search_data
.backwards
= FALSE
;
1302 search_data
.search_bar
= FALSE
;
1304 g_free(search_data
.text
);
1305 g_free(search_data
.original_text
);
1306 search_data
.text
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(user_data
)))));
1307 search_data
.original_text
= g_strdup(search_data
.text
);
1308 search_data
.flags
= int_search_flags(settings
.find_case_sensitive
,
1309 settings
.find_match_whole_word
, settings
.find_regexp
, settings
.find_match_word_start
);
1311 if (EMPTY(search_data
.text
))
1315 gtk_widget_grab_focus(find_dlg
.entry
);
1318 if (search_data
.flags
& SCFIND_REGEXP
)
1320 GRegex
*regex
= compile_regex(search_data
.text
, search_data
.flags
);
1324 g_regex_unref(regex
);
1326 else if (settings
.find_escape_sequences
)
1328 if (! utils_str_replace_escape(search_data
.text
, FALSE
))
1331 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(user_data
), search_data
.original_text
, 0);
1335 case GEANY_RESPONSE_FIND
:
1336 case GEANY_RESPONSE_FIND_PREVIOUS
:
1338 gint result
= document_find_text(doc
, search_data
.text
, search_data
.original_text
, search_data
.flags
,
1339 (response
== GEANY_RESPONSE_FIND_PREVIOUS
), NULL
, TRUE
, GTK_WIDGET(find_dlg
.dialog
));
1340 ui_set_search_entry_background(find_dlg
.entry
, (result
> -1));
1341 check_close
= search_prefs
.hide_find_dialog
;
1344 case GEANY_RESPONSE_FIND_IN_FILE
:
1345 search_find_usage(search_data
.text
, search_data
.original_text
, search_data
.flags
, FALSE
);
1348 case GEANY_RESPONSE_FIND_IN_SESSION
:
1349 search_find_usage(search_data
.text
, search_data
.original_text
, search_data
.flags
, TRUE
);
1352 case GEANY_RESPONSE_MARK
:
1354 gint count
= search_mark_all(doc
, search_data
.text
, search_data
.flags
);
1357 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), search_data
.original_text
);
1359 ui_set_statusbar(FALSE
,
1360 ngettext("Found %d match for \"%s\".",
1361 "Found %d matches for \"%s\".", count
),
1362 count
, search_data
.original_text
);
1367 gtk_widget_hide(find_dlg
.dialog
);
1373 on_replace_entry_activate(GtkEntry
*entry
, gpointer user_data
)
1375 on_replace_dialog_response(NULL
, GEANY_RESPONSE_REPLACE
, NULL
);
1379 static void replace_in_session(GeanyDocument
*doc
,
1380 gint search_flags_re
, gboolean search_replace_escape_re
,
1381 const gchar
*find
, const gchar
*replace
,
1382 const gchar
*original_find
, const gchar
*original_replace
)
1384 guint n
, page_count
, rep_count
= 0, file_count
= 0;
1386 /* replace in all documents following notebook tab order */
1387 page_count
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets
.notebook
));
1388 for (n
= 0; n
< page_count
; n
++)
1390 GeanyDocument
*tmp_doc
= document_get_from_page(n
);
1393 reps
= document_replace_all(tmp_doc
, find
, replace
, original_find
, original_replace
, search_flags_re
);
1398 if (file_count
== 0)
1401 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), original_find
);
1404 /* if only one file was changed, don't override that document's status message
1405 * so we don't have to translate 4 messages for ngettext */
1407 ui_set_statusbar(FALSE
, _("Replaced %u matches in %u documents."),
1408 rep_count
, file_count
);
1410 /* show which docs had replacements: */
1411 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow
.notebook
), MSG_STATUS
);
1413 ui_save_buttons_toggle(doc
->changed
); /* update save all */
1418 on_replace_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
)
1420 GeanyDocument
*doc
= document_get_current();
1421 gint search_flags_re
;
1422 gboolean search_backwards_re
, search_replace_escape_re
;
1423 gchar
*find
, *replace
, *original_find
= NULL
, *original_replace
= NULL
;
1425 gtk_window_get_position(GTK_WINDOW(replace_dlg
.dialog
),
1426 &replace_dlg
.position
[0], &replace_dlg
.position
[1]);
1428 stash_group_update(replace_prefs
, replace_dlg
.dialog
);
1430 if (response
== GTK_RESPONSE_CANCEL
|| response
== GTK_RESPONSE_DELETE_EVENT
)
1432 gtk_widget_hide(replace_dlg
.dialog
);
1436 search_backwards_re
= settings
.replace_search_backwards
;
1437 search_replace_escape_re
= settings
.replace_escape_sequences
;
1438 find
= g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg
.find_entry
)));
1439 replace
= g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg
.replace_entry
)));
1441 search_flags_re
= int_search_flags(settings
.replace_case_sensitive
,
1442 settings
.replace_match_whole_word
, settings
.replace_regexp
,
1443 settings
.replace_match_word_start
);
1445 if ((response
!= GEANY_RESPONSE_FIND
) && (search_flags_re
& SCFIND_MATCHCASE
)
1446 && (strcmp(find
, replace
) == 0))
1449 original_find
= g_strdup(find
);
1450 original_replace
= g_strdup(replace
);
1452 if (search_flags_re
& SCFIND_REGEXP
)
1454 GRegex
*regex
= compile_regex(find
, search_flags_re
);
1456 g_regex_unref(regex
);
1457 /* find escapes will be handled by GRegex */
1458 if (!regex
|| !utils_str_replace_escape(replace
, TRUE
))
1461 else if (search_replace_escape_re
)
1463 if (! utils_str_replace_escape(find
, FALSE
) ||
1464 ! utils_str_replace_escape(replace
, FALSE
))
1468 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(
1469 gtk_widget_get_parent(replace_dlg
.find_entry
)), original_find
, 0);
1470 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(
1471 gtk_widget_get_parent(replace_dlg
.replace_entry
)), original_replace
, 0);
1475 case GEANY_RESPONSE_REPLACE_AND_FIND
:
1477 gint rep
= document_replace_text(doc
, find
, original_find
, replace
, search_flags_re
,
1478 search_backwards_re
);
1480 document_find_text(doc
, find
, original_find
, search_flags_re
, search_backwards_re
,
1484 case GEANY_RESPONSE_REPLACE
:
1485 document_replace_text(doc
, find
, original_find
, replace
, search_flags_re
,
1486 search_backwards_re
);
1489 case GEANY_RESPONSE_FIND
:
1491 gint result
= document_find_text(doc
, find
, original_find
, search_flags_re
,
1492 search_backwards_re
, NULL
, TRUE
, GTK_WIDGET(dialog
));
1493 ui_set_search_entry_background(replace_dlg
.find_entry
, (result
> -1));
1496 case GEANY_RESPONSE_REPLACE_IN_FILE
:
1497 if (! document_replace_all(doc
, find
, replace
, original_find
, original_replace
, search_flags_re
))
1501 case GEANY_RESPONSE_REPLACE_IN_SESSION
:
1502 replace_in_session(doc
, search_flags_re
, search_replace_escape_re
, find
, replace
, original_find
, original_replace
);
1505 case GEANY_RESPONSE_REPLACE_IN_SEL
:
1506 document_replace_sel(doc
, find
, replace
, original_find
, original_replace
, search_flags_re
);
1511 case GEANY_RESPONSE_REPLACE_IN_SEL
:
1512 case GEANY_RESPONSE_REPLACE_IN_FILE
:
1513 case GEANY_RESPONSE_REPLACE_IN_SESSION
:
1514 if (settings
.replace_close_dialog
)
1515 gtk_widget_hide(replace_dlg
.dialog
);
1519 g_free(original_find
);
1520 g_free(original_replace
);
1525 gtk_widget_grab_focus(replace_dlg
.find_entry
);
1528 g_free(original_find
);
1529 g_free(original_replace
);
1533 static GString
*get_grep_options(void)
1535 GString
*gstr
= g_string_new("-nHI"); /* line numbers, filenames, ignore binaries */
1537 if (settings
.fif_invert_results
)
1538 g_string_append_c(gstr
, 'v');
1539 if (! settings
.fif_case_sensitive
)
1540 g_string_append_c(gstr
, 'i');
1541 if (settings
.fif_match_whole_word
)
1542 g_string_append_c(gstr
, 'w');
1543 if (settings
.fif_recursive
)
1544 g_string_append_c(gstr
, 'r');
1546 if (!settings
.fif_regexp
)
1547 g_string_append_c(gstr
, 'F');
1549 g_string_append_c(gstr
, 'E');
1551 if (settings
.fif_use_extra_options
)
1553 g_strstrip(settings
.fif_extra_options
);
1555 if (*settings
.fif_extra_options
!= 0)
1557 g_string_append_c(gstr
, ' ');
1558 g_string_append(gstr
, settings
.fif_extra_options
);
1561 g_strstrip(settings
.fif_files
);
1562 if (settings
.fif_files_mode
!= FILES_MODE_ALL
&& *settings
.fif_files
)
1566 /* put --include= before each pattern */
1567 tmp
= g_string_new(settings
.fif_files
);
1568 do {} while (utils_string_replace_all(tmp
, " ", " "));
1569 g_string_prepend_c(tmp
, ' ');
1570 utils_string_replace_all(tmp
, " ", " --include=");
1571 g_string_append(gstr
, tmp
->str
);
1572 g_string_free(tmp
, TRUE
);
1579 on_find_in_files_dialog_response(GtkDialog
*dialog
, gint response
,
1580 G_GNUC_UNUSED gpointer user_data
)
1582 gtk_window_get_position(GTK_WINDOW(fif_dlg
.dialog
), &fif_dlg
.position
[0], &fif_dlg
.position
[1]);
1584 stash_group_update(fif_prefs
, fif_dlg
.dialog
);
1586 if (response
== GTK_RESPONSE_ACCEPT
)
1588 GtkWidget
*search_combo
= fif_dlg
.search_combo
;
1589 const gchar
*search_text
=
1590 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(search_combo
))));
1591 GtkWidget
*dir_combo
= fif_dlg
.dir_combo
;
1592 const gchar
*utf8_dir
=
1593 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(dir_combo
))));
1594 GeanyEncodingIndex enc_idx
= gtk_combo_box_get_active(
1595 GTK_COMBO_BOX(fif_dlg
.encoding_combo
));
1597 if (G_UNLIKELY(EMPTY(utf8_dir
)))
1598 ui_set_statusbar(FALSE
, _("Invalid directory for find in files."));
1599 else if (!EMPTY(search_text
))
1602 GString
*opts
= get_grep_options();
1603 const gchar
*enc
= (enc_idx
== GEANY_ENCODING_UTF_8
) ? NULL
:
1604 encodings_get_charset_from_index(enc_idx
);
1606 locale_dir
= utils_get_locale_from_utf8(utf8_dir
);
1608 if (search_find_in_files(search_text
, locale_dir
, opts
->str
, enc
))
1610 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(search_combo
), search_text
, 0);
1611 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(fif_dlg
.files_combo
), NULL
, 0);
1612 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(dir_combo
), utf8_dir
, 0);
1613 gtk_widget_hide(fif_dlg
.dialog
);
1616 g_string_free(opts
, TRUE
);
1619 ui_set_statusbar(FALSE
, _("No text to find."));
1622 gtk_widget_hide(fif_dlg
.dialog
);
1627 search_find_in_files(const gchar
*utf8_search_text
, const gchar
*dir
, const gchar
*opts
,
1630 gchar
**argv_prefix
, **argv
, **opts_argv
;
1631 gchar
*command_grep
;
1632 gchar
*search_text
= NULL
;
1633 gint opts_argv_len
, i
;
1637 GError
*error
= NULL
;
1638 gboolean ret
= FALSE
;
1639 gssize utf8_text_len
;
1641 if (EMPTY(utf8_search_text
) || ! dir
) return TRUE
;
1643 command_grep
= g_find_program_in_path(tool_prefs
.grep_cmd
);
1644 if (command_grep
== NULL
)
1646 ui_set_statusbar(TRUE
, _("Cannot execute grep tool '%s';"
1647 " check the path setting in Preferences."), tool_prefs
.grep_cmd
);
1651 if (! g_shell_parse_argv(opts
, &opts_argv_len
, &opts_argv
, &error
))
1653 ui_set_statusbar(TRUE
, _("Cannot parse extra options: %s"), error
->message
);
1654 g_error_free(error
);
1655 g_free(command_grep
);
1659 /* convert the search text in the preferred encoding (if the text is not valid UTF-8. assume
1660 * it is already in the preferred encoding) */
1661 utf8_text_len
= strlen(utf8_search_text
);
1662 if (enc
!= NULL
&& g_utf8_validate(utf8_search_text
, utf8_text_len
, NULL
))
1664 search_text
= g_convert(utf8_search_text
, utf8_text_len
, enc
, "UTF-8", NULL
, NULL
, NULL
);
1666 if (search_text
== NULL
)
1667 search_text
= g_strdup(utf8_search_text
);
1669 /* set grep command and options */
1670 argv_prefix
= g_new0(gchar
*, 1 + opts_argv_len
+ 3 + 1); /* last +1 for recursive arg */
1672 argv_prefix
[0] = command_grep
;
1673 for (i
= 0; i
< opts_argv_len
; i
++)
1675 argv_prefix
[i
+ 1] = g_strdup(opts_argv
[i
]);
1677 g_strfreev(opts_argv
);
1679 i
++; /* correct for tool_prefs.grep_cmd */
1680 argv_prefix
[i
++] = g_strdup("--");
1681 argv_prefix
[i
++] = search_text
;
1683 /* finally add the arguments(files to be searched) */
1684 if (strstr(argv_prefix
[1], "r")) /* recursive option set */
1686 /* Use '.' so we get relative paths in the output */
1687 argv_prefix
[i
++] = g_strdup(".");
1688 argv_prefix
[i
++] = NULL
;
1693 argv_prefix
[i
++] = NULL
;
1694 argv
= search_get_argv((const gchar
**)argv_prefix
, dir
);
1695 g_strfreev(argv_prefix
);
1698 if (argv
== NULL
) /* no files */
1703 gtk_list_store_clear(msgwindow
.store_msg
);
1704 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow
.notebook
), MSG_MESSAGE
);
1706 if (! g_spawn_async_with_pipes(dir
, (gchar
**)argv
, NULL
, G_SPAWN_DO_NOT_REAP_CHILD
,
1707 NULL
, NULL
, &child_pid
,
1708 NULL
, &stdout_fd
, &stderr_fd
, &error
))
1710 geany_debug("%s: g_spawn_async_with_pipes() failed: %s", G_STRFUNC
, error
->message
);
1711 ui_set_statusbar(TRUE
, _("Process failed (%s)"), error
->message
);
1712 g_error_free(error
);
1717 gchar
*str
, *utf8_str
;
1719 ui_progress_bar_start(_("Searching..."));
1721 msgwin_set_messages_dir(dir
);
1722 /* we can pass 'enc' without strdup'ing it here because it's a global const string and
1723 * always exits longer than the lifetime of this function */
1724 utils_set_up_io_channel(stdout_fd
, G_IO_IN
| G_IO_PRI
| G_IO_ERR
| G_IO_HUP
| G_IO_NVAL
,
1725 TRUE
, search_read_io
, (gpointer
) enc
);
1726 utils_set_up_io_channel(stderr_fd
, G_IO_IN
| G_IO_PRI
| G_IO_ERR
| G_IO_HUP
| G_IO_NVAL
,
1727 TRUE
, search_read_io_stderr
, (gpointer
) enc
);
1728 g_child_watch_add(child_pid
, search_close_pid
, NULL
);
1730 str
= g_strdup_printf(_("%s %s -- %s (in directory: %s)"),
1731 tool_prefs
.grep_cmd
, opts
, utf8_search_text
, dir
);
1732 utf8_str
= utils_get_utf8_from_locale(str
);
1733 msgwin_msg_add_string(COLOR_BLUE
, -1, NULL
, utf8_str
);
1734 utils_free_pointers(2, str
, utf8_str
, NULL
);
1742 static gboolean
pattern_list_match(GSList
*patterns
, const gchar
*str
)
1746 foreach_slist(item
, patterns
)
1748 if (g_pattern_match_string(item
->data
, str
))
1755 /* Creates an argument vector of strings, copying argv_prefix[] values for
1756 * the first arguments, then followed by filenames found in dir.
1757 * Returns NULL if no files were found, otherwise returned vector should be fully freed. */
1758 static gchar
**search_get_argv(const gchar
**argv_prefix
, const gchar
*dir
)
1760 guint prefix_len
, list_len
, i
, j
;
1762 GSList
*list
, *item
, *patterns
= NULL
;
1763 GError
*error
= NULL
;
1765 g_return_val_if_fail(dir
!= NULL
, NULL
);
1767 prefix_len
= g_strv_length((gchar
**)argv_prefix
);
1768 list
= utils_get_file_list(dir
, &list_len
, &error
);
1771 ui_set_statusbar(TRUE
, _("Could not open directory (%s)"), error
->message
);
1772 g_error_free(error
);
1778 argv
= g_new(gchar
*, prefix_len
+ list_len
+ 1);
1780 for (i
= 0, j
= 0; i
< prefix_len
; i
++)
1782 if (g_str_has_prefix(argv_prefix
[i
], "--include="))
1784 const gchar
*pat
= &(argv_prefix
[i
][10]); /* the pattern part of the argument */
1786 patterns
= g_slist_prepend(patterns
, g_pattern_spec_new(pat
));
1789 argv
[j
++] = g_strdup(argv_prefix
[i
]);
1796 foreach_slist(item
, list
)
1798 if (pattern_list_match(patterns
, item
->data
))
1799 argv
[j
++] = item
->data
;
1803 foreach_slist(pat
, patterns
)
1804 g_pattern_spec_free(pat
->data
);
1805 g_slist_free(patterns
);
1809 foreach_slist(item
, list
)
1810 argv
[j
++] = item
->data
;
1819 static gboolean
read_fif_io(GIOChannel
*source
, GIOCondition condition
, gchar
*enc
, gint msg_color
)
1821 if (condition
& (G_IO_IN
| G_IO_PRI
))
1823 gchar
*msg
, *utf8_msg
;
1825 while (g_io_channel_read_line(source
, &msg
, NULL
, NULL
, NULL
) && msg
)
1830 /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */
1833 if (! g_utf8_validate(msg
, -1, NULL
))
1835 utf8_msg
= g_convert(msg
, -1, "UTF-8", enc
, NULL
, NULL
, NULL
);
1837 if (utf8_msg
== NULL
)
1843 msgwin_msg_add_string(msg_color
, -1, NULL
, utf8_msg
);
1845 if (utf8_msg
!= msg
)
1850 if (condition
& (G_IO_ERR
| G_IO_HUP
| G_IO_NVAL
))
1857 static gboolean
search_read_io(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
1859 return read_fif_io(source
, condition
, data
, COLOR_BLACK
);
1863 static gboolean
search_read_io_stderr(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
1865 return read_fif_io(source
, condition
, data
, COLOR_DARK_RED
);
1869 static void search_close_pid(GPid child_pid
, gint status
, gpointer user_data
)
1871 const gchar
*msg
= _("Search failed.");
1873 gint exit_status
= 1;
1875 if (WIFEXITED(status
))
1877 exit_status
= WEXITSTATUS(status
);
1879 else if (WIFSIGNALED(status
))
1882 g_warning("Find in Files: The command failed unexpectedly (signal received).");
1885 gint exit_status
= status
;
1888 switch (exit_status
)
1892 gint count
= gtk_tree_model_iter_n_children(
1893 GTK_TREE_MODEL(msgwindow
.store_msg
), NULL
) - 1;
1894 gchar
*text
= ngettext(
1895 "Search completed with %d match.",
1896 "Search completed with %d matches.", count
);
1898 msgwin_msg_add(COLOR_BLUE
, -1, NULL
, text
, count
);
1899 ui_set_statusbar(FALSE
, text
, count
);
1903 msg
= _("No matches found.");
1905 msgwin_msg_add_string(COLOR_BLUE
, -1, NULL
, msg
);
1906 ui_set_statusbar(FALSE
, "%s", msg
);
1910 g_spawn_close_pid(child_pid
);
1911 ui_progress_bar_stop();
1915 static GRegex
*compile_regex(const gchar
*str
, gint sflags
)
1918 GError
*error
= NULL
;
1919 gint rflags
= G_REGEX_MULTILINE
;
1921 if (~sflags
& SCFIND_MATCHCASE
)
1922 rflags
|= G_REGEX_CASELESS
;
1923 if (sflags
& (SCFIND_WHOLEWORD
| SCFIND_WORDSTART
))
1925 geany_debug("%s: Unsupported regex flags found!", G_STRFUNC
);
1928 regex
= g_regex_new(str
, rflags
, 0, &error
);
1931 ui_set_statusbar(FALSE
, _("Bad regex: %s"), error
->message
);
1932 g_error_free(error
);
1938 /* groups that don't exist are handled OK as len = end - start = (-1) - (-1) = 0 */
1939 static gchar
*get_regex_match_string(const gchar
*text
, const GeanyMatchInfo
*match
, guint nth
)
1941 const gint start
= match
->matches
[nth
].start
;
1942 const gint end
= match
->matches
[nth
].end
;
1943 return g_strndup(&text
[start
], end
- start
);
1947 static gint
find_regex(ScintillaObject
*sci
, guint pos
, GRegex
*regex
, GeanyMatchInfo
*match
)
1953 g_return_val_if_fail(pos
<= (guint
)sci_get_length(sci
), -1);
1955 /* Warning: any SCI calls will invalidate 'text' after calling SCI_GETCHARACTERPOINTER */
1956 text
= (void*)scintilla_send_message(sci
, SCI_GETCHARACTERPOINTER
, 0, 0);
1958 /* Warning: minfo will become invalid when 'text' does! */
1959 if (g_regex_match_full(regex
, text
, -1, pos
, 0, &minfo
, NULL
))
1963 /* copy whole match text and offsets before they become invalid */
1964 SETPTR(match
->match_text
, g_match_info_fetch(minfo
, 0));
1966 foreach_range(i
, G_N_ELEMENTS(match
->matches
))
1968 gint start
= -1, end
= -1;
1970 g_match_info_fetch_pos(minfo
, (gint
)i
, &start
, &end
);
1971 match
->matches
[i
].start
= start
;
1972 match
->matches
[i
].end
= end
;
1974 match
->start
= match
->matches
[0].start
;
1975 match
->end
= match
->matches
[0].end
;
1978 g_match_info_free(minfo
);
1983 gint
search_find_prev(ScintillaObject
*sci
, const gchar
*str
, gint flags
, GeanyMatchInfo
**match_
)
1987 g_return_val_if_fail(! (flags
& SCFIND_REGEXP
), -1);
1989 ret
= sci_search_prev(sci
, flags
, str
);
1990 if (ret
!= -1 && match_
)
1991 *match_
= match_info_new(flags
, ret
, ret
+ strlen(str
));
1996 gint
search_find_next(ScintillaObject
*sci
, const gchar
*str
, gint flags
, GeanyMatchInfo
**match_
)
1998 GeanyMatchInfo
*match
;
2003 if (~flags
& SCFIND_REGEXP
)
2005 ret
= sci_search_next(sci
, flags
, str
);
2006 if (ret
!= -1 && match_
)
2007 *match_
= match_info_new(flags
, ret
, ret
+ strlen(str
));
2011 regex
= compile_regex(str
, flags
);
2015 match
= match_info_new(flags
, 0, 0);
2017 pos
= sci_get_current_position(sci
);
2018 ret
= find_regex(sci
, pos
, regex
, match
);
2019 /* avoid re-matching the same position in case of empty matches */
2020 if (ret
== pos
&& match
->matches
[0].start
== match
->matches
[0].end
)
2021 ret
= find_regex(sci
, pos
+ 1, regex
, match
);
2023 sci_set_selection(sci
, match
->start
, match
->end
);
2025 if (ret
!= -1 && match_
)
2028 geany_match_info_free(match
);
2030 g_regex_unref(regex
);
2035 gint
search_replace_match(ScintillaObject
*sci
, const GeanyMatchInfo
*match
, const gchar
*replace_text
)
2041 sci_set_target_start(sci
, match
->start
);
2042 sci_set_target_end(sci
, match
->end
);
2044 if (! (match
->flags
& SCFIND_REGEXP
))
2045 return sci_replace_target(sci
, replace_text
, FALSE
);
2047 str
= g_string_new(replace_text
);
2050 gchar
*ptr
= &str
->str
[i
];
2060 /* backslash or unnecessary escape */
2061 if (c
== '\\' || !isdigit(c
))
2063 g_string_erase(str
, i
, 1);
2068 g_string_erase(str
, i
, 2);
2069 /* fix match offsets by subtracting index of whole match start from the string */
2070 grp
= get_regex_match_string(match
->match_text
- match
->matches
[0].start
, match
, c
- '0');
2071 g_string_insert(str
, i
, grp
);
2075 ret
= sci_replace_target(sci
, str
->str
, FALSE
);
2076 g_string_free(str
, TRUE
);
2081 gint
search_find_text(ScintillaObject
*sci
, gint flags
, struct Sci_TextToFind
*ttf
, GeanyMatchInfo
**match_
)
2083 GeanyMatchInfo
*match
= NULL
;
2087 if (~flags
& SCFIND_REGEXP
)
2089 ret
= sci_find_text(sci
, flags
, ttf
);
2090 if (ret
!= -1 && match_
)
2091 *match_
= match_info_new(flags
, ttf
->chrgText
.cpMin
, ttf
->chrgText
.cpMax
);
2095 regex
= compile_regex(ttf
->lpstrText
, flags
);
2099 match
= match_info_new(flags
, 0, 0);
2101 ret
= find_regex(sci
, ttf
->chrg
.cpMin
, regex
, match
);
2102 if (ret
>= ttf
->chrg
.cpMax
)
2106 ttf
->chrgText
.cpMin
= match
->start
;
2107 ttf
->chrgText
.cpMax
= match
->end
;
2110 if (ret
!= -1 && match_
)
2113 geany_match_info_free(match
);
2115 g_regex_unref(regex
);
2120 static gint
find_document_usage(GeanyDocument
*doc
, const gchar
*search_text
, gint flags
)
2122 gchar
*buffer
, *short_file_name
;
2123 struct Sci_TextToFind ttf
;
2125 gint prev_line
= -1;
2126 GSList
*match
, *matches
;
2128 g_return_val_if_fail(doc
!= NULL
, 0);
2130 short_file_name
= g_path_get_basename(DOC_FILENAME(doc
));
2133 ttf
.chrg
.cpMax
= sci_get_length(doc
->editor
->sci
);
2134 ttf
.lpstrText
= (gchar
*)search_text
;
2136 matches
= find_range(doc
->editor
->sci
, flags
, &ttf
);
2137 foreach_slist (match
, matches
)
2139 GeanyMatchInfo
*info
= match
->data
;
2140 gint line
= sci_get_line_from_position(doc
->editor
->sci
, info
->start
);
2142 if (line
!= prev_line
)
2144 buffer
= sci_get_line(doc
->editor
->sci
, line
);
2145 msgwin_msg_add(COLOR_BLACK
, line
+ 1, doc
,
2146 "%s:%d: %s", short_file_name
, line
+ 1, g_strstrip(buffer
));
2152 geany_match_info_free(info
);
2154 g_slist_free(matches
);
2155 g_free(short_file_name
);
2160 void search_find_usage(const gchar
*search_text
, const gchar
*original_search_text
,
2161 gint flags
, gboolean in_session
)
2166 doc
= document_get_current();
2167 g_return_if_fail(doc
!= NULL
);
2169 if (G_UNLIKELY(EMPTY(search_text
)))
2175 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow
.notebook
), MSG_MESSAGE
);
2176 gtk_list_store_clear(msgwindow
.store_msg
);
2179 { /* use current document */
2180 count
= find_document_usage(doc
, search_text
, flags
);
2185 for (i
= 0; i
< documents_array
->len
; i
++)
2187 if (documents
[i
]->is_valid
)
2189 count
+= find_document_usage(documents
[i
], search_text
, flags
);
2194 if (count
== 0) /* no matches were found */
2196 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), original_search_text
);
2197 msgwin_msg_add(COLOR_BLUE
, -1, NULL
, _("No matches found for \"%s\"."), original_search_text
);
2201 ui_set_statusbar(FALSE
, ngettext(
2202 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count
),
2203 count
, original_search_text
);
2204 msgwin_msg_add(COLOR_BLUE
, -1, NULL
, ngettext(
2205 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count
),
2206 count
, original_search_text
);
2211 /* ttf is updated to include the last match position (ttf->chrg.cpMin) and
2212 * the new search range end (ttf->chrg.cpMax).
2213 * Note: Normally you would call sci_start/end_undo_action() around this call. */
2214 guint
search_replace_range(ScintillaObject
*sci
, struct Sci_TextToFind
*ttf
,
2215 gint flags
, const gchar
*replace_text
)
2218 gint offset
= 0; /* difference between search pos and replace pos */
2219 GSList
*match
, *matches
;
2221 g_return_val_if_fail(sci
!= NULL
&& ttf
->lpstrText
!= NULL
&& replace_text
!= NULL
, 0);
2222 if (! *ttf
->lpstrText
)
2225 matches
= find_range(sci
, flags
, ttf
);
2226 foreach_slist (match
, matches
)
2228 GeanyMatchInfo
*info
= match
->data
;
2231 info
->start
+= offset
;
2232 info
->end
+= offset
;
2234 replace_len
= search_replace_match(sci
, info
, replace_text
);
2235 offset
+= replace_len
- (info
->end
- info
->start
);
2238 /* on last match, update the last match/new range end */
2241 ttf
->chrg
.cpMin
= info
->start
;
2242 ttf
->chrg
.cpMax
+= offset
;
2245 geany_match_info_free(info
);
2247 g_slist_free(matches
);
2253 void search_find_again(gboolean change_direction
)
2255 GeanyDocument
*doc
= document_get_current();
2257 g_return_if_fail(doc
!= NULL
);
2259 if (search_data
.text
)
2261 gboolean forward
= ! search_data
.backwards
;
2262 gint result
= document_find_text(doc
, search_data
.text
, search_data
.original_text
, search_data
.flags
,
2263 change_direction
? forward
: !forward
, NULL
, FALSE
, NULL
);
2266 editor_display_current_line(doc
->editor
, 0.3F
);
2268 if (search_data
.search_bar
)
2269 ui_set_search_entry_background(
2270 toolbar_get_widget_child_by_name("SearchEntry"), (result
> -1));