style: cargo fmt with the workspace tab policy (hard_tabs)

Whitespace only; the fork has no own rustfmt.toml so the Oak root
policy applies.
This commit is contained in:
2026-08-11 23:06:13 +08:00
parent 507d7e6e8d
commit af5482d774
318 changed files with 132564 additions and 132170 deletions
+277 -277
View File
@@ -6,77 +6,77 @@ use std::{cell::RefCell, rc::Rc};
pub const DEFAULT_INPUT_CONTEXT: &str = "EditableText";
gpui::actions!(
actions,
[
/// Blur focus from the input.
Escape,
/// Insert a newline at the cursor position.
Enter,
/// Insert a tab character at the cursor position.
Tab,
/// Delete the character before the cursor.
DeleteLeft,
/// Delete the character after the cursor.
DeleteRight,
/// Delete the word before the cursor.
DeleteWordLeft,
/// Delete the word after the cursor.
DeleteWordRight,
/// Delete from the cursor to the beginning of the line.
DeleteToLineStart,
/// Delete from the cursor to the end of the line.
DeleteToLineEnd,
/// Move the cursor one character to the left.
NavLeft,
/// Move the cursor one character to the right.
NavRight,
/// Move the cursor up one visual line.
NavUp,
/// Move the cursor down one visual line.
NavDown,
/// Move cursor to the start of the current line.
NavLineStart,
/// Move cursor to the end of the current line.
NavLineEnd,
/// Move cursor to the beginning of the content.
NavDocumentStart,
/// Move cursor to the end of the content.
NavDocumentEnd,
/// Move cursor one word to the left.
NavWordLeft,
/// Move cursor one word to the right.
NavWordRight,
/// Select all text content.
SelectAll,
/// Extend selection one character to the left.
SelectLeft,
/// Extend selection one character to the right.
SelectRight,
/// Extend selection up one visual line.
SelectUp,
/// Extend selection down one visual line.
SelectDown,
/// Extend selection to the beginning of the content.
SelectDocumentStart,
/// Extend selection to the end of the content.
SelectDocumentEnd,
/// Extend selection one word to the left.
SelectWordLeft,
/// Extend selection one word to the right.
SelectWordRight,
/// Cut selected text to clipboard.
Cut,
/// Copy selected text to clipboard.
Copy,
/// Paste from clipboard at the cursor position.
Paste,
/// Undo the last edit.
Undo,
/// Redo the last undone edit.
Redo,
/// Show the platform character palette.
ShowCharacterPalette,
]
actions,
[
/// Blur focus from the input.
Escape,
/// Insert a newline at the cursor position.
Enter,
/// Insert a tab character at the cursor position.
Tab,
/// Delete the character before the cursor.
DeleteLeft,
/// Delete the character after the cursor.
DeleteRight,
/// Delete the word before the cursor.
DeleteWordLeft,
/// Delete the word after the cursor.
DeleteWordRight,
/// Delete from the cursor to the beginning of the line.
DeleteToLineStart,
/// Delete from the cursor to the end of the line.
DeleteToLineEnd,
/// Move the cursor one character to the left.
NavLeft,
/// Move the cursor one character to the right.
NavRight,
/// Move the cursor up one visual line.
NavUp,
/// Move the cursor down one visual line.
NavDown,
/// Move cursor to the start of the current line.
NavLineStart,
/// Move cursor to the end of the current line.
NavLineEnd,
/// Move cursor to the beginning of the content.
NavDocumentStart,
/// Move cursor to the end of the content.
NavDocumentEnd,
/// Move cursor one word to the left.
NavWordLeft,
/// Move cursor one word to the right.
NavWordRight,
/// Select all text content.
SelectAll,
/// Extend selection one character to the left.
SelectLeft,
/// Extend selection one character to the right.
SelectRight,
/// Extend selection up one visual line.
SelectUp,
/// Extend selection down one visual line.
SelectDown,
/// Extend selection to the beginning of the content.
SelectDocumentStart,
/// Extend selection to the end of the content.
SelectDocumentEnd,
/// Extend selection one word to the left.
SelectWordLeft,
/// Extend selection one word to the right.
SelectWordRight,
/// Cut selected text to clipboard.
Cut,
/// Copy selected text to clipboard.
Copy,
/// Paste from clipboard at the cursor position.
Paste,
/// Undo the last edit.
Undo,
/// Redo the last undone edit.
Redo,
/// Show the platform character palette.
ShowCharacterPalette,
]
);
/// Creates a collection of default keystroke bindings for EditableText actions.
@@ -122,235 +122,235 @@ gpui::actions!(
///
/// TODO: Collection does not supply a way to unbind a default keystroke
pub fn default_bindings() -> gpui::ActionBindingCollection {
let mut bindings = gpui::ActionBindingCollection::default()
.with::<DeleteLeft>("backspace")
.with::<DeleteRight>("delete")
.with::<Tab>("tab")
.with::<Enter>("enter")
.with::<NavLeft>("left")
.with::<NavRight>("right")
.with::<NavUp>("up")
.with::<NavDown>("down")
.with::<SelectAll>("secondary-a")
.with::<SelectLeft>("shift-left")
.with::<SelectRight>("shift-right")
.with::<SelectUp>("shift-up")
.with::<SelectDown>("shift-down")
.with::<Copy>("secondary-c")
.with::<Cut>("secondary-x")
.with::<Paste>("secondary-v")
.with::<Undo>("secondary-z")
.with::<Redo>("secondary-shift-z")
.with::<Escape>("escape")
.with::<ShowCharacterPalette>("secondary-space");
let mut bindings = gpui::ActionBindingCollection::default()
.with::<DeleteLeft>("backspace")
.with::<DeleteRight>("delete")
.with::<Tab>("tab")
.with::<Enter>("enter")
.with::<NavLeft>("left")
.with::<NavRight>("right")
.with::<NavUp>("up")
.with::<NavDown>("down")
.with::<SelectAll>("secondary-a")
.with::<SelectLeft>("shift-left")
.with::<SelectRight>("shift-right")
.with::<SelectUp>("shift-up")
.with::<SelectDown>("shift-down")
.with::<Copy>("secondary-c")
.with::<Cut>("secondary-x")
.with::<Paste>("secondary-v")
.with::<Undo>("secondary-z")
.with::<Redo>("secondary-shift-z")
.with::<Escape>("escape")
.with::<ShowCharacterPalette>("secondary-space");
#[cfg(target_os = "macos")]
{
bindings = bindings
.with::<DeleteWordLeft>("alt-backspace")
.with::<DeleteWordRight>("alt-delete")
.with::<DeleteToLineStart>("cmd-backspace")
.with::<DeleteToLineEnd>("ctrl-k")
// Mac keyboards don't have Home/End keys, so cmd-left/right are standard
.with::<NavLineStart>("cmd-left")
.with::<NavLineEnd>("cmd-right")
.with::<NavDocumentStart>("cmd-up")
.with::<NavDocumentEnd>("cmd-down")
.with::<SelectDocumentStart>("cmd-shift-up")
.with::<SelectDocumentEnd>("cmd-shift-down")
.with::<NavWordLeft>("alt-left")
.with::<NavWordRight>("alt-right")
.with::<SelectWordLeft>("alt-shift-left")
.with::<SelectWordRight>("alt-shift-right");
}
#[cfg(target_os = "macos")]
{
bindings = bindings
.with::<DeleteWordLeft>("alt-backspace")
.with::<DeleteWordRight>("alt-delete")
.with::<DeleteToLineStart>("cmd-backspace")
.with::<DeleteToLineEnd>("ctrl-k")
// Mac keyboards don't have Home/End keys, so cmd-left/right are standard
.with::<NavLineStart>("cmd-left")
.with::<NavLineEnd>("cmd-right")
.with::<NavDocumentStart>("cmd-up")
.with::<NavDocumentEnd>("cmd-down")
.with::<SelectDocumentStart>("cmd-shift-up")
.with::<SelectDocumentEnd>("cmd-shift-down")
.with::<NavWordLeft>("alt-left")
.with::<NavWordRight>("alt-right")
.with::<SelectWordLeft>("alt-shift-left")
.with::<SelectWordRight>("alt-shift-right");
}
#[cfg(not(target_os = "macos"))]
{
bindings = bindings
.with::<DeleteWordLeft>("ctrl-backspace")
.with::<DeleteWordRight>("ctrl-delete")
.with::<DeleteToLineStart>("ctrl-shift-backspace")
.with::<DeleteToLineEnd>("ctrl-shift-delete")
.with::<NavLineStart>("home")
.with::<NavLineEnd>("end")
.with::<NavDocumentStart>("ctrl-home")
.with::<NavDocumentEnd>("ctrl-end")
.with::<SelectDocumentStart>("ctrl-shift-home")
.with::<SelectDocumentEnd>("ctrl-shift-end")
.with::<NavWordLeft>("ctrl-left")
.with::<NavWordRight>("ctrl-right")
.with::<SelectWordLeft>("ctrl-shift-left")
.with::<SelectWordRight>("ctrl-shift-right");
}
#[cfg(not(target_os = "macos"))]
{
bindings = bindings
.with::<DeleteWordLeft>("ctrl-backspace")
.with::<DeleteWordRight>("ctrl-delete")
.with::<DeleteToLineStart>("ctrl-shift-backspace")
.with::<DeleteToLineEnd>("ctrl-shift-delete")
.with::<NavLineStart>("home")
.with::<NavLineEnd>("end")
.with::<NavDocumentStart>("ctrl-home")
.with::<NavDocumentEnd>("ctrl-end")
.with::<SelectDocumentStart>("ctrl-shift-home")
.with::<SelectDocumentEnd>("ctrl-shift-end")
.with::<NavWordLeft>("ctrl-left")
.with::<NavWordRight>("ctrl-right")
.with::<SelectWordLeft>("ctrl-shift-left")
.with::<SelectWordRight>("ctrl-shift-right");
}
bindings
bindings
}
/// Declares stubs for all editable-text actions that an element's state entity can implement.
pub trait EditableTextActionHandler<Context>: Sized {
/// Blur focus from the input.
fn escape(&mut self, _: &Escape, _w: &mut Window, _cx: &mut Context) {}
/// Blur focus from the input.
fn escape(&mut self, _: &Escape, _w: &mut Window, _cx: &mut Context) {}
/// Insert a newline at the cursor position.
fn insert_enter(&mut self, _: &Enter, _w: &mut Window, _cx: &mut Context) {}
/// Insert a tab character at the cursor position.
fn insert_tab(&mut self, _: &Tab, _w: &mut Window, _cx: &mut Context) {}
/// Insert a newline at the cursor position.
fn insert_enter(&mut self, _: &Enter, _w: &mut Window, _cx: &mut Context) {}
/// Insert a tab character at the cursor position.
fn insert_tab(&mut self, _: &Tab, _w: &mut Window, _cx: &mut Context) {}
/// Delete the character before the cursor.
fn delete_left(&mut self, _: &DeleteLeft, _w: &mut Window, _cx: &mut Context) {}
/// Delete the character after the cursor.
fn delete_right(&mut self, _: &DeleteRight, _w: &mut Window, _cx: &mut Context) {}
/// Delete the word before the cursor.
fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, _cx: &mut Context) {}
/// Delete the word after the cursor.
fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, _cx: &mut Context) {}
/// Delete from the cursor to the beginning of the line.
fn delete_to_line_start(&mut self, _: &DeleteToLineStart, _w: &mut Window, _cx: &mut Context) {}
/// Delete from the cursor to the end of the line.
fn delete_to_line_end(&mut self, _: &DeleteToLineEnd, _w: &mut Window, _cx: &mut Context) {}
/// Delete the character before the cursor.
fn delete_left(&mut self, _: &DeleteLeft, _w: &mut Window, _cx: &mut Context) {}
/// Delete the character after the cursor.
fn delete_right(&mut self, _: &DeleteRight, _w: &mut Window, _cx: &mut Context) {}
/// Delete the word before the cursor.
fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, _cx: &mut Context) {}
/// Delete the word after the cursor.
fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, _cx: &mut Context) {}
/// Delete from the cursor to the beginning of the line.
fn delete_to_line_start(&mut self, _: &DeleteToLineStart, _w: &mut Window, _cx: &mut Context) {}
/// Delete from the cursor to the end of the line.
fn delete_to_line_end(&mut self, _: &DeleteToLineEnd, _w: &mut Window, _cx: &mut Context) {}
/// Move the cursor one character to the left.
fn nav_left(&mut self, _: &NavLeft, _w: &mut Window, _cx: &mut Context) {}
/// Move the cursor one character to the right.
fn nav_right(&mut self, _: &NavRight, _w: &mut Window, _cx: &mut Context) {}
/// Move the cursor up one visual line.
fn nav_up(&mut self, _: &NavUp, _w: &mut Window, _cx: &mut Context) {}
/// Move the cursor down one visual line.
fn nav_down(&mut self, _: &NavDown, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor to the start of the current line.
fn nav_line_start(&mut self, _: &NavLineStart, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor to the end of the current line.
fn nav_line_end(&mut self, _: &NavLineEnd, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor to the start of the document.
fn nav_start(&mut self, _: &NavDocumentStart, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor to the end of the document.
fn nav_end(&mut self, _: &NavDocumentEnd, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor one word to the left.
fn nav_left_word(&mut self, _: &NavWordLeft, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor one word to the right.
fn nav_right_word(&mut self, _: &NavWordRight, _w: &mut Window, _cx: &mut Context) {}
/// Move the cursor one character to the left.
fn nav_left(&mut self, _: &NavLeft, _w: &mut Window, _cx: &mut Context) {}
/// Move the cursor one character to the right.
fn nav_right(&mut self, _: &NavRight, _w: &mut Window, _cx: &mut Context) {}
/// Move the cursor up one visual line.
fn nav_up(&mut self, _: &NavUp, _w: &mut Window, _cx: &mut Context) {}
/// Move the cursor down one visual line.
fn nav_down(&mut self, _: &NavDown, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor to the start of the current line.
fn nav_line_start(&mut self, _: &NavLineStart, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor to the end of the current line.
fn nav_line_end(&mut self, _: &NavLineEnd, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor to the start of the document.
fn nav_start(&mut self, _: &NavDocumentStart, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor to the end of the document.
fn nav_end(&mut self, _: &NavDocumentEnd, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor one word to the left.
fn nav_left_word(&mut self, _: &NavWordLeft, _w: &mut Window, _cx: &mut Context) {}
/// Move cursor one word to the right.
fn nav_right_word(&mut self, _: &NavWordRight, _w: &mut Window, _cx: &mut Context) {}
/// Select the entire document.
fn select_all(&mut self, _: &SelectAll, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection one character to the left.
fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection one character to the right.
fn select_right(&mut self, _: &SelectRight, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection up one visual line.
fn select_up(&mut self, _: &SelectUp, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection down one visual line.
fn select_down(&mut self, _: &SelectDown, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection to the beginning of the document.
fn select_start(&mut self, _: &SelectDocumentStart, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection to the end of the document.
fn select_end(&mut self, _: &SelectDocumentEnd, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection one word to the left.
fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection one word to the right.
fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, _cx: &mut Context) {}
/// Select the entire document.
fn select_all(&mut self, _: &SelectAll, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection one character to the left.
fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection one character to the right.
fn select_right(&mut self, _: &SelectRight, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection up one visual line.
fn select_up(&mut self, _: &SelectUp, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection down one visual line.
fn select_down(&mut self, _: &SelectDown, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection to the beginning of the document.
fn select_start(&mut self, _: &SelectDocumentStart, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection to the end of the document.
fn select_end(&mut self, _: &SelectDocumentEnd, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection one word to the left.
fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, _cx: &mut Context) {}
/// Extend selection one word to the right.
fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, _cx: &mut Context) {}
/// Cut selected text to clipboard.
fn cut(&mut self, _: &Cut, _w: &mut Window, _cx: &mut Context) {}
/// Copy selected text to clipboard.
fn copy(&mut self, _: &Copy, _w: &mut Window, _cx: &mut Context) {}
/// Paste from clipboard at the cursor position.
fn paste(&mut self, _: &Paste, _w: &mut Window, _cx: &mut Context) {}
/// Cut selected text to clipboard.
fn cut(&mut self, _: &Cut, _w: &mut Window, _cx: &mut Context) {}
/// Copy selected text to clipboard.
fn copy(&mut self, _: &Copy, _w: &mut Window, _cx: &mut Context) {}
/// Paste from clipboard at the cursor position.
fn paste(&mut self, _: &Paste, _w: &mut Window, _cx: &mut Context) {}
/// Undo the last edit.
fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Context) {}
/// Redo the last undone edit.
fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Context) {}
/// Undo the last edit.
fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Context) {}
/// Redo the last undone edit.
fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Context) {}
/// Show the platform character palette.
fn show_character_palette(
&mut self,
_: &ShowCharacterPalette,
window: &mut Window,
_cx: &mut Context,
) {
window.show_character_palette();
}
/// Show the platform character palette.
fn show_character_palette(
&mut self,
_: &ShowCharacterPalette,
window: &mut Window,
_cx: &mut Context,
) {
window.show_character_palette();
}
fn on_mouse_down(
&mut self,
_event: &gpui::MouseDownEvent,
_text_position: gpui::Point<gpui::Pixels>,
_w: &mut Window,
_cx: &mut Context,
) {
}
fn on_mouse_up(&mut self, _event: &gpui::MouseUpEvent, _w: &mut Window, _cx: &mut Context) {}
fn on_mouse_move(
&mut self,
_event: &gpui::MouseMoveEvent,
_text_position: gpui::Point<gpui::Pixels>,
_w: &mut Window,
_cx: &mut Context,
) {
}
fn on_mouse_down(
&mut self,
_event: &gpui::MouseDownEvent,
_text_position: gpui::Point<gpui::Pixels>,
_w: &mut Window,
_cx: &mut Context,
) {
}
fn on_mouse_up(&mut self, _event: &gpui::MouseUpEvent, _w: &mut Window, _cx: &mut Context) {}
fn on_mouse_move(
&mut self,
_event: &gpui::MouseMoveEvent,
_text_position: gpui::Point<gpui::Pixels>,
_w: &mut Window,
_cx: &mut Context,
) {
}
}
/// Registers an handler function of [`EditableTextActionHandler`]
/// which is processed via the return value of [`EditableTextActionElement::state_entity_rc`].
macro_rules! register_action {
($action_element:expr, $func:ident) => {{
let entity_rc = $action_element.state_entity_rc().clone();
$action_element
.interactivity()
.on_action(move |action, window, cx| {
let weak_entity = entity_rc.borrow();
if let Some(entity) = weak_entity.upgrade() {
entity.update(cx, |state, cx| {
state.$func(action, window, cx);
});
}
});
}};
($action_element:expr, $func:ident) => {{
let entity_rc = $action_element.state_entity_rc().clone();
$action_element
.interactivity()
.on_action(move |action, window, cx| {
let weak_entity = entity_rc.borrow();
if let Some(entity) = weak_entity.upgrade() {
entity.update(cx, |state, cx| {
state.$func(action, window, cx);
});
}
});
}};
}
/// Generic trait to support an element backed by an internal state entity to bind to all editable-text input actions.
pub(super) trait EditableTextActionElement<State> {
fn state_entity_rc(&self) -> &Rc<RefCell<WeakEntity<State>>>;
fn state_entity_rc(&self) -> &Rc<RefCell<WeakEntity<State>>>;
fn register_actions(&mut self)
where
Self: InteractiveElement,
State: for<'app> EditableTextActionHandler<gpui::Context<'app, State>>,
State: 'static,
{
register_action!(self, escape);
register_action!(self, insert_enter);
register_action!(self, insert_tab);
register_action!(self, delete_left);
register_action!(self, delete_right);
register_action!(self, delete_word_left);
register_action!(self, delete_word_right);
register_action!(self, delete_to_line_start);
register_action!(self, delete_to_line_end);
register_action!(self, nav_left);
register_action!(self, nav_right);
register_action!(self, nav_up);
register_action!(self, nav_down);
register_action!(self, nav_line_start);
register_action!(self, nav_line_end);
register_action!(self, nav_start);
register_action!(self, nav_end);
register_action!(self, nav_left_word);
register_action!(self, nav_right_word);
register_action!(self, select_all);
register_action!(self, select_left);
register_action!(self, select_right);
register_action!(self, select_up);
register_action!(self, select_down);
register_action!(self, select_start);
register_action!(self, select_end);
register_action!(self, select_left_word);
register_action!(self, select_right_word);
register_action!(self, cut);
register_action!(self, copy);
register_action!(self, paste);
register_action!(self, undo);
register_action!(self, redo);
register_action!(self, show_character_palette);
}
fn register_actions(&mut self)
where
Self: InteractiveElement,
State: for<'app> EditableTextActionHandler<gpui::Context<'app, State>>,
State: 'static,
{
register_action!(self, escape);
register_action!(self, insert_enter);
register_action!(self, insert_tab);
register_action!(self, delete_left);
register_action!(self, delete_right);
register_action!(self, delete_word_left);
register_action!(self, delete_word_right);
register_action!(self, delete_to_line_start);
register_action!(self, delete_to_line_end);
register_action!(self, nav_left);
register_action!(self, nav_right);
register_action!(self, nav_up);
register_action!(self, nav_down);
register_action!(self, nav_line_start);
register_action!(self, nav_line_end);
register_action!(self, nav_start);
register_action!(self, nav_end);
register_action!(self, nav_left_word);
register_action!(self, nav_right_word);
register_action!(self, select_all);
register_action!(self, select_left);
register_action!(self, select_right);
register_action!(self, select_up);
register_action!(self, select_down);
register_action!(self, select_start);
register_action!(self, select_end);
register_action!(self, select_left_word);
register_action!(self, select_right_word);
register_action!(self, cut);
register_action!(self, copy);
register_action!(self, paste);
register_action!(self, undo);
register_action!(self, redo);
register_action!(self, show_character_palette);
}
}
+127 -127
View File
@@ -7,156 +7,156 @@ pub const BLINK_INTERVAL_500MS: Duration = Duration::from_millis(500);
/// Events emitted that the [`Caret`] listens to.
pub enum CaretNotify {
/// The caret should pause blinking in response to a user-action
PauseBlinking,
/// The caret should pause blinking in response to a user-action
PauseBlinking,
}
/// State of an EditableText caret cursor, which supports features like blinking.
/// Blinking is disabled by default.
pub struct Caret {
/// The frequency at which the caret blinks
interval: Duration,
generation: usize,
/// Whether the caret is presently visible in this frame
visible: bool,
/// Whether the caret's EditableText element is currently focused.
/// Caret is only eligible to be blinking if currently focused.
has_focus: bool,
/// true when blinking is active but paused for some number of frames
paused: bool,
#[allow(dead_code)]
subscriptions: SmallVec<[Subscription; 2]>,
/// Tracks whether we were focused on the last update.
was_focused: bool,
/// The frequency at which the caret blinks
interval: Duration,
generation: usize,
/// Whether the caret is presently visible in this frame
visible: bool,
/// Whether the caret's EditableText element is currently focused.
/// Caret is only eligible to be blinking if currently focused.
has_focus: bool,
/// true when blinking is active but paused for some number of frames
paused: bool,
#[allow(dead_code)]
subscriptions: SmallVec<[Subscription; 2]>,
/// Tracks whether we were focused on the last update.
was_focused: bool,
}
impl Default for Caret {
fn default() -> Self {
Self {
interval: Duration::ZERO,
generation: Default::default(),
visible: false,
has_focus: false,
paused: false,
subscriptions: SmallVec::new(),
was_focused: false,
}
}
fn default() -> Self {
Self {
interval: Duration::ZERO,
generation: Default::default(),
visible: false,
has_focus: false,
paused: false,
subscriptions: SmallVec::new(),
was_focused: false,
}
}
}
impl Caret {
/// Returns the duration of the current blink interval
pub fn blink_interval(&self) -> Duration {
self.interval
}
/// Returns the duration of the current blink interval
pub fn blink_interval(&self) -> Duration {
self.interval
}
/// Sets the blinking interval of the caret.
pub fn set_blink_interval(&mut self, interval: Duration) {
self.interval = interval;
}
/// Sets the blinking interval of the caret.
pub fn set_blink_interval(&mut self, interval: Duration) {
self.interval = interval;
}
/// Sets the blinking interval of the caret.
pub fn with_blink_interval(mut self, interval: Duration) -> Self {
self.set_blink_interval(interval);
self
}
/// Sets the blinking interval of the caret.
pub fn with_blink_interval(mut self, interval: Duration) -> Self {
self.set_blink_interval(interval);
self
}
/// Sets the blinking interval of the caret to the global "default".
/// The true default of the caret is "do not blink".
pub fn with_blink_interval_500ms(self) -> Self {
self.with_blink_interval(BLINK_INTERVAL_500MS)
}
/// Sets the blinking interval of the caret to the global "default".
/// The true default of the caret is "do not blink".
pub fn with_blink_interval_500ms(self) -> Self {
self.with_blink_interval(BLINK_INTERVAL_500MS)
}
/// Listens for CaretNotify events on an entity (e.g. [`EditableTextState`]).
pub fn subscribe_to<E>(&mut self, emitter: &Entity<E>, cx: &mut Context<Self>)
where
E: EventEmitter<CaretNotify>,
{
let handle = cx.subscribe(emitter, |state, _emitter, event, cx| match event {
CaretNotify::PauseBlinking => {
if state.interval.is_zero() || !state.has_focus {
return;
}
/// Listens for CaretNotify events on an entity (e.g. [`EditableTextState`]).
pub fn subscribe_to<E>(&mut self, emitter: &Entity<E>, cx: &mut Context<Self>)
where
E: EventEmitter<CaretNotify>,
{
let handle = cx.subscribe(emitter, |state, _emitter, event, cx| match event {
CaretNotify::PauseBlinking => {
if state.interval.is_zero() || !state.has_focus {
return;
}
// Temporarily pauses blinking and leaves the caret visible. Blinking will resume after
// the pre-established interval elapses from the time this is called.
if !state.visible {
state.visible = true;
}
state.paused = true;
state.restart_blink_ticker(cx);
cx.notify();
}
});
self.subscriptions.push(handle);
}
// Temporarily pauses blinking and leaves the caret visible. Blinking will resume after
// the pre-established interval elapses from the time this is called.
if !state.visible {
state.visible = true;
}
state.paused = true;
state.restart_blink_ticker(cx);
cx.notify();
}
});
self.subscriptions.push(handle);
}
/// Processes updates during prepaint and returns whether the caret is currently visible.
pub(super) fn update_focus(&mut self, is_focused: bool, cx: &mut Context<Self>) -> bool {
let was_focused = self.was_focused;
self.was_focused = is_focused;
/// Processes updates during prepaint and returns whether the caret is currently visible.
pub(super) fn update_focus(&mut self, is_focused: bool, cx: &mut Context<Self>) -> bool {
let was_focused = self.was_focused;
self.was_focused = is_focused;
// Caret has no blinking interval, it is always visible
if self.interval.is_zero() {
return is_focused;
}
// Caret has no blinking interval, it is always visible
if self.interval.is_zero() {
return is_focused;
}
match (was_focused, is_focused) {
// Caret has a blinking interval, and gained focused.
(false, true) => {
self.has_focus = true;
self.paused = false;
match (was_focused, is_focused) {
// Caret has a blinking interval, and gained focused.
(false, true) => {
self.has_focus = true;
self.paused = false;
// Render in this frame and restart the blinking ticker.
self.visible = true;
self.restart_blink_ticker(cx);
true
}
// Caret has a blinking interval and lost focus
(true, false) => {
self.has_focus = false;
self.visible = false;
self.paused = false;
cx.notify();
false
}
// Has a blinking interval, but focus has not changed.
// Only render if currently visible (based on blink ticker).
_ => self.visible,
}
}
// Render in this frame and restart the blinking ticker.
self.visible = true;
self.restart_blink_ticker(cx);
true
}
// Caret has a blinking interval and lost focus
(true, false) => {
self.has_focus = false;
self.visible = false;
self.paused = false;
cx.notify();
false
}
// Has a blinking interval, but focus has not changed.
// Only render if currently visible (based on blink ticker).
_ => self.visible,
}
}
fn restart_blink_ticker(&mut self, cx: &mut Context<Self>) {
let generation = self.generation.wrapping_add(1);
self.generation = generation;
fn restart_blink_ticker(&mut self, cx: &mut Context<Self>) {
let generation = self.generation.wrapping_add(1);
self.generation = generation;
let interval = self.interval;
cx.spawn(async move |this, cx| {
cx.background_executor().timer(interval).await;
let interval = self.interval;
cx.spawn(async move |this, cx| {
cx.background_executor().timer(interval).await;
let Some(this) = this.upgrade() else { return };
this.update(cx, |this, cx| {
// If the generation has changed, that means a new task was spawned.
// This one should be no-op since a new task is owning the blinking state.
if this.generation == generation {
// PauseBlinking increments the generation via restart_ticker,
// so we can always unpause the blinking if the generation is unchanged.
this.paused = false;
let Some(this) = this.upgrade() else { return };
this.update(cx, |this, cx| {
// If the generation has changed, that means a new task was spawned.
// This one should be no-op since a new task is owning the blinking state.
if this.generation == generation {
// PauseBlinking increments the generation via restart_ticker,
// so we can always unpause the blinking if the generation is unchanged.
this.paused = false;
// This was the last tick/blink before we lost focus.
// Should now go inert until focus is regained.
if !this.has_focus {
return;
}
// This was the last tick/blink before we lost focus.
// Should now go inert until focus is regained.
if !this.has_focus {
return;
}
// We still have focus, toggle whether caret is visible and make sure the owning element re-renders.
this.visible = !this.visible;
cx.notify();
// We still have focus, toggle whether caret is visible and make sure the owning element re-renders.
this.visible = !this.visible;
cx.notify();
// Start a fresh cycle by respawning the task.
this.restart_blink_ticker(cx);
}
});
})
.detach();
}
// Start a fresh cycle by respawning the task.
this.restart_blink_ticker(cx);
}
});
})
.detach();
}
}
File diff suppressed because it is too large Load Diff
+112 -112
View File
@@ -1,7 +1,7 @@
use smallvec::SmallVec;
use std::{
ops::Range,
time::{Duration, Instant},
ops::Range,
time::{Duration, Instant},
};
/// Maximum number of history entries to keep.
@@ -12,147 +12,147 @@ pub const DEFAULT_GROUP_INTERVAL: Duration = Duration::from_millis(300);
// TODO: Should history get attached directly to storage? currently its per text field and operate both on storage and selection
pub struct EditableTextHistory {
/// The maximum duration between changes to `content` that can be grouped together as a single entry in the history log.
grouping_interval: Duration,
/// Stack of previous states for undo.
undo_stack: SmallVec<[HistoryEntry; MAX_HISTORY_LEN]>,
/// Stack of undone states for redo.
redo_stack: SmallVec<[HistoryEntry; MAX_HISTORY_LEN]>,
/// The maximum duration between changes to `content` that can be grouped together as a single entry in the history log.
grouping_interval: Duration,
/// Stack of previous states for undo.
undo_stack: SmallVec<[HistoryEntry; MAX_HISTORY_LEN]>,
/// Stack of undone states for redo.
redo_stack: SmallVec<[HistoryEntry; MAX_HISTORY_LEN]>,
}
impl Default for EditableTextHistory {
fn default() -> Self {
Self {
grouping_interval: DEFAULT_GROUP_INTERVAL,
undo_stack: Default::default(),
redo_stack: Default::default(),
}
}
fn default() -> Self {
Self {
grouping_interval: DEFAULT_GROUP_INTERVAL,
undo_stack: Default::default(),
redo_stack: Default::default(),
}
}
}
/// A patch-based history entry for memory-efficient undo/redo operations.
/// Instead of storing the full content, we store only the change needed to reverse the edit.
#[derive(Clone, Debug)]
pub struct HistoryEntry {
/// The byte range that was modified (after the edit, for undo; before the edit, for redo).
pub range: Range<usize>,
/// The text that was replaced (to restore on undo).
pub old_text: String,
/// The length of the new text that replaced old_text (to know how much to remove on undo).
pub new_text_len: usize,
/// The selection range before the edit.
pub selected_range: (usize, usize),
/// Timestamp for grouping consecutive edits.
pub timestamp: Instant,
/// The byte range that was modified (after the edit, for undo; before the edit, for redo).
pub range: Range<usize>,
/// The text that was replaced (to restore on undo).
pub old_text: String,
/// The length of the new text that replaced old_text (to know how much to remove on undo).
pub new_text_len: usize,
/// The selection range before the edit.
pub selected_range: (usize, usize),
/// Timestamp for grouping consecutive edits.
pub timestamp: Instant,
}
#[derive(Clone, Debug)]
pub enum HistoryKind {
Undo,
Redo,
Undo,
Redo,
}
impl EditableTextHistory {
pub fn set_grouping_interval(&mut self, interval: Duration) {
self.grouping_interval = interval;
}
pub fn set_grouping_interval(&mut self, interval: Duration) {
self.grouping_interval = interval;
}
pub fn record(
&mut self,
range: Range<usize>,
old_text: &str,
new_text_len: usize,
selected_range: (usize, usize),
) {
let now = Instant::now();
pub fn record(
&mut self,
range: Range<usize>,
old_text: &str,
new_text_len: usize,
selected_range: (usize, usize),
) {
let now = Instant::now();
// Check if we should group with the last entry
if let Some(last) = self.undo_stack.last_mut()
&& now.duration_since(last.timestamp) < self.grouping_interval
{
// The change was triggered within group interval timing.
// Try to extend the existing patch (which is a mutation).
// If extending successeds, then we can early-out. Otherwise the mutation is non-contiguous.
if last.extend(&range, new_text_len) {
return;
}
}
// Check if we should group with the last entry
if let Some(last) = self.undo_stack.last_mut()
&& now.duration_since(last.timestamp) < self.grouping_interval
{
// The change was triggered within group interval timing.
// Try to extend the existing patch (which is a mutation).
// If extending successeds, then we can early-out. Otherwise the mutation is non-contiguous.
if last.extend(&range, new_text_len) {
return;
}
}
// Limit history size
if self.undo_stack.len() >= MAX_HISTORY_LEN {
self.undo_stack.remove(0);
}
// Limit history size
if self.undo_stack.len() >= MAX_HISTORY_LEN {
self.undo_stack.remove(0);
}
self.push(
HistoryKind::Undo,
HistoryEntry {
range: range.start..range.start + new_text_len,
old_text: old_text.to_string(),
new_text_len,
selected_range,
timestamp: now,
},
);
self.push(
HistoryKind::Undo,
HistoryEntry {
range: range.start..range.start + new_text_len,
old_text: old_text.to_string(),
new_text_len,
selected_range,
timestamp: now,
},
);
// New edit invalidates redo stack
self.redo_stack.clear();
}
// New edit invalidates redo stack
self.redo_stack.clear();
}
fn stack(&self, kind: HistoryKind) -> &SmallVec<[HistoryEntry; MAX_HISTORY_LEN]> {
// NOTE: Could be an internal map
match kind {
HistoryKind::Undo => &self.undo_stack,
HistoryKind::Redo => &self.redo_stack,
}
}
fn stack(&self, kind: HistoryKind) -> &SmallVec<[HistoryEntry; MAX_HISTORY_LEN]> {
// NOTE: Could be an internal map
match kind {
HistoryKind::Undo => &self.undo_stack,
HistoryKind::Redo => &self.redo_stack,
}
}
fn stack_mut(&mut self, kind: HistoryKind) -> &mut SmallVec<[HistoryEntry; MAX_HISTORY_LEN]> {
match kind {
HistoryKind::Undo => &mut self.undo_stack,
HistoryKind::Redo => &mut self.redo_stack,
}
}
fn stack_mut(&mut self, kind: HistoryKind) -> &mut SmallVec<[HistoryEntry; MAX_HISTORY_LEN]> {
match kind {
HistoryKind::Undo => &mut self.undo_stack,
HistoryKind::Redo => &mut self.redo_stack,
}
}
pub fn has_next(&self, kind: HistoryKind) -> bool {
!self.stack(kind).is_empty()
}
pub fn has_next(&self, kind: HistoryKind) -> bool {
!self.stack(kind).is_empty()
}
pub fn push(&mut self, kind: HistoryKind, entry: HistoryEntry) {
self.stack_mut(kind).push(entry);
}
pub fn push(&mut self, kind: HistoryKind, entry: HistoryEntry) {
self.stack_mut(kind).push(entry);
}
pub fn take(&mut self, kind: HistoryKind) -> Option<HistoryEntry> {
self.stack_mut(kind).pop()
}
pub fn take(&mut self, kind: HistoryKind) -> Option<HistoryEntry> {
self.stack_mut(kind).pop()
}
}
impl HistoryEntry {
fn extend(&mut self, range: &Range<usize>, new_text_len: usize) -> bool {
// NOTE: Could be more robust. Currently only supports human-written extensions from start towards end.
fn extend(&mut self, range: &Range<usize>, new_text_len: usize) -> bool {
// NOTE: Could be more robust. Currently only supports human-written extensions from start towards end.
// ranges must be contiguous in order to integrate/extend
if self.range.end != range.start {
return false;
}
// ranges must be contiguous in order to integrate/extend
if self.range.end != range.start {
return false;
}
self.range.end = range.start + new_text_len;
self.new_text_len += new_text_len;
self.range.end = range.start + new_text_len;
self.new_text_len += new_text_len;
true
}
true
}
pub fn char_range(&self, max_len: usize) -> Range<usize> {
let undo_start = self.range.start;
let undo_end = (self.range.start + self.new_text_len).min(max_len);
undo_start..undo_end
}
pub fn char_range(&self, max_len: usize) -> Range<usize> {
let undo_start = self.range.start;
let undo_end = (self.range.start + self.new_text_len).min(max_len);
undo_start..undo_end
}
pub fn as_inverted(self, prev_text_at_range: String) -> Self {
HistoryEntry {
range: self.range.start..self.range.start + self.old_text.len(),
old_text: prev_text_at_range,
new_text_len: self.old_text.len(),
selected_range: self.selected_range,
timestamp: self.timestamp,
}
}
pub fn as_inverted(self, prev_text_at_range: String) -> Self {
HistoryEntry {
range: self.range.start..self.range.start + self.old_text.len(),
old_text: prev_text_at_range,
new_text_len: self.old_text.len(),
selected_range: self.selected_range,
timestamp: self.timestamp,
}
}
}
@@ -4,100 +4,100 @@ use std::{ops::Range, sync::Arc};
/// Data used across successive layout requests to gauge whether layout must be recomputed.
#[derive(Default, Clone, Copy)]
pub(super) struct EditableTextLayoutState {
/// The last known width at which the lines were wrapped.
pub wrap_width: Option<Pixels>,
/// The last known size of the text, as generated during layout.
pub size: Option<Size<Pixels>>,
/// The last seen version of `storage` (for tracking when lines need to be reprocessed during layout)
pub last_seen_storage_version: u16,
/// The last known width at which the lines were wrapped.
pub wrap_width: Option<Pixels>,
/// The last known size of the text, as generated during layout.
pub size: Option<Size<Pixels>>,
/// The last seen version of `storage` (for tracking when lines need to be reprocessed during layout)
pub last_seen_storage_version: u16,
}
/// Internal state/result after the element has recomputed layout.
#[derive(Default)]
pub(super) struct EditableTextLayoutResult {
/// Whether the element supports multiple lines of text
pub supports_multiline: bool,
/// Whether the element is currently accepting inputs
pub accepts_input: bool,
/// The last seen scroll position and size of the element
pub scroll_bounds: Bounds<Pixels>,
pub state: EditableTextLayoutState,
/// The `ShapedLine` produced by the painter's `prepaint`.
/// Cached so IME `bounds_for_range` / `character_index_for_point` can evaluate without re-shaping.
pub lines: Vec<TextLineSegment>,
pub line_height: Pixels,
/// The next position the scroll view should move to.
/// Set by the state in response to user actions.
pub next_scroll_offset: Option<Point<Pixels>>,
/// Whether the element supports multiple lines of text
pub supports_multiline: bool,
/// Whether the element is currently accepting inputs
pub accepts_input: bool,
/// The last seen scroll position and size of the element
pub scroll_bounds: Bounds<Pixels>,
pub state: EditableTextLayoutState,
/// The `ShapedLine` produced by the painter's `prepaint`.
/// Cached so IME `bounds_for_range` / `character_index_for_point` can evaluate without re-shaping.
pub lines: Vec<TextLineSegment>,
pub line_height: Pixels,
/// The next position the scroll view should move to.
/// Set by the state in response to user actions.
pub next_scroll_offset: Option<Point<Pixels>>,
}
/// A segment of text that is a single logical/document line but can take up multiple rows due to wrapping.
pub(super) struct TextLineSegment {
/// The utf8 byte range in the content string that this line covers.
pub text_range: Range<usize>,
/// The shaped and wrapped text for this line, if available.
pub wrapped_line: Option<Arc<WrappedLine>>,
/// The utf8 byte range in the content string that this line covers.
pub text_range: Range<usize>,
/// The shaped and wrapped text for this line, if available.
pub wrapped_line: Option<Arc<WrappedLine>>,
/// The y-coordinate of this segment which can be multiplied by the line_height
/// to get its pixel location relative to the bounds of the text area.
pub pos_y: usize,
/// The y-coordinate of this segment which can be multiplied by the line_height
/// to get its pixel location relative to the bounds of the text area.
pub pos_y: usize,
}
impl TextLineSegment {
/// The number of visual lines this segment encapsulates,
/// since it can occupy multiple rows due to wrapping.
pub fn row_count(&self) -> usize {
let count = self
.wrapped_line
.as_ref()
.map(|line| line.wrap_boundaries().len());
count.unwrap_or_default() + 1
}
/// The number of visual lines this segment encapsulates,
/// since it can occupy multiple rows due to wrapping.
pub fn row_count(&self) -> usize {
let count = self
.wrapped_line
.as_ref()
.map(|line| line.wrap_boundaries().len());
count.unwrap_or_default() + 1
}
/// Returns true if the line contains a given position (e.g. for finding the line containing the caret).
/// If `includes_end` is true, the end of the line is treated as inclusive instead of exclusive.
pub fn contains_position(&self, pos: usize, include_end: bool) -> bool {
if self.text_range.is_empty() {
return pos == self.text_range.start;
}
/// Returns true if the line contains a given position (e.g. for finding the line containing the caret).
/// If `includes_end` is true, the end of the line is treated as inclusive instead of exclusive.
pub fn contains_position(&self, pos: usize, include_end: bool) -> bool {
if self.text_range.is_empty() {
return pos == self.text_range.start;
}
if include_end {
(self.text_range.start..=self.text_range.end).contains(&pos)
} else {
self.text_range.contains(&pos)
}
}
if include_end {
(self.text_range.start..=self.text_range.end).contains(&pos)
} else {
self.text_range.contains(&pos)
}
}
/// Returns the index of the character within this segment that is closest
/// to the provided screen space position.
/// The character index returned is in absolute space; it is not relative to this segment.
pub fn character_index_at_point(&self, point: Point<Pixels>, line_height: Pixels) -> usize {
let mut offset = 0usize;
if !self.text_range.is_empty()
&& let Some(wrapped) = &self.wrapped_line
{
offset = wrapped
.closest_index_for_position(point, line_height)
.unwrap_or_else(|closest| closest)
.min(wrapped.text.len());
}
self.text_range.start + offset
}
/// Returns the index of the character within this segment that is closest
/// to the provided screen space position.
/// The character index returned is in absolute space; it is not relative to this segment.
pub fn character_index_at_point(&self, point: Point<Pixels>, line_height: Pixels) -> usize {
let mut offset = 0usize;
if !self.text_range.is_empty()
&& let Some(wrapped) = &self.wrapped_line
{
offset = wrapped
.closest_index_for_position(point, line_height)
.unwrap_or_else(|closest| closest)
.min(wrapped.text.len());
}
self.text_range.start + offset
}
/// Returns the screen space position of the character at the position provided.
/// The position of the character must be absolute to the string this segment
/// partially represents, it is converted to a relative offset internally.
pub fn position_for_index(
&self,
character_index: usize,
line_height: Pixels,
) -> Option<Point<Pixels>> {
let wrapped = self.wrapped_line.as_ref()?;
// the position in the text relative to this line segment
let relative_text_pos = character_index
.saturating_sub(self.text_range.start)
.min(wrapped.text.len());
// the screen position of the character in this line segment
wrapped.position_for_index(relative_text_pos, line_height)
}
/// Returns the screen space position of the character at the position provided.
/// The position of the character must be absolute to the string this segment
/// partially represents, it is converted to a relative offset internally.
pub fn position_for_index(
&self,
character_index: usize,
line_height: Pixels,
) -> Option<Point<Pixels>> {
let wrapped = self.wrapped_line.as_ref()?;
// the position in the text relative to this line segment
let relative_text_pos = character_index
.saturating_sub(self.text_range.start)
.min(wrapped.text.len());
// the screen position of the character in this line segment
wrapped.position_for_index(relative_text_pos, line_height)
}
}
File diff suppressed because it is too large Load Diff
+191 -191
View File
@@ -4,202 +4,202 @@ use unicode_segmentation::UnicodeSegmentation;
/// Describes a boundary within a chunk of text.
pub enum TextBoundary {
/// The utf-8 character
Graphmeme,
/// The current word (using whitespace as delimiters)
Word,
/// The current line
Line,
/// The entire document
Document,
/// The utf-8 character
Graphmeme,
/// The current word (using whitespace as delimiters)
Word,
/// The current line
Line,
/// The entire document
Document,
}
/// Implement this trait to create a storage medium that can be used as the content of EditableText elements.
/// Default implementation is [`StringStorage`].
pub trait UnicodeTextStorage {
/// Returns the version/generation of the content, which should be incremented ever time the
/// content is changed so that rendering elements can reprocess the contents via the text layout engine.
fn version(&self) -> u16;
/// Returns the version/generation of the content, which should be incremented ever time the
/// content is changed so that rendering elements can reprocess the contents via the text layout engine.
fn version(&self) -> u16;
/// Returns a reference to the utf8 string.
fn content_utf8(&self) -> &str;
/// 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;
/// Returns the UTF-16 length of the content.
fn len_utf16(&self) -> usize;
/// Replace contents within the provided range with the given str slice.
fn replace_range(&mut self, range: Range<usize>, text: &str);
/// Replace contents within the provided range with the given str slice.
fn replace_range(&mut self, range: Range<usize>, text: &str);
/// Returns the utf16 position equivalent of the provided utf8 character position.
fn utf_offset_8to16(&self, pos_uft8: usize) -> usize {
// Fast path: if offset is 0, return 0
if pos_uft8 == 0 {
return 0;
}
/// Returns the utf16 position equivalent of the provided utf8 character position.
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();
}
// 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
}
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
}
/// Returns the utf8 position equivalent of the provided utf16 character position.
fn utf_offset_16to8(&self, pos_utf16: usize) -> usize {
// Fast path: if offset is 0, return 0
if pos_utf16 == 0 {
return 0;
}
/// Returns the utf8 position equivalent of the provided utf16 character position.
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()
}
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()
}
/// Converts a utf8 character range into a utf16 character range.
fn utf_range_8to16(&self, range_utf8: &Range<usize>) -> Range<usize> {
self.utf_offset_8to16(range_utf8.start)..self.utf_offset_8to16(range_utf8.end)
}
/// Converts a utf8 character range into a utf16 character range.
fn utf_range_8to16(&self, range_utf8: &Range<usize>) -> Range<usize> {
self.utf_offset_8to16(range_utf8.start)..self.utf_offset_8to16(range_utf8.end)
}
/// Converts a utf16 character range into a utf8 character range.
fn utf_range_16to8(&self, range_utf16: &Range<usize>) -> Range<usize> {
self.utf_offset_16to8(range_utf16.start)..self.utf_offset_16to8(range_utf16.end)
}
/// Converts a utf16 character range into a utf8 character range.
fn utf_range_16to8(&self, range_utf16: &Range<usize>) -> Range<usize> {
self.utf_offset_16to8(range_utf16.start)..self.utf_offset_16to8(range_utf16.end)
}
/// Builds a utf8 character range based on a caret position within the storage,
/// the direction to traverse, and the boundary to stop at.
/// The start of the range will be the earlier position (destination if Back, caret if Forward),
/// and the end will be the later position (caret if Back, destination if Forward).
fn range_from_caret(
&self,
caret: usize,
direction: NavigationDirection,
magnitude: TextBoundary,
) -> Range<usize> {
let offset = self.offset_from_caret(caret, direction, magnitude);
match direction {
NavigationDirection::Back => offset..caret,
NavigationDirection::Forward => caret..offset,
}
}
/// Builds a utf8 character range based on a caret position within the storage,
/// the direction to traverse, and the boundary to stop at.
/// The start of the range will be the earlier position (destination if Back, caret if Forward),
/// and the end will be the later position (caret if Back, destination if Forward).
fn range_from_caret(
&self,
caret: usize,
direction: NavigationDirection,
magnitude: TextBoundary,
) -> Range<usize> {
let offset = self.offset_from_caret(caret, direction, magnitude);
match direction {
NavigationDirection::Back => offset..caret,
NavigationDirection::Forward => caret..offset,
}
}
/// Finds the next location from the caret based on the direction to traverse and the boundary to stop at.
fn offset_from_caret(
&self,
caret: usize,
direction: NavigationDirection,
boundary: TextBoundary,
) -> usize {
use NavigationDirection::*;
use TextBoundary::*;
match (direction, boundary) {
(Back, Graphmeme) => {
if caret == 0 {
return 0;
}
/// Finds the next location from the caret based on the direction to traverse and the boundary to stop at.
fn offset_from_caret(
&self,
caret: usize,
direction: NavigationDirection,
boundary: TextBoundary,
) -> usize {
use NavigationDirection::*;
use TextBoundary::*;
match (direction, boundary) {
(Back, Graphmeme) => {
if caret == 0 {
return 0;
}
let str = self.content_utf8();
let iter = str[..caret.min(str.len())].grapheme_indices(true);
iter.map(|(i, _)| i).next_back().unwrap_or(0)
}
(Forward, Graphmeme) => {
let str = self.content_utf8();
let len_utf8 = str.len();
if caret >= len_utf8 {
return len_utf8;
}
let str = self.content_utf8();
let iter = str[..caret.min(str.len())].grapheme_indices(true);
iter.map(|(i, _)| i).next_back().unwrap_or(0)
}
(Forward, Graphmeme) => {
let str = self.content_utf8();
let len_utf8 = str.len();
if caret >= len_utf8 {
return len_utf8;
}
let mut iter = str[caret..].grapheme_indices(true);
iter.nth(1).map(|(i, _)| caret + i).unwrap_or(len_utf8)
}
(Back, Word) => {
if caret == 0 {
return 0;
}
let mut iter = str[caret..].grapheme_indices(true);
iter.nth(1).map(|(i, _)| caret + i).unwrap_or(len_utf8)
}
(Back, Word) => {
if caret == 0 {
return 0;
}
let str = self.content_utf8();
let str = &str[..caret.min(str.len())];
let str = self.content_utf8();
let str = &str[..caret.min(str.len())];
let mut last_word_start = 0;
for (idx, _) in str.unicode_word_indices() {
if idx < caret {
last_word_start = idx;
}
}
let mut last_word_start = 0;
for (idx, _) in str.unicode_word_indices() {
if idx < caret {
last_word_start = idx;
}
}
if last_word_start == 0 && caret > 0 {
let trimmed = str.trim_end();
if trimmed.is_empty() {
return 0;
}
for (idx, _) in trimmed.unicode_word_indices() {
last_word_start = idx;
}
}
if last_word_start == 0 && caret > 0 {
let trimmed = str.trim_end();
if trimmed.is_empty() {
return 0;
}
for (idx, _) in trimmed.unicode_word_indices() {
last_word_start = idx;
}
}
last_word_start
}
(Forward, Word) => {
let str = self.content_utf8();
let len_utf8 = str.len();
if caret >= len_utf8 {
return len_utf8;
}
last_word_start
}
(Forward, Word) => {
let str = self.content_utf8();
let len_utf8 = str.len();
if caret >= len_utf8 {
return len_utf8;
}
let str = &str[caret..];
for (idx, word) in str.unicode_word_indices() {
let word_end = caret + idx + word.len();
if word_end > caret {
return word_end;
}
}
len_utf8
}
// Returns the utf-8 character position of first character after the first new-line
// preceding the character at the provided utf-8 character position.
(Back, Line) => {
let str = self.content_utf8();
let iter = str[..caret.min(str.len())].rfind('\n');
iter.map(|pos| pos + 1).unwrap_or(0)
}
// Returns the utf-8 character position of the character immediately before the first
// new-line character after the character at the provided utf-8 character position.
(Forward, Line) => {
let str = self.content_utf8();
let iter = str[caret.min(str.len())..].find('\n');
iter.map(|pos| caret + pos).unwrap_or(str.len())
}
(Back, Document) => 0,
(Forward, Document) => self.content_utf8().len(),
}
}
let str = &str[caret..];
for (idx, word) in str.unicode_word_indices() {
let word_end = caret + idx + word.len();
if word_end > caret {
return word_end;
}
}
len_utf8
}
// Returns the utf-8 character position of first character after the first new-line
// preceding the character at the provided utf-8 character position.
(Back, Line) => {
let str = self.content_utf8();
let iter = str[..caret.min(str.len())].rfind('\n');
iter.map(|pos| pos + 1).unwrap_or(0)
}
// Returns the utf-8 character position of the character immediately before the first
// new-line character after the character at the provided utf-8 character position.
(Forward, Line) => {
let str = self.content_utf8();
let iter = str[caret.min(str.len())..].find('\n');
iter.map(|pos| caret + pos).unwrap_or(str.len())
}
(Back, Document) => 0,
(Forward, Document) => self.content_utf8().len(),
}
}
/// Returns the start and end of the word the position resides within.
fn word_range_at(&self, position: usize) -> Range<usize> {
let offset = position.min(self.content_utf8().len());
/// Returns the start and end of the word the position resides within.
fn word_range_at(&self, position: usize) -> Range<usize> {
let offset = position.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;
}
}
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
}
offset..offset
}
}
/// [`UnicodeTextStorage`] implementation for [`String`].
@@ -207,35 +207,35 @@ pub trait UnicodeTextStorage {
/// Its a decent default for editable text fields though.
#[derive(Clone, Default)]
pub struct StringStorage {
value: String,
version: u16,
value: String,
version: u16,
}
impl<S> From<S> for StringStorage
where
S: Into<String>,
S: Into<String>,
{
fn from(value: S) -> Self {
Self {
value: value.into(),
version: u16::default(),
}
}
fn from(value: S) -> Self {
Self {
value: value.into(),
version: u16::default(),
}
}
}
impl UnicodeTextStorage for StringStorage {
fn version(&self) -> u16 {
self.version
}
fn version(&self) -> u16 {
self.version
}
fn content_utf8(&self) -> &str {
self.value.as_str()
}
fn content_utf8(&self) -> &str {
self.value.as_str()
}
fn len_utf16(&self) -> usize {
self.value.chars().map(|c| c.len_utf16()).sum()
}
fn len_utf16(&self) -> usize {
self.value.chars().map(|c| c.len_utf16()).sum()
}
fn replace_range(&mut self, range: Range<usize>, text: &str) {
self.value.replace_range(range, &text);
self.version = self.version.wrapping_add(1);
}
fn replace_range(&mut self, range: Range<usize>, text: &str) {
self.value.replace_range(range, &text);
self.version = self.version.wrapping_add(1);
}
}