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