diff --git a/CHANGELOG.md b/CHANGELOG.md index a7c6ee12f7..72bce6d800 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 +- `page_half_up` and `page_half_down` keybindings [@jyn514](https://github.com/jyn514) ([109](https://github.com/extrawurst/gitui/issues/109)) + ### Changed * use [tombi](https://github.com/tombi-toml/tombi) for all toml file formatting diff --git a/filetreelist/src/filetree.rs b/filetreelist/src/filetree.rs index 866c2f996f..40549e4c7c 100644 --- a/filetreelist/src/filetree.rs +++ b/filetreelist/src/filetree.rs @@ -14,7 +14,9 @@ pub enum MoveSelection { Top, End, PageDown, + PageHalfDown, PageUp, + PageHalfUp, } #[derive(Clone, Copy, PartialEq)] @@ -124,8 +126,12 @@ impl FileTree { &self, current_index: usize, direction: Direction, + half_page: bool, ) -> Option { - let page_size = self.window_height.get().unwrap_or(0); + let mut page_size = self.window_height.get().unwrap_or(0); + if half_page { + page_size = page_size.div_ceil(2); + } if direction == Direction::Up { self.get_new_selection( @@ -156,12 +162,28 @@ impl FileTree { } MoveSelection::Top => Some(0), MoveSelection::End => self.selection_end(), - MoveSelection::PageUp => self - .selection_page_updown(selection, Direction::Up), + MoveSelection::PageUp => self.selection_page_updown( + selection, + Direction::Up, + false, + ), + MoveSelection::PageHalfUp => self + .selection_page_updown( + selection, + Direction::Up, + true, + ), MoveSelection::PageDown => self .selection_page_updown( selection, Direction::Down, + false, + ), + MoveSelection::PageHalfDown => self + .selection_page_updown( + selection, + Direction::Down, + true, ), }; diff --git a/src/components/commit_details/details.rs b/src/components/commit_details/details.rs index 42825900a0..8c2772c695 100644 --- a/src/components/commit_details/details.rs +++ b/src/components/commit_details/details.rs @@ -375,12 +375,24 @@ impl Component for DetailsComponent { ) { self.move_scroll_top(ScrollType::PageUp) .into() + } else if key_match( + e, + self.key_config.keys.page_half_up, + ) { + self.move_scroll_top(ScrollType::PageHalfUp) + .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.page_half_down, + ) { + self.move_scroll_top(ScrollType::PageHalfDown) + .into() } else if key_match(e, self.key_config.keys.home) || key_match(e, self.key_config.keys.shift_up) { diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index a84060151d..5b960a9299 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -371,9 +371,15 @@ impl CommitList { ScrollType::PageUp => { self.selection.saturating_sub(page_offset) } + ScrollType::PageHalfUp => { + self.selection.saturating_sub(page_offset.div_ceil(2)) + } ScrollType::PageDown => { self.selection.saturating_add(page_offset) } + ScrollType::PageHalfDown => { + self.selection.saturating_add(page_offset.div_ceil(2)) + } ScrollType::Home => 0, ScrollType::End => self.selection_max(), }; @@ -848,9 +854,19 @@ impl Component for CommitList { 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_half_up, + ) { + self.move_selection(ScrollType::PageHalfUp)? } 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.page_half_down, + ) { + self.move_selection(ScrollType::PageHalfDown)? } else if key_match( k, self.key_config.keys.log_mark_commit, diff --git a/src/components/diff.rs b/src/components/diff.rs index 2eb492db29..309cbe2f61 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -236,12 +236,24 @@ impl DiffComponent { as usize, ) } + ScrollType::PageHalfDown => { + self.selection.get_bottom().saturating_add( + self.current_size.get().1.div_ceil(2) + as usize, + ) + } ScrollType::PageUp => { self.selection.get_top().saturating_sub( self.current_size.get().1.saturating_sub(1) as usize, ) } + ScrollType::PageHalfUp => { + self.selection.get_top().saturating_sub( + self.current_size.get().1.div_ceil(2) + as usize, + ) + } }; self.update_selection(new_start); @@ -848,10 +860,22 @@ impl Component for DiffComponent { } else if key_match(e, self.key_config.keys.page_up) { self.move_selection(ScrollType::PageUp); Ok(EventState::Consumed) + } else if key_match( + e, + self.key_config.keys.page_half_up, + ) { + self.move_selection(ScrollType::PageHalfUp); + Ok(EventState::Consumed) } else if key_match(e, self.key_config.keys.page_down) { self.move_selection(ScrollType::PageDown); Ok(EventState::Consumed) + } else if key_match( + e, + self.key_config.keys.page_half_down, + ) { + self.move_selection(ScrollType::PageHalfDown); + Ok(EventState::Consumed) } else if key_match( e, self.key_config.keys.move_right, diff --git a/src/components/mod.rs b/src/components/mod.rs index 51322e18ae..a5407767d6 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -176,7 +176,9 @@ pub enum ScrollType { Home, End, PageUp, + PageHalfUp, PageDown, + PageHalfDown, } #[derive(Copy, Clone)] diff --git a/src/components/status_tree.rs b/src/components/status_tree.rs index ac4fc9f6a8..492193c5c1 100644 --- a/src/components/status_tree.rs +++ b/src/components/status_tree.rs @@ -471,6 +471,7 @@ impl Component for StatusTreeComponent { CommandBlocking::PassingOn } + #[allow(clippy::too_many_lines)] fn event(&mut self, ev: &Event) -> Result { if self.focused { if let Event::Key(e) = ev { @@ -539,11 +540,25 @@ impl Component for StatusTreeComponent { Ok(self .move_selection(MoveSelection::PageUp) .into()) + } else if key_match( + e, + self.key_config.keys.page_half_up, + ) { + Ok(self + .move_selection(MoveSelection::PageHalfUp) + .into()) } else if key_match(e, self.key_config.keys.page_down) { Ok(self .move_selection(MoveSelection::PageDown) .into()) + } else if key_match( + e, + self.key_config.keys.page_half_down, + ) { + Ok(self + .move_selection(MoveSelection::PageHalfDown) + .into()) } else if key_match(e, self.key_config.keys.move_left) { Ok(self diff --git a/src/components/utils/scroll_vertical.rs b/src/components/utils/scroll_vertical.rs index 1f7ed77918..70351dbd20 100644 --- a/src/components/utils/scroll_vertical.rs +++ b/src/components/utils/scroll_vertical.rs @@ -38,9 +38,13 @@ impl VerticalScroll { ScrollType::PageDown => old .saturating_sub(1) .saturating_add(self.visual_height.get()), + ScrollType::PageHalfDown => old + .saturating_add(self.visual_height.get().div_ceil(2)), ScrollType::PageUp => old .saturating_add(1) .saturating_sub(self.visual_height.get()), + ScrollType::PageHalfUp => old + .saturating_sub(self.visual_height.get().div_ceil(2)), ScrollType::Home => 0, ScrollType::End => max, }; @@ -222,5 +226,23 @@ mod tests { assert!(!scroll.move_top(ScrollType::PageUp)); assert_eq!(scroll.get_top(), 0); + + assert!(!scroll.move_top(ScrollType::PageHalfUp)); + assert_eq!(scroll.get_top(), 0); + + assert!(scroll.move_top(ScrollType::PageHalfDown)); + assert_eq!(scroll.get_top(), 4); + + assert!(scroll.move_top(ScrollType::PageHalfDown)); + assert_eq!(scroll.get_top(), 8); + + assert!(scroll.move_top(ScrollType::PageHalfDown)); + assert_eq!(scroll.get_top(), 10); + + assert!(!scroll.move_top(ScrollType::PageHalfDown)); + assert_eq!(scroll.get_top(), 10); + + assert!(scroll.move_top(ScrollType::PageHalfUp)); + assert_eq!(scroll.get_top(), 6); } } diff --git a/src/components/utils/statustree.rs b/src/components/utils/statustree.rs index 6147e57ead..a94ad8ccf9 100644 --- a/src/components/utils/statustree.rs +++ b/src/components/utils/statustree.rs @@ -30,7 +30,9 @@ pub enum MoveSelection { Home, End, PageDown, + PageHalfDown, PageUp, + PageHalfUp, } #[derive(Copy, Clone, Debug)] @@ -146,11 +148,25 @@ impl StatusTree { MoveSelection::PageUp => self.selection_page_updown( selection, (0..=selection).rev(), + false, ), + MoveSelection::PageHalfUp => self + .selection_page_updown( + selection, + (0..=selection).rev(), + true, + ), MoveSelection::PageDown => self .selection_page_updown( selection, selection..(self.tree.len()), + false, + ), + MoveSelection::PageHalfDown => self + .selection_page_updown( + selection, + selection..(self.tree.len()), + true, ), }; @@ -295,8 +311,12 @@ impl StatusTree { &self, current_index: usize, range: impl Iterator, + half_page: bool, ) -> SelectionChange { - let page_size = self.window_height.get().unwrap_or(0); + let mut page_size = self.window_height.get().unwrap_or(0); + if half_page { + page_size = page_size.div_ceil(2); + } let new_index = range .filter(|index| { diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs index 24a9507a49..eab63fc42f 100644 --- a/src/keys/key_list.rs +++ b/src/keys/key_list.rs @@ -63,7 +63,9 @@ pub struct KeysList { pub popup_up: GituiKeyEvent, pub popup_down: GituiKeyEvent, pub page_down: GituiKeyEvent, + pub page_half_down: GituiKeyEvent, pub page_up: GituiKeyEvent, + pub page_half_up: GituiKeyEvent, pub shift_up: GituiKeyEvent, pub shift_down: GituiKeyEvent, pub enter: GituiKeyEvent, @@ -162,6 +164,8 @@ impl Default for KeysList { popup_down: GituiKeyEvent::new(KeyCode::Down, KeyModifiers::empty()), page_down: GituiKeyEvent::new(KeyCode::PageDown, KeyModifiers::empty()), page_up: GituiKeyEvent::new(KeyCode::PageUp, KeyModifiers::empty()), + page_half_down: GituiKeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL), + page_half_up: GituiKeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL), shift_up: GituiKeyEvent::new(KeyCode::Up, KeyModifiers::SHIFT), shift_down: GituiKeyEvent::new(KeyCode::Down, KeyModifiers::SHIFT), enter: GituiKeyEvent::new(KeyCode::Enter, KeyModifiers::empty()), diff --git a/src/popups/blame_file.rs b/src/popups/blame_file.rs index b7d1304b33..83f1d9e61a 100644 --- a/src/popups/blame_file.rs +++ b/src/popups/blame_file.rs @@ -286,9 +286,19 @@ impl Component for BlameFilePopup { self.key_config.keys.page_down, ) { self.move_selection(ScrollType::PageDown); + } else if key_match( + key, + self.key_config.keys.page_half_down, + ) { + self.move_selection(ScrollType::PageHalfDown); } 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.page_half_up, + ) { + self.move_selection(ScrollType::PageHalfUp); } else if key_match( key, self.key_config.keys.move_right, @@ -728,11 +738,25 @@ impl BlameFilePopup { ScrollType::PageUp => old_selection.saturating_sub( self.current_height.get().saturating_sub(2), ), + ScrollType::PageHalfUp => old_selection.saturating_sub( + self.current_height + .get() + .saturating_sub(2) + .div_ceil(2), + ), ScrollType::PageDown => old_selection .saturating_add( self.current_height.get().saturating_sub(2), ) .min(max_selection), + ScrollType::PageHalfDown => old_selection + .saturating_add( + self.current_height + .get() + .saturating_sub(2) + .div_ceil(2), + ) + .min(max_selection), }; let needs_update = new_selection != old_selection; diff --git a/src/popups/branchlist.rs b/src/popups/branchlist.rs index fa66ffffad..550b919634 100644 --- a/src/popups/branchlist.rs +++ b/src/popups/branchlist.rs @@ -270,10 +270,18 @@ impl BranchListPopup { return self .move_selection(ScrollType::PageDown) .map(Into::into); + } else if key_match(e, self.key_config.keys.page_half_down) { + return self + .move_selection(ScrollType::PageHalfDown) + .map(Into::into); } else if key_match(e, self.key_config.keys.page_up) { return self .move_selection(ScrollType::PageUp) .map(Into::into); + } else if key_match(e, self.key_config.keys.page_half_up) { + return self + .move_selection(ScrollType::PageHalfUp) + .map(Into::into); } else if key_match(e, self.key_config.keys.home) { return self .move_selection(ScrollType::Home) @@ -436,9 +444,17 @@ impl BranchListPopup { ScrollType::PageDown => self .selection .saturating_add(self.current_height.get()), + ScrollType::PageHalfDown => { + self.selection.saturating_add( + self.current_height.get().div_ceil(2), + ) + } ScrollType::PageUp => self .selection .saturating_sub(self.current_height.get()), + ScrollType::PageHalfUp => self.selection.saturating_sub( + self.current_height.get().div_ceil(2), + ), ScrollType::Home => 0, ScrollType::End => { let num_branches: u16 = diff --git a/src/popups/file_revlog.rs b/src/popups/file_revlog.rs index 771ae857fe..197285b9c2 100644 --- a/src/popups/file_revlog.rs +++ b/src/popups/file_revlog.rs @@ -318,9 +318,17 @@ impl FileRevlogPopup { ScrollType::End => max_selection, ScrollType::PageUp => old_selection .saturating_sub(height_in_items.saturating_sub(2)), + ScrollType::PageHalfUp => old_selection.saturating_sub( + height_in_items.saturating_sub(2).div_ceil(2), + ), ScrollType::PageDown => old_selection .saturating_add(height_in_items.saturating_sub(2)) .min(max_selection), + ScrollType::PageHalfDown => old_selection + .saturating_add( + height_in_items.saturating_sub(2).div_ceil(2), + ) + .min(max_selection), }; let needs_update = new_selection != old_selection; @@ -564,11 +572,21 @@ impl Component for FileRevlogPopup { } 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.page_half_up, + ) { + self.move_selection(ScrollType::PageHalfUp)?; } else if key_match( key, self.key_config.keys.page_down, ) { self.move_selection(ScrollType::PageDown)?; + } else if key_match( + key, + self.key_config.keys.page_half_down, + ) { + self.move_selection(ScrollType::PageHalfDown)?; } } diff --git a/src/popups/remotelist.rs b/src/popups/remotelist.rs index c09986aef3..018209e5a5 100644 --- a/src/popups/remotelist.rs +++ b/src/popups/remotelist.rs @@ -204,10 +204,18 @@ impl RemoteListPopup { return self .move_selection(ScrollType::PageDown) .map(Into::into); + } else if key_match(e, self.key_config.keys.page_half_down) { + return self + .move_selection(ScrollType::PageHalfDown) + .map(Into::into); } else if key_match(e, self.key_config.keys.page_up) { return self .move_selection(ScrollType::PageUp) .map(Into::into); + } else if key_match(e, self.key_config.keys.page_half_up) { + return self + .move_selection(ScrollType::PageHalfUp) + .map(Into::into); } else if key_match(e, self.key_config.keys.home) { return self .move_selection(ScrollType::Home) @@ -397,9 +405,17 @@ impl RemoteListPopup { ScrollType::PageDown => self .selection .saturating_add(self.current_height.get()), + ScrollType::PageHalfDown => { + self.selection.saturating_add( + self.current_height.get().div_ceil(2), + ) + } ScrollType::PageUp => self .selection .saturating_sub(self.current_height.get()), + ScrollType::PageHalfUp => self.selection.saturating_sub( + self.current_height.get().div_ceil(2), + ), ScrollType::Home => 0, ScrollType::End => { let num_branches: u16 = diff --git a/src/popups/submodules.rs b/src/popups/submodules.rs index f40fe0ba5e..42f711ac65 100644 --- a/src/popups/submodules.rs +++ b/src/popups/submodules.rs @@ -163,10 +163,22 @@ impl Component for SubmodulesListPopup { return self .move_selection(ScrollType::PageDown) .map(Into::into); + } else if key_match( + e, + self.key_config.keys.page_half_down, + ) { + return self + .move_selection(ScrollType::PageHalfDown) + .map(Into::into); } else if key_match(e, self.key_config.keys.page_up) { return self .move_selection(ScrollType::PageUp) .map(Into::into); + } else if key_match(e, self.key_config.keys.page_half_up) + { + return self + .move_selection(ScrollType::PageHalfUp) + .map(Into::into); } else if key_match(e, self.key_config.keys.home) { return self .move_selection(ScrollType::Home) @@ -301,9 +313,17 @@ impl SubmodulesListPopup { ScrollType::PageDown => self .selection .saturating_add(self.current_height.get()), + ScrollType::PageHalfDown => { + self.selection.saturating_add( + self.current_height.get().div_ceil(2), + ) + } ScrollType::PageUp => self .selection .saturating_sub(self.current_height.get()), + ScrollType::PageHalfUp => self.selection.saturating_sub( + self.current_height.get().div_ceil(2), + ), ScrollType::Home => 0, ScrollType::End => { let count: u16 = self.submodules.len().try_into()?; diff --git a/src/popups/taglist.rs b/src/popups/taglist.rs index 959251b2c0..abd9f7a365 100644 --- a/src/popups/taglist.rs +++ b/src/popups/taglist.rs @@ -216,9 +216,19 @@ impl Component for TagListPopup { self.key_config.keys.page_down, ) { self.move_selection(ScrollType::PageDown); + } else if key_match( + key, + self.key_config.keys.page_half_down, + ) { + self.move_selection(ScrollType::PageHalfDown); } 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.page_half_up, + ) { + self.move_selection(ScrollType::PageHalfUp); } else if key_match( key, self.key_config.keys.move_right, @@ -400,11 +410,17 @@ impl TagListPopup { ScrollType::PageUp => old_selection.saturating_sub( self.current_height.get().saturating_sub(1), ), + ScrollType::PageHalfUp => old_selection.saturating_sub( + self.current_height.get().div_ceil(2), + ), ScrollType::PageDown => old_selection .saturating_add( self.current_height.get().saturating_sub(1), ) .min(max_selection), + ScrollType::PageHalfDown => old_selection + .saturating_add(self.current_height.get().div_ceil(2)) + .min(max_selection), }; let needs_update = new_selection != old_selection; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f0ee1539f9..91dc122d60 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -132,10 +132,12 @@ pub fn common_nav( Some(MoveSelection::Down) } else if key_match(key, key_config.keys.move_up) { 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_half_up) { + Some(MoveSelection::PageHalfUp) } else if key_match(key, key_config.keys.page_down) { Some(MoveSelection::PageDown) + } else if key_match(key, key_config.keys.page_half_down) { + Some(MoveSelection::PageHalfDown) } else if key_match(key, key_config.keys.move_right) { Some(MoveSelection::Right) } else if key_match(key, key_config.keys.move_left) {