diff --git a/src/ui/mod.rs b/src/ui/mod.rs index e80c15f..234ef0a 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -3,8 +3,9 @@ use std::{path::PathBuf, process::ExitCode}; use crate::book::Book; -use anyhow::Result; +use anyhow::{anyhow, Result}; use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers}; +use ironcalc::base::Model; use ratatui::{ buffer::Buffer, layout::{Constraint, Flex, Layout}, @@ -87,7 +88,7 @@ impl Default for Address { pub struct Workspace<'ws> { name: PathBuf, book: Book, - state: AppState<'ws>, + pub(crate) state: AppState<'ws>, text_area: TextArea<'ws>, } @@ -104,6 +105,13 @@ impl<'ws> Workspace<'ws> { ws } + pub fn new_empty(locale: &str, tz: &str) -> Result { + Ok(Self::new( + Book::new(Model::new_empty("", locale, tz).map_err(|e| anyhow!("{}", e))?), + PathBuf::default(), + )) + } + /// Loads a workspace from a path. pub fn load(path: &PathBuf, locale: &str, tz: &str) -> Result { let book = load_book(path, locale, tz)?; @@ -178,23 +186,28 @@ impl<'ws> Workspace<'ws> { match self.state.modality() { Modality::Navigate => vec![ "Navigate Mode:".to_string(), - "* e: Enter edit mode for current cell".to_string(), + "* e,i: Enter edit mode for current cell".to_string(), + "* ENTER/RETURN: Go down one cell".to_string(), + "* TAB: Go over one cell".to_string(), "* h,j,k,l: vim style navigation".to_string(), "* CTRl-r: Add a row".to_string(), "* CTRl-c: Add a column".to_string(), "* CTRl-l: Grow column width by 1".to_string(), "* CTRl-h: Shrink column width by 1".to_string(), + "* CTRl-n: Next sheet. Starts over at beginning if at end.".to_string(), + "* CTRl-p: Previous sheet. Starts over at end if at beginning.".to_string(), "* q exit".to_string(), "* Ctrl-S Save sheet".to_string(), ], Modality::CellEdit => vec![ "Edit Mode:".to_string(), - "* ESC: Exit edit mode".to_string(), + "* ESC, ENTER/RETURN: Exit edit mode".to_string(), "Otherwise edit as normal".to_string(), ], Modality::Command => vec![ "Command Mode:".to_string(), "* ESC: Exit command mode".to_string(), + "* ENTER/RETURN: run command and exit command mode".to_string(), ], _ => vec!["General help".to_string()], } @@ -284,7 +297,8 @@ impl<'ws> Workspace<'ws> { self.book.set_sheet_name(idx, name)?; } _ => { - self.book.set_sheet_name(self.book.current_sheet as usize, name)?; + self.book + .set_sheet_name(self.book.current_sheet as usize, name)?; } } Ok(true) @@ -381,10 +395,24 @@ impl<'ws> Workspace<'ws> { KeyCode::Char('q') => { return Ok(Some(ExitCode::SUCCESS)); } - KeyCode::Char('j') | KeyCode::Down if key.modifiers != KeyModifiers::CONTROL => { + KeyCode::Char('j') | KeyCode::Down + if key.modifiers != KeyModifiers::CONTROL => + { self.move_down()?; self.handle_movement_change(); } + KeyCode::Enter + if key.modifiers != KeyModifiers::SHIFT => + { + self.move_down()?; + self.handle_movement_change(); + } + KeyCode::Enter + if key.modifiers == KeyModifiers::SHIFT => + { + self.move_up()?; + self.handle_movement_change(); + } KeyCode::Char('k') | KeyCode::Up if key.modifiers != KeyModifiers::CONTROL => { self.move_up()?; self.handle_movement_change(); @@ -393,10 +421,24 @@ impl<'ws> Workspace<'ws> { self.move_left()?; self.handle_movement_change(); } - KeyCode::Char('l') | KeyCode::Right if key.modifiers != KeyModifiers::CONTROL => { + KeyCode::Char('l') | KeyCode::Right + if key.modifiers != KeyModifiers::CONTROL => + { self.move_right()?; self.handle_movement_change(); } + KeyCode::Tab + if key.modifiers != KeyModifiers::SHIFT => + { + self.move_right()?; + self.handle_movement_change(); + } + KeyCode::Tab + if key.modifiers == KeyModifiers::SHIFT => + { + self.move_left()?; + self.handle_movement_change(); + } _ => { // noop } @@ -476,7 +518,6 @@ impl<'ws> Workspace<'ws> { self.book.save_to_xlsx(path.into().as_str())?; Ok(()) } - } fn load_book(path: &PathBuf, locale: &str, tz: &str) -> Result { diff --git a/src/ui/test.rs b/src/ui/test.rs index cb3e3a5..d8761f4 100644 --- a/src/ui/test.rs +++ b/src/ui/test.rs @@ -1,4 +1,9 @@ +use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; + +use crate::ui::Modality; + use super::cmd::{parse, Cmd}; +use super::Workspace; #[test] fn test_write_cmd() { @@ -187,4 +192,61 @@ fn test_cmd_rename_sheet_with_idx_and_name() { assert_eq!(cmd, Cmd::RenameSheet(Some(0), "test")); } +fn construct_key_event(code: KeyCode) -> Event { + construct_modified_key_event(code, KeyModifiers::empty()) +} + +fn construct_modified_key_event(code: KeyCode, mods: KeyModifiers) -> Event { + Event::Key(KeyEvent::new(code, mods)) +} + // TODO(zaphar): Interaction testing for input. +#[test] +fn test_input_navitation_enter_key() { + let mut ws = + Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook"); + let row = ws.book.location.row; + assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last()); + ws.handle_input(construct_key_event(KeyCode::Enter)) + .expect("Failed to handle enter key"); + assert_eq!(row + 1, ws.book.location.row); +} + +#[test] +fn test_input_navitation_tab_key() { + let mut ws = + Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook"); + let col = dbg!(ws.book.location.col); + assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last()); + ws.handle_input(construct_key_event(KeyCode::Tab)) + .expect("Failed to handle enter key"); + assert_eq!(col + 1, ws.book.location.col); +} + +#[test] +fn test_input_navitation_shift_enter_key() { + let mut ws = + Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook"); + let row = ws.book.location.row; + assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last()); + ws.handle_input(construct_key_event(KeyCode::Enter)) + .expect("Failed to handle enter key"); + assert_eq!(row + 1, ws.book.location.row); + ws.handle_input(construct_modified_key_event(KeyCode::Enter, KeyModifiers::SHIFT)) + .expect("Failed to handle enter key"); + assert_eq!(row, ws.book.location.row); +} + +#[test] +fn test_input_navitation_shift_tab_key() { + let mut ws = + Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook"); + let col = dbg!(ws.book.location.col); + assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last()); + ws.handle_input(construct_key_event(KeyCode::Tab)) + .expect("Failed to handle enter key"); + assert_eq!(col + 1, ws.book.location.col); + ws.handle_input(construct_modified_key_event(KeyCode::Tab, KeyModifiers::SHIFT)) + .expect("Failed to handle enter key"); + assert_eq!(col, ws.book.location.col); +}