From 4ae7f357c13cf744e8234da2474186511f5e9d20 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 18 Dec 2024 20:29:56 -0500 Subject: [PATCH] chore: utility address range helper --- src/book/mod.rs | 118 ++++++++++++++++++++++++++++++++++++++---------- src/ui/mod.rs | 20 +++----- 2 files changed, 102 insertions(+), 36 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index f6dce1a..91c11b9 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -18,6 +18,64 @@ mod test; const COL_PIXELS: f64 = 5.0; +#[derive(Debug, Clone)] +pub struct AddressRange<'book> { + pub start: &'book Address, + pub end: &'book Address, +} + +impl<'book> AddressRange<'book> { + pub fn as_rows(&self) -> Vec> { + let (row_range, col_range) = self.get_ranges(); + let mut rows = Vec::with_capacity(row_range.len()); + for ri in row_range.iter() { + let mut row = Vec::with_capacity(col_range.len()); + for ci in col_range.iter() { + row.push(Address { row: *ri, col: *ci }); + } + rows.push(row); + } + rows + } + + pub fn as_series(&self) -> Vec
{ + let (row_range, col_range) = self.get_ranges(); + let mut rows = Vec::with_capacity(row_range.len() * col_range.len()); + for ri in row_range.iter() { + for ci in col_range.iter() { + rows.push(Address { row: *ri, col: *ci }); + } + } + rows + } + + fn get_ranges(&self) -> (Vec, Vec) { + let row_range = if self.start.row <= self.end.row { + (self.start.row..=self.end.row) + .into_iter() + .collect::>() + } else { + let mut v = (self.start.row..=self.end.row) + .into_iter() + .collect::>(); + v.reverse(); + v + }; + let col_range = if self.start.col <= self.end.col { + (self.start.col..=self.end.col) + .into_iter() + .collect::>() + } else { + let mut v = (self.start.col..=self.end.col) + .into_iter() + .collect::>(); + v.reverse(); + v + }; + (row_range, col_range) + } +} + /// A spreadsheet book with some internal state tracking. pub struct Book { pub(crate) model: Model, @@ -103,18 +161,35 @@ impl Book { self.location.col = *col; Ok(()) } - + /// Extend a cell to the rest of the range. pub fn extend_to(&mut self, from: &Address, to: &Address) -> Result<()> { - for ri in from.row..=to.row { - for ci in from.col..=to.col { - if ri == from.row && ci == from.col { - continue; - } - let contents = self.model.extend_to(self.current_sheet, from.row as i32, from.col as i32, ri as i32, ci as i32).map_err(|e| anyhow!(e))?; - self.model.set_user_input(self.current_sheet, ri as i32, ci as i32, contents) - .map_err(|e| anyhow!(e))?; - } + for cell in (AddressRange { + start: from, + end: to, + }) + .as_series() + .iter() + .skip(1) + { + let contents = self + .model + .extend_to( + self.current_sheet, + from.row as i32, + from.col as i32, + cell.row as i32, + cell.col as i32, + ) + .map_err(|e| anyhow!(e))?; + self.model + .set_user_input( + self.current_sheet, + cell.row as i32, + cell.col as i32, + contents, + ) + .map_err(|e| anyhow!(e))?; } self.evaluate(); Ok(()) @@ -123,13 +198,12 @@ impl Book { pub fn clear_current_cell(&mut self) -> Result<()> { self.clear_cell_contents(self.current_sheet as u32, self.location.clone()) } - + pub fn clear_current_cell_all(&mut self) -> Result<()> { self.clear_cell_all(self.current_sheet as u32, self.location.clone()) } - - pub fn clear_cell_contents(&mut self, sheet: u32, Address { row, col, }: Address) -> Result<()> { + pub fn clear_cell_contents(&mut self, sheet: u32, Address { row, col }: Address) -> Result<()> { Ok(self .model .cell_clear_contents(sheet, row as i32, col as i32) @@ -144,8 +218,8 @@ impl Book { } Ok(()) } - - pub fn clear_cell_all(&mut self, sheet: u32, Address { row, col, }: Address) -> Result<()> { + + pub fn clear_cell_all(&mut self, sheet: u32, Address { row, col }: Address) -> Result<()> { Ok(self .model .cell_clear_all(sheet, row as i32, col as i32) @@ -161,7 +235,6 @@ impl Book { Ok(()) } - /// Get a cells formatted content. pub fn get_current_cell_rendered(&self) -> Result { Ok(self.get_cell_addr_rendered(&self.location)?) @@ -174,7 +247,7 @@ impl Book { .get_formatted_cell_value(self.current_sheet, *row as i32, *col as i32) .map_err(|s| anyhow!("Unable to format cell {}", s))?) } - + /// Get a cells actual content unformatted as a string. pub fn get_cell_addr_contents(&self, Address { row, col }: &Address) -> Result { Ok(self @@ -183,7 +256,6 @@ impl Book { .map_err(|s| anyhow!("Unable to format cell {}", s))?) } - /// Get a cells actual content as a string. pub fn get_current_cell_contents(&self) -> Result { Ok(self @@ -290,7 +362,7 @@ impl Book { .enumerate() .find(|(_idx, sheet)| sheet.name == name) { - self.current_sheet =idx as u32; + self.current_sheet = idx as u32; return true; } false @@ -309,7 +381,7 @@ impl Book { } self.current_sheet = next; } - + pub fn select_prev_sheet(&mut self) { let len = self.model.workbook.worksheets.len() as u32; let next = if self.current_sheet == 0 { @@ -320,7 +392,6 @@ impl Book { self.current_sheet = next; } - /// Select a sheet by id. pub fn select_sheet_by_id(&mut self, id: u32) -> bool { if let Some((idx, _sheet)) = self @@ -353,13 +424,14 @@ impl Book { .worksheet_mut(self.current_sheet) .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?) } - + pub(crate) fn get_sheet_name_by_idx(&self, idx: usize) -> Result<&str> { Ok(&self .model .workbook .worksheet(idx as u32) - .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?.name) + .map_err(|s| anyhow!("Invalid Worksheet: {}", s))? + .name) } pub(crate) fn get_sheet_by_idx_mut(&mut self, idx: usize) -> Result<&mut Worksheet> { Ok(self diff --git a/src/ui/mod.rs b/src/ui/mod.rs index c84a51e..53974e5 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,7 +1,7 @@ //! Ui rendering logic use std::{path::PathBuf, process::ExitCode}; -use crate::book::Book; +use crate::book::{AddressRange, Book}; use anyhow::{anyhow, Result}; use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers}; @@ -598,25 +598,19 @@ impl<'ws> Workspace<'ws> { self.update_range_selection()?; match &self.state.range_select.get_range() { Some(( - Address { - row: row_start, - col: col_start, - }, - Address { - row: row_end, - col: col_end, - }, + start, + end, )) => { let mut rows = Vec::new(); - for ri in (*row_start)..=(*row_end) { + for row in (AddressRange { start, end, }).as_rows() { let mut cols = Vec::new(); - for ci in (*col_start)..=(*col_end) { + for cell in row { cols.push(if formatted { self.book - .get_cell_addr_rendered(&Address { row: ri, col: ci })? + .get_cell_addr_rendered(&cell)? } else { self.book - .get_cell_addr_contents(&Address { row: ri, col: ci })? + .get_cell_addr_contents(&cell)? }); } rows.push(cols);