input: Fix Input may out of char boundary (#901)

```
byte index 3 is not a char boundary; it is inside '吧' (bytes 1..4) of `L吧2`:    0: <unknown>
```
This commit is contained in:
Jason Lee 2025-05-26 22:42:31 +08:00 committed by GitHub
parent 1cbbf6ef18
commit 5419600fcd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -640,8 +640,8 @@ impl InputState {
cx: &mut Context<Self>,
) {
let text: SharedString = text.into();
let range = self.range_to_utf16(&(self.cursor_offset()..self.cursor_offset()));
self.replace_text_in_range(Some(range), &text, window, cx);
let range_utf16 = self.range_to_utf16(&(self.cursor_offset()..self.cursor_offset()));
self.replace_text_in_range(Some(range_utf16), &text, window, cx);
self.selected_range = self.selected_range.end..self.selected_range.end;
}
@ -783,7 +783,11 @@ impl InputState {
}
if !self.selected_range.is_empty() {
self.move_to(self.selected_range.start.saturating_sub(1), window, cx);
self.move_to(
self.previous_boundary(self.selected_range.start.saturating_sub(1)),
window,
cx,
);
}
self.pause_blink_cursor(cx);
self.move_vertical(-1, window, cx);
@ -795,7 +799,11 @@ impl InputState {
}
if !self.selected_range.is_empty() {
self.move_to(self.selected_range.end.saturating_sub(1), window, cx);
self.move_to(
self.next_boundary(self.selected_range.end.saturating_sub(1)),
window,
cx,
);
}
self.pause_blink_cursor(cx);
@ -825,7 +833,7 @@ impl InputState {
return;
}
let offset = self.start_of_line(window, cx).saturating_sub(1);
self.select_to(offset, window, cx);
self.select_to(self.previous_boundary(offset), window, cx);
}
pub(super) fn select_down(
@ -964,8 +972,8 @@ impl InputState {
/// 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)
let prev_str = self.text_for_range_utf8(0..offset);
UnicodeSegmentation::split_word_bound_indices(prev_str)
.filter(|(_, s)| !s.trim_start().is_empty())
.next_back()
.map(|(i, _)| i)
@ -975,8 +983,8 @@ impl InputState {
/// 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)
let next_str = self.text_for_range_utf8(offset..self.text.len());
UnicodeSegmentation::split_word_bound_indices(next_str)
.find(|(_, s)| !s.trim_start().is_empty())
.map(|(i, s)| offset + i + s.len())
.unwrap_or(self.text.len())
@ -1199,7 +1207,7 @@ impl InputState {
if is_eof {
new_offset += 1;
}
self.move_to(new_offset, window, cx);
self.move_to(self.next_boundary(new_offset), window, cx);
// Add indent
self.replace_text_in_range(
@ -1297,6 +1305,7 @@ impl InputState {
);
removed_len += tab_indent.len();
}
// +1 for "\n"
offset += line.len().saturating_sub(tab_indent.len()) + 1;
}
@ -1306,7 +1315,10 @@ impl InputState {
// Selected none
let start_offset = self.selected_range.start;
let offset = self.start_of_line_of_selection(window, cx);
if self.text[offset..].starts_with(tab_indent.as_ref()) {
if self
.text_for_range_utf8(offset..self.text.len())
.starts_with(tab_indent.as_ref())
{
self.replace_text_in_range(
Some(self.range_to_utf16(&(offset..offset + tab_indent.len()))),
"",
@ -1416,7 +1428,9 @@ impl InputState {
return;
}
let selected_text = self.text[self.selected_range.clone()].to_string();
let selected_text = self
.text_for_range_utf8(self.selected_range.clone())
.to_string();
cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
}
@ -1425,7 +1439,9 @@ impl InputState {
return;
}
let selected_text = self.text[self.selected_range.clone()].to_string();
let selected_text = self
.text_for_range_utf8(self.selected_range.clone())
.to_string();
cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
self.replace_text_in_range(None, "", window, cx);
}
@ -1658,7 +1674,7 @@ impl InputState {
let mut start = offset;
let mut end = start;
let prev_text = self
.text_for_range(self.range_to_utf16(&(0..start + 1)), &mut None, window, cx)
.text_for_range(self.range_to_utf16(&(0..start)), &mut None, window, cx)
.unwrap_or_default();
let next_text = self
.text_for_range(
@ -1672,16 +1688,16 @@ impl InputState {
let prev_chars = prev_text.chars().rev();
let next_chars = next_text.chars();
let mut last_char_len = 0;
for (_, c) in prev_chars.enumerate() {
let pre_chars_count = prev_chars.clone().count();
for (ix, c) in prev_chars.enumerate() {
if !is_word(c) {
break;
}
last_char_len = c.len_utf8();
start = start.saturating_sub(last_char_len);
if ix < pre_chars_count {
start = start.saturating_sub(c.len_utf8());
}
}
start += last_char_len;
for (_, c) in next_chars.enumerate() {
if !is_word(c) {
@ -1691,19 +1707,8 @@ impl InputState {
end += c.len_utf8();
}
// Ensure at least one character is selected
if start == end {
end = end + 1;
// Avoid select empty range
match self.text.get(start..end) {
None => return,
Some(part) => {
if part.trim().len() == 0 {
return;
}
}
}
return;
}
self.selected_range = start..end;
@ -1891,6 +1896,11 @@ impl InputState {
self.mode.update_auto_grow(&self.text_wrapper);
}
}
fn text_for_range_utf8(&mut self, range: impl Into<Range<usize>>) -> &str {
let range = self.range_from_utf16(&self.range_to_utf16(&range.into()));
&self.text[range]
}
}
impl EntityInputHandler for InputState {
@ -1953,8 +1963,10 @@ impl EntityInputHandler for InputState {
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
let pending_text: SharedString =
(self.text[0..range.start].to_owned() + new_text + &self.text[range.end..]).into();
let pending_text: SharedString = (self.text_for_range_utf8(0..range.start).to_owned()
+ new_text
+ self.text_for_range_utf8(range.end..self.text.len()))
.into();
// Check if the new text is valid
if !self.is_valid_input(&pending_text) {
return;
@ -1994,8 +2006,10 @@ impl EntityInputHandler for InputState {
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
let pending_text: SharedString =
(self.text[0..range.start].to_owned() + new_text + &self.text[range.end..]).into();
let pending_text: SharedString = (self.text_for_range_utf8(0..range.start).to_owned()
+ new_text
+ self.text_for_range_utf8(range.end..self.text.len()))
.into();
if !self.is_valid_input(&pending_text) {
return;
}