From 08d13206cd97c4de8b7b9bcd2d94ad741417fb15 Mon Sep 17 00:00:00 2001 From: achristmascarl Date: Tue, 30 Jul 2024 15:07:51 -0400 Subject: [PATCH 1/6] add method for finding next word end --- src/word.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/word.rs b/src/word.rs index 8edb814..954f830 100644 --- a/src/word.rs +++ b/src/word.rs @@ -43,6 +43,26 @@ pub fn find_word_end_forward(line: &str, start_col: usize) -> Option { None } +pub fn find_word_end_next(line: &str, start_col: usize) -> Option { + let mut it = line.chars().enumerate().skip(start_col); + let (mut cur_col, cur_char) = it.next()?; + let mut cur = CharKind::new(cur_char); + for (next_col, c) in it { + let next = CharKind::new(c); + // if cursor started at the end of a word, don't stop + if next_col.saturating_sub(start_col) > 1 && cur != CharKind::Space && next != cur { + return Some(next_col.saturating_sub(1)); + } + cur = next; + cur_col = next_col; + } + // if end of line is whitespace, don't stop the cursor + if cur != CharKind::Space && cur_col.saturating_sub(start_col) > 1 { + return Some(cur_col); + } + None +} + pub fn find_word_start_backward(line: &str, start_col: usize) -> Option { let idx = line .char_indices() From 311b185ff2febcfc56ea73cac5137ffe6fa47658 Mon Sep 17 00:00:00 2001 From: achristmascarl Date: Tue, 30 Jul 2024 15:08:29 -0400 Subject: [PATCH 2/6] new WordEnd enum member for CursorMove --- src/cursor.rs | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/cursor.rs b/src/cursor.rs index e9b084b..ca28ea4 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,5 +1,5 @@ use crate::widget::Viewport; -use crate::word::{find_word_start_backward, find_word_start_forward}; +use crate::word::{find_word_end_next, find_word_start_backward, find_word_start_forward}; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; #[cfg(feature = "serde")] @@ -121,6 +121,29 @@ pub enum CursorMove { /// assert_eq!(textarea.cursor(), (0, 8)); /// ``` WordForward, + /// Move cursor forward to the next end of word. Word boundary appears at spaces, punctuations, and others. For example + /// `fn foo(a)` consists of words `fn`, `foo`, `(`, `a`, `)`. When the cursor is at the end of line, it moves to the + /// end of the first word of the next line. + /// ``` + /// use tui_textarea::{TextArea, CursorMove}; + /// + /// let mut textarea = TextArea::from(["aaa bbb (c)", " dd"]); + /// + /// + /// textarea.move_cursor(CursorMove::WordEnd); + /// assert_eq!(textarea.cursor(), (0, 2)); + /// textarea.move_cursor(CursorMove::WordEnd); + /// assert_eq!(textarea.cursor(), (0, 6)); + /// textarea.move_cursor(CursorMove::WordEnd); + /// assert_eq!(textarea.cursor(), (0, 8)); + /// textarea.move_cursor(CursorMove::WordEnd); + /// assert_eq!(textarea.cursor(), (0, 9)); + /// textarea.move_cursor(CursorMove::WordEnd); + /// assert_eq!(textarea.cursor(), (0, 10)); + /// textarea.move_cursor(CursorMove::WordEnd); + /// assert_eq!(textarea.cursor(), (1, 2)); + /// ``` + WordEnd, /// Move cursor backward by one word. Word boundary appears at spaces, punctuations, and others. For example /// `fn foo(a)` consists of words `fn`, `foo`, `(`, `a`, `)`.When the cursor is at the head of line, it moves to /// the end of previous line. @@ -263,6 +286,19 @@ impl CursorMove { let row = lines.len() - 1; Some((row, fit_col(col, &lines[row]))) } + WordEnd => { + if let Some(col) = find_word_end_next(&lines[row], col) { + Some((row, col)) + } else if row + 1 < lines.len() { + if let Some(col) = find_word_end_next(&lines[row + 1], 0) { + Some((row + 1, col)) + } else { + Some((row + 1, lines[row + 1].chars().count())) + } + } else { + Some((row, lines[row].chars().count())) + } + } WordForward => { if let Some(col) = find_word_start_forward(&lines[row], col) { Some((row, col)) From d016971a5f39c71a00927213ce2897b3c3f318d1 Mon Sep 17 00:00:00 2001 From: achristmascarl Date: Tue, 30 Jul 2024 15:09:03 -0400 Subject: [PATCH 3/6] update tests, examples, readme --- README.md | 1 + examples/vim.rs | 5 +++++ tests/cursor.rs | 1 + 3 files changed, 7 insertions(+) diff --git a/README.md b/README.md index 579223d..2cb125c 100644 --- a/README.md +++ b/README.md @@ -497,6 +497,7 @@ notify how to move the cursor. | `textarea.move_cursor(CursorMove::Up)` | Move cursor up by one line | | `textarea.move_cursor(CursorMove::Down)` | Move cursor down by one line | | `textarea.move_cursor(CursorMove::WordForward)` | Move cursor forward by word | +| `textarea.move_cursor(CursorMove::WordEnd)` | Move cursor to next end of word | | `textarea.move_cursor(CursorMove::WordBack)` | Move cursor backward by word | | `textarea.move_cursor(CursorMove::ParagraphForward)` | Move cursor up by paragraph | | `textarea.move_cursor(CursorMove::ParagraphBack)` | Move cursor down by paragraph | diff --git a/examples/vim.rs b/examples/vim.rs index d182fc4..3977f80 100644 --- a/examples/vim.rs +++ b/examples/vim.rs @@ -112,6 +112,11 @@ impl Vim { key: Key::Char('w'), .. } => textarea.move_cursor(CursorMove::WordForward), + Input { + key: Key::Char('e'), + ctrl: false, + .. + } => textarea.move_cursor(CursorMove::WordEnd), Input { key: Key::Char('b'), ctrl: false, diff --git a/tests/cursor.rs b/tests/cursor.rs index b35ca3c..22b09e2 100644 --- a/tests/cursor.rs +++ b/tests/cursor.rs @@ -17,6 +17,7 @@ fn empty_textarea() { Top, Bottom, WordForward, + WordEnd, WordBack, ParagraphForward, ParagraphBack, From 18f2af88c99c3074785ee3384f280247fe6cb086 Mon Sep 17 00:00:00 2001 From: achristmascarl Date: Tue, 30 Jul 2024 15:40:22 -0400 Subject: [PATCH 4/6] fix behavior at end of line --- src/word.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/word.rs b/src/word.rs index 954f830..dcad8be 100644 --- a/src/word.rs +++ b/src/word.rs @@ -57,7 +57,7 @@ pub fn find_word_end_next(line: &str, start_col: usize) -> Option { cur_col = next_col; } // if end of line is whitespace, don't stop the cursor - if cur != CharKind::Space && cur_col.saturating_sub(start_col) > 1 { + if cur != CharKind::Space && cur_col.saturating_sub(start_col) >= 1 { return Some(cur_col); } None From 757ea187afd2a4697b899ecda9d0f6ac85ab4908 Mon Sep 17 00:00:00 2001 From: achristmascarl Date: Tue, 30 Jul 2024 15:44:46 -0400 Subject: [PATCH 5/6] ignore readme test --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2cb125c..cbeba61 100644 --- a/README.md +++ b/README.md @@ -663,7 +663,7 @@ Values of the following types can be serialized/deserialized: Here is an example for deserializing key input from JSON using [serde_json][]. -```rust +```rust,ignore use tui_textarea::Input; let json = r#" From eae80347d91cdd859f18d1156b12d1851dab1bd8 Mon Sep 17 00:00:00 2001 From: achristmascarl Date: Tue, 30 Jul 2024 16:11:36 -0400 Subject: [PATCH 6/6] adjust if else structure --- src/cursor.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/cursor.rs b/src/cursor.rs index ca28ea4..2ed576f 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -289,14 +289,15 @@ impl CursorMove { WordEnd => { if let Some(col) = find_word_end_next(&lines[row], col) { Some((row, col)) + } else if let Some(col) = (row + 1 < lines.len()) + .then(|| find_word_end_next(&lines[row + 1], 0)) + .unwrap_or_default() + { + Some((row + 1, col)) } else if row + 1 < lines.len() { - if let Some(col) = find_word_end_next(&lines[row + 1], 0) { - Some((row + 1, col)) - } else { - Some((row + 1, lines[row + 1].chars().count())) - } + Some((row + 1, lines[row + 1].chars().count().saturating_sub(1))) } else { - Some((row, lines[row].chars().count())) + Some((row, lines[row].chars().count().saturating_sub(1))) } } WordForward => {