/* * search.c - this file is part of Geany, a fast and lightweight IDE * * Copyright 2006-2007 Enrico Tröger * Copyright 2006-2007 Nick Treleaven * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Id$ */ /* * Find, Replace, Find in Files dialog related functions. * Note that the basic text find functions are in document.c. */ #include #include "geany.h" #include "search.h" #include "prefs.h" #include "support.h" #include "utils.h" #include "msgwindow.h" #include "document.h" #include "sciwrappers.h" #include "ui_utils.h" #include "editor.h" #include #include #ifdef G_OS_UNIX # include # include #endif enum { GEANY_RESPONSE_FIND = 1, GEANY_RESPONSE_FIND_PREVIOUS, GEANY_RESPONSE_FIND_IN_FILE, GEANY_RESPONSE_FIND_IN_SESSION, GEANY_RESPONSE_MARK, GEANY_RESPONSE_REPLACE, GEANY_RESPONSE_REPLACE_AND_FIND, GEANY_RESPONSE_REPLACE_IN_SESSION, GEANY_RESPONSE_REPLACE_IN_FILE, GEANY_RESPONSE_REPLACE_IN_SEL }; GeanySearchData search_data; SearchPrefs search_prefs = {NULL}; static struct { GtkWidget *find_dialog; GtkWidget *replace_dialog; GtkWidget *find_in_files_dialog; } widgets; static gboolean search_read_io (GIOChannel *source, GIOCondition condition, gpointer data); static void search_close_pid(GPid child_pid, gint status, gpointer user_data); static gchar **search_get_argv(const gchar **argv_prefix, const gchar *dir); static void on_find_replace_checkbutton_toggled(GtkToggleButton *togglebutton, gpointer user_data); static void on_find_dialog_response(GtkDialog *dialog, gint response, gpointer user_data); static void on_find_entry_activate(GtkEntry *entry, gpointer user_data); static void on_replace_dialog_response(GtkDialog *dialog, gint response, gpointer user_data); static void on_replace_entry_activate(GtkEntry *entry, gpointer user_data); static gboolean on_widget_key_pressed_set_focus(GtkWidget *widget, GdkEventKey *event, gpointer user_data); static void on_find_in_files_dialog_response(GtkDialog *dialog, gint response, gpointer user_data); static gboolean search_find_in_files(const gchar *search_text, const gchar *dir, const gchar *opts); void search_init() { widgets.find_dialog = NULL; widgets.replace_dialog = NULL; widgets.find_in_files_dialog = NULL; search_data.text = NULL; } #define FREE_WIDGET(wid) \ if (wid && GTK_IS_WIDGET(wid)) gtk_widget_destroy(wid); void search_finalize() { FREE_WIDGET(widgets.find_dialog); FREE_WIDGET(widgets.replace_dialog); FREE_WIDGET(widgets.find_in_files_dialog); g_free(search_data.text); } static GtkWidget *add_find_checkboxes(GtkDialog *dialog) { GtkWidget *checkbox1, *checkbox2, *check_regexp, *check_back, *checkbox5, *checkbox7, *hbox, *fbox, *mbox; GtkTooltips *tooltips = GTK_TOOLTIPS(lookup_widget(app->window, "tooltips")); check_regexp = gtk_check_button_new_with_mnemonic(_("_Use regular expressions")); g_object_set_data_full(G_OBJECT(dialog), "check_regexp", gtk_widget_ref(check_regexp), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp), FALSE); gtk_tooltips_set_tip(tooltips, check_regexp, _("Use POSIX-like regular expressions. " "For detailed information about using regular expressions, please read the documentation."), NULL); g_signal_connect(G_OBJECT(check_regexp), "toggled", G_CALLBACK(on_find_replace_checkbutton_toggled), GTK_WIDGET(dialog)); if (dialog != GTK_DIALOG(widgets.find_dialog)) { check_back = gtk_check_button_new_with_mnemonic(_("Search _backwards")); g_object_set_data_full(G_OBJECT(dialog), "check_back", gtk_widget_ref(check_back), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(check_back), FALSE); } else { // align the two checkboxes at the top of the hbox GtkSizeGroup *label_size; check_back = gtk_label_new(NULL); label_size = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL); gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size), check_back); gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size), check_regexp); g_object_unref(label_size); } checkbox7 = gtk_check_button_new_with_mnemonic(_("Use _escape sequences")); g_object_set_data_full(G_OBJECT(dialog), "check_escape", gtk_widget_ref(checkbox7), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(checkbox7), FALSE); gtk_tooltips_set_tip(tooltips, checkbox7, _("Replace \\\\, \\t, \\n, \\r and \\uXXXX (Unicode chararacters) with the " "corresponding control characters."), NULL); // Search features fbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(fbox), check_regexp); gtk_container_add(GTK_CONTAINER(fbox), checkbox7); gtk_container_add(GTK_CONTAINER(fbox), check_back); checkbox1 = gtk_check_button_new_with_mnemonic(_("C_ase sensitive")); g_object_set_data_full(G_OBJECT(dialog), "check_case", gtk_widget_ref(checkbox1), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1), FALSE); checkbox2 = gtk_check_button_new_with_mnemonic(_("Match only a _whole word")); g_object_set_data_full(G_OBJECT(dialog), "check_word", gtk_widget_ref(checkbox2), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2), FALSE); checkbox5 = gtk_check_button_new_with_mnemonic(_("Match from s_tart of word")); g_object_set_data_full(G_OBJECT(dialog), "check_wordstart", gtk_widget_ref(checkbox5), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(checkbox5), FALSE); // Matching options mbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(mbox), checkbox1); gtk_container_add(GTK_CONTAINER(mbox), checkbox2); gtk_container_add(GTK_CONTAINER(mbox), checkbox5); hbox = gtk_hbox_new(TRUE, 6); gtk_container_add(GTK_CONTAINER(hbox), fbox); gtk_container_add(GTK_CONTAINER(hbox), mbox); return hbox; } static void send_find_dialog_response(GtkButton *button, gpointer user_data) { gtk_dialog_response(GTK_DIALOG(widgets.find_dialog), GPOINTER_TO_INT(user_data)); } static gchar *get_default_text(gint idx) { gchar *s = NULL; if (sci_get_lines_selected(doc_list[idx].sci) == 1) { gint len = sci_get_selected_text_length(doc_list[idx].sci); s = g_malloc(len + 1); sci_get_selected_text(doc_list[idx].sci, s); } else if (sci_get_lines_selected(doc_list[idx].sci) == 0) { // use the word at current cursor position gchar word[GEANY_MAX_WORD_LENGTH]; editor_find_current_word(doc_list[idx].sci, -1, word, sizeof(word), NULL); if (word[0] != '\0') s = g_strdup(word); } return s; } // store text, clear search flags so we can use Search->Find Next/Previous static void setup_find_next(const gchar *text) { g_free(search_data.text); search_data.text = g_strdup(text); search_data.flags = 0; search_data.backwards = FALSE; search_data.search_bar = FALSE; } /* Search for next match of the current "selection" * For X11 based systems, this will try to use the system-wide * x-selection first. If it doesn't find anything suitable in * the x-selection (or if we are on Win32) it will try to use * the scintilla selection or current token instead. * Search flags are always zero. */ void search_find_selection(gint idx, gboolean search_backwards) { gchar *s = NULL; #ifdef G_OS_UNIX GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); #endif g_return_if_fail(DOC_IDX_VALID(idx)); #ifdef G_OS_UNIX s=gtk_clipboard_wait_for_text(clipboard); if (s) { if (strchr(s,'\n') || strchr(s, '\r')) { g_free(s); s=NULL; }; } #endif if (!s) { s=get_default_text(idx); } if (s) { setup_find_next(s); // allow find next/prev document_find_text(idx, s, 0, search_backwards, TRUE, NULL); g_free(s); } } void search_show_find_dialog() { gint idx = document_get_cur_idx(); gchar *sel = NULL; g_return_if_fail(DOC_IDX_VALID(idx)); sel = get_default_text(idx); if (widgets.find_dialog == NULL) { GtkWidget *label, *entry, *sbox, *vbox; GtkWidget *exp, *bbox, *button, *check_close; GtkTooltips *tooltips = GTK_TOOLTIPS(lookup_widget(app->window, "tooltips")); widgets.find_dialog = gtk_dialog_new_with_buttons(_("Find"), GTK_WINDOW(app->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL, NULL); vbox = ui_dialog_vbox_new(GTK_DIALOG(widgets.find_dialog)); gtk_widget_set_name(widgets.find_dialog, "GeanyDialogSearch"); gtk_box_set_spacing(GTK_BOX(vbox), 9); button = ui_button_new_with_image(GTK_STOCK_GO_BACK, _("_Previous")); gtk_dialog_add_action_widget(GTK_DIALOG(widgets.find_dialog), button, GEANY_RESPONSE_FIND_PREVIOUS); g_object_set_data_full(G_OBJECT(widgets.find_dialog), "btn_previous", gtk_widget_ref(button), (GDestroyNotify)gtk_widget_unref); button = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, _("_Next")); gtk_dialog_add_action_widget(GTK_DIALOG(widgets.find_dialog), button, GEANY_RESPONSE_FIND); label = gtk_label_new_with_mnemonic(_("_Search for:")); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); entry = gtk_combo_box_entry_new_text(); gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry); gtk_entry_set_max_length(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))), 248); gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))), 50); if (sel) gtk_entry_set_text(GTK_ENTRY(GTK_BIN(entry)->child), sel); g_object_set_data_full(G_OBJECT(widgets.find_dialog), "entry", gtk_widget_ref(entry), (GDestroyNotify)gtk_widget_unref); g_signal_connect((gpointer) gtk_bin_get_child(GTK_BIN(entry)), "activate", G_CALLBACK(on_find_entry_activate), NULL); g_signal_connect((gpointer) widgets.find_dialog, "response", G_CALLBACK(on_find_dialog_response), entry); g_signal_connect((gpointer) widgets.find_dialog, "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); sbox = gtk_hbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(sbox), label, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(sbox), entry, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), sbox, TRUE, FALSE, 0); gtk_container_add(GTK_CONTAINER(vbox), add_find_checkboxes(GTK_DIALOG(widgets.find_dialog))); // Now add the multiple match options exp = gtk_expander_new(_("Find All")); bbox = gtk_hbutton_box_new(); button = gtk_button_new_with_mnemonic(_("_Mark")); gtk_tooltips_set_tip(tooltips, button, _("Mark all matches in the current document."), NULL); gtk_container_add(GTK_CONTAINER(bbox), button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(send_find_dialog_response), GINT_TO_POINTER(GEANY_RESPONSE_MARK)); button = gtk_button_new_with_mnemonic(_("In Sessi_on")); gtk_container_add(GTK_CONTAINER(bbox), button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(send_find_dialog_response), GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_SESSION)); button = gtk_button_new_with_mnemonic(_("_In Document")); gtk_container_add(GTK_CONTAINER(bbox), button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(send_find_dialog_response), GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_FILE)); // close window checkbox check_close = gtk_check_button_new_with_mnemonic(_("Close _dialog")); g_object_set_data_full(G_OBJECT(widgets.find_dialog), "check_close", gtk_widget_ref(check_close), (GDestroyNotify) gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(check_close), FALSE); gtk_tooltips_set_tip(tooltips, check_close, _("Disable this option to keep the dialog open."), NULL); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_close), TRUE); gtk_container_add(GTK_CONTAINER(bbox), check_close); gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), check_close, TRUE); ui_hbutton_box_copy_layout( GTK_BUTTON_BOX(GTK_DIALOG(widgets.find_dialog)->action_area), GTK_BUTTON_BOX(bbox)); gtk_container_add(GTK_CONTAINER(exp), bbox); gtk_container_add(GTK_CONTAINER(vbox), exp); gtk_widget_show_all(widgets.find_dialog); } else { // only set selection if the dialog is not already visible if (! GTK_WIDGET_VISIBLE(widgets.find_dialog) && sel) gtk_entry_set_text(GTK_ENTRY(GTK_BIN( lookup_widget(widgets.find_dialog, "entry"))->child), sel); gtk_widget_grab_focus(GTK_WIDGET(GTK_BIN(lookup_widget(widgets.find_dialog, "entry"))->child)); gtk_widget_show(widgets.find_dialog); // bring the dialog back in the foreground in case it is already open but the focus is away gtk_window_present(GTK_WINDOW(widgets.find_dialog)); } g_free(sel); } static void send_replace_dialog_response(GtkButton *button, gpointer user_data) { gtk_dialog_response(GTK_DIALOG(widgets.replace_dialog), GPOINTER_TO_INT(user_data)); } void search_show_replace_dialog() { gint idx = document_get_cur_idx(); gchar *sel = NULL; if (idx == -1 || ! doc_list[idx].is_valid) return; sel = get_default_text(idx); if (widgets.replace_dialog == NULL) { GtkWidget *label_find, *label_replace, *entry_find, *entry_replace, *check_close, *button, *rbox, *fbox, *vbox, *exp, *bbox; GtkSizeGroup *label_size; GtkTooltips *tooltips = GTK_TOOLTIPS(lookup_widget(app->window, "tooltips")); widgets.replace_dialog = gtk_dialog_new_with_buttons(_("Replace"), GTK_WINDOW(app->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL, NULL); vbox = ui_dialog_vbox_new(GTK_DIALOG(widgets.replace_dialog)); gtk_box_set_spacing(GTK_BOX(vbox), 9); gtk_widget_set_name(widgets.replace_dialog, "GeanyDialogSearch"); button = gtk_button_new_from_stock(GTK_STOCK_FIND); gtk_dialog_add_action_widget(GTK_DIALOG(widgets.replace_dialog), button, GEANY_RESPONSE_FIND); button = gtk_button_new_with_mnemonic(_("_Replace")); gtk_dialog_add_action_widget(GTK_DIALOG(widgets.replace_dialog), button, GEANY_RESPONSE_REPLACE); button = gtk_button_new_with_mnemonic(_("Replace & Fi_nd")); gtk_dialog_add_action_widget(GTK_DIALOG(widgets.replace_dialog), button, GEANY_RESPONSE_REPLACE_AND_FIND); label_find = gtk_label_new_with_mnemonic(_("_Search for:")); gtk_misc_set_alignment(GTK_MISC(label_find), 0, 0.5); label_replace = gtk_label_new_with_mnemonic(_("Re_place with:")); gtk_misc_set_alignment(GTK_MISC(label_replace), 0, 0.5); entry_find = gtk_combo_box_entry_new_text(); gtk_label_set_mnemonic_widget(GTK_LABEL(label_find), entry_find); gtk_entry_set_max_length(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find))), 248); gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find))), 50); if (sel) gtk_entry_set_text(GTK_ENTRY(GTK_BIN(entry_find)->child), sel); g_object_set_data_full(G_OBJECT(widgets.replace_dialog), "entry_find", gtk_widget_ref(entry_find), (GDestroyNotify)gtk_widget_unref); entry_replace = gtk_combo_box_entry_new_text(); gtk_label_set_mnemonic_widget(GTK_LABEL(label_replace), entry_replace); gtk_entry_set_max_length(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace))), 248); gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace))), 50); g_object_set_data_full(G_OBJECT(widgets.replace_dialog), "entry_replace", gtk_widget_ref(entry_replace), (GDestroyNotify)gtk_widget_unref); g_signal_connect((gpointer) gtk_bin_get_child(GTK_BIN(entry_find)), "key-press-event", G_CALLBACK(on_widget_key_pressed_set_focus), gtk_bin_get_child(GTK_BIN(entry_replace))); g_signal_connect((gpointer) gtk_bin_get_child(GTK_BIN(entry_replace)), "activate", G_CALLBACK(on_replace_entry_activate), NULL); g_signal_connect((gpointer) widgets.replace_dialog, "response", G_CALLBACK(on_replace_dialog_response), entry_replace); g_signal_connect((gpointer) widgets.replace_dialog, "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); fbox = gtk_hbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(fbox), label_find, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(fbox), entry_find, TRUE, TRUE, 0); rbox = gtk_hbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(rbox), label_replace, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(rbox), entry_replace, TRUE, TRUE, 0); label_size = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); gtk_size_group_add_widget(label_size, label_find); gtk_size_group_add_widget(label_size, label_replace); g_object_unref(G_OBJECT(label_size)); // auto destroy the size group gtk_box_pack_start(GTK_BOX(vbox), fbox, TRUE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), rbox, TRUE, FALSE, 0); gtk_container_add(GTK_CONTAINER(vbox), add_find_checkboxes(GTK_DIALOG(widgets.replace_dialog))); // Now add the multiple replace options exp = gtk_expander_new(_("Replace All")); bbox = gtk_hbutton_box_new(); button = gtk_button_new_with_mnemonic(_("In Se_lection")); gtk_tooltips_set_tip(tooltips, button, _("Replace all matches found in the currently selected text"), NULL); gtk_container_add(GTK_CONTAINER(bbox), button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(send_replace_dialog_response), GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SEL)); button = gtk_button_new_with_mnemonic(_("In Sessi_on")); gtk_container_add(GTK_CONTAINER(bbox), button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(send_replace_dialog_response), GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SESSION)); button = gtk_button_new_with_mnemonic(_("_In Document")); gtk_container_add(GTK_CONTAINER(bbox), button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(send_replace_dialog_response), GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_FILE)); // close window checkbox check_close = gtk_check_button_new_with_mnemonic(_("Close _dialog")); g_object_set_data_full(G_OBJECT(widgets.replace_dialog), "check_close", gtk_widget_ref(check_close), (GDestroyNotify) gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(check_close), FALSE); gtk_tooltips_set_tip(tooltips, check_close, _("Disable this option to keep the dialog open."), NULL); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_close), TRUE); gtk_container_add(GTK_CONTAINER(bbox), check_close); gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), check_close, TRUE); ui_hbutton_box_copy_layout( GTK_BUTTON_BOX(GTK_DIALOG(widgets.replace_dialog)->action_area), GTK_BUTTON_BOX(bbox)); gtk_container_add(GTK_CONTAINER(exp), bbox); gtk_container_add(GTK_CONTAINER(vbox), exp); gtk_widget_show_all(widgets.replace_dialog); } else { // only set selection if the dialog is not already visible if (! GTK_WIDGET_VISIBLE(widgets.replace_dialog) && sel) gtk_entry_set_text(GTK_ENTRY(GTK_BIN( lookup_widget(widgets.replace_dialog, "entry_find"))->child), sel); gtk_widget_grab_focus(GTK_WIDGET(GTK_BIN(lookup_widget(widgets.replace_dialog, "entry_find"))->child)); gtk_widget_show(widgets.replace_dialog); // bring the dialog back in the foreground in case it is already open but the focus is away gtk_window_present(GTK_WINDOW(widgets.replace_dialog)); } g_free(sel); } static void on_extra_options_toggled(GtkToggleButton *togglebutton, gpointer user_data) { // disable extra option entry when checkbutton not checked gtk_widget_set_sensitive(GTK_WIDGET(user_data), gtk_toggle_button_get_active(togglebutton)); } void search_show_find_in_files_dialog() { static GtkWidget *combo = NULL; static GtkWidget *dir_combo; GtkWidget *entry; // the child GtkEntry of combo (or dir_combo) gint idx = document_get_cur_idx(); gchar *sel = NULL; gchar *cur_dir; if (idx == -1 || ! doc_list[idx].is_valid) return; if (widgets.find_in_files_dialog == NULL) { GtkWidget *label, *label1, *checkbox1, *checkbox2, *check_wholeword, *check_recursive, *check_extra, *entry_extra; GtkWidget *dbox, *sbox, *cbox, *rbox, *rbtn, *hbox, *vbox; GtkSizeGroup *size_group; GtkTooltips *tooltips = GTK_TOOLTIPS(lookup_widget(app->window, "tooltips")); widgets.find_in_files_dialog = gtk_dialog_new_with_buttons( _("Find in Files"), GTK_WINDOW(app->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); vbox = ui_dialog_vbox_new(GTK_DIALOG(widgets.find_in_files_dialog)); gtk_box_set_spacing(GTK_BOX(vbox), 9); gtk_widget_set_name(widgets.find_in_files_dialog, "GeanyDialogSearch"); gtk_dialog_add_button(GTK_DIALOG(widgets.find_in_files_dialog), "gtk-find", GTK_RESPONSE_ACCEPT); gtk_dialog_set_default_response(GTK_DIALOG(widgets.find_in_files_dialog), GTK_RESPONSE_ACCEPT); label1 = gtk_label_new_with_mnemonic(_("_Directory:")); gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5); dir_combo = gtk_combo_box_entry_new_text(); entry = gtk_bin_get_child(GTK_BIN(dir_combo)); gtk_label_set_mnemonic_widget(GTK_LABEL(label1), entry); gtk_entry_set_max_length(GTK_ENTRY(entry), 248); gtk_entry_set_width_chars(GTK_ENTRY(entry), 50); g_object_set_data_full(G_OBJECT(widgets.find_in_files_dialog), "dir_combo", gtk_widget_ref(dir_combo), (GDestroyNotify)gtk_widget_unref); dbox = ui_path_box_new(NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_ENTRY(entry)); gtk_box_pack_start(GTK_BOX(dbox), label1, FALSE, FALSE, 0); label = gtk_label_new_with_mnemonic(_("_Search for:")); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); combo = gtk_combo_box_entry_new_text(); entry = gtk_bin_get_child(GTK_BIN(combo)); gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry); gtk_entry_set_max_length(GTK_ENTRY(entry), 248); gtk_entry_set_width_chars(GTK_ENTRY(entry), 50); gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); sbox = gtk_hbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(sbox), label, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(sbox), combo, TRUE, TRUE, 0); size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); gtk_size_group_add_widget(size_group, label1); gtk_size_group_add_widget(size_group, label); g_object_unref(G_OBJECT(size_group)); // auto destroy the size group rbox = gtk_vbox_new(FALSE, 0); rbtn = gtk_radio_button_new_with_mnemonic(NULL, _("_Fixed strings")); // Make fixed strings the default to speed up searching all files in directory. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rbtn), TRUE); g_object_set_data_full(G_OBJECT(widgets.find_in_files_dialog), "radio_fgrep", gtk_widget_ref(rbtn), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(rbtn), FALSE); gtk_container_add(GTK_CONTAINER(rbox), rbtn); rbtn = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(rbtn), _("_Grep regular expressions")); g_object_set_data_full(G_OBJECT(widgets.find_in_files_dialog), "radio_grep", gtk_widget_ref(rbtn), (GDestroyNotify)gtk_widget_unref); gtk_tooltips_set_tip(tooltips, rbtn, _("See grep's manual page for more information."), NULL); gtk_button_set_focus_on_click(GTK_BUTTON(rbtn), FALSE); gtk_container_add(GTK_CONTAINER(rbox), rbtn); rbtn = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(rbtn), _("_Extended regular expressions")); gtk_tooltips_set_tip(tooltips, rbtn, _("See grep's manual page for more information."), NULL); gtk_button_set_focus_on_click(GTK_BUTTON(rbtn), FALSE); gtk_container_add(GTK_CONTAINER(rbox), rbtn); check_recursive = gtk_check_button_new_with_mnemonic(_("_Recurse in subfolders")); g_object_set_data_full(G_OBJECT(widgets.find_in_files_dialog), "check_recursive", gtk_widget_ref(check_recursive), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(check_recursive), FALSE); checkbox1 = gtk_check_button_new_with_mnemonic(_("C_ase sensitive")); g_object_set_data_full(G_OBJECT(widgets.find_in_files_dialog), "check_case", gtk_widget_ref(checkbox1), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox1), TRUE); check_wholeword = gtk_check_button_new_with_mnemonic(_("Match only a _whole word")); g_object_set_data_full(G_OBJECT(widgets.find_in_files_dialog), "check_wholeword", gtk_widget_ref(check_wholeword), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(check_wholeword), FALSE); checkbox2 = gtk_check_button_new_with_mnemonic(_("_Invert search results")); g_object_set_data_full(G_OBJECT(widgets.find_in_files_dialog), "check_invert", gtk_widget_ref(checkbox2), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2), FALSE); gtk_tooltips_set_tip(tooltips, checkbox2, _("Invert the sense of matching, to select non-matching lines."), NULL); cbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(cbox), checkbox1); gtk_container_add(GTK_CONTAINER(cbox), check_wholeword); gtk_container_add(GTK_CONTAINER(cbox), checkbox2); gtk_container_add(GTK_CONTAINER(cbox), check_recursive); hbox = gtk_hbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(hbox), rbox); gtk_container_add(GTK_CONTAINER(hbox), cbox); gtk_box_pack_start(GTK_BOX(vbox), dbox, TRUE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), sbox, TRUE, FALSE, 0); gtk_container_add(GTK_CONTAINER(vbox), hbox); check_extra = gtk_check_button_new_with_mnemonic(_("E_xtra options:")); g_object_set_data_full(G_OBJECT(widgets.find_in_files_dialog), "check_extra", gtk_widget_ref(check_extra), (GDestroyNotify)gtk_widget_unref); gtk_button_set_focus_on_click(GTK_BUTTON(check_extra), FALSE); entry_extra = gtk_entry_new(); if (search_prefs.fif_extra_options) gtk_entry_set_text(GTK_ENTRY(entry_extra), search_prefs.fif_extra_options); gtk_widget_set_sensitive(entry_extra, FALSE); g_object_set_data_full(G_OBJECT(widgets.find_in_files_dialog), "entry_extra", gtk_widget_ref(entry_extra), (GDestroyNotify)gtk_widget_unref); gtk_tooltips_set_tip(tooltips, entry_extra, _("Other options to pass to Grep"), NULL); // enable entry_extra when check_extra is checked g_signal_connect(G_OBJECT(check_extra), "toggled", G_CALLBACK(on_extra_options_toggled), entry_extra); hbox = gtk_hbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(hbox), check_extra, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), entry_extra, TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(vbox), hbox); g_signal_connect((gpointer) dir_combo, "key-press-event", G_CALLBACK(on_widget_key_pressed_set_focus), combo); g_signal_connect((gpointer) widgets.find_in_files_dialog, "response", G_CALLBACK(on_find_in_files_dialog_response), combo); g_signal_connect((gpointer) widgets.find_in_files_dialog, "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); gtk_widget_show_all(widgets.find_in_files_dialog); sel = get_default_text(idx); } entry = GTK_BIN(combo)->child; // only set selection if the dialog is not already visible, or has just been created if (! sel && ! GTK_WIDGET_VISIBLE(widgets.find_in_files_dialog)) sel = get_default_text(idx); if (sel) gtk_entry_set_text(GTK_ENTRY(entry), sel); g_free(sel); entry = GTK_BIN(dir_combo)->child; cur_dir = utils_get_current_file_dir(); if (cur_dir) { gtk_entry_set_text(GTK_ENTRY(entry), cur_dir); g_free(cur_dir); } // put the focus to the directory entry if it is empty if (utils_str_equal(gtk_entry_get_text(GTK_ENTRY(entry)), "")) gtk_widget_grab_focus(dir_combo); else gtk_widget_grab_focus(combo); gtk_widget_show(widgets.find_in_files_dialog); // bring the dialog back in the foreground in case it is already open but the focus is away gtk_window_present(GTK_WINDOW(widgets.find_in_files_dialog)); } static void on_find_replace_checkbutton_toggled(GtkToggleButton *togglebutton, gpointer user_data) { GtkWidget *dialog = GTK_WIDGET(user_data); GtkToggleButton *chk_regexp = GTK_TOGGLE_BUTTON( lookup_widget(dialog, "check_regexp")); if (togglebutton == chk_regexp) { gboolean regex_set = gtk_toggle_button_get_active(chk_regexp); GtkWidget *check_word = lookup_widget(dialog, "check_word"); GtkWidget *check_wordstart = lookup_widget(dialog, "check_wordstart"); GtkToggleButton *check_case = GTK_TOGGLE_BUTTON( lookup_widget(dialog, "check_case")); static gboolean case_state = FALSE; // state before regex enabled // hide options that don't apply to regex searches if (dialog == widgets.find_dialog) gtk_widget_set_sensitive(lookup_widget(dialog, "btn_previous"), ! regex_set); else gtk_widget_set_sensitive(lookup_widget(dialog, "check_back"), ! regex_set); gtk_widget_set_sensitive(check_word, ! regex_set); gtk_widget_set_sensitive(check_wordstart, ! regex_set); if (regex_set) // regex enabled { // Enable case sensitive but remember original case toggle state case_state = gtk_toggle_button_get_active(check_case); gtk_toggle_button_set_active(check_case, TRUE); } else // regex disabled { // If case sensitive is still enabled, revert to what it was before we enabled it if (gtk_toggle_button_get_active(check_case) == TRUE) gtk_toggle_button_set_active(check_case, case_state); } } } static gint search_mark(gint idx, const gchar *search_text, gint flags) { gint pos, line, count = 0; struct TextToFind ttf; g_return_val_if_fail(DOC_IDX_VALID(idx), 0); ttf.chrg.cpMin = 0; ttf.chrg.cpMax = sci_get_length(doc_list[idx].sci); ttf.lpstrText = (gchar *)search_text; while (1) { pos = sci_find_text(doc_list[idx].sci, flags, &ttf); if (pos == -1) break; line = sci_get_line_from_position(doc_list[idx].sci, pos); sci_set_marker_at_line(doc_list[idx].sci, line, TRUE, 1); ttf.chrg.cpMin = ttf.chrgText.cpMax + 1; count++; } return count; } static void on_find_entry_activate(GtkEntry *entry, gpointer user_data) { on_find_dialog_response(NULL, GEANY_RESPONSE_FIND, lookup_widget(GTK_WIDGET(entry), "entry")); } static void on_find_dialog_response(GtkDialog *dialog, gint response, gpointer user_data) { if (response == GTK_RESPONSE_CANCEL || response == GTK_RESPONSE_DELETE_EVENT) gtk_widget_hide(widgets.find_dialog); else { gint idx = document_get_cur_idx(); gboolean search_replace_escape; gboolean fl1 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.find_dialog), "check_case"))), fl2 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.find_dialog), "check_word"))), fl3 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.find_dialog), "check_regexp"))), fl4 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.find_dialog), "check_wordstart"))), check_close = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.find_dialog), "check_close"))); search_replace_escape = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.find_dialog), "check_escape"))); search_data.backwards = FALSE; search_data.search_bar = FALSE; if (! DOC_IDX_VALID(idx)) return; g_free(search_data.text); search_data.text = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(user_data))))); if (strlen(search_data.text) == 0 || (search_replace_escape && ! utils_str_replace_escape(search_data.text))) { utils_beep(); gtk_widget_grab_focus(GTK_WIDGET(GTK_BIN(lookup_widget(widgets.find_dialog, "entry"))->child)); return; } ui_combo_box_add_to_history(GTK_COMBO_BOX(user_data), search_data.text); search_data.flags = (fl1 ? SCFIND_MATCHCASE : 0) | (fl2 ? SCFIND_WHOLEWORD : 0) | (fl3 ? SCFIND_REGEXP | SCFIND_POSIX: 0) | (fl4 ? SCFIND_WORDSTART : 0); switch (response) { case GEANY_RESPONSE_FIND: case GEANY_RESPONSE_FIND_PREVIOUS: document_find_text(idx, search_data.text, search_data.flags, (response == GEANY_RESPONSE_FIND_PREVIOUS), TRUE, GTK_WIDGET(widgets.find_dialog)); check_close = FALSE; if (prefs.suppress_search_dialogs) check_close = TRUE; break; case GEANY_RESPONSE_FIND_IN_FILE: search_find_usage(search_data.text, search_data.flags, FALSE); break; case GEANY_RESPONSE_FIND_IN_SESSION: search_find_usage(search_data.text, search_data.flags, TRUE); break; case GEANY_RESPONSE_MARK: if (DOC_IDX_VALID(idx)) { gint count = search_mark(idx, search_data.text, search_data.flags); if (count == 0) ui_set_statusbar(_("No matches found for \"%s\"."), search_data.text); else ui_set_statusbar(_("Found %d matches for \"%s\"."), count, search_data.text); } break; } if (check_close) gtk_widget_hide(widgets.find_dialog); } } static void on_replace_entry_activate(GtkEntry *entry, gpointer user_data) { on_replace_dialog_response(NULL, GEANY_RESPONSE_REPLACE, NULL); } static void on_replace_dialog_response(GtkDialog *dialog, gint response, gpointer user_data) { gint idx = document_get_cur_idx(); GtkWidget *entry_find = lookup_widget(GTK_WIDGET(widgets.replace_dialog), "entry_find"); GtkWidget *entry_replace = lookup_widget(GTK_WIDGET(widgets.replace_dialog), "entry_replace"); gint search_flags_re; gboolean search_backwards_re, search_replace_escape_re; gboolean fl1, fl2, fl3, fl4; gboolean close_window; gchar *find, *replace; if (response == GTK_RESPONSE_CANCEL || response == GTK_RESPONSE_DELETE_EVENT) { gtk_widget_hide(widgets.replace_dialog); return; } fl1 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.replace_dialog), "check_case"))); fl2 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.replace_dialog), "check_word"))); fl3 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.replace_dialog), "check_regexp"))); fl4 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.replace_dialog), "check_wordstart"))); close_window = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.replace_dialog), "check_close"))); search_backwards_re = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.replace_dialog), "check_back"))); search_replace_escape_re = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(GTK_WIDGET(widgets.replace_dialog), "check_escape"))); find = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find))))); replace = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace))))); if ((response != GEANY_RESPONSE_FIND) && fl1 && (strcasecmp(find, replace) == 0)) { utils_beep(); gtk_widget_grab_focus(GTK_WIDGET(GTK_BIN(lookup_widget(widgets.replace_dialog, "entry_find"))->child)); return; } ui_combo_box_add_to_history(GTK_COMBO_BOX(entry_find), find); ui_combo_box_add_to_history(GTK_COMBO_BOX(entry_replace), replace); if (search_replace_escape_re && (! utils_str_replace_escape(find) || ! utils_str_replace_escape(replace))) { utils_beep(); gtk_widget_grab_focus(GTK_WIDGET(GTK_BIN(lookup_widget(widgets.replace_dialog, "entry_find"))->child)); return; } search_flags_re = (fl1 ? SCFIND_MATCHCASE : 0) | (fl2 ? SCFIND_WHOLEWORD : 0) | (fl3 ? SCFIND_REGEXP | SCFIND_POSIX : 0) | (fl4 ? SCFIND_WORDSTART : 0); switch (response) { case GEANY_RESPONSE_REPLACE_AND_FIND: { gint rep = document_replace_text(idx, find, replace, search_flags_re, search_backwards_re); if (rep != -1) document_find_text(idx, find, search_flags_re, search_backwards_re, TRUE, NULL); break; } case GEANY_RESPONSE_REPLACE: { document_replace_text(idx, find, replace, search_flags_re, search_backwards_re); break; } case GEANY_RESPONSE_FIND: { document_find_text(idx, find, search_flags_re, search_backwards_re, TRUE, GTK_WIDGET(dialog)); break; } case GEANY_RESPONSE_REPLACE_IN_FILE: { document_replace_all(idx, find, replace, search_flags_re, search_replace_escape_re); if (close_window) gtk_widget_hide(widgets.replace_dialog); break; } case GEANY_RESPONSE_REPLACE_IN_SESSION: { guint n, count = 0; // replace in all documents following notebook tab order for (n = 0; (gint) n < gtk_notebook_get_n_pages(GTK_NOTEBOOK(app->notebook)); n++) { gint ix = document_get_n_idx(n); if (! doc_list[ix].is_valid) continue; if (document_replace_all(ix, find, replace, search_flags_re, search_replace_escape_re)) count++; } ui_set_statusbar(_("Replaced text in %u files."), count); // show which docs had replacements: gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_STATUS); ui_save_buttons_toggle(doc_list[idx].changed); // update save all if (close_window) gtk_widget_hide(widgets.replace_dialog); break; } case GEANY_RESPONSE_REPLACE_IN_SEL: { document_replace_sel(idx, find, replace, search_flags_re, search_replace_escape_re); if (close_window) gtk_widget_hide(widgets.replace_dialog); break; } } g_free(find); g_free(replace); } static gboolean on_widget_key_pressed_set_focus(GtkWidget *widget, GdkEventKey *event, gpointer user_data) { // catch tabulator key to set the focus in the replace entry instead of // setting it to the combo box if (event->keyval == GDK_Tab) { gtk_widget_grab_focus(GTK_WIDGET(user_data)); return TRUE; } return FALSE; } static GString *get_grep_options() { gboolean fgrep = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(widgets.find_in_files_dialog, "radio_fgrep"))); gboolean grep = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(widgets.find_in_files_dialog, "radio_grep"))); gboolean invert = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(widgets.find_in_files_dialog, "check_invert"))); gboolean case_sens = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(widgets.find_in_files_dialog, "check_case"))); gboolean whole_word = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(widgets.find_in_files_dialog, "check_wholeword"))); gboolean recursive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(widgets.find_in_files_dialog, "check_recursive"))); gboolean extra = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( lookup_widget(widgets.find_in_files_dialog, "check_extra"))); GString *gstr = g_string_new("-nHI"); // line numbers, filenames, ignore binaries if (invert) g_string_append_c(gstr, 'v'); if (! case_sens) g_string_append_c(gstr, 'i'); if (whole_word) g_string_append_c(gstr, 'w'); if (recursive) g_string_append_c(gstr, 'r'); if (fgrep) g_string_append_c(gstr, 'F'); else if (! grep) g_string_append_c(gstr, 'E'); if (extra) { gchar *text = g_strdup(gtk_entry_get_text(GTK_ENTRY( lookup_widget(widgets.find_in_files_dialog, "entry_extra")))); text = g_strstrip(text); if (*text != 0) { g_string_append_c(gstr, ' '); g_string_append(gstr, text); } g_free(text); } return gstr; } static void on_find_in_files_dialog_response(GtkDialog *dialog, gint response, gpointer user_data) { if (response == GTK_RESPONSE_ACCEPT) { const gchar *search_text = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(user_data)))); GtkWidget *dir_combo = lookup_widget(widgets.find_in_files_dialog, "dir_combo"); const gchar *utf8_dir = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(dir_combo)))); // update extra options pref g_free(search_prefs.fif_extra_options); search_prefs.fif_extra_options = g_strdup(gtk_entry_get_text(GTK_ENTRY( lookup_widget(widgets.find_in_files_dialog, "entry_extra")))); if (utf8_dir == NULL || utils_str_equal(utf8_dir, "")) ui_set_statusbar(_("Invalid directory for find in files.")); else if (search_text && *search_text) { gchar *locale_dir; GString *opts = get_grep_options(); locale_dir = utils_get_locale_from_utf8(utf8_dir); if (search_find_in_files(search_text, locale_dir, opts->str)) { ui_combo_box_add_to_history(GTK_COMBO_BOX(user_data), search_text); ui_combo_box_add_to_history(GTK_COMBO_BOX(dir_combo), utf8_dir); gtk_widget_hide(widgets.find_in_files_dialog); } g_free(locale_dir); g_string_free(opts, TRUE); } else ui_set_statusbar(_("No text to find.")); } else gtk_widget_hide(widgets.find_in_files_dialog); } static gboolean search_find_in_files(const gchar *search_text, const gchar *dir, const gchar *opts) { gchar **argv_prefix, **argv, **opts_argv; guint opts_argv_len, i; GPid child_pid; gint stdout_fd, stdin_fd; GError *error = NULL; gboolean ret = FALSE; if (! search_text || ! *search_text || ! dir) return TRUE; if (! g_file_test(prefs.tools_grep_cmd, G_FILE_TEST_IS_EXECUTABLE)) { msgwin_status_add(_("Cannot execute grep tool '%s';" " check the path setting in Preferences."), prefs.tools_grep_cmd); return FALSE; } opts_argv = g_strsplit(opts, " ", -1); opts_argv_len = g_strv_length(opts_argv); // set grep command and options argv_prefix = g_new0(gchar*, 1 + opts_argv_len + 3 + 1); // last +1 for recursive arg argv_prefix[0] = g_strdup(prefs.tools_grep_cmd); for (i = 0; i < opts_argv_len; i++) { argv_prefix[i + 1] = g_strdup(opts_argv[i]); } g_strfreev(opts_argv); i++; // correct for prefs.tools_grep_cmd argv_prefix[i++] = g_strdup("--"); argv_prefix[i++] = g_strdup(search_text); // finally add the arguments(files to be searched) if (strstr(argv_prefix[1], "r")) // recursive option set { argv_prefix[i++] = g_strdup("."); argv_prefix[i++] = NULL; argv = argv_prefix; } else { argv_prefix[i++] = NULL; argv = search_get_argv((const gchar**)argv_prefix, dir); g_strfreev(argv_prefix); } if (argv == NULL) // no files { g_strfreev(argv); return FALSE; } gtk_list_store_clear(msgwindow.store_msg); gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_MESSAGE); if (! g_spawn_async_with_pipes(dir, (gchar**)argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &child_pid, &stdin_fd, &stdout_fd, NULL, &error)) { geany_debug("%s: g_spawn_async_with_pipes() failed: %s", __func__, error->message); msgwin_status_add(_("Process failed (%s)"), error->message); g_error_free(error); ret = FALSE; } else { gchar *str, *utf8_str; g_free(msgwindow.find_in_files_dir); msgwindow.find_in_files_dir = g_strdup(dir); utils_set_up_io_channel(stdout_fd, G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP|G_IO_NVAL, TRUE, search_read_io, NULL); g_child_watch_add(child_pid, search_close_pid, NULL); str = g_strdup_printf(_("%s %s -- %s (in directory: %s)"), prefs.tools_grep_cmd, opts, search_text, dir); utf8_str = utils_get_utf8_from_locale(str); msgwin_msg_add(COLOR_BLUE, -1, -1, utf8_str); utils_free_pointers(str, utf8_str, NULL); ret = TRUE; } g_strfreev(argv); return ret; } /* Creates an argument vector of strings, copying argv_prefix[] values for * the first arguments, then followed by filenames found in dir. * Returns NULL if no files were found, otherwise returned vector should be fully freed. */ static gchar **search_get_argv(const gchar **argv_prefix, const gchar *dir) { guint prefix_len, list_len, i, j; gchar **argv; GSList *list, *item; GError *error = NULL; g_return_val_if_fail(dir != NULL, NULL); prefix_len = g_strv_length((gchar**)argv_prefix); list = utils_get_file_list(dir, &list_len, &error); if (error) { msgwin_status_add(_("Could not open directory (%s)"), error->message); g_error_free(error); return NULL; } if (list == NULL) return NULL; argv = g_new(gchar*, prefix_len + list_len + 1); for (i = 0; i < prefix_len; i++) argv[i] = g_strdup(argv_prefix[i]); item = list; for (j = 0; j < list_len; j++) { argv[i++] = item->data; item = g_slist_next(item); } argv[i] = NULL; g_slist_free(list); return argv; } static gboolean search_read_io (GIOChannel *source, GIOCondition condition, gpointer data) { if (condition & (G_IO_IN | G_IO_PRI)) { gchar *msg; while (g_io_channel_read_line(source, &msg, NULL, NULL, NULL) && msg) { msgwin_msg_add(COLOR_BLACK, -1, -1, g_strstrip(msg)); g_free(msg); } } if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) return FALSE; return TRUE; } static void search_close_pid(GPid child_pid, gint status, gpointer user_data) { #ifdef G_OS_UNIX const gchar *msg = _("Search failed."); gint color = COLOR_DARK_RED; if (WIFEXITED(status)) { switch (WEXITSTATUS(status)) { case 0: { gint count = gtk_tree_model_iter_n_children( GTK_TREE_MODEL(msgwindow.store_msg), NULL) - 1; msgwin_msg_add_fmt(COLOR_BLUE, -1, -1, _("Search completed with %d matches."), count); ui_set_statusbar(_("Search completed with %d matches."), count); break; } case 1: msg = _("No matches found."); color = COLOR_BLUE; default: msgwin_msg_add(color, -1, -1, msg); ui_set_statusbar("%s", msg); break; } } #endif utils_beep(); g_spawn_close_pid(child_pid); } static gint find_document_usage(gint idx, const gchar *search_text, gint flags) { gchar *buffer, *short_file_name; struct TextToFind ttf; gint count = 0; g_return_val_if_fail(DOC_IDX_VALID(idx), 0); short_file_name = g_path_get_basename(DOC_FILENAME(idx)); ttf.chrg.cpMin = 0; ttf.chrg.cpMax = sci_get_length(doc_list[idx].sci); ttf.lpstrText = (gchar *)search_text; while (1) { gint pos, line, start, find_len; pos = sci_find_text(doc_list[idx].sci, flags, &ttf); if (pos == -1) break; // no more matches find_len = ttf.chrgText.cpMax - ttf.chrgText.cpMin; if (find_len == 0) break; // Ignore regex ^ or $ count++; line = sci_get_line_from_position(doc_list[idx].sci, pos); buffer = sci_get_line(doc_list[idx].sci, line); msgwin_msg_add_fmt(COLOR_BLACK, line + 1, idx, "%s:%d : %s", short_file_name, line + 1, g_strstrip(buffer)); g_free(buffer); start = ttf.chrgText.cpMax + 1; ttf.chrg.cpMin = start; } g_free(short_file_name); return count; } void search_find_usage(const gchar *search_text, gint flags, gboolean in_session) { gint idx; gboolean found = FALSE; idx = document_get_cur_idx(); g_return_if_fail(DOC_IDX_VALID(idx)); gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_MESSAGE); gtk_list_store_clear(msgwindow.store_msg); if (! in_session) { // use current document found = (find_document_usage(idx, search_text, flags) > 0); } else { guint i; for (i = 0; i < doc_array->len; i++) { if (doc_list[i].is_valid) if (find_document_usage(i, search_text, flags) > 0) found = TRUE; } } if (! found) // no matches were found { ui_set_statusbar(_("No matches found for \"%s\"."), search_text); msgwin_msg_add_fmt(COLOR_BLUE, -1, -1, _("No matches found for \"%s\"."), search_text); } else { gint count = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(msgwindow.store_msg), NULL); ui_set_statusbar(_("Found %d matches for \"%s\"."), count, search_text); msgwin_msg_add_fmt(COLOR_BLUE, -1, -1, _("Found %d matches for \"%s\"."), count, search_text); } }