diff options
author | Tim Oram <dev@mitmaro.ca> | 2022-04-20 09:15:43 -0230 |
---|---|---|
committer | Tim Oram <dev@mitmaro.ca> | 2022-04-20 09:51:11 -0230 |
commit | 190a0596fee6de9ff336b5d739e1aa6e5d1a89a4 (patch) | |
tree | 38deff933e6237efa45855fcffad8bcf60ecc2d4 | |
parent | 8bd68f558cff4c1eeed637383c079146892a4727 (diff) |
Add keybindings for view scrolling
-rw-r--r-- | CHANGELOG.md | 6 | ||||
-rw-r--r-- | README.md | 70 | ||||
-rw-r--r-- | src/config/src/key_bindings.rs | 59 | ||||
-rw-r--r-- | src/core/src/modules/show_commit/util.rs | 15 | ||||
-rw-r--r-- | src/core/src/util.rs | 2 | ||||
-rw-r--r-- | src/input/src/event_handler.rs | 68 | ||||
-rw-r--r-- | src/input/src/key_bindings.rs | 26 | ||||
-rw-r--r-- | src/input/src/testutil.rs | 10 | ||||
-rw-r--r-- | src/view/src/render_slice/mod.rs | 10 | ||||
-rw-r--r-- | src/view/src/render_slice/render_action.rs | 2 | ||||
-rw-r--r-- | src/view/src/render_slice/tests.rs | 45 | ||||
-rw-r--r-- | src/view/src/scroll_position.rs | 144 | ||||
-rw-r--r-- | src/view/src/sender.rs | 28 | ||||
-rw-r--r-- | src/view/src/testutil.rs | 2 |
14 files changed, 378 insertions, 109 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de422f..edb56cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ # Change Log All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +The format is based on [Keep a Changelog](http://keepachangelog.com/). + +## [2.2.0] - 2022-04-15 +### Added +- Added mew keybindings for customizing the scrolling the view. ([#647](https://github.com/MitMaro/git-interactive-rebase-tool/pull/647)) ## [2.1.0] - 2021-04-20 @@ -25,7 +25,7 @@ Native cross-platform full feature terminal based [sequence editor][git-sequence ### Cross-platform -Built and works on Linux, macOS, Windows and BSD. +Built and works on Linux, macOS, Windows and many others. ### Set action @@ -120,36 +120,44 @@ The tool has built-in help that can be accessed by hitting the `?` key. Key bindings can be configured, see [configuration](readme/customization.md#key-bindings) for more information. -| Key | Mode | Description | -| ------------ | ------ | ----------- | -| Up | All | Move selection up | -| Down | All | Move selection down | -| Page Up | All | Move selection up five lines | -| Page Down | All | Move selection down five lines | -| Home | All | Move selection to start of list | -| End | All | Move selection to home of list | -| `q` | Normal | Abort interactive rebase | -| `Q` | Normal | Immediately abort interactive rebase | -| `w` | Normal | Write interactive rebase file | -| `W` | Normal | Immediately write interactive rebase file | -| `?` | All | Show help | -| `c` | Normal | Show commit information | -| `j` | All | Move selected commit(s) down | -| `k` | All | Move selected commit(s) up | -| `b` | Normal | Toggle break action | -| `p` | All | Set selected commit(s) to be picked | -| `r` | All | Set selected commit(s) to be reworded | -| `e` | All | Set selected commit(s) to be edited | -| `s` | All | Set selected commit(s) to be squashed | -| `f` | All | Set selected commit(s) to be fixed-up | -| `d` | All | Set selected commit(s) to be dropped | -| `E` | Normal | Edit the command of an exec action | -| `v` | All | Enter and exit visual mode | -| `d` | Diff | Show full commit diff | -| `I` | Normal | Insert a new line | -| `Delete` | All | Remove selected lines | -| `Control+z` | All | Undo the previous change | -| `Control+y` | All | Redo the previously undone change | +| Key | Mode | Description | +|-------------| ------ |-------------------------------------------| +| Up | All | Move selection up | +| Down | All | Move selection down | +| Page Up | All | Move selection up five lines | +| Page Down | All | Move selection down five lines | +| Home | All | Move selection to start of list | +| End | All | Move selection to home of list | +| `q` | Normal | Abort interactive rebase | +| `Q` | Normal | Immediately abort interactive rebase | +| `w` | Normal | Write interactive rebase file | +| `W` | Normal | Immediately write interactive rebase file | +| `?` | All | Show help | +| `c` | Normal | Show commit information | +| `j` | All | Move selected commit(s) down | +| `k` | All | Move selected commit(s) up | +| `b` | Normal | Toggle break action | +| `p` | All | Set selected commit(s) to be picked | +| `r` | All | Set selected commit(s) to be reworded | +| `e` | All | Set selected commit(s) to be edited | +| `s` | All | Set selected commit(s) to be squashed | +| `f` | All | Set selected commit(s) to be fixed-up | +| `d` | All | Set selected commit(s) to be dropped | +| `E` | Normal | Edit the command of an exec action | +| `v` | All | Enter and exit visual mode | +| `I` | Normal | Insert a new line | +| `Delete` | All | Remove selected lines | +| `Control+z` | All | Undo the previous change | +| `Control+y` | All | Redo the previously undone change | +| Down | Diff | Scroll view down | +| Up | Diff | Scroll view up | +| Left | Diff | Scroll view left | +| Right | Diff | Scroll view right | +| Home | Diff | Scroll view to the top | +| End | Diff | Scroll view to the end | +| PageUp | Diff | Scroll view a step up | +| PageDown | Diff | Scroll view a step down | +| `d` | Diff | Show full commit diff | ## Development diff --git a/src/config/src/key_bindings.rs b/src/config/src/key_bindings.rs index 55eb5ce..a6a5d80 100644 --- a/src/config/src/key_bindings.rs +++ b/src/config/src/key_bindings.rs @@ -38,10 +38,9 @@ pub struct KeyBindings { pub help: Vec<String>, /// Key bindings for inserting a line. pub insert_line: Vec<String>, + /// Key bindings for moving down. pub move_down: Vec<String>, - /// Key bindings for moving down a step. - pub move_down_step: Vec<String>, /// Key bindings for moving to the end. pub move_end: Vec<String>, /// Key bindings for moving to the start. @@ -50,14 +49,34 @@ pub struct KeyBindings { pub move_left: Vec<String>, /// Key bindings for moving to the right. pub move_right: Vec<String>, - /// Key bindings for moving the selection down. - pub move_selection_down: Vec<String>, - /// Key bindings for moving the selection up. - pub move_selection_up: Vec<String>, /// Key bindings for moving up. pub move_up: Vec<String>, + /// Key bindings for moving down a step. + pub move_down_step: Vec<String>, /// Key bindings for moving up a step. pub move_up_step: Vec<String>, + /// Key bindings for moving the selection down. + pub move_selection_down: Vec<String>, + /// Key bindings for moving the selection up. + pub move_selection_up: Vec<String>, + + /// Key bindings for scrolling down. + pub scroll_down: Vec<String>, + /// Key bindings for scrolling to the end. + pub scroll_end: Vec<String>, + /// Key bindings for scrolling to the start. + pub scroll_home: Vec<String>, + /// Key bindings for scrolling to the left. + pub scroll_left: Vec<String>, + /// Key bindings for scrolling to the right. + pub scroll_right: Vec<String>, + /// Key bindings for scrolling up. + pub scroll_up: Vec<String>, + /// Key bindings for scrolling down a step. + pub scroll_step_down: Vec<String>, + /// Key bindings for scrolling up a step. + pub scroll_step_up: Vec<String>, + /// Key bindings for opening the external editor. pub open_in_external_editor: Vec<String>, /// Key bindings for rebasing. @@ -110,15 +129,23 @@ impl KeyBindings { help: get_input(git_config, "interactive-rebase-tool.inputHelp", "?")?, insert_line: get_input(git_config, "interactive-rebase-tool.insertLine", "I")?, move_down: get_input(git_config, "interactive-rebase-tool.inputMoveDown", "Down")?, - move_down_step: get_input(git_config, "interactive-rebase-tool.inputMoveStepDown", "PageDown")?, move_end: get_input(git_config, "interactive-rebase-tool.inputMoveEnd", "End")?, move_home: get_input(git_config, "interactive-rebase-tool.inputMoveHome", "Home")?, move_left: get_input(git_config, "interactive-rebase-tool.inputMoveLeft", "Left")?, move_right: get_input(git_config, "interactive-rebase-tool.inputMoveRight", "Right")?, - move_selection_down: get_input(git_config, "interactive-rebase-tool.inputMoveSelectionDown", "j")?, - move_selection_up: get_input(git_config, "interactive-rebase-tool.inputMoveSelectionUp", "k")?, + move_down_step: get_input(git_config, "interactive-rebase-tool.inputMoveStepDown", "PageDown")?, move_up_step: get_input(git_config, "interactive-rebase-tool.inputMoveStepUp", "PageUp")?, move_up: get_input(git_config, "interactive-rebase-tool.inputMoveUp", "Up")?, + move_selection_down: get_input(git_config, "interactive-rebase-tool.inputMoveSelectionDown", "j")?, + move_selection_up: get_input(git_config, "interactive-rebase-tool.inputMoveSelectionUp", "k")?, + scroll_down: get_input(git_config, "interactive-rebase-tool.inputScrollDown", "Down")?, + scroll_end: get_input(git_config, "interactive-rebase-tool.inputScrollEnd", "End")?, + scroll_home: get_input(git_config, "interactive-rebase-tool.inputScrollHome", "Home")?, + scroll_left: get_input(git_config, "interactive-rebase-tool.inputScrollLeft", "Left")?, + scroll_right: get_input(git_config, "interactive-rebase-tool.inputScrollRight", "Right")?, + scroll_up: get_input(git_config, "interactive-rebase-tool.inputScrollUp", "Up")?, + scroll_step_down: get_input(git_config, "interactive-rebase-tool.inputScrollStepDown", "PageDown")?, + scroll_step_up: get_input(git_config, "interactive-rebase-tool.inputScrollStepUp", "PageUp")?, open_in_external_editor: get_input(git_config, "interactive-rebase-tool.inputOpenInExternalEditor", "!")?, rebase: get_input(git_config, "interactive-rebase-tool.inputRebase", "w")?, redo: get_input(git_config, "interactive-rebase-tool.inputRedo", "control+y")?, @@ -278,15 +305,23 @@ mod tests { #[case::help("inputHelp", "?", |bindings: KeyBindings| bindings.help)] #[case::insert_line("insertLine", "I", |bindings: KeyBindings| bindings.insert_line)] #[case::move_down("inputMoveDown", "Down", |bindings: KeyBindings| bindings.move_down)] - #[case::move_down_step("inputMoveStepDown", "PageDown", |bindings: KeyBindings| bindings.move_down_step)] #[case::move_end("inputMoveEnd", "End", |bindings: KeyBindings| bindings.move_end)] #[case::move_home("inputMoveHome", "Home", |bindings: KeyBindings| bindings.move_home)] #[case::move_left("inputMoveLeft", "Left", |bindings: KeyBindings| bindings.move_left)] #[case::move_right("inputMoveRight", "Right", |bindings: KeyBindings| bindings.move_right)] - #[case::move_selection_down("inputMoveSelectionDown", "j", |bindings: KeyBindings| bindings.move_selection_down)] - #[case::move_selection_up("inputMoveSelectionUp", "k", |bindings: KeyBindings| bindings.move_selection_up)] #[case::move_up("inputMoveUp", "Up", |bindings: KeyBindings| bindings.move_up)] + #[case::move_down_step("inputMoveStepDown", "PageDown", |bindings: KeyBindings| bindings.move_down_step)] #[case::move_up_step("inputMoveStepUp", "PageUp", |bindings: KeyBindings| bindings.move_up_step)] + #[case::move_selection_down("inputMoveSelectionDown", "j", |bindings: KeyBindings| bindings.move_selection_down)] + #[case::move_selection_up("inputMoveSelectionUp", "k", |bindings: KeyBindings| bindings.move_selection_up)] + #[case::scroll_down("inputScrollDown", "Down", |bindings: KeyBindings| bindings.scroll_down)] + #[case::scroll_end("inputScrollEnd", "End", |bindings: KeyBindings| bindings.scroll_end)] + #[case::scroll_home("inputScrollHome", "Home", |bindings: KeyBindings| bindings.scroll_home)] + #[case::scroll_left("inputScrollLeft", "Left", |bindings: KeyBindings| bindings.scroll_left)] + #[case::scroll_right("inputScrollRight", "Right", |bindings: KeyBindings| bindings.scroll_right)] + #[case::scroll_up("inputScrollUp", "Up", |bindings: KeyBindings| bindings.scroll_up)] + #[case::scroll_page_down("inputScrollStepDown", "PageDown", |bindings: KeyBindings| bindings.scroll_step_down)] + #[case::scroll_page_up("inputScrollStepUp", "PageUp", |bindings: KeyBindings| bindings.scroll_step_up)] #[case::open_in_external_editor( "inputOpenInExternalEditor", "!", diff --git a/src/core/src/modules/show_commit/util.rs b/src/core/src/modules/show_commit/util.rs index 19f5d43..f45b375 100644 --- a/src/core/src/modules/show_commit/util.rs +++ b/src/core/src/modules/show_commit/util.rs @@ -12,15 +12,18 @@ const TO_FILE_INDICATOR_SHORT: &str = "\u{2192}"; // "→" pub(super) fn get_show_commit_help_lines(key_bindings: &KeyBindings) -> Vec<(Vec<String>, String)> { vec![ - (key_bindings.move_up.clone(), String::from("Scroll up")), - (key_bindings.move_down.clone(), String::from("Scroll down")), - (key_bindings.move_up_step.clone(), String::from("Scroll up half a page")), + (key_bindings.scroll_up.clone(), String::from("Scroll up")), + (key_bindings.scroll_down.clone(), String::from("Scroll down")), ( - key_bindings.move_down_step.clone(), + key_bindings.scroll_step_up.clone(), + String::from("Scroll up half a page"), + ), + ( + key_bindings.scroll_step_down.clone(), String::from("Scroll down half a page"), ), - (key_bindings.move_right.clone(), String::from("Scroll right")), - (key_bindings.move_left.clone(), String::from("Scroll left")), + (key_bindings.scroll_right.clone(), String::from("Scroll right")), + (key_bindings.scroll_left.clone(), String::from("Scroll left")), (key_bindings.show_diff.clone(), String::from("Show full diff")), (key_bindings.help.clone(), String::from("Show help")), ] diff --git a/src/core/src/util.rs b/src/core/src/util.rs index bdb0400..8f59034 100644 --- a/src/core/src/util.rs +++ b/src/core/src/util.rs @@ -45,6 +45,8 @@ pub(crate) fn handle_view_data_scroll(event: Event, view_sender: &ViewSender) -> Event::Standard(meta_event) if meta_event == StandardEvent::ScrollRight => view_sender.scroll_right(), Event::Standard(meta_event) if meta_event == StandardEvent::ScrollDown => view_sender.scroll_down(), Event::Standard(meta_event) if meta_event == StandardEvent::ScrollUp => view_sender.scroll_up(), + Event::Standard(meta_event) if meta_event == StandardEvent::ScrollTop => view_sender.scroll_top(), + Event::Standard(meta_event) if meta_event == StandardEvent::ScrollBottom => view_sender.scroll_bottom(), Event::Standard(meta_event) if meta_event == StandardEvent::ScrollJumpDown => view_sender.scroll_page_down(), Event::Standard(meta_event) if meta_event == StandardEvent::ScrollJumpUp => view_sender.scroll_page_up(), _ => return None, diff --git a/src/input/src/event_handler.rs b/src/input/src/event_handler.rs index 78923aa..fdf6aa6 100644 --- a/src/input/src/event_handler.rs +++ b/src/input/src/event_handler.rs @@ -44,7 +44,7 @@ impl<CustomKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent> } if input_options.contains(InputOptions::MOVEMENT) { - if let Some(evt) = Self::handle_movement_inputs(event) { + if let Some(evt) = Self::handle_movement_inputs(&self.key_bindings, event) { return evt; } } @@ -74,42 +74,54 @@ impl<CustomKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent> } #[allow(clippy::wildcard_enum_match_arm)] - fn handle_movement_inputs(event: Event<CustomEvent>) -> Option<Event<CustomEvent>> { - match event { + fn handle_movement_inputs( + key_bindings: &KeyBindings<CustomKeybinding, CustomEvent>, + event: Event<CustomEvent>, + ) -> Option<Event<CustomEvent>> { + Some(match event { + e if key_bindings.scroll_down.contains(&e) => Event::from(StandardEvent::ScrollDown), + e if key_bindings.scroll_end.contains(&e) => Event::from(StandardEvent::ScrollBottom), + e if key_bindings.scroll_home.contains(&e) => Event::from(StandardEvent::ScrollTop), + e if key_bindings.scroll_left.contains(&e) => Event::from(StandardEvent::ScrollLeft), + e if key_bindings.scroll_right.contains(&e) => Event::from(StandardEvent::ScrollRight), + e if key_bindings.scroll_up.contains(&e) => Event::from(StandardEvent::ScrollUp), + e if key_bindings.scroll_step_down.contains(&e) => Event::from(StandardEvent::ScrollJumpDown), + e if key_bindings.scroll_step_up.contains(&e) => Event::from(StandardEvent::ScrollJumpUp), + // these are required, since in some contexts (like editing), other keybindings will not work Event::Key(KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::NONE, - }) => Some(Event::from(StandardEvent::ScrollUp)), + }) => Event::from(StandardEvent::ScrollUp), Event::Key(KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::NONE, - }) => Some(Event::from(StandardEvent::ScrollDown)), + }) => Event::from(StandardEvent::ScrollDown), Event::Key(KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::NONE, - }) => Some(Event::from(StandardEvent::ScrollLeft)), + }) => Event::from(StandardEvent::ScrollLeft), Event::Key(KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::NONE, - }) => Some(Event::from(StandardEvent::ScrollRight)), + }) => Event::from(StandardEvent::ScrollRight), Event::Key(KeyEvent { code: KeyCode::PageUp, modifiers: KeyModifiers::NONE, - }) => Some(Event::from(StandardEvent::ScrollJumpUp)), + }) => Event::from(StandardEvent::ScrollJumpUp), Event::Key(KeyEvent { code: KeyCode::PageDown, modifiers: KeyModifiers::NONE, - }) => Some(Event::from(StandardEvent::ScrollJumpDown)), + }) => Event::from(StandardEvent::ScrollJumpDown), Event::Key(KeyEvent { code: KeyCode::Home, modifiers: KeyModifiers::NONE, - }) => Some(Event::from(StandardEvent::ScrollTop)), + }) => Event::from(StandardEvent::ScrollTop), Event::Key(KeyEvent { code: KeyCode::End, modifiers: KeyModifiers::NONE, - }) => Some(Event::from(StandardEvent::ScrollBottom)), - _ => None, - } + }) => Event::from(StandardEvent::ScrollBottom), + _ => return None, + }) } fn handle_undo_redo( @@ -133,7 +145,10 @@ mod tests { use rstest::rstest; use super::*; - use crate::testutil::local::{create_test_keybindings, Event, EventHandler}; + use crate::{ + map_keybindings, + testutil::local::{create_test_keybindings, Event, EventHandler}, + }; #[rstest] #[case::standard(Event::Key(KeyEvent { @@ -223,6 +238,31 @@ mod tests { } #[rstest] + #[case::standard(Event::from(KeyCode::Up), Event::from(StandardEvent::ScrollUp))] + #[case::standard(Event::from(KeyCode::Down), Event::from(StandardEvent::ScrollDown))] + #[case::standard(Event::from(KeyCode::Left), Event::from(StandardEvent::ScrollLeft))] + #[case::standard(Event::from(KeyCode::Right), Event::from(StandardEvent::ScrollRight))] + #[case::standard(Event::from(KeyCode::PageUp), Event::from(StandardEvent::ScrollJumpUp))] + #[case::standard(Event::from(KeyCode::PageDown), Event::from(StandardEvent::ScrollJumpDown))] + #[case::standard(Event::from(KeyCode::Home), Event::from(StandardEvent::ScrollTop))] + #[case::standard(Event::from(KeyCode::End), Event::from(StandardEvent::ScrollBottom))] + #[case::other(Event::from('a'), Event::from(KeyCode::Null))] + fn default_movement_inputs(#[case] event: Event, #[case] expected: Event) { + let mut bindings = create_test_keybindings(); + bindings.scroll_down = map_keybindings(&[String::from("x")]); + bindings.scroll_end = map_keybindings(&[String::from("x")]); + bindings.scroll_home = map_keybindings(&[String::from("x")]); + bindings.scroll_left = map_keybindings(&[String::from("x")]); + bindings.scroll_right = map_keybindings(&[String::from("x")]); + bindings.scroll_up = map_keybindings(&[String::from("x")]); + bindings.scroll_step_down = map_keybindings(&[String::from("x")]); + bindings.scroll_step_up = map_keybindings(&[String::from("x")]); + let event_handler = EventHandler::new(bindings); + let result = event_handler.read_event(event, &InputOptions::MOVEMENT, |_, _| Event::from(KeyCode::Null)); + assert_eq!(result, expected); + } + + #[rstest] #[case::standard(Event::Key(KeyEvent { code: KeyCode::Char('z'), modifiers: KeyModifiers::CONTROL, diff --git a/src/input/src/key_bindings.rs b/src/input/src/key_bindings.rs index a0be033..fe3fff3 100644 --- a/src/input/src/key_bindings.rs +++ b/src/input/src/key_bindings.rs @@ -8,6 +8,24 @@ pub struct KeyBindings<CustomKeybinding: crate::CustomKeybinding, CustomEvent: c pub redo: Vec<Event<CustomEvent>>, /// Key bindings for undoing a change. pub undo: Vec<Event<CustomEvent>>, + + /// Key bindings for scrolling down. + pub scroll_down: Vec<Event<CustomEvent>>, + /// Key bindings for scrolling to the end. + pub scroll_end: Vec<Event<CustomEvent>>, + /// Key bindings for scrolling to the start. + pub scroll_home: Vec<Event<CustomEvent>>, + /// Key bindings for scrolling to the left. + pub scroll_left: Vec<Event<CustomEvent>>, + /// Key bindings for scrolling to the right. + pub scroll_right: Vec<Event<CustomEvent>>, + /// Key bindings for scrolling up. + pub scroll_up: Vec<Event<CustomEvent>>, + /// Key bindings for scrolling down a step. + pub scroll_step_down: Vec<Event<CustomEvent>>, + /// Key bindings for scrolling up a step. + pub scroll_step_up: Vec<Event<CustomEvent>>, + /// Custom keybindings pub custom: CustomKeybinding, } @@ -73,6 +91,14 @@ impl<CustomKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent> Self { redo: map_keybindings(&key_bindings.redo), undo: map_keybindings(&key_bindings.undo), + scroll_down: map_keybindings(&key_bindings.scroll_down), + scroll_end: map_keybindings(&key_bindings.scroll_end), + scroll_home: map_keybindings(&key_bindings.scroll_home), + scroll_left: map_keybindings(&key_bindings.scroll_left), + scroll_right: map_keybindings(&key_bindings.scroll_right), + scroll_up: map_keybindings(&key_bindings.scroll_up), + scroll_step_down: map_keybindings(&key_bindings.scroll_step_down), + scroll_step_up: map_keybindings(&key_bindings.scroll_step_up), custom: CustomKeybinding::new(key_bindings), } } diff --git a/src/input/src/testutil.rs b/src/input/src/testutil.rs index 95c43b0..ef7399c 100644 --- a/src/input/src/testutil.rs +++ b/src/input/src/testutil.rs @@ -1,7 +1,7 @@ //! Utilities for writing tests that interact with input events. use super::{Event, EventHandler, KeyBindings, KeyCode, KeyEvent, KeyModifiers}; -use crate::{event_action::EventAction, Sender}; +use crate::{event_action::EventAction, map_keybindings, Sender}; #[cfg(test)] pub(crate) mod local { @@ -46,6 +46,14 @@ pub fn create_test_keybindings<TestKeybinding: crate::CustomKeybinding, CustomEv modifiers: KeyModifiers::CONTROL, } })], + scroll_down: map_keybindings(&[String::from("Down")]), + scroll_end: map_keybindings(&[String::from("End")]), + scroll_home: map_keybindings(&[String::from("Home")]), + scroll_left: map_keybindings(&[String::from("Left")]), + scroll_right: map_keybindings(&[String::from("Right")]), + scroll_up: map_keybindings(&[String::from("Up")]), + scroll_step_down: map_keybindings(&[String::from("PageDown")]), + scroll_step_up: map_keybindings(&[String::from("PageUp")]), custom: custom_key_bindings, } } diff --git a/src/view/src/render_slice/mod.rs b/src/view/src/render_slice/mod.rs index 9227087..857b9a5 100644 --- a/src/view/src/render_slice/mod.rs +++ b/src/view/src/render_slice/mod.rs @@ -79,6 +79,14 @@ impl RenderSlice { self.actions.push_back(RenderAction::ScrollRight); } + pub fn record_scroll_top(&mut self) { + self.actions.push_back(RenderAction::ScrollTop); + } + + pub fn record_scroll_bottom(&mut self) { + self.actions.push_back(RenderAction::ScrollBottom); + } + pub fn record_resize(&mut self, width: usize, height: usize) { self.actions.push_back(RenderAction::Resize(width, height)); } @@ -97,6 +105,8 @@ impl RenderSlice { RenderAction::ScrollUp => self.scroll_position.scroll_up(), RenderAction::ScrollRight => self.scroll_position.scroll_right(), RenderAction::ScrollLeft => self.scroll_position.scroll_left(), + RenderAction::ScrollTop => self.scroll_position.scroll_top(), + RenderAction::ScrollBottom => self.scroll_position.scroll_bottom(), RenderAction::PageUp => self.scroll_position.page_up(), RenderAction::PageDown => self.scroll_position.page_down(), RenderAction::Resize(width, height) => self.set_size(width, height), diff --git a/src/view/src/render_slice/render_action.rs b/src/view/src/render_slice/render_action.rs index 05ffc4b..df0cbd2 100644 --- a/src/view/src/render_slice/render_action.rs +++ b/src/view/src/render_slice/render_action.rs @@ -4,6 +4,8 @@ pub(crate) enum RenderAction { ScrollUp, ScrollRight, ScrollLeft, + ScrollTop, + ScrollBottom, PageUp, PageDown, Resize(usize, usize), diff --git a/src/view/src/render_slice/tests.rs b/src/view/src/render_slice/tests.rs index 9837f89..8fda4a5 100644 --- a/src/view/src/render_slice/tests.rs +++ b/src/view/src/render_slice/tests.rs @@ -210,6 +210,51 @@ fn resize_action() { } #[test] +fn scroll_top_action() { + let view_data = create_view_data(2, 10, 2); + let mut render_slice = create_render_slice(100, 8, &view_data); + for _ in 0..6 { + render_slice.scroll_position.scroll_down(); + } + render_slice.record_scroll_top(); + render_slice.sync_view_data(&view_data); + assert_rendered(&render_slice, &[ + "{LEADING}", + "{Normal}L(1)", + "{Normal}L(2)", + "{BODY}", + "{Normal}B(1)", + "{Normal}B(2)", + "{Normal}B(3)", + "{Normal}B(4)", + "{TRAILING}", + "{Normal}T(1)", + "{Normal}T(2)", + ]); +} + +#[test] +fn scroll_bottom_action() { + let view_data = create_view_data(2, 10, 2); + let mut render_slice = create_render_slice(100, 8, &view_data); + render_slice.record_scroll_bottom(); + render_slice.sync_view_data(&view_data); + assert_rendered(&render_slice, &[ + "{LEADING}", + "{Normal}L(1)", + "{Normal}L(2)", + "{BODY}", + "{Normal}B(7)", + "{Normal}B(8)", + "{Normal}B(9)", + "{Normal}B(10)", + "{TRAILING}", + "{Normal}T(1)", + "{Normal}T(2)", + ]); +} + +#[test] fn resize_action_zero_width() { let view_data = create_view_data(0, 3, 0); let mut render_slice = create_render_slice(1, 1, &view_data); diff --git a/src/view/src/scroll_position.rs b/src/view/src/scroll_position.rs index 06a1b8a..9a589b6 100644 --- a/src/view/src/scroll_position.rs +++ b/src/view/src/scroll_position.rs @@ -1,9 +1,11 @@ #[derive(Copy, Clone, Debug, PartialEq)] enum ScrollDirection { - Up, - Down, - Left, - Right, + Top, + Up(usize), + Down(usize), + Left(usize), + Right(usize), + Bottom, } #[derive(Debug)] @@ -38,27 +40,35 @@ impl ScrollPosition { } pub(crate) fn scroll_up(&mut self) { - self.update_top(1, ScrollDirection::Up); + self.update_top(ScrollDirection::Up(1)); } pub(crate) fn scroll_down(&mut self) { - self.update_top(1, ScrollDirection::Down); + self.update_top(ScrollDirection::Down(1)); } pub(crate) fn page_up(&mut self) { - self.update_top(self.view_height / 2, ScrollDirection::Up); + self.update_top(ScrollDirection::Up(self.view_height / 2)); } pub(crate) fn page_down(&mut self) { - self.update_top(self.view_height / 2, ScrollDirection::Down); + self.update_top(ScrollDirection::Down(self.view_height / 2)); } pub(crate) fn scroll_left(&mut self) { - self.update_left(1, ScrollDirection::Left); + self.update_left(ScrollDirection::Left(1)); } pub(crate) fn scroll_right(&mut self) { - self.update_left(1, ScrollDirection::Right); + self.update_left(ScrollDirection::Right(1)); + } + + pub(crate) fn scroll_top(&mut self) { + self.update_top(ScrollDirection::Top); + } + + pub(crate) fn scroll_bottom(&mut self) { + self.update_top(ScrollDirection::Bottom); } pub(crate) fn ensure_line_visible(&mut self, line_index: usize) { @@ -141,10 +151,9 @@ impl ScrollPosition { } // recalculate left to remove any padding space to the right else if self.max_line_width > self.view_width && self.max_line_width <= (self.view_width + self.left_value) { - self.update_left( + self.update_left(ScrollDirection::Left( self.view_width + self.left_value - self.max_line_width, - ScrollDirection::Left, - ); + )); } } @@ -154,14 +163,13 @@ impl ScrollPosition { } // recalculate top to remove any padding space below the set of lines else if self.lines_length > self.view_height && self.lines_length <= (self.view_height + self.top_value) { - self.update_top( + self.update_top(ScrollDirection::Up( self.view_height + self.top_value - self.lines_length, - ScrollDirection::Up, - ); + )); } } - fn update_top(&mut self, amount: usize, direction: ScrollDirection) { + fn update_top(&mut self, direction: ScrollDirection) { if self.view_height >= self.lines_length { self.top_value = 0; return; @@ -169,41 +177,53 @@ impl ScrollPosition { let current_value = self.top_value; - if direction == ScrollDirection::Up { - if current_value < amount { - self.top_value = 0; - } - else { - self.top_value = current_value - amount; - } - } - else if current_value + amount + self.view_height > self.lines_length { - self.top_value = self.lines_length - self.view_height; - } - else { - self.top_value = current_value + amount; + match direction { + ScrollDirection::Top => self.top_value = 0, + ScrollDirection::Up(amount) => { + if current_value < amount { + self.top_value = 0; + } + else { + self.top_value = current_value - amount; + } + }, + ScrollDirection::Down(amount) => { + if current_value + amount + self.view_height > self.lines_length { + self.top_value = self.lines_length - self.view_height; + } + else { + self.top_value = current_value + amount; + } + }, + ScrollDirection::Bottom => self.top_value = self.lines_length - self.view_height, + ScrollDirection::Left(_) | ScrollDirection::Right(_) => {}, } } - fn update_left(&mut self, amount: usize, direction: ScrollDirection) { + fn update_left(&mut self, direction: ScrollDirection) { if self.view_width >= self.max_line_width { self.left_value = 0; return; } - if direction == ScrollDirection::Left { - if self.left_value < amount { - self.left_value = 0; - } - else { - self.left_value -= amount; - } - } - else if self.left_value + amount + self.view_width > self.max_line_width { - self.left_value = self.max_line_width - self.view_width; - } - else { - self.left_value += amount; + match direction { + ScrollDirection::Left(amount) => { + if self.left_value < amount { + self.left_value = 0; + } + else { + self.left_value -= amount; + } + }, + ScrollDirection::Right(amount) => { + if self.left_value + amount + self.view_width > self.max_line_width { + self.left_value = self.max_line_width - self.view_width; + } + else { + self.left_value += amount; + } + }, + ScrollDirection::Top | ScrollDirection::Up(_) | ScrollDirection::Down(_) | ScrollDirection::Bottom => {}, } } } @@ -575,6 +595,42 @@ mod tests { } #[test] + fn scroll_position_scroll_top() { + let mut scroll_position = ScrollPosition::new(); + scroll_position.lines_length = 5; + scroll_position.view_height = 10; + scroll_position.scroll_top(); + assert_eq!(scroll_position.get_top_position(), 0); + } + + #[test] + fn scroll_position_scroll_bottom_view_size_equal_list() { + let mut scroll_position = ScrollPosition::new(); + scroll_position.lines_length = 10; + scroll_position.view_height = 10; + scroll_position.scroll_bottom(); + assert_eq!(scroll_position.get_top_position(), 0); + } + + #[test] + fn scroll_position_scroll_bottom_view_size_less_list() { + let mut scroll_position = ScrollPosition::new(); + scroll_position.lines_length = 10; + scroll_position.view_height = 5; + scroll_position.scroll_bottom(); + assert_eq!(scroll_position.get_top_position(), 5); + } + + #[test] + fn scroll_position_scroll_bottom_view_size_greater_list() { + let mut scroll_position = ScrollPosition::new(); + scroll_position.lines_length = 5; + scroll_position.view_height = 15; + scroll_position.scroll_bottom(); + assert_eq!(scroll_position.get_top_position(), 0); + } + + #[test] fn scroll_position_resize_set_height_width() { let mut scroll_position = ScrollPosition::new(); scroll_position.resize(111, 222); diff --git a/src/vie |