Improve API docs for editor_insert_text_block().
[geany-mirror.git] / src / search.c
blobee1b30238c615a5fcb470810323023e6df0806e3
1 /*
2 * search.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2006-2010 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2010 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * $Id$
25 * Find, Replace, Find in Files dialog related functions.
26 * Note that the basic text find functions are in document.c.
29 #include <gdk/gdkkeysyms.h>
31 #include "geany.h"
32 #include "search.h"
33 #include "prefs.h"
34 #include "support.h"
35 #include "utils.h"
36 #include "document.h"
37 #include "msgwindow.h"
38 #include "sciwrappers.h"
39 #include "ui_utils.h"
40 #include "editor.h"
41 #include "encodings.h"
42 #include "project.h"
43 #include "keyfile.h"
44 #include "stash.h"
46 #include <unistd.h>
47 #include <string.h>
48 #include <ctype.h>
50 #ifdef G_OS_UNIX
51 # include <sys/types.h>
52 # include <sys/wait.h>
53 #endif
55 #ifdef HAVE_REGEX_H
56 # include <regex.h>
57 #else
58 # include "gnuregex.h"
59 #endif
62 enum
64 GEANY_RESPONSE_FIND = 1,
65 GEANY_RESPONSE_FIND_PREVIOUS,
66 GEANY_RESPONSE_FIND_IN_FILE,
67 GEANY_RESPONSE_FIND_IN_SESSION,
68 GEANY_RESPONSE_MARK,
69 GEANY_RESPONSE_REPLACE,
70 GEANY_RESPONSE_REPLACE_AND_FIND,
71 GEANY_RESPONSE_REPLACE_IN_SESSION,
72 GEANY_RESPONSE_REPLACE_IN_FILE,
73 GEANY_RESPONSE_REPLACE_IN_SEL
76 enum
78 FIF_FGREP,
79 FIF_GREP,
80 FIF_EGREP
84 GeanySearchData search_data;
86 GeanySearchPrefs search_prefs;
89 static struct
91 gint fif_mode;
92 gchar *fif_extra_options;
93 gboolean fif_case_sensitive;
94 gboolean fif_match_whole_word;
95 gboolean fif_invert_results;
96 gboolean fif_recursive;
97 gboolean fif_use_extra_options;
99 settings = {0, NULL, FALSE, FALSE, FALSE, FALSE, FALSE};
101 static StashGroup *fif_prefs = NULL;
104 static struct
106 GtkWidget *dialog;
107 GtkWidget *entry;
108 gboolean all_expanded;
109 gint position[2]; /* x, y */
111 find_dlg = {NULL, NULL, FALSE, {0, 0}};
113 static struct
115 GtkWidget *dialog;
116 GtkWidget *find_entry;
117 GtkWidget *replace_entry;
118 gboolean all_expanded;
119 gint position[2]; /* x, y */
121 replace_dlg = {NULL, NULL, NULL, FALSE, {0, 0}};
123 static struct
125 GtkWidget *dialog;
126 GtkWidget *dir_combo;
127 GtkWidget *search_combo;
128 GtkWidget *encoding_combo;
129 gint position[2]; /* x, y */
131 fif_dlg = {NULL, NULL, NULL, NULL, {0, 0}};
134 static gboolean search_read_io(GIOChannel *source, GIOCondition condition, gpointer data);
135 static gboolean search_read_io_stderr(GIOChannel *source, GIOCondition condition, gpointer data);
137 static void search_close_pid(GPid child_pid, gint status, gpointer user_data);
139 static gchar **search_get_argv(const gchar **argv_prefix, const gchar *dir);
142 static void
143 on_find_replace_checkbutton_toggled(GtkToggleButton *togglebutton, gpointer user_data);
145 static void
146 on_find_dialog_response(GtkDialog *dialog, gint response, gpointer user_data);
148 static void
149 on_find_entry_activate(GtkEntry *entry, gpointer user_data);
151 static void
152 on_replace_dialog_response(GtkDialog *dialog, gint response, gpointer user_data);
154 static void
155 on_replace_entry_activate(GtkEntry *entry, gpointer user_data);
157 static gboolean
158 on_widget_key_pressed_set_focus(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
160 static void
161 on_find_in_files_dialog_response(GtkDialog *dialog, gint response, gpointer user_data);
163 static gboolean
164 search_find_in_files(const gchar *utf8_search_text, const gchar *dir, const gchar *opts,
165 const gchar *enc);
168 static void init_prefs(void)
170 StashGroup *group;
172 group = stash_group_new("search");
173 configuration_add_pref_group(group, TRUE);
174 stash_group_add_toggle_button(group, &search_prefs.use_current_file_dir,
175 "pref_search_current_file_dir", TRUE, "check_fif_current_dir");
176 stash_group_add_boolean(group, &find_dlg.all_expanded, "find_all_expanded", FALSE);
177 stash_group_add_boolean(group, &replace_dlg.all_expanded, "replace_all_expanded", FALSE);
178 /* dialog positions */
179 stash_group_add_integer(group, &find_dlg.position[0], "position_find_x", -1);
180 stash_group_add_integer(group, &find_dlg.position[1], "position_find_y", -1);
181 stash_group_add_integer(group, &replace_dlg.position[0], "position_replace_x", -1);
182 stash_group_add_integer(group, &replace_dlg.position[1], "position_replace_y", -1);
183 stash_group_add_integer(group, &fif_dlg.position[0], "position_fif_x", -1);
184 stash_group_add_integer(group, &fif_dlg.position[1], "position_fif_y", -1);
186 group = stash_group_new("search");
187 fif_prefs = group;
188 configuration_add_pref_group(group, FALSE);
189 stash_group_add_radio_buttons(group, &settings.fif_mode, "fif_mode", FIF_FGREP,
190 "radio_fgrep", FIF_FGREP,
191 "radio_grep", FIF_GREP,
192 "radio_egrep", FIF_EGREP,
193 NULL);
194 stash_group_add_entry(group, &settings.fif_extra_options,
195 "fif_extra_options", "", "entry_extra");
196 stash_group_add_toggle_button(group, &settings.fif_case_sensitive,
197 "fif_case_sensitive", TRUE, "check_case");
198 stash_group_add_toggle_button(group, &settings.fif_match_whole_word,
199 "fif_match_whole_word", FALSE, "check_wholeword");
200 stash_group_add_toggle_button(group, &settings.fif_invert_results,
201 "fif_invert_results", FALSE, "check_invert");
202 stash_group_add_toggle_button(group, &settings.fif_recursive,
203 "fif_recursive", FALSE, "check_recursive");
204 stash_group_add_toggle_button(group, &settings.fif_use_extra_options,
205 "fif_use_extra_options", FALSE, "check_extra");
209 void search_init(void)
211 search_data.text = NULL;
212 init_prefs();
216 #define FREE_WIDGET(wid) \
217 if (wid && GTK_IS_WIDGET(wid)) gtk_widget_destroy(wid);
219 void search_finalize(void)
221 FREE_WIDGET(find_dlg.dialog);
222 FREE_WIDGET(replace_dlg.dialog);
223 FREE_WIDGET(fif_dlg.dialog);
224 g_free(search_data.text);
228 static GtkWidget *add_find_checkboxes(GtkDialog *dialog)
230 GtkWidget *checkbox1, *checkbox2, *check_regexp, *check_back, *checkbox5,
231 *checkbox7, *hbox, *fbox, *mbox;
233 check_regexp = gtk_check_button_new_with_mnemonic(_("_Use regular expressions"));
234 g_object_set_data_full(G_OBJECT(dialog), "check_regexp",
235 g_object_ref(check_regexp), (GDestroyNotify) g_object_unref);
236 gtk_button_set_focus_on_click(GTK_BUTTON(check_regexp), FALSE);
237 ui_widget_set_tooltip_text(check_regexp, _("Use POSIX-like regular expressions. "
238 "For detailed information about using regular expressions, please read the documentation."));
239 g_signal_connect(check_regexp, "toggled",
240 G_CALLBACK(on_find_replace_checkbutton_toggled), GTK_WIDGET(dialog));
242 if (dialog != GTK_DIALOG(find_dlg.dialog))
244 check_back = gtk_check_button_new_with_mnemonic(_("Search _backwards"));
245 g_object_set_data_full(G_OBJECT(dialog), "check_back",
246 g_object_ref(check_back), (GDestroyNotify)g_object_unref);
247 gtk_button_set_focus_on_click(GTK_BUTTON(check_back), FALSE);
249 else
250 { /* align the two checkboxes at the top of the hbox */
251 GtkSizeGroup *label_size;
252 check_back = gtk_label_new(NULL);
253 label_size = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);
254 gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size), check_back);
255 gtk_size_group_add_widget(GTK_SIZE_GROUP(label_size), check_regexp);
256 g_object_unref(label_size);
258 checkbox7 = gtk_check_button_new_with_mnemonic(_("Use _escape sequences"));
259 g_object_set_data_full(G_OBJECT(dialog), "check_escape",
260 g_object_ref(checkbox7), (GDestroyNotify)g_object_unref);
261 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox7), FALSE);
262 ui_widget_set_tooltip_text(checkbox7,
263 _("Replace \\\\, \\t, \\n, \\r and \\uXXXX (Unicode chararacters) with the "
264 "corresponding control characters"));
266 /* Search features */
267 fbox = gtk_vbox_new(FALSE, 0);
268 gtk_container_add(GTK_CONTAINER(fbox), check_regexp);
269 gtk_container_add(GTK_CONTAINER(fbox), checkbox7);
270 gtk_container_add(GTK_CONTAINER(fbox), check_back);
272 checkbox1 = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
273 g_object_set_data_full(G_OBJECT(dialog), "check_case",
274 g_object_ref(checkbox1), (GDestroyNotify)g_object_unref);
275 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1), FALSE);
277 checkbox2 = gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
278 g_object_set_data_full(G_OBJECT(dialog), "check_word",
279 g_object_ref(checkbox2), (GDestroyNotify)g_object_unref);
280 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2), FALSE);
282 checkbox5 = gtk_check_button_new_with_mnemonic(_("Match from s_tart of word"));
283 g_object_set_data_full(G_OBJECT(dialog), "check_wordstart",
284 g_object_ref(checkbox5), (GDestroyNotify)g_object_unref);
285 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox5), FALSE);
287 /* Matching options */
288 mbox = gtk_vbox_new(FALSE, 0);
289 gtk_container_add(GTK_CONTAINER(mbox), checkbox1);
290 gtk_container_add(GTK_CONTAINER(mbox), checkbox2);
291 gtk_container_add(GTK_CONTAINER(mbox), checkbox5);
293 hbox = gtk_hbox_new(TRUE, 6);
294 gtk_container_add(GTK_CONTAINER(hbox), fbox);
295 gtk_container_add(GTK_CONTAINER(hbox), mbox);
296 return hbox;
300 static void send_find_dialog_response(GtkButton *button, gpointer user_data)
302 gtk_dialog_response(GTK_DIALOG(find_dlg.dialog), GPOINTER_TO_INT(user_data));
306 /* store text, clear search flags so we can use Search->Find Next/Previous */
307 static void setup_find_next(const gchar *text)
309 g_free(search_data.text);
310 search_data.text = g_strdup(text);
311 search_data.flags = 0;
312 search_data.backwards = FALSE;
313 search_data.search_bar = FALSE;
317 /* Search for next match of the current "selection"
318 * For X11 based systems, this will try to use the system-wide
319 * x-selection first. If it doesn't find anything suitable in
320 * the x-selection (or if we are on Win32) it will try to use
321 * the scintilla selection or current token instead.
322 * Search flags are always zero.
324 void search_find_selection(GeanyDocument *doc, gboolean search_backwards)
326 gchar *s = NULL;
327 #ifdef G_OS_UNIX
328 GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
329 #endif
331 g_return_if_fail(doc != NULL);
333 #ifdef G_OS_UNIX
334 s = gtk_clipboard_wait_for_text(clipboard);
335 if (s)
337 if (strchr(s,'\n') || strchr(s, '\r'))
339 g_free(s);
340 s = NULL;
343 #endif
345 if (!s)
346 s = editor_get_default_selection(doc->editor, TRUE, NULL);
348 if (s)
350 setup_find_next(s); /* allow find next/prev */
352 if (document_find_text(doc, s, 0, search_backwards, FALSE, NULL) > -1)
353 editor_display_current_line(doc->editor, 0.3F);
354 g_free(s);
359 /* this will load a GTK rc style to set a monospace font for text fields(GtkEntry) in all
360 * search dialogs. This needs to be done only once.
361 * The monospace font should increase readibility of regular expressions containing spaces, points,
362 * commas and similar (#1907117). */
363 static void load_monospace_style(void)
365 static const gchar *rcstyle =
366 "style \"geany-monospace\"\n" \
367 "{\n" \
368 " font_name=\"Monospace\"\n" \
369 "}\n" \
370 "widget \"GeanyDialogSearch.*.GtkEntry\" style \"geany-monospace\"";
371 static gboolean load = TRUE;
373 if (load)
375 gtk_rc_parse_string(rcstyle);
376 load = FALSE;
381 static void on_expander_activated(GtkExpander *exp, gpointer data)
383 gboolean *setting = data;
385 *setting = gtk_expander_get_expanded(exp);
389 static void create_find_dialog(void)
391 GtkWidget *label, *entry, *sbox, *vbox;
392 GtkWidget *exp, *bbox, *button, *check_close;
394 load_monospace_style();
396 find_dlg.dialog = gtk_dialog_new_with_buttons(_("Find"),
397 GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT,
398 GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL, NULL);
399 vbox = ui_dialog_vbox_new(GTK_DIALOG(find_dlg.dialog));
400 gtk_widget_set_name(find_dlg.dialog, "GeanyDialogSearch");
401 gtk_box_set_spacing(GTK_BOX(vbox), 9);
403 button = ui_button_new_with_image(GTK_STOCK_GO_BACK, _("_Previous"));
404 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg.dialog), button,
405 GEANY_RESPONSE_FIND_PREVIOUS);
406 g_object_set_data_full(G_OBJECT(find_dlg.dialog), "btn_previous",
407 g_object_ref(button), (GDestroyNotify)g_object_unref);
409 button = ui_button_new_with_image(GTK_STOCK_GO_FORWARD, _("_Next"));
410 gtk_dialog_add_action_widget(GTK_DIALOG(find_dlg.dialog), button,
411 GEANY_RESPONSE_FIND);
413 label = gtk_label_new_with_mnemonic(_("_Search for:"));
414 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
416 entry = gtk_combo_box_entry_new_text();
417 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))));
418 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
419 gtk_entry_set_max_length(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))), 248);
420 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))), 50);
421 find_dlg.entry = GTK_BIN(entry)->child;
422 g_object_set_data_full(G_OBJECT(find_dlg.dialog), "entry",
423 g_object_ref(entry), (GDestroyNotify)g_object_unref);
425 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry)), "activate",
426 G_CALLBACK(on_find_entry_activate), NULL);
427 g_signal_connect(find_dlg.dialog, "response",
428 G_CALLBACK(on_find_dialog_response), entry);
429 g_signal_connect(find_dlg.dialog, "delete-event",
430 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
432 sbox = gtk_hbox_new(FALSE, 6);
433 gtk_box_pack_start(GTK_BOX(sbox), label, FALSE, FALSE, 0);
434 gtk_box_pack_start(GTK_BOX(sbox), entry, TRUE, TRUE, 0);
435 gtk_box_pack_start(GTK_BOX(vbox), sbox, TRUE, FALSE, 0);
437 gtk_container_add(GTK_CONTAINER(vbox),
438 add_find_checkboxes(GTK_DIALOG(find_dlg.dialog)));
440 /* Now add the multiple match options */
441 exp = gtk_expander_new_with_mnemonic(_("_Find All"));
442 gtk_expander_set_expanded(GTK_EXPANDER(exp), find_dlg.all_expanded);
443 g_signal_connect_after(exp, "activate",
444 G_CALLBACK(on_expander_activated), &find_dlg.all_expanded);
446 bbox = gtk_hbutton_box_new();
448 button = gtk_button_new_with_mnemonic(_("_Mark"));
449 ui_widget_set_tooltip_text(button,
450 _("Mark all matches in the current document"));
451 gtk_container_add(GTK_CONTAINER(bbox), button);
452 g_signal_connect(button, "clicked", G_CALLBACK(send_find_dialog_response),
453 GINT_TO_POINTER(GEANY_RESPONSE_MARK));
455 button = gtk_button_new_with_mnemonic(_("In Sessi_on"));
456 gtk_container_add(GTK_CONTAINER(bbox), button);
457 g_signal_connect(button, "clicked", G_CALLBACK(send_find_dialog_response),
458 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_SESSION));
460 button = gtk_button_new_with_mnemonic(_("_In Document"));
461 gtk_container_add(GTK_CONTAINER(bbox), button);
462 g_signal_connect(button, "clicked", G_CALLBACK(send_find_dialog_response),
463 GINT_TO_POINTER(GEANY_RESPONSE_FIND_IN_FILE));
465 /* close window checkbox */
466 check_close = gtk_check_button_new_with_mnemonic(_("Close _dialog"));
467 g_object_set_data_full(G_OBJECT(find_dlg.dialog), "check_close",
468 g_object_ref(check_close), (GDestroyNotify) g_object_unref);
469 gtk_button_set_focus_on_click(GTK_BUTTON(check_close), FALSE);
470 ui_widget_set_tooltip_text(check_close,
471 _("Disable this option to keep the dialog open"));
472 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_close), TRUE);
473 gtk_container_add(GTK_CONTAINER(bbox), check_close);
474 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), check_close, TRUE);
476 ui_hbutton_box_copy_layout(
477 GTK_BUTTON_BOX(GTK_DIALOG(find_dlg.dialog)->action_area),
478 GTK_BUTTON_BOX(bbox));
479 gtk_container_add(GTK_CONTAINER(exp), bbox);
480 gtk_container_add(GTK_CONTAINER(vbox), exp);
484 static void set_dialog_position(GtkWidget *dialog, gint *position)
486 if (position[0] >= 0)
487 gtk_window_move(GTK_WINDOW(dialog), position[0], position[1]);
491 void search_show_find_dialog(void)
493 GeanyDocument *doc = document_get_current();
494 gchar *sel = NULL;
496 g_return_if_fail(doc != NULL);
498 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
500 if (find_dlg.dialog == NULL)
502 create_find_dialog();
503 if (sel)
504 gtk_entry_set_text(GTK_ENTRY(find_dlg.entry), sel);
506 set_dialog_position(find_dlg.dialog, find_dlg.position);
507 gtk_widget_show_all(find_dlg.dialog);
509 else
511 /* only set selection if the dialog is not already visible */
512 if (! GTK_WIDGET_VISIBLE(find_dlg.dialog) && sel)
513 gtk_entry_set_text(GTK_ENTRY(find_dlg.entry), sel);
514 gtk_widget_grab_focus(find_dlg.entry);
515 set_dialog_position(find_dlg.dialog, find_dlg.position);
516 gtk_widget_show(find_dlg.dialog);
517 if (sel != NULL) /* when we have a selection, reset the entry widget's background colour */
518 ui_set_search_entry_background(find_dlg.entry, TRUE);
519 /* bring the dialog back in the foreground in case it is already open but the focus is away */
520 gtk_window_present(GTK_WINDOW(find_dlg.dialog));
523 g_free(sel);
527 static void send_replace_dialog_response(GtkButton *button, gpointer user_data)
529 gtk_dialog_response(GTK_DIALOG(replace_dlg.dialog), GPOINTER_TO_INT(user_data));
533 static void create_replace_dialog(void)
535 GtkWidget *label_find, *label_replace, *entry_find, *entry_replace,
536 *check_close, *button, *rbox, *fbox, *vbox, *exp, *bbox;
537 GtkSizeGroup *label_size;
539 load_monospace_style();
541 replace_dlg.dialog = gtk_dialog_new_with_buttons(_("Replace"),
542 GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT,
543 GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL, NULL);
544 vbox = ui_dialog_vbox_new(GTK_DIALOG(replace_dlg.dialog));
545 gtk_box_set_spacing(GTK_BOX(vbox), 9);
546 gtk_widget_set_name(replace_dlg.dialog, "GeanyDialogSearch");
548 button = gtk_button_new_from_stock(GTK_STOCK_FIND);
549 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg.dialog), button,
550 GEANY_RESPONSE_FIND);
551 button = gtk_button_new_with_mnemonic(_("_Replace"));
552 gtk_button_set_image(GTK_BUTTON(button),
553 gtk_image_new_from_stock(GTK_STOCK_FIND_AND_REPLACE, GTK_ICON_SIZE_BUTTON));
554 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg.dialog), button,
555 GEANY_RESPONSE_REPLACE);
556 button = gtk_button_new_with_mnemonic(_("Replace & Fi_nd"));
557 gtk_button_set_image(GTK_BUTTON(button),
558 gtk_image_new_from_stock(GTK_STOCK_FIND_AND_REPLACE, GTK_ICON_SIZE_BUTTON));
559 gtk_dialog_add_action_widget(GTK_DIALOG(replace_dlg.dialog), button,
560 GEANY_RESPONSE_REPLACE_AND_FIND);
562 label_find = gtk_label_new_with_mnemonic(_("_Search for:"));
563 gtk_misc_set_alignment(GTK_MISC(label_find), 0, 0.5);
565 label_replace = gtk_label_new_with_mnemonic(_("Replace wit_h:"));
566 gtk_misc_set_alignment(GTK_MISC(label_replace), 0, 0.5);
568 entry_find = gtk_combo_box_entry_new_text();
569 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find))));
570 gtk_label_set_mnemonic_widget(GTK_LABEL(label_find), entry_find);
571 gtk_entry_set_max_length(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find))), 248);
572 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_find))), 50);
573 g_object_set_data_full(G_OBJECT(replace_dlg.dialog), "entry_find",
574 g_object_ref(entry_find), (GDestroyNotify)g_object_unref);
575 replace_dlg.find_entry = GTK_BIN(entry_find)->child;
577 entry_replace = gtk_combo_box_entry_new_text();
578 ui_entry_add_clear_icon(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace))));
579 gtk_label_set_mnemonic_widget(GTK_LABEL(label_replace), entry_replace);
580 gtk_entry_set_max_length(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace))), 248);
581 gtk_entry_set_width_chars(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry_replace))), 50);
582 g_object_set_data_full(G_OBJECT(replace_dlg.dialog), "entry_replace",
583 g_object_ref(entry_replace), (GDestroyNotify)g_object_unref);
584 replace_dlg.replace_entry = GTK_BIN(entry_replace)->child;
586 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_find)),
587 "key-press-event", G_CALLBACK(on_widget_key_pressed_set_focus),
588 gtk_bin_get_child(GTK_BIN(entry_replace)));
589 g_signal_connect(gtk_bin_get_child(GTK_BIN(entry_replace)), "activate",
590 G_CALLBACK(on_replace_entry_activate), NULL);
591 g_signal_connect(replace_dlg.dialog, "response",
592 G_CALLBACK(on_replace_dialog_response), entry_replace);
593 g_signal_connect(replace_dlg.dialog, "delete-event",
594 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
596 fbox = gtk_hbox_new(FALSE, 6);
597 gtk_box_pack_start(GTK_BOX(fbox), label_find, FALSE, FALSE, 0);
598 gtk_box_pack_start(GTK_BOX(fbox), entry_find, TRUE, TRUE, 0);
600 rbox = gtk_hbox_new(FALSE, 6);
601 gtk_box_pack_start(GTK_BOX(rbox), label_replace, FALSE, FALSE, 0);
602 gtk_box_pack_start(GTK_BOX(rbox), entry_replace, TRUE, TRUE, 0);
604 label_size = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
605 gtk_size_group_add_widget(label_size, label_find);
606 gtk_size_group_add_widget(label_size, label_replace);
607 g_object_unref(G_OBJECT(label_size)); /* auto destroy the size group */
609 gtk_box_pack_start(GTK_BOX(vbox), fbox, TRUE, FALSE, 0);
610 gtk_box_pack_start(GTK_BOX(vbox), rbox, TRUE, FALSE, 0);
611 gtk_container_add(GTK_CONTAINER(vbox),
612 add_find_checkboxes(GTK_DIALOG(replace_dlg.dialog)));
614 /* Now add the multiple replace options */
615 exp = gtk_expander_new_with_mnemonic(_("Re_place All"));
616 gtk_expander_set_expanded(GTK_EXPANDER(exp), replace_dlg.all_expanded);
617 g_signal_connect_after(exp, "activate",
618 G_CALLBACK(on_expander_activated), &replace_dlg.all_expanded);
620 bbox = gtk_hbutton_box_new();
622 button = gtk_button_new_with_mnemonic(_("In Sessi_on"));
623 gtk_container_add(GTK_CONTAINER(bbox), button);
624 g_signal_connect(button, "clicked", G_CALLBACK(send_replace_dialog_response),
625 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SESSION));
627 button = gtk_button_new_with_mnemonic(_("_In Document"));
628 gtk_container_add(GTK_CONTAINER(bbox), button);
629 g_signal_connect(button, "clicked", G_CALLBACK(send_replace_dialog_response),
630 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_FILE));
632 button = gtk_button_new_with_mnemonic(_("In Se_lection"));
633 ui_widget_set_tooltip_text(button,
634 _("Replace all matches found in the currently selected text"));
635 gtk_container_add(GTK_CONTAINER(bbox), button);
636 g_signal_connect(button, "clicked", G_CALLBACK(send_replace_dialog_response),
637 GINT_TO_POINTER(GEANY_RESPONSE_REPLACE_IN_SEL));
639 /* close window checkbox */
640 check_close = gtk_check_button_new_with_mnemonic(_("Close _dialog"));
641 g_object_set_data_full(G_OBJECT(replace_dlg.dialog), "check_close",
642 g_object_ref(check_close), (GDestroyNotify) g_object_unref);
643 gtk_button_set_focus_on_click(GTK_BUTTON(check_close), FALSE);
644 ui_widget_set_tooltip_text(check_close,
645 _("Disable this option to keep the dialog open"));
646 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_close), TRUE);
647 gtk_container_add(GTK_CONTAINER(bbox), check_close);
648 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), check_close, TRUE);
650 ui_hbutton_box_copy_layout(
651 GTK_BUTTON_BOX(GTK_DIALOG(replace_dlg.dialog)->action_area),
652 GTK_BUTTON_BOX(bbox));
653 gtk_container_add(GTK_CONTAINER(exp), bbox);
654 gtk_container_add(GTK_CONTAINER(vbox), exp);
658 void search_show_replace_dialog(void)
660 GeanyDocument *doc = document_get_current();
661 gchar *sel = NULL;
663 if (doc == NULL)
664 return;
666 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
668 if (replace_dlg.dialog == NULL)
670 create_replace_dialog();
671 if (sel)
672 gtk_entry_set_text(GTK_ENTRY(replace_dlg.find_entry), sel);
674 set_dialog_position(replace_dlg.dialog, replace_dlg.position);
675 gtk_widget_show_all(replace_dlg.dialog);
677 else
679 /* only set selection if the dialog is not already visible */
680 if (! GTK_WIDGET_VISIBLE(replace_dlg.dialog) && sel)
681 gtk_entry_set_text(GTK_ENTRY(replace_dlg.find_entry), sel);
682 if (sel != NULL) /* when we have a selection, reset the entry widget's background colour */
683 ui_set_search_entry_background(replace_dlg.find_entry, TRUE);
684 gtk_widget_grab_focus(replace_dlg.find_entry);
685 set_dialog_position(replace_dlg.dialog, replace_dlg.position);
686 gtk_widget_show(replace_dlg.dialog);
687 /* bring the dialog back in the foreground in case it is already open but the focus is away */
688 gtk_window_present(GTK_WINDOW(replace_dlg.dialog));
690 g_free(sel);
694 static void on_extra_options_toggled(GtkToggleButton *togglebutton, gpointer user_data)
696 /* disable extra option entry when checkbutton not checked */
697 gtk_widget_set_sensitive(GTK_WIDGET(user_data),
698 gtk_toggle_button_get_active(togglebutton));
702 static void create_fif_dialog(void)
704 GtkWidget *dir_combo, *combo, *e_combo, *entry;
705 GtkWidget *label, *label1, *label2, *checkbox1, *checkbox2, *check_wholeword,
706 *check_recursive, *check_extra, *entry_extra;
707 GtkWidget *dbox, *sbox, *cbox, *rbox, *rbtn, *hbox, *vbox, *ebox;
708 GtkSizeGroup *size_group;
709 gchar *encoding_string;
710 guint i;
712 load_monospace_style();
714 fif_dlg.dialog = gtk_dialog_new_with_buttons(
715 _("Find in Files"), GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT,
716 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
717 vbox = ui_dialog_vbox_new(GTK_DIALOG(fif_dlg.dialog));
718 gtk_box_set_spacing(GTK_BOX(vbox), 9);
719 gtk_widget_set_name(fif_dlg.dialog, "GeanyDialogSearch");
721 gtk_dialog_add_button(GTK_DIALOG(fif_dlg.dialog), "gtk-find", GTK_RESPONSE_ACCEPT);
722 gtk_dialog_set_default_response(GTK_DIALOG(fif_dlg.dialog),
723 GTK_RESPONSE_ACCEPT);
725 label1 = gtk_label_new_with_mnemonic(_("_Directory:"));
726 gtk_misc_set_alignment(GTK_MISC(label1), 1, 0.5);
728 dir_combo = gtk_combo_box_entry_new_text();
729 entry = gtk_bin_get_child(GTK_BIN(dir_combo));
730 ui_entry_add_clear_icon(GTK_ENTRY(entry));
731 gtk_label_set_mnemonic_widget(GTK_LABEL(label1), entry);
732 gtk_entry_set_max_length(GTK_ENTRY(entry), 248);
733 gtk_entry_set_width_chars(GTK_ENTRY(entry), 50);
734 fif_dlg.dir_combo = dir_combo;
736 dbox = ui_path_box_new(NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
737 GTK_ENTRY(entry));
738 gtk_box_pack_start(GTK_BOX(dbox), label1, FALSE, FALSE, 0);
740 label = gtk_label_new_with_mnemonic(_("_Search for:"));
741 gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
743 combo = gtk_combo_box_entry_new_text();
744 entry = gtk_bin_get_child(GTK_BIN(combo));
745 ui_entry_add_clear_icon(GTK_ENTRY(entry));
746 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
747 gtk_entry_set_max_length(GTK_ENTRY(entry), 248);
748 gtk_entry_set_width_chars(GTK_ENTRY(entry), 50);
749 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
750 fif_dlg.search_combo = combo;
752 sbox = gtk_hbox_new(FALSE, 6);
753 gtk_box_pack_start(GTK_BOX(sbox), label, FALSE, FALSE, 0);
754 gtk_box_pack_start(GTK_BOX(sbox), combo, TRUE, TRUE, 0);
756 label2 = gtk_label_new_with_mnemonic(_("E_ncoding:"));
757 gtk_misc_set_alignment(GTK_MISC(label2), 1, 0.5);
759 e_combo = gtk_combo_box_new_text();
760 for (i = 0; i < GEANY_ENCODINGS_MAX; i++)
762 encoding_string = encodings_to_string(&encodings[i]);
763 gtk_combo_box_append_text(GTK_COMBO_BOX(e_combo), encoding_string);
764 g_free(encoding_string);
766 gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(e_combo), 3);
767 gtk_label_set_mnemonic_widget(GTK_LABEL(label2), e_combo);
768 fif_dlg.encoding_combo = e_combo;
770 ebox = gtk_hbox_new(FALSE, 6);
771 gtk_box_pack_start(GTK_BOX(ebox), label2, FALSE, FALSE, 0);
772 gtk_box_pack_start(GTK_BOX(ebox), e_combo, TRUE, TRUE, 0);
774 size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
775 gtk_size_group_add_widget(size_group, label1);
776 gtk_size_group_add_widget(size_group, label);
777 gtk_size_group_add_widget(size_group, label2);
778 g_object_unref(G_OBJECT(size_group)); /* auto destroy the size group */
780 rbox = gtk_vbox_new(FALSE, 0);
781 rbtn = gtk_radio_button_new_with_mnemonic(NULL, _("Fixed s_trings"));
782 /* Make fixed strings the default to speed up searching all files in directory. */
783 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rbtn), TRUE);
784 g_object_set_data_full(G_OBJECT(fif_dlg.dialog), "radio_fgrep",
785 g_object_ref(rbtn), (GDestroyNotify)g_object_unref);
786 gtk_button_set_focus_on_click(GTK_BUTTON(rbtn), FALSE);
787 gtk_container_add(GTK_CONTAINER(rbox), rbtn);
789 rbtn = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(rbtn),
790 _("_Grep regular expressions"));
791 g_object_set_data_full(G_OBJECT(fif_dlg.dialog), "radio_grep",
792 g_object_ref(rbtn), (GDestroyNotify)g_object_unref);
793 ui_widget_set_tooltip_text(rbtn, _("See grep's manual page for more information"));
794 gtk_button_set_focus_on_click(GTK_BUTTON(rbtn), FALSE);
795 gtk_container_add(GTK_CONTAINER(rbox), rbtn);
797 rbtn = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(rbtn),
798 _("_Extended regular expressions"));
799 g_object_set_data_full(G_OBJECT(fif_dlg.dialog), "radio_egrep",
800 g_object_ref(rbtn), (GDestroyNotify)g_object_unref);
801 ui_widget_set_tooltip_text(rbtn, _("See grep's manual page for more information"));
802 gtk_button_set_focus_on_click(GTK_BUTTON(rbtn), FALSE);
803 gtk_container_add(GTK_CONTAINER(rbox), rbtn);
805 check_recursive = gtk_check_button_new_with_mnemonic(_("_Recurse in subfolders"));
806 g_object_set_data_full(G_OBJECT(fif_dlg.dialog), "check_recursive",
807 g_object_ref(check_recursive), (GDestroyNotify)g_object_unref);
808 gtk_button_set_focus_on_click(GTK_BUTTON(check_recursive), FALSE);
810 checkbox1 = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
811 g_object_set_data_full(G_OBJECT(fif_dlg.dialog), "check_case",
812 g_object_ref(checkbox1), (GDestroyNotify)g_object_unref);
813 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox1), FALSE);
814 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox1), TRUE);
816 check_wholeword = gtk_check_button_new_with_mnemonic(_("Match only a _whole word"));
817 g_object_set_data_full(G_OBJECT(fif_dlg.dialog), "check_wholeword",
818 g_object_ref(check_wholeword), (GDestroyNotify)g_object_unref);
819 gtk_button_set_focus_on_click(GTK_BUTTON(check_wholeword), FALSE);
821 checkbox2 = gtk_check_button_new_with_mnemonic(_("_Invert search results"));
822 g_object_set_data_full(G_OBJECT(fif_dlg.dialog), "check_invert",
823 g_object_ref(checkbox2), (GDestroyNotify)g_object_unref);
824 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2), FALSE);
825 ui_widget_set_tooltip_text(checkbox2,
826 _("Invert the sense of matching, to select non-matching lines"));
828 cbox = gtk_vbox_new(FALSE, 0);
829 gtk_container_add(GTK_CONTAINER(cbox), checkbox1);
830 gtk_container_add(GTK_CONTAINER(cbox), check_wholeword);
831 gtk_container_add(GTK_CONTAINER(cbox), checkbox2);
832 gtk_container_add(GTK_CONTAINER(cbox), check_recursive);
834 hbox = gtk_hbox_new(FALSE, 6);
835 gtk_container_add(GTK_CONTAINER(hbox), rbox);
836 gtk_container_add(GTK_CONTAINER(hbox), cbox);
838 gtk_box_pack_start(GTK_BOX(vbox), dbox, TRUE, FALSE, 0);
839 gtk_box_pack_start(GTK_BOX(vbox), sbox, TRUE, FALSE, 0);
840 gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
841 gtk_container_add(GTK_CONTAINER(vbox), hbox);
843 check_extra = gtk_check_button_new_with_mnemonic(_("E_xtra options:"));
844 g_object_set_data_full(G_OBJECT(fif_dlg.dialog), "check_extra",
845 g_object_ref(check_extra), (GDestroyNotify)g_object_unref);
846 gtk_button_set_focus_on_click(GTK_BUTTON(check_extra), FALSE);
848 entry_extra = gtk_entry_new();
849 ui_entry_add_clear_icon(GTK_ENTRY(entry_extra));
850 gtk_widget_set_sensitive(entry_extra, FALSE);
851 ui_widget_set_tooltip_text(entry_extra, _("Other options to pass to Grep"));
852 ui_hookup_widget(fif_dlg.dialog, entry_extra, "entry_extra");
854 /* enable entry_extra when check_extra is checked */
855 g_signal_connect(check_extra, "toggled",
856 G_CALLBACK(on_extra_options_toggled), entry_extra);
858 hbox = gtk_hbox_new(FALSE, 6);
859 gtk_box_pack_start(GTK_BOX(hbox), check_extra, FALSE, FALSE, 0);
860 gtk_box_pack_start(GTK_BOX(hbox), entry_extra, TRUE, TRUE, 0);
861 gtk_container_add(GTK_CONTAINER(vbox), hbox);
863 g_signal_connect(dir_combo, "key-press-event",
864 G_CALLBACK(on_widget_key_pressed_set_focus), combo);
865 g_signal_connect(fif_dlg.dialog, "response",
866 G_CALLBACK(on_find_in_files_dialog_response), NULL);
867 g_signal_connect(fif_dlg.dialog, "delete-event",
868 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
872 /* dir is the directory to search in (UTF-8 encoding), maybe NULL to determine it the usual way
873 * by using the current file's path */
874 void search_show_find_in_files_dialog(const gchar *dir)
876 GtkWidget *entry; /* for child GtkEntry of a GtkComboBoxEntry */
877 GeanyDocument *doc = document_get_current();
878 gchar *sel = NULL;
879 gchar *cur_dir = NULL;
880 GeanyEncodingIndex enc_idx = GEANY_ENCODING_UTF_8;
882 if (fif_dlg.dialog == NULL)
884 create_fif_dialog();
885 gtk_widget_show_all(fif_dlg.dialog);
886 if (doc)
887 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
889 stash_group_display(fif_prefs, fif_dlg.dialog);
891 /* only set selection if the dialog is not already visible, or has just been created */
892 if (doc && ! sel && ! GTK_WIDGET_VISIBLE(fif_dlg.dialog))
893 sel = editor_get_default_selection(doc->editor, search_prefs.use_current_word, NULL);
895 entry = GTK_BIN(fif_dlg.search_combo)->child;
896 if (sel)
897 gtk_entry_set_text(GTK_ENTRY(entry), sel);
898 g_free(sel);
900 /* add project's base path directory to the dir list, we do this here once
901 * (in create_fif_dialog() it would fail if a project is opened after dialog creation) */
902 if (app->project != NULL && NZV(app->project->base_path))
904 ui_combo_box_prepend_text_once(GTK_COMBO_BOX(fif_dlg.dir_combo),
905 app->project->base_path);
908 entry = GTK_BIN(fif_dlg.dir_combo)->child;
909 if (NZV(dir))
910 cur_dir = g_strdup(dir); /* custom directory argument passed */
911 else
913 gboolean entry_empty = ! NZV(gtk_entry_get_text(GTK_ENTRY(entry)));
915 if (search_prefs.use_current_file_dir || entry_empty)
917 cur_dir = utils_get_current_file_dir_utf8();
919 /* use default_open_path if no directory could be determined
920 * (e.g. when no files are open) */
921 if (!cur_dir)
922 cur_dir = g_strdup(utils_get_default_dir_utf8());
923 if (!cur_dir)
924 cur_dir = g_get_current_dir();
927 if (cur_dir)
929 gtk_entry_set_text(GTK_ENTRY(entry), cur_dir);
930 g_free(cur_dir);
933 /* set the encoding of the current file */
934 if (doc != NULL)
935 enc_idx = encodings_get_idx_from_charset(doc->encoding);
936 gtk_combo_box_set_active(GTK_COMBO_BOX(fif_dlg.encoding_combo), enc_idx);
938 /* put the focus to the directory entry if it is empty */
939 if (utils_str_equal(gtk_entry_get_text(GTK_ENTRY(entry)), ""))
940 gtk_widget_grab_focus(fif_dlg.dir_combo);
941 else
942 gtk_widget_grab_focus(fif_dlg.search_combo);
944 /* set dialog window position */
945 set_dialog_position(fif_dlg.dialog, fif_dlg.position);
947 gtk_widget_show(fif_dlg.dialog);
948 /* bring the dialog back in the foreground in case it is already open but the focus is away */
949 gtk_window_present(GTK_WINDOW(fif_dlg.dialog));
953 static void
954 on_find_replace_checkbutton_toggled(GtkToggleButton *togglebutton, gpointer user_data)
956 GtkWidget *dialog = GTK_WIDGET(user_data);
957 GtkToggleButton *chk_regexp = GTK_TOGGLE_BUTTON(
958 ui_lookup_widget(dialog, "check_regexp"));
960 if (togglebutton == chk_regexp)
962 gboolean regex_set = gtk_toggle_button_get_active(chk_regexp);
963 GtkWidget *check_word = ui_lookup_widget(dialog, "check_word");
964 GtkWidget *check_wordstart = ui_lookup_widget(dialog, "check_wordstart");
965 GtkToggleButton *check_case = GTK_TOGGLE_BUTTON(
966 ui_lookup_widget(dialog, "check_case"));
967 GtkWidget *check_escape = ui_lookup_widget(dialog, "check_escape");
968 static gboolean case_state = FALSE; /* state before regex enabled */
970 /* hide options that don't apply to regex searches */
971 gtk_widget_set_sensitive(check_escape, ! regex_set);
973 if (dialog == find_dlg.dialog)
974 gtk_widget_set_sensitive(ui_lookup_widget(dialog, "btn_previous"), ! regex_set);
975 else
976 gtk_widget_set_sensitive(ui_lookup_widget(dialog, "check_back"), ! regex_set);
978 gtk_widget_set_sensitive(check_word, ! regex_set);
979 gtk_widget_set_sensitive(check_wordstart, ! regex_set);
981 if (regex_set) /* regex enabled */
983 /* Enable case sensitive but remember original case toggle state */
984 case_state = gtk_toggle_button_get_active(check_case);
985 gtk_toggle_button_set_active(check_case, TRUE);
987 else /* regex disabled */
989 /* If case sensitive is still enabled, revert to what it was before we enabled it */
990 if (gtk_toggle_button_get_active(check_case) == TRUE)
991 gtk_toggle_button_set_active(check_case, case_state);
997 /* Clears markers if text is null/empty.
998 * @return Number of matches marked. */
999 gint search_mark_all(GeanyDocument *doc, const gchar *search_text, gint flags)
1001 gint pos, count = 0;
1002 gsize len;
1003 struct Sci_TextToFind ttf;
1005 g_return_val_if_fail(doc != NULL, 0);
1007 /* clear previous search indicators */
1008 editor_indicator_clear(doc->editor, GEANY_INDICATOR_SEARCH);
1010 if (!NZV(search_text))
1011 return 0;
1013 ttf.chrg.cpMin = 0;
1014 ttf.chrg.cpMax = sci_get_length(doc->editor->sci);
1015 ttf.lpstrText = (gchar *)search_text;
1016 while (TRUE)
1018 pos = sci_find_text(doc->editor->sci, flags, &ttf);
1019 if (pos == -1) break;
1021 len = ttf.chrgText.cpMax - ttf.chrgText.cpMin;
1022 if (len)
1023 editor_indicator_set_on_range(doc->editor, GEANY_INDICATOR_SEARCH, pos, pos + len);
1025 ttf.chrg.cpMin = ttf.chrgText.cpMax + 1;
1026 count++;
1028 return count;
1032 static void
1033 on_find_entry_activate(GtkEntry *entry, gpointer user_data)
1035 on_find_dialog_response(NULL, GEANY_RESPONSE_FIND,
1036 ui_lookup_widget(GTK_WIDGET(entry), "entry"));
1040 static gint get_search_flags(GtkWidget *dialog)
1042 gboolean fl1, fl2, fl3, fl4;
1044 fl1 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1045 ui_lookup_widget(dialog, "check_case")));
1046 fl2 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1047 ui_lookup_widget(dialog, "check_word")));
1048 fl3 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1049 ui_lookup_widget(dialog, "check_regexp")));
1050 fl4 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1051 ui_lookup_widget(dialog, "check_wordstart")));
1053 return (fl1 ? SCFIND_MATCHCASE : 0) |
1054 (fl2 ? SCFIND_WHOLEWORD : 0) |
1055 (fl3 ? SCFIND_REGEXP | SCFIND_POSIX : 0) |
1056 (fl4 ? SCFIND_WORDSTART : 0);
1060 static void
1061 on_find_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
1063 gtk_window_get_position(GTK_WINDOW(find_dlg.dialog),
1064 &find_dlg.position[0], &find_dlg.position[1]);
1066 if (response == GTK_RESPONSE_CANCEL || response == GTK_RESPONSE_DELETE_EVENT)
1067 gtk_widget_hide(find_dlg.dialog);
1068 else
1070 GeanyDocument *doc = document_get_current();
1071 gboolean search_replace_escape;
1072 gboolean check_close = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1073 ui_lookup_widget(GTK_WIDGET(find_dlg.dialog), "check_close")));
1075 if (doc == NULL)
1076 return;
1078 search_replace_escape = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1079 ui_lookup_widget(GTK_WIDGET(find_dlg.dialog), "check_escape")));
1080 search_data.backwards = FALSE;
1081 search_data.search_bar = FALSE;
1083 g_free(search_data.text);
1084 search_data.text = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(user_data)))));
1085 search_data.flags = get_search_flags(find_dlg.dialog);
1087 if (strlen(search_data.text) == 0)
1089 fail:
1090 utils_beep();
1091 gtk_widget_grab_focus(find_dlg.entry);
1092 return;
1094 if (search_replace_escape || search_data.flags & SCFIND_REGEXP)
1096 if (! utils_str_replace_escape(search_data.text, search_data.flags & SCFIND_REGEXP))
1097 goto fail;
1099 ui_combo_box_add_to_history(GTK_COMBO_BOX(user_data), search_data.text);
1101 switch (response)
1103 case GEANY_RESPONSE_FIND:
1104 case GEANY_RESPONSE_FIND_PREVIOUS:
1106 gint result = document_find_text(doc, search_data.text, search_data.flags,
1107 (response == GEANY_RESPONSE_FIND_PREVIOUS), TRUE, GTK_WIDGET(find_dlg.dialog));
1108 ui_set_search_entry_background(find_dlg.entry, (result > -1));
1109 check_close = FALSE;
1110 if (search_prefs.suppress_dialogs)
1111 check_close = TRUE;
1112 break;
1114 case GEANY_RESPONSE_FIND_IN_FILE:
1115 search_find_usage(search_data.text, search_data.flags, FALSE);
1116 break;
1118 case GEANY_RESPONSE_FIND_IN_SESSION:
1119 search_find_usage(search_data.text, search_data.flags, TRUE);
1120 break;
1122 case GEANY_RESPONSE_MARK:
1124 gint count = search_mark_all(doc, search_data.text, search_data.flags);
1126 if (count == 0)
1127 ui_set_statusbar(FALSE, _("No matches found for \"%s\"."), search_data.text);
1128 else
1129 ui_set_statusbar(FALSE,
1130 ngettext("Found %d match for \"%s\".",
1131 "Found %d matches for \"%s\".", count),
1132 count, search_data.text);
1134 break;
1136 if (check_close)
1137 gtk_widget_hide(find_dlg.dialog);
1142 static void
1143 on_replace_entry_activate(GtkEntry *entry, gpointer user_data)
1145 on_replace_dialog_response(NULL, GEANY_RESPONSE_REPLACE, NULL);
1149 static void replace_in_session(GeanyDocument *doc,
1150 gint search_flags_re, gboolean search_replace_escape_re,
1151 const gchar *find, const gchar *replace)
1153 guint n, page_count, rep_count = 0, file_count = 0;
1155 /* replace in all documents following notebook tab order */
1156 page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
1157 for (n = 0; n < page_count; n++)
1159 GeanyDocument *tmp_doc = document_get_from_page(n);
1160 gint reps = 0;
1162 reps = document_replace_all(tmp_doc, find, replace, search_flags_re,
1163 search_replace_escape_re);
1164 rep_count += reps;
1165 if (reps)
1166 file_count++;
1168 if (file_count == 0)
1170 utils_beep();
1171 ui_set_statusbar(FALSE, _("No matches found for \"%s\"."), find);
1172 return;
1174 /* if only one file was changed, don't override that document's status message
1175 * so we don't have to translate 4 messages for ngettext */
1176 if (file_count > 1)
1177 ui_set_statusbar(FALSE, _("Replaced %u matches in %u documents."),
1178 rep_count, file_count);
1180 /* show which docs had replacements: */
1181 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_STATUS);
1183 ui_save_buttons_toggle(doc->changed); /* update save all */
1187 static void
1188 on_replace_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
1190 GeanyDocument *doc = document_get_current();
1191 gint search_flags_re;
1192 gboolean search_backwards_re, search_replace_escape_re;
1193 gboolean close_window;
1194 gchar *find, *replace;
1196 gtk_window_get_position(GTK_WINDOW(replace_dlg.dialog),
1197 &replace_dlg.position[0], &replace_dlg.position[1]);
1199 if (response == GTK_RESPONSE_CANCEL || response == GTK_RESPONSE_DELETE_EVENT)
1201 gtk_widget_hide(replace_dlg.dialog);
1202 return;
1205 close_window = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1206 ui_lookup_widget(GTK_WIDGET(replace_dlg.dialog), "check_close")));
1207 search_backwards_re = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1208 ui_lookup_widget(GTK_WIDGET(replace_dlg.dialog), "check_back")));
1209 search_replace_escape_re = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1210 ui_lookup_widget(GTK_WIDGET(replace_dlg.dialog), "check_escape")));
1211 find = g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg.find_entry)));
1212 replace = g_strdup(gtk_entry_get_text(GTK_ENTRY(replace_dlg.replace_entry)));
1214 search_flags_re = get_search_flags(replace_dlg.dialog);
1216 if ((response != GEANY_RESPONSE_FIND) && (search_flags_re & SCFIND_MATCHCASE)
1217 && (strcmp(find, replace) == 0))
1219 fail:
1220 utils_beep();
1221 gtk_widget_grab_focus(replace_dlg.find_entry);
1222 return;
1224 if (search_flags_re & SCFIND_REGEXP)
1226 if (! utils_str_replace_escape(find, TRUE) ||
1227 ! utils_str_replace_escape(replace, TRUE))
1228 goto fail;
1230 else if (search_replace_escape_re)
1232 if (! utils_str_replace_escape(find, FALSE) ||
1233 ! utils_str_replace_escape(replace, FALSE))
1234 goto fail;
1237 ui_combo_box_add_to_history(GTK_COMBO_BOX(
1238 gtk_widget_get_parent(replace_dlg.find_entry)), find);
1239 ui_combo_box_add_to_history(GTK_COMBO_BOX(
1240 gtk_widget_get_parent(replace_dlg.replace_entry)), replace);
1242 switch (response)
1244 case GEANY_RESPONSE_REPLACE_AND_FIND:
1246 gint rep = document_replace_text(doc, find, replace, search_flags_re,
1247 search_backwards_re);
1248 if (rep != -1)
1249 document_find_text(doc, find, search_flags_re, search_backwards_re,
1250 TRUE, NULL);
1251 break;
1253 case GEANY_RESPONSE_REPLACE:
1254 document_replace_text(doc, find, replace, search_flags_re,
1255 search_backwards_re);
1256 break;
1258 case GEANY_RESPONSE_FIND:
1260 gint result = document_find_text(doc, find, search_flags_re,
1261 search_backwards_re, TRUE, GTK_WIDGET(dialog));
1262 ui_set_search_entry_background(replace_dlg.find_entry, (result > -1));
1263 break;
1265 case GEANY_RESPONSE_REPLACE_IN_FILE:
1266 if (! document_replace_all(doc, find, replace, search_flags_re,
1267 search_replace_escape_re))
1268 utils_beep();
1269 break;
1271 case GEANY_RESPONSE_REPLACE_IN_SESSION:
1272 replace_in_session(doc, search_flags_re, search_replace_escape_re, find, replace);
1273 break;
1275 case GEANY_RESPONSE_REPLACE_IN_SEL:
1276 document_replace_sel(doc, find, replace, search_flags_re, search_replace_escape_re);
1277 break;
1279 switch (response)
1281 case GEANY_RESPONSE_REPLACE_IN_SEL:
1282 case GEANY_RESPONSE_REPLACE_IN_FILE:
1283 case GEANY_RESPONSE_REPLACE_IN_SESSION:
1284 if (close_window)
1285 gtk_widget_hide(replace_dlg.dialog);
1287 g_free(find);
1288 g_free(replace);
1292 static gboolean
1293 on_widget_key_pressed_set_focus(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
1295 /* catch tabulator key to set the focus in the replace entry instead of
1296 * setting it to the combo box */
1297 if (event->keyval == GDK_Tab)
1299 gtk_widget_grab_focus(GTK_WIDGET(user_data));
1300 return TRUE;
1302 return FALSE;
1306 static GString *get_grep_options(void)
1308 gboolean invert = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1309 ui_lookup_widget(fif_dlg.dialog, "check_invert")));
1310 gboolean case_sens = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1311 ui_lookup_widget(fif_dlg.dialog, "check_case")));
1312 gboolean whole_word = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1313 ui_lookup_widget(fif_dlg.dialog, "check_wholeword")));
1314 gboolean recursive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1315 ui_lookup_widget(fif_dlg.dialog, "check_recursive")));
1316 gboolean extra = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1317 ui_lookup_widget(fif_dlg.dialog, "check_extra")));
1318 GString *gstr = g_string_new("-nHI"); /* line numbers, filenames, ignore binaries */
1320 if (invert)
1321 g_string_append_c(gstr, 'v');
1322 if (! case_sens)
1323 g_string_append_c(gstr, 'i');
1324 if (whole_word)
1325 g_string_append_c(gstr, 'w');
1326 if (recursive)
1327 g_string_append_c(gstr, 'r');
1329 if (settings.fif_mode == FIF_FGREP)
1330 g_string_append_c(gstr, 'F');
1331 else if (settings.fif_mode == FIF_EGREP)
1332 g_string_append_c(gstr, 'E');
1334 if (extra)
1336 g_strstrip(settings.fif_extra_options);
1338 if (*settings.fif_extra_options != 0)
1340 g_string_append_c(gstr, ' ');
1341 g_string_append(gstr, settings.fif_extra_options);
1344 return gstr;
1348 static void
1349 on_find_in_files_dialog_response(GtkDialog *dialog, gint response,
1350 G_GNUC_UNUSED gpointer user_data)
1352 gtk_window_get_position(GTK_WINDOW(fif_dlg.dialog), &fif_dlg.position[0], &fif_dlg.position[1]);
1354 stash_group_update(fif_prefs, fif_dlg.dialog);
1356 if (response == GTK_RESPONSE_ACCEPT)
1358 GtkWidget *search_combo = fif_dlg.search_combo;
1359 const gchar *search_text =
1360 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(search_combo))));
1361 GtkWidget *dir_combo = fif_dlg.dir_combo;
1362 const gchar *utf8_dir =
1363 gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(dir_combo))));
1364 GeanyEncodingIndex enc_idx = gtk_combo_box_get_active(
1365 GTK_COMBO_BOX(fif_dlg.encoding_combo));
1367 if (!NZV(utf8_dir))
1368 ui_set_statusbar(FALSE, _("Invalid directory for find in files."));
1369 else if (NZV(search_text))
1371 gchar *locale_dir;
1372 GString *opts = get_grep_options();
1373 const gchar *enc = (enc_idx == GEANY_ENCODING_UTF_8) ? NULL :
1374 encodings_get_charset_from_index(enc_idx);
1376 locale_dir = utils_get_locale_from_utf8(utf8_dir);
1378 if (search_find_in_files(search_text, locale_dir, opts->str, enc))
1380 ui_combo_box_add_to_history(GTK_COMBO_BOX(search_combo), search_text);
1381 ui_combo_box_add_to_history(GTK_COMBO_BOX(dir_combo), utf8_dir);
1382 gtk_widget_hide(fif_dlg.dialog);
1384 g_free(locale_dir);
1385 g_string_free(opts, TRUE);
1387 else
1388 ui_set_statusbar(FALSE, _("No text to find."));
1390 else
1391 gtk_widget_hide(fif_dlg.dialog);
1395 static gboolean
1396 search_find_in_files(const gchar *utf8_search_text, const gchar *dir, const gchar *opts,
1397 const gchar *enc)
1399 gchar **argv_prefix, **argv, **opts_argv;
1400 gchar *command_grep;
1401 gchar *search_text = NULL;
1402 guint opts_argv_len, i;
1403 GPid child_pid;
1404 gint stdout_fd;
1405 gint stderr_fd;
1406 GError *error = NULL;
1407 gboolean ret = FALSE;
1408 gssize utf8_text_len;
1410 if (! NZV(utf8_search_text) || ! dir) return TRUE;
1412 command_grep = g_find_program_in_path(tool_prefs.grep_cmd);
1413 if (command_grep == NULL)
1415 ui_set_statusbar(TRUE, _("Cannot execute grep tool '%s';"
1416 " check the path setting in Preferences."), tool_prefs.grep_cmd);
1417 return FALSE;
1420 /* convert the search text in the preferred encoding (if the text is not valid UTF-8. assume
1421 * it is already in the preferred encoding) */
1422 utf8_text_len = strlen(utf8_search_text);
1423 if (enc != NULL && g_utf8_validate(utf8_search_text, utf8_text_len, NULL))
1425 search_text = g_convert(utf8_search_text, utf8_text_len, enc, "UTF-8", NULL, NULL, NULL);
1427 if (search_text == NULL)
1428 search_text = g_strdup(utf8_search_text);
1430 opts_argv = g_strsplit(opts, " ", -1);
1431 opts_argv_len = g_strv_length(opts_argv);
1433 /* set grep command and options */
1434 argv_prefix = g_new0(gchar*, 1 + opts_argv_len + 3 + 1); /* last +1 for recursive arg */
1436 argv_prefix[0] = command_grep;
1437 for (i = 0; i < opts_argv_len; i++)
1439 argv_prefix[i + 1] = g_strdup(opts_argv[i]);
1441 g_strfreev(opts_argv);
1443 i++; /* correct for tool_prefs.grep_cmd */
1444 argv_prefix[i++] = g_strdup("--");
1445 argv_prefix[i++] = g_strdup(search_text);
1447 /* finally add the arguments(files to be searched) */
1448 if (strstr(argv_prefix[1], "r")) /* recursive option set */
1450 argv_prefix[i++] = g_strdup(".");
1451 argv_prefix[i++] = NULL;
1452 argv = argv_prefix;
1454 else
1456 argv_prefix[i++] = NULL;
1457 argv = search_get_argv((const gchar**)argv_prefix, dir);
1458 g_strfreev(argv_prefix);
1461 if (argv == NULL) /* no files */
1463 g_strfreev(argv);
1464 return FALSE;
1467 gtk_list_store_clear(msgwindow.store_msg);
1468 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_MESSAGE);
1470 if (! g_spawn_async_with_pipes(dir, (gchar**)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
1471 NULL, NULL, &child_pid,
1472 NULL, &stdout_fd, &stderr_fd, &error))
1474 geany_debug("%s: g_spawn_async_with_pipes() failed: %s", G_STRFUNC, error->message);
1475 ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message);
1476 g_error_free(error);
1477 ret = FALSE;
1479 else
1481 gchar *str, *utf8_str;
1483 ui_progress_bar_start(_("Searching..."));
1485 g_free(msgwindow.find_in_files_dir);
1486 msgwindow.find_in_files_dir = g_strdup(dir);
1487 /* we can pass 'enc' without strdup'ing it here because it's a global const string and
1488 * always exits longer than the lifetime of this function */
1489 utils_set_up_io_channel(stdout_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
1490 TRUE, search_read_io, (gpointer) enc);
1491 utils_set_up_io_channel(stderr_fd, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
1492 TRUE, search_read_io_stderr, (gpointer) enc);
1493 g_child_watch_add(child_pid, search_close_pid, NULL);
1495 str = g_strdup_printf(_("%s %s -- %s (in directory: %s)"),
1496 tool_prefs.grep_cmd, opts, utf8_search_text, dir);
1497 utf8_str = utils_get_utf8_from_locale(str);
1498 msgwin_msg_add_string(COLOR_BLUE, -1, NULL, utf8_str);
1499 utils_free_pointers(2, str, utf8_str, NULL);
1500 ret = TRUE;
1502 g_strfreev(argv);
1503 return ret;
1507 /* Creates an argument vector of strings, copying argv_prefix[] values for
1508 * the first arguments, then followed by filenames found in dir.
1509 * Returns NULL if no files were found, otherwise returned vector should be fully freed. */
1510 static gchar **search_get_argv(const gchar **argv_prefix, const gchar *dir)
1512 guint prefix_len, list_len, i, j;
1513 gchar **argv;
1514 GSList *list, *item;
1515 GError *error = NULL;
1517 g_return_val_if_fail(dir != NULL, NULL);
1519 prefix_len = g_strv_length((gchar**)argv_prefix);
1520 list = utils_get_file_list(dir, &list_len, &error);
1521 if (error)
1523 ui_set_statusbar(TRUE, _("Could not open directory (%s)"), error->message);
1524 g_error_free(error);
1525 return NULL;
1527 if (list == NULL) return NULL;
1529 argv = g_new(gchar*, prefix_len + list_len + 1);
1531 for (i = 0; i < prefix_len; i++)
1532 argv[i] = g_strdup(argv_prefix[i]);
1534 item = list;
1535 for (j = 0; j < list_len; j++)
1537 argv[i++] = item->data;
1538 item = g_slist_next(item);
1540 argv[i] = NULL;
1542 g_slist_free(list);
1543 return argv;
1547 static gboolean read_fif_io(GIOChannel *source, GIOCondition condition, gchar *enc, gint msg_color)
1549 if (condition & (G_IO_IN | G_IO_PRI))
1551 gchar *msg, *utf8_msg;
1553 while (g_io_channel_read_line(source, &msg, NULL, NULL, NULL) && msg)
1555 utf8_msg = NULL;
1557 g_strstrip(msg);
1558 /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */
1559 if (enc != NULL)
1561 if (! g_utf8_validate(msg, -1, NULL))
1563 utf8_msg = g_convert(msg, -1, "UTF-8", enc, NULL, NULL, NULL);
1565 if (utf8_msg == NULL)
1566 utf8_msg = msg;
1568 else
1569 utf8_msg = msg;
1571 msgwin_msg_add_string(msg_color, -1, NULL, utf8_msg);
1573 if (utf8_msg != msg)
1574 g_free(utf8_msg);
1575 g_free(msg);
1578 if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
1579 return FALSE;
1581 return TRUE;
1585 static gboolean search_read_io(GIOChannel *source, GIOCondition condition, gpointer data)
1587 return read_fif_io(source, condition, data, COLOR_BLACK);
1591 static gboolean search_read_io_stderr(GIOChannel *source, GIOCondition condition, gpointer data)
1593 return read_fif_io(source, condition, data, COLOR_DARK_RED);
1597 static void search_close_pid(GPid child_pid, gint status, gpointer user_data)
1599 /* TODO: port this also to Windows API */
1600 #ifdef G_OS_UNIX
1601 const gchar *msg = _("Search failed.");
1602 gint exit_status = 1;
1604 if (WIFEXITED(status))
1606 exit_status = WEXITSTATUS(status);
1608 else if (WIFSIGNALED(status))
1610 exit_status = -1;
1611 g_warning("Find in Files: The command failed unexpectedly (signal received).");
1614 switch (exit_status)
1616 case 0:
1618 gint count = gtk_tree_model_iter_n_children(
1619 GTK_TREE_MODEL(msgwindow.store_msg), NULL) - 1;
1620 gchar *text = ngettext(
1621 "Search completed with %d match.",
1622 "Search completed with %d matches.", count);
1624 msgwin_msg_add(COLOR_BLUE, -1, NULL, text, count);
1625 ui_set_statusbar(FALSE, text, count);
1626 break;
1628 case 1:
1629 msg = _("No matches found.");
1630 default:
1631 msgwin_msg_add_string(COLOR_BLUE, -1, NULL, msg);
1632 ui_set_statusbar(FALSE, "%s", msg);
1633 break;
1635 #endif
1637 utils_beep();
1638 g_spawn_close_pid(child_pid);
1639 ui_progress_bar_stop();
1643 static gboolean compile_regex(regex_t *regex, const gchar *str, gint sflags)
1645 gint err;
1646 gint rflags = REG_EXTENDED | REG_NEWLINE;
1648 if (~sflags & SCFIND_MATCHCASE)
1649 rflags |= REG_ICASE;
1651 err = regcomp(regex, str, rflags);
1652 if (err != 0)
1654 gchar buf[256];
1656 regerror(err, regex, buf, sizeof buf);
1657 ui_set_statusbar(FALSE, _("Bad regex: %s"), buf);
1658 return FALSE;
1660 return TRUE;
1664 /* groups that don't exist are handled OK as len = end - start = (-1) - (-1) = 0 */
1665 static gchar *get_regex_match_string(const gchar *text, regmatch_t *pmatch, gint match_idx)
1667 return g_strndup(&text[pmatch[match_idx].rm_so],
1668 pmatch[match_idx].rm_eo - pmatch[match_idx].rm_so);
1672 static regmatch_t regex_matches[10];
1673 /* All matching text from regex_matches[0].rm_so to regex_matches[0].rm_eo */
1674 static gchar *regex_match_text = NULL;
1676 static gint find_regex(ScintillaObject *sci, guint pos, regex_t *regex)
1678 const gchar *text;
1679 gint flags = 0;
1681 g_return_val_if_fail(pos <= (guint)sci_get_length(sci), FALSE);
1683 if (sci_get_col_from_position(sci, pos) != 0)
1684 flags = REG_NOTBOL;
1685 /* Warning: any SCI calls will invalidate 'text' after calling SCI_GETCHARACTERPOINTER */
1686 text = (void*)scintilla_send_message(sci, SCI_GETCHARACTERPOINTER, 0, 0);
1687 text += pos;
1689 if (regexec(regex, text, G_N_ELEMENTS(regex_matches), regex_matches, flags) == 0)
1691 setptr(regex_match_text, get_regex_match_string(text, regex_matches, 0));
1692 return regex_matches[0].rm_so + pos;
1694 setptr(regex_match_text, NULL);
1695 return -1;
1699 gint search_find_next(ScintillaObject *sci, const gchar *str, gint flags)
1701 regex_t regex;
1702 gint ret = -1;
1703 gint pos;
1705 if (~flags & SCFIND_REGEXP)
1706 return sci_search_next(sci, flags, str);
1708 if (!compile_regex(&regex, str, flags))
1709 return -1;
1711 pos = sci_get_current_position(sci);
1712 ret = find_regex(sci, pos, &regex);
1713 if (ret >= 0)
1714 sci_set_selection(sci, ret, regex_matches[0].rm_eo + pos);
1716 regfree(&regex);
1717 return ret;
1721 gint search_replace_target(ScintillaObject *sci, const gchar *replace_text,
1722 gboolean regex)
1724 GString *str;
1725 gint ret = 0;
1726 gint i = 0;
1728 if (!regex)
1729 return sci_replace_target(sci, replace_text, FALSE);
1731 str = g_string_new(replace_text);
1732 while (str->str[i])
1734 gchar *ptr = &str->str[i];
1735 gchar *grp;
1736 gchar c;
1738 if (ptr[0] != '\\')
1740 i++;
1741 continue;
1743 c = ptr[1];
1744 /* backslash or unnecessary escape */
1745 if (c == '\\' || !isdigit(c))
1747 g_string_erase(str, i, 1);
1748 i++;
1749 continue;
1751 /* digit escape */
1752 g_string_erase(str, i, 2);
1753 /* fix match offsets by subtracting index of whole match start from the string */
1754 grp = get_regex_match_string(regex_match_text - regex_matches[0].rm_so,
1755 regex_matches, c - '0');
1756 g_string_insert(str, i, grp);
1757 i += strlen(grp);
1758 g_free(grp);
1760 ret = sci_replace_target(sci, str->str, FALSE);
1761 g_string_free(str, TRUE);
1762 return ret;
1766 static gint search_find_text(ScintillaObject *sci, gint flags, struct Sci_TextToFind *ttf)
1768 regex_t regex;
1769 gint pos;
1770 gint ret;
1772 if (~flags & SCFIND_REGEXP)
1773 return sci_find_text(sci, flags, ttf);
1775 if (!compile_regex(&regex, ttf->lpstrText, flags))
1776 return -1;
1778 pos = ttf->chrg.cpMin;
1779 ret = find_regex(sci, pos, &regex);
1780 if (ret >= 0)
1782 ttf->chrgText.cpMin = regex_matches[0].rm_so + pos;
1783 ttf->chrgText.cpMax = regex_matches[0].rm_eo + pos;
1785 regfree(&regex);
1786 return ret;
1790 static gint find_document_usage(GeanyDocument *doc, const gchar *search_text, gint flags)
1792 gchar *buffer, *short_file_name;
1793 struct Sci_TextToFind ttf;
1794 gint count = 0;
1795 gint prev_line = -1;
1797 g_return_val_if_fail(doc != NULL, 0);
1799 short_file_name = g_path_get_basename(DOC_FILENAME(doc));
1801 ttf.chrg.cpMin = 0;
1802 ttf.chrg.cpMax = sci_get_length(doc->editor->sci);
1803 ttf.lpstrText = (gchar *)search_text;
1804 while (1)
1806 gint pos, line, start, find_len;
1808 pos = search_find_text(doc->editor->sci, flags, &ttf);
1809 if (pos == -1)
1810 break; /* no more matches */
1811 find_len = ttf.chrgText.cpMax - ttf.chrgText.cpMin;
1812 if (find_len == 0)
1813 break; /* Ignore regex ^ or $ */
1815 count++;
1816 line = sci_get_line_from_position(doc->editor->sci, pos);
1817 if (line != prev_line)
1819 buffer = sci_get_line(doc->editor->sci, line);
1820 msgwin_msg_add(COLOR_BLACK, line + 1, doc,
1821 "%s:%d: %s", short_file_name, line + 1, g_strstrip(buffer));
1822 g_free(buffer);
1823 prev_line = line;
1826 start = ttf.chrgText.cpMax + 1;
1827 ttf.chrg.cpMin = start;
1829 g_free(short_file_name);
1830 return count;
1834 void search_find_usage(const gchar *search_text, gint flags, gboolean in_session)
1836 GeanyDocument *doc;
1837 gint count = 0;
1839 doc = document_get_current();
1840 g_return_if_fail(doc != NULL);
1842 if (!NZV(search_text))
1844 utils_beep();
1845 return;
1848 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_MESSAGE);
1849 gtk_list_store_clear(msgwindow.store_msg);
1851 if (! in_session)
1852 { /* use current document */
1853 count = find_document_usage(doc, search_text, flags);
1855 else
1857 guint i;
1858 for (i = 0; i < documents_array->len; i++)
1860 if (documents[i]->is_valid)
1862 count += find_document_usage(documents[i], search_text, flags);
1867 if (count == 0) /* no matches were found */
1869 ui_set_statusbar(FALSE, _("No matches found for \"%s\"."), search_text);
1870 msgwin_msg_add(COLOR_BLUE, -1, NULL, _("No matches found for \"%s\"."), search_text);
1872 else
1874 ui_set_statusbar(FALSE, ngettext(
1875 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count),
1876 count, search_text);
1877 msgwin_msg_add(COLOR_BLUE, -1, NULL, ngettext(
1878 "Found %d match for \"%s\".", "Found %d matches for \"%s\".", count),
1879 count, search_text);
1884 /* ttf is updated to include the last match position (ttf->chrg.cpMin) and
1885 * the new search range end (ttf->chrg.cpMax).
1886 * Note: Normally you would call sci_start/end_undo_action() around this call. */
1887 /* Warning: Scintilla recommends caching replacements to do all at once to avoid
1888 * performance issues with SCI_GETCHARACTERPOINTER. */
1889 guint search_replace_range(ScintillaObject *sci, struct Sci_TextToFind *ttf,
1890 gint flags, const gchar *replace_text)
1892 gint count = 0;
1893 const gchar *find_text = ttf->lpstrText;
1894 gint start = ttf->chrg.cpMin;
1895 gint end = ttf->chrg.cpMax;
1897 g_return_val_if_fail(sci != NULL && find_text != NULL && replace_text != NULL, 0);
1898 if (! *find_text)
1899 return 0;
1901 while (TRUE)
1903 gint search_pos;
1904 gint find_len = 0, replace_len = 0;
1906 search_pos = search_find_text(sci, flags, ttf);
1907 find_len = ttf->chrgText.cpMax - ttf->chrgText.cpMin;
1908 if (search_pos == -1)
1909 break; /* no more matches */
1910 if (find_len == 0 && ! NZV(replace_text))
1911 break; /* nothing to do */
1913 if (search_pos + find_len > end)
1914 break; /* found text is partly out of range */
1915 else
1917 gint movepastEOL = 0;
1919 sci_set_target_start(sci, search_pos);
1920 sci_set_target_end(sci, search_pos + find_len);
1922 if (find_len <= 0)
1924 gchar chNext = sci_get_char_at(sci, sci_get_target_end(sci));
1926 if (chNext == '\r' || chNext == '\n')
1927 movepastEOL = 1;
1929 replace_len = search_replace_target(sci, replace_text,
1930 flags & SCFIND_REGEXP);
1931 count++;
1932 if (search_pos == end)
1933 break; /* Prevent hang when replacing regex $ */
1935 /* make the next search start after the replaced text */
1936 start = search_pos + replace_len + movepastEOL;
1937 if (find_len == 0)
1938 start = sci_get_position_after(sci, start); /* prevent '[ ]*' regex rematching part of replaced text */
1939 ttf->chrg.cpMin = start;
1940 end += replace_len - find_len; /* update end of range now text has changed */
1941 ttf->chrg.cpMax = end;
1944 return count;