diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..01deb32 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +use_small_heuristics = "Max" +max_width = 120 +struct_lit_single_line = false \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c721ddc..cbc322b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,11 @@ -// #![feature(impl_trait_in_bindings)] - mod calc; mod ctx; -use std::io; +use std::{fmt::Display, io, path::PathBuf}; use ratatui::{ crossterm::event, layout::{Constraint, Layout}, - text::*, widgets::{Paragraph, Widget}, *, }; @@ -50,17 +47,64 @@ fn main() -> Result<(), std::io::Error> { return res; } +enum Mode { + Insert(Editor), + Chord(Chord), + Normal, + Command(Chord), +} + +impl Display for Mode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Mode::Insert(_) => write!(f, "- INSERT -"), + Mode::Chord(_) => write!(f, "- CHORD -"), + Mode::Normal => write!(f, "- NORMAL -"), + Mode::Command(_) => write!(f, "- COMMAND -"), + } + } +} + +impl Mode { + fn process_key(app: &mut App, key: char) { + match key { + // < + 'h' => app.grid.selected_cell.0 = app.grid.selected_cell.0.saturating_sub(1), + // v + 'j' => app.grid.selected_cell.1 = app.grid.selected_cell.1.saturating_add(1), + // ^ + 'k' => app.grid.selected_cell.1 = app.grid.selected_cell.1.saturating_sub(1), + // > + 'l' => app.grid.selected_cell.0 = app.grid.selected_cell.0.saturating_add(1), + // edit cell + 'i' | 'a' => { + let (x, y) = app.grid.selected_cell; + + let val = app.grid.get_cell_raw(x, y).as_ref().map(|f| f.as_raw_string()).unwrap_or(String::new()); + + app.mode = Mode::Insert(Editor::new(val, (x, y))); + } + 'I' => { /* insert col before */ } + 'A' => { /* insert col after */ } + 'o' => { /* insert row below */ } + 'O' => { /* insert row above */ } + ':' => app.mode = Mode::Command(Chord::new(':')), + // loose chars will put you into chord mode + c => app.mode = Mode::Chord(Chord::new(c)), + } + } +} + struct App { exit: bool, grid: Grid, - /// Buffer for key-chords - chord_buf: String, - editor: Option, + mode: Mode, + file: Option, } impl Widget for &App { fn render(self, area: prelude::Rect, buf: &mut prelude::Buffer) { - Paragraph::new("Status").render(area, buf); + Paragraph::new(self.mode.to_string()).render(area, buf); } } @@ -69,8 +113,8 @@ impl App { Self { exit: false, grid: Grid::new(), - chord_buf: String::new(), - editor: None, + mode: Mode::Normal, + file: None, } } @@ -81,98 +125,134 @@ impl App { } Ok(()) } - fn draw(&self, frame: &mut Frame) { + fn draw(&self, frame: &mut Frame) { let layout = Layout::default() .direction(layout::Direction::Vertical) - .constraints([ - Constraint::Length(1), - Constraint::Min(1), - Constraint::Length(1), - ]) + .constraints([Constraint::Length(1), Constraint::Min(1), Constraint::Length(1)]) .split(frame.area()); - if let Some(editor) = &self.editor { - frame.render_widget(editor, layout[0]); - } else { - frame.render_widget(Paragraph::new("sc_rs"), layout[0]); + let bottom_split = Layout::default() + .direction(layout::Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(layout[2]); + + match &self.mode { + Mode::Insert(editor) => { + frame.render_widget(editor, layout[0]); + } + Mode::Command(editor) => { + frame.render_widget(editor, bottom_split[0]); + } + Mode::Chord(chord) => frame.render_widget(chord, bottom_split[0]), + Mode::Normal => frame.render_widget( + Paragraph::new({ + let (x, y) = self.grid.selected_cell; + let cell = self.grid.get_cell_raw(x, y).as_ref().map(|f| f.as_raw_string()).unwrap_or_default(); + cell + }), + layout[0], + ), } frame.render_widget(&self.grid, layout[1]); - frame.render_widget(self, layout[2]); + frame.render_widget(self, bottom_split[1]); } fn handle_events(&mut self) -> io::Result<()> { - match event::read()? { - event::Event::Key(key_event) => match key_event.code { - event::KeyCode::Enter => { - if let Some(editor) = &self.editor { - let loc= self.grid.selected_cell; - - let val = editor.buf.trim().to_string(); - - // insert as number if at all possible - if let Ok(val) = val.parse::() { - self.grid.set_cell_raw(loc, val); - } else { - self.grid.set_cell_raw(loc, val); - }; - - self.editor = None; - } - } - event::KeyCode::Backspace => { - if let Some(editor) = &mut self.editor { - editor.buf.pop(); - } - } - event::KeyCode::F(_) => todo!(), - event::KeyCode::Char(c) => { - - if let Some(editor) = &mut self.editor { - editor.buf += &c.to_string(); - return Ok(()); - } - - if !self.chord_buf.is_empty() {} - - match c { - 'q' => self.exit = true, - // < - 'h' => self.grid.selected_cell.0 = self.grid.selected_cell.0.saturating_sub(1), - // v - 'j' => self.grid.selected_cell.1 = self.grid.selected_cell.1.saturating_add(1), - // ^ - 'k' => self.grid.selected_cell.1 = self.grid.selected_cell.1.saturating_sub(1), - // > - 'l' => self.grid.selected_cell.0 = self.grid.selected_cell.0.saturating_add(1), - // edit cell - 'i' | 'a' => { - let (x,y) = self.grid.selected_cell; - let starting_val = if let Some(val) = self.grid.get_cell_raw(x, y) { - val.as_raw_string() - } else { - String::new() - }; - self.editor = Some(Editor::from(starting_val)) - }, - 'I' => {/* insert col before */} - 'A' => {/* insert col after */} - 'o' => {/* insert row below */} - 'O' => {/* insert row above */} - ':' => {/* enter command mode */} - c => { - // start entering c for words - self.chord_buf += &c.to_string(); + match &mut self.mode { + Mode::Chord(chord) => match event::read()? { + event::Event::Key(key) => match key.code { + event::KeyCode::Esc => self.mode = Mode::Normal, + event::KeyCode::Char(c) => { + chord.buf.push(c); + match chord.as_string()[0..chord.buf.len() - 1].parse::() { + Ok(num) => match c { + 'G' => { + let sel = self.grid.selected_cell; + self.grid.selected_cell = (sel.0, num); + self.mode = Mode::Normal; + } + _ => { + if c.is_alphabetic() { + self.mode = Mode::Normal; + for _ in 0..num { + Mode::process_key(self, c); + } + } + } + }, + _ => {} } } + _ => {} + }, + _ => {} + }, + Mode::Insert(editor) => match event::read()? { + event::Event::Key(key) => match key.code { + event::KeyCode::Esc => { + // just cancel the operation + self.mode = Mode::Normal; + } + event::KeyCode::Enter => { + let v = editor.buf.trim().to_string(); + + if let Ok(v) = v.parse::() { + self.grid.set_cell_raw(editor.location, v); + } else { + self.grid.set_cell_raw(editor.location, v); + } + + self.mode = Mode::Normal; + } + event::KeyCode::Backspace => { + editor.buf.pop(); + } + event::KeyCode::Char(c) => { + editor.buf += &c.to_string(); + } + _ => {} + }, + _ => {} + }, + Mode::Normal => match event::read()? { + event::Event::Key(key_event) => match key_event.code { + event::KeyCode::F(_) => todo!(), + event::KeyCode::Char(c) => { + Mode::process_key(self, c); + } + _ => todo!(), + }, + _ => todo!(), + }, + 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::Enter => { + // [':', 'q'] + match editor.buf[1] { + 'w' => {} + 'q' => self.exit = true, + _ => {} + } + self.mode = Mode::Normal; + } + event::KeyCode::Backspace => { + editor.buf.pop(); + } + event::KeyCode::Char(c) => { + editor.add_char(c); + } + _ => {} }, _ => {} }, - _ => {} - event::Event::Paste(_) => todo!(), - event::Event::Resize(_, _) => todo!(), } + Ok(()) } } @@ -180,19 +260,54 @@ impl App { struct Editor { buf: String, cursor: usize, + location: (usize, usize), } - -impl From for Editor { - fn from(value: String) -> Self { +impl Editor { + fn new(value: String, loc: (usize, usize)) -> Self { Self { buf: value.to_string(), cursor: value.len(), + location: loc, } } } impl Widget for &Editor { fn render(self, area: prelude::Rect, buf: &mut prelude::Buffer) { + // TODO add visual cursor Paragraph::new(self.buf.clone()).render(area, buf); } -} \ No newline at end of file +} + +struct Chord { + buf: Vec, +} + +impl Chord { + fn new(inital: char) -> Self { + let mut buf = Vec::new(); + buf.push(inital); + + Self { + buf, + } + } + + fn backspace(&mut self) { + self.buf.pop(); + } + + fn add_char(&mut self, c: char) { + self.buf.push(c) + } + + fn as_string(&self) -> String { + self.buf.iter().collect() + } +} + +impl Widget for &Chord { + fn render(self, area: prelude::Rect, buf: &mut prelude::Buffer) { + Paragraph::new(self.buf.iter().collect::()).render(area, buf); + } +}