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_editor_paddings(&self, paddings: gpui::Edges<gpui::Pixels>, cx: &mut App) {
130 dispatch!(self, |state| state
131 .update(cx, |state, _| state.set_editor_paddings(paddings)))
132 }
133
134 pub(crate) fn ensure_highlighter_factory(
135 &self,
136 factory: gpui_base::input::InputHighlighterFactory,
137 cx: &mut App,
138 ) {
139 dispatch!(self, |state| state
140 .update(cx, |state, _| state.ensure_highlighter_factory(factory)))
141 }
142
143 pub(crate) fn on_context_menu(
144 &self,
145 handler: std::rc::Rc<
146 dyn Fn(
147 gpui_base::input::NativeMenu,
148 gpui_base::input::InputContextMenuCapabilities,
149 gpui::Point<gpui::Pixels>,
150 &mut Window,
151 &mut App,
152 ),
153 >,
154 cx: &mut App,
155 ) {
156 dispatch!(self, |state| state
157 .update(cx, |state, _| state.on_context_menu(handler)))
158 }
159
160 pub(crate) fn touch_selection(&self, cx: &App) -> Option<gpui_base::TouchSelectionSnapshot> {
162 dispatch!(self, |state| state.read(cx).touch_selection())
163 }
164
165 pub(crate) fn context_menu_capabilities(
166 &self,
167 cx: &App,
168 ) -> gpui_base::input::InputContextMenuCapabilities {
169 dispatch!(self, |state| state.read(cx).context_menu_capabilities())
170 }
171
172 pub(crate) fn is_all_selected(&self, cx: &App) -> bool {
175 dispatch!(self, |state| {
176 let state = state.read(cx);
177 state.selected_range() == (0..state.text().len())
178 })
179 }
180
181 pub(crate) fn begin_edge_drag(
182 &self,
183 edge: gpui_base::SelectionEdge,
184 finger: gpui::Point<gpui::Pixels>,
185 cx: &mut App,
186 ) {
187 dispatch!(self, |state| state
188 .update(cx, |state, cx| state.begin_edge_drag(edge, finger, cx)))
189 }
190
191 pub(crate) fn update_edge_drag(&self, finger: gpui::Point<gpui::Pixels>, cx: &mut App) {
192 dispatch!(self, |state| state
193 .update(cx, |state, cx| state.update_edge_drag(finger, cx)))
194 }
195
196 pub(crate) fn end_edge_drag(&self, cx: &mut App) {
197 dispatch!(self, |state| state
198 .update(cx, |state, cx| state.end_edge_drag(cx)))
199 }
200
201 pub(crate) fn close_edit_menu(&self, cx: &mut App) {
202 dispatch!(self, |state| state
203 .update(cx, |state, cx| state.close_edit_menu(cx)))
204 }
205
206 pub(crate) fn select_all_from_edit_menu(&self, window: &mut Window, cx: &mut App) {
207 dispatch!(self, |state| state.update(cx, |state, cx| state
208 .select_all_from_edit_menu(window, cx)))
209 }
210
211 pub(super) fn render_overlays(
213 &self,
214 window: &mut Window,
215 cx: &mut App,
216 ) -> super::overlay::InputOverlays {
217 dispatch!(self, |state| super::overlay::render_overlays(
218 state, window, cx
219 ))
220 }
221
222 pub(crate) fn replace_all(&self, value: String, window: &mut Window, cx: &mut App) {
223 dispatch!(self, |state| state
224 .update(cx, |state, cx| state.replace_all(value, window, cx)))
225 }
226
227 pub(crate) fn into_any_element(self) -> gpui::AnyElement {
229 use gpui::IntoElement as _;
230 dispatch!(self, |state| state.into_any_element())
231 }
232}
233
234impl From<&TextInputState> for AnyInputState {
235 fn from(state: &TextInputState) -> Self {
236 match state {
237 TextInputState::Input(state) => Self::Input(state.clone()),
238 TextInputState::Textarea(state) => Self::Textarea(state.clone()),
239 TextInputState::Editor(state) => Self::Editor(state.clone()),
240 }
241 }
242}
243
244impl From<Entity<InputState>> for TextInputState {
245 fn from(state: Entity<InputState>) -> Self {
246 Self::Input(state)
247 }
248}
249
250impl From<Entity<TextareaState>> for TextInputState {
251 fn from(state: Entity<TextareaState>) -> Self {
252 Self::Textarea(state)
253 }
254}
255
256impl From<Entity<EditorState>> for TextInputState {
257 fn from(state: Entity<EditorState>) -> Self {
258 Self::Editor(state)
259 }
260}
261
262impl AnyInputState {
263 pub(crate) fn input_bounds(&self, cx: &App) -> Option<Bounds<Pixels>> {
264 match self {
265 Self::Input(state) => Some(state.read(cx).input_bounds()),
266 Self::Textarea(state) => Some(state.read(cx).input_bounds()),
267 Self::Editor(state) => Some(state.read(cx).input_bounds()),
268 Self::Otp(_) => None,
269 }
270 }
271
272 pub(crate) fn set_selection_focus(&self, focus: Option<FocusHandle>, cx: &mut App) {
273 match self {
274 Self::Input(state) => {
275 state.update(cx, |state, cx| state.set_selection_focus(focus, cx))
276 }
277 Self::Textarea(state) => {
278 state.update(cx, |state, cx| state.set_selection_focus(focus, cx))
279 }
280 Self::Editor(state) => {
281 state.update(cx, |state, cx| state.set_selection_focus(focus, cx))
282 }
283 Self::Otp(_) => {}
284 }
285 }
286
287 pub fn as_input(&self) -> Option<&Entity<InputState>> {
289 match self {
290 Self::Input(state) => Some(state),
291 _ => None,
292 }
293 }
294
295 pub fn as_textarea(&self) -> Option<&Entity<TextareaState>> {
297 match self {
298 Self::Textarea(state) => Some(state),
299 _ => None,
300 }
301 }
302
303 pub fn as_editor(&self) -> Option<&Entity<EditorState>> {
305 match self {
306 Self::Editor(state) => Some(state),
307 _ => None,
308 }
309 }
310
311 pub fn as_otp(&self) -> Option<&Entity<OtpState>> {
313 match self {
314 Self::Otp(state) => Some(state),
315 _ => None,
316 }
317 }
318
319 pub fn value(&self, cx: &App) -> SharedString {
323 match self {
324 Self::Input(state) => state.read(cx).value(),
325 Self::Textarea(state) => state.read(cx).value(),
326 Self::Editor(state) => state.read(cx).value(),
327 Self::Otp(state) => state.read(cx).value().clone(),
328 }
329 }
330
331 pub fn focus_handle(&self, cx: &App) -> FocusHandle {
333 match self {
334 Self::Input(state) => state.focus_handle(cx),
335 Self::Textarea(state) => state.focus_handle(cx),
336 Self::Editor(state) => state.focus_handle(cx),
337 Self::Otp(state) => state.focus_handle(cx),
338 }
339 }
340}
341
342impl From<Entity<InputState>> for AnyInputState {
343 fn from(state: Entity<InputState>) -> Self {
344 Self::Input(state)
345 }
346}
347
348impl From<Entity<TextareaState>> for AnyInputState {
349 fn from(state: Entity<TextareaState>) -> Self {
350 Self::Textarea(state)
351 }
352}
353
354impl From<Entity<EditorState>> for AnyInputState {
355 fn from(state: Entity<EditorState>) -> Self {
356 Self::Editor(state)
357 }
358}
359
360impl From<Entity<OtpState>> for AnyInputState {
361 fn from(state: Entity<OtpState>) -> Self {
362 Self::Otp(state)
363 }
364}
365
366pub(super) fn sync_focused_input_registry(
369 state: impl Into<AnyInputState>,
370 window: &mut Window,
371 cx: &mut App,
372) {
373 let state = state.into();
374 let focused = state.focus_handle(cx).is_focused(window);
375 let unchanged = WindowState::try_read(window, cx)
379 .is_some_and(|root| (root.focused_input.as_ref() == Some(&state)) == focused);
380 if unchanged {
381 return;
382 }
383 WindowState::try_update(window, cx, |root, _, cx| {
384 if focused {
385 root.focused_input = Some(state.clone());
386 } else if root.focused_input.as_ref() == Some(&state) {
387 root.focused_input = None;
388 }
389 cx.notify();
390 });
391}