Compare commits
4 Commits
ef4429a38f
...
b8fd938120
| Author | SHA1 | Date | |
|---|---|---|---|
| b8fd938120 | |||
| 3be92aea3c | |||
| 902af1311d | |||
| 9c44db0d92 |
@@ -1,13 +1,25 @@
|
||||
use std::{
|
||||
cmp::{max, min}, collections::HashMap, io, path::PathBuf
|
||||
cmp::{max, min},
|
||||
collections::HashMap,
|
||||
io,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use ratatui::{
|
||||
DefaultTerminal, Frame, crossterm::event, layout::{self, Constraint, Layout, Rect}, prelude, style::{Color, Modifier, Style}, widgets::{Paragraph, Widget}
|
||||
DefaultTerminal, Frame,
|
||||
crossterm::event,
|
||||
layout::{self, Constraint, Layout, Rect},
|
||||
prelude,
|
||||
style::{Color, Modifier, Style},
|
||||
widgets::{Paragraph, Widget},
|
||||
};
|
||||
|
||||
use crate::app::{
|
||||
clipboard::Clipboard, error_msg::StatusMessage, logic::{calc::Grid, cell::CellType}, mode::Mode, screen::ScreenSpace
|
||||
clipboard::Clipboard,
|
||||
error_msg::StatusMessage,
|
||||
logic::{calc::Grid, cell::CellType},
|
||||
mode::Mode,
|
||||
screen::ScreenSpace,
|
||||
};
|
||||
|
||||
pub struct App {
|
||||
@@ -144,6 +156,13 @@ impl Widget for &App {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(bound) = suggest_upper_bound {
|
||||
let bound = bound as usize;
|
||||
if bound < display.len() {
|
||||
display.truncate(bound - 2);
|
||||
display.push('…');
|
||||
}
|
||||
}
|
||||
}
|
||||
None => should_render = false,
|
||||
}
|
||||
@@ -210,27 +229,7 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let layout = Layout::default()
|
||||
.direction(layout::Direction::Vertical)
|
||||
.constraints([Constraint::Length(1), Constraint::Min(1)])
|
||||
.split(frame.area());
|
||||
|
||||
let cmd_line = layout[0];
|
||||
let body = layout[1];
|
||||
|
||||
let len = match &self.mode {
|
||||
Mode::Insert(edit) | Mode::Command(edit) | Mode::Chord(edit) => edit.len(),
|
||||
Mode::Normal => {
|
||||
let (x, y) = self.grid.cursor();
|
||||
let cell = self.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string().len()).unwrap_or_default();
|
||||
cell
|
||||
}
|
||||
Mode::Visual(_) => 0,
|
||||
};
|
||||
// min 20 chars, expand if needed
|
||||
let len = max(len as u16 + 1, 20);
|
||||
|
||||
fn file_name_display(&self) -> String {
|
||||
let file_name_status = {
|
||||
let mut file_name = "[No Name]";
|
||||
let mut icon = "";
|
||||
@@ -246,39 +245,46 @@ impl App {
|
||||
}
|
||||
format!("{file_name}{icon}")
|
||||
};
|
||||
file_name_status
|
||||
}
|
||||
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let (x, y) = self.grid.cursor();
|
||||
let current_cell = self.grid.get_cell_raw(x, y);
|
||||
let len = self.mode.chars_to_display(current_cell);
|
||||
let file_name_status = self.file_name_display();
|
||||
|
||||
// layout
|
||||
// ======================================================
|
||||
let layout = Layout::default()
|
||||
.direction(layout::Direction::Vertical)
|
||||
.constraints([Constraint::Length(1), Constraint::Min(1)])
|
||||
.split(frame.area());
|
||||
let cmd_line = layout[0];
|
||||
let body = layout[1];
|
||||
|
||||
let cmd_line_split = Layout::default()
|
||||
.direction(layout::Direction::Horizontal)
|
||||
.constraints([Constraint::Length(len), Constraint::Length(file_name_status.len() as u16 + 1), Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.constraints([
|
||||
Constraint::Length(len),
|
||||
Constraint::Length(file_name_status.len() as u16 + 1),
|
||||
Constraint::Percentage(50),
|
||||
Constraint::Percentage(50),
|
||||
])
|
||||
.split(cmd_line);
|
||||
|
||||
let cmd_line_left = cmd_line_split[0];
|
||||
let cmd_line_status = cmd_line_split[1];
|
||||
let cmd_line_right = cmd_line_split[2];
|
||||
let cmd_line_debug = cmd_line_split[3];
|
||||
// ======================================================
|
||||
|
||||
match &self.mode {
|
||||
Mode::Insert(editor) => {
|
||||
frame.render_widget(editor, cmd_line_left);
|
||||
}
|
||||
Mode::Command(editor) => {
|
||||
frame.render_widget(editor, cmd_line_left);
|
||||
}
|
||||
Mode::Chord(chord) => frame.render_widget(chord, cmd_line_left),
|
||||
Mode::Normal => frame.render_widget(
|
||||
Paragraph::new({
|
||||
let (x, y) = self.grid.cursor();
|
||||
let cell = self.grid.get_cell_raw(x, y).as_ref().map(|f| f.to_string()).unwrap_or_default();
|
||||
cell
|
||||
}),
|
||||
cmd_line_left,
|
||||
),
|
||||
Mode::Visual(_) => {}
|
||||
}
|
||||
self.mode.render(frame, cmd_line_left, current_cell);
|
||||
|
||||
frame.render_widget(self, body);
|
||||
frame.render_widget(&self.msg, cmd_line_right);
|
||||
frame.render_widget(Paragraph::new(file_name_status), cmd_line_status);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!(
|
||||
|
||||
@@ -296,3 +296,57 @@ fn copy_paste_vars_translate() {
|
||||
let a = app.grid.get_cell("A0").as_ref().expect("Should've been set by paste");
|
||||
assert_eq!(a.to_string(), "=A1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_paste_double_locked_var() {
|
||||
let mut app = App::new();
|
||||
|
||||
app.grid.set_cell("A0", 0.);
|
||||
app.grid.set_cell("A1", "=$A$0".to_string());
|
||||
|
||||
// Copy A0
|
||||
app.grid.mv_cursor_to(0, 1);
|
||||
app.mode = super::mode::Mode::Chord(Chord::new('y'));
|
||||
Mode::process_key(&mut app, 'y');
|
||||
|
||||
app.grid.mv_cursor_to(1, 0);
|
||||
Mode::process_key(&mut app, 'p');
|
||||
let c = app.grid.get_cell("B0").as_ref().expect("Just set it");
|
||||
assert_eq!(c.to_string(), "=$A$0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_paste_x_locked_var() {
|
||||
let mut app = App::new();
|
||||
|
||||
app.grid.set_cell("A0", 0.);
|
||||
app.grid.set_cell("A1", "=$A0".to_string());
|
||||
|
||||
// Copy A0
|
||||
app.grid.mv_cursor_to(0, 1);
|
||||
app.mode = super::mode::Mode::Chord(Chord::new('y'));
|
||||
Mode::process_key(&mut app, 'y');
|
||||
|
||||
app.grid.mv_cursor_to(1, 2);
|
||||
Mode::process_key(&mut app, 'p');
|
||||
let c = app.grid.get_cell("B2").as_ref().expect("Just set it");
|
||||
assert_eq!(c.to_string(), "=$A1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_paste_y_locked_var() {
|
||||
let mut app = App::new();
|
||||
|
||||
app.grid.set_cell("A0", 0.);
|
||||
app.grid.set_cell("A1", "=A$0".to_string());
|
||||
|
||||
// Copy A0
|
||||
app.grid.mv_cursor_to(0, 1);
|
||||
app.mode = super::mode::Mode::Chord(Chord::new('y'));
|
||||
Mode::process_key(&mut app, 'y');
|
||||
|
||||
app.grid.mv_cursor_to(1, 2);
|
||||
Mode::process_key(&mut app, 'p');
|
||||
let c = app.grid.get_cell("B2").as_ref().expect("Just set it");
|
||||
assert_eq!(c.to_string(), "=B$0");
|
||||
}
|
||||
@@ -12,13 +12,14 @@ use crate::app::{
|
||||
cell::{CSV_DELIMITER, CellType},
|
||||
ctx,
|
||||
},
|
||||
mode::Mode,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::app::app::App;
|
||||
|
||||
pub const LEN: usize = 1000;
|
||||
pub const CSV_EXT: &str = "csv";
|
||||
pub const CUSTOM_EXT: &str = "nscim";
|
||||
|
||||
pub struct Grid {
|
||||
// a b c ...
|
||||
@@ -81,16 +82,13 @@ impl Grid {
|
||||
/// Save file to `path` as a csv. Path with have `csv` appended to it if it
|
||||
/// does not already have the extension.
|
||||
pub fn save_to(&mut self, path: impl Into<PathBuf>) -> std::io::Result<()> {
|
||||
let mut path = path.into();
|
||||
|
||||
const CSV: &str = "csv";
|
||||
const CUSTOM_EXT: &str = "nscim";
|
||||
let path = path.into();
|
||||
|
||||
let resolve_values;
|
||||
|
||||
match path.extension() {
|
||||
Some(ext) => match ext.to_str() {
|
||||
Some(CSV) => {
|
||||
Some(CSV_EXT) => {
|
||||
resolve_values = true;
|
||||
}
|
||||
Some(CUSTOM_EXT) => {
|
||||
@@ -98,12 +96,12 @@ impl Grid {
|
||||
}
|
||||
_ => {
|
||||
resolve_values = false;
|
||||
path.add_extension(CUSTOM_EXT);
|
||||
// path.add_extension(CUSTOM_EXT);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
resolve_values = false;
|
||||
path.add_extension(CUSTOM_EXT);
|
||||
// path.add_extension(CUSTOM_EXT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +379,8 @@ impl Grid {
|
||||
/// Parse values in the format of A0, C10 ZZ99, etc, and
|
||||
/// turn them into an X,Y index.
|
||||
pub fn parse_to_idx(i: &str) -> Option<(usize, usize)> {
|
||||
let i = i.replace('$', "");
|
||||
|
||||
let chars = i.chars().take_while(|c| c.is_alphabetic()).collect::<Vec<char>>();
|
||||
let nums = i.chars().skip(chars.len()).take_while(|c| c.is_numeric()).collect::<String>();
|
||||
|
||||
@@ -476,6 +476,9 @@ fn cell_strings() {
|
||||
#[test]
|
||||
fn alphanumeric_indexing() {
|
||||
assert_eq!(Grid::parse_to_idx("A0"), Some((0, 0)));
|
||||
assert_eq!(Grid::parse_to_idx("$A0"), Some((0, 0)));
|
||||
assert_eq!(Grid::parse_to_idx("A$0"), Some((0, 0)));
|
||||
assert_eq!(Grid::parse_to_idx("$A$0"), Some((0, 0)));
|
||||
assert_eq!(Grid::parse_to_idx("AA0"), Some((26, 0)));
|
||||
assert_eq!(Grid::parse_to_idx("A1"), Some((0, 1)));
|
||||
assert_eq!(Grid::parse_to_idx("A10"), Some((0, 10)));
|
||||
|
||||
@@ -66,6 +66,37 @@ impl CellType {
|
||||
let mut rolling = eq.clone();
|
||||
// translate standard vars A0 -> A1
|
||||
for old_var in ctx.dump_vars() {
|
||||
let mut lock_x = false;
|
||||
let mut lock_y = false;
|
||||
|
||||
if old_var.contains('$') {
|
||||
let locations = old_var.char_indices().filter(|(_, c)| *c == '$').map(|(i, _)| i).collect::<Vec<usize>>();
|
||||
match locations.len() {
|
||||
1 => {
|
||||
if locations[0] == 0 {
|
||||
// locking the X axis (A,B,C...)
|
||||
lock_x = true;
|
||||
} else if locations[0] < old_var.len() {
|
||||
// inside the string somewhere, gonna assume this means to lock Y (1,2,3...)
|
||||
lock_y = true;
|
||||
|
||||
} else {
|
||||
// where tf is this dollar sign?
|
||||
// (It's somewhere malformed, like A0$ or something)
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
// YOLO, lock both X & Y
|
||||
continue; // just pretend you never even saw this var
|
||||
}
|
||||
_ => {
|
||||
// Could probably optimize the code or something so you only go over the string
|
||||
// once, instead of contains() then getting the indexes of where it is.
|
||||
// You could then put your no-$ code here.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((src_x, src_y)) = Grid::parse_to_idx(&old_var) {
|
||||
let (x1, y1) = from;
|
||||
let x1 = x1 as i32;
|
||||
@@ -74,12 +105,29 @@ impl CellType {
|
||||
let x2 = x2 as i32;
|
||||
let y2 = y2 as i32;
|
||||
|
||||
let dest_x = (src_x as i32 + (x2 - x1)) as usize;
|
||||
let dest_y = (src_y as i32 + (y2 - y1)) as usize;
|
||||
let dest_x = if lock_x {
|
||||
src_x as usize
|
||||
} else {
|
||||
(src_x as i32 + (x2 - x1)) as usize
|
||||
};
|
||||
|
||||
let dest_y = if lock_y {
|
||||
src_y as usize
|
||||
} else {
|
||||
(src_y as i32 + (y2 - y1)) as usize
|
||||
};
|
||||
|
||||
let alpha = Grid::num_to_char(dest_x);
|
||||
let alpha = alpha.trim();
|
||||
let new_var = format!("{alpha}{dest_y}");
|
||||
|
||||
let new_var = if lock_x {
|
||||
format!("${alpha}{dest_y}")
|
||||
} else if lock_y {
|
||||
format!("{alpha}${dest_y}")
|
||||
} else {
|
||||
format!("{alpha}{dest_y}")
|
||||
};
|
||||
|
||||
|
||||
// swap out vars
|
||||
rolling = replace_fn(&rolling, &old_var, &new_var);
|
||||
|
||||
@@ -295,7 +295,7 @@ impl Context for ExtractionContext {
|
||||
registry.push(identifier.to_owned())
|
||||
} else { panic!("The RwLock should always be write-able") }
|
||||
// Ok(Value::Int(1))
|
||||
todo!();
|
||||
unimplemented!("Extracting function identifier not implemented yet")
|
||||
}
|
||||
|
||||
fn are_builtin_functions_disabled(&self) -> bool {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use std::{cmp::min, fmt::Display, path::PathBuf};
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
fmt::Display,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use ratatui::{
|
||||
prelude,
|
||||
@@ -9,7 +13,10 @@ use ratatui::{
|
||||
use crate::app::{
|
||||
app::App,
|
||||
error_msg::StatusMessage,
|
||||
logic::calc::LEN,
|
||||
logic::{
|
||||
calc::{CSV_EXT, CUSTOM_EXT, LEN},
|
||||
cell::CellType,
|
||||
},
|
||||
};
|
||||
|
||||
pub enum Mode {
|
||||
@@ -59,13 +66,32 @@ impl Mode {
|
||||
"w" => {
|
||||
// first try the passed argument as file
|
||||
if let Some(arg) = args.get(1) {
|
||||
if let Err(e) = app.grid.save_to(arg) {
|
||||
let mut path: PathBuf = arg.into();
|
||||
match path.extension() {
|
||||
Some(s) => {
|
||||
match s.to_str() {
|
||||
// leave the file alone, it already has
|
||||
// a valid extension
|
||||
Some(CSV_EXT) | Some(CUSTOM_EXT) => {}
|
||||
_ => {
|
||||
path.add_extension(CUSTOM_EXT);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
path.add_extension(CUSTOM_EXT);
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = app.grid.save_to(&path) {
|
||||
app.msg = StatusMessage::error(format!("{e}"));
|
||||
} else {
|
||||
// file saving was a success, adopt the provided file
|
||||
// if we don't already have one (this is how vim works)
|
||||
let path: PathBuf = arg.into();
|
||||
app.msg = StatusMessage::info(format!("Saved file {}", path.file_name().map(|f| f.to_str().unwrap_or("n/a")).unwrap_or("n/a")));
|
||||
app.msg = StatusMessage::info(format!(
|
||||
"Saved file {}",
|
||||
path.file_name().map(|f| f.to_str().unwrap_or("n/a")).unwrap_or("n/a")
|
||||
));
|
||||
|
||||
if let None = app.file {
|
||||
app.file = Some(path)
|
||||
@@ -76,7 +102,10 @@ impl Mode {
|
||||
if let Err(e) = app.grid.save_to(file) {
|
||||
app.msg = StatusMessage::error(format!("{e}"));
|
||||
} else {
|
||||
app.msg = StatusMessage::info(format!("Saved file {}", file.file_name().map(|f| f.to_str().unwrap_or("n/a")).unwrap_or("n/a")));
|
||||
app.msg = StatusMessage::info(format!(
|
||||
"Saved file {}",
|
||||
file.file_name().map(|f| f.to_str().unwrap_or("n/a")).unwrap_or("n/a")
|
||||
));
|
||||
}
|
||||
// you need to provide a file from *somewhere*
|
||||
} else {
|
||||
@@ -168,13 +197,13 @@ impl Mode {
|
||||
'A' => {
|
||||
let c = app.grid.cursor();
|
||||
app.grid.insert_column_after(c);
|
||||
app.grid.mv_cursor_to(c.0+1, c.1);
|
||||
app.grid.mv_cursor_to(c.0 + 1, c.1);
|
||||
}
|
||||
// insert row below
|
||||
'o' => {
|
||||
let c = app.grid.cursor();
|
||||
app.grid.insert_row_below(c);
|
||||
app.grid.mv_cursor_to(c.0, c.1+1);
|
||||
app.grid.mv_cursor_to(c.0, c.1 + 1);
|
||||
}
|
||||
// insert row above
|
||||
'O' => {
|
||||
@@ -200,7 +229,7 @@ impl Mode {
|
||||
|
||||
match key {
|
||||
'd' | 'x' => {
|
||||
app.clipboard.clipboard_cut((x1,y1), (x2,y2), &mut app.grid);
|
||||
app.clipboard.clipboard_cut((x1, y1), (x2, y2), &mut app.grid);
|
||||
app.mode = Mode::Normal
|
||||
}
|
||||
'y' => {
|
||||
@@ -284,7 +313,7 @@ impl Mode {
|
||||
app.clipboard.paste(&mut app.grid, false);
|
||||
app.grid.apply_momentum(app.clipboard.momentum());
|
||||
app.mode = Mode::Normal;
|
||||
let plural = if app.clipboard.qty() > 1 {"cells"} else {"cell"};
|
||||
let plural = if app.clipboard.qty() > 1 { "cells" } else { "cell" };
|
||||
app.msg = StatusMessage::info(format!("Pasted {plural}, no formatting"));
|
||||
return;
|
||||
}
|
||||
@@ -294,8 +323,42 @@ impl Mode {
|
||||
}
|
||||
}
|
||||
// IDK why it works but it does. Keystrokes are process somewhere else?
|
||||
Mode::Insert(_chord) => {},
|
||||
Mode::Command(_chord) => {},
|
||||
Mode::Insert(_chord) => {}
|
||||
Mode::Command(_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::Normal => {
|
||||
let len = cell.as_ref().map(|f| f.to_string().len()).unwrap_or_default();
|
||||
len
|
||||
}
|
||||
Mode::Visual(_) => 0,
|
||||
};
|
||||
// min 20 chars, expand if needed
|
||||
let len = max(len as u16 + 1, 20);
|
||||
len
|
||||
}
|
||||
|
||||
pub fn render(&self, f: &mut ratatui::Frame, area: prelude::Rect, cell: &Option<CellType>) {
|
||||
match &self {
|
||||
Mode::Insert(editor) => {
|
||||
f.render_widget(editor, area);
|
||||
}
|
||||
Mode::Command(editor) => {
|
||||
f.render_widget(editor, area);
|
||||
}
|
||||
Mode::Chord(chord) => f.render_widget(chord, area),
|
||||
Mode::Normal => f.render_widget(
|
||||
Paragraph::new({
|
||||
let cell = cell.as_ref().map(|f| f.to_string()).unwrap_or_default();
|
||||
cell
|
||||
}),
|
||||
area,
|
||||
),
|
||||
Mode::Visual(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user