From 7a5bd63fde9de7ba0d41117b19c59fa42542af66 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 30 Jan 2025 19:43:25 -0500 Subject: [PATCH 01/12] wip: Convert over to UserModel Step 1: update ironcalc version and plug it in --- Cargo.lock | 117 ++++------------------- Cargo.toml | 3 +- src/book/mod.rs | 210 +++++++++++++++++++++++++----------------- src/ui/mod.rs | 2 +- src/ui/render/test.rs | 6 +- 5 files changed, 149 insertions(+), 189 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7d3e8f..980ec09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,15 +28,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "aho-corasick" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" -dependencies = [ - "memchr", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -182,12 +173,6 @@ dependencies = [ "syn", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" @@ -293,9 +278,9 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +checksum = "9c6ac4f2c0bf0f44e9161aec9675e1050aa4a530663c4a9e37e108fa948bca9f" dependencies = [ "chrono", "chrono-tz-build", @@ -304,12 +289,11 @@ dependencies = [ [[package]] name = "chrono-tz-build" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" dependencies = [ "parse-zoneinfo", - "phf", "phf_codegen", ] @@ -431,7 +415,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.6.0", + "bitflags", "crossterm_winapi", "futures-core", "mio", @@ -483,19 +467,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "csv-sniffer" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b8e952164bb270a505d6cb6136624174c34cfb9abd16e0011f5e53058317f39" -dependencies = [ - "bitflags 1.3.2", - "csv", - "csv-core", - "memchr", - "regex 0.2.11", -] - [[package]] name = "csvx" version = "0.1.17" @@ -869,8 +840,8 @@ dependencies = [ [[package]] name = "ironcalc" -version = "0.2.0" -source = "git+https://github.com/ironcalc/IronCalc#98dc557a017b2ad640fb46eece17afda14177e59" +version = "0.3.0" +source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#99125f1fea1c8c72c61f8cba94d847ed3471a4af" dependencies = [ "bitcode", "chrono", @@ -885,18 +856,17 @@ dependencies = [ [[package]] name = "ironcalc_base" -version = "0.2.0" -source = "git+https://github.com/ironcalc/IronCalc#98dc557a017b2ad640fb46eece17afda14177e59" +version = "0.3.0" +source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#99125f1fea1c8c72c61f8cba94d847ed3471a4af" dependencies = [ "bitcode", "chrono", "chrono-tz", "csv", - "csv-sniffer", "js-sys", "once_cell", "rand", - "regex 1.11.1", + "regex", "ryu", "serde", ] @@ -949,12 +919,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.161" @@ -1085,7 +1049,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ - "regex 1.11.1", + "regex", ] [[package]] @@ -1251,7 +1215,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.6.0", + "bitflags", "cassowary", "compact_str", "crossterm", @@ -1281,20 +1245,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.6.0", -] - -[[package]] -name = "regex" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" -dependencies = [ - "aho-corasick 0.6.10", - "memchr", - "regex-syntax 0.5.6", - "thread_local", - "utf8-ranges", + "bitflags", ] [[package]] @@ -1303,10 +1254,10 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ - "aho-corasick 1.1.3", + "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] @@ -1315,18 +1266,9 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ - "aho-corasick 1.1.3", + "aho-corasick", "memchr", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-syntax" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" -dependencies = [ - "ucd-util", + "regex-syntax", ] [[package]] @@ -1370,7 +1312,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "regex 1.11.1", + "regex", "relative-path", "rustc_version", "syn", @@ -1398,7 +1340,7 @@ version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1636,15 +1578,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -dependencies = [ - "lazy_static", -] - [[package]] name = "time" version = "0.3.36" @@ -1722,12 +1655,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ucd-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" - [[package]] name = "unicode-ident" version = "1.0.13" @@ -1763,12 +1690,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" -[[package]] -name = "utf8-ranges" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" - [[package]] name = "utf8parse" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 5da5cb0..66a5066 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,8 @@ anyhow = { version = "1.0.91", features = ["backtrace"] } clap = { version = "4.5.20", features = ["derive"] } crossterm = { version = "0.28.1", features = ["event-stream", "serde"] } csvx = "0.1.17" -ironcalc = { git = "https://github.com/ironcalc/IronCalc" } +# this revision introduces a way to get the Model back out of the UserModel +ironcalc = { git = "https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c" } futures = "0.3.31" ratatui = "0.29.0" thiserror = "1.0.65" diff --git a/src/book/mod.rs b/src/book/mod.rs index e454cd4..1ebb658 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -3,9 +3,10 @@ use std::cmp::max; use anyhow::{anyhow, Result}; use ironcalc::{ base::{ + expressions::types::Area, types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, worksheet::WorksheetDimension, - Model, + Model, UserModel, }, export::save_xlsx_to_writer, import::load_from_xlsx, @@ -37,7 +38,7 @@ impl<'book> AddressRange<'book> { } 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()); @@ -78,14 +79,14 @@ impl<'book> AddressRange<'book> { /// A spreadsheet book with some internal state tracking. pub struct Book { - pub(crate) model: Model, + pub(crate) model: UserModel, pub current_sheet: u32, pub location: crate::ui::Address, } impl Book { /// Construct a new book from a Model - pub fn new(model: Model) -> Self { + pub fn new(model: UserModel) -> Self { Self { model, current_sheet: 0, @@ -93,9 +94,17 @@ impl Book { } } + pub fn from_model(model: Model) -> Self { + Self::new(UserModel::from_model(model)) + } + /// Construct a new book from an xlsx file. pub fn new_from_xlsx(path: &str) -> Result { - Ok(Self::new(load_from_xlsx(path, "en", "America/New_York")?)) + Ok(Self::from_model(load_from_xlsx( + path, + "en", + "America/New_York", + )?)) } /// Evaluate the spreadsheet calculating formulas and style changes. @@ -107,7 +116,7 @@ impl Book { // TODO(zaphar): Should I support ICalc? /// Construct a new book from a path. pub fn new_from_xlsx_with_locale(path: &str, locale: &str, tz: &str) -> Result { - Ok(Self::new(load_from_xlsx(path, locale, tz)?)) + Ok(Self::from_model(load_from_xlsx(path, locale, tz)?)) } /// Save book to an xlsx file. @@ -116,7 +125,7 @@ impl Book { let file_path = std::path::Path::new(path); let file = std::fs::File::create(file_path)?; let writer = std::io::BufWriter::new(file); - save_xlsx_to_writer(&self.model, writer)?; + save_xlsx_to_writer(self.model.get_model(), writer)?; Ok(()) } @@ -124,10 +133,9 @@ impl Book { /// is the sheet name and the u32 is the sheet index. pub fn get_all_sheets_identifiers(&self) -> Vec<(String, u32)> { self.model - .workbook - .worksheets + .get_worksheets_properties() .iter() - .map(|sheet| (sheet.get_name(), sheet.get_sheet_id())) + .map(|sheet| (sheet.name.to_owned(), sheet.sheet_id)) .collect() } @@ -142,11 +150,12 @@ impl Book { } pub fn new_sheet(&mut self, sheet_name: Option<&str>) -> Result<()> { - let (_, idx) = self.model.new_sheet(); - if let Some(name) = sheet_name { - self.set_sheet_name(idx as usize, name)?; - } - Ok(()) + todo!("We need to figure out how to find the new sheet index so we can rename it"); + //let (_, idx) = self.model.new_sheet(); + //if let Some(name) = sheet_name { + // self.set_sheet_name(idx as usize, name)?; + //} + //Ok(()) } /// Get the sheet data for the current worksheet. @@ -172,8 +181,10 @@ impl Book { .iter() .skip(1) { + // TODO(jeremy): Is there a better way to do this using UserModel? let contents = self .model + .get_model() .extend_to( self.current_sheet, from.row as i32, @@ -187,7 +198,7 @@ impl Book { self.current_sheet, cell.row as i32, cell.col as i32, - contents, + &contents, ) .map_err(|e| anyhow!(e))?; } @@ -206,32 +217,53 @@ impl Book { 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) + .range_clear_contents(&Area { + sheet, + row: row as i32, + column: col as i32, + width: 1, + height: 1, + }) .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?) } pub fn clear_cell_range(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> { - for row in start.row..=end.row { - for col in start.col..=end.col { - self.clear_cell_contents(sheet, Address { row, col })?; - } - } + let area = Area { + sheet, + row: start.row as i32, + column: start.col as i32, + width: (end.row - start.row) as i32, + height: (end.col - end.row) as i32, + }; + self.model + .range_clear_contents(&area) + .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?; Ok(()) } 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) + .range_clear_all(&Area { + sheet, + row: row as i32, + column: col as i32, + width: 1, + height: 1, + }) .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?) } pub fn clear_cell_range_all(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> { - for row in start.row..=end.row { - for col in start.col..=end.col { - self.clear_cell_all(sheet, Address { row, col })?; - } - } + let area = Area { + sheet, + row: start.row as i32, + column: start.col as i32, + width: (end.row - start.row) as i32, + height: (end.col - end.row) as i32, + }; + self.model.range_clear_all(&area) + .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?; Ok(()) } @@ -244,7 +276,7 @@ impl Book { // TODO(jwall): This is modeled a little weird. We should probably record // the error *somewhere* but for the user there is nothing to be done except // not use a style. - match self.model.get_style_for_cell(sheet, cell.row as i32, cell.col as i32) + match self.model.get_model().get_style_for_cell(sheet, cell.row as i32, cell.col as i32) { Err(_) => None, Ok(s) => Some(s), @@ -252,12 +284,12 @@ impl Book { } fn get_column(&self, sheet: u32, col: usize) -> Result> { - Ok(self.model.workbook.worksheet(sheet) + Ok(self.model.get_model().workbook.worksheet(sheet) .map_err(|e| anyhow!("{}", e))?.cols.get(col)) } fn get_row(&self, sheet: u32, col: usize) -> Result> { - Ok(self.model.workbook.worksheet(sheet) + Ok(self.model.get_model().workbook.worksheet(sheet) .map_err(|e| anyhow!("{}", e))?.rows.get(col)) } @@ -267,7 +299,7 @@ impl Book { // not use a style. if let Some(col) = self.get_column(sheet, col)? { if let Some(style_idx) = col.style.map(|idx| idx as usize) { - let styles = &self.model.workbook.styles; + let styles = &self.model.get_model().workbook.styles; if styles.cell_style_xfs.len() <= style_idx { return Ok(Some(Style { alignment: None, @@ -289,7 +321,7 @@ impl Book { // not use a style. if let Some(row) = self.get_row(sheet, row)? { let style_idx = row.s as usize; - let styles = &self.model.workbook.styles; + let styles = &self.model.get_model().workbook.styles; if styles.cell_style_xfs.len() <= style_idx { return Ok(Some(Style { alignment: None, @@ -316,40 +348,34 @@ impl Book { } pub fn set_cell_style(&mut self, style: &Style, sheet: u32, cell: &Address) -> Result<()> { - self.model.set_cell_style(sheet, cell.row as i32, cell.col as i32, style) - .map_err(|s| anyhow!("Unable to format cell {}", s))?; - Ok(()) + todo!() + //self.model.set_cell_style(sheet, cell.row as i32, cell.col as i32, style) + // .map_err(|s| anyhow!("Unable to format cell {}", s))?; + //Ok(()) } pub fn set_col_style(&mut self, style: &Style, sheet: u32, col: usize) -> Result<()> { - let idx = self.create_or_get_style_idx(style); - let sheet = self.model.workbook.worksheet_mut(sheet) - .map_err(|e| anyhow!("{}", e))?; - let width = sheet.get_column_width(col as i32) - .map_err(|e| anyhow!("{}", e))?; - sheet.set_column_style(col as i32, idx) - .map_err(|e| anyhow!("{}", e))?; - sheet.set_column_width(col as i32, width) - .map_err(|e| anyhow!("{}", e))?; - Ok(()) + todo!() + //let idx = self.create_or_get_style_idx(style); + //let sheet = self.model.workbook.worksheet_mut(sheet) + // .map_err(|e| anyhow!("{}", e))?; + //let width = sheet.get_column_width(col as i32) + // .map_err(|e| anyhow!("{}", e))?; + //sheet.set_column_style(col as i32, idx) + // .map_err(|e| anyhow!("{}", e))?; + //sheet.set_column_width(col as i32, width) + // .map_err(|e| anyhow!("{}", e))?; + //Ok(()) } pub fn set_row_style(&mut self, style: &Style, sheet: u32, row: usize) -> Result<()> { - let idx = self.create_or_get_style_idx(style); - self.model.workbook.worksheet_mut(sheet) - .map_err(|e| anyhow!("{}", e))? - .set_row_style(row as i32, idx) - .map_err(|e| anyhow!("{}", e))?; - Ok(()) - } - - fn create_or_get_style_idx(&mut self, style: &Style) -> i32 { - let idx = if let Some(style_idx) = self.model.workbook.styles.get_style_index(style) { - style_idx - } else { - self.model.workbook.styles.create_new_style(style) - }; - idx + todo!() + //let idx = self.create_or_get_style_idx(style); + //self.model.workbook.worksheet_mut(sheet) + // .map_err(|e| anyhow!("{}", e))? + // .set_row_style(row as i32, idx) + // .map_err(|e| anyhow!("{}", e))?; + //Ok(()) } /// Get a cells rendered content for display. @@ -395,7 +421,8 @@ impl Book { self.current_sheet, location.row as i32, location.col as i32, - value.into(), + // TODO(jwall): This could probably be made more efficient + &value.into(), ) .map_err(|e| anyhow!("Invalid cell contents: {}", e))?; Ok(()) @@ -403,9 +430,11 @@ impl Book { /// Insert `count` rows at a `row_idx`. pub fn insert_rows(&mut self, row_idx: usize, count: usize) -> Result<()> { - self.model - .insert_rows(self.current_sheet, row_idx as i32, count as i32) - .map_err(|e| anyhow!("Unable to insert row(s): {}", e))?; + for i in 0..count { + self.model + .insert_row(self.current_sheet, (row_idx + i) as i32) + .map_err(|e| anyhow!("Unable to insert row(s): {}", e))?; + } if self.location.row >= row_idx { self.move_to(&Address { row: self.location.row + count, @@ -417,9 +446,11 @@ impl Book { /// Insert `count` columns at a `col_idx`. pub fn insert_columns(&mut self, col_idx: usize, count: usize) -> Result<()> { - self.model - .insert_columns(self.current_sheet, col_idx as i32, count as i32) - .map_err(|e| anyhow!("Unable to insert column(s): {}", e))?; + for i in 0..count { + self.model + .insert_column(self.current_sheet, (col_idx + i) as i32) + .map_err(|e| anyhow!("Unable to insert column(s): {}", e))?; + } if self.location.col >= col_idx { self.move_to(&Address { row: self.location.row, @@ -467,7 +498,7 @@ impl Book { /// Select a sheet by name. pub fn select_sheet_by_name(&mut self, name: &str) -> bool { if let Some((idx, _sheet)) = self - .model + .model.get_model() .workbook .worksheets .iter() @@ -482,11 +513,12 @@ impl Book { /// Get all sheet names pub fn get_sheet_names(&self) -> Vec { - self.model.workbook.get_worksheet_names() + self.model.get_model().workbook.get_worksheet_names() } pub fn select_next_sheet(&mut self) { - let len = self.model.workbook.worksheets.len() as u32; + // TODO(jwall): Is there a cleaner way to do this with UserModel? + let len = self.model.get_model().workbook.worksheets.len() as u32; let mut next = self.current_sheet + 1; if next == len { next = 0; @@ -495,7 +527,8 @@ impl Book { } pub fn select_prev_sheet(&mut self) { - let len = self.model.workbook.worksheets.len() as u32; + // TODO(jwall): Is there a cleaner way to do this with UserModel? + let len = self.model.get_model().workbook.worksheets.len() as u32; let next = if self.current_sheet == 0 { len - 1 } else { @@ -506,8 +539,9 @@ impl Book { /// Select a sheet by id. pub fn select_sheet_by_id(&mut self, id: u32) -> bool { + // TODO(jwall): Is there a cleaner way to do this with UserModel? if let Some((idx, _sheet)) = self - .model + .model.get_model() .workbook .worksheets .iter() @@ -522,42 +556,46 @@ impl Book { /// Get the current `Worksheet`. pub(crate) fn get_sheet(&self) -> Result<&Worksheet> { + // TODO(jwall): Is there a cleaner way to do this with UserModel? Ok(self - .model + .model.get_model() .workbook .worksheet(self.current_sheet) .map_err(|s| anyhow!("Invalid Worksheet id: {}: error: {}", self.current_sheet, s))?) } pub(crate) fn get_sheet_mut(&mut self) -> Result<&mut Worksheet> { - Ok(self - .model - .workbook - .worksheet_mut(self.current_sheet) - .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?) + todo!("Is there a clean way to do this with UserModel?") + //Ok(self + // .model.get_model() + // .workbook + // .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> { + // TODO(jwall): Is there a cleaner way to do this with UserModel? Ok(&self - .model + .model.get_model() .workbook .worksheet(idx as u32) .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 - .model - .workbook - .worksheet_mut(idx as u32) - .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?) + todo!("Is there a clean way to do this with UserModel?") + //Ok(self + // .model + // .workbook + // .worksheet_mut(idx as u32) + // .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?) } } impl Default for Book { fn default() -> Self { let mut book = - Book::new(Model::new_empty("default_name", "en", "America/New_York").unwrap()); + Book::new(UserModel::new_empty("default_name", "en", "America/New_York").unwrap()); book.update_cell(&Address { row: 1, col: 1 }, "").unwrap(); book } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ac85fc7..4ef356b 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -187,7 +187,7 @@ impl<'ws> Workspace<'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))?), + Book::from_model(Model::new_empty("", locale, tz).map_err(|e| anyhow!("{}", e))?), PathBuf::default(), )) } diff --git a/src/ui/render/test.rs b/src/ui/render/test.rs index dc253aa..68f0ecd 100644 --- a/src/ui/render/test.rs +++ b/src/ui/render/test.rs @@ -8,7 +8,7 @@ use super::{Address, Book, Viewport, ViewportState}; #[test] fn test_viewport_get_visible_columns() { let mut state = ViewportState::default(); - let book = Book::new( + let book = Book::from_model( 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"); @@ -26,7 +26,7 @@ fn test_viewport_get_visible_columns() { #[test] fn test_viewport_get_visible_rows() { let mut state = dbg!(ViewportState::default()); - let book = Book::new( + let book = Book::from_model( Model::new_empty("test", "en", "America/New_York").expect("Failed to make model"), ); let height = 6; @@ -45,7 +45,7 @@ fn test_viewport_get_visible_rows() { #[test] fn test_viewport_visible_columns_after_length_change() { let mut state = ViewportState::default(); - let mut book = Book::new( + let mut book = Book::from_model( 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"); From d8b31916120bdad35ae5b6b6d08bc7afda37bad7 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Tue, 4 Feb 2025 19:27:13 -0500 Subject: [PATCH 02/12] wip: new_sheet and cell styling with UserModel --- src/book/mod.rs | 30 +++++++++++++++--------------- src/ui/mod.rs | 35 ++++++++++++++++++----------------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index 1ebb658..bcd1543 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -3,10 +3,7 @@ use std::cmp::max; use anyhow::{anyhow, Result}; use ironcalc::{ base::{ - expressions::types::Area, - types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, - worksheet::WorksheetDimension, - Model, UserModel, + calc_result::Range, expressions::types::Area, types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, worksheet::WorksheetDimension, Model, UserModel }, export::save_xlsx_to_writer, import::load_from_xlsx, @@ -150,12 +147,13 @@ impl Book { } pub fn new_sheet(&mut self, sheet_name: Option<&str>) -> Result<()> { - todo!("We need to figure out how to find the new sheet index so we can rename it"); - //let (_, idx) = self.model.new_sheet(); - //if let Some(name) = sheet_name { - // self.set_sheet_name(idx as usize, name)?; - //} - //Ok(()) + self.model.new_sheet().map_err(|e| anyhow!(e))?; + let idx = self.model.get_selected_sheet(); + if let Some(name) = sheet_name { + self.set_sheet_name(idx as usize, name)?; + } + self.model.set_selected_sheet(self.current_sheet).map_err(|e| anyhow!(e))?; + Ok(()) } /// Get the sheet data for the current worksheet. @@ -347,11 +345,12 @@ impl Book { } } - pub fn set_cell_style(&mut self, style: &Style, sheet: u32, cell: &Address) -> Result<()> { - todo!() - //self.model.set_cell_style(sheet, cell.row as i32, cell.col as i32, style) - // .map_err(|s| anyhow!("Unable to format cell {}", s))?; - //Ok(()) + pub fn set_cell_style(&mut self, style: &[(&str, &str)], area: &Area) -> Result<()> { + for (path, val) in style { + self.model.update_range_style(area, path, val) + .map_err(|s| anyhow!("Unable to format cell {}", s))?; + } + Ok(()) } pub fn set_col_style(&mut self, style: &Style, sheet: u32, col: usize) -> Result<()> { @@ -582,6 +581,7 @@ impl Book { .map_err(|s| anyhow!("Invalid Worksheet: {}", s))? .name) } + pub(crate) fn get_sheet_by_idx_mut(&mut self, idx: usize) -> Result<&mut Worksheet> { todo!("Is there a clean way to do this with UserModel?") //Ok(self diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 4ef356b..752ae75 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -5,7 +5,7 @@ use crate::book::{AddressRange, Book}; use anyhow::{anyhow, Result}; use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers}; -use ironcalc::base::Model; +use ironcalc::base::{expressions::types::Area, Model}; use ratatui::{ buffer::Buffer, layout::{Constraint, Flex, Layout}, @@ -494,25 +494,26 @@ impl<'ws> Workspace<'ws> { Ok(None) } Ok(Some(Cmd::ColorCell(color))) => { - if let Some((start, end)) = self.state.range_select.get_range() { - for ri in start.row..=end.row { - for ci in start.col..=end.col { - let address = Address { row: ri, col: ci }; - let sheet = self.book.current_sheet; - let mut style = self.book.get_cell_style(sheet, &address) - .expect("I think this should be impossible.").clone(); - style.fill.bg_color = Some(color.to_string()); - self.book.set_cell_style(&style, sheet, &address)?; - } + let sheet = self.book.current_sheet; + let area = if let Some((start, end)) = self.state.range_select.get_range() { + Area { + sheet, + row: start.row as i32, + column: start.col as i32, + width: (end.col - start.col) as i32, + height: (end.row - start.row) as i32 } } else { let address = self.book.location.clone(); - let sheet = self.book.current_sheet; - let mut style = self.book.get_cell_style(sheet, &address) - .expect("I think this should be impossible.").clone(); - style.fill.bg_color = Some(color.to_string()); - self.book.set_cell_style(&style, sheet, &address)?; - } + Area { + sheet, + row: address.row as i32, + column: address.col as i32, + width: 1, + height: 1 + } + }; + self.book.set_cell_style(&[("fill.bg_color", color)], &area)?; Ok(None) } Ok(None) => { From 43f07f58bcc787a26ae37e3ec1dbdf7ee986b3bf Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Fri, 7 Feb 2025 17:54:37 -0500 Subject: [PATCH 03/12] wip: sheet renaming and col sizing with UserModel --- src/book/mod.rs | 45 ++++++++++++++++----------------------------- src/ui/mod.rs | 4 ++-- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index bcd1543..f61b0d6 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -141,8 +141,8 @@ impl Book { Ok(&self.get_sheet()?.name) } - pub fn set_sheet_name(&mut self, idx: usize, sheet_name: &str) -> Result<()> { - self.get_sheet_by_idx_mut(idx)?.set_name(sheet_name); + pub fn set_sheet_name(&mut self, idx: u32, sheet_name: &str) -> Result<()> { + self.model.rename_sheet(idx, sheet_name).map_err(|e| anyhow!(e))?; Ok(()) } @@ -150,7 +150,7 @@ impl Book { self.model.new_sheet().map_err(|e| anyhow!(e))?; let idx = self.model.get_selected_sheet(); if let Some(name) = sheet_name { - self.set_sheet_name(idx as usize, name)?; + self.set_sheet_name(idx, name)?; } self.model.set_selected_sheet(self.current_sheet).map_err(|e| anyhow!(e))?; Ok(()) @@ -353,7 +353,7 @@ impl Book { Ok(()) } - pub fn set_col_style(&mut self, style: &Style, sheet: u32, col: usize) -> Result<()> { + pub fn set_col_style(&mut self, style: &[(&str, &str)], sheet: u32, col: usize) -> Result<()> { todo!() //let idx = self.create_or_get_style_idx(style); //let sheet = self.model.workbook.worksheet_mut(sheet) @@ -367,7 +367,7 @@ impl Book { //Ok(()) } - pub fn set_row_style(&mut self, style: &Style, sheet: u32, row: usize) -> Result<()> { + pub fn set_row_style(&mut self, style: &[(&str, &str)], sheet: u32, row: usize) -> Result<()> { todo!() //let idx = self.create_or_get_style_idx(style); //self.model.workbook.worksheet_mut(sheet) @@ -466,16 +466,21 @@ impl Book { /// Get column size pub fn get_col_size(&self, idx: usize) -> Result { - Ok((self - .get_sheet()? - .get_column_width(idx as i32) + self.get_column_size_for_sheet(self.current_sheet, idx) + } + + pub fn get_column_size_for_sheet(&self, sheet: u32, idx: usize) -> std::result::Result { + Ok((self.model.get_column_width(sheet, idx as i32) .map_err(|e| anyhow!("Error getting column width: {:?}", e))? / COL_PIXELS) as usize) } - pub fn set_col_size(&mut self, idx: usize, cols: usize) -> Result<()> { - self.get_sheet_mut()? - .set_column_width(idx as i32, cols as f64 * COL_PIXELS) + pub fn set_col_size(&mut self, col: usize, width: usize) -> Result<()> { + self.set_column_size_for_sheet(self.current_sheet, col, width) + } + + pub fn set_column_size_for_sheet(&mut self, sheet: u32, col: usize, width: usize) -> std::result::Result<(), anyhow::Error> { + self.model.set_column_width(sheet, col as i32, width as f64 * COL_PIXELS) .map_err(|e| anyhow!("Error setting column width: {:?}", e))?; Ok(()) } @@ -563,15 +568,6 @@ impl Book { .map_err(|s| anyhow!("Invalid Worksheet id: {}: error: {}", self.current_sheet, s))?) } - pub(crate) fn get_sheet_mut(&mut self) -> Result<&mut Worksheet> { - todo!("Is there a clean way to do this with UserModel?") - //Ok(self - // .model.get_model() - // .workbook - // .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> { // TODO(jwall): Is there a cleaner way to do this with UserModel? Ok(&self @@ -581,15 +577,6 @@ impl Book { .map_err(|s| anyhow!("Invalid Worksheet: {}", s))? .name) } - - pub(crate) fn get_sheet_by_idx_mut(&mut self, idx: usize) -> Result<&mut Worksheet> { - todo!("Is there a clean way to do this with UserModel?") - //Ok(self - // .model - // .workbook - // .worksheet_mut(idx as u32) - // .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?) - } } impl Default for Book { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 752ae75..8498dc3 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -445,11 +445,11 @@ impl<'ws> Workspace<'ws> { Ok(Some(Cmd::RenameSheet(idx, name))) => { match idx { Some(idx) => { - self.book.set_sheet_name(idx, name)?; + self.book.set_sheet_name(idx as u32, name)?; } _ => { self.book - .set_sheet_name(self.book.current_sheet as usize, name)?; + .set_sheet_name(self.book.current_sheet, name)?; } } Ok(None) From 8dd6f6d614163aa2c856f961b067c5cb0017cd57 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Fri, 7 Feb 2025 19:10:51 -0500 Subject: [PATCH 04/12] wip: cleanup, todos, unused code, formatting --- src/book/mod.rs | 21 +++++++++++++-------- src/ui/mod.rs | 17 ++--------------- src/ui/render/viewport.rs | 1 - 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index f61b0d6..a5817e1 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -3,7 +3,7 @@ use std::cmp::max; use anyhow::{anyhow, Result}; use ironcalc::{ base::{ - calc_result::Range, expressions::types::Area, types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, worksheet::WorksheetDimension, Model, UserModel + expressions::types::Area, types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, worksheet::WorksheetDimension, Model, UserModel }, export::save_xlsx_to_writer, import::load_from_xlsx, @@ -110,7 +110,6 @@ impl Book { self.model.evaluate(); } - // TODO(zaphar): Should I support ICalc? /// Construct a new book from a path. pub fn new_from_xlsx_with_locale(path: &str, locale: &str, tz: &str) -> Result { Ok(Self::from_model(load_from_xlsx(path, locale, tz)?)) @@ -180,6 +179,8 @@ impl Book { .skip(1) { // TODO(jeremy): Is there a better way to do this using UserModel? + // Looks like this is the recommended way: + // https://docs.rs/ironcalc_base/latest/ironcalc_base/struct.UserModel.html#method.auto_fill_columns let contents = self .model .get_model() @@ -407,21 +408,21 @@ impl Book { /// Update the current cell in a book. /// This update won't be reflected until you call `Book::evaluate`. - pub fn edit_current_cell>(&mut self, value: S) -> Result<()> { + pub fn edit_current_cell>(&mut self, value: S) -> Result<()> { self.update_cell(&self.location.clone(), value)?; Ok(()) } /// Update an entry in the current sheet for a book. /// This update won't be reflected until you call `Book::evaluate`. - pub fn update_cell>(&mut self, location: &Address, value: S) -> Result<()> { + pub fn update_cell>(&mut self, location: &Address, value: S) -> Result<()> { self.model .set_user_input( self.current_sheet, location.row as i32, location.col as i32, // TODO(jwall): This could probably be made more efficient - &value.into(), + value.as_ref(), ) .map_err(|e| anyhow!("Invalid cell contents: {}", e))?; Ok(()) @@ -521,29 +522,28 @@ impl Book { } pub fn select_next_sheet(&mut self) { - // TODO(jwall): Is there a cleaner way to do this with UserModel? let len = self.model.get_model().workbook.worksheets.len() as u32; let mut next = self.current_sheet + 1; if next == len { next = 0; } + self.model.set_selected_sheet(next).expect("Unexpected error selecting sheet"); self.current_sheet = next; } pub fn select_prev_sheet(&mut self) { - // TODO(jwall): Is there a cleaner way to do this with UserModel? let len = self.model.get_model().workbook.worksheets.len() as u32; let next = if self.current_sheet == 0 { len - 1 } else { self.current_sheet - 1 }; + self.model.set_selected_sheet(next).expect("Unexpected error selecting sheet"); self.current_sheet = next; } /// Select a sheet by id. pub fn select_sheet_by_id(&mut self, id: u32) -> bool { - // TODO(jwall): Is there a cleaner way to do this with UserModel? if let Some((idx, _sheet)) = self .model.get_model() .workbook @@ -552,6 +552,7 @@ impl Book { .enumerate() .find(|(_idx, sheet)| sheet.sheet_id == id) { + self.model.set_selected_sheet(idx as u32).expect("Unexpected error selecting sheet"); self.current_sheet = idx as u32; return true; } @@ -561,6 +562,8 @@ impl Book { /// Get the current `Worksheet`. pub(crate) fn get_sheet(&self) -> Result<&Worksheet> { // TODO(jwall): Is there a cleaner way to do this with UserModel? + // Looks like it should be done with: + // https://docs.rs/ironcalc_base/latest/ironcalc_base/struct.UserModel.html#method.get_worksheets_properties Ok(self .model.get_model() .workbook @@ -570,6 +573,8 @@ impl Book { pub(crate) fn get_sheet_name_by_idx(&self, idx: usize) -> Result<&str> { // TODO(jwall): Is there a cleaner way to do this with UserModel? + // Looks like it should be done with: + // https://docs.rs/ironcalc_base/latest/ironcalc_base/struct.UserModel.html#method.get_worksheets_properties Ok(&self .model.get_model() .workbook diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 8498dc3..530ae04 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -469,13 +469,7 @@ impl<'ws> Workspace<'ws> { let row_count = _count.unwrap_or(1); let row = self.book.location.row; for r in row..(row+row_count) { - let mut style = if let Some(style) = self.book.get_row_style(self.book.current_sheet, r)? { - style - } else { - self.book.create_style() - }; - style.fill.bg_color = Some(color.to_string()); - self.book.set_row_style(&style, self.book.current_sheet, r)?; + self.book.set_row_style(&[("fill.bg_color", color)], self.book.current_sheet, r)?; } Ok(None) } @@ -483,13 +477,7 @@ impl<'ws> Workspace<'ws> { let col_count = _count.unwrap_or(1); let col = self.book.location.col; for c in col..(col+col_count) { - let mut style = if let Some(style) = self.book.get_column_style(self.book.current_sheet, c)? { - style - } else { - self.book.create_style() - }; - style.fill.bg_color = Some(color.to_string()); - self.book.set_col_style(&style, self.book.current_sheet, c)?; + self.book.set_col_style(&[("fill.bg_color", color)], self.book.current_sheet, c)?; } Ok(None) } @@ -628,7 +616,6 @@ impl<'ws> Workspace<'ws> { .modifiers .contains(KeyModifiers::CONTROL) => { - // TODO(zaphar): Share the algorithm below between both copies self.copy_range(true)?; self.exit_range_select_mode()?; } diff --git a/src/ui/render/viewport.rs b/src/ui/render/viewport.rs index e37e8f8..ff1824f 100644 --- a/src/ui/render/viewport.rs +++ b/src/ui/render/viewport.rs @@ -248,7 +248,6 @@ pub(crate) fn map_color(color: Option<&String>, otherwise: Color) -> Color { candidate => { // TODO(jeremy): Should we support more syntaxes than hex string? // hsl(...) ?? - // rgb(...) ?? if candidate.starts_with("#") { if let Ok(rgb) = colorsys::Rgb::from_hex_str(candidate) { // Note that the colorsys rgb model clamps the f64 values to no more From e798350cd27abab191d3596438d56009de479228 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Fri, 7 Feb 2025 19:16:57 -0500 Subject: [PATCH 05/12] wip: fix area calculation bug --- src/book/mod.rs | 27 +++++++++++++-------------- src/ui/mod.rs | 4 ++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index a5817e1..b248971 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -227,13 +227,7 @@ impl Book { } pub fn clear_cell_range(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> { - let area = Area { - sheet, - row: start.row as i32, - column: start.col as i32, - width: (end.row - start.row) as i32, - height: (end.col - end.row) as i32, - }; + let area = calculate_area(sheet, start, end); self.model .range_clear_contents(&area) .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?; @@ -254,13 +248,7 @@ impl Book { } pub fn clear_cell_range_all(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> { - let area = Area { - sheet, - row: start.row as i32, - column: start.col as i32, - width: (end.row - start.row) as i32, - height: (end.col - end.row) as i32, - }; + let area = calculate_area(sheet, start, end); self.model.range_clear_all(&area) .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?; Ok(()) @@ -584,6 +572,17 @@ impl Book { } } +fn calculate_area(sheet: u32, start: Address, end: Address) -> Area { + let area = Area { + sheet, + row: start.row as i32, + column: start.col as i32, + height: (end.row - start.row + 1) as i32, + width: (end.col - start.col + 1) as i32, + }; + area +} + impl Default for Book { fn default() -> Self { let mut book = diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 530ae04..bc5f916 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -131,7 +131,7 @@ impl<'ws> AppState<'ws> { } } -// TODO(jwall): This should probably move to a different module. +// TODO(jwall): Should we just be using `Area` for this?. /// The Address in a Table. #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)] pub struct Address { @@ -539,7 +539,7 @@ impl<'ws> Workspace<'ws> { self.handle_numeric_prefix(d); } KeyCode::Char('D') => { - if let Some((start, end)) = self.state.range_select.get_range() { + if let Some((start, end)) = dbg!(self.state.range_select.get_range()) { self.book.clear_cell_range_all( self.state .range_select From e7169dcb44dec6b850a31e6faca79b867730a503 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Sat, 8 Feb 2025 17:04:32 -0500 Subject: [PATCH 06/12] wip: convert named colors to hex strings and fix off by one --- src/ui/cmd.rs | 72 +++++++++++++++++++++++++++++++++++++------------- src/ui/mod.rs | 10 +++---- src/ui/test.rs | 36 +++++++++++++++++++------ 3 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/ui/cmd.rs b/src/ui/cmd.rs index 2a4e092..2a8b2a1 100644 --- a/src/ui/cmd.rs +++ b/src/ui/cmd.rs @@ -7,9 +7,9 @@ pub enum Cmd<'a> { Write(Option<&'a str>), InsertRows(usize), InsertColumns(usize), - ColorRows(Option, &'a str), - ColorColumns(Option, &'a str), - ColorCell(&'a str), + ColorRows(Option, String), + ColorColumns(Option, String), + ColorCell(String), RenameSheet(Option, &'a str), NewSheet(Option<&'a str>), SelectSheet(&'a str), @@ -165,10 +165,7 @@ fn try_consume_color_cell<'cmd, 'i: 'cmd>( if input.remaining() > 0 && !is_ws(&mut input) { return Err("Invalid command: Did you mean to type `color-cell `?"); } - let arg = input.span(0..).trim(); - if arg.len() == 0 { - return Err("Invalid command: Did you mean to type `color-cell `?"); - } + let arg = parse_color(input.span(0..).trim())?; return Ok(Some(Cmd::ColorCell(arg))); } @@ -330,10 +327,7 @@ fn try_consume_color_rows<'cmd, 'i: 'cmd>( return Err("Invalid command: Did you mean to type `color-rows [count] `?"); } let (idx, rest) = try_consume_usize(input.clone()); - let arg = rest.span(0..).trim(); - if arg.is_empty() { - return Err("Invalid command: `color-rows` requires a color argument"); - } + let arg = parse_color(rest.span(0..).trim())?; return Ok(Some(Cmd::ColorRows(idx, arg))); } @@ -350,19 +344,59 @@ fn try_consume_color_columns<'cmd, 'i: 'cmd>( return Err("Invalid command: Did you mean to type `color-columns [count] `?"); } let (idx, rest) = try_consume_usize(input.clone()); - let arg = rest.span(0..).trim(); - if arg.is_empty() { - return Err("Invalid command: `color-columns` requires a color argument"); - } + let arg = parse_color(rest.span(0..).trim())?; return Ok(Some(Cmd::ColorColumns(idx, arg))); } -fn try_consume_usize<'cmd, 'i: 'cmd>( - mut input: StrCursor<'i>, -) -> (Option, StrCursor<'i>) { +pub(crate) fn parse_color(color: &str) -> Result { + use colorsys::{Ansi256, Rgb}; + if color.is_empty() { + return Err("Invalid command: `color-columns` requires a color argument"); + } + let parsed = match color.to_lowercase().as_str() { + "black" => Ansi256::new(0).as_rgb().to_hex_string(), + "red" => Ansi256::new(1).as_rgb().to_hex_string(), + "green" => Ansi256::new(2).as_rgb().to_hex_string(), + "yellow" => Ansi256::new(3).as_rgb().to_hex_string(), + "blue" => Ansi256::new(4).as_rgb().to_hex_string(), + "magenta" => Ansi256::new(5).as_rgb().to_hex_string(), + "cyan" => Ansi256::new(6).as_rgb().to_hex_string(), + "gray" | "grey" => Ansi256::new(7).as_rgb().to_hex_string(), + "darkgrey" | "darkgray" => Ansi256::new(8).as_rgb().to_hex_string(), + "lightred" => Ansi256::new(9).as_rgb().to_hex_string(), + "lightgreen" => Ansi256::new(10).as_rgb().to_hex_string(), + "lightyellow" => Ansi256::new(11).as_rgb().to_hex_string(), + "lightblue" => Ansi256::new(12).as_rgb().to_hex_string(), + "lightmagenta" => Ansi256::new(13).as_rgb().to_hex_string(), + "lightcyan" => Ansi256::new(14).as_rgb().to_hex_string(), + "white" => Ansi256::new(15).as_rgb().to_hex_string(), + candidate => { + if candidate.starts_with("#") { + candidate.to_string() + } else if candidate.starts_with("rgb(") { + if let Ok(rgb) = ::from_str(candidate) { + // Note that the colorsys rgb model clamps the f64 values to no more + // than 255.0 so the below casts are safe. + rgb.to_hex_string() + } else { + return Err("Invalid color"); + } + } else { + return Err("Invalid color"); + } + } + }; + Ok(parsed) +} + +fn try_consume_usize<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> (Option, StrCursor<'i>) { let mut out = String::new(); let original_input = input.clone(); - while input.peek_next().map(|c| (*c as char).is_ascii_digit()).unwrap_or(false) { + while input + .peek_next() + .map(|c| (*c as char).is_ascii_digit()) + .unwrap_or(false) + { out.push(*input.next().unwrap() as char); } if out.len() > 0 { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index bc5f916..aa02384 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -469,7 +469,7 @@ impl<'ws> Workspace<'ws> { let row_count = _count.unwrap_or(1); let row = self.book.location.row; for r in row..(row+row_count) { - self.book.set_row_style(&[("fill.bg_color", color)], self.book.current_sheet, r)?; + self.book.set_row_style(&[("fill.bg_color", &color)], self.book.current_sheet, r)?; } Ok(None) } @@ -477,7 +477,7 @@ impl<'ws> Workspace<'ws> { let col_count = _count.unwrap_or(1); let col = self.book.location.col; for c in col..(col+col_count) { - self.book.set_col_style(&[("fill.bg_color", color)], self.book.current_sheet, c)?; + self.book.set_col_style(&[("fill.bg_color", &color)], self.book.current_sheet, c)?; } Ok(None) } @@ -488,8 +488,8 @@ impl<'ws> Workspace<'ws> { sheet, row: start.row as i32, column: start.col as i32, - width: (end.col - start.col) as i32, - height: (end.row - start.row) as i32 + width: (end.col - start.col + 1) as i32, + height: (end.row - start.row + 1) as i32 } } else { let address = self.book.location.clone(); @@ -501,7 +501,7 @@ impl<'ws> Workspace<'ws> { height: 1 } }; - self.book.set_cell_style(&[("fill.bg_color", color)], &area)?; + self.book.set_cell_style(&[("fill.bg_color", &color)], &area)?; Ok(None) } Ok(None) => { diff --git a/src/ui/test.rs b/src/ui/test.rs index 5f22d0f..11fb9b5 100644 --- a/src/ui/test.rs +++ b/src/ui/test.rs @@ -2,6 +2,7 @@ use std::process::ExitCode; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crate::ui::cmd::parse_color; use crate::ui::{Address, Modality}; use super::cmd::{parse, Cmd}; @@ -33,6 +34,10 @@ impl InputScript { self.event(construct_key_event(KeyCode::Tab)) } + pub fn enter(self) -> Self { + self.event(construct_key_event(KeyCode::Enter)) + } + pub fn modified_char(self, c: char, mods: KeyModifiers) -> Self { self.event(construct_modified_key_event(KeyCode::Char(c), mods)) } @@ -42,10 +47,6 @@ impl InputScript { self } - pub fn enter(self) -> Self { - self.event(construct_key_event(KeyCode::Enter)) - } - pub fn esc(self) -> Self { self.event(construct_key_event(KeyCode::Esc)) } @@ -267,7 +268,7 @@ fn test_cmd_color_rows_with_color() { let output = result.unwrap(); assert!(output.is_some()); let cmd = output.unwrap(); - assert_eq!(cmd, Cmd::ColorRows(None, "red")); + assert_eq!(cmd, Cmd::ColorRows(None, parse_color("red").unwrap())); } #[test] @@ -278,7 +279,7 @@ fn test_cmd_color_rows_with_idx_and_color() { let output = result.unwrap(); assert!(output.is_some()); let cmd = output.unwrap(); - assert_eq!(cmd, Cmd::ColorRows(Some(1), "red")); + assert_eq!(cmd, Cmd::ColorRows(Some(1), parse_color("red").unwrap())); } #[test] @@ -289,7 +290,7 @@ fn test_cmd_color_columns_with_color() { let output = result.unwrap(); assert!(output.is_some()); let cmd = output.unwrap(); - assert_eq!(cmd, Cmd::ColorColumns(None, "red")); + assert_eq!(cmd, Cmd::ColorColumns(None, parse_color("red").unwrap())); } #[test] @@ -300,7 +301,7 @@ fn test_cmd_color_columns_with_idx_and_color() { let output = result.unwrap(); assert!(output.is_some()); let cmd = output.unwrap(); - assert_eq!(cmd, Cmd::ColorColumns(Some(1), "red")); + assert_eq!(cmd, Cmd::ColorColumns(Some(1), parse_color("red").unwrap())); } @@ -1152,6 +1153,25 @@ fn test_extend_to_range() { assert_eq!("=B2+1".to_string(), extended_cell); } +#[test] +fn test_color_cells() { + let mut ws = new_workspace(); + script() + .char('v') + .chars("jjll") + .char(':') + .chars("color-cell red") + .enter() + .run(&mut ws) + .expect("Unable to run script"); + for ri in 1..=3 { + for ci in 1..=3 { + let style = ws.book.get_cell_style(ws.book.current_sheet, &Address { row: ri, col: ci }).expect("failed to get style"); + assert_eq!("#800000", style.fill.bg_color.expect(&format!("No background color set for {}:{}", ri, ci)).as_str()); + } + } +} + fn new_workspace<'a>() -> Workspace<'a> { Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook") } From f6c9e95fda07f5fc8fc2008a124b78dd53ec3949 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Sat, 8 Feb 2025 18:33:26 -0500 Subject: [PATCH 07/12] chore: formatting pass --- src/book/mod.rs | 88 ++++++++++++++++++++++-------- src/ui/cmd.rs | 2 +- src/ui/mod.rs | 84 +++++++++++++++-------------- src/ui/render/test.rs | 16 +++--- src/ui/test.rs | 121 +++++++++++++++++++++++++++++------------- 5 files changed, 206 insertions(+), 105 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index b248971..f93a37d 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -3,7 +3,10 @@ use std::cmp::max; use anyhow::{anyhow, Result}; use ironcalc::{ base::{ - expressions::types::Area, types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, worksheet::WorksheetDimension, Model, UserModel + expressions::types::Area, + types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, + worksheet::WorksheetDimension, + Model, UserModel, }, export::save_xlsx_to_writer, import::load_from_xlsx, @@ -141,7 +144,9 @@ impl Book { } pub fn set_sheet_name(&mut self, idx: u32, sheet_name: &str) -> Result<()> { - self.model.rename_sheet(idx, sheet_name).map_err(|e| anyhow!(e))?; + self.model + .rename_sheet(idx, sheet_name) + .map_err(|e| anyhow!(e))?; Ok(()) } @@ -151,7 +156,9 @@ impl Book { if let Some(name) = sheet_name { self.set_sheet_name(idx, name)?; } - self.model.set_selected_sheet(self.current_sheet).map_err(|e| anyhow!(e))?; + self.model + .set_selected_sheet(self.current_sheet) + .map_err(|e| anyhow!(e))?; Ok(()) } @@ -249,7 +256,8 @@ impl Book { pub fn clear_cell_range_all(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> { let area = calculate_area(sheet, start, end); - self.model.range_clear_all(&area) + self.model + .range_clear_all(&area) .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?; Ok(()) } @@ -263,7 +271,10 @@ impl Book { // TODO(jwall): This is modeled a little weird. We should probably record // the error *somewhere* but for the user there is nothing to be done except // not use a style. - match self.model.get_model().get_style_for_cell(sheet, cell.row as i32, cell.col as i32) + match self + .model + .get_model() + .get_style_for_cell(sheet, cell.row as i32, cell.col as i32) { Err(_) => None, Ok(s) => Some(s), @@ -271,13 +282,25 @@ impl Book { } fn get_column(&self, sheet: u32, col: usize) -> Result> { - Ok(self.model.get_model().workbook.worksheet(sheet) - .map_err(|e| anyhow!("{}", e))?.cols.get(col)) + Ok(self + .model + .get_model() + .workbook + .worksheet(sheet) + .map_err(|e| anyhow!("{}", e))? + .cols + .get(col)) } fn get_row(&self, sheet: u32, col: usize) -> Result> { - Ok(self.model.get_model().workbook.worksheet(sheet) - .map_err(|e| anyhow!("{}", e))?.rows.get(col)) + Ok(self + .model + .get_model() + .workbook + .worksheet(sheet) + .map_err(|e| anyhow!("{}", e))? + .rows + .get(col)) } pub fn get_column_style(&self, sheet: u32, col: usize) -> Result> { @@ -336,7 +359,8 @@ impl Book { pub fn set_cell_style(&mut self, style: &[(&str, &str)], area: &Area) -> Result<()> { for (path, val) in style { - self.model.update_range_style(area, path, val) + self.model + .update_range_style(area, path, val) .map_err(|s| anyhow!("Unable to format cell {}", s))?; } Ok(()) @@ -458,8 +482,14 @@ impl Book { self.get_column_size_for_sheet(self.current_sheet, idx) } - pub fn get_column_size_for_sheet(&self, sheet: u32, idx: usize) -> std::result::Result { - Ok((self.model.get_column_width(sheet, idx as i32) + pub fn get_column_size_for_sheet( + &self, + sheet: u32, + idx: usize, + ) -> std::result::Result { + Ok((self + .model + .get_column_width(sheet, idx as i32) .map_err(|e| anyhow!("Error getting column width: {:?}", e))? / COL_PIXELS) as usize) } @@ -468,8 +498,14 @@ impl Book { self.set_column_size_for_sheet(self.current_sheet, col, width) } - pub fn set_column_size_for_sheet(&mut self, sheet: u32, col: usize, width: usize) -> std::result::Result<(), anyhow::Error> { - self.model.set_column_width(sheet, col as i32, width as f64 * COL_PIXELS) + pub fn set_column_size_for_sheet( + &mut self, + sheet: u32, + col: usize, + width: usize, + ) -> std::result::Result<(), anyhow::Error> { + self.model + .set_column_width(sheet, col as i32, width as f64 * COL_PIXELS) .map_err(|e| anyhow!("Error setting column width: {:?}", e))?; Ok(()) } @@ -491,7 +527,8 @@ impl Book { /// Select a sheet by name. pub fn select_sheet_by_name(&mut self, name: &str) -> bool { if let Some((idx, _sheet)) = self - .model.get_model() + .model + .get_model() .workbook .worksheets .iter() @@ -515,7 +552,9 @@ impl Book { if next == len { next = 0; } - self.model.set_selected_sheet(next).expect("Unexpected error selecting sheet"); + self.model + .set_selected_sheet(next) + .expect("Unexpected error selecting sheet"); self.current_sheet = next; } @@ -526,21 +565,26 @@ impl Book { } else { self.current_sheet - 1 }; - self.model.set_selected_sheet(next).expect("Unexpected error selecting sheet"); + self.model + .set_selected_sheet(next) + .expect("Unexpected error selecting sheet"); 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 - .model.get_model() + .model + .get_model() .workbook .worksheets .iter() .enumerate() .find(|(_idx, sheet)| sheet.sheet_id == id) { - self.model.set_selected_sheet(idx as u32).expect("Unexpected error selecting sheet"); + self.model + .set_selected_sheet(idx as u32) + .expect("Unexpected error selecting sheet"); self.current_sheet = idx as u32; return true; } @@ -553,7 +597,8 @@ impl Book { // Looks like it should be done with: // https://docs.rs/ironcalc_base/latest/ironcalc_base/struct.UserModel.html#method.get_worksheets_properties Ok(self - .model.get_model() + .model + .get_model() .workbook .worksheet(self.current_sheet) .map_err(|s| anyhow!("Invalid Worksheet id: {}: error: {}", self.current_sheet, s))?) @@ -564,7 +609,8 @@ impl Book { // Looks like it should be done with: // https://docs.rs/ironcalc_base/latest/ironcalc_base/struct.UserModel.html#method.get_worksheets_properties Ok(&self - .model.get_model() + .model + .get_model() .workbook .worksheet(idx as u32) .map_err(|s| anyhow!("Invalid Worksheet: {}", s))? diff --git a/src/ui/cmd.rs b/src/ui/cmd.rs index 2a8b2a1..fed68d1 100644 --- a/src/ui/cmd.rs +++ b/src/ui/cmd.rs @@ -382,7 +382,7 @@ pub(crate) fn parse_color(color: &str) -> Result { return Err("Invalid color"); } } else { - return Err("Invalid color"); + return Err("Invalid color"); } } }; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index aa02384..042c1a8 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -244,10 +244,13 @@ impl<'ws> Workspace<'ws> { /// Move to the top row without changing columns pub fn move_to_top(&mut self) -> Result<()> { - self.book.move_to(&Address { row: 1, col: self.book.location.col })?; + self.book.move_to(&Address { + row: 1, + col: self.book.location.col, + })?; Ok(()) } - + /// Move a row up in the current sheet. pub fn move_up(&mut self) -> Result<()> { let mut loc = self.book.location.clone(); @@ -320,7 +323,8 @@ impl<'ws> Workspace<'ws> { "Edit Mode:".to_string(), "* ENTER/RETURN: Exit edit mode and save changes".to_string(), "* Ctrl-r: Enter Range Selection mode".to_string(), - "* v: Enter Range Selection mode with the start of the range already selected".to_string(), + "* v: Enter Range Selection mode with the start of the range already selected" + .to_string(), "* ESC: Exit edit mode and discard changes".to_string(), "Otherwise edit as normal".to_string(), ], @@ -448,8 +452,7 @@ impl<'ws> Workspace<'ws> { self.book.set_sheet_name(idx as u32, name)?; } _ => { - self.book - .set_sheet_name(self.book.current_sheet, name)?; + self.book.set_sheet_name(self.book.current_sheet, name)?; } } Ok(None) @@ -462,22 +465,28 @@ impl<'ws> Workspace<'ws> { self.book.select_sheet_by_name(name); Ok(None) } - Ok(Some(Cmd::Quit)) => { - Ok(Some(ExitCode::SUCCESS)) - } + Ok(Some(Cmd::Quit)) => Ok(Some(ExitCode::SUCCESS)), Ok(Some(Cmd::ColorRows(_count, color))) => { let row_count = _count.unwrap_or(1); let row = self.book.location.row; - for r in row..(row+row_count) { - self.book.set_row_style(&[("fill.bg_color", &color)], self.book.current_sheet, r)?; + for r in row..(row + row_count) { + self.book.set_row_style( + &[("fill.bg_color", &color)], + self.book.current_sheet, + r, + )?; } Ok(None) } Ok(Some(Cmd::ColorColumns(_count, color))) => { let col_count = _count.unwrap_or(1); let col = self.book.location.col; - for c in col..(col+col_count) { - self.book.set_col_style(&[("fill.bg_color", &color)], self.book.current_sheet, c)?; + for c in col..(col + col_count) { + self.book.set_col_style( + &[("fill.bg_color", &color)], + self.book.current_sheet, + c, + )?; } Ok(None) } @@ -489,7 +498,7 @@ impl<'ws> Workspace<'ws> { row: start.row as i32, column: start.col as i32, width: (end.col - start.col + 1) as i32, - height: (end.row - start.row + 1) as i32 + height: (end.row - start.row + 1) as i32, } } else { let address = self.book.location.clone(); @@ -498,10 +507,11 @@ impl<'ws> Workspace<'ws> { row: address.row as i32, column: address.col as i32, width: 1, - height: 1 + height: 1, } }; - self.book.set_cell_style(&[("fill.bg_color", &color)], &area)?; + self.book + .set_cell_style(&[("fill.bg_color", &color)], &area)?; Ok(None) } Ok(None) => { @@ -611,11 +621,7 @@ impl<'ws> Workspace<'ws> { })?; self.state.range_select.sheet = Some(self.book.current_sheet); } - KeyCode::Char('C') - if key - .modifiers - .contains(KeyModifiers::CONTROL) => - { + KeyCode::Char('C') if key.modifiers.contains(KeyModifiers::CONTROL) => { self.copy_range(true)?; self.exit_range_select_mode()?; } @@ -632,7 +638,10 @@ impl<'ws> Workspace<'ws> { self.exit_range_select_mode()?; } KeyCode::Char('x') => { - if let (Some(from), Some(to)) = (self.state.range_select.start.as_ref(), self.state.range_select.end.as_ref()) { + if let (Some(from), Some(to)) = ( + self.state.range_select.start.as_ref(), + self.state.range_select.end.as_ref(), + ) { self.book.extend_to(from, to)?; } self.exit_range_select_mode()?; @@ -651,20 +660,15 @@ impl<'ws> Workspace<'ws> { fn copy_range(&mut self, formatted: bool) -> Result<(), anyhow::Error> { self.update_range_selection()?; match &self.state.range_select.get_range() { - Some(( - start, - end, - )) => { + Some((start, end)) => { let mut rows = Vec::new(); - for row in (AddressRange { start, end, }).as_rows() { + for row in (AddressRange { start, end }).as_rows() { let mut cols = Vec::new(); for cell in row { cols.push(if formatted { - self.book - .get_cell_addr_rendered(&cell)? + self.book.get_cell_addr_rendered(&cell)? } else { - self.book - .get_cell_addr_contents(&cell)? + self.book.get_cell_addr_contents(&cell)? }); } rows.push(cols); @@ -673,11 +677,9 @@ impl<'ws> Workspace<'ws> { } None => { self.state.clipboard = Some(ClipboardContents::Cell(if formatted { - self.book - .get_current_cell_rendered()? + self.book.get_current_cell_rendered()? } else { - self.book - .get_current_cell_contents()? + self.book.get_current_cell_contents()? })); } } @@ -743,11 +745,7 @@ impl<'ws> Workspace<'ws> { self.book.get_current_cell_rendered()?, )); } - KeyCode::Char('C') - if key - .modifiers - .contains(KeyModifiers::CONTROL) => - { + KeyCode::Char('C') if key.modifiers.contains(KeyModifiers::CONTROL) => { self.state.clipboard = Some(ClipboardContents::Cell( self.book.get_current_cell_rendered()?, )); @@ -861,7 +859,13 @@ impl<'ws> Workspace<'ws> { } KeyCode::Char('g') => { // TODO(zaphar): This really needs a better state machine. - if self.state.char_queue.first().map(|c| *c == 'g').unwrap_or(false) { + if self + .state + .char_queue + .first() + .map(|c| *c == 'g') + .unwrap_or(false) + { self.state.char_queue.pop(); self.move_to_top()?; } else { diff --git a/src/ui/render/test.rs b/src/ui/render/test.rs index 68f0ecd..aed6856 100644 --- a/src/ui/render/test.rs +++ b/src/ui/render/test.rs @@ -14,8 +14,8 @@ fn test_viewport_get_visible_columns() { let default_size = book.get_col_size(1).expect("Failed to get column size"); let width = dbg!(dbg!(default_size) * 12 / 2); let app_state = AppState::default(); - let viewport = - Viewport::new(&book, Some(&app_state.range_select)).with_selected(Address { row: 1, col: 17 }); + let viewport = Viewport::new(&book, Some(&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"); @@ -31,8 +31,8 @@ fn test_viewport_get_visible_rows() { ); let height = 6; let app_state = AppState::default(); - let viewport = - Viewport::new(&book, Some(&app_state.range_select)).with_selected(Address { row: 17, col: 1 }); + let viewport = Viewport::new(&book, Some(&app_state.range_select)) + .with_selected(Address { row: 17, col: 1 }); let rows = dbg!(viewport.get_visible_rows(height as u16, &mut state)); assert_eq!(height - 1, rows.len()); assert_eq!( @@ -65,8 +65,8 @@ fn test_viewport_visible_columns_after_length_change() { .expect("Failed to set column size"); { let app_state = AppState::default(); - let viewport = - Viewport::new(&book, Some(&app_state.range_select)).with_selected(Address { row: 1, col: 1 }); + let viewport = Viewport::new(&book, Some(&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"); @@ -97,7 +97,9 @@ fn test_color_mapping() { ("darkgrey", Color::DarkGray), ("darkgray", Color::DarkGray), ("#35f15b", Color::Rgb(53, 241, 91)), - ].map(|(s, c)| (Some(s.to_string()), c)) { + ] + .map(|(s, c)| (Some(s.to_string()), c)) + { assert_eq!(super::viewport::map_color(s.as_ref(), Color::Gray), c); } } diff --git a/src/ui/test.rs b/src/ui/test.rs index 11fb9b5..ffbeb02 100644 --- a/src/ui/test.rs +++ b/src/ui/test.rs @@ -304,7 +304,6 @@ fn test_cmd_color_columns_with_idx_and_color() { assert_eq!(cmd, Cmd::ColorColumns(Some(1), parse_color("red").unwrap())); } - #[test] fn test_input_navitation_enter_key() { let mut ws = new_workspace(); @@ -1004,8 +1003,7 @@ macro_rules! assert_range_clear { .run(&mut ws) .expect("Failed to handle script"); assert_eq!(Some(&Modality::RangeSelect), ws.state.modality_stack.last()); - $script.run(&mut ws) - .expect("Failed to handle script"); + $script.run(&mut ws).expect("Failed to handle script"); assert_eq!( "".to_string(), ws.book @@ -1023,18 +1021,21 @@ macro_rules! assert_range_clear { #[test] fn test_range_select_clear_upper_d() { - assert_range_clear!(script() - .char('j') - .char('l') - .char('D')); + assert_range_clear!(script().char('j').char('l').char('D')); } #[test] fn test_range_select_movement() { let mut ws = new_workspace(); - ws.book.new_sheet(Some("s2")).expect("Unable create s2 sheet"); - ws.book.new_sheet(Some("s3")).expect("Unable create s3 sheet"); - script().ctrl('r').run(&mut ws) + ws.book + .new_sheet(Some("s2")) + .expect("Unable create s2 sheet"); + ws.book + .new_sheet(Some("s3")) + .expect("Unable create s3 sheet"); + script() + .ctrl('r') + .run(&mut ws) .expect("failed to run script"); assert_eq!(Some(&Modality::RangeSelect), ws.state.modality_stack.last()); script() @@ -1064,10 +1065,7 @@ fn test_range_select_movement() { #[test] fn test_range_select_clear_lower_d() { - assert_range_clear!(script() - .char('j') - .char('l') - .char('d')); + assert_range_clear!(script().char('j').char('l').char('d')); } macro_rules! assert_range_copy { @@ -1075,8 +1073,12 @@ macro_rules! assert_range_copy { let mut ws = new_workspace(); let top_left_addr = Address { row: 2, col: 2 }; let bot_right_addr = Address { row: 4, col: 4 }; - ws.book.update_cell(&top_left_addr, "top_left").expect("Failed to update top left"); - ws.book.update_cell(&bot_right_addr, "bot_right").expect("Failed to update top left"); + ws.book + .update_cell(&top_left_addr, "top_left") + .expect("Failed to update top left"); + ws.book + .update_cell(&bot_right_addr, "bot_right") + .expect("Failed to update top left"); assert!(ws.state.clipboard.is_none()); script() .ctrl('r') @@ -1085,7 +1087,14 @@ macro_rules! assert_range_copy { .char(' ') .run(&mut ws) .expect("failed to run script"); - assert_eq!(&top_left_addr, ws.state.range_select.start.as_ref().expect("Didn't find a start of range")); + assert_eq!( + &top_left_addr, + ws.state + .range_select + .start + .as_ref() + .expect("Didn't find a start of range") + ); script() .char('2') .char('j') @@ -1093,27 +1102,53 @@ macro_rules! assert_range_copy { .char('l') .run(&mut ws) .expect("failed to run script"); - assert_eq!(&bot_right_addr, ws.state.range_select.end.as_ref().expect("Didn't find a start of range")); - assert_eq!(&Address { row: 1, col: 1}, ws.state.range_select.original_location - .as_ref().expect("Expected an original location")); - assert_eq!(0, ws.state.range_select.original_sheet. - expect("Expected an original sheet")); - assert_eq!(Some(&Modality::RangeSelect), ws.state.modality_stack.iter().last()); + assert_eq!( + &bot_right_addr, + ws.state + .range_select + .end + .as_ref() + .expect("Didn't find a start of range") + ); + assert_eq!( + &Address { row: 1, col: 1 }, + ws.state + .range_select + .original_location + .as_ref() + .expect("Expected an original location") + ); + assert_eq!( + 0, + ws.state + .range_select + .original_sheet + .expect("Expected an original sheet") + ); + assert_eq!( + Some(&Modality::RangeSelect), + ws.state.modality_stack.iter().last() + ); dbg!(ws.state.range_select.get_range()); - $script.run(&mut ws) - .expect("failed to run script"); + $script.run(&mut ws).expect("failed to run script"); assert!(ws.state.clipboard.is_some()); match ws.state.clipboard.unwrap() { crate::ui::ClipboardContents::Cell(_) => assert!(false, "Not rows in Clipboard"), crate::ui::ClipboardContents::Range(rows) => { - assert_eq!(vec![ - vec!["top_left".to_string(), "".to_string(), "".to_string()], - vec!["".to_string(), "".to_string(), "".to_string()], - vec!["".to_string(), "".to_string(), "bot_right".to_string()], - ], rows); - }, + assert_eq!( + vec![ + vec!["top_left".to_string(), "".to_string(), "".to_string()], + vec!["".to_string(), "".to_string(), "".to_string()], + vec!["".to_string(), "".to_string(), "bot_right".to_string()], + ], + rows + ); + } } - assert_eq!(Some(&Modality::Navigate), ws.state.modality_stack.iter().last()); + assert_eq!( + Some(&Modality::Navigate), + ws.state.modality_stack.iter().last() + ); }}; } @@ -1140,7 +1175,9 @@ fn test_range_select_copy_capital_c() { #[test] fn test_extend_to_range() { let mut ws = new_workspace(); - ws.book.edit_current_cell("=B1+1").expect("Failed to edit cell"); + ws.book + .edit_current_cell("=B1+1") + .expect("Failed to edit cell"); ws.book.evaluate(); script() .char('v') @@ -1148,7 +1185,9 @@ fn test_extend_to_range() { .char('x') .run(&mut ws) .expect("Unable to run script"); - let extended_cell = ws.book.get_cell_addr_contents(&Address { row: 2, col: 1 }) + let extended_cell = ws + .book + .get_cell_addr_contents(&Address { row: 2, col: 1 }) .expect("Failed to get cell contents"); assert_eq!("=B2+1".to_string(), extended_cell); } @@ -1166,8 +1205,18 @@ fn test_color_cells() { .expect("Unable to run script"); for ri in 1..=3 { for ci in 1..=3 { - let style = ws.book.get_cell_style(ws.book.current_sheet, &Address { row: ri, col: ci }).expect("failed to get style"); - assert_eq!("#800000", style.fill.bg_color.expect(&format!("No background color set for {}:{}", ri, ci)).as_str()); + let style = ws + .book + .get_cell_style(ws.book.current_sheet, &Address { row: ri, col: ci }) + .expect("failed to get style"); + assert_eq!( + "#800000", + style + .fill + .bg_color + .expect(&format!("No background color set for {}:{}", ri, ci)) + .as_str() + ); } } } From 3219e01176ccb0a53beeddbe359f0dc143b23585 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Sun, 9 Feb 2025 20:10:40 -0500 Subject: [PATCH 08/12] wip: support column and row styling --- src/book/mod.rs | 153 ++++++++++++++------------------------ src/ui/mod.rs | 14 ++-- src/ui/render/viewport.rs | 11 +-- src/ui/test.rs | 52 +++++++++++++ 4 files changed, 117 insertions(+), 113 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index f93a37d..a83f658 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, Result}; use ironcalc::{ base::{ expressions::types::Area, - types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, + types::{SheetData, Style, Worksheet}, worksheet::WorksheetDimension, Model, UserModel, }, @@ -17,7 +17,12 @@ use crate::ui::Address; #[cfg(test)] mod test; -const COL_PIXELS: f64 = 5.0; +pub(crate) const COL_PIXELS: f64 = 5.0; +// NOTE(zaphar): This is stolen from ironcalc but ironcalc doesn't expose it +// publically. +pub(crate) const LAST_COLUMN: i32 = 16_384; +pub(crate) const LAST_ROW: i32 = 1_048_576; + #[derive(Debug, Clone)] pub struct AddressRange<'book> { @@ -281,82 +286,6 @@ impl Book { } } - fn get_column(&self, sheet: u32, col: usize) -> Result> { - Ok(self - .model - .get_model() - .workbook - .worksheet(sheet) - .map_err(|e| anyhow!("{}", e))? - .cols - .get(col)) - } - - fn get_row(&self, sheet: u32, col: usize) -> Result> { - Ok(self - .model - .get_model() - .workbook - .worksheet(sheet) - .map_err(|e| anyhow!("{}", e))? - .rows - .get(col)) - } - - pub fn get_column_style(&self, sheet: u32, col: usize) -> Result> { - // TODO(jwall): This is modeled a little weird. We should probably record - // the error *somewhere* but for the user there is nothing to be done except - // not use a style. - if let Some(col) = self.get_column(sheet, col)? { - if let Some(style_idx) = col.style.map(|idx| idx as usize) { - let styles = &self.model.get_model().workbook.styles; - if styles.cell_style_xfs.len() <= style_idx { - return Ok(Some(Style { - alignment: None, - num_fmt: styles.num_fmts[style_idx].format_code.clone(), - fill: styles.fills[style_idx].clone(), - font: styles.fonts[style_idx].clone(), - border: styles.borders[style_idx].clone(), - quote_prefix: false, - })); - } - } - } - return Ok(None); - } - - pub fn get_row_style(&self, sheet: u32, row: usize) -> Result> { - // TODO(jwall): This is modeled a little weird. We should probably record - // the error *somewhere* but for the user there is nothing to be done except - // not use a style. - if let Some(row) = self.get_row(sheet, row)? { - let style_idx = row.s as usize; - let styles = &self.model.get_model().workbook.styles; - if styles.cell_style_xfs.len() <= style_idx { - return Ok(Some(Style { - alignment: None, - num_fmt: styles.num_fmts[style_idx].format_code.clone(), - fill: styles.fills[style_idx].clone(), - font: styles.fonts[style_idx].clone(), - border: styles.borders[style_idx].clone(), - quote_prefix: false, - })); - } - } - return Ok(None); - } - - pub fn create_style(&mut self) -> Style { - Style { - alignment: None, - num_fmt: String::new(), - fill: Fill::default(), - font: Font::default(), - border: Border::default(), - quote_prefix: false, - } - } - pub fn set_cell_style(&mut self, style: &[(&str, &str)], area: &Area) -> Result<()> { for (path, val) in style { self.model @@ -366,28 +295,56 @@ impl Book { Ok(()) } - pub fn set_col_style(&mut self, style: &[(&str, &str)], sheet: u32, col: usize) -> Result<()> { - todo!() - //let idx = self.create_or_get_style_idx(style); - //let sheet = self.model.workbook.worksheet_mut(sheet) - // .map_err(|e| anyhow!("{}", e))?; - //let width = sheet.get_column_width(col as i32) - // .map_err(|e| anyhow!("{}", e))?; - //sheet.set_column_style(col as i32, idx) - // .map_err(|e| anyhow!("{}", e))?; - //sheet.set_column_width(col as i32, width) - // .map_err(|e| anyhow!("{}", e))?; - //Ok(()) + fn get_col_range(&self, sheet: u32, col_idx: usize) -> Area { + Area { + sheet, + row: 1, + column: col_idx as i32, + width: 1, + height: LAST_ROW, + } } - pub fn set_row_style(&mut self, style: &[(&str, &str)], sheet: u32, row: usize) -> Result<()> { - todo!() - //let idx = self.create_or_get_style_idx(style); - //self.model.workbook.worksheet_mut(sheet) - // .map_err(|e| anyhow!("{}", e))? - // .set_row_style(row as i32, idx) - // .map_err(|e| anyhow!("{}", e))?; - //Ok(()) + fn get_row_range(&self, sheet: u32, row_idx: usize) -> Area { + Area { + sheet, + row: row_idx as i32, + column: 1, + width: LAST_COLUMN, + height: 1, + } + } + + pub fn set_col_style( + &mut self, + style: &[(&str, &str)], + sheet: u32, + col_idx: usize, + ) -> Result<()> { + // TODO(jeremy): This is a little hacky and the underlying model + // supports a better mechanism but UserModel doesn't support it yet. + // https://github.com/ironcalc/IronCalc/issues/273 + // https://github.com/ironcalc/IronCalc/pull/276 is the coming fix. + // NOTE(jwall): Because of the number of cells required to modify + // this is crazy slow + let area = self.get_col_range(sheet, col_idx); + self.set_cell_style(style, &area)?; + Ok(()) + } + + pub fn set_row_style( + &mut self, + style: &[(&str, &str)], + sheet: u32, + row_idx: usize, + ) -> Result<()> { + // TODO(jeremy): This is a little hacky and the underlying model + // supports a better mechanism but UserModel doesn't support it yet. + // https://github.com/ironcalc/IronCalc/issues/273 + // https://github.com/ironcalc/IronCalc/pull/276 is the coming fix. + let area = self.get_row_range(sheet, row_idx); + self.set_cell_style(style, &area)?; + Ok(()) } /// Get a cells rendered content for display. diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 042c1a8..0f7f110 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::{AddressRange, Book}; +use crate::book::{self, AddressRange, Book}; use anyhow::{anyhow, Result}; use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers}; @@ -235,7 +235,7 @@ impl<'ws> Workspace<'ws> { /// Move a row down in the current sheet. pub fn move_down(&mut self) -> Result<()> { let mut loc = self.book.location.clone(); - if loc.row < render::viewport::LAST_ROW { + if loc.row < (book::LAST_ROW as usize) { loc.row += 1; self.book.move_to(&loc)?; } @@ -274,7 +274,7 @@ impl<'ws> Workspace<'ws> { /// Move a column to the left in the current sheet. pub fn move_right(&mut self) -> Result<()> { let mut loc = self.book.location.clone(); - if loc.col < render::viewport::LAST_COLUMN { + if loc.col < (book::LAST_COLUMN as usize) { loc.col += 1; self.book.move_to(&loc)?; } @@ -466,8 +466,8 @@ impl<'ws> Workspace<'ws> { Ok(None) } Ok(Some(Cmd::Quit)) => Ok(Some(ExitCode::SUCCESS)), - Ok(Some(Cmd::ColorRows(_count, color))) => { - let row_count = _count.unwrap_or(1); + Ok(Some(Cmd::ColorRows(count, color))) => { + let row_count = count.unwrap_or(1); let row = self.book.location.row; for r in row..(row + row_count) { self.book.set_row_style( @@ -478,8 +478,8 @@ impl<'ws> Workspace<'ws> { } Ok(None) } - Ok(Some(Cmd::ColorColumns(_count, color))) => { - let col_count = _count.unwrap_or(1); + Ok(Some(Cmd::ColorColumns(count, color))) => { + let col_count = count.unwrap_or(1); let col = self.book.location.col; for c in col..(col + col_count) { self.book.set_col_style( diff --git a/src/ui/render/viewport.rs b/src/ui/render/viewport.rs index ff1824f..e3bf60e 100644 --- a/src/ui/render/viewport.rs +++ b/src/ui/render/viewport.rs @@ -7,14 +7,9 @@ use ratatui::{ widgets::{Block, Cell, Row, StatefulWidget, Table, Widget}, }; +use crate::book; use super::{Address, Book, RangeSelection}; -// TODO(zaphar): Move this to the book module. -// NOTE(zaphar): This is stolen from ironcalc but ironcalc doesn't expose it -// publically. -pub(crate) const LAST_COLUMN: usize = 16_384; -pub(crate) const LAST_ROW: usize = 1_048_576; - /// A visible column to show in our Viewport. #[derive(Clone, Debug)] pub struct VisibleColumn { @@ -68,7 +63,7 @@ impl<'ws> Viewport<'ws> { let start_row = std::cmp::min(self.selected.row, state.prev_corner.row); let mut start = start_row; let mut end = start_row; - for row_idx in start_row..=LAST_ROW { + for row_idx in start_row..=(book::LAST_ROW as usize) { let updated_length = length + 1; if updated_length <= height { length = updated_length; @@ -95,7 +90,7 @@ impl<'ws> Viewport<'ws> { // We start out with a length of 5 already reserved let mut length = 5; let start_idx = std::cmp::min(self.selected.col, state.prev_corner.col); - for idx in start_idx..=LAST_COLUMN { + for idx in start_idx..=(book::LAST_COLUMN as usize) { let size = self.book.get_col_size(idx)? as u16; let updated_length = length + size; let col = VisibleColumn { idx, length: size }; diff --git a/src/ui/test.rs b/src/ui/test.rs index ffbeb02..356b695 100644 --- a/src/ui/test.rs +++ b/src/ui/test.rs @@ -2,6 +2,7 @@ use std::process::ExitCode; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crate::book; use crate::ui::cmd::parse_color; use crate::ui::{Address, Modality}; @@ -1221,6 +1222,57 @@ fn test_color_cells() { } } +#[test] +fn test_color_row() { + let mut ws = new_workspace(); + script() + .char(':') + .chars("color-rows red") + .enter() + .run(&mut ws) + .expect("Unable to run script"); + for ci in [1, book::LAST_COLUMN] { + let style = ws + .book + .get_cell_style(ws.book.current_sheet, &Address { row: 1, col: ci as usize }) + .expect("failed to get style"); + assert_eq!( + "#800000", + style + .fill + .bg_color + .expect(&format!("No background color set for {}:{}", 1, ci)) + .as_str() + ); + } +} + +#[test] +fn test_color_col() { + let mut ws = new_workspace(); + script() + .char(':') + .chars("color-columns red") + .enter() + .run(&mut ws) + .expect("Unable to run script"); + for ri in [1, book::LAST_ROW] { + let style = ws + .book + .get_cell_style(ws.book.current_sheet, &Address { row: ri as usize, col: 1 }) + .expect("failed to get style"); + assert_eq!( + "#800000", + style + .fill + .bg_color + .expect(&format!("No background color set for {}:{}", ri, 1)) + .as_str() + ); + } +} + + fn new_workspace<'a>() -> Workspace<'a> { Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook") } From 7ffd420029e05dc66c927160dd1fe2cc853b626f Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 13 Feb 2025 19:42:25 -0500 Subject: [PATCH 09/12] docs: style path documentation --- Cargo.lock | 4 ++-- src/book/mod.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 980ec09..4c73722 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,7 +841,7 @@ dependencies = [ [[package]] name = "ironcalc" version = "0.3.0" -source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#99125f1fea1c8c72c61f8cba94d847ed3471a4af" +source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#7e54cb6aa27f6d02fc3b090de4fb127c67900dc8" dependencies = [ "bitcode", "chrono", @@ -857,7 +857,7 @@ dependencies = [ [[package]] name = "ironcalc_base" version = "0.3.0" -source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#99125f1fea1c8c72c61f8cba94d847ed3471a4af" +source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#7e54cb6aa27f6d02fc3b090de4fb127c67900dc8" dependencies = [ "bitcode", "chrono", diff --git a/src/book/mod.rs b/src/book/mod.rs index a83f658..33672fe 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -278,14 +278,26 @@ impl Book { // not use a style. match self .model - .get_model() - .get_style_for_cell(sheet, cell.row as i32, cell.col as i32) + .get_cell_style(sheet, cell.row as i32, cell.col as i32) { Err(_) => None, Ok(s) => Some(s), } } + /// Set the cell style + /// Valid style paths are: + /// * fill.bg_color background color + /// * fill.fg_color foreground color + /// * font.b bold + /// * font.i italicize + /// * font.strike strikethrough + /// * font.color font color + /// * num_fmt number format + /// * alignment turn off alignment + /// * alignment.horizontal make alignment horzontal + /// * alignment.vertical make alignment vertical + /// * alignment.wrap_text wrap cell text pub fn set_cell_style(&mut self, style: &[(&str, &str)], area: &Area) -> Result<()> { for (path, val) in style { self.model @@ -315,6 +327,19 @@ impl Book { } } + /// Set the column style. + /// Valid style paths are: + /// * fill.bg_color background color + /// * fill.fg_color foreground color + /// * font.b bold + /// * font.i italicize + /// * font.strike strikethrough + /// * font.color font color + /// * num_fmt number format + /// * alignment turn off alignment + /// * alignment.horizontal make alignment horzontal + /// * alignment.vertical make alignment vertical + /// * alignment.wrap_text wrap cell text pub fn set_col_style( &mut self, style: &[(&str, &str)], @@ -332,6 +357,19 @@ impl Book { Ok(()) } + /// Set the row style + /// Valid style paths are: + /// * fill.bg_color background color + /// * fill.fg_color foreground color + /// * font.b bold + /// * font.i italicize + /// * font.strike strikethrough + /// * font.color font color + /// * num_fmt number format + /// * alignment turn off alignment + /// * alignment.horizontal make alignment horzontal + /// * alignment.vertical make alignment vertical + /// * alignment.wrap_text wrap cell text pub fn set_row_style( &mut self, style: &[(&str, &str)], From 5d23410f00088e00698e06004ca1f672654ff0d7 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Fri, 14 Feb 2025 19:35:10 -0500 Subject: [PATCH 10/12] wip: calcuate_area shouldnt --- src/book/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index 33672fe..0bd3c37 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -239,7 +239,7 @@ impl Book { } pub fn clear_cell_range(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> { - let area = calculate_area(sheet, start, end); + let area = calculate_area(sheet, &start, &end); self.model .range_clear_contents(&area) .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?; @@ -260,7 +260,7 @@ impl Book { } pub fn clear_cell_range_all(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> { - let area = calculate_area(sheet, start, end); + let area = calculate_area(sheet, &start, &end); self.model .range_clear_all(&area) .map_err(|s| anyhow!("Unable to clear cell contents {}", s))?; @@ -613,7 +613,7 @@ impl Book { } } -fn calculate_area(sheet: u32, start: Address, end: Address) -> Area { +fn calculate_area(sheet: u32, start: &Address, end: &Address) -> Area { let area = Area { sheet, row: start.row as i32, From d456501565ee64e2776970c349213a7abe350ae4 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Sat, 15 Feb 2025 09:10:55 -0500 Subject: [PATCH 11/12] update: ironcalc version for styling api fixes --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- src/book/mod.rs | 3 --- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c73722..00c182c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,7 +841,7 @@ dependencies = [ [[package]] name = "ironcalc" version = "0.3.0" -source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#7e54cb6aa27f6d02fc3b090de4fb127c67900dc8" +source = "git+https://github.com/ironcalc/IronCalc?rev=b2c5027f56a16a0c606b01a071b816b941972aef#b2c5027f56a16a0c606b01a071b816b941972aef" dependencies = [ "bitcode", "chrono", @@ -857,7 +857,7 @@ dependencies = [ [[package]] name = "ironcalc_base" version = "0.3.0" -source = "git+https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c#7e54cb6aa27f6d02fc3b090de4fb127c67900dc8" +source = "git+https://github.com/ironcalc/IronCalc?rev=b2c5027f56a16a0c606b01a071b816b941972aef#b2c5027f56a16a0c606b01a071b816b941972aef" dependencies = [ "bitcode", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 66a5066..8b31e0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,13 @@ clap = { version = "4.5.20", features = ["derive"] } crossterm = { version = "0.28.1", features = ["event-stream", "serde"] } csvx = "0.1.17" # this revision introduces a way to get the Model back out of the UserModel -ironcalc = { git = "https://github.com/ironcalc/IronCalc?rev=264fcac63cc93b08a4b4a6764815e0c0adf6a53c" } +ironcalc = { git = "https://github.com/ironcalc/IronCalc?rev=b2c5027f56a16a0c606b01a071b816b941972aef" } futures = "0.3.31" ratatui = "0.29.0" thiserror = "1.0.65" tui-textarea = "0.7.0" tui-prompts = "0.5.0" -slice-utils = { git = "https://dev.zaphar.net/zaphar/slice-cursor-rs.git", ref = "main" } +slice-utils = { git = "https://dev.zaphar.net/zaphar/slice-cursor-rs.git" } tui-popup = "0.6.0" serde_json = "1.0.133" colorsys = "0.6.7" diff --git a/src/book/mod.rs b/src/book/mod.rs index 0bd3c37..6651a82 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -190,9 +190,6 @@ impl Book { .iter() .skip(1) { - // TODO(jeremy): Is there a better way to do this using UserModel? - // Looks like this is the recommended way: - // https://docs.rs/ironcalc_base/latest/ironcalc_base/struct.UserModel.html#method.auto_fill_columns let contents = self .model .get_model() From 49c768dd76d5d3a6d56b672e9543bd22c55f2819 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Sat, 15 Feb 2025 09:42:27 -0500 Subject: [PATCH 12/12] fix: nix build naersk no like the rev syntax --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- result | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) create mode 120000 result diff --git a/Cargo.lock b/Cargo.lock index 00c182c..5783a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,7 +841,7 @@ dependencies = [ [[package]] name = "ironcalc" version = "0.3.0" -source = "git+https://github.com/ironcalc/IronCalc?rev=b2c5027f56a16a0c606b01a071b816b941972aef#b2c5027f56a16a0c606b01a071b816b941972aef" +source = "git+https://github.com/ironcalc/IronCalc#b2c5027f56a16a0c606b01a071b816b941972aef" dependencies = [ "bitcode", "chrono", @@ -857,7 +857,7 @@ dependencies = [ [[package]] name = "ironcalc_base" version = "0.3.0" -source = "git+https://github.com/ironcalc/IronCalc?rev=b2c5027f56a16a0c606b01a071b816b941972aef#b2c5027f56a16a0c606b01a071b816b941972aef" +source = "git+https://github.com/ironcalc/IronCalc#b2c5027f56a16a0c606b01a071b816b941972aef" dependencies = [ "bitcode", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 8b31e0c..32ceef4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ clap = { version = "4.5.20", features = ["derive"] } crossterm = { version = "0.28.1", features = ["event-stream", "serde"] } csvx = "0.1.17" # this revision introduces a way to get the Model back out of the UserModel -ironcalc = { git = "https://github.com/ironcalc/IronCalc?rev=b2c5027f56a16a0c606b01a071b816b941972aef" } +ironcalc = { git = "https://github.com/ironcalc/IronCalc" } futures = "0.3.31" ratatui = "0.29.0" thiserror = "1.0.65" diff --git a/result b/result new file mode 120000 index 0000000..eacebb1 --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/k826wsv9zc73jamdff1yl1rky2bw9lc6-sheetui-0.1.0 \ No newline at end of file