add cell-overflowing, api changes

This commit is contained in:
2025-11-12 11:34:02 -07:00
parent c4b82ff650
commit 9ea3f99743
3 changed files with 113 additions and 73 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/target /target
/.vscode /.vscode
/*.csv

View File

@@ -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::{ use ratatui::{
DefaultTerminal, Frame, DefaultTerminal, Frame,
@@ -10,7 +15,10 @@ use ratatui::{
}; };
use crate::app::{ 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 { pub struct App {
@@ -25,7 +33,6 @@ pub struct App {
impl Widget for &App { impl Widget for &App {
fn render(self, area: prelude::Rect, buf: &mut prelude::Buffer) { 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 (x_max, y_max) = self.screen.how_many_cells_fit_in(&area, &self.vars);
let is_selected = |x: usize, y: usize| -> bool { let is_selected = |x: usize, y: usize| -> bool {
@@ -52,6 +59,9 @@ impl Widget for &App {
let mut display = String::new(); let mut display = String::new();
let mut style = Style::new().fg(Color::White); 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, // Minus 1 because of header cells,
// the grid is shifted over (1,1), so if you need // the grid is shifted over (1,1), so if you need
// to index the grid, these are you values. // 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 ORANGE1: Color = Color::Rgb(200, 160, 0);
const ORANGE2: Color = Color::Rgb(180, 130, 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) { match (x == 0, y == 0) {
// 0,0 vi mode // 0,0 vi mode
(true, true) => { (true, true) => {
@@ -106,28 +119,44 @@ impl Widget for &App {
} }
// grid squares // grid squares
(false, false) => { (false, false) => {
if let Some(cell) = self.grid.get_cell_raw(x_idx, y_idx) { match self.grid.get_cell_raw(x_idx, y_idx) {
match cell { Some(cell) => {
CellType::Number(c) => display = c.to_string(), match cell {
CellType::String(s) => display = s.to_owned(), CellType::Number(c) => display = c.to_string(),
CellType::Equation(e) => { CellType::String(s) => display = s.to_owned(),
if let Some(val) = self.grid.evaluate(e) { CellType::Equation(e) => {
display = val.to_string(); match self.grid.evaluate(e) {
style = Style::new() Ok(val) => {
.underline_color(Color::DarkGray) display = val.to_string();
.add_modifier(Modifier::UNDERLINED); style = Style::new()
} else { .underline_color(Color::DarkGray)
// the formula is broken .add_modifier(Modifier::UNDERLINED);
display = e.to_owned(); }
style = Style::new() Err(err) => {
.fg(Color::Red) // the formula is broken
.underline_color(Color::Red) display = err.to_owned();
.add_modifier(Modifier::UNDERLINED) 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 { if (x_idx, y_idx) == self.grid.selected_cell {
should_render = true;
style = Style::new().fg(Color::Black).bg(Color::White); style = Style::new().fg(Color::Black).bg(Color::White);
// modify the style of the cell you are editing // modify the style of the cell you are editing
if let Mode::Insert(_) = self.mode { 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; if should_render {
let h = self.screen.get_cell_height(&self.vars) as u16; let x_off = area.x + (x * cell_width);
let area = Rect::new( let y_off = area.y + (y * cell_height);
area.x + (x * w),
area.y + (y * h),
w,
h,
);
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 body = layout[1];
let len = match &self.mode { let len = match &self.mode {
Mode::Insert(edit) | Mode::Insert(edit) | Mode::Command(edit) | Mode::Chord(edit) => edit.len(),
Mode::Command(edit) |
Mode::Chord(edit) => edit.len(),
Mode::Normal => { Mode::Normal => {
let (x, y) = self.grid.selected_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(); let cell = self.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string().len()).unwrap_or_default();
cell cell
}, }
Mode::Visual(_) => 0, Mode::Visual(_) => 0,
}; };
// min 20 chars, expand if needed // 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() let bottom_split = Layout::default()
.direction(layout::Direction::Horizontal) .direction(layout::Direction::Horizontal)
@@ -233,15 +268,19 @@ impl App {
frame.render_widget(self, body); frame.render_widget(self, body);
frame.render_widget(&self.error_msg, cmd_line_right); frame.render_widget(&self.error_msg, cmd_line_right);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
frame.render_widget(Paragraph::new(format!("x/w y/h: cursor{:?} scroll({}, {}) cell({}, {}) screen({}, {}) len{len}", frame.render_widget(
self.grid.selected_cell, Paragraph::new(format!(
self.screen.scroll_x(), "x/w y/h: cursor{:?} scroll({}, {}) cell({}, {}) screen({}, {}) len{len}",
self.screen.scroll_y(), self.grid.selected_cell,
self.screen.get_cell_width(&self.vars), self.screen.scroll_x(),
self.screen.get_cell_height(&self.vars), self.screen.scroll_y(),
body.width, self.screen.get_cell_width(&self.vars),
body.height, self.screen.get_cell_height(&self.vars),
)), cmd_line_debug); body.width,
body.height,
)),
cmd_line_debug,
);
} }
fn handle_events(&mut self) -> io::Result<()> { fn handle_events(&mut self) -> io::Result<()> {

View File

@@ -120,12 +120,12 @@ impl Grid {
/// Only evaluates equations, such as `=10` or `=A1/C2` not, /// Only evaluates equations, such as `=10` or `=A1/C2` not,
/// strings or numbers. /// strings or numbers.
pub fn evaluate(&self, mut eq: &str) -> Option<f64> { pub fn evaluate(&self, mut eq: &str) -> Result<f64, String> {
if eq.starts_with('=') { if eq.starts_with('=') {
eq = &eq[1..]; eq = &eq[1..];
} else { } else {
// Should be evaluating an equation // Should be evaluating an equation
return None return Err("not eq".to_string());
} }
let ctx = ctx::CallbackContext::new(&self); let ctx = ctx::CallbackContext::new(&self);
@@ -135,24 +135,24 @@ impl Grid {
if e.is_number() { if e.is_number() {
if e.is_float() { if e.is_float() {
let val = e.as_float().expect("Value lied about being a float"); let val = e.as_float().expect("Value lied about being a float");
return Some(val); return Ok(val);
} else if e.is_int() { } else if e.is_int() {
let i = e.as_int().expect("Value lied about being an 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 { Err(e) => match e {
EvalexprError::VariableIdentifierNotFound(_e) => { EvalexprError::VariableIdentifierNotFound(e) => {
// panic!("Will not be able to parse this equation, cell {e} not found") // 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 // 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") { if let Some(cell) = grid.get_cell("D0") {
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_some()); assert!(res.is_ok());
assert_eq!(res.unwrap(), 6.); assert_eq!(res.unwrap(), 6.);
return; return;
} }
@@ -391,7 +391,7 @@ fn circular_reference_cells() {
if let Some(cell) = grid.get_cell("A0") { if let Some(cell) = grid.get_cell("A0") {
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_none()); assert!(res.is_err());
return; return;
} }
panic!("Cell not found"); panic!("Cell not found");
@@ -405,14 +405,14 @@ fn invalid_equations() {
grid.set_cell("A0", "=invalid".to_string()); grid.set_cell("A0", "=invalid".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_none()); assert!(res.is_err());
// Test an "equation" that's just 1 number // Test an "equation" that's just 1 number
grid.set_cell("B0", "=10".to_string()); grid.set_cell("B0", "=10".to_string());
let cell = grid.get_cell("B0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("B0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_some()); assert!(res.is_ok());
assert!(res.is_some_and(|v| v == 10.)); assert!(res.is_ok_and(|v| v == 10.));
} }
@@ -443,35 +443,35 @@ fn avg_function() {
grid.set_cell("A0", "=avg(5)".to_string()); grid.set_cell("A0", "=avg(5)".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_some()); assert!(res.is_ok());
assert_eq!(res.unwrap(), 5.); assert_eq!(res.unwrap(), 5.);
grid.set_cell("A0", "=avg(5,10)".to_string()); grid.set_cell("A0", "=avg(5,10)".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_some()); assert!(res.is_ok());
assert_eq!(res.unwrap(), 7.5); assert_eq!(res.unwrap(), 7.5);
grid.set_cell("A0", "=avg(5,10,15)".to_string()); grid.set_cell("A0", "=avg(5,10,15)".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_some()); assert!(res.is_ok());
assert_eq!(res.unwrap(), 10.); assert_eq!(res.unwrap(), 10.);
grid.set_cell("A0", "=avg(foo)".to_string()); grid.set_cell("A0", "=avg(foo)".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_none()); assert!(res.is_err());
grid.set_cell("A0", "=avg(1, foo)".to_string()); grid.set_cell("A0", "=avg(1, foo)".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_none()); assert!(res.is_err());
grid.set_cell("A0", "=avg()".to_string()); grid.set_cell("A0", "=avg()".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_none()); assert!(res.is_err());
} }
#[test] #[test]
@@ -481,28 +481,28 @@ fn sum_function() {
grid.set_cell("A0", "=sum(5)".to_string()); grid.set_cell("A0", "=sum(5)".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_some()); assert!(res.is_ok());
assert_eq!(res.unwrap(), 5.); assert_eq!(res.unwrap(), 5.);
grid.set_cell("A0", "=sum(5,10)".to_string()); grid.set_cell("A0", "=sum(5,10)".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_some()); assert!(res.is_ok());
assert_eq!(res.unwrap(), 15.); assert_eq!(res.unwrap(), 15.);
grid.set_cell("A0", "=sum(foo)".to_string()); grid.set_cell("A0", "=sum(foo)".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_none()); assert!(res.is_err());
grid.set_cell("A0", "=sum(1, foo)".to_string()); grid.set_cell("A0", "=sum(1, foo)".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_none()); assert!(res.is_err());
grid.set_cell("A0", "=sum()".to_string()); grid.set_cell("A0", "=sum()".to_string());
let cell = grid.get_cell("A0").as_ref().expect("Just set the cell"); let cell = grid.get_cell("A0").as_ref().expect("Just set the cell");
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_none()); assert!(res.is_err());
} }