diff --git a/examples/modal.rs b/examples/modal.rs index 633ff5a..57df60b 100644 --- a/examples/modal.rs +++ b/examples/modal.rs @@ -43,6 +43,54 @@ impl fmt::Display for Mode { } } +// State machine to handle pending key inputs +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum PendingState { + None, + G, + D, // 'Delete' operator + Y, // 'Yank' operator + C, // 'Change' operator +} + +impl PendingState { + fn operator(&self) -> Option { + match self { + Self::D => Some('d'), + Self::Y => Some('y'), + Self::C => Some('c'), + _ => None, + } + } + + fn transition(self, input: &Input) -> Option { + match input { + Input { key: Key::Null, .. } => None, + Input { + key: Key::Char('g'), + ctrl: false, + .. + } if self != Self::G => Some(Self::G), + Input { + key: Key::Char('d'), + ctrl: false, + .. + } if self != Self::D => Some(Self::D), + Input { + key: Key::Char('y'), + ctrl: false, + .. + } if self != Self::Y => Some(Self::Y), + Input { + key: Key::Char('c'), + ctrl: false, + .. + } if self != Self::C => Some(Self::C), + _ => Some(Self::None), + } + } +} + fn main() -> io::Result<()> { let stdout = io::stdout(); let mut stdout = stdout.lock(); @@ -62,6 +110,8 @@ fn main() -> io::Result<()> { }; let mut mode = Mode::Normal; + let mut pending = PendingState::None; + loop { // Show help message and current mode in title of the block let title = format!("{} MODE ({})", mode, mode.help_message()); @@ -76,6 +126,12 @@ fn main() -> io::Result<()> { term.draw(|f| f.render_widget(textarea.widget(), f.size()))?; let input = crossterm::event::read()?.into(); + + if pending.operator().is_some() && !textarea.is_selecting() { + textarea.start_selection(); + } + let next_pending = pending.transition(&input); // Calculate next state before moving `input` + match mode { Mode::Normal => match input { // Mappings in normal mode @@ -242,6 +298,30 @@ fn main() -> io::Result<()> { textarea.move_cursor(CursorMove::End); } Input { key: Key::Esc, .. } => textarea.cancel_selection(), + Input { + key: Key::Char('g'), + ctrl: false, + .. + } if pending == PendingState::G => { + textarea.move_cursor(CursorMove::Top); + } + Input { + key: Key::Char('G'), + ctrl: false, + .. + } => { + textarea.move_cursor(CursorMove::Bottom); + } + Input { + key: Key::Char(c), + ctrl: false, + .. + } if pending.operator() == Some(c) => { + // Handle yy, dd, cc. (This is not strictly the same behavior as Vim) + textarea.move_cursor(CursorMove::Head); + textarea.start_selection(); + textarea.move_cursor(CursorMove::Down); + } Input { key: Key::Char('y'), ctrl: false, @@ -254,6 +334,14 @@ fn main() -> io::Result<()> { } if textarea.is_selecting() => { textarea.cut(); } + Input { + key: Key::Char('c'), + ctrl: false, + .. + } if textarea.is_selecting() => { + textarea.cut(); + mode = Mode::Insert; + } _ => {} }, Mode::Insert => match input { @@ -270,6 +358,21 @@ fn main() -> io::Result<()> { } }, } + + if let Some(next_pending) = next_pending { + match pending { + PendingState::D => { + textarea.cut(); + } + PendingState::Y => textarea.copy(), + PendingState::C => { + textarea.cut(); + mode = Mode::Insert; + } + _ => {} + } + pending = next_pending; + } } disable_raw_mode()?;