Skip to main content

gpui_base/input/base/
state.rs

1//! A text input field that allows the user to enter text.
2//!
3//! Based on the `Input` example from the `gpui` crate.
4//! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs
5use gpui::TextAlign;
6use gpui::{
7    Action, App, AppContext, Bounds, ClipboardItem, Context, Edges, Entity, EntityInputHandler,
8    EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding,
9    KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _,
10    Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription,
11    UTF16Selection, Window, actions, div, point, prelude::FluentBuilder as _, px,
12};
13use ropey::{Rope, RopeSlice};
14use serde::Deserialize;
15use std::borrow::Cow;
16use std::cell::Cell;
17use std::ops::Range;
18use std::rc::Rc;
19use sum_tree::Bias;
20use unicode_segmentation::*;
21
22use super::{
23    DiagnosticSet, DisplayMap, InputContextMenuCapabilities, InputEditorStyle,
24    InputHighlighterFactory, MASK_CHAR, MaskPattern, NativeMenu, NumberStep, WrappingIndent,
25    blink_cursor::BlinkCursor,
26    change::Change,
27    element::{EditorScrollbar, EditorScrollbarSnapshot, TextElement},
28    kind::InputModeKind,
29    mask_pattern::normalize_number_input,
30    mode::LayoutMode,
31    undo_manager::{EditIntent, UndoManager},
32};
33use crate::actions::{SelectDown, SelectLeft, SelectRight, SelectUp};
34use crate::input::blink_cursor::CURSOR_WIDTH;
35use crate::input::movement::MoveDirection;
36use crate::input::{
37    InputExtras as _, Position, RopeExt as _, Selection, element::RIGHT_MARGIN, layout::LastLayout,
38};
39use crate::{AutoScroll, StepAction};
40
41#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
42#[action(namespace = input, no_json)]
43pub struct Enter {
44    /// Is confirm with secondary.
45    pub secondary: bool,
46    /// Whether the Shift modifier was held when Enter was pressed.
47    pub shift: bool,
48}
49
50impl Enter {
51    /// Returns true if `action` is a primary `Enter` action (`secondary: false`),
52    /// regardless of whether Shift was held.
53    pub fn is_primary(action: &dyn Action) -> bool {
54        action.partial_eq(&Enter {
55            secondary: false,
56            shift: false,
57        }) || action.partial_eq(&Enter {
58            secondary: false,
59            shift: true,
60        })
61    }
62}
63
64actions!(
65    input,
66    [
67        Backspace,
68        Delete,
69        DeleteToBeginningOfLine,
70        DeleteToEndOfLine,
71        DeleteToPreviousWordStart,
72        DeleteToNextWordEnd,
73        Indent,
74        Outdent,
75        IndentInline,
76        OutdentInline,
77        MoveUp,
78        MoveDown,
79        MoveLeft,
80        MoveRight,
81        MoveHome,
82        MoveEnd,
83        MovePageUp,
84        MovePageDown,
85        SelectAll,
86        SelectToStartOfLine,
87        SelectToEndOfLine,
88        SelectToStart,
89        SelectToEnd,
90        SelectToPreviousWordStart,
91        SelectToNextWordEnd,
92        ShowCharacterPalette,
93        Copy,
94        Cut,
95        Paste,
96        Undo,
97        Redo,
98        MoveToStartOfLine,
99        MoveToEndOfLine,
100        MoveToStart,
101        MoveToEnd,
102        MoveToPreviousWord,
103        MoveToNextWord,
104        Escape,
105        ToggleCodeActions,
106        Search,
107        Replace,
108        GoToDefinition,
109    ]
110);
111
112#[derive(Clone)]
113pub enum InputEvent {
114    Change,
115    PressEnter { secondary: bool, shift: bool },
116    Focus,
117    Blur,
118}
119
120pub(super) const CONTEXT: &str = "Input";
121
122pub(crate) fn init(cx: &mut App) {
123    cx.bind_keys([
124        KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
125        KeyBinding::new("shift-backspace", Backspace, Some(CONTEXT)),
126        #[cfg(target_os = "macos")]
127        KeyBinding::new("ctrl-backspace", Backspace, Some(CONTEXT)),
128        KeyBinding::new("delete", Delete, Some(CONTEXT)),
129        KeyBinding::new("shift-delete", Delete, Some(CONTEXT)),
130        #[cfg(target_os = "macos")]
131        KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
132        #[cfg(target_os = "macos")]
133        KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
134        #[cfg(target_os = "macos")]
135        KeyBinding::new("alt-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
136        #[cfg(not(target_os = "macos"))]
137        KeyBinding::new("ctrl-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
138        #[cfg(target_os = "macos")]
139        KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)),
140        #[cfg(not(target_os = "macos"))]
141        KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)),
142        KeyBinding::new(
143            "enter",
144            Enter {
145                secondary: false,
146                shift: false,
147            },
148            Some(CONTEXT),
149        ),
150        KeyBinding::new(
151            "shift-enter",
152            Enter {
153                secondary: false,
154                shift: true,
155            },
156            Some(CONTEXT),
157        ),
158        KeyBinding::new(
159            "secondary-enter",
160            Enter {
161                secondary: true,
162                shift: false,
163            },
164            Some(CONTEXT),
165        ),
166        KeyBinding::new("escape", Escape, Some(CONTEXT)),
167        KeyBinding::new("up", MoveUp, Some(CONTEXT)),
168        KeyBinding::new("down", MoveDown, Some(CONTEXT)),
169        KeyBinding::new("left", MoveLeft, Some(CONTEXT)),
170        KeyBinding::new("right", MoveRight, Some(CONTEXT)),
171        KeyBinding::new("pageup", MovePageUp, Some(CONTEXT)),
172        KeyBinding::new("pagedown", MovePageDown, Some(CONTEXT)),
173        KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
174        KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
175        #[cfg(target_os = "macos")]
176        KeyBinding::new("cmd-]", Indent, Some(CONTEXT)),
177        #[cfg(not(target_os = "macos"))]
178        KeyBinding::new("ctrl-]", Indent, Some(CONTEXT)),
179        #[cfg(target_os = "macos")]
180        KeyBinding::new("cmd-[", Outdent, Some(CONTEXT)),
181        #[cfg(not(target_os = "macos"))]
182        KeyBinding::new("ctrl-[", Outdent, Some(CONTEXT)),
183        KeyBinding::new("shift-left", SelectLeft, Some(CONTEXT)),
184        KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
185        KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
186        KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
187        KeyBinding::new("home", MoveHome, Some(CONTEXT)),
188        KeyBinding::new("end", MoveEnd, Some(CONTEXT)),
189        KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
190        KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
191        #[cfg(target_os = "macos")]
192        KeyBinding::new("ctrl-shift-a", SelectToStartOfLine, Some(CONTEXT)),
193        #[cfg(target_os = "macos")]
194        KeyBinding::new("ctrl-shift-e", SelectToEndOfLine, Some(CONTEXT)),
195        #[cfg(target_os = "macos")]
196        KeyBinding::new("shift-cmd-left", SelectToStartOfLine, Some(CONTEXT)),
197        #[cfg(target_os = "macos")]
198        KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
199        #[cfg(target_os = "macos")]
200        KeyBinding::new("alt-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
201        #[cfg(not(target_os = "macos"))]
202        KeyBinding::new("ctrl-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
203        #[cfg(target_os = "macos")]
204        KeyBinding::new("alt-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
205        #[cfg(not(target_os = "macos"))]
206        KeyBinding::new("ctrl-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
207        #[cfg(target_os = "macos")]
208        KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
209        #[cfg(target_os = "macos")]
210        KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
211        #[cfg(not(target_os = "macos"))]
212        KeyBinding::new("ctrl-a", SelectAll, Some(CONTEXT)),
213        #[cfg(target_os = "macos")]
214        KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
215        #[cfg(not(target_os = "macos"))]
216        KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
217        #[cfg(target_os = "macos")]
218        KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
219        #[cfg(not(target_os = "macos"))]
220        KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
221        #[cfg(target_os = "macos")]
222        KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
223        #[cfg(not(target_os = "macos"))]
224        KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
225        #[cfg(target_os = "macos")]
226        KeyBinding::new("ctrl-a", MoveHome, Some(CONTEXT)),
227        #[cfg(target_os = "macos")]
228        KeyBinding::new("cmd-left", MoveHome, Some(CONTEXT)),
229        #[cfg(target_os = "macos")]
230        KeyBinding::new("ctrl-e", MoveEnd, Some(CONTEXT)),
231        #[cfg(target_os = "macos")]
232        KeyBinding::new("cmd-right", MoveEnd, Some(CONTEXT)),
233        #[cfg(target_os = "macos")]
234        KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
235        #[cfg(target_os = "macos")]
236        KeyBinding::new("cmd-shift-z", Redo, Some(CONTEXT)),
237        #[cfg(target_os = "macos")]
238        KeyBinding::new("cmd-up", MoveToStart, Some(CONTEXT)),
239        #[cfg(target_os = "macos")]
240        KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
241        #[cfg(target_os = "macos")]
242        KeyBinding::new("alt-left", MoveToPreviousWord, Some(CONTEXT)),
243        #[cfg(target_os = "macos")]
244        KeyBinding::new("alt-right", MoveToNextWord, Some(CONTEXT)),
245        #[cfg(not(target_os = "macos"))]
246        KeyBinding::new("ctrl-left", MoveToPreviousWord, Some(CONTEXT)),
247        #[cfg(not(target_os = "macos"))]
248        KeyBinding::new("ctrl-right", MoveToNextWord, Some(CONTEXT)),
249        #[cfg(target_os = "macos")]
250        KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
251        #[cfg(target_os = "macos")]
252        KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
253        #[cfg(not(target_os = "macos"))]
254        KeyBinding::new("ctrl-z", Undo, Some(CONTEXT)),
255        #[cfg(not(target_os = "macos"))]
256        KeyBinding::new("ctrl-y", Redo, Some(CONTEXT)),
257        #[cfg(target_os = "macos")]
258        KeyBinding::new("cmd-.", ToggleCodeActions, Some(CONTEXT)),
259        #[cfg(not(target_os = "macos"))]
260        KeyBinding::new("ctrl-.", ToggleCodeActions, Some(CONTEXT)),
261        #[cfg(target_os = "macos")]
262        KeyBinding::new("cmd-f", Search, Some(CONTEXT)),
263        #[cfg(not(target_os = "macos"))]
264        KeyBinding::new("ctrl-f", Search, Some(CONTEXT)),
265        #[cfg(target_os = "macos")]
266        KeyBinding::new("cmd-shift-f", Replace, Some(CONTEXT)),
267        #[cfg(not(target_os = "macos"))]
268        KeyBinding::new("ctrl-h", Replace, Some(CONTEXT)),
269    ]);
270}
271
272/// The shared text-editing engine behind [`crate::input::InputState`],
273/// [`crate::input::TextareaState`] and [`crate::input::EditorState`].
274///
275/// `M` is the mode marker: it carries no data and only decides which methods
276/// exist, so an ordinary input cannot reach the editor's language features.
277///
278/// The three states are type aliases of this one, which is why this name is
279/// public: an alias is only as usable as the type behind it, so hiding this
280/// would leave `InputState` unable to do anything. Prefer naming the aliases
281/// β€” write `InputState`, not `InputBaseState<InputMode>`.
282pub struct InputBaseState<M: InputModeKind> {
283    /// State only this mode needs. See [`InputModeKind::Extras`].
284    pub(crate) extras: M::Extras,
285    pub(super) focus_handle: FocusHandle,
286    pub(super) mode: LayoutMode,
287    pub(super) text: Rope,
288    pub(super) display_map: DisplayMap,
289    pub(super) undo_manager: UndoManager,
290    pub(super) search_session: super::SearchSession,
291    pub(super) searchable: bool,
292    pub(super) replaceable: bool,
293    pub(super) soft_wrap: bool,
294    pub(super) wrapping_indent: WrappingIndent,
295    pub(super) scroll_beyond_last_line: Option<usize>,
296    pub(super) cursor_surrounding_lines: Option<usize>,
297    pub(super) blink_cursor: Entity<BlinkCursor>,
298    pub(super) loading: bool,
299    /// Range in UTF-8 length for the selected text.
300    ///
301    /// - "Hello δΈ–η•ŒπŸ’" = 16
302    /// - "πŸ’" = 4
303    pub(super) selected_range: Selection,
304    /// Range for save the selected word, use to keep word range when drag move.
305    pub(super) selected_word_range: Option<Selection>,
306    pub(super) selection_reversed: bool,
307    /// The marked range is the temporary insert text on IME typing.
308    pub(super) ime_marked_range: Option<Selection>,
309    pub(super) last_layout: Option<LastLayout>,
310    pub(super) last_cursor: Option<usize>,
311    /// The input container bounds
312    pub(super) input_bounds: Bounds<Pixels>,
313    /// The text bounds
314    pub(super) last_bounds: Option<Bounds<Pixels>>,
315    pub(super) last_selected_range: Option<Selection>,
316    pub(super) selecting: bool,
317    pub(crate) disabled: bool,
318    pub(crate) readonly: bool,
319    pub(crate) text_align: TextAlign,
320    pub(super) masked: bool,
321    pub(super) clean_on_escape: bool,
322    pub(super) submit_on_enter: bool,
323    pub(super) show_whitespaces: bool,
324    /// This flag tells the renderer to prefer the end of the current visual line.
325    pub(crate) cursor_line_end_affinity: bool,
326    pub(super) pattern: Option<regex::Regex>,
327    pub(super) validate: Option<Box<dyn Fn(&str, &mut App) -> bool + 'static>>,
328    /// The step strategy for [`super::NumberInput`] to increment/decrement.
329    /// See [`Self::step`] and [`Self::step_by`].
330    pub(crate) number_step: Option<NumberStep>,
331    /// The minimum value for [`super::NumberInput`]. See [`Self::min`].
332    pub(crate) number_min: Option<f64>,
333    /// The maximum value for [`super::NumberInput`]. See [`Self::max`].
334    pub(crate) number_max: Option<f64>,
335    pub(crate) scroll_handle: ScrollHandle,
336    /// The deferred scroll offset to apply on next layout.
337    pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,
338    /// The size of the scrollable content.
339    pub(crate) scroll_size: gpui::Size<Pixels>,
340    pub(super) editor_scrollbar_snapshot: Cell<Option<EditorScrollbarSnapshot>>,
341    pub(super) editor_paddings: Edges<Pixels>,
342    /// The style this state paints with: what was projected onto it, with
343    /// every colour left unset resolved from the palette that is current. It
344    /// is rebuilt at the top of every render, which is what keeps it current
345    /// when the palette changes after the state was built.
346    pub(super) editor_style: InputEditorStyle,
347    /// What a consumer projected, kept verbatim so that resolution never
348    /// consumes its own output: resolving in place would fill the unset
349    /// colours once and then never see them as unset again, which is the same
350    /// freeze in a different place.
351    projected_editor_style: InputEditorStyle,
352
353    /// The mask pattern for formatting the input text
354    pub(crate) mask_pattern: MaskPattern,
355    /// Whether the `mask_pattern` was explicitly set (via [`Self::mask_pattern`]
356    /// or [`Self::set_mask_pattern`]), to let [`super::NumberInput`] only apply
357    /// its default mask when the user has not made an explicit choice.
358    pub(super) mask_pattern_set: bool,
359    pub(super) placeholder: SharedString,
360
361    /// Diagnostic currently requested by pointer hover; applications render it.
362    pub(super) diagnostic_popover: Option<Rc<crate::input::DiagnosticEntry>>,
363
364    context_menu_handler: Option<
365        Rc<dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App)>,
366    >,
367    pending_context_menu: Option<(Point<Pixels>, usize)>,
368
369    /// Whether the context menu that shows on right-click is enabled.
370    ///
371    pub(super) enable_context_menu: bool,
372
373    /// A flag to indicate if we are currently inserting a completion item.
374    pub(super) completion_inserting: bool,
375    pub(super) overlay_action_handler: Option<
376        Rc<
377            dyn Fn(
378                super::InputOverlayKind,
379                Box<dyn Action>,
380                &mut Window,
381                &mut Context<InputBaseState<M>>,
382            ) -> bool,
383        >,
384    >,
385
386    /// A flag to indicate if we have a pending update to the text.
387    ///
388    /// If true, will call some update (for example LSP, Syntax Highlight) before render.
389    _pending_update: bool,
390    /// A flag to indicate if we should ignore the next completion event.
391    pub(super) silent_replace_text: bool,
392    /// A flag to indicate if we should emit InputEvents.
393    pub(super) emit_events: bool,
394
395    /// To remember the horizontal column (x-coordinate) of the cursor position for keep column for move up/down.
396    ///
397    /// The first element is the x-coordinate (Pixels), preferred to use this.
398    /// The second element is the column (usize), fallback to use this.
399    pub(super) preferred_column: Option<(Pixels, usize)>,
400    _subscriptions: Vec<Subscription>,
401
402    pub(super) auto_scroll: AutoScroll,
403}
404
405/// Read-only styling data exposed to presentation facades.
406///
407/// The fields are private and read through the methods below, so that a new
408/// one can be added without breaking the facades.
409#[derive(Clone)]
410pub struct InputPresentation {
411    focus_handle: FocusHandle,
412    disabled: bool,
413    readonly: bool,
414    loading: bool,
415    masked: bool,
416    multi_line: bool,
417    code_editor: bool,
418    text_align: TextAlign,
419    placeholder: SharedString,
420    mask_placeholder: Option<String>,
421}
422
423impl InputPresentation {
424    pub fn focus_handle(&self) -> &FocusHandle {
425        &self.focus_handle
426    }
427
428    pub fn is_disabled(&self) -> bool {
429        self.disabled
430    }
431
432    pub fn is_readonly(&self) -> bool {
433        self.readonly
434    }
435
436    /// Returns true if the user is allowed to change the text.
437    ///
438    /// See also: [`InputBaseState::is_editable`].
439    pub fn is_editable(&self) -> bool {
440        !self.disabled && !self.readonly
441    }
442
443    pub fn is_loading(&self) -> bool {
444        self.loading
445    }
446
447    pub fn is_masked(&self) -> bool {
448        self.masked
449    }
450
451    pub fn is_multi_line(&self) -> bool {
452        self.multi_line
453    }
454
455    pub fn is_code_editor(&self) -> bool {
456        self.code_editor
457    }
458
459    pub fn text_align(&self) -> TextAlign {
460        self.text_align
461    }
462
463    pub fn placeholder(&self) -> &SharedString {
464        &self.placeholder
465    }
466
467    /// The placeholder derived from the mask pattern, e.g.: `(___) ___-____`.
468    pub fn mask_placeholder(&self) -> Option<&str> {
469        self.mask_placeholder.as_deref()
470    }
471}
472
473impl<M: InputModeKind> EventEmitter<InputEvent> for InputBaseState<M> {}
474
475impl<M: InputModeKind> InputBaseState<M> {
476    #[doc(hidden)]
477    pub fn cursor_layout(&self) -> Option<(Bounds<Pixels>, Pixels)> {
478        let layout = self.last_layout.as_ref()?;
479        Some((layout.cursor_bounds?, layout.line_height))
480    }
481
482    pub fn input_bounds(&self) -> Bounds<Pixels> {
483        self.input_bounds
484    }
485
486    pub fn text_bounds(&self) -> Option<Bounds<Pixels>> {
487        self.last_bounds
488    }
489
490    pub fn diagnostic_popover(&self) -> Option<Rc<crate::input::DiagnosticEntry>> {
491        self.diagnostic_popover.clone()
492    }
493
494    pub fn presentation(&self) -> InputPresentation {
495        InputPresentation {
496            focus_handle: self.focus_handle.clone(),
497            disabled: self.disabled,
498            readonly: self.readonly,
499            loading: self.loading,
500            masked: self.masked,
501            multi_line: self.is_multi_line(),
502            code_editor: self.is_code_editor(),
503            text_align: self.text_align,
504            placeholder: self.placeholder.clone(),
505            mask_placeholder: self.mask_pattern.placeholder(),
506        }
507    }
508
509    /// Whether this input spans more than one line.
510    ///
511    /// Answered by the mode marker, which is fixed when the state is built.
512    /// [`LayoutMode`] holds the row counts and growth policy, not the kind.
513    #[inline]
514    /// Whether this input paints scrollbars.
515    ///
516    /// Only a multi-line input can scroll: a single-line input keeps its
517    /// caret in view by moving its own offset, and never has a viewport a
518    /// user could drag. Adding the editor scrollbar to every input put a
519    /// thumb inside every text field, which is a control the field does not
520    /// have.
521    pub(crate) fn shows_scrollbar(&self) -> bool {
522        self.is_multi_line()
523    }
524
525    pub fn is_multi_line(&self) -> bool {
526        M::MULTI_LINE
527    }
528
529    /// Whether this input is a single-line text field. See [`Self::is_multi_line`].
530    #[inline]
531    pub fn is_single_line(&self) -> bool {
532        !M::MULTI_LINE
533    }
534
535    /// Whether this input is a source-code editor.
536    #[inline]
537    pub fn is_code_editor(&self) -> bool {
538        M::CODE_EDITOR
539    }
540
541    /// Whether the user is allowed to copy the selection out.
542    ///
543    /// A masked input keeps its value out of the clipboard.
544    pub fn is_copyable(&self) -> bool {
545        !self.selected_range.is_empty() && !self.masked
546    }
547
548    pub fn context_menu_capabilities(&self) -> InputContextMenuCapabilities {
549        let (go_to_definition, code_actions) = self.extras.context_menu_capabilities();
550        InputContextMenuCapabilities::new()
551            .disabled(self.disabled)
552            .readonly(self.readonly)
553            .code_editor(self.is_code_editor())
554            .selection(!self.selected_range.is_empty())
555            .masked(self.masked)
556            .go_to_definition(go_to_definition)
557            .code_actions(code_actions)
558    }
559
560    pub fn set_text_align(&mut self, text_align: TextAlign, cx: &mut Context<Self>) {
561        if !self.is_single_line() || self.text_align == text_align {
562            return;
563        }
564
565        self.text_align = text_align;
566        cx.notify();
567    }
568
569    /// Flip the password mask.
570    ///
571    /// Setting the mask is a single-line method, but flipping it stays here:
572    /// the reveal button is rendered from the generic path, and it can only be
573    /// switched on through [`crate::input::InputState`] anyway.
574    pub fn toggle_masked(&mut self, _: &mut Window, cx: &mut Context<Self>) {
575        self.masked = !self.masked;
576        cx.notify();
577    }
578
579    pub fn on_context_menu(
580        &mut self,
581        handler: Rc<
582            dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App),
583        >,
584    ) {
585        self.context_menu_handler = Some(handler);
586    }
587
588    /// Build the engine. Each mode's own `new` sets its layout on top of this.
589    fn new_in_mode(window: &mut Window, cx: &mut Context<Self>) -> Self {
590        let focus_handle = cx.focus_handle().tab_stop(true);
591        let blink_cursor = cx.new(|_| BlinkCursor::new());
592        let undo_manager = UndoManager::new();
593
594        let _subscriptions = vec![
595            // Observe the blink cursor to repaint the view when it changes.
596            cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
597            // Blink the cursor when the window is active, pause when it's not.
598            cx.observe_window_activation(window, |input, window, cx| {
599                if window.is_window_active() {
600                    let focus_handle = input.focus_handle.clone();
601                    if focus_handle.is_focused(window) {
602                        input.blink_cursor.update(cx, |blink_cursor, cx| {
603                            blink_cursor.start(cx);
604                        });
605                    }
606                }
607            }),
608            cx.on_focus(&focus_handle, window, Self::on_focus),
609            cx.on_blur(&focus_handle, window, Self::on_blur),
610        ];
611
612        let text_style = window.text_style();
613
614        Self {
615            extras: M::Extras::default(),
616            focus_handle: focus_handle.clone(),
617            text: "".into(),
618            display_map: DisplayMap::new(text_style.font(), window.rem_size(), None),
619            search_session: super::SearchSession::default(),
620            searchable: false,
621            replaceable: true,
622            soft_wrap: true,
623            wrapping_indent: WrappingIndent::default(),
624            scroll_beyond_last_line: None,
625            cursor_surrounding_lines: None,
626            blink_cursor,
627            undo_manager,
628            selected_range: Selection::default(),
629            selected_word_range: None,
630            selection_reversed: false,
631            ime_marked_range: None,
632            input_bounds: Bounds::default(),
633            selecting: false,
634            disabled: false,
635            readonly: false,
636            text_align: TextAlign::Left,
637            masked: false,
638            clean_on_escape: false,
639            submit_on_enter: false,
640            show_whitespaces: false,
641            loading: false,
642            pattern: None,
643            validate: None,
644            number_step: Some(NumberStep::Fixed(1.)),
645            number_min: None,
646            number_max: None,
647            mode: LayoutMode::default(),
648            last_layout: None,
649            last_bounds: None,
650            last_selected_range: None,
651            last_cursor: None,
652            scroll_handle: ScrollHandle::new(),
653            scroll_size: gpui::size(px(0.), px(0.)),
654            editor_scrollbar_snapshot: Cell::new(None),
655            editor_paddings: Edges::default(),
656            deferred_scroll_offset: None,
657            preferred_column: None,
658            placeholder: SharedString::default(),
659            mask_pattern: MaskPattern::default(),
660            mask_pattern_set: false,
661            editor_style: InputEditorStyle::default(),
662            projected_editor_style: InputEditorStyle::default(),
663            diagnostic_popover: None,
664            context_menu_handler: None,
665            pending_context_menu: None,
666            enable_context_menu: true,
667            completion_inserting: false,
668            overlay_action_handler: None,
669            silent_replace_text: false,
670            emit_events: true,
671            _subscriptions,
672            _pending_update: false,
673            cursor_line_end_affinity: false,
674            auto_scroll: AutoScroll::default(),
675        }
676    }
677
678    /// Sets whether the context menu that shows on right-click is enabled.
679    ///
680    /// The context menu is enabled by default.
681    /// This value is ignored if a custom context menu builder is defined on the input.
682    pub fn context_menu(mut self, enable: bool) -> Self {
683        self.enable_context_menu = enable;
684        self
685    }
686
687    pub fn set_context_menu_enabled(&mut self, enabled: bool) {
688        self.enable_context_menu = enabled;
689    }
690
691    /// Set whether search UI allows replacement, default is true.
692    #[doc(hidden)]
693    pub fn replaceable(mut self, allow: bool) -> Self {
694        self.replaceable = allow;
695        self
696    }
697
698    /// Set placeholder
699    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
700        self.placeholder = placeholder.into();
701        self
702    }
703
704    /// Set highlighter language for for [`LayoutMode::CodeEditor`] mode.
705    pub fn set_highlighter(
706        &mut self,
707        new_language: impl Into<SharedString>,
708        cx: &mut Context<Self>,
709    ) {
710        match &mut self.mode {
711            LayoutMode::CodeEditor {
712                language,
713                highlighter,
714                ..
715            } => {
716                *language = new_language.into();
717                *highlighter.borrow_mut() = None;
718            }
719            _ => {}
720        }
721        cx.notify();
722    }
723
724    fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
725        match &mut self.mode {
726            LayoutMode::CodeEditor { highlighter, .. } => {
727                *highlighter.borrow_mut() = None;
728            }
729            _ => {}
730        }
731        cx.notify();
732    }
733
734    /// Install the parser/highlighter adapter used by code-editor mode.
735    pub fn set_highlighter_factory(
736        &mut self,
737        factory: InputHighlighterFactory,
738        cx: &mut Context<Self>,
739    ) {
740        self.mode.set_highlighter_factory(factory);
741        self._pending_update = true;
742        cx.notify();
743    }
744
745    /// Install a default adapter without replacing an application-provided one.
746    pub fn ensure_highlighter_factory(&mut self, factory: InputHighlighterFactory) {
747        self.mode.ensure_highlighter_factory(factory);
748    }
749
750    pub fn set_editor_style(&mut self, style: InputEditorStyle) {
751        self.editor_style = style.clone();
752        self.projected_editor_style = style;
753    }
754
755    /// Set presentation padding for multi-line text and its scrollbar layout.
756    #[doc(hidden)]
757    pub fn set_editor_paddings(&mut self, paddings: Edges<Pixels>) {
758        self.editor_paddings = paddings;
759    }
760
761    pub fn apply_highlighter_fold_candidates(
762        &mut self,
763        candidates: Vec<crate::input::FoldRange>,
764        cx: &mut Context<Self>,
765    ) {
766        if self.mode.is_folding() {
767            self.display_map.set_fold_candidates(candidates);
768        }
769        cx.notify();
770    }
771
772    #[inline]
773    pub fn diagnostics(&self) -> Option<&DiagnosticSet> {
774        self.mode.diagnostics()
775    }
776
777    #[inline]
778    pub fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
779        self.mode.diagnostics_mut()
780    }
781
782    /// Set placeholder
783    pub fn set_placeholder(
784        &mut self,
785        placeholder: impl Into<SharedString>,
786        _: &mut Window,
787        cx: &mut Context<Self>,
788    ) {
789        self.placeholder = placeholder.into();
790        cx.notify();
791    }
792
793    /// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.
794    ///
795    /// Returns:
796    ///
797    /// - The index of the line (zero-based) containing the offset.
798    /// - The index of the sub-line (zero-based) within the line containing the offset.
799    /// - The position of the offset.
800    pub(super) fn line_and_position_for_offset(
801        &self,
802        offset: usize,
803    ) -> (usize, usize, Option<Point<Pixels>>) {
804        let Some(last_layout) = &self.last_layout else {
805            return (0, 0, None);
806        };
807        let line_height = last_layout.line_height;
808
809        let mut y_offset = last_layout.visible_top;
810        for (vi, line) in last_layout.lines.iter().enumerate() {
811            let prev_lines_offset = last_layout.visible_line_byte_offsets[vi];
812            let local_offset = offset.saturating_sub(prev_lines_offset);
813            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
814                let sub_line_index = (pos.y / line_height) as usize;
815                let adjusted_pos = point(pos.x + last_layout.line_number_width, pos.y + y_offset);
816                return (vi, sub_line_index, Some(adjusted_pos));
817            }
818
819            y_offset += line.size(line_height).height;
820        }
821        (0, 0, None)
822    }
823
824    /// Set the text of the input field.
825    ///
826    /// For single-line inputs the caret is placed at the end of the text while
827    /// the view is scrolled back to the start, so a long value shows its
828    /// beginning instead of its tail (matching HTML `<input>`). Multi-line
829    /// inputs reset the selection to `0..0`.
830    pub fn set_value(
831        &mut self,
832        value: impl Into<SharedString>,
833        window: &mut Window,
834        cx: &mut Context<Self>,
835    ) {
836        self.undo_manager.set_ignoring(true);
837        self.emit_events = false;
838        self.replace_text(value, window, cx);
839        self.undo_manager.set_ignoring(false);
840        self.emit_events = true;
841
842        self.reset_selection();
843        self.reset_lsp_state();
844        self.reset_scroll_to_start();
845
846        self.undo_manager.clear();
847        cx.notify();
848    }
849
850    /// Replace the entire text content while preserving undo history.
851    ///
852    /// Unlike [`set_value`](Self::set_value), this method records the
853    /// replacement in the undo stack, allowing the user to undo/redo
854    /// the change. The selection is placed at the end of the new text
855    /// for single-line inputs, or cleared (0..0) for multi-line inputs.
856    ///
857    /// Use this when programmatically replacing the full text but the
858    /// user should still be able to undo the operation β€” e.g. formatting.
859    pub fn replace_all(
860        &mut self,
861        text: impl Into<SharedString>,
862        window: &mut Window,
863        cx: &mut Context<Self>,
864    ) {
865        self.replace_text(text, window, cx);
866        self.reset_selection();
867        self.reset_lsp_state();
868        self.reset_scroll_to_start();
869
870        cx.notify();
871    }
872
873    /// Perform `f` with the user-facing edit restrictions lifted.
874    ///
875    /// The `disabled` and `readonly` modes only reject the changes made by the
876    /// user, the programmatic APIs must always be able to update the text.
877    fn with_edits_allowed(&mut self, f: impl FnOnce(&mut Self)) {
878        let (was_disabled, was_readonly) = (self.disabled, self.readonly);
879        (self.disabled, self.readonly) = (false, false);
880        f(self);
881        (self.disabled, self.readonly) = (was_disabled, was_readonly);
882    }
883
884    /// Insert text at the current cursor position.
885    ///
886    /// And the cursor will be moved to the end of inserted text.
887    pub fn insert(
888        &mut self,
889        text: impl Into<SharedString>,
890        window: &mut Window,
891        cx: &mut Context<Self>,
892    ) {
893        let text: SharedString = text.into();
894        self.with_edits_allowed(|this| {
895            this.undo_manager.pending_intent = Some(EditIntent::Atomic);
896            let range_utf16 = this.range_to_utf16(&(this.cursor()..this.cursor()));
897            this.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
898            this.selected_range = (this.selected_range.end..this.selected_range.end).into();
899        });
900    }
901
902    /// Replace text at the current cursor position.
903    ///
904    /// And the cursor will be moved to the end of replaced text.
905    pub fn replace(
906        &mut self,
907        text: impl Into<SharedString>,
908        window: &mut Window,
909        cx: &mut Context<Self>,
910    ) {
911        let text: SharedString = text.into();
912        self.with_edits_allowed(|this| {
913            this.undo_manager.pending_intent = Some(EditIntent::Atomic);
914            this.replace_text_in_range_silent(None, &text, window, cx);
915            this.selected_range = (this.selected_range.end..this.selected_range.end).into();
916        });
917    }
918
919    fn replace_text(
920        &mut self,
921        text: impl Into<SharedString>,
922        window: &mut Window,
923        cx: &mut Context<Self>,
924    ) {
925        let text: SharedString = text.into();
926        self.with_edits_allowed(|this| {
927            this.undo_manager.pending_intent = Some(EditIntent::Atomic);
928            let range = 0..this.text.chars().map(|c| c.len_utf16()).sum();
929            this.replace_text_in_range_silent(Some(range), &text, window, cx);
930            this.reset_highlighter(cx);
931        });
932    }
933
934    fn reset_selection(&mut self) {
935        // For single-line inputs the caret is placed at the end of the text
936        // (matching HTML `<input>`); multi-line inputs reset the selection to
937        // `0..0`.
938        if self.is_single_line() {
939            let end = self.text.len();
940            self.selected_range = (end..end).into();
941        } else {
942            self.selected_range.clear();
943        }
944    }
945
946    fn reset_lsp_state(&mut self) {
947        if self.is_code_editor() {
948            self._pending_update = true;
949            M::reset_language_features(self);
950        }
951    }
952
953    fn reset_scroll_to_start(&mut self) {
954        // Move scroll to the start. For single-line the caret is at the end, so
955        // override the cursor-follow scroll for the next painted frame to keep
956        // the start visible; the deferred offset is consumed during that paint.
957        self.scroll_handle.set_offset(point(px(0.), px(0.)));
958        if self.is_single_line() {
959            self.deferred_scroll_offset = Some(point(px(0.), px(0.)));
960        }
961    }
962
963    /// Set with disabled mode.
964    ///
965    /// See also: [`Self::set_disabled`].
966    #[allow(unused)]
967    pub(crate) fn disabled(mut self, disabled: bool) -> Self {
968        self.disabled = disabled;
969        self
970    }
971
972    pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
973        if self.disabled == disabled {
974            return;
975        }
976
977        self.disabled = disabled;
978        cx.notify();
979    }
980
981    /// Set with read-only mode.
982    ///
983    /// Unlike [`Self::disabled`], a read-only input keeps the normal appearance,
984    /// focus, cursor, selection and copy behavior, it only rejects any change
985    /// of the text made by the user.
986    ///
987    /// See also: [`Self::set_readonly`].
988    #[allow(unused)]
989    pub(crate) fn readonly(mut self, readonly: bool) -> Self {
990        self.readonly = readonly;
991        self
992    }
993
994    pub fn set_readonly(&mut self, readonly: bool, cx: &mut Context<Self>) {
995        if self.readonly == readonly {
996            return;
997        }
998
999        self.readonly = readonly;
1000        if readonly {
1001            self.search_session.replace_mode = false;
1002        }
1003        cx.notify();
1004    }
1005
1006    /// Returns true if the user is allowed to change the text.
1007    ///
1008    /// This is false when the input is `disabled` or `readonly`, the programmatic
1009    /// APIs (e.g.: [`Self::set_value`], [`Self::insert`]) are not limited by this.
1010    pub fn is_editable(&self) -> bool {
1011        !self.disabled && !self.readonly
1012    }
1013
1014    /// Set true to clear the input by pressing Escape key.
1015    pub fn clean_on_escape(mut self) -> Self {
1016        self.clean_on_escape = true;
1017        self
1018    }
1019
1020    pub fn set_clean_on_escape(&mut self, clean: bool) {
1021        self.clean_on_escape = clean;
1022    }
1023
1024    /// Set true to treat `Enter` as a submit action in multi-line mode,
1025    /// while `Shift+Enter` inserts a newline.
1026    ///
1027    /// Default is `false` (both `Enter` and `Shift+Enter` insert a newline).
1028    #[doc(hidden)]
1029    pub fn submit_on_enter(mut self, submit: bool) -> Self {
1030        self.submit_on_enter = submit;
1031        self
1032    }
1033
1034    pub fn set_submit_on_enter(&mut self, submit: bool, cx: &mut Context<Self>) {
1035        self.submit_on_enter = submit;
1036        cx.notify();
1037    }
1038
1039    /// Set whether to show whitespace characters.
1040    #[doc(hidden)]
1041    pub fn show_whitespaces(mut self, show: bool) -> Self {
1042        self.show_whitespaces = show;
1043        self
1044    }
1045
1046    /// Update whether to show whitespace characters.
1047    pub fn set_show_whitespaces(&mut self, show: bool, _: &mut Window, cx: &mut Context<Self>) {
1048        self.show_whitespaces = show;
1049        cx.notify();
1050    }
1051
1052    /// Empty rows reserved below the last line of content ("scroll
1053    /// beyond last line"), code-editor mode only. Mirrors VSCode's
1054    /// `editor.scrollBeyondLastLine` / Zed's `scroll_beyond_last_line`.
1055    ///
1056    /// - `None` (default): half the viewport, floored at
1057    ///   [`BOTTOM_MARGIN_ROWS`] line-heights.
1058    /// - `Some(0)`: no trailing space; the cursor sits flush with the
1059    ///   last row at scroll-max.
1060    /// - `Some(n)`: exactly `n` rows.
1061    pub fn scroll_beyond_last_line(mut self, rows: Option<usize>) -> Self {
1062        self.scroll_beyond_last_line = rows;
1063        self
1064    }
1065
1066    /// Update [`Self::scroll_beyond_last_line`] after construction.
1067    pub fn set_scroll_beyond_last_line(
1068        &mut self,
1069        rows: Option<usize>,
1070        _: &mut Window,
1071        cx: &mut Context<Self>,
1072    ) {
1073        if self.scroll_beyond_last_line == rows {
1074            return;
1075        }
1076        self.scroll_beyond_last_line = rows;
1077        cx.notify();
1078    }
1079
1080    /// Minimum number of lines the cursor is kept clear of the viewport's
1081    /// top/bottom edge before auto-scroll engages. Mirrors VSCode's
1082    /// `editor.cursorSurroundingLines` / Zed's `vertical_scroll_margin`.
1083    /// Orthogonal to [`Self::scroll_beyond_last_line`], which sizes the
1084    /// empty region; this controls the cursor's resting distance from the
1085    /// edge.
1086    ///
1087    /// - `None` (default): [`BOTTOM_MARGIN_ROWS`] lines, falling back to
1088    ///   one line on small viewports.
1089    /// - `Some(n)`: exactly `n` lines, clamped to half the viewport.
1090    pub fn cursor_surrounding_lines(mut self, lines: Option<usize>) -> Self {
1091        self.cursor_surrounding_lines = lines;
1092        self
1093    }
1094
1095    /// Update [`Self::cursor_surrounding_lines`] after construction.
1096    pub fn set_cursor_surrounding_lines(
1097        &mut self,
1098        lines: Option<usize>,
1099        _: &mut Window,
1100        cx: &mut Context<Self>,
1101    ) {
1102        if self.cursor_surrounding_lines == lines {
1103            return;
1104        }
1105        self.cursor_surrounding_lines = lines;
1106        cx.notify();
1107    }
1108
1109    /// Set the default value of the input field.
1110    pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {
1111        let text: SharedString = value.into();
1112        self.text = Rope::from(self.normalize_input(&text).as_ref());
1113        if let Some(diagnostics) = self.mode.diagnostics_mut() {
1114            diagnostics.reset(&self.text)
1115        }
1116        // Note: We can't call display_map.set_text here because it needs cx.
1117        // The text will be set during prepare_if_need in element.rs
1118        self._pending_update = true;
1119        self
1120    }
1121
1122    /// Return the value of the input field as an owned string.
1123    ///
1124    /// The string is materialized on each call. See [`Self::text`] for the
1125    /// [`Rope`] the state owns, which is borrowed and costs nothing to read.
1126    pub fn value(&self) -> SharedString {
1127        SharedString::new(self.text.to_string())
1128    }
1129
1130    /// Return the portion of the value within the input field that
1131    /// is selected by the user, as an owned string.
1132    ///
1133    /// The string is materialized on each call. See [`Self::selected_text`]
1134    /// for the same selection borrowed out of the [`Rope`] the state owns.
1135    pub fn selected_value(&self) -> SharedString {
1136        SharedString::new(self.selected_text().to_string())
1137    }
1138
1139    /// Return the value without mask.
1140    pub fn unmask_value(&self) -> SharedString {
1141        self.mask_pattern.unmask(&self.text.to_string()).into()
1142    }
1143
1144    /// Kept so existing render paths keep compiling.
1145    ///
1146    /// Configuration used to be collected by a facade and applied here; the
1147    /// state now configures itself, so this does nothing and can be deleted at
1148    /// the call site.
1149    #[doc(hidden)]
1150    pub fn prepare(&mut self, _: &mut Window, _: &mut Context<Self>) {}
1151
1152    /// Return the text [`Rope`] of the input field.
1153    ///
1154    /// Borrowed from the state, so reading even a large document copies
1155    /// nothing. See [`Self::value`] when an owned string is wanted.
1156    pub fn text(&self) -> &Rope {
1157        &self.text
1158    }
1159
1160    /// Return the (0-based) [`Position`] of the cursor.
1161    pub fn cursor_position(&self) -> Position {
1162        let offset = self.cursor();
1163        self.text.offset_to_position(offset)
1164    }
1165
1166    /// Set (0-based) [`Position`] of the cursor.
1167    ///
1168    /// This will move the cursor to the specified line and column, and update the selection range.
1169    pub fn set_cursor_position(
1170        &mut self,
1171        position: impl Into<Position>,
1172        window: &mut Window,
1173        cx: &mut Context<Self>,
1174    ) {
1175        let position: Position = position.into();
1176        let offset = self.text.position_to_offset(&position);
1177
1178        self.move_to(offset, None, cx);
1179        self.update_preferred_column();
1180        self.focus(window, cx);
1181    }
1182
1183    /// Focus the input field.
1184    pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1185        self.focus_handle.focus(window, cx);
1186        self.blink_cursor.update(cx, |cursor, cx| {
1187            cursor.start(cx);
1188        });
1189    }
1190
1191    /// Refresh the input, so the next render re-runs syntax highlighting and
1192    /// the LSP providers, not just a redraw.
1193    ///
1194    /// Assigning the `lsp` providers (or other render-affecting state) at
1195    /// runtime does not take effect until the text next changes. Call this
1196    /// afterwards to force the refresh on the next render.
1197    ///
1198    /// ```ignore
1199    /// input.update(cx, |state, cx| {
1200    ///     state.extras.lsp.hover_provider = Some(provider);
1201    ///     state.refresh(cx);
1202    /// });
1203    /// ```
1204    pub fn refresh(&mut self, cx: &mut Context<Self>) {
1205        self._pending_update = true;
1206        cx.notify();
1207    }
1208
1209    pub(super) fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
1210        self.undo_manager.break_transaction_coalescing();
1211        self.select_to(self.previous_boundary(self.cursor()), cx);
1212    }
1213
1214    pub(super) fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
1215        self.undo_manager.break_transaction_coalescing();
1216        self.select_to(self.next_boundary(self.cursor()), cx);
1217    }
1218
1219    pub(super) fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
1220        if self.is_single_line() {
1221            return;
1222        }
1223        self.undo_manager.break_transaction_coalescing();
1224        let offset = self.start_of_line().saturating_sub(1);
1225        self.select_to(self.previous_boundary(offset), cx);
1226    }
1227
1228    pub(super) fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
1229        if self.is_single_line() {
1230            return;
1231        }
1232        self.undo_manager.break_transaction_coalescing();
1233        let offset = (self.end_of_line() + 1).min(self.text.len());
1234        self.select_to(self.next_boundary(offset), cx);
1235    }
1236
1237    pub(super) fn on_action_select_all(
1238        &mut self,
1239        _: &SelectAll,
1240        window: &mut Window,
1241        cx: &mut Context<Self>,
1242    ) {
1243        self.select_all(window, cx);
1244    }
1245
1246    pub(super) fn select_to_start(
1247        &mut self,
1248        _: &SelectToStart,
1249        _: &mut Window,
1250        cx: &mut Context<Self>,
1251    ) {
1252        self.undo_manager.break_transaction_coalescing();
1253        self.select_to(0, cx);
1254    }
1255
1256    pub(super) fn select_to_end(
1257        &mut self,
1258        _: &SelectToEnd,
1259        _: &mut Window,
1260        cx: &mut Context<Self>,
1261    ) {
1262        self.undo_manager.break_transaction_coalescing();
1263        let end = self.text.len();
1264        self.select_to(end, cx);
1265    }
1266
1267    pub(super) fn select_to_start_of_line(
1268        &mut self,
1269        _: &SelectToStartOfLine,
1270        _: &mut Window,
1271        cx: &mut Context<Self>,
1272    ) {
1273        self.undo_manager.break_transaction_coalescing();
1274        let offset = self.start_of_line();
1275        self.select_to(offset, cx);
1276    }
1277
1278    pub(super) fn select_to_end_of_line(
1279        &mut self,
1280        _: &SelectToEndOfLine,
1281        _: &mut Window,
1282        cx: &mut Context<Self>,
1283    ) {
1284        self.undo_manager.break_transaction_coalescing();
1285        let offset = self.end_of_line();
1286        // Mirrors MoveEnd: the caret belongs at the end of the visual row it is on.
1287        self.select_to_with_affinity(offset, true, cx);
1288    }
1289
1290    pub(super) fn select_to_previous_word(
1291        &mut self,
1292        _: &SelectToPreviousWordStart,
1293        _: &mut Window,
1294        cx: &mut Context<Self>,
1295    ) {
1296        self.undo_manager.break_transaction_coalescing();
1297        let offset = self.previous_start_of_word();
1298        self.select_to(offset, cx);
1299    }
1300
1301    pub(super) fn select_to_next_word(
1302        &mut self,
1303        _: &SelectToNextWordEnd,
1304        _: &mut Window,
1305        cx: &mut Context<Self>,
1306    ) {
1307        self.undo_manager.break_transaction_coalescing();
1308        let offset = self.next_end_of_word();
1309        self.select_to(offset, cx);
1310    }
1311
1312    /// Return the start offset of the previous word.
1313    pub(super) fn previous_start_of_word(&mut self) -> usize {
1314        if self.masked {
1315            // The mask replaces every character, so the displayed text has no
1316            // word boundaries to move or delete by. Collapse the word to the
1317            // whole text.
1318            return 0;
1319        }
1320
1321        let offset = self.selected_range.start;
1322        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1323        // FIXME: Avoid to_string
1324        let left_part = self.text.slice(0..offset).to_string();
1325
1326        UnicodeSegmentation::split_word_bound_indices(left_part.as_str())
1327            .rfind(|(_, s)| !s.trim_start().is_empty())
1328            .map(|(i, _)| i)
1329            .unwrap_or(0)
1330    }
1331
1332    /// Return the next end offset of the next word.
1333    pub(super) fn next_end_of_word(&mut self) -> usize {
1334        if self.masked {
1335            // See `previous_start_of_word`.
1336            return self.text.len();
1337        }
1338
1339        let offset = self.cursor();
1340        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1341        let right_part = self.text.slice(offset..self.text.len()).to_string();
1342
1343        UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
1344            .find(|(_, s)| !s.trim_start().is_empty())
1345            .map(|(i, s)| offset + i + s.len())
1346            .unwrap_or(self.text.len())
1347    }
1348
1349    /// Get start of line byte offset of cursor.
1350    ///
1351    /// When soft wrap is active, first press goes to visual line start,
1352    /// second press (already at visual start) goes to logical line start.
1353    pub(super) fn start_of_line(&self) -> usize {
1354        if self.is_single_line() {
1355            return 0;
1356        }
1357
1358        let row = self.text.offset_to_point(self.cursor()).row;
1359        let logical_start = self.text.line_start_offset(row);
1360
1361        if self.soft_wrap && self.is_code_editor() {
1362            let wrap_point = self.display_map.offset_to_wrap_display_point_with_affinity(
1363                self.cursor(),
1364                self.cursor_line_end_affinity,
1365            );
1366            if let Some(line) = self.display_map.line(row)
1367                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1368            {
1369                let visual_start = logical_start + range.start;
1370                if self.cursor() != visual_start {
1371                    return visual_start;
1372                }
1373            }
1374        }
1375
1376        logical_start
1377    }
1378
1379    /// Get end of line byte offset of cursor.
1380    ///
1381    /// When soft wrap is active, first press goes to visual line end,
1382    /// second press (already at visual end) goes to logical line end.
1383    pub(super) fn end_of_line(&self) -> usize {
1384        if self.is_single_line() {
1385            return self.text.len();
1386        }
1387
1388        let row = self.text.offset_to_point(self.cursor()).row;
1389        let logical_start = self.text.line_start_offset(row);
1390        let logical_end = self.text.line_end_offset(row);
1391
1392        if self.soft_wrap && self.is_code_editor() {
1393            // Use the row the caret is drawn on: at a wrap boundary the raw offset would name
1394            // the next row, and a second End press would keep walking down instead of falling
1395            // through to the logical line end.
1396            let wrap_point = self.display_map.offset_to_wrap_display_point_with_affinity(
1397                self.cursor(),
1398                self.cursor_line_end_affinity,
1399            );
1400            if let Some(line) = self.display_map.line(row)
1401                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1402            {
1403                let visual_end = logical_start + range.end;
1404                if self.cursor() != visual_end {
1405                    return visual_end;
1406                }
1407            }
1408        }
1409
1410        logical_end
1411    }
1412
1413    /// Get start line of selection start or end (The min value).
1414    ///
1415    /// This is means is always get the first line of selection.
1416    pub(super) fn start_of_line_of_selection(
1417        &mut self,
1418        window: &mut Window,
1419        cx: &mut Context<Self>,
1420    ) -> usize {
1421        if self.is_single_line() {
1422            return 0;
1423        }
1424
1425        let mut offset =
1426            self.previous_boundary(self.selected_range.start.min(self.selected_range.end));
1427        if self.text.char_at(offset) == Some('\r') {
1428            offset += 1;
1429        }
1430
1431        let line = self
1432            .text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx)
1433            .unwrap_or_default()
1434            .rfind('\n')
1435            .map(|i| i + 1)
1436            .unwrap_or(0);
1437        line
1438    }
1439
1440    /// Get indent string of next line.
1441    ///
1442    /// To get current and next line indent, to return more depth one.
1443    pub(super) fn indent_of_next_line(&mut self) -> String {
1444        if self.is_single_line() {
1445            return "".into();
1446        }
1447
1448        let mut current_indent = String::new();
1449        let mut next_indent = String::new();
1450        let current_line_start_pos = self.start_of_line();
1451        let next_line_start_pos = self.end_of_line();
1452        for c in self.text.slice(current_line_start_pos..).chars() {
1453            if !c.is_whitespace() {
1454                break;
1455            }
1456            if c == '\n' || c == '\r' {
1457                break;
1458            }
1459            current_indent.push(c);
1460        }
1461
1462        for c in self.text.slice(next_line_start_pos..).chars() {
1463            if !c.is_whitespace() {
1464                break;
1465            }
1466            if c == '\n' || c == '\r' {
1467                break;
1468            }
1469            next_indent.push(c);
1470        }
1471
1472        if next_indent.len() > current_indent.len() {
1473            return next_indent;
1474        } else {
1475            return current_indent;
1476        }
1477    }
1478
1479    pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
1480        let intent = if self.selected_range.is_empty() {
1481            self.select_to(self.previous_boundary(self.cursor()), cx);
1482            EditIntent::Backspace
1483        } else {
1484            EditIntent::Atomic
1485        };
1486        self.undo_manager.pending_intent = Some(intent);
1487        self.replace_text_in_range(None, "", window, cx);
1488        self.pause_blink_cursor(cx);
1489    }
1490
1491    pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
1492        let intent = if self.selected_range.is_empty() {
1493            self.select_to(self.next_boundary(self.cursor()), cx);
1494            EditIntent::DeleteForward
1495        } else {
1496            EditIntent::Atomic
1497        };
1498        self.undo_manager.pending_intent = Some(intent);
1499        self.replace_text_in_range(None, "", window, cx);
1500        self.pause_blink_cursor(cx);
1501    }
1502
1503    pub(super) fn delete_to_beginning_of_line(
1504        &mut self,
1505        _: &DeleteToBeginningOfLine,
1506        window: &mut Window,
1507        cx: &mut Context<Self>,
1508    ) {
1509        if !self.selected_range.is_empty() {
1510            self.replace_text_in_range(None, "", window, cx);
1511            self.pause_blink_cursor(cx);
1512            return;
1513        }
1514
1515        let mut offset = self.start_of_line();
1516        if offset == self.cursor() {
1517            offset = offset.saturating_sub(1);
1518        }
1519        self.replace_text_in_range_silent(
1520            Some(self.range_to_utf16(&(offset..self.cursor()))),
1521            "",
1522            window,
1523            cx,
1524        );
1525        self.pause_blink_cursor(cx);
1526    }
1527
1528    pub(super) fn delete_to_end_of_line(
1529        &mut self,
1530        _: &DeleteToEndOfLine,
1531        window: &mut Window,
1532        cx: &mut Context<Self>,
1533    ) {
1534        if !self.selected_range.is_empty() {
1535            self.replace_text_in_range(None, "", window, cx);
1536            self.pause_blink_cursor(cx);
1537            return;
1538        }
1539
1540        let mut offset = self.end_of_line();
1541        if offset == self.cursor() {
1542            offset = (offset + 1).clamp(0, self.text.len());
1543        }
1544        self.replace_text_in_range_silent(
1545            Some(self.range_to_utf16(&(self.cursor()..offset))),
1546            "",
1547            window,
1548            cx,
1549        );
1550        self.pause_blink_cursor(cx);
1551    }
1552
1553    pub(super) fn delete_previous_word(
1554        &mut self,
1555        _: &DeleteToPreviousWordStart,
1556        window: &mut Window,
1557        cx: &mut Context<Self>,
1558    ) {
1559        if !self.selected_range.is_empty() {
1560            self.replace_text_in_range(None, "", window, cx);
1561            self.pause_blink_cursor(cx);
1562            return;
1563        }
1564
1565        let offset = self.previous_start_of_word();
1566        self.replace_text_in_range_silent(
1567            Some(self.range_to_utf16(&(offset..self.cursor()))),
1568            "",
1569            window,
1570            cx,
1571        );
1572        self.pause_blink_cursor(cx);
1573    }
1574
1575    pub(super) fn delete_next_word(
1576        &mut self,
1577        _: &DeleteToNextWordEnd,
1578        window: &mut Window,
1579        cx: &mut Context<Self>,
1580    ) {
1581        if !self.selected_range.is_empty() {
1582            self.replace_text_in_range(None, "", window, cx);
1583            self.pause_blink_cursor(cx);
1584            return;
1585        }
1586
1587        let offset = self.next_end_of_word();
1588        self.replace_text_in_range_silent(
1589            Some(self.range_to_utf16(&(self.cursor()..offset))),
1590            "",
1591            window,
1592            cx,
1593        );
1594        self.pause_blink_cursor(cx);
1595    }
1596
1597    pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {
1598        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
1599            return;
1600        }
1601
1602        // Clear inline completion on enter (user chose not to accept it)
1603        if M::has_inline_completion(self) {
1604            M::clear_inline_completion(self, cx);
1605        }
1606
1607        // In multi-line mode with `submit_on_enter` enabled, a plain `Enter`
1608        // (without Shift) is treated as submit: propagate the action and emit
1609        // PressEnter without inserting a newline. `Shift+Enter` still inserts
1610        // a newline.
1611        let insert_newline = self.is_multi_line() && (!self.submit_on_enter || action.shift);
1612
1613        if insert_newline {
1614            // Get current line indent
1615            let indent = if self.is_code_editor() {
1616                self.indent_of_next_line()
1617            } else {
1618                "".to_string()
1619            };
1620
1621            // Add newline and indent
1622            let new_line_text = format!("\n{}", indent);
1623            self.replace_text_in_range_silent(None, &new_line_text, window, cx);
1624            self.pause_blink_cursor(cx);
1625        } else {
1626            // Single line input or submit-on-enter: just emit the event
1627            // (e.g.: in a dialog to confirm, or a chat textarea to send).
1628            self.undo_manager.break_transaction_coalescing();
1629            cx.propagate();
1630        }
1631
1632        cx.emit(InputEvent::PressEnter {
1633            secondary: action.secondary,
1634            shift: action.shift,
1635        });
1636    }
1637
1638    pub fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1639        self.replace_text("", window, cx);
1640        self.selected_range = (0..0).into();
1641        self.scroll_to(0, None, cx);
1642    }
1643
1644    pub(super) fn escape(&mut self, action: &Escape, window: &mut Window, cx: &mut Context<Self>) {
1645        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
1646            return;
1647        }
1648
1649        // Clear inline completion on escape
1650        if M::has_inline_completion(self) {
1651            M::clear_inline_completion(self, cx);
1652            return; // Consume the escape, don't propagate
1653        }
1654
1655        if self.ime_marked_range.is_some() {
1656            self.unmark_text(window, cx);
1657        }
1658
1659        if self.clean_on_escape {
1660            return self.clean(window, cx);
1661        }
1662
1663        cx.propagate();
1664    }
1665
1666    /// Show the right-click context menu as a native OS menu.
1667    pub(crate) fn handle_right_click_menu(
1668        &mut self,
1669        position: Point<Pixels>,
1670        offset: usize,
1671        window: &mut Window,
1672        cx: &mut Context<Self>,
1673    ) {
1674        if self.disabled {
1675            return;
1676        }
1677        if crate::GlobalState::is_in_deferred_context(cx) {
1678            return;
1679        }
1680
1681        if !self.selected_range.contains(offset) {
1682            self.move_to(offset, None, cx);
1683        }
1684
1685        if self.is_code_editor() {
1686            M::on_hover_definition(self, offset, window, cx);
1687        }
1688
1689        if let Some(handler) = self.context_menu_handler.clone() {
1690            let capabilities = self.context_menu_capabilities();
1691            cx.defer_in(window, move |_, window, cx| {
1692                handler(NativeMenu::new(), capabilities, position, window, cx);
1693            });
1694        }
1695    }
1696
1697    pub(super) fn on_mouse_down(
1698        &mut self,
1699        event: &MouseDownEvent,
1700        window: &mut Window,
1701        cx: &mut Context<Self>,
1702    ) {
1703        self.undo_manager.break_transaction_coalescing();
1704        // Input has its own text selection; suppress the window-level text
1705        // selection (Root) so it does not start a drag from here.
1706        crate::global_state::GlobalState::suppress_text_selection(cx);
1707
1708        // Clear inline completion on any mouse interaction
1709        M::clear_inline_completion(self, cx);
1710
1711        // If there have IME marked range and is empty (Means pressed Esc to abort IME typing)
1712        // Clear the marked range.
1713        if let Some(ime_marked_range) = &self.ime_marked_range {
1714            if ime_marked_range.len() == 0 {
1715                self.ime_marked_range = None;
1716            }
1717        }
1718
1719        self.selecting = true;
1720        let (offset, line_end_affinity) = self.index_for_mouse_position(event.position);
1721
1722        if M::on_click(self, event, offset, window, cx) {
1723            return;
1724        }
1725
1726        // Triple click to select line
1727        if event.button == MouseButton::Left && event.click_count >= 3 {
1728            self.select_line(offset, window, cx);
1729            return;
1730        }
1731
1732        // Double click to select word
1733        if event.button == MouseButton::Left && event.click_count == 2 {
1734            self.select_word(offset, window, cx);
1735            return;
1736        }
1737
1738        // Show Mouse context menu
1739        if event.button == MouseButton::Right {
1740            if self.enable_context_menu {
1741                if !self.selected_range.contains(offset) {
1742                    self.move_to(offset, None, cx);
1743                }
1744                self.pending_context_menu = Some((event.position, offset));
1745            }
1746            return;
1747        }
1748
1749        if event.modifiers.shift {
1750            self.select_to_with_affinity(offset, line_end_affinity, cx);
1751        } else {
1752            self.move_to_with_affinity(offset, None, line_end_affinity, cx)
1753        }
1754    }
1755
1756    pub(super) fn on_mouse_up(
1757        &mut self,
1758        event: &MouseUpEvent,
1759        window: &mut Window,
1760        cx: &mut Context<Self>,
1761    ) {
1762        if event.button == MouseButton::Right {
1763            if let Some((position, offset)) = self.pending_context_menu.take() {
1764                self.handle_right_click_menu(position, offset, window, cx);
1765            }
1766        }
1767        if self.selected_range.is_empty() {
1768            self.selection_reversed = false;
1769        }
1770        self.selecting = false;
1771        self.selected_word_range = None;
1772        self.auto_scroll.stop();
1773    }
1774
1775    pub(super) fn on_mouse_move(
1776        &mut self,
1777        event: &MouseMoveEvent,
1778        window: &mut Window,
1779        cx: &mut Context<Self>,
1780    ) {
1781        // Check if mouse is within bounds
1782        let within_bounds = self
1783            .last_bounds
1784            .as_ref()
1785            .map(|bounds| bounds.contains(&event.position))
1786            .unwrap_or(false);
1787
1788        if !within_bounds {
1789            // Clear hover when mouse leaves the input
1790            M::clear_hover_state(self, cx);
1791            return;
1792        }
1793
1794        // Show diagnostic popover on mouse move
1795        let (offset, _) = self.index_for_mouse_position(event.position);
1796        M::on_mouse_move(self, offset, event, window, cx);
1797
1798        if self.is_code_editor() {
1799            if let Some(diagnostic) = self
1800                .mode
1801                .diagnostics()
1802                .and_then(|set| set.for_offset(offset))
1803            {
1804                self.diagnostic_popover = Some(Rc::new(diagnostic.clone()));
1805                cx.notify();
1806            } else {
1807                self.diagnostic_popover = None;
1808            }
1809        }
1810    }
1811
1812    pub(super) fn on_scroll_wheel(
1813        &mut self,
1814        event: &ScrollWheelEvent,
1815        window: &mut Window,
1816        cx: &mut Context<Self>,
1817    ) {
1818        let line_height = self
1819            .last_layout
1820            .as_ref()
1821            .map(|layout| layout.line_height)
1822            .unwrap_or(window.line_height());
1823        let delta = event.delta.pixel_delta(line_height);
1824
1825        let old_offset = self.scroll_handle.offset();
1826        self.update_scroll_offset(Some(old_offset + delta), cx);
1827
1828        // Only stop propagation if the offset actually changed
1829        if self.scroll_handle.offset() != old_offset {
1830            cx.stop_propagation();
1831        }
1832
1833        self.diagnostic_popover = None;
1834    }
1835
1836    pub(super) fn update_scroll_offset(
1837        &mut self,
1838        offset: Option<Point<Pixels>>,
1839        cx: &mut Context<Self>,
1840    ) {
1841        let mut offset = offset.unwrap_or(self.scroll_handle.offset());
1842        // In addition to left alignment, a cursor position will be reserved on the right side
1843        let safe_x_offset = if self.text_align == TextAlign::Left {
1844            px(0.)
1845        } else {
1846            -CURSOR_WIDTH
1847        };
1848
1849        let safe_y_range =
1850            (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
1851        let safe_x_range = (-self.scroll_size.width + self.input_bounds.size.width + safe_x_offset)
1852            .min(safe_x_offset)..px(0.);
1853
1854        offset.y = if self.is_single_line() {
1855            px(0.)
1856        } else {
1857            offset.y.clamp(safe_y_range.start, safe_y_range.end)
1858        };
1859        offset.x = offset.x.clamp(safe_x_range.start, safe_x_range.end);
1860        self.scroll_handle.set_offset(offset);
1861        cx.notify();
1862    }
1863
1864    /// Scroll to make the given offset visible.
1865    ///
1866    /// If `direction` is Some, will keep edges at the same side.
1867    pub(crate) fn scroll_to(
1868        &mut self,
1869        offset: usize,
1870        direction: Option<MoveDirection>,
1871        cx: &mut Context<Self>,
1872    ) {
1873        let Some(last_layout) = self.last_layout.as_ref() else {
1874            return;
1875        };
1876        let Some(bounds) = self.last_bounds.as_ref() else {
1877            return;
1878        };
1879
1880        let mut scroll_offset = self.scroll_handle.offset();
1881        let was_offset = scroll_offset;
1882        let line_height = last_layout.line_height;
1883
1884        let point = self.text.offset_to_point(offset);
1885
1886        let row = point.row;
1887
1888        // Calculate row offset by multiplying the number of lines before it with the line height
1889        let mut row_offset_y = line_height * self.display_map.buffer_line_to_display_row(row);
1890
1891        // For Right alignment use 0 margin: the cursor indicator is clamped inside bounds
1892        // in layout_cursor, so shifting the text here would cause a first-click visual jump.
1893        let safety_margin = match last_layout.text_align {
1894            TextAlign::Left => RIGHT_MARGIN,
1895            TextAlign::Right => px(0.),
1896            TextAlign::Center => CURSOR_WIDTH,
1897        };
1898        if let Some(line) = last_layout
1899            .lines
1900            .get(row.saturating_sub(last_layout.visible_range.start))
1901        {
1902            // Check to scroll horizontally and soft wrap lines
1903            if let Some(pos) = line.position_for_index(point.column, last_layout, false) {
1904                let bounds_width = bounds.size.width - last_layout.line_number_width;
1905                let col_offset_x = pos.x;
1906                row_offset_y += pos.y;
1907                if col_offset_x - safety_margin < -scroll_offset.x {
1908                    // If the position is out of the visible area, scroll to make it visible
1909                    scroll_offset.x = -col_offset_x + safety_margin;
1910                } else if col_offset_x + safety_margin > -scroll_offset.x + bounds_width {
1911                    scroll_offset.x = -(col_offset_x - bounds_width + safety_margin);
1912                }
1913            }
1914        }
1915
1916        // Scroll the row into view. Use the same edge clearance helper as
1917        // `TextElement::layout_cursor` so both scroll-into-view paths agree
1918        // (a mismatch flickered on `Down` at end-of-buffer with a small
1919        // `cursor_surrounding_lines` override).
1920        let edge_height = if direction.is_some() && self.is_code_editor() {
1921            super::element::cursor_surrounding_padding(
1922                self.mode.is_auto_grow(),
1923                self.cursor_surrounding_lines,
1924                last_layout.visible_range.len(),
1925                line_height,
1926            )
1927        } else {
1928            line_height
1929        };
1930        if row_offset_y - edge_height + line_height < -scroll_offset.y {
1931            // Scroll up
1932            scroll_offset.y = -row_offset_y + edge_height - line_height;
1933        } else if row_offset_y + edge_height > -scroll_offset.y + bounds.size.height {
1934            // Scroll down
1935            scroll_offset.y = -(row_offset_y - bounds.size.height + edge_height);
1936        }
1937
1938        // Avoid necessary scroll, when it was already in the correct position.
1939        if direction == Some(MoveDirection::Up) {
1940            scroll_offset.y = scroll_offset.y.max(was_offset.y);
1941        } else if direction == Some(MoveDirection::Down) {
1942            scroll_offset.y = scroll_offset.y.min(was_offset.y);
1943        }
1944
1945        // Clamp the deferred target into the same safe range that
1946        // `update_scroll_offset` enforces on persist, so paint never shows an
1947        // over-scrolled frame before the post-paint clamp pulls it back.
1948        let safe_y_min = (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.));
1949        scroll_offset.x = scroll_offset.x.min(px(0.));
1950        scroll_offset.y = scroll_offset.y.clamp(safe_y_min, px(0.));
1951        self.deferred_scroll_offset = Some(scroll_offset);
1952        cx.notify();
1953    }
1954
1955    pub(super) fn show_character_palette(
1956        &mut self,
1957        _: &ShowCharacterPalette,
1958        window: &mut Window,
1959        _: &mut Context<Self>,
1960    ) {
1961        window.show_character_palette();
1962    }
1963
1964    pub(super) fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
1965        if !self.is_copyable() {
1966            return;
1967        }
1968
1969        let selected_text = self.text.slice(self.selected_range).to_string();
1970        cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
1971    }
1972
1973    pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
1974        if !self.is_copyable() {
1975            return;
1976        }
1977
1978        let selected_text = self.text.slice(self.selected_range).to_string();
1979        cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
1980
1981        self.undo_manager.pending_intent = Some(EditIntent::Atomic);
1982        self.replace_text_in_range_silent(None, "", window, cx);
1983    }
1984
1985    pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
1986        if let Some(clipboard) = cx.read_from_clipboard() {
1987            let new_text = clipboard.text().unwrap_or_default();
1988            self.undo_manager.pending_intent = Some(EditIntent::Atomic);
1989            self.replace_text_in_range_silent(None, &new_text, window, cx);
1990            self.scroll_to(self.cursor(), None, cx);
1991        }
1992    }
1993
1994    fn push_history(
1995        &mut self,
1996        text: &Rope,
1997        range: &Range<usize>,
1998        new_text: &str,
1999        requested_intent: Option<EditIntent>,
2000        selection_before: Selection,
2001        selection_after: Option<Selection>,
2002    ) {
2003        if self.undo_manager.is_ignoring() {
2004            return;
2005        }
2006
2007        let range =
2008            text.clip_offset(range.start, Bias::Left)..text.clip_offset(range.end, Bias::Right);
2009        let old_text = text.slice(range.clone()).to_string();
2010        let new_range = range.start..range.start + new_text.len();
2011
2012        let intent = requested_intent.unwrap_or_else(|| {
2013            if range.is_empty()
2014                && old_text.is_empty()
2015                && !new_text.is_empty()
2016                && !new_text.contains(['\n', '\r'])
2017            {
2018                EditIntent::Typing
2019            } else {
2020                EditIntent::Atomic
2021            }
2022        });
2023
2024        let selection_before = match intent {
2025            EditIntent::Backspace => Selection::new(range.end, range.end),
2026            EditIntent::DeleteForward => Selection::new(range.start, range.start),
2027            EditIntent::Typing | EditIntent::Atomic => selection_before,
2028        };
2029        let selection_after =
2030            selection_after.unwrap_or_else(|| Selection::new(new_range.end, new_range.end));
2031
2032        self.undo_manager.record_transaction(
2033            Change::new(
2034                range,
2035                &old_text,
2036                new_range,
2037                new_text,
2038                selection_before,
2039                selection_after,
2040            ),
2041            intent,
2042        );
2043    }
2044
2045    pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
2046        self.undo_manager.set_ignoring(true);
2047        if let Some(changes) = self.undo_manager.undo() {
2048            let selection = changes.last().unwrap().selection_before;
2049            for change in &changes {
2050                let range_utf16 = self.range_to_utf16(&change.new_range.into());
2051                self.replace_text_in_range_silent(Some(range_utf16), &change.old_text, window, cx);
2052            }
2053            self.selected_range = selection;
2054        }
2055        self.undo_manager.set_ignoring(false);
2056    }
2057
2058    pub(super) fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
2059        self.undo_manager.set_ignoring(true);
2060        if let Some(changes) = self.undo_manager.redo() {
2061            let selection = changes.last().unwrap().selection_after;
2062            for change in &changes {
2063                let range_utf16 = self.range_to_utf16(&change.old_range.into());
2064                self.replace_text_in_range_silent(Some(range_utf16), &change.new_text, window, cx);
2065            }
2066            self.selected_range = selection;
2067        }
2068        self.undo_manager.set_ignoring(false);
2069    }
2070
2071    /// Get byte offset of the cursor.
2072    ///
2073    /// The offset is the UTF-8 offset.
2074    pub fn cursor(&self) -> usize {
2075        if let Some(ime_marked_range) = &self.ime_marked_range {
2076            return ime_marked_range.end;
2077        }
2078
2079        if self.selection_reversed {
2080            self.selected_range.start
2081        } else {
2082            self.selected_range.end
2083        }
2084    }
2085
2086    /// Visible row range in the last laid-out viewport, `None` before first layout.
2087    pub fn visible_row_range(&self) -> Option<std::ops::Range<usize>> {
2088        self.last_layout.as_ref().map(|l| l.visible_range.clone())
2089    }
2090
2091    /// Current scroll offset of the editor viewport.
2092    pub fn scroll_offset(&self) -> gpui::Point<gpui::Pixels> {
2093        self.scroll_handle.offset()
2094    }
2095
2096    /// Set scroll offset of the editor viewport.
2097    ///
2098    /// The offset will be clamped to the valid range, and applied after the next layout.
2099    pub fn set_scroll_offset(&mut self, offset: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
2100        self.deferred_scroll_offset = Some(offset);
2101        cx.notify();
2102    }
2103
2104    /// Laid-out line height; `None` before first layout.
2105    pub fn line_height(&self) -> Option<gpui::Pixels> {
2106        self.last_layout.as_ref().map(|l| l.line_height)
2107    }
2108
2109    /// Returns the current selection as a byte range into the text.
2110    ///
2111    /// The range is empty (`start == end`) when no text is selected; in
2112    /// that case the offset equals `cursor()`. Byte offsets are measured
2113    /// in the underlying rope's byte units.
2114    pub fn selected_range(&self) -> std::ops::Range<usize> {
2115        self.selected_range.into()
2116    }
2117
2118    pub fn select_all(&mut self, _: &mut Window, cx: &mut Context<Self>) {
2119        self.undo_manager.break_transaction_coalescing();
2120        self.selected_range = (0..self.text.len()).into();
2121        cx.notify();
2122    }
2123
2124    /// Set the selected range using UTF-8 byte offsets.
2125    ///
2126    /// Non-empty ranges expand to character boundaries. Empty ranges remain empty and are
2127    /// clipped to the preceding character boundary.
2128    pub fn set_selected_range(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
2129        let end_bias = if range.start == range.end {
2130            Bias::Left
2131        } else {
2132            Bias::Right
2133        };
2134        let start = self.text.clip_offset(range.start, Bias::Left);
2135        let end = self.text.clip_offset(range.end, end_bias);
2136
2137        self.move_to(start, None, cx);
2138        self.selection_reversed = false;
2139        self.selected_word_range = None;
2140        self.select_to(end, cx);
2141    }
2142
2143    /// Resolve a mouse position to a byte offset in the text.
2144    ///
2145    /// Also reports the caret's line-end affinity for that offset: `true` when the position
2146    /// landed on the wrap boundary of a non-final visual row, meaning the caret belongs at the
2147    /// end of that row rather than at the start of the next one. Callers that place or extend a
2148    /// selection must pass it on, or clicking past the last glyph of a wrapped row leaves a
2149    /// caret one row below the pointer.
2150    pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> (usize, bool) {
2151        // If the text is empty, always return 0
2152        if self.text.len() == 0 {
2153            return (0, false);
2154        }
2155
2156        let (Some(bounds), Some(last_layout)) =
2157            (self.last_bounds.as_ref(), self.last_layout.as_ref())
2158        else {
2159            return (0, false);
2160        };
2161
2162        let line_height = last_layout.line_height;
2163        let line_number_width = last_layout.line_number_width;
2164
2165        // TIP: About the IBeam cursor
2166        //
2167        // If cursor style is IBeam, the mouse mouse position is in the middle of the cursor (This is special in OS)
2168
2169        // The position is relative to the bounds of the text input
2170        //
2171        // bounds.origin:
2172        //
2173        // - included the input padding.
2174        // - included the scroll offset.
2175        let inner_position = position - bounds.origin - point(line_number_width, px(0.));
2176
2177        let mut y_offset = last_layout.visible_top;
2178
2179        // Traverse visible buffer lines (compact, no hidden entries)
2180        for (vi, (line_layout, _buffer_line)) in last_layout
2181            .lines
2182            .iter()
2183            .zip(last_layout.visible_buffer_lines.iter())
2184            .enumerate()
2185        {
2186            let line_start_offset = last_layout.visible_line_byte_offsets[vi];
2187
2188            // Calculate line origin for this display row
2189            let line_origin = point(px(0.), y_offset);
2190            let pos = inner_position - line_origin;
2191
2192            // Return offset by use closest_index_for_x if is single line mode.
2193            if self.is_single_line() {
2194                let local_index = line_layout.closest_index_for_x(pos.x, last_layout);
2195                // A single line never wraps, so there is no boundary to disambiguate.
2196                return (self.resolve_index(line_start_offset + local_index), false);
2197            }
2198
2199            // Check if mouse is in this line's bounds
2200            if let Some((local_index, line_end_affinity)) =
2201                line_layout.closest_index_for_position(pos, last_layout)
2202            {
2203                return (
2204                    self.resolve_index(line_start_offset + local_index),
2205                    line_end_affinity,
2206                );
2207            } else if pos.y < px(0.) {
2208                // Mouse is above this line, return start of this line
2209                return (self.resolve_index(line_start_offset), false);
2210            }
2211
2212            y_offset += line_layout.size(line_height).height;
2213        }
2214
2215        // Mouse is below all visible lines, return end of text
2216        (self.text.len(), false)
2217    }
2218
2219    /// Map a display byte index back to a text offset, undoing the mask expansion when the input
2220    /// is masked.
2221    fn resolve_index(&self, index: usize) -> usize {
2222        if self.masked {
2223            self.text.char_index_to_offset(index / MASK_CHAR.len_utf8())
2224        } else {
2225            index.min(self.text.len())
2226        }
2227    }
2228
2229    /// Returns a y offsetted point for the line origin.
2230    /// Select the text from the current cursor position to the given offset.
2231    ///
2232    /// The offset is the UTF-8 offset.
2233    ///
2234    /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
2235    pub(crate) fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
2236        self.select_to_with_affinity(offset, false, cx);
2237    }
2238
2239    /// Like [`Self::select_to`], but also carries the caret's line-end affinity.
2240    ///
2241    /// See [`Self::move_to_with_affinity`] for why the affinity travels with the offset. Note
2242    /// that plain [`Self::select_to`] clears the affinity: every offset it is given came from
2243    /// the text rather than from a visual position, so the caret has no reason to keep sticking
2244    /// to the end of a wrapped row.
2245    pub(crate) fn select_to_with_affinity(
2246        &mut self,
2247        offset: usize,
2248        line_end_affinity: bool,
2249        cx: &mut Context<Self>,
2250    ) {
2251        M::clear_inline_completion(self, cx);
2252
2253        self.cursor_line_end_affinity = line_end_affinity;
2254        let offset = offset.clamp(0, self.text.len());
2255        if self.selection_reversed {
2256            self.selected_range.start = offset
2257        } else {
2258            self.selected_range.end = offset
2259        };
2260
2261        if self.selected_range.end < self.selected_range.start {
2262            self.selection_reversed = !self.selection_reversed;
2263            self.selected_range = (self.selected_range.end..self.selected_range.start).into();
2264        }
2265
2266        // Ensure keep word selected range
2267        if let Some(word_range) = self.selected_word_range.as_ref() {
2268            if self.selected_range.start > word_range.start {
2269                self.selected_range.start = word_range.start;
2270            }
2271            if self.selected_range.end < word_range.end {
2272                self.selected_range.end = word_range.end;
2273            }
2274        }
2275        if self.selected_range.is_empty() {
2276            self.update_preferred_column();
2277        }
2278        cx.notify()
2279    }
2280
2281    /// Unselects the currently selected text.
2282    pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
2283        self.undo_manager.break_transaction_coalescing();
2284        let offset = self.cursor();
2285        self.selected_range = (offset..offset).into();
2286        cx.notify()
2287    }
2288
2289    #[inline]
2290    pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {
2291        self.text.offset_utf16_to_offset(offset)
2292    }
2293
2294    #[inline]
2295    pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {
2296        self.text.offset_to_offset_utf16(offset)
2297    }
2298
2299    #[inline]
2300    pub(crate) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
2301        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
2302    }
2303
2304    #[inline]
2305    pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
2306        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
2307    }
2308
2309    /// If offset falls on a hidden (folded) line, clamp backward to the end of
2310    /// the fold header line (last visible position before the fold).
2311    fn clamp_offset_to_visible_backward(&self, offset: usize) -> usize {
2312        let line = self.text.offset_to_point(offset).row;
2313        if self.display_map.is_buffer_line_hidden(line) {
2314            for fold in self.display_map.folded_ranges() {
2315                if line > fold.start_line && line <= fold.end_line {
2316                    return self.text.line_end_offset(fold.start_line);
2317                }
2318            }
2319        }
2320        offset
2321    }
2322
2323    /// If offset falls on a hidden (folded) line, clamp forward to the start of
2324    /// the fold end line (first visible position after the fold).
2325    fn clamp_offset_to_visible_forward(&self, offset: usize) -> usize {
2326        let line = self.text.offset_to_point(offset).row;
2327        if self.display_map.is_buffer_line_hidden(line) {
2328            for fold in self.display_map.folded_ranges() {
2329                if line > fold.start_line && line <= fold.end_line {
2330                    return self.text.line_start_offset(fold.end_line);
2331                }
2332            }
2333        }
2334        offset
2335    }
2336
2337    pub(super) fn previous_boundary(&self, offset: usize) -> usize {
2338        let mut offset = self.text.clip_offset(offset.saturating_sub(1), Bias::Left);
2339        if let Some(ch) = self.text.char_at(offset) {
2340            if ch == '\r' {
2341                offset -= 1;
2342            }
2343        }
2344
2345        self.clamp_offset_to_visible_backward(offset)
2346    }
2347
2348    pub(super) fn next_boundary(&self, offset: usize) -> usize {
2349        let mut offset = self.text.clip_offset(offset + 1, Bias::Right);
2350        if let Some(ch) = self.text.char_at(offset) {
2351            if ch == '\r' {
2352                offset += 1;
2353            }
2354        }
2355
2356        self.clamp_offset_to_visible_forward(offset)
2357    }
2358
2359    /// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
2360    pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
2361        (self.focus_handle.is_focused(window) || M::is_context_menu_open(self, cx))
2362            && !self.disabled
2363            && self.blink_cursor.read(cx).visible()
2364            && window.is_window_active()
2365    }
2366
2367    fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {
2368        self.blink_cursor.update(cx, |cursor, cx| {
2369            cursor.start(cx);
2370        });
2371        cx.emit(InputEvent::Focus);
2372    }
2373
2374    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2375        if M::is_context_menu_open(self, cx) {
2376            return;
2377        }
2378
2379        self.undo_manager.break_transaction_coalescing();
2380
2381        // NOTE: Do not cancel select, when blur.
2382        // Because maybe user want to copy the selected text by AppMenuBar (will take focus handle).
2383
2384        M::clear_hover_state(self, cx);
2385        self.diagnostic_popover = None;
2386        M::clear_inline_completion(self, cx);
2387        self.blink_cursor.update(cx, |cursor, cx| {
2388            cursor.stop(cx);
2389        });
2390        self.clamp_number_value(window, cx);
2391        cx.emit(InputEvent::Blur);
2392        cx.notify();
2393    }
2394
2395    /// Clamp the number value to the `min`/`max` range, used on blur.
2396    ///
2397    /// Out-of-range values are allowed while typing (e.g. `1` is an
2398    /// intermediate state of `15` when min is 10), and clamped on blur.
2399    fn clamp_number_value(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2400        if !self.is_single_line() {
2401            return;
2402        }
2403        if !matches!(self.mask_pattern, MaskPattern::Number { .. }) {
2404            return;
2405        }
2406        if self.number_min.is_none() && self.number_max.is_none() {
2407            return;
2408        }
2409
2410        let Ok(value) = self.unmask_value().parse::<f64>() else {
2411            return;
2412        };
2413
2414        let clamped = match (self.number_min, self.number_max) {
2415            (Some(min), _) if value < min => min,
2416            (_, Some(max)) if value > max => max,
2417            _ => return,
2418        };
2419
2420        // The clamped value must pass the `pattern`/`validate` check,
2421        // otherwise keep the value as is.
2422        let new_text = clamped.to_string();
2423        if !self.is_valid_input(&new_text, cx) {
2424            return;
2425        }
2426
2427        let range = self.range_to_utf16(&(0..self.text.len()));
2428        self.replace_text_in_range_silent(Some(range), &new_text, window, cx);
2429    }
2430
2431    pub(super) fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
2432        self.blink_cursor.update(cx, |cursor, cx| {
2433            cursor.pause(cx);
2434        });
2435    }
2436
2437    pub(super) fn on_key_down(&mut self, _: &KeyDownEvent, _: &mut Window, cx: &mut Context<Self>) {
2438        self.pause_blink_cursor(cx);
2439    }
2440
2441    pub(super) fn on_drag_move(
2442        &mut self,
2443        event: &MouseMoveEvent,
2444        window: &mut Window,
2445        cx: &mut Context<Self>,
2446    ) {
2447        if self.text.len() == 0 {
2448            return;
2449        }
2450
2451        if self.last_layout.is_none() {
2452            return;
2453        }
2454
2455        if !self.focus_handle.is_focused(window) {
2456            return;
2457        }
2458
2459        if !self.selecting {
2460            return;
2461        }
2462
2463        self.auto_scroll.last_drag_position = Some(event.position);
2464        let (offset, line_end_affinity) = self.index_for_mouse_position(event.position);
2465        self.select_to_with_affinity(offset, line_end_affinity, cx);
2466
2467        if !self.is_single_line() {
2468            let delta = AutoScroll::compute_delta(event.position.y, self.input_bounds);
2469            // Input's ScrollHandle uses negative-y-is-down; negate the positive-towards-bottom delta.
2470            let scroll_delta = delta.map(|d| -d);
2471            self.auto_scroll.set(scroll_delta, cx, |delta, state, cx| {
2472                let current = state.scroll_handle.offset();
2473                state.update_scroll_offset(Some(point(current.x, current.y + delta)), cx);
2474                if let Some(pos) = state.auto_scroll.last_drag_position {
2475                    let (offset, line_end_affinity) = state.index_for_mouse_position(pos);
2476                    state.select_to_with_affinity(offset, line_end_affinity, cx);
2477                }
2478            });
2479        }
2480    }
2481
2482    /// Normalize the inserted text before applying it to the input.
2483    ///
2484    /// For number inputs (with [`MaskPattern::Number`]), this converts
2485    /// full-width number characters into their ASCII equivalents,
2486    /// e.g. `12。5` -> `12.5`.
2487    fn normalize_input<'a>(&self, new_text: &'a str) -> Cow<'a, str> {
2488        let normalized = if matches!(self.mask_pattern, MaskPattern::Number { .. }) {
2489            normalize_number_input(new_text)
2490        } else {
2491            Cow::Borrowed(new_text)
2492        };
2493
2494        if self.is_single_line() && normalized.contains(['\n', '\r']) {
2495            Cow::Owned(normalized.replace(['\n', '\r'], ""))
2496        } else {
2497            normalized
2498        }
2499    }
2500
2501    pub(crate) fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {
2502        if new_text.is_empty() {
2503            return true;
2504        }
2505
2506        if let Some(validate) = &self.validate {
2507            if !validate(new_text, cx) {
2508                return false;
2509            }
2510        }
2511
2512        if !self.mask_pattern.is_valid(new_text) {
2513            return false;
2514        }
2515
2516        let Some(pattern) = &self.pattern else {
2517            return true;
2518        };
2519
2520        pattern.is_match(new_text)
2521    }
2522
2523    /// Set the mask pattern for formatting the input text.
2524    ///
2525    /// The pattern can contain:
2526    /// - 9: Any digit or dot
2527    /// - A: Any letter
2528    /// - *: Any character
2529    /// - Other characters will be treated as literal mask characters
2530    ///
2531    /// Example: "(999)999-999" for phone numbers
2532    pub fn mask_pattern(mut self, pattern: impl Into<MaskPattern>) -> Self {
2533        self.mask_pattern = pattern.into();
2534        self.mask_pattern_set = true;
2535        if let Some(placeholder) = self.mask_pattern.placeholder() {
2536            self.placeholder = placeholder.into();
2537        }
2538        self
2539    }
2540
2541    pub fn set_mask_pattern(
2542        &mut self,
2543        pattern: impl Into<MaskPattern>,
2544        _: &mut Window,
2545        cx: &mut Context<Self>,
2546    ) {
2547        self.mask_pattern = pattern.into();
2548        self.mask_pattern_set = true;
2549        if let Some(placeholder) = self.mask_pattern.placeholder() {
2550            self.placeholder = placeholder.into();
2551        }
2552        cx.notify();
2553    }
2554
2555    /// Apply the default numeric mask unless the caller explicitly selected a mask.
2556    pub fn ensure_number_mask(&mut self) {
2557        if self.mask_pattern_set {
2558            return;
2559        }
2560        self.mask_pattern = MaskPattern::Number {
2561            separator: None,
2562            fraction: None,
2563        };
2564    }
2565
2566    pub(super) fn set_input_bounds(&mut self, new_bounds: Bounds<Pixels>, cx: &mut Context<Self>) {
2567        let wrap_width_changed = self.input_bounds.size.width != new_bounds.size.width;
2568        self.input_bounds = new_bounds;
2569
2570        // Update display_map wrap_width if changed.
2571        if let Some(last_layout) = self.last_layout.as_ref() {
2572            if wrap_width_changed {
2573                let wrap_width = if !self.soft_wrap {
2574                    // None to disable wrapping (will use Pixels::MAX)
2575                    None
2576                } else {
2577                    last_layout.wrap_width
2578                };
2579
2580                self.display_map.on_layout_changed(wrap_width, cx);
2581                if self.is_multi_line() {
2582                    self.mode.update_auto_grow(&self.display_map);
2583                }
2584                cx.notify();
2585            }
2586        }
2587    }
2588
2589    /// Return the selected portion of the text, borrowed out of the [`Rope`]
2590    /// the state owns.
2591    ///
2592    /// See [`Self::selected_value`] when an owned string is wanted.
2593    pub fn selected_text(&self) -> RopeSlice<'_> {
2594        let range_utf16 = self.range_to_utf16(&self.selected_range.into());
2595        let range = self.range_from_utf16(&range_utf16);
2596        self.text.slice(range)
2597    }
2598
2599    /// Return the rendered bounds for a UTF-8 byte range in the current input contents.
2600    ///
2601    /// Returns `None` when the requested range is not currently laid out or visible.
2602    pub fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
2603        let Some(last_layout) = self.last_layout.as_ref() else {
2604            return None;
2605        };
2606
2607        let Some(last_bounds) = self.last_bounds else {
2608            return None;
2609        };
2610
2611        let (_, _, start_pos) = self.line_and_position_for_offset(range.start);
2612        let (_, _, end_pos) = self.line_and_position_for_offset(range.end);
2613
2614        let Some(start_pos) = start_pos else {
2615            return None;
2616        };
2617        let Some(end_pos) = end_pos else {
2618            return None;
2619        };
2620
2621        Some(Bounds::from_corners(
2622            last_bounds.origin + start_pos,
2623            last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),
2624        ))
2625    }
2626
2627    /// Replace text in range in silent.
2628    ///
2629    /// This will not trigger any UI interaction, such as auto-completion.
2630    pub(crate) fn replace_text_in_range_silent(
2631        &mut self,
2632        range_utf16: Option<Range<usize>>,
2633        new_text: &str,
2634        window: &mut Window,
2635        cx: &mut Context<Self>,
2636    ) {
2637        self.silent_replace_text = true;
2638        self.replace_text_in_range(range_utf16, new_text, window, cx);
2639        self.silent_replace_text = false;
2640    }
2641
2642    /// Update fold candidates from tree-sitter syntax tree (full extraction).
2643    /// Used only on initial load or language changes.
2644    fn update_fold_candidates(&mut self) {
2645        if !self.mode.is_folding() {
2646            return;
2647        }
2648
2649        let Some(highlighter_rc) = self.mode.highlighter() else {
2650            return;
2651        };
2652
2653        let highlighter = highlighter_rc.borrow();
2654        let Some(highlighter) = highlighter.as_ref() else {
2655            return;
2656        };
2657
2658        let fold_ranges = highlighter.fold_ranges(&self.text);
2659        self.display_map.set_fold_candidates(fold_ranges);
2660    }
2661
2662    /// Incrementally update fold candidates after a text edit.
2663    /// Only traverses the edited region of the syntax tree instead of the full tree.
2664    fn update_fold_candidates_incremental(&mut self, edit_range: &Range<usize>, new_text: &str) {
2665        if !self.mode.is_folding() {
2666            return;
2667        }
2668
2669        let Some(highlighter_rc) = self.mode.highlighter() else {
2670            return;
2671        };
2672
2673        let highlighter = highlighter_rc.borrow();
2674        let Some(highlighter) = highlighter.as_ref() else {
2675            return;
2676        };
2677
2678        // The new byte range in the updated text after the edit
2679        let new_end = edit_range.start + new_text.len();
2680        self.display_map.update_fold_candidates_for_edit(
2681            |range, text| highlighter.fold_ranges_for_edit(range, text),
2682            edit_range.start..new_end,
2683            &self.text,
2684        );
2685    }
2686}
2687
2688impl<M: InputModeKind> EntityInputHandler for InputBaseState<M> {
2689    fn text_for_range(
2690        &mut self,
2691        range_utf16: Range<usize>,
2692        adjusted_range: &mut Option<Range<usize>>,
2693        _window: &mut Window,
2694        _cx: &mut Context<Self>,
2695    ) -> Option<String> {
2696        let range = self.range_from_utf16(&range_utf16);
2697        adjusted_range.replace(self.range_to_utf16(&range));
2698        Some(self.text.slice(range).to_string())
2699    }
2700
2701    fn selected_text_range(
2702        &mut self,
2703        _ignore_disabled_input: bool,
2704        _window: &mut Window,
2705        _cx: &mut Context<Self>,
2706    ) -> Option<UTF16Selection> {
2707        Some(UTF16Selection {
2708            range: self.range_to_utf16(&self.selected_range.into()),
2709            reversed: false,
2710        })
2711    }
2712
2713    fn marked_text_range(
2714        &self,
2715        _window: &mut Window,
2716        _cx: &mut Context<Self>,
2717    ) -> Option<Range<usize>> {
2718        self.ime_marked_range
2719            .map(|range| self.range_to_utf16(&range.into()))
2720    }
2721
2722    fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
2723        self.ime_marked_range = None;
2724        self.undo_manager.commit_transaction();
2725    }
2726
2727    /// Replace text in range.
2728    ///
2729    /// - If the new text is invalid, it will not be replaced.
2730    /// - If `range_utf16` is not provided, the current selected range will be used.
2731    fn replace_text_in_range(
2732        &mut self,
2733        range_utf16: Option<Range<usize>>,
2734        new_text: &str,
2735        window: &mut Window,
2736        cx: &mut Context<Self>,
2737    ) {
2738        let requested_intent = self.undo_manager.pending_intent.take();
2739        if !self.is_editable() {
2740            return;
2741        }
2742        let selection_before = self.selected_range;
2743
2744        if self.blink_cursor.read(cx).visible() {
2745            self.pause_blink_cursor(cx);
2746        }
2747
2748        // NOTE: The normalization keeps the UTF-16 length, but may change the
2749        // UTF-8 byte length, so all the byte-offset calculations below must
2750        // use the normalized text.
2751        let new_text = self.normalize_input(new_text);
2752        let new_text: &str = &new_text;
2753
2754        let range = range_utf16
2755            .as_ref()
2756            .map(|range_utf16| self.range_from_utf16(range_utf16))
2757            .or(self.ime_marked_range.map(|range| {
2758                let range = self.range_to_utf16(&(range.start..range.end));
2759                self.range_from_utf16(&range)
2760            }))
2761            .unwrap_or(self.selected_range.into());
2762
2763        let old_text = self.text.clone();
2764        self.text.replace(range.clone(), new_text);
2765
2766        let mut new_offset = (range.start + new_text.len()).min(self.text.len());
2767
2768        // True if the mask has changed the text, e.g. regrouping the
2769        // separators or completing a leading dot.
2770        let mut mask_changed = false;
2771
2772        if self.is_single_line() {
2773            let pending_text = self.text.to_string();
2774            // Check if the new text is valid.
2775            //
2776            // Only reject the edit if the old text was valid, to avoid
2777            // trapping a pre-existing invalid text (e.g. a `default_value`
2778            // that does not conform), the user can still edit to fix it.
2779            if !self.is_valid_input(&pending_text, cx)
2780                && self.is_valid_input(&old_text.to_string(), cx)
2781            {
2782                self.text = old_text;
2783                return;
2784            }
2785
2786            if !self.mask_pattern.is_none() {
2787                let mask_text = self.mask_pattern.mask(&pending_text);
2788                mask_changed = mask_text.as_str() != pending_text;
2789                self.text = Rope::from(mask_text.as_str());
2790                let new_text_len =
2791                    (new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
2792                new_offset = (range.start + new_text_len).min(mask_text.len());
2793            }
2794        }
2795
2796        if mask_changed {
2797            // Masking rewrites the whole document, so ranges recorded against
2798            // the old text no longer point at anything.
2799            M::reset_annotations(self);
2800        } else {
2801            M::adjust_annotations(self, &range, new_text.len());
2802        }
2803        if mask_changed {
2804            // A segment-based history entry no longer matches the masked
2805            // document, record a whole-document change instead, so that
2806            // undo/redo can restore the text exactly.
2807            self.push_history(
2808                &old_text,
2809                &(0..old_text.len()),
2810                &self.text.to_string(),
2811                Some(EditIntent::Atomic),
2812                selection_before,
2813                Some(Selection::new(new_offset, new_offset)),
2814            );
2815        } else {
2816            self.push_history(
2817                &old_text,
2818                &range,
2819                &new_text,
2820                requested_intent,
2821                selection_before,
2822                None,
2823            );
2824        }
2825        // A commit ends the IME composition: macOS delivers `insertText:` for
2826        // the confirmed candidate without a following `unmarkText`, so close
2827        // the transaction here. Leaving it open would keep merging every later
2828        // edit into the same change, which then carries the text and selection
2829        // of the first composition.
2830        self.undo_manager.commit_transaction();
2831        if let Some(diagnostics) = self.mode.diagnostics_mut() {
2832            diagnostics.reset(&self.text)
2833        }
2834        // Adjust folds before updating wrap map: remove overlapping folds and shift others
2835        self.display_map
2836            .adjust_folds_for_edit(&old_text, &range, new_text);
2837        self.display_map
2838            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
2839
2840        self.mode.update_highlighter::<M>(
2841            super::mode::HighlighterUpdate {
2842                selected_range: &range,
2843                old_text: &old_text,
2844                new_text: &self.text,
2845                change_text: &new_text,
2846                force: true,
2847            },
2848            window,
2849            cx,
2850        );
2851
2852        self.update_fold_candidates_incremental(&range, new_text);
2853        M::refresh_language_features(self, window, cx);
2854        self.selected_range = (new_offset..new_offset).into();
2855        self.ime_marked_range.take();
2856        self.update_preferred_column();
2857        self.update_search(cx);
2858        if self.is_multi_line() {
2859            self.mode.update_auto_grow(&self.display_map);
2860        }
2861        if !self.silent_replace_text {
2862            M::on_text_typed(self, &range, &new_text, window, cx);
2863        }
2864        if self.emit_events {
2865            cx.emit(InputEvent::Change);
2866        }
2867        cx.notify();
2868    }
2869
2870    /// Mark text is the IME temporary insert on typing.
2871    fn replace_and_mark_text_in_range(
2872        &mut self,
2873        range_utf16: Option<Range<usize>>,
2874        new_text: &str,
2875        new_selected_range_utf16: Option<Range<usize>>,
2876        window: &mut Window,
2877        cx: &mut Context<Self>,
2878    ) {
2879        let requested_intent = self.undo_manager.pending_intent.take();
2880        if !self.is_editable() {
2881            return;
2882        }
2883        let selection_before = self.selected_range;
2884
2885        let starts_composition = self.ime_marked_range.is_none();
2886        if starts_composition {
2887            self.undo_manager.begin_transaction();
2888        }
2889
2890        M::reset_language_features(self);
2891
2892        // See the same NOTE in `replace_text_in_range`.
2893        let new_text = self.normalize_input(new_text);
2894        let new_text: &str = &new_text;
2895
2896        let range = range_utf16
2897            .as_ref()
2898            .map(|range_utf16| self.range_from_utf16(range_utf16))
2899            .or(self.ime_marked_range.map(|range| {
2900                let range = self.range_to_utf16(&(range.start..range.end));
2901                self.range_from_utf16(&range)
2902            }))
2903            .unwrap_or(self.selected_range.into());
2904
2905        let old_text = self.text.clone();
2906        self.text.replace(range.clone(), new_text);
2907
2908        if self.is_single_line() {
2909            let pending_text = self.text.to_string();
2910            // See the same NOTE in `replace_text_in_range`.
2911            if !self.is_valid_input(&pending_text, cx)
2912                && self.is_valid_input(&old_text.to_string(), cx)
2913            {
2914                self.text = old_text;
2915                if starts_composition {
2916                    self.undo_manager.commit_transaction();
2917                }
2918                return;
2919            }
2920        }
2921
2922        M::adjust_annotations(self, &range, new_text.len());
2923        if let Some(diagnostics) = self.mode.diagnostics_mut() {
2924            diagnostics.reset(&self.text)
2925        }
2926        // Adjust folds before updating wrap map: remove overlapping folds and shift others
2927        self.display_map
2928            .adjust_folds_for_edit(&old_text, &range, new_text);
2929        self.display_map
2930            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
2931
2932        self.mode.update_highlighter::<M>(
2933            super::mode::HighlighterUpdate {
2934                selected_range: &range,
2935                old_text: &old_text,
2936                new_text: &self.text,
2937                change_text: &new_text,
2938                force: true,
2939            },
2940            window,
2941            cx,
2942        );
2943
2944        self.update_fold_candidates_incremental(&range, new_text);
2945        M::refresh_language_features(self, window, cx);
2946        if new_text.is_empty() {
2947            // Cancel selection, when cancel IME input.
2948            self.selected_range = (range.start..range.start).into();
2949            self.ime_marked_range = None;
2950        } else {
2951            self.ime_marked_range = Some((range.start..range.start + new_text.len()).into());
2952            self.selected_range = new_selected_range_utf16
2953                .as_ref()
2954                .map(|range_utf16| {
2955                    let new_text = Rope::from(new_text);
2956                    range.start + new_text.offset_utf16_to_offset(range_utf16.start)
2957                        ..range.start + new_text.offset_utf16_to_offset(range_utf16.end)
2958                })
2959                .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len())
2960                .into();
2961        }
2962        if self.is_multi_line() {
2963            self.mode.update_auto_grow(&self.display_map);
2964        }
2965        self.push_history(
2966            &old_text,
2967            &range,
2968            new_text,
2969            requested_intent,
2970            selection_before,
2971            Some(self.selected_range),
2972        );
2973        if new_text.is_empty() {
2974            self.undo_manager.commit_transaction();
2975        }
2976        cx.notify();
2977    }
2978
2979    /// Used to position IME candidates.
2980    fn bounds_for_range(
2981        &mut self,
2982        range_utf16: Range<usize>,
2983        bounds: Bounds<Pixels>,
2984        _window: &mut Window,
2985        _cx: &mut Context<Self>,
2986    ) -> Option<Bounds<Pixels>> {
2987        let last_layout = self.last_layout.as_ref()?;
2988        let line_height = last_layout.line_height;
2989        let line_number_width = last_layout.line_number_width;
2990        let range = self.range_from_utf16(&range_utf16);
2991
2992        let mut start_origin = None;
2993        let mut end_origin = None;
2994        let line_number_origin = point(line_number_width, px(0.));
2995        let mut y_offset = last_layout.visible_top;
2996
2997        for (vi, line) in last_layout.lines.iter().enumerate() {
2998            if start_origin.is_some() && end_origin.is_some() {
2999                break;
3000            }
3001
3002            let index_offset = last_layout.visible_line_byte_offsets[vi];
3003
3004            if start_origin.is_none() {
3005                if let Some(p) = line.position_for_index(
3006                    range.start.saturating_sub(index_offset),
3007                    last_layout,
3008                    false,
3009                ) {
3010                    start_origin = Some(p + point(px(0.), y_offset));
3011                }
3012            }
3013
3014            if end_origin.is_none() {
3015                if let Some(p) = line.position_for_index(
3016                    range.end.saturating_sub(index_offset),
3017                    last_layout,
3018                    false,
3019                ) {
3020                    end_origin = Some(p + point(px(0.), y_offset));
3021                }
3022            }
3023
3024            y_offset += line.size(line_height).height;
3025        }
3026
3027        let start_origin = start_origin.unwrap_or_default();
3028        let mut end_origin = end_origin.unwrap_or_default();
3029        // Ensure at same line.
3030        end_origin.y = start_origin.y;
3031
3032        Some(Bounds::from_corners(
3033            bounds.origin + line_number_origin + start_origin,
3034            // + line_height for show IME panel under the cursor line.
3035            bounds.origin + line_number_origin + point(end_origin.x, end_origin.y + line_height),
3036        ))
3037    }
3038
3039    fn character_index_for_point(
3040        &mut self,
3041        point: gpui::Point<Pixels>,
3042        _window: &mut Window,
3043        _cx: &mut Context<Self>,
3044    ) -> Option<usize> {
3045        let last_layout = self.last_layout.as_ref()?;
3046        let line_point = self.last_bounds?.localize(&point)?;
3047
3048        for (vi, line) in last_layout.lines.iter().enumerate() {
3049            let offset = last_layout.visible_line_byte_offsets[vi];
3050            if let Some(utf8_index) = line.index_for_position(line_point, last_layout) {
3051                return Some(self.offset_to_utf16(offset + utf8_index));
3052            }
3053        }
3054
3055        None
3056    }
3057}
3058
3059impl<M: InputModeKind> Focusable for InputBaseState<M> {
3060    fn focus_handle(&self, _cx: &App) -> FocusHandle {
3061        self.focus_handle.clone()
3062    }
3063}
3064
3065impl<M: InputModeKind> Render for InputBaseState<M> {
3066    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3067        // Before anything reads it: the element resolves this style during
3068        // layout and paint, and both happen after this call in the same frame.
3069        self.editor_style = self
3070            .projected_editor_style
3071            .resolved(&crate::Theme::global(cx).tokens);
3072        let entity = cx.entity();
3073        if self._pending_update {
3074            self.mode.update_highlighter::<M>(
3075                super::mode::HighlighterUpdate {
3076                    selected_range: &(0..0),
3077                    old_text: &self.text,
3078                    new_text: &self.text,
3079                    change_text: "",
3080                    force: false,
3081                },
3082                window,
3083                cx,
3084            );
3085
3086            self.update_fold_candidates();
3087            M::refresh_language_features(self, window, cx);
3088            self._pending_update = false;
3089        }
3090
3091        let element = div()
3092            .id("input-state")
3093            .key_context(CONTEXT)
3094            .track_focus(&self.focus_handle)
3095            .when(self.is_editable(), |this| {
3096                this.on_action(window.listener_for(&entity, InputBaseState::backspace))
3097                    .on_action(window.listener_for(&entity, InputBaseState::delete))
3098                    .on_action(
3099                        window.listener_for(&entity, InputBaseState::delete_to_beginning_of_line),
3100                    )
3101                    .on_action(window.listener_for(&entity, InputBaseState::delete_to_end_of_line))
3102                    .on_action(window.listener_for(&entity, InputBaseState::delete_previous_word))
3103                    .on_action(window.listener_for(&entity, InputBaseState::delete_next_word))
3104                    .on_action(window.listener_for(&entity, InputBaseState::enter))
3105                    .on_action(window.listener_for(&entity, InputBaseState::escape))
3106                    .on_action(window.listener_for(&entity, InputBaseState::paste))
3107                    .on_action(window.listener_for(&entity, InputBaseState::cut))
3108                    .on_action(window.listener_for(&entity, InputBaseState::undo))
3109                    .on_action(window.listener_for(&entity, InputBaseState::redo))
3110                    .when(self.is_multi_line(), |this| {
3111                        this.on_action(window.listener_for(&entity, InputBaseState::indent_inline))
3112                            .on_action(window.listener_for(&entity, InputBaseState::outdent_inline))
3113                            .on_action(window.listener_for(&entity, InputBaseState::indent_block))
3114                            .on_action(window.listener_for(&entity, InputBaseState::outdent_block))
3115                    })
3116            })
3117            .on_action(window.listener_for(&entity, InputBaseState::left))
3118            .on_action(window.listener_for(&entity, InputBaseState::right))
3119            .on_action(window.listener_for(&entity, InputBaseState::select_left))
3120            .on_action(window.listener_for(&entity, InputBaseState::select_right))
3121            .when(self.is_multi_line(), |this| {
3122                this.on_action(window.listener_for(&entity, InputBaseState::up))
3123                    .on_action(window.listener_for(&entity, InputBaseState::down))
3124                    .on_action(window.listener_for(&entity, InputBaseState::select_up))
3125                    .on_action(window.listener_for(&entity, InputBaseState::select_down))
3126                    .on_action(window.listener_for(&entity, InputBaseState::page_up))
3127                    .on_action(window.listener_for(&entity, InputBaseState::page_down))
3128            })
3129            .on_action(window.listener_for(&entity, InputBaseState::on_action_select_all))
3130            .on_action(window.listener_for(&entity, InputBaseState::select_to_start_of_line))
3131            .on_action(window.listener_for(&entity, InputBaseState::select_to_end_of_line))
3132            .on_action(window.listener_for(&entity, InputBaseState::select_to_previous_word))
3133            .on_action(window.listener_for(&entity, InputBaseState::select_to_next_word))
3134            .on_action(window.listener_for(&entity, InputBaseState::home))
3135            .on_action(window.listener_for(&entity, InputBaseState::end))
3136            .on_action(window.listener_for(&entity, InputBaseState::move_to_start))
3137            .on_action(window.listener_for(&entity, InputBaseState::move_to_end))
3138            .on_action(window.listener_for(&entity, InputBaseState::move_to_previous_word))
3139            .on_action(window.listener_for(&entity, InputBaseState::move_to_next_word))
3140            .on_action(window.listener_for(&entity, InputBaseState::select_to_start))
3141            .on_action(window.listener_for(&entity, InputBaseState::select_to_end))
3142            .on_action(window.listener_for(&entity, InputBaseState::show_character_palette))
3143            .on_action(window.listener_for(&entity, InputBaseState::copy))
3144            .on_action(window.listener_for(&entity, InputBaseState::on_action_search))
3145            .on_action(window.listener_for(&entity, InputBaseState::on_action_replace))
3146            .on_key_down(window.listener_for(&entity, InputBaseState::on_key_down))
3147            .on_mouse_down(
3148                MouseButton::Left,
3149                window.listener_for(&entity, InputBaseState::on_mouse_down),
3150            )
3151            .on_mouse_down(
3152                MouseButton::Right,
3153                window.listener_for(&entity, InputBaseState::on_mouse_down),
3154            )
3155            .on_mouse_up(
3156                MouseButton::Left,
3157                window.listener_for(&entity, InputBaseState::on_mouse_up),
3158            )
3159            .on_mouse_up(
3160                MouseButton::Right,
3161                window.listener_for(&entity, InputBaseState::on_mouse_up),
3162            )
3163            .on_mouse_move(window.listener_for(&entity, InputBaseState::on_mouse_move))
3164            .on_scroll_wheel(window.listener_for(&entity, InputBaseState::on_scroll_wheel))
3165            .when(!self.disabled, |this| this.cursor_text())
3166            .flex_1()
3167            .when(self.is_multi_line(), |this| this.h_full())
3168            .flex_grow_1()
3169            .overflow_x_hidden()
3170            .when(self.is_multi_line(), |this| {
3171                this.pt(self.editor_paddings.top)
3172                    .pr(self.editor_paddings.right)
3173                    .pb(self.editor_paddings.bottom)
3174                    .pl(self.editor_paddings.left)
3175            })
3176            .child(TextElement::new(entity.clone()).placeholder(self.placeholder.clone()))
3177            .when(self.shows_scrollbar(), |this| {
3178                this.child(EditorScrollbar::new(entity.clone()))
3179            });
3180
3181        // Actions only one mode handles are registered by that mode, where
3182        // `Self` is concrete enough to name its own entity type.
3183        M::register_actions(element, &entity, window)
3184    }
3185}
3186
3187#[cfg(test)]
3188mod tests {
3189    use super::*;
3190
3191    use crate::theme::Theme;
3192    use gpui::{TestAppContext, VisualTestContext};
3193
3194    use crate::input::{EditorMode, InputMode, TextareaMode};
3195
3196    struct TestRoot<M: InputModeKind>(Entity<InputBaseState<M>>);
3197
3198    impl<M: InputModeKind> Render for TestRoot<M> {
3199        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3200            div().size_full().child(self.0.clone())
3201        }
3202    }
3203
3204    struct InputView<M: InputModeKind> {
3205        input: Entity<InputBaseState<M>>,
3206        window_handle: gpui::WindowHandle<TestRoot<M>>,
3207    }
3208
3209    /// Helper to open a state of one mode in a window for testing.
3210    impl<M: InputModeKind> InputView<M> {
3211        fn build_with(
3212            cx: &mut TestAppContext,
3213            make: impl FnOnce(&mut Window, &mut Context<InputBaseState<M>>) -> InputBaseState<M>
3214            + 'static,
3215        ) -> Self {
3216            let mut input: Option<Entity<InputBaseState<M>>> = None;
3217
3218            let window = cx.update(|cx| {
3219                cx.open_window(Default::default(), |window, cx| {
3220                    // Set up the theme first
3221                    cx.set_global(Theme::default());
3222                    // Initialize input keybindings
3223                    super::super::init(cx);
3224
3225                    input = Some(cx.new(|cx| make(window, cx)));
3226
3227                    cx.new(|_| TestRoot(input.clone().unwrap()))
3228                })
3229                .unwrap()
3230            });
3231
3232            Self {
3233                input: input.clone().unwrap(),
3234                window_handle: window,
3235            }
3236        }
3237    }
3238
3239    impl InputView<EditorMode> {
3240        /// An editor state, for the tests that exercise code-editor behavior.
3241        fn new(cx: &mut TestAppContext) -> Self {
3242            Self::build_editor(cx, |state| state)
3243        }
3244
3245        fn build_editor(
3246            cx: &mut TestAppContext,
3247            f: impl FnOnce(InputBaseState<EditorMode>) -> InputBaseState<EditorMode> + 'static,
3248        ) -> Self {
3249            Self::build_with(cx, move |window, cx| {
3250                f(crate::input::EditorState::new(window, cx).language("sql"))
3251            })
3252        }
3253    }
3254
3255    impl InputView<TextareaMode> {
3256        fn build_textarea(
3257            cx: &mut TestAppContext,
3258            f: impl FnOnce(InputBaseState<TextareaMode>) -> InputBaseState<TextareaMode> + 'static,
3259        ) -> Self {
3260            Self::build_with(cx, move |window, cx| {
3261                f(crate::input::TextareaState::new(window, cx))
3262            })
3263        }
3264    }
3265
3266    impl InputView<InputMode> {
3267        /// A single-line state, the default these tests were written against.
3268        fn build(
3269            cx: &mut TestAppContext,
3270            f: impl FnOnce(InputBaseState<InputMode>) -> InputBaseState<InputMode> + 'static,
3271        ) -> Self {
3272            Self::build_with(cx, move |window, cx| {
3273                f(crate::input::InputState::new(window, cx))
3274            })
3275        }
3276    }
3277
3278    #[gpui::test]
3279    fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) {
3280        cx.update(crate::init);
3281
3282        // A single-line input keeps its caret in view by moving its own offset;
3283        // it has no viewport to drag, so a scrollbar in a text field is a
3284        // control that does not exist.
3285        let single = InputView::build(cx, |state| state);
3286        single
3287            .input
3288            .update(cx, |state, _| assert!(!state.shows_scrollbar()));
3289
3290        let multi = InputView::build_textarea(cx, |state| state);
3291        multi
3292            .input
3293            .update(cx, |state, _| assert!(state.shows_scrollbar()));
3294    }
3295
3296    #[gpui::test]
3297    fn context_menu_handler_is_deferred_and_respects_disabled(cx: &mut TestAppContext) {
3298        use std::{cell::Cell, rc::Rc};
3299        cx.update(crate::init);
3300        let input_view = InputView::new(cx);
3301        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3302        let input = input_view.input;
3303        let calls = Rc::new(Cell::new(0usize));
3304        let items = Rc::new(Cell::new(0usize));
3305
3306        cx.update(|window, cx| {
3307            input.update(cx, |state, cx| {
3308                let calls2 = calls.clone();
3309                let items2 = items.clone();
3310                state.on_context_menu(Rc::new(move |menu, _, _, _, _| {
3311                    calls2.set(calls2.get() + 1);
3312                    items2.set(menu.items.len());
3313                }));
3314                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
3315            })
3316        });
3317        assert_eq!(calls.get(), 1);
3318        assert_eq!(items.get(), 0);
3319
3320        cx.update(|window, cx| {
3321            input.update(cx, |state, cx| {
3322                state.disabled = true;
3323                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
3324            })
3325        });
3326        assert_eq!(calls.get(), 1);
3327    }
3328
3329    #[gpui::test]
3330    fn test_readonly_rejects_user_edits_only(cx: &mut TestAppContext) {
3331        let input_view = InputView::new(cx);
3332        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3333        let input = input_view.input;
3334
3335        cx.update(|window, cx| {
3336            input.update(cx, |state, cx| {
3337                state.set_value("hello", window, cx);
3338                state.set_readonly(true, cx);
3339            });
3340        });
3341
3342        cx.update(|_, cx| {
3343            input.read_with(cx, |state, _| {
3344                assert!(!state.is_editable());
3345                assert!(!state.is_replaceable());
3346            });
3347        });
3348
3349        // Typing (and IME) goes through the input handler, it must be rejected.
3350        cx.update(|window, cx| {
3351            input.update(cx, |state, cx| {
3352                state.replace_text_in_range(None, " world", window, cx);
3353                state.replace_and_mark_text_in_range(None, "あ", None, window, cx);
3354            });
3355        });
3356        cx.update(|_, cx| {
3357            input.read_with(cx, |state, _| assert_eq!(state.value(), "hello"));
3358        });
3359
3360        // The programmatic APIs are not limited by the readonly mode.
3361        cx.update(|window, cx| {
3362            input.update(cx, |state, cx| {
3363                state.insert(" world", window, cx);
3364                state.set_value("changed", window, cx);
3365            });
3366        });
3367        cx.update(|_, cx| {
3368            input.read_with(cx, |state, _| assert_eq!(state.value(), "changed"));
3369        });
3370
3371        // And the user can edit again after leaving the readonly mode.
3372        // The caret is at the start, because `set_value` has reset the selection.
3373        cx.update(|window, cx| {
3374            input.update(cx, |state, cx| {
3375                state.set_readonly(false, cx);
3376                state.replace_text_in_range(None, "!", window, cx);
3377            });
3378        });
3379        cx.update(|_, cx| {
3380            input.read_with(cx, |state, _| {
3381                assert!(state.is_editable());
3382                assert_eq!(state.value(), "!changed");
3383            });
3384        });
3385    }
3386
3387    /// Regression test: `scroll_to` at end-of-buffer must produce a deferred
3388    /// scroll target within the safe scroll range, so the painted frame
3389    /// matches what `update_scroll_offset` persists (no jitter). A small
3390    /// `cursor_surrounding_lines` override used to mismatch the hardcoded
3391    /// 3-line edge clearance in `scroll_to`, overshooting `safe_y_min`.
3392    #[gpui::test]
3393    fn test_scroll_to_eob_does_not_overshoot_safe_range(cx: &mut TestAppContext) {
3394        let input_view = InputView::new(cx);
3395        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3396        let input = input_view.input;
3397
3398        // JetBrains-style: 1 trailing empty row + 1-line cursor surrounding.
3399        cx.update(|window, cx| {
3400            input.update(cx, |state, cx| {
3401                state.set_scroll_beyond_last_line(Some(1), window, cx);
3402                state.set_cursor_surrounding_lines(Some(1), window, cx);
3403                let text: String = (1..=50)
3404                    .map(|i| format!("line {i}"))
3405                    .collect::<Vec<_>>()
3406                    .join("\n");
3407                state.set_value(text, window, cx);
3408            });
3409        });
3410        cx.run_until_parked();
3411
3412        // Sanity: paint populated `scroll_size` and `input_bounds` β€” without
3413        // these, `safe_y_min` below collapses to 0 and the assertion is vacuous.
3414        cx.update(|_, cx| {
3415            input.read_with(cx, |state, _| {
3416                assert!(
3417                    state.scroll_size.height > px(0.),
3418                    "scroll_size not populated by initial paint"
3419                );
3420                assert!(
3421                    state.input_bounds.size.height > px(0.),
3422                    "input_bounds not populated by initial paint"
3423                );
3424            });
3425        });
3426
3427        // Move cursor to end with downward direction β€” same code path as a
3428        // `Down` keystroke at EOB. `scroll_to` runs synchronously inside
3429        // `move_to`; inspect `deferred_scroll_offset` in the same closure
3430        // before the next paint consumes and clears it.
3431        cx.update(|_, cx| {
3432            input.update(cx, |state, cx| {
3433                let end = state.text.len();
3434                state.move_to(end, Some(MoveDirection::Down), cx);
3435
3436                let deferred = state
3437                    .deferred_scroll_offset
3438                    .expect("scroll_to should populate deferred_scroll_offset");
3439                let safe_y_min =
3440                    (-state.scroll_size.height + state.input_bounds.size.height).min(px(0.));
3441
3442                assert!(
3443                    deferred.y >= safe_y_min,
3444                    "deferred_scroll_offset.y = {:?} below safe_y_min = {:?} \
3445                     β€” paint would jitter (Bug C regression)",
3446                    deferred.y,
3447                    safe_y_min,
3448                );
3449            });
3450        });
3451    }
3452
3453    #[gpui::test]
3454    fn test_number_step(cx: &mut TestAppContext) {
3455        let input = InputView::build(cx, |state| state).input;
3456
3457        cx.update(|cx| {
3458            input.update(cx, |_state, cx| {
3459                assert_eq!(
3460                    NumberStep::from(5.).value(123., StepAction::Increment, cx),
3461                    5.
3462                );
3463
3464                // The step can differ by direction at a boundary: at 1.0 it
3465                // is 0.1 going down and 0.5 going up.
3466                let step = NumberStep::by_value(|value, action, _cx| {
3467                    let below = match action {
3468                        StepAction::Increment => value < 1.0,
3469                        StepAction::Decrement => value <= 1.0,
3470                    };
3471                    if below { 0.1 } else { 0.5 }
3472                });
3473                assert_eq!(step.value(0.5, StepAction::Increment, cx), 0.1);
3474                assert_eq!(step.value(1.0, StepAction::Increment, cx), 0.5);
3475                assert_eq!(step.value(1.0, StepAction::Decrement, cx), 0.1);
3476                assert_eq!(step.value(2.0, StepAction::Decrement, cx), 0.5);
3477            });
3478        });
3479    }
3480
3481    #[gpui::test]
3482    fn test_number_input_normalization(cx: &mut TestAppContext) {
3483        let input_view = InputView::build(cx, |state| {
3484            state.mask_pattern(MaskPattern::Number {
3485                separator: None,
3486                fraction: None,
3487            })
3488        });
3489        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3490        let input = input_view.input;
3491
3492        // Full-width digits and the ideographic full stop are normalized,
3493        // and the cursor is at the end (in normalized bytes, not the
3494        // original 12 bytes).
3495        cx.update(|window, cx| {
3496            input.update(cx, |state, cx| {
3497                state.replace_text_in_range(None, "12。5", window, cx);
3498            });
3499        });
3500        cx.run_until_parked();
3501        cx.update(|_, cx| {
3502            input.read_with(cx, |state, _| {
3503                assert_eq!(state.value(), "12.5");
3504                let cursor: Range<usize> = state.selected_range.into();
3505                assert_eq!(cursor, 4..4);
3506            });
3507        });
3508
3509        // Non-numeric input is rejected.
3510        cx.update(|window, cx| {
3511            input.update(cx, |state, cx| {
3512                state.replace_text_in_range(None, "abc", window, cx);
3513            });
3514        });
3515        cx.run_until_parked();
3516        cx.update(|_, cx| {
3517            input.read_with(cx, |state, _| {
3518                assert_eq!(state.value(), "12.5");
3519            });
3520        });
3521
3522        // A bare leading dot is kept as-is (normalized from the ideographic
3523        // full stop), not completed to "0.", so it stays editable.
3524        cx.update(|window, cx| {
3525            input.update(cx, |state, cx| {
3526                let range = state.range_to_utf16(&(0..state.text.len()));
3527                state.replace_text_in_range(Some(range), "。", window, cx);
3528            });
3529        });
3530        cx.run_until_parked();
3531        cx.update(|_, cx| {
3532            input.read_with(cx, |state, _| {
3533                assert_eq!(state.value(), ".");
3534                let cursor: Range<usize> = state.selected_range.into();
3535                assert_eq!(cursor, 1..1);
3536            });
3537        });
3538    }
3539
3540    #[gpui::test]
3541    fn test_number_input_normalization_with_separator(cx: &mut TestAppContext) {
3542        let input_view = InputView::build(cx, |state| {
3543            state.mask_pattern(MaskPattern::Number {
3544                separator: Some(','),
3545                fraction: Some(2),
3546            })
3547        });
3548        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3549        let input = input_view.input;
3550
3551        cx.update(|window, cx| {
3552            input.update(cx, |state, cx| {
3553                state.replace_text_in_range(None, "1234", window, cx);
3554            });
3555        });
3556        cx.run_until_parked();
3557        cx.update(|_, cx| {
3558            input.read_with(cx, |state, _| {
3559                assert_eq!(state.value(), "1,234");
3560                assert_eq!(state.unmask_value(), "1234");
3561            });
3562        });
3563    }
3564
3565    #[gpui::test]
3566    fn test_number_input_clamp_on_blur(cx: &mut TestAppContext) {
3567        let input_view = InputView::build(cx, |state| {
3568            state
3569                .mask_pattern(MaskPattern::Number {
3570                    separator: None,
3571                    fraction: None,
3572                })
3573                .min(10.)
3574                .max(100.)
3575        });
3576        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3577        let input = input_view.input;
3578
3579        // Out-of-range values are allowed while typing, and clamped on blur.
3580        cx.update(|window, cx| {
3581            input.update(cx, |state, cx| {
3582                state.replace_text_in_range(None, "1000", window, cx);
3583                assert_eq!(state.value(), "1000");
3584                state.clamp_number_value(window, cx);
3585                assert_eq!(state.value(), "100");
3586
3587                let range = state.range_to_utf16(&(0..state.text.len()));
3588                state.replace_text_in_range(Some(range), "1", window, cx);
3589                assert_eq!(state.value(), "1");
3590                state.clamp_number_value(window, cx);
3591                assert_eq!(state.value(), "10");
3592            });
3593        });
3594    }
3595
3596    #[gpui::test]
3597    fn test_number_input_undo_with_mask(cx: &mut TestAppContext) {
3598        let input_view = InputView::build(cx, |state| {
3599            state.mask_pattern(MaskPattern::Number {
3600                separator: Some(','),
3601                fraction: None,
3602            })
3603        });
3604        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3605        let input = input_view.input;
3606
3607        // When the mask changes the text (regrouping separators), a
3608        // whole-document change is recorded, so undo/redo can restore it.
3609        cx.update(|window, cx| {
3610            input.update(cx, |state, cx| {
3611                state.replace_text_in_range(None, "1234", window, cx);
3612                assert_eq!(state.value(), "1,234");
3613                state.replace_text_in_range(None, "5", window, cx);
3614                assert_eq!(state.value(), "12,345");
3615
3616                // Each whole-document mask rewrite is an atomic undo step.
3617                // Before the whole-document history fix, undo produced a
3618                // corrupted value like "1,2344".
3619                state.undo(&Undo, window, cx);
3620                assert_eq!(state.value(), "1,234");
3621                state.undo(&Undo, window, cx);
3622                assert_eq!(state.value(), "");
3623                state.redo(&Redo, window, cx);
3624                assert_eq!(state.value(), "1,234");
3625                state.redo(&Redo, window, cx);
3626                assert_eq!(state.value(), "12,345");
3627            });
3628        });
3629    }
3630
3631    #[gpui::test]
3632    fn test_undo_manager_coalesces_adjacent_typing_transactions(cx: &mut TestAppContext) {
3633        let input_view = InputView::build(cx, |state| state);
3634        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3635        let input = input_view.input;
3636
3637        cx.update(|window, cx| {
3638            input.update(cx, |state, cx| {
3639                state.replace_text_in_range(None, "a", window, cx);
3640                state.replace_text_in_range(None, "b", window, cx);
3641                assert_eq!(state.value(), "ab");
3642
3643                state.undo(&Undo, window, cx);
3644                assert_eq!(state.value(), "");
3645            });
3646        });
3647    }
3648
3649    #[gpui::test]
3650    fn test_undo_manager_cursor_movement_splits_typing(cx: &mut TestAppContext) {
3651        let input_view = InputView::build(cx, |state| state);
3652        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3653        let input = input_view.input;
3654
3655        cx.update(|window, cx| {
3656            input.update(cx, |state, cx| {
3657                state.replace_text_in_range(None, "a", window, cx);
3658                state.replace_text_in_range(None, "b", window, cx);
3659                state.left(&MoveLeft, window, cx);
3660                state.replace_text_in_range(None, "x", window, cx);
3661                assert_eq!(state.value(), "axb");
3662
3663                state.undo(&Undo, window, cx);
3664                assert_eq!(state.value(), "ab");
3665                state.undo(&Undo, window, cx);
3666                assert_eq!(state.value(), "");
3667            });
3668        });
3669    }
3670
3671    #[gpui::test]
3672    fn test_undo_manager_splits_backward_and_forward_delete(cx: &mut TestAppContext) {
3673        let input_view = InputView::build(cx, |state| state);
3674        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3675        let input = input_view.input;
3676
3677        cx.update(|window, cx| {
3678            input.update(cx, |state, cx| {
3679                state.set_value("abcd", window, cx);
3680                state.set_selected_range(2..2, cx);
3681                state.backspace(&Backspace, window, cx);
3682                state.delete(&Delete, window, cx);
3683                assert_eq!(state.value(), "ad");
3684
3685                state.undo(&Undo, window, cx);
3686                assert_eq!(state.value(), "acd");
3687                state.undo(&Undo, window, cx);
3688                assert_eq!(state.value(), "abcd");
3689            });
3690        });
3691    }
3692
3693    #[gpui::test]
3694    fn test_undo_manager_coalesces_directional_character_deletes(cx: &mut TestAppContext) {
3695        let input_view = InputView::build(cx, |state| state);
3696        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3697        let input = input_view.input;
3698
3699        cx.update(|window, cx| {
3700            input.update(cx, |state, cx| {
3701                state.set_value("abcd", window, cx);
3702                state.backspace(&Backspace, window, cx);
3703                state.backspace(&Backspace, window, cx);
3704                assert_eq!(state.value(), "ab");
3705                state.undo(&Undo, window, cx);
3706                assert_eq!(state.value(), "abcd");
3707                assert_eq!(state.selected_range(), 4..4);
3708
3709                state.set_value("abcd", window, cx);
3710                state.set_selected_range(1..1, cx);
3711                state.delete(&Delete, window, cx);
3712                state.delete(&Delete, window, cx);
3713                assert_eq!(state.value(), "ad");
3714                state.undo(&Undo, window, cx);
3715                assert_eq!(state.value(), "abcd");
3716                assert_eq!(state.selected_range(), 1..1);
3717            });
3718        });
3719    }
3720
3721    #[gpui::test]
3722    fn test_undo_manager_atomic_paste_isolated_from_typing(cx: &mut TestAppContext) {
3723        let input_view = InputView::build(cx, |state| state);
3724        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3725        let input = input_view.input;
3726
3727        cx.update(|window, cx| {
3728            cx.write_to_clipboard(ClipboardItem::new_string("P".to_string()));
3729            input.update(cx, |state, cx| {
3730                state.replace_text_in_range(None, "a", window, cx);
3731                state.paste(&Paste, window, cx);
3732                state.replace_text_in_range(None, "b", window, cx);
3733                assert_eq!(state.value(), "aPb");
3734
3735                state.undo(&Undo, window, cx);
3736                assert_eq!(state.value(), "aP");
3737                state.undo(&Undo, window, cx);
3738                assert_eq!(state.value(), "a");
3739                state.undo(&Undo, window, cx);
3740                assert_eq!(state.value(), "");
3741            });
3742        });
3743    }
3744
3745    #[gpui::test]
3746    fn test_undo_manager_programmatic_insert_is_atomic(cx: &mut TestAppContext) {
3747        let input_view = InputView::build(cx, |state| state);
3748        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3749        let input = input_view.input;
3750
3751        cx.update(|window, cx| {
3752            input.update(cx, |state, cx| {
3753                state.replace_text_in_range(None, "a", window, cx);
3754                state.insert("P", window, cx);
3755                state.replace_text_in_range(None, "b", window, cx);
3756                assert_eq!(state.value(), "aPb");
3757
3758                state.undo(&Undo, window, cx);
3759                assert_eq!(state.value(), "aP");
3760                state.undo(&Undo, window, cx);
3761                assert_eq!(state.value(), "a");
3762            });
3763        });
3764    }
3765
3766    #[gpui::test]
3767    fn test_undo_manager_selection_round_trip_splits_typing(cx: &mut TestAppContext) {
3768        let input_view = InputView::build(cx, |state| state);
3769        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3770        let input = input_view.input;
3771
3772        cx.update(|window, cx| {
3773            input.update(cx, |state, cx| {
3774                state.replace_text_in_range(None, "a", window, cx);
3775                state.replace_text_in_range(None, "b", window, cx);
3776                state.select_all(window, cx);
3777                state.unselect(window, cx);
3778                state.replace_text_in_range(None, "c", window, cx);
3779
3780                state.undo(&Undo, window, cx);
3781                assert_eq!(state.value(), "ab");
3782                state.undo(&Undo, window, cx);
3783                assert_eq!(state.value(), "");
3784            });
3785        });
3786    }
3787
3788    #[gpui::test]
3789    fn test_undo_manager_enter_is_atomic(cx: &mut TestAppContext) {
3790        let input_view = InputView::build_textarea(cx, |state| state);
3791        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3792        let input = input_view.input;
3793
3794        cx.update(|window, cx| {
3795            input.update(cx, |state, cx| {
3796                state.replace_text_in_range(None, "a", window, cx);
3797                state.enter(
3798                    &Enter {
3799                        secondary: false,
3800                        shift: false,
3801                    },
3802                    window,
3803                    cx,
3804                );
3805                state.replace_text_in_range(None, "b", window, cx);
3806
3807                state.undo(&Undo, window, cx);
3808                assert_eq!(state.value(), "a\n");
3809                state.undo(&Undo, window, cx);
3810                assert_eq!(state.value(), "a");
3811                state.undo(&Undo, window, cx);
3812                assert_eq!(state.value(), "");
3813            });
3814        });
3815    }
3816
3817    #[gpui::test]
3818    fn test_undo_manager_single_line_return_commits_the_typing_session(cx: &mut TestAppContext) {
3819        let input_view = InputView::build(cx, |state| state);
3820        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3821        let input = input_view.input;
3822
3823        cx.update(|window, cx| {
3824            input.update(cx, |state, cx| {
3825                for part in ["a", "b", "c"] {
3826                    state.replace_text_in_range(None, part, window, cx);
3827                }
3828                state.enter(
3829                    &Enter {
3830                        secondary: false,
3831                        shift: false,
3832                    },
3833                    window,
3834                    cx,
3835                );
3836                for part in ["d", "e", "f"] {
3837                    state.replace_text_in_range(None, part, window, cx);
3838                }
3839                assert_eq!(state.value(), "abcdef");
3840
3841                state.undo(&Undo, window, cx);
3842                assert_eq!(state.value(), "abc");
3843                state.undo(&Undo, window, cx);
3844                assert_eq!(state.value(), "");
3845            });
3846        });
3847    }
3848
3849    #[gpui::test]
3850    fn test_undo_manager_submit_on_enter_commits_the_textarea_session(cx: &mut TestAppContext) {
3851        let input_view = InputView::build_textarea(cx, |state| state.submit_on_enter(true));
3852        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3853        let input = input_view.input;
3854
3855        cx.update(|window, cx| {
3856            input.update(cx, |state, cx| {
3857                state.replace_text_in_range(None, "before submit", window, cx);
3858                state.enter(
3859                    &Enter {
3860                        secondary: false,
3861                        shift: false,
3862                    },
3863                    window,
3864                    cx,
3865                );
3866                state.replace_text_in_range(None, " after submit", window, cx);
3867                assert_eq!(state.value(), "before submit after submit");
3868
3869                state.undo(&Undo, window, cx);
3870                assert_eq!(state.value(), "before submit");
3871                state.undo(&Undo, window, cx);
3872                assert_eq!(state.value(), "");
3873            });
3874        });
3875    }
3876
3877    #[gpui::test]
3878    fn test_undo_manager_blur_commits_the_typing_session(cx: &mut TestAppContext) {
3879        let input_view = InputView::build(cx, |state| state);
3880        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3881        let input = input_view.input;
3882
3883        cx.update(|window, cx| {
3884            input.update(cx, |state, cx| {
3885                state.replace_text_in_range(None, "before blur", window, cx);
3886                state.on_blur(window, cx);
3887                state.on_focus(window, cx);
3888                state.replace_text_in_range(None, " after focus", window, cx);
3889                assert_eq!(state.value(), "before blur after focus");
3890
3891                state.undo(&Undo, window, cx);
3892                assert_eq!(state.value(), "before blur");
3893                state.undo(&Undo, window, cx);
3894                assert_eq!(state.value(), "");
3895            });
3896        });
3897    }
3898
3899    #[gpui::test]
3900    fn test_undo_manager_keeps_rapid_lines_in_distinct_transactions(cx: &mut TestAppContext) {
3901        let input_view = InputView::build_textarea(cx, |state| state);
3902        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3903        let input = input_view.input;
3904
3905        cx.update(|window, cx| {
3906            input.update(cx, |state, cx| {
3907                let enter = Enter {
3908                    secondary: false,
3909                    shift: false,
3910                };
3911                state.replace_text_in_range(None, "a", window, cx);
3912                state.enter(&enter, window, cx);
3913                state.replace_text_in_range(None, "b", window, cx);
3914                state.enter(&enter, window, cx);
3915                state.replace_text_in_range(None, "c", window, cx);
3916                assert_eq!(state.value(), "a\nb\nc");
3917
3918                for expected in ["a\nb\n", "a\nb", "a\n", "a", ""] {
3919                    state.undo(&Undo, window, cx);
3920                    assert_eq!(state.value(), expected);
3921                }
3922            });
3923        });
3924    }
3925
3926    #[gpui::test]
3927    fn test_undo_manager_coalesces_long_unicode_typing_without_a_timer(cx: &mut TestAppContext) {
3928        let input_view = InputView::build_textarea(cx, |state| state);
3929        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3930        let input = input_view.input;
3931        let parts = [
3932            "The ",
3933            "quick ",
3934            "brown fox, ",
3935            "δ½ ε₯½οΌŒδΈ–η•Œ ",
3936            "πŸ¦€ jumps over 13 lazy dogs.",
3937        ];
3938        let expected = parts.concat();
3939
3940        cx.update(|window, cx| {
3941            input.update(cx, |state, cx| {
3942                for part in parts {
3943                    state.replace_text_in_range(None, part, window, cx);
3944                }
3945                assert_eq!(state.value(), expected);
3946
3947                state.undo(&Undo, window, cx);
3948                assert_eq!(state.value(), "");
3949                state.redo(&Redo, window, cx);
3950                assert_eq!(state.value(), expected);
3951            });
3952        });
3953    }
3954
3955    #[gpui::test]
3956    fn test_undo_manager_long_multiline_sequence_has_structural_boundaries(
3957        cx: &mut TestAppContext,
3958    ) {
3959        let input_view = InputView::build_textarea(cx, |state| state);
3960        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3961        let input = input_view.input;
3962        let enter = Enter {
3963            secondary: false,
3964            shift: false,
3965        };
3966
3967        cx.update(|window, cx| {
3968            input.update(cx, |state, cx| {
3969                for (index, line) in [
3970                    "first line with punctuation!",
3971                    "η¬¬δΊŒθ‘ŒεŒ…ε« Unicode πŸ¦€",
3972                    "third line has several words",
3973                ]
3974                .into_iter()
3975                .enumerate()
3976                {
3977                    for chunk in line.split_inclusive(' ') {
3978                        state.replace_text_in_range(None, chunk, window, cx);
3979                    }
3980                    if index < 2 {
3981                        state.enter(&enter, window, cx);
3982                    }
3983                }
3984
3985                assert_eq!(
3986                    state.value(),
3987                    "first line with punctuation!\nη¬¬δΊŒθ‘ŒεŒ…ε« Unicode πŸ¦€\nthird line has several words"
3988                );
3989                state.undo(&Undo, window, cx);
3990                assert_eq!(
3991                    state.value(),
3992                    "first line with punctuation!\nη¬¬δΊŒθ‘ŒεŒ…ε« Unicode πŸ¦€\n"
3993                );
3994                state.undo(&Undo, window, cx);
3995                assert_eq!(
3996                    state.value(),
3997                    "first line with punctuation!\nη¬¬δΊŒθ‘ŒεŒ…ε« Unicode πŸ¦€"
3998                );
3999                state.undo(&Undo, window, cx);
4000                assert_eq!(state.value(), "first line with punctuation!\n");
4001            });
4002        });
4003    }
4004
4005    #[gpui::test]
4006    fn test_masked_input_keeps_its_value_out_of_the_clipboard(cx: &mut TestAppContext) {
4007        let input_view = InputView::build(cx, |state| state);
4008        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4009        let input = input_view.input;
4010
4011        cx.update(|window, cx| {
4012            input.update(cx, |state, cx| {
4013                state.set_value("hunter2", window, cx);
4014                state.set_masked(true, window, cx);
4015                state.select_all(window, cx);
4016                cx.write_to_clipboard(ClipboardItem::new_string("sentinel".into()));
4017
4018                state.copy(&Copy, window, cx);
4019                assert_eq!(
4020                    cx.read_from_clipboard().and_then(|item| item.text()),
4021                    Some("sentinel".to_string())
4022                );
4023
4024                // Cut neither copies nor deletes.
4025                state.cut(&Cut, window, cx);
4026                assert_eq!(state.value(), "hunter2");
4027                assert_eq!(
4028                    cx.read_from_clipboard().and_then(|item| item.text()),
4029                    Some("sentinel".to_string())
4030                );
4031
4032                // Revealing the value restores both.
4033                state.set_masked(false, window, cx);
4034                state.copy(&Copy, window, cx);
4035                assert_eq!(
4036                    cx.read_from_clipboard().and_then(|item| item.text()),
4037                    Some("hunter2".to_string())
4038                );
4039            });
4040        });
4041    }
4042
4043    #[gpui::test]
4044    fn test_masked_input_collapses_word_boundaries(cx: &mut TestAppContext) {
4045        let input_view = InputView::build(cx, |state| state);
4046        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4047        let input = input_view.input;
4048
4049        cx.update(|window, cx| {
4050            input.update(cx, |state, cx| {
4051                state.set_value("aaa bbb ccc", window, cx);
4052                state.set_masked(true, window, cx);
4053                state.set_selected_range(7..7, cx);
4054
4055                // The mask hides word boundaries, so a word delete takes
4056                // everything before the caret and leaves the rest.
4057                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
4058                assert_eq!(state.value(), " ccc");
4059                assert_eq!(state.selected_range(), 0..0);
4060
4061                state.delete_next_word(&DeleteToNextWordEnd, window, cx);
4062                assert_eq!(state.value(), "");
4063
4064                // A double click takes the whole value, not one word.
4065                state.set_value("aaa bbb ccc", window, cx);
4066                state.select_word(9, window, cx);
4067                assert_eq!(state.selected_range(), 0..11);
4068
4069                // Unmasked, the same delete only takes one word.
4070                state.set_masked(false, window, cx);
4071                state.set_value("aaa bbb ccc", window, cx);
4072                state.set_selected_range(11..11, cx);
4073                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
4074                assert_eq!(state.value(), "aaa bbb ");
4075
4076                state.set_value("aaa bbb ccc", window, cx);
4077                state.select_word(9, window, cx);
4078                assert_eq!(state.selected_range(), 8..11);
4079            });
4080        });
4081    }
4082
4083    #[gpui::test]
4084    fn test_masked_input_disables_the_copy_context_menu_items(cx: &mut TestAppContext) {
4085        let input_view = InputView::build(cx, |state| state);
4086        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4087        let input = input_view.input;
4088
4089        cx.update(|window, cx| {
4090            input.update(cx, |state, cx| {
4091                state.set_value("hunter2", window, cx);
4092                state.select_all(window, cx);
4093                assert!(state.context_menu_capabilities().is_copyable());
4094
4095                state.set_masked(true, window, cx);
4096                let capabilities = state.context_menu_capabilities();
4097                assert!(capabilities.is_masked());
4098                assert!(capabilities.has_selection());
4099                assert!(!capabilities.is_copyable());
4100            });
4101        });
4102    }
4103
4104    #[gpui::test]
4105    fn test_undo_manager_cut_and_repeated_pastes_are_distinct_transactions(
4106        cx: &mut TestAppContext,
4107    ) {
4108        let input_view = InputView::build(cx, |state| state);
4109        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4110        let input = input_view.input;
4111
4112        cx.update(|window, cx| {
4113            input.update(cx, |state, cx| {
4114                state.replace_text_in_range(None, "alpha beta gamma", window, cx);
4115                state.set_selected_range(6..10, cx);
4116                state.cut(&Cut, window, cx);
4117                assert_eq!(state.value(), "alpha  gamma");
4118
4119                state.paste(&Paste, window, cx);
4120                state.paste(&Paste, window, cx);
4121                assert_eq!(state.value(), "alpha betabeta gamma");
4122
4123                state.undo(&Undo, window, cx);
4124                assert_eq!(state.value(), "alpha beta gamma");
4125                state.undo(&Undo, window, cx);
4126                assert_eq!(state.value(), "alpha  gamma");
4127                state.undo(&Undo, window, cx);
4128                assert_eq!(state.value(), "alpha beta gamma");
4129            });
4130        });
4131    }
4132
4133    #[gpui::test]
4134    fn test_undo_manager_word_and_line_deletes_do_not_coalesce(cx: &mut TestAppContext) {
4135        let input_view = InputView::build_textarea(cx, |state| state);
4136        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4137        let input = input_view.input;
4138
4139        cx.update(|window, cx| {
4140            input.update(cx, |state, cx| {
4141                state.set_value("one two three\nfour five", window, cx);
4142                state.set_selected_range(13..13, cx);
4143                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
4144                assert_eq!(state.value(), "one two \nfour five");
4145                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
4146                assert_eq!(state.value(), "one two four five");
4147
4148                state.undo(&Undo, window, cx);
4149                assert_eq!(state.value(), "one two \nfour five");
4150                state.undo(&Undo, window, cx);
4151                assert_eq!(state.value(), "one two three\nfour five");
4152            });
4153        });
4154    }
4155
4156    #[gpui::test]
4157    fn test_undo_manager_multiline_replacement_is_one_atomic_transaction(cx: &mut TestAppContext) {
4158        let input_view = InputView::build_textarea(cx, |state| state);
4159        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4160        let input = input_view.input;
4161
4162        cx.update(|window, cx| {
4163            input.update(cx, |state, cx| {
4164                state.replace_text_in_range(None, "before", window, cx);
4165                state.set_selected_range(0..6, cx);
4166                state.replace_text_in_range(None, "line one\nline two\nη¬¬δΈ‰θ‘Œ", window, cx);
4167                assert_eq!(state.value(), "line one\nline two\nη¬¬δΈ‰θ‘Œ");
4168
4169                state.undo(&Undo, window, cx);
4170                assert_eq!(state.value(), "before");
4171                assert_eq!(state.selected_range(), 0..6);
4172                state.redo(&Redo, window, cx);
4173                assert_eq!(state.value(), "line one\nline two\nη¬¬δΈ‰θ‘Œ");
4174            });
4175        });
4176    }
4177
4178    #[gpui::test]
4179    fn test_undo_manager_composition_isolated_from_long_typing(cx: &mut TestAppContext) {
4180        let input_view = InputView::build(cx, |state| state);
4181        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4182        let input = input_view.input;
4183
4184        cx.update(|window, cx| {
4185            input.update(cx, |state, cx| {
4186                state.replace_text_in_range(None, "prefix ", window, cx);
4187                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
4188                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
4189                state.replace_and_mark_text_in_range(None, "δ½ ", None, window, cx);
4190                state.unmark_text(window, cx);
4191                state.replace_text_in_range(None, " suffix", window, cx);
4192                assert_eq!(state.value(), "prefix δ½  suffix");
4193
4194                state.undo(&Undo, window, cx);
4195                assert_eq!(state.value(), "prefix δ½ ");
4196                state.undo(&Undo, window, cx);
4197                assert_eq!(state.value(), "prefix ");
4198                state.undo(&Undo, window, cx);
4199                assert_eq!(state.value(), "");
4200            });
4201        });
4202    }
4203
4204    #[gpui::test]
4205    fn test_undo_manager_selected_replacement_is_atomic(cx: &mut TestAppContext) {
4206        let input_view = InputView::build(cx, |state| state);
4207        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4208        let input = input_view.input;
4209
4210        cx.update(|window, cx| {
4211            input.update(cx, |state, cx| {
4212                state.replace_text_in_range(None, "abc", window, cx);
4213                state.set_selected_range(1..2, cx);
4214                state.replace_text_in_range(None, "X", window, cx);
4215                state.replace_text_in_range(None, "z", window, cx);
4216                assert_eq!(state.value(), "aXzc");
4217
4218                state.undo(&Undo, window, cx);
4219                assert_eq!(state.value(), "aXc");
4220                state.undo(&Undo, window, cx);
4221                assert_eq!(state.value(), "abc");
4222                state.undo(&Undo, window, cx);
4223                assert_eq!(state.value(), "");
4224            });
4225        });
4226    }
4227
4228    #[gpui::test]
4229    fn test_number_input_leading_dot_editable(cx: &mut TestAppContext) {
4230        let input_view = InputView::build(cx, |state| {
4231            state.mask_pattern(MaskPattern::Number {
4232                separator: None,
4233                fraction: None,
4234            })
4235        });
4236        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4237        let input = input_view.input;
4238
4239        cx.update(|window, cx| {
4240            input.update(cx, |state, cx| {
4241                state.replace_text_in_range(None, "1.2", window, cx);
4242
4243                // Delete the integer part "1": the value keeps the leading dot
4244                // (".2"), not completed to "0.2", so the digits before the dot
4245                // stay editable.
4246                let range = state.range_to_utf16(&(0..1));
4247                state.replace_text_in_range(Some(range), "", window, cx);
4248                assert_eq!(state.value(), ".2");
4249                let cursor: Range<usize> = state.selected_range.into();
4250                assert_eq!(cursor, 0..0);
4251
4252                // The user can type a new integer part.
4253                state.replace_text_in_range(Some(0..0), "3", window, cx);
4254                assert_eq!(state.value(), "3.2");
4255            });
4256        });
4257    }
4258
4259    #[gpui::test]
4260    fn test_number_input_escape_invalid_text(cx: &mut TestAppContext) {
4261        // A pre-existing invalid text (e.g. a `default_value` that does not
4262        // conform) must not trap the user, the edit is allowed to fix it.
4263        let input_view = InputView::build(cx, |state| {
4264            state
4265                .mask_pattern(MaskPattern::Number {
4266                    separator: None,
4267                    fraction: None,
4268                })
4269                .default_value("1,234")
4270        });
4271        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4272        let input = input_view.input;
4273
4274        cx.update(|window, cx| {
4275            input.update(cx, |state, cx| {
4276                // Delete the last char, the pending text "1,23" is still
4277                // invalid, but the edit is allowed since the old text was
4278                // already invalid.
4279                let range = state.range_to_utf16(&(4..5));
4280                state.replace_text_in_range(Some(range), "", window, cx);
4281                assert_eq!(state.value(), "1,23");
4282
4283                // Once the text becomes valid, the validation works as usual.
4284                let range = state.range_to_utf16(&(1..2));
4285                state.replace_text_in_range(Some(range), "", window, cx);
4286                assert_eq!(state.value(), "123");
4287                state.replace_text_in_range(None, "a", window, cx);
4288                assert_eq!(state.value(), "123");
4289            });
4290        });
4291    }
4292
4293    /// After `set_value` on a single-line input the caret sits at the end (like
4294    /// HTML `<input>`), yet the view is scrolled back to the start so a long
4295    /// value shows its beginning instead of its tail.
4296    #[gpui::test]
4297    fn test_set_value_single_line_caret_at_end_view_at_start(cx: &mut TestAppContext) {
4298        let input_view = InputView::build(cx, |state| state);
4299        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4300        let input = input_view.input;
4301
4302        // Long enough to overflow any reasonable single-line input width.
4303        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
4304        let len = value.len();
4305
4306        // Right after `set_value`, before the next paint consumes the deferred
4307        // offset: caret is at the end, and the view is forced back to the start.
4308        cx.update(|window, cx| {
4309            input.update(cx, |state, cx| {
4310                state.set_value(value.clone(), window, cx);
4311
4312                assert_eq!(
4313                    state.selected_range,
4314                    Selection::new(len, len),
4315                    "single-line caret should be at the end after set_value"
4316                );
4317                assert_eq!(
4318                    state.deferred_scroll_offset,
4319                    Some(point(px(0.), px(0.))),
4320                    "the view should be forced back to the start"
4321                );
4322            });
4323        });
4324
4325        // After a paint, the steady-state view stays at the start (x == 0) even
4326        // though the caret is at the far end.
4327        cx.run_until_parked();
4328        cx.update(|_, cx| {
4329            input.read_with(cx, |state, _| {
4330                assert!(
4331                    state.scroll_size.width > state.input_bounds.size.width,
4332                    "value must overflow the input width or this test is vacuous"
4333                );
4334                assert_eq!(
4335                    state.scroll_handle.offset().x,
4336                    px(0.),
4337                    "long value should display from its start, not its tail"
4338                );
4339            });
4340        });
4341    }
4342
4343    /// `replace_all` on a single-line input replaces the text, puts the
4344    /// caret at the end, and β€” like `set_value` β€” snaps the view back to the
4345    /// start so a long value shows its beginning instead of its tail.
4346    #[gpui::test]
4347    fn test_replace_all_single_line(cx: &mut TestAppContext) {
4348        let input_view = InputView::build(cx, |state| state);
4349        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4350        let input = input_view.input;
4351
4352        // Long enough to overflow any reasonable single-line input width.
4353        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
4354        let len = value.len();
4355
4356        // Right after `replace_all`, before the next paint consumes the
4357        // deferred offset: caret is at the end, and the view is forced back
4358        // to the start.
4359        cx.update(|window, cx| {
4360            input.update(cx, |state, cx| {
4361                state.set_value("hello", window, cx);
4362                state.replace_all(value.clone(), window, cx);
4363                assert_eq!(state.value(), value);
4364                assert_eq!(
4365                    state.selected_range,
4366                    Selection::new(len, len),
4367                    "single-line caret should be at the end after replace_all"
4368                );
4369                assert_eq!(
4370                    state.scroll_handle.offset(),
4371                    point(px(0.), px(0.)),
4372                    "the scroll offset should be reset to the start"
4373                );
4374                assert_eq!(
4375                    state.deferred_scroll_offset,
4376                    Some(point(px(0.), px(0.))),
4377                    "single-line should set a deferred scroll offset to keep the start visible"
4378                );
4379            });
4380        });
4381
4382        // After a paint, the steady-state view stays at the start (x == 0)
4383        // even though the caret is at the far end.
4384        cx.run_until_parked();
4385        cx.update(|_, cx| {
4386            input.read_with(cx, |state, _| {
4387                assert!(
4388                    state.scroll_size.width > state.input_bounds.size.width,
4389                    "value must overflow the input width or this test is vacuous"
4390                );
4391                assert_eq!(
4392                    state.scroll_handle.offset().x,
4393                    px(0.),
4394                    "long value should display from its start, not its tail"
4395                );
4396            });
4397        });
4398    }
4399
4400    #[gpui::test]
4401    fn test_single_line_removes_newlines(cx: &mut TestAppContext) {
4402        let input_view = InputView::build(cx, |state| state.default_value("default\nvalue"));
4403        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4404        let input = input_view.input;
4405
4406        cx.update(|window, cx| {
4407            input.update(cx, |state, cx| {
4408                assert_eq!(state.value(), "defaultvalue");
4409
4410                state.set_value("first\nsecond\r\nthird\rfourth", window, cx);
4411                assert_eq!(state.value(), "firstsecondthirdfourth");
4412
4413                state.set_value("", window, cx);
4414                state.insert("a\nb", window, cx);
4415                assert_eq!(state.value(), "ab");
4416            });
4417
4418            cx.write_to_clipboard(ClipboardItem::new_string("a\r\nb\nc\rd".to_string()));
4419            input.update(cx, |state, cx| {
4420                state.set_value("", window, cx);
4421                state.paste(&Paste, window, cx);
4422                assert_eq!(state.value(), "abcd");
4423            });
4424        });
4425
4426        cx.run_until_parked();
4427    }
4428
4429    /// `replace_all` on a multi-line (non-code-editor) input clears the
4430    /// selection to `0..0` and resets the scroll offset, but does not set a
4431    /// deferred scroll offset (single-line only).
4432    #[gpui::test]
4433    fn test_replace_all_multi_line(cx: &mut TestAppContext) {
4434        let input_view = InputView::build_textarea(cx, |state| state);
4435        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4436        let input = input_view.input;
4437
4438        cx.update(|window, cx| {
4439            input.update(cx, |state, cx| {
4440                state.set_value("foo\nbar", window, cx);
4441                state.replace_all("baz\nqux", window, cx);
4442                assert_eq!(state.value(), "baz\nqux");
4443                assert_eq!(
4444                    state.selected_range,
4445                    Selection::new(0, 0),
4446                    "multi-line selection should be cleared after replace_all"
4447                );
4448                assert_eq!(
4449                    state.scroll_handle.offset(),
4450                    point(px(0.), px(0.)),
4451                    "the scroll offset should be reset to the start"
4452                );
4453                assert!(
4454                    state.deferred_scroll_offset.is_none(),
4455                    "multi-line should not set a deferred scroll offset"
4456                );
4457            });
4458        });
4459    }
4460
4461    /// Unlike `set_value`, `replace_all` records the change so the user can
4462    /// undo it back to the previous text and redo to the new text.
4463    #[gpui::test]
4464    fn test_replace_all_preserves_undo_history(cx: &mut TestAppContext) {
4465        let input_view = InputView::build(cx, |state| state);
4466        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4467        let input = input_view.input;
4468
4469        cx.update(|window, cx| {
4470            input.update(cx, |state, cx| {
4471                // Seed with a value and clear history so the baseline is clean.
4472                state.set_value("first", window, cx);
4473                assert!(
4474                    !state.undo_manager.has_undos(),
4475                    "history should be empty after set_value"
4476                );
4477
4478                // replace_all records a single undoable change.
4479                state.replace_all("second", window, cx);
4480                assert_eq!(state.value(), "second");
4481                assert!(
4482                    state.undo_manager.has_undos(),
4483                    "replace_all should record an undo step"
4484                );
4485
4486                // Undo restores the previous text.
4487                state.undo(&Undo, window, cx);
4488                assert_eq!(state.value(), "first");
4489
4490                // Redo reapplies the replacement.
4491                state.redo(&Redo, window, cx);
4492                assert_eq!(state.value(), "second");
4493            });
4494        });
4495    }
4496
4497    /// `replace_all` on a code editor marks a pending update and resets LSP
4498    /// state, so diagnostics/completions refresh against the new text.
4499    #[gpui::test]
4500    fn test_replace_all_code_editor(cx: &mut TestAppContext) {
4501        let input_view = InputView::new(cx);
4502        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4503        let input = input_view.input;
4504
4505        cx.update(|window, cx| {
4506            input.update(cx, |state, cx| {
4507                // Plant a pending-update flag and some LSP state to verify reset.
4508                state.set_value("select 1", window, cx);
4509                state._pending_update = false;
4510
4511                state.replace_all("select 2", window, cx);
4512                assert_eq!(state.value(), "select 2");
4513                assert!(
4514                    state._pending_update,
4515                    "replace_all on a code editor should request a pending update"
4516                );
4517            });
4518        });
4519    }
4520
4521    #[gpui::test]
4522    fn test_set_selected_range(cx: &mut TestAppContext) {
4523        let input_view = InputView::build(cx, |state| state.default_value("hello world"));
4524        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4525        let input = input_view.input;
4526
4527        cx.update(|_, cx| {
4528            input.update(cx, |s, cx| {
4529                s.set_selected_range(0..5, cx);
4530                assert_eq!(s.selected_range(), 0..5);
4531                assert_eq!(s.selected_text().to_string(), "hello");
4532
4533                s.set_selected_range(6..11, cx);
4534                assert_eq!(s.selected_text().to_string(), "world");
4535
4536                // clamped + collapsed
4537                s.set_selected_range(100..100, cx);
4538                assert_eq!(s.selected_range(), 11..11);
4539            });
4540        });
4541    }
4542
4543    #[gpui::test]
4544    fn test_set_selected_range_clips_to_utf8_boundaries(cx: &mut TestAppContext) {
4545        let input_view = InputView::build(cx, |state| state.default_value("Γ©x"));
4546        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4547        let input = input_view.input;
4548
4549        cx.update(|window, cx| {
4550            input.update(cx, |state, cx| {
4551                state.set_selected_range(0..1, cx);
4552                assert_eq!(state.selected_range(), 0..2);
4553                state.copy(&Copy, window, cx);
4554
4555                state.set_selected_range(1..1, cx);
4556                assert_eq!(state.selected_range(), 0..0);
4557            });
4558        });
4559    }
4560
4561    #[gpui::test]
4562    fn test_ime_selection_is_relative_to_replacement_start(cx: &mut TestAppContext) {
4563        let input_view = InputView::build(cx, |state| state.default_value("δ½ ε₯½ "));
4564        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4565        let input = input_view.input;
4566
4567        cx.update(|window, cx| {
4568            input.update(cx, |state, cx| {
4569                state.set_selected_range(7..7, cx);
4570                state.replace_and_mark_text_in_range(None, "s", Some(1..1), window, cx);
4571                state.replace_and_mark_text_in_range(None, "sh", Some(2..2), window, cx);
4572
4573                assert_eq!(state.value(), "δ½ ε₯½ sh");
4574                assert_eq!(state.selected_range(), 9..9);
4575                assert_eq!(state.ime_marked_range, Some((7..9).into()));
4576            });
4577        });
4578    }
4579
4580    #[gpui::test]
4581    fn test_undo_manager_composition_is_one_undo_group(cx: &mut TestAppContext) {
4582        let input_view = InputView::build(cx, |state| state);
4583        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4584        let input = input_view.input;
4585
4586        cx.update(|window, cx| {
4587            input.update(cx, |state, cx| {
4588                state.set_value("a", window, cx);
4589                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
4590                state.replace_and_mark_text_in_range(None, "sh", None, window, cx);
4591                state.replace_text_in_range(None, "是", window, cx);
4592                assert_eq!(state.value(), "a是");
4593
4594                state.undo(&Undo, window, cx);
4595                assert_eq!(state.value(), "a");
4596                state.redo(&Redo, window, cx);
4597                assert_eq!(state.value(), "a是");
4598            });
4599        });
4600    }
4601
4602    #[gpui::test]
4603    fn test_undo_manager_consecutive_compositions_are_separate_groups(cx: &mut TestAppContext) {
4604        let input_view = InputView::build(cx, |state| state);
4605        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4606        let input = input_view.input;
4607
4608        cx.update(|window, cx| {
4609            input.update(cx, |state, cx| {
4610                // First composition: "jin" -> "今倩"
4611                state.replace_and_mark_text_in_range(None, "j", None, window, cx);
4612                state.replace_and_mark_text_in_range(None, "jin", None, window, cx);
4613                state.replace_text_in_range(None, "今倩", window, cx);
4614                // Second composition: "wo" -> "ζˆ‘δ»¬"
4615                state.replace_and_mark_text_in_range(None, "w", None, window, cx);
4616                state.replace_and_mark_text_in_range(None, "wo", None, window, cx);
4617                state.replace_text_in_range(None, "ζˆ‘δ»¬", window, cx);
4618                assert_eq!(state.value(), "δ»Šε€©ζˆ‘δ»¬");
4619                assert_eq!(state.selected_range(), 12..12);
4620
4621                state.undo(&Undo, window, cx);
4622                assert_eq!(state.value(), "今倩");
4623                assert_eq!(state.selected_range(), 6..6);
4624
4625                state.undo(&Undo, window, cx);
4626                assert_eq!(state.value(), "");
4627                assert_eq!(state.selected_range(), 0..0);
4628
4629                state.redo(&Redo, window, cx);
4630                assert_eq!(state.value(), "今倩");
4631                assert_eq!(state.selected_range(), 6..6);
4632
4633                state.redo(&Redo, window, cx);
4634                assert_eq!(state.value(), "δ»Šε€©ζˆ‘δ»¬");
4635                assert_eq!(state.selected_range(), 12..12);
4636            });
4637        });
4638    }
4639
4640    #[gpui::test]
4641    fn test_undo_manager_typing_after_composition_is_a_separate_group(cx: &mut TestAppContext) {
4642        let input_view = InputView::build(cx, |state| state);
4643        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4644        let input = input_view.input;
4645
4646        cx.update(|window, cx| {
4647            input.update(cx, |state, cx| {
4648                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
4649                state.replace_text_in_range(None, "δ½ ", window, cx);
4650                state.undo_manager.pending_intent = Some(EditIntent::Typing);
4651                state.replace_text_in_range(None, "a", window, cx);
4652                state.undo_manager.pending_intent = Some(EditIntent::Typing);
4653                state.replace_text_in_range(None, "b", window, cx);
4654                assert_eq!(state.value(), "δ½ ab");
4655
4656                state.undo(&Undo, window, cx);
4657                assert_eq!(state.value(), "δ½ ");
4658
4659                state.undo(&Undo, window, cx);
4660                assert_eq!(state.value(), "");
4661            });
4662        });
4663    }
4664
4665    #[gpui::test]
4666    fn test_undo_manager_composition_cancel_leaves_no_entry(cx: &mut TestAppContext) {
4667        let input_view = InputView::build(cx, |state| state);
4668        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4669        let input = input_view.input;
4670
4671        cx.update(|window, cx| {
4672            input.update(cx, |state, cx| {
4673                state.set_value("a", window, cx);
4674                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
4675                state.replace_and_mark_text_in_range(None, "", None, window, cx);
4676
4677                assert_eq!(state.value(), "a");
4678                assert!(!state.undo_manager.has_undos());
4679            });
4680        });
4681    }
4682
4683    #[gpui::test]
4684    fn test_undo_manager_selection_restored_by_undo_and_redo(cx: &mut TestAppContext) {
4685        let input_view = InputView::build(cx, |state| state);
4686        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4687        let input = input_view.input;
4688
4689        cx.update(|window, cx| {
4690            input.update(cx, |state, cx| {
4691                state.set_value("abc", window, cx);
4692                state.set_selected_range(1..2, cx);
4693                state.replace_text_in_range(None, "X", window, cx);
4694
4695                state.undo(&Undo, window, cx);
4696                assert_eq!(state.value(), "abc");
4697                assert_eq!(state.selected_range(), 1..2);
4698
4699                state.redo(&Redo, window, cx);
4700                assert_eq!(state.value(), "aXc");
4701                assert_eq!(state.selected_range(), 2..2);
4702            });
4703        });
4704    }
4705
4706    #[gpui::test]
4707    fn test_undo_manager_forward_delete_restores_cursor(cx: &mut TestAppContext) {
4708        let input_view = InputView::build(cx, |state| state);
4709        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4710        let input = input_view.input;
4711
4712        cx.update(|window, cx| {
4713            input.update(cx, |state, cx| {
4714                state.set_value("abc", window, cx);
4715                state.set_selected_range(1..1, cx);
4716                state.delete(&Delete, window, cx);
4717
4718                state.undo(&Undo, window, cx);
4719                assert_eq!(state.value(), "abc");
4720                assert_eq!(state.selected_range(), 1..1);
4721            });
4722        });
4723    }
4724
4725    #[gpui::test]
4726    fn test_undo_manager_selection_movement_preserves_redo(cx: &mut TestAppContext) {
4727        let input_view = InputView::build(cx, |state| state);
4728        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4729        let input = input_view.input;
4730
4731        cx.update(|window, cx| {
4732            input.update(cx, |state, cx| {
4733                state.replace_text_in_range(None, "ab", window, cx);
4734                state.undo(&Undo, window, cx);
4735                state.set_selected_range(0..0, cx);
4736                state.redo(&Redo, window, cx);
4737                assert_eq!(state.value(), "ab");
4738
4739                state.undo(&Undo, window, cx);
4740                state.replace_text_in_range(None, "x", window, cx);
4741                state.redo(&Redo, window, cx);
4742                assert_eq!(state.value(), "x");
4743            });
4744        });
4745    }
4746
4747    #[gpui::test]
4748    fn test_undo_manager_noop_edit_preserves_redo(cx: &mut TestAppContext) {
4749        let input_view = InputView::build(cx, |state| state);
4750        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4751        let input = input_view.input;
4752
4753        cx.update(|window, cx| {
4754            input.update(cx, |state, cx| {
4755                state.replace_text_in_range(None, "a", window, cx);
4756                state.undo(&Undo, window, cx);
4757                state.backspace(&Backspace, window, cx);
4758                state.redo(&Redo, window, cx);
4759                assert_eq!(state.value(), "a");
4760            });
4761        });
4762    }
4763
4764    #[gpui::test]
4765    fn test_undo_manager_noop_edit_breaks_coalescing_without_clearing_history(
4766        cx: &mut TestAppContext,
4767    ) {
4768        let input_view = InputView::build(cx, |state| state);
4769        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4770        let input = input_view.input;
4771
4772        cx.update(|window, cx| {
4773            input.update(cx, |state, cx| {
4774                state.replace_text_in_range(None, "alpha", window, cx);
4775                state.replace_text_in_range(None, "", window, cx);
4776                state.replace_text_in_range(None, "beta", window, cx);
4777                assert_eq!(state.value(), "alphabeta");
4778
4779                state.undo(&Undo, window, cx);
4780                assert_eq!(state.value(), "alpha");
4781                state.undo(&Undo, window, cx);
4782                assert_eq!(state.value(), "");
4783            });
4784        });
4785    }
4786
4787    #[gpui::test]
4788    fn test_undo_manager_masked_redo_restores_actual_cursor(cx: &mut TestAppContext) {
4789        let input_view = InputView::build(cx, |state| {
4790            state.mask_pattern(MaskPattern::Number {
4791                separator: Some(','),
4792                fraction: None,
4793            })
4794        });
4795        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4796        let input = input_view.input;
4797
4798        cx.update(|window, cx| {
4799            input.update(cx, |state, cx| {
4800                state.set_value("12345", window, cx);
4801                state.set_selected_range(2..2, cx);
4802                state.replace_text_in_range(None, "9", window, cx);
4803                let selection_after_edit = state.selected_range();
4804                assert_ne!(selection_after_edit.end, state.value().len());
4805
4806                state.undo(&Undo, window, cx);
4807                state.redo(&Redo, window, cx);
4808                assert_eq!(state.selected_range(), selection_after_edit);
4809            });
4810        });
4811    }
4812
4813    /// Unfolding at a position opens exactly the folds hiding it.
4814    ///
4815    /// A fold keeps its own first and last line visible, so a position on
4816    /// either of them opens nothing. Nested folds all open at once, sibling
4817    /// folds stay closed, and the opened ranges stay fold candidates.
4818    #[gpui::test]
4819    fn test_unfold_at(cx: &mut TestAppContext) {
4820        use crate::input::{FoldRange, Position};
4821
4822        let view = InputView::<EditorMode>::new(cx);
4823        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
4824        let input = view.input;
4825
4826        // An outer fold over lines 0..=5, a fold nested inside it, and a
4827        // sibling fold that must never be touched.
4828        cx.update(|window, cx| {
4829            input.update(cx, |state, cx| {
4830                state.set_value("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl", window, cx);
4831                state.apply_highlighter_fold_candidates(
4832                    vec![
4833                        FoldRange::new(0, 5),
4834                        FoldRange::new(2, 4),
4835                        FoldRange::new(7, 10),
4836                    ],
4837                    cx,
4838                );
4839                state.display_map.set_folded(0, true);
4840                state.display_map.set_folded(2, true);
4841                state.display_map.set_folded(7, true);
4842            });
4843        });
4844
4845        // The outer fold's own first and last line stay visible, so neither
4846        // position opens anything.
4847        for line in [0, 5] {
4848            cx.update(|_, cx| {
4849                input.update(cx, |state, cx| {
4850                    assert!(!state.display_map.is_buffer_line_hidden(line));
4851                    assert!(!state.unfold_at(Position::new(line as u32, 0), cx));
4852                });
4853                input.read_with(cx, |state, _| {
4854                    assert!(state.display_map.is_folded_at(0));
4855                    assert!(state.display_map.is_folded_at(2));
4856                    assert!(state.display_map.is_folded_at(7));
4857                });
4858            });
4859        }
4860
4861        // Line 3 is hidden by both the outer and the nested fold, so both
4862        // open; the sibling fold does not.
4863        cx.update(|_, cx| {
4864            input.update(cx, |state, cx| {
4865                assert!(state.unfold_at(Position::new(3, 0), cx));
4866            });
4867            input.read_with(cx, |state, _| {
4868                assert!(!state.display_map.is_buffer_line_hidden(3));
4869                assert!(!state.display_map.is_folded_at(0));
4870                assert!(!state.display_map.is_folded_at(2));
4871                assert!(state.display_map.is_folded_at(7));
4872                // The opened ranges are still candidates for refolding.
4873                assert!(state.display_map.is_fold_candidate(0));
4874                assert!(state.display_map.is_fold_candidate(2));
4875            });
4876        });
4877
4878        // Nothing is hidden there any more, so a second call is a no-op.
4879        cx.update(|_, cx| {
4880            input.update(cx, |state, cx| {
4881                assert!(!state.unfold_at(Position::new(3, 0), cx));
4882            });
4883        });
4884    }
4885
4886    /// Losing focus hides the hover popover but keeps the decorations.
4887    ///
4888    /// Both used to be dropped by one call, so clicking away threw away
4889    /// decorations the application had installed and never asked to remove.
4890    #[gpui::test]
4891    fn test_blur_keeps_decorations(cx: &mut TestAppContext) {
4892        let view = InputView::<EditorMode>::new(cx);
4893        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
4894        let input = view.input;
4895
4896        cx.update(|window, cx| {
4897            input.update(cx, |state, cx| {
4898                state.set_value("select 1", window, cx);
4899                let _collection = state.create_decorations_collection(
4900                    vec![crate::input::TextDecoration::new(
4901                        0..6,
4902                        gpui::HighlightStyle {
4903                            font_weight: Some(gpui::FontWeight::BOLD),
4904                            ..Default::default()
4905                        },
4906                    )],
4907                    cx,
4908                );
4909                state.present_hover(
4910                    0..6,
4911                    lsp_types::Hover {
4912                        contents: lsp_types::HoverContents::Scalar(
4913                            lsp_types::MarkedString::String("docs".into()),
4914                        ),
4915                        range: None,
4916                    },
4917                    cx,
4918                );
4919                assert!(state.hover_popover().is_some());
4920
4921                state.on_blur(window, cx);
4922
4923                assert!(
4924                    state.hover_popover().is_none(),
4925                    "blur should hide the hover popover"
4926                );
4927                let decorations = state.extras.decoration_layers();
4928                assert!(
4929                    decorations.iter().any(|layer| !layer.is_empty()),
4930                    "blur must not discard decorations"
4931                );
4932            });
4933        });
4934    }
4935
4936    /// The mode marker is the only source of truth for the kind of input.
4937    ///
4938    /// An auto-growing textarea capped at one row used to report itself as
4939    /// single-line, because the answer was derived from the row counts.
4940    #[gpui::test]
4941    fn test_kind_does_not_follow_the_row_count(cx: &mut TestAppContext) {
4942        let view = InputView::build_textarea(cx, |state| state.auto_grow(1, 1));
4943        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
4944        view.input.read_with(&mut cx, |state, _| {
4945            assert!(state.is_multi_line());
4946            assert!(!state.is_single_line());
4947            assert!(!state.is_code_editor());
4948        });
4949    }
4950
4951    /// Soft wrap is on by default, for every mode that can wrap.
4952    ///
4953    /// The default lives in the shared constructor, where a mode-specific
4954    /// `new` can silently fail to restore it; this pins it down.
4955    #[gpui::test]
4956    fn test_soft_wrap_is_enabled_by_default(cx: &mut TestAppContext) {
4957        let textarea = InputView::build_textarea(cx, |state| state);
4958        let mut textarea_cx = VisualTestContext::from_window(textarea.window_handle.into(), cx);
4959        textarea
4960            .input
4961            .read_with(&mut textarea_cx, |state, _| assert!(state.soft_wrap));
4962
4963        let editor = InputView::<EditorMode>::new(cx);
4964        let mut editor_cx = VisualTestContext::from_window(editor.window_handle.into(), cx);
4965        editor
4966            .input
4967            .read_with(&mut editor_cx, |state, _| assert!(state.soft_wrap));
4968    }
4969}
4970
4971/// Methods that only a single-line input offers.
4972impl InputBaseState<crate::input::InputMode> {
4973    /// Create a single-line text input state.
4974    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
4975        Self::new_in_mode(window, cx)
4976    }
4977
4978    /// Set a custom step function of the [`super::NumberInput`].
4979    ///
4980    /// The `f` receives the current value and the [`StepAction`], and returns
4981    /// the step to apply, so the step can vary with the value.
4982    ///
4983    /// # Example
4984    ///
4985    /// ```ignore
4986    /// // At the boundary 1.0 the step is 0.1 going down and 0.5 going up.
4987    /// InputState::new(window, cx).step_by(|value, action, _cx| match action {
4988    ///     StepAction::Increment => if value < 1.0 { 0.1 } else { 0.5 },
4989    ///     StepAction::Decrement => if value <= 1.0 { 0.1 } else { 0.5 },
4990    /// })
4991    /// ```
4992    pub fn step_by(mut self, f: impl Fn(f64, StepAction, &mut App) -> f64 + 'static) -> Self {
4993        self.number_step = Some(NumberStep::by_value(f));
4994        self
4995    }
4996
4997    /// Set with password masked state.
4998    pub fn masked(mut self, masked: bool) -> Self {
4999        self.masked = masked;
5000        self
5001    }
5002
5003    /// Set the password masked state of the input field.
5004    pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
5005        self.masked = masked;
5006        cx.notify();
5007    }
5008
5009    /// Set the regular expression pattern of the input field.
5010    pub fn pattern(mut self, pattern: regex::Regex) -> Self {
5011        self.pattern = Some(pattern);
5012        self
5013    }
5014
5015    /// Set the regular expression pattern of the input field with reference.
5016    pub fn set_pattern(
5017        &mut self,
5018        pattern: regex::Regex,
5019        _window: &mut Window,
5020        _cx: &mut Context<Self>,
5021    ) {
5022        self.pattern = Some(pattern);
5023    }
5024
5025    /// Set the validation function of the input field.
5026    pub fn validate(mut self, f: impl Fn(&str, &mut App) -> bool + 'static) -> Self {
5027        self.validate = Some(Box::new(f));
5028        self
5029    }
5030
5031    pub fn set_validator(
5032        &mut self,
5033        validate: impl Fn(&str, &mut App) -> bool + 'static,
5034        _cx: &mut Context<Self>,
5035    ) {
5036        self.validate = Some(Box::new(validate));
5037    }
5038
5039    /// Set the step value of the [`super::NumberInput`] for increment/decrement.
5040    ///
5041    /// If any of `step`, `min`, `max` is set, the [`super::NumberInput`] will
5042    /// update the value internally (step by `step`, default 1, clamp to the
5043    /// `min`/`max` range and emit [`InputEvent::Change`]) instead of emitting
5044    /// [`super::NumberInputEvent::Step`].
5045    ///
5046    /// See also [`Self::step_by`] to calculate the step value
5047    /// based on the current value.
5048    pub fn step(mut self, step: impl Into<NumberStep>) -> Self {
5049        self.number_step = Some(step.into());
5050        self
5051    }
5052
5053    /// Set the minimum value of the [`super::NumberInput`].
5054    ///
5055    /// The value will be clamped to the minimum value on stepping and on
5056    /// blur (only if the clamped value passes the `pattern`/`validate` check).
5057    /// See also [`Self::step`].
5058    pub fn min(mut self, min: f64) -> Self {
5059        self.number_min = Some(min);
5060        self
5061    }
5062
5063    /// Set the maximum value of the [`super::NumberInput`].
5064    ///
5065    /// The value will be clamped to the maximum value on stepping and on
5066    /// blur (only if the clamped value passes the `pattern`/`validate` check).
5067    /// See also [`Self::step`].
5068    pub fn max(mut self, max: f64) -> Self {
5069        self.number_max = Some(max);
5070        self
5071    }
5072
5073    /// Update the step value after construction, `None` to fall back to
5074    /// emitting [`super::NumberInputEvent::Step`] (if `min`, `max` are unset).
5075    ///
5076    /// See [`Self::step`] and [`Self::step_by`].
5077    pub fn set_step(
5078        &mut self,
5079        step: impl Into<Option<NumberStep>>,
5080        _: &mut Window,
5081        _: &mut Context<Self>,
5082    ) {
5083        self.number_step = step.into();
5084    }
5085
5086    /// Update the minimum value after construction. See [`Self::min`].
5087    pub fn set_min(&mut self, min: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
5088        self.number_min = min;
5089    }
5090
5091    /// Update the maximum value after construction. See [`Self::max`].
5092    pub fn set_max(&mut self, max: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
5093        self.number_max = max;
5094    }
5095
5096    /// Set true to show spinner at the input right.
5097    pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
5098        self.loading = loading;
5099        cx.notify();
5100    }
5101}
5102
5103/// Methods shared by the two multi-line modes, and reachable on neither a
5104/// single-line input nor anything else.
5105impl<M: crate::input::MultiLineMode> InputBaseState<M> {
5106    /// Set this input is searchable, default is false (Default true for Code Editor).
5107    #[doc(hidden)]
5108    pub fn searchable(mut self, searchable: bool) -> Self {
5109        self.searchable = searchable;
5110        self
5111    }
5112
5113    pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
5114        self.searchable = searchable;
5115        cx.notify();
5116    }
5117
5118    /// Set the soft wrap mode, default is true.
5119    #[doc(hidden)]
5120    pub fn soft_wrap(mut self, wrap: bool) -> Self {
5121        self.soft_wrap = wrap;
5122        self
5123    }
5124
5125    /// Update the soft wrap mode, default is true.
5126    pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
5127        self.soft_wrap = wrap;
5128        if wrap {
5129            let wrap_width = self
5130                .last_layout
5131                .as_ref()
5132                .and_then(|b| b.wrap_width)
5133                .unwrap_or(self.input_bounds.size.width);
5134
5135            self.display_map.on_layout_changed(Some(wrap_width), cx);
5136
5137            // Reset scroll to left 0
5138            let mut offset = self.scroll_handle.offset();
5139            offset.x = px(0.);
5140            self.scroll_handle.set_offset(offset);
5141        } else {
5142            self.display_map.on_layout_changed(None, cx);
5143        }
5144        cx.notify();
5145    }
5146
5147    /// Set how soft-wrapped continuation lines are indented, default is [`WrappingIndent::Same`]
5148    #[doc(hidden)]
5149    pub fn wrapping_indent(mut self, wrapping_indent: WrappingIndent) -> Self {
5150        self.wrapping_indent = wrapping_indent;
5151        self
5152    }
5153
5154    /// Update how soft-wrapped continuation lines are indented.
5155    pub fn set_wrapping_indent(
5156        &mut self,
5157        wrapping_indent: WrappingIndent,
5158        _: &mut Window,
5159        cx: &mut Context<Self>,
5160    ) {
5161        self.wrapping_indent = wrapping_indent;
5162        self.display_map.set_wrapping_indent(wrapping_indent, cx);
5163        cx.notify();
5164    }
5165}
5166
5167/// Methods that only ordinary multi-line text offers.
5168impl InputBaseState<crate::input::TextareaMode> {
5169    /// Create a multi-line text state.
5170    ///
5171    /// Being multi-line is carried by the mode, not by the layout, so the
5172    /// default plain-text layout needs no adjustment here.
5173    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
5174        Self::new_in_mode(window, cx)
5175    }
5176
5177    pub fn set_auto_grow(&mut self, min_rows: usize, max_rows: usize, cx: &mut Context<Self>) {
5178        self.mode = LayoutMode::auto_grow(min_rows, max_rows.max(min_rows));
5179        cx.notify();
5180    }
5181
5182    /// Set the number of rows for the multi-line Textarea.
5183    ///
5184    /// This is only used when `multi_line` is set to true.
5185    ///
5186    /// default: 2
5187    #[doc(hidden)]
5188    pub fn rows(mut self, rows: usize) -> Self {
5189        match &mut self.mode {
5190            LayoutMode::PlainText { rows: r, .. } | LayoutMode::CodeEditor { rows: r, .. } => {
5191                *r = rows
5192            }
5193            LayoutMode::AutoGrow {
5194                max_rows: max_r,
5195                rows: r,
5196                ..
5197            } => {
5198                *r = rows;
5199                *max_r = rows;
5200            }
5201        }
5202        self
5203    }
5204
5205    pub fn set_rows(&mut self, rows: usize, cx: &mut Context<Self>) {
5206        match &mut self.mode {
5207            LayoutMode::PlainText { rows: value, .. }
5208            | LayoutMode::CodeEditor { rows: value, .. } => *value = rows,
5209            LayoutMode::AutoGrow {
5210                rows: value,
5211                max_rows,
5212                ..
5213            } => {
5214                *value = rows;
5215                *max_rows = rows;
5216            }
5217        }
5218        cx.notify();
5219    }
5220
5221    /// Grow with the content from `min_rows` through `max_rows`.
5222    pub fn auto_grow(mut self, min_rows: usize, max_rows: usize) -> Self {
5223        self.mode = LayoutMode::auto_grow(min_rows, max_rows);
5224        self
5225    }
5226}
5227
5228/// Methods that only a source-code editor offers.
5229impl InputBaseState<crate::input::EditorMode> {
5230    /// Create a source-code editor state.
5231    ///
5232    /// Default options: line numbers on, tab size 2 with soft tabs, indent
5233    /// guides on, multi-line, and search enabled. Set the language for syntax
5234    /// highlighting with [`Self::language`]; without one the text is shown
5235    /// unhighlighted.
5236    ///
5237    /// The editor aims at simple code editing or display, not at being a
5238    /// full-featured code editor. It offers syntax highlighting, auto indent,
5239    /// line numbers, and handles large text up to about 50K lines.
5240    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
5241        let mut state = Self::new_in_mode(window, cx);
5242        state.mode = LayoutMode::code_editor();
5243        state.searchable = true;
5244        state
5245    }
5246
5247    /// Set the language to highlight, e.g. `"rust"`.
5248    ///
5249    /// See [`Self::set_highlighter`] to change it after construction.
5250    pub fn language(mut self, language: impl Into<SharedString>) -> Self {
5251        if let LayoutMode::CodeEditor {
5252            language: l,
5253            highlighter,
5254            ..
5255        } = &mut self.mode
5256        {
5257            *l = language.into();
5258            *highlighter.borrow_mut() = None;
5259        }
5260        self
5261    }
5262
5263    /// Set enable/disable code folding.
5264    ///
5265    /// Default: true
5266    #[doc(hidden)]
5267    pub fn folding(mut self, folding: bool) -> Self {
5268        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
5269            *f = folding;
5270        }
5271        self
5272    }
5273
5274    /// Set code folding at runtime.
5275    ///
5276    /// When disabling, all existing folds are cleared.
5277    pub fn set_folding(&mut self, folding: bool, _: &mut Window, cx: &mut Context<Self>) {
5278        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
5279            *f = folding;
5280        }
5281        if !folding {
5282            self.display_map.clear_folds();
5283        }
5284        cx.notify();
5285    }
5286
5287    /// Unfold any folded ranges that hide the given position.
5288    ///
5289    /// Use this to reveal a position before acting on it (e.g. before
5290    /// [`Self::set_cursor_position`], which stops at a fold boundary),
5291    /// without touching folds elsewhere in the buffer. Fold candidates are
5292    /// kept, so the opened ranges can be folded again from the gutter.
5293    ///
5294    /// A fold keeps its own first and last line visible, so a position on
5295    /// either of them opens nothing. Nested folds all open, since opening
5296    /// only the outermost would leave the position hidden.
5297    ///
5298    /// Returns whether any fold was opened.
5299    pub fn unfold_at(&mut self, position: impl Into<Position>, cx: &mut Context<Self>) -> bool {
5300        let offset = self.text.position_to_offset(&position.into());
5301        let line = self.text.offset_to_point(offset).row;
5302        // A fold hides start_line + 1 ..= end_line - 1, so a line is hidden
5303        // exactly when some folded range strictly contains it.
5304        let covering: Vec<usize> = self
5305            .display_map
5306            .folded_ranges()
5307            .iter()
5308            .filter(|fold| line > fold.start_line && line < fold.end_line)
5309            .map(|fold| fold.start_line)
5310            .collect();
5311        if covering.is_empty() {
5312            return false;
5313        }
5314
5315        for start_line in covering {
5316            self.display_map.set_folded(start_line, false);
5317        }
5318        cx.notify();
5319        true
5320    }
5321
5322    /// Set enable/disable line number.
5323    #[doc(hidden)]
5324    pub fn line_number(mut self, line_number: bool) -> Self {
5325        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
5326            *l = line_number;
5327        }
5328        self
5329    }
5330
5331    /// Set line number.
5332    pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
5333        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
5334            *l = line_number;
5335        }
5336        cx.notify();
5337    }
5338}