Skip to main content

gpui_component/input/
state.rs

1use crate::root::WindowState;
2use gpui::{App, Entity, FocusHandle, Focusable as _, SharedString, Window};
3use gpui_base::OtpState;
4use ropey::Rope;
5
6use super::{EditorState, InputState, TextareaState};
7
8/// Any input-like state, regardless of which input element renders it.
9///
10/// [`InputState`], [`TextareaState`], [`EditorState`] and [`OtpState`] are
11/// separate types, so an API that refers to “whatever input is here” takes this
12/// enum instead of one of them. [`crate::WindowExt::focused_input`] returns it.
13///
14/// Use [`Self::as_input`] and friends to get the concrete state back, or
15/// [`Self::value`] and [`Self::focus_handle`] when the kind does not matter.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum AnyInputState {
18    /// A single-line [`crate::input::Input`] state.
19    Input(Entity<InputState>),
20    /// A multi-line [`crate::input::Textarea`] state.
21    Textarea(Entity<TextareaState>),
22    /// A source-code [`crate::input::Editor`] state.
23    Editor(Entity<EditorState>),
24    /// A one-time-code [`crate::input::OtpInput`] state.
25    Otp(Entity<OtpState>),
26}
27
28/// The three states the [`crate::input::Input`] element can render.
29///
30/// [`AnyInputState`] also covers `OtpState`, which is a different engine with
31/// none of these methods, so the element takes this narrower enum instead.
32///
33/// Everything here forwards to a method the shared engine offers for every
34/// mode, which is why the element does not need to be generic over the mode to
35/// call them.
36#[derive(Debug, Clone)]
37pub(crate) enum TextInputState {
38    Input(Entity<InputState>),
39    Textarea(Entity<TextareaState>),
40    Editor(Entity<EditorState>),
41}
42
43/// Runs `$body` against whichever concrete state this is.
44macro_rules! dispatch {
45    ($self:expr, |$state:ident| $body:expr) => {
46        match $self {
47            TextInputState::Input($state) => $body,
48            TextInputState::Textarea($state) => $body,
49            TextInputState::Editor($state) => $body,
50        }
51    };
52}
53
54impl TextInputState {
55    pub(crate) fn install_token_presentation(
56        &self,
57        renderer: Option<gpui_base::input::InlineTokenRenderer>,
58        listener: Option<gpui_base::input::InlineTokenClickListener>,
59        secret: bool,
60        cx: &mut App,
61    ) {
62        dispatch!(self, |state| state.update(cx, |state, _| state
63            .install_token_presentation(renderer, listener, secret)))
64    }
65
66    pub(crate) fn install_token_hover_presentation(
67        &self,
68        hover_listener: Option<gpui_base::input::InlineTokenHoverListener>,
69        cx: &mut App,
70    ) {
71        dispatch!(self, |state| state.update(cx, |state, _| state
72            .install_token_hover_presentation(hover_listener)))
73    }
74
75    pub(crate) fn entity_id(&self) -> gpui::EntityId {
76        dispatch!(self, |state| state.entity_id())
77    }
78
79    pub(crate) fn presentation(&self, cx: &App) -> gpui_base::input::InputPresentation {
80        dispatch!(self, |state| state.read(cx).presentation())
81    }
82
83    /// The text of the input, borrowed from the state that owns it.
84    ///
85    /// The state holds the only copy; nothing on the render path snapshots it.
86    pub(crate) fn text<'a>(&self, cx: &'a App) -> &'a Rope {
87        dispatch!(self, |state| state.read(cx).text())
88    }
89
90    pub(crate) fn focus(&self, window: &mut Window, cx: &mut App) {
91        dispatch!(self, |state| state
92            .update(cx, |state, cx| state.focus(window, cx)))
93    }
94
95    pub(crate) fn clean(&self, window: &mut Window, cx: &mut App) {
96        dispatch!(self, |state| state
97            .update(cx, |state, cx| state.clean(window, cx)))
98    }
99
100    pub(crate) fn toggle_masked(&self, window: &mut Window, cx: &mut App) {
101        dispatch!(self, |state| state
102            .update(cx, |state, cx| state.toggle_masked(window, cx)))
103    }
104
105    pub(crate) fn set_text_align(&self, align: gpui::TextAlign, cx: &mut App) {
106        dispatch!(self, |state| state
107            .update(cx, |state, cx| state.set_text_align(align, cx)))
108    }
109
110    pub(crate) fn set_disabled(&self, disabled: bool, cx: &mut App) {
111        dispatch!(self, |state| state
112            .update(cx, |state, cx| state.set_disabled(disabled, cx)))
113    }
114
115    pub(crate) fn set_readonly(&self, readonly: bool, cx: &mut App) {
116        dispatch!(self, |state| state
117            .update(cx, |state, cx| state.set_readonly(readonly, cx)))
118    }
119
120    pub(crate) fn set_editor_style(&self, style: gpui_base::input::InputEditorStyle, cx: &mut App) {
121        dispatch!(self, |state| state
122            .update(cx, |state, _| state.set_editor_style(style)))
123    }
124
125    pub(crate) fn set_editor_paddings(&self, paddings: gpui::Edges<gpui::Pixels>, cx: &mut App) {
126        dispatch!(self, |state| state
127            .update(cx, |state, _| state.set_editor_paddings(paddings)))
128    }
129
130    pub(crate) fn ensure_highlighter_factory(
131        &self,
132        factory: gpui_base::input::InputHighlighterFactory,
133        cx: &mut App,
134    ) {
135        dispatch!(self, |state| state
136            .update(cx, |state, _| state.ensure_highlighter_factory(factory)))
137    }
138
139    pub(crate) fn on_context_menu(
140        &self,
141        handler: std::rc::Rc<
142            dyn Fn(
143                gpui_base::input::NativeMenu,
144                gpui_base::input::InputContextMenuCapabilities,
145                gpui::Point<gpui::Pixels>,
146                &mut Window,
147                &mut App,
148            ),
149        >,
150        cx: &mut App,
151    ) {
152        dispatch!(self, |state| state
153            .update(cx, |state, _| state.on_context_menu(handler)))
154    }
155
156    /// The selection a long press made, laid out for its handles and menu.
157    pub(crate) fn touch_selection(&self, cx: &App) -> Option<gpui_base::TouchSelectionSnapshot> {
158        dispatch!(self, |state| state.read(cx).touch_selection())
159    }
160
161    pub(crate) fn context_menu_capabilities(
162        &self,
163        cx: &App,
164    ) -> gpui_base::input::InputContextMenuCapabilities {
165        dispatch!(self, |state| state.read(cx).context_menu_capabilities())
166    }
167
168    /// Whether every character is already selected, so Select All has nothing
169    /// left to offer.
170    pub(crate) fn is_all_selected(&self, cx: &App) -> bool {
171        dispatch!(self, |state| {
172            let state = state.read(cx);
173            state.selected_range() == (0..state.text().len())
174        })
175    }
176
177    pub(crate) fn begin_edge_drag(
178        &self,
179        edge: gpui_base::SelectionEdge,
180        finger: gpui::Point<gpui::Pixels>,
181        cx: &mut App,
182    ) {
183        dispatch!(self, |state| state
184            .update(cx, |state, cx| state.begin_edge_drag(edge, finger, cx)))
185    }
186
187    pub(crate) fn update_edge_drag(&self, finger: gpui::Point<gpui::Pixels>, cx: &mut App) {
188        dispatch!(self, |state| state
189            .update(cx, |state, cx| state.update_edge_drag(finger, cx)))
190    }
191
192    pub(crate) fn end_edge_drag(&self, cx: &mut App) {
193        dispatch!(self, |state| state
194            .update(cx, |state, cx| state.end_edge_drag(cx)))
195    }
196
197    pub(crate) fn close_edit_menu(&self, cx: &mut App) {
198        dispatch!(self, |state| state
199            .update(cx, |state, cx| state.close_edit_menu(cx)))
200    }
201
202    pub(crate) fn select_all_from_edit_menu(&self, window: &mut Window, cx: &mut App) {
203        dispatch!(self, |state| state.update(cx, |state, cx| state
204            .select_all_from_edit_menu(window, cx)))
205    }
206
207    /// Builds and syncs this input's overlays. See [`super::overlay`].
208    pub(super) fn render_overlays(
209        &self,
210        window: &mut Window,
211        cx: &mut App,
212    ) -> super::overlay::InputOverlays {
213        dispatch!(self, |state| super::overlay::render_overlays(
214            state, window, cx
215        ))
216    }
217
218    pub(crate) fn replace_all(&self, value: String, window: &mut Window, cx: &mut App) {
219        dispatch!(self, |state| state
220            .update(cx, |state, cx| state.replace_all(value, window, cx)))
221    }
222
223    /// The text element itself, as a child to place in the frame.
224    pub(crate) fn into_any_element(self) -> gpui::AnyElement {
225        use gpui::IntoElement as _;
226        dispatch!(self, |state| state.into_any_element())
227    }
228}
229
230impl From<&TextInputState> for AnyInputState {
231    fn from(state: &TextInputState) -> Self {
232        match state {
233            TextInputState::Input(state) => Self::Input(state.clone()),
234            TextInputState::Textarea(state) => Self::Textarea(state.clone()),
235            TextInputState::Editor(state) => Self::Editor(state.clone()),
236        }
237    }
238}
239
240impl From<Entity<InputState>> for TextInputState {
241    fn from(state: Entity<InputState>) -> Self {
242        Self::Input(state)
243    }
244}
245
246impl From<Entity<TextareaState>> for TextInputState {
247    fn from(state: Entity<TextareaState>) -> Self {
248        Self::Textarea(state)
249    }
250}
251
252impl From<Entity<EditorState>> for TextInputState {
253    fn from(state: Entity<EditorState>) -> Self {
254        Self::Editor(state)
255    }
256}
257
258impl AnyInputState {
259    /// Returns the [`InputState`], if this is an `Input` state.
260    pub fn as_input(&self) -> Option<&Entity<InputState>> {
261        match self {
262            Self::Input(state) => Some(state),
263            _ => None,
264        }
265    }
266
267    /// Returns the [`TextareaState`], if this is a `Textarea` state.
268    pub fn as_textarea(&self) -> Option<&Entity<TextareaState>> {
269        match self {
270            Self::Textarea(state) => Some(state),
271            _ => None,
272        }
273    }
274
275    /// Returns the [`EditorState`], if this is an `Editor` state.
276    pub fn as_editor(&self) -> Option<&Entity<EditorState>> {
277        match self {
278            Self::Editor(state) => Some(state),
279            _ => None,
280        }
281    }
282
283    /// Returns the [`OtpState`], if this is an `OtpInput` state.
284    pub fn as_otp(&self) -> Option<&Entity<OtpState>> {
285        match self {
286            Self::Otp(state) => Some(state),
287            _ => None,
288        }
289    }
290
291    /// Returns the value of the input.
292    ///
293    /// A masked input returns its masked value, the same as what is rendered.
294    pub fn value(&self, cx: &App) -> SharedString {
295        match self {
296            Self::Input(state) => state.read(cx).value(),
297            Self::Textarea(state) => state.read(cx).value(),
298            Self::Editor(state) => state.read(cx).value(),
299            Self::Otp(state) => state.read(cx).value().clone(),
300        }
301    }
302
303    /// Returns the focus handle of the input.
304    pub fn focus_handle(&self, cx: &App) -> FocusHandle {
305        match self {
306            Self::Input(state) => state.focus_handle(cx),
307            Self::Textarea(state) => state.focus_handle(cx),
308            Self::Editor(state) => state.focus_handle(cx),
309            Self::Otp(state) => state.focus_handle(cx),
310        }
311    }
312}
313
314impl From<Entity<InputState>> for AnyInputState {
315    fn from(state: Entity<InputState>) -> Self {
316        Self::Input(state)
317    }
318}
319
320impl From<Entity<TextareaState>> for AnyInputState {
321    fn from(state: Entity<TextareaState>) -> Self {
322        Self::Textarea(state)
323    }
324}
325
326impl From<Entity<EditorState>> for AnyInputState {
327    fn from(state: Entity<EditorState>) -> Self {
328        Self::Editor(state)
329    }
330}
331
332impl From<Entity<OtpState>> for AnyInputState {
333    fn from(state: Entity<OtpState>) -> Self {
334        Self::Otp(state)
335    }
336}
337
338/// Registers `state` as the window's focused input while it holds focus, and
339/// unregisters it once focus moves elsewhere.
340pub(super) fn sync_focused_input_registry(
341    state: impl Into<AnyInputState>,
342    window: &mut Window,
343    cx: &mut App,
344) {
345    let state = state.into();
346    let focused = state.focus_handle(cx).is_focused(window);
347    WindowState::try_update(window, cx, |root, _, cx| {
348        if focused {
349            root.focused_input = Some(state.clone());
350        } else if root.focused_input.as_ref() == Some(&state) {
351            root.focused_input = None;
352        }
353        cx.notify();
354    });
355}