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| {
|
let mask_input = cx.new(|cx| {
|
||||||
InputState::new(window, cx)
|
InputState::new(window, cx)
|
||||||
.masked(true)
|
.masked(true)
|
||||||
.default_value("this-is-password")
|
.default_value("this-is-password-中文🚀🎉")
|
||||||
});
|
});
|
||||||
|
|
||||||
let prefix_input1 =
|
let prefix_input1 =
|
||||||
|
|
|
||||||
|
|
@ -76,12 +76,20 @@ impl TextElement {
|
||||||
let line_number_width = last_layout.line_number_width;
|
let line_number_width = last_layout.line_number_width;
|
||||||
|
|
||||||
let mut selected_range = state.selected_range;
|
let mut selected_range = state.selected_range;
|
||||||
|
|
||||||
if let Some(ime_marked_range) = &state.ime_marked_range {
|
if let Some(ime_marked_range) = &state.ime_marked_range {
|
||||||
selected_range = (ime_marked_range.end..ime_marked_range.end).into();
|
selected_range = (ime_marked_range.end..ime_marked_range.end).into();
|
||||||
}
|
}
|
||||||
let is_selected_all = selected_range.len() == state.text.len();
|
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 current_row = None;
|
||||||
let mut scroll_offset = state.scroll_handle.offset();
|
let mut scroll_offset = state.scroll_handle.offset();
|
||||||
let mut cursor_bounds = None;
|
let mut cursor_bounds = None;
|
||||||
|
|
@ -442,6 +450,12 @@ impl TextElement {
|
||||||
return None;
|
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 {
|
let (start_ix, end_ix) = if selected_range.start < selected_range.end {
|
||||||
(selected_range.start, selected_range.end)
|
(selected_range.start, selected_range.end)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ pub trait RopeExt {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use gpui_component::input::{Rope, RopeExt};
|
/// use gpui_component::{Rope, RopeExt};
|
||||||
///
|
///
|
||||||
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
/// assert_eq!(rope.line_start_offset(0), 0);
|
/// 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.
|
/// 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");
|
/// 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(0), 5); // "Hello\n"
|
||||||
/// assert_eq!(rope.line_end_offset(1), 12); // "World\r\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`.
|
/// 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");
|
/// 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(0).to_string(), "Hello");
|
||||||
/// assert_eq!(rope.slice_line(1).to_string(), "World\r");
|
/// 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.
|
/// 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");
|
/// 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(0..2).to_string(), "Hello\nWorld\r");
|
||||||
/// assert_eq!(rope.slice_lines(1..3).to_string(), "World\r\nThis is a test 中文");
|
/// 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`.
|
/// 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 rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
/// let lines: Vec<_> = rope.iter_lines().map(|r| r.to_string()).collect();
|
/// let lines: Vec<_> = rope.iter_lines().map(|r| r.to_string()).collect();
|
||||||
/// assert_eq!(lines, vec!["Hello", "World\r", "This is a test 中文", "Rope"]);
|
/// 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.
|
/// 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");
|
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
/// assert_eq!(rope.lines_len(), 4);
|
/// assert_eq!(rope.lines_len(), 4);
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -134,7 +134,7 @@ pub trait RopeExt {
|
||||||
/// If the row is out of bounds, return 0.
|
/// 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");
|
/// 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(0), 5); // "Hello"
|
||||||
/// assert_eq!(rope.line_len(1), 6); // "World\r"
|
/// assert_eq!(rope.line_len(1), 6); // "World\r"
|
||||||
|
|
@ -151,7 +151,7 @@ pub trait RopeExt {
|
||||||
/// - If the range is out of bounds.
|
/// - 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");
|
/// let mut rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
/// rope.replace(6..11, "Universe");
|
/// rope.replace(6..11, "Universe");
|
||||||
/// assert_eq!(rope.to_string(), "Hello\nUniverse\r\nThis is a test 中文\nRope");
|
/// assert_eq!(rope.to_string(), "Hello\nUniverse\r\nThis is a test 中文\nRope");
|
||||||
|
|
@ -207,7 +207,7 @@ pub trait RopeExt {
|
||||||
/// - Otherwise return the ix.
|
/// - Otherwise return the ix.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use gpui_component::input::{Rope, RopeExt};
|
/// use gpui_component::{Rope, RopeExt};
|
||||||
/// use sum_tree::Bias;
|
/// use sum_tree::Bias;
|
||||||
///
|
///
|
||||||
/// let rope = Rope::from("Hello 中文🎉 test\nRope");
|
/// let rope = Rope::from("Hello 中文🎉 test\nRope");
|
||||||
|
|
@ -217,6 +217,38 @@ pub trait RopeExt {
|
||||||
/// assert_eq!(rope.clip_offset(7, Bias::Right), 9);
|
/// assert_eq!(rope.clip_offset(7, Bias::Right), 9);
|
||||||
/// ```
|
/// ```
|
||||||
fn clip_offset(&self, offset: usize, bias: Bias) -> usize;
|
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 {
|
impl RopeExt for Rope {
|
||||||
|
|
@ -388,6 +420,15 @@ impl RopeExt for Rope {
|
||||||
self.ceil_char_boundary(offset)
|
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)]
|
#[cfg(test)]
|
||||||
|
|
@ -396,7 +437,7 @@ mod tests {
|
||||||
use sum_tree::Bias;
|
use sum_tree::Bias;
|
||||||
use tree_sitter::Point;
|
use tree_sitter::Point;
|
||||||
|
|
||||||
use crate::input::{Position, RopeExt};
|
use crate::{input::Position, RopeExt};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slice_line() {
|
fn test_slice_line() {
|
||||||
|
|
@ -637,4 +678,22 @@ mod tests {
|
||||||
assert_eq!(rope.clip_offset(26, Bias::Left), 26);
|
assert_eq!(rope.clip_offset(26, Bias::Left), 26);
|
||||||
assert_eq!(rope.clip_offset(100, 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.
|
// Return offset by use closest_index_for_x if is single line mode.
|
||||||
if self.mode.is_single_line() {
|
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) {
|
if let Some(v) = line_layout.closest_index_for_position(pos, line_height) {
|
||||||
|
|
@ -1685,10 +1686,17 @@ impl InputState {
|
||||||
index += line_layout.len() + 1;
|
index += line_layout.len() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if index > self.text.len() {
|
let index = if index > self.text.len() {
|
||||||
self.text.len()
|
self.text.len()
|
||||||
} else {
|
} else {
|
||||||
index
|
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 crate::Disableable;
|
||||||
pub use event::InteractiveElementExt;
|
pub use event::InteractiveElementExt;
|
||||||
pub use index_path::IndexPath;
|
pub use index_path::IndexPath;
|
||||||
|
pub use input::{Rope, RopeExt, RopeLines};
|
||||||
#[cfg(any(feature = "inspector", debug_assertions))]
|
#[cfg(any(feature = "inspector", debug_assertions))]
|
||||||
pub use inspector::*;
|
pub use inspector::*;
|
||||||
pub use menu::{context_menu, popup_menu};
|
pub use menu::{context_menu, popup_menu};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue