diff --git a/crates/gpui_elements/src/editable_text.rs b/crates/gpui_elements/src/editable_text.rs index cf7fee6d3f..e5b7e600e2 100644 --- a/crates/gpui_elements/src/editable_text.rs +++ b/crates/gpui_elements/src/editable_text.rs @@ -1,23 +1,17 @@ pub mod actions; -mod input_element; -mod input_state; +mod element; pub mod notify; -mod shared_element; -mod shared_state; +mod state; mod storage; -mod text_area_element; -mod text_area_state; -pub use input_element::*; -pub use input_state::*; -pub use shared_state::*; +pub use element::*; +pub use state::*; pub use storage::*; -pub use text_area_element::*; -pub use text_area_state::*; /* TODO list - disabled input/area - remove gpuikit based input +- auto-scroll when cursor moves - cursor blinking - color styling configs - undo/redo @@ -27,6 +21,4 @@ pub use text_area_state::*; */ /* Open questions: -Is is practical and worthwhile to have separate element and state implementations? -The only real difference is how some input-action handler functions are processed. */ diff --git a/crates/gpui_elements/src/editable_text/actions.rs b/crates/gpui_elements/src/editable_text/actions.rs index 31073545f0..3cc2671dcd 100644 --- a/crates/gpui_elements/src/editable_text/actions.rs +++ b/crates/gpui_elements/src/editable_text/actions.rs @@ -1,9 +1,6 @@ +use gpui::{Action, Context, InteractiveElement, WeakEntity, Window}; use std::{cell::RefCell, rc::Rc}; -use gpui::{Action, AppContext, Context, InteractiveElement, WeakEntity, Window}; - -use crate::editable_text::StateBackedEditableText; - /// The key context used for input element keybindings. pub const DEFAULT_INPUT_CONTEXT: &str = "Input"; @@ -146,69 +143,59 @@ pub fn default_bindings() -> gpui::ActionBindingCollection { bindings } -pub trait EditableTextActionHandler<'app>: Sized { - type Context: AppContext; +pub trait EditableTextActionHandler: Sized { + fn escape(&mut self, _: &Escape, _w: &mut Window, _cx: &mut Context) {} - fn escape(&mut self, _: &Escape, _w: &mut Window, _cx: &mut Self::Context) {} + fn insert_enter(&mut self, _: &Enter, _w: &mut Window, _cx: &mut Context) {} + fn insert_tab(&mut self, _: &Tab, _w: &mut Window, _cx: &mut Context) {} - fn insert_enter(&mut self, _: &Enter, _w: &mut Window, _cx: &mut Self::Context) {} - fn insert_tab(&mut self, _: &Tab, _w: &mut Window, _cx: &mut Self::Context) {} + fn backspace(&mut self, _: &Backspace, _w: &mut Window, _cx: &mut Context) {} + fn delete(&mut self, _: &Delete, _w: &mut Window, _cx: &mut Context) {} - fn backspace(&mut self, _: &Backspace, _w: &mut Window, _cx: &mut Self::Context) {} - fn delete(&mut self, _: &Delete, _w: &mut Window, _cx: &mut Self::Context) {} - - fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, _cx: &mut Self::Context) {} - fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, _cx: &mut Self::Context) { - } + fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, _cx: &mut Context) {} + fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, _cx: &mut Context) {} fn delete_to_line_start( &mut self, _: &DeleteToBeginningOfLine, _w: &mut Window, - _cx: &mut Self::Context, - ) { - } - fn delete_to_line_end( - &mut self, - _: &DeleteToEndOfLine, - _w: &mut Window, - _cx: &mut Self::Context, + _cx: &mut Context, ) { } + fn delete_to_line_end(&mut self, _: &DeleteToEndOfLine, _w: &mut Window, _cx: &mut Context) {} - fn nav_left(&mut self, _: &Left, _w: &mut Window, _cx: &mut Self::Context) {} - fn nav_right(&mut self, _: &Right, _w: &mut Window, _cx: &mut Self::Context) {} - fn nav_up(&mut self, _: &Up, _w: &mut Window, _cx: &mut Self::Context) {} - fn nav_down(&mut self, _: &Down, _w: &mut Window, _cx: &mut Self::Context) {} - fn nav_line_start(&mut self, _: &Home, _w: &mut Window, _cx: &mut Self::Context) {} - fn nav_line_end(&mut self, _: &End, _w: &mut Window, _cx: &mut Self::Context) {} - fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, _cx: &mut Self::Context) {} - fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, _cx: &mut Self::Context) {} - fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, _cx: &mut Self::Context) {} - fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, _cx: &mut Self::Context) {} + fn nav_left(&mut self, _: &Left, _w: &mut Window, _cx: &mut Context) {} + fn nav_right(&mut self, _: &Right, _w: &mut Window, _cx: &mut Context) {} + fn nav_up(&mut self, _: &Up, _w: &mut Window, _cx: &mut Context) {} + fn nav_down(&mut self, _: &Down, _w: &mut Window, _cx: &mut Context) {} + fn nav_line_start(&mut self, _: &Home, _w: &mut Window, _cx: &mut Context) {} + fn nav_line_end(&mut self, _: &End, _w: &mut Window, _cx: &mut Context) {} + fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, _cx: &mut Context) {} + fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, _cx: &mut Context) {} + fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, _cx: &mut Context) {} + fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, _cx: &mut Context) {} - fn select_all(&mut self, _: &SelectAll, _w: &mut Window, _cx: &mut Self::Context) {} - fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, _cx: &mut Self::Context) {} - fn select_right(&mut self, _: &SelectRight, _w: &mut Window, _cx: &mut Self::Context) {} - fn select_up(&mut self, _: &SelectUp, _w: &mut Window, _cx: &mut Self::Context) {} - fn select_down(&mut self, _: &SelectDown, _w: &mut Window, _cx: &mut Self::Context) {} - fn select_start(&mut self, _: &SelectToBeginning, _w: &mut Window, _cx: &mut Self::Context) {} - fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, _cx: &mut Self::Context) {} - fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, _cx: &mut Self::Context) {} - fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, _cx: &mut Self::Context) { - } + fn select_all(&mut self, _: &SelectAll, _w: &mut Window, _cx: &mut Context) {} + fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, _cx: &mut Context) {} + fn select_right(&mut self, _: &SelectRight, _w: &mut Window, _cx: &mut Context) {} + fn select_up(&mut self, _: &SelectUp, _w: &mut Window, _cx: &mut Context) {} + fn select_down(&mut self, _: &SelectDown, _w: &mut Window, _cx: &mut Context) {} + fn select_start(&mut self, _: &SelectToBeginning, _w: &mut Window, _cx: &mut Context) {} + fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, _cx: &mut Context) {} + fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, _cx: &mut Context) {} + fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, _cx: &mut Context) {} - fn cut(&mut self, _: &Cut, _w: &mut Window, _cx: &mut Self::Context) {} - fn copy(&mut self, _: &Copy, _w: &mut Window, _cx: &mut Self::Context) {} - fn paste(&mut self, _: &Paste, _w: &mut Window, _cx: &mut Self::Context) {} + fn cut(&mut self, _: &Cut, _w: &mut Window, _cx: &mut Context) {} + fn copy(&mut self, _: &Copy, _w: &mut Window, _cx: &mut Context) {} + fn paste(&mut self, _: &Paste, _w: &mut Window, _cx: &mut Context) {} - fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Self::Context) {} - fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Self::Context) {} + fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Context) {} + fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Context) {} fn show_character_palette( &mut self, _: &ShowCharacterPalette, window: &mut Window, - _cx: &mut Self::Context, + _cx: &mut Context, ) { window.show_character_palette(); } @@ -218,35 +205,30 @@ pub trait EditableTextActionHandler<'app>: Sized { _event: &gpui::MouseDownEvent, _text_position: gpui::Point, _w: &mut Window, - _cx: &mut Self::Context, - ) { - } - fn on_mouse_up( - &mut self, - _event: &gpui::MouseUpEvent, - _w: &mut Window, - _cx: &mut Self::Context, + _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, _w: &mut Window, - _cx: &mut Self::Context, + _cx: &mut Context, ) { } } -pub(super) trait EditableTextActionElement: StateBackedEditableText { - fn state_entity_rc(&self) -> &Rc>>; +pub(super) trait EditableTextActionElement { + fn state_entity_rc(&self) -> &Rc>>; fn register_action( &mut self, - listener: fn(&mut Self::State, &ActionType, &mut Window, &mut Context), + listener: fn(&mut State, &ActionType, &mut Window, &mut Context), ) where Self: InteractiveElement, ActionType: Action + std::fmt::Debug, + State: 'static, { let entity_rc = self.state_entity_rc().clone(); self.interactivity() @@ -263,10 +245,9 @@ pub(super) trait EditableTextActionElement: StateBackedEditableText { fn register_actions(&mut self) where Self: InteractiveElement, - Self::State: - for<'app> EditableTextActionHandler<'app, Context = gpui::Context<'app, Self::State>>, + State: for<'app> EditableTextActionHandler>, + State: 'static, { - use super::actions::*; self.register_action(|state, action, window, cx| state.escape(action, window, cx)); self.register_action(|state, action, window, cx| state.insert_enter(action, window, cx)); self.register_action(|state, action, window, cx| state.insert_tab(action, window, cx)); diff --git a/crates/gpui_elements/src/editable_text/shared_element.rs b/crates/gpui_elements/src/editable_text/element.rs similarity index 81% rename from crates/gpui_elements/src/editable_text/shared_element.rs rename to crates/gpui_elements/src/editable_text/element.rs index d61b8fd102..295cf9767b 100644 --- a/crates/gpui_elements/src/editable_text/shared_element.rs +++ b/crates/gpui_elements/src/editable_text/element.rs @@ -1,15 +1,112 @@ use crate::editable_text::{ - TextInputLayoutData, TextLineSegment, - actions::{EditableTextActionElement, EditableTextActionHandler}, + EditableTextState, InitStorage, TextInputLayoutData, TextLineSegment, + actions::{DEFAULT_INPUT_CONTEXT, EditableTextActionElement, EditableTextActionHandler}, }; use gpui::{ - App, Bounds, Context, CursorStyle, DispatchPhase, Display, ElementInputHandler, Entity, - FocusHandle, Focusable, Hitbox, HitboxBehavior, Hsla, InteractiveElement, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, SharedString, Size, - Style, TextAlign, TextLayout, Window, WrappedLine, fill, point, px, size, + App, Bounds, CursorStyle, DispatchPhase, Display, Element, ElementId, ElementInputHandler, + Entity, FocusHandle, Focusable, Hitbox, HitboxBehavior, Hsla, InteractiveElement, + Interactivity, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + PaintQuad, Pixels, Point, SharedString, Size, StatefulInteractiveElement, Style, + StyleRefinement, Styled, TextAlign, TextLayout, WeakEntity, Window, WrappedLine, fill, point, + px, size, }; use smallvec::SmallVec; -use std::{ops::Range, sync::Arc}; +use std::{cell::RefCell, ops::Range, rc::Rc, sync::Arc}; + +#[track_caller] +pub fn editable_text(id: impl Into) -> EditableTextElement { + let mut this = EditableTextElement { + interactivity: Interactivity::new(), + state_entity: Rc::new(RefCell::new(WeakEntity::new_invalid())), + supports_multiline: true, + init_storage: InitStorage::default(), + placeholder: None, + }; + this.interactivity.element_id = Some(id.into()); + + this = this.key_context(DEFAULT_INPUT_CONTEXT); + this.register_actions(); + + this +} + +#[track_caller] +pub fn text_input(id: impl Into) -> EditableTextElement { + editable_text(id).multiline(false) +} + +#[track_caller] +pub fn text_area(id: impl Into) -> EditableTextElement { + editable_text(id).multiline(true) +} + +// TODO: Disabled flag/state? +pub struct EditableTextElement { + interactivity: Interactivity, + // Populated on first render with an entity stored/attached to the view. + // This reference is shared with the action handlers, which are processed between renders + // and therefore cannot otherwise access state attached to the view. + state_entity: Rc>>, + init_storage: InitStorage, + supports_multiline: bool, + placeholder: Option, +} + +impl EditableTextElement { + pub fn multiline(mut self, enabled: bool) -> Self { + self.supports_multiline = enabled; + self + } + + pub fn placeholder(mut self, text: impl Into) -> Self { + self.placeholder = Some(text.into()); + self + } + + /// Swaps the default storage container (standard String) with a custom initializer of [`UnicodeTextStorage`]. + pub fn with_storage(mut self, fn_init: impl Into) -> Self { + self.init_storage = fn_init.into(); + self + } + + /// Swaps the default storage container. The new initializer is a standard String using the provided value. + /// + /// Incompatible with [`with_storage`] (they establish the same internal value). + /// If you initialize custom storage, you should be able to initialize its default value. + pub fn default_value(mut self, value: impl Into) -> Self { + let storage = super::StringStorage::from(value.into()); + self.init_storage = InitStorage::new_typed(move |_cx| storage.clone()); + self + } +} + +impl InteractiveElement for EditableTextElement { + fn interactivity(&mut self) -> &mut Interactivity { + &mut self.interactivity + } +} + +// forced implementation since the API for the element doesnt use Stateful +impl StatefulInteractiveElement for EditableTextElement {} + +impl Styled for EditableTextElement { + fn style(&mut self) -> &mut StyleRefinement { + &mut self.interactivity.base_style + } +} + +impl IntoElement for EditableTextElement { + type Element = Self; + fn into_element(self) -> Self::Element { + self + } +} + +impl EditableTextActionElement for EditableTextElement { + fn state_entity_rc(&self) -> &Rc>> { + &self.state_entity + } +} pub enum PrepaintElement { Line { @@ -48,23 +145,32 @@ pub struct PrepaintState { pub caret_visible: bool, } -pub trait EditableTextElement: InteractiveElement + EditableTextActionElement { - fn init_state(&self, cx: &mut Context) -> Self::State; - fn placeholder(&self) -> &Option; +impl Element for EditableTextElement { + type RequestLayoutState = LayoutState; + type PrepaintState = PrepaintState; - fn shared_request_layout( + fn id(&self) -> Option { + self.interactivity.element_id.clone() + } + + fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { + self.interactivity.source_location() + } + + fn request_layout( &mut self, global_id: Option<&gpui::GlobalElementId>, inspector_id: Option<&gpui::InspectorElementId>, - window: &mut gpui::Window, - cx: &mut gpui::App, - ) -> (gpui::LayoutId, LayoutState) { + window: &mut Window, + cx: &mut App, + ) -> (gpui::LayoutId, Self::RequestLayoutState) { // Fetches or initializes the internal state of the field let state = match &self.interactivity().element_id { None => unimplemented!("all input elements must be assigned an id"), Some(element_id) => { - let state = window - .use_keyed_state(element_id.clone(), cx, |_window, cx| self.init_state(cx)); + let state = window.use_keyed_state(element_id.clone(), cx, |_window, cx| { + EditableTextState::new(self.init_storage.exec(cx), cx) + }); // store a reference to the entity owned by the element for access in action handlers *self.state_entity_rc().borrow_mut() = state.downgrade(); state @@ -84,8 +190,9 @@ pub trait EditableTextElement: InteractiveElement + EditableTextActionElement { // TODO: This required a gpui api change in order to sync the focus handle between Interactivity and TextInputStateBase self.interactivity().track_focus(focus_handle); - let placeholder = self.placeholder().clone(); + let placeholder = self.placeholder.clone(); let placeholder_color = Hsla::white().opacity(0.5); // TODO: as an element param + let supports_multiline = self.supports_multiline; let layout_id = self.interactivity().request_layout( global_id, inspector_id, @@ -209,6 +316,7 @@ pub trait EditableTextElement: InteractiveElement + EditableTextActionElement { } let layout_data = TextInputLayoutData { + supports_multiline, wrap_width, size: Some(size), last_seen_storage_version, @@ -235,15 +343,15 @@ pub trait EditableTextElement: InteractiveElement + EditableTextActionElement { (layout_id, layout_state) } - fn shared_prepaint( + fn prepaint( &mut self, global_id: Option<&gpui::GlobalElementId>, inspector_id: Option<&gpui::InspectorElementId>, - bounds: gpui::Bounds, - request_layout: &mut LayoutState, - window: &mut gpui::Window, - cx: &mut gpui::App, - ) -> PrepaintState { + bounds: Bounds, + request_layout: &mut Self::RequestLayoutState, + window: &mut Window, + cx: &mut App, + ) -> Self::PrepaintState { // should reflect the text content layout size of the stored text, // so that scrolling can take it into account during prepaint. let content_size = { @@ -430,19 +538,16 @@ pub trait EditableTextElement: InteractiveElement + EditableTextActionElement { } } - fn shared_paint( + fn paint( &mut self, global_id: Option<&gpui::GlobalElementId>, inspector_id: Option<&gpui::InspectorElementId>, - bounds: gpui::Bounds, - request_layout: &mut LayoutState, - prepaint: &mut PrepaintState, - window: &mut gpui::Window, - cx: &mut gpui::App, - ) where - Self::State: - for<'app> EditableTextActionHandler<'app, Context = Context<'app, Self::State>>, - { + bounds: Bounds, + request_layout: &mut Self::RequestLayoutState, + prepaint: &mut Self::PrepaintState, + window: &mut Window, + cx: &mut App, + ) { if let Some(hitbox) = &prepaint.hitbox { window.set_cursor_style(CursorStyle::IBeam, hitbox); } diff --git a/crates/gpui_elements/src/editable_text/input_element.rs b/crates/gpui_elements/src/editable_text/input_element.rs deleted file mode 100644 index db5ddcfef0..0000000000 --- a/crates/gpui_elements/src/editable_text/input_element.rs +++ /dev/null @@ -1,158 +0,0 @@ -use crate::editable_text::{ - EditableTextInputState, InitStorage, StateBackedEditableText, - actions::{DEFAULT_INPUT_CONTEXT, EditableTextActionElement}, - shared_element::{self, EditableTextElement}, -}; -use gpui::{ - App, Bounds, Element, ElementId, InteractiveElement, Interactivity, IntoElement, Pixels, - SharedString, StatefulInteractiveElement, StyleRefinement, Styled, WeakEntity, Window, -}; -use std::{cell::RefCell, rc::Rc}; - -#[track_caller] -pub fn input(id: impl Into) -> TextInputElement { - let mut this = TextInputElement { - interactivity: Interactivity::new(), - state_entity: Rc::new(RefCell::new(WeakEntity::new_invalid())), - init_storage: InitStorage::default(), - placeholder: None, - }; - this.interactivity.element_id = Some(id.into()); - - this = this.key_context(DEFAULT_INPUT_CONTEXT); - this.register_actions(); - - this -} - -// TODO: Disabled flag/state? -pub struct TextInputElement { - interactivity: Interactivity, - // Populated on first render with an entity stored/attached to the view. - // This reference is shared with the action handlers, which are processed between renders - // and therefore cannot otherwise access state attached to the view. - state_entity: Rc>>, - init_storage: InitStorage, - placeholder: Option, -} - -impl TextInputElement { - pub fn placeholder(mut self, text: impl Into) -> Self { - self.placeholder = Some(text.into()); - self - } - - /// Swaps the default storage container (standard String) with a custom initializer of [`UnicodeTextStorage`]. - pub fn with_storage(mut self, fn_init: impl Into) -> Self { - self.init_storage = fn_init.into(); - self - } - - /// Swaps the default storage container. The new initializer is a standard String using the provided value. - /// - /// Incompatible with [`with_storage`] (they establish the same internal value). - /// If you initialize custom storage, you should be able to initialize its default value. - pub fn default_value(mut self, value: impl Into) -> Self { - let storage = super::StringStorage::from(value.into()); - self.init_storage = InitStorage::new_typed(move |_cx| storage.clone()); - self - } -} - -impl InteractiveElement for TextInputElement { - fn interactivity(&mut self) -> &mut Interactivity { - &mut self.interactivity - } -} - -// forced implementation since the API for the element doesnt use Stateful -impl StatefulInteractiveElement for TextInputElement {} - -impl Styled for TextInputElement { - fn style(&mut self) -> &mut StyleRefinement { - &mut self.interactivity.base_style - } -} - -impl IntoElement for TextInputElement { - type Element = Self; - fn into_element(self) -> Self::Element { - self - } -} - -impl StateBackedEditableText for TextInputElement { - type State = EditableTextInputState; -} - -impl EditableTextActionElement for TextInputElement { - fn state_entity_rc(&self) -> &Rc>> { - &self.state_entity - } -} - -impl EditableTextElement for TextInputElement { - fn init_state(&self, cx: &mut gpui::prelude::Context) -> Self::State { - Self::State::new(self.init_storage.exec(cx), cx) - } - - fn placeholder(&self) -> &Option { - &self.placeholder - } -} - -impl Element for TextInputElement { - type RequestLayoutState = shared_element::LayoutState; - type PrepaintState = shared_element::PrepaintState; - - fn id(&self) -> Option { - self.interactivity.element_id.clone() - } - - fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { - self.interactivity.source_location() - } - - fn request_layout( - &mut self, - global_id: Option<&gpui::GlobalElementId>, - inspector_id: Option<&gpui::InspectorElementId>, - window: &mut Window, - cx: &mut App, - ) -> (gpui::LayoutId, Self::RequestLayoutState) { - self.shared_request_layout(global_id, inspector_id, window, cx) - } - - fn prepaint( - &mut self, - global_id: Option<&gpui::GlobalElementId>, - inspector_id: Option<&gpui::InspectorElementId>, - bounds: Bounds, - request_layout: &mut Self::RequestLayoutState, - window: &mut Window, - cx: &mut App, - ) -> Self::PrepaintState { - self.shared_prepaint(global_id, inspector_id, bounds, request_layout, window, cx) - } - - fn paint( - &mut self, - global_id: Option<&gpui::GlobalElementId>, - inspector_id: Option<&gpui::InspectorElementId>, - bounds: Bounds, - request_layout: &mut Self::RequestLayoutState, - prepaint: &mut Self::PrepaintState, - window: &mut Window, - cx: &mut App, - ) { - self.shared_paint( - global_id, - inspector_id, - bounds, - request_layout, - prepaint, - window, - cx, - ); - } -} diff --git a/crates/gpui_elements/src/editable_text/input_state.rs b/crates/gpui_elements/src/editable_text/input_state.rs deleted file mode 100644 index 89dc75489c..0000000000 --- a/crates/gpui_elements/src/editable_text/input_state.rs +++ /dev/null @@ -1,365 +0,0 @@ -use super::notify::TextHistoryPushed; -use crate::editable_text::{ - EditableTextStateBase, EditableTextStateNotifier, TextBoundary, UnicodeTextStorage, - notify::TextChanged, -}; -use gpui::{ - Bounds, Context, EntityInputHandler, EventEmitter, NavigationDirection, Pixels, Point, - UTF16Selection, Window, -}; -use std::ops::Range; - -pub struct EditableTextInputState { - internal: EditableTextStateBase, -} - -impl EventEmitter for EditableTextInputState {} -impl EventEmitter for EditableTextInputState {} - -impl std::ops::Deref for EditableTextInputState { - type Target = EditableTextStateBase; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} -impl std::ops::DerefMut for EditableTextInputState { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.internal - } -} - -impl EditableTextInputState { - pub fn new(storage: impl Into>, cx: &mut Context) -> Self { - let internal = EditableTextStateBase::new(storage, cx); - Self { internal } - } -} - -impl EditableTextStateNotifier for Context<'_, EditableTextInputState> { - fn notify_changed(&mut self) { - self.notify(); - } - - fn emit_text_changed(&mut self, event: TextChanged) { - self.emit(event); - } - - fn emit_history(&mut self, event: TextHistoryPushed) { - self.emit(event); - } -} - -impl EntityInputHandler for EditableTextInputState { - fn text_for_range( - &mut self, - range_utf16: Range, - adjusted_range: &mut Option>, - _window: &mut Window, - _cx: &mut Context, - ) -> Option { - self.internal - .ime_text_for_range(range_utf16, adjusted_range) - } - - fn selected_text_range( - &mut self, - ignore_disabled_input: bool, - _window: &mut Window, - _cx: &mut Context, - ) -> Option { - self.internal.ime_selected_text_range(ignore_disabled_input) - } - - fn marked_text_range( - &self, - _window: &mut Window, - _cx: &mut Context, - ) -> Option> { - self.internal.ime_marked_text_range() - } - - fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context) { - self.internal.ime_unmark_text(); - } - - fn replace_text_in_range( - &mut self, - range_utf16: Option>, - text_to_insert: &str, - _window: &mut Window, - cx: &mut Context, - ) { - let range_utf8 = self.internal.ime_resolve_range(range_utf16); - self.internal - .replace_text_in_range_bytes(range_utf8, text_to_insert, cx); - //cx.emit(CursorTrigger::PauseBlinkingForUserAction); - cx.emit_text_changed(TextChanged); - cx.notify_changed(); - } - - fn replace_and_mark_text_in_range( - &mut self, - range_utf16: Option>, - text_to_insert: &str, - new_selected_range_utf16: Option>, - _window: &mut Window, - cx: &mut Context, - ) { - let range = self.internal.ime_resolve_range(range_utf16); - self.internal - .replace_text_in_range_bytes(range.clone(), text_to_insert, cx); - self.internal - .ime_mark_text_in_range(&range, text_to_insert.len()); - self.internal.ime_mark_selected_range( - &range, - &new_selected_range_utf16, - text_to_insert.len(), - ); - cx.emit_text_changed(TextChanged); - cx.notify_changed(); - } - - fn bounds_for_range( - &mut self, - range_utf16: Range, - bounds: Bounds, - window: &mut Window, - _cx: &mut Context, - ) -> Option> { - self.internal - .ime_bounds_for_range(range_utf16, bounds, window) - } - - fn character_index_for_point( - &mut self, - point: Point, - window: &mut Window, - _cx: &mut Context, - ) -> Option { - let index = self - .internal - .index_for_pixel_point(point, window.line_height()); - Some(self.internal.storage().utf_offset_8to16(index)) - } -} - -use super::actions::*; -impl<'app> EditableTextActionHandler<'app> for EditableTextInputState { - type Context = gpui::Context<'app, Self>; - - fn escape(&mut self, _: &Escape, window: &mut Window, cx: &mut Self::Context) { - self.internal.set_selected_range(0..0); - cx.notify(); - - window.blur(); - } - - fn insert_enter(&mut self, _: &Enter, _w: &mut Window, _cx: &mut Self::Context) {} - - fn insert_tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Self::Context) { - self.replace_text_in_range(None, "\t", window, cx); - } - - fn backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Self::Context) { - self.internal - .delete(NavigationDirection::Back, TextBoundary::Graphmeme, cx); - } - - fn delete(&mut self, _: &Delete, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .delete(NavigationDirection::Forward, TextBoundary::Graphmeme, cx); - } - - fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .delete(NavigationDirection::Back, TextBoundary::Word, cx); - } - - fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .delete(NavigationDirection::Forward, TextBoundary::Word, cx); - } - - fn delete_to_line_start( - &mut self, - _: &DeleteToBeginningOfLine, - _w: &mut Window, - cx: &mut Self::Context, - ) { - self.internal - .delete(NavigationDirection::Back, TextBoundary::Line, cx); - } - - fn delete_to_line_end( - &mut self, - _: &DeleteToEndOfLine, - _w: &mut Window, - cx: &mut Self::Context, - ) { - self.internal - .delete(NavigationDirection::Forward, TextBoundary::Line, cx); - } - - fn nav_left(&mut self, _: &Left, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx); - } - - fn nav_right(&mut self, _: &Right, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx); - } - - fn nav_up(&mut self, _: &Up, _w: &mut Window, cx: &mut Self::Context) { - // semantically equivalent to line - self.internal - .nav_linear(NavigationDirection::Back, TextBoundary::Line, cx); - } - - fn nav_down(&mut self, _: &Down, _w: &mut Window, cx: &mut Self::Context) { - // semantically equivalent to line - self.internal - .nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx); - } - - fn nav_line_start(&mut self, _: &Home, _w: &mut Window, cx: &mut Self::Context) { - // semantically equivalent to document - self.internal - .nav_linear(NavigationDirection::Back, TextBoundary::Line, cx); - } - - fn nav_line_end(&mut self, _: &End, _w: &mut Window, cx: &mut Self::Context) { - // semantically equivalent to document - self.internal - .nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx); - } - - fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Back, TextBoundary::Document, cx); - } - - fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Forward, TextBoundary::Document, cx); - } - - fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Back, TextBoundary::Word, cx); - } - - fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Forward, TextBoundary::Word, cx); - } - - fn select_all(&mut self, _: &SelectAll, _w: &mut Window, cx: &mut Self::Context) { - self.internal.select_all(cx); - } - - fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx); - } - - fn select_right(&mut self, _: &SelectRight, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx); - } - - fn select_up(&mut self, _: &SelectUp, _w: &mut Window, cx: &mut Self::Context) { - // semantically equivalent to select document - self.internal - .select_linear(NavigationDirection::Back, TextBoundary::Document, cx); - } - - fn select_down(&mut self, _: &SelectDown, _w: &mut Window, cx: &mut Self::Context) { - // semantically equivalent to select document - self.internal - .select_linear(NavigationDirection::Forward, TextBoundary::Document, cx); - } - - fn select_start(&mut self, _: &SelectToBeginning, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Back, TextBoundary::Document, cx); - } - - fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Forward, TextBoundary::Document, cx); - } - - fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Back, TextBoundary::Word, cx); - } - - fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Forward, TextBoundary::Word, cx); - } - - fn cut(&mut self, _: &Cut, _w: &mut Window, cx: &mut Self::Context) { - self.internal.cut(cx); - } - - fn copy(&mut self, _: &Copy, _w: &mut Window, cx: &mut Self::Context) { - self.internal.copy(cx); - } - - fn paste(&mut self, _: &Paste, _w: &mut Window, cx: &mut Self::Context) { - self.internal.paste(cx); - } - - fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Self::Context) { - // TODO: STUB - } - - fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Self::Context) { - // TODO: STUB - } - - fn on_mouse_down( - &mut self, - event: &gpui::MouseDownEvent, - text_position: gpui::Point, - window: &mut Window, - cx: &mut Self::Context, - ) { - let character_pos = self - .internal - .index_for_pixel_point(text_position, window.line_height()); - self.internal.on_mouse_down( - text_position, - character_pos, - event.click_count, - event.modifiers.shift, - window, - cx, - ); - } - - fn on_mouse_up( - &mut self, - _event: &gpui::MouseUpEvent, - _w: &mut Window, - _cx: &mut Self::Context, - ) { - self.internal.on_mouse_up(); - } - - fn on_mouse_move( - &mut self, - _event: &gpui::MouseMoveEvent, - text_position: Point, - window: &mut Window, - cx: &mut Self::Context, - ) { - let character_pos = self - .internal - .index_for_pixel_point(text_position, window.line_height()); - self.internal.on_mouse_move(character_pos, cx); - } -} diff --git a/crates/gpui_elements/src/editable_text/shared_state.rs b/crates/gpui_elements/src/editable_text/state.rs similarity index 63% rename from crates/gpui_elements/src/editable_text/shared_state.rs rename to crates/gpui_elements/src/editable_text/state.rs index ca681d144b..cab7811516 100644 --- a/crates/gpui_elements/src/editable_text/shared_state.rs +++ b/crates/gpui_elements/src/editable_text/state.rs @@ -4,26 +4,12 @@ use crate::editable_text::{ notify::{TextChanged, TextHistoryPushed}, }; use gpui::{ - App, Bounds, ClipboardItem, EntityInputHandler, FocusHandle, Focusable, NavigationDirection, - Pixels, Point, Size, UTF16Selection, Window, WrappedLine, point, + App, Bounds, ClipboardItem, Context, EntityInputHandler, EventEmitter, FocusHandle, Focusable, + NavigationDirection, Pixels, Point, Size, UTF16Selection, Window, WrappedLine, point, }; use std::{ops::Range, sync::Arc}; -pub trait EditableTextStateNotifier { - fn notify_changed(&mut self); - fn emit_text_changed(&mut self, event: TextChanged); - fn emit_history(&mut self, event: TextHistoryPushed); -} - -pub trait StateBackedEditableText { - type State: 'static - + std::ops::Deref - + std::ops::DerefMut - + EntityInputHandler - + for<'app> EditableTextActionHandler<'app>; -} - -pub struct EditableTextStateBase { +pub struct EditableTextState { storage: Box, /// The utf-8 character range that is currently selected by the user. @@ -51,6 +37,7 @@ pub struct EditableTextStateBase { #[derive(Default)] pub(super) struct TextInputLayoutData { + pub supports_multiline: bool, /// The last known width at which the lines were wrapped. pub wrap_width: Option, /// The last known size of the text, as generated during layout. @@ -85,14 +72,17 @@ impl TextLineSegment { } } -impl Focusable for EditableTextStateBase { +impl EventEmitter for EditableTextState {} +impl EventEmitter for EditableTextState {} + +impl Focusable for EditableTextState { fn focus_handle(&self, _: &App) -> FocusHandle { self.focus_handle.clone() } } -impl EditableTextStateBase { - pub fn new(storage: impl Into>, cx: &mut App) -> Self { +impl EditableTextState { + pub fn new(storage: impl Into>, cx: &mut Context) -> Self { Self { storage: storage.into(), @@ -141,7 +131,7 @@ impl EditableTextStateBase { } } -impl EditableTextStateBase { +impl EditableTextState { /// Returns the utf-8 character position of the start of the line that contains the provided pixel-point. pub fn index_for_pixel_point(&self, point: Point, line_height: Pixels) -> usize { let storage_len_utf8 = self.storage.content_utf8().len(); @@ -177,39 +167,8 @@ impl EditableTextStateBase { } } -impl EditableTextStateBase { - pub fn ime_text_for_range( - &self, - range_utf16: Range, - adjusted_range: &mut Option>, - ) -> Option { - let range = self.storage.utf_range_16to8(&range_utf16); - let storage_len_utf8 = self.storage.content_utf8().len(); - let clamped_range = range.start.min(storage_len_utf8)..range.end.min(storage_len_utf8); - adjusted_range.replace(self.storage.utf_range_8to16(&clamped_range)); - Some(self.storage.content_utf8()[clamped_range].to_string()) - } - - pub fn ime_selected_text_range(&self, _ignore_disabled_input: bool) -> Option { - let selection_range = self.selected_range(); - let direction = self.selection_direction(); - Some(UTF16Selection { - range: self.storage.utf_range_8to16(&selection_range), - reversed: direction == Some(NavigationDirection::Back), - }) - } - - pub fn ime_marked_text_range(&self) -> Option> { - self.marked_range - .as_ref() - .map(|range| self.storage.utf_range_8to16(range)) - } - - pub fn ime_unmark_text(&mut self) { - self.marked_range = None; - } - - pub fn ime_resolve_range(&self, range_utf16: Option>) -> Range { +impl EditableTextState { + fn ime_resolve_range(&self, range_utf16: Option>) -> Range { // Use a series of fallbacks to pick the range to operate on. // Fallback order: IME provided range, active IME marked range, selection let range = range_utf16.map(|range_utf16| self.storage.utf_range_16to8(&range_utf16)); @@ -230,13 +189,8 @@ impl EditableTextStateBase { self.selected_range = new_caret..new_caret; } - fn emit_change_for_undo( - &self, - cx: &mut impl EditableTextStateNotifier, - range: Range, - length: usize, - ) { - cx.emit_history(TextHistoryPushed::new( + fn emit_change_for_undo(&self, cx: &mut Context, range: Range, length: usize) { + cx.emit(TextHistoryPushed::new( range.clone(), length, &*self.storage, @@ -248,7 +202,7 @@ impl EditableTextStateBase { &mut self, range: Range, mut text_to_insert: &str, - cx: &mut impl EditableTextStateNotifier, + cx: &mut Context, ) { // TODO: Apply text sanitization // single-line fields should prune \n and \r @@ -272,14 +226,14 @@ impl EditableTextStateBase { self.marked_range = None; } - pub fn ime_mark_text_in_range(&mut self, range: &Range, text_len: usize) { + fn ime_mark_text_in_range(&mut self, range: &Range, text_len: usize) { self.marked_range = match text_len { 0 => None, _ => Some(range.start..range.start + text_len), }; } - pub fn ime_mark_selected_range( + fn ime_mark_selected_range( &mut self, range_overwritten: &Range, new_selected_range_utf16: &Option>, @@ -298,84 +252,30 @@ impl EditableTextStateBase { }) }; } - - pub fn ime_bounds_for_range( - &mut self, - range_utf16: Range, - bounds: Bounds, - window: &mut Window, - ) -> Option> { - let range = self.storage.utf_range_16to8(&range_utf16); - let line_height = window.line_height(); - - for line in &self.layout_data.lines { - let y_offset = line.pos_y * line_height; - if line.text_range.is_empty() { - if range.start == line.text_range.start { - return Some(Bounds::from_corners( - bounds.origin + point(Pixels::ZERO, y_offset), - bounds.origin + point(gpui::px(4.), y_offset + line_height), - )); - } - } else if line.text_range.contains(&range.start) { - if let Some(wrapped) = &line.wrapped_line { - let local_start = range.start - line.text_range.start; - let local_end = (range.end - line.text_range.start).min(wrapped.text.len()); - - let start_pos = wrapped - .position_for_index(local_start, line_height) - .unwrap_or(point(Pixels::ZERO, Pixels::ZERO)); - let end_pos = wrapped - .position_for_index(local_end, line_height) - .unwrap_or_else(|| { - let last_line_y = line_height * (line.num_visual_lines - 1) as f32; - point(wrapped.width(), last_line_y) - }); - - let start_visual_line = (start_pos.y / line_height).floor() as usize; - let end_visual_line = (end_pos.y / line_height).floor() as usize; - - if start_visual_line == end_visual_line { - return Some(Bounds::from_corners( - bounds.origin + start_pos + point(Pixels::ZERO, y_offset), - bounds.origin + point(end_pos.x, y_offset + start_pos.y + line_height), - )); - } else { - return Some(Bounds::from_corners( - bounds.origin + start_pos + point(Pixels::ZERO, y_offset), - bounds.origin - + point(wrapped.width(), y_offset + start_pos.y + line_height), - )); - } - } - } - } - None - } } -impl EditableTextStateBase { - pub fn move_to(&mut self, caret_pos: usize, cx: &mut impl EditableTextStateNotifier) { +impl EditableTextState { + pub fn move_to(&mut self, caret_pos: usize, cx: &mut Context) { //cx.emit(CursorTrigger::PauseBlinkingForUserAction); let caret_pos = caret_pos.min(self.storage.content_utf8().len()); self.selected_range = caret_pos..caret_pos; //self.scroll_to_cursor(); - cx.notify_changed(); + cx.notify(); } - pub fn select_to(&mut self, caret_pos: usize, cx: &mut impl EditableTextStateNotifier) { + pub fn select_to(&mut self, caret_pos: usize, cx: &mut Context) { //cx.emit(CursorTrigger::PauseBlinkingForUserAction); let caret_pos = caret_pos.min(self.storage().content_utf8().len()); self.selected_range.start = caret_pos; //self.scroll_to_cursor(); - cx.notify_changed(); + cx.notify(); } - pub fn delete( + pub fn delete_linear( &mut self, direction: NavigationDirection, boundary: TextBoundary, - cx: &mut impl EditableTextStateNotifier, + cx: &mut Context, ) { let range = self.selected_range(); let range = match range.is_empty() { @@ -390,15 +290,15 @@ impl EditableTextStateBase { self.replace_text(&range, ""); self.marked_range = None; - cx.emit_text_changed(TextChanged); - cx.notify_changed(); + cx.emit(TextChanged); + cx.notify(); } pub fn nav_linear( &mut self, direction: NavigationDirection, boundary: TextBoundary, - cx: &mut impl EditableTextStateNotifier, + cx: &mut Context, ) { let caret_pos = match self.selected_range.is_empty() { false => match direction { @@ -488,27 +388,359 @@ impl EditableTextStateBase { (direction > 0).then(|| self.storage.content_utf8().len()) } - pub fn select_all(&mut self, cx: &mut impl EditableTextStateNotifier) { + pub fn select_document(&mut self, cx: &mut Context) { self.selected_range = 0..self.storage.content_utf8().len(); - cx.notify_changed(); + cx.notify(); } pub fn select_linear( &mut self, direction: NavigationDirection, boundary: TextBoundary, - cx: &mut impl EditableTextStateNotifier, + cx: &mut Context, ) { let caret_pos = self .storage .offset_from_caret(self.caret_pos(), direction, boundary); self.select_to(caret_pos, cx); } +} - pub fn cut(&mut self, cx: &mut T) - where - T: EditableTextStateNotifier + std::ops::Deref, - { +impl EntityInputHandler for EditableTextState { + fn text_for_range( + &mut self, + range_utf16: Range, + adjusted_range: &mut Option>, + _window: &mut Window, + _cx: &mut Context, + ) -> Option { + let range = self.storage.utf_range_16to8(&range_utf16); + let storage_len_utf8 = self.storage.content_utf8().len(); + let clamped_range = range.start.min(storage_len_utf8)..range.end.min(storage_len_utf8); + adjusted_range.replace(self.storage.utf_range_8to16(&clamped_range)); + Some(self.storage.content_utf8()[clamped_range].to_string()) + } + + fn selected_text_range( + &mut self, + _ignore_disabled_input: bool, + _window: &mut Window, + _cx: &mut Context, + ) -> Option { + let selection_range = self.selected_range(); + let direction = self.selection_direction(); + Some(UTF16Selection { + range: self.storage.utf_range_8to16(&selection_range), + reversed: direction == Some(NavigationDirection::Back), + }) + } + + fn marked_text_range( + &self, + _window: &mut Window, + _cx: &mut Context, + ) -> Option> { + self.marked_range + .as_ref() + .map(|range| self.storage.utf_range_8to16(range)) + } + + fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context) { + self.marked_range = None; + } + + fn replace_text_in_range( + &mut self, + range_utf16: Option>, + text_to_insert: &str, + _window: &mut Window, + cx: &mut Context, + ) { + let range_utf8 = self.ime_resolve_range(range_utf16); + self.replace_text_in_range_bytes(range_utf8, text_to_insert, cx); + //cx.emit(CursorTrigger::PauseBlinkingForUserAction); + cx.emit(TextChanged); + cx.notify(); + } + + fn replace_and_mark_text_in_range( + &mut self, + range_utf16: Option>, + text_to_insert: &str, + new_selected_range_utf16: Option>, + _window: &mut Window, + cx: &mut Context, + ) { + let range = self.ime_resolve_range(range_utf16); + self.replace_text_in_range_bytes(range.clone(), text_to_insert, cx); + self.ime_mark_text_in_range(&range, text_to_insert.len()); + self.ime_mark_selected_range(&range, &new_selected_range_utf16, text_to_insert.len()); + cx.emit(TextChanged); + cx.notify(); + } + + fn bounds_for_range( + &mut self, + range_utf16: Range, + bounds: Bounds, + window: &mut Window, + _cx: &mut Context, + ) -> Option> { + let range = self.storage.utf_range_16to8(&range_utf16); + let line_height = window.line_height(); + + for line in &self.layout_data.lines { + let y_offset = line.pos_y * line_height; + if line.text_range.is_empty() { + if range.start == line.text_range.start { + return Some(Bounds::from_corners( + bounds.origin + point(Pixels::ZERO, y_offset), + bounds.origin + point(gpui::px(4.), y_offset + line_height), + )); + } + } else if line.text_range.contains(&range.start) { + if let Some(wrapped) = &line.wrapped_line { + let local_start = range.start - line.text_range.start; + let local_end = (range.end - line.text_range.start).min(wrapped.text.len()); + + let start_pos = wrapped + .position_for_index(local_start, line_height) + .unwrap_or(point(Pixels::ZERO, Pixels::ZERO)); + let end_pos = wrapped + .position_for_index(local_end, line_height) + .unwrap_or_else(|| { + let last_line_y = line_height * (line.num_visual_lines - 1) as f32; + point(wrapped.width(), last_line_y) + }); + + let start_visual_line = (start_pos.y / line_height).floor() as usize; + let end_visual_line = (end_pos.y / line_height).floor() as usize; + + if start_visual_line == end_visual_line { + return Some(Bounds::from_corners( + bounds.origin + start_pos + point(Pixels::ZERO, y_offset), + bounds.origin + point(end_pos.x, y_offset + start_pos.y + line_height), + )); + } else { + return Some(Bounds::from_corners( + bounds.origin + start_pos + point(Pixels::ZERO, y_offset), + bounds.origin + + point(wrapped.width(), y_offset + start_pos.y + line_height), + )); + } + } + } + } + None + } + + fn character_index_for_point( + &mut self, + point: Point, + window: &mut Window, + _cx: &mut Context, + ) -> Option { + let index = self.index_for_pixel_point(point, window.line_height()); + Some(self.storage().utf_offset_8to16(index)) + } +} + +use super::actions::*; +impl<'app> EditableTextActionHandler> for EditableTextState { + fn escape(&mut self, _: &Escape, window: &mut Window, cx: &mut Context<'app, Self>) { + self.set_selected_range(0..0); + cx.notify(); + + window.blur(); + } + + fn insert_enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<'app, Self>) { + if self.layout_data.supports_multiline { + // TODO: Why is the cursor appearing at the start of the entire field instead of on the new line? + self.replace_text_in_range(None, "\n", window, cx); + } + } + + fn insert_tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<'app, Self>) { + self.replace_text_in_range(None, "\t", window, cx); + } + + fn backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Context<'app, Self>) { + self.delete_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx); + } + + fn delete(&mut self, _: &Delete, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.delete_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx); + } + + fn delete_word_left( + &mut self, + _: &DeleteWordLeft, + _w: &mut Window, + cx: &mut Context<'app, Self>, + ) { + self.delete_linear(NavigationDirection::Back, TextBoundary::Word, cx); + } + + fn delete_word_right( + &mut self, + _: &DeleteWordRight, + _w: &mut Window, + cx: &mut Context<'app, Self>, + ) { + self.delete_linear(NavigationDirection::Forward, TextBoundary::Word, cx); + } + + fn delete_to_line_start( + &mut self, + _: &DeleteToBeginningOfLine, + _w: &mut Window, + cx: &mut Context<'app, Self>, + ) { + self.delete_linear(NavigationDirection::Back, TextBoundary::Line, cx); + } + + fn delete_to_line_end( + &mut self, + _: &DeleteToEndOfLine, + _w: &mut Window, + cx: &mut Context<'app, Self>, + ) { + self.delete_linear(NavigationDirection::Forward, TextBoundary::Line, cx); + } + + fn nav_left(&mut self, _: &Left, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.nav_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx); + } + + fn nav_right(&mut self, _: &Right, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.nav_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx); + } + + fn nav_up(&mut self, _: &Up, window: &mut Window, cx: &mut Context<'app, Self>) { + if !self.layout_data.supports_multiline { + // semantically equivalent to line + self.nav_linear(NavigationDirection::Back, TextBoundary::Line, cx); + return; + } + + if let Some(caret_pos) = self.find_position_in_vertical_direction(-1, window.line_height()) + { + self.move_to(caret_pos, cx); + } + } + + fn nav_down(&mut self, _: &Down, window: &mut Window, cx: &mut Context<'app, Self>) { + if !self.layout_data.supports_multiline { + // semantically equivalent to line + self.nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx); + return; + } + + if let Some(caret_pos) = self.find_position_in_vertical_direction(1, window.line_height()) { + self.move_to(caret_pos, cx); + } + } + + fn nav_line_start(&mut self, _: &Home, _w: &mut Window, cx: &mut Context<'app, Self>) { + // [when not multiline] semantically equivalent to document + self.nav_linear(NavigationDirection::Back, TextBoundary::Line, cx); + } + + fn nav_line_end(&mut self, _: &End, _w: &mut Window, cx: &mut Context<'app, Self>) { + // [when not multiline] semantically equivalent to document + self.nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx); + } + + fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.nav_linear(NavigationDirection::Back, TextBoundary::Document, cx); + } + + fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.nav_linear(NavigationDirection::Forward, TextBoundary::Document, cx); + } + + fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.nav_linear(NavigationDirection::Back, TextBoundary::Word, cx); + } + + fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.nav_linear(NavigationDirection::Forward, TextBoundary::Word, cx); + } + + fn select_all(&mut self, _: &SelectAll, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.select_document(cx); + } + + fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.select_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx); + } + + fn select_right(&mut self, _: &SelectRight, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.select_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx); + } + + fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<'app, Self>) { + if !self.layout_data.supports_multiline { + // semantically equivalent to select document + self.select_linear(NavigationDirection::Back, TextBoundary::Document, cx); + return; + } + + if let Some(caret_pos) = self.find_position_in_vertical_direction(-1, window.line_height()) + { + self.select_to(caret_pos, cx); + //self.scroll_to_cursor(); + cx.notify(); + } + } + + fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<'app, Self>) { + if !self.layout_data.supports_multiline { + // semantically equivalent to select document + self.select_linear(NavigationDirection::Forward, TextBoundary::Document, cx); + return; + } + + if let Some(caret_pos) = self.find_position_in_vertical_direction(1, window.line_height()) { + self.select_to(caret_pos, cx); + //self.scroll_to_cursor(); + cx.notify(); + } + } + + fn select_start( + &mut self, + _: &SelectToBeginning, + _w: &mut Window, + cx: &mut Context<'app, Self>, + ) { + self.select_linear(NavigationDirection::Back, TextBoundary::Document, cx); + } + + fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, cx: &mut Context<'app, Self>) { + self.select_linear(NavigationDirection::Forward, TextBoundary::Document, cx); + } + + fn select_left_word( + &mut self, + _: &SelectWordLeft, + _w: &mut Window, + cx: &mut Context<'app, Self>, + ) { + self.select_linear(NavigationDirection::Back, TextBoundary::Word, cx); + } + + fn select_right_word( + &mut self, + _: &SelectWordRight, + _w: &mut Window, + cx: &mut Context<'app, Self>, + ) { + self.select_linear(NavigationDirection::Forward, TextBoundary::Word, cx); + } + + fn cut(&mut self, _: &Cut, _w: &mut Window, cx: &mut Context<'app, Self>) { if !self.selected_range.is_empty() { // Cut selected text let slice = &self.storage.content_utf8()[self.selected_range.clone()]; @@ -545,40 +777,43 @@ impl EditableTextStateBase { self.replace_text_in_range_bytes(self.selected_range.clone(), "", cx); } - cx.emit_text_changed(TextChanged); - cx.notify_changed(); + cx.emit(TextChanged); + cx.notify(); } - pub fn copy(&mut self, app: &mut App) { + fn copy(&mut self, _: &Copy, _w: &mut Window, cx: &mut Context<'app, Self>) { if !self.selected_range.is_empty() { let slice = &self.storage.content_utf8()[self.selected_range.clone()]; - app.write_to_clipboard(ClipboardItem::new_string(slice.to_string())); + cx.write_to_clipboard(ClipboardItem::new_string(slice.to_string())); } } - pub fn paste(&mut self, cx: &mut T) - where - T: EditableTextStateNotifier + std::ops::Deref, - { + fn paste(&mut self, _: &Paste, _w: &mut Window, cx: &mut Context<'app, Self>) { let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) else { return; }; self.replace_text_in_range_bytes(self.ime_resolve_range(None), &text, cx); - cx.emit_text_changed(TextChanged); - cx.notify_changed(); + cx.emit(TextChanged); + cx.notify(); } - pub fn on_mouse_down( + fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Context<'app, Self>) { + // TODO: STUB + } + + fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Context<'app, Self>) { + // TODO: STUB + } + + fn on_mouse_down( &mut self, - position: Point, - character_pos: usize, - click_count: usize, - shift: bool, + event: &gpui::MouseDownEvent, + text_position: gpui::Point, window: &mut Window, - cx: &mut Context, - ) where - Context: EditableTextStateNotifier + std::ops::DerefMut, - { + cx: &mut Context<'app, Self>, + ) { + let character_pos = self.index_for_pixel_point(text_position, window.line_height()); + window.focus(&self.focus_handle, cx); self.is_selecting = true; @@ -586,22 +821,23 @@ impl EditableTextStateBase { .last_click_position .map(|last| { let threshold = gpui::px(4.); - (position.x - last.x).abs() < threshold && (position.y - last.y).abs() < threshold + (text_position.x - last.x).abs() < threshold + && (text_position.y - last.y).abs() < threshold }) .unwrap_or(false); - if is_same_position && click_count > 1 { - self.click_count = click_count; + if is_same_position && event.click_count > 1 { + self.click_count = event.click_count; } else { self.click_count = 1; } - self.last_click_position = Some(position); + self.last_click_position = Some(text_position); match self.click_count { 2 => { let (word_start, word_end) = self.storage.word_range_at(character_pos); self.selected_range = word_start..word_end; - cx.notify_changed(); + cx.notify(); } 3 => { let line_start = self.storage.find_line_start(character_pos); @@ -612,10 +848,10 @@ impl EditableTextStateBase { line_end }; self.selected_range = line_start..line_end_with_newline; - cx.notify_changed(); + cx.notify(); } _ => { - if shift { + if event.modifiers.shift { self.select_to(character_pos, cx); } else { self.move_to(character_pos, cx); @@ -624,11 +860,23 @@ impl EditableTextStateBase { } } - pub fn on_mouse_up(&mut self) { + fn on_mouse_up( + &mut self, + _event: &gpui::MouseUpEvent, + _w: &mut Window, + _cx: &mut Context<'app, Self>, + ) { self.is_selecting = false; } - pub fn on_mouse_move(&mut self, character_pos: usize, cx: &mut impl EditableTextStateNotifier) { + fn on_mouse_move( + &mut self, + _event: &gpui::MouseMoveEvent, + text_position: Point, + window: &mut Window, + cx: &mut Context<'app, Self>, + ) { + let character_pos = self.index_for_pixel_point(text_position, window.line_height()); if self.is_selecting && self.click_count == 1 { self.select_to(character_pos, cx); } diff --git a/crates/gpui_elements/src/editable_text/text_area_element.rs b/crates/gpui_elements/src/editable_text/text_area_element.rs deleted file mode 100644 index 685ace54fd..0000000000 --- a/crates/gpui_elements/src/editable_text/text_area_element.rs +++ /dev/null @@ -1,157 +0,0 @@ -use crate::editable_text::{ - EditableTextAreaState, InitStorage, StateBackedEditableText, - actions::{DEFAULT_INPUT_CONTEXT, EditableTextActionElement}, - shared_element::{self, EditableTextElement}, -}; -use gpui::{ - App, Bounds, Element, ElementId, InteractiveElement, Interactivity, IntoElement, Pixels, - SharedString, StatefulInteractiveElement, StyleRefinement, Styled, WeakEntity, Window, -}; -use std::{cell::RefCell, rc::Rc}; - -pub fn text_area(id: impl Into) -> EditableTextAreaElement { - let mut this = EditableTextAreaElement { - interactivity: Interactivity::new(), - state_entity: Rc::new(RefCell::new(WeakEntity::new_invalid())), - init_storage: InitStorage::default(), - placeholder: None, - }; - this.interactivity.element_id = Some(id.into()); - - this = this.key_context(DEFAULT_INPUT_CONTEXT); - this.register_actions(); - - this -} - -// TODO: Disabled flag/state? -pub struct EditableTextAreaElement { - interactivity: Interactivity, - // Populated on first render with an entity stored/attached to the view. - // This reference is shared with the action handlers, which are processed between renders - // and therefore cannot otherwise access state attached to the view. - state_entity: Rc>>, - init_storage: InitStorage, - placeholder: Option, -} - -impl EditableTextAreaElement { - pub fn placeholder(mut self, text: impl Into) -> Self { - self.placeholder = Some(text.into()); - self - } - - /// Swaps the default storage container (standard String) with a custom initializer of [`UnicodeTextStorage`]. - pub fn with_storage(mut self, fn_init: impl Into) -> Self { - self.init_storage = fn_init.into(); - self - } - - /// Swaps the default storage container. The new initializer is a standard String using the provided value. - /// - /// Incompatible with [`with_storage`] (they establish the same internal value). - /// If you initialize custom storage, you should be able to initialize its default value. - pub fn default_value(mut self, value: impl Into) -> Self { - let storage = super::StringStorage::from(value.into()); - self.init_storage = InitStorage::new_typed(move |_cx| storage.clone()); - self - } -} - -impl InteractiveElement for EditableTextAreaElement { - fn interactivity(&mut self) -> &mut Interactivity { - &mut self.interactivity - } -} - -// forced implementation since the API for the element doesnt use Stateful -impl StatefulInteractiveElement for EditableTextAreaElement {} - -impl Styled for EditableTextAreaElement { - fn style(&mut self) -> &mut StyleRefinement { - &mut self.interactivity.base_style - } -} - -impl IntoElement for EditableTextAreaElement { - type Element = Self; - fn into_element(self) -> Self::Element { - self - } -} - -impl StateBackedEditableText for EditableTextAreaElement { - type State = EditableTextAreaState; -} - -impl EditableTextActionElement for EditableTextAreaElement { - fn state_entity_rc(&self) -> &Rc>> { - &self.state_entity - } -} - -impl EditableTextElement for EditableTextAreaElement { - fn init_state(&self, cx: &mut gpui::prelude::Context) -> Self::State { - Self::State::new(self.init_storage.exec(cx), cx) - } - - fn placeholder(&self) -> &Option { - &self.placeholder - } -} - -impl Element for EditableTextAreaElement { - type RequestLayoutState = shared_element::LayoutState; - type PrepaintState = shared_element::PrepaintState; - - fn id(&self) -> Option { - self.interactivity.element_id.clone() - } - - fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { - self.interactivity.source_location() - } - - fn request_layout( - &mut self, - global_id: Option<&gpui::GlobalElementId>, - inspector_id: Option<&gpui::InspectorElementId>, - window: &mut Window, - cx: &mut App, - ) -> (gpui::LayoutId, Self::RequestLayoutState) { - self.shared_request_layout(global_id, inspector_id, window, cx) - } - - fn prepaint( - &mut self, - global_id: Option<&gpui::GlobalElementId>, - inspector_id: Option<&gpui::InspectorElementId>, - bounds: Bounds, - request_layout: &mut Self::RequestLayoutState, - window: &mut Window, - cx: &mut App, - ) -> Self::PrepaintState { - self.shared_prepaint(global_id, inspector_id, bounds, request_layout, window, cx) - } - - fn paint( - &mut self, - global_id: Option<&gpui::GlobalElementId>, - inspector_id: Option<&gpui::InspectorElementId>, - bounds: Bounds, - request_layout: &mut Self::RequestLayoutState, - prepaint: &mut Self::PrepaintState, - window: &mut Window, - cx: &mut App, - ) { - self.shared_paint( - global_id, - inspector_id, - bounds, - request_layout, - prepaint, - window, - cx, - ); - } -} diff --git a/crates/gpui_elements/src/editable_text/text_area_state.rs b/crates/gpui_elements/src/editable_text/text_area_state.rs deleted file mode 100644 index 9b25ab0cfb..0000000000 --- a/crates/gpui_elements/src/editable_text/text_area_state.rs +++ /dev/null @@ -1,381 +0,0 @@ -use crate::editable_text::{ - EditableTextStateBase, EditableTextStateNotifier, TextBoundary, UnicodeTextStorage, - notify::{TextChanged, TextHistoryPushed}, -}; -use gpui::{ - Bounds, Context, EntityInputHandler, EventEmitter, NavigationDirection, Pixels, Point, - UTF16Selection, Window, -}; -use std::ops::Range; - -pub struct EditableTextAreaState { - internal: EditableTextStateBase, -} - -impl EventEmitter for EditableTextAreaState {} -impl EventEmitter for EditableTextAreaState {} - -impl std::ops::Deref for EditableTextAreaState { - type Target = EditableTextStateBase; - - fn deref(&self) -> &Self::Target { - &self.internal - } -} -impl std::ops::DerefMut for EditableTextAreaState { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.internal - } -} - -impl EditableTextAreaState { - pub fn new(storage: impl Into>, cx: &mut Context) -> Self { - let internal = EditableTextStateBase::new(storage, cx); - Self { internal } - } -} - -impl EditableTextStateNotifier for Context<'_, EditableTextAreaState> { - fn notify_changed(&mut self) { - self.notify(); - } - - fn emit_text_changed(&mut self, event: TextChanged) { - self.emit(event); - } - - fn emit_history(&mut self, event: TextHistoryPushed) { - self.emit(event); - } -} - -impl EntityInputHandler for EditableTextAreaState { - fn text_for_range( - &mut self, - range_utf16: Range, - adjusted_range: &mut Option>, - _window: &mut Window, - _cx: &mut Context, - ) -> Option { - self.internal - .ime_text_for_range(range_utf16, adjusted_range) - } - - fn selected_text_range( - &mut self, - ignore_disabled_input: bool, - _window: &mut Window, - _cx: &mut Context, - ) -> Option { - self.internal.ime_selected_text_range(ignore_disabled_input) - } - - fn marked_text_range( - &self, - _window: &mut Window, - _cx: &mut Context, - ) -> Option> { - self.internal.ime_marked_text_range() - } - - fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context) { - self.internal.ime_unmark_text(); - } - - fn replace_text_in_range( - &mut self, - range_utf16: Option>, - text_to_insert: &str, - _window: &mut Window, - cx: &mut Context, - ) { - let range_utf8 = self.internal.ime_resolve_range(range_utf16); - self.internal - .replace_text_in_range_bytes(range_utf8, text_to_insert, cx); - //cx.emit(CursorTrigger::PauseBlinkingForUserAction); - cx.emit_text_changed(TextChanged); - cx.notify_changed(); - } - - fn replace_and_mark_text_in_range( - &mut self, - range_utf16: Option>, - text_to_insert: &str, - new_selected_range_utf16: Option>, - _window: &mut Window, - cx: &mut Context, - ) { - let range = self.internal.ime_resolve_range(range_utf16); - self.internal - .replace_text_in_range_bytes(range.clone(), text_to_insert, cx); - self.internal - .ime_mark_text_in_range(&range, text_to_insert.len()); - self.internal.ime_mark_selected_range( - &range, - &new_selected_range_utf16, - text_to_insert.len(), - ); - cx.emit_text_changed(TextChanged); - cx.notify_changed(); - } - - fn bounds_for_range( - &mut self, - range_utf16: Range, - bounds: Bounds, - window: &mut Window, - _cx: &mut Context, - ) -> Option> { - self.internal - .ime_bounds_for_range(range_utf16, bounds, window) - } - - fn character_index_for_point( - &mut self, - point: Point, - window: &mut Window, - _cx: &mut Context, - ) -> Option { - let index = self - .internal - .index_for_pixel_point(point, window.line_height()); - Some(self.internal.storage().utf_offset_8to16(index)) - } -} - -use super::actions::*; -impl<'app> EditableTextActionHandler<'app> for EditableTextAreaState { - type Context = gpui::Context<'app, Self>; - - fn escape(&mut self, _: &Escape, window: &mut Window, cx: &mut Self::Context) { - self.internal.set_selected_range(0..0); - cx.notify(); - - window.blur(); - } - - fn insert_enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Self::Context) { - // TODO: Why is the cursor appearing at the start of the entire field instead of on the new line? - self.replace_text_in_range(None, "\n", window, cx); - } - - fn insert_tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Self::Context) { - self.replace_text_in_range(None, "\t", window, cx); - } - - fn backspace(&mut self, _: &Backspace, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .delete(NavigationDirection::Back, TextBoundary::Graphmeme, cx); - } - - fn delete(&mut self, _: &Delete, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .delete(NavigationDirection::Forward, TextBoundary::Graphmeme, cx); - } - - fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .delete(NavigationDirection::Back, TextBoundary::Word, cx); - } - - fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .delete(NavigationDirection::Forward, TextBoundary::Word, cx); - } - - fn delete_to_line_start( - &mut self, - _: &DeleteToBeginningOfLine, - _w: &mut Window, - cx: &mut Self::Context, - ) { - self.internal - .delete(NavigationDirection::Back, TextBoundary::Line, cx); - } - - fn delete_to_line_end( - &mut self, - _: &DeleteToEndOfLine, - _w: &mut Window, - cx: &mut Self::Context, - ) { - self.internal - .delete(NavigationDirection::Forward, TextBoundary::Line, cx); - } - - fn nav_left(&mut self, _: &Left, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx); - } - - fn nav_right(&mut self, _: &Right, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx); - } - - fn nav_up(&mut self, _: &Up, window: &mut Window, cx: &mut Self::Context) { - if let Some(caret_pos) = self - .internal - .find_position_in_vertical_direction(-1, window.line_height()) - { - self.internal.move_to(caret_pos, cx); - } - } - - fn nav_down(&mut self, _: &Down, window: &mut Window, cx: &mut Self::Context) { - if let Some(caret_pos) = self - .internal - .find_position_in_vertical_direction(1, window.line_height()) - { - self.internal.move_to(caret_pos, cx); - } - } - - fn nav_line_start(&mut self, _: &Home, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Back, TextBoundary::Line, cx); - } - - fn nav_line_end(&mut self, _: &End, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx); - } - - fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Back, TextBoundary::Document, cx); - } - - fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Forward, TextBoundary::Document, cx); - } - - fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Back, TextBoundary::Word, cx); - } - - fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .nav_linear(NavigationDirection::Forward, TextBoundary::Word, cx); - } - - fn select_all(&mut self, _: &SelectAll, _w: &mut Window, cx: &mut Self::Context) { - self.internal.select_all(cx); - } - - fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx); - } - - fn select_right(&mut self, _: &SelectRight, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx); - } - - fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Self::Context) { - if let Some(caret_pos) = self - .internal - .find_position_in_vertical_direction(-1, window.line_height()) - { - self.internal.select_to(caret_pos, cx); - //self.scroll_to_cursor(); - cx.notify_changed(); - } - } - - fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Self::Context) { - if let Some(caret_pos) = self - .internal - .find_position_in_vertical_direction(1, window.line_height()) - { - self.internal.select_to(caret_pos, cx); - //self.scroll_to_cursor(); - cx.notify_changed(); - } - } - - fn select_start(&mut self, _: &SelectToBeginning, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Back, TextBoundary::Document, cx); - } - - fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Forward, TextBoundary::Document, cx); - } - - fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Back, TextBoundary::Word, cx); - } - - fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, cx: &mut Self::Context) { - self.internal - .select_linear(NavigationDirection::Forward, TextBoundary::Word, cx); - } - - fn cut(&mut self, _: &Cut, _w: &mut Window, cx: &mut Self::Context) { - self.internal.cut(cx); - } - - fn copy(&mut self, _: &Copy, _w: &mut Window, cx: &mut Self::Context) { - self.internal.copy(cx); - } - - fn paste(&mut self, _: &Paste, _w: &mut Window, cx: &mut Self::Context) { - self.internal.paste(cx); - } - - fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Self::Context) { - // TODO: STUB - } - - fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Self::Context) { - // TODO: STUB - } - - fn on_mouse_down( - &mut self, - event: &gpui::MouseDownEvent, - text_position: gpui::Point, - window: &mut Window, - cx: &mut Self::Context, - ) { - let character_pos = self - .internal - .index_for_pixel_point(text_position, window.line_height()); - self.internal.on_mouse_down( - text_position, - character_pos, - event.click_count, - event.modifiers.shift, - window, - cx, - ); - } - - fn on_mouse_up( - &mut self, - _event: &gpui::MouseUpEvent, - _w: &mut Window, - _cx: &mut Self::Context, - ) { - self.internal.on_mouse_up(); - } - - fn on_mouse_move( - &mut self, - _event: &gpui::MouseMoveEvent, - text_position: Point, - window: &mut Window, - cx: &mut Self::Context, - ) { - let character_pos = self - .internal - .index_for_pixel_point(text_position, window.line_height()); - self.internal.on_mouse_move(character_pos, cx); - } -}