1use gpui::{App, Entity, FocusHandle, Focusable as _, SharedString, Window};
2use gpui_base::OtpState;
3use ropey::Rope;
4
5use super::{EditorState, InputState, TextareaState};
6use crate::Root;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum AnyInputState {
18 Input(Entity<InputState>),
20 Textarea(Entity<TextareaState>),
22 Editor(Entity<EditorState>),
24 Otp(Entity<OtpState>),
26}
27
28#[derive(Debug, Clone)]
37pub(crate) enum TextInputState {
38 Input(Entity<InputState>),
39 Textarea(Entity<TextareaState>),
40 Editor(Entity<EditorState>),
41}
42
43macro_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 entity_id(&self) -> gpui::EntityId {
56 dispatch!(self, |state| state.entity_id())
57 }
58
59 pub(crate) fn presentation(&self, cx: &App) -> gpui_base::input::InputPresentation {
60 dispatch!(self, |state| state.read(cx).presentation())
61 }
62
63 pub(crate) fn text<'a>(&self, cx: &'a App) -> &'a Rope {
67 dispatch!(self, |state| state.read(cx).text())
68 }
69
70 pub(crate) fn focus(&self, window: &mut Window, cx: &mut App) {
71 dispatch!(self, |state| state
72 .update(cx, |state, cx| state.focus(window, cx)))
73 }
74
75 pub(crate) fn clean(&self, window: &mut Window, cx: &mut App) {
76 dispatch!(self, |state| state
77 .update(cx, |state, cx| state.clean(window, cx)))
78 }
79
80 pub(crate) fn toggle_masked(&self, window: &mut Window, cx: &mut App) {
81 dispatch!(self, |state| state
82 .update(cx, |state, cx| state.toggle_masked(window, cx)))
83 }
84
85 pub(crate) fn set_text_align(&self, align: gpui::TextAlign, cx: &mut App) {
86 dispatch!(self, |state| state
87 .update(cx, |state, cx| state.set_text_align(align, cx)))
88 }
89
90 pub(crate) fn set_disabled(&self, disabled: bool, cx: &mut App) {
91 dispatch!(self, |state| state
92 .update(cx, |state, cx| state.set_disabled(disabled, cx)))
93 }
94
95 pub(crate) fn set_readonly(&self, readonly: bool, cx: &mut App) {
96 dispatch!(self, |state| state
97 .update(cx, |state, cx| state.set_readonly(readonly, cx)))
98 }
99
100 pub(crate) fn set_editor_style(&self, style: gpui_base::input::InputEditorStyle, cx: &mut App) {
101 dispatch!(self, |state| state
102 .update(cx, |state, _| state.set_editor_style(style)))
103 }
104
105 pub(crate) fn set_editor_paddings(&self, paddings: gpui::Edges<gpui::Pixels>, cx: &mut App) {
106 dispatch!(self, |state| state
107 .update(cx, |state, _| state.set_editor_paddings(paddings)))
108 }
109
110 pub(crate) fn ensure_highlighter_factory(
111 &self,
112 factory: gpui_base::input::InputHighlighterFactory,
113 cx: &mut App,
114 ) {
115 dispatch!(self, |state| state
116 .update(cx, |state, _| state.ensure_highlighter_factory(factory)))
117 }
118
119 pub(crate) fn on_context_menu(
120 &self,
121 handler: std::rc::Rc<
122 dyn Fn(
123 gpui_base::input::NativeMenu,
124 gpui_base::input::InputContextMenuCapabilities,
125 gpui::Point<gpui::Pixels>,
126 &mut Window,
127 &mut App,
128 ),
129 >,
130 cx: &mut App,
131 ) {
132 dispatch!(self, |state| state
133 .update(cx, |state, _| state.on_context_menu(handler)))
134 }
135
136 pub(crate) fn touch_selection(&self, cx: &App) -> Option<gpui_base::TouchSelectionSnapshot> {
138 dispatch!(self, |state| state.read(cx).touch_selection())
139 }
140
141 pub(crate) fn context_menu_capabilities(
142 &self,
143 cx: &App,
144 ) -> gpui_base::input::InputContextMenuCapabilities {
145 dispatch!(self, |state| state.read(cx).context_menu_capabilities())
146 }
147
148 pub(crate) fn is_all_selected(&self, cx: &App) -> bool {
151 dispatch!(self, |state| {
152 let state = state.read(cx);
153 state.selected_range() == (0..state.text().len())
154 })
155 }
156
157 pub(crate) fn begin_edge_drag(
158 &self,
159 edge: gpui_base::SelectionEdge,
160 finger: gpui::Point<gpui::Pixels>,
161 cx: &mut App,
162 ) {
163 dispatch!(self, |state| state
164 .update(cx, |state, cx| state.begin_edge_drag(edge, finger, cx)))
165 }
166
167 pub(crate) fn update_edge_drag(&self, finger: gpui::Point<gpui::Pixels>, cx: &mut App) {
168 dispatch!(self, |state| state
169 .update(cx, |state, cx| state.update_edge_drag(finger, cx)))
170 }
171
172 pub(crate) fn end_edge_drag(&self, cx: &mut App) {
173 dispatch!(self, |state| state
174 .update(cx, |state, cx| state.end_edge_drag(cx)))
175 }
176
177 pub(crate) fn close_edit_menu(&self, cx: &mut App) {
178 dispatch!(self, |state| state
179 .update(cx, |state, cx| state.close_edit_menu(cx)))
180 }
181
182 pub(crate) fn select_all_from_edit_menu(&self, window: &mut Window, cx: &mut App) {
183 dispatch!(self, |state| state.update(cx, |state, cx| state
184 .select_all_from_edit_menu(window, cx)))
185 }
186
187 pub(super) fn render_overlays(
189 &self,
190 window: &mut Window,
191 cx: &mut App,
192 ) -> super::overlay::InputOverlays {
193 dispatch!(self, |state| super::overlay::render_overlays(
194 state, window, cx
195 ))
196 }
197
198 pub(crate) fn replace_all(&self, value: String, window: &mut Window, cx: &mut App) {
199 dispatch!(self, |state| state
200 .update(cx, |state, cx| state.replace_all(value, window, cx)))
201 }
202
203 pub(crate) fn into_any_element(self) -> gpui::AnyElement {
205 use gpui::IntoElement as _;
206 dispatch!(self, |state| state.into_any_element())
207 }
208}
209
210impl From<&TextInputState> for AnyInputState {
211 fn from(state: &TextInputState) -> Self {
212 match state {
213 TextInputState::Input(state) => Self::Input(state.clone()),
214 TextInputState::Textarea(state) => Self::Textarea(state.clone()),
215 TextInputState::Editor(state) => Self::Editor(state.clone()),
216 }
217 }
218}
219
220impl From<Entity<InputState>> for TextInputState {
221 fn from(state: Entity<InputState>) -> Self {
222 Self::Input(state)
223 }
224}
225
226impl From<Entity<TextareaState>> for TextInputState {
227 fn from(state: Entity<TextareaState>) -> Self {
228 Self::Textarea(state)
229 }
230}
231
232impl From<Entity<EditorState>> for TextInputState {
233 fn from(state: Entity<EditorState>) -> Self {
234 Self::Editor(state)
235 }
236}
237
238impl AnyInputState {
239 pub fn as_input(&self) -> Option<&Entity<InputState>> {
241 match self {
242 Self::Input(state) => Some(state),
243 _ => None,
244 }
245 }
246
247 pub fn as_textarea(&self) -> Option<&Entity<TextareaState>> {
249 match self {
250 Self::Textarea(state) => Some(state),
251 _ => None,
252 }
253 }
254
255 pub fn as_editor(&self) -> Option<&Entity<EditorState>> {
257 match self {
258 Self::Editor(state) => Some(state),
259 _ => None,
260 }
261 }
262
263 pub fn as_otp(&self) -> Option<&Entity<OtpState>> {
265 match self {
266 Self::Otp(state) => Some(state),
267 _ => None,
268 }
269 }
270
271 pub fn value(&self, cx: &App) -> SharedString {
275 match self {
276 Self::Input(state) => state.read(cx).value(),
277 Self::Textarea(state) => state.read(cx).value(),
278 Self::Editor(state) => state.read(cx).value(),
279 Self::Otp(state) => state.read(cx).value().clone(),
280 }
281 }
282
283 pub fn focus_handle(&self, cx: &App) -> FocusHandle {
285 match self {
286 Self::Input(state) => state.focus_handle(cx),
287 Self::Textarea(state) => state.focus_handle(cx),
288 Self::Editor(state) => state.focus_handle(cx),
289 Self::Otp(state) => state.focus_handle(cx),
290 }
291 }
292}
293
294impl From<Entity<InputState>> for AnyInputState {
295 fn from(state: Entity<InputState>) -> Self {
296 Self::Input(state)
297 }
298}
299
300impl From<Entity<TextareaState>> for AnyInputState {
301 fn from(state: Entity<TextareaState>) -> Self {
302 Self::Textarea(state)
303 }
304}
305
306impl From<Entity<EditorState>> for AnyInputState {
307 fn from(state: Entity<EditorState>) -> Self {
308 Self::Editor(state)
309 }
310}
311
312impl From<Entity<OtpState>> for AnyInputState {
313 fn from(state: Entity<OtpState>) -> Self {
314 Self::Otp(state)
315 }
316}
317
318pub(super) fn sync_focused_input_registry(
321 state: impl Into<AnyInputState>,
322 window: &mut Window,
323 cx: &mut App,
324) {
325 let state = state.into();
326 let focused = state.focus_handle(cx).is_focused(window);
327 Root::try_update(window, cx, |root, _, cx| {
328 if focused {
329 root.focused_input = Some(state.clone());
330 } else if root.focused_input.as_ref() == Some(&state) {
331 root.focused_input = None;
332 }
333 cx.notify();
334 });
335}