From 63ae3503d4d71f1ddaa53ca93c03d3c29996778c Mon Sep 17 00:00:00 2001 From: rhysd Date: Sun, 24 Jul 2022 08:41:05 +0900 Subject: [PATCH] add document and doc tests for `TextArea::scroll` and `CursorMove::InViewport` --- README.md | 5 +++++ src/cursor.rs | 31 ++++++++++++++++++++++++++++ src/textarea.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/README.md b/README.md index 357fd1a..2a722dd 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ text editor can be easily put as part of your TUI application. - Line number - Cursor line highlight - Search with regular expressions +- Mouse scrolling - Yank support. Paste text deleted with `C-k`, `C-j`, ... - Backend agnostic. [crossterm][], [termion][], and your own backend are all supported - Multiple textarea widgets in the same screen @@ -410,9 +411,11 @@ notify how to move the cursor. | `textarea.move_cursor(CursorMove::Top)` | Move cursor to top of lines | | `textarea.move_cursor(CursorMove::Bottom)` | Move cursor to bottom of lines | | `textarea.move_cursor(CursorMove::Jump(row, col))` | Move cursor to (row, col) position | +| `textarea.move_cursor(CursorMove::InViewport)` | Move cursor to stay in the viewport | | `textarea.set_search_pattern(pattern)` | Set a pattern for text search | | `textarea.search_forward(match_cursor)` | Move cursor to next match of text search | | `textarea.search_back(match_cursor)` | Move cursor to previous match of text search | +| `textarea.scroll(rows, cols)` | Scroll the viewport | 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. @@ -465,6 +468,8 @@ loop { Input { key: Key::Esc, .. } => textarea.set_search_pattern("").unwrap(), Input { key: Key::Char('n'), .. } => textarea.search_forward(), Input { key: Key::Char('N'), .. } => textarea.search_back(), + Input { key: Key::Char('e'), ctrl: true .. } => textarea.scroll(1, 0), + Input { key: Key::Char('y'), ctrl: true .. } => textarea.scroll(-1, 0), _ => {}, }, Mode::Insert => match read()?.into() { diff --git a/src/cursor.rs b/src/cursor.rs index d031b64..6c52530 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -188,6 +188,37 @@ pub enum CursorMove { /// assert_eq!(textarea.cursor(), (2, 4)); /// ``` Jump(u16, u16), + /// Move cursor to keep it within the viewport. For example, when a viewport displays line 8 to line 16: + /// + /// - cursor at line 4 is moved to line 8 + /// - cursor at line 20 is moved to line 16 + /// - cursor at line 12 is not moved + /// + /// This is useful when you moved a cursor but you don't want to move the viewport. + /// ``` + /// # use tui::buffer::Buffer; + /// # use tui::layout::Rect; + /// # use tui::widgets::Widget; + /// use tui_textarea::{TextArea, CursorMove}; + /// + /// // Let's say terminal height is 8. + /// + /// // Create textarea with 20 lines "0", "1", "2", "3", ... + /// // The viewport is displaying from line 1 to line 8. + /// let mut textarea: TextArea = (0..20).into_iter().map(|i| i.to_string()).collect(); + /// # // Call `render` at least once to populate terminal size + /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 }; + /// # let mut b = Buffer::empty(r.clone()); + /// # textarea.widget().render(r, &mut b); + /// + /// // Move cursor to the end of lines (line 20). It is outside the viewport (line 1 to line 8) + /// textarea.move_cursor(CursorMove::Bottom); + /// assert_eq!(textarea.cursor(), (19, 0)); + /// + /// // Cursor is moved to line 8 to enter the viewport + /// textarea.move_cursor(CursorMove::InViewport); + /// assert_eq!(textarea.cursor(), (7, 0)); + /// ``` InViewport, } diff --git a/src/textarea.rs b/src/textarea.rs index f8f682c..0152301 100644 --- a/src/textarea.rs +++ b/src/textarea.rs @@ -519,6 +519,20 @@ impl<'a> TextArea<'a> { self.insert_newline(); true } + Input { + key: Key::MouseScrollDown, + .. + } => { + self.scroll(1, 0); + false + } + Input { + key: Key::MouseScrollUp, + .. + } => { + self.scroll(-1, 0); + false + } _ => false, } } @@ -1460,6 +1474,7 @@ impl<'a> TextArea<'a> { } /// Set the text style at matches of text search. The default style is colored with blue in background. + /// /// ``` /// use tui::style::{Style, Color}; /// use tui_textarea::TextArea; @@ -1477,6 +1492,45 @@ impl<'a> TextArea<'a> { self.search.style = style; } + /// Scroll the textarea by `delta_row` rows (vertically) and `delta_col` columns (horizontally). Positive scroll + /// amount means scrolling down or right. And negative scroll amount means scrolling up or left. The cursor will not + /// move until it goes out the viewport. When the cursor position is outside the viewport after scroll, the cursor + /// position will be adjusted to stay in the viewport using the same logic as [`CursorMove::InViewport`]. + /// + /// ``` + /// # use tui::buffer::Buffer; + /// # use tui::layout::Rect; + /// # use tui::widgets::Widget; + /// use tui_textarea::TextArea; + /// + /// // Let's say terminal height is 8. + /// + /// // Create textarea with 20 lines "0", "1", "2", "3", ... + /// let mut textarea: TextArea = (0..20).into_iter().map(|i| i.to_string()).collect(); + /// # // Call `render` at least once to populate terminal size + /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 }; + /// # let mut b = Buffer::empty(r.clone()); + /// # textarea.widget().render(r, &mut b); + /// + /// // Scroll down by 15 lines. Since terminal height is 8, cursor will go out + /// // the viewport. + /// textarea.scroll(15, 0); + /// // So the cursor position was updated to stay in the viewport after the scrolling. + /// assert_eq!(textarea.cursor(), (15, 0)); + /// + /// // Scroll up by 5 lines. Since the scroll amount is smaller than the terminal + /// // height, cursor position will not be updated. + /// textarea.scroll(-5, 0); + /// assert_eq!(textarea.cursor(), (15, 0)); + /// + /// // Scroll up by 5 lines again. The terminal height is 8. So a cursor reaches to + /// // the top of viewport after scrolling up by 7 lines. Since we have already + /// // scrolled up by 5 lines, scrolling up by 5 lines again makes the cursor overrun + /// // the viewport by 5 - 2 = 3 lines. To keep the cursor stay in the viewport, the + /// // cursor position will be adjusted from line 15 to line 12. + /// textarea.scroll(-5, 0); + /// assert_eq!(textarea.cursor(), (12, 0)); + /// ``` pub fn scroll(&mut self, delta_row: i16, delta_col: i16) { self.viewport.scroll(delta_row, delta_col); self.move_cursor(CursorMove::InViewport);