Compare commits

6 Commits

Author SHA1 Message Date
9de22b9680 lol
All checks were successful
Test Rust project / test (ubuntu-latest, stable) (push) Successful in 46s
2026-02-09 14:16:35 -07:00
3f336578f5 Continue on #53, solve failing test
All checks were successful
Test Rust project / test (ubuntu-latest, stable) (push) Successful in 46s
2026-02-09 13:33:56 -07:00
5fbff13428 close #6
Some checks failed
Test Rust project / test (ubuntu-latest, stable) (push) Failing after 1m15s
2026-02-09 13:24:09 -07:00
7a23ee5bc0 don't let user go oob 2026-02-09 13:14:44 -07:00
ea2e633d7d Merge pull request 'Close #43' (#54) from issue-43 into master
All checks were successful
Test Rust project / test (ubuntu-latest, stable) (push) Successful in 48s
Reviewed-on: #54
2026-02-09 20:00:38 +00:00
d9f29434e9 Close #43
All checks were successful
Test Rust project / test (ubuntu-latest, stable) (push) Successful in 49s
This takes rendering time from ~0.5ms to ~0.5ms.
When highlighting a range it may get up to 1.5ms.
2026-02-09 12:58:49 -07:00
5 changed files with 90 additions and 34 deletions

View File

@@ -19,7 +19,7 @@ use crate::app::{
clipboard::Clipboard, clipboard::Clipboard,
error_msg::StatusMessage, error_msg::StatusMessage,
logic::{ logic::{
calc::{Grid, get_header_size}, calc::{Grid, LEN, get_header_size},
cell::CellType, cell::CellType,
}, },
mode::Mode, mode::Mode,
@@ -42,9 +42,11 @@ 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 now = std::time::Instant::now();
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_visually_selected = |x: usize, y: usize| -> bool {
if let Mode::Visual((mut x1, mut y1)) | Mode::VisualCmd((mut x1, mut y1), _) = self.mode { if let Mode::Visual((mut x1, mut y1)) | Mode::VisualCmd((mut x1, mut y1), _) = self.mode {
let (mut x2, mut y2) = self.grid.cursor(); let (mut x2, mut y2) = self.grid.cursor();
x1 += 1; x1 += 1;
@@ -63,10 +65,40 @@ impl Widget for &App {
false false
}; };
for x in 0..x_max { // cells that are related by reference to the cursor's cell
for y in 0..y_max { // (inputs to formulas and such)
let mut display = String::new(); let cells_of_interest: Vec<(usize, usize)> = {
let mut style = Style::new(); let ctx = crate::app::logic::ctx::ExtractionContext::new();
let (x, y) = self.grid.cursor();
if let Some(cell) = self.grid.get_cell_raw(x, y) {
if let CellType::Equation(eq) = cell {
let _ = evalexpr::eval_with_context(&eq[1..], &ctx);
let vars = ctx.dump_vars();
let mut interest = Vec::new();
for var in vars {
if let Some(a) = Grid::parse_to_idx(&var) {
interest.push(a);
} else if let Some((start, end)) = Grid::range_as_indices(&var) {
// insert coords:
// (start, 0..len)
// ..
// (end, 0..len)
for x in start..=end {
for y in 0..=super::logic::calc::LEN {
interest.push((x,y))
}
}
}
}
interest
} else {
Vec::new()
}
} else {
Vec::new()
}
};
// Custom width for the header of each row // Custom width for the header of each row
let row_header_width = get_header_size() as u16; let row_header_width = get_header_size() as u16;
@@ -75,6 +107,11 @@ impl Widget for &App {
let cell_width = self.screen.get_cell_width(&self.vars) as u16; let cell_width = self.screen.get_cell_width(&self.vars) as u16;
let cell_height = self.screen.get_cell_height(&self.vars) as u16; let cell_height = self.screen.get_cell_height(&self.vars) as u16;
for x in 0..x_max {
for y in 0..y_max {
let mut display = String::new();
let mut style = Style::new();
// 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.
@@ -87,6 +124,11 @@ impl Widget for &App {
y_idx = y as usize - 1 + self.screen.scroll_y(); y_idx = y as usize - 1 + self.screen.scroll_y();
} }
// don't render non-accessible cells
if x_idx > LEN-1 {
continue;
}
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);
@@ -169,6 +211,7 @@ impl Widget for &App {
} }
} }
// ===================================================
// Allow for text in one cell to visually overflow into empty cells // Allow for text in one cell to visually overflow into empty cells
suggest_upper_bound = Some(display.len() as u16); suggest_upper_bound = Some(display.len() as u16);
// check for cells to the right, see if we should truncate the cell width // check for cells to the right, see if we should truncate the cell width
@@ -185,18 +228,25 @@ impl Widget for &App {
display.push('…'); display.push('…');
} }
} }
// ===================================================
} }
// Don't render blank cells // Don't render blank cells
None => should_render = false, None => should_render = false,
} }
if is_selected(x.into(), y.into()) { if cells_of_interest.contains(&(x_idx, y_idx)) {
style = style.fg(Color::Yellow);
should_render = true;
}
if is_visually_selected(x.into(), y.into()) {
style = style.bg(Color::Blue); style = style.bg(Color::Blue);
// Make it so that cells render when selected. This fixes issue #32 // Make it so that cells render when selected. This fixes issue #32
should_render = true; should_render = true;
} }
if (x_idx, y_idx) == self.grid.cursor() { if (x_idx, y_idx) == self.grid.cursor() {
should_render = true; 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 {
@@ -235,6 +285,9 @@ impl Widget for &App {
} }
} }
} }
// let ns = now.elapsed().as_nanos() as f64;
// eprintln!("Rendered in {}ms", ns/1_000_000.);
} }
} }

