Compare commits

...

3 Commits

Author SHA1 Message Date
d5d58694bb redo
All checks were successful
Test Rust project / test (ubuntu-latest, stable) (push) Successful in 43s
Undo / Redo are currently bound to Pageup/down. This is ideally temporary, I just need to figure out ctrl+r for redo. "u" is also bound to undo as it should be
2026-02-07 20:46:22 -07:00
c2e0661a45 solve #29 2026-02-07 20:37:01 -07:00
6ec7d90ac5 undo 2026-02-07 20:30:19 -07:00
4 changed files with 128 additions and 110 deletions

View File

@@ -18,7 +18,10 @@ use ratatui::{
use crate::app::{ use crate::app::{
clipboard::Clipboard, clipboard::Clipboard,
error_msg::StatusMessage, error_msg::StatusMessage,
logic::{calc::{Grid, get_header_size}, cell::CellType}, logic::{
calc::{Grid, get_header_size},
cell::CellType,
},
mode::Mode, mode::Mode,
screen::ScreenSpace, screen::ScreenSpace,
}; };
@@ -387,17 +390,20 @@ impl App {
event::KeyCode::Enter => { event::KeyCode::Enter => {
let v = editor.as_string(); let v = editor.as_string();
let cursor = self.grid.cursor();
self.grid.transact_on_grid(|grid| {
// try to insert as a float // try to insert as a float
if let Ok(v) = v.parse::<f64>() { if let Ok(v) = v.parse::<f64>() {
self.grid.set_cell_raw(self.grid.cursor(), Some(v)); grid.set_cell_raw(cursor, Some(v));
} else { } else {
// if you can't, then insert as a string // if you can't, then insert as a string
if !v.is_empty() { if !v.is_empty() {
self.grid.set_cell_raw(self.grid.cursor(), Some(v)); grid.set_cell_raw(cursor, Some(v.to_owned()));
} else { } else {
self.grid.set_cell_raw::<CellType>(self.grid.cursor(), None); grid.set_cell_raw::<CellType>(cursor, None);
} }
} }
});
self.mode = Mode::Normal; self.mode = Mode::Normal;
} }
@@ -413,10 +419,23 @@ impl App {
}, },
Mode::Normal => match event::read()? { Mode::Normal => match event::read()? {
event::Event::Key(key_event) => match key_event.code { event::Event::Key(key_event) => match key_event.code {
event::KeyCode::F(_) => todo!(), event::KeyCode::F(n) => {},
event::KeyCode::Char(c) => { event::KeyCode::Char(c) => Mode::process_key(self, c),
Mode::process_key(self, c); // Pretend that the arrow keys are vim movement keys
event::KeyCode::Left => Mode::process_key(self, 'h'),
event::KeyCode::Right => Mode::process_key(self, 'l'),
event::KeyCode::Up => Mode::process_key(self, 'k'),
event::KeyCode::Down => Mode::process_key(self, 'j'),
// Getting ctrl to work isn't going will right now. Use page keys for the time being.
event::KeyCode::PageUp => self.grid.redo(),
event::KeyCode::PageDown => self.grid.undo(),
event::KeyCode::Modifier(modifier_key_code) => {
if let event::ModifierKeyCode::LeftControl | event::ModifierKeyCode::RightControl = modifier_key_code {
// TODO my terminal (alacritty) isn't showing me ctrl presses. I know
// that they work tho, since ctrl+r works here in neovim.
// panic!("heard ctrl");
} }
},
_ => {} _ => {}
}, },
_ => {} _ => {}

View File

@@ -18,12 +18,7 @@ pub struct Clipboard {
impl Clipboard { impl Clipboard {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self { clipboard: Vec::new(), last_paste_cell: (0, 0), momentum: (0, 1), source_cell: (0, 0) }
clipboard: Vec::new(),
last_paste_cell: (0, 0),
momentum: (0, 1),
source_cell: (0, 0),
}
} }
/// Panics if clipboard is 0 length (if you call after you /// Panics if clipboard is 0 length (if you call after you
@@ -49,6 +44,8 @@ impl Clipboard {
// cursor // cursor
let (cx, cy) = into.cursor(); let (cx, cy) = into.cursor();
let cursor = into.cursor();
into.transact_on_grid(|grid| {
// iterate thru the clipbaord's cells // iterate thru the clipbaord's cells
for (x, row) in self.clipboard.iter().enumerate() { for (x, row) in self.clipboard.iter().enumerate() {
for (y, cell) in row.iter().enumerate() { for (y, cell) in row.iter().enumerate() {
@@ -56,18 +53,19 @@ impl Clipboard {
if translate { if translate {
if let Some(cell) = cell { if let Some(cell) = cell {
let trans = cell.translate_cell(self.source_cell, into.cursor()); let trans = cell.translate_cell(self.source_cell, cursor);
into.set_cell_raw(idx, Some(trans)); grid.set_cell_raw(idx, Some(trans));
} else { } else {
// The cell at this location doesn't exist (empty) // The cell at this location doesn't exist (empty)
into.set_cell_raw::<CellType>(idx, None); grid.set_cell_raw::<CellType>(idx, None);
} }
} else { } else {
// translate = false // translate = false
into.set_cell_raw::<CellType>(idx, cell.clone()); grid.set_cell_raw::<CellType>(idx, cell.clone());
} }
} }
} }
});
let (lx, ly) = self.last_paste_cell; let (lx, ly) = self.last_paste_cell;
self.momentum = (cx as i32 - lx as i32, cy as i32 - ly as i32); self.momentum = (cx as i32 - lx as i32, cy as i32 - ly as i32);
@@ -109,16 +107,19 @@ impl Clipboard {
// size the clipboard appropriately // size the clipboard appropriately
self.clipboard.clear(); self.clipboard.clear();
from.transact_on_grid(|grid| {
// clone data into clipboard // clone data into clipboard
for x in low_x..=hi_x { for x in low_x..=hi_x {
let mut col = Vec::new(); let mut col = Vec::new();
for y in low_y..=hi_y { for y in low_y..=hi_y {
let a = from.get_cell_raw(x, y); let a = grid.get_cell_raw(x, y);
col.push(a.clone()); col.push(a.clone());
from.set_cell_raw::<CellType>((x, y), None); grid.set_cell_raw::<CellType>((x, y), None);
} }
self.clipboard.push(col); self.clipboard.push(col);
} }
});
self.last_paste_cell = (low_x, low_y); self.last_paste_cell = (low_x, low_y);
} }
} }
@@ -394,5 +395,4 @@ fn copy_paste_range_in_function() {
let a = app.grid.get_cell("B1").as_ref().expect("Should've been set by paste"); let a = app.grid.get_cell("B1").as_ref().expect("Should've been set by paste");
assert_eq!(a.to_string(), "=sum(A:A)"); assert_eq!(a.to_string(), "=sum(A:A)");
} }

View File

@@ -7,10 +7,10 @@ use std::{
use evalexpr::*; use evalexpr::*;
use crate::app::{ use crate::app::logic::{
logic::{ calc::internal::CellGrid,
calc::internal::CellGrid, cell::{CSV_DELIMITER, CellType}, ctx cell::{CSV_DELIMITER, CellType},
} ctx,
}; };
#[cfg(test)] #[cfg(test)]
@@ -154,6 +154,8 @@ impl Grid {
let mut buf = String::new(); let mut buf = String::new();
file.read_to_string(&mut buf)?; file.read_to_string(&mut buf)?;
grid.transact_on_grid(|grid| {
for (yi, line) in buf.lines().enumerate() { for (yi, line) in buf.lines().enumerate() {
let cells = Self::parse_csv_line(line); let cells = Self::parse_csv_line(line);
@@ -162,6 +164,7 @@ impl Grid {
grid.set_cell_raw((xi, yi), cell); grid.set_cell_raw((xi, yi), cell);
} }
} }
});
// force dirty back off, we just read the data so it's gtg // force dirty back off, we just read the data so it's gtg
grid.dirty = false; grid.dirty = false;
@@ -236,14 +239,14 @@ impl Grid {
&mut self.grid_history[self.current_grid] &mut self.grid_history[self.current_grid]
} }
fn undo(&mut self) { pub fn undo(&mut self) {
self.current_grid.saturating_sub(1); self.current_grid = self.current_grid.saturating_sub(1);
} }
fn redo(&mut self) { pub fn redo(&mut self) {
self.current_grid += 1; self.current_grid = min(self.grid_history.len() - 1, self.current_grid + 1);
} }
fn transact_on_grid<F>(&mut self, mut action: F) pub fn transact_on_grid<F>(&mut self, mut action: F)
where where
F: FnMut(&mut CellGrid) -> (), F: FnMut(&mut CellGrid) -> (),
{ {
@@ -505,19 +508,11 @@ impl Grid {
/// transactions on the grid instead of direct access. /// transactions on the grid instead of direct access.
pub fn set_cell<T: Into<CellType>>(&mut self, cell_id: &str, val: T) { pub fn set_cell<T: Into<CellType>>(&mut self, cell_id: &str, val: T) {
if let Some(loc) = Self::parse_to_idx(cell_id) { if let Some(loc) = Self::parse_to_idx(cell_id) {
self.set_cell_raw(loc, Some(val)); self.get_grid_mut().set_cell_raw(loc, Some(val))
} }
self.dirty = true; 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.get_grid_mut().set_cell_raw((x,y), val.map(|v| v.into()));
self.dirty = true;
}
/// Get cells via text like: /// Get cells via text like:
/// A6, /// A6,
/// F0, /// F0,

View File

@@ -2,7 +2,8 @@ use std::{
cmp::{max, min}, cmp::{max, min},
fmt::Display, fmt::Display,
fs, fs,
path::PathBuf, process::Command, path::PathBuf,
process::Command,
}; };
use ratatui::{ use ratatui::{
@@ -153,11 +154,13 @@ impl Mode {
let mut save_range = |to: &str| { let mut save_range = |to: &str| {
let mut g = Grid::new(); let mut g = Grid::new();
g.transact_on_grid(|grid| {
for (i, x) in (low_x..=hi_x).enumerate() { for (i, x) in (low_x..=hi_x).enumerate() {
for (j, y) in (low_y..=hi_y).enumerate() { for (j, y) in (low_y..=hi_y).enumerate() {
g.set_cell_raw((i, j), app.grid.get_cell_raw(x, y).clone()); grid.set_cell_raw((i, j), grid.get_cell_raw(x, y).clone());
} }
} }
});
if let Err(_e) = g.save_to(to) { if let Err(_e) = g.save_to(to) {
app.msg = StatusMessage::error("Failed to save file"); app.msg = StatusMessage::error("Failed to save file");
} }
@@ -171,22 +174,24 @@ impl Mode {
} }
} }
} }
return "unknown" return "unknown";
}; };
match args[0] { match args[0] {
"f" | "fill" => { "f" | "fill" => {
app.grid.transact_on_grid(|grid| {
for (i, x) in (low_x..=hi_x).enumerate() { for (i, x) in (low_x..=hi_x).enumerate() {
for (j, y) in (low_y..=hi_y).enumerate() { for (j, y) in (low_y..=hi_y).enumerate() {
let arg = args.get(1) let arg = args
.get(1)
.map(|s| s.replace("xi", &i.to_string())) .map(|s| s.replace("xi", &i.to_string()))
.map(|s| s.replace("yi", &j.to_string())) .map(|s| s.replace("yi", &j.to_string()))
.map(|s| s.replace("x", &x.to_string())) .map(|s| s.replace("x", &x.to_string()))
.map(|s| s.replace("y", &y.to_string())) .map(|s| s.replace("y", &y.to_string()));
; grid.set_cell_raw((x, y), arg);
app.grid.set_cell_raw((x,y), arg);
} }
} }
});
app.mode = Mode::Normal app.mode = Mode::Normal
} }
@@ -202,11 +207,7 @@ impl Mode {
// Use gnuplot to plot the selected data. // Use gnuplot to plot the selected data.
// * Temp data will be stored in /tmp/ // * Temp data will be stored in /tmp/
// * Output will either be plot.png or a name that you pass in // * Output will either be plot.png or a name that you pass in
let output_filename = if let Some(arg1) = args.get(1) { let output_filename = if let Some(arg1) = args.get(1) { arg1 } else { "plot.png" };
arg1
} else {
"plot.png"
};
save_range("/tmp/plot.csv"); save_range("/tmp/plot.csv");
let plot = include_str!("../../template.gnuplot"); let plot = include_str!("../../template.gnuplot");
@@ -220,7 +221,9 @@ impl Mode {
let cmd_res = Command::new("gnuplot").arg("/tmp/plot.p").output(); let cmd_res = Command::new("gnuplot").arg("/tmp/plot.p").output();
if let Err(err) = cmd_res { if let Err(err) = cmd_res {
match err.kind() { match err.kind() {
std::io::ErrorKind::NotFound => app.msg = StatusMessage::error("Error - Is gnuplot installed?"), std::io::ErrorKind::NotFound => {
app.msg = StatusMessage::error("Error - Is gnuplot installed?")
}
_ => app.msg = StatusMessage::error(format!("{err}")), _ => app.msg = StatusMessage::error(format!("{err}")),
}; };
} else { } else {
@@ -276,7 +279,7 @@ impl Mode {
// 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);
return; return;
} }
// edit cell // edit cell
@@ -319,6 +322,11 @@ impl Mode {
app.mode = Mode::Command(Chord::new(':')) app.mode = Mode::Command(Chord::new(':'))
} }
} }
// undo
'u' => {
app.grid.undo();
}
// paste
'p' => { 'p' => {
app.clipboard.paste(&mut app.grid, true); app.clipboard.paste(&mut app.grid, true);
app.grid.apply_momentum(app.clipboard.momentum()); app.grid.apply_momentum(app.clipboard.momentum());
@@ -508,9 +516,7 @@ pub struct Chord {
impl From<String> for Chord { impl From<String> for Chord {
fn from(value: String) -> Self { fn from(value: String) -> Self {
let b = value.as_bytes().iter().map(|f| *f as char).collect(); let b = value.as_bytes().iter().map(|f| *f as char).collect();
Chord { Chord { buf: b }
buf: b,
}
} }
} }
@@ -519,9 +525,7 @@ impl Chord {
let mut buf = Vec::new(); let mut buf = Vec::new();
buf.push(inital); buf.push(inital);
Self { Self { buf }
buf,
}
} }
pub fn backspace(&mut self) { pub fn backspace(&mut self) {