Merge pull request #464 from eht16/undeprecate_plugins
[geany-mirror.git] / src / search.c
blob5a5f4c2f63444afa260057f478373022c9ba09ed
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 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
31 #include "search.h"
33 #include "app.h"
34 #include "document.h"
35 #include "encodings.h"
36 #include "keyfile.h"
37 #include "msgwindow.h"
38 #include "prefs.h"
39 #include "sciwrappers.h"
40 #include "stash.h"
41 #include "support.h"
42 #include "toolbar.h"
43 #include "ui_utils.h"
44 #include "utils.h"
46 #include "gtkcompat.h"
48 #include <unistd.h>
49 #include <string.h>
50 #include <ctype.h>
52 #ifdef G_OS_UNIX
53 # include <sys/types.h>
54 # include <sys/wait.h>
55 #endif
57 #include <gdk/gdkkeysyms.h>
59 enum
61 GEANY_RESPONSE_FIND = 1,
62 GEANY_RESPONSE_FIND_PREVIOUS,
63 GEANY_RESPONSE_FIND_IN_FILE,
64 GEANY_RESPONSE_FIND_IN_SESSION,
65 GEANY_RESPONSE_MARK,
66 GEANY_RESPONSE_REPLACE,
67 GEANY_RESPONSE_REPLACE_AND_FIND,
68 GEANY_RESPONSE_REPLACE_IN_SESSION,
69 GEANY_RESPONSE_REPLACE_IN_FILE,
70 GEANY_RESPONSE_REPLACE_IN_SEL
74 enum
76 FILES_MODE_ALL,
77 FILES_MODE_PROJECT,
78 FILES_MODE_CUSTOM
82 GeanySearchData search_data;
83 GeanySearchPrefs search_prefs;
86 static struct
88 gboolean fif_regexp;
89 gboolean fif_case_sensitive;
90 gboolean fif_match_whole_word;
91 gboolean fif_invert_results;
92 gboolean fif_recursive;
93 gboolean fif_use_extra_options;
94 gchar *fif_extra_options;
95 gint fif_files_mode;
96 gchar *fif_files;
97 gboolean find_regexp;
98 gboolean find_regexp_multiline;
99 gboolean find_escape_sequences;
100 gboolean find_case_sensitive;
101 gboolean find_match_whole_word;
102 gboolean find_match_word_start;
103 gboolean find_close_dialog;
104 gboolean replace_regexp;
105 gboolean replace_regexp_multiline;
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);
159 static GRegex *compile_regex(const gchar *str, GeanyFindFlags sflags);
162 static void
163 on_find_replace_checkbutton_toggled(GtkToggleButton *togglebutton, gpointer user_data);
165 static void
166 on_find_dialog_response(GtkDialog *dialog, gint response, gpointer user_data);
168 static void
169 on_find_entry_activate(GtkEntry *entry, gpointer user_data);
171 static void
172 on_find_entry_activate_backward(GtkEntry *entry, gpointer user_data);
174 static void
175 on_replace_dialog_response(GtkDialog *dialog, gint response, gpointer user_data);
177 static void
178 on_replace_find_entry_activate(GtkEntry *entry, gpointer user_data);
180 static void
181 on_replace_entry_activate(GtkEntry *entry, gpointer user_data);
183 static void
184 on_find_in_files_dialog_response(GtkDialog *dialog, gint response, gpointer user_data);
186 static gboolean
187 search_find_in_files(const gchar *utf8_search_text, const gchar *dir, const gchar *opts,
188 const gchar *enc);
191 static void init_prefs(void)
193 StashGroup *group;
195 group = stash_group_new("search");
196 configuration_add_pref_group(group, TRUE);
197 stash_group_add_toggle_button(group, &search_prefs.always_wrap,
198 "pref_search_hide_find_dialog", FALSE, "check_always_wrap_search");
199 stash_group_add_toggle_button(group, &search_prefs.hide_find_dialog,
200 "pref_search_always_wrap", FALSE, "check_hide_find_dialog");
201 stash_group_add_toggle_button(group, &search_prefs.use_current_file_dir,
202 "pref_search_current_file_dir", TRUE, "check_fif_current_dir");
203 stash_group_add_boolean(group, &find_dlg.all_expanded, "find_all_expanded", FALSE);
204 stash_group_add_boolean(group, &replace_dlg.all_expanded, "replace_all_expanded", FALSE);
205 /* dialog positions */
206 stash_group_add_integer(group, &find_dlg.position[0], "position_find_x", -1);
207 stash_group_add_integer(group, &find_dlg.position[1], "position_find_y", -1);
208 stash_group_add_integer(group, &replace_dlg.position[0], "position_replace_x", -1);
209 stash_group_add_integer(group, &replace_dlg.position[1], "position_replace_y", -1);
210 stash_group_add_integer(group, &fif_dlg.position[0], "position_fif_x", -1);
211 stash_group_add_integer(group, &fif_dlg.position[1], "position_fif_y", -1);
213 memset(&settings, '\0', sizeof(settings));
215 group = stash_group_new("search");
216 fif_prefs = group;
217 configuration_add_pref_group(group, FALSE);
218 stash_group_add_toggle_button(group, &settings.fif_regexp,
219 "fif_regexp", FALSE, "check_regexp");
220 stash_group_add_toggle_button(group, &settings.fif_case_sensitive,
221 "fif_case_sensitive", TRUE, "check_case");
222 stash_group_add_toggle_button(group, &settings.fif_match_whole_word,
223 "fif_match_whole_word", FALSE, "check_wholeword");
224 stash_group_add_toggle_button(group, &settings.fif_invert_results,
225 "fif_invert_results", FALSE, "check_invert");
226 stash_group_add_toggle_button(group, &settings.fif_recursive,
227 "fif_recursive", FALSE, "check_recursive");
228 stash_group_add_entry(group, &settings.fif_extra_options,
229 "fif_extra_options", "", "entry_extra");
230 stash_group_add_toggle_button(group, &settings.fif_use_extra_options,
231 "fif_use_extra_options", FALSE, "check_extra");
232 stash_group_add_entry(group, &settings.fif_files,
233 "fif_files", "", "entry_files");
234 stash_group_add_combo_box(group, &settings.fif_files_mode,
235 "fif_files_mode", FILES_MODE_ALL, "combo_files_mode");
237 group = stash_group_new("search");
238 find_prefs = group;
239 configuration_add_pref_group(group, FALSE);
240 stash_group_add_toggle_button(group, &settings.find_regexp,
241 "find_regexp", FALSE, "check_regexp");
242 stash_group_add_toggle_button(group, &settings.find_regexp_multiline,
243 "find_regexp_multiline", FALSE, "check_multiline");
244 stash_group_add_toggle_button(group, &settings.find_case_sensitive,
245 "find_case_sensitive", FALSE, "check_case");
246 stash_group_add_toggle_button(group, &settings.find_escape_sequences,
247 "find_escape_sequences", FALSE, "check_escape");
248 stash_group_add_toggle_button(group, &settings.find_match_whole_word,
249 "find_match_whole_word", FALSE, "check_word");
250 stash_group_add_toggle_button(group, &settings.find_match_word_start,
251 "find_match_word_start", FALSE, "check_wordstart");
252 stash_group_add_toggle_button(group, &settings.find_close_dialog,
253 "find_close_dialog", TRUE, "check_close");
255 group = stash_group_new("search");
256 replace_prefs = group;
257 configuration_add_pref_group(group, FALSE);
258 stash_group_add_toggle_button(group, &settings.replace_regexp,
259 "replace_regexp", FALSE, "check_regexp");
260 stash_group_add_toggle_button(group, &settings.replace_regexp_multiline,
261 "replace_regexp_multiline", FALSE, "check_multiline");
262 stash_group_add_toggle_button(group, &settings.replace_case_sensitive,
263 "replace_case_sensitive", FALSE, "check_case");
264 stash_group_add_toggle_button(group, &settings.replace_escape_sequences,
265 "replace_escape_sequences", FALSE, "check_escape");
266 stash_group_add_toggle_button(group, &settings.replace_match_whole_word,
267 "replace_match_whole_word", FALSE, "check_word");
268 stash_group_add_toggle_button(group, &settings.replace_match_word_start,
269 "replace_match_word_start", FALSE, "check_wordstart");
270 stash_group_add_toggle_button(group, &settings.replace_search_backwards,
271 "replace_search_backwards", FALSE, "check_back");
272 stash_group_add_toggle_button(group, &settings.replace_close_dialog,
273 "replace_close_dialog", TRUE, "check_close");
277 void search_init(void)
279 search_data.text = NULL;
280 search_data.original_text = NULL;
281 init_prefs();
285 #define FREE_WIDGET(wid) \
286 if (wid && GTK_IS_WIDGET(wid)) gtk_widget_destroy(wid);
288 void search_finalize(void)
290 FREE_WIDGET(find_dlg.dialog);
291 FREE_WIDGET(replace_dlg.dialog);
292 FREE_WIDGET(fif_dlg.dialog);
293 g_free(search_data.text);
294 g_free(search_data.original_text);
298 static void on_widget_toggled_set_insensitive(
299 GtkToggleButton *togglebutton, gpointer user_data)
301 gtk_widget_set_sensitive(GTK_WIDGET(user_data),
302 !gtk_toggle_button_get_active(togglebutton));
306 static GtkWidget *add_find_checkboxes(GtkDialog *dialog)
308 GtkWidget *checkbox1, *checkbox2, *check_regexp, *check_back, *checkbox5,
309 *checkbox7, *check_multiline, *hbox, *fbox, *mbox;
311 check_regexp = gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
312 ui_hookup_widget(dialog, check_regexp, "check_regexp");
313 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp), FALSE);
314 gtk_widget_set_tooltip_text(check_regexp, _("Use POSIX-like regular expressions. "
315 "For detailed information about using regular expressions, please read the documentation."));
316 g_signal_connect(check_regexp, "toggled",
317 G_CALLBACK(on_find_replace_checkbutton_toggled), dialog);
319 checkbox7 = gtk_check_button_new_with_mnemonic(_("Use _escape sequences"));
320 ui_hookup_widget(dialog, checkbox7, "check_escape");
321 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox7), FALSE);
322 gtk_widget_set_tooltip_text(checkbox7,
323 _("Replace \\\\, \\t, \\n, \\r and \\uXXXX (Unicode characters) with the "
324 "corresponding control characters"));
326 check_multiline = gtk_check_button_new_with_mnemonic(_("Use multi-_line matching"));
327 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_multiline), FALSE);
328 gtk_widget_set_sensitive(check_multiline, FALSE);
329 ui_hookup_widget(dialog, check_multiline, "check_multiline");
330 gtk_button_set_focus_on_click(GTK_BUTTON(check_multiline), FALSE);
331 gtk_widget_set_tooltip_text(check_multiline, _("Perform regular expression "
332 "matching on the whole buffer at once rather than line by line, allowing "
333 "matches to span multiple lines. In this mode, newline characters are part "
334 "of the input and can be captured as normal characters by the pattern."));
336 /* Search features */
337 fbox = gtk_vbox_new(FALSE, 0);
338 gtk_box_pack_start(GTK_BOX(fbox), check_regexp, FALSE, FALSE, 0);
339 gtk_box_pack_start(GTK_BOX(fbox), check_multiline, FALSE, FALSE, 0);
340 gtk_box_pack_start(GTK_BOX(fbox), checkbox7, FALSE, FALSE, 0);
342 if (dialog != GTK_DIALOG(find_dlg.dialog))
344 check_back = gtk_check_button_new_with_mnemonic(_("Search _backwards"));
345 ui_hookup_widget(dialog, check_back, "check_back");
346 gtk_button_set_focus_on_click(GTK_BUTTON(check_back), FALSE);
347 gtk_container_add(GTK_CONTAINER(fbox), check_back);
350 checkbox1 = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
351 ui_hookup_widget(dialog, checkbox1, "check_case");
352 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1), FALSE);
354 checkbox2 = gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
355 ui_hookup_widget(dialog, checkbox2, "check_word");
356 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2), FALSE);
358 checkbox5 = gtk_check_button_new_with_mnemonic(_("Match from s_tart of word"));
359 ui_hookup_widget(dialog, checkbox5, "check_wordstart");
360 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox5), FALSE);
362 /* disable wordstart when wholeword is checked */
363 g_signal_connect(checkbox2, "toggled",
364 G_CALLBACK(on_widget_toggled_set_insensitive), checkbox5);
366 /* Matching options */
367 mbox = gtk_vbox_new(FALSE, 0);
368 gtk_box_pack_start(GTK_BOX(mbox), checkbox1, FALSE, FALSE, 0);
369 gtk_box_pack_start(GTK_BOX(mbox), checkbox2, FALSE, FALSE, 0);
370 gtk_box_pack_start(GTK_BOX(mbox), checkbox5, FALSE, FALSE, 0);
372 hbox = gtk_hbox_new(TRUE, 6);
373 gtk_container_add(GTK_CONTAINER(hbox), fbox);
374 gtk_container_add(GTK_CONTAINER(hbox), mbox);
375 return hbox;
379 static void send_find_dialog_response(GtkButton *button, gpointer user_data)
381 gtk_dialog_response(GTK_DIALOG(find_dlg.dialog), GPOINTER_TO_INT(user_data));
385 /* store text, clear search flags so we can use Search->Find Next/Previous */
386 static void setup_find_next(const gchar *text)
388 g_free(search_data.text);
389 g_free(search_data.original_text);
390 search_data.text = g_strdup(text);
391 search_data.original_text = g_strdup(text);
392 search_data.flags = 0;
393 search_data.backwards = FALSE;
394 search_data.search_bar = FALSE;
398 /* Search for next match of the current "selection".
399 * Optionally for X11 based systems, this will try to use the system-wide
400 * x-selection first.
401 * If it doesn't find a suitable search string it will try to use
402 * the current word instead, or just repeat the last search.
403 * Search flags are always zero.
405 void search_find_selection(GeanyDocument *doc, gboolean search_backwards)
407 gchar *s = NULL;
409 g_return_if_fail(DOC_VALID(doc));
411 #ifdef G_OS_UNIX
412 if (search_prefs.find_selection_type == GEANY_FIND_SEL_X)
414 GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
416 s = gtk_clipboard_wait_for_text(clipboard);
417 if (s && (strchr(s,'\n') || strchr(s, '\r')))
419 g_free(s);
420 s = NULL;
423 #endif
425 if (!s && sci_has_selection(doc->editor->sci))
426 s = sci_get_selection_contents(doc->editor->sci);
428 if (!s && search_prefs.find_selection_type != GEANY_FIND_SEL_AGAIN)
430 /* get the current word */
431 s = editor_get_default_selection(doc->editor, TRUE, NULL);
434 if (s)
436 setup_find_next(s); /* allow find next/prev */
438 if (document_find_text(doc, s, NULL, 0, search_backwards, NULL, FALSE, NULL) > -1)
439 editor_display_current_line(doc->editor, 0.3F);
440 g_free(s);
442 else if (search_prefs.find_selection_type == GEANY_FIND_SEL_AGAIN)
444 /* Repeat last search (in case selection was lost) */
445 search_find_again(search_backwards);
447 else
449 utils_beep();
454 static void on_expander_activated(GtkExpander *exp, gpointer data)
456 gboolean *setting = data;
458 *setting = gtk_expander_get_expanded(exp);
462 static void create_find_dialog(void)
464 GtkWidget *label, *entry, *sbox, *vbox;
465 GtkWidget *exp, *bbox, *button, *check_close;
467 find_dlg.dialog = gtk_dialog_new_with_buttons(_("Find"),
468 GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT,
469 GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL, NULL);
470 vbox = ui_dialog_vbox_new(GTK_DIALOG(find_dlg.dialog));
471 gtk_widget_set_name(find_dlg.dialog, "GeanyDialogSearch");
472 gtk_box_set_spacing(GTK_BOX(vbox), 9);
474 button = ui_button_new_with_image(GTK_STOCK_GO_BACK, _("_Previous"));
475 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg.dialog), button,
476 GEANY_RESPONSE_FIND_PREVIOUS);
477 ui_hookup_widget(find_dlg.dialog, button, "btn_previous");
479 button = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, _("_Next"));
480 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg.dialog), button,
481 GEANY_RESPONSE_FIND);
483 label = gtk_label_new_with_mnemonic(_("_Search for:"));
484 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
486 entry = gtk_combo_box_text_new_with_entry();
487 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))));
488 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
489 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))), 50);
490 find_dlg.entry = gtk_bin_get_child(GTK_BIN(entry));
492 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry)), "activate",
493 G_CALLBACK(on_find_entry_activate), entry);
494 ui_entry_add_activate_backward_signal(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))));
495 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry)), "activate-backward",
496 G_CALLBACK(on_find_entry_activate_backward), entry);
497 g_signal_connect(find_dlg.dialog, "response",
498 G_CALLBACK(on_find_dialog_response), entry);
499 g_signal_connect(find_dlg.dialog, "delete-event",
500 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
502 sbox = gtk_hbox_new(FALSE, 6);
503 gtk_box_pack_start(GTK_BOX(sbox), label, FALSE, FALSE, 0);
504 gtk_box_pack_start(GTK_BOX(sbox), entry, TRUE, TRUE, 0);
505 gtk_box_pack_start(GTK_BOX(vbox), sbox, TRUE, FALSE, 0);
507 gtk_container_add(GTK_CONTAINER(vbox),
508 add_find_checkboxes(GTK_DIALOG(find_dlg.dialog)));
510 /* Now add the multiple match options */
511 exp = gtk_expander_new_with_mnemonic(_("_Find All"));
512 gtk_expander_set_expanded(GTK_EXPANDER(exp), find_dlg.all_expanded);
513 g_signal_connect_after(exp, "activate",
514 G_CALLBACK(on_expander_activated), &find_dlg.all_expanded);
516 bbox = gtk_hbutton_box_new();
518 button = gtk_button_new_with_mnemonic(_("_Mark"));
519 gtk_widget_set_tooltip_text(button,
520 _("Mark all matches in the current document"));
521 gtk_container_add(GTK_CONTAINER(bbox), button);
522 g_signal_connect(button, "clicked", G_CALLBACK(send_find_dialog_response),
523 GINT_TO_POINTER(GEANY_RESPONSE_MARK));
525 button = gtk_button_new_with_mnemonic(_("In Sessi_on"));
526 gtk_container_add(GTK_CONTAINER(bbox), button);
527 g_signal_connect(button, "clicked", G_CALLBACK(send_find_dialog_response),
528 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_SESSION));
530 button = gtk_button_new_with_mnemonic(_("_In Document"));
531 gtk_container_add(GTK_CONTAINER(bbox), button);
532 g_signal_connect(button, "clicked", G_CALLBACK(send_find_dialog_response),
533 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_FILE));
535 /* close window checkbox */
536 check_close = gtk_check_button_new_with_mnemonic(_("Close _dialog"));
537 ui_hookup_widget(find_dlg.dialog, check_close, "check_close");
538 gtk_button_set_focus_on_click(GTK_BUTTON(check_close), FALSE);
539 gtk_widget_set_tooltip_text(check_close,
540 _("Disable this option to keep the dialog open"));
541 gtk_container_add(GTK_CONTAINER(bbox), check_close);
542 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), check_close, TRUE);
544 ui_hbutton_box_copy_layout(
545 GTK_BUTTON_BOX(gtk_dialog_get_action_area(GTK_DIALOG(find_dlg.dialog))),
546 GTK_BUTTON_BOX(bbox));
547 gtk_container_add(GTK_CONTAINER(exp), bbox);
548 gtk_container_add(GTK_CONTAINER(vbox), exp);
552 static void set_dialog_position(GtkWidget *dialog, gint *position)
554 if (position[0] >= 0)
555 gtk_window_move(GTK_WINDOW(dialog), position[0], position[1]);
559 void search_show_find_dialog(void)
561 GeanyDocument *doc = document_get_current();
562 gchar *sel = NULL;
564 g_return_if_fail(doc != NULL);
566 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
568 if (find_dlg.dialog == NULL)
570 create_find_dialog();
571 stash_group_display(find_prefs, find_dlg.dialog);
572 if (sel)
573 gtk_entry_set_text(GTK_ENTRY(find_dlg.entry), sel);
575 set_dialog_position(find_dlg.dialog, find_dlg.position);
576 gtk_widget_show_all(find_dlg.dialog);
578 else
580 /* only set selection if the dialog is not already visible */
581 if (! gtk_widget_get_visible(find_dlg.dialog) && sel)
582 gtk_entry_set_text(GTK_ENTRY(find_dlg.entry), sel);
583 gtk_widget_grab_focus(find_dlg.entry);
584 set_dialog_position(find_dlg.dialog, find_dlg.position);
585 gtk_widget_show(find_dlg.dialog);
586 if (sel != NULL) /* when we have a selection, reset the entry widget's background colour */
587 ui_set_search_entry_background(find_dlg.entry, TRUE);
588 /* bring the dialog back in the foreground in case it is already open but the focus is away */
589 gtk_window_present(GTK_WINDOW(find_dlg.dialog));
592 g_free(sel);
596 static void send_replace_dialog_response(GtkButton *button, gpointer user_data)
598 gtk_dialog_response(GTK_DIALOG(replace_dlg.dialog), GPOINTER_TO_INT(user_data));
602 static gboolean
603 on_widget_key_pressed_set_focus(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
605 if (event->keyval == GDK_Tab)
607 gtk_widget_grab_focus(GTK_WIDGET(user_data));
608 return TRUE;
610 return FALSE;
614 static void create_replace_dialog(void)
616 GtkWidget *label_find, *label_replace, *entry_find, *entry_replace,
617 *check_close, *button, *rbox, *fbox, *vbox, *exp, *bbox;
618 GtkSizeGroup *label_size;
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_text_new_with_entry();
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_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find))), 50);
651 ui_hookup_widget(replace_dlg.dialog, entry_find, "entry_find");
652 replace_dlg.find_entry = gtk_bin_get_child(GTK_BIN(entry_find));
654 entry_replace = gtk_combo_box_text_new_with_entry();
655 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace))));
656 gtk_label_set_mnemonic_widget(GTK_LABEL(label_replace), entry_replace);
657 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace))), 50);
658 ui_hookup_widget(replace_dlg.dialog, entry_replace, "entry_replace");
659 replace_dlg.replace_entry = gtk_bin_get_child(GTK_BIN(entry_replace));
661 /* tab from find to the replace entry */
662 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_find)),
663 "key-press-event", G_CALLBACK(on_widget_key_pressed_set_focus),
664 gtk_bin_get_child(GTK_BIN(entry_replace)));
665 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_find)), "activate",
666 G_CALLBACK(on_replace_find_entry_activate), NULL);
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), NULL);
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_get_action_area(GTK_DIALOG(replace_dlg.dialog))),
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_get_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 && !EMPTY(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;
869 fif_dlg.dialog = gtk_dialog_new_with_buttons(
870 _("Find in Files"), GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT,
871 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
872 vbox = ui_dialog_vbox_new(GTK_DIALOG(fif_dlg.dialog));
873 gtk_box_set_spacing(GTK_BOX(vbox), 9);
874 gtk_widget_set_name(fif_dlg.dialog, "GeanyDialogSearch");
876 gtk_dialog_add_button(GTK_DIALOG(fif_dlg.dialog), GTK_STOCK_FIND, GTK_RESPONSE_ACCEPT);
877 gtk_dialog_set_default_response(GTK_DIALOG(fif_dlg.dialog),
878 GTK_RESPONSE_ACCEPT);
880 label = gtk_label_new_with_mnemonic(_("_Search for:"));
881 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
883 combo = gtk_combo_box_text_new_with_entry();
884 entry = gtk_bin_get_child(GTK_BIN(combo));
885 ui_entry_add_clear_icon(GTK_ENTRY(entry));
886 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
887 gtk_entry_set_width_chars(GTK_ENTRY(entry), 50);
888 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
889 fif_dlg.search_combo = combo;
891 sbox = gtk_hbox_new(FALSE, 6);
892 gtk_box_pack_start(GTK_BOX(sbox), label, FALSE, FALSE, 0);
893 gtk_box_pack_start(GTK_BOX(sbox), combo, TRUE, TRUE, 0);
895 /* make labels same width */
896 size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
897 gtk_size_group_add_widget(size_group, label);
899 label3 = gtk_label_new_with_mnemonic(_("Fi_les:"));
900 gtk_misc_set_alignment(GTK_MISC(label3), 0, 0.5);
902 combo_files_mode = create_fif_file_mode_combo();
903 gtk_label_set_mnemonic_widget(GTK_LABEL(label3), combo_files_mode);
904 ui_hookup_widget(fif_dlg.dialog, combo_files_mode, "combo_files_mode");
905 fif_dlg.files_mode_combo = combo_files_mode;
907 fcombo = gtk_combo_box_text_new_with_entry();
908 entry = gtk_bin_get_child(GTK_BIN(fcombo));
909 ui_entry_add_clear_icon(GTK_ENTRY(entry));
910 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
911 gtk_widget_set_tooltip_text(entry, _("File patterns, e.g. *.c *.h"));
912 ui_hookup_widget(fif_dlg.dialog, entry, "entry_files");
913 fif_dlg.files_combo = fcombo;
915 /* update the entry when selection is changed */
916 g_signal_connect(combo_files_mode, "changed", G_CALLBACK(update_file_patterns), fcombo);
918 hbox = gtk_hbox_new(FALSE, 6);
919 gtk_box_pack_start(GTK_BOX(hbox), label3, FALSE, FALSE, 0);
920 gtk_box_pack_start(GTK_BOX(hbox), combo_files_mode, FALSE, FALSE, 0);
921 gtk_box_pack_start(GTK_BOX(hbox), fcombo, TRUE, TRUE, 0);
923 label1 = gtk_label_new_with_mnemonic(_("_Directory:"));
924 gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
926 dir_combo = gtk_combo_box_text_new_with_entry();
927 entry = gtk_bin_get_child(GTK_BIN(dir_combo));
928 ui_entry_add_clear_icon(GTK_ENTRY(entry));
929 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
930 gtk_label_set_mnemonic_widget(GTK_LABEL(label1), entry);
931 gtk_entry_set_width_chars(GTK_ENTRY(entry), 50);
932 fif_dlg.dir_combo = dir_combo;
934 /* tab from files to the dir entry */
935 g_signal_connect(gtk_bin_get_child(GTK_BIN(fcombo)), "key-press-event",
936 G_CALLBACK(on_widget_key_pressed_set_focus), entry);
938 dbox = ui_path_box_new(NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
939 GTK_ENTRY(entry));
940 gtk_box_pack_start(GTK_BOX(dbox), label1, FALSE, FALSE, 0);
942 label2 = gtk_label_new_with_mnemonic(_("E_ncoding:"));
943 gtk_misc_set_alignment(GTK_MISC(label2), 0, 0.5);
945 e_combo = ui_create_encodings_combo_box(FALSE, GEANY_ENCODING_UTF_8);
946 gtk_label_set_mnemonic_widget(GTK_LABEL(label2), e_combo);
947 fif_dlg.encoding_combo = e_combo;
949 ebox = gtk_hbox_new(FALSE, 6);
950 gtk_box_pack_start(GTK_BOX(ebox), label2, FALSE, FALSE, 0);
951 gtk_box_pack_start(GTK_BOX(ebox), e_combo, TRUE, TRUE, 0);
953 gtk_size_group_add_widget(size_group, label1);
954 gtk_size_group_add_widget(size_group, label2);
955 gtk_size_group_add_widget(size_group, label3);
956 g_object_unref(G_OBJECT(size_group)); /* auto destroy the size group */
958 gtk_box_pack_start(GTK_BOX(vbox), sbox, TRUE, FALSE, 0);
959 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 0);
960 gtk_box_pack_start(GTK_BOX(vbox), dbox, TRUE, FALSE, 0);
961 gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
963 check_regexp = gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
964 ui_hookup_widget(fif_dlg.dialog, check_regexp, "check_regexp");
965 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp), FALSE);
966 gtk_widget_set_tooltip_text(check_regexp, _("See grep's manual page for more information"));
968 check_recursive = gtk_check_button_new_with_mnemonic(_("_Recurse in subfolders"));
969 ui_hookup_widget(fif_dlg.dialog, check_recursive, "check_recursive");
970 gtk_button_set_focus_on_click(GTK_BUTTON(check_recursive), FALSE);
972 checkbox1 = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
973 ui_hookup_widget(fif_dlg.dialog, checkbox1, "check_case");
974 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1), FALSE);
975 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox1), TRUE);
977 check_wholeword = gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
978 ui_hookup_widget(fif_dlg.dialog, check_wholeword, "check_wholeword");
979 gtk_button_set_focus_on_click(GTK_BUTTON(check_wholeword), FALSE);
981 checkbox2 = gtk_check_button_new_with_mnemonic(_("_Invert search results"));
982 ui_hookup_widget(fif_dlg.dialog, checkbox2, "check_invert");
983 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2), FALSE);
984 gtk_widget_set_tooltip_text(checkbox2,
985 _("Invert the sense of matching, to select non-matching lines"));
987 lbox = gtk_vbox_new(FALSE, 0);
988 gtk_container_add(GTK_CONTAINER(lbox), check_regexp);
989 gtk_container_add(GTK_CONTAINER(lbox), checkbox2);
990 gtk_container_add(GTK_CONTAINER(lbox), check_recursive);
992 rbox = gtk_vbox_new(FALSE, 0);
993 gtk_container_add(GTK_CONTAINER(rbox), checkbox1);
994 gtk_container_add(GTK_CONTAINER(rbox), check_wholeword);
995 gtk_container_add(GTK_CONTAINER(rbox), gtk_label_new(NULL));
997 hbox = gtk_hbox_new(FALSE, 6);
998 gtk_container_add(GTK_CONTAINER(hbox), lbox);
999 gtk_container_add(GTK_CONTAINER(hbox), rbox);
1000 gtk_container_add(GTK_CONTAINER(vbox), hbox);
1002 check_extra = gtk_check_button_new_with_mnemonic(_("E_xtra options:"));
1003 ui_hookup_widget(fif_dlg.dialog, check_extra, "check_extra");
1004 gtk_button_set_focus_on_click(GTK_BUTTON(check_extra), FALSE);
1006 entry_extra = gtk_entry_new();
1007 ui_entry_add_clear_icon(GTK_ENTRY(entry_extra));
1008 gtk_entry_set_activates_default(GTK_ENTRY(entry_extra), TRUE);
1009 gtk_widget_set_sensitive(entry_extra, FALSE);
1010 gtk_widget_set_tooltip_text(entry_extra, _("Other options to pass to Grep"));
1011 ui_hookup_widget(fif_dlg.dialog, entry_extra, "entry_extra");
1013 /* enable entry_extra when check_extra is checked */
1014 g_signal_connect(check_extra, "toggled",
1015 G_CALLBACK(on_widget_toggled_set_sensitive), entry_extra);
1017 hbox = gtk_hbox_new(FALSE, 6);
1018 gtk_box_pack_start(GTK_BOX(hbox), check_extra, FALSE, FALSE, 0);
1019 gtk_box_pack_start(GTK_BOX(hbox), entry_extra, TRUE, TRUE, 0);
1020 gtk_container_add(GTK_CONTAINER(vbox), hbox);
1022 g_signal_connect(fif_dlg.dialog, "response",
1023 G_CALLBACK(on_find_in_files_dialog_response), NULL);
1024 g_signal_connect(fif_dlg.dialog, "delete-event",
1025 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1030 * Shows the Find in Files dialog.
1032 * @param dir The directory to search in (UTF-8 encoding). May be @c NULL
1033 * to determine it the usual way by using the current document's path.
1035 * @since 0.14, plugin API 53
1037 GEANY_API_SYMBOL
1038 void search_show_find_in_files_dialog(const gchar *dir)
1040 search_show_find_in_files_dialog_full(NULL, dir);
1044 void search_show_find_in_files_dialog_full(const gchar *text, const gchar *dir)
1046 GtkWidget *entry; /* for child GtkEntry of a GtkComboBoxEntry */
1047 GeanyDocument *doc = document_get_current();
1048 gchar *sel = NULL;
1049 gchar *cur_dir = NULL;
1050 GeanyEncodingIndex enc_idx = GEANY_ENCODING_UTF_8;
1052 if (fif_dlg.dialog == NULL)
1054 create_fif_dialog();
1055 gtk_widget_show_all(fif_dlg.dialog);
1056 if (doc && !text)
1057 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
1059 stash_group_display(fif_prefs, fif_dlg.dialog);
1061 if (!text)
1063 /* only set selection if the dialog is not already visible, or has just been created */
1064 if (doc && ! sel && ! gtk_widget_get_visible(fif_dlg.dialog))
1065 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
1067 text = sel;
1069 entry = gtk_bin_get_child(GTK_BIN(fif_dlg.search_combo));
1070 if (text)
1071 gtk_entry_set_text(GTK_ENTRY(entry), text);
1072 g_free(sel);
1074 /* add project's base path directory to the dir list, we do this here once
1075 * (in create_fif_dialog() it would fail if a project is opened after dialog creation) */
1076 if (app->project != NULL && !EMPTY(app->project->base_path))
1078 ui_combo_box_prepend_text_once(GTK_COMBO_BOX_TEXT(fif_dlg.dir_combo),
1079 app->project->base_path);
1082 entry = gtk_bin_get_child(GTK_BIN(fif_dlg.dir_combo));
1083 if (!EMPTY(dir))
1084 cur_dir = g_strdup(dir); /* custom directory argument passed */
1085 else
1087 if (search_prefs.use_current_file_dir)
1089 static gchar *last_cur_dir = NULL;
1090 static GeanyDocument *last_doc = NULL;
1092 /* Only set the directory entry once for the current document */
1093 cur_dir = utils_get_current_file_dir_utf8();
1094 if (doc == last_doc && cur_dir && utils_str_equal(cur_dir, last_cur_dir))
1096 /* in case the user now wants the current directory, add it to history */
1097 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(fif_dlg.dir_combo), cur_dir, 0);
1098 SETPTR(cur_dir, NULL);
1100 else
1101 SETPTR(last_cur_dir, g_strdup(cur_dir));
1103 last_doc = doc;
1105 if (!cur_dir && EMPTY(gtk_entry_get_text(GTK_ENTRY(entry))))
1107 /* use default_open_path if no directory could be determined
1108 * (e.g. when no files are open) */
1109 if (!cur_dir)
1110 cur_dir = g_strdup(utils_get_default_dir_utf8());
1111 if (!cur_dir)
1112 cur_dir = g_get_current_dir();
1115 if (cur_dir)
1117 gtk_entry_set_text(GTK_ENTRY(entry), cur_dir);
1118 g_free(cur_dir);
1121 update_fif_file_mode_combo();
1122 update_file_patterns(fif_dlg.files_mode_combo, fif_dlg.files_combo);
1124 /* set the encoding of the current file */
1125 if (doc != NULL)
1126 enc_idx = encodings_get_idx_from_charset(doc->encoding);
1127 ui_encodings_combo_box_set_active_encoding(GTK_COMBO_BOX(fif_dlg.encoding_combo), enc_idx);
1129 /* put the focus to the directory entry if it is empty */
1130 if (utils_str_equal(gtk_entry_get_text(GTK_ENTRY(entry)), ""))
1131 gtk_widget_grab_focus(fif_dlg.dir_combo);
1132 else
1133 gtk_widget_grab_focus(fif_dlg.search_combo);
1135 /* set dialog window position */
1136 set_dialog_position(fif_dlg.dialog, fif_dlg.position);
1138 gtk_widget_show(fif_dlg.dialog);
1139 /* bring the dialog back in the foreground in case it is already open but the focus is away */
1140 gtk_window_present(GTK_WINDOW(fif_dlg.dialog));
1144 static void
1145 on_find_replace_checkbutton_toggled(GtkToggleButton *togglebutton, gpointer user_data)
1147 GtkWidget *dialog = GTK_WIDGET(user_data);
1148 GtkToggleButton *chk_regexp = GTK_TOGGLE_BUTTON(
1149 ui_lookup_widget(dialog, "check_regexp"));
1151 if (togglebutton == chk_regexp)
1153 gboolean regex_set = gtk_toggle_button_get_active(chk_regexp);
1154 GtkWidget *check_word = ui_lookup_widget(dialog, "check_word");
1155 GtkWidget *check_wordstart = ui_lookup_widget(dialog, "check_wordstart");
1156 GtkWidget *check_escape = ui_lookup_widget(dialog, "check_escape");
1157 GtkWidget *check_multiline = ui_lookup_widget(dialog, "check_multiline");
1158 gboolean replace = (dialog != find_dlg.dialog);
1159 const char *back_button[2] = { "btn_previous" , "check_back" };
1161 /* hide options that don't apply to regex searches */
1162 gtk_widget_set_sensitive(check_escape, ! regex_set);
1163 gtk_widget_set_sensitive(ui_lookup_widget(dialog, back_button[replace]), ! regex_set);
1164 gtk_widget_set_sensitive(check_word, ! regex_set);
1165 gtk_widget_set_sensitive(check_wordstart, ! regex_set);
1166 gtk_widget_set_sensitive(check_multiline, regex_set);
1171 static GeanyMatchInfo *match_info_new(GeanyFindFlags flags, gint start, gint end)
1173 GeanyMatchInfo *info = g_slice_alloc(sizeof *info);
1175 info->flags = flags;
1176 info->start = start;
1177 info->end = end;
1178 info->match_text = NULL;
1180 return info;
1183 void geany_match_info_free(GeanyMatchInfo *info)
1185 g_free(info->match_text);
1186 g_slice_free1(sizeof *info, info);
1190 /* find all in the given range.
1191 * Returns a list of allocated GeanyMatchInfo, should be freed using:
1193 * foreach_slist(node, matches)
1194 * geany_match_info_free(node->data);
1195 * g_slist_free(matches); */
1196 static GSList *find_range(ScintillaObject *sci, GeanyFindFlags flags, struct Sci_TextToFind *ttf)
1198 GSList *matches = NULL;
1199 GeanyMatchInfo *info;
1201 g_return_val_if_fail(sci != NULL && ttf->lpstrText != NULL, NULL);
1202 if (! *ttf->lpstrText)
1203 return NULL;
1205 while (search_find_text(sci, flags, ttf, &info) != -1)
1207 if (ttf->chrgText.cpMax > ttf->chrg.cpMax)
1209 /* found text is partially out of range */
1210 geany_match_info_free(info);
1211 break;
1214 matches = g_slist_prepend(matches, info);
1215 ttf->chrg.cpMin = ttf->chrgText.cpMax;
1217 /* avoid rematching with empty matches like "(?=[a-z])" or "^$".
1218 * note we cannot assume a match will always be empty or not and then break out, since
1219 * matches like "a?(?=b)" will sometimes be empty and sometimes not */
1220 if (ttf->chrgText.cpMax == ttf->chrgText.cpMin)
1221 ttf->chrg.cpMin ++;
1224 return g_slist_reverse(matches);
1228 /* Clears markers if text is null/empty.
1229 * @return Number of matches marked. */
1230 gint search_mark_all(GeanyDocument *doc, const gchar *search_text, GeanyFindFlags flags)
1232 gint count = 0;
1233 struct Sci_TextToFind ttf;
1234 GSList *match, *matches;
1236 g_return_val_if_fail(DOC_VALID(doc), 0);
1238 /* clear previous search indicators */
1239 editor_indicator_clear(doc->editor, GEANY_INDICATOR_SEARCH);
1241 if (G_UNLIKELY(EMPTY(search_text)))
1242 return 0;
1244 ttf.chrg.cpMin = 0;
1245 ttf.chrg.cpMax = sci_get_length(doc->editor->sci);
1246 ttf.lpstrText = (gchar *)search_text;
1248 matches = find_range(doc->editor->sci, flags, &ttf);
1249 foreach_slist (match, matches)
1251 GeanyMatchInfo *info = match->data;
1253 if (info->end != info->start)
1254 editor_indicator_set_on_range(doc->editor, GEANY_INDICATOR_SEARCH, info->start, info->end);
1255 count++;
1257 geany_match_info_free(info);
1259 g_slist_free(matches);
1261 return count;
1265 static void
1266 on_find_entry_activate(GtkEntry *entry, gpointer user_data)
1268 on_find_dialog_response(NULL, GEANY_RESPONSE_FIND, user_data);
1272 static void
1273 on_find_entry_activate_backward(GtkEntry *entry, gpointer user_data)
1275 /* can't search backwards with a regexp */
1276 if (search_data.flags & GEANY_FIND_REGEXP)
1277 utils_beep();
1278 else
1279 on_find_dialog_response(NULL, GEANY_RESPONSE_FIND_PREVIOUS, user_data);
1283 static GeanyFindFlags int_search_flags(gint match_case, gint whole_word, gint regexp, gint multiline, gint word_start)
1285 return (match_case ? GEANY_FIND_MATCHCASE : 0) |
1286 (regexp ? GEANY_FIND_REGEXP : 0) |
1287 (whole_word ? GEANY_FIND_WHOLEWORD : 0) |
1288 (multiline ? GEANY_FIND_MULTILINE : 0) |
1289 /* SCFIND_WORDSTART overrides SCFIND_WHOLEWORD, but we want the opposite */
1290 (word_start && !whole_word ? GEANY_FIND_WORDSTART : 0);
1294 static void
1295 on_find_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
1297 gtk_window_get_position(GTK_WINDOW(find_dlg.dialog),
1298 &find_dlg.position[0], &find_dlg.position[1]);
1300 stash_group_update(find_prefs, find_dlg.dialog);
1302 if (response == GTK_RESPONSE_CANCEL || response == GTK_RESPONSE_DELETE_EVENT)
1303 gtk_widget_hide(find_dlg.dialog);
1304 else
1306 GeanyDocument *doc = document_get_current();
1307 gboolean check_close = settings.find_close_dialog;
1309 if (doc == NULL)
1310 return;
1312 search_data.backwards = FALSE;
1313 search_data.search_bar = FALSE;
1315 g_free(search_data.text);
1316 g_free(search_data.original_text);
1317 search_data.text = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(user_data)))));
1318 search_data.original_text = g_strdup(search_data.text);
1319 search_data.flags = int_search_flags(settings.find_case_sensitive,
1320 settings.find_match_whole_word, settings.find_regexp, settings.find_regexp_multiline,
1321 settings.find_match_word_start);
1323 if (EMPTY(search_data.text))
1325 fail:
1326 utils_beep();
1327 gtk_widget_grab_focus(find_dlg.entry);
1328 return;
1330 if (search_data.flags & GEANY_FIND_REGEXP)
1332 GRegex *regex = compile_regex(search_data.text, search_data.flags);
1333 if (!regex)
1334 goto fail;
1335 else
1336 g_regex_unref(regex);
1338 else if (settings.find_escape_sequences)
1340 if (! utils_str_replace_escape(search_data.text, FALSE))
1341 goto fail;
1343 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(user_data), search_data.original_text, 0);
1345 switch (response)
1347 case GEANY_RESPONSE_FIND:
1348 case GEANY_RESPONSE_FIND_PREVIOUS:
1350 gint result = document_find_text(doc, search_data.text, search_data.original_text, search_data.flags,
1351 (response == GEANY_RESPONSE_FIND_PREVIOUS), NULL, TRUE, GTK_WIDGET(find_dlg.dialog));
1352 ui_set_search_entry_background(find_dlg.entry, (result > -1));
1353 check_close = search_prefs.hide_find_dialog;
1354 break;
1356 case GEANY_RESPONSE_FIND_IN_FILE:
1357 search_find_usage(search_data.text, search_data.original_text, search_data.flags, FALSE);
1358 break;
1360 case GEANY_RESPONSE_FIND_IN_SESSION:
1361 search_find_usage(search_data.text, search_data.original_text, search_data.flags, TRUE);
1362 break;
1364 case GEANY_RESPONSE_MARK:
1366 gint count = search_mark_all(doc, search_data.text, search_data.flags);
1368 if (count == 0)
1369 ui_set_statusbar(FALSE, _("No matches found for \"%s\"."), search_data.original_text);
1370 else
1371 ui_set_statusbar(FALSE,
1372 ngettext("Found %d match for \"%s\".",
1373 "Found %d matches for \"%s\".", count),
1374 count, search_data.original_text);
1376 break;
1378 if (check_close)
1379 gtk_widget_hide(find_dlg.dialog);
1384 static void
1385 on_replace_find_entry_activate(GtkEntry *entry, gpointer user_data)
1387 on_replace_dialog_response(NULL, GEANY_RESPONSE_FIND, NULL);
1391 static void
1392 on_replace_entry_activate(GtkEntry *entry, gpointer user_data)
1394 on_replace_dialog_response(NULL,
1395 search_prefs.replace_and_find_by_default ? GEANY_RESPONSE_REPLACE_AND_FIND : GEANY_RESPONSE_REPLACE,
1396 NULL);
1400 static void replace_in_session(GeanyDocument *doc,
1401 GeanyFindFlags search_flags_re, gboolean search_replace_escape_re,
1402 const gchar *find, const gchar *replace,
1403 const gchar *original_find, const gchar *original_replace)
1405 guint n, page_count, rep_count = 0, file_count = 0;
1407 /* replace in all documents following notebook tab order */
1408 page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
1409 for (n = 0; n < page_count; n++)
1411 GeanyDocument *tmp_doc = document_get_from_page(n);
1412 gint reps = 0;
1414 reps = document_replace_all(tmp_doc, find, replace, original_find, original_replace, search_flags_re);
1415 rep_count += reps;
1416 if (reps)
1417 file_count++;
1419 if (file_count == 0)
1421 utils_beep();
1422 ui_set_statusbar(FALSE, _("No matches found for \"%s\"."), original_find);
1423 return;
1425 /* if only one file was changed, don't override that document's status message
1426 * so we don't have to translate 4 messages for ngettext */
1427 if (file_count > 1)
1428 ui_set_statusbar(FALSE, _("Replaced %u matches in %u documents."),
1429 rep_count, file_count);
1431 /* show which docs had replacements: */
1432 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_STATUS);
1434 ui_save_buttons_toggle(doc->changed); /* update save all */
1438 static void
1439 on_replace_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
1441 GeanyDocument *doc = document_get_current();
1442 GeanyFindFlags search_flags_re;
1443 gboolean search_backwards_re, search_replace_escape_re;
1444 gchar *find, *replace, *original_find = NULL, *original_replace = NULL;
1446 gtk_window_get_position(GTK_WINDOW(replace_dlg.dialog),
1447 &replace_dlg.position[0], &replace_dlg.position[1]);
1449 stash_group_update(replace_prefs, replace_dlg.dialog);
1451 if (response == GTK_RESPONSE_CANCEL || response == GTK_RESPONSE_DELETE_EVENT)
1453 gtk_widget_hide(replace_dlg.dialog);
1454 return;
1457 search_backwards_re = settings.replace_search_backwards;
1458 search_replace_escape_re = settings.replace_escape_sequences;
1459 find = g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg.find_entry)));
1460 replace = g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg.replace_entry)));
1462 search_flags_re = int_search_flags(settings.replace_case_sensitive,
1463 settings.replace_match_whole_word, settings.replace_regexp,
1464 settings.replace_regexp_multiline, settings.replace_match_word_start);
1466 if ((response != GEANY_RESPONSE_FIND) && (search_flags_re & GEANY_FIND_MATCHCASE)
1467 && (strcmp(find, replace) == 0))
1468 goto fail;
1470 original_find = g_strdup(find);
1471 original_replace = g_strdup(replace);
1473 if (search_flags_re & GEANY_FIND_REGEXP)
1475 GRegex *regex = compile_regex(find, search_flags_re);
1476 if (regex)
1477 g_regex_unref(regex);
1478 /* find escapes will be handled by GRegex */
1479 if (!regex || !utils_str_replace_escape(replace, TRUE))
1480 goto fail;
1482 else if (search_replace_escape_re)
1484 if (! utils_str_replace_escape(find, FALSE) ||
1485 ! utils_str_replace_escape(replace, FALSE))
1486 goto fail;
1489 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(
1490 gtk_widget_get_parent(replace_dlg.find_entry)), original_find, 0);
1491 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(
1492 gtk_widget_get_parent(replace_dlg.replace_entry)), original_replace, 0);
1494 switch (response)
1496 case GEANY_RESPONSE_REPLACE_AND_FIND:
1498 gint rep = document_replace_text(doc, find, original_find, replace, search_flags_re,
1499 search_backwards_re);
1500 if (rep != -1)
1501 document_find_text(doc, find, original_find, search_flags_re, search_backwards_re,
1502 NULL, TRUE, NULL);
1503 break;
1505 case GEANY_RESPONSE_REPLACE:
1506 document_replace_text(doc, find, original_find, replace, search_flags_re,
1507 search_backwards_re);
1508 break;
1510 case GEANY_RESPONSE_FIND:
1512 gint result = document_find_text(doc, find, original_find, search_flags_re,
1513 search_backwards_re, NULL, TRUE, GTK_WIDGET(dialog));
1514 ui_set_search_entry_background(replace_dlg.find_entry, (result > -1));
1515 break;
1517 case GEANY_RESPONSE_REPLACE_IN_FILE:
1518 if (! document_replace_all(doc, find, replace, original_find, original_replace, search_flags_re))
1519 utils_beep();
1520 break;
1522 case GEANY_RESPONSE_REPLACE_IN_SESSION:
1523 replace_in_session(doc, search_flags_re, search_replace_escape_re, find, replace, original_find, original_replace);
1524 break;
1526 case GEANY_RESPONSE_REPLACE_IN_SEL:
1527 document_replace_sel(doc, find, replace, original_find, original_replace, search_flags_re);
1528 break;
1530 switch (response)
1532 case GEANY_RESPONSE_REPLACE_IN_SEL:
1533 case GEANY_RESPONSE_REPLACE_IN_FILE:
1534 case GEANY_RESPONSE_REPLACE_IN_SESSION:
1535 if (settings.replace_close_dialog)
1536 gtk_widget_hide(replace_dlg.dialog);
1538 g_free(find);
1539 g_free(replace);
1540 g_free(original_find);
1541 g_free(original_replace);
1542 return;
1544 fail:
1545 utils_beep();
1546 gtk_widget_grab_focus(replace_dlg.find_entry);
1547 g_free(find);
1548 g_free(replace);
1549 g_free(original_find);
1550 g_free(original_replace);
1554 static GString *get_grep_options(void)
1556 GString *gstr = g_string_new("-nHI"); /* line numbers, filenames, ignore binaries */
1558 if (settings.fif_invert_results)
1559 g_string_append_c(gstr, 'v');
1560 if (! settings.fif_case_sensitive)
1561 g_string_append_c(gstr, 'i');
1562 if (settings.fif_match_whole_word)
1563 g_string_append_c(gstr, 'w');
1564 if (settings.fif_recursive)
1565 g_string_append_c(gstr, 'r');
1567 if (!settings.fif_regexp)
1568 g_string_append_c(gstr, 'F');
1569 else
1570 g_string_append_c(gstr, 'E');
1572 if (settings.fif_use_extra_options)
1574 g_strstrip(settings.fif_extra_options);
1576 if (*settings.fif_extra_options != 0)
1578 g_string_append_c(gstr, ' ');
1579 g_string_append(gstr, settings.fif_extra_options);
1582 g_strstrip(settings.fif_files);
1583 if (settings.fif_files_mode != FILES_MODE_ALL && *settings.fif_files)
1585 GString *tmp;
1587 /* put --include= before each pattern */
1588 tmp = g_string_new(settings.fif_files);
1589 do {} while (utils_string_replace_all(tmp, " ", " "));
1590 g_string_prepend_c(tmp, ' ');
1591 utils_string_replace_all(tmp, " ", " --include=");
1592 g_string_append(gstr, tmp->str);
1593 g_string_free(tmp, TRUE);
1595 return gstr;
1599 static void
1600 on_find_in_files_dialog_response(GtkDialog *dialog, gint response,
1601 G_GNUC_UNUSED gpointer user_data)
1603 gtk_window_get_position(GTK_WINDOW(fif_dlg.dialog), &fif_dlg.position[0], &fif_dlg.position[1]);
1605 stash_group_update(fif_prefs, fif_dlg.dialog);
1607 if (response == GTK_RESPONSE_ACCEPT)
1609 GtkWidget *search_combo = fif_dlg.search_combo;
1610 const gchar *search_text =
1611 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(search_combo))));
1612 GtkWidget *dir_combo = fif_dlg.dir_combo;
1613 const gchar *utf8_dir =
1614 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(dir_combo))));
1615 GeanyEncodingIndex enc_idx =
1616 ui_encodings_combo_box_get_active_encoding(GTK_COMBO_BOX(fif_dlg.encoding_combo));
1618 if (G_UNLIKELY(EMPTY(utf8_dir)))
1619 ui_set_statusbar(FALSE, _("Invalid directory for find in files."));
1620 else if (!EMPTY(search_text))
1622 gchar *locale_dir;
1623 GString *opts = get_grep_options();
1624 const gchar *enc = (enc_idx == GEANY_ENCODING_UTF_8) ? NULL :
1625 encodings_get_charset_from_index(enc_idx);
1627 locale_dir = utils_get_locale_from_utf8(utf8_dir);
1629 if (search_find_in_files(search_text, locale_dir, opts->str, enc))
1631 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(search_combo), search_text, 0);
1632 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(fif_dlg.files_combo), NULL, 0);
1633 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(dir_combo), utf8_dir, 0);
1634 gtk_widget_hide(fif_dlg.dialog);
1636 g_free(locale_dir);
1637 g_string_free(opts, TRUE);
1639 else
1640 ui_set_statusbar(FALSE, _("No text to find."));
1642 else
1643 gtk_widget_hide(fif_dlg.dialog);
1647 static gboolean
1648 search_find_in_files(const gchar *utf8_search_text, const gchar *dir, const gchar *opts,
1649 const gchar *enc)
1651 gchar **argv_prefix, **argv, **opts_argv;
1652 gchar *command_grep;
1653 gchar *search_text = NULL;
1654 gint opts_argv_len, i;
1655 GPid child_pid;
1656 gint stdout_fd;
1657 gint stderr_fd;
1658 GError *error = NULL;
1659 gboolean ret = FALSE;
1660 gssize utf8_text_len;
1662 if (EMPTY(utf8_search_text) || ! dir) return TRUE;
1664 command_grep = g_find_program_in_path(tool_prefs.grep_cmd);
1665 if (command_grep == NULL)
1667 ui_set_statusbar(TRUE, _("Cannot execute grep tool '%s';"
1668 " check the path setting in Preferences."), tool_prefs.grep_cmd);
1669 return FALSE;
1672 if (! g_shell_parse_argv(opts, &opts_argv_len, &opts_argv, &error))
1674 ui_set_statusbar(TRUE, _("Cannot parse extra options: %s"), error->message);
1675 g_error_free(error);
1676 g_free(command_grep);
1677 return FALSE;
1680 /* convert the search text in the preferred encoding (if the text is not valid UTF-8. assume
1681 * it is already in the preferred encoding) */
1682 utf8_text_len = strlen(utf8_search_text);
1683 if (enc != NULL && g_utf8_validate(utf8_search_text, utf8_text_len, NULL))
1685 search_text = g_convert(utf8_search_text, utf8_text_len, enc, "UTF-8", NULL, NULL, NULL);
1687 if (search_text == NULL)
1688 search_text = g_strdup(utf8_search_text);
1690 /* set grep command and options */
1691 argv_prefix = g_new0(gchar*, 1 + opts_argv_len + 3 + 1); /* last +1 for recursive arg */
1693 argv_prefix[0] = command_grep;
1694 for (i = 0; i < opts_argv_len; i++)
1696 argv_prefix[i + 1] = g_strdup(opts_argv[i]);
1698 g_strfreev(opts_argv);
1700 i++; /* correct for tool_prefs.grep_cmd */
1701 argv_prefix[i++] = g_strdup("--");
1702 argv_prefix[i++] = search_text;
1704 /* finally add the arguments(files to be searched) */
1705 if (strstr(argv_prefix[1], "r")) /* recursive option set */
1707 /* Use '.' so we get relative paths in the output */
1708 argv_prefix[i++] = g_strdup(".");
1709 argv_prefix[i++] = NULL;
1710 argv = argv_prefix;
1712 else
1714 argv_prefix[i++] = NULL;
1715 argv = search_get_argv((const gchar**)argv_prefix, dir);
1716 g_strfreev(argv_prefix);
1719 if (argv == NULL) /* no files */
1721 return FALSE;
1724 gtk_list_store_clear(msgwindow.store_msg);
1725 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_MESSAGE);
1727 if (! g_spawn_async_with_pipes(dir, (gchar**)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
1728 NULL, NULL, &child_pid,
1729 NULL, &stdout_fd, &stderr_fd, &error))
1731 geany_debug("%s: g_spawn_async_with_pipes() failed: %s", G_STRFUNC, error->message);
1732 ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message);
1733 g_error_free(error);
1734 ret = FALSE;
1736 else
1738 gchar *str, *utf8_str;
1740 ui_progress_bar_start(_("Searching..."));
1742 msgwin_set_messages_dir(dir);
1743 /* we can pass 'enc' without strdup'ing it here because it's a global const string and
1744 * always exits longer than the lifetime of this function */
1745 utils_set_up_io_channel(stdout_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
1746 TRUE, search_read_io, (gpointer) enc);
1747 utils_set_up_io_channel(stderr_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
1748 TRUE, search_read_io_stderr, (gpointer) enc);
1749 g_child_watch_add(child_pid, search_close_pid, NULL);
1751 str = g_strdup_printf(_("%s %s -- %s (in directory: %s)"),
1752 tool_prefs.grep_cmd, opts, utf8_search_text, dir);
1753 utf8_str = utils_get_utf8_from_locale(str);
1754 msgwin_msg_add_string(COLOR_BLUE, -1, NULL, utf8_str);
1755 utils_free_pointers(2, str, utf8_str, NULL);
1756 ret = TRUE;
1758 g_strfreev(argv);
1759 return ret;
1763 static gboolean pattern_list_match(GSList *patterns, const gchar *str)
1765 GSList *item;
1767 foreach_slist(item, patterns)
1769 if (g_pattern_match_string(item->data, str))
1770 return TRUE;
1772 return FALSE;
1776 /* Creates an argument vector of strings, copying argv_prefix[] values for
1777 * the first arguments, then followed by filenames found in dir.
1778 * Returns NULL if no files were found, otherwise returned vector should be fully freed. */
1779 static gchar **search_get_argv(const gchar **argv_prefix, const gchar *dir)
1781 guint prefix_len, list_len, i, j;
1782 gchar **argv;
1783 GSList *list, *item, *patterns = NULL;
1784 GError *error = NULL;
1786 g_return_val_if_fail(dir != NULL, NULL);
1788 prefix_len = g_strv_length((gchar**)argv_prefix);
1789 list = utils_get_file_list(dir, &list_len, &error);
1790 if (error)
1792 ui_set_statusbar(TRUE, _("Could not open directory (%s)"), error->message);
1793 g_error_free(error);
1794 return NULL;
1796 if (list == NULL)
1797 return NULL;
1799 argv = g_new(gchar*, prefix_len + list_len + 1);
1801 for (i = 0, j = 0; i < prefix_len; i++)
1803 if (g_str_has_prefix(argv_prefix[i], "--include="))
1805 const gchar *pat = &(argv_prefix[i][10]); /* the pattern part of the argument */
1807 patterns = g_slist_prepend(patterns, g_pattern_spec_new(pat));
1809 else
1810 argv[j++] = g_strdup(argv_prefix[i]);
1813 if (patterns)
1815 GSList *pat;
1817 foreach_slist(item, list)
1819 if (pattern_list_match(patterns, item->data))
1820 argv[j++] = item->data;
1821 else
1822 g_free(item->data);
1824 foreach_slist(pat, patterns)
1825 g_pattern_spec_free(pat->data);
1826 g_slist_free(patterns);
1828 else
1830 foreach_slist(item, list)
1831 argv[j++] = item->data;
1834 argv[j] = NULL;
1835 g_slist_free(list);
1836 return argv;
1840 static gboolean read_fif_io(GIOChannel *source, GIOCondition condition, gchar *enc, gint msg_color)
1842 if (condition & (G_IO_IN | G_IO_PRI))
1844 gchar *msg, *utf8_msg;
1845 GIOStatus st;
1847 while ((st = g_io_channel_read_line(source, &msg, NULL, NULL, NULL)) != G_IO_STATUS_ERROR &&
1848 st != G_IO_STATUS_EOF && msg)
1850 utf8_msg = NULL;
1852 g_strstrip(msg);
1853 /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */
1854 if (enc != NULL)
1856 if (! g_utf8_validate(msg, -1, NULL))
1858 utf8_msg = g_convert(msg, -1, "UTF-8", enc, NULL, NULL, NULL);
1860 if (utf8_msg == NULL)
1861 utf8_msg = msg;
1863 else
1864 utf8_msg = msg;
1866 msgwin_msg_add_string(msg_color, -1, NULL, utf8_msg);
1868 if (utf8_msg != msg)
1869 g_free(utf8_msg);
1870 g_free(msg);
1872 if (st == G_IO_STATUS_ERROR || st == G_IO_STATUS_EOF)
1873 return FALSE;
1875 if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
1876 return FALSE;
1878 return TRUE;
1882 static gboolean search_read_io(GIOChannel *source, GIOCondition condition, gpointer data)
1884 return read_fif_io(source, condition, data, COLOR_BLACK);
1888 static gboolean search_read_io_stderr(GIOChannel *source, GIOCondition condition, gpointer data)
1890 return read_fif_io(source, condition, data, COLOR_DARK_RED);
1894 static void search_close_pid(GPid child_pid, gint status, gpointer user_data)
1896 const gchar *msg = _("Search failed.");
1897 #ifdef G_OS_UNIX
1898 gint exit_status = 1;
1900 if (WIFEXITED(status))
1902 exit_status = WEXITSTATUS(status);
1904 else if (WIFSIGNALED(status))
1906 exit_status = -1;
1907 g_warning("Find in Files: The command failed unexpectedly (signal received).");
1909 #else
1910 gint exit_status = status;
1911 #endif
1913 switch (exit_status)
1915 case 0:
1917 gint count = gtk_tree_model_iter_n_children(
1918 GTK_TREE_MODEL(msgwindow.store_msg), NULL) - 1;
1919 gchar *text = ngettext(
1920 "Search completed with %d match.",
1921 "Search completed with %d matches.", count);
1923 msgwin_msg_add(COLOR_BLUE, -1, NULL, text, count);
1924 ui_set_statusbar(FALSE, text, count);
1925 break;
1927 case 1:
1928 msg = _("No matches found.");
1929 default:
1930 msgwin_msg_add_string(COLOR_BLUE, -1, NULL, msg);
1931 ui_set_statusbar(FALSE, "%s", msg);
1932 break;
1934 utils_beep();
1935 g_spawn_close_pid(child_pid);
1936 ui_progress_bar_stop();
1940 static GRegex *compile_regex(const gchar *str, GeanyFindFlags sflags)
1942 GRegex *regex;
1943 GError *error = NULL;
1944 gint rflags = 0;
1946 if (sflags & GEANY_FIND_MULTILINE)
1947 rflags |= G_REGEX_MULTILINE;
1948 if (~sflags & GEANY_FIND_MATCHCASE)
1949 rflags |= G_REGEX_CASELESS;
1950 if (sflags & (GEANY_FIND_WHOLEWORD | GEANY_FIND_WORDSTART))
1952 geany_debug("%s: Unsupported regex flags found!", G_STRFUNC);
1955 regex = g_regex_new(str, rflags, 0, &error);
1956 if (!regex)
1958 ui_set_statusbar(FALSE, _("Bad regex: %s"), error->message);
1959 g_error_free(error);
1961 return regex;
1965 /* groups that don't exist are handled OK as len = end - start = (-1) - (-1) = 0 */
1966 static gchar *get_regex_match_string(const gchar *text, const GeanyMatchInfo *match, guint nth)
1968 const gint start = match->matches[nth].start;
1969 const gint end = match->matches[nth].end;
1970 return g_strndup(&text[start], end - start);
1974 static gint find_regex(ScintillaObject *sci, guint pos, GRegex *regex, gboolean multiline, GeanyMatchInfo *match)
1976 const gchar *text;
1977 GMatchInfo *minfo;
1978 guint document_length;
1979 gint ret = -1;
1980 gint offset = 0;
1982 document_length = (guint)sci_get_length(sci);
1983 if (document_length <= 0)
1984 return -1; /* skip empty documents */
1986 g_return_val_if_fail(pos <= document_length, -1);
1988 if (multiline)
1990 /* Warning: any SCI calls will invalidate 'text' after calling SCI_GETCHARACTERPOINTER */
1991 text = (void*)scintilla_send_message(sci, SCI_GETCHARACTERPOINTER, 0, 0);
1992 g_regex_match_full(regex, text, -1, pos, 0, &minfo, NULL);
1994 else /* single-line mode, manually match against each line */
1996 gint line = sci_get_line_from_position(sci, pos);
1998 for (;;)
2000 gint start = sci_get_position_from_line(sci, line);
2001 gint end = sci_get_line_end_position(sci, line);
2003 text = (void*)scintilla_send_message(sci, SCI_GETRANGEPOINTER, start, end - start);
2004 if (g_regex_match_full(regex, text, end - start, pos - start, 0, &minfo, NULL))
2006 offset = start;
2007 break;
2009 else /* not found, try next line */
2011 line ++;
2012 if (line >= sci_get_line_count(sci))
2013 break;
2014 pos = sci_get_position_from_line(sci, line);
2015 /* don't free last info, it's freed below */
2016 g_match_info_free(minfo);
2021 /* Warning: minfo will become invalid when 'text' does! */
2022 if (g_match_info_matches(minfo))
2024 guint i;
2026 /* copy whole match text and offsets before they become invalid */
2027 SETPTR(match->match_text, g_match_info_fetch(minfo, 0));
2029 foreach_range(i, G_N_ELEMENTS(match->matches))
2031 gint start = -1, end = -1;
2033 g_match_info_fetch_pos(minfo, (gint)i, &start, &end);
2034 match->matches[i].start = offset + start;
2035 match->matches[i].end = offset + end;
2037 match->start = match->matches[0].start;
2038 match->end = match->matches[0].end;
2039 ret = match->start;
2041 g_match_info_free(minfo);
2042 return ret;
2046 static gint geany_find_flags_to_sci_flags(GeanyFindFlags flags)
2048 g_warn_if_fail(! (flags & GEANY_FIND_REGEXP) || ! (flags & GEANY_FIND_MULTILINE));
2050 return ((flags & GEANY_FIND_MATCHCASE) ? SCFIND_MATCHCASE : 0) |
2051 ((flags & GEANY_FIND_WHOLEWORD) ? SCFIND_WHOLEWORD : 0) |
2052 ((flags & GEANY_FIND_REGEXP) ? SCFIND_REGEXP | SCFIND_POSIX : 0) |
2053 ((flags & GEANY_FIND_WORDSTART) ? SCFIND_WORDSTART : 0);
2057 gint search_find_prev(ScintillaObject *sci, const gchar *str, GeanyFindFlags flags, GeanyMatchInfo **match_)
2059 gint ret;
2061 g_return_val_if_fail(! (flags & GEANY_FIND_REGEXP), -1);
2063 ret = sci_search_prev(sci, geany_find_flags_to_sci_flags(flags), str);
2064 if (ret != -1 && match_)
2065 *match_ = match_info_new(flags, ret, ret + strlen(str));
2066 return ret;
2070 gint search_find_next(ScintillaObject *sci, const gchar *str, GeanyFindFlags flags, GeanyMatchInfo **match_)
2072 GeanyMatchInfo *match;
2073 GRegex *regex;
2074 gint ret = -1;
2075 gint pos;
2077 if (~flags & GEANY_FIND_REGEXP)
2079 ret = sci_search_next(sci, geany_find_flags_to_sci_flags(flags), str);
2080 if (ret != -1 && match_)
2081 *match_ = match_info_new(flags, ret, ret + strlen(str));
2082 return ret;
2085 regex = compile_regex(str, flags);
2086 if (!regex)
2087 return -1;
2089 match = match_info_new(flags, 0, 0);
2091 pos = sci_get_current_position(sci);
2092 ret = find_regex(sci, pos, regex, flags & GEANY_FIND_MULTILINE, match);
2093 /* avoid re-matching the same position in case of empty matches */
2094 if (ret == pos && match->matches[0].start == match->matches[0].end)
2095 ret = find_regex(sci, pos + 1, regex, flags & GEANY_FIND_MULTILINE, match);
2096 if (ret >= 0)
2097 sci_set_selection(sci, match->start, match->end);
2099 if (ret != -1 && match_)
2100 *match_ = match;
2101 else
2102 geany_match_info_free(match);
2104 g_regex_unref(regex);
2105 return ret;
2109 gint search_replace_match(ScintillaObject *sci, const GeanyMatchInfo *match, const gchar *replace_text)
2111 GString *str;
2112 gint ret = 0;
2113 gint i = 0;
2115 sci_set_target_start(sci, match->start);
2116 sci_set_target_end(sci, match->end);
2118 if (! (match->flags & GEANY_FIND_REGEXP))
2119 return sci_replace_target(sci, replace_text, FALSE);
2121 str = g_string_new(replace_text);
2122 while (str->str[i])
2124 gchar *ptr = &str->str[i];
2125 gchar *grp;
2126 gchar c;
2128 if (ptr[0] != '\\')
2130 i++;
2131 continue;
2133 c = ptr[1];
2134 /* backslash or unnecessary escape */
2135 if (c == '\\' || !isdigit(c))
2137 g_string_erase(str, i, 1);
2138 i++;
2139 continue;
2141 /* digit escape */
2142 g_string_erase(str, i, 2);
2143 /* fix match offsets by subtracting index of whole match start from the string */
2144 grp = get_regex_match_string(match->match_text - match->matches[0].start, match, c - '0');
2145 g_string_insert(str, i, grp);
2146 i += strlen(grp);
2147 g_free(grp);
2149 ret = sci_replace_target(sci, str->str, FALSE);
2150 g_string_free(str, TRUE);
2151 return ret;
2155 gint search_find_text(ScintillaObject *sci, GeanyFindFlags flags, struct Sci_TextToFind *ttf, GeanyMatchInfo **match_)
2157 GeanyMatchInfo *match = NULL;
2158 GRegex *regex;
2159 gint ret;
2161 if (~flags & GEANY_FIND_REGEXP)
2163 ret = sci_find_text(sci, geany_find_flags_to_sci_flags(flags), ttf);
2164 if (ret != -1 && match_)
2165 *match_ = match_info_new(flags, ttf->chrgText.cpMin, ttf->chrgText.cpMax);
2166 return ret;
2169 regex = compile_regex(ttf->lpstrText, flags);
2170 if (!regex)
2171 return -1;
2173 match = match_info_new(flags, 0, 0);
2175 ret = find_regex(sci, ttf->chrg.cpMin, regex, flags & GEANY_FIND_MULTILINE, match);
2176 if (ret >= ttf->chrg.cpMax)
2177 ret = -1;
2178 else if (ret >= 0)
2180 ttf->chrgText.cpMin = match->start;
2181 ttf->chrgText.cpMax = match->end;
2184 if (ret != -1 && match_)
2185 *match_ = match;
2186 else
2187 geany_match_info_free(match);
2189 g_regex_unref(regex);
2190 return ret;
2194 static gint find_document_usage(GeanyDocument *doc, const gchar *search_text, GeanyFindFlags flags)
2196 gchar *buffer, *short_file_name;
2197 struct Sci_TextToFind ttf;
2198 gint count = 0;
2199 gint prev_line = -1;
2200 GSList *match, *matches;
2202 g_return_val_if_fail(DOC_VALID(doc), 0);
2204 short_file_name = g_path_get_basename(DOC_FILENAME(doc));
2206 ttf.chrg.cpMin = 0;
2207 ttf.chrg.cpMax = sci_get_length(doc->editor->sci);
2208 ttf.lpstrText = (gchar *)search_text;
2210 matches = find_range(doc->editor->sci, flags, &ttf);
2211 foreach_slist (match, matches)
2213 GeanyMatchInfo *info = match->data;
2214 gint line = sci_get_line_from_position(doc->editor->sci, info->start);
2216 if (line != prev_line)
2218 buffer = sci_get_line(doc->editor->sci, line);
2219 msgwin_msg_add(COLOR_BLACK, line + 1, doc,
2220 "%s:%d: %s", short_file_name, line + 1, g_strstrip(buffer));
2221 g_free(buffer);
2222 prev_line = line;
2224 count++;
2226 geany_match_info_free(info);
2228 g_slist_free(matches);
2229 g_free(short_file_name);
2230 return count;
2234 void search_find_usage(const gchar *search_text, const gchar *original_search_text,
2235 GeanyFindFlags flags, gboolean in_session)
2237 GeanyDocument *doc;
2238 gint count = 0;
2240 doc = document_get_current();
2241 g_return_if_fail(doc != NULL);
2243 if (G_UNLIKELY(EMPTY(search_text)))
2245 utils_beep();
2246 return;
2249 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_MESSAGE);
2250 gtk_list_store_clear(msgwindow.store_msg);
2252 if (! in_session)
2253 { /* use current document */
2254 count = find_document_usage(doc, search_text, flags);
2256 else
2258 guint i;
2259 for (i = 0; i < documents_array->len; i++)
2261 if (documents[i]->is_valid)
2263 count += find_document_usage(documents[i], search_text, flags);
2268 if (count == 0) /* no matches were found */
2270 ui_set_statusbar(FALSE, _("No matches found for \"%s\"."), original_search_text);
2271 msgwin_msg_add(COLOR_BLUE, -1, NULL, _("No matches found for \"%s\"."), original_search_text);
2273 else
2275 ui_set_statusbar(FALSE, ngettext(
2276 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count),
2277 count, original_search_text);
2278 msgwin_msg_add(COLOR_BLUE, -1, NULL, ngettext(
2279 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count),
2280 count, original_search_text);
2285 /* ttf is updated to include the last match position (ttf->chrg.cpMin) and
2286 * the new search range end (ttf->chrg.cpMax).
2287 * Note: Normally you would call sci_start/end_undo_action() around this call. */
2288 guint search_replace_range(ScintillaObject *sci, struct Sci_TextToFind *ttf,
2289 GeanyFindFlags flags, const gchar *replace_text)
2291 gint count = 0;
2292 gint offset = 0; /* difference between search pos and replace pos */
2293 GSList *match, *matches;
2295 g_return_val_if_fail(sci != NULL && ttf->lpstrText != NULL && replace_text != NULL, 0);
2296 if (! *ttf->lpstrText)
2297 return 0;
2299 matches = find_range(sci, flags, ttf);
2300 foreach_slist (match, matches)
2302 GeanyMatchInfo *info = match->data;
2303 gint replace_len;
2305 info->start += offset;
2306 info->end += offset;
2308 replace_len = search_replace_match(sci, info, replace_text);
2309 offset += replace_len - (info->end - info->start);
2310 count ++;
2312 /* on last match, update the last match/new range end */
2313 if (! match->next)
2315 ttf->chrg.cpMin = info->start;
2316 ttf->chrg.cpMax += offset;
2319 geany_match_info_free(info);
2321 g_slist_free(matches);
2323 return count;
2327 void search_find_again(gboolean change_direction)
2329 GeanyDocument *doc = document_get_current();
2331 g_return_if_fail(doc != NULL);
2333 if (search_data.text)
2335 gboolean forward = ! search_data.backwards;
2336 gint result = document_find_text(doc, search_data.text, search_data.original_text, search_data.flags,
2337 change_direction ? forward : !forward, NULL, FALSE, NULL);
2339 if (result > -1)
2340 editor_display_current_line(doc->editor, 0.3F);
2342 if (search_data.search_bar)
2343 ui_set_search_entry_background(
2344 toolbar_get_widget_child_by_name("SearchEntry"), (result > -1));