input: Add to support move, select and delete by word. (#591)

Close #466 

https://github.com/user-attachments/assets/48cda905-f2bf-4896-b3ae-08b7cd61467d
This commit is contained in:
Jason Lee 2025-01-30 10:47:11 +08:00 committed by GitHub
parent 6fa1888ee6
commit 43f529c6d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 130 additions and 1 deletions

View file

@ -26,7 +26,7 @@ serde = "1.0.203"
serde_json = "1"
smallvec = "1.13.2"
smol = "1"
unicode-segmentation = "1.11.0"
unicode-segmentation = "1.12.0"
usvg = { version = "0.44.0", default-features = false, features = [
"system-fonts",
"text",

View file

@ -42,6 +42,8 @@ actions!(
Delete,
DeleteToBeginningOfLine,
DeleteToEndOfLine,
DeleteToPreviousWordStart,
DeleteToNextWordEnd,
Enter,
Up,
Down,
@ -58,6 +60,8 @@ actions!(
SelectToEndOfLine,
SelectToStart,
SelectToEnd,
SelectToPreviousWordStart,
SelectToNextWordEnd,
ShowCharacterPalette,
Copy,
Cut,
@ -68,6 +72,8 @@ actions!(
MoveToEndOfLine,
MoveToStart,
MoveToEnd,
MoveToPreviousWord,
MoveToNextWord,
TextChanged,
]
);
@ -90,6 +96,14 @@ pub fn init(cx: &mut App) {
KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("alt-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)),
KeyBinding::new("enter", Enter, Some(CONTEXT)),
KeyBinding::new("up", Up, Some(CONTEXT)),
KeyBinding::new("down", Down, Some(CONTEXT)),
@ -112,6 +126,14 @@ pub fn init(cx: &mut App) {
#[cfg(target_os = "macos")]
KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("alt-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("alt-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
@ -146,6 +168,14 @@ pub fn init(cx: &mut App) {
#[cfg(target_os = "macos")]
KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("alt-left", MoveToPreviousWord, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("alt-right", MoveToNextWord, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-left", MoveToPreviousWord, Some(CONTEXT)),
#[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-right", MoveToNextWord, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
#[cfg(target_os = "macos")]
KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
@ -659,6 +689,26 @@ impl TextInput {
self.move_to(end, window, cx);
}
fn move_to_previous_word(
&mut self,
_: &MoveToPreviousWord,
window: &mut Window,
cx: &mut Context<Self>,
) {
let offset = self.previous_start_of_word();
self.move_to(offset, window, cx);
}
fn move_to_next_word(
&mut self,
_: &MoveToNextWord,
window: &mut Window,
cx: &mut Context<Self>,
) {
let offset = self.next_end_of_word();
self.move_to(offset, window, cx);
}
fn select_to_start(&mut self, _: &SelectToStart, window: &mut Window, cx: &mut Context<Self>) {
self.select_to(0, window, cx);
}
@ -688,6 +738,47 @@ impl TextInput {
self.select_to(self.next_boundary(offset), window, cx);
}
fn select_to_previous_word(
&mut self,
_: &SelectToPreviousWordStart,
window: &mut Window,
cx: &mut Context<Self>,
) {
let offset = self.previous_start_of_word();
self.select_to(offset, window, cx);
}
fn select_to_next_word(
&mut self,
_: &SelectToNextWordEnd,
window: &mut Window,
cx: &mut Context<Self>,
) {
let offset = self.next_end_of_word();
self.select_to(offset, window, cx);
}
/// Return the start offset of the previous word.
fn previous_start_of_word(&mut self) -> usize {
let offset = self.selected_range.start;
let prev_str = &self.text[..offset].to_string();
UnicodeSegmentation::split_word_bound_indices(prev_str as &str)
.filter(|(_, s)| !s.trim_start().is_empty())
.next_back()
.map(|(i, _)| i)
.unwrap_or(0)
}
/// Return the next end offset of the next word.
fn next_end_of_word(&mut self) -> usize {
let offset = self.cursor_offset();
let next_str = &self.text[offset..].to_string();
UnicodeSegmentation::split_word_bound_indices(next_str as &str)
.find(|(_, s)| !s.trim_start().is_empty())
.map(|(i, s)| offset + i + s.len())
.unwrap_or(self.text.len())
}
/// Get start of line
fn start_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> usize {
if self.is_single_line() {
@ -787,6 +878,38 @@ impl TextInput {
self.pause_blink_cursor(cx);
}
fn delete_previous_word(
&mut self,
_: &DeleteToPreviousWordStart,
window: &mut Window,
cx: &mut Context<Self>,
) {
let offset = self.previous_start_of_word();
self.replace_text_in_range(
Some(self.range_to_utf16(&(offset..self.cursor_offset()))),
"",
window,
cx,
);
self.pause_blink_cursor(cx);
}
fn delete_next_word(
&mut self,
_: &DeleteToNextWordEnd,
window: &mut Window,
cx: &mut Context<Self>,
) {
let offset = self.next_end_of_word();
self.replace_text_in_range(
Some(self.range_to_utf16(&(self.cursor_offset()..offset))),
"",
window,
cx,
);
self.pause_blink_cursor(cx);
}
fn enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<Self>) {
if self.is_multi_line() {
let is_eof = self.selected_range.end == self.text.len();
@ -1533,6 +1656,8 @@ impl Render for TextInput {
.on_action(cx.listener(Self::delete))
.on_action(cx.listener(Self::delete_to_beginning_of_line))
.on_action(cx.listener(Self::delete_to_end_of_line))
.on_action(cx.listener(Self::delete_previous_word))
.on_action(cx.listener(Self::delete_next_word))
.on_action(cx.listener(Self::enter))
})
.on_action(cx.listener(Self::left))
@ -1548,10 +1673,14 @@ impl Render for TextInput {
.on_action(cx.listener(Self::select_all))
.on_action(cx.listener(Self::select_to_start_of_line))
.on_action(cx.listener(Self::select_to_end_of_line))
.on_action(cx.listener(Self::select_to_previous_word))
.on_action(cx.listener(Self::select_to_next_word))
.on_action(cx.listener(Self::home))
.on_action(cx.listener(Self::end))
.on_action(cx.listener(Self::move_to_start))
.on_action(cx.listener(Self::move_to_end))
.on_action(cx.listener(Self::move_to_previous_word))
.on_action(cx.listener(Self::move_to_next_word))
.on_action(cx.listener(Self::select_to_start))
.on_action(cx.listener(Self::select_to_end))
.on_action(cx.listener(Self::show_character_palette))