Skip to content

Commit

Permalink
input: Add to support move, select and delete by word. (#591)
Browse files Browse the repository at this point in the history
  • Loading branch information
huacnlee authored Jan 30, 2025
1 parent 6fa1888 commit 43f529c
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 1 deletion.
2 changes: 1 addition & 1 deletion crates/ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
129 changes: 129 additions & 0 deletions crates/ui/src/input/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ actions!(
Delete,
DeleteToBeginningOfLine,
DeleteToEndOfLine,
DeleteToPreviousWordStart,
DeleteToNextWordEnd,
Enter,
Up,
Down,
Expand All @@ -58,6 +60,8 @@ actions!(
SelectToEndOfLine,
SelectToStart,
SelectToEnd,
SelectToPreviousWordStart,
SelectToNextWordEnd,
ShowCharacterPalette,
Copy,
Cut,
Expand All @@ -68,6 +72,8 @@ actions!(
MoveToEndOfLine,
MoveToStart,
MoveToEnd,
MoveToPreviousWord,
MoveToNextWord,
TextChanged,
]
);
Expand All @@ -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)),
Expand All @@ -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)),
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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))
Expand All @@ -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))
Expand Down

0 comments on commit 43f529c

Please sign in to comment.