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:
parent
6fa1888ee6
commit
43f529c6d2
2 changed files with 130 additions and 1 deletions
|
|
@ -26,7 +26,7 @@ serde = "1.0.203"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
smallvec = "1.13.2"
|
smallvec = "1.13.2"
|
||||||
smol = "1"
|
smol = "1"
|
||||||
unicode-segmentation = "1.11.0"
|
unicode-segmentation = "1.12.0"
|
||||||
usvg = { version = "0.44.0", default-features = false, features = [
|
usvg = { version = "0.44.0", default-features = false, features = [
|
||||||
"system-fonts",
|
"system-fonts",
|
||||||
"text",
|
"text",
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ actions!(
|
||||||
Delete,
|
Delete,
|
||||||
DeleteToBeginningOfLine,
|
DeleteToBeginningOfLine,
|
||||||
DeleteToEndOfLine,
|
DeleteToEndOfLine,
|
||||||
|
DeleteToPreviousWordStart,
|
||||||
|
DeleteToNextWordEnd,
|
||||||
Enter,
|
Enter,
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
|
|
@ -58,6 +60,8 @@ actions!(
|
||||||
SelectToEndOfLine,
|
SelectToEndOfLine,
|
||||||
SelectToStart,
|
SelectToStart,
|
||||||
SelectToEnd,
|
SelectToEnd,
|
||||||
|
SelectToPreviousWordStart,
|
||||||
|
SelectToNextWordEnd,
|
||||||
ShowCharacterPalette,
|
ShowCharacterPalette,
|
||||||
Copy,
|
Copy,
|
||||||
Cut,
|
Cut,
|
||||||
|
|
@ -68,6 +72,8 @@ actions!(
|
||||||
MoveToEndOfLine,
|
MoveToEndOfLine,
|
||||||
MoveToStart,
|
MoveToStart,
|
||||||
MoveToEnd,
|
MoveToEnd,
|
||||||
|
MoveToPreviousWord,
|
||||||
|
MoveToNextWord,
|
||||||
TextChanged,
|
TextChanged,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
@ -90,6 +96,14 @@ pub fn init(cx: &mut App) {
|
||||||
KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
|
KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
|
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("enter", Enter, Some(CONTEXT)),
|
||||||
KeyBinding::new("up", Up, Some(CONTEXT)),
|
KeyBinding::new("up", Up, Some(CONTEXT)),
|
||||||
KeyBinding::new("down", Down, Some(CONTEXT)),
|
KeyBinding::new("down", Down, Some(CONTEXT)),
|
||||||
|
|
@ -112,6 +126,14 @@ pub fn init(cx: &mut App) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
|
KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[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)),
|
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
|
KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
|
||||||
|
|
@ -146,6 +168,14 @@ pub fn init(cx: &mut App) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
|
KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[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)),
|
KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
|
KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
|
||||||
|
|
@ -659,6 +689,26 @@ impl TextInput {
|
||||||
self.move_to(end, window, cx);
|
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>) {
|
fn select_to_start(&mut self, _: &SelectToStart, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.select_to(0, window, cx);
|
self.select_to(0, window, cx);
|
||||||
}
|
}
|
||||||
|
|
@ -688,6 +738,47 @@ impl TextInput {
|
||||||
self.select_to(self.next_boundary(offset), window, cx);
|
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
|
/// Get start of line
|
||||||
fn start_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> usize {
|
fn start_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> usize {
|
||||||
if self.is_single_line() {
|
if self.is_single_line() {
|
||||||
|
|
@ -787,6 +878,38 @@ impl TextInput {
|
||||||
self.pause_blink_cursor(cx);
|
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>) {
|
fn enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if self.is_multi_line() {
|
if self.is_multi_line() {
|
||||||
let is_eof = self.selected_range.end == self.text.len();
|
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))
|
||||||
.on_action(cx.listener(Self::delete_to_beginning_of_line))
|
.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_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::enter))
|
||||||
})
|
})
|
||||||
.on_action(cx.listener(Self::left))
|
.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_all))
|
||||||
.on_action(cx.listener(Self::select_to_start_of_line))
|
.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_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::home))
|
||||||
.on_action(cx.listener(Self::end))
|
.on_action(cx.listener(Self::end))
|
||||||
.on_action(cx.listener(Self::move_to_start))
|
.on_action(cx.listener(Self::move_to_start))
|
||||||
.on_action(cx.listener(Self::move_to_end))
|
.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_start))
|
||||||
.on_action(cx.listener(Self::select_to_end))
|
.on_action(cx.listener(Self::select_to_end))
|
||||||
.on_action(cx.listener(Self::show_character_palette))
|
.on_action(cx.listener(Self::show_character_palette))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue