diff --git a/Cargo.lock b/Cargo.lock index e6d43dc..8c381be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "a1" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8470e27ace1e4691e30b3c05a719397c10e799d3ed1814dad8d6797580d9ddd" + [[package]] name = "allocator-api2" version = "0.2.18" @@ -425,6 +431,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" name = "sheetui" version = "0.1.0" dependencies = [ + "a1", "clap", "crossterm", "ratatui", diff --git a/Cargo.toml b/Cargo.toml index 09699f6..e47228c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +a1 = "1.0.1" clap = { version = "4.5.20", features = ["derive"] } crossterm = "0.28.1" ratatui = "0.29.0" diff --git a/src/main.rs b/src/main.rs index 1536935..ed209ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,9 +5,12 @@ use crossterm::event::{self, Event, KeyCode, KeyEventKind}; use ratatui::{ self, layout::{Constraint, Layout}, - widgets::{Cell, Row, Table, Tabs}, + widgets::{Table, Tabs}, Frame, }; +use sheet::{Address, Computable, Tbl}; + +mod sheet; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -27,20 +30,17 @@ fn run(terminal: &mut ratatui::DefaultTerminal) -> std::io::Result<()> { } } -fn generate_default_rows<'a>(row_count: usize, col_count: usize) -> Vec> { - (0..row_count) - .map(|ri| { - (0..col_count) - .map(|ci| Cell::new(format!(" {}:{} ", ri, ci))) - .collect::() - .height(2) - }) - .collect::>() +fn generate_default_table<'a>() -> Table<'a> { + let mut tbl = Tbl::new(); + tbl.update_entry(Address::new(5, 5), Computable::Text("loc: 5, 5".to_owned())); + tbl.update_entry(Address::new(10, 10), Computable::Number(10.10)); + tbl.update_entry(Address::new(0, 0), Computable::Formula("".to_owned())); + tbl.into() } fn draw(frame: &mut Frame) { use Constraint::{Min, Percentage}; - let table = Table::default().rows(generate_default_rows(10, 10)); + let table = generate_default_table(); let tabs = Tabs::new(vec!["sheet1"]).select(0); let rects = Layout::vertical([Min(1), Percentage(90)]).split(frame.area()); @@ -49,8 +49,8 @@ fn draw(frame: &mut Frame) { } fn main() -> std::io::Result<()> { - let args = Args::parse(); - + let _ = Args::parse(); + let mut terminal = ratatui::init(); terminal.clear()?; let app_result = run(&mut terminal); diff --git a/src/sheet/mod.rs b/src/sheet/mod.rs new file mode 100644 index 0000000..749cdb3 --- /dev/null +++ b/src/sheet/mod.rs @@ -0,0 +1,94 @@ +//! DataModel for a SpreadSheet +//! +//! # Overview +//! +//! Sheets can contain a [Tbl]. Tbl's contain a collection of [Address] to [Computable] +//! associations. From this we can compute the dimensions of a Tbl as well as render +//! them into a [Table] Widget. + +use ratatui::widgets::{Cell, Row, Table}; + +use std::collections::BTreeMap; + +/// The Address in a [Tbl]. +#[derive(Default, Debug, PartialEq, PartialOrd, Ord, Eq)] +pub struct Address { + row: usize, + col: usize, +} + +impl Address { + pub fn new(row: usize, col: usize) -> Self { + Self { row, col } + } +} + +/// The computable value located at an [Address]. +#[derive(Debug)] +pub enum Computable { + Text(String), + Number(f64), + Formula(String), +} + +impl Default for Computable { + fn default() -> Self { + Self::Text("".to_owned()) + } +} + +/// A single table of addressable computable values. +#[derive(Default, Debug)] +pub struct Tbl { + addresses: BTreeMap, +} + +impl Tbl { + pub fn new() -> Self { + Self::default() + } + + pub fn dimensions(&self) -> (usize, usize) { + let (mut row, mut col) = (0, 0); + for (addr, _) in &self.addresses { + row = std::cmp::max(row, addr.row); + col = std::cmp::max(col, addr.col); + } + (row, col) + } + + pub fn get_computable(&self, row: usize, col: usize) -> Option<&Computable> { + self.addresses.get(&Address::new(row, col)) + } + + pub fn update_entry(&mut self, address: Address, computable: Computable) { + // TODO(zaphar): At some point we'll need to store the graph of computation + // dependencies + self.addresses.insert(address, computable); + } +} + +impl<'t> From for Table<'t> { + fn from(value: Tbl) -> Self { + let (row, col) = value.dimensions(); + let rows = (0..=row) + .map(|ri| { + (0..=col) + .map(|ci| { + match value.get_computable(ri, ci) { + // TODO(zaphar): Style information + Some(Computable::Text(s)) => Cell::new(format!(" {}", s)), + Some(Computable::Number(f)) => Cell::new(format!(" {}", f)), + Some(Computable::Formula(_expr)) => Cell::new(format!(" .formula. ")), + None => Cell::new(format!(" {}:{} ", ri, ci)), + } + }) + .collect::() + }) + .collect::>(); + Table::default().rows(rows) + } +} + +#[cfg(test)] +mod tests; diff --git a/src/sheet/tests.rs b/src/sheet/tests.rs new file mode 100644 index 0000000..90be3c4 --- /dev/null +++ b/src/sheet/tests.rs @@ -0,0 +1,12 @@ +use super::*; + +#[test] +fn test_dimensions_calculation() { + let mut tbl = Tbl::new(); + tbl.update_entry(Address::new(0, 0), Computable::Text(String::new())); + assert_eq!((0, 0), tbl.dimensions()); + tbl.update_entry(Address::new(0, 10), Computable::Text(String::new())); + assert_eq!((0, 10), tbl.dimensions()); + tbl.update_entry(Address::new(20, 5), Computable::Text(String::new())); + assert_eq!((20, 10), tbl.dimensions()); +}