1use crate::root::WindowState;
2use gpui::{App, Bounds, Entity, FocusHandle, Focusable as _, Pixels, SharedString, Window};
3use gpui_base::OtpState;
4use ropey::Rope;
5
6use super::{EditorState, InputState, TextareaState};
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 has_selection_focus(&self, window: &Window, cx: &App) -> bool {
56 dispatch!(self, |state| state.read(cx).has_selection_focus(window, cx))
57 }
58
59 pub(crate) fn install_token_presentation(
60 &self,
61 renderer: Option<gpui_base::input::InlineTokenRenderer>,
62 listener: Option<gpui_base::input::InlineTokenClickListener>,
63 secret: bool,
64 cx: &mut App,
65 ) {
66 dispatch!(self, |state| state.update(cx, |state, _| state
67 .install_token_presentation(renderer, listener, secret)))
68 }
69
70 pub(crate) fn install_token_hover_presentation(
71 &self,
72 hover_listener: Option<gpui_base::input::InlineTokenHoverListener>,
73 cx: &mut App,
74 ) {
75 dispatch!(self, |state| state.update(cx, |state, _| state
76 .install_token_hover_presentation(hover_listener)))
77 }
78
79 pub(crate) fn entity_id(&self) -> gpui::EntityId {
80 dispatch!(self, |state| state.entity_id())
81 }
82
83 pub(crate) fn presentation(&self, cx: &App) -> gpui_base::input::InputPresentation {
84 dispatch!(self, |state| state.read(cx).presentation())
85 }
86
87 pub(crate) fn text<'a>(&self, cx: &'a App) -> &'a Rope {
91 dispatch!(self, |state| state.read(cx).text())
92 }
93
94 pub(crate) fn focus(&self, window: &mut Window, cx: &mut App) {
95 dispatch!(self, |state| state
96 .update(cx, |state, cx| state.focus(window, cx)))
97 }
98
99 pub(crate) fn clean(&self, window: &mut Window, cx: &mut App) {
100 dispatch!(self, |state| state
101 .update(cx, |state, cx| state.clean(window, cx)))
102 }
103
104 pub(crate) fn toggle_masked(&self, window: &mut Window, cx: &mut App) {
105 dispatch!(self, |state| state
106 .update(cx, |state, cx| state.toggle_masked(window, cx)))
107 }
108
109 pub(crate) fn set_text_align(&self, align: gpui::TextAlign, cx: &mut App) {
110 dispatch!(self, |state| state
111 .update(cx, |state, cx| state.set_text_align(align, cx)))
112 }
113
114 pub(crate) fn set_disabled(&self, disabled: bool, cx: &mut App) {
115 dispatch!(self, |state| state
116 .update(cx, |state, cx| state.set_disabled(disabled, cx)))
117 }
118
119 pub(crate) fn set_readonly(&self, readonly: bool, cx: &mut App) {
120 dispatch!(self, |state| state
121 .update(cx, |state, cx| state.set_readonly(readonly, cx)))
122 }
123
124 pub(crate) fn set_editor_style(&self, style: gpui_base::input::InputEditorStyle, cx: &mut App) {
125 dispatch!(self, |state| state
126 .update(cx, |state, _| state.set_editor_style(style)))
127 }
128
129 pub(crate) fn set_accessibility(
130 &self,
131 accessibility: gpui_base::input::InputAccessibility,
132 cx: &mut App,
133 ) {
134 dispatch!(self, |state| state
135 .update(cx, |state, _| state.set_accessibility(accessibility)))
136 }
137 pub(crate) fn set_editor_paddings(&self, paddings: gpui::Edges<gpui::Pixels>, cx: &mut App) {
138 dispatch!(self, |state| state
139 .update(cx, |state, _| state.set_editor_paddings(paddings)))
140 }
141
142 pub(crate) fn ensure_highlighter_factory(
143 &self,
144 factory: gpui_base::input::InputHighlighterFactory,
145 cx: &mut App,
146 ) {
147 dispatch!(self, |state| state
148 .update(cx, |state, _| state.ensure_highlighter_factory(factory)))
149 }
150
151 pub(crate) fn on_context_menu(
152 &self,
153 handler: std::rc::Rc<
154 dyn Fn(
155 gpui_base::input::NativeMenu,
156 gpui_base::input::InputContextMenuCapabilities,
157 gpui::Point<gpui::Pixels>,
158 &mut Window,
159 &mut App,
160 ),
161 >,
162 cx: &mut App,
163 ) {
164 dispatch!(self, |state| state
165 .update(cx, |state, _| state.on_context_menu(handler)))
166 }
167
168 pub(crate) fn touch_selection(&self, cx: &App) -> Option<gpui_base::TouchSelectionSnapshot> {
170 dispatch!(self, |state| state.read(cx).touch_selection())
171 }
172
173 pub(crate) fn context_menu_capabilities(
174 &self,
175 cx: &App,
176 ) -> gpui_base::input::InputContextMenuCapabilities {
177 dispatch!(self, |state| state.read(cx).context_menu_capabilities())
178 }
179
180 pub(crate) fn is_all_selected(&self, cx: &App) -> bool {
183 dispatch!(self, |state| {
184 let state = state.read(cx);
185 state.selected_range() == (0..state.text().len())
186 })
187 }
188
189 pub(crate) fn begin_edge_drag(
190 &self,
191 edge: gpui_base::SelectionEdge,
192 finger: gpui::Point<gpui::Pixels>,
193 cx: &mut App,
194 ) {
195 dispatch!(self, |state| state
196 .update(cx, |state, cx| state.begin_edge_drag(edge, finger, cx)))
197 }
198
199 pub(crate) fn update_edge_drag(&self, finger: gpui::Point<gpui::Pixels>, cx: &mut App) {
200 dispatch!(self, |state| state
201 .update(cx, |state, cx| state.update_edge_drag(finger, cx)))
202 }
203
204 pub(crate) fn end_edge_drag(&self, cx: &mut App) {
205 dispatch!(self, |state| state
206 .update(cx, |state, cx| state.end_edge_drag(cx)))
207 }
208
209 pub(crate) fn close_edit_menu(&self, cx: &mut App) {
210 dispatch!(self, |state| state
211 .update(cx, |state, cx| state.close_edit_menu(cx)))
212 }
213
214 pub(crate) fn select_all_from_edit_menu(&self, window: &mut Window, cx: &mut App) {
215 dispatch!(self, |state| state.update(cx, |state, cx| state
216 .select_all_from_edit_menu(window, cx)))
217 }
218
219 pub(super) fn render_overlays(
221 &self,
222 window: &mut Window,
223 cx: &mut App,
224 ) -> super::overlay::InputOverlays {
225 dispatch!(self, |state| super::overlay::render_overlays(
226 state, window, cx
227 ))
228 }
229
230 pub(crate) fn into_any_element(self) -> gpui::AnyElement {
232 use gpui::IntoElement as _;
233 dispatch!(self, |state| state.into_any_element())
234 }
235}
236
237impl From<&TextInputState> for AnyInputState {
238 fn from(state: &TextInputState) -> Self {
239 match state {
240 TextInputState::Input(state) => Self::Input(state.clone()),
241 TextInputState::Textarea(state) => Self::Textarea(state.clone()),
242 TextInputState::Editor(state) => Self::Editor(state.clone()),
243 }
244 }
245}
246
247impl From<Entity<InputState>> for TextInputState {
248 fn from(state: Entity<InputState>) -> Self {
249 Self::Input(state)
250 }
251}
252
253impl From<Entity<TextareaState>> for TextInputState {
254 fn from(state: Entity<TextareaState>) -> Self {
255 Self::Textarea(state)
256 }
257}
258
259impl From<Entity<EditorState>> for TextInputState {
260 fn from(state: Entity<EditorState>) -> Self {
261 Self::Editor(state)
262 }
263}
264
265impl AnyInputState {
266 pub(crate) fn input_bounds(&self, cx: &App) -> Option<Bounds<Pixels>> {
267 match self {
268 Self::Input(state) => Some(state.read(cx).input_bounds()),
269 Self::Textarea(state) => Some(state.read(cx).input_bounds()),
270 Self::Editor(state) => Some(state.read(cx).input_bounds()),
271 Self::Otp(_) => None,
272 }
273 }
274
275 pub(crate) fn set_selection_focus(&self, focus: Option<FocusHandle>, cx: &mut App) {
276 match self {
277 Self::Input(state) => {
278 state.update(cx, |state, cx| state.set_selection_focus(focus, cx))
279 }
280 Self::Textarea(state) => {
281 state.update(cx, |state, cx| state.set_selection_focus(focus, cx))
282 }
283 Self::Editor(state) => {
284 state.update(cx, |state, cx| state.set_selection_focus(focus, cx))
285 }
286 Self::Otp(_) => {}
287 }
288 }
289
290 pub fn as_input(&self) -> Option<&Entity<InputState>> {
292 match self {
293 Self::Input(state) => Some(state),
294 _ => None,
295 }
296 }
297
298 pub fn as_textarea(&self) -> Option<&Entity<TextareaState>> {
300 match self {
301 Self::Textarea(state) => Some(state),
302 _ => None,
303 }
304 }
305
306 pub fn as_editor(&self) -> Option<&Entity<EditorState>> {
308 match self {
309 Self::Editor(state) => Some(state),
310 _ => None,
311 }
312 }
313
314 pub fn as_otp(&self) -> Option<&Entity<OtpState>> {
316 match self {
317 Self::Otp(state) => Some(state),
318 _ => None,
319 }
320 }
321
322 pub fn value(&self, cx: &App) -> SharedString {
326 match self {
327 Self::Input(state) => state.read(cx).value(),
328 Self::Textarea(state) => state.read(cx).value(),
329 Self::Editor(state) => state.read(cx).value(),
330 Self::Otp(state) => state.read(cx).value().clone(),
331 }
332 }
333
334 pub fn focus_handle(&self, cx: &App) -> FocusHandle {
336 match self {
337 Self::Input(state) => state.focus_handle(cx),
338 Self::Textarea(state) => state.focus_handle(cx),
339 Self::Editor(state) => state.focus_handle(cx),
340 Self::Otp(state) => state.focus_handle(cx),
341 }
342 }
343}
344
345impl From<Entity<InputState>> for AnyInputState {
346 fn from(state: Entity<InputState>) -> Self {
347 Self::Input(state)
348 }
349}
350
351impl From<Entity<TextareaState>> for AnyInputState {
352 fn from(state: Entity<TextareaState>) -> Self {
353 Self::Textarea(state)
354 }
355}
356
357impl From<Entity<EditorState>> for AnyInputState {
358 fn from(state: Entity<EditorState>) -> Self {
359 Self::Editor(state)
360 }
361}
362
363impl From<Entity<OtpState>> for AnyInputState {
364 fn from(state: Entity<OtpState>) -> Self {
365 Self::Otp(state)
366 }
367}
368
369pub(super) fn sync_focused_input_registry(
372 state: impl Into<AnyInputState>,
373 window: &mut Window,
374 cx: &mut App,
375) {
376 let state = state.into();
377 let focused = state.focus_handle(cx).is_focused(window);
378 let unchanged = WindowState::try_read(window, cx)
382 .is_some_and(|root| (root.focused_input.as_ref() == Some(&state)) == focused);
383 if unchanged {
384 return;
385 }
386 WindowState::try_update(window, cx, |root, _, cx| {
387 if focused {
388 root.focused_input = Some(state.clone());
389 } else if root.focused_input.as_ref() == Some(&state) {
390 root.focused_input = None;
391 }
392 cx.notify();
393 });
394}