From e7d5427c12f1fcf8c3f4bb4df9e1d46405e3f89c Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 24 Nov 2025 10:59:07 +0000 Subject: [PATCH 1/6] Implement find matching bracket --- src/MainWindow.vala | 11 +++++ src/Widgets/SourceView.vala | 98 +++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/src/MainWindow.vala b/src/MainWindow.vala index bcf37aeb6..a9ad236dc 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -80,6 +80,7 @@ namespace Scratch { public const string ACTION_COLLAPSE_ALL_FOLDERS = "action-collapse-all-folders"; public const string ACTION_ORDER_FOLDERS = "action-order-folders"; public const string ACTION_GO_TO = "action-go-to"; + public const string ACTION_GO_TO_MATCHING = "action-go-to-matching"; public const string ACTION_SORT_LINES = "action-sort-lines"; public const string ACTION_NEW_TAB = "action-new-tab"; public const string ACTION_NEW_FROM_CLIPBOARD = "action-new-from-clipboard"; @@ -149,6 +150,7 @@ namespace Scratch { { ACTION_TOGGLE_SHOW_FIND, action_toggle_show_find, null, "false" }, { ACTION_TEMPLATES, action_templates }, { ACTION_GO_TO, action_go_to }, + { ACTION_GO_TO_MATCHING, action_go_to_matching }, { ACTION_SORT_LINES, action_sort_lines }, { ACTION_NEW_TAB, action_new_tab }, { ACTION_NEW_FROM_CLIPBOARD, action_new_tab_from_clipboard }, @@ -1371,6 +1373,15 @@ namespace Scratch { toolbar.format_bar.line_menubutton.active = true; } + private void action_go_to_matching () { + var doc = document_view.current_document; + if (doc == null) { + return; + } else { + doc.source_view.goto_matching (); + } + } + private void action_templates () { plugins.plugin_iface.template_manager.show_window (this); } diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index d85e048e2..fceccd145 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -610,6 +610,13 @@ namespace Scratch.Widgets { } menu.add (comment_item); + + if (true) {//TODO Check preceding char is bracket + var match_item = new Gtk.MenuItem.with_label (_("Goto Matching Bracket")) { + action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_GO_TO_MATCHING + }; + menu.add (match_item); + } } menu.show_all (); @@ -704,5 +711,96 @@ namespace Scratch.Widgets { return Source.REMOVE; }); } + + private const string OPEN_BRACKETS = "{(["; + private const string CLOSE_BRACKETS = "})]"; + private bool is_open_bracket (unichar c, out unichar matching) { + var index = OPEN_BRACKETS.index_of_char (c); + if (index >= 0) { + matching = CLOSE_BRACKETS[index]; + return true; + } + + return false; + } + + private bool is_close_bracket (unichar c, out unichar matching) { + var index = CLOSE_BRACKETS.index_of_char (c); + if (index >= 0) { + matching = OPEN_BRACKETS[index]; + return true; + } + + return false; + } + + private uint get_indent (Gtk.TextIter t) { + var indent_iter = t.copy (); + if (indent_iter.backward_line ()) { + indent_iter.forward_line (); + }; + uint spaces = 0; + while (indent_iter.forward_char () && indent_iter.get_char ().isspace ()) { + spaces++; + } + + return spaces; + } + + public void goto_matching () { + uint start_indent = 0, end_indent = 0; + var insert_mark = buffer.get_mark ("insert"); + Gtk.TextIter insert_iter; + buffer.get_iter_at_mark (out insert_iter, insert_mark); + insert_iter.backward_char (); + var insert_char = insert_iter.get_char (); + var end = insert_iter.copy (); + unichar matching; + if (is_open_bracket (insert_char, out matching)) { + start_indent = get_indent (insert_iter); + uint opening = 0; + unichar c; + while (end.forward_char ()) { + c = end.get_char (); + if (is_open_bracket (c, out matching)) { + opening++; + } else if (is_close_bracket (c, out matching)) { + if (opening > 0) { + opening--; + } else { + end_indent = get_indent (end); + end.forward_char (); + buffer.place_cursor (end); + break; + } + } + } + } else if (is_close_bracket (insert_char, out matching)) { + start_indent = get_indent (insert_iter); + uint closing = 0; + unichar c; + while (end.backward_char ()) { + c = end.get_char (); + if (is_close_bracket (c, out matching)) { + closing++; + } else if (is_open_bracket (c, out matching)) { + if (closing > 0) { + closing--; + } else { + end_indent = get_indent (end); + end.forward_char (); + buffer.place_cursor (end); + break; + } + } + } + } else { + warning ("not found"); + } + + if (start_indent != end_indent) { + critical ("Mismatched bracket"); + } + } } } From 699ab1dba20b211025edde39e0857e2ab40f4e0f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 24 Nov 2025 11:14:53 +0000 Subject: [PATCH 2/6] Show warning dialog on mismatch --- src/Widgets/SourceView.vala | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index fceccd145..19172e68b 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -748,7 +748,8 @@ namespace Scratch.Widgets { } public void goto_matching () { - uint start_indent = 0, end_indent = 0; + uint start_indent = 0, end_indent = 0, same = 0; + bool found = false; var insert_mark = buffer.get_mark ("insert"); Gtk.TextIter insert_iter; buffer.get_iter_at_mark (out insert_iter, insert_mark); @@ -763,14 +764,15 @@ namespace Scratch.Widgets { while (end.forward_char ()) { c = end.get_char (); if (is_open_bracket (c, out matching)) { - opening++; + same++; } else if (is_close_bracket (c, out matching)) { - if (opening > 0) { - opening--; + if (same > 0) { + same--; } else { end_indent = get_indent (end); end.forward_char (); buffer.place_cursor (end); + found = true; break; } } @@ -782,24 +784,35 @@ namespace Scratch.Widgets { while (end.backward_char ()) { c = end.get_char (); if (is_close_bracket (c, out matching)) { - closing++; + same++; } else if (is_open_bracket (c, out matching)) { - if (closing > 0) { - closing--; + if (same > 0) { + same--; } else { end_indent = get_indent (end); end.forward_char (); buffer.place_cursor (end); + found = true; break; } } } } else { - warning ("not found"); + warning ("not bracket"); + return; } - if (start_indent != end_indent) { - critical ("Mismatched bracket"); + if (start_indent != end_indent || !found) { + var parent_window = get_toplevel () as Gtk.Window; + var dialog = new Granite.MessageDialog ( + found ? _("Matching bracket has incorrect indent") : _("No matching bracket found"), + _("You may have omitted a required bracket"), + new ThemedIcon ("dialog-warning"), + Gtk.ButtonsType.CLOSE + ); + dialog.transient_for = parent_window; + dialog.response.connect (dialog.destroy); + dialog.present (); } } } From 37a3660c420f600790aee89b9517e3d572c73d69 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 24 Nov 2025 12:17:21 +0000 Subject: [PATCH 3/6] Some refinements --- src/Widgets/SourceView.vala | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index 19172e68b..975702097 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -734,7 +734,7 @@ namespace Scratch.Widgets { return false; } - private uint get_indent (Gtk.TextIter t) { + private uint get_indent_spaces (Gtk.TextIter t) { var indent_iter = t.copy (); if (indent_iter.backward_line ()) { indent_iter.forward_line (); @@ -749,36 +749,43 @@ namespace Scratch.Widgets { public void goto_matching () { uint start_indent = 0, end_indent = 0, same = 0; + int start_line = -1, end_line = -1; bool found = false; var insert_mark = buffer.get_mark ("insert"); Gtk.TextIter insert_iter; buffer.get_iter_at_mark (out insert_iter, insert_mark); + start_line = insert_iter.get_line () + 1; insert_iter.backward_char (); var insert_char = insert_iter.get_char (); var end = insert_iter.copy (); unichar matching; if (is_open_bracket (insert_char, out matching)) { - start_indent = get_indent (insert_iter); + start_indent = get_indent_spaces (insert_iter); uint opening = 0; unichar c; while (end.forward_char ()) { c = end.get_char (); + end_line = buffer.get_line_count (); if (is_open_bracket (c, out matching)) { same++; } else if (is_close_bracket (c, out matching)) { if (same > 0) { same--; } else { - end_indent = get_indent (end); + end_indent = get_indent_spaces (end); + end_line = end.get_line () + 1; + warning ("end line %i", end_line); end.forward_char (); buffer.place_cursor (end); + scroll_to_iter (end, 0.1, false, 0.0, 0.0); found = true; break; } } } } else if (is_close_bracket (insert_char, out matching)) { - start_indent = get_indent (insert_iter); + start_indent = get_indent_spaces (insert_iter); + end_line = 0; uint closing = 0; unichar c; while (end.backward_char ()) { @@ -789,9 +796,11 @@ namespace Scratch.Widgets { if (same > 0) { same--; } else { - end_indent = get_indent (end); + end_indent = get_indent_spaces (end); + end_line = end.get_line () + 1; end.forward_char (); buffer.place_cursor (end); + scroll_to_iter (end, 0.1, false, 0.0, 0.0); found = true; break; } @@ -803,10 +812,12 @@ namespace Scratch.Widgets { } if (start_indent != end_indent || !found) { + var min_line = int.min (start_line, end_line); + var max_line = int.max (start_line, end_line); var parent_window = get_toplevel () as Gtk.Window; var dialog = new Granite.MessageDialog ( found ? _("Matching bracket has incorrect indent") : _("No matching bracket found"), - _("You may have omitted a required bracket"), + _("You may have omitted a required bracket or inserted an extra bracket between lines %i and %i").printf (min_line, max_line), new ThemedIcon ("dialog-warning"), Gtk.ButtonsType.CLOSE ); From 5e51725be8bcf2bf29296557292df3704ec6af24 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 24 Nov 2025 13:33:42 +0000 Subject: [PATCH 4/6] Skip irrelevant lines --- src/Services/CommentToggler.vala | 21 +++++++++++++++- src/Widgets/SourceView.vala | 41 +++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/Services/CommentToggler.vala b/src/Services/CommentToggler.vala index 56e721f44..cbfca86f7 100644 --- a/src/Services/CommentToggler.vala +++ b/src/Services/CommentToggler.vala @@ -21,7 +21,8 @@ public class Scratch.CommentToggler { private enum CommentType { NONE, LINE, - BLOCK + BLOCK, + EMPTY } private static CommentType get_comment_tags_for_lang (Gtk.SourceLanguage lang, @@ -73,6 +74,20 @@ public class Scratch.CommentToggler { return type != CommentType.NONE; } + public static bool line_is_commented_or_empty ( + Gtk.SourceBuffer buffer, + int line_index, + Gtk.SourceLanguage lang) { + + bool commented = false; + Gtk.TextIter start_iter, end_iter; + buffer.get_iter_at_line_index (out start_iter, line_index, 0); + buffer.get_iter_at_line_index (out end_iter, line_index, int.MAX); //Returns end of line iter + return lines_already_commented ( + buffer, start_iter, end_iter, 1, lang + ) != CommentType.NONE; + } + // Returns whether or not all lines within a region are already commented. // This is to detect whether to toggle comments on or off. If all lines are commented, then we want to remove // those comments. If only some are commented, then the user likely selected a chunk of code that already contained @@ -86,6 +101,10 @@ public class Scratch.CommentToggler { string start_tag, end_tag; var type = get_comment_tags_for_lang (lang, CommentType.BLOCK, out start_tag, out end_tag); var selection = buffer.get_slice (start, end, true); + if (selection.length == 0) { + return CommentType.EMPTY; + } + if (type == CommentType.BLOCK) { var regex_string = """^\s*(?:%s)+[\s\S]*(?:%s)+$"""; regex_string = regex_string.printf (Regex.escape_string (start_tag), Regex.escape_string (end_tag)); diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index 975702097..ffe2a1c13 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -747,25 +747,46 @@ namespace Scratch.Widgets { return spaces; } + // Return index of next uncommented, not empty line + private bool skip_commented_lines (out int new_index, int start_index, int step = 1) { + new_index = start_index; + while (CommentToggler.line_is_commented_or_empty ( + (Gtk.SourceBuffer)buffer, new_index, language + )) { + new_index += step; + } +} + return new_index != start_index; + } + public void goto_matching () { uint start_indent = 0, end_indent = 0, same = 0; - int start_line = -1, end_line = -1; + int start_line = -1, end_line = -1, new_line = -1; + unichar c; bool found = false; var insert_mark = buffer.get_mark ("insert"); - Gtk.TextIter insert_iter; + Gtk.TextIter insert_iter, start_iter, end_iter; buffer.get_iter_at_mark (out insert_iter, insert_mark); - start_line = insert_iter.get_line () + 1; + start_line = insert_iter.get_line (); + if (skip_commented_lines (out new_line, start_line)) { + return; + } + + start_line++; // Change from index to visible number insert_iter.backward_char (); var insert_char = insert_iter.get_char (); var end = insert_iter.copy (); unichar matching; if (is_open_bracket (insert_char, out matching)) { start_indent = get_indent_spaces (insert_iter); - uint opening = 0; - unichar c; + end_line = buffer.get_line_count (); while (end.forward_char ()) { + if (end.starts_line () && skip_commented_lines (out new_line, end.get_line ())) { + end.set_line (new_line); + continue; + } c = end.get_char (); - end_line = buffer.get_line_count (); + if (is_open_bracket (c, out matching)) { same++; } else if (is_close_bracket (c, out matching)) { @@ -786,9 +807,12 @@ namespace Scratch.Widgets { } else if (is_close_bracket (insert_char, out matching)) { start_indent = get_indent_spaces (insert_iter); end_line = 0; - uint closing = 0; - unichar c; while (end.backward_char ()) { + if (end.ends_line () && skip_commented_lines (out new_line, end.get_line (), -1)) { + end.set_line (new_line); + end.forward_to_line_end (); + continue; + } c = end.get_char (); if (is_close_bracket (c, out matching)) { same++; @@ -807,7 +831,6 @@ namespace Scratch.Widgets { } } } else { - warning ("not bracket"); return; } From 2f7d004f1f0555c3a0ba2a41613b7b6556f62311 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 24 Nov 2025 14:23:14 +0000 Subject: [PATCH 5/6] Add accel; --- src/MainWindow.vala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MainWindow.vala b/src/MainWindow.vala index a9ad236dc..4a244c2ea 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -216,6 +216,7 @@ namespace Scratch { action_accelerators.set (ACTION_SAVE, "s"); action_accelerators.set (ACTION_SAVE_AS, "s"); action_accelerators.set (ACTION_GO_TO, "i"); + action_accelerators.set (ACTION_GO_TO_MATCHING, "i"); action_accelerators.set (ACTION_SORT_LINES, "F5"); action_accelerators.set (ACTION_NEW_TAB, "n"); action_accelerators.set (ACTION_DUPLICATE_TAB, "k" ); From 0e625dedbc3033c4928b1c3dd5b739ee383a5037 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 24 Nov 2025 14:23:28 +0000 Subject: [PATCH 6/6] Remove typo --- src/Widgets/SourceView.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index ffe2a1c13..7234c7f16 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -755,7 +755,7 @@ namespace Scratch.Widgets { )) { new_index += step; } -} + return new_index != start_index; }