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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user