From 170259ebfc753c7441418d49f62dc6ff8de04b99 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 30 Jun 2025 18:02:21 +0100 Subject: [PATCH 1/7] Global search follows app case-sensitive setting --- src/Dialogs/GlobalSearchDialog.vala | 41 ++++++++---------------- src/FolderManager/ProjectFolderItem.vala | 23 +++++++++++-- src/Widgets/SearchBar.vala | 12 ++++--- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/Dialogs/GlobalSearchDialog.vala b/src/Dialogs/GlobalSearchDialog.vala index e43a907f97..60edac0a8f 100644 --- a/src/Dialogs/GlobalSearchDialog.vala +++ b/src/Dialogs/GlobalSearchDialog.vala @@ -23,7 +23,6 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { public string folder_name { get; construct; } public bool is_repo { get; construct; } private Granite.ValidatedEntry search_term_entry; - private Gtk.Switch case_switch; private Gtk.Switch regex_switch; public string search_term { @@ -46,43 +45,33 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { } } - public bool case_sensitive { - get { - return case_switch.active; - } + public bool case_sensitive { get; construct; } - set { - case_switch.active = value; - } - } - - public GlobalSearchDialog (string folder_name, bool is_repo) { + public GlobalSearchDialog (string folder_name, bool is_repo, bool case_sensitive) { Object ( - transient_for: ((Gtk.Application) GLib.Application.get_default ()).active_window, folder_name: folder_name, is_repo: is_repo, - image_icon: new ThemedIcon ("edit-find") + case_sensitive: case_sensitive ); } construct { - primary_text = _("Search for text in “%s”").printf (folder_name); - secondary_text = _("The search term must be at least 3 characters long."); + transient_for = ((Gtk.Application) GLib.Application.get_default ()).active_window; + image_icon = new ThemedIcon ("edit-find"); search_term_entry = new Granite.ValidatedEntry () { margin_bottom = 12, width_chars = 30 //Most searches are less than this, can expand window if required }; - case_switch = new Gtk.Switch () { - active = false, - halign = Gtk.Align.START, - hexpand = true - }; + var case_text = case_sensitive ? _("Search will be case sensitive") : _("Search will case insensitive"); + + primary_text = _("Search for text in “%s”").printf (folder_name); + secondary_text = "%s\n\n%s".printf ( + _("The search term must be at least 3 characters long."), + case_text + ); - var case_label = new Gtk.Label (_("Case sensitive:")) { - halign = Gtk.Align.END - }; regex_switch = new Gtk.Switch () { active = false, @@ -98,10 +87,8 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { row_spacing = 6 }; layout.attach (search_term_entry, 0, 0, 2); - layout.attach (case_label, 0, 1); - layout.attach (case_switch, 1, 1); - layout.attach (regex_label, 0, 2); - layout.attach (regex_switch, 1, 2); + layout.attach (regex_label, 0, 1); + layout.attach (regex_switch, 1, 1); layout.show_all (); custom_bin.add (layout); diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index c53be4d553..de089a0fdc 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -511,6 +511,10 @@ namespace Scratch.FolderManager { return is_git_repo ? monitored_repo.is_valid_new_local_branch_name (new_name) : false; } + // The parameter "is_explicit" indicates whether a global search was requested + // via a context menu on an explicitly chosen folder, in which case everything in that + // folder will be searched, or whether the hot-key was used in which case the search will + // take place on the active project and will omit certain folders public void global_search ( GLib.File start_folder = this.file.file, string? term = null, @@ -528,6 +532,21 @@ namespace Scratch.FolderManager { bool case_sensitive = false; Regex? pattern = null; + var case_mode = (CaseSensitiveMode)(Scratch.settings.get_enum ("case-sensitive-search")); + switch (case_mode) { + case NEVER: + case_sensitive = false; + break; + case MIXED: + case_sensitive = (term != term.ascii_up () && term != term.ascii_down ()); + break; + case ALWAYS: + case_sensitive = true; + break; + default: + assert_not_reached (); + } + var folder_name = start_folder.get_basename (); if (this.file.file.equal (start_folder)) { folder_name = name; @@ -535,9 +554,9 @@ namespace Scratch.FolderManager { var dialog = new Scratch.Dialogs.GlobalSearchDialog ( folder_name, - monitored_repo != null && monitored_repo.git_repo != null + monitored_repo != null && monitored_repo.git_repo != null, + case_sensitive ) { - case_sensitive = case_sensitive, use_regex = use_regex, search_term = term }; diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index d4c1d0f979..e05e2dd88b 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -19,13 +19,15 @@ * with this program. If not, see . */ +public enum Scratch.CaseSensitiveMode { + NEVER, + MIXED, + ALWAYS +} + namespace Scratch.Widgets { public class SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayout Widget - enum CaseSensitiveMode { - NEVER, - MIXED, - ALWAYS - } + public weak MainWindow window { get; construct; } From 7f1cc6f797fad16e2362e5cba27ad21c2cd15e34 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 30 Jun 2025 18:33:14 +0100 Subject: [PATCH 2/7] Honor whole word search (initial) --- src/Dialogs/GlobalSearchDialog.vala | 14 +++++++++----- src/FolderManager/ProjectFolderItem.vala | 8 ++++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Dialogs/GlobalSearchDialog.vala b/src/Dialogs/GlobalSearchDialog.vala index 60edac0a8f..9baab5a1e5 100644 --- a/src/Dialogs/GlobalSearchDialog.vala +++ b/src/Dialogs/GlobalSearchDialog.vala @@ -46,12 +46,14 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { } public bool case_sensitive { get; construct; } + public bool wholeword { get; construct; } - public GlobalSearchDialog (string folder_name, bool is_repo, bool case_sensitive) { + public GlobalSearchDialog (string folder_name, bool is_repo, bool case_sensitive, bool wholeword) { Object ( folder_name: folder_name, is_repo: is_repo, - case_sensitive: case_sensitive + case_sensitive: case_sensitive, + wholeword: wholeword ); } @@ -64,12 +66,14 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { width_chars = 30 //Most searches are less than this, can expand window if required }; - var case_text = case_sensitive ? _("Search will be case sensitive") : _("Search will case insensitive"); + var case_text = case_sensitive ? _("Search will be case sensitive") : _("Search will be case insensitive"); + var wholeword_text = wholeword ? _("Search will match only whole words") : _("Search will match words and parts of words"); primary_text = _("Search for text in “%s”").printf (folder_name); - secondary_text = "%s\n\n%s".printf ( + secondary_text = "%s\n\n%s\n%s".printf ( _("The search term must be at least 3 characters long."), - case_text + case_text, + wholeword_text ); diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index de089a0fdc..ce9d954258 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -532,6 +532,7 @@ namespace Scratch.FolderManager { bool case_sensitive = false; Regex? pattern = null; + var wholeword_search = Scratch.settings.get_boolean ("wholeword-search"); var case_mode = (CaseSensitiveMode)(Scratch.settings.get_enum ("case-sensitive-search")); switch (case_mode) { case NEVER: @@ -555,7 +556,8 @@ namespace Scratch.FolderManager { var dialog = new Scratch.Dialogs.GlobalSearchDialog ( folder_name, monitored_repo != null && monitored_repo.git_repo != null, - case_sensitive + case_sensitive, + wholeword_search ) { use_regex = use_regex, search_term = term @@ -566,7 +568,6 @@ namespace Scratch.FolderManager { case Gtk.ResponseType.ACCEPT: search_term = dialog.search_term; use_regex = dialog.use_regex; - case_sensitive = dialog.case_sensitive; break; default: @@ -588,6 +589,9 @@ namespace Scratch.FolderManager { if (!use_regex) { search_term = Regex.escape_string (search_term); + if (wholeword_search) { + search_term = "\\b%s\\b".printf (search_term); + } } try { From 407f0d2e3841a74fefda85f93ff8343de46864ec Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 1 Jul 2025 11:40:16 +0100 Subject: [PATCH 3/7] Tweak whole word regex --- src/FolderManager/ProjectFolderItem.vala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index ce9d954258..407a071fdb 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -588,11 +588,10 @@ namespace Scratch.FolderManager { win.actions.lookup_action ("action-find").activate (search_variant); if (!use_regex) { - search_term = Regex.escape_string (search_term); if (wholeword_search) { search_term = "\\b%s\\b".printf (search_term); } - } + } // else use search_term as is - TODO do we need to escape it? try { var flags = RegexCompileFlags.MULTILINE; @@ -678,7 +677,7 @@ namespace Scratch.FolderManager { } private void perform_match (GLib.File target, - Regex pattern, + Regex search_regex, bool check_is_text = false, FileInfo? target_info = null) { string contents; @@ -722,7 +721,7 @@ namespace Scratch.FolderManager { MatchInfo? match_info = null; int match_count = 0; try { - for (pattern.match (contents, 0, out match_info); + for (search_regex.match (contents, RegexMatchFlags.NOTEMPTY, out match_info); match_info.matches (); match_info.next ()) { From 8b90149deb6731522c7f6b789b5aaa132564c62d Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 1 Jul 2025 12:47:29 +0100 Subject: [PATCH 4/7] Track regex search setting --- src/Dialogs/GlobalSearchDialog.vala | 71 +++++++++++------------- src/FolderManager/ProjectFolderItem.vala | 7 +-- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/src/Dialogs/GlobalSearchDialog.vala b/src/Dialogs/GlobalSearchDialog.vala index 9baab5a1e5..34907c5652 100644 --- a/src/Dialogs/GlobalSearchDialog.vala +++ b/src/Dialogs/GlobalSearchDialog.vala @@ -23,7 +23,6 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { public string folder_name { get; construct; } public bool is_repo { get; construct; } private Granite.ValidatedEntry search_term_entry; - private Gtk.Switch regex_switch; public string search_term { get { @@ -35,25 +34,17 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { } } - public bool use_regex { - get { - return regex_switch.active; - } - - set { - regex_switch.active = value; - } - } - public bool case_sensitive { get; construct; } public bool wholeword { get; construct; } + public bool use_regex { get; construct; } - public GlobalSearchDialog (string folder_name, bool is_repo, bool case_sensitive, bool wholeword) { + public GlobalSearchDialog (string folder_name, bool is_repo, bool case_sensitive, bool wholeword, bool use_regex) { Object ( folder_name: folder_name, is_repo: is_repo, case_sensitive: case_sensitive, - wholeword: wholeword + wholeword: wholeword, + use_regex: use_regex ); } @@ -66,36 +57,33 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { width_chars = 30 //Most searches are less than this, can expand window if required }; - var case_text = case_sensitive ? _("Search will be case sensitive") : _("Search will be case insensitive"); - var wholeword_text = wholeword ? _("Search will match only whole words") : _("Search will match words and parts of words"); + string case_text = "", wholeword_text = "", regex_text = ""; + if (use_regex) { + regex_text = _("The search term will be treated as a regex expression"); + } else { + case_text = case_sensitive ? _("Search will be case sensitive") : _("Search will be case insensitive"); + wholeword_text = wholeword ? _("Search will match only whole words") : ""; + } primary_text = _("Search for text in “%s”").printf (folder_name); - secondary_text = "%s\n\n%s\n%s".printf ( - _("The search term must be at least 3 characters long."), - case_text, - wholeword_text - ); - - - regex_switch = new Gtk.Switch () { - active = false, - halign = Gtk.Align.START - }; + secondary_text = _("The search term must be at least 3 characters long."); + + var box = new Gtk.Box (VERTICAL, 0); + if (!use_regex) { + box.add (new Gtk.Label (case_text)); + if (wholeword_text != "") { + box.add (new Gtk.Label (wholeword_text)); + } + } - var regex_label = new Gtk.Label (_("Use regular expressions:")) { - halign = Gtk.Align.END - }; + box.add (search_term_entry); - var layout = new Gtk.Grid () { - column_spacing = 12, - row_spacing = 6 - }; - layout.attach (search_term_entry, 0, 0, 2); - layout.attach (regex_label, 0, 1); - layout.attach (regex_switch, 1, 1); - layout.show_all (); + if (use_regex) { + box.add (new Gtk.Label (regex_text)); + } - custom_bin.add (layout); + custom_bin.add (box); + custom_bin.show_all (); add_button (_("Cancel"), Gtk.ResponseType.CANCEL); @@ -110,6 +98,13 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { search_term_entry.changed.connect (() => { search_term_entry.is_valid = search_term_entry.text.length >= 3; + if (use_regex) { + try { + var search_regex = new Regex (search_term_entry.text, 0); + } catch { + search_term_entry.is_valid = false; + } + } }); } } diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 407a071fdb..fe2a5483da 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -523,7 +523,6 @@ namespace Scratch.FolderManager { /* For now set all options to the most inclusive (except case). * The ability to set these in the dialog (or by parameter) may be added later. */ string? search_term = null; - bool use_regex = false; bool search_tracked_only = false; bool recurse_subfolders = true; bool check_is_text = true; @@ -534,6 +533,7 @@ namespace Scratch.FolderManager { var wholeword_search = Scratch.settings.get_boolean ("wholeword-search"); var case_mode = (CaseSensitiveMode)(Scratch.settings.get_enum ("case-sensitive-search")); + var use_regex = Scratch.settings.get_boolean ("regex-search"); switch (case_mode) { case NEVER: case_sensitive = false; @@ -557,9 +557,9 @@ namespace Scratch.FolderManager { folder_name, monitored_repo != null && monitored_repo.git_repo != null, case_sensitive, - wholeword_search + wholeword_search, + use_regex ) { - use_regex = use_regex, search_term = term }; @@ -567,7 +567,6 @@ namespace Scratch.FolderManager { switch (response) { case Gtk.ResponseType.ACCEPT: search_term = dialog.search_term; - use_regex = dialog.use_regex; break; default: From 79f3b7d7576ac9a17af09ef2eb522feda36d6e06 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 1 Jul 2025 12:51:01 +0100 Subject: [PATCH 5/7] Simplify --- src/Dialogs/GlobalSearchDialog.vala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Dialogs/GlobalSearchDialog.vala b/src/Dialogs/GlobalSearchDialog.vala index 34907c5652..077cbe4665 100644 --- a/src/Dialogs/GlobalSearchDialog.vala +++ b/src/Dialogs/GlobalSearchDialog.vala @@ -70,18 +70,16 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { var box = new Gtk.Box (VERTICAL, 0); if (!use_regex) { - box.add (new Gtk.Label (case_text)); + box.add (new Gtk.Label (case_text) { halign = START }); if (wholeword_text != "") { - box.add (new Gtk.Label (wholeword_text)); + box.add (new Gtk.Label (wholeword_text) { halign = START }); } + } else { + box.add (new Gtk.Label (regex_text) { halign = START }); } box.add (search_term_entry); - if (use_regex) { - box.add (new Gtk.Label (regex_text)); - } - custom_bin.add (box); custom_bin.show_all (); From eac65f2210eb7e98fd213b7912b4f80f704e6620 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 1 Jul 2025 13:01:17 +0100 Subject: [PATCH 6/7] Disable other settings when use regex --- src/Widgets/SearchBar.vala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index e05e2dd88b..8fb50588d2 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -174,8 +174,13 @@ namespace Scratch.Widgets { Scratch.settings.bind ("cyclic-search", cycle_search_button, "active", SettingsBindFlags.DEFAULT); Scratch.settings.bind ("wholeword-search", whole_word_search_button, "active", SettingsBindFlags.DEFAULT); - Scratch.settings.bind ("regex-search", regex_search_button, "active", SettingsBindFlags.DEFAULT); Scratch.settings.bind ("case-sensitive-search", case_sensitive_search_button, "active-id", SettingsBindFlags.DEFAULT); + Scratch.settings.bind ("regex-search", regex_search_button, "active", SettingsBindFlags.DEFAULT); + // These settings are ignored when regex searching + regex_search_button.bind_property ("active", cycle_search_button, "sensitive", SYNC_CREATE | INVERT_BOOLEAN); + regex_search_button.bind_property ("active", whole_word_search_button, "sensitive", SYNC_CREATE | INVERT_BOOLEAN); + regex_search_button.bind_property ("active", case_sensitive_search_label, "sensitive", SYNC_CREATE | INVERT_BOOLEAN); + regex_search_button.bind_property ("active", case_sensitive_search_button, "sensitive", SYNC_CREATE | INVERT_BOOLEAN); var search_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { margin_top = 3, From effddd047156a842175d0f9fd2cd6f6aa567c7e3 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 16 Oct 2025 10:26:00 +0100 Subject: [PATCH 7/7] Update src/Dialogs/GlobalSearchDialog.vala Use boolean Co-authored-by: Ryan Kornheisl --- src/Dialogs/GlobalSearchDialog.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dialogs/GlobalSearchDialog.vala b/src/Dialogs/GlobalSearchDialog.vala index 077cbe4665..7510233b3d 100644 --- a/src/Dialogs/GlobalSearchDialog.vala +++ b/src/Dialogs/GlobalSearchDialog.vala @@ -71,7 +71,7 @@ public class Scratch.Dialogs.GlobalSearchDialog : Granite.MessageDialog { var box = new Gtk.Box (VERTICAL, 0); if (!use_regex) { box.add (new Gtk.Label (case_text) { halign = START }); - if (wholeword_text != "") { + if (wholeword) { box.add (new Gtk.Label (wholeword_text) { halign = START }); } } else {