Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command-line "Go-To-Line" option #1382

Merged
merged 15 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions src/Application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ namespace Scratch {
private static string _data_home_folder_unsaved;
private static bool create_new_tab = false;
private static bool create_new_window = false;
private LocationJumpManager location_jump_manager;

const OptionEntry[] ENTRIES = {
{ "new-tab", 't', 0, OptionArg.NONE, null, N_("New Tab"), null },
{ "new-window", 'n', 0, OptionArg.NONE, null, N_("New Window"), null },
{ "version", 'v', 0, OptionArg.NONE, null, N_("Print version info and exit"), null },
{ "go-to", 'g', 0, OptionArg.STRING, null, "Open file at specified selection range", "<start_line[.start_column][-end_line[.end_column]]>" },
{ GLib.OPTION_REMAINING, 0, 0, OptionArg.FILENAME_ARRAY, null, null, N_("[FILE…]") },
{ null }
};
Expand All @@ -46,6 +48,7 @@ namespace Scratch {
_data_home_folder_unsaved = Path.build_filename (
Environment.get_user_data_dir (), Constants.PROJECT_NAME, "unsaved"
);

}

construct {
Expand All @@ -66,6 +69,7 @@ namespace Scratch {
service_settings = new GLib.Settings (Constants.PROJECT_NAME + ".services");
privacy_settings = new GLib.Settings ("org.gnome.desktop.privacy");

location_jump_manager = new LocationJumpManager ();
Environment.set_variable ("GTK_USE_PORTAL", "1", true);

GLib.Intl.setlocale (LocaleCategory.ALL, "");
Expand Down Expand Up @@ -94,6 +98,7 @@ namespace Scratch {
};

var options = command_line.get_options_dict ();
location_jump_manager.clear ();

if (options.contains ("new-tab")) {
create_new_tab = true;
Expand All @@ -103,6 +108,25 @@ namespace Scratch {
create_new_window = true;
}

if (options.contains ("go-to")) {
var go_to_string_variant = options.lookup_value ("go-to", GLib.VariantType.STRING);
string selection_range_string = (string) go_to_string_variant.get_string ();
location_jump_manager.parse_selection_range_string (selection_range_string);
debug ("go-to arg value: %s", selection_range_string);
}

if (location_jump_manager.has_selection_range () && options.contains (GLib.OPTION_REMAINING)) {
(unowned string)[] file_list = options.lookup_value (
GLib.OPTION_REMAINING,
VariantType.BYTESTRING_ARRAY
).get_bytestring_array ();

if (file_list.length == 1) {
unowned string selection_range_file_path = file_list[0];
location_jump_manager.file = command_line.create_file_for_arg (selection_range_file_path);
}
}

activate ();

if (options.contains (GLib.OPTION_REMAINING)) {
Expand All @@ -126,7 +150,12 @@ namespace Scratch {

protected override void activate () {
if (active_window == null) {
add_window (new MainWindow (true)); // Will restore documents if required
if (location_jump_manager.has_selection_range () && location_jump_manager.has_override_target ()) {
RestoreOverride restore_override = location_jump_manager.create_restore_override ();
add_window (new MainWindow.with_restore_override (true, restore_override));
} else {
add_window (new MainWindow (true)); // Will restore documents if required
}
} else if (create_new_window) {
create_new_window = false;
add_window (new MainWindow (false)); // Will NOT restore documents in additional windows
Expand All @@ -143,15 +172,19 @@ namespace Scratch {

protected override void open (File[] files, string hint) {
var window = get_last_window ();

foreach (var file in files) {
bool is_folder;
if (Scratch.Services.FileHandler.can_open_file (file, out is_folder)) {
if (is_folder) {
window.open_folder (file);
} else {
debug ("Files length: %d\n", files.length);
var doc = new Scratch.Services.Document (window.actions, file);
window.open_document (doc);
if (location_jump_manager.has_selection_range != null && files.length == 1) {
window.open_document_at_selected_range (doc, true, location_jump_manager.range);
} else {
window.open_document (doc);
}
}
}
}
Expand Down
31 changes: 30 additions & 1 deletion src/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace Scratch {

public Scratch.Application app { get; private set; }
public bool restore_docs { get; construct; }
public RestoreOverride restore_override { get; construct set; }

public Scratch.Widgets.DocumentView document_view;

Expand Down Expand Up @@ -158,6 +159,14 @@ namespace Scratch {
);
}

public MainWindow.with_restore_override (bool restore_docs, RestoreOverride restore_override) {
Object (
icon_name: Constants.PROJECT_NAME,
restore_docs: restore_docs,
restore_override: restore_override
);
}

static construct {
action_accelerators.set (ACTION_FIND + "::", "<Control>f");
action_accelerators.set (ACTION_FIND_NEXT, "<Control>g");
Expand Down Expand Up @@ -594,6 +603,7 @@ namespace Scratch {
string focused_document = settings.get_string ("focused-document");
string uri;
int pos;
bool was_restore_overriden = false;
while (doc_info_iter.next ("(si)", out uri, out pos)) {
if (uri != "") {
GLib.File file;
Expand All @@ -610,7 +620,12 @@ namespace Scratch {
var doc = new Scratch.Services.Document (actions, file);
bool is_focused = file.get_uri () == focused_document;
if (doc.exists () || !doc.is_file_temporary) {
open_document (doc, is_focused, pos);
if (restore_override != null && (file.get_path () == restore_override.file.get_path ())) {
open_document_at_selected_range (doc, true, restore_override.range, true);
was_restore_overriden = true;
} else {
open_document (doc, was_restore_overriden ? false : is_focused, pos);
}
}

if (is_focused) { //Maybe expand to show all opened documents?
Expand All @@ -623,6 +638,7 @@ namespace Scratch {

Idle.add (() => {
document_view.request_placeholder_if_empty ();
restore_override = null;
return Source.REMOVE;
});
}
Expand Down Expand Up @@ -686,6 +702,19 @@ namespace Scratch {
document_view.open_document (doc, focus, cursor_position);
}

public void open_document_at_selected_range (Scratch.Services.Document doc,
bool focus = true,
SelectionRange range = SelectionRange.EMPTY,
bool is_override = false) {
if (restore_override != null && is_override == false) {
return;
}

FolderManager.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file);
doc.source_view.project = project;
document_view.open_document (doc, focus, 0, range);
}

// Close a document
public void close_document (Scratch.Services.Document doc) {
document_view.close_document (doc);
Expand Down
93 changes: 93 additions & 0 deletions src/Services/LocationJumpManager.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2023 elementary, Inc. <https://elementary.io>
*
* Authored by: Colin Kiama <[email protected]>
*/

namespace Scratch {
jeremypw marked this conversation as resolved.
Show resolved Hide resolved
public class LocationJumpManager : GLib.Object {
public GLib.File file { get; set; }
public SelectionRange range { get; set; }

public bool has_override_target () {
if (file == null) {
return false;
}

bool is_override_target = false;

if (privacy_settings.get_boolean ("remember-recent-files")) {
var doc_infos = settings.get_value ("opened-files");
var doc_info_iter = new VariantIter (doc_infos);

string uri;
int pos;
while (doc_info_iter.next ("(si)", out uri, out pos)) {
if (uri != "") {
GLib.File file_to_restore;
if (Uri.parse_scheme (uri) != null) {
file_to_restore = File.new_for_uri (uri);
} else {
file_to_restore = File.new_for_commandline_arg (uri);
}

if (file_to_restore.query_exists () && file_to_restore.get_path () == file.get_path ()) {
is_override_target = true;
break;
}
}
}
}

return is_override_target;
}

public RestoreOverride create_restore_override () {
return new RestoreOverride (file, range);
}

public void clear () {
range = SelectionRange.EMPTY;
file = null;
}

public bool has_selection_range () {
return range != SelectionRange.EMPTY;
}

public bool parse_selection_range_string (string selection_range_string) {
Regex go_to_line_regex = /^(?<start_line>[0-9]+)+(?:\.(?<start_column>[0-9]+)+)?(?:-(?:(?<end_line>[0-9]+)+(?:\.(?<end_column>[0-9]+)+)?))?$/; // vala-lint=space-before-paren, line-length
MatchInfo match_info;
if (go_to_line_regex.match (selection_range_string, 0, out match_info)) {
range = parse_go_to_range_from_match_info (match_info);
debug ("Selection Range - start_line: %d", range.start_line);
debug ("Selection Range - start_column: %d", range.start_column);
debug ("Selection Range - end_line: %d", range.end_line);
debug ("Selection Range - end_column: %d", range.end_column);
}

return true;
}

private static SelectionRange parse_go_to_range_from_match_info (GLib.MatchInfo match_info) {
return SelectionRange () {
start_line = parse_num_from_match_info (match_info, "start_line"),
end_line = parse_num_from_match_info (match_info, "end_line"),
start_column = parse_num_from_match_info (match_info, "start_column"),
end_column = parse_num_from_match_info (match_info, "end_column"),
};
}

private static int parse_num_from_match_info (MatchInfo match_info, string match_name) {
var str = match_info.fetch_named (match_name);
int num = 0;

if (str != null) {
int.try_parse (str, out num);
}

return num;
}
}
}
18 changes: 18 additions & 0 deletions src/Services/RestoreOverride.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2023 elementary, Inc. <https://elementary.io>
*
* Authored by: Colin Kiama <[email protected]>
*/

public class RestoreOverride : GLib.Object {
public GLib.File file { get; construct; }
public SelectionRange range { get; construct; }

public RestoreOverride (GLib.File file, SelectionRange range) {
Object (
file: file,
range: range
);
}
}
15 changes: 15 additions & 0 deletions src/Structs/SelectionRange.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2023 elementary, Inc. <https://elementary.io>
*
* Authored by: Colin Kiama <[email protected]>
*/

public struct SelectionRange {
public int start_line;
public int start_column;
public int end_line;
public int end_column;

public const SelectionRange EMPTY = {0, 0, 0, 0};
}
16 changes: 14 additions & 2 deletions src/Widgets/DocumentView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public class Scratch.Widgets.DocumentView : Granite.Widgets.DynamicNotebook {
}
}

public void open_document (Services.Document doc, bool focus = true, int cursor_position = 0) {
public void open_document (Services.Document doc, bool focus = true, int cursor_position = 0, SelectionRange range = SelectionRange.EMPTY) {
for (int n = 0; n <= docs.length (); n++) {
var nth_doc = docs.nth_data (n);
if (nth_doc == null) {
Expand All @@ -226,6 +226,15 @@ public class Scratch.Widgets.DocumentView : Granite.Widgets.DynamicNotebook {
}

debug ("This Document was already opened! Not opening a duplicate!");
if (range != SelectionRange.EMPTY) {
Idle.add_full (GLib.Priority.LOW, () => { // This helps ensures new tab is drawn before opening document.
current_document.source_view.select_range (range);
save_opened_files ();

return false;
});
}

return;
}
}
Expand All @@ -242,9 +251,12 @@ public class Scratch.Widgets.DocumentView : Granite.Widgets.DynamicNotebook {
doc.focus ();
}

if (cursor_position > 0) {
if (range != SelectionRange.EMPTY) {
doc.source_view.select_range (range);
} else if (cursor_position > 0) {
doc.source_view.cursor_position = cursor_position;
}

save_opened_files ();
});

Expand Down
29 changes: 29 additions & 0 deletions src/Widgets/SourceView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,35 @@ namespace Scratch.Widgets {
buffer.end_user_action ();
}

public void select_range (SelectionRange range) {
if (range.start_line < 0) {
return;
}

Gtk.TextIter start_iter;
buffer.get_start_iter (out start_iter);
start_iter.set_line (range.start_line - 1);

if (range.start_column > 0) {
start_iter.set_visible_line_offset (range.start_column - 1);
}

Gtk.TextIter end_iter = start_iter.copy ();
if (range.end_line > 0) {
end_iter.set_line (range.end_line - 1);

if (range.end_column > 0) {
end_iter.set_visible_line_offset (range.end_column - 1);
}
}

buffer.select_range (start_iter, end_iter);
Idle.add (() => {
scroll_to_iter (end_iter, 0.25, false, 0, 0);
return Source.REMOVE;
});
}

public void set_text (string text, bool opening = true) {
var source_buffer = (Gtk.SourceBuffer) buffer;
if (opening) {
Expand Down
4 changes: 3 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ code_files = files(
'Services/DocumentManager.vala',
'Services/FileHandler.vala',
'Services/GitManager.vala',
'Services/LocationJumpManager.vala',
'Services/MonitoredRepository.vala',
'Services/PluginManager.vala',
'Services/RestoreOverride.vala',
'Services/Settings.vala',
'Services/TemplateManager.vala',
'Widgets/ChooseProjectButton.vala',
Expand All @@ -57,7 +59,7 @@ code_files = files(
'SymbolPane/C/CtagsSymbol.vala',
'SymbolPane/C/CtagsSymbolIter.vala',
'SymbolPane/C/CtagsSymbolOutline.vala',

'Structs/SelectionRange.vala'
)

executable(
Expand Down