From 73f129a6858098b485ce2321e7141d30da815280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Coss=C3=ADo?= Date: Wed, 17 Dec 2025 16:40:15 -0300 Subject: [PATCH] git: New actions for git panel navigation (#43701) I could not find any related issue, but at least I want to use the git panel like this :) Being used to `lazygit`, this PR makes navigation of the git panel more similar to the CLI tool. Instead of selecting -> enter'ing for skimming each file, I just want to move between the files in the git panel and have the diff multibuffer advance to the appropriate file. This also adheres to the behavior of the outline panel, which I like better. If the multibuffer is not active, it behaves same as before (just selecting the file in the panel, nothing else). I did not modify existing `menu::Select*` actions in case anybody still prefers previous behavior. https://github.com/user-attachments/assets/2d1303d4-50c8-4500-ab3b-302eb7d4afda Release Notes: - Improved navigation of the git panel, by advancing the "Uncommitted Changes" multibuffer to the current selected file. To restore the old behavior, you can bind `up` and `down` to `menu::SelectPrevious` and `menu::SelectNext` under the `GitPanel` context in your keymap. Co-authored-by: Cole Miller --- assets/keymaps/default-linux.json | 4 +- assets/keymaps/default-macos.json | 8 +-- assets/keymaps/default-windows.json | 4 +- crates/git_ui/src/git_panel.rs | 80 ++++++++++++++++++++++++----- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index f09ac0a812c3e875618c57da15bcf16e1f983d6e..ec21bc152edf969f57ac341e4b92f78c9e5da11a 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -905,8 +905,8 @@ "bindings": { "left": "git_panel::CollapseSelectedEntry", "right": "git_panel::ExpandSelectedEntry", - "up": "menu::SelectPrevious", - "down": "menu::SelectNext", + "up": "git_panel::PreviousEntry", + "down": "git_panel::NextEntry", "enter": "menu::Confirm", "alt-y": "git::StageFile", "alt-shift-y": "git::UnstageFile", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 1d489771febc770e300b63e265024ffca3d14a90..fd2605a6ad99177c887d6f804ec2ac70724f16f8 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -981,12 +981,12 @@ "context": "GitPanel && ChangesList", "use_key_equivalents": true, "bindings": { + "up": "git_panel::PreviousEntry", + "down": "git_panel::NextEntry", + "cmd-up": "git_panel::FirstEntry", + "cmd-down": "git_panel::LastEntry", "left": "git_panel::CollapseSelectedEntry", "right": "git_panel::ExpandSelectedEntry", - "up": "menu::SelectPrevious", - "down": "menu::SelectNext", - "cmd-up": "menu::SelectFirst", - "cmd-down": "menu::SelectLast", "enter": "menu::Confirm", "cmd-alt-y": "git::ToggleStaged", "space": "git::ToggleStaged", diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 9154cc43afb86c287329229c6f0d699f59a82b36..4a700e2c9190a8ae23ed53edaa075703fa07b855 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -908,10 +908,10 @@ "context": "GitPanel && ChangesList", "use_key_equivalents": true, "bindings": { + "up": "git_panel::PreviousEntry", + "down": "git_panel::NextEntry", "left": "git_panel::CollapseSelectedEntry", "right": "git_panel::ExpandSelectedEntry", - "up": "menu::SelectPrevious", - "down": "menu::SelectNext", "enter": "menu::Confirm", "alt-y": "git::StageFile", "shift-alt-y": "git::UnstageFile", diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index cf73406b3851b46ad1a7d056d6cb335666b9ac65..90c9b92cf882f25f50cebab776fc328a22cda022 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -46,7 +46,7 @@ use language_model::{ ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, ZED_CLOUD_PROVIDER_ID, }; -use menu::{Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrevious}; +use menu; use multi_buffer::ExcerptInfo; use notifications::status_toast::{StatusToast, ToastIcon}; use panel::{ @@ -93,6 +93,14 @@ actions!( FocusEditor, /// Focuses on the changes list. FocusChanges, + /// Select next git panel menu item, and show it in the diff view + NextEntry, + /// Select previous git panel menu item, and show it in the diff view + PreviousEntry, + /// Select first git panel menu item, and show it in the diff view + FirstEntry, + /// Select last git panel menu item, and show it in the diff view + LastEntry, /// Toggles automatic co-author suggestions. ToggleFillCoAuthors, /// Toggles sorting entries by path vs status. @@ -914,12 +922,12 @@ impl GitPanel { if let GitListEntry::Directory(dir_entry) = entry { if dir_entry.expanded { - self.select_next(&SelectNext, window, cx); + self.select_next(&menu::SelectNext, window, cx); } else { self.toggle_directory(&dir_entry.key, window, cx); } } else { - self.select_next(&SelectNext, window, cx); + self.select_next(&menu::SelectNext, window, cx); } } @@ -937,14 +945,19 @@ impl GitPanel { if dir_entry.expanded { self.toggle_directory(&dir_entry.key, window, cx); } else { - self.select_previous(&SelectPrevious, window, cx); + self.select_previous(&menu::SelectPrevious, window, cx); } } else { - self.select_previous(&SelectPrevious, window, cx); + self.select_previous(&menu::SelectPrevious, window, cx); } } - fn select_first(&mut self, _: &SelectFirst, _window: &mut Window, cx: &mut Context) { + fn select_first( + &mut self, + _: &menu::SelectFirst, + _window: &mut Window, + cx: &mut Context, + ) { let first_entry = match &self.view_mode { GitPanelViewMode::Flat => self .entries @@ -967,7 +980,7 @@ impl GitPanel { fn select_previous( &mut self, - _: &SelectPrevious, + _: &menu::SelectPrevious, _window: &mut Window, cx: &mut Context, ) { @@ -1016,7 +1029,7 @@ impl GitPanel { self.scroll_to_selected_entry(cx); } - fn select_next(&mut self, _: &SelectNext, _window: &mut Window, cx: &mut Context) { + fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context) { let item_count = self.entries.len(); if item_count == 0 { return; @@ -1054,13 +1067,50 @@ impl GitPanel { self.scroll_to_selected_entry(cx); } - fn select_last(&mut self, _: &SelectLast, _window: &mut Window, cx: &mut Context) { + fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context) { if self.entries.last().is_some() { self.selected_entry = Some(self.entries.len() - 1); self.scroll_to_selected_entry(cx); } } + /// Show diff view at selected entry, only if the diff view is open + fn move_diff_to_entry(&mut self, window: &mut Window, cx: &mut Context) { + maybe!({ + let workspace = self.workspace.upgrade()?; + + if let Some(project_diff) = workspace.read(cx).item_of_type::(cx) { + let entry = self.entries.get(self.selected_entry?)?.status_entry()?; + + project_diff.update(cx, |project_diff, cx| { + project_diff.move_to_entry(entry.clone(), window, cx); + }); + } + + Some(()) + }); + } + + fn first_entry(&mut self, _: &FirstEntry, window: &mut Window, cx: &mut Context) { + self.select_first(&menu::SelectFirst, window, cx); + self.move_diff_to_entry(window, cx); + } + + fn last_entry(&mut self, _: &LastEntry, window: &mut Window, cx: &mut Context) { + self.select_last(&menu::SelectLast, window, cx); + self.move_diff_to_entry(window, cx); + } + + fn next_entry(&mut self, _: &NextEntry, window: &mut Window, cx: &mut Context) { + self.select_next(&menu::SelectNext, window, cx); + self.move_diff_to_entry(window, cx); + } + + fn previous_entry(&mut self, _: &PreviousEntry, window: &mut Window, cx: &mut Context) { + self.select_previous(&menu::SelectPrevious, window, cx); + self.move_diff_to_entry(window, cx); + } + fn focus_editor(&mut self, _: &FocusEditor, window: &mut Window, cx: &mut Context) { self.commit_editor.update(cx, |editor, cx| { window.focus(&editor.focus_handle(cx), cx); @@ -1074,7 +1124,7 @@ impl GitPanel { .as_ref() .is_some_and(|active_repository| active_repository.read(cx).status_summary().count > 0); if have_entries && self.selected_entry.is_none() { - self.select_first(&SelectFirst, window, cx); + self.select_first(&menu::SelectFirst, window, cx); } } @@ -4726,8 +4776,8 @@ impl GitPanel { git::AddToGitignore.boxed_clone(), ) .separator() - .action("Open Diff", Confirm.boxed_clone()) - .action("Open File", SecondaryConfirm.boxed_clone()) + .action("Open Diff", menu::Confirm.boxed_clone()) + .action("Open File", menu::SecondaryConfirm.boxed_clone()) .separator() .action_disabled_when(is_created, "View File History", Box::new(git::FileHistory)) }); @@ -5390,6 +5440,10 @@ impl Render for GitPanel { .on_action(cx.listener(Self::select_next)) .on_action(cx.listener(Self::select_previous)) .on_action(cx.listener(Self::select_last)) + .on_action(cx.listener(Self::first_entry)) + .on_action(cx.listener(Self::next_entry)) + .on_action(cx.listener(Self::previous_entry)) + .on_action(cx.listener(Self::last_entry)) .on_action(cx.listener(Self::close_panel)) .on_action(cx.listener(Self::open_diff)) .on_action(cx.listener(Self::open_file)) @@ -6855,7 +6909,7 @@ mod tests { // the Project Diff's active path. panel.update_in(cx, |panel, window, cx| { panel.selected_entry = Some(1); - panel.open_diff(&Confirm, window, cx); + panel.open_diff(&menu::Confirm, window, cx); }); cx.run_until_parked();