feat: range copy mode

This commit is contained in:
Jeremy Wall 2024-12-04 18:51:18 -05:00
parent 626748db0f
commit c62fd08043
3 changed files with 142 additions and 1 deletions

View File

@ -30,6 +30,7 @@ pub enum Modality {
CellEdit,
Command,
Dialog,
RangeCopy,
}
#[derive(Debug)]
@ -38,6 +39,9 @@ pub struct AppState<'ws> {
pub viewport_state: ViewportState,
pub command_state: TextState<'ws>,
pub numeric_prefix: Vec<char>,
pub original_location: Option<Address>,
pub start_range: Option<Address>,
pub end_range: Option<Address>,
dirty: bool,
popup: Vec<String>,
}
@ -49,6 +53,9 @@ impl<'ws> Default for AppState<'ws> {
viewport_state: Default::default(),
command_state: Default::default(),
numeric_prefix: Default::default(),
original_location: Default::default(),
start_range: Default::default(),
end_range: Default::default(),
dirty: Default::default(),
popup: Default::default(),
}
@ -197,6 +204,7 @@ impl<'ws> Workspace<'ws> {
Modality::CellEdit => self.handle_edit_input(key)?,
Modality::Command => self.handle_command_input(key)?,
Modality::Dialog => self.handle_dialog_input(key)?,
Modality::RangeCopy => self.handle_range_copy_input(key)?,
};
return Ok(result);
}
@ -363,6 +371,57 @@ impl<'ws> Workspace<'ws> {
self.state.numeric_prefix.push(digit);
}
fn handle_range_copy_input(&mut self, key: event::KeyEvent) -> Result<Option<ExitCode>> {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Esc => {
self.state.reset_n_prefix();
}
KeyCode::Char(d) if d.is_ascii_digit() => {
self.handle_numeric_prefix(d);
}
KeyCode::Char('h') => {
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
ws.move_left()?;
dbg!(&ws.book.location);
Ok(())
})?;
}
KeyCode::Char('j') => {
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
ws.move_down()?;
Ok(())
})?;
}
KeyCode::Char('k') => {
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
ws.move_up()?;
dbg!(&ws.book.location);
Ok(())
})?;
}
KeyCode::Char('l') => {
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
ws.move_right()?;
Ok(())
})?;
}
KeyCode::Char(' ') => {
if self.state.start_range.is_none() {
self.state.start_range = dbg!(Some(self.book.location.clone()));
} else {
self.state.end_range = dbg!(Some(self.book.location.clone()));
self.exit_range_copy_mode()?;
}
}
_ => {
// moop
}
}
}
Ok(None)
}
fn handle_navigation_input(&mut self, key: event::KeyEvent) -> Result<Option<ExitCode>> {
if key.kind == KeyEventKind::Press {
match key.code {
@ -381,6 +440,9 @@ impl<'ws> Workspace<'ws> {
KeyCode::Char('s') if key.modifiers == KeyModifiers::CONTROL => {
self.save_file()?;
}
KeyCode::Char('r') if key.modifiers == KeyModifiers::CONTROL => {
self.enter_range_copy_mode();
}
KeyCode::Char('h') if key.modifiers == KeyModifiers::ALT => {
self.enter_dialog_mode(self.render_help_text());
}
@ -506,6 +568,14 @@ impl<'ws> Workspace<'ws> {
self.state.popup = msg;
self.state.modality_stack.push(Modality::Dialog);
}
fn enter_range_copy_mode(&mut self) {
self.state.original_location = Some(self.book.location.clone());
self.state.start_range = None;
self.state.end_range = None;
self.state.modality_stack.push(Modality::RangeCopy);
}
fn enter_edit_mode(&mut self) {
self.state.modality_stack.push(Modality::CellEdit);
@ -530,6 +600,14 @@ impl<'ws> Workspace<'ws> {
self.state.pop_modality();
Ok(())
}
fn exit_range_copy_mode(&mut self) -> Result<()> {
self.book.location = self.state.original_location.clone().expect("Missing original location after range copy");
self.state.original_location = None;
self.state.pop_modality();
Ok(())
}
fn exit_edit_mode(&mut self, keep: bool) -> Result<()> {
self.text_area.set_cursor_line_style(Style::default());
@ -542,6 +620,7 @@ impl<'ws> Workspace<'ws> {
self.text_area = reset_text_area(self.book.get_current_cell_contents()?);
}
self.state.dirty = false;
self.state.pop_modality();
Ok(())
}

View File

@ -95,6 +95,7 @@ impl<'widget, 'ws: 'widget> Widget for &'widget mut Workspace<'ws> {
Modality::CellEdit => "edit",
Modality::Command => "command",
Modality::Dialog => "",
Modality::RangeCopy => "range-copy",
})
.title_bottom(
Line::from(format!(

View File

@ -1,6 +1,6 @@
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
use crate::ui::Modality;
use crate::ui::{Address, Modality};
use super::cmd::{parse, Cmd};
use super::Workspace;
@ -351,3 +351,64 @@ fn test_navigation_tab_next_numeric_prefix()
.expect("Failed to handle 'Ctrl-n' key event");
assert_eq!("Sheet1", ws.book.get_sheet_name().expect("Failed to get sheet name"));
}
#[test]
fn test_range_copy() {
let mut ws =
Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook");
assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last());
ws.book.move_to(&Address { row: 1, col: 1, }).expect("Failed to move to row");
let original_loc = ws.book.location.clone();
ws.handle_input(construct_modified_key_event(KeyCode::Char('r'), KeyModifiers::CONTROL))
.expect("Failed to handle 'Ctrl-r' key event");
assert_eq!(Some(&Modality::RangeCopy), ws.state.modality_stack.last());
assert_eq!(Some(original_loc.clone()), ws.state.original_location);
assert!(ws.state.start_range.is_none());
assert!(ws.state.end_range.is_none());
ws.handle_input(construct_key_event(KeyCode::Char('l')))
.expect("Failed to handle 'l' key event");
ws.handle_input(construct_key_event(KeyCode::Char(' ')))
.expect("Failed to handle ' ' key event");
assert_eq!(Some(Address {row:1, col:2, }), ws.state.start_range);
ws.handle_input(construct_key_event(KeyCode::Char('j')))
.expect("Failed to handle 'j' key event");
ws.handle_input(construct_key_event(KeyCode::Char(' ')))
.expect("Failed to handle ' ' key event");
assert!(ws.state.original_location.is_none());
assert_eq!(Some(Address {row:1, col:2, }), ws.state.start_range);
assert_eq!(Some(Address {row:2, col:2, }), ws.state.end_range);
assert_eq!(original_loc, ws.book.location);
assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last());
ws.book.move_to(&Address { row: 5, col: 5, }).expect("Failed to move to row");
let original_loc_2 = ws.book.location.clone();
assert_eq!(Address { row: 5, col: 5 }, original_loc_2);
ws.handle_input(construct_modified_key_event(KeyCode::Char('r'), KeyModifiers::CONTROL))
.expect("Failed to handle 'Ctrl-r' key event");
assert_eq!(Some(&Modality::RangeCopy), ws.state.modality_stack.last());
assert_eq!(Some(original_loc_2.clone()), ws.state.original_location);
assert!(ws.state.start_range.is_none());
assert!(ws.state.end_range.is_none());
ws.handle_input(construct_key_event(KeyCode::Char('h')))
.expect("Failed to handle 'h' key event");
ws.handle_input(construct_key_event(KeyCode::Char(' ')))
.expect("Failed to handle ' ' key event");
assert_eq!(Some(Address {row:5, col:4, }), ws.state.start_range);
ws.handle_input(construct_key_event(KeyCode::Char('k')))
.expect("Failed to handle 'k' key event");
ws.handle_input(construct_key_event(KeyCode::Char(' ')))
.expect("Failed to handle ' ' key event");
assert!(ws.state.original_location.is_none());
assert_eq!(Some(Address {row:5, col:4, }), ws.state.start_range);
assert_eq!(Some(Address {row:4, col:4, }), ws.state.end_range);
assert_eq!(original_loc_2, ws.book.location);
assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last());
}