feat: The beginnings of a reactive table datamodel

This commit is contained in:
Jeremy Wall 2024-10-25 18:31:04 -04:00
parent 3d2bfc79fb
commit 507fa630d5
5 changed files with 127 additions and 13 deletions

7
Cargo.lock generated
View File

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "a1"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8470e27ace1e4691e30b3c05a719397c10e799d3ed1814dad8d6797580d9ddd"
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.18" version = "0.2.18"
@ -425,6 +431,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
name = "sheetui" name = "sheetui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"a1",
"clap", "clap",
"crossterm", "crossterm",
"ratatui", "ratatui",

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
a1 = "1.0.1"
clap = { version = "4.5.20", features = ["derive"] } clap = { version = "4.5.20", features = ["derive"] }
crossterm = "0.28.1" crossterm = "0.28.1"
ratatui = "0.29.0" ratatui = "0.29.0"

View File

@ -5,9 +5,12 @@ use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use ratatui::{ use ratatui::{
self, self,
layout::{Constraint, Layout}, layout::{Constraint, Layout},
widgets::{Cell, Row, Table, Tabs}, widgets::{Table, Tabs},
Frame, Frame,
}; };
use sheet::{Address, Computable, Tbl};
mod sheet;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[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<Row<'a>> { fn generate_default_table<'a>() -> Table<'a> {
(0..row_count) let mut tbl = Tbl::new();
.map(|ri| { tbl.update_entry(Address::new(5, 5), Computable::Text("loc: 5, 5".to_owned()));
(0..col_count) tbl.update_entry(Address::new(10, 10), Computable::Number(10.10));
.map(|ci| Cell::new(format!(" {}:{} ", ri, ci))) tbl.update_entry(Address::new(0, 0), Computable::Formula("".to_owned()));
.collect::<Row>() tbl.into()
.height(2)
})
.collect::<Vec<Row>>()
} }
fn draw(frame: &mut Frame) { fn draw(frame: &mut Frame) {
use Constraint::{Min, Percentage}; 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 tabs = Tabs::new(vec!["sheet1"]).select(0);
let rects = Layout::vertical([Min(1), Percentage(90)]).split(frame.area()); 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<()> { fn main() -> std::io::Result<()> {
let args = Args::parse(); let _ = Args::parse();
let mut terminal = ratatui::init(); let mut terminal = ratatui::init();
terminal.clear()?; terminal.clear()?;
let app_result = run(&mut terminal); let app_result = run(&mut terminal);

94
src/sheet/mod.rs Normal file
View File

@ -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<Address, Computable>,
}
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<Tbl> 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::<Row>()
})
.collect::<Vec<Row>>();
Table::default().rows(rows)
}
}
#[cfg(test)]
mod tests;

12
src/sheet/tests.rs Normal file
View File

@ -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());
}