wip: bits of polish

* show what you are selecting in range mode
* Fix error with cell edit not updating after Enter
This commit is contained in:
Jeremy Wall 2024-12-05 17:27:06 -05:00
parent 5f6f45141c
commit 61a6e93515
5 changed files with 155 additions and 84 deletions

View File

@ -33,17 +33,40 @@ pub enum Modality {
RangeSelect, RangeSelect,
} }
#[derive(Debug, Default)]
pub struct RangeSelection {
pub original_location: Option<Address>,
pub original_sheet: Option<u32>,
pub sheet: Option<u32>,
pub start: Option<Address>,
pub end: Option<Address>,
}
impl RangeSelection {
pub fn get_range(&self) -> Option<(Address, Address)> {
if let (Some(start), Some(end)) = (&self.start, &self.end) {
return Some((
Address {
row: std::cmp::min(start.row, end.row),
col: std::cmp::min(start.col, end.col),
},
Address {
row: std::cmp::max(start.row, end.row),
col: std::cmp::max(start.col, end.col),
},
));
}
None
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct AppState<'ws> { pub struct AppState<'ws> {
pub modality_stack: Vec<Modality>, pub modality_stack: Vec<Modality>,
pub viewport_state: ViewportState, pub viewport_state: ViewportState,
pub command_state: TextState<'ws>, pub command_state: TextState<'ws>,
pub numeric_prefix: Vec<char>, pub numeric_prefix: Vec<char>,
pub original_location: Option<Address>, pub range_select: RangeSelection,
pub original_sheet: Option<u32>,
pub range_sheet: Option<u32>,
pub start_range: Option<Address>,
pub end_range: Option<Address>,
dirty: bool, dirty: bool,
popup: Vec<String>, popup: Vec<String>,
} }
@ -55,11 +78,7 @@ impl<'ws> Default for AppState<'ws> {
viewport_state: Default::default(), viewport_state: Default::default(),
command_state: Default::default(), command_state: Default::default(),
numeric_prefix: Default::default(), numeric_prefix: Default::default(),
original_location: Default::default(), range_select: Default::default(),
original_sheet: Default::default(),
range_sheet: Default::default(),
start_range: Default::default(),
end_range: Default::default(),
dirty: Default::default(), dirty: Default::default(),
popup: Default::default(), popup: Default::default(),
} }
@ -175,29 +194,22 @@ impl<'ws> Workspace<'ws> {
pub fn selected_range_to_string(&self) -> String { pub fn selected_range_to_string(&self) -> String {
let state = &self.state; let state = &self.state;
let start = state if let Some((start, end)) = state.range_select.get_range() {
.start_range let a1 = format!("{}{}", start.to_range_part(), format!(":{}", end.to_range_part()));
.as_ref() if let Some(range_sheet) = state.range_select.sheet {
.map(|addr| addr.to_range_part()) if range_sheet != self.book.current_sheet {
.unwrap_or_else(|| String::new()); return format!(
let end = state "{}!{}",
.end_range self.book
.as_ref() .get_sheet_name_by_idx(range_sheet as usize)
.map(|addr| format!(":{}", addr.to_range_part())) .expect("No such sheet index"),
.unwrap_or_else(|| String::new()); a1
if let Some(range_sheet) = state.range_sheet { );
if range_sheet != self.book.current_sheet { }
return format!(
"{}!{}{}",
self.book
.get_sheet_name_by_idx(range_sheet as usize)
.expect("No such sheet index"),
start,
end
);
} }
return a1;
} }
format!("{}:{}", start, end) return String::new()
} }
/// Move a row down in the current sheet. /// Move a row down in the current sheet.
@ -342,7 +354,8 @@ impl<'ws> Workspace<'ws> {
return Ok(None); return Ok(None);
} }
KeyCode::Char('p') if key.modifiers == KeyModifiers::CONTROL => { KeyCode::Char('p') if key.modifiers == KeyModifiers::CONTROL => {
self.text_area.set_yank_text(self.selected_range_to_string()); self.text_area
.set_yank_text(self.selected_range_to_string());
self.text_area.paste(); self.text_area.paste();
self.state.dirty = true; self.state.dirty = true;
return Ok(None); return Ok(None);
@ -438,7 +451,13 @@ impl<'ws> Workspace<'ws> {
if key.kind == KeyEventKind::Press { if key.kind == KeyEventKind::Press {
match key.code { match key.code {
KeyCode::Esc => { KeyCode::Esc => {
self.state.reset_n_prefix(); if self.state.numeric_prefix.len() > 0 {
self.state.reset_n_prefix();
} else {
self.state.range_select.start = None;
self.state.range_select.end = None;
self.exit_range_select_mode()?;
}
} }
KeyCode::Char('h') if key.modifiers == KeyModifiers::ALT => { KeyCode::Char('h') if key.modifiers == KeyModifiers::ALT => {
self.enter_dialog_mode(self.render_help_text()); self.enter_dialog_mode(self.render_help_text());
@ -452,46 +471,52 @@ impl<'ws> Workspace<'ws> {
ws.move_left()?; ws.move_left()?;
Ok(()) Ok(())
})?; })?;
self.maybe_update_range_end();
} }
KeyCode::Char('j') => { KeyCode::Char('j') => {
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> { self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
ws.move_down()?; ws.move_down()?;
Ok(()) Ok(())
})?; })?;
self.maybe_update_range_end();
} }
KeyCode::Char('k') => { KeyCode::Char('k') => {
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> { self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
ws.move_up()?; ws.move_up()?;
Ok(()) Ok(())
})?; })?;
self.maybe_update_range_end();
} }
KeyCode::Char('l') => { KeyCode::Char('l') => {
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> { self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
ws.move_right()?; ws.move_right()?;
Ok(()) Ok(())
})?; })?;
self.maybe_update_range_end();
} }
KeyCode::Char(' ') => { KeyCode::Char(' ') | KeyCode::Enter => {
if self.state.start_range.is_none() { if self.state.range_select.start.is_none() {
self.state.start_range = Some(self.book.location.clone()); self.state.range_select.start = Some(self.book.location.clone());
} else { } else {
self.state.end_range = Some(self.book.location.clone()); self.state.range_select.end = Some(self.book.location.clone());
self.exit_range_select_mode()?; self.exit_range_select_mode()?;
} }
} }
KeyCode::Char('n') if key.modifiers == KeyModifiers::CONTROL => { KeyCode::Char('n') if key.modifiers == KeyModifiers::CONTROL => {
// TODO(jwall): We should reset our range selections.
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> { self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
ws.book.select_next_sheet(); ws.book.select_next_sheet();
Ok(()) Ok(())
})?; })?;
self.state.range_sheet = Some(self.book.current_sheet); self.state.range_select.sheet = Some(self.book.current_sheet);
} }
KeyCode::Char('p') if key.modifiers == KeyModifiers::CONTROL => { KeyCode::Char('p') if key.modifiers == KeyModifiers::CONTROL => {
// TODO(jwall): We should reset our range selections.
self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> { self.run_with_prefix(|ws: &mut Workspace<'_>| -> Result<()> {
ws.book.select_prev_sheet(); ws.book.select_prev_sheet();
Ok(()) Ok(())
})?; })?;
self.state.range_sheet = Some(self.book.current_sheet); self.state.range_select.sheet = Some(self.book.current_sheet);
} }
_ => { _ => {
// moop // moop
@ -501,6 +526,12 @@ impl<'ws> Workspace<'ws> {
Ok(None) Ok(None)
} }
fn maybe_update_range_end(&mut self) {
if self.state.range_select.start.is_some() {
self.state.range_select.end = Some(self.book.location.clone());
}
}
fn handle_navigation_input(&mut self, key: event::KeyEvent) -> Result<Option<ExitCode>> { fn handle_navigation_input(&mut self, key: event::KeyEvent) -> Result<Option<ExitCode>> {
if key.kind == KeyEventKind::Press { if key.kind == KeyEventKind::Press {
match key.code { match key.code {
@ -652,11 +683,11 @@ impl<'ws> Workspace<'ws> {
} }
fn enter_range_select_mode(&mut self) { fn enter_range_select_mode(&mut self) {
self.state.range_sheet = Some(self.book.current_sheet); self.state.range_select.sheet = Some(self.book.current_sheet);
self.state.original_sheet = Some(self.book.current_sheet); self.state.range_select.original_sheet = Some(self.book.current_sheet);
self.state.original_location = Some(self.book.location.clone()); self.state.range_select.original_location = Some(self.book.location.clone());
self.state.start_range = None; self.state.range_select.start = None;
self.state.end_range = None; self.state.range_select.end = None;
self.state.modality_stack.push(Modality::RangeSelect); self.state.modality_stack.push(Modality::RangeSelect);
} }
@ -687,18 +718,21 @@ impl<'ws> Workspace<'ws> {
fn exit_range_select_mode(&mut self) -> Result<()> { fn exit_range_select_mode(&mut self) -> Result<()> {
self.book.current_sheet = self self.book.current_sheet = self
.state .state
.range_select
.original_sheet .original_sheet
.clone() .clone()
.expect("Missing original sheet"); .expect("Missing original sheet");
self.book.location = self self.book.location = self
.state .state
.range_select
.original_location .original_location
.clone() .clone()
.expect("Missing original location after range copy"); .expect("Missing original location after range copy");
self.state.original_location = None; self.state.range_select.original_location = None;
self.state.pop_modality(); self.state.pop_modality();
if self.state.modality() == &Modality::CellEdit { if self.state.modality() == &Modality::CellEdit {
self.text_area.set_yank_text(self.selected_range_to_string()); self.text_area
.set_yank_text(self.selected_range_to_string());
self.text_area.paste(); self.text_area.paste();
self.state.dirty = true; self.state.dirty = true;
} }
@ -712,9 +746,8 @@ impl<'ws> Workspace<'ws> {
if self.state.dirty && keep { if self.state.dirty && keep {
self.book.edit_current_cell(contents)?; self.book.edit_current_cell(contents)?;
self.book.evaluate(); self.book.evaluate();
} else {
self.text_area = reset_text_area(self.book.get_current_cell_contents()?);
} }
self.text_area = reset_text_area(self.book.get_current_cell_contents()?);
self.state.dirty = false; self.state.dirty = false;
self.state.pop_modality(); self.state.pop_modality();
Ok(()) Ok(())

View File

@ -44,7 +44,7 @@ impl<'ws> Workspace<'ws> {
Box::new(move |rect: Rect, buf: &mut Buffer, ws: &mut Self| { Box::new(move |rect: Rect, buf: &mut Buffer, ws: &mut Self| {
let sheet_name = ws.book.get_sheet_name().unwrap_or("Unknown"); let sheet_name = ws.book.get_sheet_name().unwrap_or("Unknown");
let table_block = Block::bordered().title_top(sheet_name); let table_block = Block::bordered().title_top(sheet_name);
let viewport = Viewport::new(&ws.book) let viewport = Viewport::new(&ws.book, &ws.state.range_select)
.with_selected(ws.book.location.clone()) .with_selected(ws.book.location.clone())
.block(table_block); .block(table_block);
StatefulWidget::render(viewport, rect, buf, &mut ws.state.viewport_state); StatefulWidget::render(viewport, rect, buf, &mut ws.state.viewport_state);

View File

@ -1,15 +1,23 @@
use ironcalc::base::Model; use ironcalc::base::Model;
use crate::ui::AppState;
use super::{Address, Book, Viewport, ViewportState}; use super::{Address, Book, Viewport, ViewportState};
#[test] #[test]
fn test_viewport_get_visible_columns() { fn test_viewport_get_visible_columns() {
let mut state = ViewportState::default(); let mut state = ViewportState::default();
let book = Book::new(Model::new_empty("test", "en", "America/New_York").expect("Failed to make model")); let book = Book::new(
Model::new_empty("test", "en", "America/New_York").expect("Failed to make model"),
);
let default_size = book.get_col_size(1).expect("Failed to get column size"); let default_size = book.get_col_size(1).expect("Failed to get column size");
let width = dbg!(dbg!(default_size) * 12 / 2); let width = dbg!(dbg!(default_size) * 12 / 2);
let viewport = Viewport::new(&book).with_selected(Address { row: 1, col: 17 }); let app_state = AppState::default();
let cols = viewport.get_visible_columns((width + 5) as u16, &mut state).expect("Failed to get visible columns"); let viewport =
Viewport::new(&book, &app_state.range_select).with_selected(Address { row: 1, col: 17 });
let cols = viewport
.get_visible_columns((width + 5) as u16, &mut state)
.expect("Failed to get visible columns");
assert_eq!(5, cols.len()); assert_eq!(5, cols.len());
assert_eq!(17, cols.last().expect("Failed to get last column").idx); assert_eq!(17, cols.last().expect("Failed to get last column").idx);
} }
@ -17,32 +25,50 @@ fn test_viewport_get_visible_columns() {
#[test] #[test]
fn test_viewport_get_visible_rows() { fn test_viewport_get_visible_rows() {
let mut state = dbg!(ViewportState::default()); let mut state = dbg!(ViewportState::default());
let book = Book::new(Model::new_empty("test", "en", "America/New_York").expect("Failed to make model")); let book = Book::new(
Model::new_empty("test", "en", "America/New_York").expect("Failed to make model"),
);
let height = 6; let height = 6;
let viewport = Viewport::new(&book).with_selected(Address { row: 17, col: 1 }); let app_state = AppState::default();
let viewport =
Viewport::new(&book, &app_state.range_select).with_selected(Address { row: 17, col: 1 });
let rows = dbg!(viewport.get_visible_rows(height as u16, &mut state)); let rows = dbg!(viewport.get_visible_rows(height as u16, &mut state));
assert_eq!(height - 1, rows.len()); assert_eq!(height - 1, rows.len());
assert_eq!(17 - (height - 2), *rows.first().expect("Failed to get first row")); assert_eq!(
17 - (height - 2),
*rows.first().expect("Failed to get first row")
);
assert_eq!(17, *rows.last().expect("Failed to get last row")); assert_eq!(17, *rows.last().expect("Failed to get last row"));
} }
#[test] #[test]
fn test_viewport_visible_columns_after_length_change() { fn test_viewport_visible_columns_after_length_change() {
let mut state = ViewportState::default(); let mut state = ViewportState::default();
let mut book = Book::new(Model::new_empty("test", "en", "America/New_York").expect("Failed to make model")); let mut book = Book::new(
Model::new_empty("test", "en", "America/New_York").expect("Failed to make model"),
);
let default_size = book.get_col_size(1).expect("Failed to get column size"); let default_size = book.get_col_size(1).expect("Failed to get column size");
let width = dbg!(dbg!(default_size) * 12 / 2); let width = dbg!(dbg!(default_size) * 12 / 2);
{ {
let viewport = Viewport::new(&book).with_selected(Address { row: 1, col: 17 }); let app_state = AppState::default();
let cols = viewport.get_visible_columns((width + 5) as u16, &mut state).expect("Failed to get visible columns"); let viewport = Viewport::new(&book, &app_state.range_select)
.with_selected(Address { row: 1, col: 17 });
let cols = viewport
.get_visible_columns((width + 5) as u16, &mut state)
.expect("Failed to get visible columns");
assert_eq!(5, cols.len()); assert_eq!(5, cols.len());
assert_eq!(17, cols.last().expect("Failed to get last column").idx); assert_eq!(17, cols.last().expect("Failed to get last column").idx);
} }
book.set_col_size(1, default_size * 6).expect("Failed to set column size"); book.set_col_size(1, default_size * 6)
.expect("Failed to set column size");
{ {
let viewport = Viewport::new(&book).with_selected(Address { row: 1, col: 1 }); let app_state = AppState::default();
let cols = viewport.get_visible_columns((width + 5) as u16, &mut state).expect("Failed to get visible columns"); let viewport =
Viewport::new(&book, &app_state.range_select).with_selected(Address { row: 1, col: 1 });
let cols = viewport
.get_visible_columns((width + 5) as u16, &mut state)
.expect("Failed to get visible columns");
assert_eq!(1, cols.len()); assert_eq!(1, cols.len());
assert_eq!(1, cols.last().expect("Failed to get last column").idx); assert_eq!(1, cols.last().expect("Failed to get last column").idx);
} }

View File

@ -7,7 +7,7 @@ use ratatui::{
widgets::{Block, Cell, Row, StatefulWidget, Table, Widget}, widgets::{Block, Cell, Row, StatefulWidget, Table, Widget},
}; };
use super::{Address, Book}; use super::{Address, AppState, Book, RangeSelection};
// TODO(zaphar): Move this to the book module. // TODO(zaphar): Move this to the book module.
// NOTE(zaphar): This is stolen from ironcalc but ironcalc doesn't expose it // NOTE(zaphar): This is stolen from ironcalc but ironcalc doesn't expose it
@ -34,10 +34,11 @@ pub struct ViewportState {
} }
/// A renderable viewport over a book. /// A renderable viewport over a book.
pub struct Viewport<'book> { pub struct Viewport<'ws> {
pub(crate) selected: Address, pub(crate) selected: Address,
book: &'book Book, book: &'ws Book,
block: Option<Block<'book>>, range_selection: &'ws RangeSelection,
block: Option<Block<'ws>>,
} }
pub(crate) const COLNAMES: [&'static str; 26] = [ pub(crate) const COLNAMES: [&'static str; 26] = [
@ -45,10 +46,11 @@ pub(crate) const COLNAMES: [&'static str; 26] = [
"T", "U", "V", "W", "X", "Y", "Z", "T", "U", "V", "W", "X", "Y", "Z",
]; ];
impl<'book> Viewport<'book> { impl<'ws> Viewport<'ws> {
pub fn new(book: &'book Book) -> Self { pub fn new(book: &'ws Book, app_state: &'ws RangeSelection) -> Self {
Self { Self {
book, book,
range_selection: app_state,
selected: Default::default(), selected: Default::default(),
block: None, block: None,
} }
@ -127,7 +129,7 @@ impl<'book> Viewport<'book> {
return Ok(visible); return Ok(visible);
} }
pub fn block(mut self, block: Block<'book>) -> Self { pub fn block(mut self, block: Block<'ws>) -> Self {
self.block = Some(block); self.block = Some(block);
self self
} }
@ -158,7 +160,17 @@ impl<'book> Viewport<'book> {
.book .book
.get_cell_addr_rendered(&Address { row: ri, col: *ci }) .get_cell_addr_rendered(&Address { row: ri, col: *ci })
.unwrap(); .unwrap();
let cell = Cell::new(Text::raw(content)); let mut cell = Cell::new(Text::raw(content));
if let Some((start, end)) = &self.range_selection.get_range() {
if ri >= start.row
&& ri <= end.row
&& *ci >= start.col
&& *ci <= end.col
{
// This is a selected range
cell = cell.fg(Color::Black).bg(Color::LightBlue)
}
}
match (self.book.location.row == ri, self.book.location.col == *ci) { match (self.book.location.row == ri, self.book.location.col == *ci) {
(true, true) => cell.fg(Color::White).bg(Color::Rgb(57, 61, 71)), (true, true) => cell.fg(Color::White).bg(Color::Rgb(57, 61, 71)),
_ => cell, _ => cell,
@ -197,7 +209,7 @@ impl<'book> Viewport<'book> {
} }
} }
impl<'book> StatefulWidget for Viewport<'book> { impl<'ws> StatefulWidget for Viewport<'ws> {
type State = ViewportState; type State = ViewportState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {

View File

@ -363,24 +363,24 @@ fn test_range_copy() {
ws.handle_input(construct_modified_key_event(KeyCode::Char('r'), KeyModifiers::CONTROL)) ws.handle_input(construct_modified_key_event(KeyCode::Char('r'), KeyModifiers::CONTROL))
.expect("Failed to handle 'Ctrl-r' key event"); .expect("Failed to handle 'Ctrl-r' key event");
assert_eq!(Some(&Modality::RangeSelect), ws.state.modality_stack.last()); assert_eq!(Some(&Modality::RangeSelect), ws.state.modality_stack.last());
assert_eq!(Some(original_loc.clone()), ws.state.original_location); assert_eq!(Some(original_loc.clone()), ws.state.range_select.original_location);
assert!(ws.state.start_range.is_none()); assert!(ws.state.range_select.start.is_none());
assert!(ws.state.end_range.is_none()); assert!(ws.state.range_select.end.is_none());
ws.handle_input(construct_key_event(KeyCode::Char('l'))) ws.handle_input(construct_key_event(KeyCode::Char('l')))
.expect("Failed to handle 'l' key event"); .expect("Failed to handle 'l' key event");
ws.handle_input(construct_key_event(KeyCode::Char(' '))) ws.handle_input(construct_key_event(KeyCode::Char(' ')))
.expect("Failed to handle ' ' key event"); .expect("Failed to handle ' ' key event");
assert_eq!(Some(Address {row:1, col:2, }), ws.state.start_range); assert_eq!(Some(Address {row:1, col:2, }), ws.state.range_select.start);
ws.handle_input(construct_key_event(KeyCode::Char('j'))) ws.handle_input(construct_key_event(KeyCode::Char('j')))
.expect("Failed to handle 'j' key event"); .expect("Failed to handle 'j' key event");
ws.handle_input(construct_key_event(KeyCode::Char(' '))) ws.handle_input(construct_key_event(KeyCode::Char(' ')))
.expect("Failed to handle ' ' key event"); .expect("Failed to handle ' ' key event");
assert!(ws.state.original_location.is_none()); assert!(ws.state.range_select.original_location.is_none());
assert_eq!(Some(Address {row:1, col:2, }), ws.state.start_range); assert_eq!(Some(Address {row:1, col:2, }), ws.state.range_select.start);
assert_eq!(Some(Address {row:2, col:2, }), ws.state.end_range); assert_eq!(Some(Address {row:2, col:2, }), ws.state.range_select.end);
assert_eq!(original_loc, ws.book.location); assert_eq!(original_loc, ws.book.location);
assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last()); assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last());
@ -391,24 +391,24 @@ fn test_range_copy() {
ws.handle_input(construct_modified_key_event(KeyCode::Char('r'), KeyModifiers::CONTROL)) ws.handle_input(construct_modified_key_event(KeyCode::Char('r'), KeyModifiers::CONTROL))
.expect("Failed to handle 'Ctrl-r' key event"); .expect("Failed to handle 'Ctrl-r' key event");
assert_eq!(Some(&Modality::RangeSelect), ws.state.modality_stack.last()); assert_eq!(Some(&Modality::RangeSelect), ws.state.modality_stack.last());
assert_eq!(Some(original_loc_2.clone()), ws.state.original_location); assert_eq!(Some(original_loc_2.clone()), ws.state.range_select.original_location);
assert!(ws.state.start_range.is_none()); assert!(ws.state.range_select.start.is_none());
assert!(ws.state.end_range.is_none()); assert!(ws.state.range_select.end.is_none());
ws.handle_input(construct_key_event(KeyCode::Char('h'))) ws.handle_input(construct_key_event(KeyCode::Char('h')))
.expect("Failed to handle 'h' key event"); .expect("Failed to handle 'h' key event");
ws.handle_input(construct_key_event(KeyCode::Char(' '))) ws.handle_input(construct_key_event(KeyCode::Char(' ')))
.expect("Failed to handle ' ' key event"); .expect("Failed to handle ' ' key event");
assert_eq!(Some(Address {row:5, col:4, }), ws.state.start_range); assert_eq!(Some(Address {row:5, col:4, }), ws.state.range_select.start);
ws.handle_input(construct_key_event(KeyCode::Char('k'))) ws.handle_input(construct_key_event(KeyCode::Char('k')))
.expect("Failed to handle 'k' key event"); .expect("Failed to handle 'k' key event");
ws.handle_input(construct_key_event(KeyCode::Char(' '))) ws.handle_input(construct_key_event(KeyCode::Char(' ')))
.expect("Failed to handle ' ' key event"); .expect("Failed to handle ' ' key event");
assert!(ws.state.original_location.is_none()); assert!(ws.state.range_select.original_location.is_none());
assert_eq!(Some(Address {row:5, col:4, }), ws.state.start_range); assert_eq!(Some(Address {row:5, col:4, }), ws.state.range_select.start);
assert_eq!(Some(Address {row:4, col:4, }), ws.state.end_range); assert_eq!(Some(Address {row:4, col:4, }), ws.state.range_select.end);
assert_eq!(original_loc_2, ws.book.location); assert_eq!(original_loc_2, ws.book.location);
assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last()); assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.last());
} }