diff --git a/.gitignore b/.gitignore index 0f84cc9..374729b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -/.vscode \ No newline at end of file +/.vscode +/*.csv \ No newline at end of file diff --git a/src/app/app.rs b/src/app/app.rs index 83db28e..a46e8a9 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -1,4 +1,9 @@ -use std::{cmp::{max, min}, collections::HashMap, io, path::PathBuf}; +use std::{ + cmp::{max, min}, + collections::HashMap, + io, + path::PathBuf, +}; use ratatui::{ DefaultTerminal, Frame, @@ -10,7 +15,10 @@ use ratatui::{ }; use crate::app::{ - error_msg::ErrorMessage, logic::calc::{CellType, Grid}, mode::Mode, screen::ScreenSpace + error_msg::ErrorMessage, + logic::calc::{CellType, Grid}, + mode::Mode, + screen::ScreenSpace, }; pub struct App { @@ -25,7 +33,6 @@ pub struct App { impl Widget for &App { fn render(self, area: prelude::Rect, buf: &mut prelude::Buffer) { - let (x_max, y_max) = self.screen.how_many_cells_fit_in(&area, &self.vars); let is_selected = |x: usize, y: usize| -> bool { @@ -52,6 +59,9 @@ impl Widget for &App { let mut display = String::new(); let mut style = Style::new().fg(Color::White); + let cell_width = self.screen.get_cell_width(&self.vars) as u16; + let cell_height = self.screen.get_cell_height(&self.vars) as u16; + // Minus 1 because of header cells, // the grid is shifted over (1,1), so if you need // to index the grid, these are you values. @@ -71,6 +81,9 @@ impl Widget for &App { const ORANGE1: Color = Color::Rgb(200, 160, 0); const ORANGE2: Color = Color::Rgb(180, 130, 0); + let mut should_render = true; + let mut suggest_upper_bound = None; + match (x == 0, y == 0) { // 0,0 vi mode (true, true) => { @@ -106,28 +119,44 @@ impl Widget for &App { } // grid squares (false, false) => { - if let Some(cell) = self.grid.get_cell_raw(x_idx, y_idx) { - match cell { - CellType::Number(c) => display = c.to_string(), - CellType::String(s) => display = s.to_owned(), - CellType::Equation(e) => { - if let Some(val) = self.grid.evaluate(e) { - display = val.to_string(); - style = Style::new() - .underline_color(Color::DarkGray) - .add_modifier(Modifier::UNDERLINED); - } else { - // the formula is broken - display = e.to_owned(); - style = Style::new() - .fg(Color::Red) - .underline_color(Color::Red) - .add_modifier(Modifier::UNDERLINED) + match self.grid.get_cell_raw(x_idx, y_idx) { + Some(cell) => { + match cell { + CellType::Number(c) => display = c.to_string(), + CellType::String(s) => display = s.to_owned(), + CellType::Equation(e) => { + match self.grid.evaluate(e) { + Ok(val) => { + display = val.to_string(); + style = Style::new() + .underline_color(Color::DarkGray) + .add_modifier(Modifier::UNDERLINED); + } + Err(err) => { + // the formula is broken + display = err.to_owned(); + style = Style::new() + .fg(Color::Red) + .underline_color(Color::Red) + .add_modifier(Modifier::UNDERLINED) + } + } + } + } + + suggest_upper_bound = Some(display.len() as u16); + // check for cells to the right, see if we should truncate the cell width + for i in 1..(display.len() as f32 / cell_width as f32).ceil() as usize { + if let Some(_) = self.grid.get_cell_raw(x_idx + i, y_idx) { + suggest_upper_bound = Some(cell_width * i as u16); + break; } } } + None => should_render = false, } if (x_idx, y_idx) == self.grid.selected_cell { + should_render = true; style = Style::new().fg(Color::Black).bg(Color::White); // modify the style of the cell you are editing if let Mode::Insert(_) = self.mode { @@ -136,16 +165,24 @@ impl Widget for &App { } } } - let w = self.screen.get_cell_width(&self.vars) as u16; - let h = self.screen.get_cell_height(&self.vars) as u16; - let area = Rect::new( - area.x + (x * w), - area.y + (y * h), - w, - h, - ); + if should_render { + let x_off = area.x + (x * cell_width); + let y_off = area.y + (y * cell_height); - Paragraph::new(display).style(style).render(area, buf); + let area = if let Some(suggestion) = suggest_upper_bound { + let max_available_width = area.width - x_off; + // draw the biggest cell possible, without going OOB off the screen + let width = min(max_available_width, suggestion as u16); + // Don't draw too small tho, we want full-sized cells, minium + let width = max(cell_width, width); + + Rect::new(x_off, y_off, width, cell_height) + } else { + Rect::new(x_off, y_off, cell_width, cell_height) + }; + + Paragraph::new(display).style(style).render(area, buf); + } } } } @@ -189,18 +226,16 @@ impl App { let body = layout[1]; let len = match &self.mode { - Mode::Insert(edit) | - Mode::Command(edit) | - Mode::Chord(edit) => edit.len(), + Mode::Insert(edit) | Mode::Command(edit) | Mode::Chord(edit) => edit.len(), Mode::Normal => { - let (x, y) = self.grid.selected_cell; - let cell = self.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string().len()).unwrap_or_default(); - cell - }, + let (x, y) = self.grid.selected_cell; + let cell = self.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string().len()).unwrap_or_default(); + cell + } Mode::Visual(_) => 0, }; // min 20 chars, expand if needed - let len = max(len as u16 +1, 20); + let len = max(len as u16 + 1, 20); let bottom_split = Layout::default() .direction(layout::Direction::Horizontal) @@ -233,15 +268,19 @@ impl App { frame.render_widget(self, body); frame.render_widget(&self.error_msg, cmd_line_right); #[cfg(debug_assertions)] - frame.render_widget(Paragraph::new(format!("x/w y/h: cursor{:?} scroll({}, {}) cell({}, {}) screen({}, {}) len{len}", - self.grid.selected_cell, - self.screen.scroll_x(), - self.screen.scroll_y(), - self.screen.get_cell_width(&self.vars), - self.screen.get_cell_height(&self.vars), - body.width, - body.height, - )), cmd_line_debug); + frame.render_widget( + Paragraph::new(format!( + "x/w y/h: cursor{:?} scroll({}, {}) cell({}, {}) screen({}, {}) len{len}", + self.grid.selected_cell, + self.screen.scroll_x(), + self.screen.scroll_y(), + self.screen.get_cell_width(&self.vars), + self.screen.get_cell_height(&self.vars), + body.width, + body.height, + )), + cmd_line_debug, + ); } fn handle_events(&mut self) -> io::Result<()> { diff --git a/src/app/logic/calc.rs b/src/app/logic/calc.rs index 7e17aba..db81364 100644 --- a/src/app/logic/calc.rs +++ b/src/app/logic/calc.rs @@ -120,12 +120,12 @@ impl Grid { /// Only evaluates equations, such as `=10` or `=A1/C2` not, /// strings or numbers. - pub fn evaluate(&self, mut eq: &str) -> Option { + pub fn evaluate(&self, mut eq: &str) -> Result { if eq.starts_with('=') { eq = &eq[1..]; } else { // Should be evaluating an equation - return None + return Err("not eq".to_string()); } let ctx = ctx::CallbackContext::new(&self); @@ -135,24 +135,24 @@ impl Grid { if e.is_number() { if e.is_float() { let val = e.as_float().expect("Value lied about being a float"); - return Some(val); + return Ok(val); } else if e.is_int() { let i = e.as_int().expect("Value lied about being an int"); - return Some(i as f64) + return Ok(i as f64) } } - return None + return Err("Result is NaN".to_string()) } Err(e) => match e { - EvalexprError::VariableIdentifierNotFound(_e) => { + EvalexprError::VariableIdentifierNotFound(e) => { // panic!("Will not be able to parse this equation, cell {e} not found") - return None + return Err(format!("{e} is not a variable")) } - EvalexprError::TypeError { expected: _, actual: _ } => { + EvalexprError::TypeError { expected: e, actual: a } => { // IE: You put a string into a function that wants a float - return None + return Err(format!("Wanted {e:?}, got {a}")) } - _ => panic!("{}", e), + _ => return Err(e.to_string()), }, } } @@ -375,7 +375,7 @@ fn fn_of_fn() { if let Some(cell) = grid.get_cell("D0") { let res = grid.evaluate(&cell.to_string()); - assert!(res.is_some()); + assert!(res.is_ok()); assert_eq!(res.unwrap(), 6.); return; } @@ -391,7 +391,7 @@ fn circular_reference_cells() { if let Some(cell) = grid.get_cell("A0") { let res = grid.evaluate(&cell.to_string()); - assert!(res.is_none()); + assert!(res.is_err()); return; } panic!("Cell not found"); @@ -405,14 +405,14 @@ fn invalid_equations() { grid.set_cell("A0", "=invalid".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_none()); + assert!(res.is_err()); // Test an "equation" that's just 1 number grid.set_cell("B0", "=10".to_string()); let cell = grid.get_cell("B0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_some()); - assert!(res.is_some_and(|v| v == 10.)); + assert!(res.is_ok()); + assert!(res.is_ok_and(|v| v == 10.)); } @@ -443,35 +443,35 @@ fn avg_function() { grid.set_cell("A0", "=avg(5)".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_some()); + assert!(res.is_ok()); assert_eq!(res.unwrap(), 5.); grid.set_cell("A0", "=avg(5,10)".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_some()); + assert!(res.is_ok()); assert_eq!(res.unwrap(), 7.5); grid.set_cell("A0", "=avg(5,10,15)".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_some()); + assert!(res.is_ok()); assert_eq!(res.unwrap(), 10.); grid.set_cell("A0", "=avg(foo)".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_none()); + assert!(res.is_err()); grid.set_cell("A0", "=avg(1, foo)".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_none()); + assert!(res.is_err()); grid.set_cell("A0", "=avg()".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_none()); + assert!(res.is_err()); } #[test] @@ -481,28 +481,28 @@ fn sum_function() { grid.set_cell("A0", "=sum(5)".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_some()); + assert!(res.is_ok()); assert_eq!(res.unwrap(), 5.); grid.set_cell("A0", "=sum(5,10)".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_some()); + assert!(res.is_ok()); assert_eq!(res.unwrap(), 15.); grid.set_cell("A0", "=sum(foo)".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_none()); + assert!(res.is_err()); grid.set_cell("A0", "=sum(1, foo)".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_none()); + assert!(res.is_err()); grid.set_cell("A0", "=sum()".to_string()); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_none()); + assert!(res.is_err()); }