Skip to content

Commit

Permalink
implement operator pending mode and gg in modal example
Browse files Browse the repository at this point in the history
  • Loading branch information
rhysd committed Nov 16, 2023
1 parent 13ce6aa commit 9e5b6eb
Showing 1 changed file with 107 additions and 0 deletions.
107 changes: 107 additions & 0 deletions examples/modal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,58 @@ 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 is_operator_pending(&self) -> bool {
matches!(self, Self::D | Self::Y | Self::C)
}

fn operator_char(&self) -> Option<char> {
match self {
Self::D => Some('d'),
Self::Y => Some('y'),
Self::C => Some('c'),
_ => None,
}
}

fn transition(self, input: &Input) -> Option<Self> {
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();
Expand All @@ -62,6 +114,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());
Expand All @@ -76,6 +130,12 @@ fn main() -> io::Result<()> {
term.draw(|f| f.render_widget(textarea.widget(), f.size()))?;

let input = crossterm::event::read()?.into();

if pending.is_operator_pending() && !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
Expand Down Expand Up @@ -242,6 +302,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_char() == 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,
Expand All @@ -254,6 +338,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 {
Expand All @@ -270,6 +362,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()?;
Expand Down

0 comments on commit 9e5b6eb

Please sign in to comment.