Compare commits
7 Commits
full-copy-
...
4239844e0e
| Author | SHA1 | Date | |
|---|---|---|---|
| 4239844e0e | |||
| 5f9cd85faf | |||
| 74955032cc | |||
| 7b2eb751ab | |||
| b41a69781c | |||
| f654ce37a6 | |||
| 1825460074 |
@@ -25,6 +25,8 @@ Based loosely off sc-im (spreadsheet calculator improvised), which has dumb keyb
|
|||||||
| `gg` | Go to beginning of the column |
|
| `gg` | Go to beginning of the column |
|
||||||
| `G` | Go to end of column |
|
| `G` | Go to end of column |
|
||||||
| `gG` | Go to end of the the visual column |
|
| `gG` | Go to end of the the visual column |
|
||||||
|
| `u`/`Page Down` | Undo[^undo] |
|
||||||
|
| `Page Up` | Redo[^undo] |
|
||||||
| `i`/`a` | Enter insert mode on current cell |
|
| `i`/`a` | Enter insert mode on current cell |
|
||||||
| `r` | Enter insert mode on current cell, deleting contents |
|
| `r` | Enter insert mode on current cell, deleting contents |
|
||||||
| `v` | Enter visual mode |
|
| `v` | Enter visual mode |
|
||||||
@@ -43,6 +45,8 @@ Based loosely off sc-im (spreadsheet calculator improvised), which has dumb keyb
|
|||||||
| n`G` | Jump to row "n" |
|
| n`G` | Jump to row "n" |
|
||||||
| nX | Press "X", "n" times |
|
| nX | Press "X", "n" times |
|
||||||
|
|
||||||
|
[^undo]: Page up/down keybinds are (probably) temporary until I can get ctrl+r to work for redo, which is the propper key for this action. See issue #25 for the status on this.
|
||||||
|
|
||||||
### Visual mode
|
### Visual mode
|
||||||
|
|
||||||
| Key | Action |
|
| Key | Action |
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ impl Widget for &App {
|
|||||||
fn center_text(text: &str, avaliable_space: i32) -> String {
|
fn center_text(text: &str, avaliable_space: i32) -> String {
|
||||||
let margin = avaliable_space - text.len() as i32;
|
let margin = avaliable_space - text.len() as i32;
|
||||||
let margin = margin / 2;
|
let margin = margin / 2;
|
||||||
let l_margin = (0..margin).into_iter().map(|_| ' ').collect::<String>();
|
let l_margin = (0..margin).map(|_| ' ').collect::<String>();
|
||||||
let r_margin = (0..(margin - (l_margin.len() as i32))).into_iter().map(|_| ' ').collect::<String>();
|
let r_margin = (0..(margin - (l_margin.len() as i32))).map(|_| ' ').collect::<String>();
|
||||||
format!("{l_margin}{text}{r_margin}")
|
format!("{l_margin}{text}{r_margin}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ impl Widget for &App {
|
|||||||
|
|
||||||
let bg = if y_idx == self.grid.cursor().1 {
|
let bg = if y_idx == self.grid.cursor().1 {
|
||||||
Color::DarkGray
|
Color::DarkGray
|
||||||
} else if y_idx % 2 == 0 {
|
} else if y_idx.is_multiple_of(2) {
|
||||||
ORANGE1
|
ORANGE1
|
||||||
} else {
|
} else {
|
||||||
ORANGE2
|
ORANGE2
|
||||||
@@ -127,7 +127,7 @@ impl Widget for &App {
|
|||||||
|
|
||||||
let bg = if x_idx == self.grid.cursor().0 {
|
let bg = if x_idx == self.grid.cursor().0 {
|
||||||
Color::DarkGray
|
Color::DarkGray
|
||||||
} else if x_idx % 2 == 0 {
|
} else if x_idx.is_multiple_of(2) {
|
||||||
ORANGE1
|
ORANGE1
|
||||||
} else {
|
} else {
|
||||||
ORANGE2
|
ORANGE2
|
||||||
@@ -173,7 +173,7 @@ impl Widget for &App {
|
|||||||
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
|
||||||
for i in 1..(display.len() as f32 / cell_width as f32).ceil() as usize {
|
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) {
|
if self.grid.get_cell_raw(x_idx + i, y_idx).is_some() {
|
||||||
suggest_upper_bound = Some(cell_width * i as u16);
|
suggest_upper_bound = Some(cell_width * i as u16);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -222,7 +222,7 @@ impl Widget for &App {
|
|||||||
} else if let Some(suggestion) = suggest_upper_bound {
|
} else if let Some(suggestion) = suggest_upper_bound {
|
||||||
let max_available_width = area.width - x_off;
|
let max_available_width = area.width - x_off;
|
||||||
// draw the biggest cell possible, without going OOB off the screen
|
// draw the biggest cell possible, without going OOB off the screen
|
||||||
let width = min(max_available_width, suggestion as u16);
|
let width = min(max_available_width, suggestion);
|
||||||
// Don't draw too small tho, we want full-sized cells, minium
|
// Don't draw too small tho, we want full-sized cells, minium
|
||||||
let width = max(cell_width, width);
|
let width = max(cell_width, width);
|
||||||
|
|
||||||
@@ -280,7 +280,6 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn file_name_display(&self) -> String {
|
fn file_name_display(&self) -> String {
|
||||||
let file_name_status = {
|
|
||||||
let mut file_name = "[No Name]";
|
let mut file_name = "[No Name]";
|
||||||
let mut icon = "";
|
let mut icon = "";
|
||||||
if let Some(file) = &self.file {
|
if let Some(file) = &self.file {
|
||||||
@@ -294,8 +293,6 @@ impl App {
|
|||||||
icon = "[+]";
|
icon = "[+]";
|
||||||
}
|
}
|
||||||
format!("{file_name}{icon}")
|
format!("{file_name}{icon}")
|
||||||
};
|
|
||||||
file_name_status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, frame: &mut Frame) {
|
fn draw(&self, frame: &mut Frame) {
|
||||||
@@ -326,6 +323,8 @@ impl App {
|
|||||||
let cmd_line_left = cmd_line_split[0];
|
let cmd_line_left = cmd_line_split[0];
|
||||||
let cmd_line_status = cmd_line_split[1];
|
let cmd_line_status = cmd_line_split[1];
|
||||||
let cmd_line_right = cmd_line_split[2];
|
let cmd_line_right = cmd_line_split[2];
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
let cmd_line_debug = cmd_line_split[3];
|
let cmd_line_debug = cmd_line_split[3];
|
||||||
// ======================================================
|
// ======================================================
|
||||||
|
|
||||||
@@ -360,7 +359,7 @@ impl App {
|
|||||||
event::KeyCode::Char(c) => chord.add_char(c),
|
event::KeyCode::Char(c) => chord.add_char(c),
|
||||||
event::KeyCode::Enter => {
|
event::KeyCode::Enter => {
|
||||||
// tmp is to get around reference issues.
|
// tmp is to get around reference issues.
|
||||||
let tmp = pos.clone();
|
let tmp = *pos;
|
||||||
Mode::process_cmd(self);
|
Mode::process_cmd(self);
|
||||||
self.mode = Mode::Visual(tmp)
|
self.mode = Mode::Visual(tmp)
|
||||||
}
|
}
|
||||||
@@ -419,7 +418,7 @@ 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(n) => {},
|
event::KeyCode::F(_n) => {}
|
||||||
event::KeyCode::Char(c) => Mode::process_key(self, c),
|
event::KeyCode::Char(c) => Mode::process_key(self, c),
|
||||||
// Pretend that the arrow keys are vim movement keys
|
// Pretend that the arrow keys are vim movement keys
|
||||||
event::KeyCode::Left => Mode::process_key(self, 'h'),
|
event::KeyCode::Left => Mode::process_key(self, 'h'),
|
||||||
@@ -430,12 +429,14 @@ impl App {
|
|||||||
event::KeyCode::PageUp => self.grid.redo(),
|
event::KeyCode::PageUp => self.grid.redo(),
|
||||||
event::KeyCode::PageDown => self.grid.undo(),
|
event::KeyCode::PageDown => self.grid.undo(),
|
||||||
event::KeyCode::Modifier(modifier_key_code) => {
|
event::KeyCode::Modifier(modifier_key_code) => {
|
||||||
if let event::ModifierKeyCode::LeftControl | event::ModifierKeyCode::RightControl = 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
|
// TODO my terminal (alacritty) isn't showing me ctrl presses. I know
|
||||||
// that they work tho, since ctrl+r works here in neovim.
|
// that they work tho, since ctrl+r works here in neovim.
|
||||||
// panic!("heard ctrl");
|
// panic!("heard ctrl");
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ impl Widget for &StatusMessage {
|
|||||||
let msg = if self.start.elapsed().as_secs() > 3 {
|
let msg = if self.start.elapsed().as_secs() > 3 {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
self.msg.clone().unwrap_or(String::new())
|
self.msg.clone().unwrap_or_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let style = match self.msg_type {
|
let style = match self.msg_type {
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ use crate::app::app::App;
|
|||||||
use crate::app::mode::Mode;
|
use crate::app::mode::Mode;
|
||||||
|
|
||||||
pub fn get_header_size() -> usize {
|
pub fn get_header_size() -> usize {
|
||||||
let row_header_width = LEN.to_string().len();
|
LEN.to_string().len()
|
||||||
row_header_width
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LEN: usize = 1001;
|
pub const LEN: usize = 1001;
|
||||||
@@ -77,6 +76,7 @@ mod internal {
|
|||||||
}
|
}
|
||||||
&self.cells[x][y]
|
&self.cells[x][y]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cell_raw<T: Into<CellType>>(&mut self, (x, y): (usize, usize), val: Option<T>) {
|
pub fn set_cell_raw<T: Into<CellType>>(&mut self, (x, y): (usize, usize), val: Option<T>) {
|
||||||
// TODO check oob
|
// TODO check oob
|
||||||
self.cells[x][y] = val.map(|v| v.into());
|
self.cells[x][y] = val.map(|v| v.into());
|
||||||
@@ -215,7 +215,7 @@ impl Grid {
|
|||||||
if let Ok(val) = self.evaluate(&cell.to_string())
|
if let Ok(val) = self.evaluate(&cell.to_string())
|
||||||
&& resolve_values
|
&& resolve_values
|
||||||
{
|
{
|
||||||
format!("{}{}", val.to_string(), delim)
|
format!("{val}{delim}")
|
||||||
} else {
|
} else {
|
||||||
format!("{}{}", cell.escaped_csv_string(), delim)
|
format!("{}{}", cell.escaped_csv_string(), delim)
|
||||||
}
|
}
|
||||||
@@ -231,24 +231,21 @@ impl Grid {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_grid<'a>(&'a self) -> &'a CellGrid {
|
pub fn get_grid(&self) -> &CellGrid {
|
||||||
&self.grid_history[self.current_grid]
|
&self.grid_history[self.current_grid]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_grid_mut<'a>(&'a mut self) -> &'a mut CellGrid {
|
|
||||||
&mut self.grid_history[self.current_grid]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn undo(&mut self) {
|
pub fn undo(&mut self) {
|
||||||
self.current_grid = self.current_grid.saturating_sub(1);
|
self.current_grid = self.current_grid.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redo(&mut self) {
|
pub fn redo(&mut self) {
|
||||||
self.current_grid = min(self.grid_history.len() - 1, self.current_grid + 1);
|
self.current_grid = min(self.grid_history.len() - 1, self.current_grid + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub 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),
|
||||||
{
|
{
|
||||||
// push on a new reality
|
// push on a new reality
|
||||||
let new = self.get_grid().clone();
|
let new = self.get_grid().clone();
|
||||||
@@ -379,8 +376,15 @@ impl Grid {
|
|||||||
f.custom_translate_cell((0, 0), (0, 1), |rolling, old, new| {
|
f.custom_translate_cell((0, 0), (0, 1), |rolling, old, new| {
|
||||||
if let Some((_, arg_y)) = Grid::parse_to_idx(old) {
|
if let Some((_, arg_y)) = Grid::parse_to_idx(old) {
|
||||||
if arg_y < insertion_y { rolling.to_owned() } else { rolling.replace(old, new) }
|
if arg_y < insertion_y { rolling.to_owned() } else { rolling.replace(old, new) }
|
||||||
|
} else if Grid::range_as_indices(old).is_some() {
|
||||||
|
// ranges are not changed when moved vertically
|
||||||
|
rolling.to_string()
|
||||||
} else {
|
} else {
|
||||||
unimplemented!("Invalid variable wanted to be translated")
|
#[cfg(debug_assertions)]
|
||||||
|
unimplemented!("Invalid variable wanted to be translated");
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
rolling.to_string()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}) {
|
}) {
|
||||||
@@ -405,8 +409,26 @@ impl Grid {
|
|||||||
if let Some((arg_x, _)) = Grid::parse_to_idx(old) {
|
if let Some((arg_x, _)) = Grid::parse_to_idx(old) {
|
||||||
// add 1 because of the insertion
|
// add 1 because of the insertion
|
||||||
if arg_x < insertion_x { rolling.to_owned() } else { rolling.replace(old, new) }
|
if arg_x < insertion_x { rolling.to_owned() } else { rolling.replace(old, new) }
|
||||||
|
} else if let Some((start, end)) = Grid::range_as_indices(old) {
|
||||||
|
let mut range_start = Grid::num_to_char(start);
|
||||||
|
let mut range_end = Grid::num_to_char(end);
|
||||||
|
|
||||||
|
if start >= insertion_x {
|
||||||
|
range_start = Grid::num_to_char(start+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if end >= insertion_x {
|
||||||
|
range_end = Grid::num_to_char(end+1);
|
||||||
|
}
|
||||||
|
let new = format!("{range_start}:{range_end}");
|
||||||
|
|
||||||
|
rolling.replace(old, &new)
|
||||||
} else {
|
} else {
|
||||||
unimplemented!("Invalid variable wanted to be translated")
|
#[cfg(debug_assertions)]
|
||||||
|
unimplemented!("Invalid variable wanted to be translated");
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
rolling.to_string()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}) {
|
}) {
|
||||||
@@ -431,7 +453,7 @@ impl Grid {
|
|||||||
return Err(format!("\"{eq}\" is not an equation"));
|
return Err(format!("\"{eq}\" is not an equation"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = ctx::CallbackContext::new(&self);
|
let ctx = ctx::CallbackContext::new(self);
|
||||||
|
|
||||||
let prep_for_return = |v: Value| {
|
let prep_for_return = |v: Value| {
|
||||||
if v.is_number() {
|
if v.is_number() {
|
||||||
@@ -467,14 +489,27 @@ impl Grid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn range_as_indices(range: &str) -> Option<(usize, usize)> {
|
||||||
|
let v = range.split(':').collect::<Vec<&str>>();
|
||||||
|
if v.len() == 2 {
|
||||||
|
let start_col = v[0];
|
||||||
|
let end_col = v[1];
|
||||||
|
|
||||||
|
let start_idx = Grid::char_to_idx(start_col);
|
||||||
|
let end_idx = Grid::char_to_idx(end_col);
|
||||||
|
|
||||||
|
return Some((start_idx, end_idx));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn char_to_idx(i: &str) -> usize {
|
pub fn char_to_idx(i: &str) -> usize {
|
||||||
let x_idx = i
|
i
|
||||||
.chars()
|
.chars()
|
||||||
.filter(|f| f.is_alphabetic())
|
.filter(|f| f.is_alphabetic())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, c)| ((c.to_ascii_lowercase() as usize).saturating_sub(97)) + (26 * idx))
|
.map(|(idx, c)| ((c.to_ascii_lowercase() as usize).saturating_sub(97)) + (26 * idx))
|
||||||
.fold(0, |a, b| a + b);
|
.sum()
|
||||||
x_idx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse values in the format of A0, C10 ZZ99, etc, and
|
/// Parse values in the format of A0, C10 ZZ99, etc, and
|
||||||
@@ -503,12 +538,11 @@ impl Grid {
|
|||||||
|
|
||||||
/// Helper for tests
|
/// Helper for tests
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
/// Don't ever remove this from being just a test-helper.
|
pub fn set_cell<T: Into<CellType> + Clone>(&mut self, cell_id: &str, val: T) {
|
||||||
/// This function doesn't correctly use the undo/redo api, which would require doing
|
|
||||||
/// transactions on the grid instead of direct access.
|
|
||||||
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.get_grid_mut().set_cell_raw(loc, Some(val))
|
self.transact_on_grid(|grid| {
|
||||||
|
grid.set_cell_raw(loc, Some(val.clone()));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
@@ -528,7 +562,7 @@ impl Grid {
|
|||||||
if x >= LEN || y >= LEN {
|
if x >= LEN || y >= LEN {
|
||||||
return &None;
|
return &None;
|
||||||
}
|
}
|
||||||
&self.get_grid().get_cell_raw(x, y)
|
self.get_grid().get_cell_raw(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn num_to_char(idx: usize) -> String {
|
pub fn num_to_char(idx: usize) -> String {
|
||||||
@@ -545,7 +579,7 @@ impl Grid {
|
|||||||
}
|
}
|
||||||
word[1] = ((idx % 26) + 65) as u8 as char;
|
word[1] = ((idx % 26) + 65) as u8 as char;
|
||||||
|
|
||||||
word.iter().collect()
|
word.iter().filter(|a| !a.is_ascii_whitespace()).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,8 +705,8 @@ fn alphanumeric_indexing() {
|
|||||||
assert_eq!(Grid::parse_to_idx("="), None);
|
assert_eq!(Grid::parse_to_idx("="), None);
|
||||||
assert_eq!(Grid::parse_to_idx("A:A"), None);
|
assert_eq!(Grid::parse_to_idx("A:A"), None);
|
||||||
|
|
||||||
assert_eq!(Grid::num_to_char(0).trim(), "A");
|
assert_eq!(Grid::num_to_char(0), "A");
|
||||||
assert_eq!(Grid::num_to_char(25).trim(), "Z");
|
assert_eq!(Grid::num_to_char(25), "Z");
|
||||||
assert_eq!(Grid::num_to_char(26), "AA");
|
assert_eq!(Grid::num_to_char(26), "AA");
|
||||||
assert_eq!(Grid::num_to_char(51), "AZ");
|
assert_eq!(Grid::num_to_char(51), "AZ");
|
||||||
assert_eq!(Grid::num_to_char(701), "ZZ");
|
assert_eq!(Grid::num_to_char(701), "ZZ");
|
||||||
@@ -1078,6 +1112,66 @@ fn insert_col_before_3() {
|
|||||||
assert_eq!(cell.to_string(), "=B0*B1");
|
assert_eq!(cell.to_string(), "=B0*B1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_col_before_static_range() {
|
||||||
|
let mut grid = Grid::new();
|
||||||
|
|
||||||
|
grid.set_cell("A0", 2.);
|
||||||
|
grid.set_cell("A1", 2.);
|
||||||
|
grid.set_cell("B0", "=sum(A:A)".to_string());
|
||||||
|
|
||||||
|
grid.mv_cursor_to(1, 0);
|
||||||
|
grid.insert_column_before(grid.cursor());
|
||||||
|
|
||||||
|
let cell = grid.get_cell("C0").as_ref().expect("Just set it");
|
||||||
|
assert_eq!(cell.to_string(), "=sum(A:A)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_col_before_move_range() {
|
||||||
|
let mut grid = Grid::new();
|
||||||
|
|
||||||
|
grid.set_cell("B0", 2.);
|
||||||
|
grid.set_cell("B1", 2.);
|
||||||
|
grid.set_cell("A0", "=sum(B:B)".to_string());
|
||||||
|
|
||||||
|
grid.mv_cursor_to(0, 0);
|
||||||
|
grid.insert_column_after(grid.cursor());
|
||||||
|
|
||||||
|
let cell = grid.get_cell("A0").as_ref().expect("Just set it");
|
||||||
|
assert_eq!(cell.to_string(), "=sum(C:C)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_row_before_static_range() {
|
||||||
|
let mut grid = Grid::new();
|
||||||
|
|
||||||
|
grid.set_cell("A0", 2.);
|
||||||
|
grid.set_cell("A1", 2.);
|
||||||
|
grid.set_cell("B0", "=sum(A:A)".to_string());
|
||||||
|
|
||||||
|
grid.mv_cursor_to(0, 0);
|
||||||
|
grid.insert_row_above(grid.cursor());
|
||||||
|
|
||||||
|
let cell = grid.get_cell("B1").as_ref().expect("Just set it");
|
||||||
|
assert_eq!(cell.to_string(), "=sum(A:A)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_row_before_move_range() {
|
||||||
|
let mut grid = Grid::new();
|
||||||
|
|
||||||
|
grid.set_cell("A0", 2.);
|
||||||
|
grid.set_cell("A1", 2.);
|
||||||
|
grid.set_cell("B0", "=sum(A:A)".to_string());
|
||||||
|
|
||||||
|
grid.mv_cursor_to(0, 0);
|
||||||
|
grid.insert_row_below(grid.cursor());
|
||||||
|
|
||||||
|
let cell = grid.get_cell("B0").as_ref().expect("Just set it");
|
||||||
|
assert_eq!(cell.to_string(), "=sum(A:A)");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_row_above_1() {
|
fn insert_row_above_1() {
|
||||||
let mut grid = Grid::new();
|
let mut grid = Grid::new();
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ pub enum CellType {
|
|||||||
Equation(String),
|
Equation(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<CellType> for f64 {
|
impl From<f64> for CellType {
|
||||||
fn into(self) -> CellType {
|
fn from(value: f64) -> Self {
|
||||||
CellType::duck_type(self.to_string())
|
CellType::duck_type(value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<CellType> for String {
|
impl From<String> for CellType {
|
||||||
fn into(self) -> CellType {
|
fn from(value: String) -> Self {
|
||||||
CellType::duck_type(self)
|
CellType::duck_type(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,14 +37,10 @@ impl CellType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// escape string of it has a comma
|
// escape string of it has a comma
|
||||||
if display.contains(CSV_DELIMITER) {
|
if display.contains(CSV_DELIMITER) { format!("\"{display}\"") } else { display }
|
||||||
format!("\"{display}\"")
|
|
||||||
} else {
|
|
||||||
display
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn duck_type<'a>(value: impl Into<String>) -> Self {
|
fn duck_type(value: impl Into<String>) -> Self {
|
||||||
let value = value.into();
|
let value = value.into();
|
||||||
|
|
||||||
if let Ok(parse) = value.parse::<f64>() {
|
if let Ok(parse) = value.parse::<f64>() {
|
||||||
@@ -56,7 +52,12 @@ impl CellType {
|
|||||||
|
|
||||||
/// `replace_fn` takes the string, the old value, and then the new value.
|
/// `replace_fn` takes the string, the old value, and then the new value.
|
||||||
/// It can be thought of as `echo $1 | sed s/$2/$3/g`
|
/// It can be thought of as `echo $1 | sed s/$2/$3/g`
|
||||||
pub fn custom_translate_cell(&self, from: (usize, usize), to: (usize, usize), replace_fn: impl Fn(&str, &str, &str) -> String) -> CellType {
|
pub fn custom_translate_cell(
|
||||||
|
&self,
|
||||||
|
from: (usize, usize),
|
||||||
|
to: (usize, usize),
|
||||||
|
replace_fn: impl Fn(&str, &str, &str) -> String,
|
||||||
|
) -> CellType {
|
||||||
match self {
|
match self {
|
||||||
// don't translate non-equations
|
// don't translate non-equations
|
||||||
CellType::Number(_) | CellType::String(_) => return self.clone(),
|
CellType::Number(_) | CellType::String(_) => return self.clone(),
|
||||||
@@ -73,10 +74,8 @@ impl CellType {
|
|||||||
let mut lock_y = false;
|
let mut lock_y = false;
|
||||||
|
|
||||||
if old_var.contains('$') {
|
if old_var.contains('$') {
|
||||||
let locations = old_var
|
let locations =
|
||||||
.char_indices()
|
old_var.char_indices().filter(|(_, c)| *c == '$').map(|(i, _)| i).collect::<Vec<usize>>();
|
||||||
.filter(|(_, c)| *c == '$').map(|(i, _)| i)
|
|
||||||
.collect::<Vec<usize>>();
|
|
||||||
match locations.len() {
|
match locations.len() {
|
||||||
1 => {
|
1 => {
|
||||||
if locations[0] == 0 {
|
if locations[0] == 0 {
|
||||||
@@ -110,20 +109,11 @@ impl CellType {
|
|||||||
let x2 = x2 as i32;
|
let x2 = x2 as i32;
|
||||||
let y2 = y2 as i32;
|
let y2 = y2 as i32;
|
||||||
|
|
||||||
let dest_x = if lock_x {
|
let dest_x = if lock_x { src_x } else { (src_x as i32 + (x2 - x1)) as usize };
|
||||||
src_x as usize
|
|
||||||
} else {
|
|
||||||
(src_x as i32 + (x2 - x1)) as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
let dest_y = if lock_y {
|
let dest_y = if lock_y { src_y } else { (src_y as i32 + (y2 - y1)) as usize };
|
||||||
src_y as usize
|
|
||||||
} else {
|
|
||||||
(src_y as i32 + (y2 - y1)) as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
let alpha = Grid::num_to_char(dest_x);
|
let alpha = Grid::num_to_char(dest_x);
|
||||||
let alpha = alpha.trim();
|
|
||||||
|
|
||||||
// Persist the "$" locking
|
// Persist the "$" locking
|
||||||
let new_var = if lock_x {
|
let new_var = if lock_x {
|
||||||
@@ -141,19 +131,12 @@ impl CellType {
|
|||||||
// why you coping invalid stuff, nerd?
|
// why you coping invalid stuff, nerd?
|
||||||
//
|
//
|
||||||
// could be copying a range
|
// could be copying a range
|
||||||
if old_var.contains(':') {
|
if let Some(parts) = Grid::range_as_indices(&old_var) {
|
||||||
let parts = old_var.split(':').collect::<Vec<&str>>();
|
|
||||||
// This means the var was formatted as X:X
|
|
||||||
if parts.len() == 2 {
|
|
||||||
// how far is the movement?
|
// how far is the movement?
|
||||||
let dx = to.0 as i32 - from.0 as i32;
|
let dx = to.0 as i32 - from.0 as i32;
|
||||||
|
|
||||||
let range_start = parts[0];
|
let xs = parts.0 as i32;
|
||||||
let range_end = parts[1];
|
let xe = parts.1 as i32;
|
||||||
|
|
||||||
// get the letters as numbers
|
|
||||||
let xs = Grid::char_to_idx(range_start) as i32;
|
|
||||||
let xe = Grid::char_to_idx(range_end) as i32;
|
|
||||||
|
|
||||||
// apply movement
|
// apply movement
|
||||||
let mut new_range_start = xs + dx;
|
let mut new_range_start = xs + dx;
|
||||||
@@ -170,8 +153,7 @@ impl CellType {
|
|||||||
// convert the index back into a letter and then submit it
|
// convert the index back into a letter and then submit it
|
||||||
let start = Grid::num_to_char(new_range_start as usize);
|
let start = Grid::num_to_char(new_range_start as usize);
|
||||||
let end = Grid::num_to_char(new_range_end as usize);
|
let end = Grid::num_to_char(new_range_end as usize);
|
||||||
equation = replace_fn(&equation, &old_var, &format!("{}:{}", start.trim(), end.trim()));
|
equation = replace_fn(&equation, &old_var, &format!("{start}:{end}"));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,4 +188,3 @@ impl PartialEq for CellType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,26 +16,12 @@ pub struct CallbackContext<'a> {
|
|||||||
|
|
||||||
impl<'a> CallbackContext<'a> {
|
impl<'a> CallbackContext<'a> {
|
||||||
fn expand_range(&self, range: &str) -> Option<Vec<&CellType>> {
|
fn expand_range(&self, range: &str) -> Option<Vec<&CellType>> {
|
||||||
let v = range.split(':').collect::<Vec<&str>>();
|
if let Some((start, end)) = Grid::range_as_indices(range) {
|
||||||
if v.len() == 2 {
|
|
||||||
let start_col = v[0];
|
|
||||||
let end_col = v[1];
|
|
||||||
|
|
||||||
let as_index = |s: &str| {
|
|
||||||
s.char_indices()
|
|
||||||
// .filter(|f| f.1 as u8 >= 97) // prevent sub with overflow errors
|
|
||||||
.map(|(idx, c)| ((c.to_ascii_lowercase() as usize).saturating_sub(97)) + (26 * idx))
|
|
||||||
.fold(0, |a, b| a + b)
|
|
||||||
};
|
|
||||||
|
|
||||||
let start_idx = as_index(start_col);
|
|
||||||
let end_idx = as_index(end_col);
|
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
for x in start_idx..=end_idx {
|
for x in start..=end {
|
||||||
for y in 0..=self.variables.get_grid().max_y_at_x(x) {
|
for y in 0..=self.variables.get_grid().max_y_at_x(x) {
|
||||||
if let Some(s) = self.variables.get_grid().get_cell_raw(x, y) {
|
if let Some(s) = self.variables.get_cell_raw(x, y) {
|
||||||
buf.push(s);
|
buf.push(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ impl Mode {
|
|||||||
path.file_name().map(|f| f.to_str().unwrap_or("n/a")).unwrap_or("n/a")
|
path.file_name().map(|f| f.to_str().unwrap_or("n/a")).unwrap_or("n/a")
|
||||||
));
|
));
|
||||||
|
|
||||||
if let None = app.file {
|
if app.file.is_none() {
|
||||||
app.file = Some(path)
|
app.file = Some(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ impl Mode {
|
|||||||
}
|
}
|
||||||
"export" => {
|
"export" => {
|
||||||
if let Some(arg1) = args.get(1) {
|
if let Some(arg1) = args.get(1) {
|
||||||
save_range(&arg1);
|
save_range(arg1);
|
||||||
} else {
|
} else {
|
||||||
app.msg = StatusMessage::error("export <path.csv>")
|
app.msg = StatusMessage::error("export <path.csv>")
|
||||||
}
|
}
|
||||||
@@ -286,7 +286,7 @@ impl Mode {
|
|||||||
'i' | 'a' => {
|
'i' | 'a' => {
|
||||||
let (x, y) = app.grid.cursor();
|
let (x, y) = app.grid.cursor();
|
||||||
|
|
||||||
let val = app.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string()).unwrap_or(String::new());
|
let val = app.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string()).unwrap_or_default();
|
||||||
|
|
||||||
app.mode = Mode::Insert(Chord::from(val));
|
app.mode = Mode::Insert(Chord::from(val));
|
||||||
}
|
}
|
||||||
@@ -477,14 +477,12 @@ impl Mode {
|
|||||||
let len = match &self {
|
let len = match &self {
|
||||||
Mode::Insert(edit) | Mode::VisualCmd(_, edit) | Mode::Command(edit) | Mode::Chord(edit) => edit.len(),
|
Mode::Insert(edit) | Mode::VisualCmd(_, edit) | Mode::Command(edit) | Mode::Chord(edit) => edit.len(),
|
||||||
Mode::Normal => {
|
Mode::Normal => {
|
||||||
let len = cell.as_ref().map(|f| f.to_string().len()).unwrap_or_default();
|
cell.as_ref().map(|f| f.to_string().len()).unwrap_or_default()
|
||||||
len
|
|
||||||
}
|
}
|
||||||
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);
|
max(len as u16 + 1, 20)
|
||||||
len
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, f: &mut ratatui::Frame, area: prelude::Rect, cell: &Option<CellType>) {
|
pub fn render(&self, f: &mut ratatui::Frame, area: prelude::Rect, cell: &Option<CellType>) {
|
||||||
@@ -498,8 +496,7 @@ impl Mode {
|
|||||||
Mode::Chord(chord) => f.render_widget(chord, area),
|
Mode::Chord(chord) => f.render_widget(chord, area),
|
||||||
Mode::Normal => f.render_widget(
|
Mode::Normal => f.render_widget(
|
||||||
Paragraph::new({
|
Paragraph::new({
|
||||||
let cell = cell.as_ref().map(|f| f.to_string()).unwrap_or_default();
|
cell.as_ref().map(|f| f.to_string()).unwrap_or_default()
|
||||||
cell
|
|
||||||
}),
|
}),
|
||||||
area,
|
area,
|
||||||
),
|
),
|
||||||
@@ -522,8 +519,7 @@ impl From<String> for Chord {
|
|||||||
|
|
||||||
impl Chord {
|
impl Chord {
|
||||||
pub fn new(inital: char) -> Self {
|
pub fn new(inital: char) -> Self {
|
||||||
let mut buf = Vec::new();
|
let buf = vec![inital];
|
||||||
buf.push(inital);
|
|
||||||
|
|
||||||
Self { buf }
|
Self { buf }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ impl ScreenSpace {
|
|||||||
// ======= X =======
|
// ======= X =======
|
||||||
// screen seems to be 2 cells smaller than it should be
|
// screen seems to be 2 cells smaller than it should be
|
||||||
// this is probably related to issue #6
|
// this is probably related to issue #6
|
||||||
let x_cells = (screen_size.0 / self.get_cell_width(vars) as usize) -2;
|
let x_cells = (screen_size.0 / self.get_cell_width(vars)) -2;
|
||||||
// ======= Y =======
|
// ======= Y =======
|
||||||
// screen seems to be 2 cells smaller than it should be
|
// screen seems to be 2 cells smaller than it should be
|
||||||
// this is probably related to issue #6
|
// this is probably related to issue #6
|
||||||
let y_cells = (screen_size.1 / self.get_cell_height(vars) as usize) -2;
|
let y_cells = (screen_size.1 / self.get_cell_height(vars)) -2;
|
||||||
(x_cells,y_cells)
|
(x_cells,y_cells)
|
||||||
} else {
|
} else {
|
||||||
(0,0)
|
(0,0)
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
#![allow(clippy::needless_return)]
|
||||||
|
#![allow(clippy::len_zero)]
|
||||||
|
#![allow(clippy::collapsible_if)]
|
||||||
|
#![allow(clippy::collapsible_else_if)]
|
||||||
|
#![allow(clippy::collapsible_match)]
|
||||||
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
|
|
||||||
use std::env::args;
|
use std::env::args;
|
||||||
|
|||||||
Reference in New Issue
Block a user