geany/src/search.c
Nick Treleaven ef1399e000 Rename MyApp -> GeanyApp.
Move most GeanyApp fields into:
GeanyPrefs for (most) Preferences dialog fields;
UIPrefs for non-Prefs dialog visual settings;
UIWidgets for less commonly used widgets such as menuitems and
dialogs;
GeanyStatus for various states the application can be in.
Move some GeanyApp fields into EditorPrefs (and one into each of
CommandLineOptions and SidebarTreeviews).
Add plugin API prefs field.
Move disabling build widgets on Windows to build_init().
Make build callbacks static.
Add treeviews_init() to prepare popup menus and open files treeview.
Replace treeviews_find_node() with treeviews_select_openfiles_item().
Make utils_isbrace() and utils_is_opening_brace() take an
'include_angles' argument (to separate from editor_prefs).
Make 'Goto matching brace' keybinding include <> angle brackets.


git-svn-id: https://geany.svn.sourceforge.net/svnroot/geany/trunk@1815 ea778897-0a13-0410-b9d1-a72fbfd435f5
2007-08-23 11:34:06 +00:00

1387 lines
47 KiB
C

/*
* search.c - this file is part of Geany, a fast and lightweight IDE
*
* Copyright 2006-2007 Enrico Tröger <enrico.troeger@uvena.de>
* Copyright 2006-2007 Nick Treleaven <nick.treleaven@btinternet.com>
*
* 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 <gdk/gdkkeysyms.h>
#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 <unistd.h>
#include <string.h>
#ifdef G_OS_UNIX
# include <sys/types.h>
# include <sys/wait.h>
#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(-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(-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.");
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(-1, -1, _("Search completed with %d matches."), count);
ui_set_statusbar(_("Search completed with %d matches."), count);
break;
}
case 1:
msg = _("No matches found.");
default:
msgwin_msg_add(-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(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(-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(-1, -1, _("Found %d matches for \"%s\"."), count, search_text);
}
}