save
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use std::{collections::HashMap, io, path::PathBuf};
|
use std::{cmp::{max, min}, collections::HashMap, io, path::PathBuf};
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
DefaultTerminal, Frame,
|
DefaultTerminal, Frame,
|
||||||
@@ -164,6 +164,13 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_with_file(file: impl Into<PathBuf> + Clone) -> std::io::Result<Self> {
|
||||||
|
let mut app = Self::new();
|
||||||
|
app.file = Some(file.clone().into());
|
||||||
|
app.grid = Grid::new_from_file(file.into())?;
|
||||||
|
Ok(app)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, mut term: DefaultTerminal) -> Result<(), std::io::Error> {
|
pub fn run(&mut self, mut term: DefaultTerminal) -> Result<(), std::io::Error> {
|
||||||
while !self.exit {
|
while !self.exit {
|
||||||
term.draw(|frame| self.draw(frame))?;
|
term.draw(|frame| self.draw(frame))?;
|
||||||
@@ -181,13 +188,28 @@ impl App {
|
|||||||
let cmd_line = layout[0];
|
let cmd_line = layout[0];
|
||||||
let body = layout[1];
|
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.selected_cell;
|
||||||
|
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);
|
||||||
|
|
||||||
let bottom_split = Layout::default()
|
let bottom_split = Layout::default()
|
||||||
.direction(layout::Direction::Horizontal)
|
.direction(layout::Direction::Horizontal)
|
||||||
.constraints([Constraint::Min(30), Constraint::Min(100)])
|
.constraints([Constraint::Length(len), Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
.split(cmd_line);
|
.split(cmd_line);
|
||||||
|
|
||||||
let cmd_line_left = bottom_split[0];
|
let cmd_line_left = bottom_split[0];
|
||||||
let cmd_line_right = bottom_split[1];
|
let cmd_line_right = bottom_split[1];
|
||||||
|
let cmd_line_debug = bottom_split[2];
|
||||||
|
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Insert(editor) => {
|
Mode::Insert(editor) => {
|
||||||
@@ -211,7 +233,7 @@ impl App {
|
|||||||
frame.render_widget(self, body);
|
frame.render_widget(self, body);
|
||||||
frame.render_widget(&self.error_msg, cmd_line_right);
|
frame.render_widget(&self.error_msg, cmd_line_right);
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
frame.render_widget(Paragraph::new(format!("x/w y/h: cursor{:?} scroll({}, {}) cell({}, {}) screen({}, {})",
|
frame.render_widget(Paragraph::new(format!("x/w y/h: cursor{:?} scroll({}, {}) cell({}, {}) screen({}, {}) len{len}",
|
||||||
self.grid.selected_cell,
|
self.grid.selected_cell,
|
||||||
self.screen.scroll_x(),
|
self.screen.scroll_x(),
|
||||||
self.screen.scroll_y(),
|
self.screen.scroll_y(),
|
||||||
@@ -219,7 +241,7 @@ impl App {
|
|||||||
self.screen.get_cell_height(&self.vars),
|
self.screen.get_cell_height(&self.vars),
|
||||||
body.width,
|
body.width,
|
||||||
body.height,
|
body.height,
|
||||||
)), cmd_line_right);
|
)), cmd_line_debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_events(&mut self) -> io::Result<()> {
|
fn handle_events(&mut self) -> io::Result<()> {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::fmt::Display;
|
use std::{fmt::Display, fs, io::{Read, Write}, path::PathBuf};
|
||||||
|
|
||||||
use evalexpr::*;
|
use evalexpr::*;
|
||||||
use ratatui::buffer::Cell;
|
|
||||||
|
|
||||||
use crate::app::logic::ctx;
|
use crate::app::logic::ctx;
|
||||||
|
|
||||||
@@ -17,6 +16,8 @@ pub struct Grid {
|
|||||||
cells: Vec<Vec<Option<CellType>>>,
|
cells: Vec<Vec<Option<CellType>>>,
|
||||||
/// (X, Y)
|
/// (X, Y)
|
||||||
pub selected_cell: (usize, usize),
|
pub selected_cell: (usize, usize),
|
||||||
|
/// Have unsaved modifications been made?
|
||||||
|
dirty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Grid {
|
impl std::fmt::Debug for Grid {
|
||||||
@@ -28,11 +29,43 @@ impl std::fmt::Debug for Grid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Grid {
|
impl Grid {
|
||||||
|
pub fn needs_to_be_saved(&self) -> bool {
|
||||||
|
self.dirty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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();
|
||||||
|
match path.extension() {
|
||||||
|
Some(ext) => {
|
||||||
|
if ext != "csv" {
|
||||||
|
path.add_extension("csv");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {path.add_extension("csv");},
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut f = fs::OpenOptions::new().write(true).append(false).truncate(true).create(true).open(path)?;
|
||||||
|
let (mx, my) = self.max();
|
||||||
|
for y in 0..=my {
|
||||||
|
for x in 0..=mx {
|
||||||
|
let cell = &self.cells[x][y];
|
||||||
|
let display = cell.as_ref().map(|f| f.to_string()).unwrap_or(String::new());
|
||||||
|
write!(f, "{display},")?;
|
||||||
|
}
|
||||||
|
write!(f, "\n")?;
|
||||||
|
}
|
||||||
|
f.flush()?;
|
||||||
|
|
||||||
|
self.dirty = false;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate over the entire grid and see where
|
/// Iterate over the entire grid and see where
|
||||||
/// the farthest modified cell is.
|
/// the farthest modified cell is.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn max(&self) -> (usize, usize) {
|
fn max(&self) -> (usize, usize) {
|
||||||
let mut max_x = 0;
|
let mut max_x = 0;
|
||||||
let mut max_y = 0;
|
let mut max_y = 0;
|
||||||
|
|
||||||
@@ -52,6 +85,22 @@ impl Grid {
|
|||||||
(max_x, max_y)
|
(max_x, max_y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_from_file(path: impl Into<PathBuf>) -> std::io::Result<Self> {
|
||||||
|
let mut grid = Self::new();
|
||||||
|
|
||||||
|
let mut file = fs::OpenOptions::new().read(true).open(path.into())?;
|
||||||
|
let mut buf = String::new();
|
||||||
|
file.read_to_string(&mut buf)?;
|
||||||
|
for (yi, line) in buf.lines().enumerate() {
|
||||||
|
for (xi, cell) in line.split(',').enumerate() {
|
||||||
|
// This gets automatically duck-typed
|
||||||
|
grid.set_cell_raw((xi, yi), Some(cell.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(grid)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut a = Vec::with_capacity(LEN);
|
let mut a = Vec::with_capacity(LEN);
|
||||||
for _ in 0..LEN {
|
for _ in 0..LEN {
|
||||||
@@ -65,6 +114,7 @@ impl Grid {
|
|||||||
Self {
|
Self {
|
||||||
cells: a,
|
cells: a,
|
||||||
selected_cell: (0, 0),
|
selected_cell: (0, 0),
|
||||||
|
dirty: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +196,7 @@ impl Grid {
|
|||||||
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());
|
||||||
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get cells via text like:
|
/// Get cells via text like:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cmp::min,
|
cmp::min,
|
||||||
fmt::Display,
|
fmt::Display, fs,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
@@ -54,16 +54,40 @@ impl Mode {
|
|||||||
|
|
||||||
match args[0] {
|
match args[0] {
|
||||||
"w" => {
|
"w" => {
|
||||||
if let Some(file) = &app.file {
|
// first try the passed argument as file
|
||||||
unimplemented!("Figure out how we want to save Grid to a csv or something")
|
if let Some(arg) = args.get(1) {
|
||||||
} else {
|
if let Err(e) = app.grid.save_to(arg) {
|
||||||
if let Some(arg) = args.get(1) {
|
app.error_msg = ErrorMessage::new(format!("{e}"));
|
||||||
unimplemented!("Saving a file")
|
} else {
|
||||||
|
// file saving was a success, adopt the provided file
|
||||||
|
// if we don't already have one (this is how vim works)
|
||||||
|
if let None = app.file {
|
||||||
|
app.file = Some(arg.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// then try the file that we opened the program with
|
||||||
|
} else if let Some(file) = &app.file {
|
||||||
|
if let Err(e) = app.grid.save_to(file) {
|
||||||
|
app.error_msg = ErrorMessage::new(format!("{e}"));
|
||||||
|
}
|
||||||
|
// you need to provide a file from *somewhere*
|
||||||
|
} else {
|
||||||
app.error_msg = ErrorMessage::new("No file selected");
|
app.error_msg = ErrorMessage::new("No file selected");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"q" => app.exit = true,
|
// quit
|
||||||
|
"q" => {
|
||||||
|
if app.grid.needs_to_be_saved() {
|
||||||
|
app.exit = false;
|
||||||
|
app.error_msg = ErrorMessage::new("File not saved");
|
||||||
|
} else {
|
||||||
|
app.exit = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// force quit
|
||||||
|
"q!" => {
|
||||||
|
app.exit = true;
|
||||||
|
}
|
||||||
"set" => {
|
"set" => {
|
||||||
if let Some(arg) = args.get(1) {
|
if let Some(arg) = args.get(1) {
|
||||||
let parts: Vec<&str> = arg.split('=').collect();
|
let parts: Vec<&str> = arg.split('=').collect();
|
||||||
@@ -238,6 +262,9 @@ impl Chord {
|
|||||||
pub fn as_string(&self) -> String {
|
pub fn as_string(&self) -> String {
|
||||||
self.buf.iter().collect()
|
self.buf.iter().collect()
|
||||||
}
|
}
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.buf.len()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &Chord {
|
impl Widget for &Chord {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::{collections::HashMap, env::VarError, sync::RwLock};
|
use std::{collections::HashMap, sync::RwLock};
|
||||||
|
|
||||||
use ratatui::prelude;
|
use ratatui::prelude;
|
||||||
|
|
||||||
use crate::app::{app::App, logic::calc::LEN};
|
use crate::app::logic::calc::LEN;
|
||||||
|
|
||||||
pub struct ScreenSpace {
|
pub struct ScreenSpace {
|
||||||
/// This is measured in cells
|
/// This is measured in cells
|
||||||
|
|||||||
17
src/main.rs
17
src/main.rs
@@ -1,10 +1,25 @@
|
|||||||
mod app;
|
mod app;
|
||||||
|
|
||||||
|
use std::env::args;
|
||||||
|
|
||||||
use crate::app::{app::App};
|
use crate::app::{app::App};
|
||||||
|
|
||||||
fn main() -> Result<(), std::io::Error> {
|
fn main() -> Result<(), std::io::Error> {
|
||||||
|
|
||||||
|
let args = args().collect::<Vec<String>>();
|
||||||
|
let mut app = if args.len() > 1 {
|
||||||
|
let file = &args[1];
|
||||||
|
match App::new_with_file(file) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
App::new()
|
||||||
|
};
|
||||||
|
|
||||||
let term = ratatui::init();
|
let term = ratatui::init();
|
||||||
let mut app = App::new();
|
|
||||||
let res = app.run(term);
|
let res = app.run(term);
|
||||||
ratatui::restore();
|
ratatui::restore();
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
Reference in New Issue
Block a user