View File

@@ -489,6 +489,8 @@ impl Grid {
} }
} }
/// Gets the indices of the range labels.
/// A:B -> (0,1)
pub fn range_as_indices(range: &str) -> Option<(usize, usize)> { pub fn range_as_indices(range: &str) -> Option<(usize, usize)> {
let v = range.split(':').collect::<Vec<&str>>(); let v = range.split(':').collect::<Vec<&str>>();
if v.len() == 2 { if v.len() == 2 {
@@ -761,14 +763,16 @@ fn fn_of_fn() {
grid.set_cell("C0", "=A0+B0".to_string()); grid.set_cell("C0", "=A0+B0".to_string());
grid.set_cell("D0", "=C0*2".to_string()); grid.set_cell("D0", "=C0*2".to_string());
if let Some(cell) = grid.get_cell("D0") { let cell = grid.get_cell("D0");
assert!(cell.is_some());
if let Some(cell) = cell {
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), (6.).into()); assert_eq!(res.unwrap(), (6.).into());
return; return;
} }
panic!("Cell not found");
} }
// Two cells that have a circular dependency to solve for a value // Two cells that have a circular dependency to solve for a value
@@ -778,12 +782,14 @@ fn circular_reference_cells() {
grid.set_cell("A0", "=B0".to_string()); grid.set_cell("A0", "=B0".to_string());
grid.set_cell("B0", "=A0".to_string()); grid.set_cell("B0", "=A0".to_string());
if let Some(cell) = grid.get_cell("A0") { let cell = grid.get_cell("A0");
assert!(cell.is_some());
if let Some(cell) = cell {
let res = grid.evaluate(&cell.to_string()); let res = grid.evaluate(&cell.to_string());
assert!(res.is_err()); assert!(res.is_err());
return; return;
} }
panic!("Cell not found");
} }
#[test] #[test]

View File

@@ -188,7 +188,14 @@ impl<'a> Context for CallbackContext<'a> {
return None; return None;
} }
e => panic!("> Error {e}\n> Equation: '{eq}'"), e => {
let msg = format!("> Error {e}\n> Equation: '{eq}'");
#[cfg(debug_assertions)]
panic!("{msg}");
#[cfg(not(debug_assertions))]
eprintln!("{msg}");
},
} }
} }
} }

View File

@@ -273,13 +273,13 @@ impl Mode {
// Go to end of row // Go to end of row
'$' => { '$' => {
let (_, y) = app.grid.cursor(); let (_, y) = app.grid.cursor();
app.grid.mv_cursor_to(super::logic::calc::LEN, y); app.grid.mv_cursor_to(super::logic::calc::LEN-1, y);
return; return;
} }
// Go to bottom of column // Go to bottom of column
'G' => { 'G' => {
let (x, _) = app.grid.cursor(); let (x, _) = app.grid.cursor();
app.grid.mv_cursor_to(x, super::logic::calc::LEN); app.grid.mv_cursor_to(x, super::logic::calc::LEN-1);
return; return;
} }
// edit cell // edit cell

View File

@@ -1,4 +1,4 @@
use std::{collections::HashMap, sync::RwLock}; use std::{cmp::min, collections::HashMap, sync::RwLock};
use ratatui::prelude; use ratatui::prelude;
@@ -123,22 +123,12 @@ impl ScreenSpace {
l.1 = area.height as usize; l.1 = area.height as usize;
} }
// let width = (area.width as usize + calc::get_header_size() -1) / self.get_cell_width(vars); let width = (area.width as usize / self.get_cell_width(vars)) + 1;
let width = area.width as usize / self.get_cell_width(vars);
let height = area.height as usize / self.get_cell_height(vars); let height = area.height as usize / self.get_cell_height(vars);
let x_max = let x_max = min(LEN-1, width);
if width > LEN { let y_max = min(LEN-1, height);
LEN - 1
} else {
width
};
let y_max =
if height > LEN {
LEN - 1
} else {
height
};
(x_max as u16, y_max as u16) (x_max as u16, y_max as u16)
} }
@@ -152,7 +142,7 @@ fn fit_cells() {
app.vars.insert("height".to_string(), 1.to_string()); app.vars.insert("height".to_string(), 1.to_string());
let (x,y) = app.screen.how_many_cells_fit_in(&prelude::Rect::new(0, 0, 181, 14), &app.vars); let (x,y) = app.screen.how_many_cells_fit_in(&prelude::Rect::new(0, 0, 181, 14), &app.vars);
assert_eq!(x, 18); assert_eq!(x, 19);
assert_eq!(y, 14); assert_eq!(y, 14);
} }
@@ -166,7 +156,7 @@ fn scroll() {
// We have to check how many cells fit, because screen learns the width // We have to check how many cells fit, because screen learns the width
// of the area by rumour here. // of the area by rumour here.
let (x,y) = app.screen.how_many_cells_fit_in(&prelude::Rect::new(0, 0, 181, 14), &app.vars); let (x,y) = app.screen.how_many_cells_fit_in(&prelude::Rect::new(0, 0, 181, 14), &app.vars);
assert_eq!(x, 18); assert_eq!(x, 19);
assert_eq!(y, 14); assert_eq!(y, 14);
// we aren't scrolled at all yet // we aren't scrolled at all yet