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