mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-22 13:00:22 -04:00
feat: range copy mode
This commit is contained in:
parent
626748db0f
commit
c62fd08043
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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!(
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user