Use proper argument lists
[geany-mirror.git] / src / search.c
blob6641b4e9ade6eb7c73b7b2bd4247c4f7bf42f840
1 /*
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>
29 #include "geany.h"
30 #include "search.h"
31 #include "prefs.h"
32 #include "support.h"
33 #include "utils.h"
34 #include "document.h"
35 #include "msgwindow.h"
36 #include "sciwrappers.h"
37 #include "ui_utils.h"
38 #include "editor.h"
39 #include "encodings.h"
40 #include "project.h"
41 #include "keyfile.h"
42 #include "stash.h"
43 #include "toolbar.h"
44 #include "gtkcompat.h"
46 #include <unistd.h>
47 #include <string.h>
48 #include <ctype.h>
50 #ifdef G_OS_UNIX
51 # include <sys/types.h>
52 # include <sys/wait.h>
53 #endif
56 enum
58 GEANY_RESPONSE_FIND = 1,
59 GEANY_RESPONSE_FIND_PREVIOUS,
60 GEANY_RESPONSE_FIND_IN_FILE,
61 GEANY_RESPONSE_FIND_IN_SESSION,
62 GEANY_RESPONSE_MARK,
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
71 enum
73 FILES_MODE_ALL,
74 FILES_MODE_PROJECT,
75 FILES_MODE_CUSTOM
79 GeanySearchData search_data;
80 GeanySearchPrefs search_prefs;
83 static struct
85 gboolean fif_regexp;
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;
92 gint fif_files_mode;
93 gchar *fif_files;
94 gboolean find_regexp;
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;
108 settings;
110 static StashGroup *fif_prefs = NULL;
111 static StashGroup *find_prefs = NULL;
112 static StashGroup *replace_prefs = NULL;
115 static struct
117 GtkWidget *dialog;
118 GtkWidget *entry;
119 gboolean all_expanded;
120 gint position[2]; /* x, y */
122 find_dlg = {NULL, NULL, FALSE, {0, 0}};
124 static struct
126 GtkWidget *dialog;
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}};
134 static struct
136 GtkWidget *dialog;
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);
157 static void
158 on_find_replace_checkbutton_toggled(GtkToggleButton *togglebutton, gpointer user_data);
160 static void
161 on_find_dialog_response(GtkDialog *dialog, gint response, gpointer user_data);
163 static void
164 on_find_entry_activate(GtkEntry *entry, gpointer user_data);
166 static void
167 on_find_entry_activate_backward(GtkEntry *entry, gpointer user_data);
169 static void
170 on_replace_dialog_response(GtkDialog *dialog, gint response, gpointer user_data);
172 static void
173 on_replace_find_entry_activate(GtkEntry *entry, gpointer user_data);
175 static void
176 on_replace_entry_activate(GtkEntry *entry, gpointer user_data);
178 static void
179 on_find_in_files_dialog_response(GtkDialog *dialog, gint response, gpointer user_data);
181 static gboolean
182 search_find_in_files(const gchar *utf8_search_text, const gchar *dir, const gchar *opts,
183 const gchar *enc);
186 static void init_prefs(void)
188 StashGroup *group;
190 group = stash_group_new("search");
191 configuration_add_pref_group(group, TRUE);
192 stash_group_add_toggle_button(group, &search_prefs.always_wrap,
193 "pref_search_hide_find_dialog", FALSE, "check_always_wrap_search");
194 stash_group_add_toggle_button(group, &search_prefs.hide_find_dialog,
195 "pref_search_always_wrap", FALSE, "check_hide_find_dialog");
196 stash_group_add_toggle_button(group, &search_prefs.use_current_file_dir,
197 "pref_search_current_file_dir", TRUE, "check_fif_current_dir");
198 stash_group_add_boolean(group, &find_dlg.all_expanded, "find_all_expanded", FALSE);
199 stash_group_add_boolean(group, &replace_dlg.all_expanded, "replace_all_expanded", FALSE);
200 /* dialog positions */
201 stash_group_add_integer(group, &find_dlg.position[0], "position_find_x", -1);
202 stash_group_add_integer(group, &find_dlg.position[1], "position_find_y", -1);
203 stash_group_add_integer(group, &replace_dlg.position[0], "position_replace_x", -1);
204 stash_group_add_integer(group, &replace_dlg.position[1], "position_replace_y", -1);
205 stash_group_add_integer(group, &fif_dlg.position[0], "position_fif_x", -1);
206 stash_group_add_integer(group, &fif_dlg.position[1], "position_fif_y", -1);
208 memset(&settings, '\0', sizeof(settings));
210 group = stash_group_new("search");
211 fif_prefs = group;
212 configuration_add_pref_group(group, FALSE);
213 stash_group_add_toggle_button(group, &settings.fif_regexp,
214 "fif_regexp", FALSE, "check_regexp");
215 stash_group_add_toggle_button(group, &settings.fif_case_sensitive,
216 "fif_case_sensitive", TRUE, "check_case");
217 stash_group_add_toggle_button(group, &settings.fif_match_whole_word,
218 "fif_match_whole_word", FALSE, "check_wholeword");
219 stash_group_add_toggle_button(group, &settings.fif_invert_results,
220 "fif_invert_results", FALSE, "check_invert");
221 stash_group_add_toggle_button(group, &settings.fif_recursive,
222 "fif_recursive", FALSE, "check_recursive");
223 stash_group_add_entry(group, &settings.fif_extra_options,
224 "fif_extra_options", "", "entry_extra");
225 stash_group_add_toggle_button(group, &settings.fif_use_extra_options,
226 "fif_use_extra_options", FALSE, "check_extra");
227 stash_group_add_entry(group, &settings.fif_files,
228 "fif_files", "", "entry_files");
229 stash_group_add_combo_box(group, &settings.fif_files_mode,
230 "fif_files_mode", FILES_MODE_ALL, "combo_files_mode");
232 group = stash_group_new("search");
233 find_prefs = group;
234 configuration_add_pref_group(group, FALSE);
235 stash_group_add_toggle_button(group, &settings.find_regexp,
236 "find_regexp", FALSE, "check_regexp");
237 stash_group_add_toggle_button(group, &settings.find_case_sensitive,
238 "find_case_sensitive", FALSE, "check_case");
239 stash_group_add_toggle_button(group, &settings.find_escape_sequences,
240 "find_escape_sequences", FALSE, "check_escape");
241 stash_group_add_toggle_button(group, &settings.find_match_whole_word,
242 "find_match_whole_word", FALSE, "check_word");
243 stash_group_add_toggle_button(group, &settings.find_match_word_start,
244 "find_match_word_start", FALSE, "check_wordstart");
245 stash_group_add_toggle_button(group, &settings.find_close_dialog,
246 "find_close_dialog", TRUE, "check_close");
248 group = stash_group_new("search");
249 replace_prefs = group;
250 configuration_add_pref_group(group, FALSE);
251 stash_group_add_toggle_button(group, &settings.replace_regexp,
252 "replace_regexp", FALSE, "check_regexp");
253 stash_group_add_toggle_button(group, &settings.replace_case_sensitive,
254 "replace_case_sensitive", FALSE, "check_case");
255 stash_group_add_toggle_button(group, &settings.replace_escape_sequences,
256 "replace_escape_sequences", FALSE, "check_escape");
257 stash_group_add_toggle_button(group, &settings.replace_match_whole_word,
258 "replace_match_whole_word", FALSE, "check_word");
259 stash_group_add_toggle_button(group, &settings.replace_match_word_start,
260 "replace_match_word_start", FALSE, "check_wordstart");
261 stash_group_add_toggle_button(group, &settings.replace_search_backwards,
262 "replace_search_backwards", FALSE, "check_back");
263 stash_group_add_toggle_button(group, &settings.replace_close_dialog,
264 "replace_close_dialog", TRUE, "check_close");
268 void search_init(void)
270 search_data.text = NULL;
271 search_data.original_text = NULL;
272 init_prefs();
276 #define FREE_WIDGET(wid) \
277 if (wid && GTK_IS_WIDGET(wid)) gtk_widget_destroy(wid);
279 void search_finalize(void)
281 FREE_WIDGET(find_dlg.dialog);
282 FREE_WIDGET(replace_dlg.dialog);
283 FREE_WIDGET(fif_dlg.dialog);
284 g_free(search_data.text);
285 g_free(search_data.original_text);
289 static void on_widget_toggled_set_insensitive(
290 GtkToggleButton *togglebutton, gpointer user_data)
292 gtk_widget_set_sensitive(GTK_WIDGET(user_data),
293 !gtk_toggle_button_get_active(togglebutton));
297 static GtkWidget *add_find_checkboxes(GtkDialog *dialog)
299 GtkWidget *checkbox1, *checkbox2, *check_regexp, *check_back, *checkbox5,
300 *checkbox7, *hbox, *fbox, *mbox;
302 check_regexp = gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
303 ui_hookup_widget(dialog, check_regexp, "check_regexp");
304 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp), FALSE);
305 gtk_widget_set_tooltip_text(check_regexp, _("Use POSIX-like regular expressions. "
306 "For detailed information about using regular expressions, please read the documentation."));
307 g_signal_connect(check_regexp, "toggled",
308 G_CALLBACK(on_find_replace_checkbutton_toggled), dialog);
310 if (dialog != GTK_DIALOG(find_dlg.dialog))
312 check_back = gtk_check_button_new_with_mnemonic(_("Search _backwards"));
313 ui_hookup_widget(dialog, check_back, "check_back");
314 gtk_button_set_focus_on_click(GTK_BUTTON(check_back), FALSE);
316 else
317 { /* align the two checkboxes at the top of the hbox */
318 GtkSizeGroup *label_size;
319 check_back = gtk_label_new(NULL);
320 label_size = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);
321 gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size), check_back);
322 gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size), check_regexp);
323 g_object_unref(label_size);
325 checkbox7 = gtk_check_button_new_with_mnemonic(_("Use _escape sequences"));
326 ui_hookup_widget(dialog, checkbox7, "check_escape");
327 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox7), FALSE);
328 gtk_widget_set_tooltip_text(checkbox7,
329 _("Replace \\\\, \\t, \\n, \\r and \\uXXXX (Unicode chararacters) with the "
330 "corresponding control characters"));
332 /* Search features */
333 fbox = gtk_vbox_new(FALSE, 0);
334 gtk_container_add(GTK_CONTAINER(fbox), check_regexp);
335 gtk_container_add(GTK_CONTAINER(fbox), checkbox7);
336 gtk_container_add(GTK_CONTAINER(fbox), check_back);
338 checkbox1 = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
339 ui_hookup_widget(dialog, checkbox1, "check_case");
340 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1), FALSE);
342 checkbox2 = gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
343 ui_hookup_widget(dialog, checkbox2, "check_word");
344 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2), FALSE);
346 checkbox5 = gtk_check_button_new_with_mnemonic(_("Match from s_tart of word"));
347 ui_hookup_widget(dialog, checkbox5, "check_wordstart");
348 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox5), FALSE);
350 /* disable wordstart when wholeword is checked */
351 g_signal_connect(checkbox2, "toggled",
352 G_CALLBACK(on_widget_toggled_set_insensitive), checkbox5);
354 /* Matching options */
355 mbox = gtk_vbox_new(FALSE, 0);
356 gtk_container_add(GTK_CONTAINER(mbox), checkbox1);
357 gtk_container_add(GTK_CONTAINER(mbox), checkbox2);
358 gtk_container_add(GTK_CONTAINER(mbox), checkbox5);
360 hbox = gtk_hbox_new(TRUE, 6);
361 gtk_container_add(GTK_CONTAINER(hbox), fbox);
362 gtk_container_add(GTK_CONTAINER(hbox), mbox);
363 return hbox;
367 static void send_find_dialog_response(GtkButton *button, gpointer user_data)
369 gtk_dialog_response(GTK_DIALOG(find_dlg.dialog), GPOINTER_TO_INT(user_data));
373 /* store text, clear search flags so we can use Search->Find Next/Previous */
374 static void setup_find_next(const gchar *text)
376 g_free(search_data.text);
377 g_free(search_data.original_text);
378 search_data.text = g_strdup(text);
379 search_data.original_text = g_strdup(text);
380 search_data.flags = 0;
381 search_data.backwards = FALSE;
382 search_data.search_bar = FALSE;
386 /* Search for next match of the current "selection".
387 * Optionally for X11 based systems, this will try to use the system-wide
388 * x-selection first.
389 * If it doesn't find a suitable search string it will try to use
390 * the current word instead, or just repeat the last search.
391 * Search flags are always zero.
393 void search_find_selection(GeanyDocument *doc, gboolean search_backwards)
395 gchar *s = NULL;
397 g_return_if_fail(DOC_VALID(doc));
399 #ifdef G_OS_UNIX
400 if (search_prefs.find_selection_type == GEANY_FIND_SEL_X)
402 GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
404 s = gtk_clipboard_wait_for_text(clipboard);
405 if (s && (strchr(s,'\n') || strchr(s, '\r')))
407 g_free(s);
408 s = NULL;
411 #endif
413 if (!s && sci_has_selection(doc->editor->sci))
414 s = sci_get_selection_contents(doc->editor->sci);
416 if (!s && search_prefs.find_selection_type != GEANY_FIND_SEL_AGAIN)
418 /* get the current word */
419 s = editor_get_default_selection(doc->editor, TRUE, NULL);
422 if (s)
424 setup_find_next(s); /* allow find next/prev */
426 if (document_find_text(doc, s, NULL, 0, search_backwards, NULL, FALSE, NULL) > -1)
427 editor_display_current_line(doc->editor, 0.3F);
428 g_free(s);
430 else if (search_prefs.find_selection_type == GEANY_FIND_SEL_AGAIN)
432 /* Repeat last search (in case selection was lost) */
433 search_find_again(search_backwards);
435 else
437 utils_beep();
442 static void on_expander_activated(GtkExpander *exp, gpointer data)
444 gboolean *setting = data;
446 *setting = gtk_expander_get_expanded(exp);
450 static void create_find_dialog(void)
452 GtkWidget *label, *entry, *sbox, *vbox;
453 GtkWidget *exp, *bbox, *button, *check_close;
455 find_dlg.dialog = gtk_dialog_new_with_buttons(_("Find"),
456 GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT,
457 GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL, NULL);
458 vbox = ui_dialog_vbox_new(GTK_DIALOG(find_dlg.dialog));
459 gtk_widget_set_name(find_dlg.dialog, "GeanyDialogSearch");
460 gtk_box_set_spacing(GTK_BOX(vbox), 9);
462 button = ui_button_new_with_image(GTK_STOCK_GO_BACK, _("_Previous"));
463 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg.dialog), button,
464 GEANY_RESPONSE_FIND_PREVIOUS);
465 ui_hookup_widget(find_dlg.dialog, button, "btn_previous");
467 button = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, _("_Next"));
468 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg.dialog), button,
469 GEANY_RESPONSE_FIND);
471 label = gtk_label_new_with_mnemonic(_("_Search for:"));
472 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
474 entry = gtk_combo_box_text_new_with_entry();
475 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))));
476 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
477 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))), 50);
478 find_dlg.entry = gtk_bin_get_child(GTK_BIN(entry));
480 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry)), "activate",
481 G_CALLBACK(on_find_entry_activate), entry);
482 ui_entry_add_activate_backward_signal(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))));
483 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry)), "activate-backward",
484 G_CALLBACK(on_find_entry_activate_backward), entry);
485 g_signal_connect(find_dlg.dialog, "response",
486 G_CALLBACK(on_find_dialog_response), entry);
487 g_signal_connect(find_dlg.dialog, "delete-event",
488 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
490 sbox = gtk_hbox_new(FALSE, 6);
491 gtk_box_pack_start(GTK_BOX(sbox), label, FALSE, FALSE, 0);
492 gtk_box_pack_start(GTK_BOX(sbox), entry, TRUE, TRUE, 0);
493 gtk_box_pack_start(GTK_BOX(vbox), sbox, TRUE, FALSE, 0);
495 gtk_container_add(GTK_CONTAINER(vbox),
496 add_find_checkboxes(GTK_DIALOG(find_dlg.dialog)));
498 /* Now add the multiple match options */
499 exp = gtk_expander_new_with_mnemonic(_("_Find All"));
500 gtk_expander_set_expanded(GTK_EXPANDER(exp), find_dlg.all_expanded);
501 g_signal_connect_after(exp, "activate",
502 G_CALLBACK(on_expander_activated), &find_dlg.all_expanded);
504 bbox = gtk_hbutton_box_new();
506 button = gtk_button_new_with_mnemonic(_("_Mark"));
507 gtk_widget_set_tooltip_text(button,
508 _("Mark all matches in the current document"));
509 gtk_container_add(GTK_CONTAINER(bbox), button);
510 g_signal_connect(button, "clicked", G_CALLBACK(send_find_dialog_response),
511 GINT_TO_POINTER(GEANY_RESPONSE_MARK));
513 button = gtk_button_new_with_mnemonic(_("In Sessi_on"));
514 gtk_container_add(GTK_CONTAINER(bbox), button);
515 g_signal_connect(button, "clicked", G_CALLBACK(send_find_dialog_response),
516 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_SESSION));
518 button = gtk_button_new_with_mnemonic(_("_In Document"));
519 gtk_container_add(GTK_CONTAINER(bbox), button);
520 g_signal_connect(button, "clicked", G_CALLBACK(send_find_dialog_response),
521 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_FILE));
523 /* close window checkbox */
524 check_close = gtk_check_button_new_with_mnemonic(_("Close _dialog"));
525 ui_hookup_widget(find_dlg.dialog, check_close, "check_close");
526 gtk_button_set_focus_on_click(GTK_BUTTON(check_close), FALSE);
527 gtk_widget_set_tooltip_text(check_close,
528 _("Disable this option to keep the dialog open"));
529 gtk_container_add(GTK_CONTAINER(bbox), check_close);
530 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), check_close, TRUE);
532 ui_hbutton_box_copy_layout(
533 GTK_BUTTON_BOX(gtk_dialog_get_action_area(GTK_DIALOG(find_dlg.dialog))),
534 GTK_BUTTON_BOX(bbox));
535 gtk_container_add(GTK_CONTAINER(exp), bbox);
536 gtk_container_add(GTK_CONTAINER(vbox), exp);
540 static void set_dialog_position(GtkWidget *dialog, gint *position)
542 if (position[0] >= 0)
543 gtk_window_move(GTK_WINDOW(dialog), position[0], position[1]);
547 void search_show_find_dialog(void)
549 GeanyDocument *doc = document_get_current();
550 gchar *sel = NULL;
552 g_return_if_fail(doc != NULL);
554 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
556 if (find_dlg.dialog == NULL)
558 create_find_dialog();
559 stash_group_display(find_prefs, find_dlg.dialog);
560 if (sel)
561 gtk_entry_set_text(GTK_ENTRY(find_dlg.entry), sel);
563 set_dialog_position(find_dlg.dialog, find_dlg.position);
564 gtk_widget_show_all(find_dlg.dialog);
566 else
568 /* only set selection if the dialog is not already visible */
569 if (! gtk_widget_get_visible(find_dlg.dialog) && sel)
570 gtk_entry_set_text(GTK_ENTRY(find_dlg.entry), sel);
571 gtk_widget_grab_focus(find_dlg.entry);
572 set_dialog_position(find_dlg.dialog, find_dlg.position);
573 gtk_widget_show(find_dlg.dialog);
574 if (sel != NULL) /* when we have a selection, reset the entry widget's background colour */
575 ui_set_search_entry_background(find_dlg.entry, TRUE);
576 /* bring the dialog back in the foreground in case it is already open but the focus is away */
577 gtk_window_present(GTK_WINDOW(find_dlg.dialog));
580 g_free(sel);
584 static void send_replace_dialog_response(GtkButton *button, gpointer user_data)
586 gtk_dialog_response(GTK_DIALOG(replace_dlg.dialog), GPOINTER_TO_INT(user_data));
590 static gboolean
591 on_widget_key_pressed_set_focus(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
593 if (event->keyval == GDK_Tab)
595 gtk_widget_grab_focus(GTK_WIDGET(user_data));
596 return TRUE;
598 return FALSE;
602 static void create_replace_dialog(void)
604 GtkWidget *label_find, *label_replace, *entry_find, *entry_replace,
605 *check_close, *button, *rbox, *fbox, *vbox, *exp, *bbox;
606 GtkSizeGroup *label_size;
608 replace_dlg.dialog = gtk_dialog_new_with_buttons(_("Replace"),
609 GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT,
610 GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL, NULL);
611 vbox = ui_dialog_vbox_new(GTK_DIALOG(replace_dlg.dialog));
612 gtk_box_set_spacing(GTK_BOX(vbox), 9);
613 gtk_widget_set_name(replace_dlg.dialog, "GeanyDialogSearch");
615 button = gtk_button_new_from_stock(GTK_STOCK_FIND);
616 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg.dialog), button,
617 GEANY_RESPONSE_FIND);
618 button = gtk_button_new_with_mnemonic(_("_Replace"));
619 gtk_button_set_image(GTK_BUTTON(button),
620 gtk_image_new_from_stock(GTK_STOCK_FIND_AND_REPLACE, GTK_ICON_SIZE_BUTTON));
621 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg.dialog), button,
622 GEANY_RESPONSE_REPLACE);
623 button = gtk_button_new_with_mnemonic(_("Replace & Fi_nd"));
624 gtk_button_set_image(GTK_BUTTON(button),
625 gtk_image_new_from_stock(GTK_STOCK_FIND_AND_REPLACE, GTK_ICON_SIZE_BUTTON));
626 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg.dialog), button,
627 GEANY_RESPONSE_REPLACE_AND_FIND);
629 label_find = gtk_label_new_with_mnemonic(_("_Search for:"));
630 gtk_misc_set_alignment(GTK_MISC(label_find), 0, 0.5);
632 label_replace = gtk_label_new_with_mnemonic(_("Replace wit_h:"));
633 gtk_misc_set_alignment(GTK_MISC(label_replace), 0, 0.5);
635 entry_find = gtk_combo_box_text_new_with_entry();
636 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find))));
637 gtk_label_set_mnemonic_widget(GTK_LABEL(label_find), entry_find);
638 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find))), 50);
639 ui_hookup_widget(replace_dlg.dialog, entry_find, "entry_find");
640 replace_dlg.find_entry = gtk_bin_get_child(GTK_BIN(entry_find));
642 entry_replace = gtk_combo_box_text_new_with_entry();
643 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace))));
644 gtk_label_set_mnemonic_widget(GTK_LABEL(label_replace), entry_replace);
645 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace))), 50);
646 ui_hookup_widget(replace_dlg.dialog, entry_replace, "entry_replace");
647 replace_dlg.replace_entry = gtk_bin_get_child(GTK_BIN(entry_replace));
649 /* tab from find to the replace entry */
650 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_find)),
651 "key-press-event", G_CALLBACK(on_widget_key_pressed_set_focus),
652 gtk_bin_get_child(GTK_BIN(entry_replace)));
653 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_find)), "activate",
654 G_CALLBACK(on_replace_find_entry_activate), NULL);
655 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_replace)), "activate",
656 G_CALLBACK(on_replace_entry_activate), NULL);
657 g_signal_connect(replace_dlg.dialog, "response",
658 G_CALLBACK(on_replace_dialog_response), NULL);
659 g_signal_connect(replace_dlg.dialog, "delete-event",
660 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
662 fbox = gtk_hbox_new(FALSE, 6);
663 gtk_box_pack_start(GTK_BOX(fbox), label_find, FALSE, FALSE, 0);
664 gtk_box_pack_start(GTK_BOX(fbox), entry_find, TRUE, TRUE, 0);
666 rbox = gtk_hbox_new(FALSE, 6);
667 gtk_box_pack_start(GTK_BOX(rbox), label_replace, FALSE, FALSE, 0);
668 gtk_box_pack_start(GTK_BOX(rbox), entry_replace, TRUE, TRUE, 0);
670 label_size = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
671 gtk_size_group_add_widget(label_size, label_find);
672 gtk_size_group_add_widget(label_size, label_replace);
673 g_object_unref(G_OBJECT(label_size)); /* auto destroy the size group */
675 gtk_box_pack_start(GTK_BOX(vbox), fbox, TRUE, FALSE, 0);
676 gtk_box_pack_start(GTK_BOX(vbox), rbox, TRUE, FALSE, 0);
677 gtk_container_add(GTK_CONTAINER(vbox),
678 add_find_checkboxes(GTK_DIALOG(replace_dlg.dialog)));
680 /* Now add the multiple replace options */
681 exp = gtk_expander_new_with_mnemonic(_("Re_place All"));
682 gtk_expander_set_expanded(GTK_EXPANDER(exp), replace_dlg.all_expanded);
683 g_signal_connect_after(exp, "activate",
684 G_CALLBACK(on_expander_activated), &replace_dlg.all_expanded);
686 bbox = gtk_hbutton_box_new();
688 button = gtk_button_new_with_mnemonic(_("In Sessi_on"));
689 gtk_container_add(GTK_CONTAINER(bbox), button);
690 g_signal_connect(button, "clicked", G_CALLBACK(send_replace_dialog_response),
691 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SESSION));
693 button = gtk_button_new_with_mnemonic(_("_In Document"));
694 gtk_container_add(GTK_CONTAINER(bbox), button);
695 g_signal_connect(button, "clicked", G_CALLBACK(send_replace_dialog_response),
696 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_FILE));
698 button = gtk_button_new_with_mnemonic(_("In Se_lection"));
699 gtk_widget_set_tooltip_text(button,
700 _("Replace all matches found in the currently selected text"));
701 gtk_container_add(GTK_CONTAINER(bbox), button);
702 g_signal_connect(button, "clicked", G_CALLBACK(send_replace_dialog_response),
703 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SEL));
705 /* close window checkbox */
706 check_close = gtk_check_button_new_with_mnemonic(_("Close _dialog"));
707 ui_hookup_widget(replace_dlg.dialog, check_close, "check_close");
708 gtk_button_set_focus_on_click(GTK_BUTTON(check_close), FALSE);
709 gtk_widget_set_tooltip_text(check_close,
710 _("Disable this option to keep the dialog open"));
711 gtk_container_add(GTK_CONTAINER(bbox), check_close);
712 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), check_close, TRUE);
714 ui_hbutton_box_copy_layout(
715 GTK_BUTTON_BOX(gtk_dialog_get_action_area(GTK_DIALOG(replace_dlg.dialog))),
716 GTK_BUTTON_BOX(bbox));
717 gtk_container_add(GTK_CONTAINER(exp), bbox);
718 gtk_container_add(GTK_CONTAINER(vbox), exp);
722 void search_show_replace_dialog(void)
724 GeanyDocument *doc = document_get_current();
725 gchar *sel = NULL;
727 if (doc == NULL)
728 return;
730 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
732 if (replace_dlg.dialog == NULL)
734 create_replace_dialog();
735 stash_group_display(replace_prefs, replace_dlg.dialog);
736 if (sel)
737 gtk_entry_set_text(GTK_ENTRY(replace_dlg.find_entry), sel);
739 set_dialog_position(replace_dlg.dialog, replace_dlg.position);
740 gtk_widget_show_all(replace_dlg.dialog);
742 else
744 /* only set selection if the dialog is not already visible */
745 if (! gtk_widget_get_visible(replace_dlg.dialog) && sel)
746 gtk_entry_set_text(GTK_ENTRY(replace_dlg.find_entry), sel);
747 if (sel != NULL) /* when we have a selection, reset the entry widget's background colour */
748 ui_set_search_entry_background(replace_dlg.find_entry, TRUE);
749 gtk_widget_grab_focus(replace_dlg.find_entry);
750 set_dialog_position(replace_dlg.dialog, replace_dlg.position);
751 gtk_widget_show(replace_dlg.dialog);
752 /* bring the dialog back in the foreground in case it is already open but the focus is away */
753 gtk_window_present(GTK_WINDOW(replace_dlg.dialog));
756 g_free(sel);
760 static void on_widget_toggled_set_sensitive(GtkToggleButton *togglebutton, gpointer user_data)
762 /* disable extra option entry when checkbutton not checked */
763 gtk_widget_set_sensitive(GTK_WIDGET(user_data),
764 gtk_toggle_button_get_active(togglebutton));
768 static void update_file_patterns(GtkWidget *mode_combo, GtkWidget *fcombo)
770 gint selection;
771 GtkWidget *entry;
773 entry = gtk_bin_get_child(GTK_BIN(fcombo));
775 selection = gtk_combo_box_get_active(GTK_COMBO_BOX(mode_combo));
777 if (selection == FILES_MODE_ALL)
779 gtk_entry_set_text(GTK_ENTRY(entry), "");
780 gtk_widget_set_sensitive(fcombo, FALSE);
782 else if (selection == FILES_MODE_CUSTOM)
784 gtk_widget_set_sensitive(fcombo, TRUE);
786 else if (selection == FILES_MODE_PROJECT)
788 if (app->project && !EMPTY(app->project->file_patterns))
790 gchar *patterns;
792 patterns = g_strjoinv(" ", app->project->file_patterns);
793 gtk_entry_set_text(GTK_ENTRY(entry), patterns);
794 g_free(patterns);
796 else
798 gtk_entry_set_text(GTK_ENTRY(entry), "");
801 gtk_widget_set_sensitive(fcombo, FALSE);
806 /* creates the combo to choose which files include in the search */
807 static GtkWidget *create_fif_file_mode_combo(void)
809 GtkWidget *combo;
810 GtkCellRenderer *renderer;
811 GtkListStore *store;
812 GtkTreeIter iter;
814 /* text/sensitive */
815 store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_BOOLEAN);
816 gtk_list_store_append(store, &iter);
817 gtk_list_store_set(store, &iter, 0, _("all"), 1, TRUE, -1);
818 gtk_list_store_append(store, &iter);
819 gtk_list_store_set(store, &iter, 0, _("project"), 1, app->project != NULL, -1);
820 gtk_list_store_append(store, &iter);
821 gtk_list_store_set(store, &iter, 0, _("custom"), 1, TRUE, -1);
823 combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
824 g_object_unref(store);
825 gtk_widget_set_tooltip_text(combo, _("All: search all files in the directory\n"
826 "Project: use file patterns defined in the project settings\n"
827 "Custom: specify file patterns manually"));
829 renderer = gtk_cell_renderer_text_new();
830 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
831 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "text", 0, "sensitive", 1, NULL);
833 return combo;
837 /* updates the sensitivity of the project combo item */
838 static void update_fif_file_mode_combo(void)
840 GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(fif_dlg.files_mode_combo));
841 GtkTreeIter iter;
843 /* "1" refers to the second list entry, project */
844 if (gtk_tree_model_get_iter_from_string(model, &iter, "1"))
845 gtk_list_store_set(GTK_LIST_STORE(model), &iter, 1, app->project != NULL, -1);
849 static void create_fif_dialog(void)
851 GtkWidget *dir_combo, *combo, *fcombo, *e_combo, *entry;
852 GtkWidget *label, *label1, *label2, *label3, *checkbox1, *checkbox2, *check_wholeword,
853 *check_recursive, *check_extra, *entry_extra, *check_regexp, *combo_files_mode;
854 GtkWidget *dbox, *sbox, *lbox, *rbox, *hbox, *vbox, *ebox;
855 GtkSizeGroup *size_group;
856 gchar *encoding_string;
857 guint i;
859 fif_dlg.dialog = gtk_dialog_new_with_buttons(
860 _("Find in Files"), GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT,
861 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
862 vbox = ui_dialog_vbox_new(GTK_DIALOG(fif_dlg.dialog));
863 gtk_box_set_spacing(GTK_BOX(vbox), 9);
864 gtk_widget_set_name(fif_dlg.dialog, "GeanyDialogSearch");
866 gtk_dialog_add_button(GTK_DIALOG(fif_dlg.dialog), GTK_STOCK_FIND, GTK_RESPONSE_ACCEPT);
867 gtk_dialog_set_default_response(GTK_DIALOG(fif_dlg.dialog),
868 GTK_RESPONSE_ACCEPT);
870 label = gtk_label_new_with_mnemonic(_("_Search for:"));
871 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
873 combo = gtk_combo_box_text_new_with_entry();
874 entry = gtk_bin_get_child(GTK_BIN(combo));
875 ui_entry_add_clear_icon(GTK_ENTRY(entry));
876 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
877 gtk_entry_set_width_chars(GTK_ENTRY(entry), 50);
878 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
879 fif_dlg.search_combo = combo;
881 sbox = gtk_hbox_new(FALSE, 6);
882 gtk_box_pack_start(GTK_BOX(sbox), label, FALSE, FALSE, 0);
883 gtk_box_pack_start(GTK_BOX(sbox), combo, TRUE, TRUE, 0);
885 /* make labels same width */
886 size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
887 gtk_size_group_add_widget(size_group, label);
889 label3 = gtk_label_new_with_mnemonic(_("Fi_les:"));
890 gtk_misc_set_alignment(GTK_MISC(label3), 0, 0.5);
892 combo_files_mode = create_fif_file_mode_combo();
893 gtk_label_set_mnemonic_widget(GTK_LABEL(label3), combo_files_mode);
894 ui_hookup_widget(fif_dlg.dialog, combo_files_mode, "combo_files_mode");
895 fif_dlg.files_mode_combo = combo_files_mode;
897 fcombo = gtk_combo_box_text_new_with_entry();
898 entry = gtk_bin_get_child(GTK_BIN(fcombo));
899 ui_entry_add_clear_icon(GTK_ENTRY(entry));
900 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
901 gtk_widget_set_tooltip_text(entry, _("File patterns, e.g. *.c *.h"));
902 ui_hookup_widget(fif_dlg.dialog, entry, "entry_files");
903 fif_dlg.files_combo = fcombo;
905 /* update the entry when selection is changed */
906 g_signal_connect(combo_files_mode, "changed", G_CALLBACK(update_file_patterns), fcombo);
908 hbox = gtk_hbox_new(FALSE, 6);
909 gtk_box_pack_start(GTK_BOX(hbox), label3, FALSE, FALSE, 0);
910 gtk_box_pack_start(GTK_BOX(hbox), combo_files_mode, FALSE, FALSE, 0);
911 gtk_box_pack_start(GTK_BOX(hbox), fcombo, TRUE, TRUE, 0);
913 label1 = gtk_label_new_with_mnemonic(_("_Directory:"));
914 gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
916 dir_combo = gtk_combo_box_text_new_with_entry();
917 entry = gtk_bin_get_child(GTK_BIN(dir_combo));
918 ui_entry_add_clear_icon(GTK_ENTRY(entry));
919 gtk_label_set_mnemonic_widget(GTK_LABEL(label1), entry);
920 gtk_entry_set_width_chars(GTK_ENTRY(entry), 50);
921 fif_dlg.dir_combo = dir_combo;
923 /* tab from files to the dir entry */
924 g_signal_connect(gtk_bin_get_child(GTK_BIN(fcombo)), "key-press-event",
925 G_CALLBACK(on_widget_key_pressed_set_focus), entry);
927 dbox = ui_path_box_new(NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
928 GTK_ENTRY(entry));
929 gtk_box_pack_start(GTK_BOX(dbox), label1, FALSE, FALSE, 0);
931 label2 = gtk_label_new_with_mnemonic(_("E_ncoding:"));
932 gtk_misc_set_alignment(GTK_MISC(label2), 0, 0.5);
934 e_combo = gtk_combo_box_text_new();
935 for (i = 0; i < GEANY_ENCODINGS_MAX; i++)
937 encoding_string = encodings_to_string(&encodings[i]);
938 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(e_combo), encoding_string);
939 g_free(encoding_string);
941 gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(e_combo), 3);
942 gtk_label_set_mnemonic_widget(GTK_LABEL(label2), e_combo);
943 fif_dlg.encoding_combo = e_combo;
945 ebox = gtk_hbox_new(FALSE, 6);
946 gtk_box_pack_start(GTK_BOX(ebox), label2, FALSE, FALSE, 0);
947 gtk_box_pack_start(GTK_BOX(ebox), e_combo, TRUE, TRUE, 0);
949 gtk_size_group_add_widget(size_group, label1);
950 gtk_size_group_add_widget(size_group, label2);
951 gtk_size_group_add_widget(size_group, label3);
952 g_object_unref(G_OBJECT(size_group)); /* auto destroy the size group */
954 gtk_box_pack_start(GTK_BOX(vbox), sbox, TRUE, FALSE, 0);
955 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 0);
956 gtk_box_pack_start(GTK_BOX(vbox), dbox, TRUE, FALSE, 0);
957 gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
959 check_regexp = gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
960 ui_hookup_widget(fif_dlg.dialog, check_regexp, "check_regexp");
961 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp), FALSE);
962 gtk_widget_set_tooltip_text(check_regexp, _("See grep's manual page for more information"));
964 check_recursive = gtk_check_button_new_with_mnemonic(_("_Recurse in subfolders"));
965 ui_hookup_widget(fif_dlg.dialog, check_recursive, "check_recursive");
966 gtk_button_set_focus_on_click(GTK_BUTTON(check_recursive), FALSE);
968 checkbox1 = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
969 ui_hookup_widget(fif_dlg.dialog, checkbox1, "check_case");
970 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1), FALSE);
971 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox1), TRUE);
973 check_wholeword = gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
974 ui_hookup_widget(fif_dlg.dialog, check_wholeword, "check_wholeword");
975 gtk_button_set_focus_on_click(GTK_BUTTON(check_wholeword), FALSE);
977 checkbox2 = gtk_check_button_new_with_mnemonic(_("_Invert search results"));
978 ui_hookup_widget(fif_dlg.dialog, checkbox2, "check_invert");
979 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2), FALSE);
980 gtk_widget_set_tooltip_text(checkbox2,
981 _("Invert the sense of matching, to select non-matching lines"));
983 lbox = gtk_vbox_new(FALSE, 0);
984 gtk_container_add(GTK_CONTAINER(lbox), check_regexp);
985 gtk_container_add(GTK_CONTAINER(lbox), checkbox2);
986 gtk_container_add(GTK_CONTAINER(lbox), check_recursive);
988 rbox = gtk_vbox_new(FALSE, 0);
989 gtk_container_add(GTK_CONTAINER(rbox), checkbox1);
990 gtk_container_add(GTK_CONTAINER(rbox), check_wholeword);
991 gtk_container_add(GTK_CONTAINER(rbox), gtk_label_new(NULL));
993 hbox = gtk_hbox_new(FALSE, 6);
994 gtk_container_add(GTK_CONTAINER(hbox), lbox);
995 gtk_container_add(GTK_CONTAINER(hbox), rbox);
996 gtk_container_add(GTK_CONTAINER(vbox), hbox);
998 check_extra = gtk_check_button_new_with_mnemonic(_("E_xtra options:"));
999 ui_hookup_widget(fif_dlg.dialog, check_extra, "check_extra");
1000 gtk_button_set_focus_on_click(GTK_BUTTON(check_extra), FALSE);
1002 entry_extra = gtk_entry_new();
1003 ui_entry_add_clear_icon(GTK_ENTRY(entry_extra));
1004 gtk_widget_set_sensitive(entry_extra, FALSE);
1005 gtk_widget_set_tooltip_text(entry_extra, _("Other options to pass to Grep"));
1006 ui_hookup_widget(fif_dlg.dialog, entry_extra, "entry_extra");
1008 /* enable entry_extra when check_extra is checked */
1009 g_signal_connect(check_extra, "toggled",
1010 G_CALLBACK(on_widget_toggled_set_sensitive), entry_extra);
1012 hbox = gtk_hbox_new(FALSE, 6);
1013 gtk_box_pack_start(GTK_BOX(hbox), check_extra, FALSE, FALSE, 0);
1014 gtk_box_pack_start(GTK_BOX(hbox), entry_extra, TRUE, TRUE, 0);
1015 gtk_container_add(GTK_CONTAINER(vbox), hbox);
1017 g_signal_connect(fif_dlg.dialog, "response",
1018 G_CALLBACK(on_find_in_files_dialog_response), NULL);
1019 g_signal_connect(fif_dlg.dialog, "delete-event",
1020 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1025 * Shows the Find in Files dialog.
1027 * @param dir The directory to search in (UTF-8 encoding). May be @c NULL
1028 * to determine it the usual way by using the current document's path.
1030 * @since 0.14, plugin API 53
1032 void search_show_find_in_files_dialog(const gchar *dir)
1034 search_show_find_in_files_dialog_full(NULL, dir);
1038 void search_show_find_in_files_dialog_full(const gchar *text, const gchar *dir)
1040 GtkWidget *entry; /* for child GtkEntry of a GtkComboBoxEntry */
1041 GeanyDocument *doc = document_get_current();
1042 gchar *sel = NULL;
1043 gchar *cur_dir = NULL;
1044 GeanyEncodingIndex enc_idx = GEANY_ENCODING_UTF_8;
1046 if (fif_dlg.dialog == NULL)
1048 create_fif_dialog();
1049 gtk_widget_show_all(fif_dlg.dialog);
1050 if (doc && !text)
1051 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
1053 stash_group_display(fif_prefs, fif_dlg.dialog);
1055 if (!text)
1057 /* only set selection if the dialog is not already visible, or has just been created */
1058 if (doc && ! sel && ! gtk_widget_get_visible(fif_dlg.dialog))
1059 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
1061 text = sel;
1063 entry = gtk_bin_get_child(GTK_BIN(fif_dlg.search_combo));
1064 if (text)
1065 gtk_entry_set_text(GTK_ENTRY(entry), text);
1066 g_free(sel);
1068 /* add project's base path directory to the dir list, we do this here once
1069 * (in create_fif_dialog() it would fail if a project is opened after dialog creation) */
1070 if (app->project != NULL && !EMPTY(app->project->base_path))
1072 ui_combo_box_prepend_text_once(GTK_COMBO_BOX_TEXT(fif_dlg.dir_combo),
1073 app->project->base_path);
1076 entry = gtk_bin_get_child(GTK_BIN(fif_dlg.dir_combo));
1077 if (!EMPTY(dir))
1078 cur_dir = g_strdup(dir); /* custom directory argument passed */
1079 else
1081 if (search_prefs.use_current_file_dir)
1083 static gchar *last_cur_dir = NULL;
1084 static GeanyDocument *last_doc = NULL;
1086 /* Only set the directory entry once for the current document */
1087 cur_dir = utils_get_current_file_dir_utf8();
1088 if (doc == last_doc && cur_dir && utils_str_equal(cur_dir, last_cur_dir))
1090 /* in case the user now wants the current directory, add it to history */
1091 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(fif_dlg.dir_combo), cur_dir, 0);
1092 SETPTR(cur_dir, NULL);
1094 else
1095 SETPTR(last_cur_dir, g_strdup(cur_dir));
1097 last_doc = doc;
1099 if (!cur_dir && EMPTY(gtk_entry_get_text(GTK_ENTRY(entry))))
1101 /* use default_open_path if no directory could be determined
1102 * (e.g. when no files are open) */
1103 if (!cur_dir)
1104 cur_dir = g_strdup(utils_get_default_dir_utf8());
1105 if (!cur_dir)
1106 cur_dir = g_get_current_dir();
1109 if (cur_dir)
1111 gtk_entry_set_text(GTK_ENTRY(entry), cur_dir);
1112 g_free(cur_dir);
1115 update_fif_file_mode_combo();
1116 update_file_patterns(fif_dlg.files_mode_combo, fif_dlg.files_combo);
1118 /* set the encoding of the current file */
1119 if (doc != NULL)
1120 enc_idx = encodings_get_idx_from_charset(doc->encoding);
1121 gtk_combo_box_set_active(GTK_COMBO_BOX(fif_dlg.encoding_combo), enc_idx);
1123 /* put the focus to the directory entry if it is empty */
1124 if (utils_str_equal(gtk_entry_get_text(GTK_ENTRY(entry)), ""))
1125 gtk_widget_grab_focus(fif_dlg.dir_combo);
1126 else
1127 gtk_widget_grab_focus(fif_dlg.search_combo);
1129 /* set dialog window position */
1130 set_dialog_position(fif_dlg.dialog, fif_dlg.position);
1132 gtk_widget_show(fif_dlg.dialog);
1133 /* bring the dialog back in the foreground in case it is already open but the focus is away */
1134 gtk_window_present(GTK_WINDOW(fif_dlg.dialog));
1138 static void
1139 on_find_replace_checkbutton_toggled(GtkToggleButton *togglebutton, gpointer user_data)
1141 GtkWidget *dialog = GTK_WIDGET(user_data);
1142 GtkToggleButton *chk_regexp = GTK_TOGGLE_BUTTON(
1143 ui_lookup_widget(dialog, "check_regexp"));
1145 if (togglebutton == chk_regexp)
1147 gboolean regex_set = gtk_toggle_button_get_active(chk_regexp);
1148 GtkWidget *check_word = ui_lookup_widget(dialog, "check_word");
1149 GtkWidget *check_wordstart = ui_lookup_widget(dialog, "check_wordstart");
1150 GtkWidget *check_escape = ui_lookup_widget(dialog, "check_escape");
1151 gboolean replace = (dialog != find_dlg.dialog);
1152 const char *back_button[2] = { "btn_previous" , "check_back" };
1154 /* hide options that don't apply to regex searches */
1155 gtk_widget_set_sensitive(check_escape, ! regex_set);
1156 gtk_widget_set_sensitive(ui_lookup_widget(dialog, back_button[replace]), ! regex_set);
1157 gtk_widget_set_sensitive(check_word, ! regex_set);
1158 gtk_widget_set_sensitive(check_wordstart, ! regex_set);
1163 static GeanyMatchInfo *match_info_new(gint flags, gint start, gint end)
1165 GeanyMatchInfo *info = g_slice_alloc(sizeof *info);
1167 info->flags = flags;
1168 info->start = start;
1169 info->end = end;
1170 info->match_text = NULL;
1172 return info;
1175 void geany_match_info_free(GeanyMatchInfo *info)
1177 g_free(info->match_text);
1178 g_slice_free1(sizeof *info, info);
1182 /* find all in the given range.
1183 * Returns a list of allocated GeanyMatchInfo, should be freed using:
1185 * foreach_slist(node, matches)
1186 * geany_match_info_free(node->data);
1187 * g_slist_free(matches); */
1188 static GSList *find_range(ScintillaObject *sci, gint flags, struct Sci_TextToFind *ttf)
1190 GSList *matches = NULL;
1191 GeanyMatchInfo *info;
1193 g_return_val_if_fail(sci != NULL && ttf->lpstrText != NULL, NULL);
1194 if (! *ttf->lpstrText)
1195 return NULL;
1197 while (search_find_text(sci, flags, ttf, &info) != -1)
1199 if (ttf->chrgText.cpMax > ttf->chrg.cpMax)
1201 /* found text is partially out of range */
1202 geany_match_info_free(info);
1203 break;
1206 matches = g_slist_prepend(matches, info);
1207 ttf->chrg.cpMin = ttf->chrgText.cpMax;
1209 /* avoid rematching with empty matches like "(?=[a-z])" or "^$".
1210 * note we cannot assume a match will always be empty or not and then break out, since
1211 * matches like "a?(?=b)" will sometimes be empty and sometimes not */
1212 if (ttf->chrgText.cpMax == ttf->chrgText.cpMin)
1213 ttf->chrg.cpMin ++;
1216 return g_slist_reverse(matches);
1220 /* Clears markers if text is null/empty.
1221 * @return Number of matches marked. */
1222 gint search_mark_all(GeanyDocument *doc, const gchar *search_text, gint flags)
1224 gint count = 0;
1225 struct Sci_TextToFind ttf;
1226 GSList *match, *matches;
1228 g_return_val_if_fail(DOC_VALID(doc), 0);
1230 /* clear previous search indicators */
1231 editor_indicator_clear(doc->editor, GEANY_INDICATOR_SEARCH);
1233 if (G_UNLIKELY(EMPTY(search_text)))
1234 return 0;
1236 ttf.chrg.cpMin = 0;
1237 ttf.chrg.cpMax = sci_get_length(doc->editor->sci);
1238 ttf.lpstrText = (gchar *)search_text;
1240 matches = find_range(doc->editor->sci, flags, &ttf);
1241 foreach_slist (match, matches)
1243 GeanyMatchInfo *info = match->data;
1245 if (info->end != info->start)
1246 editor_indicator_set_on_range(doc->editor, GEANY_INDICATOR_SEARCH, info->start, info->end);
1247 count++;
1249 geany_match_info_free(info);
1251 g_slist_free(matches);
1253 return count;
1257 static void
1258 on_find_entry_activate(GtkEntry *entry, gpointer user_data)
1260 on_find_dialog_response(NULL, GEANY_RESPONSE_FIND, user_data);
1264 static void
1265 on_find_entry_activate_backward(GtkEntry *entry, gpointer user_data)
1267 /* can't search backwards with a regexp */
1268 if (search_data.flags & SCFIND_REGEXP)
1269 utils_beep();
1270 else
1271 on_find_dialog_response(NULL, GEANY_RESPONSE_FIND_PREVIOUS, user_data);
1275 static gboolean int_search_flags(gint match_case, gint whole_word, gint regexp, gint word_start)
1277 return (match_case ? SCFIND_MATCHCASE : 0) |
1278 (regexp ? SCFIND_REGEXP | SCFIND_POSIX : 0) |
1279 (whole_word ? SCFIND_WHOLEWORD : 0) |
1280 /* SCFIND_WORDSTART overrides SCFIND_WHOLEWORD, but we want the opposite */
1281 (word_start && !whole_word ? SCFIND_WORDSTART : 0);
1285 static void
1286 on_find_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
1288 gtk_window_get_position(GTK_WINDOW(find_dlg.dialog),
1289 &find_dlg.position[0], &find_dlg.position[1]);
1291 stash_group_update(find_prefs, find_dlg.dialog);
1293 if (response == GTK_RESPONSE_CANCEL || response == GTK_RESPONSE_DELETE_EVENT)
1294 gtk_widget_hide(find_dlg.dialog);
1295 else
1297 GeanyDocument *doc = document_get_current();
1298 gboolean check_close = settings.find_close_dialog;
1300 if (doc == NULL)
1301 return;
1303 search_data.backwards = FALSE;
1304 search_data.search_bar = FALSE;
1306 g_free(search_data.text);
1307 g_free(search_data.original_text);
1308 search_data.text = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(user_data)))));
1309 search_data.original_text = g_strdup(search_data.text);
1310 search_data.flags = int_search_flags(settings.find_case_sensitive,
1311 settings.find_match_whole_word, settings.find_regexp, settings.find_match_word_start);
1313 if (EMPTY(search_data.text))
1315 fail:
1316 utils_beep();
1317 gtk_widget_grab_focus(find_dlg.entry);
1318 return;
1320 if (search_data.flags & SCFIND_REGEXP)
1322 GRegex *regex = compile_regex(search_data.text, search_data.flags);
1323 if (!regex)
1324 goto fail;
1325 else
1326 g_regex_unref(regex);
1328 else if (settings.find_escape_sequences)
1330 if (! utils_str_replace_escape(search_data.text, FALSE))
1331 goto fail;
1333 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(user_data), search_data.original_text, 0);
1335 switch (response)
1337 case GEANY_RESPONSE_FIND:
1338 case GEANY_RESPONSE_FIND_PREVIOUS:
1340 gint result = document_find_text(doc, search_data.text, search_data.original_text, search_data.flags,
1341 (response == GEANY_RESPONSE_FIND_PREVIOUS), NULL, TRUE, GTK_WIDGET(find_dlg.dialog));
1342 ui_set_search_entry_background(find_dlg.entry, (result > -1));
1343 check_close = search_prefs.hide_find_dialog;
1344 break;
1346 case GEANY_RESPONSE_FIND_IN_FILE:
1347 search_find_usage(search_data.text, search_data.original_text, search_data.flags, FALSE);
1348 break;
1350 case GEANY_RESPONSE_FIND_IN_SESSION:
1351 search_find_usage(search_data.text, search_data.original_text, search_data.flags, TRUE);
1352 break;
1354 case GEANY_RESPONSE_MARK:
1356 gint count = search_mark_all(doc, search_data.text, search_data.flags);
1358 if (count == 0)
1359 ui_set_statusbar(FALSE, _("No matches found for \"%s\"."), search_data.original_text);
1360 else
1361 ui_set_statusbar(FALSE,
1362 ngettext("Found %d match for \"%s\".",
1363 "Found %d matches for \"%s\".", count),
1364 count, search_data.original_text);
1366 break;
1368 if (check_close)
1369 gtk_widget_hide(find_dlg.dialog);
1374 static void
1375 on_replace_find_entry_activate(GtkEntry *entry, gpointer user_data)
1377 on_replace_dialog_response(NULL, GEANY_RESPONSE_FIND, NULL);
1381 static void
1382 on_replace_entry_activate(GtkEntry *entry, gpointer user_data)
1384 on_replace_dialog_response(NULL, GEANY_RESPONSE_REPLACE, NULL);
1388 static void replace_in_session(GeanyDocument *doc,
1389 gint search_flags_re, gboolean search_replace_escape_re,
1390 const gchar *find, const gchar *replace,
1391 const gchar *original_find, const gchar *original_replace)
1393 guint n, page_count, rep_count = 0, file_count = 0;
1395 /* replace in all documents following notebook tab order */
1396 page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
1397 for (n = 0; n < page_count; n++)
1399 GeanyDocument *tmp_doc = document_get_from_page(n);
1400 gint reps = 0;
1402 reps = document_replace_all(tmp_doc, find, replace, original_find, original_replace, search_flags_re);
1403 rep_count += reps;
1404 if (reps)
1405 file_count++;
1407 if (file_count == 0)
1409 utils_beep();
1410 ui_set_statusbar(FALSE, _("No matches found for \"%s\"."), original_find);
1411 return;
1413 /* if only one file was changed, don't override that document's status message
1414 * so we don't have to translate 4 messages for ngettext */
1415 if (file_count > 1)
1416 ui_set_statusbar(FALSE, _("Replaced %u matches in %u documents."),
1417 rep_count, file_count);
1419 /* show which docs had replacements: */
1420 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_STATUS);
1422 ui_save_buttons_toggle(doc->changed); /* update save all */
1426 static void
1427 on_replace_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
1429 GeanyDocument *doc = document_get_current();
1430 gint search_flags_re;
1431 gboolean search_backwards_re, search_replace_escape_re;
1432 gchar *find, *replace, *original_find = NULL, *original_replace = NULL;
1434 gtk_window_get_position(GTK_WINDOW(replace_dlg.dialog),
1435 &replace_dlg.position[0], &replace_dlg.position[1]);
1437 stash_group_update(replace_prefs, replace_dlg.dialog);
1439 if (response == GTK_RESPONSE_CANCEL || response == GTK_RESPONSE_DELETE_EVENT)
1441 gtk_widget_hide(replace_dlg.dialog);
1442 return;
1445 search_backwards_re = settings.replace_search_backwards;
1446 search_replace_escape_re = settings.replace_escape_sequences;
1447 find = g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg.find_entry)));
1448 replace = g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg.replace_entry)));
1450 search_flags_re = int_search_flags(settings.replace_case_sensitive,
1451 settings.replace_match_whole_word, settings.replace_regexp,
1452 settings.replace_match_word_start);
1454 if ((response != GEANY_RESPONSE_FIND) && (search_flags_re & SCFIND_MATCHCASE)
1455 && (strcmp(find, replace) == 0))
1456 goto fail;
1458 original_find = g_strdup(find);
1459 original_replace = g_strdup(replace);
1461 if (search_flags_re & SCFIND_REGEXP)
1463 GRegex *regex = compile_regex(find, search_flags_re);
1464 if (regex)
1465 g_regex_unref(regex);
1466 /* find escapes will be handled by GRegex */
1467 if (!regex || !utils_str_replace_escape(replace, TRUE))
1468 goto fail;
1470 else if (search_replace_escape_re)
1472 if (! utils_str_replace_escape(find, FALSE) ||
1473 ! utils_str_replace_escape(replace, FALSE))
1474 goto fail;
1477 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(
1478 gtk_widget_get_parent(replace_dlg.find_entry)), original_find, 0);
1479 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(
1480 gtk_widget_get_parent(replace_dlg.replace_entry)), original_replace, 0);
1482 switch (response)
1484 case GEANY_RESPONSE_REPLACE_AND_FIND:
1486 gint rep = document_replace_text(doc, find, original_find, replace, search_flags_re,
1487 search_backwards_re);
1488 if (rep != -1)
1489 document_find_text(doc, find, original_find, search_flags_re, search_backwards_re,
1490 NULL, TRUE, NULL);
1491 break;
1493 case GEANY_RESPONSE_REPLACE:
1494 document_replace_text(doc, find, original_find, replace, search_flags_re,
1495 search_backwards_re);
1496 break;
1498 case GEANY_RESPONSE_FIND:
1500 gint result = document_find_text(doc, find, original_find, search_flags_re,
1501 search_backwards_re, NULL, TRUE, GTK_WIDGET(dialog));
1502 ui_set_search_entry_background(replace_dlg.find_entry, (result > -1));
1503 break;
1505 case GEANY_RESPONSE_REPLACE_IN_FILE:
1506 if (! document_replace_all(doc, find, replace, original_find, original_replace, search_flags_re))
1507 utils_beep();
1508 break;
1510 case GEANY_RESPONSE_REPLACE_IN_SESSION:
1511 replace_in_session(doc, search_flags_re, search_replace_escape_re, find, replace, original_find, original_replace);
1512 break;
1514 case GEANY_RESPONSE_REPLACE_IN_SEL:
1515 document_replace_sel(doc, find, replace, original_find, original_replace, search_flags_re);
1516 break;
1518 switch (response)
1520 case GEANY_RESPONSE_REPLACE_IN_SEL:
1521 case GEANY_RESPONSE_REPLACE_IN_FILE:
1522 case GEANY_RESPONSE_REPLACE_IN_SESSION:
1523 if (settings.replace_close_dialog)
1524 gtk_widget_hide(replace_dlg.dialog);
1526 g_free(find);
1527 g_free(replace);
1528 g_free(original_find);
1529 g_free(original_replace);
1530 return;
1532 fail:
1533 utils_beep();
1534 gtk_widget_grab_focus(replace_dlg.find_entry);
1535 g_free(find);
1536 g_free(replace);
1537 g_free(original_find);
1538 g_free(original_replace);
1542 static GString *get_grep_options(void)
1544 GString *gstr = g_string_new("-nHI"); /* line numbers, filenames, ignore binaries */
1546 if (settings.fif_invert_results)
1547 g_string_append_c(gstr, 'v');
1548 if (! settings.fif_case_sensitive)
1549 g_string_append_c(gstr, 'i');
1550 if (settings.fif_match_whole_word)
1551 g_string_append_c(gstr, 'w');
1552 if (settings.fif_recursive)
1553 g_string_append_c(gstr, 'r');
1555 if (!settings.fif_regexp)
1556 g_string_append_c(gstr, 'F');
1557 else
1558 g_string_append_c(gstr, 'E');
1560 if (settings.fif_use_extra_options)
1562 g_strstrip(settings.fif_extra_options);
1564 if (*settings.fif_extra_options != 0)
1566 g_string_append_c(gstr, ' ');
1567 g_string_append(gstr, settings.fif_extra_options);
1570 g_strstrip(settings.fif_files);
1571 if (settings.fif_files_mode != FILES_MODE_ALL && *settings.fif_files)
1573 GString *tmp;
1575 /* put --include= before each pattern */
1576 tmp = g_string_new(settings.fif_files);
1577 do {} while (utils_string_replace_all(tmp, " ", " "));
1578 g_string_prepend_c(tmp, ' ');
1579 utils_string_replace_all(tmp, " ", " --include=");
1580 g_string_append(gstr, tmp->str);
1581 g_string_free(tmp, TRUE);
1583 return gstr;
1587 static void
1588 on_find_in_files_dialog_response(GtkDialog *dialog, gint response,
1589 G_GNUC_UNUSED gpointer user_data)
1591 gtk_window_get_position(GTK_WINDOW(fif_dlg.dialog), &fif_dlg.position[0], &fif_dlg.position[1]);
1593 stash_group_update(fif_prefs, fif_dlg.dialog);
1595 if (response == GTK_RESPONSE_ACCEPT)
1597 GtkWidget *search_combo = fif_dlg.search_combo;
1598 const gchar *search_text =
1599 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(search_combo))));
1600 GtkWidget *dir_combo = fif_dlg.dir_combo;
1601 const gchar *utf8_dir =
1602 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(dir_combo))));
1603 GeanyEncodingIndex enc_idx = gtk_combo_box_get_active(
1604 GTK_COMBO_BOX(fif_dlg.encoding_combo));
1606 if (G_UNLIKELY(EMPTY(utf8_dir)))
1607 ui_set_statusbar(FALSE, _("Invalid directory for find in files."));
1608 else if (!EMPTY(search_text))
1610 gchar *locale_dir;
1611 GString *opts = get_grep_options();
1612 const gchar *enc = (enc_idx == GEANY_ENCODING_UTF_8) ? NULL :
1613 encodings_get_charset_from_index(enc_idx);
1615 locale_dir = utils_get_locale_from_utf8(utf8_dir);
1617 if (search_find_in_files(search_text, locale_dir, opts->str, enc))
1619 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(search_combo), search_text, 0);
1620 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(fif_dlg.files_combo), NULL, 0);
1621 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(dir_combo), utf8_dir, 0);
1622 gtk_widget_hide(fif_dlg.dialog);
1624 g_free(locale_dir);
1625 g_string_free(opts, TRUE);
1627 else
1628 ui_set_statusbar(FALSE, _("No text to find."));
1630 else
1631 gtk_widget_hide(fif_dlg.dialog);
1635 static gboolean
1636 search_find_in_files(const gchar *utf8_search_text, const gchar *dir, const gchar *opts,
1637 const gchar *enc)
1639 gchar **argv_prefix, **argv, **opts_argv;
1640 gchar *command_grep;
1641 gchar *search_text = NULL;
1642 gint opts_argv_len, i;
1643 GPid child_pid;
1644 gint stdout_fd;
1645 gint stderr_fd;
1646 GError *error = NULL;
1647 gboolean ret = FALSE;
1648 gssize utf8_text_len;
1650 if (EMPTY(utf8_search_text) || ! dir) return TRUE;
1652 command_grep = g_find_program_in_path(tool_prefs.grep_cmd);
1653 if (command_grep == NULL)
1655 ui_set_statusbar(TRUE, _("Cannot execute grep tool '%s';"
1656 " check the path setting in Preferences."), tool_prefs.grep_cmd);
1657 return FALSE;
1660 if (! g_shell_parse_argv(opts, &opts_argv_len, &opts_argv, &error))
1662 ui_set_statusbar(TRUE, _("Cannot parse extra options: %s"), error->message);
1663 g_error_free(error);
1664 g_free(command_grep);
1665 return FALSE;
1668 /* convert the search text in the preferred encoding (if the text is not valid UTF-8. assume
1669 * it is already in the preferred encoding) */
1670 utf8_text_len = strlen(utf8_search_text);
1671 if (enc != NULL && g_utf8_validate(utf8_search_text, utf8_text_len, NULL))
1673 search_text = g_convert(utf8_search_text, utf8_text_len, enc, "UTF-8", NULL, NULL, NULL);
1675 if (search_text == NULL)
1676 search_text = g_strdup(utf8_search_text);
1678 /* set grep command and options */
1679 argv_prefix = g_new0(gchar*, 1 + opts_argv_len + 3 + 1); /* last +1 for recursive arg */
1681 argv_prefix[0] = command_grep;
1682 for (i = 0; i < opts_argv_len; i++)
1684 argv_prefix[i + 1] = g_strdup(opts_argv[i]);
1686 g_strfreev(opts_argv);
1688 i++; /* correct for tool_prefs.grep_cmd */
1689 argv_prefix[i++] = g_strdup("--");
1690 argv_prefix[i++] = search_text;
1692 /* finally add the arguments(files to be searched) */
1693 if (strstr(argv_prefix[1], "r")) /* recursive option set */
1695 /* Use '.' so we get relative paths in the output */
1696 argv_prefix[i++] = g_strdup(".");
1697 argv_prefix[i++] = NULL;
1698 argv = argv_prefix;
1700 else
1702 argv_prefix[i++] = NULL;
1703 argv = search_get_argv((const gchar**)argv_prefix, dir);
1704 g_strfreev(argv_prefix);
1707 if (argv == NULL) /* no files */
1709 return FALSE;
1712 gtk_list_store_clear(msgwindow.store_msg);
1713 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_MESSAGE);
1715 if (! g_spawn_async_with_pipes(dir, (gchar**)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
1716 NULL, NULL, &child_pid,
1717 NULL, &stdout_fd, &stderr_fd, &error))
1719 geany_debug("%s: g_spawn_async_with_pipes() failed: %s", G_STRFUNC, error->message);
1720 ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message);
1721 g_error_free(error);
1722 ret = FALSE;
1724 else
1726 gchar *str, *utf8_str;
1728 ui_progress_bar_start(_("Searching..."));
1730 msgwin_set_messages_dir(dir);
1731 /* we can pass 'enc' without strdup'ing it here because it's a global const string and
1732 * always exits longer than the lifetime of this function */
1733 utils_set_up_io_channel(stdout_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
1734 TRUE, search_read_io, (gpointer) enc);
1735 utils_set_up_io_channel(stderr_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
1736 TRUE, search_read_io_stderr, (gpointer) enc);
1737 g_child_watch_add(child_pid, search_close_pid, NULL);
1739 str = g_strdup_printf(_("%s %s -- %s (in directory: %s)"),
1740 tool_prefs.grep_cmd, opts, utf8_search_text, dir);
1741 utf8_str = utils_get_utf8_from_locale(str);
1742 msgwin_msg_add_string(COLOR_BLUE, -1, NULL, utf8_str);
1743 utils_free_pointers(2, str, utf8_str, NULL);
1744 ret = TRUE;
1746 g_strfreev(argv);
1747 return ret;
1751 static gboolean pattern_list_match(GSList *patterns, const gchar *str)
1753 GSList *item;
1755 foreach_slist(item, patterns)
1757 if (g_pattern_match_string(item->data, str))
1758 return TRUE;
1760 return FALSE;
1764 /* Creates an argument vector of strings, copying argv_prefix[] values for
1765 * the first arguments, then followed by filenames found in dir.
1766 * Returns NULL if no files were found, otherwise returned vector should be fully freed. */
1767 static gchar **search_get_argv(const gchar **argv_prefix, const gchar *dir)
1769 guint prefix_len, list_len, i, j;
1770 gchar **argv;
1771 GSList *list, *item, *patterns = NULL;
1772 GError *error = NULL;
1774 g_return_val_if_fail(dir != NULL, NULL);
1776 prefix_len = g_strv_length((gchar**)argv_prefix);
1777 list = utils_get_file_list(dir, &list_len, &error);
1778 if (error)
1780 ui_set_statusbar(TRUE, _("Could not open directory (%s)"), error->message);
1781 g_error_free(error);
1782 return NULL;
1784 if (list == NULL)
1785 return NULL;
1787 argv = g_new(gchar*, prefix_len + list_len + 1);
1789 for (i = 0, j = 0; i < prefix_len; i++)
1791 if (g_str_has_prefix(argv_prefix[i], "--include="))
1793 const gchar *pat = &(argv_prefix[i][10]); /* the pattern part of the argument */
1795 patterns = g_slist_prepend(patterns, g_pattern_spec_new(pat));
1797 else
1798 argv[j++] = g_strdup(argv_prefix[i]);
1801 if (patterns)
1803 GSList *pat;
1805 foreach_slist(item, list)
1807 if (pattern_list_match(patterns, item->data))
1808 argv[j++] = item->data;
1809 else
1810 g_free(item->data);
1812 foreach_slist(pat, patterns)
1813 g_pattern_spec_free(pat->data);
1814 g_slist_free(patterns);
1816 else
1818 foreach_slist(item, list)
1819 argv[j++] = item->data;
1822 argv[j] = NULL;
1823 g_slist_free(list);
1824 return argv;
1828 static gboolean read_fif_io(GIOChannel *source, GIOCondition condition, gchar *enc, gint msg_color)
1830 if (condition & (G_IO_IN | G_IO_PRI))
1832 gchar *msg, *utf8_msg;
1834 while (g_io_channel_read_line(source, &msg, NULL, NULL, NULL) && msg)
1836 utf8_msg = NULL;
1838 g_strstrip(msg);
1839 /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */
1840 if (enc != NULL)
1842 if (! g_utf8_validate(msg, -1, NULL))
1844 utf8_msg = g_convert(msg, -1, "UTF-8", enc, NULL, NULL, NULL);
1846 if (utf8_msg == NULL)
1847 utf8_msg = msg;
1849 else
1850 utf8_msg = msg;
1852 msgwin_msg_add_string(msg_color, -1, NULL, utf8_msg);
1854 if (utf8_msg != msg)
1855 g_free(utf8_msg);
1856 g_free(msg);
1859 if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
1860 return FALSE;
1862 return TRUE;
1866 static gboolean search_read_io(GIOChannel *source, GIOCondition condition, gpointer data)
1868 return read_fif_io(source, condition, data, COLOR_BLACK);
1872 static gboolean search_read_io_stderr(GIOChannel *source, GIOCondition condition, gpointer data)
1874 return read_fif_io(source, condition, data, COLOR_DARK_RED);
1878 static void search_close_pid(GPid child_pid, gint status, gpointer user_data)
1880 const gchar *msg = _("Search failed.");
1881 #ifdef G_OS_UNIX
1882 gint exit_status = 1;
1884 if (WIFEXITED(status))
1886 exit_status = WEXITSTATUS(status);
1888 else if (WIFSIGNALED(status))
1890 exit_status = -1;
1891 g_warning("Find in Files: The command failed unexpectedly (signal received).");
1893 #else
1894 gint exit_status = status;
1895 #endif
1897 switch (exit_status)
1899 case 0:
1901 gint count = gtk_tree_model_iter_n_children(
1902 GTK_TREE_MODEL(msgwindow.store_msg), NULL) - 1;
1903 gchar *text = ngettext(
1904 "Search completed with %d match.",
1905 "Search completed with %d matches.", count);
1907 msgwin_msg_add(COLOR_BLUE, -1, NULL, text, count);
1908 ui_set_statusbar(FALSE, text, count);
1909 break;
1911 case 1:
1912 msg = _("No matches found.");
1913 default:
1914 msgwin_msg_add_string(COLOR_BLUE, -1, NULL, msg);
1915 ui_set_statusbar(FALSE, "%s", msg);
1916 break;
1918 utils_beep();
1919 g_spawn_close_pid(child_pid);
1920 ui_progress_bar_stop();
1924 static GRegex *compile_regex(const gchar *str, gint sflags)
1926 GRegex *regex;
1927 GError *error = NULL;
1928 gint rflags = G_REGEX_MULTILINE;
1930 if (~sflags & SCFIND_MATCHCASE)
1931 rflags |= G_REGEX_CASELESS;
1932 if (sflags & (SCFIND_WHOLEWORD | SCFIND_WORDSTART))
1934 geany_debug("%s: Unsupported regex flags found!", G_STRFUNC);
1937 regex = g_regex_new(str, rflags, 0, &error);
1938 if (!regex)
1940 ui_set_statusbar(FALSE, _("Bad regex: %s"), error->message);
1941 g_error_free(error);
1943 return regex;
1947 /* groups that don't exist are handled OK as len = end - start = (-1) - (-1) = 0 */
1948 static gchar *get_regex_match_string(const gchar *text, const GeanyMatchInfo *match, guint nth)
1950 const gint start = match->matches[nth].start;
1951 const gint end = match->matches[nth].end;
1952 return g_strndup(&text[start], end - start);
1956 static gint find_regex(ScintillaObject *sci, guint pos, GRegex *regex, GeanyMatchInfo *match)
1958 const gchar *text;
1959 GMatchInfo *minfo;
1960 gint ret = -1;
1962 g_return_val_if_fail(pos <= (guint)sci_get_length(sci), -1);
1964 /* Warning: any SCI calls will invalidate 'text' after calling SCI_GETCHARACTERPOINTER */
1965 text = (void*)scintilla_send_message(sci, SCI_GETCHARACTERPOINTER, 0, 0);
1967 /* Warning: minfo will become invalid when 'text' does! */
1968 if (g_regex_match_full(regex, text, -1, pos, 0, &minfo, NULL))
1970 guint i;
1972 /* copy whole match text and offsets before they become invalid */
1973 SETPTR(match->match_text, g_match_info_fetch(minfo, 0));
1975 foreach_range(i, G_N_ELEMENTS(match->matches))
1977 gint start = -1, end = -1;
1979 g_match_info_fetch_pos(minfo, (gint)i, &start, &end);
1980 match->matches[i].start = start;
1981 match->matches[i].end = end;
1983 match->start = match->matches[0].start;
1984 match->end = match->matches[0].end;
1985 ret = match->start;
1987 g_match_info_free(minfo);
1988 return ret;
1992 gint search_find_prev(ScintillaObject *sci, const gchar *str, gint flags, GeanyMatchInfo **match_)
1994 gint ret;
1996 g_return_val_if_fail(! (flags & SCFIND_REGEXP), -1);
1998 ret = sci_search_prev(sci, flags, str);
1999 if (ret != -1 && match_)
2000 *match_ = match_info_new(flags, ret, ret + strlen(str));
2001 return ret;
2005 gint search_find_next(ScintillaObject *sci, const gchar *str, gint flags, GeanyMatchInfo **match_)
2007 GeanyMatchInfo *match;
2008 GRegex *regex;
2009 gint ret = -1;
2010 gint pos;
2012 if (~flags & SCFIND_REGEXP)
2014 ret = sci_search_next(sci, flags, str);
2015 if (ret != -1 && match_)
2016 *match_ = match_info_new(flags, ret, ret + strlen(str));
2017 return ret;
2020 regex = compile_regex(str, flags);
2021 if (!regex)
2022 return -1;
2024 match = match_info_new(flags, 0, 0);
2026 pos = sci_get_current_position(sci);
2027 ret = find_regex(sci, pos, regex, match);
2028 /* avoid re-matching the same position in case of empty matches */
2029 if (ret == pos && match->matches[0].start == match->matches[0].end)
2030 ret = find_regex(sci, pos + 1, regex, match);
2031 if (ret >= 0)
2032 sci_set_selection(sci, match->start, match->end);
2034 if (ret != -1 && match_)
2035 *match_ = match;
2036 else
2037 geany_match_info_free(match);
2039 g_regex_unref(regex);
2040 return ret;
2044 gint search_replace_match(ScintillaObject *sci, const GeanyMatchInfo *match, const gchar *replace_text)
2046 GString *str;
2047 gint ret = 0;
2048 gint i = 0;
2050 sci_set_target_start(sci, match->start);
2051 sci_set_target_end(sci, match->end);
2053 if (! (match->flags & SCFIND_REGEXP))
2054 return sci_replace_target(sci, replace_text, FALSE);
2056 str = g_string_new(replace_text);
2057 while (str->str[i])
2059 gchar *ptr = &str->str[i];
2060 gchar *grp;
2061 gchar c;
2063 if (ptr[0] != '\\')
2065 i++;
2066 continue;
2068 c = ptr[1];
2069 /* backslash or unnecessary escape */
2070 if (c == '\\' || !isdigit(c))
2072 g_string_erase(str, i, 1);
2073 i++;
2074 continue;
2076 /* digit escape */
2077 g_string_erase(str, i, 2);
2078 /* fix match offsets by subtracting index of whole match start from the string */
2079 grp = get_regex_match_string(match->match_text - match->matches[0].start, match, c - '0');
2080 g_string_insert(str, i, grp);
2081 i += strlen(grp);
2082 g_free(grp);
2084 ret = sci_replace_target(sci, str->str, FALSE);
2085 g_string_free(str, TRUE);
2086 return ret;
2090 gint search_find_text(ScintillaObject *sci, gint flags, struct Sci_TextToFind *ttf, GeanyMatchInfo **match_)
2092 GeanyMatchInfo *match = NULL;
2093 GRegex *regex;
2094 gint ret;
2096 if (~flags & SCFIND_REGEXP)
2098 ret = sci_find_text(sci, flags, ttf);
2099 if (ret != -1 && match_)
2100 *match_ = match_info_new(flags, ttf->chrgText.cpMin, ttf->chrgText.cpMax);
2101 return ret;
2104 regex = compile_regex(ttf->lpstrText, flags);
2105 if (!regex)
2106 return -1;
2108 match = match_info_new(flags, 0, 0);
2110 ret = find_regex(sci, ttf->chrg.cpMin, regex, match);
2111 if (ret >= ttf->chrg.cpMax)
2112 ret = -1;
2113 else if (ret >= 0)
2115 ttf->chrgText.cpMin = match->start;
2116 ttf->chrgText.cpMax = match->end;
2119 if (ret != -1 && match_)
2120 *match_ = match;
2121 else
2122 geany_match_info_free(match);
2124 g_regex_unref(regex);
2125 return ret;
2129 static gint find_document_usage(GeanyDocument *doc, const gchar *search_text, gint flags)
2131 gchar *buffer, *short_file_name;
2132 struct Sci_TextToFind ttf;
2133 gint count = 0;
2134 gint prev_line = -1;
2135 GSList *match, *matches;
2137 g_return_val_if_fail(DOC_VALID(doc), 0);
2139 short_file_name = g_path_get_basename(DOC_FILENAME(doc));
2141 ttf.chrg.cpMin = 0;
2142 ttf.chrg.cpMax = sci_get_length(doc->editor->sci);
2143 ttf.lpstrText = (gchar *)search_text;
2145 matches = find_range(doc->editor->sci, flags, &ttf);
2146 foreach_slist (match, matches)
2148 GeanyMatchInfo *info = match->data;
2149 gint line = sci_get_line_from_position(doc->editor->sci, info->start);
2151 if (line != prev_line)
2153 buffer = sci_get_line(doc->editor->sci, line);
2154 msgwin_msg_add(COLOR_BLACK, line + 1, doc,
2155 "%s:%d: %s", short_file_name, line + 1, g_strstrip(buffer));
2156 g_free(buffer);
2157 prev_line = line;
2159 count++;
2161 geany_match_info_free(info);
2163 g_slist_free(matches);
2164 g_free(short_file_name);
2165 return count;
2169 void search_find_usage(const gchar *search_text, const gchar *original_search_text,
2170 gint flags, gboolean in_session)
2172 GeanyDocument *doc;
2173 gint count = 0;
2175 doc = document_get_current();
2176 g_return_if_fail(doc != NULL);
2178 if (G_UNLIKELY(EMPTY(search_text)))
2180 utils_beep();
2181 return;
2184 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_MESSAGE);
2185 gtk_list_store_clear(msgwindow.store_msg);
2187 if (! in_session)
2188 { /* use current document */
2189 count = find_document_usage(doc, search_text, flags);
2191 else
2193 guint i;
2194 for (i = 0; i < documents_array->len; i++)
2196 if (documents[i]->is_valid)
2198 count += find_document_usage(documents[i], search_text, flags);
2203 if (count == 0) /* no matches were found */
2205 ui_set_statusbar(FALSE, _("No matches found for \"%s\"."), original_search_text);
2206 msgwin_msg_add(COLOR_BLUE, -1, NULL, _("No matches found for \"%s\"."), original_search_text);
2208 else
2210 ui_set_statusbar(FALSE, ngettext(
2211 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count),
2212 count, original_search_text);
2213 msgwin_msg_add(COLOR_BLUE, -1, NULL, ngettext(
2214 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count),
2215 count, original_search_text);
2220 /* ttf is updated to include the last match position (ttf->chrg.cpMin) and
2221 * the new search range end (ttf->chrg.cpMax).
2222 * Note: Normally you would call sci_start/end_undo_action() around this call. */
2223 guint search_replace_range(ScintillaObject *sci, struct Sci_TextToFind *ttf,
2224 gint flags, const gchar *replace_text)
2226 gint count = 0;
2227 gint offset = 0; /* difference between search pos and replace pos */
2228 GSList *match, *matches;
2230 g_return_val_if_fail(sci != NULL && ttf->lpstrText != NULL && replace_text != NULL, 0);
2231 if (! *ttf->lpstrText)
2232 return 0;
2234 matches = find_range(sci, flags, ttf);
2235 foreach_slist (match, matches)
2237 GeanyMatchInfo *info = match->data;
2238 gint replace_len;
2240 info->start += offset;
2241 info->end += offset;
2243 replace_len = search_replace_match(sci, info, replace_text);
2244 offset += replace_len - (info->end - info->start);
2245 count ++;
2247 /* on last match, update the last match/new range end */
2248 if (! match->next)
2250 ttf->chrg.cpMin = info->start;
2251 ttf->chrg.cpMax += offset;
2254 geany_match_info_free(info);
2256 g_slist_free(matches);
2258 return count;
2262 void search_find_again(gboolean change_direction)
2264 GeanyDocument *doc = document_get_current();
2266 g_return_if_fail(doc != NULL);
2268 if (search_data.text)
2270 gboolean forward = ! search_data.backwards;
2271 gint result = document_find_text(doc, search_data.text, search_data.original_text, search_data.flags,
2272 change_direction ? forward : !forward, NULL, FALSE, NULL);
2274 if (result > -1)
2275 editor_display_current_line(doc->editor, 0.3F);
2277 if (search_data.search_bar)
2278 ui_set_search_entry_background(
2279 toolbar_get_widget_child_by_name("SearchEntry"), (result > -1));