From a6354dd20476df436e7c2bfec768e2f107ea4da3 Mon Sep 17 00:00:00 2001 From: Blagoy Simandoff Date: Sat, 25 Apr 2026 13:25:01 +0100 Subject: [PATCH 1/2] feat(controls): vim like keybindings for nav --- src/components/commit_details/details.rs | 7 ++----- src/components/commit_details/mod.rs | 6 +++--- src/components/commitlist.rs | 5 ++--- src/components/diff.rs | 13 ++++--------- src/components/revision_files.rs | 3 +-- src/components/status_tree.rs | 13 ++++--------- src/keys/key_config.rs | 22 ++++++++++++++++++++-- src/popups/blame_file.rs | 13 +++---------- src/popups/branchlist.rs | 6 +++--- src/popups/checkout_option.rs | 8 ++------ src/popups/compare_commits.rs | 9 +++------ src/popups/file_revlog.rs | 14 ++++---------- src/popups/help.rs | 5 ++--- src/popups/inspect_commit.rs | 9 +++------ src/popups/options.rs | 18 ++++-------------- src/popups/remotelist.rs | 4 ++-- src/popups/reset.rs | 8 ++------ src/popups/submodules.rs | 4 ++-- src/popups/taglist.rs | 14 ++++---------- src/tabs/revlog.rs | 6 ++---- src/tabs/status.rs | 10 ++++------ src/ui/mod.rs | 6 +++--- 22 files changed, 79 insertions(+), 124 deletions(-) diff --git a/src/components/commit_details/details.rs b/src/components/commit_details/details.rs index 42825900a0..102f952243 100644 --- a/src/components/commit_details/details.rs +++ b/src/components/commit_details/details.rs @@ -362,12 +362,9 @@ impl Component for DetailsComponent { if self.focused { if let Event::Key(e) = event { return Ok( - if key_match(e, self.key_config.keys.move_up) { + if self.key_config.is_nav_up(e) { self.move_scroll_top(ScrollType::Up).into() - } else if key_match( - e, - self.key_config.keys.move_down, - ) { + } else if self.key_config.is_nav_down(e) { self.move_scroll_top(ScrollType::Down).into() } else if key_match( e, diff --git a/src/components/commit_details/mod.rs b/src/components/commit_details/mod.rs index b8872c396b..5f863ec9de 100644 --- a/src/components/commit_details/mod.rs +++ b/src/components/commit_details/mod.rs @@ -9,7 +9,7 @@ use super::{ use crate::{ accessors, app::Environment, - keys::{key_match, SharedKeyConfig}, + keys::SharedKeyConfig, strings, }; use anyhow::Result; @@ -208,13 +208,13 @@ impl Component for CommitDetailsComponent { if self.focused() { if let Event::Key(e) = ev { - return if key_match(e, self.key_config.keys.move_down) + return if self.key_config.is_nav_down(e) && self.details_focused() { self.set_details_focus(false); self.file_tree.focus(true); Ok(EventState::Consumed) - } else if key_match(e, self.key_config.keys.move_up) + } else if self.key_config.is_nav_up(e) && self.file_tree.focused() && !self.is_compare() { diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index a84060151d..5ff58c95c5 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -831,10 +831,9 @@ impl Component for CommitList { fn event(&mut self, ev: &Event) -> Result { if let Event::Key(k) = ev { let selection_changed = - if key_match(k, self.key_config.keys.move_up) { + if self.key_config.is_nav_up(k) { self.move_selection(ScrollType::Up)? - } else if key_match(k, self.key_config.keys.move_down) - { + } else if self.key_config.is_nav_down(k) { self.move_selection(ScrollType::Down)? } else if key_match(k, self.key_config.keys.shift_up) || key_match(k, self.key_config.keys.home) diff --git a/src/components/diff.rs b/src/components/diff.rs index 04779caada..d28a05996b 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -831,8 +831,7 @@ impl Component for DiffComponent { fn event(&mut self, ev: &Event) -> Result { if self.focused() { if let Event::Key(e) = ev { - return if key_match(e, self.key_config.keys.move_down) - { + return if self.key_config.is_nav_down(e) { self.move_selection(ScrollType::Down); Ok(EventState::Consumed) } else if key_match( @@ -851,7 +850,7 @@ impl Component for DiffComponent { } else if key_match(e, self.key_config.keys.home) { self.move_selection(ScrollType::Home); Ok(EventState::Consumed) - } else if key_match(e, self.key_config.keys.move_up) { + } else if self.key_config.is_nav_up(e) { self.move_selection(ScrollType::Up); Ok(EventState::Consumed) } else if key_match(e, self.key_config.keys.page_up) { @@ -861,15 +860,11 @@ impl Component for DiffComponent { { self.move_selection(ScrollType::PageDown); Ok(EventState::Consumed) - } else if key_match( - e, - self.key_config.keys.move_right, - ) { + } else if self.key_config.is_nav_right(e) { self.horizontal_scroll .move_right(HorizontalScrollType::Right); Ok(EventState::Consumed) - } else if key_match(e, self.key_config.keys.move_left) - { + } else if key_match(e, self.key_config.keys.move_left) { self.horizontal_scroll .move_right(HorizontalScrollType::Left); Ok(EventState::Consumed) diff --git a/src/components/revision_files.rs b/src/components/revision_files.rs index 1e15ec086f..f239a09632 100644 --- a/src/components/revision_files.rs +++ b/src/components/revision_files.rs @@ -496,8 +496,7 @@ impl Component for RevisionFilesComponent { self.hide(); return Ok(EventState::Consumed); } - } else if key_match(key, self.key_config.keys.move_right) - { + } else if self.key_config.is_nav_right(key) { if is_tree_focused { self.focus = Focus::File; self.current_file.focus(true); diff --git a/src/components/status_tree.rs b/src/components/status_tree.rs index ac4fc9f6a8..7c01ac38f3 100644 --- a/src/components/status_tree.rs +++ b/src/components/status_tree.rs @@ -518,12 +518,11 @@ impl Component for StatusTreeComponent { } else if key_match(e, self.key_config.keys.copy) { self.copy_file_path(); Ok(EventState::Consumed) - } else if key_match(e, self.key_config.keys.move_down) - { + } else if self.key_config.is_nav_down(e) { Ok(self .move_selection(MoveSelection::Down) .into()) - } else if key_match(e, self.key_config.keys.move_up) { + } else if self.key_config.is_nav_up(e) { Ok(self.move_selection(MoveSelection::Up).into()) } else if key_match(e, self.key_config.keys.home) || key_match(e, self.key_config.keys.shift_up) @@ -544,15 +543,11 @@ impl Component for StatusTreeComponent { Ok(self .move_selection(MoveSelection::PageDown) .into()) - } else if key_match(e, self.key_config.keys.move_left) - { + } else if key_match(e, self.key_config.keys.move_left) { Ok(self .move_selection(MoveSelection::Left) .into()) - } else if key_match( - e, - self.key_config.keys.move_right, - ) { + } else if self.key_config.is_nav_right(e) { Ok(self .move_selection(MoveSelection::Right) .into()) diff --git a/src/keys/key_config.rs b/src/keys/key_config.rs index 45a9ffdfb2..07b2b6f515 100644 --- a/src/keys/key_config.rs +++ b/src/keys/key_config.rs @@ -1,11 +1,11 @@ use anyhow::Result; -use crossterm::event::{KeyCode, KeyModifiers}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use std::{fs::canonicalize, path::PathBuf, rc::Rc}; use crate::{args::get_app_config_path, strings::symbol}; use super::{ - key_list::{GituiKeyEvent, KeysList}, + key_list::{key_match, GituiKeyEvent, KeysList}, symbols::KeySymbols, }; @@ -52,6 +52,24 @@ impl KeyConfig { Ok(Self { keys, symbols }) } + pub fn is_nav_up(&self, key: &KeyEvent) -> bool { + key_match(key, self.keys.move_up) + || (key.code == KeyCode::Char('k') + && key.modifiers == KeyModifiers::NONE) + } + + pub fn is_nav_down(&self, key: &KeyEvent) -> bool { + key_match(key, self.keys.move_down) + || (key.code == KeyCode::Char('j') + && key.modifiers == KeyModifiers::NONE) + } + + pub fn is_nav_right(&self, key: &KeyEvent) -> bool { + key_match(key, self.keys.move_right) + || (key.code == KeyCode::Char('l') + && key.modifiers == KeyModifiers::NONE) + } + fn get_key_symbol(&self, k: KeyCode) -> &str { match k { KeyCode::Enter => &self.symbols.enter, diff --git a/src/popups/blame_file.rs b/src/popups/blame_file.rs index b7d1304b33..d838742696 100644 --- a/src/popups/blame_file.rs +++ b/src/popups/blame_file.rs @@ -257,13 +257,9 @@ impl Component for BlameFilePopup { if let Event::Key(key) = event { if key_match(key, self.key_config.keys.exit_popup) { self.hide_stacked(false); - } else if key_match(key, self.key_config.keys.move_up) - { + } else if self.key_config.is_nav_up(key) { self.move_selection(ScrollType::Up); - } else if key_match( - key, - self.key_config.keys.move_down, - ) { + } else if self.key_config.is_nav_down(key) { self.move_selection(ScrollType::Down); } else if key_match( key, @@ -289,10 +285,7 @@ impl Component for BlameFilePopup { } else if key_match(key, self.key_config.keys.page_up) { self.move_selection(ScrollType::PageUp); - } else if key_match( - key, - self.key_config.keys.move_right, - ) { + } else if self.key_config.is_nav_right(key) { if let Some(commit_id) = self.selected_commit() { self.hide_stacked(true); self.queue.push(InternalEvent::OpenPopup( diff --git a/src/popups/branchlist.rs b/src/popups/branchlist.rs index fa66ffffad..66c704231f 100644 --- a/src/popups/branchlist.rs +++ b/src/popups/branchlist.rs @@ -170,7 +170,7 @@ impl Component for BranchListPopup { "rebase error:", self.rebase_branch() ); - } else if key_match(e, self.key_config.keys.move_right) + } else if self.key_config.is_nav_right(e) && self.valid_selection() { self.inspect_head_of_branch(); @@ -258,11 +258,11 @@ impl BranchListPopup { fn move_event(&mut self, e: &KeyEvent) -> Result { if key_match(e, self.key_config.keys.exit_popup) { self.hide(); - } else if key_match(e, self.key_config.keys.move_down) { + } else if self.key_config.is_nav_down(e) { return self .move_selection(ScrollType::Up) .map(Into::into); - } else if key_match(e, self.key_config.keys.move_up) { + } else if self.key_config.is_nav_up(e) { return self .move_selection(ScrollType::Down) .map(Into::into); diff --git a/src/popups/checkout_option.rs b/src/popups/checkout_option.rs index c2abdd0a84..18e4a0e69a 100644 --- a/src/popups/checkout_option.rs +++ b/src/popups/checkout_option.rs @@ -200,13 +200,9 @@ impl Component for CheckoutOptionPopup { if let Event::Key(key) = &event { if key_match(key, self.key_config.keys.exit_popup) { self.hide(); - } else if key_match( - key, - self.key_config.keys.move_down, - ) { + } else if self.key_config.is_nav_down(key) { self.change_kind(true); - } else if key_match(key, self.key_config.keys.move_up) - { + } else if self.key_config.is_nav_up(key) { self.change_kind(false); } else if key_match(key, self.key_config.keys.enter) { try_or_popup!( diff --git a/src/popups/compare_commits.rs b/src/popups/compare_commits.rs index 5ff6ef87aa..f70f93bc67 100644 --- a/src/popups/compare_commits.rs +++ b/src/popups/compare_commits.rs @@ -124,15 +124,12 @@ impl Component for CompareCommitsPopup { } else { self.hide_stacked(false); } - } else if key_match( - e, - self.key_config.keys.move_right, - ) && self.can_focus_diff() + } else if self.key_config.is_nav_right(e) + && self.can_focus_diff() { self.details.focus(false); self.diff.focus(true); - } else if key_match(e, self.key_config.keys.move_left) - { + } else if key_match(e, self.key_config.keys.move_left) { self.hide_stacked(false); } diff --git a/src/popups/file_revlog.rs b/src/popups/file_revlog.rs index 771ae857fe..4d4098f38e 100644 --- a/src/popups/file_revlog.rs +++ b/src/popups/file_revlog.rs @@ -507,10 +507,8 @@ impl Component for FileRevlogPopup { } else { self.hide_stacked(false); } - } else if key_match( - key, - self.key_config.keys.move_right, - ) && self.can_focus_diff() + } else if self.key_config.is_nav_right(key) + && self.can_focus_diff() { self.diff.focus(true); } else if key_match(key, self.key_config.keys.enter) { @@ -537,13 +535,9 @@ impl Component for FileRevlogPopup { ), )); } - } else if key_match(key, self.key_config.keys.move_up) - { + } else if self.key_config.is_nav_up(key) { self.move_selection(ScrollType::Up)?; - } else if key_match( - key, - self.key_config.keys.move_down, - ) { + } else if self.key_config.is_nav_down(key) { self.move_selection(ScrollType::Down)?; } else if key_match( key, diff --git a/src/popups/help.rs b/src/popups/help.rs index 0ca3bb0e27..ef30dc7411 100644 --- a/src/popups/help.rs +++ b/src/popups/help.rs @@ -137,10 +137,9 @@ impl Component for HelpPopup { if let Event::Key(e) = ev { if key_match(e, self.key_config.keys.exit_popup) { self.hide(); - } else if key_match(e, self.key_config.keys.move_down) - { + } else if self.key_config.is_nav_down(e) { self.move_selection(true); - } else if key_match(e, self.key_config.keys.move_up) { + } else if self.key_config.is_nav_up(e) { self.move_selection(false); } } diff --git a/src/popups/inspect_commit.rs b/src/popups/inspect_commit.rs index 4acfe88e90..a7b2d83ee4 100644 --- a/src/popups/inspect_commit.rs +++ b/src/popups/inspect_commit.rs @@ -161,15 +161,12 @@ impl Component for InspectCommitPopup { } else { self.hide_stacked(false); } - } else if key_match( - e, - self.key_config.keys.move_right, - ) && self.can_focus_diff() + } else if self.key_config.is_nav_right(e) + && self.can_focus_diff() { self.details.focus(false); self.diff.focus(true); - } else if key_match(e, self.key_config.keys.move_left) - { + } else if key_match(e, self.key_config.keys.move_left) { self.hide_stacked(false); } else if key_match( e, diff --git a/src/popups/options.rs b/src/popups/options.rs index b365c64817..28ac17aae3 100644 --- a/src/popups/options.rs +++ b/src/popups/options.rs @@ -322,23 +322,13 @@ impl Component for OptionsPopup { if let Event::Key(key) = &event { if key_match(key, self.key_config.keys.exit_popup) { self.hide(); - } else if key_match(key, self.key_config.keys.move_up) - { + } else if self.key_config.is_nav_up(key) { self.move_selection(true); - } else if key_match( - key, - self.key_config.keys.move_down, - ) { + } else if self.key_config.is_nav_down(key) { self.move_selection(false); - } else if key_match( - key, - self.key_config.keys.move_right, - ) { + } else if self.key_config.is_nav_right(key) { self.switch_option(true); - } else if key_match( - key, - self.key_config.keys.move_left, - ) { + } else if key_match(key, self.key_config.keys.move_left) { self.switch_option(false); } } diff --git a/src/popups/remotelist.rs b/src/popups/remotelist.rs index c09986aef3..1813d7d1ac 100644 --- a/src/popups/remotelist.rs +++ b/src/popups/remotelist.rs @@ -192,11 +192,11 @@ impl RemoteListPopup { fn move_event(&mut self, e: &KeyEvent) -> Result { if key_match(e, self.key_config.keys.exit_popup) { self.hide(); - } else if key_match(e, self.key_config.keys.move_down) { + } else if self.key_config.is_nav_down(e) { return self .move_selection(ScrollType::Up) .map(Into::into); - } else if key_match(e, self.key_config.keys.move_up) { + } else if self.key_config.is_nav_up(e) { return self .move_selection(ScrollType::Down) .map(Into::into); diff --git a/src/popups/reset.rs b/src/popups/reset.rs index d40d2a6ffe..43907b875d 100644 --- a/src/popups/reset.rs +++ b/src/popups/reset.rs @@ -230,13 +230,9 @@ impl Component for ResetPopup { if let Event::Key(key) = &event { if key_match(key, self.key_config.keys.exit_popup) { self.hide(); - } else if key_match( - key, - self.key_config.keys.move_down, - ) { + } else if self.key_config.is_nav_down(key) { self.change_kind(true); - } else if key_match(key, self.key_config.keys.move_up) - { + } else if self.key_config.is_nav_up(key) { self.change_kind(false); } else if key_match(key, self.key_config.keys.enter) { self.reset(); diff --git a/src/popups/submodules.rs b/src/popups/submodules.rs index f40fe0ba5e..8771607600 100644 --- a/src/popups/submodules.rs +++ b/src/popups/submodules.rs @@ -151,11 +151,11 @@ impl Component for SubmodulesListPopup { if let Event::Key(e) = ev { if key_match(e, self.key_config.keys.exit_popup) { self.hide(); - } else if key_match(e, self.key_config.keys.move_down) { + } else if self.key_config.is_nav_down(e) { return self .move_selection(ScrollType::Up) .map(Into::into); - } else if key_match(e, self.key_config.keys.move_up) { + } else if self.key_config.is_nav_up(e) { return self .move_selection(ScrollType::Down) .map(Into::into); diff --git a/src/popups/taglist.rs b/src/popups/taglist.rs index 959251b2c0..90ec14d031 100644 --- a/src/popups/taglist.rs +++ b/src/popups/taglist.rs @@ -187,13 +187,9 @@ impl Component for TagListPopup { if let Event::Key(key) = event { if key_match(key, self.key_config.keys.exit_popup) { self.hide(); - } else if key_match(key, self.key_config.keys.move_up) - { + } else if self.key_config.is_nav_up(key) { self.move_selection(ScrollType::Up); - } else if key_match( - key, - self.key_config.keys.move_down, - ) { + } else if self.key_config.is_nav_down(key) { self.move_selection(ScrollType::Down); } else if key_match( key, @@ -219,10 +215,8 @@ impl Component for TagListPopup { } else if key_match(key, self.key_config.keys.page_up) { self.move_selection(ScrollType::PageUp); - } else if key_match( - key, - self.key_config.keys.move_right, - ) && self.can_show_annotation() + } else if self.key_config.is_nav_right(key) + && self.can_show_annotation() { self.show_annotation(); } else if key_match( diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 9200c93f00..e17d3fe0b3 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -494,10 +494,8 @@ impl Component for Revlog { Ok(EventState::Consumed) }, ); - } else if key_match( - k, - self.key_config.keys.move_right, - ) && self.commit_details.is_visible() + } else if self.key_config.is_nav_right(k) + && self.commit_details.is_visible() { self.inspect_commit(); return Ok(EventState::Consumed); diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 135cf18e4e..e08c601735 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -839,10 +839,8 @@ impl Component for Status { { self.switch_focus(self.focus.toggled_focus()) .map(Into::into) - } else if key_match( - k, - self.key_config.keys.move_right, - ) && self.can_focus_diff() + } else if self.key_config.is_nav_right(k) + && self.can_focus_diff() { self.switch_focus(Focus::Diff).map(Into::into) } else if key_match( @@ -854,12 +852,12 @@ impl Component for Status { DiffTarget::WorkingDir => Focus::WorkDir, }) .map(Into::into) - } else if key_match(k, self.key_config.keys.move_down) + } else if self.key_config.is_nav_down(k) && self.focus == Focus::WorkDir && !self.index.is_empty() { self.switch_focus(Focus::Stage).map(Into::into) - } else if key_match(k, self.key_config.keys.move_up) + } else if self.key_config.is_nav_up(k) && self.focus == Focus::Stage && !self.index_wd.is_empty() { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f0ee1539f9..88589056f5 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -128,15 +128,15 @@ pub fn common_nav( key: &crossterm::event::KeyEvent, key_config: &SharedKeyConfig, ) -> Option { - if key_match(key, key_config.keys.move_down) { + if key_config.is_nav_down(key) { Some(MoveSelection::Down) - } else if key_match(key, key_config.keys.move_up) { + } else if key_config.is_nav_up(key) { Some(MoveSelection::Up) } else if key_match(key, key_config.keys.page_up) { Some(MoveSelection::PageUp) } else if key_match(key, key_config.keys.page_down) { Some(MoveSelection::PageDown) - } else if key_match(key, key_config.keys.move_right) { + } else if key_config.is_nav_right(key) { Some(MoveSelection::Right) } else if key_match(key, key_config.keys.move_left) { Some(MoveSelection::Left) From 6e68424ab1fe7682cae243489f3f4e2e753f3d39 Mon Sep 17 00:00:00 2001 From: Blagoy Simandoff Date: Sat, 25 Apr 2026 13:32:16 +0100 Subject: [PATCH 2/2] chore(checklist): make check + fmt + tests + changelog --- CHANGELOG.md | 3 ++ src/components/commit_details/details.rs | 51 +++++++----------- src/components/commit_details/mod.rs | 5 +- src/components/commitlist.rs | 66 +++++++++++------------- src/components/diff.rs | 3 +- src/components/status_tree.rs | 3 +- src/keys/key_config.rs | 65 +++++++++++++++++++++++ src/popups/compare_commits.rs | 3 +- src/popups/inspect_commit.rs | 3 +- src/popups/options.rs | 5 +- 10 files changed, 132 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bdad9ba8f..dc41871cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added +* vim-like navigation: `j`/`k` for up/down, `l` to open preview + ### Changed * use [tombi](https://github.com/tombi-toml/tombi) for all toml file formatting * open the external editor from the status diff view [[@WaterWhisperer](https://github.com/WaterWhisperer)] ([#2805](https://github.com/gitui-org/gitui/issues/2805)) diff --git a/src/components/commit_details/details.rs b/src/components/commit_details/details.rs index 102f952243..1f3d7dfbf4 100644 --- a/src/components/commit_details/details.rs +++ b/src/components/commit_details/details.rs @@ -361,37 +361,26 @@ impl Component for DetailsComponent { fn event(&mut self, event: &Event) -> Result { if self.focused { if let Event::Key(e) = event { - return Ok( - if self.key_config.is_nav_up(e) { - self.move_scroll_top(ScrollType::Up).into() - } else if self.key_config.is_nav_down(e) { - self.move_scroll_top(ScrollType::Down).into() - } else if key_match( - e, - self.key_config.keys.page_up, - ) { - self.move_scroll_top(ScrollType::PageUp) - .into() - } else if key_match( - e, - self.key_config.keys.page_down, - ) { - self.move_scroll_top(ScrollType::PageDown) - .into() - } else if key_match(e, self.key_config.keys.home) - || key_match(e, self.key_config.keys.shift_up) - { - self.move_scroll_top(ScrollType::Home).into() - } else if key_match(e, self.key_config.keys.end) - || key_match( - e, - self.key_config.keys.shift_down, - ) { - self.move_scroll_top(ScrollType::End).into() - } else { - EventState::NotConsumed - }, - ); + return Ok(if self.key_config.is_nav_up(e) { + self.move_scroll_top(ScrollType::Up).into() + } else if self.key_config.is_nav_down(e) { + self.move_scroll_top(ScrollType::Down).into() + } else if key_match(e, self.key_config.keys.page_up) { + self.move_scroll_top(ScrollType::PageUp).into() + } else if key_match(e, self.key_config.keys.page_down) + { + self.move_scroll_top(ScrollType::PageDown).into() + } else if key_match(e, self.key_config.keys.home) + || key_match(e, self.key_config.keys.shift_up) + { + self.move_scroll_top(ScrollType::Home).into() + } else if key_match(e, self.key_config.keys.end) + || key_match(e, self.key_config.keys.shift_down) + { + self.move_scroll_top(ScrollType::End).into() + } else { + EventState::NotConsumed + }); } } diff --git a/src/components/commit_details/mod.rs b/src/components/commit_details/mod.rs index 5f863ec9de..972443c59f 100644 --- a/src/components/commit_details/mod.rs +++ b/src/components/commit_details/mod.rs @@ -7,10 +7,7 @@ use super::{ Component, DrawableComponent, EventState, StatusTreeComponent, }; use crate::{ - accessors, - app::Environment, - keys::SharedKeyConfig, - strings, + accessors, app::Environment, keys::SharedKeyConfig, strings, }; use anyhow::Result; use asyncgit::{ diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 5ff58c95c5..7a55386ec5 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -830,41 +830,37 @@ impl DrawableComponent for CommitList { impl Component for CommitList { fn event(&mut self, ev: &Event) -> Result { if let Event::Key(k) = ev { - let selection_changed = - if self.key_config.is_nav_up(k) { - self.move_selection(ScrollType::Up)? - } else if self.key_config.is_nav_down(k) { - self.move_selection(ScrollType::Down)? - } else if key_match(k, self.key_config.keys.shift_up) - || key_match(k, self.key_config.keys.home) - { - self.move_selection(ScrollType::Home)? - } else if key_match( - k, - self.key_config.keys.shift_down, - ) || key_match(k, self.key_config.keys.end) - { - self.move_selection(ScrollType::End)? - } else if key_match(k, self.key_config.keys.page_up) { - self.move_selection(ScrollType::PageUp)? - } else if key_match(k, self.key_config.keys.page_down) - { - self.move_selection(ScrollType::PageDown)? - } else if key_match( - k, - self.key_config.keys.log_mark_commit, - ) { - self.mark(); - true - } else if key_match( - k, - self.key_config.keys.log_checkout_commit, - ) { - self.checkout(); - true - } else { - false - }; + let selection_changed = if self.key_config.is_nav_up(k) { + self.move_selection(ScrollType::Up)? + } else if self.key_config.is_nav_down(k) { + self.move_selection(ScrollType::Down)? + } else if key_match(k, self.key_config.keys.shift_up) + || key_match(k, self.key_config.keys.home) + { + self.move_selection(ScrollType::Home)? + } else if key_match(k, self.key_config.keys.shift_down) + || key_match(k, self.key_config.keys.end) + { + self.move_selection(ScrollType::End)? + } else if key_match(k, self.key_config.keys.page_up) { + self.move_selection(ScrollType::PageUp)? + } else if key_match(k, self.key_config.keys.page_down) { + self.move_selection(ScrollType::PageDown)? + } else if key_match( + k, + self.key_config.keys.log_mark_commit, + ) { + self.mark(); + true + } else if key_match( + k, + self.key_config.keys.log_checkout_commit, + ) { + self.checkout(); + true + } else { + false + }; return Ok(selection_changed.into()); } diff --git a/src/components/diff.rs b/src/components/diff.rs index d28a05996b..35a04a7787 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -864,7 +864,8 @@ impl Component for DiffComponent { self.horizontal_scroll .move_right(HorizontalScrollType::Right); Ok(EventState::Consumed) - } else if key_match(e, self.key_config.keys.move_left) { + } else if key_match(e, self.key_config.keys.move_left) + { self.horizontal_scroll .move_right(HorizontalScrollType::Left); Ok(EventState::Consumed) diff --git a/src/components/status_tree.rs b/src/components/status_tree.rs index 7c01ac38f3..eb15cacf43 100644 --- a/src/components/status_tree.rs +++ b/src/components/status_tree.rs @@ -543,7 +543,8 @@ impl Component for StatusTreeComponent { Ok(self .move_selection(MoveSelection::PageDown) .into()) - } else if key_match(e, self.key_config.keys.move_left) { + } else if key_match(e, self.key_config.keys.move_left) + { Ok(self .move_selection(MoveSelection::Left) .into()) diff --git a/src/keys/key_config.rs b/src/keys/key_config.rs index 07b2b6f515..4f5ebe5533 100644 --- a/src/keys/key_config.rs +++ b/src/keys/key_config.rs @@ -153,6 +153,71 @@ mod tests { use std::io::Write; use tempfile::NamedTempFile; + fn key_event(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent { + KeyEvent::new(code, modifiers) + } + + #[test] + fn test_vim_nav_up() { + let config = KeyConfig::default(); + assert!(config + .is_nav_up(&key_event(KeyCode::Up, KeyModifiers::NONE))); + assert!(config.is_nav_up(&key_event( + KeyCode::Char('k'), + KeyModifiers::NONE + ))); + assert!(!config.is_nav_up(&key_event( + KeyCode::Char('k'), + KeyModifiers::CONTROL + ))); + assert!(!config.is_nav_up(&key_event( + KeyCode::Char('j'), + KeyModifiers::NONE + ))); + } + + #[test] + fn test_vim_nav_down() { + let config = KeyConfig::default(); + assert!(config.is_nav_down(&key_event( + KeyCode::Down, + KeyModifiers::NONE + ))); + assert!(config.is_nav_down(&key_event( + KeyCode::Char('j'), + KeyModifiers::NONE + ))); + assert!(!config.is_nav_down(&key_event( + KeyCode::Char('j'), + KeyModifiers::CONTROL + ))); + assert!(!config.is_nav_down(&key_event( + KeyCode::Char('k'), + KeyModifiers::NONE + ))); + } + + #[test] + fn test_vim_nav_right() { + let config = KeyConfig::default(); + assert!(config.is_nav_right(&key_event( + KeyCode::Right, + KeyModifiers::NONE + ))); + assert!(config.is_nav_right(&key_event( + KeyCode::Char('l'), + KeyModifiers::NONE + ))); + assert!(!config.is_nav_right(&key_event( + KeyCode::Char('l'), + KeyModifiers::CONTROL + ))); + assert!(!config.is_nav_right(&key_event( + KeyCode::Char('j'), + KeyModifiers::NONE + ))); + } + #[test] fn test_get_hint() { let config = KeyConfig::default(); diff --git a/src/popups/compare_commits.rs b/src/popups/compare_commits.rs index f70f93bc67..5694720b84 100644 --- a/src/popups/compare_commits.rs +++ b/src/popups/compare_commits.rs @@ -129,7 +129,8 @@ impl Component for CompareCommitsPopup { { self.details.focus(false); self.diff.focus(true); - } else if key_match(e, self.key_config.keys.move_left) { + } else if key_match(e, self.key_config.keys.move_left) + { self.hide_stacked(false); } diff --git a/src/popups/inspect_commit.rs b/src/popups/inspect_commit.rs index a7b2d83ee4..1396debdf8 100644 --- a/src/popups/inspect_commit.rs +++ b/src/popups/inspect_commit.rs @@ -166,7 +166,8 @@ impl Component for InspectCommitPopup { { self.details.focus(false); self.diff.focus(true); - } else if key_match(e, self.key_config.keys.move_left) { + } else if key_match(e, self.key_config.keys.move_left) + { self.hide_stacked(false); } else if key_match( e, diff --git a/src/popups/options.rs b/src/popups/options.rs index 28ac17aae3..5ae1abaed1 100644 --- a/src/popups/options.rs +++ b/src/popups/options.rs @@ -328,7 +328,10 @@ impl Component for OptionsPopup { self.move_selection(false); } else if self.key_config.is_nav_right(key) { self.switch_option(true); - } else if key_match(key, self.key_config.keys.move_left) { + } else if key_match( + key, + self.key_config.keys.move_left, + ) { self.switch_option(false); } }