From c757b166bbe913c6b87ab8e9ba84fca27c283108 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 11 Aug 2026 17:40:57 +0100 Subject: [PATCH 01/10] Use SPDX license and copyright id --- src/Widgets/SearchBar.vala | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index 54efbf8f30..e035843631 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -1,23 +1,9 @@ /* - * Copyright (C) 2011-2012 Lucas Baudin - * 2013 Mario Guerriero - 2014-2026 elementary, Inc. (https://elementary.io) - * - * This file is part of Code. - * - * Code is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Code is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ +* SPDX-License-Identifier: GPL-3.0-or-later +* SPDX-FileCopyrightText: 2014-2026 elementary, Inc. (https://elementary.io) +* 2013 Mario Guerriero +* 2011-2012 Lucas Baudin +*/ public enum Scratch.CaseSensitiveMode { NEVER, From 0383697b83e67de01e1fa94d0407c70a75d0a16e Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 11 Aug 2026 17:43:09 +0100 Subject: [PATCH 02/10] Move private members after public properties --- src/Widgets/SearchBar.vala | 49 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index e035843631..af93a6ea34 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -16,31 +16,6 @@ namespace Scratch.Widgets { public weak MainWindow window { get; construct; } - - private Gtk.Button tool_arrow_up; - private Gtk.Button tool_arrow_down; - - /** - * Is the search cyclic? e.g., when you are at the bottom, if you press - * "Down", it will go at the start of the file to search for the content - * of the search entry. - **/ - private Granite.SwitchModelButton cycle_search_button ; - private Gtk.ComboBoxText case_sensitive_search_button; - private Granite.SwitchModelButton regex_search_button; - private Granite.SwitchModelButton whole_word_search_button; - private Gtk.SearchEntry search_entry; - private Gtk.SearchEntry replace_entry; - private Gtk.Label search_occurence_count_label; - private Gtk.Button replace_tool_button; - private Gtk.Button replace_all_tool_button; - private Scratch.Widgets.SourceView? text_view = null; - private Gtk.TextBuffer? text_buffer = null; - private Gtk.SourceSearchContext? search_context; - private uint update_search_label_timeout_id = 0; - private Gtk.Revealer revealer; - private Gtk.EventControllerKey key_controller; - public bool is_focused { get { return search_is_focused || replace_is_focused; @@ -95,6 +70,30 @@ namespace Scratch.Widgets { } } + private Gtk.Button tool_arrow_up; + private Gtk.Button tool_arrow_down; + + /** + * Is the search cyclic? e.g., when you are at the bottom, if you press + * "Down", it will go at the start of the file to search for the content + * of the search entry. + **/ + private Granite.SwitchModelButton cycle_search_button ; + private Gtk.ComboBoxText case_sensitive_search_button; + private Granite.SwitchModelButton regex_search_button; + private Granite.SwitchModelButton whole_word_search_button; + private Gtk.SearchEntry search_entry; + private Gtk.SearchEntry replace_entry; + private Gtk.Label search_occurence_count_label; + private Gtk.Button replace_tool_button; + private Gtk.Button replace_all_tool_button; + private Scratch.Widgets.SourceView? text_view = null; + private Gtk.TextBuffer? text_buffer = null; + private Gtk.SourceSearchContext? search_context; + private uint update_search_label_timeout_id = 0; + private Gtk.Revealer revealer; + private Gtk.EventControllerKey key_controller; + public SearchBar (MainWindow window) { Object (window: window); } From 32e97b2ad2389856f6f3eba16b805840bc0853fe Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 11 Aug 2026 17:46:24 +0100 Subject: [PATCH 03/10] Move public functions before private functions --- src/Widgets/SearchBar.vala | 256 ++++++++++++++++++------------------- 1 file changed, 128 insertions(+), 128 deletions(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index af93a6ea34..96cff20c44 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -296,78 +296,6 @@ namespace Scratch.Widgets { on_text_buffer_changed (); } - private void on_text_buffer_changed () { - update_search_widgets (); - } - - private void on_replace_entry_activate () { - if (text_buffer == null) { - warning ("No valid buffer to replace"); - return; - } - - Gtk.TextIter? start_iter, end_iter; - text_buffer.get_iter_at_offset (out start_iter, text_buffer.cursor_position); - - if (search_for_iter (start_iter, out end_iter)) { - string replace_string = replace_entry.text; - try { - cancel_update_search_widgets (); - search_context.replace (start_iter, end_iter, replace_string, replace_string.length); - update_search_widgets (); - debug ("Replaced \"%s\" with \"%s\"", search_entry.text, replace_entry.text); - } catch (Error e) { - critical (e.message); - } - } - } - - private void on_replace_all_entry_activate () { - if (text_buffer == null || this.window.get_current_document () == null) { - debug ("No valid buffer to replace"); - return; - } - - string replace_string = replace_entry.text; - this.window.get_current_document ().toggle_changed_handlers (false); - try { - cancel_update_search_widgets (); - search_context.replace_all (replace_string, replace_string.length); - update_search_widgets (); - } catch (Error e) { - critical (e.message); - } - - this.window.get_current_document ().toggle_changed_handlers (true); - } - - // Called when one of the settings buttons or the search term changes - private void on_search_parameters_changed () { - if (search_context != null) { - var search_string = search_entry.text; - search_context.settings.search_text = search_string; - var case_mode = (CaseSensitiveMode)(case_sensitive_search_button.active); - switch (case_mode) { - case CaseSensitiveMode.NEVER: - search_context.settings.case_sensitive = false; - break; - case CaseSensitiveMode.MIXED: - search_context.settings.case_sensitive = !((search_string.up () == search_string) || (search_string.down () == search_string)); - break; - case CaseSensitiveMode.ALWAYS: - search_context.settings.case_sensitive = true; - break; - default: - assert_not_reached (); - } - - search_context.settings.at_word_boundaries = whole_word_search_button.active; - search_context.settings.regex_enabled = regex_search_button.active; - } - - update_search_widgets (); - } - public bool search () { search_entry.grab_focus (); if (search_context == null) { @@ -413,62 +341,6 @@ namespace Scratch.Widgets { } } - private bool has_matches () { - if (text_buffer == null || search_entry.text == "") { - return false; - } - - bool has_wrapped_around; - Gtk.TextIter? start_iter, end_iter; - text_buffer.get_start_iter (out start_iter); - return search_context.forward (start_iter, out start_iter, out end_iter, out has_wrapped_around); - } - - private bool search_for_iter (Gtk.TextIter? start_iter, out Gtk.TextIter? end_iter) { - end_iter = start_iter; - - if (search_context == null) { - critical ("Trying to search forwards with no search context"); - return false; - } - - bool has_wrapped_around; - bool found = search_context.forward (start_iter, out start_iter, out end_iter, out has_wrapped_around); - if (found) { - text_buffer.select_range (start_iter, end_iter); - if (has_wrapped_around) { - start_iter.backward_lines (3); - } else { - start_iter.forward_lines (3); - } - text_view.scroll_to_iter (start_iter, 0, false, 0, 0); - } - - return found; - } - - private bool search_for_iter_backward (Gtk.TextIter? start_iter, out Gtk.TextIter? end_iter) { - end_iter = start_iter; - - if (search_context == null) { - critical ("Trying to search backwards with no search context"); - return false; - } - - bool has_wrapped_around; - bool found = search_context.backward (start_iter, out start_iter, out end_iter, out has_wrapped_around); - if (found) { - text_buffer.select_range (start_iter, end_iter); - if (has_wrapped_around) { - start_iter.forward_lines (3); - } else { - start_iter.backward_lines (3); - } - text_view.scroll_to_iter (start_iter, 0, false, 0, 0); - } - return found; - } - public void search_previous () { /* Get selection range */ Gtk.TextIter? start_iter, end_iter; @@ -564,6 +436,134 @@ namespace Scratch.Widgets { return false; } + private void on_text_buffer_changed () { + update_search_widgets (); + } + + private void on_replace_entry_activate () { + if (text_buffer == null) { + warning ("No valid buffer to replace"); + return; + } + + Gtk.TextIter? start_iter, end_iter; + text_buffer.get_iter_at_offset (out start_iter, text_buffer.cursor_position); + + if (search_for_iter (start_iter, out end_iter)) { + string replace_string = replace_entry.text; + try { + cancel_update_search_widgets (); + search_context.replace (start_iter, end_iter, replace_string, replace_string.length); + update_search_widgets (); + debug ("Replaced \"%s\" with \"%s\"", search_entry.text, replace_entry.text); + } catch (Error e) { + critical (e.message); + } + } + } + + private void on_replace_all_entry_activate () { + if (text_buffer == null || this.window.get_current_document () == null) { + debug ("No valid buffer to replace"); + return; + } + + string replace_string = replace_entry.text; + this.window.get_current_document ().toggle_changed_handlers (false); + try { + cancel_update_search_widgets (); + search_context.replace_all (replace_string, replace_string.length); + update_search_widgets (); + } catch (Error e) { + critical (e.message); + } + + this.window.get_current_document ().toggle_changed_handlers (true); + } + + // Called when one of the settings buttons or the search term changes + private void on_search_parameters_changed () { + if (search_context != null) { + var search_string = search_entry.text; + search_context.settings.search_text = search_string; + var case_mode = (CaseSensitiveMode)(case_sensitive_search_button.active); + switch (case_mode) { + case CaseSensitiveMode.NEVER: + search_context.settings.case_sensitive = false; + break; + case CaseSensitiveMode.MIXED: + search_context.settings.case_sensitive = !((search_string.up () == search_string) || (search_string.down () == search_string)); + break; + case CaseSensitiveMode.ALWAYS: + search_context.settings.case_sensitive = true; + break; + default: + assert_not_reached (); + } + + search_context.settings.at_word_boundaries = whole_word_search_button.active; + search_context.settings.regex_enabled = regex_search_button.active; + } + + update_search_widgets (); + } + + private bool has_matches () { + if (text_buffer == null || search_entry.text == "") { + return false; + } + + bool has_wrapped_around; + Gtk.TextIter? start_iter, end_iter; + text_buffer.get_start_iter (out start_iter); + return search_context.forward (start_iter, out start_iter, out end_iter, out has_wrapped_around); + } + + private bool search_for_iter (Gtk.TextIter? start_iter, out Gtk.TextIter? end_iter) { + end_iter = start_iter; + + if (search_context == null) { + critical ("Trying to search forwards with no search context"); + return false; + } + + bool has_wrapped_around; + bool found = search_context.forward (start_iter, out start_iter, out end_iter, out has_wrapped_around); + if (found) { + text_buffer.select_range (start_iter, end_iter); + if (has_wrapped_around) { + start_iter.backward_lines (3); + } else { + start_iter.forward_lines (3); + } + text_view.scroll_to_iter (start_iter, 0, false, 0, 0); + } + + return found; + } + + private bool search_for_iter_backward (Gtk.TextIter? start_iter, out Gtk.TextIter? end_iter) { + end_iter = start_iter; + + if (search_context == null) { + critical ("Trying to search backwards with no search context"); + return false; + } + + bool has_wrapped_around; + bool found = search_context.backward (start_iter, out start_iter, out end_iter, out has_wrapped_around); + if (found) { + text_buffer.select_range (start_iter, end_iter); + if (has_wrapped_around) { + start_iter.forward_lines (3); + } else { + start_iter.backward_lines (3); + } + text_view.scroll_to_iter (start_iter, 0, false, 0, 0); + } + return found; + } + private void cancel_update_search_widgets () { if (update_search_label_timeout_id > 0) { Source.remove (update_search_label_timeout_id); From b2276cfc67710529abdebc63eb16facc79e3382c Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 12 Aug 2026 11:18:12 +0100 Subject: [PATCH 04/10] Update widget creation style --- src/Widgets/SearchBar.vala | 45 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index 96cff20c44..9181ee3504 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -110,25 +110,27 @@ namespace Scratch.Widgets { var app_instance = (Scratch.Application) GLib.Application.get_default (); - tool_arrow_down = new Gtk.Button.from_icon_name ("go-down-symbolic", Gtk.IconSize.SMALL_TOOLBAR); + tool_arrow_down = new Gtk.Button.from_icon_name ("go-down-symbolic", Gtk.IconSize.SMALL_TOOLBAR) { + sensitive = false, + tooltip_markup = Granite.markup_accel_tooltip ( + app_instance.get_accels_for_action ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_NEXT + ), + _("Search next") + ) + }; tool_arrow_down.clicked.connect (search_next); - tool_arrow_down.sensitive = false; - tool_arrow_down.tooltip_markup = Granite.markup_accel_tooltip ( - app_instance.get_accels_for_action ( - Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_NEXT - ), - _("Search next") - ); - - tool_arrow_up = new Gtk.Button.from_icon_name ("go-up-symbolic", Gtk.IconSize.SMALL_TOOLBAR); + + tool_arrow_up = new Gtk.Button.from_icon_name ("go-up-symbolic", Gtk.IconSize.SMALL_TOOLBAR) { + sensitive = false, + tooltip_markup = Granite.markup_accel_tooltip ( + app_instance.get_accels_for_action ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_PREVIOUS + ), + _("Search previous") + ) + }; tool_arrow_up.clicked.connect (search_previous); - tool_arrow_up.sensitive = false; - tool_arrow_up.tooltip_markup = Granite.markup_accel_tooltip ( - app_instance.get_accels_for_action ( - Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_PREVIOUS - ), - _("Search previous") - ); cycle_search_button = new Granite.SwitchModelButton (_("Cyclic Search")); @@ -203,10 +205,11 @@ namespace Scratch.Widgets { search_flow_box_child.can_focus = false; search_flow_box_child.add (search_box); - replace_entry = new Gtk.SearchEntry (); - replace_entry.hexpand = true; - replace_entry.placeholder_text = _("Replace With"); - replace_entry.set_icon_from_icon_name (Gtk.EntryIconPosition.PRIMARY, "edit-symbolic"); + replace_entry = new Gtk.SearchEntry () { + hexpand = true, + placeholder_text = _("Replace With"), + primary_icon_name = "edit-symbolic" + }; replace_tool_button = new Gtk.Button.with_label (_("Replace")); replace_tool_button.clicked.connect (on_replace_entry_activate); From 1be8c1f3b2f67d7eca268f17d8a9ac818b97d939 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 12 Aug 2026 11:25:46 +0100 Subject: [PATCH 05/10] Lose namespace where implicit in function parameters --- src/Widgets/SearchBar.vala | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index 9181ee3504..739fc1cdb7 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -110,7 +110,7 @@ namespace Scratch.Widgets { var app_instance = (Scratch.Application) GLib.Application.get_default (); - tool_arrow_down = new Gtk.Button.from_icon_name ("go-down-symbolic", Gtk.IconSize.SMALL_TOOLBAR) { + tool_arrow_down = new Gtk.Button.from_icon_name ("go-down-symbolic", SMALL_TOOLBAR) { sensitive = false, tooltip_markup = Granite.markup_accel_tooltip ( app_instance.get_accels_for_action ( @@ -121,7 +121,7 @@ namespace Scratch.Widgets { }; tool_arrow_down.clicked.connect (search_next); - tool_arrow_up = new Gtk.Button.from_icon_name ("go-up-symbolic", Gtk.IconSize.SMALL_TOOLBAR) { + tool_arrow_up = new Gtk.Button.from_icon_name ("go-up-symbolic", SMALL_TOOLBAR) { sensitive = false, tooltip_markup = Granite.markup_accel_tooltip ( app_instance.get_accels_for_action ( @@ -142,7 +142,7 @@ namespace Scratch.Widgets { var case_sensitive_search_label = new Gtk.Label (_("Case Sensitive")); - var case_sensitive_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); + var case_sensitive_box = new Gtk.Box (HORIZONTAL, 12); case_sensitive_box.add (case_sensitive_search_label); case_sensitive_box.add (case_sensitive_search_button); case_sensitive_box.get_style_context ().add_class (Gtk.STYLE_CLASS_MENUITEM); @@ -150,7 +150,7 @@ namespace Scratch.Widgets { regex_search_button = new Granite.SwitchModelButton (_("Use Regular Expressions")); whole_word_search_button = new Granite.SwitchModelButton (_("Match Whole Words")); - var search_option_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0) { + var search_option_box = new Gtk.Box (VERTICAL, 0) { margin_top = 3, margin_bottom = 3 }; @@ -164,9 +164,9 @@ namespace Scratch.Widgets { }; search_popover.show_all (); - var search_buttonbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); + var search_buttonbox = new Gtk.Box (HORIZONTAL, 6); search_buttonbox.add (search_occurence_count_label); - search_buttonbox.add (new Gtk.Image.from_icon_name ("pan-down-symbolic", Gtk.IconSize.SMALL_TOOLBAR)); + search_buttonbox.add (new Gtk.Image.from_icon_name ("pan-down-symbolic", SMALL_TOOLBAR)); var search_menubutton = new Gtk.MenuButton () { popover = search_popover, @@ -179,17 +179,17 @@ namespace Scratch.Widgets { whole_word_search_button.toggled.connect (on_search_parameters_changed); regex_search_button.toggled.connect (on_search_parameters_changed); - 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 ("case-sensitive-search", case_sensitive_search_button, "active-id", SettingsBindFlags.DEFAULT); - Scratch.settings.bind ("regex-search", regex_search_button, "active", SettingsBindFlags.DEFAULT); + Scratch.settings.bind ("cyclic-search", cycle_search_button, "active", DEFAULT); + Scratch.settings.bind ("wholeword-search", whole_word_search_button, "active", DEFAULT); + Scratch.settings.bind ("case-sensitive-search", case_sensitive_search_button, "active-id", DEFAULT); + Scratch.settings.bind ("regex-search", regex_search_button, "active", 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) { + var search_box = new Gtk.Box (HORIZONTAL, 0) { margin_top = 3, margin_end = 3, margin_bottom = 3, From f680dceec685440ede58baec028c6e137c629c4a Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 12 Aug 2026 11:27:45 +0100 Subject: [PATCH 06/10] Inline class namespace --- src/Widgets/SearchBar.vala | 1092 ++++++++++++++++++------------------ 1 file changed, 545 insertions(+), 547 deletions(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index 739fc1cdb7..51fa0bd419 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -11,664 +11,662 @@ public enum Scratch.CaseSensitiveMode { ALWAYS } -namespace Scratch.Widgets { - public class SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayout Widget +public class Scratch.Widgets.SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayout Widget - public weak MainWindow window { get; construct; } + public weak MainWindow window { get; construct; } - public bool is_focused { - get { - return search_is_focused || replace_is_focused; - } + public bool is_focused { + get { + return search_is_focused || replace_is_focused; } + } - public bool search_is_focused { - get { - return search_entry.has_focus; - } + public bool search_is_focused { + get { + return search_entry.has_focus; } + } - public bool replace_is_focused { - get { - return replace_entry.has_focus; - } + public bool replace_is_focused { + get { + return replace_entry.has_focus; } + } - public bool is_revealed { - get { - return revealer.child_revealed; - } + public bool is_revealed { + get { + return revealer.child_revealed; } + } - public string entry_text { - get { - return search_entry.text; - } + public string entry_text { + get { + return search_entry.text; } + } - public bool has_search_term { - get { - return search_entry.text != ""; - } + public bool has_search_term { + get { + return search_entry.text != ""; } + } - public uint search_occurrences { - get { - if (search_context == null || - search_context.settings.search_text == "") { + public uint search_occurrences { + get { + if (search_context == null || + search_context.settings.search_text == "") { - return 0; - } else { - return search_context.get_occurrences_count (); - } + return 0; + } else { + return search_context.get_occurrences_count (); } } + } - public uint transition_time_msec { - get { - return revealer.transition_duration + 10; - } + public uint transition_time_msec { + get { + return revealer.transition_duration + 10; } + } - private Gtk.Button tool_arrow_up; - private Gtk.Button tool_arrow_down; - - /** - * Is the search cyclic? e.g., when you are at the bottom, if you press - * "Down", it will go at the start of the file to search for the content - * of the search entry. - **/ - private Granite.SwitchModelButton cycle_search_button ; - private Gtk.ComboBoxText case_sensitive_search_button; - private Granite.SwitchModelButton regex_search_button; - private Granite.SwitchModelButton whole_word_search_button; - private Gtk.SearchEntry search_entry; - private Gtk.SearchEntry replace_entry; - private Gtk.Label search_occurence_count_label; - private Gtk.Button replace_tool_button; - private Gtk.Button replace_all_tool_button; - private Scratch.Widgets.SourceView? text_view = null; - private Gtk.TextBuffer? text_buffer = null; - private Gtk.SourceSearchContext? search_context; - private uint update_search_label_timeout_id = 0; - private Gtk.Revealer revealer; - private Gtk.EventControllerKey key_controller; - - public SearchBar (MainWindow window) { - Object (window: window); - } - - construct { - this.orientation = HORIZONTAL; - search_entry = new Gtk.SearchEntry () { - hexpand = true, - placeholder_text = _("Find") - }; - - search_occurence_count_label = new Gtk.Label (_("No Results")); - search_occurence_count_label.get_style_context ().add_class (Granite.STYLE_CLASS_SMALL_LABEL); - - var app_instance = (Scratch.Application) GLib.Application.get_default (); - - tool_arrow_down = new Gtk.Button.from_icon_name ("go-down-symbolic", SMALL_TOOLBAR) { - sensitive = false, - tooltip_markup = Granite.markup_accel_tooltip ( - app_instance.get_accels_for_action ( - Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_NEXT - ), - _("Search next") - ) - }; - tool_arrow_down.clicked.connect (search_next); - - tool_arrow_up = new Gtk.Button.from_icon_name ("go-up-symbolic", SMALL_TOOLBAR) { - sensitive = false, - tooltip_markup = Granite.markup_accel_tooltip ( - app_instance.get_accels_for_action ( - Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_PREVIOUS - ), - _("Search previous") - ) - }; - tool_arrow_up.clicked.connect (search_previous); - - cycle_search_button = new Granite.SwitchModelButton (_("Cyclic Search")); - - case_sensitive_search_button = new Gtk.ComboBoxText (); - case_sensitive_search_button.append ("never", _("Never")); - case_sensitive_search_button.append ("mixed", _("Mixed Case")); - case_sensitive_search_button.append ("always", _("Always")); - case_sensitive_search_button.active = 1; - - var case_sensitive_search_label = new Gtk.Label (_("Case Sensitive")); - - var case_sensitive_box = new Gtk.Box (HORIZONTAL, 12); - case_sensitive_box.add (case_sensitive_search_label); - case_sensitive_box.add (case_sensitive_search_button); - case_sensitive_box.get_style_context ().add_class (Gtk.STYLE_CLASS_MENUITEM); - - regex_search_button = new Granite.SwitchModelButton (_("Use Regular Expressions")); - whole_word_search_button = new Granite.SwitchModelButton (_("Match Whole Words")); - - var search_option_box = new Gtk.Box (VERTICAL, 0) { - margin_top = 3, - margin_bottom = 3 - }; - search_option_box.add (cycle_search_button); - search_option_box.add (case_sensitive_box); - search_option_box.add (whole_word_search_button); - search_option_box.add (regex_search_button); - - var search_popover = new Gtk.Popover (null) { - child = search_option_box - }; - search_popover.show_all (); - - var search_buttonbox = new Gtk.Box (HORIZONTAL, 6); - search_buttonbox.add (search_occurence_count_label); - search_buttonbox.add (new Gtk.Image.from_icon_name ("pan-down-symbolic", SMALL_TOOLBAR)); - - var search_menubutton = new Gtk.MenuButton () { - popover = search_popover, - tooltip_text = _("Search Options") - }; - search_menubutton.add (search_buttonbox); - - cycle_search_button.toggled.connect (on_search_parameters_changed); - case_sensitive_search_button.changed.connect (on_search_parameters_changed); - whole_word_search_button.toggled.connect (on_search_parameters_changed); - regex_search_button.toggled.connect (on_search_parameters_changed); - - Scratch.settings.bind ("cyclic-search", cycle_search_button, "active", DEFAULT); - Scratch.settings.bind ("wholeword-search", whole_word_search_button, "active", DEFAULT); - Scratch.settings.bind ("case-sensitive-search", case_sensitive_search_button, "active-id", DEFAULT); - Scratch.settings.bind ("regex-search", regex_search_button, "active", 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 (HORIZONTAL, 0) { - margin_top = 3, - margin_end = 3, - margin_bottom = 3, - margin_start = 6 - }; - search_box.get_style_context ().add_class (Gtk.STYLE_CLASS_LINKED); - search_box.add (search_entry); - search_box.add (tool_arrow_down); - search_box.add (tool_arrow_up); - search_box.add (search_menubutton); - - var search_flow_box_child = new Gtk.FlowBoxChild (); - search_flow_box_child.can_focus = false; - search_flow_box_child.add (search_box); - - replace_entry = new Gtk.SearchEntry () { - hexpand = true, - placeholder_text = _("Replace With"), - primary_icon_name = "edit-symbolic" - }; - - replace_tool_button = new Gtk.Button.with_label (_("Replace")); - replace_tool_button.clicked.connect (on_replace_entry_activate); - - replace_all_tool_button = new Gtk.Button.with_label (_("Replace all")); - replace_all_tool_button.clicked.connect (on_replace_all_entry_activate); - - var replace_grid = new Gtk.Grid () { - margin_top = 3, - margin_end = 6, - margin_bottom = 3, - margin_start = 3 - }; - replace_grid.get_style_context ().add_class (Gtk.STYLE_CLASS_LINKED); - replace_grid.add (replace_entry); - replace_grid.add (replace_tool_button); - replace_grid.add (replace_all_tool_button); - - var replace_flow_box_child = new Gtk.FlowBoxChild (); - replace_flow_box_child.can_focus = false; - replace_flow_box_child.add (replace_grid); - - // Connecting to some signals - search_entry.changed.connect (on_search_parameters_changed); - search_entry.notify["is-focus"].connect (() => { - if (search_entry.is_focus && text_buffer != null) { - Idle.add (() => { - update_search_widgets (); - search_entry.select_region (0, -1); - return Source.REMOVE; - }); - } - }); - search_entry.icon_release.connect ((p0, p1) => { - if (p0 == Gtk.EntryIconPosition.PRIMARY) { - search_next (); - } - }); - replace_entry.activate.connect (on_replace_entry_activate); - - var flowbox = new Gtk.FlowBox () { - selection_mode = Gtk.SelectionMode.NONE, - column_spacing = 6, - max_children_per_line = 2 - }; - flowbox.get_style_context ().add_class ("search-bar"); - flowbox.add (search_flow_box_child); - flowbox.add (replace_flow_box_child); - - revealer = new Gtk.Revealer () { - child = flowbox, - reveal_child = false - }; - - add (revealer); - update_search_widgets (); + private Gtk.Button tool_arrow_up; + private Gtk.Button tool_arrow_down; + + /** + * Is the search cyclic? e.g., when you are at the bottom, if you press + * "Down", it will go at the start of the file to search for the content + * of the search entry. + **/ + private Granite.SwitchModelButton cycle_search_button ; + private Gtk.ComboBoxText case_sensitive_search_button; + private Granite.SwitchModelButton regex_search_button; + private Granite.SwitchModelButton whole_word_search_button; + private Gtk.SearchEntry search_entry; + private Gtk.SearchEntry replace_entry; + private Gtk.Label search_occurence_count_label; + private Gtk.Button replace_tool_button; + private Gtk.Button replace_all_tool_button; + private Scratch.Widgets.SourceView? text_view = null; + private Gtk.TextBuffer? text_buffer = null; + private Gtk.SourceSearchContext? search_context; + private uint update_search_label_timeout_id = 0; + private Gtk.Revealer revealer; + private Gtk.EventControllerKey key_controller; + + public SearchBar (MainWindow window) { + Object (window: window); + } + + construct { + this.orientation = HORIZONTAL; + search_entry = new Gtk.SearchEntry () { + hexpand = true, + placeholder_text = _("Find") + }; + + search_occurence_count_label = new Gtk.Label (_("No Results")); + search_occurence_count_label.get_style_context ().add_class (Granite.STYLE_CLASS_SMALL_LABEL); + + var app_instance = (Scratch.Application) GLib.Application.get_default (); + + tool_arrow_down = new Gtk.Button.from_icon_name ("go-down-symbolic", SMALL_TOOLBAR) { + sensitive = false, + tooltip_markup = Granite.markup_accel_tooltip ( + app_instance.get_accels_for_action ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_NEXT + ), + _("Search next") + ) + }; + tool_arrow_down.clicked.connect (search_next); + + tool_arrow_up = new Gtk.Button.from_icon_name ("go-up-symbolic", SMALL_TOOLBAR) { + sensitive = false, + tooltip_markup = Granite.markup_accel_tooltip ( + app_instance.get_accels_for_action ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_PREVIOUS + ), + _("Search previous") + ) + }; + tool_arrow_up.clicked.connect (search_previous); + + cycle_search_button = new Granite.SwitchModelButton (_("Cyclic Search")); + + case_sensitive_search_button = new Gtk.ComboBoxText (); + case_sensitive_search_button.append ("never", _("Never")); + case_sensitive_search_button.append ("mixed", _("Mixed Case")); + case_sensitive_search_button.append ("always", _("Always")); + case_sensitive_search_button.active = 1; + + var case_sensitive_search_label = new Gtk.Label (_("Case Sensitive")); + + var case_sensitive_box = new Gtk.Box (HORIZONTAL, 12); + case_sensitive_box.add (case_sensitive_search_label); + case_sensitive_box.add (case_sensitive_search_button); + case_sensitive_box.get_style_context ().add_class (Gtk.STYLE_CLASS_MENUITEM); + + regex_search_button = new Granite.SwitchModelButton (_("Use Regular Expressions")); + whole_word_search_button = new Granite.SwitchModelButton (_("Match Whole Words")); + + var search_option_box = new Gtk.Box (VERTICAL, 0) { + margin_top = 3, + margin_bottom = 3 + }; + search_option_box.add (cycle_search_button); + search_option_box.add (case_sensitive_box); + search_option_box.add (whole_word_search_button); + search_option_box.add (regex_search_button); + + var search_popover = new Gtk.Popover (null) { + child = search_option_box + }; + search_popover.show_all (); + + var search_buttonbox = new Gtk.Box (HORIZONTAL, 6); + search_buttonbox.add (search_occurence_count_label); + search_buttonbox.add (new Gtk.Image.from_icon_name ("pan-down-symbolic", SMALL_TOOLBAR)); + + var search_menubutton = new Gtk.MenuButton () { + popover = search_popover, + tooltip_text = _("Search Options") + }; + search_menubutton.add (search_buttonbox); + + cycle_search_button.toggled.connect (on_search_parameters_changed); + case_sensitive_search_button.changed.connect (on_search_parameters_changed); + whole_word_search_button.toggled.connect (on_search_parameters_changed); + regex_search_button.toggled.connect (on_search_parameters_changed); + + Scratch.settings.bind ("cyclic-search", cycle_search_button, "active", DEFAULT); + Scratch.settings.bind ("wholeword-search", whole_word_search_button, "active", DEFAULT); + Scratch.settings.bind ("case-sensitive-search", case_sensitive_search_button, "active-id", DEFAULT); + Scratch.settings.bind ("regex-search", regex_search_button, "active", 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 (HORIZONTAL, 0) { + margin_top = 3, + margin_end = 3, + margin_bottom = 3, + margin_start = 6 + }; + search_box.get_style_context ().add_class (Gtk.STYLE_CLASS_LINKED); + search_box.add (search_entry); + search_box.add (tool_arrow_down); + search_box.add (tool_arrow_up); + search_box.add (search_menubutton); + + var search_flow_box_child = new Gtk.FlowBoxChild (); + search_flow_box_child.can_focus = false; + search_flow_box_child.add (search_box); + + replace_entry = new Gtk.SearchEntry () { + hexpand = true, + placeholder_text = _("Replace With"), + primary_icon_name = "edit-symbolic" + }; + + replace_tool_button = new Gtk.Button.with_label (_("Replace")); + replace_tool_button.clicked.connect (on_replace_entry_activate); + + replace_all_tool_button = new Gtk.Button.with_label (_("Replace all")); + replace_all_tool_button.clicked.connect (on_replace_all_entry_activate); + + var replace_grid = new Gtk.Grid () { + margin_top = 3, + margin_end = 6, + margin_bottom = 3, + margin_start = 3 + }; + replace_grid.get_style_context ().add_class (Gtk.STYLE_CLASS_LINKED); + replace_grid.add (replace_entry); + replace_grid.add (replace_tool_button); + replace_grid.add (replace_all_tool_button); + + var replace_flow_box_child = new Gtk.FlowBoxChild (); + replace_flow_box_child.can_focus = false; + replace_flow_box_child.add (replace_grid); + + // Connecting to some signals + search_entry.changed.connect (on_search_parameters_changed); + search_entry.notify["is-focus"].connect (() => { + if (search_entry.is_focus && text_buffer != null) { + Idle.add (() => { + update_search_widgets (); + search_entry.select_region (0, -1); + return Source.REMOVE; + }); + } + }); + search_entry.icon_release.connect ((p0, p1) => { + if (p0 == Gtk.EntryIconPosition.PRIMARY) { + search_next (); + } + }); + replace_entry.activate.connect (on_replace_entry_activate); + + var flowbox = new Gtk.FlowBox () { + selection_mode = Gtk.SelectionMode.NONE, + column_spacing = 6, + max_children_per_line = 2 + }; + flowbox.get_style_context ().add_class ("search-bar"); + flowbox.add (search_flow_box_child); + flowbox.add (replace_flow_box_child); + + revealer = new Gtk.Revealer () { + child = flowbox, + reveal_child = false + }; + + add (revealer); + update_search_widgets (); + + key_controller = new Gtk.EventControllerKey (window) { + propagation_phase = CAPTURE + }; + key_controller.key_pressed.connect (on_key_pressed); + } - key_controller = new Gtk.EventControllerKey (window) { - propagation_phase = CAPTURE - }; - key_controller.key_pressed.connect (on_key_pressed); + public void set_text_view (Scratch.Widgets.SourceView? text_view) { + if (this.text_view == text_view) { + // Do not needlessly recreate SearchContext - may interfere with ongoing search + return; } - public void set_text_view (Scratch.Widgets.SourceView? text_view) { - if (this.text_view == text_view) { - // Do not needlessly recreate SearchContext - may interfere with ongoing search - return; - } + cancel_update_search_widgets (); + this.text_view = text_view; + if (text_view == null) { + warning ("No SourceView is associated with SearchManager!"); + search_context = null; + return; + } else if (this.text_buffer != null) { + this.text_buffer.changed.disconnect (on_text_buffer_changed); + } - cancel_update_search_widgets (); - this.text_view = text_view; - if (text_view == null) { - warning ("No SourceView is associated with SearchManager!"); - search_context = null; - return; - } else if (this.text_buffer != null) { - this.text_buffer.changed.disconnect (on_text_buffer_changed); - } + this.text_view = text_view; + this.text_buffer = text_view.get_buffer (); + this.text_buffer.changed.connect (on_text_buffer_changed); + this.search_context = new Gtk.SourceSearchContext (text_buffer as Gtk.SourceBuffer, null); + search_context.settings.wrap_around = cycle_search_button.active; + search_context.settings.regex_enabled = regex_search_button.active; + search_context.settings.search_text = search_entry.text; + on_text_buffer_changed (); + } - this.text_view = text_view; - this.text_buffer = text_view.get_buffer (); - this.text_buffer.changed.connect (on_text_buffer_changed); - this.search_context = new Gtk.SourceSearchContext (text_buffer as Gtk.SourceBuffer, null); - search_context.settings.wrap_around = cycle_search_button.active; - search_context.settings.regex_enabled = regex_search_button.active; - search_context.settings.search_text = search_entry.text; - on_text_buffer_changed (); + public bool search () { + search_entry.grab_focus (); + if (search_context == null) { + return false; } - public bool search () { - search_entry.grab_focus (); - if (search_context == null) { - return false; - } - - search_context.highlight = false; + search_context.highlight = false; - if (!has_matches ()) { - debug ("Can't search anything in a non-existent buffer and/or without anything to search."); - return false; - } + if (!has_matches ()) { + debug ("Can't search anything in a non-existent buffer and/or without anything to search."); + return false; + } - search_context.highlight = true; + search_context.highlight = true; - Gtk.TextIter? start_iter, end_iter; - text_buffer.get_iter_at_offset (out start_iter, text_buffer.cursor_position); + Gtk.TextIter? start_iter, end_iter; + text_buffer.get_iter_at_offset (out start_iter, text_buffer.cursor_position); + if (search_for_iter (start_iter, out end_iter)) { + search_entry.get_style_context ().remove_class (Gtk.STYLE_CLASS_ERROR); + search_entry.primary_icon_name = "edit-find-symbolic"; + } else { + text_buffer.get_start_iter (out start_iter); if (search_for_iter (start_iter, out end_iter)) { search_entry.get_style_context ().remove_class (Gtk.STYLE_CLASS_ERROR); search_entry.primary_icon_name = "edit-find-symbolic"; } else { - text_buffer.get_start_iter (out start_iter); - if (search_for_iter (start_iter, out end_iter)) { - search_entry.get_style_context ().remove_class (Gtk.STYLE_CLASS_ERROR); - search_entry.primary_icon_name = "edit-find-symbolic"; - } else { - debug ("Not found: \"%s\"", search_entry.text); - start_iter.set_offset (-1); - text_buffer.select_range (start_iter, start_iter); - search_entry.get_style_context ().add_class (Gtk.STYLE_CLASS_ERROR); - search_entry.primary_icon_name = "dialog-error-symbolic"; - return false; - } - } - - return true; - } - - public void highlight_none () { - if (search_context != null) { - search_context.highlight = false; + debug ("Not found: \"%s\"", search_entry.text); + start_iter.set_offset (-1); + text_buffer.select_range (start_iter, start_iter); + search_entry.get_style_context ().add_class (Gtk.STYLE_CLASS_ERROR); + search_entry.primary_icon_name = "dialog-error-symbolic"; + return false; } } - public void search_previous () { - /* Get selection range */ - Gtk.TextIter? start_iter, end_iter; - if (text_buffer != null) { - text_buffer.get_selection_bounds (out start_iter, out end_iter); - if (!search_for_iter_backward (start_iter, out end_iter) && cycle_search_button.active) { - text_buffer.get_end_iter (out start_iter); - search_for_iter_backward (start_iter, out end_iter); - } + return true; + } - update_search_widgets (); - } + public void highlight_none () { + if (search_context != null) { + search_context.highlight = false; } + } - public void search_next () { - /* Get selection range */ - Gtk.TextIter? start_iter, end_iter, end_iter_tmp; - if (text_buffer != null) { - text_buffer.get_selection_bounds (out start_iter, out end_iter); - if (!search_for_iter (end_iter, out end_iter_tmp) && cycle_search_button.active) { - text_buffer.get_start_iter (out start_iter); - search_for_iter (start_iter, out end_iter); - } - - update_search_widgets (); + public void search_previous () { + /* Get selection range */ + Gtk.TextIter? start_iter, end_iter; + if (text_buffer != null) { + text_buffer.get_selection_bounds (out start_iter, out end_iter); + if (!search_for_iter_backward (start_iter, out end_iter) && cycle_search_button.active) { + text_buffer.get_end_iter (out start_iter); + search_for_iter_backward (start_iter, out end_iter); } - } - - public void focus_search_entry () { - search_entry.grab_focus (); - } - public void focus_replace_entry () { - replace_entry.grab_focus (); + update_search_widgets (); } + } - public void reveal (bool to_reveal) { - revealer.reveal_child = to_reveal; - // Clear entry when searchbar is hidden - if (is_revealed && !to_reveal) { - set_search_entry_text (""); + public void search_next () { + /* Get selection range */ + Gtk.TextIter? start_iter, end_iter, end_iter_tmp; + if (text_buffer != null) { + text_buffer.get_selection_bounds (out start_iter, out end_iter); + if (!search_for_iter (end_iter, out end_iter_tmp) && cycle_search_button.active) { + text_buffer.get_start_iter (out start_iter); + search_for_iter (start_iter, out end_iter); } - } - public void set_search_entry_text (string text) { - search_entry.text = text; + update_search_widgets (); } + } - private bool on_key_pressed (uint keyval, uint keycode, Gdk.ModifierType state) { - if (!(search_is_focused || replace_is_focused)) { - return false; - } - /* We don't need to perform search if there is nothing to search... */ - if (!has_search_term) { - return false; - } + public void focus_search_entry () { + search_entry.grab_focus (); + } - string key = Gdk.keyval_name (keyval); - if (Gdk.ModifierType.SHIFT_MASK in state) { - key = "" + key; - } + public void focus_replace_entry () { + replace_entry.grab_focus (); + } - if (search_is_focused) { - switch (key) { - case "Return": - case "Up": - search_previous (); - return true; - case "Return": - case "Down": - search_next (); - return true; - case "Tab": - focus_replace_entry (); - return true; - } - } else { - switch (Gdk.keyval_name (keyval)) { - case "Up": - search_previous (); - return true; - case "Down": - search_next (); - return true; - case "Tab": - focus_search_entry (); - return true; - } + public void reveal (bool to_reveal) { + revealer.reveal_child = to_reveal; + // Clear entry when searchbar is hidden + if (is_revealed && !to_reveal) { + set_search_entry_text (""); + } + } - return false; - } + public void set_search_entry_text (string text) { + search_entry.text = text; + } + private bool on_key_pressed (uint keyval, uint keycode, Gdk.ModifierType state) { + if (!(search_is_focused || replace_is_focused)) { + return false; + } + /* We don't need to perform search if there is nothing to search... */ + if (!has_search_term) { return false; } - private void on_text_buffer_changed () { - update_search_widgets (); + string key = Gdk.keyval_name (keyval); + if (Gdk.ModifierType.SHIFT_MASK in state) { + key = "" + key; } - private void on_replace_entry_activate () { - if (text_buffer == null) { - warning ("No valid buffer to replace"); - return; + if (search_is_focused) { + switch (key) { + case "Return": + case "Up": + search_previous (); + return true; + case "Return": + case "Down": + search_next (); + return true; + case "Tab": + focus_replace_entry (); + return true; + } + } else { + switch (Gdk.keyval_name (keyval)) { + case "Up": + search_previous (); + return true; + case "Down": + search_next (); + return true; + case "Tab": + focus_search_entry (); + return true; } - Gtk.TextIter? start_iter, end_iter; - text_buffer.get_iter_at_offset (out start_iter, text_buffer.cursor_position); + return false; + } - if (search_for_iter (start_iter, out end_iter)) { - string replace_string = replace_entry.text; - try { - cancel_update_search_widgets (); - search_context.replace (start_iter, end_iter, replace_string, replace_string.length); - update_search_widgets (); - debug ("Replaced \"%s\" with \"%s\"", search_entry.text, replace_entry.text); - } catch (Error e) { - critical (e.message); - } - } + return false; + } + + private void on_text_buffer_changed () { + update_search_widgets (); + } + + private void on_replace_entry_activate () { + if (text_buffer == null) { + warning ("No valid buffer to replace"); + return; } - private void on_replace_all_entry_activate () { - if (text_buffer == null || this.window.get_current_document () == null) { - debug ("No valid buffer to replace"); - return; - } + Gtk.TextIter? start_iter, end_iter; + text_buffer.get_iter_at_offset (out start_iter, text_buffer.cursor_position); + if (search_for_iter (start_iter, out end_iter)) { string replace_string = replace_entry.text; - this.window.get_current_document ().toggle_changed_handlers (false); try { cancel_update_search_widgets (); - search_context.replace_all (replace_string, replace_string.length); + search_context.replace (start_iter, end_iter, replace_string, replace_string.length); update_search_widgets (); + debug ("Replaced \"%s\" with \"%s\"", search_entry.text, replace_entry.text); } catch (Error e) { critical (e.message); } + } + } - this.window.get_current_document ().toggle_changed_handlers (true); - } - - // Called when one of the settings buttons or the search term changes - private void on_search_parameters_changed () { - if (search_context != null) { - var search_string = search_entry.text; - search_context.settings.search_text = search_string; - var case_mode = (CaseSensitiveMode)(case_sensitive_search_button.active); - switch (case_mode) { - case CaseSensitiveMode.NEVER: - search_context.settings.case_sensitive = false; - break; - case CaseSensitiveMode.MIXED: - search_context.settings.case_sensitive = !((search_string.up () == search_string) || (search_string.down () == search_string)); - break; - case CaseSensitiveMode.ALWAYS: - search_context.settings.case_sensitive = true; - break; - default: - assert_not_reached (); - } - - search_context.settings.at_word_boundaries = whole_word_search_button.active; - search_context.settings.regex_enabled = regex_search_button.active; - } + private void on_replace_all_entry_activate () { + if (text_buffer == null || this.window.get_current_document () == null) { + debug ("No valid buffer to replace"); + return; + } + string replace_string = replace_entry.text; + this.window.get_current_document ().toggle_changed_handlers (false); + try { + cancel_update_search_widgets (); + search_context.replace_all (replace_string, replace_string.length); update_search_widgets (); + } catch (Error e) { + critical (e.message); } - private bool has_matches () { - if (text_buffer == null || search_entry.text == "") { - return false; - } + this.window.get_current_document ().toggle_changed_handlers (true); + } - bool has_wrapped_around; - Gtk.TextIter? start_iter, end_iter; - text_buffer.get_start_iter (out start_iter); - return search_context.forward (start_iter, out start_iter, out end_iter, out has_wrapped_around); + // Called when one of the settings buttons or the search term changes + private void on_search_parameters_changed () { + if (search_context != null) { + var search_string = search_entry.text; + search_context.settings.search_text = search_string; + var case_mode = (CaseSensitiveMode)(case_sensitive_search_button.active); + switch (case_mode) { + case CaseSensitiveMode.NEVER: + search_context.settings.case_sensitive = false; + break; + case CaseSensitiveMode.MIXED: + search_context.settings.case_sensitive = !((search_string.up () == search_string) || (search_string.down () == search_string)); + break; + case CaseSensitiveMode.ALWAYS: + search_context.settings.case_sensitive = true; + break; + default: + assert_not_reached (); + } + + search_context.settings.at_word_boundaries = whole_word_search_button.active; + search_context.settings.regex_enabled = regex_search_button.active; } - private bool search_for_iter (Gtk.TextIter? start_iter, out Gtk.TextIter? end_iter) { - end_iter = start_iter; + update_search_widgets (); + } - if (search_context == null) { - critical ("Trying to search forwards with no search context"); - return false; - } + private bool has_matches () { + if (text_buffer == null || search_entry.text == "") { + return false; + } - bool has_wrapped_around; - bool found = search_context.forward (start_iter, out start_iter, out end_iter, out has_wrapped_around); - if (found) { - text_buffer.select_range (start_iter, end_iter); - if (has_wrapped_around) { - start_iter.backward_lines (3); - } else { - start_iter.forward_lines (3); - } - text_view.scroll_to_iter (start_iter, 0, false, 0, 0); - } + bool has_wrapped_around; + Gtk.TextIter? start_iter, end_iter; + text_buffer.get_start_iter (out start_iter); + return search_context.forward (start_iter, out start_iter, out end_iter, out has_wrapped_around); + } - return found; - } + private bool search_for_iter (Gtk.TextIter? start_iter, out Gtk.TextIter? end_iter) { + end_iter = start_iter; - private bool search_for_iter_backward (Gtk.TextIter? start_iter, out Gtk.TextIter? end_iter) { - end_iter = start_iter; + if (search_context == null) { + critical ("Trying to search forwards with no search context"); + return false; + } - if (search_context == null) { - critical ("Trying to search backwards with no search context"); - return false; + bool has_wrapped_around; + bool found = search_context.forward (start_iter, out start_iter, out end_iter, out has_wrapped_around); + if (found) { + text_buffer.select_range (start_iter, end_iter); + if (has_wrapped_around) { + start_iter.backward_lines (3); + } else { + start_iter.forward_lines (3); } + text_view.scroll_to_iter (start_iter, 0, false, 0, 0); + } - bool has_wrapped_around; - bool found = search_context.backward (start_iter, out start_iter, out end_iter, out has_wrapped_around); - if (found) { - text_buffer.select_range (start_iter, end_iter); - if (has_wrapped_around) { - start_iter.forward_lines (3); - } else { - start_iter.backward_lines (3); - } - text_view.scroll_to_iter (start_iter, 0, false, 0, 0); - } - return found; + return found; + } + + private bool search_for_iter_backward (Gtk.TextIter? start_iter, out Gtk.TextIter? end_iter) { + end_iter = start_iter; + + if (search_context == null) { + critical ("Trying to search backwards with no search context"); + return false; } - private void cancel_update_search_widgets () { - if (update_search_label_timeout_id > 0) { - Source.remove (update_search_label_timeout_id); - update_search_label_timeout_id = 0; + bool has_wrapped_around; + bool found = search_context.backward (start_iter, out start_iter, out end_iter, out has_wrapped_around); + if (found) { + text_buffer.select_range (start_iter, end_iter); + if (has_wrapped_around) { + start_iter.forward_lines (3); + } else { + start_iter.backward_lines (3); } + text_view.scroll_to_iter (start_iter, 0, false, 0, 0); } + return found; + } - // Update search occurrence label, tool arrows and replace buttons in sync - private void update_search_widgets () { - cancel_update_search_widgets (); - update_search_label_timeout_id = Timeout.add (100, () => { - update_search_label_timeout_id = 0; - if (search_context == null) { - debug ("update occurrence with null context"); - replace_tool_button.sensitive = false; - replace_all_tool_button.sensitive = false; - tool_arrow_up.sensitive = false; - tool_arrow_down.sensitive = false; - return Source.REMOVE; - } + private void cancel_update_search_widgets () { + if (update_search_label_timeout_id > 0) { + Source.remove (update_search_label_timeout_id); + update_search_label_timeout_id = 0; + } + } + + // Update search occurrence label, tool arrows and replace buttons in sync + private void update_search_widgets () { + cancel_update_search_widgets (); + update_search_label_timeout_id = Timeout.add (100, () => { + update_search_label_timeout_id = 0; + if (search_context == null) { + debug ("update occurrence with null context"); + replace_tool_button.sensitive = false; + replace_all_tool_button.sensitive = false; + tool_arrow_up.sensitive = false; + tool_arrow_down.sensitive = false; + return Source.REMOVE; + } - Gtk.TextIter? iter, start_iter, end_iter; - text_buffer.get_iter_at_offset (out iter, text_buffer.cursor_position); + Gtk.TextIter? iter, start_iter, end_iter; + text_buffer.get_iter_at_offset (out iter, text_buffer.cursor_position); - int count_of_search = search_context.get_occurrences_count (); + int count_of_search = search_context.get_occurrences_count (); - int location_of_search = 0; - bool found = search_context.forward (iter, out start_iter, out end_iter, null); - if (count_of_search > 0 && found) { - location_of_search = search_context.get_occurrence_position (start_iter, end_iter); - } + int location_of_search = 0; + bool found = search_context.forward (iter, out start_iter, out end_iter, null); + if (count_of_search > 0 && found) { + location_of_search = search_context.get_occurrence_position (start_iter, end_iter); + } - if (count_of_search > -1) { - if (count_of_search > 0) { - search_occurence_count_label.label = _("%d of %d").printf ( - location_of_search, - count_of_search - ); - } else { - search_occurence_count_label.label = _("no results"); - } + if (count_of_search > -1) { + if (count_of_search > 0) { + search_occurence_count_label.label = _("%d of %d").printf ( + location_of_search, + count_of_search + ); + } else { + search_occurence_count_label.label = _("no results"); } + } - replace_tool_button.sensitive = location_of_search > 0; - replace_all_tool_button.sensitive = count_of_search > 0; + replace_tool_button.sensitive = location_of_search > 0; + replace_all_tool_button.sensitive = count_of_search > 0; - // Update tool arrows - if (text_buffer == null || - search_entry.text == "" || - count_of_search == 0) { + // Update tool arrows + if (text_buffer == null || + search_entry.text == "" || + count_of_search == 0) { - tool_arrow_up.sensitive = false; - tool_arrow_down.sensitive = false; + tool_arrow_up.sensitive = false; + tool_arrow_down.sensitive = false; + } else { + if (cycle_search_button.active) { + tool_arrow_down.sensitive = true; + tool_arrow_up.sensitive =true; } else { - if (cycle_search_button.active) { - tool_arrow_down.sensitive = true; - tool_arrow_up.sensitive =true; - } else { - Gtk.TextIter? tmp_start_iter, tmp_end_iter; + Gtk.TextIter? tmp_start_iter, tmp_end_iter; - bool is_in_start, is_in_end; + bool is_in_start, is_in_end; - text_buffer.get_start_iter (out tmp_start_iter); - text_buffer.get_end_iter (out tmp_end_iter); + text_buffer.get_start_iter (out tmp_start_iter); + text_buffer.get_end_iter (out tmp_end_iter); - text_buffer.get_selection_bounds (out start_iter, out end_iter); + text_buffer.get_selection_bounds (out start_iter, out end_iter); - is_in_start = start_iter.compare (tmp_start_iter) == 0; - is_in_end = end_iter.compare (tmp_end_iter) == 0; + is_in_start = start_iter.compare (tmp_start_iter) == 0; + is_in_end = end_iter.compare (tmp_end_iter) == 0; - if (!is_in_end) { - tool_arrow_down.sensitive = search_context.forward ( - end_iter, out tmp_start_iter, out tmp_end_iter, null - ); - } else { - tool_arrow_down.sensitive = false; - } + if (!is_in_end) { + tool_arrow_down.sensitive = search_context.forward ( + end_iter, out tmp_start_iter, out tmp_end_iter, null + ); + } else { + tool_arrow_down.sensitive = false; + } - if (!is_in_start) { - tool_arrow_up.sensitive = search_context.backward ( - start_iter, out tmp_start_iter, out end_iter, null - ); - } else { - tool_arrow_up.sensitive = false; - } + if (!is_in_start) { + tool_arrow_up.sensitive = search_context.backward ( + start_iter, out tmp_start_iter, out end_iter, null + ); + } else { + tool_arrow_up.sensitive = false; } } + } - // Update appearance of search entry - var ctx = search_entry.get_style_context (); + // Update appearance of search entry + var ctx = search_entry.get_style_context (); - if (search_entry.text != "" && count_of_search == 0) { - ctx.add_class (Gtk.STYLE_CLASS_ERROR); - search_entry.primary_icon_name = "dialog-error-symbolic"; - } else if (ctx.has_class (Gtk.STYLE_CLASS_ERROR)) { - ctx.remove_class (Gtk.STYLE_CLASS_ERROR); - search_entry.primary_icon_name = "edit-find-symbolic"; - } + if (search_entry.text != "" && count_of_search == 0) { + ctx.add_class (Gtk.STYLE_CLASS_ERROR); + search_entry.primary_icon_name = "dialog-error-symbolic"; + } else if (ctx.has_class (Gtk.STYLE_CLASS_ERROR)) { + ctx.remove_class (Gtk.STYLE_CLASS_ERROR); + search_entry.primary_icon_name = "edit-find-symbolic"; + } - return Source.REMOVE; - }); + return Source.REMOVE; + }); - } } } From eb035aa9ae56e7ede1c2641faa311b18c5d8e942 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 12 Aug 2026 12:12:09 +0100 Subject: [PATCH 07/10] Lose unnecessary namespace "Scratch" --- src/Widgets/SearchBar.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index 51fa0bd419..0daaa71863 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -113,7 +113,7 @@ public class Scratch.Widgets.SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayou sensitive = false, tooltip_markup = Granite.markup_accel_tooltip ( app_instance.get_accels_for_action ( - Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_NEXT + MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_NEXT ), _("Search next") ) @@ -124,7 +124,7 @@ public class Scratch.Widgets.SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayou sensitive = false, tooltip_markup = Granite.markup_accel_tooltip ( app_instance.get_accels_for_action ( - Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_PREVIOUS + MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_PREVIOUS ), _("Search previous") ) From 9cb98d975e0ad9a74a03c67461b3ee86928746b9 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 12 Aug 2026 12:13:03 +0100 Subject: [PATCH 08/10] Simplify search () function; update widgets in one place only --- src/Widgets/SearchBar.vala | 52 ++------------------------------------ 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index 0daaa71863..bc60cb3cc0 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -292,6 +292,7 @@ public class Scratch.Widgets.SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayou this.text_buffer = text_view.get_buffer (); this.text_buffer.changed.connect (on_text_buffer_changed); this.search_context = new Gtk.SourceSearchContext (text_buffer as Gtk.SourceBuffer, null); + search_context.highlight = true; // There are no circumstances where we do not highlight search_context.settings.wrap_around = cycle_search_button.active; search_context.settings.regex_enabled = regex_search_button.active; search_context.settings.search_text = search_entry.text; @@ -300,49 +301,11 @@ public class Scratch.Widgets.SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayou public bool search () { search_entry.grab_focus (); - if (search_context == null) { - return false; - } - - search_context.highlight = false; - - if (!has_matches ()) { - debug ("Can't search anything in a non-existent buffer and/or without anything to search."); - return false; - } - - search_context.highlight = true; - - Gtk.TextIter? start_iter, end_iter; - text_buffer.get_iter_at_offset (out start_iter, text_buffer.cursor_position); - - if (search_for_iter (start_iter, out end_iter)) { - search_entry.get_style_context ().remove_class (Gtk.STYLE_CLASS_ERROR); - search_entry.primary_icon_name = "edit-find-symbolic"; - } else { - text_buffer.get_start_iter (out start_iter); - if (search_for_iter (start_iter, out end_iter)) { - search_entry.get_style_context ().remove_class (Gtk.STYLE_CLASS_ERROR); - search_entry.primary_icon_name = "edit-find-symbolic"; - } else { - debug ("Not found: \"%s\"", search_entry.text); - start_iter.set_offset (-1); - text_buffer.select_range (start_iter, start_iter); - search_entry.get_style_context ().add_class (Gtk.STYLE_CLASS_ERROR); - search_entry.primary_icon_name = "dialog-error-symbolic"; - return false; - } - } + update_search_widgets (); return true; } - public void highlight_none () { - if (search_context != null) { - search_context.highlight = false; - } - } - public void search_previous () { /* Get selection range */ Gtk.TextIter? start_iter, end_iter; @@ -510,17 +473,6 @@ public class Scratch.Widgets.SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayou update_search_widgets (); } - private bool has_matches () { - if (text_buffer == null || search_entry.text == "") { - return false; - } - - bool has_wrapped_around; - Gtk.TextIter? start_iter, end_iter; - text_buffer.get_start_iter (out start_iter); - return search_context.forward (start_iter, out start_iter, out end_iter, out has_wrapped_around); - } - private bool search_for_iter (Gtk.TextIter? start_iter, out Gtk.TextIter? end_iter) { end_iter = start_iter; From 8221f3ad8b096985c53732d5bd8a3aa0f53a823a Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 12 Aug 2026 12:17:49 +0100 Subject: [PATCH 09/10] Shorten some lines --- src/Widgets/SearchBar.vala | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index bc60cb3cc0..e23686e605 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -178,15 +178,25 @@ public class Scratch.Widgets.SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayou whole_word_search_button.toggled.connect (on_search_parameters_changed); regex_search_button.toggled.connect (on_search_parameters_changed); - Scratch.settings.bind ("cyclic-search", cycle_search_button, "active", DEFAULT); - Scratch.settings.bind ("wholeword-search", whole_word_search_button, "active", DEFAULT); - Scratch.settings.bind ("case-sensitive-search", case_sensitive_search_button, "active-id", DEFAULT); - Scratch.settings.bind ("regex-search", regex_search_button, "active", DEFAULT); + // Bind some application settings + settings.bind ("cyclic-search", cycle_search_button, "active", DEFAULT); + settings.bind ("wholeword-search", whole_word_search_button, "active", DEFAULT); + settings.bind ("case-sensitive-search", case_sensitive_search_button, "active-id", DEFAULT); + settings.bind ("regex-search", regex_search_button, "active", 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); + 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 (HORIZONTAL, 0) { margin_top = 3, From 893d6e55f52319402a2b75cf68b110dc9d18c53f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 12 Aug 2026 12:23:25 +0100 Subject: [PATCH 10/10] Shorten another line --- src/Widgets/SearchBar.vala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index e23686e605..da381ccedc 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -467,7 +467,10 @@ public class Scratch.Widgets.SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayou search_context.settings.case_sensitive = false; break; case CaseSensitiveMode.MIXED: - search_context.settings.case_sensitive = !((search_string.up () == search_string) || (search_string.down () == search_string)); + var found = ((search_string.up () == search_string) || + (search_string.down () == search_string) + ); + search_context.settings.case_sensitive = !found; break; case CaseSensitiveMode.ALWAYS: search_context.settings.case_sensitive = true;