@@ -1,5 +1,9 @@
|
||||
use std::{
|
||||
cmp::{max, min}, collections::HashMap, fs, io, path::PathBuf, time::SystemTime
|
||||
cmp::{max, min},
|
||||
collections::HashMap,
|
||||
fs, io,
|
||||
path::PathBuf,
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use ratatui::{
|
||||
@@ -315,6 +319,21 @@ impl App {
|
||||
|
||||
fn handle_events(&mut self) -> io::Result<()> {
|
||||
match &mut self.mode {
|
||||
Mode::VisualCmd(pos, chord) => match event::read()? {
|
||||
event::Event::Key(key) => match key.code {
|
||||
event::KeyCode::Esc => self.mode = Mode::Visual(*pos),
|
||||
event::KeyCode::Backspace => chord.backspace(),
|
||||
event::KeyCode::Char(c) => chord.add_char(c),
|
||||
event::KeyCode::Enter => {
|
||||
// tmp is to get around reference issues.
|
||||
let tmp = pos.clone();
|
||||
Mode::process_cmd(self);
|
||||
self.mode = Mode::Visual(tmp)
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
Mode::Chord(chord) => match event::read()? {
|
||||
event::Event::Key(key) => match key.code {
|
||||
event::KeyCode::Esc => self.mode = Mode::Normal,
|
||||
@@ -384,20 +403,13 @@ impl App {
|
||||
}
|
||||
Mode::Command(editor) => match event::read()? {
|
||||
event::Event::Key(key) => match key.code {
|
||||
event::KeyCode::Esc => {
|
||||
// just cancel the operation
|
||||
self.mode = Mode::Normal;
|
||||
}
|
||||
event::KeyCode::Esc => self.mode = Mode::Normal,
|
||||
event::KeyCode::Backspace => editor.backspace(),
|
||||
event::KeyCode::Char(c) => editor.add_char(c),
|
||||
event::KeyCode::Enter => {
|
||||
Mode::process_cmd(self);
|
||||
self.mode = Mode::Normal;
|
||||
}
|
||||
event::KeyCode::Backspace => {
|
||||
editor.backspace();
|
||||
}
|
||||
event::KeyCode::Char(c) => {
|
||||
editor.add_char(c);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::app::{
|
||||
logic::{
|
||||
cell::{CSV_DELIMITER, CellType},
|
||||
ctx,
|
||||
},
|
||||
}, mode::Mode,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -110,21 +110,28 @@ impl Grid {
|
||||
for x in 0..=mx {
|
||||
let cell = &self.cells[x][y];
|
||||
|
||||
// newline after the cell, because it's end of line.
|
||||
// else, just put a comma after the cell.
|
||||
let is_last = x==mx;
|
||||
let delim = if is_last {
|
||||
'\n'
|
||||
} else {
|
||||
CSV_DELIMITER
|
||||
};
|
||||
|
||||
let data = if let Some(cell) = cell {
|
||||
if let Ok(val) = self.evaluate(&cell.to_string())
|
||||
&& resolve_values
|
||||
{
|
||||
val.to_string()
|
||||
format!("{}{}", val.to_string(), delim)
|
||||
} else {
|
||||
cell.escaped_csv_string()
|
||||
format!("{}{}", cell.escaped_csv_string(), delim)
|
||||
}
|
||||
} else {
|
||||
CSV_DELIMITER.to_string()
|
||||
delim.to_string()
|
||||
};
|
||||
|
||||
write!(f, "{data}")?;
|
||||
}
|
||||
write!(f, "\n")?;
|
||||
}
|
||||
f.flush()?;
|
||||
|
||||
@@ -459,6 +466,92 @@ impl Default for Grid {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saving_csv() {
|
||||
// setup grid
|
||||
// This should be 1..10 in the A column, then
|
||||
// 1^2..10^2 in the B column.
|
||||
let mut app = App::new();
|
||||
app.grid.set_cell("A0", 1.);
|
||||
app.grid.set_cell("B0", "=A0^2".to_string());
|
||||
|
||||
app.grid.set_cell("A1", "=A0+A$0".to_string());
|
||||
app.grid.set_cell("B1", "=A1^2".to_string());
|
||||
|
||||
app.grid.mv_cursor_to(0, 1);
|
||||
app.mode = Mode::Visual(app.grid.cursor());
|
||||
Mode::process_key(&mut app, 'l');
|
||||
Mode::process_key(&mut app, 'y');
|
||||
app.mode = Mode::Normal;
|
||||
app.grid.mv_cursor_to(0, 2);
|
||||
for _ in 0..10 {
|
||||
Mode::process_key(&mut app, 'p');
|
||||
}
|
||||
// setup done
|
||||
|
||||
// insure that the cells are there
|
||||
let cell = app.grid.get_cell_raw(0, 10).as_ref().expect("Should've been set");
|
||||
let res = app.grid.evaluate(&cell.to_string()).expect("Should evaluate");
|
||||
assert_eq!(res, 11.0);
|
||||
assert_eq!(cell.escaped_csv_string(), "=A9+A$0");
|
||||
let cell = app.grid.get_cell_raw(1, 10).as_ref().expect("Should've been set");
|
||||
let res = app.grid.evaluate(&cell.to_string()).expect("Should evaluate");
|
||||
assert_eq!(res, 121.0);
|
||||
assert_eq!(cell.escaped_csv_string(), "=A10^2");
|
||||
|
||||
// set saving the file
|
||||
let filename = "/tmp/file.csv";
|
||||
app.grid.save_to(filename).expect("This will only work on linux systems");
|
||||
let mut file = fs::OpenOptions::new().read(true).open(filename).expect("Just wrote the file");
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf).expect("Just opened the file");
|
||||
let line = buf.lines().skip(10).next();
|
||||
assert_eq!(line, Some("11,121"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saving_neoscim() {
|
||||
// setup grid
|
||||
// This should be 1..10 in the A column, then
|
||||
// 1^2..10^2 in the B column.
|
||||
let mut app = App::new();
|
||||
app.grid.set_cell("A0", 1.);
|
||||
app.grid.set_cell("B0", "=A0^2".to_string());
|
||||
|
||||
app.grid.set_cell("A1", "=A0+A$0".to_string());
|
||||
app.grid.set_cell("B1", "=A1^2".to_string());
|
||||
|
||||
app.grid.mv_cursor_to(0, 1);
|
||||
app.mode = Mode::Visual(app.grid.cursor());
|
||||
Mode::process_key(&mut app, 'l');
|
||||
Mode::process_key(&mut app, 'y');
|
||||
app.mode = Mode::Normal;
|
||||
app.grid.mv_cursor_to(0, 2);
|
||||
for _ in 0..10 {
|
||||
Mode::process_key(&mut app, 'p');
|
||||
}
|
||||
// setup done
|
||||
|
||||
// insure that the cells are there
|
||||
let cell = app.grid.get_cell_raw(0, 10).as_ref().expect("Should've been set");
|
||||
let res = app.grid.evaluate(&cell.to_string()).expect("Should evaluate");
|
||||
assert_eq!(res, 11.0);
|
||||
assert_eq!(cell.escaped_csv_string(), "=A9+A$0");
|
||||
let cell = app.grid.get_cell_raw(1, 10).as_ref().expect("Should've been set");
|
||||
let res = app.grid.evaluate(&cell.to_string()).expect("Should evaluate");
|
||||
assert_eq!(res, 121.0);
|
||||
assert_eq!(cell.escaped_csv_string(), "=A10^2");
|
||||
|
||||
// set saving the file
|
||||
let filename= "/tmp/file.neoscim";
|
||||
app.grid.save_to(filename).expect("This will only work on linux systems");
|
||||
let mut file = fs::OpenOptions::new().read(true).open(filename).expect("Just wrote the file");
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf).expect("Just opened the file");
|
||||
let line = buf.lines().skip(10).next();
|
||||
assert_eq!(line, Some("=A9+A$0,=A10^2"));
|
||||
}
|
||||
|
||||
// Do cells hold strings?
|
||||
#[test]
|
||||
fn cell_strings() {
|
||||
|
||||
@@ -38,9 +38,9 @@ impl CellType {
|
||||
|
||||
// escape string of it has a comma
|
||||
if display.contains(CSV_DELIMITER) {
|
||||
format!("\"{display}\"{CSV_DELIMITER}")
|
||||
format!("\"{display}\"")
|
||||
} else {
|
||||
format!("{display}{CSV_DELIMITER}")
|
||||
display
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ pub enum Mode {
|
||||
Normal,
|
||||
Command(Chord),
|
||||
Visual((usize, usize)),
|
||||
VisualCmd((usize, usize), Chord),
|
||||
}
|
||||
|
||||
impl Display for Mode {
|
||||
@@ -33,6 +34,7 @@ impl Display for Mode {
|
||||
Mode::Chord(_) => write!(f, "CHORD"),
|
||||
Mode::Command(_) => write!(f, "COMMAND"),
|
||||
Mode::Visual(_) => write!(f, "VISUAL"),
|
||||
Mode::VisualCmd(_, _) => write!(f, "V-CMD"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +45,7 @@ impl Mode {
|
||||
// Where you are typing
|
||||
Mode::Insert(_) => Style::new().fg(Color::White).bg(Color::Blue),
|
||||
Mode::Command(_) => Style::new().fg(Color::Black).bg(Color::Magenta),
|
||||
Mode::VisualCmd(_, _) => Style::new().fg(Color::Black).bg(Color::Yellow),
|
||||
Mode::Chord(_) => Style::new().fg(Color::Black).bg(Color::LightBlue),
|
||||
// Movement-based modes
|
||||
Mode::Visual(_) => Style::new().fg(Color::Yellow),
|
||||
@@ -143,6 +146,17 @@ impl Mode {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Mode::VisualCmd(pos, editor ) = &mut app.mode {
|
||||
let cmd = &editor.as_string()[1..];
|
||||
let args = cmd.split_ascii_whitespace().collect::<Vec<&str>>();
|
||||
if args.is_empty() {
|
||||
return;
|
||||
}
|
||||
match args[0] {
|
||||
"foo" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_key(app: &mut App, key: char) {
|
||||
@@ -211,7 +225,13 @@ impl Mode {
|
||||
app.grid.insert_row_above(app.grid.cursor());
|
||||
}
|
||||
'v' => app.mode = Mode::Visual(app.grid.cursor()),
|
||||
':' => app.mode = Mode::Command(Chord::new(':')),
|
||||
':' => {
|
||||
if let Self::Visual(pos) = app.mode {
|
||||
app.mode = Mode::VisualCmd(pos, Chord::new(':'));
|
||||
} else {
|
||||
app.mode = Mode::Command(Chord::new(':'))
|
||||
}
|
||||
}
|
||||
'p' => {
|
||||
app.clipboard.paste(&mut app.grid, true);
|
||||
app.grid.apply_momentum(app.clipboard.momentum());
|
||||
@@ -323,15 +343,16 @@ impl Mode {
|
||||
}
|
||||
}
|
||||
}
|
||||
// IDK why it works but it does. Keystrokes are process somewhere else?
|
||||
// Keys are process in the handle_event method in App for these
|
||||
Mode::Insert(_chord) => {}
|
||||
Mode::Command(_chord) => {}
|
||||
Mode::VisualCmd(_pos, _chord ) => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chars_to_display(&self, cell: &Option<CellType>) -> u16 {
|
||||
let len = match &self {
|
||||
Mode::Insert(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 => {
|
||||
let len = cell.as_ref().map(|f| f.to_string().len()).unwrap_or_default();
|
||||
len
|
||||
@@ -360,6 +381,7 @@ impl Mode {
|
||||
area,
|
||||
),
|
||||
Mode::Visual(_) => {}
|
||||
Mode::VisualCmd(_, editor) => f.render_widget(editor, area),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user