diff --git a/src/main.rs b/src/main.rs index 3c15c53..3410254 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,10 +20,8 @@ use std::io::{stdout, Stdout, Write}; use crossterm::{ cursor, - event::{ Event, KeyCode, KeyEvent, read }, - terminal, - ExecutableCommand, - Result + event::{read, Event, KeyCode, KeyEvent}, + terminal, ExecutableCommand, Result, }; use ropey::Rope; @@ -38,8 +36,13 @@ impl Buffer { } } - pub fn draw(&self, scroll: Cursor, out: &mut (impl ExecutableCommand + Write)) -> Result { - let (cols, rows) = terminal::size()?; + pub fn draw( + &self, + cols: u16, + rows: u16, + scroll: Cursor, + out: &mut (impl ExecutableCommand + Write), + ) -> Result { let lr_width = self.text.len_lines().ilog10() + 1; let gutter_width = lr_width + 1; let text_width = cols as usize - gutter_width as usize; @@ -126,28 +129,44 @@ struct Cursor { pub line: usize, } +#[derive(Clone, Debug, Default)] +struct NormalState { + pub error: Option, +} + +#[derive(Clone, Debug, Default)] +struct CommandState { + pub buf: String, + pub cursor: usize, +} + #[derive(Copy, Clone, Debug)] struct InsertState { append: bool, } -#[derive(Copy, Clone, Debug, Default)] +#[derive(Clone, Debug)] enum Mode { - #[default] - Normal, - Command, + Normal(NormalState), + Command(CommandState), Visual, Insert(InsertState), } +impl Default for Mode { + fn default() -> Self { + Mode::Normal(Default::default()) + } +} + impl Mode { pub fn cursor_style(&self) -> cursor::SetCursorStyle { use cursor::SetCursorStyle as Style; match self { - Mode::Normal => Style::SteadyBlock, + Mode::Normal(_) => Style::SteadyBlock, Mode::Visual => Style::BlinkingBlock, Mode::Insert(_) => Style::BlinkingBar, - Mode::Command => Style::SteadyUnderScore, + Mode::Command(_) => Style::SteadyUnderScore, } } } @@ -184,72 +203,137 @@ impl State { } pub fn draw(&self, out: &mut impl Write) -> Result<()> { + // begin update + let (cols, rows) = terminal::size()?; out.execute(terminal::BeginSynchronizedUpdate)?; out.execute(terminal::Clear(terminal::ClearType::All))?; - let lr_width = self.buffer.draw(self.scroll, out)?; - let cursor = self.buffer.clamped_cursor(self.cursor); - let col = cursor.column.saturating_sub(self.scroll.column) as u16; - let row = cursor.line.saturating_sub(self.scroll.line) as u16; - let col = col + lr_width as u16; - out.execute(cursor::MoveTo(col, row))?; + + // draw status line + let mut set_cursor_pos = None; + let mut show_status_bar = false; + + match &self.mode { + Mode::Command(CommandState { buf, cursor }) => { + let col = *cursor as u16 + 1; + let row = rows - 1; + out.execute(cursor::MoveTo(0, row))?; + write!(out, ":{}", buf)?; + set_cursor_pos = Some((col, row)); + show_status_bar = true; + } + Mode::Normal(NormalState { error: Some(error) }) => { + out.execute(cursor::MoveTo(0, rows - 1))?; + write!(out, "{}", error)?; + show_status_bar = true; + } + _ => {} + } + + // draw buffer + let buffer_rows = if show_status_bar { rows - 1 } else { rows }; + let lr_width = self.buffer.draw(cols, buffer_rows, self.scroll, out)?; + + // draw cursor + let cursor_pos = set_cursor_pos.unwrap_or_else(|| { + // calculate cursor position on buffer + let cursor = self.buffer.clamped_cursor(self.cursor); + let col = cursor.column.saturating_sub(self.scroll.column) as u16; + let row = cursor.line.saturating_sub(self.scroll.line) as u16; + let col = col + lr_width as u16; + (col, row) + }); + + out.execute(cursor::MoveTo(cursor_pos.0, cursor_pos.1))?; out.execute(self.mode.cursor_style())?; + + // finish update out.execute(terminal::EndSynchronizedUpdate)?; Ok(()) } pub fn on_event(&mut self, event: Event) { - match self.mode { - Mode::Normal => self.on_normal_event(event), - Mode::Command => self.on_command_event(event), + match &self.mode { + Mode::Normal(state) => self.on_normal_event(event, state.clone()), + Mode::Command(state) => self.on_command_event(event, state.clone()), Mode::Visual => self.on_visual_event(event), - Mode::Insert(state) => self.on_insert_event(event, state), + Mode::Insert(state) => self.on_insert_event(event, state.clone()), } } - fn on_normal_event(&mut self, event: Event) { + fn on_normal_event(&mut self, event: Event, mut state: NormalState) { + // reset the error from the last event + state.error = None; + match event { Event::Key(KeyEvent { code, .. }) => match code { KeyCode::Char('i') => { - let state = InsertState { append: false }; + let state = InsertState { append: false }; self.mode = Mode::Insert(state); } KeyCode::Char('a') => { - let state = InsertState { append: true, }; + let state = InsertState { append: true }; self.move_cursor(Direction::Right); self.mode = Mode::Insert(state); } KeyCode::Char(':') => { - self.mode = Mode::Command; + self.mode = Mode::Command(Default::default()); } KeyCode::Char('v') => { self.mode = Mode::Visual; } - KeyCode::Esc => { - self.quit = true; - } code => self.on_any_key(code), }, event => self.on_any_event(event), } + + match self.mode { + Mode::Normal(_) => self.mode = Mode::Normal(state), + _ => {} + } } - fn on_command_event(&mut self, event: Event) { + fn on_command_event(&mut self, event: Event, mut state: CommandState) { match event { Event::Key(KeyEvent { code, .. }) => match code { - KeyCode::Esc => { - self.mode = Mode::Normal; + KeyCode::Char(c) => { + state.buf.insert(state.cursor, c); + state.cursor += 1; } - code => self.on_any_key(code), + KeyCode::Backspace => { + if state.cursor > 0 { + state.cursor -= 1; + state.buf.remove(state.cursor); + } + } + KeyCode::Delete if state.cursor < state.buf.len() => { + state.buf.remove(state.cursor); + } + KeyCode::Left if state.cursor > 0 => { + state.cursor -= 1; + } + KeyCode::Right if state.cursor < state.buf.len() => { + state.cursor += 1; + } + KeyCode::Enter => { + // TODO add to command history + let result = self.execute_command(&state.buf); + let error = result.err(); + self.mode = Mode::Normal(NormalState { error }); + return; + } + code => return self.on_any_key(code), }, - event => self.on_any_event(event), + event => return self.on_any_event(event), } + + self.mode = Mode::Command(state); } fn on_visual_event(&mut self, event: Event) { match event { Event::Key(KeyEvent { code, .. }) => match code { KeyCode::Esc => { - self.mode = Mode::Normal; + self.mode = Mode::default(); } code => self.on_any_key(code), }, @@ -284,7 +368,8 @@ impl State { if state.append { self.move_cursor(Direction::Left); } - self.mode = Mode::Normal; + + self.mode = Mode::default(); } code => self.on_any_key(code), }, @@ -304,7 +389,7 @@ impl State { fn on_any_key(&mut self, code: KeyCode) { match code { - KeyCode::Esc => self.mode = Mode::Normal, + KeyCode::Esc => self.mode = Mode::default(), KeyCode::Char('h') | KeyCode::Left => self.move_cursor(Direction::Left), KeyCode::Char('j') | KeyCode::Down => self.move_cursor(Direction::Down), KeyCode::Char('k') | KeyCode::Up => self.move_cursor(Direction::Up), @@ -313,6 +398,15 @@ impl State { } } + fn execute_command(&mut self, command: &str) -> std::result::Result<(), String> { + match command { + "q" => self.quit = true, + command => return Err(format!("unrecognized command {:?}", command)), + } + + Ok(()) + } + fn move_cursor(&mut self, direction: Direction) { self.buffer.move_cursor(&mut self.cursor, direction, true);