input: Fix Input panics when masked have non-ASCII characters. (#1382)
Close #1377
This commit is contained in:
parent
491bacfd0e
commit
59f4e26c7b
5 changed files with 96 additions and 14 deletions
|
|
@ -62,7 +62,7 @@ impl InputStory {
|
|||
let mask_input = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.masked(true)
|
||||
.default_value("this-is-password")
|
||||
.default_value("this-is-password-中文🚀🎉")
|
||||
});
|
||||
|
||||
let prefix_input1 =
|
||||
|
|
|
|||
|
|
@ -76,12 +76,20 @@ impl TextElement {
|
|||
let line_number_width = last_layout.line_number_width;
|
||||
|
||||
let mut selected_range = state.selected_range;
|
||||
|
||||
if let Some(ime_marked_range) = &state.ime_marked_range {
|
||||
selected_range = (ime_marked_range.end..ime_marked_range.end).into();
|
||||
}
|
||||
let is_selected_all = selected_range.len() == state.text.len();
|
||||
|
||||
let cursor = state.cursor();
|
||||
let mut cursor = state.cursor();
|
||||
if state.masked {
|
||||
// Because masked use `*`, 1 char with 1 byte.
|
||||
selected_range.start = state.text.offset_to_char_index(selected_range.start);
|
||||
selected_range.end = state.text.offset_to_char_index(selected_range.end);
|
||||
cursor = state.text.offset_to_char_index(cursor);
|
||||
}
|
||||
|
||||
let mut current_row = None;
|
||||
let mut scroll_offset = state.scroll_handle.offset();
|
||||
let mut cursor_bounds = None;
|
||||
|
|
@ -442,6 +450,12 @@ impl TextElement {
|
|||
return None;
|
||||
}
|
||||
|
||||
if state.masked {
|
||||
// Because masked use `*`, 1 char with 1 byte.
|
||||
selected_range.start = state.text.offset_to_char_index(selected_range.start);
|
||||
selected_range.end = state.text.offset_to_char_index(selected_range.end);
|
||||
}
|
||||
|
||||
let (start_ix, end_ix) = if selected_range.start < selected_range.end {
|
||||
(selected_range.start, selected_range.end)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ pub trait RopeExt {
|
|||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::input::{Rope, RopeExt};
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
///
|
||||
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||
/// assert_eq!(rope.line_start_offset(0), 0);
|
||||
|
|
@ -74,7 +74,7 @@ pub trait RopeExt {
|
|||
/// Return the end of the rope if the row is out of bounds.
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::input::{Rope, RopeExt};
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||
/// assert_eq!(rope.line_end_offset(0), 5); // "Hello\n"
|
||||
/// assert_eq!(rope.line_end_offset(1), 12); // "World\r\n"
|
||||
|
|
@ -84,7 +84,7 @@ pub trait RopeExt {
|
|||
/// Return a line slice at the given row (0-based) index. including `\r` if present, but not `\n`.
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::input::{Rope, RopeExt};
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||
/// assert_eq!(rope.slice_line(0).to_string(), "Hello");
|
||||
/// assert_eq!(rope.slice_line(1).to_string(), "World\r");
|
||||
|
|
@ -98,7 +98,7 @@ pub trait RopeExt {
|
|||
/// If the range is out of bounds, it will be clamped to the valid range.
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::input::{Rope, RopeExt};
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||
/// assert_eq!(rope.slice_lines(0..2).to_string(), "Hello\nWorld\r");
|
||||
/// assert_eq!(rope.slice_lines(1..3).to_string(), "World\r\nThis is a test 中文");
|
||||
|
|
@ -113,7 +113,7 @@ pub trait RopeExt {
|
|||
/// Each line slice includes `\r` if present, but not `\n`.
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::input::{Rope, RopeExt};
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||
/// let lines: Vec<_> = rope.iter_lines().map(|r| r.to_string()).collect();
|
||||
/// assert_eq!(lines, vec!["Hello", "World\r", "This is a test 中文", "Rope"]);
|
||||
|
|
@ -123,7 +123,7 @@ pub trait RopeExt {
|
|||
/// Return the number of lines in the rope.
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::input::{Rope, RopeExt};
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||
/// assert_eq!(rope.lines_len(), 4);
|
||||
/// ```
|
||||
|
|
@ -134,7 +134,7 @@ pub trait RopeExt {
|
|||
/// If the row is out of bounds, return 0.
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::input::{Rope, RopeExt};
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||
/// assert_eq!(rope.line_len(0), 5); // "Hello"
|
||||
/// assert_eq!(rope.line_len(1), 6); // "World\r"
|
||||
|
|
@ -151,7 +151,7 @@ pub trait RopeExt {
|
|||
/// - If the range is out of bounds.
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::input::{Rope, RopeExt};
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
/// let mut rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||
/// rope.replace(6..11, "Universe");
|
||||
/// assert_eq!(rope.to_string(), "Hello\nUniverse\r\nThis is a test 中文\nRope");
|
||||
|
|
@ -207,7 +207,7 @@ pub trait RopeExt {
|
|||
/// - Otherwise return the ix.
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::input::{Rope, RopeExt};
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
/// use sum_tree::Bias;
|
||||
///
|
||||
/// let rope = Rope::from("Hello 中文🎉 test\nRope");
|
||||
|
|
@ -217,6 +217,38 @@ pub trait RopeExt {
|
|||
/// assert_eq!(rope.clip_offset(7, Bias::Right), 9);
|
||||
/// ```
|
||||
fn clip_offset(&self, offset: usize, bias: Bias) -> usize;
|
||||
|
||||
/// Convert offset in characters to byte offset (0-based).
|
||||
///
|
||||
/// Run in O(n) time.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
/// let rope = Rope::from("a 中文🎉 test\nRope");
|
||||
/// assert_eq!(rope.char_index_to_offset(0), 0);
|
||||
/// assert_eq!(rope.char_index_to_offset(1), 1);
|
||||
/// assert_eq!(rope.char_index_to_offset(3), "a 中".len());
|
||||
/// assert_eq!(rope.char_index_to_offset(5), "a 中文🎉".len());
|
||||
/// ```
|
||||
fn char_index_to_offset(&self, char_index: usize) -> usize;
|
||||
|
||||
/// Convert byte offset (0-based) to offset in characters.
|
||||
///
|
||||
/// Run in O(n) time.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use gpui_component::{Rope, RopeExt};
|
||||
/// let rope = Rope::from("a 中文🎉 test\nRope");
|
||||
/// assert_eq!(rope.offset_to_char_index(0), 0);
|
||||
/// assert_eq!(rope.offset_to_char_index(1), 1);
|
||||
/// assert_eq!(rope.offset_to_char_index(3), 3);
|
||||
/// assert_eq!(rope.offset_to_char_index(4), 3);
|
||||
/// ```
|
||||
fn offset_to_char_index(&self, offset: usize) -> usize;
|
||||
}
|
||||
|
||||
impl RopeExt for Rope {
|
||||
|
|
@ -388,6 +420,15 @@ impl RopeExt for Rope {
|
|||
self.ceil_char_boundary(offset)
|
||||
}
|
||||
}
|
||||
|
||||
fn char_index_to_offset(&self, char_offset: usize) -> usize {
|
||||
self.chars().take(char_offset).map(|c| c.len_utf8()).sum()
|
||||
}
|
||||
|
||||
fn offset_to_char_index(&self, offset: usize) -> usize {
|
||||
let offset = self.clip_offset(offset, Bias::Right);
|
||||
self.slice(..offset).chars().count()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -396,7 +437,7 @@ mod tests {
|
|||
use sum_tree::Bias;
|
||||
use tree_sitter::Point;
|
||||
|
||||
use crate::input::{Position, RopeExt};
|
||||
use crate::{input::Position, RopeExt};
|
||||
|
||||
#[test]
|
||||
fn test_slice_line() {
|
||||
|
|
@ -637,4 +678,22 @@ mod tests {
|
|||
assert_eq!(rope.clip_offset(26, Bias::Left), 26);
|
||||
assert_eq!(rope.clip_offset(100, Bias::Left), 26);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_char_index_to_offset() {
|
||||
let rope = Rope::from("a 中文🎉 test\nRope");
|
||||
assert_eq!(rope.char_index_to_offset(0), 0);
|
||||
assert_eq!(rope.char_index_to_offset(1), 1);
|
||||
assert_eq!(rope.char_index_to_offset(3), "a 中".len());
|
||||
assert_eq!(rope.char_index_to_offset(5), "a 中文🎉".len());
|
||||
assert_eq!(rope.char_index_to_offset(6), "a 中文🎉 ".len());
|
||||
|
||||
assert_eq!(rope.offset_to_char_index(0), 0);
|
||||
assert_eq!(rope.offset_to_char_index(1), 1);
|
||||
assert_eq!(rope.offset_to_char_index(3), 3);
|
||||
assert_eq!(rope.offset_to_char_index(4), 3);
|
||||
assert_eq!(rope.offset_to_char_index(5), 3);
|
||||
assert_eq!(rope.offset_to_char_index(6), 4);
|
||||
assert_eq!(rope.offset_to_char_index(10), 5);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1671,7 +1671,8 @@ impl InputState {
|
|||
|
||||
// Return offset by use closest_index_for_x if is single line mode.
|
||||
if self.mode.is_single_line() {
|
||||
return line_layout.closest_index_for_x(pos.x);
|
||||
index = line_layout.closest_index_for_x(pos.x);
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(v) = line_layout.closest_index_for_position(pos, line_height) {
|
||||
|
|
@ -1685,10 +1686,17 @@ impl InputState {
|
|||
index += line_layout.len() + 1;
|
||||
}
|
||||
|
||||
if index > self.text.len() {
|
||||
let index = if index > self.text.len() {
|
||||
self.text.len()
|
||||
} else {
|
||||
index
|
||||
};
|
||||
|
||||
if self.masked {
|
||||
// When is masked, the index is char index, need convert to byte index.
|
||||
self.text.char_index_to_offset(index)
|
||||
} else {
|
||||
index
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ pub use wry;
|
|||
pub use crate::Disableable;
|
||||
pub use event::InteractiveElementExt;
|
||||
pub use index_path::IndexPath;
|
||||
pub use input::{Rope, RopeExt, RopeLines};
|
||||
#[cfg(any(feature = "inspector", debug_assertions))]
|
||||
pub use inspector::*;
|
||||
pub use menu::{context_menu, popup_menu};
|
||||
|
|
|
|||
Loading…
Reference in a new issue