Skip to content

Commit

Permalink
Add command-line "Go-To-Line" option (#1382)
Browse files Browse the repository at this point in the history
* Add option entry for command line go-to option

* Parse go-to range from command line argument  using syntax from #415

* Files can now be opened at a selected range

As long as they haven't already opened/restored

* Able to open new file at selected range while app is running

* go-to arg overrides document restore behviour

* Go to args also update seleccted range of files already opened

* Encapsulate go-to option arg handling into LocationJumpManager

Fixes #415

* Change SelectionRange.EMPTY into a public const value

* Fix lint errors

* Add license headers to new source code files

* Add null checks when parsing integers from selection range string

* Merge DocumentView.open_document methods into one method

* Amend license headers in new source code files

* Resolve code-style formatting issues

* Avoid unnecesaary function return in LocationJumpManager
  • Loading branch information
colinkiama committed Nov 28, 2023
1 parent 51dd521 commit 723348c
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 7 deletions.
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 {
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

0 comments on commit 723348c

Please sign in to comment.