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.
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>
37 #include "msgwindow.h"
38 #include "sciwrappers.h"
41 #include "encodings.h"
51 # include <sys/types.h>
52 # include <sys/wait.h>
58 # include "gnuregex.h"
64 GEANY_RESPONSE_FIND
= 1,
65 GEANY_RESPONSE_FIND_PREVIOUS
,
66 GEANY_RESPONSE_FIND_IN_FILE
,
67 GEANY_RESPONSE_FIND_IN_SESSION
,
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
84 GeanySearchData search_data
;
86 GeanySearchPrefs search_prefs
;
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
;
108 gboolean all_expanded
;
109 gint position
[2]; /* x, y */
111 find_dlg
= {NULL
, NULL
, FALSE
, {0, 0}};
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}};
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
);
143 on_find_replace_checkbutton_toggled(GtkToggleButton
*togglebutton
, gpointer user_data
);
146 on_find_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
);
149 on_find_entry_activate(GtkEntry
*entry
, gpointer user_data
);
152 on_replace_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
);
155 on_replace_entry_activate(GtkEntry
*entry
, gpointer user_data
);
158 on_widget_key_pressed_set_focus(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
);
161 on_find_in_files_dialog_response(GtkDialog
*dialog
, gint response
, gpointer user_data
);
164 search_find_in_files(const gchar
*utf8_search_text
, const gchar
*dir
, const gchar
*opts
,
168 static void init_prefs(void)
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");
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
,
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
;
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
);
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
);
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
)
328 GtkClipboard
*clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
331 g_return_if_fail(doc
!= NULL
);
334 s
= gtk_clipboard_wait_for_text(clipboard
);
337 if (strchr(s
,'\n') || strchr(s
, '\r'))
346 s
= editor_get_default_selection(doc
->editor
, TRUE
, NULL
);
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
);
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" \
368 " font_name=\"Monospace\"\n" \
370 "widget \"GeanyDialogSearch.*.GtkEntry\" style \"geany-monospace\"";
371 static gboolean load
= TRUE
;
375 gtk_rc_parse_string(rcstyle
);
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();
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();
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
);
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
));
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();
666 sel
= editor_get_default_selection(doc
->editor
, search_prefs
.use_current_word
, NULL
);
668 if (replace_dlg
.dialog
== NULL
)
670 create_replace_dialog();
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
);
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
));
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
;
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
,
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();
879 gchar
*cur_dir
= NULL
;
880 GeanyEncodingIndex enc_idx
= GEANY_ENCODING_UTF_8
;
882 if (fif_dlg
.dialog
== NULL
)
885 gtk_widget_show_all(fif_dlg
.dialog
);
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
;
897 gtk_entry_set_text(GTK_ENTRY(entry
), 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
;
910 cur_dir
= g_strdup(dir
); /* custom directory argument passed */
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) */
922 cur_dir
= g_strdup(utils_get_default_dir_utf8());
924 cur_dir
= g_get_current_dir();
929 gtk_entry_set_text(GTK_ENTRY(entry
), cur_dir
);
933 /* set the encoding of the current file */
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
);
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
));
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
);
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;
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
))
1014 ttf
.chrg
.cpMax
= sci_get_length(doc
->editor
->sci
);
1015 ttf
.lpstrText
= (gchar
*)search_text
;
1018 pos
= sci_find_text(doc
->editor
->sci
, flags
, &ttf
);
1019 if (pos
== -1) break;
1021 len
= ttf
.chrgText
.cpMax
- ttf
.chrgText
.cpMin
;
1023 editor_indicator_set_on_range(doc
->editor
, GEANY_INDICATOR_SEARCH
, pos
, pos
+ len
);
1025 ttf
.chrg
.cpMin
= ttf
.chrgText
.cpMax
+ 1;
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);
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
);
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")));
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)
1091 gtk_widget_grab_focus(find_dlg
.entry
);
1094 if (search_replace_escape
|| search_data
.flags
& SCFIND_REGEXP
)
1096 if (! utils_str_replace_escape(search_data
.text
, search_data
.flags
& SCFIND_REGEXP
))
1099 ui_combo_box_add_to_history(GTK_COMBO_BOX(user_data
), search_data
.text
);
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
)
1114 case GEANY_RESPONSE_FIND_IN_FILE
:
1115 search_find_usage(search_data
.text
, search_data
.flags
, FALSE
);
1118 case GEANY_RESPONSE_FIND_IN_SESSION
:
1119 search_find_usage(search_data
.text
, search_data
.flags
, TRUE
);
1122 case GEANY_RESPONSE_MARK
:
1124 gint count
= search_mark_all(doc
, search_data
.text
, search_data
.flags
);
1127 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), search_data
.text
);
1129 ui_set_statusbar(FALSE
,
1130 ngettext("Found %d match for \"%s\".",
1131 "Found %d matches for \"%s\".", count
),
1132 count
, search_data
.text
);
1137 gtk_widget_hide(find_dlg
.dialog
);
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
);
1162 reps
= document_replace_all(tmp_doc
, find
, replace
, search_flags_re
,
1163 search_replace_escape_re
);
1168 if (file_count
== 0)
1171 ui_set_statusbar(FALSE
, _("No matches found for \"%s\"."), find
);
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 */
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 */
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
);
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))
1221 gtk_widget_grab_focus(replace_dlg
.find_entry
);
1224 if (search_flags_re
& SCFIND_REGEXP
)
1226 if (! utils_str_replace_escape(find
, TRUE
) ||
1227 ! utils_str_replace_escape(replace
, TRUE
))
1230 else if (search_replace_escape_re
)
1232 if (! utils_str_replace_escape(find
, FALSE
) ||
1233 ! utils_str_replace_escape(replace
, FALSE
))
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
);
1244 case GEANY_RESPONSE_REPLACE_AND_FIND
:
1246 gint rep
= document_replace_text(doc
, find
, replace
, search_flags_re
,
1247 search_backwards_re
);
1249 document_find_text(doc
, find
, search_flags_re
, search_backwards_re
,
1253 case GEANY_RESPONSE_REPLACE
:
1254 document_replace_text(doc
, find
, replace
, search_flags_re
,
1255 search_backwards_re
);
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));
1265 case GEANY_RESPONSE_REPLACE_IN_FILE
:
1266 if (! document_replace_all(doc
, find
, replace
, search_flags_re
,
1267 search_replace_escape_re
))
1271 case GEANY_RESPONSE_REPLACE_IN_SESSION
:
1272 replace_in_session(doc
, search_flags_re
, search_replace_escape_re
, find
, replace
);
1275 case GEANY_RESPONSE_REPLACE_IN_SEL
:
1276 document_replace_sel(doc
, find
, replace
, search_flags_re
, search_replace_escape_re
);
1281 case GEANY_RESPONSE_REPLACE_IN_SEL
:
1282 case GEANY_RESPONSE_REPLACE_IN_FILE
:
1283 case GEANY_RESPONSE_REPLACE_IN_SESSION
:
1285 gtk_widget_hide(replace_dlg
.dialog
);
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
));
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 */
1321 g_string_append_c(gstr
, 'v');
1323 g_string_append_c(gstr
, 'i');
1325 g_string_append_c(gstr
, 'w');
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');
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
);
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
));
1368 ui_set_statusbar(FALSE
, _("Invalid directory for find in files."));
1369 else if (NZV(search_text
))
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
);
1385 g_string_free(opts
, TRUE
);
1388 ui_set_statusbar(FALSE
, _("No text to find."));
1391 gtk_widget_hide(fif_dlg
.dialog
);
1396 search_find_in_files(const gchar
*utf8_search_text
, const gchar
*dir
, const gchar
*opts
,
1399 gchar
**argv_prefix
, **argv
, **opts_argv
;
1400 gchar
*command_grep
;
1401 gchar
*search_text
= NULL
;
1402 guint opts_argv_len
, i
;
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
);
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
;
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 */
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
);
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
);
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
;
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
);
1523 ui_set_statusbar(TRUE
, _("Could not open directory (%s)"), error
->message
);
1524 g_error_free(error
);
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
]);
1535 for (j
= 0; j
< list_len
; j
++)
1537 argv
[i
++] = item
->data
;
1538 item
= g_slist_next(item
);
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
)
1558 /* enc is NULL when encoding is set to UTF-8, so we can skip any conversion */
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
)
1571 msgwin_msg_add_string(msg_color
, -1, NULL
, utf8_msg
);
1573 if (utf8_msg
!= msg
)
1578 if (condition
& (G_IO_ERR
| G_IO_HUP
| G_IO_NVAL
))
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 */
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
))
1611 g_warning("Find in Files: The command failed unexpectedly (signal received).");
1614 switch (exit_status
)
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
);
1629 msg
= _("No matches found.");
1631 msgwin_msg_add_string(COLOR_BLUE
, -1, NULL
, msg
);
1632 ui_set_statusbar(FALSE
, "%s", msg
);
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
)
1646 gint rflags
= REG_EXTENDED
| REG_NEWLINE
;
1648 if (~sflags
& SCFIND_MATCHCASE
)
1649 rflags
|= REG_ICASE
;
1651 err
= regcomp(regex
, str
, rflags
);
1656 regerror(err
, regex
, buf
, sizeof buf
);
1657 ui_set_statusbar(FALSE
, _("Bad regex: %s"), buf
);
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
)
1681 g_return_val_if_fail(pos
<= (guint
)sci_get_length(sci
), FALSE
);
1683 if (sci_get_col_from_position(sci
, pos
) != 0)
1685 /* Warning: any SCI calls will invalidate 'text' after calling SCI_GETCHARACTERPOINTER */
1686 text
= (void*)scintilla_send_message(sci
, SCI_GETCHARACTERPOINTER
, 0, 0);
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
);
1699 gint
search_find_next(ScintillaObject
*sci
, const gchar
*str
, gint flags
)
1705 if (~flags
& SCFIND_REGEXP
)
1706 return sci_search_next(sci
, flags
, str
);
1708 if (!compile_regex(®ex
, str
, flags
))
1711 pos
= sci_get_current_position(sci
);
1712 ret
= find_regex(sci
, pos
, ®ex
);
1714 sci_set_selection(sci
, ret
, regex_matches
[0].rm_eo
+ pos
);
1721 gint
search_replace_target(ScintillaObject
*sci
, const gchar
*replace_text
,
1729 return sci_replace_target(sci
, replace_text
, FALSE
);
1731 str
= g_string_new(replace_text
);
1734 gchar
*ptr
= &str
->str
[i
];
1744 /* backslash or unnecessary escape */
1745 if (c
== '\\' || !isdigit(c
))
1747 g_string_erase(str
, i
, 1);
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
);
1760 ret
= sci_replace_target(sci
, str
->str
, FALSE
);
1761 g_string_free(str
, TRUE
);
1766 static gint
search_find_text(ScintillaObject
*sci
, gint flags
, struct Sci_TextToFind
*ttf
)
1772 if (~flags
& SCFIND_REGEXP
)
1773 return sci_find_text(sci
, flags
, ttf
);
1775 if (!compile_regex(®ex
, ttf
->lpstrText
, flags
))
1778 pos
= ttf
->chrg
.cpMin
;
1779 ret
= find_regex(sci
, pos
, ®ex
);
1782 ttf
->chrgText
.cpMin
= regex_matches
[0].rm_so
+ pos
;
1783 ttf
->chrgText
.cpMax
= regex_matches
[0].rm_eo
+ pos
;
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
;
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
));
1802 ttf
.chrg
.cpMax
= sci_get_length(doc
->editor
->sci
);
1803 ttf
.lpstrText
= (gchar
*)search_text
;
1806 gint pos
, line
, start
, find_len
;
1808 pos
= search_find_text(doc
->editor
->sci
, flags
, &ttf
);
1810 break; /* no more matches */
1811 find_len
= ttf
.chrgText
.cpMax
- ttf
.chrgText
.cpMin
;
1813 break; /* Ignore regex ^ or $ */
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
));
1826 start
= ttf
.chrgText
.cpMax
+ 1;
1827 ttf
.chrg
.cpMin
= start
;
1829 g_free(short_file_name
);
1834 void search_find_usage(const gchar
*search_text
, gint flags
, gboolean in_session
)
1839 doc
= document_get_current();
1840 g_return_if_fail(doc
!= NULL
);
1842 if (!NZV(search_text
))
1848 gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow
.notebook
), MSG_MESSAGE
);
1849 gtk_list_store_clear(msgwindow
.store_msg
);
1852 { /* use current document */
1853 count
= find_document_usage(doc
, search_text
, flags
);
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
);
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
)
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);
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 */
1917 gint movepastEOL
= 0;
1919 sci_set_target_start(sci
, search_pos
);
1920 sci_set_target_end(sci
, search_pos
+ find_len
);
1924 gchar chNext
= sci_get_char_at(sci
, sci_get_target_end(sci
));
1926 if (chNext
== '\r' || chNext
== '\n')
1929 replace_len
= search_replace_target(sci
, replace_text
,
1930 flags
& SCFIND_REGEXP
);
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
;
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
;