feat: file and status tab support pageup and pagedown (#2496)

This commit is contained in:
Fatpandac 2025-04-22 02:23:05 +08:00 committed by GitHub
parent ee5c243cbf
commit 706cdf9243
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 99 additions and 4 deletions

View file

@ -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)) * 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 ### 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)) * 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)) * 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)) * 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))

View file

@ -2,7 +2,7 @@ use crate::{
error::Result, filetreeitems::FileTreeItems, error::Result, filetreeitems::FileTreeItems,
tree_iter::TreeIterator, TreeItemInfo, tree_iter::TreeIterator, TreeItemInfo,
}; };
use std::{collections::BTreeSet, path::Path}; use std::{cell::Cell, collections::BTreeSet, path::Path};
/// ///
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -30,6 +30,7 @@ pub struct FileTree {
selection: Option<usize>, selection: Option<usize>,
// caches the absolute selection translated to visual index // caches the absolute selection translated to visual index
visual_selection: Option<VisualSelection>, visual_selection: Option<VisualSelection>,
pub window_height: Cell<Option<usize>>,
} }
impl FileTree { impl FileTree {
@ -42,6 +43,7 @@ impl FileTree {
items: FileTreeItems::new(list, collapsed)?, items: FileTreeItems::new(list, collapsed)?,
selection: if list.is_empty() { None } else { Some(0) }, selection: if list.is_empty() { None } else { Some(0) },
visual_selection: None, visual_selection: None,
window_height: None.into(),
}; };
new_self.visual_selection = new_self.calc_visual_selection(); new_self.visual_selection = new_self.calc_visual_selection();
@ -112,6 +114,18 @@ impl FileTree {
} }
} }
fn selection_page_updown(
&self,
range: impl Iterator<Item = usize>,
) -> Option<usize> {
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 { pub fn move_selection(&mut self, dir: MoveSelection) -> bool {
self.selection.is_some_and(|selection| { self.selection.is_some_and(|selection| {
@ -130,9 +144,13 @@ impl FileTree {
Self::selection_start(selection) Self::selection_start(selection)
} }
MoveSelection::End => self.selection_end(selection), MoveSelection::End => self.selection_end(selection),
MoveSelection::PageDown | MoveSelection::PageUp => { MoveSelection::PageUp => {
None self.selection_page_updown((0..=selection).rev())
} }
MoveSelection::PageDown => self
.selection_page_updown(
selection..(self.items.len()),
),
}; };
let changed_index = let changed_index =
@ -514,4 +532,36 @@ mod test {
assert_eq!(s.count, 3); assert_eq!(s.count, 3);
assert_eq!(s.index, 2); 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));
}
} }

View file

@ -275,6 +275,8 @@ impl RevisionFilesComponent {
let tree_height = usize::from(area.height.saturating_sub(2)); let tree_height = usize::from(area.height.saturating_sub(2));
let tree_width = usize::from(area.width); let tree_width = usize::from(area.width);
self.tree.window_height.set(Some(tree_height));
self.tree.visual_selection().map_or_else( self.tree.visual_selection().map_or_else(
|| { || {
self.scroll.reset(); self.scroll.reset();

View file

@ -351,6 +351,7 @@ impl DrawableComponent for StatusTreeComponent {
.map(|idx| idx.saturating_sub(selection_offset)) .map(|idx| idx.saturating_sub(selection_offset))
.unwrap_or_default(); .unwrap_or_default();
let tree_height = r.height.saturating_sub(2) as usize; 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.set(ui::calc_scroll_top(
self.scroll_top.get(), self.scroll_top.get(),
@ -504,6 +505,15 @@ impl Component for StatusTreeComponent {
|| key_match(e, self.key_config.keys.shift_down) || key_match(e, self.key_config.keys.shift_down)
{ {
Ok(self.move_selection(MoveSelection::End).into()) 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) } else if key_match(e, self.key_config.keys.move_left)
{ {
Ok(self Ok(self

View file

@ -3,7 +3,7 @@ use super::filetree::{
}; };
use anyhow::Result; use anyhow::Result;
use asyncgit::StatusItem; use asyncgit::StatusItem;
use std::{cmp, collections::BTreeSet}; use std::{cell::Cell, cmp, collections::BTreeSet};
//TODO: use new `filetreelist` crate //TODO: use new `filetreelist` crate
@ -16,6 +16,8 @@ pub struct StatusTree {
// some folders may be folded up, this allows jumping // some folders may be folded up, this allows jumping
// over folders which are folded into their parent // over folders which are folded into their parent
pub available_selections: Vec<usize>, pub available_selections: Vec<usize>,
pub window_height: Cell<Option<usize>>,
} }
/// ///
@ -27,6 +29,8 @@ pub enum MoveSelection {
Right, Right,
Home, Home,
End, End,
PageDown,
PageUp,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -143,6 +147,15 @@ impl StatusTree {
} }
MoveSelection::Home => SelectionChange::new(0, false), MoveSelection::Home => SelectionChange::new(0, false),
MoveSelection::End => self.selection_end(), 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 = let changed_index =
@ -283,6 +296,25 @@ impl StatusTree {
SelectionChange::new(new_index, false) SelectionChange::new(new_index, false)
} }
fn selection_page_updown(
&self,
current_index: usize,
range: impl Iterator<Item = usize>,
) -> 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 { fn is_visible_index(&self, idx: usize) -> bool {
self.tree[idx].info.visible self.tree[idx].info.visible
} }