Skip to content
12 changes: 12 additions & 0 deletions src/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ namespace Scratch {
public const string ACTION_OPEN_PROJECT = "action-open-project";
public const string ACTION_COLLAPSE_ALL_FOLDERS = "action-collapse-all-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";
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -216,6 +218,7 @@ namespace Scratch {
action_accelerators.set (ACTION_SAVE, "<Control>s");
action_accelerators.set (ACTION_SAVE_AS, "<Control><shift>s");
action_accelerators.set (ACTION_GO_TO, "<Control>i");
action_accelerators.set (ACTION_GO_TO_MATCHING, "<Control><Shift>i");
action_accelerators.set (ACTION_SORT_LINES, "F5");
action_accelerators.set (ACTION_NEW_TAB, "<Control>n");
action_accelerators.set (ACTION_DUPLICATE_TAB, "<Control><Shift>k" );
Expand Down Expand Up @@ -1365,6 +1368,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);
}
Expand Down
21 changes: 20 additions & 1 deletion src/Services/CommentToggler.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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));
Expand Down
145 changes: 145 additions & 0 deletions src/Widgets/SourceView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,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 ();
Expand Down Expand Up @@ -717,5 +724,143 @@ 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_spaces (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;
}

// 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, new_line = -1;
unichar c;
bool found = false;
var insert_mark = buffer.get_mark ("insert");
Gtk.TextIter insert_iter, start_iter, end_iter;
buffer.get_iter_at_mark (out insert_iter, insert_mark);
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);
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 ();

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_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_spaces (insert_iter);
end_line = 0;
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++;
} else if (is_open_bracket (c, out matching)) {
if (same > 0) {
same--;
} else {
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;
}
}
}
} else {
return;
}

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 or inserted an extra bracket between lines %i and %i").printf (min_line, max_line),
new ThemedIcon ("dialog-warning"),
Gtk.ButtonsType.CLOSE
);
dialog.transient_for = parent_window;
dialog.response.connect (dialog.destroy);
dialog.present ();
}
}
}
}