diff --git a/CHANGELOG.md b/CHANGELOG.md index a8fe41fdd9..e1c4aeeb3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * execute git-hooks directly if possible (on *nix) else use sh instead of bash (without reading SHELL variable) [[@Joshix](https://github.com/Joshix-1)] ([#2483](https://github.com/extrawurst/gitui/pull/2483)) ### Added +* Files and status tab support pageUp and pageDown [[@fatpandac](https://github.com/fatpandac)] ([#1951](https://github.com/extrawurst/gitui/issues/1951)) * support loading custom syntax highlighting themes from a file [[@acuteenvy](https://github.com/acuteenvy)] ([#2565](https://github.com/gitui-org/gitui/pull/2565)) * Select syntax highlighting theme out of the defaults from syntect [[@vasilismanol](https://github.com/vasilismanol)] ([#1931](https://github.com/extrawurst/gitui/issues/1931)) * new command-line option to override the default log file path (`--logfile`) [[@acuteenvy](https://github.com/acuteenvy)] ([#2539](https://github.com/gitui-org/gitui/pull/2539)) diff --git a/filetreelist/src/filetree.rs b/filetreelist/src/filetree.rs index 023da8caf9..7ffd5c01b9 100644 --- a/filetreelist/src/filetree.rs +++ b/filetreelist/src/filetree.rs @@ -2,7 +2,7 @@ use crate::{ error::Result, filetreeitems::FileTreeItems, tree_iter::TreeIterator, TreeItemInfo, }; -use std::{collections::BTreeSet, path::Path}; +use std::{cell::Cell, collections::BTreeSet, path::Path}; /// #[derive(Copy, Clone, Debug)] @@ -30,6 +30,7 @@ pub struct FileTree { selection: Option, // caches the absolute selection translated to visual index visual_selection: Option, + pub window_height: Cell>, } impl FileTree { @@ -42,6 +43,7 @@ impl FileTree { items: FileTreeItems::new(list, collapsed)?, selection: if list.is_empty() { None } else { Some(0) }, visual_selection: None, + window_height: None.into(), }; new_self.visual_selection = new_self.calc_visual_selection(); @@ -112,6 +114,18 @@ impl FileTree { } } + fn selection_page_updown( + &self, + range: impl Iterator, + ) -> Option { + let page_size = self.window_height.get().unwrap_or(0); + + range + .filter(|index| self.is_visible_index(*index)) + .take(page_size) + .last() + } + /// pub fn move_selection(&mut self, dir: MoveSelection) -> bool { self.selection.is_some_and(|selection| { @@ -130,9 +144,13 @@ impl FileTree { Self::selection_start(selection) } MoveSelection::End => self.selection_end(selection), - MoveSelection::PageDown | MoveSelection::PageUp => { - None + MoveSelection::PageUp => { + self.selection_page_updown((0..=selection).rev()) } + MoveSelection::PageDown => self + .selection_page_updown( + selection..(self.items.len()), + ), }; let changed_index = @@ -514,4 +532,36 @@ mod test { assert_eq!(s.count, 3); assert_eq!(s.index, 2); } + + #[test] + fn test_selection_page_updown() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/b/c2"), // + Path::new("a/d"), // + Path::new("a/e"), // + ]; + + //0 a/ + //1 b/ + //2 c + //3 c2 + //4 d + //5 e + + let mut tree = + FileTree::new(&items, &BTreeSet::new()).unwrap(); + + tree.window_height.set(Some(3)); + + tree.selection = Some(0); + assert!(tree.move_selection(MoveSelection::PageDown)); + assert_eq!(tree.selection, Some(2)); + assert!(tree.move_selection(MoveSelection::PageDown)); + assert_eq!(tree.selection, Some(4)); + assert!(tree.move_selection(MoveSelection::PageUp)); + assert_eq!(tree.selection, Some(2)); + assert!(tree.move_selection(MoveSelection::PageUp)); + assert_eq!(tree.selection, Some(0)); + } } diff --git a/src/components/revision_files.rs b/src/components/revision_files.rs index 8a56071684..f3fec043d0 100644 --- a/src/components/revision_files.rs +++ b/src/components/revision_files.rs @@ -275,6 +275,8 @@ impl RevisionFilesComponent { let tree_height = usize::from(area.height.saturating_sub(2)); let tree_width = usize::from(area.width); + self.tree.window_height.set(Some(tree_height)); + self.tree.visual_selection().map_or_else( || { self.scroll.reset(); diff --git a/src/components/status_tree.rs b/src/components/status_tree.rs index 4fb762c1af..1d5f39cb6f 100644 --- a/src/components/status_tree.rs +++ b/src/components/status_tree.rs @@ -351,6 +351,7 @@ impl DrawableComponent for StatusTreeComponent { .map(|idx| idx.saturating_sub(selection_offset)) .unwrap_or_default(); let tree_height = r.height.saturating_sub(2) as usize; + self.tree.window_height.set(Some(tree_height)); self.scroll_top.set(ui::calc_scroll_top( self.scroll_top.get(), @@ -504,6 +505,15 @@ impl Component for StatusTreeComponent { || key_match(e, self.key_config.keys.shift_down) { Ok(self.move_selection(MoveSelection::End).into()) + } else if key_match(e, self.key_config.keys.page_up) { + Ok(self + .move_selection(MoveSelection::PageUp) + .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.move_left) { Ok(self diff --git a/src/components/utils/statustree.rs b/src/components/utils/statustree.rs index 2118ab0ed8..32e6c22025 100644 --- a/src/components/utils/statustree.rs +++ b/src/components/utils/statustree.rs @@ -3,7 +3,7 @@ use super::filetree::{ }; use anyhow::Result; use asyncgit::StatusItem; -use std::{cmp, collections::BTreeSet}; +use std::{cell::Cell, cmp, collections::BTreeSet}; //TODO: use new `filetreelist` crate @@ -16,6 +16,8 @@ pub struct StatusTree { // some folders may be folded up, this allows jumping // over folders which are folded into their parent pub available_selections: Vec, + + pub window_height: Cell>, } /// @@ -27,6 +29,8 @@ pub enum MoveSelection { Right, Home, End, + PageDown, + PageUp, } #[derive(Copy, Clone, Debug)] @@ -143,6 +147,15 @@ impl StatusTree { } MoveSelection::Home => SelectionChange::new(0, false), MoveSelection::End => self.selection_end(), + MoveSelection::PageUp => self.selection_page_updown( + selection, + (0..=selection).rev(), + ), + MoveSelection::PageDown => self + .selection_page_updown( + selection, + selection..(self.tree.len()), + ), }; let changed_index = @@ -283,6 +296,25 @@ impl StatusTree { SelectionChange::new(new_index, false) } + fn selection_page_updown( + &self, + current_index: usize, + range: impl Iterator, + ) -> SelectionChange { + let page_size = self.window_height.get().unwrap_or(0); + + let new_index = range + .filter(|index| { + self.available_selections.contains(index) + && self.is_visible_index(*index) + }) + .take(page_size) + .last() + .unwrap_or(current_index); + + SelectionChange::new(new_index, false) + } + fn is_visible_index(&self, idx: usize) -> bool { self.tree[idx].info.visible }