2 * search.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2006 The Geany contributors
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * Find, Replace, Find in Files dialog related functions.
23 * Note that the basic text find functions are in document.c.
35 #include "encodings.h"
36 #include "encodingsprivate.h"
38 #include "msgwindow.h"
40 #include "sciwrappers.h"
53 #include <gdk/gdkkeysyms.h>
57 GEANY_RESPONSE_FIND
= 1,
58 GEANY_RESPONSE_FIND_PREVIOUS
,
59 GEANY_RESPONSE_FIND_IN_FILE
,
60 GEANY_RESPONSE_FIND_IN_SESSION
,
62 GEANY_RESPONSE_REPLACE
,
63 GEANY_RESPONSE_REPLACE_AND_FIND
,
64 GEANY_RESPONSE_REPLACE_IN_SESSION
,
65 GEANY_RESPONSE_REPLACE_IN_FILE
,
66 GEANY_RESPONSE_REPLACE_IN_SEL
78 GeanySearchData search_data
;
79 GeanySearchPrefs search_prefs
;
85 gboolean fif_case_sensitive
;
86 gboolean fif_match_whole_word
;
87 gboolean fif_invert_results
;
88 gboolean fif_recursive
;
89 gboolean fif_use_extra_options
;
90 gchar
*fif_extra_options
;
94 gboolean find_regexp_multiline
;
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_regexp_multiline
;
102 gboolean replace_escape_sequences
;
103 gboolean replace_case_sensitive
;
104 gboolean replace_match_whole_word
;
105 gboolean replace_match_word_start
;
106 gboolean replace_search_backwards
;
107 gboolean replace_close_dialog
;
111 static StashGroup
*fif_prefs
= NULL
;
112 static StashGroup
*find_prefs
= NULL
;
113 static StashGroup
*replace_prefs
= NULL
;
120 gboolean all_expanded
;
121 gint position
[2]; /* x, y */
123 find_dlg
= {NULL
, NULL
, FALSE
, {0, 0}};
128 GtkWidget
*find_combobox
;
129 GtkWidget
*find_entry
;
130 GtkWidget
*replace_combobox
;
131 GtkWidget
*replace_entry
;
132 gboolean all_expanded
;
133 gint position
[2]; /* x, y */
135 replace_dlg
= {NULL
, NULL
, NULL
, NULL
, NULL
, FALSE
, {0, 0}};
140 GtkWidget
*dir_combo
;
141 GtkWidget
*files_combo
;
142 GtkWidget
*search_combo
;
143 GtkWidget
*encoding_combo
;
144 GtkWidget
*files_mode_combo
;
145 gint position
[2]; /* x, y */
147 fif_dlg
= {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, {0, 0}};
150 static void search_read_io(GString
*string
, GIOCondition condition
, gpointer data
);
151 static void search_read_io_stderr(GString
*string
, GIOCondition condition
, gpointer data
);
153 static void search_finished(GPid child_pid
, gint status
, gpointer user_data
);
155 static gchar
**search_get_argv(const gchar
**argv_prefix
, const gchar
*dir
);
157 static GRegex
*compile_regex(const gchar
*str
, GeanyFindFlags sflags
);
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_find_entry_activate(GtkEntry
*entry
, gpointer user_data
);
179 on_replace_entry_activate(GtkEntry
*entry
, gpointer user_data
);
182 on_find_in_files_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
);
185 search_find_in_files(const gchar
*utf8_search_text
, const gchar
*dir
, const gchar
*opts
,
189 static void init_prefs(void)
193 group
= stash_group_new("search");
194 configuration_add_pref_group(group
, TRUE
);
195 stash_group_add_toggle_button(group
, &search_prefs
.always_wrap
,
196 "pref_search_hide_find_dialog", FALSE
, "check_always_wrap_search");
197 stash_group_add_toggle_button(group
, &search_prefs
.hide_find_dialog
,
198 "pref_search_always_wrap", FALSE
, "check_hide_find_dialog");
199 stash_group_add_toggle_button(group
, &search_prefs
.use_current_file_dir
,
200 "pref_search_current_file_dir", TRUE
, "check_fif_current_dir");
202 /* dialog layout & positions */
203 group
= stash_group_new("search");
204 configuration_add_session_group(group
, FALSE
);
205 stash_group_add_boolean(group
, &find_dlg
.all_expanded
, "find_all_expanded", FALSE
);
206 stash_group_add_boolean(group
, &replace_dlg
.all_expanded
, "replace_all_expanded", FALSE
);
207 stash_group_add_integer(group
, &find_dlg
.position
[0], "position_find_x", -1);
208 stash_group_add_integer(group
, &find_dlg
.position
[1], "position_find_y", -1);
209 stash_group_add_integer(group
, &replace_dlg
.position
[0], "position_replace_x", -1);
210 stash_group_add_integer(group
, &replace_dlg
.position
[1], "position_replace_y", -1);
211 stash_group_add_integer(group
, &fif_dlg
.position
[0], "position_fif_x", -1);
212 stash_group_add_integer(group
, &fif_dlg
.position
[1], "position_fif_y", -1);
214 memset(&settings
, '\0', sizeof(settings
));
216 group
= stash_group_new("search");
218 configuration_add_pref_group(group
, FALSE
);
219 stash_group_add_toggle_button(group
, &settings
.fif_regexp
,
220 "fif_regexp", FALSE
, "check_regexp");
221 stash_group_add_toggle_button(group
, &settings
.fif_case_sensitive
,
222 "fif_case_sensitive", TRUE
, "check_case");
223 stash_group_add_toggle_button(group
, &settings
.fif_match_whole_word
,
224 "fif_match_whole_word", FALSE
, "check_wholeword");
225 stash_group_add_toggle_button(group
, &settings
.fif_invert_results
,
226 "fif_invert_results", FALSE
, "check_invert");
227 stash_group_add_toggle_button(group
, &settings
.fif_recursive
,
228 "fif_recursive", FALSE
, "check_recursive");
229 stash_group_add_entry(group
, &settings
.fif_extra_options
,
230 "fif_extra_options", "", "entry_extra");
231 stash_group_add_toggle_button(group
, &settings
.fif_use_extra_options
,
232 "fif_use_extra_options", FALSE
, "check_extra");
233 stash_group_add_entry(group
, &settings
.fif_files
,
234 "fif_files", "", "entry_files");
235 stash_group_add_combo_box(group
, &settings
.fif_files_mode
,
236 "fif_files_mode", FILES_MODE_ALL
, "combo_files_mode");
238 group
= stash_group_new("search");
240 configuration_add_pref_group(group
, FALSE
);
241 stash_group_add_toggle_button(group
, &settings
.find_regexp
,
242 "find_regexp", FALSE
, "check_regexp");
243 stash_group_add_toggle_button(group
, &settings
.find_regexp_multiline
,
244 "find_regexp_multiline", FALSE
, "check_multiline");
245 stash_group_add_toggle_button(group
, &settings
.find_case_sensitive
,
246 "find_case_sensitive", FALSE
, "check_case");
247 stash_group_add_toggle_button(group
, &settings
.find_escape_sequences
,
248 "find_escape_sequences", FALSE
, "check_escape");
249 stash_group_add_toggle_button(group
, &settings
.find_match_whole_word
,
250 "find_match_whole_word", FALSE
, "check_word");
251 stash_group_add_toggle_button(group
, &settings
.find_match_word_start
,
252 "find_match_word_start", FALSE
, "check_wordstart");
253 stash_group_add_toggle_button(group
, &settings
.find_close_dialog
,
254 "find_close_dialog", TRUE
, "check_close");
256 group
= stash_group_new("search");
257 replace_prefs
= group
;
258 configuration_add_pref_group(group
, FALSE
);
259 stash_group_add_toggle_button(group
, &settings
.replace_regexp
,
260 "replace_regexp", FALSE
, "check_regexp");
261 stash_group_add_toggle_button(group
, &settings
.replace_regexp_multiline
,
262 "replace_regexp_multiline", FALSE
, "check_multiline");
263 stash_group_add_toggle_button(group
, &settings
.replace_case_sensitive
,
264 "replace_case_sensitive", FALSE
, "check_case");
265 stash_group_add_toggle_button(group
, &settings
.replace_escape_sequences
,
266 "replace_escape_sequences", FALSE
, "check_escape");
267 stash_group_add_toggle_button(group
, &settings
.replace_match_whole_word
,
268 "replace_match_whole_word", FALSE
, "check_word");
269 stash_group_add_toggle_button(group
, &settings
.replace_match_word_start
,
270 "replace_match_word_start", FALSE
, "check_wordstart");
271 stash_group_add_toggle_button(group
, &settings
.replace_search_backwards
,
272 "replace_search_backwards", FALSE
, "check_back");
273 stash_group_add_toggle_button(group
, &settings
.replace_close_dialog
,
274 "replace_close_dialog", TRUE
, "check_close");
278 void search_init(void)
280 search_data
.text
= NULL
;
281 search_data
.original_text
= NULL
;
286 #define FREE_WIDGET(wid) \
287 if (wid && GTK_IS_WIDGET(wid)) gtk_widget_destroy(wid);
289 void search_finalize(void)
291 FREE_WIDGET(find_dlg
.dialog
);
292 FREE_WIDGET(replace_dlg
.dialog
);
293 FREE_WIDGET(fif_dlg
.dialog
);
294 g_free(search_data
.text
);
295 g_free(search_data
.original_text
);
299 static void on_widget_toggled_set_insensitive(
300 GtkToggleButton
*togglebutton
, gpointer user_data
)
302 gtk_widget_set_sensitive(GTK_WIDGET(user_data
),
303 !gtk_toggle_button_get_active(togglebutton
));
307 static GtkWidget
*add_find_checkboxes(GtkDialog
*dialog
)
309 GtkWidget
*checkbox1
, *checkbox2
, *check_regexp
, *checkbox5
,
310 *checkbox7
, *check_multiline
, *hbox
, *fbox
, *mbox
;
312 check_regexp
= gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
313 ui_hookup_widget(dialog
, check_regexp
, "check_regexp");
314 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp
), FALSE
);
315 gtk_widget_set_tooltip_text(check_regexp
, _("Use Perl-like regular expressions. "
316 "For detailed information about using regular expressions, please refer to the manual."));
317 g_signal_connect(check_regexp
, "toggled",
318 G_CALLBACK(on_find_replace_checkbutton_toggled
), dialog
);
320 checkbox7
= gtk_check_button_new_with_mnemonic(_("Use _escape sequences"));
321 ui_hookup_widget(dialog
, checkbox7
, "check_escape");
322 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox7
), FALSE
);
323 gtk_widget_set_tooltip_text(checkbox7
,
324 _("Replace \\\\, \\t, \\n, \\r and \\uXXXX (Unicode characters) with the "
325 "corresponding control characters"));
327 check_multiline
= gtk_check_button_new_with_mnemonic(_("Use multi-line matchin_g"));
328 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_multiline
), FALSE
);
329 gtk_widget_set_sensitive(check_multiline
, FALSE
);
330 ui_hookup_widget(dialog
, check_multiline
, "check_multiline");
331 gtk_button_set_focus_on_click(GTK_BUTTON(check_multiline
), FALSE
);
332 gtk_widget_set_tooltip_text(check_multiline
, _("Perform regular expression "
333 "matching on the whole buffer at once rather than line by line, allowing "
334 "matches to span multiple lines. In this mode, newline characters are part "
335 "of the input and can be captured as normal characters by the pattern."));
337 /* Search features */
338 fbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
339 gtk_box_pack_start(GTK_BOX(fbox
), check_regexp
, FALSE
, FALSE
, 0);
340 gtk_box_pack_start(GTK_BOX(fbox
), check_multiline
, FALSE
, FALSE
, 0);
341 gtk_box_pack_start(GTK_BOX(fbox
), checkbox7
, FALSE
, FALSE
, 0);
343 if (dialog
!= GTK_DIALOG(find_dlg
.dialog
))
345 GtkWidget
*check_back
= gtk_check_button_new_with_mnemonic(_("Search _backwards"));
346 ui_hookup_widget(dialog
, check_back
, "check_back");
347 gtk_button_set_focus_on_click(GTK_BUTTON(check_back
), FALSE
);
348 gtk_container_add(GTK_CONTAINER(fbox
), check_back
);
351 checkbox1
= gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
352 ui_hookup_widget(dialog
, checkbox1
, "check_case");
353 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1
), FALSE
);
355 checkbox2
= gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
356 ui_hookup_widget(dialog
, checkbox2
, "check_word");
357 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2
), FALSE
);
359 checkbox5
= gtk_check_button_new_with_mnemonic(_("Match from s_tart of word"));
360 ui_hookup_widget(dialog
, checkbox5
, "check_wordstart");
361 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox5
), FALSE
);
363 /* disable wordstart when wholeword is checked */
364 g_signal_connect(checkbox2
, "toggled",
365 G_CALLBACK(on_widget_toggled_set_insensitive
), checkbox5
);
367 /* Matching options */
368 mbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
369 gtk_box_pack_start(GTK_BOX(mbox
), checkbox1
, FALSE
, FALSE
, 0);
370 gtk_box_pack_start(GTK_BOX(mbox
), checkbox2
, FALSE
, FALSE
, 0);
371 gtk_box_pack_start(GTK_BOX(mbox
), checkbox5
, FALSE
, FALSE
, 0);
373 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
374 gtk_box_set_homogeneous(GTK_BOX(hbox
), TRUE
);
375 gtk_container_add(GTK_CONTAINER(hbox
), fbox
);
376 gtk_container_add(GTK_CONTAINER(hbox
), mbox
);
381 static void send_find_dialog_response(GtkButton
*button
, gpointer user_data
)
383 gtk_dialog_response(GTK_DIALOG(find_dlg
.dialog
), GPOINTER_TO_INT(user_data
));
387 /* store text, clear search flags so we can use Search->Find Next/Previous */
388 static void setup_find_next(const gchar
*text
)
390 g_free(search_data
.text
);
391 g_free(search_data
.original_text
);
392 search_data
.text
= g_strdup(text
);
393 search_data
.original_text
= g_strdup(text
);
394 search_data
.flags
= 0;
395 search_data
.backwards
= FALSE
;
396 search_data
.search_bar
= FALSE
;
400 /* Search for next match of the current "selection".
401 * Optionally for X11 based systems, this will try to use the system-wide
403 * If it doesn't find a suitable search string it will try to use
404 * the current word instead, or just repeat the last search.
405 * Search flags are always zero.
407 void search_find_selection(GeanyDocument
*doc
, gboolean search_backwards
)
411 g_return_if_fail(DOC_VALID(doc
));
414 if (search_prefs
.find_selection_type
== GEANY_FIND_SEL_X
)
416 GtkClipboard
*clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
418 s
= gtk_clipboard_wait_for_text(clipboard
);
419 if (s
&& (strchr(s
,'\n') || strchr(s
, '\r')))
427 if (!s
&& sci_has_selection(doc
->editor
->sci
))
428 s
= sci_get_selection_contents(doc
->editor
->sci
);
430 if (!s
&& search_prefs
.find_selection_type
!= GEANY_FIND_SEL_AGAIN
)
432 /* get the current word */
433 s
= editor_get_default_selection(doc
->editor
, TRUE
, NULL
);
438 setup_find_next(s
); /* allow find next/prev */
440 if (document_find_text(doc
, s
, NULL
, 0, search_backwards
, NULL
, FALSE
, NULL
) > -1)
441 editor_display_current_line(doc
->editor
, 0.3F
);
444 else if (search_prefs
.find_selection_type
== GEANY_FIND_SEL_AGAIN
)
446 /* Repeat last search (in case selection was lost) */
447 search_find_again(search_backwards
);
456 static void on_expander_activated(GtkExpander
*exp
, gpointer data
)
458 gboolean
*setting
= data
;
460 *setting
= gtk_expander_get_expanded(exp
);
464 static void create_find_dialog(void)
466 GtkWidget
*label
, *entry
, *sbox
, *vbox
;
467 GtkWidget
*exp
, *bbox
, *button
, *check_close
;
469 find_dlg
.dialog
= gtk_dialog_new_with_buttons(_("Find"),
470 GTK_WINDOW(main_widgets
.window
), GTK_DIALOG_DESTROY_WITH_PARENT
,
471 GTK_STOCK_CLOSE
, GTK_RESPONSE_CANCEL
, NULL
);
472 vbox
= ui_dialog_vbox_new(GTK_DIALOG(find_dlg
.dialog
));
473 gtk_widget_set_name(find_dlg
.dialog
, "GeanyDialogSearch");
474 gtk_box_set_spacing(GTK_BOX(vbox
), 9);
476 button
= ui_button_new_with_image(GTK_STOCK_GO_BACK
, _("_Previous"));
477 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg
.dialog
), button
,
478 GEANY_RESPONSE_FIND_PREVIOUS
);
479 ui_hookup_widget(find_dlg
.dialog
, button
, "btn_previous");
481 button
= ui_button_new_with_image(GTK_STOCK_GO_FORWARD
, _("_Next"));
482 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg
.dialog
), button
,
483 GEANY_RESPONSE_FIND
);
485 label
= gtk_label_new_with_mnemonic(_("_Search for:"));
486 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
488 entry
= gtk_combo_box_text_new_with_entry();
489 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry
))));
490 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), entry
);
491 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry
))), 50);
492 find_dlg
.entry
= gtk_bin_get_child(GTK_BIN(entry
));
494 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry
)), "activate",
495 G_CALLBACK(on_find_entry_activate
), entry
);
496 ui_entry_add_activate_backward_signal(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry
))));
497 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry
)), "activate-backward",
498 G_CALLBACK(on_find_entry_activate_backward
), entry
);
499 g_signal_connect(find_dlg
.dialog
, "response",
500 G_CALLBACK(on_find_dialog_response
), entry
);
501 g_signal_connect(find_dlg
.dialog
, "delete-event",
502 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
504 sbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
505 gtk_box_pack_start(GTK_BOX(sbox
), label
, FALSE
, FALSE
, 0);
506 gtk_box_pack_start(GTK_BOX(sbox
), entry
, TRUE
, TRUE
, 0);
507 gtk_box_pack_start(GTK_BOX(vbox
), sbox
, TRUE
, FALSE
, 0);
509 gtk_container_add(GTK_CONTAINER(vbox
),
510 add_find_checkboxes(GTK_DIALOG(find_dlg
.dialog
)));
512 /* Now add the multiple match options */
513 exp
= gtk_expander_new_with_mnemonic(_("_Find All"));
514 gtk_expander_set_expanded(GTK_EXPANDER(exp
), find_dlg
.all_expanded
);
515 g_signal_connect_after(exp
, "activate",
516 G_CALLBACK(on_expander_activated
), &find_dlg
.all_expanded
);
518 bbox
= gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL
);
520 button
= gtk_button_new_with_mnemonic(_("_Mark"));
521 gtk_widget_set_tooltip_text(button
,
522 _("Mark all matches in the current document"));
523 gtk_container_add(GTK_CONTAINER(bbox
), button
);
524 g_signal_connect(button
, "clicked", G_CALLBACK(send_find_dialog_response
),
525 GINT_TO_POINTER(GEANY_RESPONSE_MARK
));
527 button
= gtk_button_new_with_mnemonic(_("In Sessi_on"));
528 gtk_container_add(GTK_CONTAINER(bbox
), button
);
529 g_signal_connect(button
, "clicked", G_CALLBACK(send_find_dialog_response
),
530 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_SESSION
));
532 button
= gtk_button_new_with_mnemonic(_("_In Document"));
533 gtk_container_add(GTK_CONTAINER(bbox
), button
);
534 g_signal_connect(button
, "clicked", G_CALLBACK(send_find_dialog_response
),
535 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_FILE
));
537 /* close window checkbox */
538 check_close
= gtk_check_button_new_with_mnemonic(_("Close _dialog"));
539 ui_hookup_widget(find_dlg
.dialog
, check_close
, "check_close");
540 gtk_button_set_focus_on_click(GTK_BUTTON(check_close
), FALSE
);
541 gtk_widget_set_tooltip_text(check_close
,
542 _("Disable this option to keep the dialog open"));
543 gtk_container_add(GTK_CONTAINER(bbox
), check_close
);
544 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox
), check_close
, TRUE
);
546 ui_hbutton_box_copy_layout(
547 GTK_BUTTON_BOX(gtk_dialog_get_action_area(GTK_DIALOG(find_dlg
.dialog
))),
548 GTK_BUTTON_BOX(bbox
));
549 gtk_container_add(GTK_CONTAINER(exp
), bbox
);
550 gtk_container_add(GTK_CONTAINER(vbox
), exp
);
554 static void set_dialog_position(GtkWidget
*dialog
, gint
*position
)
556 if (position
[0] >= 0)
557 gtk_window_move(GTK_WINDOW(dialog
), position
[0], position
[1]);
561 void search_show_find_dialog(void)
563 GeanyDocument
*doc
= document_get_current();
566 g_return_if_fail(doc
!= NULL
);
568 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
570 if (find_dlg
.dialog
== NULL
)
572 create_find_dialog();
573 stash_group_display(find_prefs
, find_dlg
.dialog
);
575 gtk_entry_set_text(GTK_ENTRY(find_dlg
.entry
), sel
);
577 set_dialog_position(find_dlg
.dialog
, find_dlg
.position
);
578 gtk_widget_show_all(find_dlg
.dialog
);
584 /* update the search text from current selection */
585 gtk_entry_set_text(GTK_ENTRY(find_dlg
.entry
), sel
);
586 /* reset the entry widget's background colour */
587 ui_set_search_entry_background(find_dlg
.entry
, TRUE
);
589 gtk_widget_grab_focus(find_dlg
.entry
);
590 set_dialog_position(find_dlg
.dialog
, find_dlg
.position
);
591 gtk_widget_show(find_dlg
.dialog
);
592 /* bring the dialog back in the foreground in case it is already open but the focus is away */
593 gtk_window_present(GTK_WINDOW(find_dlg
.dialog
));
600 static void send_replace_dialog_response(GtkButton
*button
, gpointer user_data
)
602 gtk_dialog_response(GTK_DIALOG(replace_dlg
.dialog
), GPOINTER_TO_INT(user_data
));
607 on_widget_key_pressed_set_focus(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
609 if (event
->keyval
== GDK_KEY_Tab
)
611 gtk_widget_grab_focus(GTK_WIDGET(user_data
));
618 static void create_replace_dialog(void)
620 GtkWidget
*label_find
, *label_replace
,
621 *check_close
, *button
, *rbox
, *fbox
, *vbox
, *exp
, *bbox
;
622 GtkSizeGroup
*label_size
;
624 replace_dlg
.dialog
= gtk_dialog_new_with_buttons(_("Replace"),
625 GTK_WINDOW(main_widgets
.window
), GTK_DIALOG_DESTROY_WITH_PARENT
,
626 GTK_STOCK_CLOSE
, GTK_RESPONSE_CANCEL
, NULL
);
627 vbox
= ui_dialog_vbox_new(GTK_DIALOG(replace_dlg
.dialog
));
628 gtk_box_set_spacing(GTK_BOX(vbox
), 9);
629 gtk_widget_set_name(replace_dlg
.dialog
, "GeanyDialogSearch");
631 button
= gtk_button_new_from_stock(GTK_STOCK_FIND
);
632 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg
.dialog
), button
,
633 GEANY_RESPONSE_FIND
);
634 button
= gtk_button_new_with_mnemonic(_("_Replace"));
635 gtk_button_set_image(GTK_BUTTON(button
),
636 gtk_image_new_from_stock(GTK_STOCK_FIND_AND_REPLACE
, GTK_ICON_SIZE_BUTTON
));
637 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg
.dialog
), button
,
638 GEANY_RESPONSE_REPLACE
);
639 button
= gtk_button_new_with_mnemonic(_("Replace & Fi_nd"));
640 gtk_button_set_image(GTK_BUTTON(button
),
641 gtk_image_new_from_stock(GTK_STOCK_FIND_AND_REPLACE
, GTK_ICON_SIZE_BUTTON
));
642 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg
.dialog
), button
,
643 GEANY_RESPONSE_REPLACE_AND_FIND
);
645 label_find
= gtk_label_new_with_mnemonic(_("_Search for:"));
646 gtk_misc_set_alignment(GTK_MISC(label_find
), 0, 0.5);
648 label_replace
= gtk_label_new_with_mnemonic(_("Replace wit_h:"));
649 gtk_misc_set_alignment(GTK_MISC(label_replace
), 0, 0.5);
651 replace_dlg
.find_combobox
= gtk_combo_box_text_new_with_entry();
652 replace_dlg
.find_entry
= gtk_bin_get_child(GTK_BIN(replace_dlg
.find_combobox
));
653 ui_entry_add_clear_icon(GTK_ENTRY(replace_dlg
.find_entry
));
654 gtk_label_set_mnemonic_widget(GTK_LABEL(label_find
), replace_dlg
.find_combobox
);
655 gtk_entry_set_width_chars(GTK_ENTRY(replace_dlg
.find_entry
), 50);
656 ui_hookup_widget(replace_dlg
.dialog
, replace_dlg
.find_combobox
, "entry_find");
658 replace_dlg
.replace_combobox
= gtk_combo_box_text_new_with_entry();
659 replace_dlg
.replace_entry
= gtk_bin_get_child(GTK_BIN(replace_dlg
.replace_combobox
));
660 ui_entry_add_clear_icon(GTK_ENTRY(replace_dlg
.replace_entry
));
661 gtk_label_set_mnemonic_widget(GTK_LABEL(label_replace
), replace_dlg
.replace_combobox
);
662 gtk_entry_set_width_chars(GTK_ENTRY(replace_dlg
.replace_entry
), 50);
663 ui_hookup_widget(replace_dlg
.dialog
, replace_dlg
.replace_combobox
, "entry_replace");
665 /* tab from find to the replace entry */
666 g_signal_connect(replace_dlg
.find_entry
,
667 "key-press-event", G_CALLBACK(on_widget_key_pressed_set_focus
),
668 replace_dlg
.replace_entry
);
669 g_signal_connect(replace_dlg
.find_entry
, "activate",
670 G_CALLBACK(on_replace_find_entry_activate
), NULL
);
671 g_signal_connect(replace_dlg
.replace_entry
, "activate",
672 G_CALLBACK(on_replace_entry_activate
), NULL
);
673 g_signal_connect(replace_dlg
.dialog
, "response",
674 G_CALLBACK(on_replace_dialog_response
), NULL
);
675 g_signal_connect(replace_dlg
.dialog
, "delete-event",
676 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
678 fbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
679 gtk_box_pack_start(GTK_BOX(fbox
), label_find
, FALSE
, FALSE
, 0);
680 gtk_box_pack_start(GTK_BOX(fbox
), replace_dlg
.find_combobox
, TRUE
, TRUE
, 0);
682 rbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
683 gtk_box_pack_start(GTK_BOX(rbox
), label_replace
, FALSE
, FALSE
, 0);
684 gtk_box_pack_start(GTK_BOX(rbox
), replace_dlg
.replace_combobox
, TRUE
, TRUE
, 0);
686 label_size
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
687 gtk_size_group_add_widget(label_size
, label_find
);
688 gtk_size_group_add_widget(label_size
, label_replace
);
689 g_object_unref(G_OBJECT(label_size
)); /* auto destroy the size group */
691 gtk_box_pack_start(GTK_BOX(vbox
), fbox
, TRUE
, FALSE
, 0);
692 gtk_box_pack_start(GTK_BOX(vbox
), rbox
, TRUE
, FALSE
, 0);
693 gtk_container_add(GTK_CONTAINER(vbox
),
694 add_find_checkboxes(GTK_DIALOG(replace_dlg
.dialog
)));
696 /* Now add the multiple replace options */
697 exp
= gtk_expander_new_with_mnemonic(_("Re_place All"));
698 gtk_expander_set_expanded(GTK_EXPANDER(exp
), replace_dlg
.all_expanded
);
699 g_signal_connect_after(exp
, "activate",
700 G_CALLBACK(on_expander_activated
), &replace_dlg
.all_expanded
);
702 bbox
= gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL
);
704 button
= gtk_button_new_with_mnemonic(_("In Sessi_on"));
705 gtk_container_add(GTK_CONTAINER(bbox
), button
);
706 g_signal_connect(button
, "clicked", G_CALLBACK(send_replace_dialog_response
),
707 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SESSION
));
709 button
= gtk_button_new_with_mnemonic(_("_In Document"));
710 gtk_container_add(GTK_CONTAINER(bbox
), button
);
711 g_signal_connect(button
, "clicked", G_CALLBACK(send_replace_dialog_response
),
712 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_FILE
));
714 button
= gtk_button_new_with_mnemonic(_("In Se_lection"));
715 gtk_widget_set_tooltip_text(button
,
716 _("Replace all matches found in the currently selected text"));
717 gtk_container_add(GTK_CONTAINER(bbox
), button
);
718 g_signal_connect(button
, "clicked", G_CALLBACK(send_replace_dialog_response
),
719 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SEL
));
721 /* close window checkbox */
722 check_close
= gtk_check_button_new_with_mnemonic(_("Close _dialog"));
723 ui_hookup_widget(replace_dlg
.dialog
, check_close
, "check_close");
724 gtk_button_set_focus_on_click(GTK_BUTTON(check_close
), FALSE
);
725 gtk_widget_set_tooltip_text(check_close
,
726 _("Disable this option to keep the dialog open"));
727 gtk_container_add(GTK_CONTAINER(bbox
), check_close
);
728 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox
), check_close
, TRUE
);
730 ui_hbutton_box_copy_layout(
731 GTK_BUTTON_BOX(gtk_dialog_get_action_area(GTK_DIALOG(replace_dlg
.dialog
))),
732 GTK_BUTTON_BOX(bbox
));
733 gtk_container_add(GTK_CONTAINER(exp
), bbox
);
734 gtk_container_add(GTK_CONTAINER(vbox
), exp
);
738 void search_show_replace_dialog(void)
740 GeanyDocument
*doc
= document_get_current();
746 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
748 if (replace_dlg
.dialog
== NULL
)
750 create_replace_dialog();
751 stash_group_display(replace_prefs
, replace_dlg
.dialog
);
753 gtk_entry_set_text(GTK_ENTRY(replace_dlg
.find_entry
), sel
);
755 set_dialog_position(replace_dlg
.dialog
, replace_dlg
.position
);
756 gtk_widget_show_all(replace_dlg
.dialog
);
762 /* update the search text from current selection */
763 gtk_entry_set_text(GTK_ENTRY(replace_dlg
.find_entry
), sel
);
764 /* reset the entry widget's background colour */
765 ui_set_search_entry_background(replace_dlg
.find_entry
, TRUE
);
767 gtk_widget_grab_focus(replace_dlg
.find_entry
);
768 set_dialog_position(replace_dlg
.dialog
, replace_dlg
.position
);
769 gtk_widget_show(replace_dlg
.dialog
);
770 /* bring the dialog back in the foreground in case it is already open but the focus is away */
771 gtk_window_present(GTK_WINDOW(replace_dlg
.dialog
));
778 static void on_widget_toggled_set_sensitive(GtkToggleButton
*togglebutton
, gpointer user_data
)
780 /* disable extra option entry when checkbutton not checked */
781 gtk_widget_set_sensitive(GTK_WIDGET(user_data
),
782 gtk_toggle_button_get_active(togglebutton
));
786 static void update_file_patterns(GtkWidget
*mode_combo
, GtkWidget
*fcombo
)
791 entry
= gtk_bin_get_child(GTK_BIN(fcombo
));
793 selection
= gtk_combo_box_get_active(GTK_COMBO_BOX(mode_combo
));
795 if (selection
== FILES_MODE_ALL
)
797 gtk_entry_set_text(GTK_ENTRY(entry
), "");
798 gtk_widget_set_sensitive(fcombo
, FALSE
);
800 else if (selection
== FILES_MODE_CUSTOM
)
802 gtk_widget_set_sensitive(fcombo
, TRUE
);
804 else if (selection
== FILES_MODE_PROJECT
)
806 if (app
->project
&& !EMPTY(app
->project
->file_patterns
))
810 patterns
= g_strjoinv(" ", app
->project
->file_patterns
);
811 gtk_entry_set_text(GTK_ENTRY(entry
), patterns
);
816 gtk_entry_set_text(GTK_ENTRY(entry
), "");
819 gtk_widget_set_sensitive(fcombo
, FALSE
);
824 /* creates the combo to choose which files include in the search */
825 static GtkWidget
*create_fif_file_mode_combo(void)
828 GtkCellRenderer
*renderer
;
833 store
= gtk_list_store_new(2, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
834 gtk_list_store_append(store
, &iter
);
835 gtk_list_store_set(store
, &iter
, 0, _("all"), 1, TRUE
, -1);
836 gtk_list_store_append(store
, &iter
);
837 gtk_list_store_set(store
, &iter
, 0, _("project"), 1, app
->project
!= NULL
, -1);
838 gtk_list_store_append(store
, &iter
);
839 gtk_list_store_set(store
, &iter
, 0, _("custom"), 1, TRUE
, -1);
841 combo
= gtk_combo_box_new_with_model(GTK_TREE_MODEL(store
));
842 g_object_unref(store
);
843 gtk_widget_set_tooltip_text(combo
, _("All: search all files in the directory\n"
844 "Project: use file patterns defined in the project settings\n"
845 "Custom: specify file patterns manually"));
847 renderer
= gtk_cell_renderer_text_new();
848 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo
), renderer
, TRUE
);
849 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo
), renderer
, "text", 0, "sensitive", 1, NULL
);
855 /* updates the sensitivity of the project combo item */
856 static void update_fif_file_mode_combo(void)
858 GtkTreeModel
*model
= gtk_combo_box_get_model(GTK_COMBO_BOX(fif_dlg
.files_mode_combo
));
861 /* "1" refers to the second list entry, project */
862 if (gtk_tree_model_get_iter_from_string(model
, &iter
, "1"))
863 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
, 1, app
->project
!= NULL
, -1);
867 static void create_fif_dialog(void)
869 GtkWidget
*dir_combo
, *combo
, *fcombo
, *e_combo
, *entry
;
870 GtkWidget
*label
, *label1
, *label2
, *label3
, *checkbox1
, *checkbox2
, *check_wholeword
,
871 *check_recursive
, *check_extra
, *entry_extra
, *check_regexp
, *combo_files_mode
;
872 GtkWidget
*dbox
, *sbox
, *lbox
, *rbox
, *hbox
, *vbox
, *ebox
;
873 GtkSizeGroup
*size_group
;
875 fif_dlg
.dialog
= gtk_dialog_new_with_buttons(
876 _("Find in Files"), GTK_WINDOW(main_widgets
.window
), GTK_DIALOG_DESTROY_WITH_PARENT
,
877 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
, NULL
);
878 vbox
= ui_dialog_vbox_new(GTK_DIALOG(fif_dlg
.dialog
));
879 gtk_box_set_spacing(GTK_BOX(vbox
), 9);
880 gtk_widget_set_name(fif_dlg
.dialog
, "GeanyDialogSearch");
882 gtk_dialog_add_button(GTK_DIALOG(fif_dlg
.dialog
), GTK_STOCK_FIND
, GTK_RESPONSE_ACCEPT
);
883 gtk_dialog_set_default_response(GTK_DIALOG(fif_dlg
.dialog
),
884 GTK_RESPONSE_ACCEPT
);
886 label
= gtk_label_new_with_mnemonic(_("_Search for:"));
887 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
889 combo
= gtk_combo_box_text_new_with_entry();
890 entry
= gtk_bin_get_child(GTK_BIN(combo
));
891 ui_entry_add_clear_icon(GTK_ENTRY(entry
));
892 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), entry
);
893 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 50);
894 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
895 fif_dlg
.search_combo
= combo
;
897 sbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
898 gtk_box_pack_start(GTK_BOX(sbox
), label
, FALSE
, FALSE
, 0);
899 gtk_box_pack_start(GTK_BOX(sbox
), combo
, TRUE
, TRUE
, 0);
901 /* make labels same width */
902 size_group
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
903 gtk_size_group_add_widget(size_group
, label
);
905 label3
= gtk_label_new_with_mnemonic(_("File _patterns:"));
906 gtk_misc_set_alignment(GTK_MISC(label3
), 0, 0.5);
908 combo_files_mode
= create_fif_file_mode_combo();
909 gtk_label_set_mnemonic_widget(GTK_LABEL(label3
), combo_files_mode
);
910 ui_hookup_widget(fif_dlg
.dialog
, combo_files_mode
, "combo_files_mode");
911 fif_dlg
.files_mode_combo
= combo_files_mode
;
913 fcombo
= gtk_combo_box_text_new_with_entry();
914 entry
= gtk_bin_get_child(GTK_BIN(fcombo
));
915 ui_entry_add_clear_icon(GTK_ENTRY(entry
));
916 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
917 gtk_widget_set_tooltip_text(entry
,
918 _("Space separated list of file patterns (e.g. *.c *.h)"));
919 ui_hookup_widget(fif_dlg
.dialog
, entry
, "entry_files");
920 fif_dlg
.files_combo
= fcombo
;
922 /* update the entry when selection is changed */
923 g_signal_connect(combo_files_mode
, "changed", G_CALLBACK(update_file_patterns
), fcombo
);
925 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
926 gtk_box_pack_start(GTK_BOX(hbox
), label3
, FALSE
, FALSE
, 0);
927 gtk_box_pack_start(GTK_BOX(hbox
), combo_files_mode
, FALSE
, FALSE
, 0);
928 gtk_box_pack_start(GTK_BOX(hbox
), fcombo
, TRUE
, TRUE
, 0);
930 label1
= gtk_label_new_with_mnemonic(_("_Directory:"));
931 gtk_misc_set_alignment(GTK_MISC(label1
), 0, 0.5);
933 dir_combo
= gtk_combo_box_text_new_with_entry();
934 entry
= gtk_bin_get_child(GTK_BIN(dir_combo
));
935 ui_entry_add_clear_icon(GTK_ENTRY(entry
));
936 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
937 gtk_label_set_mnemonic_widget(GTK_LABEL(label1
), entry
);
938 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 50);
939 fif_dlg
.dir_combo
= dir_combo
;
941 /* tab from files to the dir entry */
942 g_signal_connect(gtk_bin_get_child(GTK_BIN(fcombo
)), "key-press-event",
943 G_CALLBACK(on_widget_key_pressed_set_focus
), entry
);
945 dbox
= ui_path_box_new(NULL
, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
,
947 gtk_box_pack_start(GTK_BOX(dbox
), label1
, FALSE
, FALSE
, 0);
949 label2
= gtk_label_new_with_mnemonic(_("E_ncoding:"));
950 gtk_misc_set_alignment(GTK_MISC(label2
), 0, 0.5);
952 e_combo
= ui_create_encodings_combo_box(FALSE
, GEANY_ENCODING_UTF_8
);
953 gtk_label_set_mnemonic_widget(GTK_LABEL(label2
), e_combo
);
954 fif_dlg
.encoding_combo
= e_combo
;
956 ebox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
957 gtk_box_pack_start(GTK_BOX(ebox
), label2
, FALSE
, FALSE
, 0);
958 gtk_box_pack_start(GTK_BOX(ebox
), e_combo
, TRUE
, TRUE
, 0);
960 gtk_size_group_add_widget(size_group
, label1
);
961 gtk_size_group_add_widget(size_group
, label2
);
962 gtk_size_group_add_widget(size_group
, label3
);
963 g_object_unref(G_OBJECT(size_group
)); /* auto destroy the size group */
965 gtk_box_pack_start(GTK_BOX(vbox
), sbox
, TRUE
, FALSE
, 0);
966 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, TRUE
, FALSE
, 0);
967 gtk_box_pack_start(GTK_BOX(vbox
), dbox
, TRUE
, FALSE
, 0);
968 gtk_box_pack_start(GTK_BOX(vbox
), ebox
, TRUE
, FALSE
, 0);
970 check_regexp
= gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
971 ui_hookup_widget(fif_dlg
.dialog
, check_regexp
, "check_regexp");
972 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp
), FALSE
);
973 gtk_widget_set_tooltip_text(check_regexp
, _("See grep's manual page for more information"));
975 check_recursive
= gtk_check_button_new_with_mnemonic(_("_Recurse in subfolders"));
976 ui_hookup_widget(fif_dlg
.dialog
, check_recursive
, "check_recursive");
977 gtk_button_set_focus_on_click(GTK_BUTTON(check_recursive
), FALSE
);
979 checkbox1
= gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
980 ui_hookup_widget(fif_dlg
.dialog
, checkbox1
, "check_case");
981 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1
), FALSE
);
982 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox1
), TRUE
);
984 check_wholeword
= gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
985 ui_hookup_widget(fif_dlg
.dialog
, check_wholeword
, "check_wholeword");
986 gtk_button_set_focus_on_click(GTK_BUTTON(check_wholeword
), FALSE
);
988 checkbox2
= gtk_check_button_new_with_mnemonic(_("_Invert search results"));
989 ui_hookup_widget(fif_dlg
.dialog
, checkbox2
, "check_invert");
990 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2
), FALSE
);
991 gtk_widget_set_tooltip_text(checkbox2
,
992 _("Invert the sense of matching, to select non-matching lines"));
994 lbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
995 gtk_container_add(GTK_CONTAINER(lbox
), check_regexp
);
996 gtk_container_add(GTK_CONTAINER(lbox
), checkbox2
);
997 gtk_container_add(GTK_CONTAINER(lbox
), check_recursive
);
999 rbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
1000 gtk_container_add(GTK_CONTAINER(rbox
), checkbox1
);
1001 gtk_container_add(GTK_CONTAINER(rbox
), check_wholeword
);
1002 gtk_container_add(GTK_CONTAINER(rbox
), gtk_label_new(NULL
));
1004 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
1005 gtk_container_add(GTK_CONTAINER(hbox
), lbox
);
1006 gtk_container_add(GTK_CONTAINER(hbox
), rbox
);
1007 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
1009 check_extra
= gtk_check_button_new_with_mnemonic(_("E_xtra options:"));
1010 ui_hookup_widget(fif_dlg
.dialog
, check_extra
, "check_extra");
1011 gtk_button_set_focus_on_click(GTK_BUTTON(check_extra
), FALSE
);
1013 entry_extra
= gtk_entry_new();
1014 ui_entry_add_clear_icon(GTK_ENTRY(entry_extra
));
1015 gtk_entry_set_activates_default(GTK_ENTRY(entry_extra
), TRUE
);
1016 gtk_widget_set_sensitive(entry_extra
, FALSE
);
1017 gtk_widget_set_tooltip_text(entry_extra
, _("Other options to pass to Grep"));
1018 ui_hookup_widget(fif_dlg
.dialog
, entry_extra
, "entry_extra");
1020 /* enable entry_extra when check_extra is checked */
1021 g_signal_connect(check_extra
, "toggled",
1022 G_CALLBACK(on_widget_toggled_set_sensitive
), entry_extra
);
1024 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
1025 gtk_box_pack_start(GTK_BOX(hbox
), check_extra
, FALSE
, FALSE
, 0);
1026 gtk_box_pack_start(GTK_BOX(hbox
), entry_extra
, TRUE
, TRUE
, 0);
1027 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
1029 g_signal_connect(fif_dlg
.dialog
, "response",
1030 G_CALLBACK(on_find_in_files_dialog_response
), NULL
);
1031 g_signal_connect(fif_dlg
.dialog
, "delete-event",
1032 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
1037 * Shows the Find in Files dialog.
1039 * @param dir The directory to search in (UTF-8 encoding). May be @c NULL
1040 * to determine it the usual way by using the current document's path.
1042 * @since 0.14, plugin API 53
1045 void search_show_find_in_files_dialog(const gchar
*dir
)
1047 search_show_find_in_files_dialog_full(NULL
, dir
);
1051 void search_show_find_in_files_dialog_full(const gchar
*text
, const gchar
*dir
)
1053 GtkWidget
*entry
; /* for child GtkEntry of a GtkComboBoxEntry */
1054 GeanyDocument
*doc
= document_get_current();
1056 gchar
*cur_dir
= NULL
;
1057 GeanyEncodingIndex enc_idx
= GEANY_ENCODING_UTF_8
;
1059 if (fif_dlg
.dialog
== NULL
)
1061 create_fif_dialog();
1062 gtk_widget_show_all(fif_dlg
.dialog
);
1064 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
1066 stash_group_display(fif_prefs
, fif_dlg
.dialog
);
1071 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
1074 entry
= gtk_bin_get_child(GTK_BIN(fif_dlg
.search_combo
));
1076 gtk_entry_set_text(GTK_ENTRY(entry
), text
);
1079 /* add project's base path directory to the dir list, we do this here once
1080 * (in create_fif_dialog() it would fail if a project is opened after dialog creation) */
1081 if (app
->project
!= NULL
&& !EMPTY(app
->project
->base_path
))
1083 ui_combo_box_prepend_text_once(GTK_COMBO_BOX_TEXT(fif_dlg
.dir_combo
),
1084 app
->project
->base_path
);
1087 entry
= gtk_bin_get_child(GTK_BIN(fif_dlg
.dir_combo
));
1089 cur_dir
= g_strdup(dir
); /* custom directory argument passed */
1092 if (search_prefs
.use_current_file_dir
)
1094 static gchar
*last_cur_dir
= NULL
;
1095 static GeanyDocument
*last_doc
= NULL
;
1097 /* Only set the directory entry once for the current document */
1098 cur_dir
= utils_get_current_file_dir_utf8();
1099 if (doc
== last_doc
&& cur_dir
&& utils_str_equal(cur_dir
, last_cur_dir
))
1101 /* in case the user now wants the current directory, add it to history */
1102 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(fif_dlg
.dir_combo
), cur_dir
, 0);
1103 SETPTR(cur_dir
, NULL
);
1106 SETPTR(last_cur_dir
, g_strdup(cur_dir
));
1110 if (!cur_dir
&& EMPTY(gtk_entry_get_text(GTK_ENTRY(entry
))))
1112 /* use default_open_path if no directory could be determined
1113 * (e.g. when no files are open) */
1115 cur_dir
= g_strdup(utils_get_default_dir_utf8());
1117 cur_dir
= g_get_current_dir();
1122 gtk_entry_set_text(GTK_ENTRY(entry
), cur_dir
);
1126 ui_set_search_entry_background(fif_dlg
.search_combo
, TRUE
);
1127 ui_set_search_entry_background(fif_dlg
.dir_combo
, TRUE
);
1128 update_fif_file_mode_combo();
1129 update_file_patterns(fif_dlg
.files_mode_combo
, fif_dlg
.files_combo
);
1131 /* set the encoding of the current file */
1133 enc_idx
= encodings_get_idx_from_charset(doc
->encoding
);
1134 ui_encodings_combo_box_set_active_encoding(GTK_COMBO_BOX(fif_dlg
.encoding_combo
), enc_idx
);
1136 /* put the focus to the directory entry if it is empty */
1137 if (utils_str_equal(gtk_entry_get_text(GTK_ENTRY(entry
)), ""))
1138 gtk_widget_grab_focus(fif_dlg
.dir_combo
);
1140 gtk_widget_grab_focus(fif_dlg
.search_combo
);
1142 /* set dialog window position */
1143 set_dialog_position(fif_dlg
.dialog
, fif_dlg
.position
);
1145 gtk_widget_show(fif_dlg
.dialog
);
1146 /* bring the dialog back in the foreground in case it is already open but the focus is away */
1147 gtk_window_present(GTK_WINDOW(fif_dlg
.dialog
));
1152 on_find_replace_checkbutton_toggled(GtkToggleButton
*togglebutton
, gpointer user_data
)
1154 GtkWidget
*dialog
= GTK_WIDGET(user_data
);
1155 GtkToggleButton
*chk_regexp
= GTK_TOGGLE_BUTTON(
1156 ui_lookup_widget(dialog
, "check_regexp"));
1158 if (togglebutton
== chk_regexp
)
1160 gboolean regex_set
= gtk_toggle_button_get_active(chk_regexp
);
1161 GtkWidget
*check_word
= ui_lookup_widget(dialog
, "check_word");
1162 GtkWidget
*check_wordstart
= ui_lookup_widget(dialog
, "check_wordstart");
1163 GtkWidget
*check_escape
= ui_lookup_widget(dialog
, "check_escape");
1164 GtkWidget
*check_multiline
= ui_lookup_widget(dialog
, "check_multiline");
1165 gboolean replace
= (dialog
!= find_dlg
.dialog
);
1166 const char *back_button
[2] = { "btn_previous" , "check_back" };
1168 /* hide options that don't apply to regex searches */
1169 gtk_widget_set_sensitive(check_escape
, ! regex_set
);
1170 gtk_widget_set_sensitive(ui_lookup_widget(dialog
, back_button
[replace
]), ! regex_set
);
1171 gtk_widget_set_sensitive(check_word
, ! regex_set
);
1172 gtk_widget_set_sensitive(check_wordstart
, ! regex_set
);
1173 gtk_widget_set_sensitive(check_multiline
, regex_set
);
1178 static GeanyMatchInfo
*match_info_new(GeanyFindFlags flags
, gint start
, gint end
)
1180 GeanyMatchInfo
*info
= g_slice_alloc(sizeof *info
);
1182 info
->flags
= flags
;
1183 info
->start
= start
;
1185 info
->match_text
= NULL
;
1190 void geany_match_info_free(GeanyMatchInfo
*info
)
1192 g_free(info
->match_text
);
1193 g_slice_free1(sizeof *info
, info
);
1197 /* find all in the given range.
1198 * Returns a list of allocated GeanyMatchInfo, should be freed using:
1200 * foreach_slist(node, matches)
1201 * geany_match_info_free(node->data);
1202 * g_slist_free(matches); */
1203 static GSList
*find_range(ScintillaObject
*sci
, GeanyFindFlags flags
, struct Sci_TextToFind
*ttf
)
1205 GSList
*matches
= NULL
;
1206 GeanyMatchInfo
*info
;
1208 g_return_val_if_fail(sci
!= NULL
&& ttf
->lpstrText
!= NULL
, NULL
);
1209 if (! *ttf
->lpstrText
)
1212 while (search_find_text(sci
, flags
, ttf
, &info
) != -1)
1214 if (ttf
->chrgText
.cpMax
> ttf
->chrg
.cpMax
)
1216 /* found text is partially out of range */
1217 geany_match_info_free(info
);
1221 matches
= g_slist_prepend(matches
, info
);
1222 ttf
->chrg
.cpMin
= ttf
->chrgText
.cpMax
;
1224 /* avoid rematching with empty matches like "(?=[a-z])" or "^$".
1225 * note we cannot assume a match will always be empty or not and then break out, since
1226 * matches like "a?(?=b)" will sometimes be empty and sometimes not */
1227 if (ttf
->chrgText
.cpMax
== ttf
->chrgText
.cpMin
)
1231 return g_slist_reverse(matches
);
1235 /* Clears markers if text is null/empty.
1236 * @return Number of matches marked. */
1237 gint
search_mark_all(GeanyDocument
*doc
, const gchar
*search_text
, GeanyFindFlags flags
)
1240 struct Sci_TextToFind ttf
;
1241 GSList
*match
, *matches
;
1243 g_return_val_if_fail(DOC_VALID(doc
), 0);
1245 /* clear previous search indicators */
1246 editor_indicator_clear(doc
->editor
, GEANY_INDICATOR_SEARCH
);
1248 if (G_UNLIKELY(EMPTY(search_text
)))
1252 ttf
.chrg
.cpMax
= sci_get_length(doc
->editor
->sci
);
1253 ttf
.lpstrText
= (gchar
*)search_text
;
1255 matches
= find_range(doc
->editor
->sci
, flags
, &ttf
);
1256 foreach_slist (match
, matches
)
1258 GeanyMatchInfo
*info
= match
->data
;
1260 if (info
->end
!= info
->start
)
1261 editor_indicator_set_on_range(doc
->editor
, GEANY_INDICATOR_SEARCH
, info
->start
, info
->end
);
1264 geany_match_info_free(info
);
1266 g_slist_free(matches
);
1273 on_find_entry_activate(GtkEntry
*entry
, gpointer user_data
)
1275 on_find_dialog_response(NULL
, GEANY_RESPONSE_FIND
, user_data
);
1280 on_find_entry_activate_backward(GtkEntry
*entry
, gpointer user_data
)
1282 /* can't search backwards with a regexp */
1283 if (search_data
.flags
& GEANY_FIND_REGEXP
)
1286 on_find_dialog_response(NULL
, GEANY_RESPONSE_FIND_PREVIOUS
, user_data
);
1290 static GeanyFindFlags
int_search_flags(gint match_case
, gint whole_word
, gint regexp
, gint multiline
, gint word_start
)
1292 return (match_case
? GEANY_FIND_MATCHCASE
: 0) |
1293 (regexp
? GEANY_FIND_REGEXP
: 0) |
1294 (whole_word
? GEANY_FIND_WHOLEWORD
: 0) |
1295 (multiline
? GEANY_FIND_MULTILINE
: 0) |
1296 /* SCFIND_WORDSTART overrides SCFIND_WHOLEWORD, but we want the opposite */
1297 (word_start
&& !whole_word
? GEANY_FIND_WORDSTART
: 0);
1302 on_find_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
)
1304 gtk_window_get_position(GTK_WINDOW(find_dlg
.dialog
),
1305 &find_dlg
.position
[0], &find_dlg
.position
[1]);
1307 stash_group_update(find_prefs
, find_dlg
.dialog
);
1309 if (response
== GTK_RESPONSE_CANCEL
|| response
== GTK_RESPONSE_DELETE_EVENT
)
1310 gtk_widget_hide(find_dlg
.dialog
);
1313 GeanyDocument
*doc
= document_get_current();
1314 gboolean check_close
= settings
.find_close_dialog
;
1319 search_data
.backwards
= FALSE
;
1320 search_data
.search_bar
= FALSE
;
1322 g_free(search_data
.text
);
1323 g_free(search_data
.original_text
);
1324 search_data
.text
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(user_data
)))));
1325 search_data
.original_text
= g_strdup(search_data
.text
);
1326 search_data
.flags
= int_search_flags(settings
.find_case_sensitive
,
1327 settings
.find_match_whole_word
, settings
.find_regexp
, settings
.find_regexp_multiline
,
1328 settings
.find_match_word_start
);
1330 if (EMPTY(search_data
.text
))
1334 gtk_widget_grab_focus(find_dlg
.entry
);
1337 if (search_data
.flags
& GEANY_FIND_REGEXP
)
1339 GRegex
*regex
= compile_regex(search_data
.text
, search_data
.flags
);
1343 g_regex_unref(regex
);
1345 else if (settings
.find_escape_sequences
)
1347 if (! utils_str_replace_escape(search_data
.text
, FALSE
))
1350 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(user_data
), search_data
.original_text
, 0);
1354 case GEANY_RESPONSE_FIND
:
1355 case GEANY_RESPONSE_FIND_PREVIOUS
:
1357 gint result
= document_find_text(doc
, search_data
.text
, search_data
.original_text
, search_data
.flags
,
1358 (response
== GEANY_RESPONSE_FIND_PREVIOUS
), NULL
, TRUE
, GTK_WIDGET(find_dlg
.dialog
));
1359 ui_set_search_entry_background(find_dlg
.entry
, (result
> -1));
1360 check_close
= search_prefs
.hide_find_dialog
;
1363 case GEANY_RESPONSE_FIND_IN_FILE
:
1364 search_find_usage(search_data
.text
, search_data
.original_text
, search_data
.flags
, FALSE
);
1367 case GEANY_RESPONSE_FIND_IN_SESSION
:
1368 search_find_usage(search_data
.text
, search_data
.original_text
, search_data
.flags
, TRUE
);
1371 case GEANY_RESPONSE_MARK
:
1373 gint count
= search_mark_all(doc
, search_data
.text
, search_data
.flags
);
1376 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), search_data
.original_text
);
1378 ui_set_statusbar(FALSE
,
1379 ngettext("Found %d match for \"%s\".",
1380 "Found %d matches for \"%s\".", count
),
1381 count
, search_data
.original_text
);
1386 gtk_widget_hide(find_dlg
.dialog
);
1392 on_replace_find_entry_activate(GtkEntry
*entry
, gpointer user_data
)
1394 on_replace_dialog_response(NULL
, GEANY_RESPONSE_FIND
, NULL
);
1399 on_replace_entry_activate(GtkEntry
*entry
, gpointer user_data
)
1401 on_replace_dialog_response(NULL
,
1402 search_prefs
.replace_and_find_by_default
? GEANY_RESPONSE_REPLACE_AND_FIND
: GEANY_RESPONSE_REPLACE
,
1407 static void replace_in_session(GeanyDocument
*doc
,
1408 GeanyFindFlags search_flags_re
, gboolean search_replace_escape_re
,
1409 const gchar
*find
, const gchar
*replace
,
1410 const gchar
*original_find
, const gchar
*original_replace
)
1412 guint n
, page_count
, rep_count
= 0, file_count
= 0;
1414 /* replace in all documents following notebook tab order */
1415 page_count
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets
.notebook
));
1416 for (n
= 0; n
< page_count
; n
++)
1418 GeanyDocument
*tmp_doc
= document_get_from_page(n
);
1421 reps
= document_replace_all(tmp_doc
, find
, replace
, original_find
, original_replace
, search_flags_re
);
1426 if (file_count
== 0)
1429 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), original_find
);
1432 /* if only one file was changed, don't override that document's status message
1433 * so we don't have to translate 4 messages for ngettext */
1435 ui_set_statusbar(FALSE
, _("Replaced %u matches in %u documents."),
1436 rep_count
, file_count
);
1438 /* show which docs had replacements: */
1439 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow
.notebook
), MSG_STATUS
);
1441 ui_save_buttons_toggle(doc
->changed
); /* update save all */
1446 on_replace_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
)
1448 GeanyDocument
*doc
= document_get_current();
1449 GeanyFindFlags search_flags_re
;
1450 gboolean search_backwards_re
, search_replace_escape_re
;
1451 gchar
*find
, *replace
, *original_find
= NULL
, *original_replace
= NULL
;
1453 gtk_window_get_position(GTK_WINDOW(replace_dlg
.dialog
),
1454 &replace_dlg
.position
[0], &replace_dlg
.position
[1]);
1456 stash_group_update(replace_prefs
, replace_dlg
.dialog
);
1458 if (response
== GTK_RESPONSE_CANCEL
|| response
== GTK_RESPONSE_DELETE_EVENT
)
1460 gtk_widget_hide(replace_dlg
.dialog
);
1464 if (response
== GEANY_RESPONSE_REPLACE_IN_SESSION
) {
1465 if (!search_prefs
.skip_confirmation_for_replace_in_session
&&
1466 ! dialogs_show_question_full(replace_dlg
.dialog
, NULL
, NULL
,
1467 _("This operation will modify all open files which contain the text to replace."),
1468 _("Are you sure to replace in the whole session?"))) {
1473 search_backwards_re
= settings
.replace_search_backwards
;
1474 search_replace_escape_re
= settings
.replace_escape_sequences
;
1475 find
= g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg
.find_entry
)));
1476 replace
= g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg
.replace_entry
)));
1478 search_flags_re
= int_search_flags(settings
.replace_case_sensitive
,
1479 settings
.replace_match_whole_word
, settings
.replace_regexp
,
1480 settings
.replace_regexp_multiline
, settings
.replace_match_word_start
);
1482 if ((response
!= GEANY_RESPONSE_FIND
) && (search_flags_re
& GEANY_FIND_MATCHCASE
)
1483 && (strcmp(find
, replace
) == 0))
1486 original_find
= g_strdup(find
);
1487 original_replace
= g_strdup(replace
);
1489 if (search_flags_re
& GEANY_FIND_REGEXP
)
1491 GRegex
*regex
= compile_regex(find
, search_flags_re
);
1493 g_regex_unref(regex
);
1494 /* find escapes will be handled by GRegex */
1495 if (!regex
|| !utils_str_replace_escape(replace
, TRUE
))
1498 else if (search_replace_escape_re
)
1500 if (! utils_str_replace_escape(find
, FALSE
) ||
1501 ! utils_str_replace_escape(replace
, FALSE
))
1505 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(replace_dlg
.find_combobox
), original_find
, 0);
1506 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(replace_dlg
.replace_combobox
), original_replace
, 0);
1510 case GEANY_RESPONSE_REPLACE_AND_FIND
:
1512 gint rep
= document_replace_text(doc
, find
, original_find
, replace
, search_flags_re
,
1513 search_backwards_re
);
1515 document_find_text(doc
, find
, original_find
, search_flags_re
, search_backwards_re
,
1519 case GEANY_RESPONSE_REPLACE
:
1520 document_replace_text(doc
, find
, original_find
, replace
, search_flags_re
,
1521 search_backwards_re
);
1524 case GEANY_RESPONSE_FIND
:
1526 gint result
= document_find_text(doc
, find
, original_find
, search_flags_re
,
1527 search_backwards_re
, NULL
, TRUE
, GTK_WIDGET(dialog
));
1528 ui_set_search_entry_background(replace_dlg
.find_entry
, (result
> -1));
1531 case GEANY_RESPONSE_REPLACE_IN_FILE
:
1532 if (! document_replace_all(doc
, find
, replace
, original_find
, original_replace
, search_flags_re
))
1536 case GEANY_RESPONSE_REPLACE_IN_SESSION
:
1537 replace_in_session(doc
, search_flags_re
, search_replace_escape_re
, find
, replace
, original_find
, original_replace
);
1540 case GEANY_RESPONSE_REPLACE_IN_SEL
:
1541 document_replace_sel(doc
, find
, replace
, original_find
, original_replace
, search_flags_re
);
1546 case GEANY_RESPONSE_REPLACE_IN_SEL
:
1547 case GEANY_RESPONSE_REPLACE_IN_FILE
:
1548 case GEANY_RESPONSE_REPLACE_IN_SESSION
:
1549 if (settings
.replace_close_dialog
)
1550 gtk_widget_hide(replace_dlg
.dialog
);
1554 g_free(original_find
);
1555 g_free(original_replace
);
1560 gtk_widget_grab_focus(replace_dlg
.find_entry
);
1563 g_free(original_find
);
1564 g_free(original_replace
);
1568 static void reset_msgwin(void)
1570 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow
.notebook
), MSG_MESSAGE
);
1571 gtk_list_store_clear(msgwindow
.store_msg
);
1572 // reset width after any long messages
1573 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(msgwindow
.tree_msg
));
1577 static GString
*get_grep_options(void)
1579 GString
*gstr
= g_string_new("-nHI"); /* line numbers, filenames, ignore binaries */
1581 if (settings
.fif_invert_results
)
1582 g_string_append_c(gstr
, 'v');
1583 if (! settings
.fif_case_sensitive
)
1584 g_string_append_c(gstr
, 'i');
1585 if (settings
.fif_match_whole_word
)
1586 g_string_append_c(gstr
, 'w');
1587 if (settings
.fif_recursive
)
1588 g_string_append_c(gstr
, 'r');
1590 if (!settings
.fif_regexp
)
1591 g_string_append_c(gstr
, 'F');
1593 g_string_append_c(gstr
, 'E');
1595 if (settings
.fif_use_extra_options
)
1597 g_strstrip(settings
.fif_extra_options
);
1599 if (*settings
.fif_extra_options
!= 0)
1601 g_string_append_c(gstr
, ' ');
1602 g_string_append(gstr
, settings
.fif_extra_options
);
1605 g_strstrip(settings
.fif_files
);
1606 if (settings
.fif_files_mode
!= FILES_MODE_ALL
&& *settings
.fif_files
)
1610 /* put --include= before each pattern */
1611 tmp
= g_string_new(settings
.fif_files
);
1612 do {} while (utils_string_replace_all(tmp
, " ", " "));
1613 g_string_prepend_c(tmp
, ' ');
1614 utils_string_replace_all(tmp
, " ", " --include=");
1615 g_string_append(gstr
, tmp
->str
);
1616 g_string_free(tmp
, TRUE
);
1623 on_find_in_files_dialog_response(GtkDialog
*dialog
, gint response
,
1624 G_GNUC_UNUSED gpointer user_data
)
1626 gtk_window_get_position(GTK_WINDOW(fif_dlg
.dialog
), &fif_dlg
.position
[0], &fif_dlg
.position
[1]);
1628 stash_group_update(fif_prefs
, fif_dlg
.dialog
);
1630 if (response
== GTK_RESPONSE_ACCEPT
)
1632 GtkWidget
*search_combo
= fif_dlg
.search_combo
;
1633 const gchar
*search_text
=
1634 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(search_combo
))));
1635 GtkWidget
*dir_combo
= fif_dlg
.dir_combo
;
1636 const gchar
*utf8_dir
=
1637 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(dir_combo
))));
1638 gchar
*locale_dir
= utils_get_locale_from_utf8(utf8_dir
);
1639 GeanyEncodingIndex enc_idx
=
1640 ui_encodings_combo_box_get_active_encoding(GTK_COMBO_BOX(fif_dlg
.encoding_combo
));
1642 if (!g_file_test(locale_dir
, G_FILE_TEST_IS_DIR
))
1644 ui_set_statusbar(FALSE
, _("Invalid directory for Find in Files."));
1645 ui_set_search_entry_background(dir_combo
, FALSE
);
1647 else if (EMPTY(search_text
))
1649 ui_set_statusbar(FALSE
, _("No text to find."));
1650 ui_set_search_entry_background(search_combo
, FALSE
);
1654 GString
*opts
= get_grep_options();
1655 const gchar
*enc
= (enc_idx
== GEANY_ENCODING_UTF_8
) ? NULL
:
1656 encodings_get_charset_from_index(enc_idx
);
1658 if (search_find_in_files(search_text
, utf8_dir
, opts
->str
, enc
))
1660 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(search_combo
), search_text
, 0);
1661 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(fif_dlg
.files_combo
), NULL
, 0);
1662 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(dir_combo
), utf8_dir
, 0);
1663 gtk_widget_hide(fif_dlg
.dialog
);
1665 g_string_free(opts
, TRUE
);
1670 gtk_widget_hide(fif_dlg
.dialog
);
1675 search_find_in_files(const gchar
*utf8_search_text
, const gchar
*utf8_dir
, const gchar
*opts
,
1678 gchar
**argv_prefix
, **argv
;
1679 gchar
*command_grep
;
1680 gchar
*command_line
, *dir
;
1681 gchar
*search_text
= NULL
;
1682 GError
*error
= NULL
;
1683 gboolean ret
= FALSE
;
1684 gssize utf8_text_len
;
1686 if (EMPTY(utf8_search_text
) || ! utf8_dir
) return TRUE
;
1688 command_grep
= g_find_program_in_path(tool_prefs
.grep_cmd
);
1689 if (command_grep
== NULL
)
1690 command_line
= g_strdup_printf("%s %s --", tool_prefs
.grep_cmd
, opts
);
1693 command_line
= g_strdup_printf("\"%s\" %s --", command_grep
, opts
);
1694 g_free(command_grep
);
1697 /* convert the search text in the preferred encoding (if the text is not valid UTF-8. assume
1698 * it is already in the preferred encoding) */
1699 utf8_text_len
= strlen(utf8_search_text
);
1700 if (enc
!= NULL
&& g_utf8_validate(utf8_search_text
, utf8_text_len
, NULL
))
1702 search_text
= g_convert(utf8_search_text
, utf8_text_len
, enc
, "UTF-8", NULL
, NULL
, NULL
);
1704 if (search_text
== NULL
)
1705 search_text
= g_strdup(utf8_search_text
);
1707 argv_prefix
= g_new(gchar
*, 3);
1708 argv_prefix
[0] = search_text
;
1709 dir
= utils_get_locale_from_utf8(utf8_dir
);
1711 /* finally add the arguments(files to be searched) */
1712 if (settings
.fif_recursive
) /* recursive option set */
1714 /* Use '.' so we get relative paths in the output */
1715 argv_prefix
[1] = g_strdup(".");
1716 argv_prefix
[2] = NULL
;
1721 argv_prefix
[1] = NULL
;
1722 argv
= search_get_argv((const gchar
**)argv_prefix
, dir
);
1723 g_strfreev(argv_prefix
);
1725 if (argv
== NULL
) /* no files */
1727 g_free(command_line
);
1733 /* we can pass 'enc' without strdup'ing it here because it's a global const string and
1734 * always exits longer than the lifetime of this function */
1735 if (spawn_with_callbacks(dir
, command_line
, argv
, NULL
, 0, NULL
, NULL
, search_read_io
,
1736 (gpointer
) enc
, 0, search_read_io_stderr
, (gpointer
) enc
, 0, search_finished
, NULL
,
1741 ui_progress_bar_start(_("Searching..."));
1742 msgwin_set_messages_dir(dir
);
1743 utf8_str
= g_strdup_printf(_("%s %s -- %s (in directory: %s)"),
1744 tool_prefs
.grep_cmd
, opts
, utf8_search_text
, utf8_dir
);
1745 msgwin_msg_add_string(COLOR_BLUE
, -1, NULL
, utf8_str
);
1751 ui_set_statusbar(TRUE
, _("Cannot execute grep tool \"%s\": %s. "
1752 "Check the path setting in Preferences."), tool_prefs
.grep_cmd
, error
->message
);
1753 g_error_free(error
);
1756 utils_free_pointers(2, dir
, command_line
, NULL
);
1762 static gboolean
pattern_list_match(GSList
*patterns
, const gchar
*str
)
1766 foreach_slist(item
, patterns
)
1768 if (g_pattern_match_string(item
->data
, str
))
1775 /* Creates an argument vector of strings, copying argv_prefix[] values for
1776 * the first arguments, then followed by filenames found in dir.
1777 * Returns NULL if no files were found, otherwise returned vector should be fully freed. */
1778 static gchar
**search_get_argv(const gchar
**argv_prefix
, const gchar
*dir
)
1780 guint prefix_len
, list_len
, i
, j
;
1782 GSList
*list
, *item
, *patterns
= NULL
;
1783 GError
*error
= NULL
;
1785 g_return_val_if_fail(dir
!= NULL
, NULL
);
1787 prefix_len
= g_strv_length((gchar
**)argv_prefix
);
1788 list
= utils_get_file_list(dir
, &list_len
, &error
);
1791 ui_set_statusbar(TRUE
, _("Could not open directory (%s)"), error
->message
);
1792 g_error_free(error
);
1798 argv
= g_new(gchar
*, prefix_len
+ list_len
+ 1);
1800 for (i
= 0, j
= 0; i
< prefix_len
; i
++)
1802 if (g_str_has_prefix(argv_prefix
[i
], "--include="))
1804 const gchar
*pat
= &(argv_prefix
[i
][10]); /* the pattern part of the argument */
1806 patterns
= g_slist_prepend(patterns
, g_pattern_spec_new(pat
));
1809 argv
[j
++] = g_strdup(argv_prefix
[i
]);
1816 foreach_slist(item
, list
)
1818 if (pattern_list_match(patterns
, item
->data
))
1819 argv
[j
++] = item
->data
;
1823 foreach_slist(pat
, patterns
)
1824 g_pattern_spec_free(pat
->data
);
1825 g_slist_free(patterns
);
1829 foreach_slist(item
, list
)
1830 argv
[j
++] = item
->data
;
1839 static void read_fif_io(gchar
*msg
, GIOCondition condition
, gchar
*enc
, gint msg_color
)
1841 if (condition
& (G_IO_IN
| G_IO_PRI
))
1843 gchar
*utf8_msg
= NULL
;
1846 /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */
1849 if (! g_utf8_validate(msg
, -1, NULL
))
1851 utf8_msg
= g_convert(msg
, -1, "UTF-8", enc
, NULL
, NULL
, NULL
);
1853 if (utf8_msg
== NULL
)
1859 msgwin_msg_add_string(msg_color
, -1, NULL
, utf8_msg
);
1861 if (utf8_msg
!= msg
)
1867 static void search_read_io(GString
*string
, GIOCondition condition
, gpointer data
)
1869 read_fif_io(string
->str
, condition
, data
, COLOR_BLACK
);
1873 static void search_read_io_stderr(GString
*string
, GIOCondition condition
, gpointer data
)
1875 read_fif_io(string
->str
, condition
, data
, COLOR_DARK_RED
);
1879 static void search_finished(GPid child_pid
, gint status
, gpointer user_data
)
1881 const gchar
*msg
= _("Search failed.");
1884 if (SPAWN_WIFEXITED(status
))
1886 exit_status
= SPAWN_WEXITSTATUS(status
);
1888 else if (SPAWN_WIFSIGNALED(status
))
1891 g_warning("Find in Files: The command failed unexpectedly (signal received).");
1898 switch (exit_status
)
1902 gint count
= gtk_tree_model_iter_n_children(
1903 GTK_TREE_MODEL(msgwindow
.store_msg
), NULL
) - 1;
1904 gchar
*text
= g_strdup_printf(ngettext(
1905 "Search completed with %d match.",
1906 "Search completed with %d matches.", count
),
1909 msgwin_msg_add_string(COLOR_BLUE
, -1, NULL
, text
);
1910 ui_set_statusbar(FALSE
, "%s", text
);
1915 msg
= _("No matches found.");
1918 msgwin_msg_add_string(COLOR_BLUE
, -1, NULL
, msg
);
1919 ui_set_statusbar(FALSE
, "%s", msg
);
1923 ui_progress_bar_stop();
1927 static GRegex
*compile_regex(const gchar
*str
, GeanyFindFlags sflags
)
1930 GError
*error
= NULL
;
1933 if (sflags
& GEANY_FIND_MULTILINE
)
1934 rflags
|= G_REGEX_MULTILINE
;
1935 if (~sflags
& GEANY_FIND_MATCHCASE
)
1936 rflags
|= G_REGEX_CASELESS
;
1937 if (sflags
& (GEANY_FIND_WHOLEWORD
| GEANY_FIND_WORDSTART
))
1939 geany_debug("%s: Unsupported regex flags found!", G_STRFUNC
);
1942 regex
= g_regex_new(str
, rflags
, 0, &error
);
1945 ui_set_statusbar(FALSE
, _("Bad regex: %s"), error
->message
);
1946 g_error_free(error
);
1952 /* groups that don't exist are handled OK as len = end - start = (-1) - (-1) = 0 */
1953 static gchar
*get_regex_match_string(const gchar
*text
, const GeanyMatchInfo
*match
, guint nth
)
1955 const gint start
= match
->matches
[nth
].start
;
1956 const gint end
= match
->matches
[nth
].end
;
1957 return g_strndup(&text
[start
], end
- start
);
1961 static gint
find_regex(ScintillaObject
*sci
, guint pos
, GRegex
*regex
, gboolean multiline
, GeanyMatchInfo
*match
)
1965 guint document_length
;
1969 document_length
= (guint
)sci_get_length(sci
);
1970 if (document_length
<= 0)
1971 return -1; /* skip empty documents */
1973 g_return_val_if_fail(pos
<= document_length
, -1);
1977 /* Warning: any SCI calls will invalidate 'text' after calling SCI_GETCHARACTERPOINTER */
1978 text
= (void*)SSM(sci
, SCI_GETCHARACTERPOINTER
, 0, 0);
1979 g_regex_match_full(regex
, text
, -1, pos
, 0, &minfo
, NULL
);
1981 else /* single-line mode, manually match against each line */
1983 gint line
= sci_get_line_from_position(sci
, pos
);
1987 gint start
= sci_get_position_from_line(sci
, line
);
1988 gint end
= sci_get_line_end_position(sci
, line
);
1990 text
= (void*)SSM(sci
, SCI_GETRANGEPOINTER
, start
, end
- start
);
1991 if (g_regex_match_full(regex
, text
, end
- start
, pos
- start
, 0, &minfo
, NULL
))
1996 else /* not found, try next line */
1999 if (line
>= sci_get_line_count(sci
))
2001 pos
= sci_get_position_from_line(sci
, line
);
2002 /* don't free last info, it's freed below */
2003 g_match_info_free(minfo
);
2008 /* Warning: minfo will become invalid when 'text' does! */
2009 if (g_match_info_matches(minfo
))
2013 /* copy whole match text and offsets before they become invalid */
2014 SETPTR(match
->match_text
, g_match_info_fetch(minfo
, 0));
2016 foreach_range(i
, G_N_ELEMENTS(match
->matches
))
2018 gint start
= -1, end
= -1;
2020 g_match_info_fetch_pos(minfo
, (gint
)i
, &start
, &end
);
2021 match
->matches
[i
].start
= offset
+ start
;
2022 match
->matches
[i
].end
= offset
+ end
;
2024 match
->start
= match
->matches
[0].start
;
2025 match
->end
= match
->matches
[0].end
;
2028 g_match_info_free(minfo
);
2033 static gint
geany_find_flags_to_sci_flags(GeanyFindFlags flags
)
2035 g_warn_if_fail(! (flags
& GEANY_FIND_REGEXP
) || ! (flags
& GEANY_FIND_MULTILINE
));
2037 return ((flags
& GEANY_FIND_MATCHCASE
) ? SCFIND_MATCHCASE
: 0) |
2038 ((flags
& GEANY_FIND_WHOLEWORD
) ? SCFIND_WHOLEWORD
: 0) |
2039 ((flags
& GEANY_FIND_REGEXP
) ? SCFIND_REGEXP
| SCFIND_POSIX
: 0) |
2040 ((flags
& GEANY_FIND_WORDSTART
) ? SCFIND_WORDSTART
: 0);
2044 gint
search_find_prev(ScintillaObject
*sci
, const gchar
*str
, GeanyFindFlags flags
, GeanyMatchInfo
**match_
)
2048 g_return_val_if_fail(! (flags
& GEANY_FIND_REGEXP
), -1);
2050 ret
= sci_search_prev(sci
, geany_find_flags_to_sci_flags(flags
), str
);
2051 if (ret
!= -1 && match_
)
2052 *match_
= match_info_new(flags
, ret
, ret
+ strlen(str
));
2057 gint
search_find_next(ScintillaObject
*sci
, const gchar
*str
, GeanyFindFlags flags
, GeanyMatchInfo
**match_
)
2059 GeanyMatchInfo
*match
;
2064 if (~flags
& GEANY_FIND_REGEXP
)
2066 ret
= sci_search_next(sci
, geany_find_flags_to_sci_flags(flags
), str
);
2067 if (ret
!= -1 && match_
)
2068 *match_
= match_info_new(flags
, ret
, ret
+ strlen(str
));
2072 regex
= compile_regex(str
, flags
);
2076 match
= match_info_new(flags
, 0, 0);
2078 pos
= sci_get_current_position(sci
);
2079 ret
= find_regex(sci
, pos
, regex
, flags
& GEANY_FIND_MULTILINE
, match
);
2080 /* avoid re-matching the same position in case of empty matches */
2081 if (ret
== pos
&& match
->matches
[0].start
== match
->matches
[0].end
)
2082 ret
= find_regex(sci
, pos
+ 1, regex
, flags
& GEANY_FIND_MULTILINE
, match
);
2084 sci_set_selection(sci
, match
->start
, match
->end
);
2086 if (ret
!= -1 && match_
)
2089 geany_match_info_free(match
);
2091 g_regex_unref(regex
);
2096 gint
search_replace_match(ScintillaObject
*sci
, const GeanyMatchInfo
*match
, const gchar
*replace_text
)
2102 sci_set_target_start(sci
, match
->start
);
2103 sci_set_target_end(sci
, match
->end
);
2105 if (! (match
->flags
& GEANY_FIND_REGEXP
))
2106 return sci_replace_target(sci
, replace_text
, FALSE
);
2108 str
= g_string_new(replace_text
);
2111 gchar
*ptr
= &str
->str
[i
];
2121 /* backslash or unnecessary escape */
2122 if (c
== '\\' || !isdigit(c
))
2124 g_string_erase(str
, i
, 1);
2129 g_string_erase(str
, i
, 2);
2130 /* fix match offsets by subtracting index of whole match start from the string */
2131 grp
= get_regex_match_string(match
->match_text
- match
->matches
[0].start
, match
, c
- '0');
2132 g_string_insert(str
, i
, grp
);
2136 ret
= sci_replace_target(sci
, str
->str
, FALSE
);
2137 g_string_free(str
, TRUE
);
2142 gint
search_find_text(ScintillaObject
*sci
, GeanyFindFlags flags
, struct Sci_TextToFind
*ttf
, GeanyMatchInfo
**match_
)
2144 GeanyMatchInfo
*match
= NULL
;
2148 if (~flags
& GEANY_FIND_REGEXP
)
2150 ret
= sci_find_text(sci
, geany_find_flags_to_sci_flags(flags
), ttf
);
2151 if (ret
!= -1 && match_
)
2152 *match_
= match_info_new(flags
, ttf
->chrgText
.cpMin
, ttf
->chrgText
.cpMax
);
2156 regex
= compile_regex(ttf
->lpstrText
, flags
);
2160 match
= match_info_new(flags
, 0, 0);
2162 ret
= find_regex(sci
, ttf
->chrg
.cpMin
, regex
, flags
& GEANY_FIND_MULTILINE
, match
);
2163 if (ret
>= ttf
->chrg
.cpMax
)
2167 ttf
->chrgText
.cpMin
= match
->start
;
2168 ttf
->chrgText
.cpMax
= match
->end
;
2171 if (ret
!= -1 && match_
)
2174 geany_match_info_free(match
);
2176 g_regex_unref(regex
);
2181 static gint
find_document_usage(GeanyDocument
*doc
, const gchar
*search_text
, GeanyFindFlags flags
)
2183 gchar
*buffer
, *short_file_name
;
2184 struct Sci_TextToFind ttf
;
2186 gint prev_line
= -1;
2187 GSList
*match
, *matches
;
2189 g_return_val_if_fail(DOC_VALID(doc
), 0);
2191 short_file_name
= g_path_get_basename(DOC_FILENAME(doc
));
2194 ttf
.chrg
.cpMax
= sci_get_length(doc
->editor
->sci
);
2195 ttf
.lpstrText
= (gchar
*)search_text
;
2197 matches
= find_range(doc
->editor
->sci
, flags
, &ttf
);
2198 foreach_slist (match
, matches
)
2200 GeanyMatchInfo
*info
= match
->data
;
2201 gint line
= sci_get_line_from_position(doc
->editor
->sci
, info
->start
);
2203 if (line
!= prev_line
)
2205 buffer
= sci_get_line(doc
->editor
->sci
, line
);
2206 msgwin_msg_add(COLOR_BLACK
, line
+ 1, doc
,
2207 "%s:%d: %s", short_file_name
, line
+ 1, g_strstrip(buffer
));
2213 geany_match_info_free(info
);
2215 g_slist_free(matches
);
2216 g_free(short_file_name
);
2221 void search_find_usage(const gchar
*search_text
, const gchar
*original_search_text
,
2222 GeanyFindFlags flags
, gboolean in_session
)
2227 doc
= document_get_current();
2228 g_return_if_fail(doc
!= NULL
);
2230 if (G_UNLIKELY(EMPTY(search_text
)))
2238 { /* use current document */
2239 count
= find_document_usage(doc
, search_text
, flags
);
2244 for (i
= 0; i
< documents_array
->len
; i
++)
2246 if (documents
[i
]->is_valid
)
2248 count
+= find_document_usage(documents
[i
], search_text
, flags
);
2253 if (count
== 0) /* no matches were found */
2255 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), original_search_text
);
2256 msgwin_msg_add(COLOR_BLUE
, -1, NULL
, _("No matches found for \"%s\"."), original_search_text
);
2260 ui_set_statusbar(FALSE
, ngettext(
2261 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count
),
2262 count
, original_search_text
);
2263 msgwin_msg_add(COLOR_BLUE
, -1, NULL
, ngettext(
2264 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count
),
2265 count
, original_search_text
);
2270 /* ttf is updated to include the last match position (ttf->chrg.cpMin) and
2271 * the new search range end (ttf->chrg.cpMax).
2272 * Note: Normally you would call sci_start/end_undo_action() around this call. */
2273 guint
search_replace_range(ScintillaObject
*sci
, struct Sci_TextToFind
*ttf
,
2274 GeanyFindFlags flags
, const gchar
*replace_text
)
2277 gint offset
= 0; /* difference between search pos and replace pos */
2278 GSList
*match
, *matches
;
2280 g_return_val_if_fail(sci
!= NULL
&& ttf
->lpstrText
!= NULL
&& replace_text
!= NULL
, 0);
2281 if (! *ttf
->lpstrText
)
2284 matches
= find_range(sci
, flags
, ttf
);
2285 foreach_slist (match
, matches
)
2287 GeanyMatchInfo
*info
= match
->data
;
2290 info
->start
+= offset
;
2291 info
->end
+= offset
;
2293 replace_len
= search_replace_match(sci
, info
, replace_text
);
2294 offset
+= replace_len
- (info
->end
- info
->start
);
2297 /* on last match, update the last match/new range end */
2300 ttf
->chrg
.cpMin
= info
->start
;
2301 ttf
->chrg
.cpMax
+= offset
;
2304 geany_match_info_free(info
);
2306 g_slist_free(matches
);
2312 void search_find_again(gboolean change_direction
)
2314 GeanyDocument
*doc
= document_get_current();
2316 g_return_if_fail(doc
!= NULL
);
2318 if (search_data
.text
)
2320 gboolean forward
= ! search_data
.backwards
;
2321 gint result
= document_find_text(doc
, search_data
.text
, search_data
.original_text
, search_data
.flags
,
2322 change_direction
? forward
: !forward
, NULL
, FALSE
, NULL
);
2325 editor_display_current_line(doc
->editor
, 0.3F
);
2327 if (search_data
.search_bar
)
2328 ui_set_search_entry_background(
2329 toolbar_get_widget_child_by_name("SearchEntry"), (result
> -1));