diff --git a/Cargo.toml b/Cargo.toml
index 0701def..0ff0735 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -70,7 +70,7 @@ name = "variable"
required-features = ["crossterm"]
[[example]]
-name = "modal"
+name = "vim"
required-features = ["crossterm"]
[[example]]
diff --git a/README.md b/README.md
index 0a21d2f..519c718 100644
--- a/README.md
+++ b/README.md
@@ -77,13 +77,15 @@ cargo run --example variable
Simple textarea with variable height following the number of lines.
-### [`modal`](./examples/modal.rs)
+### [`vim`](./examples/vim.rs)
```sh
-cargo run --example modal
+cargo run --example vim
```
-Simple modal text editor like `vi`.
+Vim-like modal text editor. Vim emulation is implemented as a state machine.
+
+
### [`popup_placeholder`](./examples/popup_placeholder.rs)
@@ -513,49 +515,9 @@ notify how to move the cursor.
| `textarea.scroll(Scrolling::HalfPageUp)` | Scroll up the viewport by half-page |
| `textarea.scroll((row, col))` | Scroll down the viewport to (row, col) position |
-To define your own key mappings, simply call the above methods in your code instead of `TextArea::input()` method. The
-following example defines modal key mappings like Vim.
+To define your own key mappings, simply call the above methods in your code instead of `TextArea::input()` method.
-```rust,ignore
-use crossterm::event::{Event, read};
-use tui_textarea::{Input, Key, CursorMove, Scrolling};
-
-let mut textarea = ...;
-
-enum Mode {
- Normal,
- Insert,
-}
-
-let mut mode = Mode::Normal;
-
-// Event loop
-loop {
- // ...
-
- match mode {
- Mode::Normal => match read()?.into() {
- Input { key: Key::Char('h'), .. } => textarea.move_cursor(CursorMove::Back),
- Input { key: Key::Char('j'), .. } => textarea.move_cursor(CursorMove::Down),
- Input { key: Key::Char('k'), .. } => textarea.move_cursor(CursorMove::Up),
- Input { key: Key::Char('l'), .. } => textarea.move_cursor(CursorMove::Forward),
- Input { key: Key::Char('i'), .. } => mode = Mode::Insert, // Enter insert mode
- // ...Add more mappings
- _ => {},
- },
- Mode::Insert => match read()?.into() {
- Input { key: Key::Esc, .. } => {
- mode = Mode::Normal; // Back to normal mode with Esc or Ctrl+C
- }
- input => {
- textarea.input(input); // Use default key mappings in insert mode
- }
- },
- }
-}
-```
-
-See [`modal` example](./examples/modal.rs) for working example. It implements more Vim-like key mappings.
+See the [`vim` example](./examples/vim.rs) for working example. It implements more Vim-like key modal mappings.
If you don't want to use default key mappings, `TextArea::input_without_shortcuts()` method can be used instead of
`TextArea::input()`. The method only handles very basic operations such as inserting/deleting single characters, tabs,
diff --git a/examples/modal.rs b/examples/modal.rs
deleted file mode 100644
index 75a5068..0000000
--- a/examples/modal.rs
+++ /dev/null
@@ -1,393 +0,0 @@
-use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
-use crossterm::terminal::{
- disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
-};
-use ratatui::backend::CrosstermBackend;
-use ratatui::style::{Color, Modifier, Style};
-use ratatui::widgets::{Block, Borders};
-use ratatui::Terminal;
-use std::env;
-use std::fmt;
-use std::fs;
-use std::io;
-use std::io::BufRead;
-use tui_textarea::{CursorMove, Input, Key, Scrolling, TextArea};
-
-enum Mode {
- Normal,
- Insert,
-}
-
-impl Mode {
- fn help_message(&self) -> &'static str {
- match self {
- Self::Normal => "type q to quit, type i to enter insert mode",
- Self::Insert => "type Esc to back to normal mode",
- }
- }
-
- fn cursor_color(&self) -> Color {
- match self {
- Self::Normal => Color::Reset,
- Self::Insert => Color::LightBlue,
- }
- }
-}
-
-impl fmt::Display for Mode {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
- match self {
- Self::Normal => write!(f, "NORMAL"),
- Self::Insert => write!(f, "INSERT"),
- }
- }
-}
-
-// 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();
-
- enable_raw_mode()?;
- crossterm::execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
- let backend = CrosstermBackend::new(stdout);
- let mut term = Terminal::new(backend)?;
-
- let mut textarea = if let Some(path) = env::args().nth(1) {
- let file = fs::File::open(path)?;
- io::BufReader::new(file)
- .lines()
- .collect::>()?
- } else {
- TextArea::default()
- };
-
- 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());
- let block = Block::default().borders(Borders::ALL).title(title);
- textarea.set_block(block);
-
- // Change the cursor color looking at current mode
- let color = mode.cursor_color();
- let style = Style::default().fg(color).add_modifier(Modifier::REVERSED);
- textarea.set_cursor_style(style);
-
- 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
- Input {
- key: Key::Char('h'),
- ..
- } => textarea.move_cursor(CursorMove::Back),
- Input {
- key: Key::Char('j'),
- ..
- } => textarea.move_cursor(CursorMove::Down),
- Input {
- key: Key::Char('k'),
- ..
- } => textarea.move_cursor(CursorMove::Up),
- Input {
- key: Key::Char('l'),
- ..
- } => textarea.move_cursor(CursorMove::Forward),
- Input {
- key: Key::Char('w'),
- ..
- } => textarea.move_cursor(CursorMove::WordForward),
- Input {
- key: Key::Char('b'),
- ctrl: false,
- ..
- } => textarea.move_cursor(CursorMove::WordBack),
- Input {
- key: Key::Char('^'),
- ..
- } => textarea.move_cursor(CursorMove::Head),
- Input {
- key: Key::Char('$'),
- ..
- } => textarea.move_cursor(CursorMove::End),
- Input {
- key: Key::Char('D'),
- ..
- } => {
- textarea.delete_line_by_end();
- }
- Input {
- key: Key::Char('C'),
- ..
- } => {
- textarea.delete_line_by_end();
- mode = Mode::Insert;
- }
- Input {
- key: Key::Char('p'),
- ..
- } => {
- textarea.paste();
- }
- Input {
- key: Key::Char('u'),
- ctrl: false,
- ..
- } => {
- textarea.undo();
- }
- Input {
- key: Key::Char('r'),
- ctrl: true,
- ..
- } => {
- textarea.redo();
- }
- Input {
- key: Key::Char('x'),
- ..
- } => {
- textarea.delete_next_char();
- }
- Input {
- key: Key::Char('i'),
- ..
- } => mode = Mode::Insert,
- Input {
- key: Key::Char('a'),
- ..
- } => {
- textarea.move_cursor(CursorMove::Forward);
- mode = Mode::Insert;
- }
- Input {
- key: Key::Char('A'),
- ..
- } => {
- textarea.move_cursor(CursorMove::End);
- mode = Mode::Insert;
- }
- Input {
- key: Key::Char('o'),
- ..
- } => {
- textarea.move_cursor(CursorMove::End);
- textarea.insert_newline();
- mode = Mode::Insert;
- }
- Input {
- key: Key::Char('O'),
- ..
- } => {
- textarea.move_cursor(CursorMove::Head);
- textarea.insert_newline();
- textarea.move_cursor(CursorMove::Up);
- mode = Mode::Insert;
- }
- Input {
- key: Key::Char('I'),
- ..
- } => {
- textarea.move_cursor(CursorMove::Head);
- mode = Mode::Insert;
- }
- Input {
- key: Key::Char('q'),
- ..
- } => break,
- Input {
- key: Key::Char('e'),
- ctrl: true,
- ..
- } => textarea.scroll((1, 0)),
- Input {
- key: Key::Char('y'),
- ctrl: true,
- ..
- } => textarea.scroll((-1, 0)),
- Input {
- key: Key::Char('d'),
- ctrl: true,
- ..
- } => textarea.scroll(Scrolling::HalfPageDown),
- Input {
- key: Key::Char('u'),
- ctrl: true,
- ..
- } => textarea.scroll(Scrolling::HalfPageUp),
- Input {
- key: Key::Char('f'),
- ctrl: true,
- ..
- } => textarea.scroll(Scrolling::PageDown),
- Input {
- key: Key::Char('b'),
- ctrl: true,
- ..
- } => textarea.scroll(Scrolling::PageUp),
- Input {
- key: Key::Char('v'),
- ctrl: false,
- ..
- } => textarea.start_selection(),
- Input {
- key: Key::Char('V'),
- ctrl: false,
- ..
- } => {
- textarea.move_cursor(CursorMove::Head);
- textarea.start_selection();
- 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();
- let cursor = textarea.cursor();
- textarea.move_cursor(CursorMove::Down);
- if cursor == textarea.cursor() {
- textarea.move_cursor(CursorMove::End); // At the last line, move to end of the line instead
- }
- }
- Input {
- key: Key::Char('y'),
- ctrl: false,
- ..
- } if textarea.is_selecting() => textarea.copy(),
- Input {
- key: Key::Char('d'),
- ctrl: false,
- ..
- } 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 {
- Input { key: Key::Esc, .. }
- | Input {
- key: Key::Char('c'),
- ctrl: true,
- ..
- } => {
- mode = Mode::Normal; // Back to normal mode with Esc or Ctrl+C
- }
- input => {
- textarea.input(input); // Use default key mappings in insert mode
- }
- },
- }
-
- 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()?;
- crossterm::execute!(
- term.backend_mut(),
- LeaveAlternateScreen,
- DisableMouseCapture
- )?;
- term.show_cursor()?;
-
- println!("Lines: {:?}", textarea.lines());
-
- Ok(())
-}
diff --git a/examples/vim.rs b/examples/vim.rs
new file mode 100644
index 0000000..e49366f
--- /dev/null
+++ b/examples/vim.rs
@@ -0,0 +1,432 @@
+use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
+use crossterm::terminal::{
+ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
+};
+use ratatui::backend::CrosstermBackend;
+use ratatui::style::{Color, Modifier, Style};
+use ratatui::widgets::{Block, Borders};
+use ratatui::Terminal;
+use std::env;
+use std::fmt;
+use std::fs;
+use std::io;
+use std::io::BufRead;
+use tui_textarea::{CursorMove, Input, Key, Scrolling, TextArea};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum Mode {
+ Normal,
+ Insert,
+ Visual,
+ Operator(char),
+}
+
+impl Mode {
+ fn help_message(&self) -> &'static str {
+ match self {
+ Self::Normal => "type q to quit, type i to enter insert 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::Operator(_) => "move cursor to apply operator",
+ }
+ }
+
+ fn cursor_color(&self) -> Color {
+ match self {
+ Self::Normal => Color::Reset,
+ Self::Insert => Color::LightBlue,
+ Self::Visual => Color::LightYellow,
+ Self::Operator(_) => Color::LightGreen,
+ }
+ }
+}
+
+impl fmt::Display for Mode {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ match self {
+ Self::Normal => write!(f, "NORMAL"),
+ Self::Insert => write!(f, "INSERT"),
+ Self::Visual => write!(f, "VISUAL"),
+ Self::Operator(c) => write!(f, "OPERATOR({})", c),
+ }
+ }
+}
+
+// State of Vim emulation
+struct Vim {
+ mode: Mode,
+ pending: Input, // Pending input to handle a sequence with two keys like gg
+}
+
+// How the Vim emulation state transitions
+enum Transition {
+ Nop,
+ Mode(Mode),
+ Pending(Input),
+ Quit,
+}
+
+impl Vim {
+ fn new(mode: Mode) -> Self {
+ Self {
+ mode,
+ pending: Input::default(),
+ }
+ }
+
+ fn with_pending(self, pending: Input) -> Self {
+ Self {
+ mode: self.mode,
+ pending,
+ }
+ }
+
+ fn input(&self, input: Input, textarea: &mut TextArea<'_>) -> Transition {
+ if input.key == Key::Null {
+ return Transition::Nop;
+ }
+
+ match self.mode {
+ Mode::Normal | Mode::Visual | Mode::Operator(_) => {
+ match input {
+ Input {
+ key: Key::Char('h'),
+ ..
+ } => textarea.move_cursor(CursorMove::Back),
+ Input {
+ key: Key::Char('j'),
+ ..
+ } => textarea.move_cursor(CursorMove::Down),
+ Input {
+ key: Key::Char('k'),
+ ..
+ } => textarea.move_cursor(CursorMove::Up),
+ Input {
+ key: Key::Char('l'),
+ ..
+ } => textarea.move_cursor(CursorMove::Forward),
+ Input {
+ key: Key::Char('w'),
+ ..
+ } => textarea.move_cursor(CursorMove::WordForward),
+ Input {
+ key: Key::Char('b'),
+ ctrl: false,
+ ..
+ } => textarea.move_cursor(CursorMove::WordBack),
+ Input {
+ key: Key::Char('^'),
+ ..
+ } => textarea.move_cursor(CursorMove::Head),
+ Input {
+ key: Key::Char('$'),
+ ..
+ } => textarea.move_cursor(CursorMove::End),
+ Input {
+ key: Key::Char('D'),
+ ..
+ } => {
+ textarea.delete_line_by_end();
+ return Transition::Mode(Mode::Normal);
+ }
+ Input {
+ key: Key::Char('C'),
+ ..
+ } => {
+ textarea.delete_line_by_end();
+ textarea.cancel_selection();
+ return Transition::Mode(Mode::Insert);
+ }
+ Input {
+ key: Key::Char('p'),
+ ..
+ } => {
+ textarea.paste();
+ return Transition::Mode(Mode::Normal);
+ }
+ Input {
+ key: Key::Char('u'),
+ ctrl: false,
+ ..
+ } => {
+ textarea.undo();
+ return Transition::Mode(Mode::Normal);
+ }
+ Input {
+ key: Key::Char('r'),
+ ctrl: true,
+ ..
+ } => {
+ textarea.redo();
+ return Transition::Mode(Mode::Normal);
+ }
+ Input {
+ key: Key::Char('x'),
+ ..
+ } => {
+ textarea.delete_next_char();
+ return Transition::Mode(Mode::Normal);
+ }
+ Input {
+ key: Key::Char('i'),
+ ..
+ } => {
+ textarea.cancel_selection();
+ return Transition::Mode(Mode::Insert);
+ }
+ Input {
+ key: Key::Char('a'),
+ ..
+ } => {
+ textarea.cancel_selection();
+ textarea.move_cursor(CursorMove::Forward);
+ return Transition::Mode(Mode::Insert);
+ }
+ Input {
+ key: Key::Char('A'),
+ ..
+ } => {
+ textarea.cancel_selection();
+ textarea.move_cursor(CursorMove::End);
+ return Transition::Mode(Mode::Insert);
+ }
+ Input {
+ key: Key::Char('o'),
+ ..
+ } => {
+ textarea.move_cursor(CursorMove::End);
+ textarea.insert_newline();
+ return Transition::Mode(Mode::Insert);
+ }
+ Input {
+ key: Key::Char('O'),
+ ..
+ } => {
+ textarea.move_cursor(CursorMove::Head);
+ textarea.insert_newline();
+ textarea.move_cursor(CursorMove::Up);
+ return Transition::Mode(Mode::Insert);
+ }
+ Input {
+ key: Key::Char('I'),
+ ..
+ } => {
+ textarea.cancel_selection();
+ textarea.move_cursor(CursorMove::Head);
+ return Transition::Mode(Mode::Insert);
+ }
+ Input {
+ key: Key::Char('q'),
+ ..
+ } => return Transition::Quit,
+ Input {
+ key: Key::Char('e'),
+ ctrl: true,
+ ..
+ } => textarea.scroll((1, 0)),
+ Input {
+ key: Key::Char('y'),
+ ctrl: true,
+ ..
+ } => textarea.scroll((-1, 0)),
+ Input {
+ key: Key::Char('d'),
+ ctrl: true,
+ ..
+ } => textarea.scroll(Scrolling::HalfPageDown),
+ Input {
+ key: Key::Char('u'),
+ ctrl: true,
+ ..
+ } => textarea.scroll(Scrolling::HalfPageUp),
+ Input {
+ key: Key::Char('f'),
+ ctrl: true,
+ ..
+ } => textarea.scroll(Scrolling::PageDown),
+ Input {
+ key: Key::Char('b'),
+ ctrl: true,
+ ..
+ } => textarea.scroll(Scrolling::PageUp),
+ Input {
+ key: Key::Char('v'),
+ ctrl: false,
+ ..
+ } if self.mode == Mode::Normal => {
+ textarea.start_selection();
+ return Transition::Mode(Mode::Visual);
+ }
+ Input {
+ key: Key::Char('V'),
+ ctrl: false,
+ ..
+ } if self.mode == Mode::Normal => {
+ textarea.move_cursor(CursorMove::Head);
+ textarea.start_selection();
+ textarea.move_cursor(CursorMove::End);
+ return Transition::Mode(Mode::Visual);
+ }
+ Input { key: Key::Esc, .. }
+ | Input {
+ key: Key::Char('v'),
+ ctrl: false,
+ ..
+ } if self.mode == Mode::Visual => {
+ textarea.cancel_selection();
+ return Transition::Mode(Mode::Normal);
+ }
+ Input {
+ key: Key::Char('g'),
+ ctrl: false,
+ ..
+ } if matches!(
+ self.pending,
+ Input {
+ key: Key::Char('g'),
+ ctrl: false,
+ ..
+ }
+ ) =>
+ {
+ 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 self.mode == Mode::Operator(c) => {
+ // Handle yy, dd, cc. (This is not strictly the same behavior as Vim)
+ textarea.move_cursor(CursorMove::Head);
+ textarea.start_selection();
+ let cursor = textarea.cursor();
+ textarea.move_cursor(CursorMove::Down);
+ if cursor == textarea.cursor() {
+ textarea.move_cursor(CursorMove::End); // At the last line, move to end of the line instead
+ }
+ }
+ Input {
+ key: Key::Char(op @ ('y' | 'd' | 'c')),
+ ctrl: false,
+ ..
+ } if self.mode == Mode::Normal => {
+ textarea.start_selection();
+ return Transition::Mode(Mode::Operator(op));
+ }
+ Input {
+ key: Key::Char('y'),
+ ctrl: false,
+ ..
+ } if self.mode == Mode::Visual => {
+ textarea.copy();
+ return Transition::Mode(Mode::Normal);
+ }
+ Input {
+ key: Key::Char('d'),
+ ctrl: false,
+ ..
+ } if self.mode == Mode::Visual => {
+ textarea.cut();
+ return Transition::Mode(Mode::Normal);
+ }
+ Input {
+ key: Key::Char('c'),
+ ctrl: false,
+ ..
+ } if self.mode == Mode::Visual => {
+ textarea.cut();
+ return Transition::Mode(Mode::Insert);
+ }
+ input => return Transition::Pending(input),
+ }
+
+ // Handle the pending operator
+ match self.mode {
+ Mode::Operator('y') => {
+ textarea.copy();
+ Transition::Mode(Mode::Normal)
+ }
+ Mode::Operator('d') => {
+ textarea.cut();
+ Transition::Mode(Mode::Normal)
+ }
+ Mode::Operator('c') => {
+ textarea.cut();
+ Transition::Mode(Mode::Insert)
+ }
+ _ => Transition::Nop,
+ }
+ }
+ Mode::Insert => match input {
+ Input { key: Key::Esc, .. }
+ | Input {
+ key: Key::Char('c'),
+ ctrl: true,
+ ..
+ } => Transition::Mode(Mode::Normal),
+ input => {
+ textarea.input(input); // Use default key mappings in insert mode
+ Transition::Mode(Mode::Insert)
+ }
+ },
+ }
+ }
+}
+
+fn main() -> io::Result<()> {
+ let stdout = io::stdout();
+ let mut stdout = stdout.lock();
+
+ enable_raw_mode()?;
+ crossterm::execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
+ let backend = CrosstermBackend::new(stdout);
+ let mut term = Terminal::new(backend)?;
+
+ let mut textarea = if let Some(path) = env::args().nth(1) {
+ let file = fs::File::open(path)?;
+ io::BufReader::new(file)
+ .lines()
+ .collect::>()?
+ } else {
+ TextArea::default()
+ };
+
+ let mut vim = Vim::new(Mode::Normal);
+ loop {
+ // Show help message and current mode in title of the block
+ let title = format!("{} MODE ({})", vim.mode, vim.mode.help_message());
+ let block = Block::default().borders(Borders::ALL).title(title);
+ textarea.set_block(block);
+
+ // Change the cursor color looking at current mode
+ let color = vim.mode.cursor_color();
+ let style = Style::default().fg(color).add_modifier(Modifier::REVERSED);
+ textarea.set_cursor_style(style);
+
+ term.draw(|f| f.render_widget(textarea.widget(), f.size()))?;
+
+ vim = match vim.input(crossterm::event::read()?.into(), &mut textarea) {
+ Transition::Nop => vim,
+ Transition::Mode(mode) => Vim::new(mode),
+ Transition::Pending(input) => vim.with_pending(input),
+ Transition::Quit => break,
+ }
+ }
+
+ disable_raw_mode()?;
+ crossterm::execute!(
+ term.backend_mut(),
+ LeaveAlternateScreen,
+ DisableMouseCapture
+ )?;
+ term.show_cursor()?;
+
+ println!("Lines: {:?}", textarea.lines());
+
+ Ok(())
+}