diff --git a/src/app.rs b/src/app.rs index 5c4c8437ae..6c1afe3f05 100644 --- a/src/app.rs +++ b/src/app.rs @@ -328,13 +328,13 @@ impl App { MouseEventKind::ScrollUp => { let scroll_lines = self.state.config.general.mouse_scroll_lines; if scroll_lines > 0 { - self.screen_mut().scroll_up(scroll_lines); + self.screen_mut().scroll_view_up(scroll_lines); } } MouseEventKind::ScrollDown => { let scroll_lines = self.state.config.general.mouse_scroll_lines; if scroll_lines > 0 { - self.screen_mut().scroll_down(scroll_lines); + self.screen_mut().scroll_view_down(scroll_lines); } } _ => return Ok(false), diff --git a/src/default_config.toml b/src/default_config.toml index b007413237..800149cadd 100644 --- a/src/default_config.toml +++ b/src/default_config.toml @@ -118,6 +118,8 @@ root.half_page_up = ["ctrl+u"] root.half_page_down = ["ctrl+d"] root.move_top = ["g+g"] root.move_bottom = ["G"] +root.scroll_view_up = ["ctrl+y"] +root.scroll_view_down = ["ctrl+e"] root.show_refs = ["Y"] root.show = ["enter"] root.discard = ["K"] diff --git a/src/ops/editor.rs b/src/ops/editor.rs index 4a48aea51f..500445c6c2 100644 --- a/src/ops/editor.rs +++ b/src/ops/editor.rs @@ -277,13 +277,13 @@ pub(crate) struct HalfPageUp; impl OpTrait for HalfPageUp { fn get_action(&self, _target: &ItemData) -> Option { Some(Rc::new(|app, _term| { - app.screen_mut().scroll_half_page_up(); + app.screen_mut().scroll_view_half_page_up(); Ok(()) })) } fn display(&self, _state: &State) -> String { - "Half page up".into() + "Scroll half page up".into() } } @@ -291,13 +291,43 @@ pub(crate) struct HalfPageDown; impl OpTrait for HalfPageDown { fn get_action(&self, _target: &ItemData) -> Option { Some(Rc::new(|app, _term| { - app.screen_mut().scroll_half_page_down(); + app.screen_mut().scroll_view_half_page_down(); Ok(()) })) } fn display(&self, _state: &State) -> String { - "Half page down".into() + "Scroll half page down".into() + } +} + +pub(crate) struct ScrollViewUp; +impl OpTrait for ScrollViewUp { + fn get_action(&self, _target: &ItemData) -> Option { + Some(Rc::new(|app, _term| { + app.close_menu(); + app.screen_mut().scroll_view_up(1); + Ok(()) + })) + } + + fn display(&self, _state: &State) -> String { + "Scroll view up".into() + } +} + +pub(crate) struct ScrollViewDown; +impl OpTrait for ScrollViewDown { + fn get_action(&self, _target: &ItemData) -> Option { + Some(Rc::new(|app, _term| { + app.close_menu(); + app.screen_mut().scroll_view_down(1); + Ok(()) + })) + } + + fn display(&self, _state: &State) -> String { + "Scroll view down".into() } } diff --git a/src/ops/mod.rs b/src/ops/mod.rs index 285e1a7d00..37fedd322a 100644 --- a/src/ops/mod.rs +++ b/src/ops/mod.rs @@ -125,6 +125,8 @@ pub(crate) enum Op { MoveBottom, HalfPageUp, HalfPageDown, + ScrollViewUp, + ScrollViewDown, Refresh, Quit, @@ -157,6 +159,8 @@ impl Op { Op::HalfPageDown => Box::new(editor::HalfPageDown), Op::MoveTop => Box::new(editor::MoveTop), Op::MoveBottom => Box::new(editor::MoveBottom), + Op::ScrollViewUp => Box::new(editor::ScrollViewUp), + Op::ScrollViewDown => Box::new(editor::ScrollViewDown), Op::Checkout => Box::new(branch::Checkout), Op::CheckoutNewBranch => Box::new(branch::CheckoutNewBranch), Op::Spinoff => Box::new(branch::Spinoff), diff --git a/src/screen/mod.rs b/src/screen/mod.rs index 4020ce404e..d7ded5a596 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -171,44 +171,24 @@ impl Screen { .unwrap_or(self.cursor) } - pub(crate) fn scroll_half_page_up(&mut self) { + pub(crate) fn scroll_view_half_page_up(&mut self) { let half_screen = self.size.height as usize / 2; - self.scroll = self.scroll.saturating_sub(half_screen); - - let nav_mode = self.selected_item_nav_mode(); - self.update_cursor(nav_mode); + self.scroll_view_up(half_screen); } - pub(crate) fn scroll_half_page_down(&mut self) { + pub(crate) fn scroll_view_half_page_down(&mut self) { let half_screen = self.size.height as usize / 2; - self.scroll = (self.scroll + half_screen).min( - self.line_index - .iter() - .copied() - .enumerate() - .map(|(line, _)| (line + 1).saturating_sub(half_screen)) - .next_back() - .unwrap_or(0), - ); - - let nav_mode = self.selected_item_nav_mode(); - self.update_cursor(nav_mode); + self.scroll_view_down(half_screen); } - pub(crate) fn scroll_up(&mut self, lines: usize) { + pub(crate) fn scroll_view_up(&mut self, lines: usize) { self.scroll = self.scroll.saturating_sub(lines); - let nav_mode = self.selected_item_nav_mode(); - self.update_cursor(nav_mode); + self.clamp_scroll(); } - pub(crate) fn scroll_down(&mut self, lines: usize) { - let max_scroll = self - .line_index - .len() - .saturating_sub(self.size.height as usize); - self.scroll = (self.scroll + lines).min(max_scroll); - let nav_mode = self.selected_item_nav_mode(); - self.update_cursor(nav_mode); + pub(crate) fn scroll_view_down(&mut self, lines: usize) { + self.scroll = self.scroll.saturating_add(lines); + self.clamp_scroll(); } pub(crate) fn toggle_section(&mut self) { @@ -242,6 +222,7 @@ impl Screen { return; } + self.clamp_scroll(); self.clamp_cursor(); if self.is_cursor_off_screen() { self.move_cursor_to_screen_center(); @@ -283,6 +264,7 @@ impl Screen { .flatten() .map(|(i, _item)| i) .collect(); + self.clamp_scroll(); } fn is_cursor_off_screen(&self) -> bool { @@ -300,6 +282,26 @@ impl Screen { .clamp(0, self.line_index.len().saturating_sub(1)); } + fn clamp_scroll(&mut self) { + if self.line_index.is_empty() { + self.scroll = 0; + return; + } + + self.scroll = self.scroll.min(self.max_scroll_with_context()); + } + + fn max_scroll_with_context(&self) -> usize { + let len = self.line_index.len(); + if len == 0 { + return 0; + } + + let max_scroll = len.saturating_sub(self.size.height as usize); + let max_scroll = max_scroll.saturating_add(BOTTOM_CONTEXT_LINES); + max_scroll.min(len.saturating_sub(1)) + } + fn move_from_unselectable(&mut self, nav_mode: NavMode) { if !self.nav_filter(self.cursor, nav_mode) { self.select_previous(nav_mode); diff --git a/src/tests/snapshots/gitu__tests__editor__scroll_past_selection.snap b/src/tests/snapshots/gitu__tests__editor__scroll_past_selection.snap index 220ba90af6..1241613be4 100644 --- a/src/tests/snapshots/gitu__tests__editor__scroll_past_selection.snap +++ b/src/tests/snapshots/gitu__tests__editor__scroll_past_selection.snap @@ -2,24 +2,24 @@ source: src/tests/editor.rs expression: ctx.redact_buffer() --- - +line 20 (file-1) | - modified file-2 | -▌@@ -0,0 +1,20 @@ | -▌+line 1 (file-2) | -▌+line 2 (file-2) | -▌+line 3 (file-2) | -▌+line 4 (file-2) | -▌+line 5 (file-2) | -▌+line 6 (file-2) | -▌+line 7 (file-2) | -▌+line 8 (file-2) | -▌+line 9 (file-2) | -▌+line 10 (file-2) | -▌+line 11 (file-2) | -▌+line 12 (file-2) | -▌+line 13 (file-2) | -▌+line 14 (file-2) | -▌+line 15 (file-2) | -▌+line 16 (file-2) | -▌+line 17 (file-2) | -styles_hash: ae0fb0ed74b80538 + +line 3 (file-2) | + +line 4 (file-2) | + +line 5 (file-2) | + +line 6 (file-2) | + +line 7 (file-2) | + +line 8 (file-2) | + +line 9 (file-2) | + +line 10 (file-2) | + +line 11 (file-2) | + +line 12 (file-2) | + +line 13 (file-2) | + +line 14 (file-2) | + +line 15 (file-2) | + +line 16 (file-2) | + +line 17 (file-2) | + +line 18 (file-2) | + +line 19 (file-2) | + +line 20 (file-2) | + modified file-3 | + @@ -0,0 +1,20 @@ | +styles_hash: ba6ffe68111a437 diff --git a/src/tests/snapshots/gitu__tests__help_menu.snap b/src/tests/snapshots/gitu__tests__help_menu.snap index 5c5364c37b..9e774ffd91 100644 --- a/src/tests/snapshots/gitu__tests__help_menu.snap +++ b/src/tests/snapshots/gitu__tests__help_menu.snap @@ -1,26 +1,25 @@ --- source: src/tests/mod.rs -assertion_line: 50 expression: ctx.redact_buffer() --- ▌On branch main | ▌Your branch is up to date with 'origin/main'. | - | - Recent commits | ────────────────────────────────────────────────────────────────────────────────| - Help Submenu On branch main | - Y Show Refs b Branch tab Fold | - k/up Up c Commit | - j/down Down f Fetch | - ctrl+k/ctrl+up Up line h/? Help | - ctrl+j/ctrl+down Down line l Log | - alt+k/alt+up Prev section m Merge | - alt+j/alt+down Next section M Remote | - alt+h/alt+left Parent section F Pull | - g+g Top P Push | - G Bottom r Rebase | - ctrl+u Half page up X Reset | - ctrl+d Half page down V Revert | - g+r Refresh A Cherry-pick | - q/esc Quit/Close z Stash | -styles_hash: d3bd90efdd7b7678 + Help Submenu On branch main | + Y Show Refs b Branch tab Fold | + k/up Up c Commit | + j/down Down f Fetch | + ctrl+k/ctrl+up Up line h/? Help | + ctrl+j/ctrl+down Down line l Log | + alt+k/alt+up Prev section m Merge | + alt+j/alt+down Next section M Remote | + alt+h/alt+left Parent section F Pull | + g+g Top P Push | + G Bottom r Rebase | + ctrl+u Scroll half page up X Reset | + ctrl+d Scroll half page down V Revert | + ctrl+y Scroll view up A Cherry-pick | + ctrl+e Scroll view down z Stash | + g+r Refresh | + q/esc Quit/Close | +styles_hash: f683951757a0712c diff --git a/src/tests/snapshots/gitu__tests__mouse_wheel_scroll_down.snap b/src/tests/snapshots/gitu__tests__mouse_wheel_scroll_down.snap index b375c732a7..681817679d 100644 --- a/src/tests/snapshots/gitu__tests__mouse_wheel_scroll_down.snap +++ b/src/tests/snapshots/gitu__tests__mouse_wheel_scroll_down.snap @@ -12,7 +12,7 @@ expression: ctx.redact_buffer() modified file07… | modified file08… | modified file09… | -▌modified file10… | + modified file10… | modified file11… | modified file12… | modified file13… | @@ -22,4 +22,4 @@ expression: ctx.redact_buffer() modified file17… | modified file18… | modified file19… | -styles_hash: 581a108fde4c40fe +styles_hash: e2b83c90b357195b diff --git a/src/tests/snapshots/gitu__tests__mouse_wheel_scroll_up.snap b/src/tests/snapshots/gitu__tests__mouse_wheel_scroll_up.snap index af7fc1c057..f40ae9408f 100644 --- a/src/tests/snapshots/gitu__tests__mouse_wheel_scroll_up.snap +++ b/src/tests/snapshots/gitu__tests__mouse_wheel_scroll_up.snap @@ -5,7 +5,7 @@ expression: ctx.redact_buffer() modified file14… | modified file15… | modified file16… | -▌modified file17… | + modified file17… | modified file18… | modified file19… | modified file20… | @@ -22,4 +22,4 @@ expression: ctx.redact_buffer() | Recent commits | ae744cc main add file30 | -styles_hash: f0f5b789b0d8de8f +styles_hash: 9d0bedcc92bfcaa8