add cell-overflowing, api changes
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
/.vscode
|
/.vscode
|
||||||
|
/*.csv
|
||||||
@@ -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,19 +119,22 @@ 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) {
|
||||||
|
Some(cell) => {
|
||||||
match cell {
|
match cell {
|
||||||
CellType::Number(c) => display = c.to_string(),
|
CellType::Number(c) => display = c.to_string(),
|
||||||
CellType::String(s) => display = s.to_owned(),
|
CellType::String(s) => display = s.to_owned(),
|
||||||
CellType::Equation(e) => {
|
CellType::Equation(e) => {
|
||||||
if let Some(val) = self.grid.evaluate(e) {
|
match self.grid.evaluate(e) {
|
||||||
|
Ok(val) => {
|
||||||
display = val.to_string();
|
display = val.to_string();
|
||||||
style = Style::new()
|
style = Style::new()
|
||||||
.underline_color(Color::DarkGray)
|
.underline_color(Color::DarkGray)
|
||||||
.add_modifier(Modifier::UNDERLINED);
|
.add_modifier(Modifier::UNDERLINED);
|
||||||
} else {
|
}
|
||||||
|
Err(err) => {
|
||||||
// the formula is broken
|
// the formula is broken
|
||||||
display = e.to_owned();
|
display = err.to_owned();
|
||||||
style = Style::new()
|
style = Style::new()
|
||||||
.fg(Color::Red)
|
.fg(Color::Red)
|
||||||
.underline_color(Color::Red)
|
.underline_color(Color::Red)
|
||||||
@@ -127,7 +143,20 @@ impl Widget for &App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,20 +165,28 @@ 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),
|
let area = if let Some(suggestion) = suggest_upper_bound {
|
||||||
w,
|
let max_available_width = area.width - x_off;
|
||||||
h,
|
// 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);
|
Paragraph::new(display).style(style).render(area, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -189,14 +226,12 @@ 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
|
||||||
@@ -233,7 +268,9 @@ 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(
|
||||||
|
Paragraph::new(format!(
|
||||||
|
"x/w y/h: cursor{:?} scroll({}, {}) cell({}, {}) screen({}, {}) len{len}",
|
||||||
self.grid.selected_cell,
|
self.grid.selected_cell,
|
||||||
self.screen.scroll_x(),
|
self.screen.scroll_x(),
|
||||||
self.screen.scroll_y(),
|
self.screen.scroll_y(),
|
||||||
@@ -241,7 +278,9 @@ impl App {
|
|||||||
self.screen.get_cell_height(&self.vars),
|
self.screen.get_cell_height(&self.vars),
|
||||||
body.width,
|
body.width,
|
||||||
body.height,
|
body.height,
|
||||||
)), cmd_line_debug);
|
)),
|
||||||
|
cmd_line_debug,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_events(&mut self) -> io::Result<()> {
|
fn handle_events(&mut self) -> io::Result<()> {
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user