ef1399e000
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
1387 lines
47 KiB
C
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);
|
|
}
|
|
}
|
|
|
|
|