diff --git a/README.md b/README.md index 6da46dc..3230b45 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Multi-line text editor can be easily put as part of your TUI application. - Line number - Cursor line highlight - Search with regular expressions +- Text selection - Mouse scrolling - Yank support. Paste text deleted with `C-k`, `C-j`, ... - Backend agnostic. [crossterm][], [termion][], [termwiz][], and your own backend are all supported @@ -250,6 +251,8 @@ Default key mappings are as follows: | `Alt+D`, `Alt+Delete` | Delete one word next to cursor | | `Ctrl+U` | Undo | | `Ctrl+R` | Redo | +| `Ctrl+C` | Copy selected text | +| `Ctrl+X` | Cut selected text | | `Ctrl+Y` | Paste yanked text | | `Ctrl+F`, `→` | Move cursor forward by one character | | `Ctrl+B`, `←` | Move cursor backward by one character | @@ -480,7 +483,11 @@ notify how to move the cursor. | `textarea.delete_next_word()` | Delete one word next to cursor | | `textarea.undo()` | Undo | | `textarea.redo()` | Redo | +| `textarea.copy()` | Copy selected text | +| `textarea.cut()` | Cut selected text | | `textarea.paste()` | Paste yanked text | +| `textarea.start_selection()` | Start text selection | +| `textarea.cancel_selection()` | Cancel text selection | | `textarea.move_cursor(CursorMove::Forward)` | Move cursor forward by one character | | `textarea.move_cursor(CursorMove::Back)` | Move cursor backward by one character | | `textarea.move_cursor(CursorMove::Up)` | Move cursor up by one line | diff --git a/examples/editor.rs b/examples/editor.rs index 5d03c43..3680348 100644 --- a/examples/editor.rs +++ b/examples/editor.rs @@ -248,7 +248,7 @@ impl<'a> Editor<'a> { Span::raw(" to save, "), Span::styled("^G", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to search, "), - Span::styled("^X", Style::default().add_modifier(Modifier::BOLD)), + Span::styled("^T", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to switch buffer"), ]) }; @@ -314,7 +314,7 @@ impl<'a> Editor<'a> { .. } => break, Input { - key: Key::Char('x'), + key: Key::Char('t'), ctrl: true, .. } => { diff --git a/examples/modal.rs b/examples/modal.rs index 6ee70e2..633ff5a 100644 --- a/examples/modal.rs +++ b/examples/modal.rs @@ -60,7 +60,7 @@ fn main() -> io::Result<()> { } else { TextArea::default() }; - textarea.set_selection_style(Style::default().bg(Color::Red)); + let mut mode = Mode::Normal; loop { // Show help message and current mode in title of the block @@ -75,41 +75,32 @@ fn main() -> io::Result<()> { term.draw(|f| f.render_widget(textarea.widget(), f.size()))?; - let input: Input = crossterm::event::read()?.into(); - - // since this sample doesnt use input a lot of the time - // it must call should_select - - textarea.should_select(&input); + let input = crossterm::event::read()?.into(); match mode { Mode::Normal => match input { // Mappings in normal mode - - // note that the navigation keys can be pressed with or without shift - // if the user is selecting text - // so we have to look for 'h' and 'H' etc. Input { - key: Key::Char('h' | 'H'), + key: Key::Char('h'), .. } => textarea.move_cursor(CursorMove::Back), Input { - key: Key::Char('j' | 'J'), + key: Key::Char('j'), .. } => textarea.move_cursor(CursorMove::Down), Input { - key: Key::Char('k' | 'K'), + key: Key::Char('k'), .. } => textarea.move_cursor(CursorMove::Up), Input { - key: Key::Char('l' | 'L'), + key: Key::Char('l'), .. } => textarea.move_cursor(CursorMove::Forward), Input { - key: Key::Char('w' | 'W'), + key: Key::Char('w'), .. } => textarea.move_cursor(CursorMove::WordForward), Input { - key: Key::Char('b' | 'B'), + key: Key::Char('b'), ctrl: false, .. } => textarea.move_cursor(CursorMove::WordBack), @@ -140,13 +131,6 @@ fn main() -> io::Result<()> { } => { textarea.paste(); } - Input { - key: Key::Char('y'), - ctrl: false, - .. - } => { - textarea.copy(); - } Input { key: Key::Char('u'), ctrl: false, @@ -182,8 +166,6 @@ fn main() -> io::Result<()> { key: Key::Char('A'), .. } => { - // stop selection is called to prevent 'A' being interpreted as selecting - textarea.stop_selection(); textarea.move_cursor(CursorMove::End); mode = Mode::Insert; } @@ -199,8 +181,6 @@ fn main() -> io::Result<()> { key: Key::Char('O'), .. } => { - // stop selection is called to prevent 'O' being interpreted as selecting - textarea.stop_selection(); textarea.move_cursor(CursorMove::Head); textarea.insert_newline(); textarea.move_cursor(CursorMove::Up); @@ -210,7 +190,6 @@ fn main() -> io::Result<()> { key: Key::Char('I'), .. } => { - textarea.stop_selection(); textarea.move_cursor(CursorMove::Head); mode = Mode::Insert; } @@ -248,6 +227,33 @@ fn main() -> io::Result<()> { 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('y'), + ctrl: false, + .. + } if textarea.is_selecting() => textarea.copy(), + Input { + key: Key::Char('d'), + ctrl: false, + .. + } if textarea.is_selecting() => { + textarea.cut(); + } _ => {} }, Mode::Insert => match input { diff --git a/examples/tuirs_editor.rs b/examples/tuirs_editor.rs index c5afb69..c40a74e 100644 --- a/examples/tuirs_editor.rs +++ b/examples/tuirs_editor.rs @@ -250,7 +250,7 @@ impl<'a> Editor<'a> { Span::raw(" to save, "), Span::styled("^G", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to search, "), - Span::styled("^X", Style::default().add_modifier(Modifier::BOLD)), + Span::styled("^T", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to switch buffer"), ]) }; @@ -316,7 +316,7 @@ impl<'a> Editor<'a> { .. } => break, Input { - key: Key::Char('x'), + key: Key::Char('t'), ctrl: true, .. } => { diff --git a/src/highlight.rs b/src/highlight.rs index 82f2ac0..d834bfa 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -10,12 +10,11 @@ use std::iter; use tui::text::Spans as Line; use unicode_width::UnicodeWidthChar as _; -#[derive(Debug)] enum Boundary { Cursor(Style), + Select(Style), #[cfg(feature = "search")] Search(Style), - Select(Style), End, } @@ -25,8 +24,8 @@ impl Boundary { match b { Boundary::Cursor(_) => 3, #[cfg(feature = "search")] - Boundary::Search(_) => 1, - Boundary::Select(_) => 2, + Boundary::Search(_) => 2, + Boundary::Select(_) => 1, Boundary::End => 0, } } @@ -36,10 +35,10 @@ impl Boundary { fn style(&self) -> Option