1
0
зеркало из https://github.com/glebtv/tui-textarea.git synced 2026-09-08 00:15:50 +03:00

implement visual mode for vim example

Этот коммит содержится в:
rhysd
2023-11-16 18:15:56 +09:00
родитель 409185f653
Коммит ad8ad19e83

Просмотреть файл

@@ -13,9 +13,11 @@ use std::io;
use std::io::BufRead; use std::io::BufRead;
use tui_textarea::{CursorMove, Input, Key, Scrolling, TextArea}; use tui_textarea::{CursorMove, Input, Key, Scrolling, TextArea};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode { enum Mode {
Normal, Normal,
Insert, Insert,
Visual,
} }
impl Mode { impl Mode {
@@ -23,6 +25,7 @@ impl Mode {
match self { match self {
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",
} }
} }
@@ -30,6 +33,7 @@ impl Mode {
match self { match self {
Self::Normal => Color::Reset, Self::Normal => Color::Reset,
Self::Insert => Color::LightBlue, Self::Insert => Color::LightBlue,
Self::Visual => Color::LightYellow,
} }
} }
} }
@@ -39,6 +43,7 @@ impl fmt::Display for Mode {
match self { match self {
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"),
} }
} }
} }
@@ -63,7 +68,7 @@ impl PendingState {
} }
} }
fn transition(self, input: &Input) -> Option<Self> { fn transition(self, input: &Input, mode: Mode) -> Option<Self> {
match input { match input {
Input { key: Key::Null, .. } => None, Input { key: Key::Null, .. } => None,
Input { Input {
@@ -75,17 +80,17 @@ impl PendingState {
key: Key::Char('d'), key: Key::Char('d'),
ctrl: false, ctrl: false,
.. ..
} if self != Self::D => Some(Self::D), } if self != Self::D && mode == Mode::Normal => Some(Self::D),
Input { Input {
key: Key::Char('y'), key: Key::Char('y'),
ctrl: false, ctrl: false,
.. ..
} if self != Self::Y => Some(Self::Y), } if self != Self::Y && mode == Mode::Normal => Some(Self::Y),
Input { Input {
key: Key::Char('c'), key: Key::Char('c'),
ctrl: false, ctrl: false,
.. ..
} if self != Self::C => Some(Self::C), } if self != Self::C && mode == Mode::Normal => Some(Self::C),
_ => Some(Self::None), _ => Some(Self::None),
} }
} }
@@ -127,65 +132,93 @@ fn main() -> io::Result<()> {
let input = crossterm::event::read()?.into(); let input = crossterm::event::read()?.into();
if pending.operator().is_some() && !textarea.is_selecting() { if pending.operator().is_some() && mode != Mode::Visual {
textarea.start_selection(); textarea.start_selection();
} }
let next_pending = pending.transition(&input); // Calculate next state before moving `input` // Calculate next pending key state before moving `input`
let next_pending = pending.transition(&input, mode);
match mode { mode = match mode {
Mode::Normal => match input { Mode::Normal | Mode::Visual => match input {
// Mappings in normal mode // Mappings in normal mode
Input { Input {
key: Key::Char('h'), key: Key::Char('h'),
.. ..
} => textarea.move_cursor(CursorMove::Back), } => {
textarea.move_cursor(CursorMove::Back);
mode
}
Input { Input {
key: Key::Char('j'), key: Key::Char('j'),
.. ..
} => textarea.move_cursor(CursorMove::Down), } => {
textarea.move_cursor(CursorMove::Down);
mode
}
Input { Input {
key: Key::Char('k'), key: Key::Char('k'),
.. ..
} => textarea.move_cursor(CursorMove::Up), } => {
textarea.move_cursor(CursorMove::Up);
mode
}
Input { Input {
key: Key::Char('l'), key: Key::Char('l'),
.. ..
} => textarea.move_cursor(CursorMove::Forward), } => {
textarea.move_cursor(CursorMove::Forward);
mode
}
Input { Input {
key: Key::Char('w'), key: Key::Char('w'),
.. ..
} => textarea.move_cursor(CursorMove::WordForward), } => {
textarea.move_cursor(CursorMove::WordForward);
mode
}
Input { Input {
key: Key::Char('b'), key: Key::Char('b'),
ctrl: false, ctrl: false,
.. ..
} => textarea.move_cursor(CursorMove::WordBack), } => {
textarea.move_cursor(CursorMove::WordBack);
mode
}
Input { Input {
key: Key::Char('^'), key: Key::Char('^'),
.. ..
} => textarea.move_cursor(CursorMove::Head), } => {
textarea.move_cursor(CursorMove::Head);
mode
}
Input { Input {
key: Key::Char('$'), key: Key::Char('$'),
.. ..
} => textarea.move_cursor(CursorMove::End), } => {
textarea.move_cursor(CursorMove::End);
mode
}
Input { Input {
key: Key::Char('D'), key: Key::Char('D'),
.. ..
} => { } => {
textarea.delete_line_by_end(); textarea.delete_line_by_end();
Mode::Normal
} }
Input { Input {
key: Key::Char('C'), key: Key::Char('C'),
.. ..
} => { } => {
textarea.delete_line_by_end(); textarea.delete_line_by_end();
mode = Mode::Insert; textarea.cancel_selection();
Mode::Insert
} }
Input { Input {
key: Key::Char('p'), key: Key::Char('p'),
.. ..
} => { } => {
textarea.paste(); textarea.paste();
Mode::Normal
} }
Input { Input {
key: Key::Char('u'), key: Key::Char('u'),
@@ -193,6 +226,7 @@ fn main() -> io::Result<()> {
.. ..
} => { } => {
textarea.undo(); textarea.undo();
Mode::Normal
} }
Input { Input {
key: Key::Char('r'), key: Key::Char('r'),
@@ -200,30 +234,37 @@ fn main() -> io::Result<()> {
.. ..
} => { } => {
textarea.redo(); textarea.redo();
Mode::Normal
} }
Input { Input {
key: Key::Char('x'), key: Key::Char('x'),
.. ..
} => { } => {
textarea.delete_next_char(); textarea.delete_next_char();
Mode::Normal
} }
Input { Input {
key: Key::Char('i'), key: Key::Char('i'),
.. ..
} => mode = Mode::Insert, } => {
textarea.cancel_selection();
Mode::Insert
}
Input { Input {
key: Key::Char('a'), key: Key::Char('a'),
.. ..
} => { } => {
textarea.cancel_selection();
textarea.move_cursor(CursorMove::Forward); textarea.move_cursor(CursorMove::Forward);
mode = Mode::Insert; Mode::Insert
} }
Input { Input {
key: Key::Char('A'), key: Key::Char('A'),
.. ..
} => { } => {
textarea.cancel_selection();
textarea.move_cursor(CursorMove::End); textarea.move_cursor(CursorMove::End);
mode = Mode::Insert; Mode::Insert
} }
Input { Input {
key: Key::Char('o'), key: Key::Char('o'),
@@ -231,7 +272,7 @@ fn main() -> io::Result<()> {
} => { } => {
textarea.move_cursor(CursorMove::End); textarea.move_cursor(CursorMove::End);
textarea.insert_newline(); textarea.insert_newline();
mode = Mode::Insert; Mode::Insert
} }
Input { Input {
key: Key::Char('O'), key: Key::Char('O'),
@@ -240,14 +281,15 @@ fn main() -> io::Result<()> {
textarea.move_cursor(CursorMove::Head); textarea.move_cursor(CursorMove::Head);
textarea.insert_newline(); textarea.insert_newline();
textarea.move_cursor(CursorMove::Up); textarea.move_cursor(CursorMove::Up);
mode = Mode::Insert; Mode::Insert
} }
Input { Input {
key: Key::Char('I'), key: Key::Char('I'),
.. ..
} => { } => {
textarea.cancel_selection();
textarea.move_cursor(CursorMove::Head); textarea.move_cursor(CursorMove::Head);
mode = Mode::Insert; Mode::Insert
} }
Input { Input {
key: Key::Char('q'), key: Key::Char('q'),
@@ -257,53 +299,84 @@ fn main() -> io::Result<()> {
key: Key::Char('e'), key: Key::Char('e'),
ctrl: true, ctrl: true,
.. ..
} => textarea.scroll((1, 0)), } => {
textarea.scroll((1, 0));
mode
}
Input { Input {
key: Key::Char('y'), key: Key::Char('y'),
ctrl: true, ctrl: true,
.. ..
} => textarea.scroll((-1, 0)), } => {
textarea.scroll((-1, 0));
mode
}
Input { Input {
key: Key::Char('d'), key: Key::Char('d'),
ctrl: true, ctrl: true,
.. ..
} => textarea.scroll(Scrolling::HalfPageDown), } => {
textarea.scroll(Scrolling::HalfPageDown);
mode
}
Input { Input {
key: Key::Char('u'), key: Key::Char('u'),
ctrl: true, ctrl: true,
.. ..
} => textarea.scroll(Scrolling::HalfPageUp), } => {
textarea.scroll(Scrolling::HalfPageUp);
mode
}
Input { Input {
key: Key::Char('f'), key: Key::Char('f'),
ctrl: true, ctrl: true,
.. ..
} => textarea.scroll(Scrolling::PageDown), } => {
textarea.scroll(Scrolling::PageDown);
mode
}
Input { Input {
key: Key::Char('b'), key: Key::Char('b'),
ctrl: true, ctrl: true,
.. ..
} => textarea.scroll(Scrolling::PageUp), } => {
textarea.scroll(Scrolling::PageUp);
mode
}
Input { Input {
key: Key::Char('v'), key: Key::Char('v'),
ctrl: false, ctrl: false,
.. ..
} => textarea.start_selection(), } if mode == Mode::Normal => {
textarea.start_selection();
Mode::Visual
}
Input { Input {
key: Key::Char('V'), key: Key::Char('V'),
ctrl: false, ctrl: false,
.. ..
} => { } if mode == Mode::Normal => {
textarea.move_cursor(CursorMove::Head); textarea.move_cursor(CursorMove::Head);
textarea.start_selection(); textarea.start_selection();
textarea.move_cursor(CursorMove::End); textarea.move_cursor(CursorMove::End);
Mode::Visual
}
Input { key: Key::Esc, .. }
| Input {
key: Key::Char('v'),
ctrl: false,
..
} if mode == Mode::Visual => {
textarea.cancel_selection();
Mode::Normal
} }
Input { key: Key::Esc, .. } => textarea.cancel_selection(),
Input { Input {
key: Key::Char('g'), key: Key::Char('g'),
ctrl: false, ctrl: false,
.. ..
} if pending == PendingState::G => { } if pending == PendingState::G => {
textarea.move_cursor(CursorMove::Top); textarea.move_cursor(CursorMove::Top);
mode
} }
Input { Input {
key: Key::Char('G'), key: Key::Char('G'),
@@ -311,6 +384,7 @@ fn main() -> io::Result<()> {
.. ..
} => { } => {
textarea.move_cursor(CursorMove::Bottom); textarea.move_cursor(CursorMove::Bottom);
mode
} }
Input { Input {
key: Key::Char(c), key: Key::Char(c),
@@ -325,28 +399,33 @@ fn main() -> io::Result<()> {
if cursor == textarea.cursor() { if cursor == textarea.cursor() {
textarea.move_cursor(CursorMove::End); // At the last line, move to end of the line instead textarea.move_cursor(CursorMove::End); // At the last line, move to end of the line instead
} }
mode
} }
Input { Input {
key: Key::Char('y'), key: Key::Char('y'),
ctrl: false, ctrl: false,
.. ..
} if textarea.is_selecting() => textarea.copy(), } if mode == Mode::Visual => {
textarea.copy();
Mode::Normal
}
Input { Input {
key: Key::Char('d'), key: Key::Char('d'),
ctrl: false, ctrl: false,
.. ..
} if textarea.is_selecting() => { } if mode == Mode::Visual => {
textarea.cut(); textarea.cut();
Mode::Normal
} }
Input { Input {
key: Key::Char('c'), key: Key::Char('c'),
ctrl: false, ctrl: false,
.. ..
} if textarea.is_selecting() => { } if mode == Mode::Visual => {
textarea.cut(); textarea.cut();
mode = Mode::Insert; Mode::Insert
} }
_ => {} _ => mode,
}, },
Mode::Insert => match input { Mode::Insert => match input {
Input { key: Key::Esc, .. } Input { key: Key::Esc, .. }
@@ -355,26 +434,31 @@ fn main() -> io::Result<()> {
ctrl: true, ctrl: true,
.. ..
} => { } => {
mode = Mode::Normal; // Back to normal mode with Esc or Ctrl+C Mode::Normal // Back to normal mode with Esc or Ctrl+C
} }
input => { input => {
textarea.input(input); // Use default key mappings in insert mode textarea.input(input); // Use default key mappings in insert mode
mode
} }
}, },
} };
if let Some(next_pending) = next_pending { if let Some(next_pending) = next_pending {
match pending { mode = match pending {
PendingState::D => { PendingState::D => {
textarea.cut(); textarea.cut();
Mode::Normal
}
PendingState::Y => {
textarea.copy();
Mode::Normal
} }
PendingState::Y => textarea.copy(),
PendingState::C => { PendingState::C => {
textarea.cut(); textarea.cut();
mode = Mode::Insert; Mode::Insert
} }
_ => {} _ => mode,
} };
pending = next_pending; pending = next_pending;
} }
} }