From 1ba7dfe85b3dd9f6017ca357d1cc54ee93b52e68 Mon Sep 17 00:00:00 2001 From: herr kaste Date: Thu, 18 Apr 2024 14:16:14 +0200 Subject: [PATCH 1/6] Bind `[o]` to open commits under the cursor in the dashboards Fixes #1872 --- Default.sublime-keymap | 8 ++++++++ common/ui.py | 14 ++++++++++++++ core/interfaces/branch.py | 6 +++++- core/interfaces/status.py | 6 +++++- core/interfaces/tags.py | 6 +++++- 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Default.sublime-keymap b/Default.sublime-keymap index dd1c90f2b..11497a3e5 100644 --- a/Default.sublime-keymap +++ b/Default.sublime-keymap @@ -471,6 +471,14 @@ // STATUS VIEW - NAVIGATION // ////////////////////////////// + { + "keys": ["o"], + "command": "gs_interface_show_commit", + "context": [ + { "key": "setting.command_mode", "operator": "equal", "operand": false }, + { "key": "selector", "operand": "meta.git-savvy.summary-header constant.other.git-savvy.sha1" } + ] + }, { "keys": ["?"], "command": "gs_interface_toggle_help", diff --git a/common/ui.py b/common/ui.py index e8a680a4d..24d36cb54 100644 --- a/common/ui.py +++ b/common/ui.py @@ -28,6 +28,7 @@ "gs_interface_close", "gs_interface_refresh", "gs_interface_toggle_help", + "gs_interface_show_commit", "gs_edit_view_complete", "gs_edit_view_close", ) @@ -628,6 +629,19 @@ def run(self, edit): self.view.run_command("gs_interface_refresh") +class gs_interface_show_commit(TextCommand): + def run(self, edit: sublime.Edit) -> None: + view = self.view + frozen_sel = list(view.sel()) + window = view.window() + assert window + + for r in view.find_by_selector("constant.other.git-savvy.sha1"): + for s in frozen_sel: + if r.a <= s.a <= r.b: + window.run_command("gs_show_commit", {"commit_hash": view.substr(r)}) + + class EditView(): def __init__(self, content, on_done, repo_path, help_text=None, window=None): diff --git a/core/interfaces/branch.py b/core/interfaces/branch.py index 13074c56e..c28b9018b 100644 --- a/core/interfaces/branch.py +++ b/core/interfaces/branch.py @@ -163,7 +163,11 @@ def cursor_is_on_active_branch(): "meta.git-savvy.branches.branch.active-branch" ) ) - on_special_symbol = partial(self.cursor_is_on_something, "meta.git-savvy.branches.branch") + on_special_symbol = partial( + self.cursor_is_on_something, + "meta.git-savvy.branches.branch" + ", constant.other.git-savvy.sha1" + ) cursor_was_on_active_branch = cursor_is_on_active_branch() yield diff --git a/core/interfaces/status.py b/core/interfaces/status.py index 11fe70cca..b6e4a9338 100644 --- a/core/interfaces/status.py +++ b/core/interfaces/status.py @@ -243,7 +243,11 @@ def refresh_view_state(self): def keep_cursor_on_something(self): # type: () -> Iterator[None] on_a_file = partial(self.cursor_is_on_something, 'meta.git-savvy.entity.filename') - on_special_symbol = partial(self.cursor_is_on_something, 'meta.git-savvy.section.body.row') + on_special_symbol = partial( + self.cursor_is_on_something, + 'meta.git-savvy.section.body.row' + ', constant.other.git-savvy.sha1' + ) was_on_a_file = on_a_file() yield diff --git a/core/interfaces/tags.py b/core/interfaces/tags.py index c147f5c01..e075f41d4 100644 --- a/core/interfaces/tags.py +++ b/core/interfaces/tags.py @@ -204,7 +204,11 @@ def sink(): @contextmanager def keep_cursor_on_something(self): # type: () -> Iterator[None] - on_special_symbol = partial(self.cursor_is_on_something, "meta.git-savvy.tags.tag") + on_special_symbol = partial( + self.cursor_is_on_something, + "meta.git-savvy.tags.tag" + ", constant.other.git-savvy.sha1" + ) yield if not on_special_symbol(): From ccc8ba66855eea68e67bc57b73f9006a01abe864 Mon Sep 17 00:00:00 2001 From: herr kaste Date: Thu, 18 Apr 2024 14:23:30 +0200 Subject: [PATCH 2/6] Add the commit hashes to the navigation targets --- core/interfaces/branch.py | 5 ++++- core/interfaces/status.py | 1 + core/interfaces/tags.py | 5 ++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core/interfaces/branch.py b/core/interfaces/branch.py index c28b9018b..15ed2ce47 100644 --- a/core/interfaces/branch.py +++ b/core/interfaces/branch.py @@ -682,7 +682,10 @@ class gs_branches_navigate_branch(GsNavigate): offset = 0 def get_available_regions(self): - return self.view.find_by_selector("constant.other.git-savvy.branches.branch.sha1") + return self.view.find_by_selector( + "constant.other.git-savvy.branches.branch.sha1" + ", meta.git-savvy.summary-header constant.other.git-savvy.sha1" + ) class gs_branches_navigate_to_active_branch(GsNavigate): diff --git a/core/interfaces/status.py b/core/interfaces/status.py index b6e4a9338..63080a953 100644 --- a/core/interfaces/status.py +++ b/core/interfaces/status.py @@ -880,6 +880,7 @@ class gs_status_navigate_file(GsNavigate): def get_available_regions(self): return self.view.find_by_selector( "meta.git-savvy.entity - meta.git-savvy.entity.filename.renamed.to" + ", meta.git-savvy.summary-header constant.other.git-savvy.sha1" ) diff --git a/core/interfaces/tags.py b/core/interfaces/tags.py index e075f41d4..5b3c9ebaf 100644 --- a/core/interfaces/tags.py +++ b/core/interfaces/tags.py @@ -540,4 +540,7 @@ class gs_tags_navigate_tag(GsNavigate): offset = 0 def get_available_regions(self): - return self.view.find_by_selector("constant.other.git-savvy.tags.sha1") + return self.view.find_by_selector( + "constant.other.git-savvy.tags.sha1" + ", meta.git-savvy.summary-header constant.other.git-savvy.sha1" + ) From 10fca7b5b4dd99b59302ca2e50f346e60e3cf569 Mon Sep 17 00:00:00 2001 From: herr kaste Date: Thu, 18 Apr 2024 14:23:42 +0200 Subject: [PATCH 3/6] Fix a comment --- core/interfaces/tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/interfaces/tags.py b/core/interfaces/tags.py index 5b3c9ebaf..932752e21 100644 --- a/core/interfaces/tags.py +++ b/core/interfaces/tags.py @@ -535,7 +535,7 @@ def run(self, edit) -> None: class gs_tags_navigate_tag(GsNavigate): """ - Move cursor to the next (or previous) selectable file in the dashboard. + Move cursor to the next (or previous) selectable tag in the dashboard. """ offset = 0 From 7d2bc970bcdcbe9084de515606285a025d7b9740 Mon Sep 17 00:00:00 2001 From: herr kaste Date: Mon, 22 Apr 2024 13:06:03 +0200 Subject: [PATCH 4/6] Fix initial cursor in the tags dashboard --- core/interfaces/tags.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/core/interfaces/tags.py b/core/interfaces/tags.py index 932752e21..4d577ccc4 100644 --- a/core/interfaces/tags.py +++ b/core/interfaces/tags.py @@ -27,6 +27,7 @@ "gs_tags_show_commit", "gs_tags_show_graph", "gs_tags_navigate_tag", + "gs_tags_navigate_to_next_tag", ) @@ -212,7 +213,7 @@ def keep_cursor_on_something(self): yield if not on_special_symbol(): - self.view.run_command("gs_tags_navigate_tag") + self.view.run_command("gs_tags_navigate_to_next_tag") @ui.section("branch_status") def render_branch_status(self, long_status): @@ -544,3 +545,14 @@ def get_available_regions(self): "constant.other.git-savvy.tags.sha1" ", meta.git-savvy.summary-header constant.other.git-savvy.sha1" ) + + +class gs_tags_navigate_to_next_tag(GsNavigate): + + """ + Move cursor to the next (or previous) selectable tag in the dashboard. + """ + offset = 0 + + def get_available_regions(self): + return self.view.find_by_selector("constant.other.git-savvy.tags.sha1") From e62e07fd5f47081c15b5a6d062415b565b8f5c1f Mon Sep 17 00:00:00 2001 From: herr kaste Date: Fri, 19 Apr 2024 14:40:25 +0200 Subject: [PATCH 5/6] Implement context sensitive help in the status dashboard --- common/ui.py | 13 +++++-- core/interfaces/status.py | 81 ++++++++++++++++++++++++++++++--------- 2 files changed, 73 insertions(+), 21 deletions(-) diff --git a/common/ui.py b/common/ui.py index 24d36cb54..5cea74f46 100644 --- a/common/ui.py +++ b/common/ui.py @@ -309,6 +309,12 @@ def wrapper(self, *args, **kwargs): return wrapper +@contextmanager +def noop_context(): + # type: () -> Iterator[None] + yield + + class ReactiveInterface(Interface, GitCommand, Generic[T_state]): state: T_state subscribe_to: Set[str] @@ -348,13 +354,14 @@ def render(self): # We check twice if a re-render is actually necessary because the state has grown # and invalidates when formatted relative dates change, t.i., too often. @distinct_until_state_changed # <== 1st check data/state - def just_render(self): - # type: () -> None + def just_render(self, keep_cursor_on_something=True): + # type: (bool) -> None content, regions = self._render_template() if content == self.view.substr(sublime.Region(0, self.view.size())): # <== 2nd check actual view content return - with self.keep_cursor_on_something(): + ctx = self.keep_cursor_on_something() if keep_cursor_on_something else noop_context() + with ctx: self.draw(self.title(), content, regions) def initial_state(self): diff --git a/core/interfaces/status.py b/core/interfaces/status.py index 63080a953..7743b7113 100644 --- a/core/interfaces/status.py +++ b/core/interfaces/status.py @@ -3,7 +3,7 @@ import os import sublime -from sublime_plugin import WindowCommand +from sublime_plugin import EventListener, WindowCommand from ..git_mixins.status import FileStatus from ..git_mixins.active_branch import format_and_limit @@ -18,6 +18,7 @@ __all__ = ( "gs_show_status", + "StatusViewContextSensitiveHelpEventListener", "gs_status_open_file", "gs_status_open_file_on_remote", "gs_status_diff_inline", @@ -52,6 +53,7 @@ class StatusViewState(TypedDict, total=False): long_status: str git_root: str show_help: bool + help_context: Optional[str] head: HeadState branches: List[Branch] recent_commits: List[Commit] @@ -95,6 +97,24 @@ def run(self): ui.show_interface(self.window, self.repo_path, "status") +class StatusViewContextSensitiveHelpEventListener(EventListener): + def on_selection_modified_async(self, view): + interface = ui.interfaces.get(view.id()) + if not isinstance(interface, StatusInterface): + return + + frozen_sel = list(view.sel()) + if all(view.match_selector(s.a, "constant.other.git-savvy.sha1") for s in frozen_sel): + next_state = "on_commit" + else: + next_state = None + + current_state = interface.state.get("help_context") + if next_state != current_state: + interface.state["help_context"] = next_state + interface.just_render(keep_cursor_on_something=False) + + class StatusInterface(ui.ReactiveInterface, GitCommand): """ @@ -122,21 +142,7 @@ class StatusInterface(ui.ReactiveInterface, GitCommand): {< help} """ - template_help = """ - ################### ############### - ## SELECTED FILE ## ## ALL FILES ## - ################### ############### - - [o] open file [a] stage all unstaged files - [s] stage file [A] stage all unstaged and untracked files - [u] unstage file [U] unstage all staged files - [d] discard changes to file [D] discard all unstaged changes - [i] skip/unskip file - [h] open file in browser - - [l] diff file inline [f] diff all files - [e] diff file [F] diff all cached files - + _template_help = """ ############# ############# ## ACTIONS ## ## STASHES ## ############# ############# @@ -166,6 +172,38 @@ class StatusInterface(ui.ReactiveInterface, GitCommand): - """ + template_help = """ + ################### ############### + ## SELECTED FILE ## ## ALL FILES ## + ################### ############### + + [o] open file [a] stage all unstaged files + [s] stage file [A] stage all unstaged and untracked files + [u] unstage file [U] unstage all staged files + [d] discard changes to file [D] discard all unstaged changes + [i] skip/unskip file + [h] open file in browser + + [l] diff file inline [f] diff all files + [e] diff file [F] diff all cached files + """ + _template_help + + template_help_on_commit = """ + ##################### ############### + ## SELECTED COMMIT ## ## ALL FILES ## + ##################### ############### + + [o] show commit [a] stage all unstaged files + [A] stage all unstaged and untracked files + [U] unstage all staged files + [D] discard all unstaged changes + + + + [f] diff all files + [F] diff all cached files + """ + _template_help + conflicts_keybindings = ui.indent_by_2(""" ############### ## CONFLICTS ## @@ -215,6 +253,11 @@ class StatusInterface(ui.ReactiveInterface, GitCommand): } state: StatusViewState + def initial_state(self): + return { + 'help_context': None, + } + def title(self): # type: () -> str return "STATUS: {}".format(os.path.basename(self.repo_path)) @@ -388,11 +431,13 @@ def render_stashes(self, stashes): " ({}) {}".format(stash.id, stash.description) for stash in stashes)) @ui.section("help") - def render_help(self, show_help): - # type: (bool) -> str + def render_help(self, show_help, help_context): + # type: (bool, Optional[str]) -> str if not show_help: return "" + if help_context == "on_commit": + return self.template_help_on_commit.format(conflicts_bindings=self.render_conflicts_bindings()) return self.template_help.format(conflicts_bindings=self.render_conflicts_bindings()) From f269625bd439e4ef63ac0ad14384ad0024bd7a8e Mon Sep 17 00:00:00 2001 From: herr kaste Date: Mon, 17 Jun 2024 10:39:28 +0200 Subject: [PATCH 6/6] Add context aware help for the "ADDED" section --- core/interfaces/status.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/interfaces/status.py b/core/interfaces/status.py index 7743b7113..8f7b1b04d 100644 --- a/core/interfaces/status.py +++ b/core/interfaces/status.py @@ -106,6 +106,8 @@ def on_selection_modified_async(self, view): frozen_sel = list(view.sel()) if all(view.match_selector(s.a, "constant.other.git-savvy.sha1") for s in frozen_sel): next_state = "on_commit" + elif all(view.match_selector(s.a, "meta.git-savvy.status.section.added") for s in frozen_sel): + next_state = "on_added" else: next_state = None @@ -188,6 +190,11 @@ class StatusInterface(ui.ReactiveInterface, GitCommand): [e] diff file [F] diff all cached files """ + _template_help + template_help_on_added = template_help.replace( + "[u] unstage file ", + "[u] unadd file " + ) + template_help_on_commit = """ ##################### ############### ## SELECTED COMMIT ## ## ALL FILES ## @@ -438,6 +445,8 @@ def render_help(self, show_help, help_context): if help_context == "on_commit": return self.template_help_on_commit.format(conflicts_bindings=self.render_conflicts_bindings()) + if help_context == "on_added": + return self.template_help_on_added.format(conflicts_bindings=self.render_conflicts_bindings()) return self.template_help.format(conflicts_bindings=self.render_conflicts_bindings())