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:
parent
1cbbf6ef18
commit
5419600fcd
1 changed files with 49 additions and 35 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue