зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-09-08 08:25:51 +03:00
make operator-pending mode variant of Mode
Этот коммит содержится в:
116
examples/vim.rs
116
examples/vim.rs
@@ -18,6 +18,7 @@ enum Mode {
|
|||||||
Normal,
|
Normal,
|
||||||
Insert,
|
Insert,
|
||||||
Visual,
|
Visual,
|
||||||
|
Operator(char),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
@@ -26,6 +27,7 @@ impl Mode {
|
|||||||
Self::Normal => "type q to quit, type i to enter insert mode",
|
Self::Normal => "type q to quit, type i to enter insert mode",
|
||||||
Self::Insert => "type Esc to back to normal mode",
|
Self::Insert => "type Esc to back to normal mode",
|
||||||
Self::Visual => "type y to yank, type d to delete, type Esc to back to normal mode",
|
Self::Visual => "type y to yank, type d to delete, type Esc to back to normal mode",
|
||||||
|
Self::Operator(_) => "move cursor to apply operator",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ impl Mode {
|
|||||||
Self::Normal => Color::Reset,
|
Self::Normal => Color::Reset,
|
||||||
Self::Insert => Color::LightBlue,
|
Self::Insert => Color::LightBlue,
|
||||||
Self::Visual => Color::LightYellow,
|
Self::Visual => Color::LightYellow,
|
||||||
|
Self::Operator(_) => Color::LightGreen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,54 +47,7 @@ impl fmt::Display for Mode {
|
|||||||
Self::Normal => write!(f, "NORMAL"),
|
Self::Normal => write!(f, "NORMAL"),
|
||||||
Self::Insert => write!(f, "INSERT"),
|
Self::Insert => write!(f, "INSERT"),
|
||||||
Self::Visual => write!(f, "VISUAL"),
|
Self::Visual => write!(f, "VISUAL"),
|
||||||
}
|
Self::Operator(c) => write!(f, "OPERATOR({})", c),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<char> {
|
|
||||||
match self {
|
|
||||||
Self::D => Some('d'),
|
|
||||||
Self::Y => Some('y'),
|
|
||||||
Self::C => Some('c'),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transition(self, input: &Input, mode: Mode) -> 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 && mode == Mode::Normal => Some(Self::D),
|
|
||||||
Input {
|
|
||||||
key: Key::Char('y'),
|
|
||||||
ctrl: false,
|
|
||||||
..
|
|
||||||
} if self != Self::Y && mode == Mode::Normal => Some(Self::Y),
|
|
||||||
Input {
|
|
||||||
key: Key::Char('c'),
|
|
||||||
ctrl: false,
|
|
||||||
..
|
|
||||||
} if self != Self::C && mode == Mode::Normal => Some(Self::C),
|
|
||||||
_ => Some(Self::None),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +71,7 @@ fn main() -> io::Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut mode = Mode::Normal;
|
let mut mode = Mode::Normal;
|
||||||
let mut pending = PendingState::None;
|
let mut pending = Input::default();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Show help message and current mode in title of the block
|
// Show help message and current mode in title of the block
|
||||||
@@ -130,16 +86,20 @@ fn main() -> io::Result<()> {
|
|||||||
|
|
||||||
term.draw(|f| f.render_widget(textarea.widget(), f.size()))?;
|
term.draw(|f| f.render_widget(textarea.widget(), f.size()))?;
|
||||||
|
|
||||||
let input = crossterm::event::read()?.into();
|
let input: Input = crossterm::event::read()?.into();
|
||||||
|
if input.key == Key::Null {
|
||||||
if pending.operator().is_some() && mode != Mode::Visual {
|
continue;
|
||||||
textarea.start_selection();
|
|
||||||
}
|
}
|
||||||
// Calculate next pending key state before moving `input`
|
|
||||||
let next_pending = pending.transition(&input, mode);
|
let operator = if let Mode::Operator(op) = mode {
|
||||||
|
textarea.start_selection();
|
||||||
|
Some(op)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
mode = match mode {
|
mode = match mode {
|
||||||
Mode::Normal | Mode::Visual => match input {
|
Mode::Normal | Mode::Visual | Mode::Operator(_) => match input {
|
||||||
// Mappings in normal mode
|
// Mappings in normal mode
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('h'),
|
key: Key::Char('h'),
|
||||||
@@ -374,8 +334,17 @@ fn main() -> io::Result<()> {
|
|||||||
key: Key::Char('g'),
|
key: Key::Char('g'),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
..
|
..
|
||||||
} if pending == PendingState::G => {
|
} if matches!(
|
||||||
|
pending,
|
||||||
|
Input {
|
||||||
|
key: Key::Char('g'),
|
||||||
|
ctrl: false,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
) =>
|
||||||
|
{
|
||||||
textarea.move_cursor(CursorMove::Top);
|
textarea.move_cursor(CursorMove::Top);
|
||||||
|
pending = Input::default();
|
||||||
mode
|
mode
|
||||||
}
|
}
|
||||||
Input {
|
Input {
|
||||||
@@ -390,7 +359,7 @@ fn main() -> io::Result<()> {
|
|||||||
key: Key::Char(c),
|
key: Key::Char(c),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
..
|
..
|
||||||
} if pending.operator() == Some(c) => {
|
} if operator == Some(c) => {
|
||||||
// Handle yy, dd, cc. (This is not strictly the same behavior as Vim)
|
// Handle yy, dd, cc. (This is not strictly the same behavior as Vim)
|
||||||
textarea.move_cursor(CursorMove::Head);
|
textarea.move_cursor(CursorMove::Head);
|
||||||
textarea.start_selection();
|
textarea.start_selection();
|
||||||
@@ -401,6 +370,11 @@ fn main() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
mode
|
mode
|
||||||
}
|
}
|
||||||
|
Input {
|
||||||
|
key: Key::Char(op @ ('y' | 'd' | 'c')),
|
||||||
|
ctrl: false,
|
||||||
|
..
|
||||||
|
} if mode == Mode::Normal => Mode::Operator(op),
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('y'),
|
key: Key::Char('y'),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
@@ -425,7 +399,10 @@ fn main() -> io::Result<()> {
|
|||||||
textarea.cut();
|
textarea.cut();
|
||||||
Mode::Insert
|
Mode::Insert
|
||||||
}
|
}
|
||||||
_ => mode,
|
input => {
|
||||||
|
pending = input;
|
||||||
|
mode
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Mode::Insert => match input {
|
Mode::Insert => match input {
|
||||||
Input { key: Key::Esc, .. }
|
Input { key: Key::Esc, .. }
|
||||||
@@ -443,23 +420,24 @@ fn main() -> io::Result<()> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(next_pending) = next_pending {
|
if let Some(op) = operator {
|
||||||
mode = match pending {
|
if mode != Mode::Normal && mode != Mode::Visual {
|
||||||
PendingState::D => {
|
mode = match op {
|
||||||
textarea.cut();
|
'y' => {
|
||||||
Mode::Normal
|
|
||||||
}
|
|
||||||
PendingState::Y => {
|
|
||||||
textarea.copy();
|
textarea.copy();
|
||||||
Mode::Normal
|
Mode::Normal
|
||||||
}
|
}
|
||||||
PendingState::C => {
|
'd' => {
|
||||||
|
textarea.cut();
|
||||||
|
Mode::Normal
|
||||||
|
}
|
||||||
|
'c' => {
|
||||||
textarea.cut();
|
textarea.cut();
|
||||||
Mode::Insert
|
Mode::Insert
|
||||||
}
|
}
|
||||||
_ => mode,
|
_ => mode,
|
||||||
};
|
}
|
||||||
pending = next_pending;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user