Compare commits

...

4 Commits

Author SHA1 Message Date
53dcf2ffc9 mock up history
All checks were successful
Test Rust project / test (ubuntu-latest, stable) (push) Successful in 56s
2026-02-06 17:04:12 -07:00
86756a94ef deprecate old function 2026-02-06 16:55:50 -07:00
d242e1af21 apply api changes 2026-02-06 16:46:15 -07:00
abffe6073f setup history of file 2026-02-06 15:55:27 -07:00
2 changed files with 206 additions and 130 deletions

View File

@@ -2,20 +2,21 @@ use std::{
cmp::{max, min},
fs::{self, File},
io::{Read, Write},
path::PathBuf
path::PathBuf,
};
use evalexpr::*;
use crate::app::{
logic::{
cell::{CSV_DELIMITER, CellType},
ctx,
}, mode::Mode,
calc::internal::CellGrid, cell::{CSV_DELIMITER, CellType}, ctx
}
};
#[cfg(test)]
use crate::app::app::App;
#[cfg(test)]
use crate::app::mode::Mode;
pub fn get_header_size() -> usize {
let row_header_width = LEN.to_string().len();
@@ -26,13 +27,109 @@ pub const LEN: usize = 1001;
pub const CSV_EXT: &str = "csv";
pub const CUSTOM_EXT: &str = "nscim";
pub struct Grid {
mod internal {
use crate::app::logic::{calc::LEN, cell::CellType};
#[derive(Clone)]
pub struct CellGrid {
// a b c ...
// 0
// 1
// 2
// ...
cells: Vec<Vec<Option<CellType>>>,
}
impl CellGrid {
pub fn new() -> Self {
let mut a = Vec::with_capacity(LEN);
for _ in 0..LEN {
let mut b = Vec::with_capacity(LEN);
for _ in 0..LEN {
b.push(None)
}
a.push(b)
}
Self { cells: a }
}
pub fn insert_row(&mut self, y: usize) {
for x in 0..LEN {
self.cells[x].insert(y, None);
self.cells[x].pop();
}
}
pub fn insert_column(&mut self, x: usize) {
let mut v = Vec::with_capacity(LEN);
for _ in 0..LEN {
v.push(None);
}
// let clone = self.grid_history[self.current_grid].clone();
self.cells.insert(x, v);
// keep the grid LEN
self.cells.pop();
}
pub fn get_cell_raw(&self, x: usize, y: usize) -> &Option<CellType> {
if x >= LEN || y >= LEN {
return &None;
}
&self.cells[x][y]
}
pub fn set_cell_raw<T: Into<CellType>>(&mut self, (x, y): (usize, usize), val: Option<T>) {
// TODO check oob
self.cells[x][y] = val.map(|v| v.into());
}
/// Iterate over the entire grid and see where
/// the farthest modified cell is.
#[must_use]
pub fn max(&self) -> (usize, usize) {
let mut max_x = 0;
let mut max_y = 0;
for (xi, x) in self.cells.iter().enumerate() {
for (yi, cell) in x.iter().enumerate() {
if cell.is_some() {
if yi > max_y {
max_y = yi
}
if xi > max_x {
max_x = xi
}
}
}
}
(max_x, max_y)
}
#[must_use]
pub fn max_y_at_x(&self, x: usize) -> usize {
let mut max_y = 0;
if let Some(col) = self.cells.get(x) {
// we could do fancy things like .take_while(not null) but then
// we would have to deal with empty cells and stuff, which sounds
// boring. This will be fast "enough", considering the grid is
// probably only like 1k cells
for (yi, cell) in col.iter().enumerate() {
if cell.is_some() {
if yi > max_y {
max_y = yi
}
}
}
}
max_y
}
}
}
pub struct Grid {
/// Which grid in history are we currently on
current_grid: usize,
/// An array of grids, thru history
grid_history: Vec<CellGrid>,
/// (X, Y)
selected_cell: (usize, usize),
/// Have unsaved modifications been made?
@@ -47,20 +144,9 @@ impl std::fmt::Debug for Grid {
impl Grid {
pub fn new() -> Self {
let mut a = Vec::with_capacity(LEN);
for _ in 0..LEN {
let mut b = Vec::with_capacity(LEN);
for _ in 0..LEN {
b.push(None)
}
a.push(b)
}
let x = CellGrid::new();
Self {
cells: a,
selected_cell: (0, 0),
dirty: false,
}
Self { current_grid: 0, grid_history: vec![x], selected_cell: (0, 0), dirty: false }
}
pub fn new_from_file(file: &mut File) -> std::io::Result<Self> {
@@ -112,19 +198,15 @@ impl Grid {
}
let mut f = fs::OpenOptions::new().write(true).append(false).truncate(true).create(true).open(path)?;
let (mx, my) = self.max();
let (mx, my) = self.get_grid().max();
for y in 0..=my {
for x in 0..=mx {
let cell = &self.cells[x][y];
let cell = &self.get_grid().get_cell_raw(x, y);
// newline after the cell, because it's end of line.
// else, just put a comma after the cell.
let is_last = x==mx;
let delim = if is_last {
'\n'
} else {
CSV_DELIMITER
};
let is_last = x == mx;
let delim = if is_last { '\n' } else { CSV_DELIMITER };
let data = if let Some(cell) = cell {
if let Ok(val) = self.evaluate(&cell.to_string())
@@ -146,6 +228,40 @@ impl Grid {
Ok(())
}
pub fn get_grid<'a>(&'a self) -> &'a CellGrid {
&self.grid_history[self.current_grid]
}
fn get_grid_mut<'a>(&'a mut self) -> &'a mut CellGrid {
&mut self.grid_history[self.current_grid]
}
fn undo(&mut self) {
self.current_grid.saturating_sub(1);
}
fn redo(&mut self) {
self.current_grid += 1;
}
fn transact_on_grid<F>(&mut self, mut action: F)
where
F: FnMut(&mut CellGrid) -> (),
{
// push on a new reality
let new = self.get_grid().clone();
self.grid_history.push(new);
self.current_grid += 1;
// delete the other fork of the history
for i in self.current_grid+1..self.grid_history.len() {
self.grid_history.remove(i);
}
action(&mut self.grid_history[self.current_grid]);
self.dirty = true;
}
pub fn needs_to_be_saved(&self) -> bool {
self.dirty
}
@@ -252,38 +368,36 @@ impl Grid {
}
pub fn insert_row_above(&mut self, (_x, insertion_y): (usize, usize)) {
self.transact_on_grid(|grid: &mut CellGrid| {
grid.insert_row(insertion_y);
for x in 0..LEN {
self.cells[x].insert(insertion_y, None);
self.cells[x].pop();
for y in 0..LEN {
if let Some(cell) = self.get_cell_raw(x, y).as_ref().map(|f| {
if let Some(cell) = grid.get_cell_raw(x, y).as_ref().map(|f| {
f.custom_translate_cell((0, 0), (0, 1), |rolling, old, new| {
if let Some((_, arg_y)) = Grid::parse_to_idx(old) {
if arg_y < insertion_y { rolling.to_owned() } else { rolling.replace(old, new) }
} else {
unimplemented!("Invalid variable wanted to be translated")
}
}
)}) {
self.set_cell_raw((x,y), Some(cell));
})
}) {
grid.set_cell_raw((x, y), Some(cell));
}
}
}
});
}
pub fn insert_row_below(&mut self, (x, y): (usize, usize)) {
self.insert_row_above((x,y+1));
self.insert_row_above((x, y + 1));
}
pub fn insert_column_before(&mut self, (insertion_x, _y): (usize, usize)) {
let mut v = Vec::with_capacity(LEN);
for _ in 0..LEN {
v.push(None);
}
self.cells.insert(insertion_x, v);
// keep the grid LEN
self.cells.pop();
self.transact_on_grid(|grid| {
grid.insert_column(insertion_x);
for x in 0..LEN {
for y in 0..LEN {
if let Some(cell) = self.get_cell_raw(x, y).as_ref().map(|f| {
if let Some(cell) = grid.get_cell_raw(x, y).as_ref().map(|f| {
f.custom_translate_cell((0, 0), (1, 0), |rolling, old, new| {
if let Some((arg_x, _)) = Grid::parse_to_idx(old) {
// add 1 because of the insertion
@@ -293,56 +407,16 @@ impl Grid {
}
})
}) {
self.set_cell_raw((x, y), Some(cell));
grid.set_cell_raw((x, y), Some(cell));
}
}
}
});
}
pub fn insert_column_after(&mut self, (x, y): (usize, usize)) {
self.insert_column_before((x + 1, y));
}
/// Iterate over the entire grid and see where
/// the farthest modified cell is.
#[must_use]
fn max(&self) -> (usize, usize) {
let mut max_x = 0;
let mut max_y = 0;
for (xi, x) in self.cells.iter().enumerate() {
for (yi, cell) in x.iter().enumerate() {
if cell.is_some() {
if yi > max_y {
max_y = yi
}
if xi > max_x {
max_x = xi
}
}
}
}
(max_x, max_y)
}
#[must_use]
pub fn max_y_at_x(&self, x: usize) -> usize {
let mut max_y = 0;
if let Some(col) = self.cells.get(x) {
// we could do fancy things like .take_while(not null) but then
// we would have to deal with empty cells and stuff, which sounds
// boring. This will be fast "enough", considering the grid is
// probably only like 1k cells
for (yi, cell) in col.iter().enumerate() {
if cell.is_some() {
if yi > max_y {
max_y = yi
}
}
}
}
max_y
}
/// Only evaluates equations, such as `=10` or `=A1/C2`, not
/// strings or numbers.
@@ -381,10 +455,7 @@ impl Grid {
EvalexprError::VariableIdentifierNotFound(var_not_found) => {
return Err(format!("\"{var_not_found}\" is not a variable"));
}
EvalexprError::TypeError {
expected: e,
actual: a,
} => {
EvalexprError::TypeError { expected: e, actual: a } => {
// IE: You put a string into a function that wants a float
return Err(format!("Wanted {e:?}, got {a}"));
}
@@ -429,15 +500,21 @@ impl Grid {
/// Helper for tests
#[cfg(test)]
/// Don't ever remove this from being just a test-helper.
/// This function doesn't correctly use the undo/redo api, which would require doing
/// transactions on the grid instead of direct access.
pub fn set_cell<T: Into<CellType>>(&mut self, cell_id: &str, val: T) {
if let Some(loc) = Self::parse_to_idx(cell_id) {
self.set_cell_raw(loc, Some(val));
}
self.dirty = true;
}
#[deprecated]
/// You should get the grid then transact on the grid it's self
pub fn set_cell_raw<T: Into<CellType>>(&mut self, (x, y): (usize, usize), val: Option<T>) {
// TODO check oob
self.cells[x][y] = val.map(|v| v.into());
self.get_grid_mut().set_cell_raw((x,y), val.map(|v| v.into()));
self.dirty = true;
}
@@ -456,7 +533,7 @@ impl Grid {
if x >= LEN || y >= LEN {
return &None;
}
&self.cells[x][y]
&self.get_grid().get_cell_raw(x,y)
}
pub fn num_to_char(idx: usize) -> String {
@@ -574,7 +651,7 @@ fn saving_neoscim() {
fn cell_strings() {
let mut grid = Grid::new();
assert!(&grid.cells[0][0].is_none());
assert!(&grid.get_grid().get_cell_raw(0,0).is_none());
grid.set_cell("A0", "Hello".to_string());
assert!(grid.get_cell("A0").is_some());
@@ -711,17 +788,17 @@ fn grid_max() {
let mut grid = Grid::new();
grid.set_cell("A0", 1.);
let (mx, my) = grid.max();
let (mx, my) = grid.get_grid().max();
assert_eq!(mx, 0);
assert_eq!(my, 0);
grid.set_cell("B0", 1.);
let (mx, my) = grid.max();
let (mx, my) = grid.get_grid().max();
assert_eq!(mx, 1);
assert_eq!(my, 0);
grid.set_cell("B5", 1.);
let (mx, my) = grid.max();
let (mx, my) = grid.get_grid().max();
assert_eq!(mx, 1);
assert_eq!(my, 5);
}
@@ -1072,7 +1149,7 @@ fn insert_row_above_3() {
#[test]
fn cell_eval_depth() {
use crate::app::mode::*;
let mut app= App::new();
let mut app = App::new();
app.grid.set_cell("A0", 1.);
app.grid.set_cell("A1", "=A0+$A$0".to_string());
@@ -1122,4 +1199,3 @@ fn return_string_from_fn() {
}
}
}

View File

@@ -34,8 +34,8 @@ impl<'a> CallbackContext<'a> {
let mut buf = Vec::new();
for x in start_idx..=end_idx {
for y in 0..=self.variables.max_y_at_x(x) {
if let Some(s) = self.variables.get_cell_raw(x, y) {
for y in 0..=self.variables.get_grid().max_y_at_x(x) {
if let Some(s) = self.variables.get_grid().get_cell_raw(x, y) {
buf.push(s);
}
}