From bde62d95eaf5899518d7388d326234e81303caa8 Mon Sep 17 00:00:00 2001 From: temportalflux Date: Sat, 20 Jun 2026 10:23:17 -0400 Subject: [PATCH] add UnicodeTextStorage trait for String and SharedString --- crates/gpui_elements/src/editable_text.rs | 2 + .../src/editable_text/actions.rs | 8 +- .../src/editable_text/storage.rs | 161 ++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 crates/gpui_elements/src/editable_text/storage.rs diff --git a/crates/gpui_elements/src/editable_text.rs b/crates/gpui_elements/src/editable_text.rs index 768a5fd18e..40e85948ef 100644 --- a/crates/gpui_elements/src/editable_text.rs +++ b/crates/gpui_elements/src/editable_text.rs @@ -1,5 +1,7 @@ mod actions; mod state_field; +mod storage; pub use actions::*; pub use state_field::*; +pub use storage::*; diff --git a/crates/gpui_elements/src/editable_text/actions.rs b/crates/gpui_elements/src/editable_text/actions.rs index 78e41b1d55..aac530dfba 100644 --- a/crates/gpui_elements/src/editable_text/actions.rs +++ b/crates/gpui_elements/src/editable_text/actions.rs @@ -190,7 +190,13 @@ pub trait EditableTextActionHandler { fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut App) {} fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut App) {} - fn show_character_palette(&mut self, _: &ShowCharacterPalette, _w: &mut Window, _cx: &mut App) { + fn show_character_palette( + &mut self, + _: &ShowCharacterPalette, + window: &mut Window, + _cx: &mut App, + ) { + window.show_character_palette(); } fn on_mouse_down( diff --git a/crates/gpui_elements/src/editable_text/storage.rs b/crates/gpui_elements/src/editable_text/storage.rs new file mode 100644 index 0000000000..a2ab788c9a --- /dev/null +++ b/crates/gpui_elements/src/editable_text/storage.rs @@ -0,0 +1,161 @@ +use gpui::SharedString; +use std::ops::Range; +use unicode_segmentation::UnicodeSegmentation; + +pub trait UnicodeTextStorage { + /// Returns a reference to the utf8 string. + fn content_utf8(&self) -> &str; + + /// Returns the UTF-16 length of the content. + fn len_utf16(&self) -> usize; + + fn utf_offset_8to16(&self, pos_uft8: usize) -> usize { + // Fast path: if offset is 0, return 0 + if pos_uft8 == 0 { + return 0; + } + + // Fast path: if offset is at or past end, return cached length + if pos_uft8 >= self.content_utf8().len() { + return self.len_utf16(); + } + + let mut count_utf16 = 0; + for (idx, character) in self.content_utf8().char_indices() { + if idx >= pos_uft8 { + break; + } + count_utf16 += character.len_utf16(); + } + count_utf16 + } + + fn utf_offset_16to8(&self, pos_utf16: usize) -> usize { + // Fast path: if offset is 0, return 0 + if pos_utf16 == 0 { + return 0; + } + + let mut count_utf16 = 0; + for (idx, character) in self.content_utf8().char_indices() { + if count_utf16 >= pos_utf16 { + return idx; + } + count_utf16 += character.len_utf16(); + } + self.content_utf8().len() + } + + fn utf_range_8to16(&self, range_utf8: &Range) -> Range { + self.utf_offset_8to16(range_utf8.start)..self.utf_offset_8to16(range_utf8.end) + } + + fn utf_range_16to8(&self, range_utf16: &Range) -> Range { + self.utf_offset_16to8(range_utf16.start)..self.utf_offset_16to8(range_utf16.end) + } + + fn previous_boundary(&self, offset: usize) -> usize { + if offset == 0 { + return 0; + } + + let text_before = &self.content_utf8()[..offset.min(self.content_utf8().len())]; + text_before + .grapheme_indices(true) + .map(|(i, _)| i) + .next_back() + .unwrap_or(0) + } + + fn next_boundary(&self, offset: usize) -> usize { + let len_utf8 = self.content_utf8().len(); + if offset >= len_utf8 { + return len_utf8; + } + + let text_after = &self.content_utf8()[offset..]; + text_after + .grapheme_indices(true) + .nth(1) + .map(|(i, _)| offset + i) + .unwrap_or(len_utf8) + } + + fn previous_word_boundary(&self, offset: usize) -> usize { + if offset == 0 { + return 0; + } + + let text_before = &self.content_utf8()[..offset.min(self.content_utf8().len())]; + + let mut last_word_start = 0; + for (idx, _) in text_before.unicode_word_indices() { + if idx < offset { + last_word_start = idx; + } + } + + if last_word_start == 0 && offset > 0 { + let trimmed = text_before.trim_end(); + if trimmed.is_empty() { + return 0; + } + for (idx, _) in trimmed.unicode_word_indices() { + last_word_start = idx; + } + } + + last_word_start + } + + fn next_word_boundary(&self, offset: usize) -> usize { + let len_utf8 = self.content_utf8().len(); + if offset >= len_utf8 { + return len_utf8; + } + + let text_after = &self.content_utf8()[offset..]; + + for (idx, word) in text_after.unicode_word_indices() { + let word_end = offset + idx + word.len(); + if word_end > offset { + return word_end; + } + } + + len_utf8 + } + + fn word_range_at(&self, offset: usize) -> (usize, usize) { + let offset = offset.min(self.content_utf8().len()); + + for (idx, word) in self.content_utf8().unicode_word_indices() { + let word_end = idx + word.len(); + if offset >= idx && offset <= word_end { + return (idx, word_end); + } + } + + (offset, offset) + } +} + +impl UnicodeTextStorage for String { + fn content_utf8(&self) -> &str { + self.as_str() + } + + fn len_utf16(&self) -> usize { + self.len() + } +} + +impl UnicodeTextStorage for SharedString { + fn content_utf8(&self) -> &str { + self.as_str() + } + + fn len_utf16(&self) -> usize { + self.len() + } +}