diff --git a/crates/actions/src/action.rs b/crates/actions/src/action.rs index 104b104..92bf0e7 100644 --- a/crates/actions/src/action.rs +++ b/crates/actions/src/action.rs @@ -270,6 +270,8 @@ pub enum UiEffect { SetChangesSort(ChangesSort), /// Show the log of git commands and their output. ShowGitOutput, + /// Look for a newer release now, ignoring the daily cache (also `U`). + CheckForUpdates, /// Show where HEAD has been (the reflog), to recover lost commits. ShowReflog, /// Show the repository's worktrees, to add, remove or prune them. diff --git a/crates/actions/src/menu.rs b/crates/actions/src/menu.rs index 91aa17b..99b690c 100644 --- a/crates/actions/src/menu.rs +++ b/crates/actions/src/menu.rs @@ -571,6 +571,8 @@ fn repository_menu(ctx: &MenuContext) -> Vec> { entry("Show Git Output", Action::Ui(UiEffect::ShowGitOutput)), entry("Show Reflog", Action::Ui(UiEffect::ShowReflog)), entry("Worktrees...", Action::Ui(UiEffect::ShowWorktrees)), + Some(MenuItem::Separator), + entry("Check for Updates", Action::Ui(UiEffect::CheckForUpdates)), ] } diff --git a/crates/actions/src/menu/tests.rs b/crates/actions/src/menu/tests.rs index 7427706..e9b3509 100644 --- a/crates/actions/src/menu/tests.rs +++ b/crates/actions/src/menu/tests.rs @@ -740,6 +740,8 @@ fn repository_menu_matches_the_vs_code_order() { "Show Git Output", "Show Reflog", "Worktrees...", + "---", + "Check for Updates", ] ); assert_eq!( @@ -1182,6 +1184,10 @@ fn repository_actions_are_prefilled() { action(&menu, "Worktrees..."), &Action::Ui(UiEffect::ShowWorktrees) ); + assert_eq!( + action(&menu, "Check for Updates"), + &Action::Ui(UiEffect::CheckForUpdates) + ); } #[test] @@ -1212,6 +1218,8 @@ fn repository_menu_without_remotes_or_branch() { "Show Git Output", "Show Reflog", "Worktrees...", + "---", + "Check for Updates", ] ); assert_eq!( diff --git a/crates/app/src/state.rs b/crates/app/src/state.rs index c7795f7..98d563e 100644 --- a/crates/app/src/state.rs +++ b/crates/app/src/state.rs @@ -372,6 +372,17 @@ pub struct AppState { } impl AppState { + /// `U` and the "..." menu's Check for Updates: shows the update that is + /// known, or asks the main loop to look again right now. + pub(super) fn check_for_updates(&mut self) { + if self.update_available.is_some() { + self.update_panel = true; + } else { + self.update_requested = true; + self.notify(Level::Info, "Checking for updates…"); + } + } + /// Shows `text` in the status bar, colored by `level`. pub fn notify(&mut self, level: Level, text: impl Into) { self.message = Some(text.into()); @@ -576,7 +587,6 @@ impl AppState { base.max(self.status_poll.cost * 10) } - #[expect(clippy::too_many_lines, reason = "routes input across the app modes")] fn on_key(&mut self, key: KeyEvent) -> Vec { if key.kind != KeyEventKind::Press { return Vec::new(); @@ -649,12 +659,7 @@ impl AppState { return Vec::new(); } (KeyCode::Char('U'), _) => { - if self.update_available.is_some() { - self.update_panel = true; - } else { - self.update_requested = true; - self.notify(Level::Info, "Checking for updates…"); - } + self.check_for_updates(); } (KeyCode::Char('r'), _) => { // A manual refresh clears old messages; automatic ones keep the diff --git a/crates/app/src/state/actions.rs b/crates/app/src/state/actions.rs index 97709c8..6104865 100644 --- a/crates/app/src/state/actions.rs +++ b/crates/app/src/state/actions.rs @@ -209,6 +209,10 @@ impl AppState { Vec::new() } Plan::Ui(UiEffect::ShowGitOutput) => self.show_git_output(), + Plan::Ui(UiEffect::CheckForUpdates) => { + self.check_for_updates(); + Vec::new() + } Plan::Ui(UiEffect::ShowReflog) => self.show_reflog(), Plan::Ui(UiEffect::ShowWorktrees) => self.show_worktrees(), Plan::Ui(UiEffect::OpenInteractiveRebase { base }) => { diff --git a/crates/app/src/state/toolbar_tests.rs b/crates/app/src/state/toolbar_tests.rs index 0f42f36..c7b63ab 100644 --- a/crates/app/src/state/toolbar_tests.rs +++ b/crates/app/src/state/toolbar_tests.rs @@ -550,3 +550,13 @@ fn create_tag_from_the_menu_is_annotated_even_without_a_message() { accept_dialogs(&mut h); assert_eq!(repo.git(&["cat-file", "-t", "v9"]), "tag"); } + +#[test] +fn check_for_updates_in_the_repository_menu_asks_for_a_check() { + let repo = FixtureRepo::sample(); + let mut h = open(&repo); + open_repo_menu(&mut h); + click_text(&mut h, "Check for Updates"); + assert!(h.state.update_requested, "the main loop looks again now"); + assert_eq!(h.state.message.as_deref(), Some("Checking for updates…")); +} diff --git a/crates/ui/tests/snapshots/menu__repository_menu_with_the_branch_submenu_open.snap b/crates/ui/tests/snapshots/menu__repository_menu_with_the_branch_submenu_open.snap index 70bfe22..7bca8d0 100644 --- a/crates/ui/tests/snapshots/menu__repository_menu_with_the_branch_submenu_open.snap +++ b/crates/ui/tests/snapshots/menu__repository_menu_with_the_branch_submenu_open.snap @@ -2,28 +2,28 @@ source: crates/ui/tests/menu.rs expression: "render(&menu, 60, 25)" --- -.┌─────────────────┐........................................ -.│ View as Tree │┌─────────────────────────┐............. -.├─────────────────┤│ Merge... │............. -.│ View & Sort ▸ ││ Rebase Branch... │............. -.├─────────────────┤├─────────────────────────┤............. -.│ Pull ││ Create Branch... │............. -.│ Push ││ Create Branch From... │............. -.│ Clone │├─────────────────────────┤............. -.│ Checkout to... ││ Rename Branch... │............. -.│ Fetch ││ Delete Branch... │............. -.├─────────────────┤│ Delete Remote Branch... │............. -.│ Commit ▸ │├─────────────────────────┤............. -.│ Changes ▸ ││ Publish Branch... │............. -.│ Pull, Push ▸ │└─────────────────────────┘............. -.│ Branch ▸ │........................................ -.│ Remote ▸ │........................................ -.│ Stash ▸ │........................................ -.│ Tags ▸ │........................................ -.├─────────────────┤........................................ -.│ Show Git Output │........................................ -.│ Show Reflog │........................................ -.│ Worktrees... │........................................ -.└─────────────────┘........................................ -............................................................ -............................................................ +.┌───────────────────┐...................................... +.│ View as Tree │┌─────────────────────────┐........... +.├───────────────────┤│ Merge... │........... +.│ View & Sort ▸ ││ Rebase Branch... │........... +.├───────────────────┤├─────────────────────────┤........... +.│ Pull ││ Create Branch... │........... +.│ Push ││ Create Branch From... │........... +.│ Clone │├─────────────────────────┤........... +.│ Checkout to... ││ Rename Branch... │........... +.│ Fetch ││ Delete Branch... │........... +.├───────────────────┤│ Delete Remote Branch... │........... +.│ Commit ▸ │├─────────────────────────┤........... +.│ Changes ▸ ││ Publish Branch... │........... +.│ Pull, Push ▸ │└─────────────────────────┘........... +.│ Branch ▸ │...................................... +.│ Remote ▸ │...................................... +.│ Stash ▸ │...................................... +.│ Tags ▸ │...................................... +.├───────────────────┤...................................... +.│ Show Git Output │...................................... +.│ Show Reflog │...................................... +.│ Worktrees... │...................................... +.├───────────────────┤...................................... +.│ Check for Updates │...................................... +.└───────────────────┘......................................