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    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point,
10    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    cursor::{CursorSelection, Selections},
28    element::{EditorScrollbar, EditorScrollbarSnapshot, TextElement},
29    kind::InputModeKind,
30    mask_pattern::normalize_number_input,
31    mode::LayoutMode,
32    undo_manager::{EditIntent, UndoManager},
33};
34use crate::actions::{SelectDown, SelectLeft, SelectRight, SelectUp};
35use crate::input::blink_cursor::CURSOR_WIDTH;
36use crate::input::movement::MoveDirection;
37use crate::input::{
38    InputExtras as _, Position, RopeExt as _, element::RIGHT_MARGIN, layout::LastLayout,
39};
40use crate::{AutoScroll, StepAction};
41
42/// Vertical clearance to retain when revealing a text position.
43pub(crate) enum ScrollPadding {
44    Minimal,
45    SurroundingLines,
46}
47
48#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
49#[action(namespace = input, no_json)]
50pub struct Enter {
51    /// Is confirm with secondary.
52    pub secondary: bool,
53    /// Whether the Shift modifier was held when Enter was pressed.
54    pub shift: bool,
55}
56
57impl Enter {
58    /// Returns true if `action` is a primary `Enter` action (`secondary: false`),
59    /// regardless of whether Shift was held.
60    pub fn is_primary(action: &dyn Action) -> bool {
61        action.partial_eq(&Enter {
62            secondary: false,
63            shift: false,
64        }) || action.partial_eq(&Enter {
65            secondary: false,
66            shift: true,
67        })
68    }
69}
70
71actions!(
72    input,
73    [
74        Backspace,
75        Delete,
76        DeleteToBeginningOfLine,
77        DeleteToEndOfLine,
78        DeleteToPreviousWordStart,
79        DeleteToNextWordEnd,
80        Indent,
81        Outdent,
82        IndentInline,
83        OutdentInline,
84        MoveUp,
85        MoveDown,
86        MoveLeft,
87        MoveRight,
88        MoveHome,
89        MoveEnd,
90        MovePageUp,
91        MovePageDown,
92        AddCursorAbove,
93        AddCursorBelow,
94        SelectAll,
95        SelectToStartOfLine,
96        SelectToEndOfLine,
97        SelectToStart,
98        SelectToEnd,
99        SelectToPreviousWordStart,
100        SelectToNextWordEnd,
101        ShowCharacterPalette,
102        Copy,
103        Cut,
104        Paste,
105        Undo,
106        Redo,
107        MoveToStartOfLine,
108        MoveToEndOfLine,
109        MoveToStart,
110        MoveToEnd,
111        MoveToPreviousWord,
112        MoveToNextWord,
113        Escape,
114        ToggleCodeActions,
115        Search,
116        Replace,
117        GoToDefinition,
118    ]
119);
120
121#[derive(Clone)]
122pub enum InputEvent {
123    Change,
124    PressEnter { secondary: bool, shift: bool },
125    Focus,
126    Blur,
127}
128
129pub(super) const CONTEXT: &str = "Input";
130
131pub(crate) fn init(cx: &mut App) {
132    cx.bind_keys([
133        KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
134        KeyBinding::new("shift-backspace", Backspace, Some(CONTEXT)),
135        #[cfg(target_os = "macos")]
136        KeyBinding::new("ctrl-backspace", Backspace, Some(CONTEXT)),
137        KeyBinding::new("delete", Delete, Some(CONTEXT)),
138        KeyBinding::new("shift-delete", Delete, Some(CONTEXT)),
139        #[cfg(target_os = "macos")]
140        KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
141        #[cfg(target_os = "macos")]
142        KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
143        #[cfg(target_os = "macos")]
144        KeyBinding::new("alt-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
145        #[cfg(not(target_os = "macos"))]
146        KeyBinding::new("ctrl-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
147        #[cfg(target_os = "macos")]
148        KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)),
149        #[cfg(not(target_os = "macos"))]
150        KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)),
151        KeyBinding::new(
152            "enter",
153            Enter {
154                secondary: false,
155                shift: false,
156            },
157            Some(CONTEXT),
158        ),
159        KeyBinding::new(
160            "shift-enter",
161            Enter {
162                secondary: false,
163                shift: true,
164            },
165            Some(CONTEXT),
166        ),
167        KeyBinding::new(
168            "secondary-enter",
169            Enter {
170                secondary: true,
171                shift: false,
172            },
173            Some(CONTEXT),
174        ),
175        KeyBinding::new("escape", Escape, Some(CONTEXT)),
176        KeyBinding::new("up", MoveUp, Some(CONTEXT)),
177        KeyBinding::new("down", MoveDown, Some(CONTEXT)),
178        KeyBinding::new("left", MoveLeft, Some(CONTEXT)),
179        KeyBinding::new("right", MoveRight, Some(CONTEXT)),
180        KeyBinding::new("pageup", MovePageUp, Some(CONTEXT)),
181        KeyBinding::new("pagedown", MovePageDown, Some(CONTEXT)),
182        KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
183        KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
184        #[cfg(target_os = "macos")]
185        KeyBinding::new("cmd-]", Indent, Some(CONTEXT)),
186        #[cfg(not(target_os = "macos"))]
187        KeyBinding::new("ctrl-]", Indent, Some(CONTEXT)),
188        #[cfg(target_os = "macos")]
189        KeyBinding::new("cmd-[", Outdent, Some(CONTEXT)),
190        #[cfg(not(target_os = "macos"))]
191        KeyBinding::new("ctrl-[", Outdent, Some(CONTEXT)),
192        KeyBinding::new("shift-left", SelectLeft, Some(CONTEXT)),
193        KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
194        KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
195        KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
196        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
197        KeyBinding::new("shift-alt-left", SelectLeft, Some(CONTEXT)),
198        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
199        KeyBinding::new("shift-alt-right", SelectRight, Some(CONTEXT)),
200        // Avoid Ctrl+Alt+arrows on Linux, where desktops may reserve them.
201        #[cfg(target_os = "macos")]
202        KeyBinding::new("cmd-alt-up", AddCursorAbove, Some(CONTEXT)),
203        #[cfg(target_os = "macos")]
204        KeyBinding::new("cmd-alt-down", AddCursorBelow, Some(CONTEXT)),
205        #[cfg(target_os = "windows")]
206        KeyBinding::new("ctrl-alt-up", AddCursorAbove, Some(CONTEXT)),
207        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
208        KeyBinding::new("shift-alt-up", AddCursorAbove, Some(CONTEXT)),
209        #[cfg(target_os = "windows")]
210        KeyBinding::new("ctrl-alt-down", AddCursorBelow, Some(CONTEXT)),
211        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
212        KeyBinding::new("shift-alt-down", AddCursorBelow, Some(CONTEXT)),
213        KeyBinding::new("home", MoveHome, Some(CONTEXT)),
214        KeyBinding::new("end", MoveEnd, Some(CONTEXT)),
215        #[cfg(not(target_os = "macos"))]
216        KeyBinding::new("ctrl-home", MoveToStart, Some(CONTEXT)),
217        #[cfg(not(target_os = "macos"))]
218        KeyBinding::new("ctrl-end", MoveToEnd, Some(CONTEXT)),
219        #[cfg(not(target_os = "macos"))]
220        KeyBinding::new("ctrl-shift-home", SelectToStart, Some(CONTEXT)),
221        #[cfg(not(target_os = "macos"))]
222        KeyBinding::new("ctrl-shift-end", SelectToEnd, Some(CONTEXT)),
223        KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
224        KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
225        #[cfg(target_os = "macos")]
226        KeyBinding::new("ctrl-shift-a", SelectToStartOfLine, Some(CONTEXT)),
227        #[cfg(target_os = "macos")]
228        KeyBinding::new("ctrl-shift-e", SelectToEndOfLine, Some(CONTEXT)),
229        #[cfg(target_os = "macos")]
230        KeyBinding::new("shift-cmd-left", SelectToStartOfLine, Some(CONTEXT)),
231        #[cfg(target_os = "macos")]
232        KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
233        #[cfg(any(target_os = "macos", target_os = "linux"))]
234        KeyBinding::new("alt-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
235        #[cfg(not(target_os = "macos"))]
236        KeyBinding::new("ctrl-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
237        #[cfg(any(target_os = "macos", target_os = "linux"))]
238        KeyBinding::new("alt-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
239        #[cfg(not(target_os = "macos"))]
240        KeyBinding::new("ctrl-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
241        #[cfg(target_os = "macos")]
242        KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
243        #[cfg(target_os = "macos")]
244        KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
245        #[cfg(not(target_os = "macos"))]
246        KeyBinding::new("ctrl-a", SelectAll, Some(CONTEXT)),
247        #[cfg(target_os = "macos")]
248        KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
249        #[cfg(not(target_os = "macos"))]
250        KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
251        #[cfg(target_os = "macos")]
252        KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
253        #[cfg(not(target_os = "macos"))]
254        KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
255        #[cfg(target_os = "macos")]
256        KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
257        #[cfg(not(target_os = "macos"))]
258        KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
259        #[cfg(target_os = "macos")]
260        KeyBinding::new("ctrl-a", MoveHome, Some(CONTEXT)),
261        #[cfg(target_os = "macos")]
262        KeyBinding::new("cmd-left", MoveHome, Some(CONTEXT)),
263        #[cfg(target_os = "macos")]
264        KeyBinding::new("ctrl-e", MoveEnd, Some(CONTEXT)),
265        #[cfg(target_os = "macos")]
266        KeyBinding::new("cmd-right", MoveEnd, Some(CONTEXT)),
267        #[cfg(target_os = "macos")]
268        KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
269        #[cfg(target_os = "macos")]
270        KeyBinding::new("cmd-shift-z", Redo, Some(CONTEXT)),
271        #[cfg(target_os = "macos")]
272        KeyBinding::new("cmd-up", MoveToStart, Some(CONTEXT)),
273        #[cfg(target_os = "macos")]
274        KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
275        #[cfg(target_os = "macos")]
276        KeyBinding::new("alt-left", MoveToPreviousWord, Some(CONTEXT)),
277        #[cfg(target_os = "macos")]
278        KeyBinding::new("alt-right", MoveToNextWord, Some(CONTEXT)),
279        #[cfg(not(target_os = "macos"))]
280        KeyBinding::new("ctrl-left", MoveToPreviousWord, Some(CONTEXT)),
281        #[cfg(not(target_os = "macos"))]
282        KeyBinding::new("ctrl-right", MoveToNextWord, Some(CONTEXT)),
283        #[cfg(target_os = "macos")]
284        KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
285        #[cfg(target_os = "macos")]
286        KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
287        #[cfg(not(target_os = "macos"))]
288        KeyBinding::new("ctrl-z", Undo, Some(CONTEXT)),
289        #[cfg(not(target_os = "macos"))]
290        KeyBinding::new("ctrl-y", Redo, Some(CONTEXT)),
291        #[cfg(target_os = "macos")]
292        KeyBinding::new("cmd-.", ToggleCodeActions, Some(CONTEXT)),
293        #[cfg(not(target_os = "macos"))]
294        KeyBinding::new("ctrl-.", ToggleCodeActions, Some(CONTEXT)),
295        #[cfg(target_os = "macos")]
296        KeyBinding::new("cmd-f", Search, Some(CONTEXT)),
297        #[cfg(not(target_os = "macos"))]
298        KeyBinding::new("ctrl-f", Search, Some(CONTEXT)),
299        #[cfg(target_os = "macos")]
300        KeyBinding::new("cmd-shift-f", Replace, Some(CONTEXT)),
301        #[cfg(not(target_os = "macos"))]
302        KeyBinding::new("ctrl-h", Replace, Some(CONTEXT)),
303    ]);
304}
305
306/// A mouse position resolved for a columnar (block) selection.
307///
308/// A position past the end of a short row has no glyph to land on, so `offset` clips to
309/// that row's end and `columns_past_line_end` keeps what the clip dropped. Together they
310/// say which column the pointer really reached, which is what a block spanning rows of
311/// different lengths has to be measured in.
312#[derive(Debug, Clone, Copy, PartialEq, Eq)]
313pub(super) struct ColumnarPoint {
314    offset: usize,
315    columns_past_line_end: usize,
316}
317
318impl ColumnarPoint {
319    pub(super) fn new(offset: usize, columns_past_line_end: usize) -> Self {
320        Self {
321            offset,
322            columns_past_line_end,
323        }
324    }
325}
326
327/// The shared text-editing engine behind [`crate::input::InputState`],
328/// [`crate::input::TextareaState`] and [`crate::input::EditorState`].
329///
330/// `M` is the mode marker: it carries no data and only decides which methods
331/// exist, so an ordinary input cannot reach the editor's language features.
332///
333/// The three states are type aliases of this one, which is why this name is
334/// public: an alias is only as usable as the type behind it, so hiding this
335/// would leave `InputState` unable to do anything. Prefer naming the aliases
336/// — write `InputState`, not `InputBaseState<InputMode>`.
337pub struct InputBaseState<M: InputModeKind> {
338    /// State only this mode needs. See [`InputModeKind::Extras`].
339    pub(crate) extras: M::Extras,
340    pub(super) focus_handle: FocusHandle,
341    pub(super) mode: LayoutMode,
342    pub(super) text: Rope,
343    pub(super) display_map: DisplayMap,
344    pub(super) undo_manager: UndoManager,
345    pub(super) search_session: super::SearchSession,
346    /// Advances every time search is explicitly invoked. See
347    /// [`InputBaseState::search_activation_revision`].
348    pub(super) search_activation_revision: u64,
349    pub(super) searchable: bool,
350    pub(super) replaceable: bool,
351    pub(super) soft_wrap: bool,
352    pub(super) wrapping_indent: WrappingIndent,
353    pub(super) scroll_beyond_last_line: Option<usize>,
354    pub(super) cursor_surrounding_lines: Option<usize>,
355    pub(super) blink_cursor: Entity<BlinkCursor>,
356    pub(super) loading: bool,
357    /// The cursors and selections.
358    ///
359    /// Always contains at least one selection where index 0 is the active cursor.
360    pub(super) selections: Selections,
361    /// Range for save the selected word, use to keep word range when drag move.
362    pub(super) selected_word_range: Option<CursorSelection>,
363    /// The marked range is the temporary insert text on IME typing.
364    pub(super) ime_marked_range: Option<CursorSelection>,
365    pub(super) last_layout: Option<LastLayout>,
366    pub(super) last_cursor: Option<usize>,
367    /// The input container bounds
368    pub(super) input_bounds: Bounds<Pixels>,
369    /// The text bounds
370    pub(super) last_bounds: Option<Bounds<Pixels>>,
371    pub(super) last_selected_range: Option<CursorSelection>,
372    pub(super) selecting: bool,
373    /// Anchor point of an in-progress columnar (block) selection.
374    pub(super) column_select_start: Option<ColumnarPoint>,
375    pub(crate) disabled: bool,
376    pub(crate) readonly: bool,
377    pub(crate) text_align: TextAlign,
378    pub(super) masked: bool,
379    pub(super) clean_on_escape: bool,
380    pub(super) submit_on_enter: bool,
381    pub(super) show_whitespaces: bool,
382    /// This flag tells the renderer to prefer the end of the current visual line.
383    pub(crate) cursor_line_end_affinity: bool,
384    pub(super) pattern: Option<regex::Regex>,
385    pub(super) validate: Option<Box<dyn Fn(&str, &mut App) -> bool + 'static>>,
386    /// The step strategy for [`super::NumberInput`] to increment/decrement.
387    /// See [`Self::step`] and [`Self::step_by`].
388    pub(crate) number_step: Option<NumberStep>,
389    /// The minimum value for [`super::NumberInput`]. See [`Self::min`].
390    pub(crate) number_min: Option<f64>,
391    /// The maximum value for [`super::NumberInput`]. See [`Self::max`].
392    pub(crate) number_max: Option<f64>,
393    pub(crate) scroll_handle: ScrollHandle,
394    /// The deferred scroll offset to apply on next layout.
395    pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,
396    /// The size of the scrollable content.
397    pub(crate) scroll_size: gpui::Size<Pixels>,
398    pub(super) editor_scrollbar_snapshot: Cell<Option<EditorScrollbarSnapshot>>,
399    pub(super) editor_paddings: Edges<Pixels>,
400    /// The style this state paints with: what was projected onto it, with
401    /// every colour left unset resolved from the palette that is current. It
402    /// is rebuilt at the top of every render, which is what keeps it current
403    /// when the palette changes after the state was built.
404    pub(super) editor_style: InputEditorStyle,
405    /// What a consumer projected, kept verbatim so that resolution never
406    /// consumes its own output: resolving in place would fill the unset
407    /// colours once and then never see them as unset again, which is the same
408    /// freeze in a different place.
409    projected_editor_style: InputEditorStyle,
410
411    /// The mask pattern for formatting the input text
412    pub(crate) mask_pattern: MaskPattern,
413    /// Whether the `mask_pattern` was explicitly set (via [`Self::mask_pattern`]
414    /// or [`Self::set_mask_pattern`]), to let [`super::NumberInput`] only apply
415    /// its default mask when the user has not made an explicit choice.
416    pub(super) mask_pattern_set: bool,
417    pub(super) placeholder: SharedString,
418
419    /// Diagnostic currently requested by pointer hover; applications render it.
420    pub(super) diagnostic_popover: Option<Rc<crate::input::DiagnosticEntry>>,
421
422    context_menu_handler: Option<
423        Rc<dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App)>,
424    >,
425    pending_context_menu: Option<(Point<Pixels>, usize)>,
426
427    /// Whether the context menu that shows on right-click is enabled.
428    ///
429    pub(super) enable_context_menu: bool,
430
431    /// A flag to indicate if we are currently inserting a completion item.
432    pub(super) completion_inserting: bool,
433    pub(super) overlay_action_handler: Option<
434        Rc<
435            dyn Fn(
436                super::InputOverlayKind,
437                Box<dyn Action>,
438                &mut Window,
439                &mut Context<InputBaseState<M>>,
440            ) -> bool,
441        >,
442    >,
443
444    /// A flag to indicate if we have a pending update to the text.
445    ///
446    /// If true, will call some update (for example LSP, Syntax Highlight) before render.
447    _pending_update: bool,
448    /// A flag to indicate if we should ignore the next completion event.
449    pub(super) silent_replace_text: bool,
450    /// A flag to indicate if we should emit InputEvents.
451    pub(super) emit_events: bool,
452
453    _subscriptions: Vec<Subscription>,
454
455    pub(super) auto_scroll: AutoScroll,
456}
457
458/// Read-only styling data exposed to presentation facades.
459///
460/// The fields are private and read through the methods below, so that a new
461/// one can be added without breaking the facades.
462#[derive(Clone)]
463pub struct InputPresentation {
464    focus_handle: FocusHandle,
465    disabled: bool,
466    readonly: bool,
467    loading: bool,
468    masked: bool,
469    multi_line: bool,
470    code_editor: bool,
471    text_align: TextAlign,
472    placeholder: SharedString,
473    mask_placeholder: Option<String>,
474}
475
476impl InputPresentation {
477    pub fn focus_handle(&self) -> &FocusHandle {
478        &self.focus_handle
479    }
480
481    pub fn is_disabled(&self) -> bool {
482        self.disabled
483    }
484
485    pub fn is_readonly(&self) -> bool {
486        self.readonly
487    }
488
489    /// Returns true if the user is allowed to change the text.
490    ///
491    /// See also: [`InputBaseState::is_editable`].
492    pub fn is_editable(&self) -> bool {
493        !self.disabled && !self.readonly
494    }
495
496    pub fn is_loading(&self) -> bool {
497        self.loading
498    }
499
500    pub fn is_masked(&self) -> bool {
501        self.masked
502    }
503
504    pub fn is_multi_line(&self) -> bool {
505        self.multi_line
506    }
507
508    pub fn is_code_editor(&self) -> bool {
509        self.code_editor
510    }
511
512    pub fn text_align(&self) -> TextAlign {
513        self.text_align
514    }
515
516    pub fn placeholder(&self) -> &SharedString {
517        &self.placeholder
518    }
519
520    /// The placeholder derived from the mask pattern, e.g.: `(___) ___-____`.
521    pub fn mask_placeholder(&self) -> Option<&str> {
522        self.mask_placeholder.as_deref()
523    }
524}
525
526impl<M: InputModeKind> EventEmitter<InputEvent> for InputBaseState<M> {}
527
528impl<M: InputModeKind> InputBaseState<M> {
529    #[doc(hidden)]
530    pub fn cursor_layout(&self) -> Option<(Bounds<Pixels>, Pixels)> {
531        let layout = self.last_layout.as_ref()?;
532        Some((layout.cursor_bounds?, layout.line_height))
533    }
534
535    pub fn input_bounds(&self) -> Bounds<Pixels> {
536        self.input_bounds
537    }
538
539    pub fn text_bounds(&self) -> Option<Bounds<Pixels>> {
540        self.last_bounds
541    }
542
543    pub fn diagnostic_popover(&self) -> Option<Rc<crate::input::DiagnosticEntry>> {
544        self.diagnostic_popover.clone()
545    }
546
547    pub fn presentation(&self) -> InputPresentation {
548        InputPresentation {
549            focus_handle: self.focus_handle.clone(),
550            disabled: self.disabled,
551            readonly: self.readonly,
552            loading: self.loading,
553            masked: self.masked,
554            multi_line: self.is_multi_line(),
555            code_editor: self.is_code_editor(),
556            text_align: self.text_align,
557            placeholder: self.placeholder.clone(),
558            mask_placeholder: self.mask_pattern.placeholder(),
559        }
560    }
561
562    /// Whether this input spans more than one line.
563    ///
564    /// Answered by the mode marker, which is fixed when the state is built.
565    /// [`LayoutMode`] holds the row counts and growth policy, not the kind.
566    #[inline]
567    /// Whether this input paints scrollbars.
568    ///
569    /// Only a multi-line input can scroll: a single-line input keeps its
570    /// caret in view by moving its own offset, and never has a viewport a
571    /// user could drag. Adding the editor scrollbar to every input put a
572    /// thumb inside every text field, which is a control the field does not
573    /// have.
574    pub(crate) fn shows_scrollbar(&self) -> bool {
575        self.is_multi_line()
576    }
577
578    pub fn is_multi_line(&self) -> bool {
579        M::MULTI_LINE
580    }
581
582    /// Whether this input is a single-line text field. See [`Self::is_multi_line`].
583    #[inline]
584    pub fn is_single_line(&self) -> bool {
585        !M::MULTI_LINE
586    }
587
588    /// Whether this input is a source-code editor.
589    #[inline]
590    pub fn is_code_editor(&self) -> bool {
591        M::CODE_EDITOR
592    }
593
594    /// Whether the user is allowed to copy the selection out.
595    ///
596    /// A masked input keeps its value out of the clipboard.
597    pub fn is_copyable(&self) -> bool {
598        self.selections.iter().any(|sel| !sel.is_empty()) && !self.masked
599    }
600
601    pub fn context_menu_capabilities(&self) -> InputContextMenuCapabilities {
602        let (go_to_definition, code_actions) = self.extras.context_menu_capabilities();
603        InputContextMenuCapabilities::new()
604            .disabled(self.disabled)
605            .readonly(self.readonly)
606            .code_editor(self.is_code_editor())
607            .selection(!self.active_selection().is_empty())
608            .masked(self.masked)
609            .go_to_definition(go_to_definition)
610            .code_actions(code_actions)
611    }
612
613    pub fn set_text_align(&mut self, text_align: TextAlign, cx: &mut Context<Self>) {
614        if !self.is_single_line() || self.text_align == text_align {
615            return;
616        }
617
618        self.text_align = text_align;
619        cx.notify();
620    }
621
622    /// Flip the password mask.
623    ///
624    /// Setting the mask is a single-line method, but flipping it stays here:
625    /// the reveal button is rendered from the generic path, and it can only be
626    /// switched on through [`crate::input::InputState`] anyway.
627    pub fn toggle_masked(&mut self, _: &mut Window, cx: &mut Context<Self>) {
628        self.masked = !self.masked;
629        cx.notify();
630    }
631
632    pub fn on_context_menu(
633        &mut self,
634        handler: Rc<
635            dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App),
636        >,
637    ) {
638        self.context_menu_handler = Some(handler);
639    }
640
641    /// Build the engine. Each mode's own `new` sets its layout on top of this.
642    fn new_in_mode(window: &mut Window, cx: &mut Context<Self>) -> Self {
643        let focus_handle = cx.focus_handle().tab_stop(true);
644        let blink_cursor = cx.new(|_| BlinkCursor::new());
645        let undo_manager = UndoManager::new();
646
647        let _subscriptions = vec![
648            // Key bindings can consume events before on_key_down. Observe input
649            // before action dispatch so every keystroke resets the blink delay.
650            cx.intercept_keystrokes({
651                let focus_handle = focus_handle.clone();
652                let blink_cursor = blink_cursor.downgrade();
653                move |_, window, cx| {
654                    if focus_handle.is_focused(window) {
655                        _ = blink_cursor.update(cx, |cursor, cx| cursor.pause(cx));
656                    }
657                }
658            }),
659            // Observe the blink cursor to repaint the view when it changes.
660            cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
661            // Blink the cursor when the window is active, pause when it's not.
662            cx.observe_window_activation(window, |input, window, cx| {
663                if window.is_window_active() {
664                    let focus_handle = input.focus_handle.clone();
665                    if focus_handle.is_focused(window) {
666                        input.blink_cursor.update(cx, |blink_cursor, cx| {
667                            blink_cursor.start(cx);
668                        });
669                    }
670                }
671            }),
672            cx.on_focus(&focus_handle, window, Self::on_focus),
673            cx.on_blur(&focus_handle, window, Self::on_blur),
674        ];
675
676        let text_style = window.text_style();
677
678        Self {
679            extras: M::Extras::default(),
680            focus_handle: focus_handle.clone(),
681            text: "".into(),
682            display_map: DisplayMap::new(text_style.font(), window.rem_size(), None),
683            search_session: super::SearchSession::default(),
684            search_activation_revision: 0,
685            searchable: false,
686            replaceable: true,
687            soft_wrap: true,
688            wrapping_indent: WrappingIndent::default(),
689            scroll_beyond_last_line: None,
690            cursor_surrounding_lines: None,
691            blink_cursor,
692            undo_manager,
693            selections: Selections::default(),
694            selected_word_range: None,
695            ime_marked_range: None,
696            input_bounds: Bounds::default(),
697            selecting: false,
698            disabled: false,
699            readonly: false,
700            text_align: TextAlign::Left,
701            masked: false,
702            clean_on_escape: false,
703            submit_on_enter: false,
704            show_whitespaces: false,
705            loading: false,
706            pattern: None,
707            validate: None,
708            number_step: Some(NumberStep::Fixed(1.)),
709            number_min: None,
710            number_max: None,
711            mode: LayoutMode::default(),
712            last_layout: None,
713            last_bounds: None,
714            last_selected_range: None,
715            column_select_start: None,
716            last_cursor: None,
717            scroll_handle: ScrollHandle::new(),
718            scroll_size: gpui::size(px(0.), px(0.)),
719            editor_scrollbar_snapshot: Cell::new(None),
720            editor_paddings: Edges::default(),
721            deferred_scroll_offset: None,
722            placeholder: SharedString::default(),
723            mask_pattern: MaskPattern::default(),
724            mask_pattern_set: false,
725            editor_style: InputEditorStyle::default(),
726            projected_editor_style: InputEditorStyle::default(),
727            diagnostic_popover: None,
728            context_menu_handler: None,
729            pending_context_menu: None,
730            enable_context_menu: true,
731            completion_inserting: false,
732            overlay_action_handler: None,
733            silent_replace_text: false,
734            emit_events: true,
735            _subscriptions,
736            _pending_update: false,
737            cursor_line_end_affinity: false,
738            auto_scroll: AutoScroll::default(),
739        }
740    }
741
742    /// Sets whether the context menu that shows on right-click is enabled.
743    ///
744    /// The context menu is enabled by default.
745    /// This value is ignored if a custom context menu builder is defined on the input.
746    pub fn context_menu(mut self, enable: bool) -> Self {
747        self.enable_context_menu = enable;
748        self
749    }
750
751    pub fn set_context_menu_enabled(&mut self, enabled: bool) {
752        self.enable_context_menu = enabled;
753    }
754
755    /// Set whether search UI allows replacement, default is true.
756    #[doc(hidden)]
757    pub fn replaceable(mut self, allow: bool) -> Self {
758        self.replaceable = allow;
759        self
760    }
761
762    /// Set placeholder
763    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
764        self.placeholder = placeholder.into();
765        self
766    }
767
768    /// Set highlighter language for for [`LayoutMode::CodeEditor`] mode.
769    pub fn set_highlighter(
770        &mut self,
771        new_language: impl Into<SharedString>,
772        cx: &mut Context<Self>,
773    ) {
774        match &mut self.mode {
775            LayoutMode::CodeEditor {
776                language,
777                highlighter,
778                ..
779            } => {
780                language.set_name(new_language.into());
781                *highlighter.borrow_mut() = None;
782            }
783            _ => {}
784        }
785        self.refresh(cx);
786    }
787
788    fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
789        match &mut self.mode {
790            LayoutMode::CodeEditor { highlighter, .. } => {
791                *highlighter.borrow_mut() = None;
792            }
793            _ => {}
794        }
795        cx.notify();
796    }
797
798    /// Install the parser/highlighter adapter used by code-editor mode.
799    pub fn set_highlighter_factory(
800        &mut self,
801        factory: InputHighlighterFactory,
802        cx: &mut Context<Self>,
803    ) {
804        self.mode.set_highlighter_factory(factory);
805        self._pending_update = true;
806        cx.notify();
807    }
808
809    /// Install a default adapter without replacing an application-provided one.
810    pub fn ensure_highlighter_factory(&mut self, factory: InputHighlighterFactory) {
811        self.mode.ensure_highlighter_factory(factory);
812    }
813
814    pub fn set_editor_style(&mut self, style: InputEditorStyle) {
815        self.editor_style = style.clone();
816        self.projected_editor_style = style;
817    }
818
819    /// Set presentation padding for multi-line text and its scrollbar layout.
820    #[doc(hidden)]
821    pub fn set_editor_paddings(&mut self, paddings: Edges<Pixels>) {
822        self.editor_paddings = paddings;
823    }
824
825    pub fn apply_highlighter_fold_candidates(
826        &mut self,
827        candidates: Vec<crate::input::FoldRange>,
828        cx: &mut Context<Self>,
829    ) {
830        if self.mode.is_folding() {
831            self.display_map.set_fold_candidates(candidates);
832        }
833        cx.notify();
834    }
835
836    #[inline]
837    pub fn diagnostics(&self) -> Option<&DiagnosticSet> {
838        self.mode.diagnostics()
839    }
840
841    #[inline]
842    pub fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
843        self.mode.diagnostics_mut()
844    }
845
846    /// Set placeholder
847    pub fn set_placeholder(
848        &mut self,
849        placeholder: impl Into<SharedString>,
850        _: &mut Window,
851        cx: &mut Context<Self>,
852    ) {
853        self.placeholder = placeholder.into();
854        cx.notify();
855    }
856
857    /// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.
858    ///
859    /// Returns:
860    ///
861    /// - The index of the line (zero-based) containing the offset.
862    /// - The index of the sub-line (zero-based) within the line containing the offset.
863    /// - The position of the offset.
864    pub(super) fn line_and_position_for_offset(
865        &self,
866        offset: usize,
867    ) -> (usize, usize, Option<Point<Pixels>>) {
868        let Some(last_layout) = &self.last_layout else {
869            return (0, 0, None);
870        };
871        let line_height = last_layout.line_height;
872
873        let mut y_offset = last_layout.visible_top;
874        for (vi, line) in last_layout.lines.iter().enumerate() {
875            let prev_lines_offset = last_layout.visible_line_byte_offsets[vi];
876            let local_offset = offset.saturating_sub(prev_lines_offset);
877            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
878                let sub_line_index = (pos.y / line_height) as usize;
879                let adjusted_pos = point(pos.x + last_layout.line_number_width, pos.y + y_offset);
880                return (vi, sub_line_index, Some(adjusted_pos));
881            }
882
883            y_offset += line.size(line_height).height;
884        }
885        (0, 0, None)
886    }
887
888    /// Set the text of the input field.
889    ///
890    /// For single-line inputs the caret is placed at the end of the text while
891    /// the view is scrolled back to the start, so a long value shows its
892    /// beginning instead of its tail (matching HTML `<input>`). Multi-line
893    /// inputs reset the selection to `0..0`.
894    pub fn set_value(
895        &mut self,
896        value: impl Into<SharedString>,
897        window: &mut Window,
898        cx: &mut Context<Self>,
899    ) {
900        self.undo_manager.set_ignoring(true);
901        self.emit_events = false;
902        self.replace_text(value, window, cx);
903        self.undo_manager.set_ignoring(false);
904        self.emit_events = true;
905
906        self.reset_selection();
907        self.reset_lsp_state();
908        self.reset_scroll_to_start();
909
910        self.undo_manager.clear();
911        cx.notify();
912    }
913
914    /// Replace the entire text content while preserving undo history.
915    ///
916    /// Unlike [`set_value`](Self::set_value), this method records the
917    /// replacement in the undo stack, allowing the user to undo/redo
918    /// the change. The selection is placed at the end of the new text
919    /// for single-line inputs, or cleared (0..0) for multi-line inputs.
920    ///
921    /// Use this when programmatically replacing the full text but the
922    /// user should still be able to undo the operation — e.g. formatting.
923    pub fn replace_all(
924        &mut self,
925        text: impl Into<SharedString>,
926        window: &mut Window,
927        cx: &mut Context<Self>,
928    ) {
929        self.replace_text(text, window, cx);
930        self.reset_selection();
931        self.reset_lsp_state();
932        self.reset_scroll_to_start();
933
934        cx.notify();
935    }
936
937    /// Perform `f` with the user-facing edit restrictions lifted.
938    ///
939    /// The `disabled` and `readonly` modes only reject the changes made by the
940    /// user, the programmatic APIs must always be able to update the text.
941    fn with_edits_allowed(&mut self, f: impl FnOnce(&mut Self)) {
942        let (was_disabled, was_readonly) = (self.disabled, self.readonly);
943        (self.disabled, self.readonly) = (false, false);
944        f(self);
945        (self.disabled, self.readonly) = (was_disabled, was_readonly);
946    }
947
948    /// Insert text at the current cursor position.
949    ///
950    /// And the cursor will be moved to the end of inserted text.
951    pub fn insert(
952        &mut self,
953        text: impl Into<SharedString>,
954        window: &mut Window,
955        cx: &mut Context<Self>,
956    ) {
957        let text: SharedString = text.into();
958        self.with_edits_allowed(|this| {
959            this.undo_manager.set_pending_intent(EditIntent::Atomic);
960            let range_utf16 = this.range_to_utf16(&(this.cursor()..this.cursor()));
961            this.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
962            let end = this.active_selection().end;
963            this.set_cursor_to(end);
964        });
965    }
966
967    /// Replace text at the current cursor position.
968    ///
969    /// And the cursor will be moved to the end of replaced text.
970    pub fn replace(
971        &mut self,
972        text: impl Into<SharedString>,
973        window: &mut Window,
974        cx: &mut Context<Self>,
975    ) {
976        let text: SharedString = text.into();
977        self.with_edits_allowed(|this| {
978            this.undo_manager.set_pending_intent(EditIntent::Atomic);
979            this.replace_text_in_range_silent(None, &text, window, cx);
980            let end = this.active_selection().end;
981            this.set_cursor_to(end);
982        });
983    }
984
985    fn replace_text(
986        &mut self,
987        text: impl Into<SharedString>,
988        window: &mut Window,
989        cx: &mut Context<Self>,
990    ) {
991        let text: SharedString = text.into();
992        self.with_edits_allowed(|this| {
993            this.undo_manager.set_pending_intent(EditIntent::Atomic);
994            let range = 0..this.text.chars().map(|c| c.len_utf16()).sum();
995            this.replace_text_in_range_silent(Some(range), &text, window, cx);
996            this.reset_highlighter(cx);
997        });
998    }
999
1000    fn reset_selection(&mut self) {
1001        self.selections.remove_all_but_active();
1002
1003        // For single-line inputs the caret is placed at the end of the text
1004        // (matching HTML `<input>`); multi-line inputs reset the selection to
1005        // `0..0`.
1006        if self.is_single_line() {
1007            let end = self.text.len();
1008            self.set_cursor_to(end);
1009        } else {
1010            self.active_selection_mut().clear();
1011        }
1012    }
1013
1014    fn reset_lsp_state(&mut self) {
1015        if self.is_code_editor() {
1016            self._pending_update = true;
1017            M::reset_language_features(self);
1018        }
1019    }
1020
1021    fn reset_scroll_to_start(&mut self) {
1022        // Move scroll to the start. For single-line the caret is at the end, so
1023        // override the cursor-follow scroll for the next painted frame to keep
1024        // the start visible; the deferred offset is consumed during that paint.
1025        self.scroll_handle.set_offset(point(px(0.), px(0.)));
1026        if self.is_single_line() {
1027            self.deferred_scroll_offset = Some(point(px(0.), px(0.)));
1028        }
1029    }
1030
1031    /// Set with disabled mode.
1032    ///
1033    /// See also: [`Self::set_disabled`].
1034    #[allow(unused)]
1035    pub(crate) fn disabled(mut self, disabled: bool) -> Self {
1036        self.disabled = disabled;
1037        self
1038    }
1039
1040    pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
1041        if self.disabled == disabled {
1042            return;
1043        }
1044
1045        self.disabled = disabled;
1046        cx.notify();
1047    }
1048
1049    /// Set with read-only mode.
1050    ///
1051    /// Unlike [`Self::disabled`], a read-only input keeps the normal appearance,
1052    /// focus, cursor, selection and copy behavior, it only rejects any change
1053    /// of the text made by the user.
1054    ///
1055    /// See also: [`Self::set_readonly`].
1056    #[allow(unused)]
1057    pub(crate) fn readonly(mut self, readonly: bool) -> Self {
1058        self.readonly = readonly;
1059        self
1060    }
1061
1062    pub fn set_readonly(&mut self, readonly: bool, cx: &mut Context<Self>) {
1063        if self.readonly == readonly {
1064            return;
1065        }
1066
1067        self.readonly = readonly;
1068        if readonly {
1069            self.search_session.replace_mode = false;
1070        }
1071        cx.notify();
1072    }
1073
1074    /// Returns true if the user is allowed to change the text.
1075    ///
1076    /// This is false when the input is `disabled` or `readonly`, the programmatic
1077    /// APIs (e.g.: [`Self::set_value`], [`Self::insert`]) are not limited by this.
1078    pub fn is_editable(&self) -> bool {
1079        !self.disabled && !self.readonly
1080    }
1081
1082    /// Set true to clear the input by pressing Escape key.
1083    pub fn clean_on_escape(mut self) -> Self {
1084        self.clean_on_escape = true;
1085        self
1086    }
1087
1088    pub fn set_clean_on_escape(&mut self, clean: bool) {
1089        self.clean_on_escape = clean;
1090    }
1091
1092    /// Set true to treat `Enter` as a submit action in multi-line mode,
1093    /// while `Shift+Enter` inserts a newline.
1094    ///
1095    /// Default is `false` (both `Enter` and `Shift+Enter` insert a newline).
1096    #[doc(hidden)]
1097    pub fn submit_on_enter(mut self, submit: bool) -> Self {
1098        self.submit_on_enter = submit;
1099        self
1100    }
1101
1102    pub fn set_submit_on_enter(&mut self, submit: bool, cx: &mut Context<Self>) {
1103        self.submit_on_enter = submit;
1104        cx.notify();
1105    }
1106
1107    /// Set whether to show whitespace characters.
1108    #[doc(hidden)]
1109    pub fn show_whitespaces(mut self, show: bool) -> Self {
1110        self.show_whitespaces = show;
1111        self
1112    }
1113
1114    /// Update whether to show whitespace characters.
1115    pub fn set_show_whitespaces(&mut self, show: bool, _: &mut Window, cx: &mut Context<Self>) {
1116        self.show_whitespaces = show;
1117        cx.notify();
1118    }
1119
1120    /// Empty rows reserved below the last line of content ("scroll
1121    /// beyond last line"), code-editor mode only. Mirrors VSCode's
1122    /// `editor.scrollBeyondLastLine` / Zed's `scroll_beyond_last_line`.
1123    ///
1124    /// - `None` (default): half the viewport, floored at
1125    ///   [`BOTTOM_MARGIN_ROWS`] line-heights.
1126    /// - `Some(0)`: no trailing space; the cursor sits flush with the
1127    ///   last row at scroll-max.
1128    /// - `Some(n)`: exactly `n` rows.
1129    pub fn scroll_beyond_last_line(mut self, rows: Option<usize>) -> Self {
1130        self.scroll_beyond_last_line = rows;
1131        self
1132    }
1133
1134    /// Update [`Self::scroll_beyond_last_line`] after construction.
1135    pub fn set_scroll_beyond_last_line(
1136        &mut self,
1137        rows: Option<usize>,
1138        _: &mut Window,
1139        cx: &mut Context<Self>,
1140    ) {
1141        if self.scroll_beyond_last_line == rows {
1142            return;
1143        }
1144        self.scroll_beyond_last_line = rows;
1145        cx.notify();
1146    }
1147
1148    /// Minimum number of lines the cursor is kept clear of the viewport's
1149    /// top/bottom edge before auto-scroll engages. Mirrors VSCode's
1150    /// `editor.cursorSurroundingLines` / Zed's `vertical_scroll_margin`.
1151    /// Orthogonal to [`Self::scroll_beyond_last_line`], which sizes the
1152    /// empty region; this controls the cursor's resting distance from the
1153    /// edge.
1154    ///
1155    /// - `None` (default): [`BOTTOM_MARGIN_ROWS`] lines, falling back to
1156    ///   one line on small viewports.
1157    /// - `Some(n)`: exactly `n` lines, clamped to half the viewport.
1158    pub fn cursor_surrounding_lines(mut self, lines: Option<usize>) -> Self {
1159        self.cursor_surrounding_lines = lines;
1160        self
1161    }
1162
1163    /// Update [`Self::cursor_surrounding_lines`] after construction.
1164    pub fn set_cursor_surrounding_lines(
1165        &mut self,
1166        lines: Option<usize>,
1167        _: &mut Window,
1168        cx: &mut Context<Self>,
1169    ) {
1170        if self.cursor_surrounding_lines == lines {
1171            return;
1172        }
1173        self.cursor_surrounding_lines = lines;
1174        cx.notify();
1175    }
1176
1177    /// Set the default value of the input field.
1178    pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {
1179        let text: SharedString = value.into();
1180        self.text = Rope::from(self.normalize_input(&text).as_ref());
1181        if let Some(diagnostics) = self.mode.diagnostics_mut() {
1182            diagnostics.reset(&self.text)
1183        }
1184        // Note: We can't call display_map.set_text here because it needs cx.
1185        // The text will be set during prepare_if_need in element.rs
1186        self._pending_update = true;
1187        self
1188    }
1189
1190    /// Return the value of the input field as an owned string.
1191    ///
1192    /// The string is materialized on each call. See [`Self::text`] for the
1193    /// [`Rope`] the state owns, which is borrowed and costs nothing to read.
1194    pub fn value(&self) -> SharedString {
1195        SharedString::new(self.text.to_string())
1196    }
1197
1198    /// Return the portion of the value within the input field that
1199    /// is selected by the user, as an owned string.
1200    ///
1201    /// The string is materialized on each call. See [`Self::selected_text`]
1202    /// for the same selection borrowed out of the [`Rope`] the state owns.
1203    pub fn selected_value(&self) -> SharedString {
1204        SharedString::new(self.selected_text().to_string())
1205    }
1206
1207    /// Return the value without mask.
1208    pub fn unmask_value(&self) -> SharedString {
1209        self.mask_pattern.unmask(&self.text.to_string()).into()
1210    }
1211
1212    /// Kept so existing render paths keep compiling.
1213    ///
1214    /// Configuration used to be collected by a facade and applied here; the
1215    /// state now configures itself, so this does nothing and can be deleted at
1216    /// the call site.
1217    #[doc(hidden)]
1218    pub fn prepare(&mut self, _: &mut Window, _: &mut Context<Self>) {}
1219
1220    /// Return the text [`Rope`] of the input field.
1221    ///
1222    /// Borrowed from the state, so reading even a large document copies
1223    /// nothing. See [`Self::value`] when an owned string is wanted.
1224    pub fn text(&self) -> &Rope {
1225        &self.text
1226    }
1227
1228    /// Return the (0-based) [`Position`] of the cursor.
1229    pub fn cursor_position(&self) -> Position {
1230        let offset = self.cursor();
1231        self.text.offset_to_position(offset)
1232    }
1233
1234    /// Set (0-based) [`Position`] of the cursor.
1235    ///
1236    /// This will move the cursor to the specified line and column, and update the selection range.
1237    pub fn set_cursor_position(
1238        &mut self,
1239        position: impl Into<Position>,
1240        window: &mut Window,
1241        cx: &mut Context<Self>,
1242    ) {
1243        let position: Position = position.into();
1244        let offset = self.text.position_to_offset(&position);
1245
1246        self.move_to(offset, None, cx);
1247        self.update_preferred_column();
1248        self.focus(window, cx);
1249    }
1250
1251    /// Focus the input field.
1252    pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1253        self.focus_handle.focus(window, cx);
1254        self.blink_cursor.update(cx, |cursor, cx| {
1255            cursor.start(cx);
1256        });
1257    }
1258
1259    /// Refresh the input, so the next render re-runs syntax highlighting and
1260    /// the LSP providers, not just a redraw.
1261    ///
1262    /// Assigning the `lsp` providers (or other render-affecting state) at
1263    /// runtime does not take effect until the text next changes. Call this
1264    /// afterwards to force the refresh on the next render.
1265    ///
1266    /// ```ignore
1267    /// input.update(cx, |state, cx| {
1268    ///     state.extras.lsp.hover_provider = Some(provider);
1269    ///     state.refresh(cx);
1270    /// });
1271    /// ```
1272    pub fn refresh(&mut self, cx: &mut Context<Self>) {
1273        self._pending_update = true;
1274        cx.notify();
1275    }
1276
1277    pub(super) fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
1278        self.undo_manager.break_transaction_coalescing();
1279        self.select_all_cursors_to(|s, sel| s.previous_boundary(sel.cursor_offset()), cx);
1280    }
1281
1282    pub(super) fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
1283        self.select_all_cursors_to(|s, sel| s.next_boundary(sel.cursor_offset()), cx);
1284    }
1285
1286    pub(super) fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
1287        if self.is_single_line() {
1288            return;
1289        }
1290        self.undo_manager.break_transaction_coalescing();
1291        self.select_all_cursors_to(
1292            |s, sel| {
1293                let offset = s
1294                    .start_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel))
1295                    .saturating_sub(1);
1296                s.previous_boundary(offset)
1297            },
1298            cx,
1299        );
1300    }
1301
1302    pub(super) fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
1303        if self.is_single_line() {
1304            return;
1305        }
1306        self.undo_manager.break_transaction_coalescing();
1307        let len = self.text.len();
1308        self.select_all_cursors_to(
1309            |s, sel| {
1310                let offset = (s.end_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel))
1311                    + 1)
1312                .min(len);
1313                s.next_boundary(offset)
1314            },
1315            cx,
1316        );
1317    }
1318
1319    pub(super) fn on_action_select_all(
1320        &mut self,
1321        _: &SelectAll,
1322        window: &mut Window,
1323        cx: &mut Context<Self>,
1324    ) {
1325        self.select_all(window, cx);
1326    }
1327
1328    pub(super) fn select_to_start(
1329        &mut self,
1330        _: &SelectToStart,
1331        _: &mut Window,
1332        cx: &mut Context<Self>,
1333    ) {
1334        self.undo_manager.break_transaction_coalescing();
1335        self.select_all_cursors_to(|_, _| 0, cx);
1336    }
1337
1338    pub(super) fn select_to_end(
1339        &mut self,
1340        _: &SelectToEnd,
1341        _: &mut Window,
1342        cx: &mut Context<Self>,
1343    ) {
1344        self.undo_manager.break_transaction_coalescing();
1345        let end = self.text.len();
1346        self.select_all_cursors_to(move |_, _| end, cx);
1347    }
1348
1349    pub(super) fn select_to_start_of_line(
1350        &mut self,
1351        _: &SelectToStartOfLine,
1352        _: &mut Window,
1353        cx: &mut Context<Self>,
1354    ) {
1355        self.undo_manager.break_transaction_coalescing();
1356        self.select_all_cursors_to(
1357            |s, sel| s.start_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1358            cx,
1359        );
1360    }
1361
1362    pub(super) fn select_to_end_of_line(
1363        &mut self,
1364        _: &SelectToEndOfLine,
1365        _: &mut Window,
1366        cx: &mut Context<Self>,
1367    ) {
1368        self.undo_manager.break_transaction_coalescing();
1369        self.select_all_cursors_to(
1370            |s, sel| s.end_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1371            cx,
1372        );
1373        // Mirrors MoveEnd: the caret belongs at the end of the visual row it is on.
1374        self.cursor_line_end_affinity = true;
1375    }
1376
1377    pub(super) fn select_to_previous_word(
1378        &mut self,
1379        _: &SelectToPreviousWordStart,
1380        _: &mut Window,
1381        cx: &mut Context<Self>,
1382    ) {
1383        self.undo_manager.break_transaction_coalescing();
1384        self.select_all_cursors_to(
1385            |s, sel| s.previous_start_of_word_at(sel.cursor_offset()),
1386            cx,
1387        );
1388    }
1389
1390    pub(super) fn select_to_next_word(
1391        &mut self,
1392        _: &SelectToNextWordEnd,
1393        _: &mut Window,
1394        cx: &mut Context<Self>,
1395    ) {
1396        self.undo_manager.break_transaction_coalescing();
1397        self.select_all_cursors_to(|s, sel| s.next_end_of_word_at(sel.cursor_offset()), cx);
1398    }
1399
1400    /// Return the start offset of the previous word.
1401    /// Return the previous start offset of the word before `offset`.
1402    pub(super) fn previous_start_of_word_at(&self, offset: usize) -> usize {
1403        if self.masked {
1404            // The mask replaces every character, so the displayed text has no
1405            // word boundaries to move or delete by. Collapse the word to the
1406            // whole text.
1407            return 0;
1408        }
1409
1410        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1411        // FIXME: Avoid to_string
1412        let left_part = self.text.slice(0..offset).to_string();
1413
1414        UnicodeSegmentation::split_word_bound_indices(left_part.as_str())
1415            .rfind(|(_, s)| !s.trim_start().is_empty())
1416            .map(|(i, _)| i)
1417            .unwrap_or(0)
1418    }
1419
1420    /// Return the next end offset of the word after `offset`.
1421    pub(super) fn next_end_of_word_at(&self, offset: usize) -> usize {
1422        if self.masked {
1423            // See `previous_start_of_word_at`.
1424            return self.text.len();
1425        }
1426
1427        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1428        let right_part = self.text.slice(offset..self.text.len()).to_string();
1429
1430        UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
1431            .find(|(_, s)| !s.trim_start().is_empty())
1432            .map(|(i, s)| offset + i + s.len())
1433            .unwrap_or(self.text.len())
1434    }
1435
1436    /// Get start of line byte offset for the given `offset`.
1437    ///
1438    /// When soft wrap is active, first press goes to visual line start,
1439    /// second press (already at visual start) goes to logical line start.
1440    pub(super) fn start_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1441        if self.is_single_line() {
1442            return 0;
1443        }
1444
1445        let row = self.text.offset_to_point(offset).row;
1446        let logical_start = self.text.line_start_offset(row);
1447
1448        if self.soft_wrap && self.is_code_editor() {
1449            let wrap_point = self
1450                .display_map
1451                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1452            if let Some(line) = self.display_map.line(row)
1453                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1454            {
1455                let visual_start = logical_start + range.start;
1456                if offset != visual_start {
1457                    return visual_start;
1458                }
1459            }
1460        }
1461
1462        logical_start
1463    }
1464
1465    /// Get end of line byte offset for the given `offset`.
1466    ///
1467    /// When soft wrap is active, first press goes to visual line end,
1468    /// second press (already at visual end) goes to logical line end.
1469    pub(super) fn end_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1470        if self.is_single_line() {
1471            return self.text.len();
1472        }
1473
1474        let row = self.text.offset_to_point(offset).row;
1475        let logical_start = self.text.line_start_offset(row);
1476        let logical_end = self.text.line_end_offset(row);
1477
1478        if self.soft_wrap && self.is_code_editor() {
1479            // Use the row the caret is drawn on: at a wrap boundary the raw offset would name
1480            // the next row, and a second End press would keep walking down instead of falling
1481            // through to the logical line end.
1482            let wrap_point = self
1483                .display_map
1484                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1485            if let Some(line) = self.display_map.line(row)
1486                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1487            {
1488                let visual_end = logical_start + range.end;
1489                if offset != visual_end {
1490                    return visual_end;
1491                }
1492            }
1493        }
1494
1495        logical_end
1496    }
1497
1498    /// Get indent string of next line.
1499    ///
1500    /// To get current and next line indent, to return more depth one.
1501    pub(super) fn indent_of_next_line(&mut self) -> String {
1502        self.indent_of_next_line_at(self.cursor())
1503    }
1504
1505    /// Get indent string of the next line, relative to the given `offset`.
1506    pub(super) fn indent_of_next_line_at(&mut self, offset: usize) -> String {
1507        if self.is_single_line() {
1508            return "".into();
1509        }
1510
1511        let mut current_indent = String::new();
1512        let mut next_indent = String::new();
1513        let line_end_affinity = self.line_end_affinity_at(offset);
1514        let current_line_start_pos = self.start_of_line_at(offset, line_end_affinity);
1515        let next_line_start_pos = self.end_of_line_at(offset, line_end_affinity);
1516        for c in self.text.slice(current_line_start_pos..).chars() {
1517            if !c.is_whitespace() {
1518                break;
1519            }
1520            if c == '\n' || c == '\r' {
1521                break;
1522            }
1523            current_indent.push(c);
1524        }
1525
1526        for c in self.text.slice(next_line_start_pos..).chars() {
1527            if !c.is_whitespace() {
1528                break;
1529            }
1530            if c == '\n' || c == '\r' {
1531                break;
1532            }
1533            next_indent.push(c);
1534        }
1535
1536        if next_indent.len() > current_indent.len() {
1537            return next_indent;
1538        }
1539
1540        // Smart indent: one extra level after lines ending with a trigger.
1541        // Skipped inside strings and comments: the trigger is literal text.
1542        if let Some(rules) = self.mode.language_config() {
1543            if self.mode.is_smart_indent()
1544                && M::editing_syntax_context(self, offset) == crate::input::SyntaxContext::Code
1545            {
1546                let line_before: String = self
1547                    .text
1548                    .slice(current_line_start_pos..offset)
1549                    .chars()
1550                    .collect();
1551                if rules.opens_indent(line_before.trim_end()) {
1552                    let tab = self.mode.tab_size();
1553                    if tab.hard_tabs {
1554                        current_indent.push('\t');
1555                    } else {
1556                        for _ in 0..tab.tab_size {
1557                            current_indent.push(' ');
1558                        }
1559                    }
1560                } else {
1561                    let line_after = self.text.slice(offset..next_line_start_pos).to_string();
1562                    if rules.closes_indent(&line_after) {
1563                        let tab = self.mode.tab_size();
1564                        if current_indent.ends_with('\t') {
1565                            current_indent.pop();
1566                        } else {
1567                            for _ in 0..tab.tab_size {
1568                                if !current_indent.ends_with(' ') {
1569                                    break;
1570                                }
1571                                current_indent.pop();
1572                            }
1573                        }
1574                    }
1575                }
1576            }
1577        }
1578
1579        current_indent
1580    }
1581
1582    /// Delete every selection as one batch. Collapsed cursors are first
1583    /// expanded to a deletion range by `collapsed_target` and non-empty
1584    /// selections delete their own range.
1585    ///
1586    /// `collapsed_intent` is the intent to record when every cursor is
1587    /// collapsed, which is what makes a run of single-character deletes undo
1588    /// as one gesture. Deleting a real selection is always atomic.
1589    fn delete_selections(
1590        &mut self,
1591        silent: bool,
1592        collapsed_intent: EditIntent,
1593        mut collapsed_target: impl FnMut(&mut Self, usize) -> Range<usize>,
1594        window: &mut Window,
1595        cx: &mut Context<Self>,
1596    ) {
1597        if !self.is_editable() {
1598            return;
1599        }
1600        let cursors: Vec<CursorSelection> = self.selections.iter().copied().collect();
1601        let intent = if cursors.iter().all(|sel| sel.is_empty()) {
1602            collapsed_intent
1603        } else {
1604            EditIntent::Atomic
1605        };
1606        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(cursors.len());
1607        for sel in &cursors {
1608            let range = if sel.is_empty() {
1609                collapsed_target(self, sel.cursor_offset())
1610            } else {
1611                sel.start..sel.end
1612            };
1613            let (start, end) = (range.start.min(range.end), range.start.max(range.end));
1614            let mut selection = *sel;
1615            selection.start = start;
1616            selection.end = end;
1617            new_selections.push(selection);
1618        }
1619        // Capture the user's selections before expanding or merging deletion
1620        // ranges. The edit ranges cannot reconstruct their original carets.
1621        self.undo_manager.begin_transaction_with(intent);
1622        self.undo_manager
1623            .record_selections(cursors.clone(), cursors.clone());
1624        self.selections.replace_all(new_selections);
1625        self.undo_manager.set_pending_intent(intent);
1626
1627        let was_silent = self.silent_replace_text;
1628        self.silent_replace_text = silent;
1629        self.replace_text_in_range(None, "", window, cx);
1630        self.silent_replace_text = was_silent;
1631        self.undo_manager
1632            .record_selections(cursors, self.selections.iter().copied().collect());
1633        self.undo_manager.commit_transaction();
1634        self.pause_blink_cursor(cx);
1635    }
1636
1637    /// Decide pairing against the pre-edit text, before an opening quote can
1638    /// change its own syntax context. Ordinary characters never query syntax.
1639    fn auto_close_target(&self, range: &Range<usize>, text: &str) -> Option<(usize, SharedString)> {
1640        if self.silent_replace_text
1641            || self.ime_marked_range.is_some()
1642            || !self.selections.is_single()
1643            || !self.active_selection().is_empty()
1644            || !range.is_empty()
1645            || range.start != self.cursor()
1646            || text.chars().count() != 1
1647            || !self.mode.is_auto_close()
1648        {
1649            return None;
1650        }
1651        let rules = self.mode.language_config()?;
1652        if self
1653            .text
1654            .chars_at(range.start)
1655            .next()
1656            .is_some_and(|c| !c.is_whitespace() && !rules.auto_close_before.contains(c))
1657        {
1658            return None;
1659        }
1660        for (open, close, not_in) in rules.closing_pairs() {
1661            let Some(prefix) = open.strip_suffix(text) else {
1662                continue;
1663            };
1664            if !self.text_before_matches(range.start, prefix) {
1665                continue;
1666            }
1667            let start = range.start - prefix.len();
1668            if open == close
1669                && (self.is_escaped_at(start)
1670                    || self
1671                        .text
1672                        .chars_at(start)
1673                        .reversed()
1674                        .next()
1675                        .is_some_and(|c| c.is_alphanumeric() || c == '_'))
1676            {
1677                continue;
1678            }
1679            if not_in.contains(&M::editing_syntax_context(self, start)) {
1680                continue;
1681            }
1682            return Some((open.len(), close.into()));
1683        }
1684        None
1685    }
1686
1687    fn text_before_matches(&self, offset: usize, text: &str) -> bool {
1688        offset >= text.len()
1689            && self.text.is_char_boundary(offset - text.len())
1690            && self.text.slice(offset - text.len()..offset) == text
1691    }
1692
1693    fn text_after_matches(&self, offset: usize, text: &str) -> bool {
1694        offset + text.len() <= self.text.len()
1695            && self.text.is_char_boundary(offset + text.len())
1696            && self.text.slice(offset..offset + text.len()) == text
1697    }
1698
1699    fn is_escaped_at(&self, offset: usize) -> bool {
1700        self.text
1701            .chars_at(offset)
1702            .reversed()
1703            .take_while(|c| *c == '\\')
1704            .count()
1705            % 2
1706            == 1
1707    }
1708
1709    /// Cursor target when a typed closer should skip over an existing one.
1710    ///
1711    /// Returns `Some(offset)` when `new_text` is a single closer from the
1712    /// active [`LanguageConfig`](crate::input::language_config::LanguageConfig) that already follows the collapsed cursor and is
1713    /// not escaped. The caller then moves the cursor without editing text or
1714    /// history. `None` means insert normally.
1715    fn skip_over_target(&self, new_text: &str) -> Option<usize> {
1716        if !self.mode.is_auto_close() || new_text.chars().count() != 1 {
1717            return None;
1718        }
1719        let rules = self.mode.language_config()?;
1720        let cursor = self.cursor();
1721        for (open, close, not_in) in rules.closing_pairs() {
1722            for (index, _) in close.char_indices() {
1723                if !close[index..].starts_with(new_text)
1724                    || !self.text_before_matches(cursor, &close[..index])
1725                    || !self.text_after_matches(cursor, &close[index..])
1726                {
1727                    continue;
1728                }
1729                let context = M::editing_syntax_context(self, cursor);
1730                let generated =
1731                    self.mode
1732                        .auto_closed_pairs()
1733                        .contains_closer(cursor, index, close.len());
1734                if !generated
1735                    && not_in.contains(&context)
1736                    && !(context == crate::input::SyntaxContext::String && open == close)
1737                {
1738                    continue;
1739                }
1740                if self.is_escaped_at(cursor - index) {
1741                    continue;
1742                }
1743                return Some(cursor + new_text.len());
1744            }
1745        }
1746        None
1747    }
1748
1749    pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
1750        // Nothing to delete at the start of the text. Propagate so an ancestor
1751        // (e.g. a command palette navigating back a level) can act on it.
1752        // This is harmless when nothing upstream is bound to backspace. With multiple
1753        // cursors the others can still delete, so only the lone-cursor case
1754        // propagates.
1755        if self.selections.is_single() && self.active_selection().is_empty() && self.cursor() == 0 {
1756            cx.propagate();
1757            return;
1758        }
1759
1760        // Pair deletion follows automatic closing rules, independently of
1761        // structural brackets used by Enter.
1762        if self.mode.is_auto_close()
1763            && self.selections.is_single()
1764            && self.active_selection().is_empty()
1765        {
1766            let off = self.cursor();
1767            let deletion = self.mode.language_config().and_then(|rules| {
1768                rules.closing_pairs().find_map(|(open, close, not_in)| {
1769                    if !self.text_before_matches(off, open) || !self.text_after_matches(off, close)
1770                    {
1771                        return None;
1772                    }
1773                    let start = off - open.len();
1774                    let generated = self
1775                        .mode
1776                        .auto_closed_pairs()
1777                        .contains(start..off, off..off + close.len());
1778                    if self.is_escaped_at(start)
1779                        || (!generated && not_in.contains(&M::editing_syntax_context(self, start)))
1780                    {
1781                        return None;
1782                    }
1783                    Some(start..off + close.len())
1784                })
1785            });
1786            if let Some(range) = deletion {
1787                let utf16 = self.range_to_utf16(&range);
1788                self.replace_text_in_range_silent(Some(utf16), "", window, cx);
1789                return;
1790            }
1791        }
1792
1793        self.delete_selections(
1794            false,
1795            EditIntent::Backspace,
1796            |s, offset| s.previous_boundary(offset)..offset,
1797            window,
1798            cx,
1799        );
1800    }
1801
1802    pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
1803        self.delete_selections(
1804            false,
1805            EditIntent::DeleteForward,
1806            |s, offset| offset..s.next_boundary(offset),
1807            window,
1808            cx,
1809        );
1810    }
1811
1812    pub(super) fn delete_to_beginning_of_line(
1813        &mut self,
1814        _: &DeleteToBeginningOfLine,
1815        window: &mut Window,
1816        cx: &mut Context<Self>,
1817    ) {
1818        self.delete_selections(
1819            true,
1820            EditIntent::Atomic,
1821            |s, offset| {
1822                let mut start = s.start_of_line_at(offset, s.line_end_affinity_at(offset));
1823                if start == offset {
1824                    start = start.saturating_sub(1);
1825                }
1826                start..offset
1827            },
1828            window,
1829            cx,
1830        );
1831    }
1832
1833    pub(super) fn delete_to_end_of_line(
1834        &mut self,
1835        _: &DeleteToEndOfLine,
1836        window: &mut Window,
1837        cx: &mut Context<Self>,
1838    ) {
1839        self.delete_selections(
1840            true,
1841            EditIntent::Atomic,
1842            |s, offset| {
1843                let mut end = s.end_of_line_at(offset, s.line_end_affinity_at(offset));
1844                if end == offset {
1845                    end = (end + 1).clamp(0, s.text.len());
1846                }
1847                offset..end
1848            },
1849            window,
1850            cx,
1851        );
1852    }
1853
1854    pub(super) fn delete_previous_word(
1855        &mut self,
1856        _: &DeleteToPreviousWordStart,
1857        window: &mut Window,
1858        cx: &mut Context<Self>,
1859    ) {
1860        self.delete_selections(
1861            true,
1862            EditIntent::Atomic,
1863            |s, offset| s.previous_start_of_word_at(offset)..offset,
1864            window,
1865            cx,
1866        );
1867    }
1868
1869    pub(super) fn delete_next_word(
1870        &mut self,
1871        _: &DeleteToNextWordEnd,
1872        window: &mut Window,
1873        cx: &mut Context<Self>,
1874    ) {
1875        self.delete_selections(
1876            true,
1877            EditIntent::Atomic,
1878            |s, offset| offset..s.next_end_of_word_at(offset),
1879            window,
1880            cx,
1881        );
1882    }
1883
1884    pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {
1885        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
1886            return;
1887        }
1888
1889        // Clear inline completion on enter (user chose not to accept it)
1890        if M::has_inline_completion(self) {
1891            M::clear_inline_completion(self, cx);
1892        }
1893
1894        // In multi-line mode with `submit_on_enter` enabled, a plain `Enter`
1895        // (without Shift) is treated as submit: propagate the action and emit
1896        // PressEnter without inserting a newline. `Shift+Enter` still inserts
1897        // a newline.
1898        let insert_newline = self.is_multi_line() && (!self.submit_on_enter || action.shift);
1899
1900        if insert_newline {
1901            if !self.selections.is_single() {
1902                // Insert a newline (with per-line indent) at every cursor.
1903                self.selections.merge_overlapping();
1904                let selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
1905                let mut edits: Vec<(Range<usize>, String)> = Vec::with_capacity(selections.len());
1906                for sel in &selections {
1907                    let indent = if self.is_code_editor() {
1908                        self.indent_of_next_line_at(sel.cursor_offset())
1909                    } else {
1910                        String::new()
1911                    };
1912                    edits.push((sel.start..sel.end, format!("\n{}", indent)));
1913                }
1914                edits.sort_by_key(|(range, _)| range.start);
1915                self.replace_text_in_ranges(&edits, window, cx);
1916                self.pause_blink_cursor(cx);
1917            } else {
1918                // Bracket split: Enter between `{|}` produces `{\n  \n}`
1919                // with the cursor on the middle line. Controlled by smart_indent,
1920                // independently of automatic closing.
1921                // Skipped inside strings and comments: splitting there would
1922                // restructure literal text.
1923                let mut split = false;
1924                if self.is_code_editor() && self.active_selection().is_empty() {
1925                    if let Some(rules) = self.mode.language_config() {
1926                        if self.mode.is_smart_indent()
1927                            && M::editing_syntax_context(self, self.cursor())
1928                                == crate::input::SyntaxContext::Code
1929                        {
1930                            let off = self.cursor();
1931                            let is_pair = rules.brackets.iter().any(|p| {
1932                                !p.open.is_empty()
1933                                    && !p.close.is_empty()
1934                                    && p.open != p.close
1935                                    && self.text_before_matches(off, p.open.as_ref())
1936                                    && self.text_after_matches(off, p.close.as_ref())
1937                            });
1938                            if is_pair {
1939                                // Base indent: leading whitespace only.
1940                                let line_end_affinity = self.line_end_affinity_at(off);
1941                                let line_start = self.start_of_line_at(off, line_end_affinity);
1942                                let mut indent = String::new();
1943                                for c in self.text.slice(line_start..).chars() {
1944                                    if !c.is_whitespace() || c == '\n' || c == '\r' {
1945                                        break;
1946                                    }
1947                                    indent.push(c);
1948                                }
1949                                // Extra level only with smart indent enabled.
1950                                let mut inner = indent.clone();
1951                                if self.mode.is_smart_indent() {
1952                                    let tab = self.mode.tab_size();
1953                                    if tab.hard_tabs {
1954                                        inner.push('\t');
1955                                    } else {
1956                                        for _ in 0..tab.tab_size {
1957                                            inner.push(' ');
1958                                        }
1959                                    }
1960                                }
1961                                let new_line_text = format!("\n{inner}\n{indent}");
1962                                self.replace_text_in_range_silent(None, &new_line_text, window, cx);
1963                                self.set_cursor_to(off + 1 + inner.len());
1964                                self.update_preferred_column();
1965                                let cursors = self.selections.iter().copied().collect::<Vec<_>>();
1966                                self.undo_manager
1967                                    .record_selections(cursors.clone(), cursors);
1968                                self.pause_blink_cursor(cx);
1969                                split = true;
1970                            }
1971                        }
1972                    }
1973                }
1974                if !split {
1975                    // Get current line indent
1976                    let indent = if self.is_code_editor() {
1977                        self.indent_of_next_line()
1978                    } else {
1979                        "".to_string()
1980                    };
1981
1982                    // Add newline and indent
1983                    let new_line_text = format!("\n{}", indent);
1984                    self.replace_text_in_range_silent(None, &new_line_text, window, cx);
1985                    self.pause_blink_cursor(cx);
1986                }
1987            }
1988        } else {
1989            // Single line input or submit-on-enter: just emit the event
1990            // (e.g.: in a dialog to confirm, or a chat textarea to send).
1991            self.undo_manager.break_transaction_coalescing();
1992            cx.propagate();
1993        }
1994
1995        cx.emit(InputEvent::PressEnter {
1996            secondary: action.secondary,
1997            shift: action.shift,
1998        });
1999    }
2000
2001    pub fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2002        self.replace_text("", window, cx);
2003        self.set_selection(0, 0);
2004        self.scroll_to(0, None, cx);
2005    }
2006
2007    pub(super) fn escape(&mut self, action: &Escape, window: &mut Window, cx: &mut Context<Self>) {
2008        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2009            return;
2010        }
2011
2012        // Collapse extra cursors back to the active one first.
2013        if !self.selections.is_single() {
2014            self.undo_manager.break_transaction_coalescing();
2015            self.selections.remove_all_but_active();
2016            cx.notify();
2017            return;
2018        }
2019
2020        // Clear inline completion on escape
2021        if M::has_inline_completion(self) {
2022            M::clear_inline_completion(self, cx);
2023            return; // Consume the escape, don't propagate
2024        }
2025
2026        if self.ime_marked_range.is_some() {
2027            self.unmark_text(window, cx);
2028        }
2029
2030        if self.clean_on_escape {
2031            return self.clean(window, cx);
2032        }
2033
2034        cx.propagate();
2035    }
2036
2037    /// Show the right-click context menu as a native OS menu.
2038    pub(crate) fn handle_right_click_menu(
2039        &mut self,
2040        position: Point<Pixels>,
2041        offset: usize,
2042        window: &mut Window,
2043        cx: &mut Context<Self>,
2044    ) {
2045        if self.disabled {
2046            return;
2047        }
2048        if crate::GlobalState::is_in_deferred_context(cx) {
2049            return;
2050        }
2051
2052        if !self.active_selection().contains(offset) {
2053            self.move_to(offset, None, cx);
2054        }
2055
2056        if self.is_code_editor() {
2057            M::on_hover_definition(self, offset, window, cx);
2058        }
2059
2060        if let Some(handler) = self.context_menu_handler.clone() {
2061            let capabilities = self.context_menu_capabilities();
2062            cx.defer_in(window, move |_, window, cx| {
2063                handler(NativeMenu::new(), capabilities, position, window, cx);
2064            });
2065        }
2066    }
2067
2068    pub(super) fn add_cursor_above(
2069        &mut self,
2070        _: &AddCursorAbove,
2071        _: &mut Window,
2072        cx: &mut Context<Self>,
2073    ) {
2074        self.add_cursor_vertical(-1, cx);
2075    }
2076
2077    pub(super) fn add_cursor_below(
2078        &mut self,
2079        _: &AddCursorBelow,
2080        _: &mut Window,
2081        cx: &mut Context<Self>,
2082    ) {
2083        self.add_cursor_vertical(1, cx);
2084    }
2085
2086    /// Add a new cursor one display line above (`move_lines < 0`) or below each
2087    /// existing cursor, preserving the column. Cursors that would not move (at
2088    /// the first/last display row) or that would duplicate an existing cursor
2089    /// are skipped.
2090    fn add_cursor_vertical(&mut self, move_lines: isize, cx: &mut Context<Self>) {
2091        if !self.is_multi_line() {
2092            return;
2093        }
2094
2095        self.pause_blink_cursor(cx);
2096        // Changing the cursor set ends the editing gesture that came before it.
2097        self.undo_manager.break_transaction_coalescing();
2098
2099        let sources: Vec<(usize, Option<(Pixels, usize)>, bool)> = self
2100            .selections
2101            .iter()
2102            .map(|sel| {
2103                (
2104                    sel.cursor_offset(),
2105                    sel.column_anchor,
2106                    self.line_end_affinity_for(sel),
2107                )
2108            })
2109            .collect();
2110        let mut offsets: std::collections::HashSet<usize> =
2111            sources.iter().map(|(offset, _, _)| *offset).collect();
2112
2113        let mut newest: Option<usize> = None;
2114        for (offset, anchor, line_end_affinity) in sources {
2115            let anchor = anchor.or_else(|| self.preferred_column_for(offset));
2116            let (target, _) = self.vertical_target(offset, anchor, line_end_affinity, move_lines);
2117            if target == offset || offsets.contains(&target) {
2118                continue;
2119            }
2120            offsets.insert(target);
2121            let id = self.selections.generate_id();
2122            let mut cursor = CursorSelection::new(id, target, target);
2123            cursor.column_anchor = anchor;
2124            self.selections.add(cursor);
2125            newest = Some(target);
2126        }
2127
2128        if let Some(newest) = newest {
2129            self.scroll_to(newest, None, cx);
2130        }
2131        cx.notify();
2132    }
2133
2134    /// Add an additional collapsed cursor at `offset`.
2135    ///
2136    /// Rejected when `offset` lands inside an existing selection or exactly on
2137    /// an existing cursor.
2138    pub(super) fn add_cursor_at(&mut self, offset: usize, cx: &mut Context<Self>) {
2139        if !self.is_multi_line() {
2140            return;
2141        }
2142
2143        for sel in self.selections.iter() {
2144            if sel.contains(offset) {
2145                return;
2146            }
2147            if sel.is_collapsed() && sel.cursor_offset() == offset {
2148                return;
2149            }
2150        }
2151
2152        self.undo_manager.break_transaction_coalescing();
2153        let id = self.selections.generate_id();
2154        self.selections
2155            .add(CursorSelection::new(id, offset, offset));
2156        cx.notify();
2157    }
2158
2159    /// Build a columnar (block) selection spanning the rows between the two
2160    /// points, one selection per display row at the same column span.
2161    ///
2162    /// The span keeps the columns the pointer sat past the end of a short row, so a row
2163    /// that runs out of text does not narrow the block for the rows that do reach that
2164    /// far. Each row is then clipped to its own end, selecting as much of the span as it
2165    /// actually has.
2166    pub(super) fn build_columnar_selection(
2167        &mut self,
2168        start: ColumnarPoint,
2169        end: ColumnarPoint,
2170        cx: &mut Context<Self>,
2171    ) {
2172        if !self.is_multi_line() {
2173            return;
2174        }
2175
2176        self.undo_manager.break_transaction_coalescing();
2177
2178        let (start_row, start_col) = self.columnar_row_column(start);
2179        let (end_row, end_col) = self.columnar_row_column(end);
2180        let (start_row, end_row) = (start_row.min(end_row), start_row.max(end_row));
2181        let (start_col, end_col) = (start_col.min(end_col), start_col.max(end_col));
2182
2183        let mut new_selections = Vec::with_capacity(end_row - start_row + 1);
2184        for row in start_row..=end_row {
2185            let sel_start = self
2186                .display_map
2187                .display_row_column_to_offset(row, start_col);
2188            let sel_end = self.display_map.display_row_column_to_offset(row, end_col);
2189            let id = self.selections.generate_id();
2190            let sel_start = self.text.clip_offset(sel_start, Bias::Left);
2191            let sel_end = self.text.clip_offset(sel_end, Bias::Left);
2192            new_selections.push(CursorSelection::new(id, sel_start, sel_end));
2193        }
2194
2195        self.selections.replace_all(new_selections);
2196        cx.notify();
2197    }
2198
2199    /// Resolve a columnar point to the display row it is on and the column it reaches,
2200    /// counting the columns beyond the row's end so a short row keeps the full span.
2201    fn columnar_row_column(&self, point: ColumnarPoint) -> (usize, usize) {
2202        let display_point = self.display_map.offset_to_wrap_display_point(point.offset);
2203        let row = self
2204            .display_map
2205            .wrap_row_to_display_row(display_point.row)
2206            .unwrap_or_else(|| {
2207                self.display_map
2208                    .nearest_visible_display_row(display_point.row)
2209            });
2210
2211        (row, display_point.column + point.columns_past_line_end)
2212    }
2213
2214    pub(super) fn on_mouse_down(
2215        &mut self,
2216        event: &MouseDownEvent,
2217        window: &mut Window,
2218        cx: &mut Context<Self>,
2219    ) {
2220        self.undo_manager.break_transaction_coalescing();
2221        // Input has its own text selection; suppress the window-level text
2222        // selection (Root) so it does not start a drag from here.
2223        crate::global_state::GlobalState::suppress_text_selection(cx);
2224
2225        // Clear inline completion on any mouse interaction
2226        M::clear_inline_completion(self, cx);
2227
2228        // If there have IME marked range and is empty (Means pressed Esc to abort IME typing)
2229        // Clear the marked range.
2230        if let Some(ime_marked_range) = &self.ime_marked_range {
2231            if ime_marked_range.len() == 0 {
2232                self.ime_marked_range = None;
2233            }
2234        }
2235
2236        self.selecting = true;
2237        let (offset, line_end_affinity, columns_past_line_end) =
2238            self.resolve_mouse_position(event.position);
2239
2240        if M::on_click(self, event, offset, window, cx) {
2241            return;
2242        }
2243
2244        // Triple click to select line
2245        if event.button == MouseButton::Left && event.click_count >= 3 {
2246            self.select_line(offset, window, cx);
2247            return;
2248        }
2249
2250        // Double click to select word
2251        if event.button == MouseButton::Left && event.click_count == 2 {
2252            self.select_word(offset, window, cx);
2253            return;
2254        }
2255
2256        // Show Mouse context menu
2257        if event.button == MouseButton::Right {
2258            if self.enable_context_menu {
2259                if !self.active_selection().contains(offset) {
2260                    self.move_to(offset, None, cx);
2261                }
2262                self.pending_context_menu = Some((event.position, offset));
2263            }
2264            return;
2265        }
2266
2267        // Multi-cursor placement, multi-line only.
2268        if self.is_multi_line() && event.button == MouseButton::Left {
2269            if event.modifiers.alt
2270                && (event.modifiers.shift || (cfg!(target_os = "linux") && event.modifiers.control))
2271            {
2272                // Alt+Shift starts a block; Linux also accepts Ghostty's Ctrl+Alt.
2273                // Mark selecting so the drag handler extends the block.
2274                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2275                self.selecting = true;
2276                self.move_to_with_affinity(offset, None, line_end_affinity, cx);
2277                return;
2278            } else if event.modifiers.alt {
2279                self.add_cursor_at(offset, cx);
2280                // Keep click-to-add behavior, but use this press as the block
2281                // anchor if the user continues dragging with the left button.
2282                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2283                return;
2284            }
2285        }
2286
2287        if event.modifiers.shift {
2288            self.select_to_with_affinity(offset, line_end_affinity, cx);
2289        } else {
2290            self.move_to_with_affinity(offset, None, line_end_affinity, cx)
2291        }
2292    }
2293
2294    pub(super) fn on_mouse_up(
2295        &mut self,
2296        event: &MouseUpEvent,
2297        window: &mut Window,
2298        cx: &mut Context<Self>,
2299    ) {
2300        if event.button == MouseButton::Right {
2301            if let Some((position, offset)) = self.pending_context_menu.take() {
2302                self.handle_right_click_menu(position, offset, window, cx);
2303            }
2304        }
2305        if self.active_selection().is_empty() {
2306            self.active_selection_mut().reversed = false;
2307        }
2308        self.selecting = false;
2309        self.selected_word_range = None;
2310        self.column_select_start = None;
2311        self.auto_scroll.stop();
2312    }
2313
2314    pub(super) fn on_mouse_move(
2315        &mut self,
2316        event: &MouseMoveEvent,
2317        window: &mut Window,
2318        cx: &mut Context<Self>,
2319    ) {
2320        // Check if mouse is within bounds
2321        let within_bounds = self
2322            .last_bounds
2323            .as_ref()
2324            .map(|bounds| bounds.contains(&event.position))
2325            .unwrap_or(false);
2326
2327        if !within_bounds {
2328            // Clear hover when mouse leaves the input
2329            M::clear_hover_state(self, cx);
2330            return;
2331        }
2332
2333        // Show diagnostic popover on mouse move
2334        let (offset, _) = self.index_for_mouse_position(event.position);
2335        M::on_mouse_move(self, offset, event, window, cx);
2336
2337        if self.is_code_editor() {
2338            if let Some(diagnostic) = self
2339                .mode
2340                .diagnostics()
2341                .and_then(|set| set.for_offset(offset))
2342            {
2343                self.diagnostic_popover = Some(Rc::new(diagnostic.clone()));
2344                cx.notify();
2345            } else {
2346                self.diagnostic_popover = None;
2347            }
2348        }
2349    }
2350
2351    pub(super) fn on_scroll_wheel(
2352        &mut self,
2353        event: &ScrollWheelEvent,
2354        window: &mut Window,
2355        cx: &mut Context<Self>,
2356    ) {
2357        let line_height = self
2358            .last_layout
2359            .as_ref()
2360            .map(|layout| layout.line_height)
2361            .unwrap_or(window.line_height());
2362        let delta = event.delta.pixel_delta(line_height);
2363
2364        let old_offset = self.scroll_handle.offset();
2365        self.update_scroll_offset(Some(old_offset + delta), cx);
2366
2367        // Only stop propagation if the offset actually changed
2368        if self.scroll_handle.offset() != old_offset {
2369            cx.stop_propagation();
2370        }
2371
2372        if self.diagnostic_popover.take().is_some() {
2373            cx.notify();
2374        }
2375    }
2376
2377    pub(super) fn update_scroll_offset(
2378        &mut self,
2379        offset: Option<Point<Pixels>>,
2380        cx: &mut Context<Self>,
2381    ) {
2382        let mut offset = offset.unwrap_or(self.scroll_handle.offset());
2383        // In addition to left alignment, a cursor position will be reserved on the right side
2384        let safe_x_offset = if self.text_align == TextAlign::Left {
2385            px(0.)
2386        } else {
2387            -CURSOR_WIDTH
2388        };
2389
2390        let safe_y_range =
2391            (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
2392        let safe_x_range = (-self.scroll_size.width + self.input_bounds.size.width + safe_x_offset)
2393            .min(safe_x_offset)..px(0.);
2394
2395        offset.y = if self.is_single_line() {
2396            px(0.)
2397        } else {
2398            offset.y.clamp(safe_y_range.start, safe_y_range.end)
2399        };
2400        offset.x = offset.x.clamp(safe_x_range.start, safe_x_range.end);
2401        if self.scroll_handle.offset() != offset {
2402            self.scroll_handle.set_offset(offset);
2403            cx.notify();
2404        }
2405    }
2406
2407    /// Scroll to make the given offset visible.
2408    ///
2409    /// If `direction` is Some, will keep edges at the same side.
2410    pub(crate) fn scroll_to(
2411        &mut self,
2412        offset: usize,
2413        direction: Option<MoveDirection>,
2414        cx: &mut Context<Self>,
2415    ) {
2416        let padding = if direction.is_some() {
2417            ScrollPadding::SurroundingLines
2418        } else {
2419            ScrollPadding::Minimal
2420        };
2421        self.scroll_to_with_padding(offset, direction, padding, cx);
2422    }
2423
2424    /// Reveal an offset with independently chosen direction restriction and padding.
2425    /// Search uses surrounding lines without restricting movement to match order.
2426    pub(crate) fn scroll_to_with_padding(
2427        &mut self,
2428        offset: usize,
2429        direction: Option<MoveDirection>,
2430        padding: ScrollPadding,
2431        cx: &mut Context<Self>,
2432    ) {
2433        let Some(last_layout) = self.last_layout.as_ref() else {
2434            return;
2435        };
2436        let Some(bounds) = self.last_bounds.as_ref() else {
2437            return;
2438        };
2439
2440        let mut scroll_offset = self.scroll_handle.offset();
2441        let was_offset = scroll_offset;
2442        let line_height = last_layout.line_height;
2443
2444        let point = self.text.offset_to_point(offset);
2445
2446        let row = point.row;
2447
2448        // Calculate row offset by multiplying the number of lines before it with the line height
2449        let mut row_offset_y = line_height * self.display_map.buffer_line_to_display_row(row);
2450
2451        // For Right alignment use 0 margin: the cursor indicator is clamped inside bounds
2452        // in layout_cursor, so shifting the text here would cause a first-click visual jump.
2453        let safety_margin = match last_layout.text_align {
2454            TextAlign::Left => RIGHT_MARGIN,
2455            TextAlign::Right => px(0.),
2456            TextAlign::Center => CURSOR_WIDTH,
2457        };
2458        if let Some(line) = last_layout
2459            .lines
2460            .get(row.saturating_sub(last_layout.visible_range.start))
2461        {
2462            // Check to scroll horizontally and soft wrap lines
2463            if let Some(pos) = line.position_for_index(point.column, last_layout, false) {
2464                let bounds_width = bounds.size.width - last_layout.line_number_width;
2465                let col_offset_x = pos.x;
2466                row_offset_y += pos.y;
2467                if col_offset_x - safety_margin < -scroll_offset.x {
2468                    // If the position is out of the visible area, scroll to make it visible
2469                    scroll_offset.x = -col_offset_x + safety_margin;
2470                } else if col_offset_x + safety_margin > -scroll_offset.x + bounds_width {
2471                    scroll_offset.x = -(col_offset_x - bounds_width + safety_margin);
2472                }
2473            }
2474        }
2475
2476        // Scroll the row into view. Use the same edge clearance helper as
2477        // `TextElement::layout_cursor` so both scroll-into-view paths agree
2478        // (a mismatch flickered on `Down` at end-of-buffer with a small
2479        // `cursor_surrounding_lines` override).
2480        let edge_height =
2481            if matches!(padding, ScrollPadding::SurroundingLines) && self.is_code_editor() {
2482                super::element::cursor_surrounding_padding(
2483                    self.mode.is_auto_grow(),
2484                    self.cursor_surrounding_lines,
2485                    last_layout.visible_range.len(),
2486                    line_height,
2487                )
2488            } else {
2489                line_height
2490            };
2491        if row_offset_y - edge_height + line_height < -scroll_offset.y {
2492            // Scroll up
2493            scroll_offset.y = -row_offset_y + edge_height - line_height;
2494        } else if row_offset_y + edge_height > -scroll_offset.y + bounds.size.height {
2495            // Scroll down
2496            scroll_offset.y = -(row_offset_y - bounds.size.height + edge_height);
2497        }
2498
2499        // Avoid necessary scroll, when it was already in the correct position.
2500        if direction == Some(MoveDirection::Up) {
2501            scroll_offset.y = scroll_offset.y.max(was_offset.y);
2502        } else if direction == Some(MoveDirection::Down) {
2503            scroll_offset.y = scroll_offset.y.min(was_offset.y);
2504        }
2505
2506        // Clamp the deferred target into the same safe range that
2507        // `update_scroll_offset` enforces on persist, so paint never shows an
2508        // over-scrolled frame before the post-paint clamp pulls it back.
2509        let safe_y_min = (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.));
2510        scroll_offset.x = scroll_offset.x.min(px(0.));
2511        scroll_offset.y = scroll_offset.y.clamp(safe_y_min, px(0.));
2512        self.deferred_scroll_offset = Some(scroll_offset);
2513        cx.notify();
2514    }
2515
2516    pub(super) fn show_character_palette(
2517        &mut self,
2518        _: &ShowCharacterPalette,
2519        window: &mut Window,
2520        _: &mut Context<Self>,
2521    ) {
2522        window.show_character_palette();
2523    }
2524
2525    /// The text of every non-empty selection, in document order.
2526    fn selected_texts(&self) -> Vec<String> {
2527        let mut selections: Vec<CursorSelection> = self
2528            .selections
2529            .iter()
2530            .copied()
2531            .filter(|sel| !sel.is_empty())
2532            .collect();
2533        selections.sort_by_key(|sel| sel.start);
2534        selections
2535            .iter()
2536            .map(|sel| self.text.slice(*sel).to_string())
2537            .collect()
2538    }
2539
2540    pub(super) fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
2541        if !self.is_copyable() {
2542            return;
2543        }
2544
2545        let texts = self.selected_texts();
2546
2547        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
2548    }
2549
2550    pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
2551        if !self.is_copyable() {
2552            return;
2553        }
2554
2555        let texts = self.selected_texts();
2556
2557        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
2558
2559        self.undo_manager.set_pending_intent(EditIntent::Atomic);
2560        self.replace_text_in_range_silent(None, "", window, cx);
2561    }
2562
2563    pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
2564        if !self.is_editable() {
2565            return;
2566        }
2567        let Some(clipboard) = cx.read_from_clipboard() else {
2568            return;
2569        };
2570        let mut new_text = clipboard.text().unwrap_or_default();
2571        // A paste is one atomic edit, never part of a typing run.
2572        self.undo_manager.set_pending_intent(EditIntent::Atomic);
2573
2574        if !self.is_multi_line() {
2575            new_text = new_text.replace('\n', "");
2576            self.replace_text_in_range_silent(None, &new_text, window, cx);
2577            self.scroll_to(self.cursor(), None, cx);
2578            return;
2579        }
2580
2581        // Distribute one clipboard line per selection when the counts match.
2582        // Otherwise insert the whole clipboard text at each cursor.
2583        if !self.selections.is_single() {
2584            self.selections.merge_overlapping();
2585        }
2586        let lines: Vec<String> = new_text.split('\n').map(|s| s.to_string()).collect();
2587        let count = self.selections.len();
2588        if count > 1 && lines.len() == count {
2589            let mut selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
2590            selections.sort_by_key(|sel| sel.start);
2591            let edits: Vec<(Range<usize>, String)> = selections
2592                .iter()
2593                .zip(lines)
2594                .map(|(sel, line)| (sel.start..sel.end, line))
2595                .collect();
2596            self.replace_text_in_ranges(&edits, window, cx);
2597        } else {
2598            self.replace_text_in_range_silent(None, &new_text, window, cx);
2599        }
2600        self.scroll_to(self.cursor(), None, cx);
2601    }
2602
2603    /// The intent of a batch the caller did not label: inserting text at
2604    /// collapsed cursors is typing, anything else stands on its own.
2605    fn typing_intent(&self, edits: &[(Range<usize>, String)], new_text: &str) -> EditIntent {
2606        if !new_text.is_empty()
2607            && !new_text.contains(['\n', '\r'])
2608            && edits.iter().all(|(range, _)| range.is_empty())
2609        {
2610            EditIntent::Typing
2611        } else {
2612            EditIntent::Atomic
2613        }
2614    }
2615
2616    /// Where a cursor stood before an edit made with this intent.
2617    ///
2618    /// Backspace and forward delete expand a collapsed cursor over the text
2619    /// they are about to remove, so the recorded cursor has to collapse back to
2620    /// the side it came from for undo to restore it where the user left it.
2621    fn collapse_for_intent(
2622        intent: EditIntent,
2623        mut selection: CursorSelection,
2624        range: &Range<usize>,
2625    ) -> CursorSelection {
2626        match intent {
2627            EditIntent::Backspace => selection.place_at(range.end, None),
2628            EditIntent::DeleteForward => selection.place_at(range.start, None),
2629            EditIntent::Typing | EditIntent::Atomic => {}
2630        }
2631        selection
2632    }
2633
2634    fn push_history(
2635        &mut self,
2636        text: &Rope,
2637        range: &Range<usize>,
2638        new_text: &str,
2639        requested_intent: Option<EditIntent>,
2640        selection_before: CursorSelection,
2641        selection_after: Option<CursorSelection>,
2642    ) -> bool {
2643        if self.undo_manager.is_ignoring() {
2644            return false;
2645        }
2646
2647        let range =
2648            text.clip_offset(range.start, Bias::Left)..text.clip_offset(range.end, Bias::Right);
2649        let old_text = text.slice(range.clone()).to_string();
2650        let new_range = range.start..range.start + new_text.len();
2651
2652        let intent = requested_intent.unwrap_or_else(|| {
2653            if range.is_empty()
2654                && old_text.is_empty()
2655                && !new_text.is_empty()
2656                && !new_text.contains(['\n', '\r'])
2657            {
2658                EditIntent::Typing
2659            } else {
2660                EditIntent::Atomic
2661            }
2662        });
2663
2664        let selection_before = Self::collapse_for_intent(intent, selection_before, &range);
2665        let selection_after =
2666            selection_after.unwrap_or_else(|| (new_range.end..new_range.end).into());
2667
2668        let open_transaction = self.undo_manager.has_open_transaction();
2669        let recorded = self
2670            .undo_manager
2671            .record_transaction(Change::new(range, &old_text, new_range, new_text), intent);
2672        // A batch records its own cursor sets. This covers a change that is a
2673        // transaction on its own.
2674        if recorded && !open_transaction {
2675            self.undo_manager
2676                .record_selections(vec![selection_before], vec![selection_after]);
2677        }
2678        recorded
2679    }
2680
2681    pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
2682        self.undo_manager.set_ignoring(true);
2683        // The manager hands the changes back in reverse application order.
2684        if let Some(replay) = self.undo_manager.undo() {
2685            for change in &replay.changes {
2686                let range_utf16 = self.range_to_utf16(&change.new_range.into());
2687                self.replace_text_in_range_silent(Some(range_utf16), &change.old_text, window, cx);
2688            }
2689            self.restore_selections(replay.selections);
2690            self.mode
2691                .restore_auto_closed_pairs(replay.auto_closed_pairs.unwrap_or_default());
2692        }
2693        self.undo_manager.set_ignoring(false);
2694    }
2695
2696    pub(super) fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
2697        self.undo_manager.set_ignoring(true);
2698        // Redo replays in forward application order.
2699        if let Some(replay) = self.undo_manager.redo() {
2700            for change in &replay.changes {
2701                let range_utf16 = self.range_to_utf16(&change.old_range.into());
2702                self.replace_text_in_range_silent(Some(range_utf16), &change.new_text, window, cx);
2703            }
2704            self.restore_selections(replay.selections);
2705            self.mode
2706                .restore_auto_closed_pairs(replay.auto_closed_pairs.unwrap_or_default());
2707        }
2708        self.undo_manager.set_ignoring(false);
2709    }
2710
2711    /// Restore a set of selections captured in a transaction, clamping offsets
2712    /// to the current text length. `None` leaves the current selections as the
2713    /// replay left them.
2714    fn restore_selections(&mut self, selections: Option<Vec<CursorSelection>>) {
2715        let Some(selections) = selections else {
2716            return;
2717        };
2718
2719        let len = self.text.len();
2720        let restored: Vec<CursorSelection> = selections
2721            .into_iter()
2722            .map(|mut sel| {
2723                sel.start = sel.start.min(len);
2724                sel.end = sel.end.min(len);
2725                sel
2726            })
2727            .collect();
2728        self.selections.replace_all(restored);
2729        self.selections.merge_overlapping();
2730    }
2731
2732    /// Get byte offset of the cursor.
2733    ///
2734    /// The offset is the UTF-8 offset.
2735    pub fn cursor(&self) -> usize {
2736        if let Some(ime_marked_range) = &self.ime_marked_range {
2737            return ime_marked_range.end;
2738        }
2739
2740        self.selections.active().cursor_offset()
2741    }
2742
2743    /// Returns the active selection.
2744    pub(super) fn active_selection(&self) -> &CursorSelection {
2745        self.selections.active()
2746    }
2747
2748    /// Returns a mutable reference to the active selection.
2749    pub(super) fn active_selection_mut(&mut self) -> &mut CursorSelection {
2750        self.selections.active_mut()
2751    }
2752
2753    /// Sets the active selection to the given range, keeping its `reversed`
2754    /// and `column_anchor` state untouched.
2755    pub(super) fn set_selection(&mut self, start: usize, end: usize) {
2756        let active = self.active_selection_mut();
2757        active.start = start;
2758        active.end = end;
2759    }
2760
2761    /// Collapses the active selection to a cursor at the given offset,
2762    /// clearing `reversed`.
2763    pub(super) fn set_cursor_to(&mut self, offset: usize) {
2764        let active = self.active_selection_mut();
2765        active.start = offset;
2766        active.end = offset;
2767        active.reversed = false;
2768    }
2769
2770    /// Visible row range in the last laid-out viewport, `None` before first layout.
2771    pub fn visible_row_range(&self) -> Option<std::ops::Range<usize>> {
2772        self.last_layout.as_ref().map(|l| l.visible_range.clone())
2773    }
2774
2775    /// Current scroll offset of the editor viewport.
2776    pub fn scroll_offset(&self) -> gpui::Point<gpui::Pixels> {
2777        self.scroll_handle.offset()
2778    }
2779
2780    /// Set scroll offset of the editor viewport.
2781    ///
2782    /// The offset will be clamped to the valid range, and applied after the next layout.
2783    pub fn set_scroll_offset(&mut self, offset: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
2784        self.deferred_scroll_offset = Some(offset);
2785        cx.notify();
2786    }
2787
2788    /// Laid-out line height; `None` before first layout.
2789    pub fn line_height(&self) -> Option<gpui::Pixels> {
2790        self.last_layout.as_ref().map(|l| l.line_height)
2791    }
2792
2793    /// Returns the active selection as a byte range into the text.
2794    ///
2795    /// With multiple cursors, this reads only the active selection.
2796    ///
2797    /// The range is empty (`start == end`) when no text is selected; in
2798    /// that case the offset equals `cursor()`. Byte offsets are measured
2799    /// in the underlying rope's byte units.
2800    pub fn selected_range(&self) -> std::ops::Range<usize> {
2801        (*self.selections.active()).into()
2802    }
2803
2804    pub fn select_all(&mut self, _: &mut Window, cx: &mut Context<Self>) {
2805        self.undo_manager.break_transaction_coalescing();
2806        self.selections.remove_all_but_active();
2807        self.set_selection(0, self.text.len());
2808        cx.notify();
2809    }
2810
2811    /// Set the selected range using UTF-8 byte offsets, removing additional cursors.
2812    ///
2813    /// Non-empty ranges expand to character boundaries. Empty ranges remain empty and are
2814    /// clipped to the preceding character boundary.
2815    pub fn set_selected_range(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
2816        let end_bias = if range.start == range.end {
2817            Bias::Left
2818        } else {
2819            Bias::Right
2820        };
2821        let start = self.text.clip_offset(range.start, Bias::Left);
2822        let end = self.text.clip_offset(range.end, end_bias);
2823
2824        self.move_to(start, None, cx);
2825        self.active_selection_mut().reversed = false;
2826        self.selected_word_range = None;
2827        self.select_to(end, cx);
2828    }
2829
2830    /// Resolve a mouse position to a byte offset in the text.
2831    ///
2832    /// Also reports the caret's line-end affinity for that offset: `true` when the position
2833    /// landed on the wrap boundary of a non-final visual row, meaning the caret belongs at the
2834    /// end of that row rather than at the start of the next one. Callers that place or extend a
2835    /// selection must pass it on, or clicking past the last glyph of a wrapped row leaves a
2836    /// caret one row below the pointer.
2837    pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> (usize, bool) {
2838        let (offset, line_end_affinity, _) = self.resolve_mouse_position(position);
2839        (offset, line_end_affinity)
2840    }
2841
2842    /// Resolve a mouse position to a text offset, the caret affinity to use for it, and
2843    /// how many columns the pointer sits past the end of that row.
2844    ///
2845    /// Only a columnar selection needs the third value; everywhere else a position past
2846    /// the end of a row means the end of that row, and
2847    /// [`Self::index_for_mouse_position`] is the call to make.
2848    fn resolve_mouse_position(&self, position: Point<Pixels>) -> (usize, bool, usize) {
2849        // If the text is empty, always return 0
2850        if self.text.len() == 0 {
2851            return (0, false, 0);
2852        }
2853
2854        let (Some(bounds), Some(last_layout)) =
2855            (self.last_bounds.as_ref(), self.last_layout.as_ref())
2856        else {
2857            return (0, false, 0);
2858        };
2859
2860        let line_height = last_layout.line_height;
2861        let line_number_width = last_layout.line_number_width;
2862
2863        // TIP: About the IBeam cursor
2864        //
2865        // If cursor style is IBeam, the mouse mouse position is in the middle of the cursor (This is special in OS)
2866
2867        // The position is relative to the bounds of the text input
2868        //
2869        // bounds.origin:
2870        //
2871        // - included the input padding.
2872        // - included the scroll offset.
2873        let inner_position = position - bounds.origin - point(line_number_width, px(0.));
2874
2875        let mut y_offset = last_layout.visible_top;
2876        // Position relative to the last line walked, kept for a pointer that ends up
2877        // below every line.
2878        let mut last_line_pos = None;
2879
2880        // Traverse visible buffer lines (compact, no hidden entries)
2881        for (vi, (line_layout, _buffer_line)) in last_layout
2882            .lines
2883            .iter()
2884            .zip(last_layout.visible_buffer_lines.iter())
2885            .enumerate()
2886        {
2887            let line_start_offset = last_layout.visible_line_byte_offsets[vi];
2888
2889            // Calculate line origin for this display row
2890            let line_origin = point(px(0.), y_offset);
2891            let pos = inner_position - line_origin;
2892
2893            // Return offset by use closest_index_for_x if is single line mode.
2894            if self.is_single_line() {
2895                let local_index = line_layout.closest_index_for_x(pos.x, last_layout);
2896                // A single line never wraps, so there is no boundary to disambiguate.
2897                return (
2898                    self.resolve_index(line_start_offset + local_index),
2899                    false,
2900                    0,
2901                );
2902            }
2903
2904            // Check if mouse is in this line's bounds
2905            if let Some((local_index, line_end_affinity)) =
2906                line_layout.closest_index_for_position(pos, last_layout)
2907            {
2908                return (
2909                    self.resolve_index(line_start_offset + local_index),
2910                    line_end_affinity,
2911                    line_layout.columns_past_line_end(pos, last_layout),
2912                );
2913            } else if pos.y < px(0.) {
2914                // Mouse is above this line, return start of this line
2915                return (self.resolve_index(line_start_offset), false, 0);
2916            }
2917
2918            y_offset += line_layout.size(line_height).height;
2919            last_line_pos = Some(pos);
2920        }
2921
2922        // Mouse is below all visible lines, return end of text. A columnar selection
2923        // still needs how far right the pointer was, so measure it against the last
2924        // line rather than reporting a block that collapses at the bottom edge.
2925        let columns_past_line_end = last_layout
2926            .lines
2927            .last()
2928            .zip(last_line_pos)
2929            .map(|(line_layout, pos)| {
2930                let last_row_top = (line_layout.size(line_height).height - line_height).max(px(0.));
2931                line_layout.columns_past_line_end(point(pos.x, last_row_top), last_layout)
2932            })
2933            .unwrap_or(0);
2934
2935        (self.text.len(), false, columns_past_line_end)
2936    }
2937
2938    /// Map a display byte index back to a text offset, undoing the mask expansion when the input
2939    /// is masked.
2940    fn resolve_index(&self, index: usize) -> usize {
2941        if self.masked {
2942            self.text.char_index_to_offset(index / MASK_CHAR.len_utf8())
2943        } else {
2944            index.min(self.text.len())
2945        }
2946    }
2947
2948    /// Returns a y offsetted point for the line origin.
2949    /// Select the text from the current cursor position to the given offset.
2950    ///
2951    /// The offset is the UTF-8 offset.
2952    ///
2953    /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
2954    /// Extend a single selection so its moving end lands at `offset`, flipping
2955    /// `reversed` when the ends cross. When a sticky `word_range` is given the
2956    /// selection is kept covering it.
2957    fn extend_selection(
2958        sel: &mut CursorSelection,
2959        offset: usize,
2960        word_range: Option<CursorSelection>,
2961    ) {
2962        if sel.reversed {
2963            sel.start = offset;
2964        } else {
2965            sel.end = offset;
2966        }
2967
2968        if sel.end < sel.start {
2969            sel.reversed = !sel.reversed;
2970            std::mem::swap(&mut sel.start, &mut sel.end);
2971        }
2972
2973        if let Some(word_range) = word_range {
2974            if sel.start > word_range.start {
2975                sel.start = word_range.start;
2976            }
2977            if sel.end < word_range.end {
2978                sel.end = word_range.end;
2979            }
2980        }
2981    }
2982
2983    /// Extend only the active selection to `offset`. Used by mouse drag.
2984    pub(crate) fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
2985        self.select_to_with_affinity(offset, false, cx);
2986    }
2987
2988    /// Like [`Self::select_to`], but also carries the caret's line-end affinity.
2989    ///
2990    /// See [`Self::move_to_with_affinity`] for why the affinity travels with the offset. Note
2991    /// that plain [`Self::select_to`] clears the affinity: every offset it is given came from
2992    /// the text rather than from a visual position, so the caret has no reason to keep sticking
2993    /// to the end of a wrapped row.
2994    pub(crate) fn select_to_with_affinity(
2995        &mut self,
2996        offset: usize,
2997        line_end_affinity: bool,
2998        cx: &mut Context<Self>,
2999    ) {
3000        M::clear_inline_completion(self, cx);
3001
3002        self.cursor_line_end_affinity = line_end_affinity;
3003        let offset = self.cursor_boundary(offset, Bias::Left);
3004        let word_range = self.selected_word_range;
3005        Self::extend_selection(self.active_selection_mut(), offset, word_range);
3006
3007        if self.active_selection().is_empty() {
3008            self.update_preferred_column();
3009        }
3010        cx.notify()
3011    }
3012
3013    /// Extend every selection to the offset produced by `f`, then merge any
3014    /// selections that now overlap. Used by keyboard selection commands.
3015    fn select_all_cursors_to(
3016        &mut self,
3017        f: impl Fn(&Self, &CursorSelection) -> usize,
3018        cx: &mut Context<Self>,
3019    ) {
3020        self.pause_blink_cursor(cx);
3021        self.undo_manager.break_transaction_coalescing();
3022        M::clear_inline_completion(self, cx);
3023
3024        let new_selections: Vec<CursorSelection> = self
3025            .selections
3026            .iter()
3027            .map(|sel| {
3028                let offset = self.cursor_boundary(f(self, sel), Bias::Left);
3029                let mut new_sel = *sel;
3030                Self::extend_selection(&mut new_sel, offset, None);
3031                new_sel
3032            })
3033            .collect();
3034        // Resolve targets using the old caret affinity before clearing it.
3035        self.cursor_line_end_affinity = false;
3036        self.selections.replace_all(new_selections);
3037        self.selections.merge_overlapping();
3038
3039        if self.active_selection().is_empty() {
3040            self.update_preferred_column();
3041        }
3042        self.scroll_to(self.cursor(), None, cx);
3043        cx.notify()
3044    }
3045
3046    /// Unselects the currently selected text.
3047    pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3048        self.undo_manager.break_transaction_coalescing();
3049        let offset = self.cursor();
3050        self.set_cursor_to(offset);
3051        cx.notify()
3052    }
3053
3054    #[inline]
3055    pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {
3056        self.text.offset_utf16_to_offset(offset)
3057    }
3058
3059    #[inline]
3060    pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {
3061        self.text.offset_to_offset_utf16(offset)
3062    }
3063
3064    #[inline]
3065    pub(crate) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
3066        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
3067    }
3068
3069    #[inline]
3070    pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
3071        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
3072    }
3073
3074    /// If offset falls on a hidden (folded) line, clamp backward to the end of
3075    /// the fold header line (last visible position before the fold).
3076    fn clamp_offset_to_visible_backward(&self, offset: usize) -> usize {
3077        let line = self.text.offset_to_point(offset).row;
3078        if self.display_map.is_buffer_line_hidden(line) {
3079            for fold in self.display_map.folded_ranges() {
3080                if line > fold.start_line && line <= fold.end_line {
3081                    return self.text.line_end_offset(fold.start_line);
3082                }
3083            }
3084        }
3085        offset
3086    }
3087
3088    /// If offset falls on a hidden (folded) line, clamp forward to the start of
3089    /// the fold end line (first visible position after the fold).
3090    fn clamp_offset_to_visible_forward(&self, offset: usize) -> usize {
3091        let line = self.text.offset_to_point(offset).row;
3092        if self.display_map.is_buffer_line_hidden(line) {
3093            for fold in self.display_map.folded_ranges() {
3094                if line > fold.start_line && line <= fold.end_line {
3095                    return self.text.line_start_offset(fold.end_line);
3096                }
3097            }
3098        }
3099        offset
3100    }
3101
3102    /// Clip a cursor/selection offset without splitting a CRLF newline. This does
3103    /// not alter the rope or its byte/UTF-16 conversion used for exact source APIs.
3104    pub(super) fn cursor_boundary(&self, offset: usize, bias: Bias) -> usize {
3105        let offset = self.text.clip_offset(offset, bias);
3106        if offset > 0
3107            && self.text.char_at(offset - 1) == Some('\r')
3108            && self.text.char_at(offset) == Some('\n')
3109        {
3110            if bias == Bias::Left {
3111                offset - 1
3112            } else {
3113                offset + 1
3114            }
3115        } else {
3116            offset
3117        }
3118    }
3119
3120    pub(super) fn previous_boundary(&self, offset: usize) -> usize {
3121        let offset = self.cursor_boundary(offset.saturating_sub(1), Bias::Left);
3122        self.clamp_offset_to_visible_backward(offset)
3123    }
3124
3125    pub(super) fn next_boundary(&self, offset: usize) -> usize {
3126        let offset = self.cursor_boundary(offset.saturating_add(1), Bias::Right);
3127        self.clamp_offset_to_visible_forward(offset)
3128    }
3129
3130    /// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
3131    pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
3132        (self.focus_handle.is_focused(window) || M::is_context_menu_open(self, cx))
3133            && !self.disabled
3134            && self.blink_cursor.read(cx).visible()
3135            && window.is_window_active()
3136    }
3137
3138    fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3139        self.blink_cursor.update(cx, |cursor, cx| {
3140            cursor.start(cx);
3141        });
3142        cx.emit(InputEvent::Focus);
3143    }
3144
3145    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3146        if M::is_context_menu_open(self, cx) {
3147            return;
3148        }
3149
3150        self.undo_manager.break_transaction_coalescing();
3151
3152        // NOTE: Do not cancel select, when blur.
3153        // Because maybe user want to copy the selected text by AppMenuBar (will take focus handle).
3154
3155        M::clear_hover_state(self, cx);
3156        self.diagnostic_popover = None;
3157        M::clear_inline_completion(self, cx);
3158        self.blink_cursor.update(cx, |cursor, cx| {
3159            cursor.stop(cx);
3160        });
3161        self.clamp_number_value(window, cx);
3162        cx.emit(InputEvent::Blur);
3163        cx.notify();
3164    }
3165
3166    /// Clamp the number value to the `min`/`max` range, used on blur.
3167    ///
3168    /// Out-of-range values are allowed while typing (e.g. `1` is an
3169    /// intermediate state of `15` when min is 10), and clamped on blur.
3170    fn clamp_number_value(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3171        if !self.is_single_line() {
3172            return;
3173        }
3174        if !matches!(self.mask_pattern, MaskPattern::Number { .. }) {
3175            return;
3176        }
3177        if self.number_min.is_none() && self.number_max.is_none() {
3178            return;
3179        }
3180
3181        let Ok(value) = self.unmask_value().parse::<f64>() else {
3182            return;
3183        };
3184
3185        let clamped = match (self.number_min, self.number_max) {
3186            (Some(min), _) if value < min => min,
3187            (_, Some(max)) if value > max => max,
3188            _ => return,
3189        };
3190
3191        // The clamped value must pass the `pattern`/`validate` check,
3192        // otherwise keep the value as is.
3193        let new_text = clamped.to_string();
3194        if !self.is_valid_input(&new_text, cx) {
3195            return;
3196        }
3197
3198        let range = self.range_to_utf16(&(0..self.text.len()));
3199        self.replace_text_in_range_silent(Some(range), &new_text, window, cx);
3200    }
3201
3202    pub(super) fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
3203        self.blink_cursor.update(cx, |cursor, cx| {
3204            cursor.pause(cx);
3205        });
3206    }
3207
3208    pub(super) fn on_drag_move(
3209        &mut self,
3210        event: &MouseMoveEvent,
3211        window: &mut Window,
3212        cx: &mut Context<Self>,
3213    ) {
3214        if self.text.len() == 0 {
3215            return;
3216        }
3217
3218        if self.last_layout.is_none() {
3219            return;
3220        }
3221
3222        if !self.focus_handle.is_focused(window) {
3223            return;
3224        }
3225
3226        if !self.selecting {
3227            return;
3228        }
3229
3230        self.auto_scroll.last_drag_position = Some(event.position);
3231        let (offset, line_end_affinity, columns_past_line_end) =
3232            self.resolve_mouse_position(event.position);
3233        if let Some(start) = self.column_select_start {
3234            let end = ColumnarPoint::new(offset, columns_past_line_end);
3235            self.build_columnar_selection(start, end, cx);
3236        } else {
3237            self.select_to_with_affinity(offset, line_end_affinity, cx);
3238        }
3239
3240        if !self.is_single_line() {
3241            let delta = AutoScroll::compute_delta(event.position.y, self.input_bounds);
3242            // Input's ScrollHandle uses negative-y-is-down; negate the positive-towards-bottom delta.
3243            let scroll_delta = delta.map(|d| -d);
3244            self.auto_scroll.set(scroll_delta, cx, |delta, state, cx| {
3245                let current = state.scroll_handle.offset();
3246                state.update_scroll_offset(Some(point(current.x, current.y + delta)), cx);
3247                if let Some(pos) = state.auto_scroll.last_drag_position {
3248                    let (offset, line_end_affinity, columns_past_line_end) =
3249                        state.resolve_mouse_position(pos);
3250                    if let Some(start) = state.column_select_start {
3251                        let end = ColumnarPoint::new(offset, columns_past_line_end);
3252                        state.build_columnar_selection(start, end, cx);
3253                    } else {
3254                        state.select_to_with_affinity(offset, line_end_affinity, cx);
3255                    }
3256                }
3257            });
3258        }
3259    }
3260
3261    /// Normalize the inserted text before applying it to the input.
3262    ///
3263    /// For number inputs (with [`MaskPattern::Number`]), this converts
3264    /// full-width number characters into their ASCII equivalents,
3265    /// e.g. `12。5` -> `12.5`.
3266    fn normalize_input<'a>(&self, new_text: &'a str) -> Cow<'a, str> {
3267        let normalized = if matches!(self.mask_pattern, MaskPattern::Number { .. }) {
3268            normalize_number_input(new_text)
3269        } else {
3270            Cow::Borrowed(new_text)
3271        };
3272
3273        if self.is_single_line() && normalized.contains(['\n', '\r']) {
3274            Cow::Owned(normalized.replace(['\n', '\r'], ""))
3275        } else {
3276            normalized
3277        }
3278    }
3279
3280    pub(crate) fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {
3281        if new_text.is_empty() {
3282            return true;
3283        }
3284
3285        if let Some(validate) = &self.validate {
3286            if !validate(new_text, cx) {
3287                return false;
3288            }
3289        }
3290
3291        if !self.mask_pattern.is_valid(new_text) {
3292            return false;
3293        }
3294
3295        let Some(pattern) = &self.pattern else {
3296            return true;
3297        };
3298
3299        pattern.is_match(new_text)
3300    }
3301
3302    /// Set the mask pattern for formatting the input text.
3303    ///
3304    /// The pattern can contain:
3305    /// - 9: Any digit or dot
3306    /// - A: Any letter
3307    /// - *: Any character
3308    /// - Other characters will be treated as literal mask characters
3309    ///
3310    /// Example: "(999)999-999" for phone numbers
3311    pub fn mask_pattern(mut self, pattern: impl Into<MaskPattern>) -> Self {
3312        self.mask_pattern = pattern.into();
3313        self.mask_pattern_set = true;
3314        if let Some(placeholder) = self.mask_pattern.placeholder() {
3315            self.placeholder = placeholder.into();
3316        }
3317        self
3318    }
3319
3320    pub fn set_mask_pattern(
3321        &mut self,
3322        pattern: impl Into<MaskPattern>,
3323        _: &mut Window,
3324        cx: &mut Context<Self>,
3325    ) {
3326        self.mask_pattern = pattern.into();
3327        self.mask_pattern_set = true;
3328        if let Some(placeholder) = self.mask_pattern.placeholder() {
3329            self.placeholder = placeholder.into();
3330        }
3331        cx.notify();
3332    }
3333
3334    /// Apply the default numeric mask unless the caller explicitly selected a mask.
3335    pub fn ensure_number_mask(&mut self) {
3336        if self.mask_pattern_set {
3337            return;
3338        }
3339        self.mask_pattern = MaskPattern::Number {
3340            separator: None,
3341            fraction: None,
3342        };
3343    }
3344
3345    pub(super) fn set_input_bounds(&mut self, new_bounds: Bounds<Pixels>, cx: &mut Context<Self>) {
3346        let wrap_width_changed = self.input_bounds.size.width != new_bounds.size.width;
3347        self.input_bounds = new_bounds;
3348
3349        // Update display_map wrap_width if changed.
3350        if let Some(last_layout) = self.last_layout.as_ref() {
3351            if wrap_width_changed {
3352                let wrap_width = if !self.soft_wrap {
3353                    // None to disable wrapping (will use Pixels::MAX)
3354                    None
3355                } else {
3356                    last_layout.wrap_width
3357                };
3358
3359                self.display_map.on_layout_changed(wrap_width, cx);
3360                if self.is_multi_line() {
3361                    self.mode.update_auto_grow(&self.display_map);
3362                }
3363                cx.notify();
3364            }
3365        }
3366    }
3367
3368    /// Return the active selection's text, borrowed out of the [`Rope`]
3369    /// the state owns.
3370    ///
3371    /// See [`Self::selected_value`] when an owned string is wanted.
3372    pub fn selected_text(&self) -> RopeSlice<'_> {
3373        let range_utf16 = self.range_to_utf16(&self.selected_range());
3374        let range = self.range_from_utf16(&range_utf16);
3375        self.text.slice(range)
3376    }
3377
3378    /// Return the rendered bounds for a UTF-8 byte range in the current input contents.
3379    ///
3380    /// Returns `None` when the requested range is not currently laid out or visible.
3381    pub fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
3382        let Some(last_layout) = self.last_layout.as_ref() else {
3383            return None;
3384        };
3385
3386        let Some(last_bounds) = self.last_bounds else {
3387            return None;
3388        };
3389
3390        let (_, _, start_pos) = self.line_and_position_for_offset(range.start);
3391        let (_, _, end_pos) = self.line_and_position_for_offset(range.end);
3392
3393        let Some(start_pos) = start_pos else {
3394            return None;
3395        };
3396        let Some(end_pos) = end_pos else {
3397            return None;
3398        };
3399
3400        Some(Bounds::from_corners(
3401            last_bounds.origin + start_pos,
3402            last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),
3403        ))
3404    }
3405
3406    /// Replace text in range in silent.
3407    ///
3408    /// This will not trigger any UI interaction, such as auto-completion.
3409    pub(crate) fn replace_text_in_range_silent(
3410        &mut self,
3411        range_utf16: Option<Range<usize>>,
3412        new_text: &str,
3413        window: &mut Window,
3414        cx: &mut Context<Self>,
3415    ) {
3416        self.silent_replace_text = true;
3417        self.replace_text_in_range(range_utf16, new_text, window, cx);
3418        self.silent_replace_text = false;
3419    }
3420
3421    /// Apply a batch of edits as one atomic history transaction.
3422    ///
3423    /// `edits` are `(byte range in the current pre-edit document, replacement)`
3424    /// pairs.
3425    pub(crate) fn replace_text_in_ranges(
3426        &mut self,
3427        edits: &[(Range<usize>, String)],
3428        window: &mut Window,
3429        cx: &mut Context<Self>,
3430    ) {
3431        if !self.is_editable() || edits.is_empty() {
3432            return;
3433        }
3434
3435        // Sort descending by start so applying front-of-vec first edits the
3436        // highest offsets first, leaving lower offsets unchanged.
3437        let mut sorted: Vec<(Range<usize>, &str)> = edits
3438            .iter()
3439            .map(|(range, text)| (range.clone(), text.as_str()))
3440            .collect();
3441        sorted.sort_by_key(|edit| std::cmp::Reverse(edit.0.start));
3442
3443        #[cfg(debug_assertions)]
3444        for pair in sorted.windows(2) {
3445            debug_assert!(
3446                pair[1].0.end <= pair[0].0.start,
3447                "replace_text_in_ranges requires disjoint ranges"
3448            );
3449        }
3450
3451        // Wrap multiple edits in one explicit transaction so they undo as a
3452        // unit. A single edit records directly, which keeps it eligible for
3453        // the undo manager's typing coalescing.
3454        let requested_intent = self.undo_manager.take_pending_intent();
3455        let selection_before = *self.active_selection();
3456        let original_selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
3457        // Snapshot the cursors before applying, so undo can restore them. A
3458        // delete has already expanded them over the text it removes, so they
3459        // collapse back to where the user left them.
3460        let selections_before: Vec<CursorSelection> = self
3461            .selections
3462            .iter()
3463            .map(|selection| {
3464                Self::collapse_for_intent(
3465                    requested_intent.unwrap_or(EditIntent::Atomic),
3466                    *selection,
3467                    &(selection.start..selection.end),
3468                )
3469            })
3470            .collect();
3471        let group = sorted.len() > 1;
3472        if group {
3473            self.undo_manager.begin_transaction();
3474        }
3475
3476        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
3477        let mut recorded = false;
3478        for (range, new_text) in &sorted {
3479            let old_text = self.text.clone();
3480            self.mode.adjust_auto_closed_pair(range, new_text.len());
3481            self.text.replace(range.clone(), new_text);
3482
3483            M::adjust_annotations(self, range, new_text.len());
3484            recorded |= self.push_history(
3485                &old_text,
3486                range,
3487                new_text,
3488                requested_intent,
3489                selection_before,
3490                None,
3491            );
3492
3493            // Incremental, single-range updates must run per edit.
3494            self.display_map
3495                .adjust_folds_for_edit(&old_text, range, new_text);
3496            self.display_map
3497                .on_text_changed(&self.text, range, &Rope::from(*new_text), cx);
3498
3499            self.mode.update_highlighter(
3500                super::mode::HighlighterUpdate {
3501                    selected_range: range,
3502                    old_text: &old_text,
3503                    new_text: &self.text,
3504                    change_text: new_text,
3505                    force: true,
3506                },
3507                window,
3508                cx,
3509            );
3510
3511            self.update_fold_candidates_incremental(range, new_text);
3512        }
3513
3514        if group {
3515            self.undo_manager.commit_transaction();
3516        }
3517
3518        // One observable update per batch instead of one per edit.
3519        if let Some(diagnostics) = self.mode.diagnostics_mut() {
3520            diagnostics.reset(&self.text)
3521        }
3522        M::refresh_language_features(self, window, cx);
3523        self.update_search(cx);
3524
3525        // Compute the resulting cursors.
3526        // One collapsed cursor per edit, at the end of its inserted text.
3527        let mut ascending: Vec<(Range<usize>, &str)> = sorted.clone();
3528        ascending.sort_by_key(|edit| edit.0.start);
3529        let text_len = self.text.len();
3530        let mut delta: isize = 0;
3531        let mut edit_results = Vec::with_capacity(ascending.len());
3532        for (range, new_text) in &ascending {
3533            let offset = ((range.start as isize + delta) as usize + new_text.len()).min(text_len);
3534            edit_results.push((range.clone(), offset));
3535            delta += new_text.len() as isize - (range.end as isize - range.start as isize);
3536        }
3537
3538        let mut used = vec![false; edit_results.len()];
3539        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(ascending.len());
3540        for selection in original_selections {
3541            if let Ok(index) = edit_results
3542                .binary_search_by_key(&(selection.start, selection.end), |(range, _)| {
3543                    (range.start, range.end)
3544                })
3545                && !used[index]
3546            {
3547                let offset = edit_results[index].1;
3548                used[index] = true;
3549                let mut selection = selection;
3550                selection.place_at(offset, None);
3551                new_selections.push(selection);
3552            }
3553        }
3554        for (index, (_, offset)) in edit_results.into_iter().enumerate() {
3555            if !used[index] {
3556                new_selections.push(CursorSelection::new(
3557                    self.selections.generate_id(),
3558                    offset,
3559                    offset,
3560                ));
3561            }
3562        }
3563        self.selections.replace_all(new_selections);
3564        self.selections.merge_overlapping();
3565
3566        // Record the cursor snapshots for undo/redo restore.
3567        let selections_after: Vec<CursorSelection> = self.selections.iter().copied().collect();
3568        if recorded {
3569            self.undo_manager.record_auto_closed_pairs(
3570                auto_closed_pairs_before,
3571                self.mode.auto_closed_pairs().clone(),
3572            );
3573            self.undo_manager
3574                .record_selections(selections_before, selections_after);
3575        }
3576
3577        self.ime_marked_range.take();
3578        self.update_preferred_column();
3579        if self.is_multi_line() {
3580            self.mode.update_auto_grow(&self.display_map);
3581        }
3582        if self.emit_events {
3583            cx.emit(InputEvent::Change);
3584        }
3585        cx.notify();
3586    }
3587
3588    /// Update fold candidates from tree-sitter syntax tree (full extraction).
3589    /// Used only on initial load or language changes.
3590    fn update_fold_candidates(&mut self) {
3591        if !self.mode.is_folding() {
3592            return;
3593        }
3594
3595        let Some(highlighter_rc) = self.mode.highlighter() else {
3596            return;
3597        };
3598
3599        let highlighter = highlighter_rc.borrow();
3600        let Some(highlighter) = highlighter.as_ref() else {
3601            return;
3602        };
3603
3604        let fold_ranges = highlighter.fold_ranges(&self.text);
3605        self.display_map.set_fold_candidates(fold_ranges);
3606    }
3607
3608    /// Incrementally update fold candidates after a text edit.
3609    /// Only traverses the edited region of the syntax tree instead of the full tree.
3610    fn update_fold_candidates_incremental(&mut self, edit_range: &Range<usize>, new_text: &str) {
3611        if !self.mode.is_folding() {
3612            return;
3613        }
3614
3615        let Some(highlighter_rc) = self.mode.highlighter() else {
3616            return;
3617        };
3618
3619        let highlighter = highlighter_rc.borrow();
3620        let Some(highlighter) = highlighter.as_ref() else {
3621            return;
3622        };
3623
3624        // The new byte range in the updated text after the edit
3625        let new_end = edit_range.start + new_text.len();
3626        self.display_map.update_fold_candidates_for_edit(
3627            |range, text| highlighter.fold_ranges_for_edit(range, text),
3628            edit_range.start..new_end,
3629            &self.text,
3630        );
3631    }
3632}
3633
3634impl<M: InputModeKind> EntityInputHandler for InputBaseState<M> {
3635    fn text_for_range(
3636        &mut self,
3637        range_utf16: Range<usize>,
3638        adjusted_range: &mut Option<Range<usize>>,
3639        _window: &mut Window,
3640        _cx: &mut Context<Self>,
3641    ) -> Option<String> {
3642        let range = self.range_from_utf16(&range_utf16);
3643        adjusted_range.replace(self.range_to_utf16(&range));
3644        Some(self.text.slice(range).to_string())
3645    }
3646
3647    fn selected_text_range(
3648        &mut self,
3649        _ignore_disabled_input: bool,
3650        _window: &mut Window,
3651        _cx: &mut Context<Self>,
3652    ) -> Option<UTF16Selection> {
3653        Some(UTF16Selection {
3654            range: self.range_to_utf16(&self.selected_range()),
3655            reversed: false,
3656        })
3657    }
3658
3659    fn marked_text_range(
3660        &self,
3661        _window: &mut Window,
3662        _cx: &mut Context<Self>,
3663    ) -> Option<Range<usize>> {
3664        self.ime_marked_range
3665            .map(|range| self.range_to_utf16(&range.into()))
3666    }
3667
3668    fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
3669        self.ime_marked_range = None;
3670        self.undo_manager.commit_transaction();
3671    }
3672
3673    /// Replace text in range.
3674    ///
3675    /// - If the new text is invalid, it will not be replaced.
3676    /// - If `range_utf16` is not provided, the current selected range will be used.
3677    fn replace_text_in_range(
3678        &mut self,
3679        range_utf16: Option<Range<usize>>,
3680        new_text: &str,
3681        window: &mut Window,
3682        cx: &mut Context<Self>,
3683    ) {
3684        let requested_intent = self.undo_manager.take_pending_intent();
3685        if !self.is_editable() {
3686            return;
3687        }
3688        let selection_before = *self.active_selection();
3689        // Committing a composition ends the transaction it opened, whether or
3690        // not the platform follows up with `unmark_text`.
3691        let ends_composition = self.ime_marked_range.is_some();
3692
3693        self.pause_blink_cursor(cx);
3694
3695        // NOTE: The normalization keeps the UTF-16 length, but may change the
3696        // UTF-8 byte length, so all the byte-offset calculations below must
3697        // use the normalized text.
3698        let new_text = self.normalize_input(new_text);
3699        let new_text: &str = &new_text;
3700
3701        let range = range_utf16
3702            .as_ref()
3703            .map(|range_utf16| self.range_from_utf16(range_utf16))
3704            .or(self.ime_marked_range.map(|range| {
3705                let range = self.range_to_utf16(&(range.start..range.end));
3706                self.range_from_utf16(&range)
3707            }))
3708            .unwrap_or(self.selected_range());
3709
3710        // Skip-over as a pure cursor move: a typed closer that already follows
3711        // the cursor moves past it without touching text or history, so Undo
3712        // never exposes a transient duplicate. Only for interactive single
3713        // keystrokes (current selection, no IME mark, not silent replays).
3714        // `take_pending_intent` above already consumed any request.
3715        if range.is_empty()
3716            && range.start == self.cursor()
3717            && self.ime_marked_range.is_none()
3718            && !self.silent_replace_text
3719            && self.is_code_editor()
3720            && self.selections.is_single()
3721            && self.active_selection().is_empty()
3722        {
3723            if let Some(target) = self.skip_over_target(new_text) {
3724                self.set_cursor_to(target);
3725                self.update_preferred_column();
3726                cx.notify();
3727                return;
3728            }
3729        }
3730
3731        if self.is_multi_line() {
3732            let multi_cursor = range_utf16.is_none()
3733                && self.ime_marked_range.is_none()
3734                && !self.selections.is_single();
3735            if multi_cursor {
3736                self.selections.merge_overlapping();
3737                let mut edits: Vec<(Range<usize>, String)> = self
3738                    .selections
3739                    .iter()
3740                    .map(|sel| (sel.start..sel.end, new_text.to_string()))
3741                    .collect();
3742                edits.sort_by_key(|(range, _)| range.start);
3743                // One keystroke across several cursors is one batch, committed
3744                // with the intent of the keystroke so a run of them coalesces
3745                // into one undo just like single-cursor typing.
3746                let intent =
3747                    requested_intent.unwrap_or_else(|| self.typing_intent(&edits, new_text));
3748                self.undo_manager.begin_transaction_with(intent);
3749                self.undo_manager.set_pending_intent(intent);
3750                self.replace_text_in_ranges(&edits, window, cx);
3751                self.undo_manager.commit_transaction();
3752            } else {
3753                if range_utf16.is_some() {
3754                    self.selections.remove_all_but_active();
3755                }
3756                if let Some(intent) = requested_intent {
3757                    self.undo_manager.set_pending_intent(intent);
3758                }
3759                if let Some((open_len, closer)) = self.auto_close_target(&range, new_text) {
3760                    // One edit keeps the pair atomic even at undo coalescing limits.
3761                    let replacement = format!("{new_text}{closer}");
3762                    self.replace_text_in_ranges(&[(range.clone(), replacement)], window, cx);
3763                    let cursor = range.start + new_text.len();
3764                    self.mode.track_auto_closed_pair(
3765                        cursor - open_len..cursor,
3766                        cursor..cursor + closer.len(),
3767                    );
3768                    self.undo_manager
3769                        .record_auto_closed_pairs_after(self.mode.auto_closed_pairs().clone());
3770                    self.set_cursor_to(cursor);
3771                    self.update_preferred_column();
3772                    self.undo_manager.record_selections(
3773                        vec![selection_before],
3774                        self.selections.iter().copied().collect(),
3775                    );
3776                } else {
3777                    self.replace_text_in_ranges(
3778                        &[(range.clone(), new_text.to_string())],
3779                        window,
3780                        cx,
3781                    );
3782                }
3783            }
3784            if ends_composition {
3785                self.undo_manager.commit_transaction();
3786            }
3787
3788            if !self.silent_replace_text {
3789                M::on_text_typed(self, &range, new_text, window, cx);
3790            }
3791            return;
3792        }
3793
3794        // Single-line path
3795        let old_text = self.text.clone();
3796        self.mode.adjust_auto_closed_pair(&range, new_text.len());
3797        self.text.replace(range.clone(), new_text);
3798
3799        let mut new_offset = (range.start + new_text.len()).min(self.text.len());
3800
3801        // True if the mask has changed the text, e.g. regrouping the
3802        // separators or completing a leading dot.
3803        let mut mask_changed = false;
3804
3805        if self.is_single_line() {
3806            let pending_text = self.text.to_string();
3807            // Check if the new text is valid.
3808            //
3809            // Only reject the edit if the old text was valid, to avoid
3810            // trapping a pre-existing invalid text (e.g. a `default_value`
3811            // that does not conform), the user can still edit to fix it.
3812            if !self.is_valid_input(&pending_text, cx)
3813                && self.is_valid_input(&old_text.to_string(), cx)
3814            {
3815                self.text = old_text;
3816                return;
3817            }
3818
3819            if !self.mask_pattern.is_none() {
3820                let mask_text = self.mask_pattern.mask(&pending_text);
3821                mask_changed = mask_text.as_str() != pending_text;
3822                self.text = Rope::from(mask_text.as_str());
3823                let new_text_len =
3824                    (new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
3825                new_offset = (range.start + new_text_len).min(mask_text.len());
3826            }
3827        }
3828
3829        if mask_changed {
3830            // Masking rewrites the whole document, so ranges recorded against
3831            // the old text no longer point at anything.
3832            M::reset_annotations(self);
3833        } else {
3834            M::adjust_annotations(self, &range, new_text.len());
3835        }
3836        if mask_changed {
3837            // A segment-based history entry no longer matches the masked
3838            // document, record a whole-document change instead, so that
3839            // undo/redo can restore the text exactly.
3840            self.push_history(
3841                &old_text,
3842                &(0..old_text.len()),
3843                &self.text.to_string(),
3844                Some(EditIntent::Atomic),
3845                selection_before,
3846                Some((new_offset..new_offset).into()),
3847            );
3848        } else {
3849            self.push_history(
3850                &old_text,
3851                &range,
3852                &new_text,
3853                requested_intent,
3854                selection_before,
3855                None,
3856            );
3857        }
3858        if let Some(diagnostics) = self.mode.diagnostics_mut() {
3859            diagnostics.reset(&self.text)
3860        }
3861        // Adjust folds before updating wrap map: remove overlapping folds and shift others
3862        self.display_map
3863            .adjust_folds_for_edit(&old_text, &range, new_text);
3864        self.display_map
3865            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
3866
3867        self.mode.update_highlighter::<M>(
3868            super::mode::HighlighterUpdate {
3869                selected_range: &range,
3870                old_text: &old_text,
3871                new_text: &self.text,
3872                change_text: &new_text,
3873                force: true,
3874            },
3875            window,
3876            cx,
3877        );
3878
3879        self.update_fold_candidates_incremental(&range, new_text);
3880        M::refresh_language_features(self, window, cx);
3881        self.set_cursor_to(new_offset);
3882        self.ime_marked_range.take();
3883        // A commit ends the IME composition: macOS delivers `insertText:` for
3884        // the confirmed candidate without a following `unmarkText`, so close
3885        // the transaction here. Leaving it open would keep merging every later
3886        // edit into the same change, which then carries the text and selection
3887        // of the first composition.
3888        if ends_composition {
3889            self.undo_manager
3890                .record_selections(vec![selection_before], vec![*self.active_selection()]);
3891            self.undo_manager.commit_transaction();
3892        }
3893        self.update_preferred_column();
3894        self.update_search(cx);
3895        if self.is_multi_line() {
3896            self.mode.update_auto_grow(&self.display_map);
3897        }
3898        if !self.silent_replace_text {
3899            M::on_text_typed(self, &range, &new_text, window, cx);
3900        }
3901        if self.emit_events {
3902            cx.emit(InputEvent::Change);
3903        }
3904        cx.notify();
3905    }
3906
3907    /// Mark text is the IME temporary insert on typing.
3908    fn replace_and_mark_text_in_range(
3909        &mut self,
3910        range_utf16: Option<Range<usize>>,
3911        new_text: &str,
3912        new_selected_range_utf16: Option<Range<usize>>,
3913        window: &mut Window,
3914        cx: &mut Context<Self>,
3915    ) {
3916        let requested_intent = self.undo_manager.take_pending_intent();
3917        if !self.is_editable() {
3918            return;
3919        }
3920        let selection_before = *self.active_selection();
3921
3922        let starts_composition = self.ime_marked_range.is_none();
3923        if starts_composition {
3924            self.undo_manager.begin_transaction();
3925        }
3926
3927        // Collapse any extra cursors so we never leave stale secondary cursors behind.
3928        self.selections.remove_all_but_active();
3929
3930        M::reset_language_features(self);
3931
3932        // See the same NOTE in `replace_text_in_range`.
3933        let new_text = self.normalize_input(new_text);
3934        let new_text: &str = &new_text;
3935
3936        let range = range_utf16
3937            .as_ref()
3938            .map(|range_utf16| self.range_from_utf16(range_utf16))
3939            .or(self.ime_marked_range.map(|range| {
3940                let range = self.range_to_utf16(&(range.start..range.end));
3941                self.range_from_utf16(&range)
3942            }))
3943            .unwrap_or(self.selected_range());
3944
3945        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
3946        let old_text = self.text.clone();
3947        self.mode.adjust_auto_closed_pair(&range, new_text.len());
3948        self.text.replace(range.clone(), new_text);
3949
3950        if self.is_single_line() {
3951            let pending_text = self.text.to_string();
3952            // See the same NOTE in `replace_text_in_range`.
3953            if !self.is_valid_input(&pending_text, cx)
3954                && self.is_valid_input(&old_text.to_string(), cx)
3955            {
3956                self.text = old_text;
3957                if starts_composition {
3958                    self.undo_manager.commit_transaction();
3959                }
3960                return;
3961            }
3962        }
3963
3964        M::adjust_annotations(self, &range, new_text.len());
3965        if let Some(diagnostics) = self.mode.diagnostics_mut() {
3966            diagnostics.reset(&self.text)
3967        }
3968        // Adjust folds before updating wrap map: remove overlapping folds and shift others
3969        self.display_map
3970            .adjust_folds_for_edit(&old_text, &range, new_text);
3971        self.display_map
3972            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
3973
3974        self.mode.update_highlighter::<M>(
3975            super::mode::HighlighterUpdate {
3976                selected_range: &range,
3977                old_text: &old_text,
3978                new_text: &self.text,
3979                change_text: &new_text,
3980                force: true,
3981            },
3982            window,
3983            cx,
3984        );
3985
3986        self.update_fold_candidates_incremental(&range, new_text);
3987        M::refresh_language_features(self, window, cx);
3988        if new_text.is_empty() {
3989            // Cancel selection, when cancel IME input.
3990            self.set_cursor_to(range.start);
3991            self.ime_marked_range = None;
3992        } else {
3993            self.ime_marked_range = Some((range.start..range.start + new_text.len()).into());
3994            let new_range = new_selected_range_utf16
3995                .as_ref()
3996                .map(|range_utf16| {
3997                    let new_text = Rope::from(new_text);
3998                    range.start + new_text.offset_utf16_to_offset(range_utf16.start)
3999                        ..range.start + new_text.offset_utf16_to_offset(range_utf16.end)
4000                })
4001                .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
4002            self.set_selection(new_range.start, new_range.end);
4003        }
4004        if self.is_multi_line() {
4005            self.mode.update_auto_grow(&self.display_map);
4006        }
4007        if self.push_history(
4008            &old_text,
4009            &range,
4010            new_text,
4011            requested_intent,
4012            selection_before,
4013            Some(*self.active_selection()),
4014        ) {
4015            self.undo_manager
4016                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4017            self.undo_manager.record_auto_closed_pairs(
4018                auto_closed_pairs_before,
4019                self.mode.auto_closed_pairs().clone(),
4020            );
4021        }
4022        if new_text.is_empty() {
4023            self.undo_manager.commit_transaction();
4024        }
4025        cx.notify();
4026    }
4027
4028    /// Used to position IME candidates.
4029    fn bounds_for_range(
4030        &mut self,
4031        range_utf16: Range<usize>,
4032        bounds: Bounds<Pixels>,
4033        _window: &mut Window,
4034        _cx: &mut Context<Self>,
4035    ) -> Option<Bounds<Pixels>> {
4036        let last_layout = self.last_layout.as_ref()?;
4037        let line_height = last_layout.line_height;
4038        let line_number_width = last_layout.line_number_width;
4039        let range = self.range_from_utf16(&range_utf16);
4040
4041        let mut start_origin = None;
4042        let mut end_origin = None;
4043        let line_number_origin = point(line_number_width, px(0.));
4044        let mut y_offset = last_layout.visible_top;
4045
4046        for (vi, line) in last_layout.lines.iter().enumerate() {
4047            if start_origin.is_some() && end_origin.is_some() {
4048                break;
4049            }
4050
4051            let index_offset = last_layout.visible_line_byte_offsets[vi];
4052
4053            if start_origin.is_none() {
4054                if let Some(p) = line.position_for_index(
4055                    range.start.saturating_sub(index_offset),
4056                    last_layout,
4057                    false,
4058                ) {
4059                    start_origin = Some(p + point(px(0.), y_offset));
4060                }
4061            }
4062
4063            if end_origin.is_none() {
4064                if let Some(p) = line.position_for_index(
4065                    range.end.saturating_sub(index_offset),
4066                    last_layout,
4067                    false,
4068                ) {
4069                    end_origin = Some(p + point(px(0.), y_offset));
4070                }
4071            }
4072
4073            y_offset += line.size(line_height).height;
4074        }
4075
4076        let start_origin = start_origin.unwrap_or_default();
4077        let mut end_origin = end_origin.unwrap_or_default();
4078        // Ensure at same line.
4079        end_origin.y = start_origin.y;
4080
4081        Some(Bounds::from_corners(
4082            bounds.origin + line_number_origin + start_origin,
4083            // + line_height for show IME panel under the cursor line.
4084            bounds.origin + line_number_origin + point(end_origin.x, end_origin.y + line_height),
4085        ))
4086    }
4087
4088    fn character_index_for_point(
4089        &mut self,
4090        point: gpui::Point<Pixels>,
4091        _window: &mut Window,
4092        _cx: &mut Context<Self>,
4093    ) -> Option<usize> {
4094        let last_layout = self.last_layout.as_ref()?;
4095        let line_point = self.last_bounds?.localize(&point)?;
4096
4097        for (vi, line) in last_layout.lines.iter().enumerate() {
4098            let offset = last_layout.visible_line_byte_offsets[vi];
4099            if let Some(utf8_index) = line.index_for_position(line_point, last_layout) {
4100                return Some(self.offset_to_utf16(offset + utf8_index));
4101            }
4102        }
4103
4104        None
4105    }
4106}
4107
4108impl<M: InputModeKind> Focusable for InputBaseState<M> {
4109    fn focus_handle(&self, _cx: &App) -> FocusHandle {
4110        self.focus_handle.clone()
4111    }
4112}
4113
4114impl<M: InputModeKind> Render for InputBaseState<M> {
4115    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
4116        // Before anything reads it: the element resolves this style during
4117        // layout and paint, and both happen after this call in the same frame.
4118        self.editor_style = self
4119            .projected_editor_style
4120            .resolved(&crate::Theme::global(cx).tokens);
4121        let entity = cx.entity();
4122        if self._pending_update {
4123            self.mode.update_highlighter::<M>(
4124                super::mode::HighlighterUpdate {
4125                    selected_range: &(0..0),
4126                    old_text: &self.text,
4127                    new_text: &self.text,
4128                    change_text: "",
4129                    force: false,
4130                },
4131                window,
4132                cx,
4133            );
4134
4135            self.update_fold_candidates();
4136            M::refresh_language_features(self, window, cx);
4137            self._pending_update = false;
4138        }
4139
4140        let element = div()
4141            .id("input-state")
4142            .key_context(CONTEXT)
4143            .track_focus(&self.focus_handle)
4144            .when(self.is_editable(), |this| {
4145                this.on_action(window.listener_for(&entity, InputBaseState::backspace))
4146                    .on_action(window.listener_for(&entity, InputBaseState::delete))
4147                    .on_action(
4148                        window.listener_for(&entity, InputBaseState::delete_to_beginning_of_line),
4149                    )
4150                    .on_action(window.listener_for(&entity, InputBaseState::delete_to_end_of_line))
4151                    .on_action(window.listener_for(&entity, InputBaseState::delete_previous_word))
4152                    .on_action(window.listener_for(&entity, InputBaseState::delete_next_word))
4153                    .on_action(window.listener_for(&entity, InputBaseState::enter))
4154                    .on_action(window.listener_for(&entity, InputBaseState::escape))
4155                    .on_action(window.listener_for(&entity, InputBaseState::paste))
4156                    .on_action(window.listener_for(&entity, InputBaseState::cut))
4157                    .on_action(window.listener_for(&entity, InputBaseState::undo))
4158                    .on_action(window.listener_for(&entity, InputBaseState::redo))
4159                    .when(self.is_multi_line(), |this| {
4160                        this.on_action(window.listener_for(&entity, InputBaseState::indent_inline))
4161                            .on_action(window.listener_for(&entity, InputBaseState::outdent_inline))
4162                            .on_action(window.listener_for(&entity, InputBaseState::indent_block))
4163                            .on_action(window.listener_for(&entity, InputBaseState::outdent_block))
4164                    })
4165            })
4166            .on_action(window.listener_for(&entity, InputBaseState::left))
4167            .on_action(window.listener_for(&entity, InputBaseState::right))
4168            .on_action(window.listener_for(&entity, InputBaseState::select_left))
4169            .on_action(window.listener_for(&entity, InputBaseState::select_right))
4170            .when(self.is_multi_line(), |this| {
4171                this.on_action(window.listener_for(&entity, InputBaseState::up))
4172                    .on_action(window.listener_for(&entity, InputBaseState::down))
4173                    .on_action(window.listener_for(&entity, InputBaseState::select_up))
4174                    .on_action(window.listener_for(&entity, InputBaseState::select_down))
4175                    .on_action(window.listener_for(&entity, InputBaseState::page_up))
4176                    .on_action(window.listener_for(&entity, InputBaseState::page_down))
4177                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_above))
4178                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_below))
4179            })
4180            .on_action(window.listener_for(&entity, InputBaseState::on_action_select_all))
4181            .on_action(window.listener_for(&entity, InputBaseState::select_to_start_of_line))
4182            .on_action(window.listener_for(&entity, InputBaseState::select_to_end_of_line))
4183            .on_action(window.listener_for(&entity, InputBaseState::select_to_previous_word))
4184            .on_action(window.listener_for(&entity, InputBaseState::select_to_next_word))
4185            .on_action(window.listener_for(&entity, InputBaseState::home))
4186            .on_action(window.listener_for(&entity, InputBaseState::end))
4187            .on_action(window.listener_for(&entity, InputBaseState::move_to_start))
4188            .on_action(window.listener_for(&entity, InputBaseState::move_to_end))
4189            .on_action(window.listener_for(&entity, InputBaseState::move_to_previous_word))
4190            .on_action(window.listener_for(&entity, InputBaseState::move_to_next_word))
4191            .on_action(window.listener_for(&entity, InputBaseState::select_to_start))
4192            .on_action(window.listener_for(&entity, InputBaseState::select_to_end))
4193            .on_action(window.listener_for(&entity, InputBaseState::show_character_palette))
4194            .on_action(window.listener_for(&entity, InputBaseState::copy))
4195            .on_action(window.listener_for(&entity, InputBaseState::on_action_search))
4196            .on_action(window.listener_for(&entity, InputBaseState::on_action_replace))
4197            .on_mouse_down(
4198                MouseButton::Left,
4199                window.listener_for(&entity, InputBaseState::on_mouse_down),
4200            )
4201            .on_mouse_down(
4202                MouseButton::Right,
4203                window.listener_for(&entity, InputBaseState::on_mouse_down),
4204            )
4205            .on_mouse_up(
4206                MouseButton::Left,
4207                window.listener_for(&entity, InputBaseState::on_mouse_up),
4208            )
4209            .on_mouse_up(
4210                MouseButton::Right,
4211                window.listener_for(&entity, InputBaseState::on_mouse_up),
4212            )
4213            .on_mouse_move(window.listener_for(&entity, InputBaseState::on_mouse_move))
4214            .on_scroll_wheel(window.listener_for(&entity, InputBaseState::on_scroll_wheel))
4215            .when(self.is_multi_line() && !self.disabled, |this| {
4216                this.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
4217            })
4218            .when(!self.disabled, |this| {
4219                if self.is_multi_line() && window.modifiers().alt {
4220                    this.cursor_crosshair()
4221                } else {
4222                    this.cursor_text()
4223                }
4224            })
4225            .flex_1()
4226            .when(self.is_multi_line(), |this| this.h_full())
4227            .flex_grow_1()
4228            .overflow_x_hidden()
4229            .when(self.is_multi_line(), |this| {
4230                this.pt(self.editor_paddings.top)
4231                    .pr(self.editor_paddings.right)
4232                    .pb(self.editor_paddings.bottom)
4233                    .pl(self.editor_paddings.left)
4234            })
4235            .child(TextElement::new(entity.clone()).placeholder(self.placeholder.clone()))
4236            .when(self.shows_scrollbar(), |this| {
4237                this.child(EditorScrollbar::new(entity.clone()))
4238            });
4239
4240        // Actions only one mode handles are registered by that mode, where
4241        // `Self` is concrete enough to name its own entity type.
4242        M::register_actions(element, &entity, window)
4243    }
4244}
4245
4246#[cfg(test)]
4247mod tests {
4248    use super::*;
4249
4250    use crate::theme::Theme;
4251    use gpui::{TestAppContext, VisualTestContext, size};
4252
4253    use crate::input::{EditorMode, EditorState, InputMode, LanguageConfig, TextareaMode};
4254
4255    fn set_test_syntax_provider(
4256        provider: Rc<dyn crate::input::SyntaxContextProvider>,
4257        cx: &mut App,
4258    ) {
4259        struct TestLanguages(Rc<dyn crate::input::SyntaxContextProvider>);
4260        impl crate::input::LanguageProvider for TestLanguages {
4261            fn syntax_context_provider(
4262                &self,
4263                _: &str,
4264            ) -> Option<Rc<dyn crate::input::SyntaxContextProvider>> {
4265                Some(self.0.clone())
4266            }
4267        }
4268        crate::input::set_language_provider(Rc::new(TestLanguages(provider)), cx);
4269    }
4270
4271    struct TestRoot<M: InputModeKind>(Entity<InputBaseState<M>>);
4272
4273    impl<M: InputModeKind> Render for TestRoot<M> {
4274        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4275            div().size_full().child(self.0.clone())
4276        }
4277    }
4278
4279    struct InputView<M: InputModeKind> {
4280        input: Entity<InputBaseState<M>>,
4281        window_handle: gpui::WindowHandle<TestRoot<M>>,
4282    }
4283
4284    /// Helper to open a state of one mode in a window for testing.
4285    impl<M: InputModeKind> InputView<M> {
4286        fn build_with(
4287            cx: &mut TestAppContext,
4288            make: impl FnOnce(&mut Window, &mut Context<InputBaseState<M>>) -> InputBaseState<M>
4289            + 'static,
4290        ) -> Self {
4291            let mut input: Option<Entity<InputBaseState<M>>> = None;
4292
4293            let window = cx.update(|cx| {
4294                cx.open_window(Default::default(), |window, cx| {
4295                    // Set up the theme first
4296                    cx.set_global(Theme::default());
4297                    // Initialize input keybindings
4298                    super::super::init(cx);
4299
4300                    input = Some(cx.new(|cx| make(window, cx)));
4301
4302                    cx.new(|_| TestRoot(input.clone().unwrap()))
4303                })
4304                .unwrap()
4305            });
4306
4307            Self {
4308                input: input.clone().unwrap(),
4309                window_handle: window,
4310            }
4311        }
4312    }
4313
4314    impl InputView<EditorMode> {
4315        /// An editor state, for the tests that exercise code-editor behavior.
4316        fn new(cx: &mut TestAppContext) -> Self {
4317            Self::build_editor(cx, |state| state)
4318        }
4319
4320        fn build_editor(
4321            cx: &mut TestAppContext,
4322            f: impl FnOnce(InputBaseState<EditorMode>) -> InputBaseState<EditorMode> + 'static,
4323        ) -> Self {
4324            Self::build_with(cx, move |window, cx| {
4325                f(crate::input::EditorState::new(window, cx).language("sql"))
4326            })
4327        }
4328    }
4329
4330    impl InputView<TextareaMode> {
4331        fn build_textarea(
4332            cx: &mut TestAppContext,
4333            f: impl FnOnce(InputBaseState<TextareaMode>) -> InputBaseState<TextareaMode> + 'static,
4334        ) -> Self {
4335            Self::build_with(cx, move |window, cx| {
4336                f(crate::input::TextareaState::new(window, cx))
4337            })
4338        }
4339    }
4340
4341    impl InputView<InputMode> {
4342        /// A single-line state, the default these tests were written against.
4343        fn build(
4344            cx: &mut TestAppContext,
4345            f: impl FnOnce(InputBaseState<InputMode>) -> InputBaseState<InputMode> + 'static,
4346        ) -> Self {
4347            Self::build_with(cx, move |window, cx| {
4348                f(crate::input::InputState::new(window, cx))
4349            })
4350        }
4351    }
4352
4353    #[gpui::test]
4354    fn test_noop_scroll_notifies_diagnostic_dismissal(cx: &mut TestAppContext) {
4355        use std::{cell::Cell, rc::Rc};
4356
4357        cx.update(crate::init);
4358        let mut input = None;
4359        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
4360            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
4361            gpui::EmptyView
4362        });
4363        let input = input.unwrap();
4364        input.update(cx, |state, cx| {
4365            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
4366            state.scroll_size = size(px(100.), px(20.));
4367            state.present_diagnostic(crate::input::DiagnosticEntry::default(), cx);
4368        });
4369        let notifications = Rc::new(Cell::new(0));
4370        let count = notifications.clone();
4371        let _subscription =
4372            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
4373
4374        window
4375            .update(cx, |_, window, cx| {
4376                input.update(cx, |state, cx| {
4377                    state.on_scroll_wheel(
4378                        &ScrollWheelEvent {
4379                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
4380                            ..Default::default()
4381                        },
4382                        window,
4383                        cx,
4384                    );
4385                    assert_eq!(state.scroll_handle.offset(), Point::default());
4386                    assert!(state.diagnostic_popover().is_none());
4387                });
4388            })
4389            .unwrap();
4390        assert_eq!(
4391            notifications.get(),
4392            1,
4393            "clearing a diagnostic must notify even when scrolling is clamped"
4394        );
4395        window
4396            .update(cx, |_, window, cx| {
4397                input.update(cx, |state, cx| {
4398                    state.on_scroll_wheel(
4399                        &ScrollWheelEvent {
4400                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
4401                            ..Default::default()
4402                        },
4403                        window,
4404                        cx,
4405                    );
4406                });
4407            })
4408            .unwrap();
4409        assert_eq!(notifications.get(), 1, "an unchanged input must stay quiet");
4410    }
4411
4412    #[gpui::test]
4413    fn test_cursor_layout_consumer_updates_after_selection(cx: &mut TestAppContext) {
4414        use std::{cell::Cell, rc::Rc};
4415        struct Panel {
4416            input: Entity<crate::input::EditorState>,
4417            observed: Rc<Cell<Option<(Bounds<Pixels>, Pixels)>>>,
4418        }
4419        impl Render for Panel {
4420            fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
4421                self.observed.set(self.input.read(cx).cursor_layout());
4422                div().size_full().child(self.input.clone())
4423            }
4424        }
4425        struct Root(Entity<Panel>);
4426        impl Render for Root {
4427            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4428                div().size_full().child(
4429                    self.0
4430                        .clone()
4431                        .cached(gpui::StyleRefinement::default().size_full()),
4432                )
4433            }
4434        }
4435        cx.update(crate::init);
4436        let observed = Rc::new(Cell::new(None));
4437        let mut input = None;
4438        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
4439            let state = cx.new(|cx| {
4440                let mut state = crate::input::EditorState::new(window, cx);
4441                state.set_value("abcdefgh", window, cx);
4442                state
4443            });
4444            input = Some(state.clone());
4445            Root(cx.new(|_| Panel {
4446                input: state,
4447                observed: observed.clone(),
4448            }))
4449        });
4450        cx.run_until_parked();
4451        let input = input.unwrap();
4452        let before = input.read_with(cx, |state, _| state.cursor_layout());
4453        assert_eq!(observed.get(), before);
4454        window
4455            .update(cx, |_, _, cx| {
4456                input.update(cx, |state, cx| state.select_to_with_affinity(3, false, cx));
4457            })
4458            .unwrap();
4459        cx.run_until_parked();
4460        window.update(cx, |_, _, cx| cx.notify()).unwrap();
4461        let after = input.read_with(cx, |state, _| state.cursor_layout());
4462        assert_ne!(after, before, "the caret must actually move");
4463        assert_eq!(
4464            observed.get(),
4465            after,
4466            "render consumers must receive the newly painted caret geometry"
4467        );
4468    }
4469
4470    #[gpui::test]
4471    fn test_input_scroll_offset_notifies_only_on_change(cx: &mut TestAppContext) {
4472        use std::{cell::Cell, rc::Rc};
4473
4474        cx.update(crate::init);
4475        let mut input = None;
4476        let _window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
4477            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
4478            gpui::EmptyView
4479        });
4480        let input = input.unwrap();
4481        let notifications = Rc::new(Cell::new(0));
4482        let count = notifications.clone();
4483        let _subscription =
4484            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
4485
4486        input.update(cx, |state, _| {
4487            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
4488            state.scroll_size = size(px(300.), px(20.));
4489        });
4490        for target in [
4491            None,
4492            Some(point(px(0.), px(0.))),
4493            Some(point(px(20.), px(50.))),
4494        ] {
4495            input.update(cx, |state, cx| state.update_scroll_offset(target, cx));
4496            assert_eq!(
4497                notifications.get(),
4498                0,
4499                "an unchanged clamped offset must stay quiet"
4500            );
4501        }
4502        input.update(cx, |state, cx| {
4503            state.update_scroll_offset(Some(point(px(-40.), px(0.))), cx);
4504            assert_eq!(state.scroll_handle.offset(), point(px(-40.), px(0.)));
4505        });
4506        assert_eq!(notifications.get(), 1, "a scroll must notify");
4507        input.update(cx, |state, cx| {
4508            state.update_scroll_offset(Some(point(px(-400.), px(50.))), cx);
4509            assert_eq!(state.scroll_handle.offset(), point(px(-200.), px(0.)));
4510        });
4511        assert_eq!(notifications.get(), 2);
4512        input.update(cx, |state, cx| {
4513            state.update_scroll_offset(Some(point(px(-500.), px(0.))), cx);
4514        });
4515        assert_eq!(
4516            notifications.get(),
4517            2,
4518            "clamping to the current offset must stay quiet"
4519        );
4520        input.update(cx, |state, cx| {
4521            state.scroll_size.width = px(110.);
4522            state.update_scroll_offset(None, cx);
4523            assert_eq!(state.scroll_handle.offset(), point(px(-10.), px(0.)));
4524        });
4525        assert_eq!(
4526            notifications.get(),
4527            3,
4528            "a smaller scroll range must clamp and notify"
4529        );
4530    }
4531
4532    #[gpui::test]
4533    fn test_input_does_not_invalidate_cached_parent_during_paint(cx: &mut TestAppContext) {
4534        use std::{cell::Cell, rc::Rc};
4535
4536        struct Panel {
4537            input: Entity<crate::input::InputState>,
4538            renders: Rc<Cell<usize>>,
4539        }
4540        impl Render for Panel {
4541            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4542                self.renders.set(self.renders.get() + 1);
4543                div().size_full().child(self.input.clone())
4544            }
4545        }
4546        struct Root(Entity<Panel>);
4547        impl Render for Root {
4548            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4549                div().size_full().child(
4550                    self.0
4551                        .clone()
4552                        .cached(gpui::StyleRefinement::default().size_full()),
4553                )
4554            }
4555        }
4556
4557        cx.update(crate::init);
4558        let renders = Rc::new(Cell::new(0));
4559        let mut input = None;
4560        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
4561            let state = cx.new(|cx| crate::input::InputState::new(window, cx));
4562            input = Some(state.clone());
4563            Root(cx.new(|_| Panel {
4564                input: state,
4565                renders: renders.clone(),
4566            }))
4567        });
4568        cx.run_until_parked();
4569        let before = renders.get();
4570        assert!(before > 0);
4571        for _ in 0..60 {
4572            window.update(cx, |_, _, cx| cx.notify()).unwrap();
4573        }
4574        assert_eq!(
4575            renders.get(),
4576            before,
4577            "an idle input must not invalidate its cached parent"
4578        );
4579
4580        let input = input.unwrap();
4581        window
4582            .update(cx, |_, window, cx| {
4583                input.update(cx, |state, cx| state.set_value("changed", window, cx));
4584            })
4585            .unwrap();
4586        assert!(
4587            renders.get() > before,
4588            "text changes must invalidate the parent"
4589        );
4590        // The first paint reports the changed text extent to layout consumers.
4591        window.update(cx, |_, _, cx| cx.notify()).unwrap();
4592        let before = renders.get();
4593        for _ in 0..60 {
4594            window.update(cx, |_, _, cx| cx.notify()).unwrap();
4595        }
4596        assert_eq!(renders.get(), before, "the edited input must settle again");
4597
4598        cx.simulate_window_resize(window.into(), size(px(240.), px(100.)));
4599        cx.run_until_parked();
4600        assert!(renders.get() > before, "resizing must invalidate layout");
4601    }
4602
4603    #[gpui::test]
4604    fn textarea_cursor_treats_crlf_as_one_newline(cx: &mut TestAppContext) {
4605        cx.update(crate::init);
4606        let view = InputView::build_textarea(cx, |state| state);
4607        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
4608        cx.update(|window, cx| {
4609            view.input.update(cx, |state, cx| {
4610                for original in ["\r\nlast", "\u{feff}first\r\nlast\n", "first\nlast\r\n"] {
4611                    state.set_value(original, window, cx);
4612                    let newline = original.find('\n').unwrap();
4613                    let end = if original.as_bytes()[newline.saturating_sub(1)] == b'\r' {
4614                        newline - 1
4615                    } else {
4616                        newline
4617                    };
4618                    state.end(&MoveEnd, window, cx);
4619                    assert_eq!(state.cursor(), end);
4620                    state.right(&MoveRight, window, cx);
4621                    assert_eq!(state.cursor(), newline + 1);
4622                    state.left(&MoveLeft, window, cx);
4623                    assert_eq!(state.cursor(), end);
4624                    state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
4625                    assert_eq!(state.selected_range(), end..end);
4626                    state.move_to_end(&MoveToEnd, window, cx);
4627                    assert_eq!(state.cursor(), original.len());
4628                    state.move_to_start(&MoveToStart, window, cx);
4629                    assert_eq!(state.cursor(), 0);
4630                    assert_eq!(state.value().as_bytes(), original.as_bytes());
4631                }
4632                // A lone CR is an ordinary character; it must not skip its neighbour.
4633                state.set_value("a\rb", window, cx);
4634                state.right(&MoveRight, window, cx);
4635                assert_eq!(state.cursor(), 1);
4636                state.right(&MoveRight, window, cx);
4637                assert_eq!(state.cursor(), 2);
4638            })
4639        });
4640    }
4641
4642    #[gpui::test]
4643    fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) {
4644        cx.update(crate::init);
4645
4646        // A single-line input keeps its caret in view by moving its own offset;
4647        // it has no viewport to drag, so a scrollbar in a text field is a
4648        // control that does not exist.
4649        let single = InputView::build(cx, |state| state);
4650        single
4651            .input
4652            .update(cx, |state, _| assert!(!state.shows_scrollbar()));
4653
4654        let multi = InputView::build_textarea(cx, |state| state);
4655        multi
4656            .input
4657            .update(cx, |state, _| assert!(state.shows_scrollbar()));
4658    }
4659
4660    #[gpui::test]
4661    fn context_menu_handler_is_deferred_and_respects_disabled(cx: &mut TestAppContext) {
4662        use std::{cell::Cell, rc::Rc};
4663        cx.update(crate::init);
4664        let input_view = InputView::new(cx);
4665        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4666        let input = input_view.input;
4667        let calls = Rc::new(Cell::new(0usize));
4668        let items = Rc::new(Cell::new(0usize));
4669
4670        cx.update(|window, cx| {
4671            input.update(cx, |state, cx| {
4672                let calls2 = calls.clone();
4673                let items2 = items.clone();
4674                state.on_context_menu(Rc::new(move |menu, _, _, _, _| {
4675                    calls2.set(calls2.get() + 1);
4676                    items2.set(menu.items.len());
4677                }));
4678                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
4679            })
4680        });
4681        assert_eq!(calls.get(), 1);
4682        assert_eq!(items.get(), 0);
4683
4684        cx.update(|window, cx| {
4685            input.update(cx, |state, cx| {
4686                state.disabled = true;
4687                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
4688            })
4689        });
4690        assert_eq!(calls.get(), 1);
4691    }
4692
4693    #[gpui::test]
4694    fn test_readonly_rejects_user_edits_only(cx: &mut TestAppContext) {
4695        let input_view = InputView::new(cx);
4696        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4697        let input = input_view.input;
4698
4699        cx.update(|window, cx| {
4700            input.update(cx, |state, cx| {
4701                state.set_value("hello", window, cx);
4702                state.set_readonly(true, cx);
4703            });
4704        });
4705
4706        cx.update(|_, cx| {
4707            input.read_with(cx, |state, _| {
4708                assert!(!state.is_editable());
4709                assert!(!state.is_replaceable());
4710            });
4711        });
4712
4713        // Typing (and IME) goes through the input handler, it must be rejected.
4714        cx.update(|window, cx| {
4715            input.update(cx, |state, cx| {
4716                state.replace_text_in_range(None, " world", window, cx);
4717                state.replace_and_mark_text_in_range(None, "あ", None, window, cx);
4718            });
4719        });
4720        cx.update(|_, cx| {
4721            input.read_with(cx, |state, _| assert_eq!(state.value(), "hello"));
4722        });
4723
4724        // The programmatic APIs are not limited by the readonly mode.
4725        cx.update(|window, cx| {
4726            input.update(cx, |state, cx| {
4727                state.insert(" world", window, cx);
4728                state.set_value("changed", window, cx);
4729            });
4730        });
4731        cx.update(|_, cx| {
4732            input.read_with(cx, |state, _| assert_eq!(state.value(), "changed"));
4733        });
4734
4735        // And the user can edit again after leaving the readonly mode.
4736        // The caret is at the start, because `set_value` has reset the selection.
4737        cx.update(|window, cx| {
4738            input.update(cx, |state, cx| {
4739                state.set_readonly(false, cx);
4740                state.replace_text_in_range(None, "!", window, cx);
4741            });
4742        });
4743        cx.update(|_, cx| {
4744            input.read_with(cx, |state, _| {
4745                assert!(state.is_editable());
4746                assert_eq!(state.value(), "!changed");
4747            });
4748        });
4749    }
4750
4751    /// Regression test: `scroll_to` at end-of-buffer must produce a deferred
4752    /// scroll target within the safe scroll range, so the painted frame
4753    /// matches what `update_scroll_offset` persists (no jitter). A small
4754    /// `cursor_surrounding_lines` override used to mismatch the hardcoded
4755    /// 3-line edge clearance in `scroll_to`, overshooting `safe_y_min`.
4756    #[gpui::test]
4757    fn test_scroll_to_eob_does_not_overshoot_safe_range(cx: &mut TestAppContext) {
4758        let input_view = InputView::new(cx);
4759        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4760        let input = input_view.input;
4761
4762        // JetBrains-style: 1 trailing empty row + 1-line cursor surrounding.
4763        cx.update(|window, cx| {
4764            input.update(cx, |state, cx| {
4765                state.set_scroll_beyond_last_line(Some(1), window, cx);
4766                state.set_cursor_surrounding_lines(Some(1), window, cx);
4767                let text: String = (1..=50)
4768                    .map(|i| format!("line {i}"))
4769                    .collect::<Vec<_>>()
4770                    .join("\n");
4771                state.set_value(text, window, cx);
4772            });
4773        });
4774        cx.run_until_parked();
4775
4776        // Sanity: paint populated `scroll_size` and `input_bounds` — without
4777        // these, `safe_y_min` below collapses to 0 and the assertion is vacuous.
4778        cx.update(|_, cx| {
4779            input.read_with(cx, |state, _| {
4780                assert!(
4781                    state.scroll_size.height > px(0.),
4782                    "scroll_size not populated by initial paint"
4783                );
4784                assert!(
4785                    state.input_bounds.size.height > px(0.),
4786                    "input_bounds not populated by initial paint"
4787                );
4788            });
4789        });
4790
4791        // Move cursor to end with downward direction — same code path as a
4792        // `Down` keystroke at EOB. `scroll_to` runs synchronously inside
4793        // `move_to`; inspect `deferred_scroll_offset` in the same closure
4794        // before the next paint consumes and clears it.
4795        cx.update(|_, cx| {
4796            input.update(cx, |state, cx| {
4797                let end = state.text.len();
4798                state.move_to(end, Some(MoveDirection::Down), cx);
4799
4800                let deferred = state
4801                    .deferred_scroll_offset
4802                    .expect("scroll_to should populate deferred_scroll_offset");
4803                let safe_y_min =
4804                    (-state.scroll_size.height + state.input_bounds.size.height).min(px(0.));
4805
4806                assert!(
4807                    deferred.y >= safe_y_min,
4808                    "deferred_scroll_offset.y = {:?} below safe_y_min = {:?} \
4809                     — paint would jitter (Bug C regression)",
4810                    deferred.y,
4811                    safe_y_min,
4812                );
4813            });
4814        });
4815    }
4816
4817    #[gpui::test]
4818    fn test_next_search_match_reveals_with_padding_after_manual_scroll(cx: &mut TestAppContext) {
4819        assert_search_reveals_with_padding_after_manual_scroll(false, cx);
4820    }
4821
4822    #[gpui::test]
4823    fn test_previous_search_match_reveals_with_padding_after_manual_scroll(
4824        cx: &mut TestAppContext,
4825    ) {
4826        assert_search_reveals_with_padding_after_manual_scroll(true, cx);
4827    }
4828
4829    fn assert_search_reveals_with_padding_after_manual_scroll(
4830        previous: bool,
4831        cx: &mut TestAppContext,
4832    ) {
4833        let input_view = InputView::new(cx);
4834        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4835        let input = input_view.input;
4836        let text = (0..160)
4837            .map(|row| {
4838                if matches!(row, 20 | 60 | 100) {
4839                    format!("match on row {row}")
4840                } else {
4841                    format!("line {row}")
4842                }
4843            })
4844            .collect::<Vec<_>>()
4845            .join("\n");
4846        let start = text.match_indices("match").nth(1).unwrap().0;
4847        let expected_match = start..start + "match".len();
4848        cx.update(|window, cx| {
4849            input.update(cx, |state, cx| {
4850                state.set_cursor_surrounding_lines(Some(3), window, cx);
4851                state.set_value(text, window, cx);
4852                state.set_search_query("match", true, cx);
4853                if previous {
4854                    state.search_session.matcher.next();
4855                    state.search_session.matcher.next();
4856                }
4857            });
4858        });
4859        cx.run_until_parked();
4860        cx.update(|_, cx| {
4861            input.update(cx, |state, cx| {
4862                let line_height = state.last_layout.as_ref().unwrap().line_height;
4863                let y = if previous { px(0.) } else { -line_height * 80. };
4864                state.set_scroll_offset(point(px(0.), y), cx);
4865            });
4866        });
4867        cx.run_until_parked();
4868        input.read_with(&cx, |state, _| {
4869            let visible = state.visible_row_range().unwrap();
4870            if previous {
4871                assert!(visible.end <= 60, "target must be below the viewport");
4872            } else {
4873                assert!(visible.start > 60, "target must be above the viewport");
4874            }
4875        });
4876        cx.update(|_, cx| {
4877            input.update(cx, |state, cx| {
4878                let range = if previous {
4879                    state.previous_search_match(cx)
4880                } else {
4881                    state.next_search_match(cx)
4882                };
4883                assert_eq!(range, Some(expected_match));
4884                assert_eq!(state.search_session.matcher.label(), "2/3");
4885            });
4886        });
4887        cx.run_until_parked();
4888        input.read_with(&cx, |state, _| {
4889            assert!(state.visible_row_range().unwrap().contains(&60));
4890            let line_height = state.last_layout.as_ref().unwrap().line_height;
4891            let target_y = line_height * 60. + state.scroll_handle.offset().y;
4892            // Three lines of edge clearance include the matched line itself.
4893            assert!(target_y >= line_height * 2. - px(0.1));
4894            assert!(
4895                target_y + line_height * 3.
4896                    <= state.last_bounds.as_ref().unwrap().size.height + px(0.1),
4897                "search must preserve the configured surrounding-line padding"
4898            );
4899        });
4900    }
4901
4902    #[gpui::test]
4903    fn test_number_step(cx: &mut TestAppContext) {
4904        let input = InputView::build(cx, |state| state).input;
4905
4906        cx.update(|cx| {
4907            input.update(cx, |_state, cx| {
4908                assert_eq!(
4909                    NumberStep::from(5.).value(123., StepAction::Increment, cx),
4910                    5.
4911                );
4912
4913                // The step can differ by direction at a boundary: at 1.0 it
4914                // is 0.1 going down and 0.5 going up.
4915                let step = NumberStep::by_value(|value, action, _cx| {
4916                    let below = match action {
4917                        StepAction::Increment => value < 1.0,
4918                        StepAction::Decrement => value <= 1.0,
4919                    };
4920                    if below { 0.1 } else { 0.5 }
4921                });
4922                assert_eq!(step.value(0.5, StepAction::Increment, cx), 0.1);
4923                assert_eq!(step.value(1.0, StepAction::Increment, cx), 0.5);
4924                assert_eq!(step.value(1.0, StepAction::Decrement, cx), 0.1);
4925                assert_eq!(step.value(2.0, StepAction::Decrement, cx), 0.5);
4926            });
4927        });
4928    }
4929
4930    #[gpui::test]
4931    fn test_number_input_normalization(cx: &mut TestAppContext) {
4932        let input_view = InputView::build(cx, |state| {
4933            state.mask_pattern(MaskPattern::Number {
4934                separator: None,
4935                fraction: None,
4936            })
4937        });
4938        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4939        let input = input_view.input;
4940
4941        // Full-width digits and the ideographic full stop are normalized,
4942        // and the cursor is at the end (in normalized bytes, not the
4943        // original 12 bytes).
4944        cx.update(|window, cx| {
4945            input.update(cx, |state, cx| {
4946                state.replace_text_in_range(None, "12。5", window, cx);
4947            });
4948        });
4949        cx.run_until_parked();
4950        cx.update(|_, cx| {
4951            input.read_with(cx, |state, _| {
4952                assert_eq!(state.value(), "12.5");
4953                let cursor: Range<usize> = state.selected_range();
4954                assert_eq!(cursor, 4..4);
4955            });
4956        });
4957
4958        // Non-numeric input is rejected.
4959        cx.update(|window, cx| {
4960            input.update(cx, |state, cx| {
4961                state.replace_text_in_range(None, "abc", window, cx);
4962            });
4963        });
4964        cx.run_until_parked();
4965        cx.update(|_, cx| {
4966            input.read_with(cx, |state, _| {
4967                assert_eq!(state.value(), "12.5");
4968            });
4969        });
4970
4971        // A bare leading dot is kept as-is (normalized from the ideographic
4972        // full stop), not completed to "0.", so it stays editable.
4973        cx.update(|window, cx| {
4974            input.update(cx, |state, cx| {
4975                let range = state.range_to_utf16(&(0..state.text.len()));
4976                state.replace_text_in_range(Some(range), "。", window, cx);
4977            });
4978        });
4979        cx.run_until_parked();
4980        cx.update(|_, cx| {
4981            input.read_with(cx, |state, _| {
4982                assert_eq!(state.value(), ".");
4983                let cursor: Range<usize> = state.selected_range();
4984                assert_eq!(cursor, 1..1);
4985            });
4986        });
4987    }
4988
4989    #[gpui::test]
4990    fn test_number_input_normalization_with_separator(cx: &mut TestAppContext) {
4991        let input_view = InputView::build(cx, |state| {
4992            state.mask_pattern(MaskPattern::Number {
4993                separator: Some(','),
4994                fraction: Some(2),
4995            })
4996        });
4997        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4998        let input = input_view.input;
4999
5000        cx.update(|window, cx| {
5001            input.update(cx, |state, cx| {
5002                state.replace_text_in_range(None, "1234", window, cx);
5003            });
5004        });
5005        cx.run_until_parked();
5006        cx.update(|_, cx| {
5007            input.read_with(cx, |state, _| {
5008                assert_eq!(state.value(), "1,234");
5009                assert_eq!(state.unmask_value(), "1234");
5010            });
5011        });
5012    }
5013
5014    #[gpui::test]
5015    fn test_number_input_clamp_on_blur(cx: &mut TestAppContext) {
5016        let input_view = InputView::build(cx, |state| {
5017            state
5018                .mask_pattern(MaskPattern::Number {
5019                    separator: None,
5020                    fraction: None,
5021                })
5022                .min(10.)
5023                .max(100.)
5024        });
5025        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5026        let input = input_view.input;
5027
5028        // Out-of-range values are allowed while typing, and clamped on blur.
5029        cx.update(|window, cx| {
5030            input.update(cx, |state, cx| {
5031                state.replace_text_in_range(None, "1000", window, cx);
5032                assert_eq!(state.value(), "1000");
5033                state.clamp_number_value(window, cx);
5034                assert_eq!(state.value(), "100");
5035
5036                let range = state.range_to_utf16(&(0..state.text.len()));
5037                state.replace_text_in_range(Some(range), "1", window, cx);
5038                assert_eq!(state.value(), "1");
5039                state.clamp_number_value(window, cx);
5040                assert_eq!(state.value(), "10");
5041            });
5042        });
5043    }
5044
5045    #[gpui::test]
5046    fn test_number_input_undo_with_mask(cx: &mut TestAppContext) {
5047        let input_view = InputView::build(cx, |state| {
5048            state.mask_pattern(MaskPattern::Number {
5049                separator: Some(','),
5050                fraction: None,
5051            })
5052        });
5053        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5054        let input = input_view.input;
5055
5056        // When the mask changes the text (regrouping separators), a
5057        // whole-document change is recorded, so undo/redo can restore it.
5058        cx.update(|window, cx| {
5059            input.update(cx, |state, cx| {
5060                state.replace_text_in_range(None, "1234", window, cx);
5061                assert_eq!(state.value(), "1,234");
5062                state.replace_text_in_range(None, "5", window, cx);
5063                assert_eq!(state.value(), "12,345");
5064
5065                // Each whole-document mask rewrite is an atomic undo step.
5066                // Before the whole-document history fix, undo produced a
5067                // corrupted value like "1,2344".
5068                state.undo(&Undo, window, cx);
5069                assert_eq!(state.value(), "1,234");
5070                state.undo(&Undo, window, cx);
5071                assert_eq!(state.value(), "");
5072                state.redo(&Redo, window, cx);
5073                assert_eq!(state.value(), "1,234");
5074                state.redo(&Redo, window, cx);
5075                assert_eq!(state.value(), "12,345");
5076            });
5077        });
5078    }
5079
5080    #[gpui::test]
5081    fn test_undo_manager_coalesces_adjacent_typing_transactions(cx: &mut TestAppContext) {
5082        let input_view = InputView::build(cx, |state| state);
5083        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5084        let input = input_view.input;
5085
5086        cx.update(|window, cx| {
5087            input.update(cx, |state, cx| {
5088                state.replace_text_in_range(None, "a", window, cx);
5089                state.replace_text_in_range(None, "b", window, cx);
5090                assert_eq!(state.value(), "ab");
5091
5092                state.undo(&Undo, window, cx);
5093                assert_eq!(state.value(), "");
5094            });
5095        });
5096    }
5097
5098    #[gpui::test]
5099    fn test_undo_manager_cursor_movement_splits_typing(cx: &mut TestAppContext) {
5100        let input_view = InputView::build(cx, |state| state);
5101        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5102        let input = input_view.input;
5103
5104        cx.update(|window, cx| {
5105            input.update(cx, |state, cx| {
5106                state.replace_text_in_range(None, "a", window, cx);
5107                state.replace_text_in_range(None, "b", window, cx);
5108                state.left(&MoveLeft, window, cx);
5109                state.replace_text_in_range(None, "x", window, cx);
5110                assert_eq!(state.value(), "axb");
5111
5112                state.undo(&Undo, window, cx);
5113                assert_eq!(state.value(), "ab");
5114                state.undo(&Undo, window, cx);
5115                assert_eq!(state.value(), "");
5116            });
5117        });
5118    }
5119
5120    #[gpui::test]
5121    fn test_undo_manager_splits_backward_and_forward_delete(cx: &mut TestAppContext) {
5122        let input_view = InputView::build(cx, |state| state);
5123        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5124        let input = input_view.input;
5125
5126        cx.update(|window, cx| {
5127            input.update(cx, |state, cx| {
5128                state.set_value("abcd", window, cx);
5129                state.set_selected_range(2..2, cx);
5130                state.backspace(&Backspace, window, cx);
5131                state.delete(&Delete, window, cx);
5132                assert_eq!(state.value(), "ad");
5133
5134                state.undo(&Undo, window, cx);
5135                assert_eq!(state.value(), "acd");
5136                state.undo(&Undo, window, cx);
5137                assert_eq!(state.value(), "abcd");
5138            });
5139        });
5140    }
5141
5142    #[gpui::test]
5143    fn test_undo_manager_coalesces_directional_character_deletes(cx: &mut TestAppContext) {
5144        let input_view = InputView::build(cx, |state| state);
5145        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5146        let input = input_view.input;
5147
5148        cx.update(|window, cx| {
5149            input.update(cx, |state, cx| {
5150                state.set_value("abcd", window, cx);
5151                state.backspace(&Backspace, window, cx);
5152                state.backspace(&Backspace, window, cx);
5153                assert_eq!(state.value(), "ab");
5154                state.undo(&Undo, window, cx);
5155                assert_eq!(state.value(), "abcd");
5156                assert_eq!(state.selected_range(), 4..4);
5157
5158                state.set_value("abcd", window, cx);
5159                state.set_selected_range(1..1, cx);
5160                state.delete(&Delete, window, cx);
5161                state.delete(&Delete, window, cx);
5162                assert_eq!(state.value(), "ad");
5163                state.undo(&Undo, window, cx);
5164                assert_eq!(state.value(), "abcd");
5165                assert_eq!(state.selected_range(), 1..1);
5166            });
5167        });
5168    }
5169
5170    #[gpui::test]
5171    fn test_undo_manager_atomic_paste_isolated_from_typing(cx: &mut TestAppContext) {
5172        let input_view = InputView::build(cx, |state| state);
5173        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5174        let input = input_view.input;
5175
5176        cx.update(|window, cx| {
5177            cx.write_to_clipboard(ClipboardItem::new_string("P".to_string()));
5178            input.update(cx, |state, cx| {
5179                state.replace_text_in_range(None, "a", window, cx);
5180                state.paste(&Paste, window, cx);
5181                state.replace_text_in_range(None, "b", window, cx);
5182                assert_eq!(state.value(), "aPb");
5183
5184                state.undo(&Undo, window, cx);
5185                assert_eq!(state.value(), "aP");
5186                state.undo(&Undo, window, cx);
5187                assert_eq!(state.value(), "a");
5188                state.undo(&Undo, window, cx);
5189                assert_eq!(state.value(), "");
5190            });
5191        });
5192    }
5193
5194    #[gpui::test]
5195    fn test_undo_manager_programmatic_insert_is_atomic(cx: &mut TestAppContext) {
5196        let input_view = InputView::build(cx, |state| state);
5197        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5198        let input = input_view.input;
5199
5200        cx.update(|window, cx| {
5201            input.update(cx, |state, cx| {
5202                state.replace_text_in_range(None, "a", window, cx);
5203                state.insert("P", window, cx);
5204                state.replace_text_in_range(None, "b", window, cx);
5205                assert_eq!(state.value(), "aPb");
5206
5207                state.undo(&Undo, window, cx);
5208                assert_eq!(state.value(), "aP");
5209                state.undo(&Undo, window, cx);
5210                assert_eq!(state.value(), "a");
5211            });
5212        });
5213    }
5214
5215    #[gpui::test]
5216    fn test_undo_manager_selection_round_trip_splits_typing(cx: &mut TestAppContext) {
5217        let input_view = InputView::build(cx, |state| state);
5218        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5219        let input = input_view.input;
5220
5221        cx.update(|window, cx| {
5222            input.update(cx, |state, cx| {
5223                state.replace_text_in_range(None, "a", window, cx);
5224                state.replace_text_in_range(None, "b", window, cx);
5225                state.select_all(window, cx);
5226                state.unselect(window, cx);
5227                state.replace_text_in_range(None, "c", window, cx);
5228
5229                state.undo(&Undo, window, cx);
5230                assert_eq!(state.value(), "ab");
5231                state.undo(&Undo, window, cx);
5232                assert_eq!(state.value(), "");
5233            });
5234        });
5235    }
5236
5237    #[gpui::test]
5238    fn test_undo_manager_enter_is_atomic(cx: &mut TestAppContext) {
5239        let input_view = InputView::build_textarea(cx, |state| state);
5240        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5241        let input = input_view.input;
5242
5243        cx.update(|window, cx| {
5244            input.update(cx, |state, cx| {
5245                state.replace_text_in_range(None, "a", window, cx);
5246                state.enter(
5247                    &Enter {
5248                        secondary: false,
5249                        shift: false,
5250                    },
5251                    window,
5252                    cx,
5253                );
5254                state.replace_text_in_range(None, "b", window, cx);
5255
5256                state.undo(&Undo, window, cx);
5257                assert_eq!(state.value(), "a\n");
5258                state.undo(&Undo, window, cx);
5259                assert_eq!(state.value(), "a");
5260                state.undo(&Undo, window, cx);
5261                assert_eq!(state.value(), "");
5262            });
5263        });
5264    }
5265
5266    #[gpui::test]
5267    fn test_undo_manager_single_line_return_commits_the_typing_session(cx: &mut TestAppContext) {
5268        let input_view = InputView::build(cx, |state| state);
5269        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5270        let input = input_view.input;
5271
5272        cx.update(|window, cx| {
5273            input.update(cx, |state, cx| {
5274                for part in ["a", "b", "c"] {
5275                    state.replace_text_in_range(None, part, window, cx);
5276                }
5277                state.enter(
5278                    &Enter {
5279                        secondary: false,
5280                        shift: false,
5281                    },
5282                    window,
5283                    cx,
5284                );
5285                for part in ["d", "e", "f"] {
5286                    state.replace_text_in_range(None, part, window, cx);
5287                }
5288                assert_eq!(state.value(), "abcdef");
5289
5290                state.undo(&Undo, window, cx);
5291                assert_eq!(state.value(), "abc");
5292                state.undo(&Undo, window, cx);
5293                assert_eq!(state.value(), "");
5294            });
5295        });
5296    }
5297
5298    #[gpui::test]
5299    fn test_undo_manager_submit_on_enter_commits_the_textarea_session(cx: &mut TestAppContext) {
5300        let input_view = InputView::build_textarea(cx, |state| state.submit_on_enter(true));
5301        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5302        let input = input_view.input;
5303
5304        cx.update(|window, cx| {
5305            input.update(cx, |state, cx| {
5306                state.replace_text_in_range(None, "before submit", window, cx);
5307                state.enter(
5308                    &Enter {
5309                        secondary: false,
5310                        shift: false,
5311                    },
5312                    window,
5313                    cx,
5314                );
5315                state.replace_text_in_range(None, " after submit", window, cx);
5316                assert_eq!(state.value(), "before submit after submit");
5317
5318                state.undo(&Undo, window, cx);
5319                assert_eq!(state.value(), "before submit");
5320                state.undo(&Undo, window, cx);
5321                assert_eq!(state.value(), "");
5322            });
5323        });
5324    }
5325
5326    #[gpui::test]
5327    fn test_undo_manager_blur_commits_the_typing_session(cx: &mut TestAppContext) {
5328        let input_view = InputView::build(cx, |state| state);
5329        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5330        let input = input_view.input;
5331
5332        cx.update(|window, cx| {
5333            input.update(cx, |state, cx| {
5334                state.replace_text_in_range(None, "before blur", window, cx);
5335                state.on_blur(window, cx);
5336                state.on_focus(window, cx);
5337                state.replace_text_in_range(None, " after focus", window, cx);
5338                assert_eq!(state.value(), "before blur after focus");
5339
5340                state.undo(&Undo, window, cx);
5341                assert_eq!(state.value(), "before blur");
5342                state.undo(&Undo, window, cx);
5343                assert_eq!(state.value(), "");
5344            });
5345        });
5346    }
5347
5348    #[gpui::test]
5349    fn test_undo_manager_keeps_rapid_lines_in_distinct_transactions(cx: &mut TestAppContext) {
5350        let input_view = InputView::build_textarea(cx, |state| state);
5351        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5352        let input = input_view.input;
5353
5354        cx.update(|window, cx| {
5355            input.update(cx, |state, cx| {
5356                let enter = Enter {
5357                    secondary: false,
5358                    shift: false,
5359                };
5360                state.replace_text_in_range(None, "a", window, cx);
5361                state.enter(&enter, window, cx);
5362                state.replace_text_in_range(None, "b", window, cx);
5363                state.enter(&enter, window, cx);
5364                state.replace_text_in_range(None, "c", window, cx);
5365                assert_eq!(state.value(), "a\nb\nc");
5366
5367                for expected in ["a\nb\n", "a\nb", "a\n", "a", ""] {
5368                    state.undo(&Undo, window, cx);
5369                    assert_eq!(state.value(), expected);
5370                }
5371            });
5372        });
5373    }
5374
5375    #[gpui::test]
5376    fn test_undo_manager_coalesces_long_unicode_typing_without_a_timer(cx: &mut TestAppContext) {
5377        let input_view = InputView::build_textarea(cx, |state| state);
5378        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5379        let input = input_view.input;
5380        let parts = [
5381            "The ",
5382            "quick ",
5383            "brown fox, ",
5384            "你好,世界 ",
5385            "🦀 jumps over 13 lazy dogs.",
5386        ];
5387        let expected = parts.concat();
5388
5389        cx.update(|window, cx| {
5390            input.update(cx, |state, cx| {
5391                for part in parts {
5392                    state.replace_text_in_range(None, part, window, cx);
5393                }
5394                assert_eq!(state.value(), expected);
5395
5396                state.undo(&Undo, window, cx);
5397                assert_eq!(state.value(), "");
5398                state.redo(&Redo, window, cx);
5399                assert_eq!(state.value(), expected);
5400            });
5401        });
5402    }
5403
5404    #[gpui::test]
5405    fn test_undo_manager_long_multiline_sequence_has_structural_boundaries(
5406        cx: &mut TestAppContext,
5407    ) {
5408        let input_view = InputView::build_textarea(cx, |state| state);
5409        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5410        let input = input_view.input;
5411        let enter = Enter {
5412            secondary: false,
5413            shift: false,
5414        };
5415
5416        cx.update(|window, cx| {
5417            input.update(cx, |state, cx| {
5418                for (index, line) in [
5419                    "first line with punctuation!",
5420                    "第二行包含 Unicode 🦀",
5421                    "third line has several words",
5422                ]
5423                .into_iter()
5424                .enumerate()
5425                {
5426                    for chunk in line.split_inclusive(' ') {
5427                        state.replace_text_in_range(None, chunk, window, cx);
5428                    }
5429                    if index < 2 {
5430                        state.enter(&enter, window, cx);
5431                    }
5432                }
5433
5434                assert_eq!(
5435                    state.value(),
5436                    "first line with punctuation!\n第二行包含 Unicode 🦀\nthird line has several words"
5437                );
5438                state.undo(&Undo, window, cx);
5439                assert_eq!(
5440                    state.value(),
5441                    "first line with punctuation!\n第二行包含 Unicode 🦀\n"
5442                );
5443                state.undo(&Undo, window, cx);
5444                assert_eq!(
5445                    state.value(),
5446                    "first line with punctuation!\n第二行包含 Unicode 🦀"
5447                );
5448                state.undo(&Undo, window, cx);
5449                assert_eq!(state.value(), "first line with punctuation!\n");
5450            });
5451        });
5452    }
5453
5454    #[gpui::test]
5455    fn test_masked_input_keeps_its_value_out_of_the_clipboard(cx: &mut TestAppContext) {
5456        let input_view = InputView::build(cx, |state| state);
5457        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5458        let input = input_view.input;
5459
5460        cx.update(|window, cx| {
5461            input.update(cx, |state, cx| {
5462                state.set_value("hunter2", window, cx);
5463                state.set_masked(true, window, cx);
5464                state.select_all(window, cx);
5465                cx.write_to_clipboard(ClipboardItem::new_string("sentinel".into()));
5466
5467                state.copy(&Copy, window, cx);
5468                assert_eq!(
5469                    cx.read_from_clipboard().and_then(|item| item.text()),
5470                    Some("sentinel".to_string())
5471                );
5472
5473                // Cut neither copies nor deletes.
5474                state.cut(&Cut, window, cx);
5475                assert_eq!(state.value(), "hunter2");
5476                assert_eq!(
5477                    cx.read_from_clipboard().and_then(|item| item.text()),
5478                    Some("sentinel".to_string())
5479                );
5480
5481                // Revealing the value restores both.
5482                state.set_masked(false, window, cx);
5483                state.copy(&Copy, window, cx);
5484                assert_eq!(
5485                    cx.read_from_clipboard().and_then(|item| item.text()),
5486                    Some("hunter2".to_string())
5487                );
5488            });
5489        });
5490    }
5491
5492    #[gpui::test]
5493    fn test_masked_input_collapses_word_boundaries(cx: &mut TestAppContext) {
5494        let input_view = InputView::build(cx, |state| state);
5495        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5496        let input = input_view.input;
5497
5498        cx.update(|window, cx| {
5499            input.update(cx, |state, cx| {
5500                state.set_value("aaa bbb ccc", window, cx);
5501                state.set_masked(true, window, cx);
5502                state.set_selected_range(7..7, cx);
5503
5504                // The mask hides word boundaries, so a word delete takes
5505                // everything before the caret and leaves the rest.
5506                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
5507                assert_eq!(state.value(), " ccc");
5508                assert_eq!(state.selected_range(), 0..0);
5509
5510                state.delete_next_word(&DeleteToNextWordEnd, window, cx);
5511                assert_eq!(state.value(), "");
5512
5513                // A double click takes the whole value, not one word.
5514                state.set_value("aaa bbb ccc", window, cx);
5515                state.select_word(9, window, cx);
5516                assert_eq!(state.selected_range(), 0..11);
5517
5518                // Unmasked, the same delete only takes one word.
5519                state.set_masked(false, window, cx);
5520                state.set_value("aaa bbb ccc", window, cx);
5521                state.set_selected_range(11..11, cx);
5522                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
5523                assert_eq!(state.value(), "aaa bbb ");
5524
5525                state.set_value("aaa bbb ccc", window, cx);
5526                state.select_word(9, window, cx);
5527                assert_eq!(state.selected_range(), 8..11);
5528            });
5529        });
5530    }
5531
5532    #[gpui::test]
5533    fn test_masked_input_disables_the_copy_context_menu_items(cx: &mut TestAppContext) {
5534        let input_view = InputView::build(cx, |state| state);
5535        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5536        let input = input_view.input;
5537
5538        cx.update(|window, cx| {
5539            input.update(cx, |state, cx| {
5540                state.set_value("hunter2", window, cx);
5541                state.select_all(window, cx);
5542                assert!(state.context_menu_capabilities().is_copyable());
5543
5544                state.set_masked(true, window, cx);
5545                let capabilities = state.context_menu_capabilities();
5546                assert!(capabilities.is_masked());
5547                assert!(capabilities.has_selection());
5548                assert!(!capabilities.is_copyable());
5549            });
5550        });
5551    }
5552
5553    #[gpui::test]
5554    fn test_undo_manager_cut_and_repeated_pastes_are_distinct_transactions(
5555        cx: &mut TestAppContext,
5556    ) {
5557        let input_view = InputView::build(cx, |state| state);
5558        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5559        let input = input_view.input;
5560
5561        cx.update(|window, cx| {
5562            input.update(cx, |state, cx| {
5563                state.replace_text_in_range(None, "alpha beta gamma", window, cx);
5564                state.set_selected_range(6..10, cx);
5565                state.cut(&Cut, window, cx);
5566                assert_eq!(state.value(), "alpha  gamma");
5567
5568                state.paste(&Paste, window, cx);
5569                state.paste(&Paste, window, cx);
5570                assert_eq!(state.value(), "alpha betabeta gamma");
5571
5572                state.undo(&Undo, window, cx);
5573                assert_eq!(state.value(), "alpha beta gamma");
5574                state.undo(&Undo, window, cx);
5575                assert_eq!(state.value(), "alpha  gamma");
5576                state.undo(&Undo, window, cx);
5577                assert_eq!(state.value(), "alpha beta gamma");
5578            });
5579        });
5580    }
5581
5582    #[gpui::test]
5583    fn test_undo_manager_word_and_line_deletes_do_not_coalesce(cx: &mut TestAppContext) {
5584        let input_view = InputView::build_textarea(cx, |state| state);
5585        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5586        let input = input_view.input;
5587
5588        cx.update(|window, cx| {
5589            input.update(cx, |state, cx| {
5590                state.set_value("one two three\nfour five", window, cx);
5591                state.set_selected_range(13..13, cx);
5592                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
5593                assert_eq!(state.value(), "one two \nfour five");
5594                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
5595                assert_eq!(state.value(), "one two four five");
5596
5597                state.undo(&Undo, window, cx);
5598                assert_eq!(state.value(), "one two \nfour five");
5599                state.undo(&Undo, window, cx);
5600                assert_eq!(state.value(), "one two three\nfour five");
5601            });
5602        });
5603    }
5604
5605    #[gpui::test]
5606    fn test_undo_manager_multiline_replacement_is_one_atomic_transaction(cx: &mut TestAppContext) {
5607        let input_view = InputView::build_textarea(cx, |state| state);
5608        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5609        let input = input_view.input;
5610
5611        cx.update(|window, cx| {
5612            input.update(cx, |state, cx| {
5613                state.replace_text_in_range(None, "before", window, cx);
5614                state.set_selected_range(0..6, cx);
5615                state.replace_text_in_range(None, "line one\nline two\n第三行", window, cx);
5616                assert_eq!(state.value(), "line one\nline two\n第三行");
5617
5618                state.undo(&Undo, window, cx);
5619                assert_eq!(state.value(), "before");
5620                assert_eq!(state.selected_range(), 0..6);
5621                state.redo(&Redo, window, cx);
5622                assert_eq!(state.value(), "line one\nline two\n第三行");
5623            });
5624        });
5625    }
5626
5627    #[gpui::test]
5628    fn test_undo_manager_composition_isolated_from_long_typing(cx: &mut TestAppContext) {
5629        let input_view = InputView::build(cx, |state| state);
5630        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5631        let input = input_view.input;
5632
5633        cx.update(|window, cx| {
5634            input.update(cx, |state, cx| {
5635                state.replace_text_in_range(None, "prefix ", window, cx);
5636                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
5637                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
5638                state.replace_and_mark_text_in_range(None, "你", None, window, cx);
5639                state.unmark_text(window, cx);
5640                state.replace_text_in_range(None, " suffix", window, cx);
5641                assert_eq!(state.value(), "prefix 你 suffix");
5642
5643                state.undo(&Undo, window, cx);
5644                assert_eq!(state.value(), "prefix 你");
5645                state.undo(&Undo, window, cx);
5646                assert_eq!(state.value(), "prefix ");
5647                state.undo(&Undo, window, cx);
5648                assert_eq!(state.value(), "");
5649            });
5650        });
5651    }
5652
5653    #[gpui::test]
5654    fn test_undo_manager_selected_replacement_is_atomic(cx: &mut TestAppContext) {
5655        let input_view = InputView::build(cx, |state| state);
5656        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5657        let input = input_view.input;
5658
5659        cx.update(|window, cx| {
5660            input.update(cx, |state, cx| {
5661                state.replace_text_in_range(None, "abc", window, cx);
5662                state.set_selected_range(1..2, cx);
5663                state.replace_text_in_range(None, "X", window, cx);
5664                state.replace_text_in_range(None, "z", window, cx);
5665                assert_eq!(state.value(), "aXzc");
5666
5667                state.undo(&Undo, window, cx);
5668                assert_eq!(state.value(), "aXc");
5669                state.undo(&Undo, window, cx);
5670                assert_eq!(state.value(), "abc");
5671                state.undo(&Undo, window, cx);
5672                assert_eq!(state.value(), "");
5673            });
5674        });
5675    }
5676
5677    #[gpui::test]
5678    fn test_number_input_leading_dot_editable(cx: &mut TestAppContext) {
5679        let input_view = InputView::build(cx, |state| {
5680            state.mask_pattern(MaskPattern::Number {
5681                separator: None,
5682                fraction: None,
5683            })
5684        });
5685        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5686        let input = input_view.input;
5687
5688        cx.update(|window, cx| {
5689            input.update(cx, |state, cx| {
5690                state.replace_text_in_range(None, "1.2", window, cx);
5691
5692                // Delete the integer part "1": the value keeps the leading dot
5693                // (".2"), not completed to "0.2", so the digits before the dot
5694                // stay editable.
5695                let range = state.range_to_utf16(&(0..1));
5696                state.replace_text_in_range(Some(range), "", window, cx);
5697                assert_eq!(state.value(), ".2");
5698                let cursor: Range<usize> = state.selected_range();
5699                assert_eq!(cursor, 0..0);
5700
5701                // The user can type a new integer part.
5702                state.replace_text_in_range(Some(0..0), "3", window, cx);
5703                assert_eq!(state.value(), "3.2");
5704            });
5705        });
5706    }
5707
5708    #[gpui::test]
5709    fn test_number_input_escape_invalid_text(cx: &mut TestAppContext) {
5710        // A pre-existing invalid text (e.g. a `default_value` that does not
5711        // conform) must not trap the user, the edit is allowed to fix it.
5712        let input_view = InputView::build(cx, |state| {
5713            state
5714                .mask_pattern(MaskPattern::Number {
5715                    separator: None,
5716                    fraction: None,
5717                })
5718                .default_value("1,234")
5719        });
5720        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5721        let input = input_view.input;
5722
5723        cx.update(|window, cx| {
5724            input.update(cx, |state, cx| {
5725                // Delete the last char, the pending text "1,23" is still
5726                // invalid, but the edit is allowed since the old text was
5727                // already invalid.
5728                let range = state.range_to_utf16(&(4..5));
5729                state.replace_text_in_range(Some(range), "", window, cx);
5730                assert_eq!(state.value(), "1,23");
5731
5732                // Once the text becomes valid, the validation works as usual.
5733                let range = state.range_to_utf16(&(1..2));
5734                state.replace_text_in_range(Some(range), "", window, cx);
5735                assert_eq!(state.value(), "123");
5736                state.replace_text_in_range(None, "a", window, cx);
5737                assert_eq!(state.value(), "123");
5738            });
5739        });
5740    }
5741
5742    /// After `set_value` on a single-line input the caret sits at the end (like
5743    /// HTML `<input>`), yet the view is scrolled back to the start so a long
5744    /// value shows its beginning instead of its tail.
5745    #[gpui::test]
5746    fn test_set_value_single_line_caret_at_end_view_at_start(cx: &mut TestAppContext) {
5747        let input_view = InputView::build(cx, |state| state);
5748        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5749        let input = input_view.input;
5750
5751        // Long enough to overflow any reasonable single-line input width.
5752        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
5753        let len = value.len();
5754
5755        // Right after `set_value`, before the next paint consumes the deferred
5756        // offset: caret is at the end, and the view is forced back to the start.
5757        cx.update(|window, cx| {
5758            input.update(cx, |state, cx| {
5759                state.set_value(value.clone(), window, cx);
5760
5761                assert_eq!(
5762                    state.selected_range(),
5763                    len..len,
5764                    "single-line caret should be at the end after set_value"
5765                );
5766                assert_eq!(
5767                    state.deferred_scroll_offset,
5768                    Some(point(px(0.), px(0.))),
5769                    "the view should be forced back to the start"
5770                );
5771            });
5772        });
5773
5774        // After a paint, the steady-state view stays at the start (x == 0) even
5775        // though the caret is at the far end.
5776        cx.run_until_parked();
5777        cx.update(|_, cx| {
5778            input.read_with(cx, |state, _| {
5779                assert!(
5780                    state.scroll_size.width > state.input_bounds.size.width,
5781                    "value must overflow the input width or this test is vacuous"
5782                );
5783                assert_eq!(
5784                    state.scroll_handle.offset().x,
5785                    px(0.),
5786                    "long value should display from its start, not its tail"
5787                );
5788            });
5789        });
5790    }
5791
5792    /// `replace_all` on a single-line input replaces the text, puts the
5793    /// caret at the end, and — like `set_value` — snaps the view back to the
5794    /// start so a long value shows its beginning instead of its tail.
5795    #[gpui::test]
5796    fn test_replace_all_single_line(cx: &mut TestAppContext) {
5797        let input_view = InputView::build(cx, |state| state);
5798        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5799        let input = input_view.input;
5800
5801        // Long enough to overflow any reasonable single-line input width.
5802        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
5803        let len = value.len();
5804
5805        // Right after `replace_all`, before the next paint consumes the
5806        // deferred offset: caret is at the end, and the view is forced back
5807        // to the start.
5808        cx.update(|window, cx| {
5809            input.update(cx, |state, cx| {
5810                state.set_value("hello", window, cx);
5811                state.replace_all(value.clone(), window, cx);
5812                assert_eq!(state.value(), value);
5813                assert_eq!(
5814                    state.selected_range(),
5815                    len..len,
5816                    "single-line caret should be at the end after replace_all"
5817                );
5818                assert_eq!(
5819                    state.scroll_handle.offset(),
5820                    point(px(0.), px(0.)),
5821                    "the scroll offset should be reset to the start"
5822                );
5823                assert_eq!(
5824                    state.deferred_scroll_offset,
5825                    Some(point(px(0.), px(0.))),
5826                    "single-line should set a deferred scroll offset to keep the start visible"
5827                );
5828            });
5829        });
5830
5831        // After a paint, the steady-state view stays at the start (x == 0)
5832        // even though the caret is at the far end.
5833        cx.run_until_parked();
5834        cx.update(|_, cx| {
5835            input.read_with(cx, |state, _| {
5836                assert!(
5837                    state.scroll_size.width > state.input_bounds.size.width,
5838                    "value must overflow the input width or this test is vacuous"
5839                );
5840                assert_eq!(
5841                    state.scroll_handle.offset().x,
5842                    px(0.),
5843                    "long value should display from its start, not its tail"
5844                );
5845            });
5846        });
5847    }
5848
5849    #[gpui::test]
5850    fn test_single_line_removes_newlines(cx: &mut TestAppContext) {
5851        let input_view = InputView::build(cx, |state| state.default_value("default\nvalue"));
5852        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5853        let input = input_view.input;
5854
5855        cx.update(|window, cx| {
5856            input.update(cx, |state, cx| {
5857                assert_eq!(state.value(), "defaultvalue");
5858
5859                state.set_value("first\nsecond\r\nthird\rfourth", window, cx);
5860                assert_eq!(state.value(), "firstsecondthirdfourth");
5861
5862                state.set_value("", window, cx);
5863                state.insert("a\nb", window, cx);
5864                assert_eq!(state.value(), "ab");
5865            });
5866
5867            cx.write_to_clipboard(ClipboardItem::new_string("a\r\nb\nc\rd".to_string()));
5868            input.update(cx, |state, cx| {
5869                state.set_value("", window, cx);
5870                state.paste(&Paste, window, cx);
5871                assert_eq!(state.value(), "abcd");
5872            });
5873        });
5874
5875        cx.run_until_parked();
5876    }
5877
5878    /// `replace_all` on a multi-line (non-code-editor) input clears the
5879    /// selection to `0..0` and resets the scroll offset, but does not set a
5880    /// deferred scroll offset (single-line only).
5881    #[gpui::test]
5882    fn test_replace_all_multi_line(cx: &mut TestAppContext) {
5883        let input_view = InputView::build_textarea(cx, |state| state);
5884        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5885        let input = input_view.input;
5886
5887        cx.update(|window, cx| {
5888            input.update(cx, |state, cx| {
5889                state.set_value("foo\nbar", window, cx);
5890                state.replace_all("baz\nqux", window, cx);
5891                assert_eq!(state.value(), "baz\nqux");
5892                assert_eq!(
5893                    state.selected_range(),
5894                    0..0,
5895                    "multi-line selection should be cleared after replace_all"
5896                );
5897                assert_eq!(
5898                    state.scroll_handle.offset(),
5899                    point(px(0.), px(0.)),
5900                    "the scroll offset should be reset to the start"
5901                );
5902                assert!(
5903                    state.deferred_scroll_offset.is_none(),
5904                    "multi-line should not set a deferred scroll offset"
5905                );
5906            });
5907        });
5908    }
5909
5910    /// Unlike `set_value`, `replace_all` records the change so the user can
5911    /// undo it back to the previous text and redo to the new text.
5912    #[gpui::test]
5913    fn test_replace_all_preserves_undo_history(cx: &mut TestAppContext) {
5914        let input_view = InputView::build(cx, |state| state);
5915        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5916        let input = input_view.input;
5917
5918        cx.update(|window, cx| {
5919            input.update(cx, |state, cx| {
5920                // Seed with a value and clear history so the baseline is clean.
5921                state.set_value("first", window, cx);
5922                assert!(
5923                    !state.undo_manager.has_undos(),
5924                    "history should be empty after set_value"
5925                );
5926
5927                // replace_all records a single undoable change.
5928                state.replace_all("second", window, cx);
5929                assert_eq!(state.value(), "second");
5930                assert!(
5931                    state.undo_manager.has_undos(),
5932                    "replace_all should record an undo step"
5933                );
5934
5935                // Undo restores the previous text.
5936                state.undo(&Undo, window, cx);
5937                assert_eq!(state.value(), "first");
5938
5939                // Redo reapplies the replacement.
5940                state.redo(&Redo, window, cx);
5941                assert_eq!(state.value(), "second");
5942            });
5943        });
5944    }
5945
5946    /// `replace_all` on a code editor marks a pending update and resets LSP
5947    /// state, so diagnostics/completions refresh against the new text.
5948    #[gpui::test]
5949    fn test_replace_all_code_editor(cx: &mut TestAppContext) {
5950        let input_view = InputView::new(cx);
5951        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5952        let input = input_view.input;
5953
5954        cx.update(|window, cx| {
5955            input.update(cx, |state, cx| {
5956                // Plant a pending-update flag and some LSP state to verify reset.
5957                state.set_value("select 1", window, cx);
5958                state._pending_update = false;
5959
5960                state.replace_all("select 2", window, cx);
5961                assert_eq!(state.value(), "select 2");
5962                assert!(
5963                    state._pending_update,
5964                    "replace_all on a code editor should request a pending update"
5965                );
5966            });
5967        });
5968    }
5969
5970    #[gpui::test]
5971    fn test_set_selected_range(cx: &mut TestAppContext) {
5972        let input_view = InputView::build(cx, |state| state.default_value("hello world"));
5973        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5974        let input = input_view.input;
5975
5976        cx.update(|_, cx| {
5977            input.update(cx, |s, cx| {
5978                s.set_selected_range(0..5, cx);
5979                assert_eq!(s.selected_range(), 0..5);
5980                assert_eq!(s.selected_text().to_string(), "hello");
5981
5982                s.set_selected_range(6..11, cx);
5983                assert_eq!(s.selected_text().to_string(), "world");
5984
5985                // clamped + collapsed
5986                s.set_selected_range(100..100, cx);
5987                assert_eq!(s.selected_range(), 11..11);
5988            });
5989        });
5990    }
5991
5992    /// A single-edit batch round-trips through undo/redo.
5993    #[gpui::test]
5994    fn test_replace_text_in_ranges_single_edit(cx: &mut TestAppContext) {
5995        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
5996        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5997        let input = input_view.input;
5998
5999        cx.update(|window, cx| {
6000            input.update(cx, |s, cx| {
6001                s.replace_text_in_ranges(&[(0..5, "HELLO".to_string())], window, cx);
6002                assert_eq!(s.value(), "HELLO world");
6003
6004                s.undo(&Undo, window, cx);
6005                assert_eq!(s.value(), "hello world");
6006
6007                s.redo(&Redo, window, cx);
6008                assert_eq!(s.value(), "HELLO world");
6009            });
6010        });
6011    }
6012
6013    /// A single undo restores the exact original text and a single redo
6014    /// re-applies all edits, verifying the back-to-front application ordering.
6015    #[gpui::test]
6016    fn test_replace_text_in_ranges_multi_edit_transaction(cx: &mut TestAppContext) {
6017        let input_view = InputView::build_textarea(cx, |state| state.default_value("aaa bbb ccc"));
6018        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6019        let input = input_view.input;
6020
6021        cx.update(|window, cx| {
6022            input.update(cx, |s, cx| {
6023                // Two edits at different positions, given in pre-edit
6024                // coordinates and in arbitrary (non-sorted) order.
6025                s.replace_text_in_ranges(
6026                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
6027                    window,
6028                    cx,
6029                );
6030                assert_eq!(s.value(), "X bbb Y");
6031
6032                // One collapsed cursor per edit, at the end of each inserted text.
6033                let cursors: Vec<usize> =
6034                    s.selections.iter().map(|sel| sel.cursor_offset()).collect();
6035                assert_eq!(cursors, vec![1, 7]);
6036
6037                // The whole batch is a single undo transaction.
6038                assert_eq!(s.undo_manager.undo_count(), 1);
6039
6040                // One undo restores the exact original text.
6041                s.undo(&Undo, window, cx);
6042                assert_eq!(s.value(), "aaa bbb ccc");
6043
6044                // One redo re-applies all edits.
6045                s.redo(&Redo, window, cx);
6046                assert_eq!(s.value(), "X bbb Y");
6047            });
6048        });
6049    }
6050
6051    /// An IME composition (marking then commit) undoes as a single unit.
6052    #[gpui::test]
6053    fn test_ime_composition_undoes_as_one_unit(cx: &mut TestAppContext) {
6054        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
6055        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6056        let input = input_view.input;
6057
6058        cx.update(|window, cx| {
6059            input.update(cx, |s, cx| {
6060                // Simulate an IME composition: mark, refine, then commit.
6061                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
6062                s.replace_and_mark_text_in_range(None, "ni", Some(2..2), window, cx);
6063                s.replace_text_in_range(None, "你", window, cx);
6064                assert_eq!(s.value(), "你");
6065
6066                // The entire composition is one undo transaction.
6067                assert_eq!(s.undo_manager.undo_count(), 1);
6068
6069                s.undo(&Undo, window, cx);
6070                assert_eq!(s.value(), "");
6071
6072                s.redo(&Redo, window, cx);
6073                assert_eq!(s.value(), "你");
6074            });
6075        });
6076    }
6077
6078    /// A keystroke right after a committed composition must be its own undo
6079    /// entry, not merged into the (finalized) composition transaction.
6080    #[gpui::test]
6081    fn test_edit_after_composition_is_separate_undo(cx: &mut TestAppContext) {
6082        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
6083        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6084        let input = input_view.input;
6085
6086        cx.update(|window, cx| {
6087            input.update(cx, |s, cx| {
6088                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
6089                s.replace_text_in_range(None, "你", window, cx);
6090                assert_eq!(s.value(), "你");
6091                assert_eq!(s.undo_manager.undo_count(), 1);
6092
6093                // Typing after the commit is a distinct transaction.
6094                s.replace_text_in_range(None, "x", window, cx);
6095                assert_eq!(s.value(), "你x");
6096                assert_eq!(s.undo_manager.undo_count(), 2);
6097
6098                s.undo(&Undo, window, cx);
6099                assert_eq!(s.value(), "你");
6100                s.undo(&Undo, window, cx);
6101                assert_eq!(s.value(), "");
6102            });
6103        });
6104    }
6105
6106    /// Canceling a composition via `unmark_text` closes its transaction so it
6107    /// does not leak and swallow a later edit.
6108    #[gpui::test]
6109    fn test_composition_cancel_via_unmark_does_not_leak(cx: &mut TestAppContext) {
6110        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
6111        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6112        let input = input_view.input;
6113
6114        cx.update(|window, cx| {
6115            input.update(cx, |s, cx| {
6116                // Start a composition, then cancel it via unmark.
6117                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
6118                s.unmark_text(window, cx);
6119                let after_cancel = s.undo_manager.undo_count();
6120
6121                // A later edit is recorded independently.
6122                s.replace_text_in_range(None, "x", window, cx);
6123                assert_eq!(s.undo_manager.undo_count(), after_cancel + 1);
6124            });
6125        });
6126    }
6127
6128    #[gpui::test]
6129    fn test_set_selected_range_clips_to_utf8_boundaries(cx: &mut TestAppContext) {
6130        let input_view = InputView::build(cx, |state| state.default_value("éx"));
6131        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6132        let input = input_view.input;
6133
6134        cx.update(|window, cx| {
6135            input.update(cx, |state, cx| {
6136                state.set_selected_range(0..1, cx);
6137                assert_eq!(state.selected_range(), 0..2);
6138                state.copy(&Copy, window, cx);
6139
6140                state.set_selected_range(1..1, cx);
6141                assert_eq!(state.selected_range(), 0..0);
6142            });
6143        });
6144    }
6145
6146    #[gpui::test]
6147    fn test_ime_selection_is_relative_to_replacement_start(cx: &mut TestAppContext) {
6148        let input_view = InputView::build(cx, |state| state.default_value("你好 "));
6149        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6150        let input = input_view.input;
6151
6152        cx.update(|window, cx| {
6153            input.update(cx, |state, cx| {
6154                state.set_selected_range(7..7, cx);
6155                state.replace_and_mark_text_in_range(None, "s", Some(1..1), window, cx);
6156                state.replace_and_mark_text_in_range(None, "sh", Some(2..2), window, cx);
6157
6158                assert_eq!(state.value(), "你好 sh");
6159                assert_eq!(state.selected_range(), 9..9);
6160                assert_eq!(state.ime_marked_range, Some((7..9).into()));
6161            });
6162        });
6163    }
6164
6165    #[gpui::test]
6166    fn test_undo_manager_composition_is_one_undo_group(cx: &mut TestAppContext) {
6167        let input_view = InputView::build(cx, |state| state);
6168        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6169        let input = input_view.input;
6170
6171        cx.update(|window, cx| {
6172            input.update(cx, |state, cx| {
6173                state.set_value("a", window, cx);
6174                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
6175                state.replace_and_mark_text_in_range(None, "sh", None, window, cx);
6176                state.replace_text_in_range(None, "是", window, cx);
6177                assert_eq!(state.value(), "a是");
6178
6179                state.undo(&Undo, window, cx);
6180                assert_eq!(state.value(), "a");
6181                state.redo(&Redo, window, cx);
6182                assert_eq!(state.value(), "a是");
6183            });
6184        });
6185    }
6186
6187    #[gpui::test]
6188    fn test_undo_manager_consecutive_compositions_are_separate_groups(cx: &mut TestAppContext) {
6189        let input_view = InputView::build(cx, |state| state);
6190        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6191        let input = input_view.input;
6192
6193        cx.update(|window, cx| {
6194            input.update(cx, |state, cx| {
6195                // First composition: "jin" -> "今天"
6196                state.replace_and_mark_text_in_range(None, "j", None, window, cx);
6197                state.replace_and_mark_text_in_range(None, "jin", None, window, cx);
6198                state.replace_text_in_range(None, "今天", window, cx);
6199                // Second composition: "wo" -> "我们"
6200                state.replace_and_mark_text_in_range(None, "w", None, window, cx);
6201                state.replace_and_mark_text_in_range(None, "wo", None, window, cx);
6202                state.replace_text_in_range(None, "我们", window, cx);
6203                assert_eq!(state.value(), "今天我们");
6204                assert_eq!(state.selected_range(), 12..12);
6205
6206                state.undo(&Undo, window, cx);
6207                assert_eq!(state.value(), "今天");
6208                assert_eq!(state.selected_range(), 6..6);
6209
6210                state.undo(&Undo, window, cx);
6211                assert_eq!(state.value(), "");
6212                assert_eq!(state.selected_range(), 0..0);
6213
6214                state.redo(&Redo, window, cx);
6215                assert_eq!(state.value(), "今天");
6216                assert_eq!(state.selected_range(), 6..6);
6217
6218                state.redo(&Redo, window, cx);
6219                assert_eq!(state.value(), "今天我们");
6220                assert_eq!(state.selected_range(), 12..12);
6221            });
6222        });
6223    }
6224
6225    #[gpui::test]
6226    fn test_undo_manager_typing_after_composition_is_a_separate_group(cx: &mut TestAppContext) {
6227        let input_view = InputView::build(cx, |state| state);
6228        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6229        let input = input_view.input;
6230
6231        cx.update(|window, cx| {
6232            input.update(cx, |state, cx| {
6233                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
6234                state.replace_text_in_range(None, "你", window, cx);
6235                state.undo_manager.set_pending_intent(EditIntent::Typing);
6236                state.replace_text_in_range(None, "a", window, cx);
6237                state.undo_manager.set_pending_intent(EditIntent::Typing);
6238                state.replace_text_in_range(None, "b", window, cx);
6239                assert_eq!(state.value(), "你ab");
6240
6241                state.undo(&Undo, window, cx);
6242                assert_eq!(state.value(), "你");
6243
6244                state.undo(&Undo, window, cx);
6245                assert_eq!(state.value(), "");
6246            });
6247        });
6248    }
6249
6250    #[gpui::test]
6251    fn test_undo_manager_composition_cancel_leaves_no_entry(cx: &mut TestAppContext) {
6252        let input_view = InputView::build(cx, |state| state);
6253        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6254        let input = input_view.input;
6255
6256        cx.update(|window, cx| {
6257            input.update(cx, |state, cx| {
6258                state.set_value("a", window, cx);
6259                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
6260                state.replace_and_mark_text_in_range(None, "", None, window, cx);
6261
6262                assert_eq!(state.value(), "a");
6263                assert!(!state.undo_manager.has_undos());
6264            });
6265        });
6266    }
6267
6268    #[gpui::test]
6269    fn test_undo_manager_selection_restored_by_undo_and_redo(cx: &mut TestAppContext) {
6270        let input_view = InputView::build(cx, |state| state);
6271        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6272        let input = input_view.input;
6273
6274        cx.update(|window, cx| {
6275            input.update(cx, |state, cx| {
6276                state.set_value("abc", window, cx);
6277                state.set_selected_range(1..2, cx);
6278                state.replace_text_in_range(None, "X", window, cx);
6279
6280                state.undo(&Undo, window, cx);
6281                assert_eq!(state.value(), "abc");
6282                assert_eq!(state.selected_range(), 1..2);
6283
6284                state.redo(&Redo, window, cx);
6285                assert_eq!(state.value(), "aXc");
6286                assert_eq!(state.selected_range(), 2..2);
6287            });
6288        });
6289    }
6290
6291    #[gpui::test]
6292    fn test_undo_manager_forward_delete_restores_cursor(cx: &mut TestAppContext) {
6293        let input_view = InputView::build(cx, |state| state);
6294        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6295        let input = input_view.input;
6296
6297        cx.update(|window, cx| {
6298            input.update(cx, |state, cx| {
6299                state.set_value("abc", window, cx);
6300                state.set_selected_range(1..1, cx);
6301                state.delete(&Delete, window, cx);
6302
6303                state.undo(&Undo, window, cx);
6304                assert_eq!(state.value(), "abc");
6305                assert_eq!(state.selected_range(), 1..1);
6306            });
6307        });
6308    }
6309
6310    #[gpui::test]
6311    fn test_undo_manager_selection_movement_preserves_redo(cx: &mut TestAppContext) {
6312        let input_view = InputView::build(cx, |state| state);
6313        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6314        let input = input_view.input;
6315
6316        cx.update(|window, cx| {
6317            input.update(cx, |state, cx| {
6318                state.replace_text_in_range(None, "ab", window, cx);
6319                state.undo(&Undo, window, cx);
6320                state.set_selected_range(0..0, cx);
6321                state.redo(&Redo, window, cx);
6322                assert_eq!(state.value(), "ab");
6323
6324                state.undo(&Undo, window, cx);
6325                state.replace_text_in_range(None, "x", window, cx);
6326                state.redo(&Redo, window, cx);
6327                assert_eq!(state.value(), "x");
6328            });
6329        });
6330    }
6331
6332    #[gpui::test]
6333    fn test_undo_manager_noop_edit_preserves_redo(cx: &mut TestAppContext) {
6334        let input_view = InputView::build(cx, |state| state);
6335        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6336        let input = input_view.input;
6337
6338        cx.update(|window, cx| {
6339            input.update(cx, |state, cx| {
6340                state.replace_text_in_range(None, "a", window, cx);
6341                state.move_to(0, None, cx);
6342                state.replace_text_in_range(None, "", window, cx);
6343                state.undo(&Undo, window, cx);
6344                assert_eq!(state.value(), "");
6345                state.redo(&Redo, window, cx);
6346                assert_eq!(state.value(), "a");
6347                assert_eq!(state.cursor(), 1);
6348            });
6349        });
6350    }
6351
6352    #[gpui::test]
6353    fn test_cursor_round_trip_stops_typing_coalescing(cx: &mut TestAppContext) {
6354        let input_view = InputView::build(cx, |state| state);
6355        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6356        let input = input_view.input;
6357
6358        cx.update(|window, cx| {
6359            input.update(cx, |state, cx| {
6360                state.replace_text_in_range(None, "a", window, cx);
6361                state.left(&MoveLeft, window, cx);
6362                state.right(&MoveRight, window, cx);
6363                state.replace_text_in_range(None, "b", window, cx);
6364
6365                state.undo(&Undo, window, cx);
6366                assert_eq!(state.value(), "a");
6367                state.undo(&Undo, window, cx);
6368                assert_eq!(state.value(), "");
6369            });
6370        });
6371    }
6372
6373    #[gpui::test]
6374    fn test_undo_manager_noop_edit_breaks_coalescing_without_clearing_history(
6375        cx: &mut TestAppContext,
6376    ) {
6377        let input_view = InputView::build(cx, |state| state);
6378        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6379        let input = input_view.input;
6380
6381        cx.update(|window, cx| {
6382            input.update(cx, |state, cx| {
6383                state.replace_text_in_range(None, "alpha", window, cx);
6384                state.replace_text_in_range(None, "", window, cx);
6385                state.replace_text_in_range(None, "beta", window, cx);
6386                assert_eq!(state.value(), "alphabeta");
6387
6388                state.undo(&Undo, window, cx);
6389                assert_eq!(state.value(), "alpha");
6390                state.undo(&Undo, window, cx);
6391                assert_eq!(state.value(), "");
6392            });
6393        });
6394    }
6395
6396    #[gpui::test]
6397    fn test_undo_manager_masked_redo_restores_actual_cursor(cx: &mut TestAppContext) {
6398        let input_view = InputView::build(cx, |state| {
6399            state.mask_pattern(MaskPattern::Number {
6400                separator: Some(','),
6401                fraction: None,
6402            })
6403        });
6404        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6405        let input = input_view.input;
6406
6407        cx.update(|window, cx| {
6408            input.update(cx, |state, cx| {
6409                state.set_value("12345", window, cx);
6410                state.set_selected_range(2..2, cx);
6411                state.replace_text_in_range(None, "9", window, cx);
6412                let selection_after_edit = state.selected_range();
6413                assert_ne!(selection_after_edit.end, state.value().len());
6414
6415                state.undo(&Undo, window, cx);
6416                state.redo(&Redo, window, cx);
6417                assert_eq!(state.selected_range(), selection_after_edit);
6418            });
6419        });
6420    }
6421
6422    /// Unfolding at a position opens exactly the folds hiding it.
6423    ///
6424    /// A fold keeps its own first and last line visible, so a position on
6425    /// either of them opens nothing. Nested folds all open at once, sibling
6426    /// folds stay closed, and the opened ranges stay fold candidates.
6427    #[gpui::test]
6428    fn test_unfold_at(cx: &mut TestAppContext) {
6429        use crate::input::{FoldRange, Position};
6430
6431        let view = InputView::<EditorMode>::new(cx);
6432        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6433        let input = view.input;
6434
6435        // An outer fold over lines 0..=5, a fold nested inside it, and a
6436        // sibling fold that must never be touched.
6437        cx.update(|window, cx| {
6438            input.update(cx, |state, cx| {
6439                state.set_value("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl", window, cx);
6440                state.apply_highlighter_fold_candidates(
6441                    vec![
6442                        FoldRange::new(0, 5),
6443                        FoldRange::new(2, 4),
6444                        FoldRange::new(7, 10),
6445                    ],
6446                    cx,
6447                );
6448                state.display_map.set_folded(0, true);
6449                state.display_map.set_folded(2, true);
6450                state.display_map.set_folded(7, true);
6451            });
6452        });
6453
6454        // The outer fold's own first and last line stay visible, so neither
6455        // position opens anything.
6456        for line in [0, 5] {
6457            cx.update(|_, cx| {
6458                input.update(cx, |state, cx| {
6459                    assert!(!state.display_map.is_buffer_line_hidden(line));
6460                    assert!(!state.unfold_at(Position::new(line as u32, 0), cx));
6461                });
6462                input.read_with(cx, |state, _| {
6463                    assert!(state.display_map.is_folded_at(0));
6464                    assert!(state.display_map.is_folded_at(2));
6465                    assert!(state.display_map.is_folded_at(7));
6466                });
6467            });
6468        }
6469
6470        // Line 3 is hidden by both the outer and the nested fold, so both
6471        // open; the sibling fold does not.
6472        cx.update(|_, cx| {
6473            input.update(cx, |state, cx| {
6474                assert!(state.unfold_at(Position::new(3, 0), cx));
6475            });
6476            input.read_with(cx, |state, _| {
6477                assert!(!state.display_map.is_buffer_line_hidden(3));
6478                assert!(!state.display_map.is_folded_at(0));
6479                assert!(!state.display_map.is_folded_at(2));
6480                assert!(state.display_map.is_folded_at(7));
6481                // The opened ranges are still candidates for refolding.
6482                assert!(state.display_map.is_fold_candidate(0));
6483                assert!(state.display_map.is_fold_candidate(2));
6484            });
6485        });
6486
6487        // Nothing is hidden there any more, so a second call is a no-op.
6488        cx.update(|_, cx| {
6489            input.update(cx, |state, cx| {
6490                assert!(!state.unfold_at(Position::new(3, 0), cx));
6491            });
6492        });
6493    }
6494
6495    /// Losing focus hides the hover popover but keeps the decorations.
6496    ///
6497    /// Both used to be dropped by one call, so clicking away threw away
6498    /// decorations the application had installed and never asked to remove.
6499    #[gpui::test]
6500    fn test_blur_keeps_decorations(cx: &mut TestAppContext) {
6501        let view = InputView::<EditorMode>::new(cx);
6502        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6503        let input = view.input;
6504
6505        cx.update(|window, cx| {
6506            input.update(cx, |state, cx| {
6507                state.set_value("select 1", window, cx);
6508                let _collection = state.create_decorations_collection(
6509                    vec![crate::input::TextDecoration::new(
6510                        0..6,
6511                        gpui::HighlightStyle {
6512                            font_weight: Some(gpui::FontWeight::BOLD),
6513                            ..Default::default()
6514                        },
6515                    )],
6516                    cx,
6517                );
6518                state.present_hover(
6519                    0..6,
6520                    lsp_types::Hover {
6521                        contents: lsp_types::HoverContents::Scalar(
6522                            lsp_types::MarkedString::String("docs".into()),
6523                        ),
6524                        range: None,
6525                    },
6526                    cx,
6527                );
6528                assert!(state.hover_popover().is_some());
6529
6530                state.on_blur(window, cx);
6531
6532                assert!(
6533                    state.hover_popover().is_none(),
6534                    "blur should hide the hover popover"
6535                );
6536                let decorations = state.extras.decoration_layers();
6537                assert!(
6538                    decorations.iter().any(|layer| !layer.is_empty()),
6539                    "blur must not discard decorations"
6540                );
6541            });
6542        });
6543    }
6544
6545    /// The mode marker is the only source of truth for the kind of input.
6546    ///
6547    /// An auto-growing textarea capped at one row used to report itself as
6548    /// single-line, because the answer was derived from the row counts.
6549    #[gpui::test]
6550    fn test_kind_does_not_follow_the_row_count(cx: &mut TestAppContext) {
6551        let view = InputView::build_textarea(cx, |state| state.auto_grow(1, 1));
6552        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6553        view.input.read_with(&mut cx, |state, _| {
6554            assert!(state.is_multi_line());
6555            assert!(!state.is_single_line());
6556            assert!(!state.is_code_editor());
6557        });
6558    }
6559
6560    /// Soft wrap is on by default, for every mode that can wrap.
6561    ///
6562    /// The default lives in the shared constructor, where a mode-specific
6563    /// `new` can silently fail to restore it; this pins it down.
6564    #[gpui::test]
6565    fn test_soft_wrap_is_enabled_by_default(cx: &mut TestAppContext) {
6566        let textarea = InputView::build_textarea(cx, |state| state);
6567        let mut textarea_cx = VisualTestContext::from_window(textarea.window_handle.into(), cx);
6568        textarea
6569            .input
6570            .read_with(&mut textarea_cx, |state, _| assert!(state.soft_wrap));
6571
6572        let editor = InputView::<EditorMode>::new(cx);
6573        let mut editor_cx = VisualTestContext::from_window(editor.window_handle.into(), cx);
6574        editor
6575            .input
6576            .read_with(&mut editor_cx, |state, _| assert!(state.soft_wrap));
6577    }
6578
6579    /// Parse a cursor spec into `(text, cursor_offsets)`. Non-empty lines are
6580    /// joined with `\n` plus a trailing `\n`. `|` marks a cursor. Leading
6581    /// whitespace is kept, so a spec can express indentation.
6582    fn parse_cursor_spec(input: &str) -> (String, Vec<usize>) {
6583        let mut full_text = String::new();
6584        let mut cursor_offsets = Vec::new();
6585        let non_empty_lines: Vec<&str> = input.lines().filter(|l| !l.is_empty()).collect();
6586
6587        for (line_idx, line) in non_empty_lines.iter().enumerate() {
6588            let mut positions = Vec::new();
6589            let mut text = String::new();
6590            for ch in line.chars() {
6591                if ch == '|' {
6592                    positions.push(text.len());
6593                } else {
6594                    text.push(ch);
6595                }
6596            }
6597
6598            if line_idx > 0 {
6599                full_text.push('\n');
6600            }
6601            let line_start = full_text.len();
6602            for pos in positions {
6603                cursor_offsets.push(line_start + pos);
6604            }
6605            full_text.push_str(&text);
6606        }
6607        full_text.push('\n');
6608
6609        (full_text, cursor_offsets)
6610    }
6611
6612    /// Build a multi-line input for multi-cursor tests.
6613    fn multi_line(cx: &mut TestAppContext) -> InputView<TextareaMode> {
6614        InputView::build_textarea(cx, |state| state)
6615    }
6616
6617    /// Set the text and cursor positions from a spec (see [`parse_cursor_spec`]).
6618    fn setup_cursors<M: InputModeKind>(
6619        cx: &mut VisualTestContext,
6620        input: &Entity<InputBaseState<M>>,
6621        spec: &str,
6622    ) {
6623        let (full_text, offsets) = parse_cursor_spec(spec);
6624        cx.update(|window, cx| {
6625            input.update(cx, |state, cx| {
6626                state.set_value(&full_text, window, cx);
6627                let selections = offsets
6628                    .into_iter()
6629                    .map(|offset| {
6630                        CursorSelection::new(state.selections.generate_id(), offset, offset)
6631                    })
6632                    .collect();
6633                state.selections.replace_all(selections);
6634                cx.notify();
6635            });
6636        });
6637    }
6638
6639    /// Assert the text and cursor positions match a spec.
6640    #[track_caller]
6641    fn assert_cursors<M: InputModeKind>(
6642        cx: &mut VisualTestContext,
6643        input: &Entity<InputBaseState<M>>,
6644        spec: &str,
6645    ) {
6646        let (expected_text, mut expected_cursors) = parse_cursor_spec(spec);
6647        expected_cursors.sort();
6648
6649        let (actual_text, mut actual_cursors) = input.read_with(cx, |state, _| {
6650            (
6651                state.text.to_string(),
6652                state
6653                    .selections
6654                    .iter()
6655                    .map(|s| s.cursor_offset())
6656                    .collect::<Vec<_>>(),
6657            )
6658        });
6659        actual_cursors.sort();
6660
6661        assert_eq!(
6662            actual_text, expected_text,
6663            "Text mismatch:\nExpected: {expected_text:?}\nActual:   {actual_text:?}"
6664        );
6665        assert_eq!(
6666            actual_cursors, expected_cursors,
6667            "Cursor mismatch:\nExpected: {expected_cursors:?}\nActual:   {actual_cursors:?}"
6668        );
6669    }
6670
6671    #[gpui::test]
6672    fn test_alt_drag_selects_a_block_and_replaces_each_row(cx: &mut TestAppContext) {
6673        cx.update(crate::init);
6674        let view = InputView::<EditorMode>::new(cx);
6675        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6676        for modifiers in [
6677            gpui::Modifiers {
6678                alt: true,
6679                ..Default::default()
6680            },
6681            gpui::Modifiers {
6682                alt: true,
6683                shift: true,
6684                ..Default::default()
6685            },
6686            #[cfg(target_os = "linux")]
6687            gpui::Modifiers {
6688                alt: true,
6689                control: true,
6690                ..Default::default()
6691            },
6692        ] {
6693            setup_cursors(&mut cx, &view.input, "|abcd\nabcd\nabcd");
6694            cx.update(|window, cx| {
6695                view.input.update(cx, |state, cx| state.focus(window, cx));
6696            });
6697            let (start, end) = view.input.read_with(&cx, |state, _| {
6698                let layout = state.last_layout.as_ref().unwrap();
6699                let origin = state.last_bounds.unwrap().origin;
6700                let position = |row: usize, col| {
6701                    let local = layout.lines[row]
6702                        .position_for_index(col, layout, false)
6703                        .unwrap();
6704                    origin
6705                        + point(
6706                            layout.line_number_width + local.x,
6707                            layout.line_height * (row as f32 + 0.5),
6708                        )
6709                };
6710                (position(0, 1), position(2, 3))
6711            });
6712            // A cached Ctrl-hover definition must not steal a column gesture.
6713            cx.update(|_, cx| {
6714                view.input.update(cx, |state, _| {
6715                    state.extras.hover_definition.update(
6716                        0..4,
6717                        vec![lsp_types::LocationLink {
6718                            origin_selection_range: None,
6719                            target_uri: "file:///tmp/column-selection.rs".parse().unwrap(),
6720                            target_range: Default::default(),
6721                            target_selection_range: Default::default(),
6722                        }],
6723                    );
6724                });
6725            });
6726            cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
6727            cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
6728            cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
6729            view.input.read_with(&cx, |state, _| {
6730                let ranges: Vec<_> = state
6731                    .selections
6732                    .iter()
6733                    .map(|sel| sel.start..sel.end)
6734                    .collect();
6735                assert_eq!(ranges, vec![1..3, 6..8, 11..13]);
6736            });
6737            // Moving after release must leave the block intact.
6738            cx.simulate_mouse_move(start, None, modifiers);
6739            cx.simulate_keystrokes("x");
6740            assert_cursors(&mut cx, &view.input, "ax|d\nax|d\nax|d");
6741        }
6742    }
6743
6744    #[gpui::test]
6745    fn test_alt_drag_extends_upward_from_an_existing_cursor(cx: &mut TestAppContext) {
6746        cx.update(crate::init);
6747        let view = InputView::<EditorMode>::new(cx);
6748        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6749        setup_cursors(&mut cx, &view.input, "abcd\nabcd\na|bcd");
6750        cx.update(|window, cx| {
6751            view.input.update(cx, |state, cx| state.focus(window, cx));
6752        });
6753        let (start, end) = view.input.read_with(&cx, |state, _| {
6754            let layout = state.last_layout.as_ref().unwrap();
6755            let origin = state.last_bounds.unwrap().origin;
6756            let local = layout.lines[0]
6757                .position_for_index(1, layout, false)
6758                .unwrap();
6759            let x = layout.line_number_width + local.x;
6760            (
6761                origin + point(x, layout.line_height * 2.5),
6762                origin + point(x, layout.line_height * 0.5),
6763            )
6764        });
6765        let modifiers = gpui::Modifiers {
6766            alt: true,
6767            ..Default::default()
6768        };
6769        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
6770        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
6771        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
6772        assert_cursors(&mut cx, &view.input, "a|bcd\na|bcd\na|bcd");
6773    }
6774
6775    #[gpui::test]
6776    fn test_alt_drag_over_a_short_row_keeps_the_block_width(cx: &mut TestAppContext) {
6777        cx.update(crate::init);
6778        let view = InputView::<EditorMode>::new(cx);
6779        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6780        setup_cursors(&mut cx, &view.input, "abcdef\nab\nabcdef");
6781        cx.update(|window, cx| {
6782            view.input.update(cx, |state, cx| state.focus(window, cx));
6783        });
6784        // Drag from row 0 column 1 down to row 1, at the x of row 0's column 5. Row 1
6785        // only has two characters, so the pointer ends past its end.
6786        let (start, end) = view.input.read_with(&cx, |state, _| {
6787            let layout = state.last_layout.as_ref().unwrap();
6788            let origin = state.last_bounds.unwrap().origin;
6789            let x_of = |index| {
6790                layout.line_number_width
6791                    + layout.lines[0]
6792                        .position_for_index(index, layout, false)
6793                        .unwrap()
6794                        .x
6795            };
6796            (
6797                origin + point(x_of(1), layout.line_height * 0.5),
6798                origin + point(x_of(5), layout.line_height * 1.5),
6799            )
6800        });
6801        let modifiers = gpui::Modifiers {
6802            alt: true,
6803            shift: true,
6804            ..Default::default()
6805        };
6806        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
6807        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
6808        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
6809
6810        // The short row is clipped to its own end; the long row keeps the full span.
6811        view.input.read_with(&cx, |state, _| {
6812            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
6813            assert_eq!(ranges, vec![(1, 5), (8, 9)]);
6814        });
6815    }
6816
6817    #[gpui::test]
6818    fn test_alt_mouse_release_outside_editor_ends_column_selection(cx: &mut TestAppContext) {
6819        cx.update(crate::init);
6820        let view = InputView::<EditorMode>::new(cx);
6821        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6822        setup_cursors(&mut cx, &view.input, "|abcd\nabcd");
6823        let position = view.input.read_with(&cx, |state, _| {
6824            let layout = state.last_layout.as_ref().unwrap();
6825            state.last_bounds.unwrap().origin
6826                + point(layout.line_number_width + px(2.), layout.line_height * 0.5)
6827        });
6828        let modifiers = gpui::Modifiers {
6829            alt: true,
6830            ..Default::default()
6831        };
6832        cx.simulate_mouse_down(position, MouseButton::Left, modifiers);
6833        cx.simulate_mouse_up(point(px(-100.), px(-100.)), MouseButton::Left, modifiers);
6834        view.input.read_with(&cx, |state, _| {
6835            assert!(!state.selecting);
6836            assert!(state.column_select_start.is_none());
6837        });
6838    }
6839
6840    #[gpui::test]
6841    fn test_consumed_keystrokes_keep_cursor_visible(cx: &mut TestAppContext) {
6842        let view = InputView::<EditorMode>::new(cx);
6843        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6844        setup_cursors(&mut cx, &view.input, "a|b");
6845        cx.update(|window, cx| {
6846            view.input.update(cx, |state, cx| {
6847                state.focus(window, cx);
6848                state.pause_blink_cursor(cx);
6849            });
6850        });
6851        cx.run_until_parked();
6852        cx.executor()
6853            .advance_clock(std::time::Duration::from_millis(300));
6854        cx.run_until_parked();
6855        view.input.read_with(&cx, |state, cx| {
6856            assert!(!state.blink_cursor.read(cx).visible());
6857        });
6858        // Copy consumes its shortcut without editing text or moving selections.
6859        for _ in 0..5 {
6860            #[cfg(target_os = "macos")]
6861            cx.simulate_keystrokes("cmd-c");
6862            #[cfg(not(target_os = "macos"))]
6863            cx.simulate_keystrokes("ctrl-c");
6864            cx.run_until_parked();
6865            cx.executor()
6866                .advance_clock(std::time::Duration::from_millis(200));
6867            cx.run_until_parked();
6868            view.input.read_with(&cx, |state, cx| {
6869                assert!(state.blink_cursor.read(cx).visible());
6870            });
6871        }
6872    }
6873
6874    #[gpui::test]
6875    fn test_multi_cursor_actions_reveal_hidden_carets(cx: &mut TestAppContext) {
6876        let view = InputView::<EditorMode>::new(cx);
6877        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6878        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
6879        cx.update(|window, cx| {
6880            view.input.update(cx, |state, cx| {
6881                // Start each action in the hidden phase without depending on a
6882                // key-down listener: actions and text input also arrive directly.
6883                for action in 0..6 {
6884                    state.blink_cursor = cx.new(|_| BlinkCursor::new());
6885                    assert!(!state.blink_cursor.read(cx).visible());
6886                    match action {
6887                        0 => state.add_cursor_above(&AddCursorAbove, window, cx),
6888                        1 => state.add_cursor_below(&AddCursorBelow, window, cx),
6889                        2 => state.select_up(&SelectUp, window, cx),
6890                        3 => state.select_down(&SelectDown, window, cx),
6891                        4 => state.replace_text_in_range(None, "x", window, cx),
6892                        _ => state.backspace(&Backspace, window, cx),
6893                    }
6894                    assert!(state.blink_cursor.read(cx).visible(), "action {action}");
6895                }
6896            });
6897        });
6898    }
6899
6900    #[gpui::test]
6901    fn test_multi_cursor_keyboard_dispatch(cx: &mut TestAppContext) {
6902        let view = InputView::<EditorMode>::new(cx);
6903        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6904        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
6905        cx.update(|window, cx| {
6906            view.input.update(cx, |state, cx| state.focus(window, cx));
6907        });
6908        #[cfg(target_os = "macos")]
6909        cx.simulate_keystrokes("cmd-alt-up");
6910        #[cfg(target_os = "windows")]
6911        cx.simulate_keystrokes("ctrl-alt-up");
6912        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
6913        cx.simulate_keystrokes("alt-shift-up");
6914        view.input
6915            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
6916        #[cfg(target_os = "macos")]
6917        cx.simulate_keystrokes("cmd-alt-down");
6918        #[cfg(target_os = "windows")]
6919        cx.simulate_keystrokes("ctrl-alt-down");
6920        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
6921        cx.simulate_keystrokes("alt-shift-down");
6922        view.input
6923            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 3));
6924        cx.simulate_keystrokes("x");
6925        assert_cursors(&mut cx, &view.input, "ax|b\nax|b\nax|b");
6926    }
6927
6928    #[gpui::test]
6929    fn test_multi_cursor_platform_word_selection_dispatch(cx: &mut TestAppContext) {
6930        let view = InputView::<EditorMode>::new(cx);
6931        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6932        setup_cursors(&mut cx, &view.input, "one |two\none |two");
6933        cx.update(|window, cx| {
6934            view.input.update(cx, |state, cx| state.focus(window, cx));
6935        });
6936        #[cfg(any(target_os = "macos", target_os = "linux"))]
6937        cx.simulate_keystrokes("alt-shift-right");
6938        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
6939        cx.simulate_keystrokes("ctrl-shift-right");
6940        cx.simulate_keystrokes("x");
6941        assert_cursors(&mut cx, &view.input, "one x|\none x|");
6942        setup_cursors(&mut cx, &view.input, "one two|\none two|");
6943        #[cfg(any(target_os = "macos", target_os = "linux"))]
6944        cx.simulate_keystrokes("alt-shift-left");
6945        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
6946        cx.simulate_keystrokes("ctrl-shift-left");
6947        cx.simulate_keystrokes("x");
6948        assert_cursors(&mut cx, &view.input, "one x|\none x|");
6949    }
6950
6951    #[cfg(not(target_os = "macos"))]
6952    #[gpui::test]
6953    fn test_multi_cursor_horizontal_selection_dispatch(cx: &mut TestAppContext) {
6954        let view = InputView::<EditorMode>::new(cx);
6955        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6956        setup_cursors(&mut cx, &view.input, "ab\na|b");
6957        cx.update(|window, cx| {
6958            view.input.update(cx, |state, cx| state.focus(window, cx));
6959        });
6960        #[cfg(target_os = "windows")]
6961        cx.simulate_keystrokes("ctrl-alt-up");
6962        #[cfg(not(target_os = "windows"))]
6963        cx.simulate_keystrokes("alt-shift-up");
6964        #[cfg(target_os = "linux")]
6965        cx.simulate_keystrokes("shift-right");
6966        #[cfg(not(target_os = "linux"))]
6967        cx.simulate_keystrokes("alt-shift-right");
6968        view.input.read_with(&cx, |state, _| {
6969            assert_eq!(
6970                state
6971                    .selections
6972                    .iter()
6973                    .map(|s| s.start..s.end)
6974                    .collect::<Vec<_>>(),
6975                vec![4..5, 1..2]
6976            );
6977        });
6978        #[cfg(target_os = "linux")]
6979        cx.simulate_keystrokes("shift-left shift-left");
6980        #[cfg(not(target_os = "linux"))]
6981        cx.simulate_keystrokes("alt-shift-left alt-shift-left");
6982        view.input.read_with(&cx, |state, _| {
6983            assert_eq!(
6984                state
6985                    .selections
6986                    .iter()
6987                    .map(|s| s.start..s.end)
6988                    .collect::<Vec<_>>(),
6989                vec![3..4, 0..1]
6990            );
6991        });
6992        cx.simulate_keystrokes("x");
6993        assert_cursors(&mut cx, &view.input, "x|b\nx|b");
6994    }
6995
6996    #[gpui::test]
6997    fn test_multi_cursor_alt_click_dispatch(cx: &mut TestAppContext) {
6998        cx.update(crate::init);
6999        let view = InputView::<EditorMode>::new(cx);
7000        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7001        setup_cursors(&mut cx, &view.input, "a|b\nab\nab");
7002        cx.update(|window, cx| {
7003            view.input.update(cx, |state, cx| state.focus(window, cx));
7004        });
7005        let position = view.input.read_with(&cx, |state, _| {
7006            let bounds = state.last_bounds.unwrap();
7007            let layout = state.last_layout.as_ref().unwrap();
7008            bounds.origin + point(layout.line_number_width + px(2.), layout.line_height * 1.5)
7009        });
7010        cx.simulate_click(
7011            position,
7012            gpui::Modifiers {
7013                alt: true,
7014                ..Default::default()
7015            },
7016        );
7017        view.input
7018            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
7019    }
7020
7021    #[gpui::test]
7022    fn test_word_delete_undo_restores_caret(cx: &mut TestAppContext) {
7023        let view = multi_line(cx);
7024        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7025        setup_cursors(&mut cx, &view.input, "hello|");
7026        cx.update(|window, cx| {
7027            view.input.update(cx, |state, cx| {
7028                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
7029                state.undo(&Undo, window, cx);
7030                assert_eq!(state.selected_range(), 5..5);
7031            });
7032        });
7033    }
7034
7035    #[gpui::test]
7036    fn test_merged_delete_undo_restores_all_carets(cx: &mut TestAppContext) {
7037        let view = multi_line(cx);
7038        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7039        setup_cursors(&mut cx, &view.input, "a|b|c");
7040        cx.update(|window, cx| {
7041            view.input.update(cx, |state, cx| {
7042                state.backspace(&Backspace, window, cx);
7043                state.undo(&Undo, window, cx);
7044            });
7045        });
7046        assert_cursors(&mut cx, &view.input, "a|b|c");
7047    }
7048
7049    #[gpui::test]
7050    fn test_shift_end_respects_soft_wrap_end(cx: &mut TestAppContext) {
7051        let view = InputView::<EditorMode>::new(cx);
7052        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7053        cx.update(|window, cx| {
7054            view.input.update(cx, |state, cx| {
7055                state.set_value("abcdef ".repeat(30), window, cx);
7056                state.display_map.on_layout_changed(Some(px(60.)), cx);
7057                let line = state.display_map.line(0).unwrap();
7058                assert!(line.wrapped_lines.len() > 1);
7059                let boundary = line.wrapped_lines[0].end;
7060                state.move_to_with_affinity(boundary, None, true, cx);
7061                state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
7062                assert_eq!(state.selected_range(), boundary..state.text.len());
7063            });
7064        });
7065    }
7066
7067    #[gpui::test]
7068    fn test_outdent_unindented_unicode_is_unchanged(cx: &mut TestAppContext) {
7069        let view = multi_line(cx);
7070        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7071        setup_cursors(&mut cx, &view.input, "|你好\n|世界");
7072        cx.update(|window, cx| {
7073            view.input.update(cx, |state, cx| {
7074                state.outdent(false, window, cx);
7075                state.outdent(true, window, cx);
7076            });
7077        });
7078        assert_cursors(&mut cx, &view.input, "|你好\n|世界");
7079    }
7080
7081    #[gpui::test]
7082    fn test_column_selection_stays_on_unicode_boundaries(cx: &mut TestAppContext) {
7083        let view = multi_line(cx);
7084        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7085        setup_cursors(&mut cx, &view.input, "ab\n你好\ncd");
7086        cx.update(|window, cx| {
7087            view.input.update(cx, |state, cx| {
7088                state.build_columnar_selection(
7089                    ColumnarPoint::new(1, 0),
7090                    ColumnarPoint::new(11, 0),
7091                    cx,
7092                );
7093                let text = state.value();
7094                for sel in state.selections.iter() {
7095                    assert!(text.is_char_boundary(sel.start));
7096                    assert!(text.is_char_boundary(sel.end));
7097                }
7098                state.replace_text_in_range(None, "X", window, cx);
7099            });
7100        });
7101    }
7102
7103    #[gpui::test]
7104    fn test_editor_decorations_follow_typing(cx: &mut TestAppContext) {
7105        let view = InputView::<EditorMode>::new(cx);
7106        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7107        cx.update(|window, cx| {
7108            view.input.update(cx, |state, cx| {
7109                state.set_value("abc def", window, cx);
7110                state.create_decorations_collection(
7111                    vec![crate::input::TextDecoration::new(
7112                        4..7,
7113                        gpui::HighlightStyle::default(),
7114                    )],
7115                    cx,
7116                );
7117                state.set_selected_range(0..0, cx);
7118                state.replace_text_in_range(None, "X", window, cx);
7119                let layers = state.extras.decoration_layers();
7120                assert_eq!(layers.into_iter().flatten().next().unwrap().range, 5..8);
7121            });
7122        });
7123    }
7124
7125    #[gpui::test]
7126    fn test_backspace_preserves_escaped_quote_terminator(cx: &mut TestAppContext) {
7127        let view = InputView::<EditorMode>::new(cx);
7128        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7129        setup_cursors(&mut cx, &view.input, r#"{"s":"\"|"}"#);
7130        cx.update(|window, cx| {
7131            view.input.update(cx, |state, cx| {
7132                set_test_syntax_provider(std::rc::Rc::new(StringAllProvider), cx);
7133                state.backspace(&Backspace, window, cx);
7134            });
7135        });
7136        assert_cursors(&mut cx, &view.input, r#"{"s":"\|"}"#);
7137    }
7138
7139    #[gpui::test]
7140    fn test_ordinary_typing_does_not_query_syntax(cx: &mut TestAppContext) {
7141        struct UnexpectedQuery;
7142        impl crate::input::SyntaxContextProvider for UnexpectedQuery {
7143            fn context_at(&self, _: &ropey::Rope, _: usize) -> crate::input::SyntaxContext {
7144                panic!("ordinary typing must not query syntax");
7145            }
7146        }
7147        let view = InputView::<EditorMode>::new(cx);
7148        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7149        setup_cursors(&mut cx, &view.input, "|");
7150        cx.update(|window, cx| {
7151            view.input.update(cx, |state, cx| {
7152                set_test_syntax_provider(std::rc::Rc::new(UnexpectedQuery), cx);
7153                for c in ["a", "b", "c"] {
7154                    state.replace_text_in_range(None, c, window, cx);
7155                }
7156            });
7157        });
7158        assert_cursors(&mut cx, &view.input, "abc|");
7159    }
7160
7161    #[gpui::test]
7162    fn test_auto_close_is_atomic_at_history_limit(cx: &mut TestAppContext) {
7163        let view = InputView::<EditorMode>::new(cx);
7164        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7165        setup_cursors(&mut cx, &view.input, "|");
7166        cx.update(|window, cx| {
7167            view.input.update(cx, |state, cx| {
7168                for _ in 0..999 {
7169                    state.replace_text_in_range(None, "a", window, cx);
7170                }
7171                state.replace_text_in_range(None, "(", window, cx);
7172                state.undo(&Undo, window, cx);
7173                assert!(!state.text.to_string().contains('('));
7174                assert!(!state.text.to_string().contains(')'));
7175                state.redo(&Redo, window, cx);
7176            });
7177        });
7178        assert_cursors(&mut cx, &view.input, &format!("{}(|)", "a".repeat(999)));
7179    }
7180
7181    #[gpui::test]
7182    fn test_comment_closer_is_inserted_literally(cx: &mut TestAppContext) {
7183        let view = InputView::<EditorMode>::new(cx);
7184        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7185        setup_cursors(&mut cx, &view.input, "// (|)");
7186        cx.update(|window, cx| {
7187            view.input.update(cx, |state, cx| {
7188                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
7189                state.replace_text_in_range(None, ")", window, cx);
7190            });
7191        });
7192        assert_cursors(&mut cx, &view.input, "// ()|)");
7193    }
7194
7195    #[gpui::test]
7196    fn test_explicit_range_skip_does_not_delete_existing_closer_on_undo(cx: &mut TestAppContext) {
7197        let view = InputView::<EditorMode>::new(cx);
7198        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7199        setup_cursors(&mut cx, &view.input, "(a|)");
7200        let before = view.input.read_with(&cx, |state, _| state.text.to_string());
7201        cx.update(|window, cx| {
7202            view.input.update(cx, |state, cx| {
7203                state.replace_text_in_range(Some(2..2), ")", window, cx);
7204            });
7205        });
7206        assert_cursors(&mut cx, &view.input, "(a)|");
7207        cx.update(|window, cx| {
7208            view.input
7209                .update(cx, |state, cx| state.undo(&Undo, window, cx));
7210        });
7211        view.input.read_with(&cx, |state, _| {
7212            assert_eq!(
7213                state.text.to_string(),
7214                before,
7215                "skip must not remove an existing closer on undo"
7216            );
7217        });
7218    }
7219
7220    #[gpui::test]
7221    fn test_pair_redo_restores_interior_cursor(cx: &mut TestAppContext) {
7222        let view = InputView::<EditorMode>::new(cx);
7223        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7224        setup_cursors(&mut cx, &view.input, "|");
7225        cx.update(|window, cx| {
7226            view.input.update(cx, |state, cx| {
7227                state.replace_text_in_range(None, "(", window, cx);
7228            });
7229        });
7230        assert_cursors(&mut cx, &view.input, "(|)");
7231        cx.update(|window, cx| {
7232            view.input.update(cx, |state, cx| {
7233                state.undo(&Undo, window, cx);
7234                state.redo(&Redo, window, cx);
7235            });
7236        });
7237        assert_cursors(&mut cx, &view.input, "(|)");
7238    }
7239
7240    #[gpui::test]
7241    fn test_pair_enter_redo_restores_interior_cursor(cx: &mut TestAppContext) {
7242        let view = InputView::<EditorMode>::new(cx);
7243        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7244        setup_cursors(&mut cx, &view.input, "{|}");
7245        cx.update(|window, cx| {
7246            view.input.update(cx, |state, cx| {
7247                state.enter(
7248                    &Enter {
7249                        secondary: false,
7250                        shift: false,
7251                    },
7252                    window,
7253                    cx,
7254                );
7255            });
7256        });
7257        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
7258        cx.update(|window, cx| {
7259            view.input.update(cx, |state, cx| {
7260                state.undo(&Undo, window, cx);
7261            });
7262        });
7263        assert_cursors(&mut cx, &view.input, "{|}");
7264        cx.update(|window, cx| {
7265            view.input
7266                .update(cx, |state, cx| state.redo(&Redo, window, cx));
7267        });
7268        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
7269    }
7270
7271    #[gpui::test]
7272    fn test_auto_close_parens(cx: &mut TestAppContext) {
7273        let view = InputView::<EditorMode>::new(cx);
7274        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7275        setup_cursors(&mut cx, &view.input, "|");
7276        cx.update(|window, cx| {
7277            view.input.update(cx, |state, cx| {
7278                state.replace_text_in_range(None, "(", window, cx);
7279            });
7280        });
7281        assert_cursors(&mut cx, &view.input, "(|)");
7282        // Selection must be collapsed: typing next must insert, not replace.
7283        cx.update(|window, cx| {
7284            view.input.update(cx, |state, cx| {
7285                state.replace_text_in_range(None, "x", window, cx);
7286            });
7287        });
7288        assert_cursors(&mut cx, &view.input, "(x|)");
7289    }
7290
7291    #[gpui::test]
7292    fn test_auto_close_skip_over_closer(cx: &mut TestAppContext) {
7293        let view = InputView::<EditorMode>::new(cx);
7294        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7295        setup_cursors(&mut cx, &view.input, "(|)");
7296        cx.update(|window, cx| {
7297            view.input.update(cx, |state, cx| {
7298                state.replace_text_in_range(None, ")", window, cx);
7299            });
7300        });
7301        // No duplicate closer, cursor moves past the existing one.
7302        assert_cursors(&mut cx, &view.input, "()|");
7303        // Selection must be collapsed, not covering blank space.
7304        view.input.read_with(&cx, |state, _| {
7305            assert!(state.active_selection().is_empty());
7306        });
7307    }
7308
7309    #[gpui::test]
7310    fn test_auto_close_no_pair_inside_word(cx: &mut TestAppContext) {
7311        let view = InputView::<EditorMode>::new(cx);
7312        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7313        setup_cursors(&mut cx, &view.input, "don|");
7314        cx.update(|window, cx| {
7315            view.input.update(cx, |state, cx| {
7316                state.replace_text_in_range(None, "'", window, cx);
7317            });
7318        });
7319        // Contraction: no auto-close.
7320        assert_cursors(&mut cx, &view.input, "don'|");
7321    }
7322
7323    #[gpui::test]
7324    fn test_auto_close_quote_after_cjk(cx: &mut TestAppContext) {
7325        let view = InputView::<EditorMode>::new(cx);
7326        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7327        // Unicode-safe previous-char lookup: must not panic on multi-byte prefix.
7328        // CJK counts as word-like, so no pairing — single quote inserted.
7329        setup_cursors(&mut cx, &view.input, "中|");
7330        cx.update(|window, cx| {
7331            view.input.update(cx, |state, cx| {
7332                state.replace_text_in_range(None, "'", window, cx);
7333            });
7334        });
7335        assert_cursors(&mut cx, &view.input, "中'|");
7336    }
7337
7338    #[gpui::test]
7339    fn test_auto_close_closer_skips_at_string_end(cx: &mut TestAppContext) {
7340        let view = InputView::<EditorMode>::new(cx);
7341        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7342        // Cursor before existing closing quote: typing `"` skips past it.
7343        setup_cursors(&mut cx, &view.input, "\"hello|\"");
7344        cx.update(|window, cx| {
7345            view.input.update(cx, |state, cx| {
7346                state.replace_text_in_range(None, "\"", window, cx);
7347            });
7348        });
7349        assert_cursors(&mut cx, &view.input, "\"hello\"|");
7350        view.input.read_with(&cx, |state, _| {
7351            assert!(state.active_selection().is_empty());
7352        });
7353    }
7354
7355    #[gpui::test]
7356    fn test_backspace_pair_with_cjk_prefix(cx: &mut TestAppContext) {
7357        let view = InputView::<EditorMode>::new(cx);
7358        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7359        // UTF-16 conversion: deleting `()` after CJK must not touch neighbors.
7360        setup_cursors(&mut cx, &view.input, "中(|)abc");
7361        cx.update(|window, cx| {
7362            view.input
7363                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
7364        });
7365        assert_cursors(&mut cx, &view.input, "中|abc");
7366    }
7367
7368    #[gpui::test]
7369    fn test_enter_split_respects_smart_indent_off(cx: &mut TestAppContext) {
7370        let view = InputView::<EditorMode>::new(cx);
7371        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7372        setup_cursors(&mut cx, &view.input, "{|}");
7373        cx.update(|window, cx| {
7374            view.input.update(cx, |state, cx| {
7375                state.set_smart_indent(false, window, cx);
7376                state.enter(
7377                    &Enter {
7378                        secondary: false,
7379                        shift: false,
7380                    },
7381                    window,
7382                    cx,
7383                );
7384            });
7385        });
7386        // Smart indent controls both structural splitting and extra indentation.
7387        assert_cursors(&mut cx, &view.input, "{\n|}");
7388    }
7389
7390    #[gpui::test]
7391    fn test_enter_split_is_independent_of_auto_close(cx: &mut TestAppContext) {
7392        let view = InputView::<EditorMode>::new(cx);
7393        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7394        setup_cursors(&mut cx, &view.input, "{|}");
7395        cx.update(|window, cx| {
7396            view.input.update(cx, |state, cx| {
7397                state.set_auto_close(false, window, cx);
7398                state.enter(
7399                    &Enter {
7400                        secondary: false,
7401                        shift: false,
7402                    },
7403                    window,
7404                    cx,
7405                );
7406            });
7407        });
7408        // Disabling automatic insertion does not disable structural indentation.
7409        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
7410    }
7411
7412    #[gpui::test]
7413    fn test_language_config_applies_before_render_and_on_language_change(cx: &mut TestAppContext) {
7414        use crate::input::{AutoClosingPair, set_language_config};
7415        let view = InputView::<EditorMode>::new(cx);
7416        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7417        cx.update(|window, cx| {
7418            set_language_config(
7419                "alpha",
7420                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
7421                cx,
7422            );
7423            set_language_config(
7424                "beta",
7425                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("‹", "›")]),
7426                cx,
7427            );
7428            let first = cx.new(|cx| EditorState::new(window, cx).language("ALPHA"));
7429            let second = cx.new(|cx| EditorState::new(window, cx).language("beta"));
7430            first.update(cx, |state, cx| {
7431                state.replace_text_in_range(None, "«", window, cx);
7432                assert_eq!(state.text().to_string(), "«»");
7433                state.set_value("", window, cx);
7434                state.set_smart_indent(false, window, cx);
7435                state.set_highlighter("beta", cx);
7436                assert!(!state.mode.is_smart_indent());
7437                state.replace_text_in_range(None, "‹", window, cx);
7438                assert_eq!(state.text().to_string(), "‹›");
7439                state.set_value("", window, cx);
7440                state.set_highlighter("unknown", cx);
7441                state.replace_text_in_range(None, "(", window, cx);
7442                assert_eq!(state.text().to_string(), "()");
7443            });
7444            second.update(cx, |state, cx| {
7445                state.replace_text_in_range(None, "‹", window, cx);
7446                assert_eq!(state.text().to_string(), "‹›");
7447            });
7448        });
7449    }
7450
7451    #[gpui::test]
7452    fn test_language_service_replacement_updates_syntax_without_render(cx: &mut TestAppContext) {
7453        use crate::input::{LanguageProvider, SyntaxContextProvider, set_language_provider};
7454        struct StringLanguages(Rc<Cell<usize>>);
7455        impl LanguageProvider for StringLanguages {
7456            fn syntax_context_provider(&self, _: &str) -> Option<Rc<dyn SyntaxContextProvider>> {
7457                self.0.set(self.0.get() + 1);
7458                Some(Rc::new(StringAllProvider))
7459            }
7460        }
7461        struct CodeLanguages;
7462        impl LanguageProvider for CodeLanguages {}
7463        let view = InputView::<EditorMode>::new(cx);
7464        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7465        cx.update(|window, cx| {
7466            let creations = Rc::new(Cell::new(0));
7467            set_language_provider(Rc::new(StringLanguages(creations.clone())), cx);
7468            view.input.update(cx, |state, cx| {
7469                state.replace_text_in_range(None, "(", window, cx);
7470                state.replace_text_in_range(None, "[", window, cx);
7471                assert_eq!(state.text().to_string(), "([");
7472                assert_eq!(
7473                    creations.get(),
7474                    1,
7475                    "retain the document provider between edits"
7476                );
7477            });
7478            set_language_provider(Rc::new(CodeLanguages), cx);
7479            view.input.update(cx, |state, cx| {
7480                state.replace_text_in_range(None, "{", window, cx);
7481                assert_eq!(state.text().to_string(), "([{}");
7482            });
7483        });
7484    }
7485
7486    #[gpui::test]
7487    fn test_language_config_updates_existing_editors(cx: &mut TestAppContext) {
7488        use crate::input::{AutoClosingPair, set_language_config};
7489        let view = InputView::<EditorMode>::new(cx);
7490        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7491        setup_cursors(&mut cx, &view.input, "|");
7492        cx.update(|window, cx| {
7493            view.input.update(cx, |state, cx| {
7494                state.set_highlighter("CUSTOM", cx);
7495                state.set_auto_close(false, window, cx);
7496                state.set_smart_indent(false, window, cx);
7497            });
7498            set_language_config(
7499                "custom",
7500                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
7501                cx,
7502            );
7503            // Batched registrations must update every affected language.
7504            set_language_config("other", LanguageConfig::default(), cx);
7505            view.input.update(cx, |state, cx| {
7506                assert!(!state.mode.is_auto_close());
7507                assert!(!state.mode.is_smart_indent());
7508                state.set_auto_close(true, window, cx);
7509                state.replace_text_in_range(None, "«", window, cx);
7510            });
7511        });
7512        assert_cursors(&mut cx, &view.input, "«|»");
7513    }
7514
7515    #[gpui::test]
7516    fn test_auto_close_before_is_language_configurable(cx: &mut TestAppContext) {
7517        let view = InputView::<EditorMode>::new(cx);
7518        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7519        setup_cursors(&mut cx, &view.input, "|word");
7520        cx.update(|window, cx| {
7521            view.input.update(cx, |state, cx| {
7522                state.replace_text_in_range(None, "(", window, cx)
7523            });
7524        });
7525        assert_cursors(&mut cx, &view.input, "(|word");
7526        setup_cursors(&mut cx, &view.input, "|word");
7527        cx.update(|window, cx| {
7528            view.input.update(cx, |state, cx| {
7529                crate::input::set_language_config(
7530                    state.language_name(),
7531                    LanguageConfig::default().auto_close_before("w"),
7532                    cx,
7533                );
7534                state.replace_text_in_range(None, "(", window, cx);
7535            });
7536        });
7537        assert_cursors(&mut cx, &view.input, "(|)word");
7538    }
7539
7540    #[gpui::test]
7541    fn test_pair_context_restrictions_are_per_pair(cx: &mut TestAppContext) {
7542        use crate::input::{AutoClosingPair, SyntaxContext};
7543        let view = InputView::<EditorMode>::new(cx);
7544        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7545        setup_cursors(&mut cx, &view.input, "|");
7546        cx.update(|window, cx| {
7547            view.input.update(cx, |state, cx| {
7548                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
7549                crate::input::set_language_config(
7550                    state.language_name(),
7551                    LanguageConfig::default().auto_closing_pairs([
7552                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::String]),
7553                        AutoClosingPair::new("[", "]").not_in([SyntaxContext::Comment]),
7554                    ]),
7555                    cx,
7556                );
7557                state.replace_text_in_range(None, "(", window, cx);
7558                state.replace_text_in_range(None, "[", window, cx);
7559            });
7560        });
7561        assert_cursors(&mut cx, &view.input, "([|)");
7562    }
7563
7564    #[gpui::test]
7565    fn test_multichar_delimiters_insert_delete_and_skip(cx: &mut TestAppContext) {
7566        use crate::input::{AutoClosingPair, SyntaxContext};
7567        struct BlockComment;
7568        impl crate::input::SyntaxContextProvider for BlockComment {
7569            fn context_at(&self, text: &ropey::Rope, _: usize) -> SyntaxContext {
7570                if text.to_string().contains("/*") {
7571                    SyntaxContext::Comment
7572                } else {
7573                    SyntaxContext::Code
7574                }
7575            }
7576        }
7577        let view = InputView::<EditorMode>::new(cx);
7578        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7579        setup_cursors(&mut cx, &view.input, "|");
7580        cx.update(|window, cx| {
7581            view.input.update(cx, |state, cx| {
7582                set_test_syntax_provider(std::rc::Rc::new(BlockComment), cx);
7583                crate::input::set_language_config(
7584                    state.language_name(),
7585                    LanguageConfig::default()
7586                        .auto_closing_pairs([AutoClosingPair::new("/*", "*/")
7587                            .not_in([SyntaxContext::String, SyntaxContext::Comment])]),
7588                    cx,
7589                );
7590                state.replace_text_in_range(None, "/", window, cx);
7591                state.replace_text_in_range(None, "*", window, cx);
7592            });
7593        });
7594        assert_cursors(&mut cx, &view.input, "/*|*/");
7595        cx.update(|window, cx| {
7596            view.input
7597                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
7598        });
7599        assert_cursors(&mut cx, &view.input, "|");
7600        cx.update(|window, cx| {
7601            view.input.update(cx, |state, cx| {
7602                state.undo(&Undo, window, cx);
7603                state.replace_text_in_range(None, "*", window, cx);
7604                state.replace_text_in_range(None, "/", window, cx);
7605            });
7606        });
7607        assert_cursors(&mut cx, &view.input, "/**/|");
7608    }
7609
7610    #[gpui::test]
7611    fn test_auto_closed_pairs_fallback_and_explicit_disable(cx: &mut TestAppContext) {
7612        use crate::input::BracketPair;
7613        let view = InputView::<EditorMode>::new(cx);
7614        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7615        setup_cursors(&mut cx, &view.input, "|");
7616        cx.update(|window, cx| {
7617            view.input.update(cx, |state, cx| {
7618                let mut rules = LanguageConfig::default().brackets([BracketPair::new("«", "»")]);
7619                rules.auto_closing_pairs = None;
7620                crate::input::set_language_config(state.language_name(), rules.clone(), cx);
7621                state.replace_text_in_range(None, "«", window, cx);
7622                crate::input::set_language_config(
7623                    state.language_name(),
7624                    rules.auto_closing_pairs([]),
7625                    cx,
7626                );
7627                state.replace_text_in_range(None, "«", window, cx);
7628            });
7629        });
7630        assert_cursors(&mut cx, &view.input, "««|»");
7631    }
7632
7633    #[gpui::test]
7634    fn test_undo_open_composition_preserves_generated_pairs(cx: &mut TestAppContext) {
7635        use crate::input::{AutoClosingPair, SyntaxContext};
7636        let view = InputView::<EditorMode>::new(cx);
7637        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7638        setup_cursors(&mut cx, &view.input, "|");
7639        cx.update(|window, cx| {
7640            view.input.update(cx, |state, cx| {
7641                crate::input::set_language_config(
7642                    state.language_name(),
7643                    LanguageConfig::default()
7644                        .auto_closing_pairs([
7645                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
7646                        ]),
7647                    cx,
7648                );
7649                state.replace_text_in_range(None, "/", window, cx);
7650                state.replace_text_in_range(None, "*", window, cx);
7651                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
7652                state.replace_and_mark_text_in_range(None, "x", Some(1..1), window, cx);
7653                state.undo(&Undo, window, cx);
7654                state.replace_text_in_range(None, "*", window, cx);
7655                state.replace_text_in_range(None, "/", window, cx);
7656            });
7657        });
7658        assert_cursors(&mut cx, &view.input, "/**/|");
7659    }
7660
7661    #[gpui::test]
7662    fn test_undo_does_not_promote_literal_comment_delimiters(cx: &mut TestAppContext) {
7663        use crate::input::{AutoClosingPair, SyntaxContext};
7664        let view = InputView::<EditorMode>::new(cx);
7665        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7666        setup_cursors(&mut cx, &view.input, "/*|*/");
7667        cx.update(|window, cx| {
7668            view.input.update(cx, |state, cx| {
7669                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
7670                crate::input::set_language_config(
7671                    state.language_name(),
7672                    LanguageConfig::default()
7673                        .auto_closing_pairs([
7674                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
7675                        ]),
7676                    cx,
7677                );
7678                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
7679                state.undo(&Undo, window, cx);
7680                state.backspace(&Backspace, window, cx);
7681            });
7682        });
7683        assert_cursors(&mut cx, &view.input, "/|*/");
7684    }
7685
7686    #[gpui::test]
7687    fn test_multiple_generated_pairs_retain_their_identity(cx: &mut TestAppContext) {
7688        use crate::input::{AutoClosingPair, SyntaxContext};
7689        struct CommentScope;
7690        impl crate::input::SyntaxContextProvider for CommentScope {
7691            fn context_at(&self, text: &ropey::Rope, offset: usize) -> SyntaxContext {
7692                if text
7693                    .to_string()
7694                    .find("*/")
7695                    .is_some_and(|end| offset < end + 2)
7696                {
7697                    SyntaxContext::Comment
7698                } else {
7699                    SyntaxContext::Code
7700                }
7701            }
7702        }
7703        let view = InputView::<EditorMode>::new(cx);
7704        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7705        setup_cursors(&mut cx, &view.input, "|");
7706        cx.update(|window, cx| {
7707            view.input.update(cx, |state, cx| {
7708                set_test_syntax_provider(std::rc::Rc::new(CommentScope), cx);
7709                crate::input::set_language_config(
7710                    state.language_name(),
7711                    LanguageConfig::default().auto_closing_pairs([
7712                        AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment]),
7713                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::Comment]),
7714                    ]),
7715                    cx,
7716                );
7717                state.replace_text_in_range(None, "/", window, cx);
7718                state.replace_text_in_range(None, "*", window, cx);
7719                state.set_selected_range(4..4, cx);
7720                state.replace_text_in_range(None, "(", window, cx);
7721                state.set_selected_range(2..2, cx);
7722                state.replace_text_in_range(None, "*", window, cx);
7723                state.replace_text_in_range(None, "/", window, cx);
7724            });
7725        });
7726        assert_cursors(&mut cx, &view.input, "/**/|()");
7727        cx.update(|window, cx| {
7728            view.input.update(cx, |state, cx| {
7729                state.set_selected_range(2..2, cx);
7730                state.backspace(&Backspace, window, cx);
7731                state.undo(&Undo, window, cx);
7732                state.redo(&Redo, window, cx);
7733            });
7734        });
7735        assert_cursors(&mut cx, &view.input, "|()");
7736    }
7737
7738    #[gpui::test]
7739    fn test_replacing_generated_opener_invalidates_the_pair(cx: &mut TestAppContext) {
7740        use crate::input::{AutoClosingPair, SyntaxContext};
7741        let view = InputView::<EditorMode>::new(cx);
7742        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7743        setup_cursors(&mut cx, &view.input, "|");
7744        cx.update(|window, cx| {
7745            view.input.update(cx, |state, cx| {
7746                crate::input::set_language_config(
7747                    state.language_name(),
7748                    LanguageConfig::default()
7749                        .auto_closing_pairs([
7750                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
7751                        ]),
7752                    cx,
7753                );
7754                state.replace_text_in_range(None, "/", window, cx);
7755                state.replace_text_in_range(None, "*", window, cx);
7756                state.replace_text_in_range(Some(0..2), "//", window, cx);
7757                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
7758                state.replace_text_in_range(None, "*", window, cx);
7759            });
7760        });
7761        assert_cursors(&mut cx, &view.input, "//*|*/");
7762    }
7763
7764    #[gpui::test]
7765    fn test_configuration_preserves_compiled_regex_options(cx: &mut TestAppContext) {
7766        use crate::input::IndentationRules;
7767        let view = InputView::<EditorMode>::new(cx);
7768        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7769        setup_cursors(&mut cx, &view.input, "BEGIN|");
7770        cx.update(|window, cx| {
7771            view.input.update(cx, |state, cx| {
7772                let rules = |insensitive| {
7773                    LanguageConfig::default().indentation_rules(IndentationRules::new(
7774                        regex::RegexBuilder::new("begin$")
7775                            .case_insensitive(insensitive)
7776                            .build()
7777                            .unwrap(),
7778                        regex::Regex::new("^end").unwrap(),
7779                    ))
7780                };
7781                crate::input::set_language_config(state.language_name(), rules(false), cx);
7782                crate::input::set_language_config(state.language_name(), rules(true), cx);
7783                state.enter(
7784                    &Enter {
7785                        secondary: false,
7786                        shift: false,
7787                    },
7788                    window,
7789                    cx,
7790                );
7791            });
7792        });
7793        assert_cursors(&mut cx, &view.input, "BEGIN\n  |");
7794    }
7795
7796    #[gpui::test]
7797    fn test_indent_patterns_are_applied_on_enter(cx: &mut TestAppContext) {
7798        use crate::input::IndentationRules;
7799        let view = InputView::<EditorMode>::new(cx);
7800        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7801        let rules = LanguageConfig::default().indentation_rules(IndentationRules::new(
7802            regex::Regex::new(r"\bbegin\s*$").unwrap(),
7803            regex::Regex::new(r"^\s*end\b").unwrap(),
7804        ));
7805        for (before, expected) in [("begin|", "begin\n  |"), ("  |end", "  \n|end")] {
7806            setup_cursors(&mut cx, &view.input, before);
7807            cx.update(|window, cx| {
7808                view.input.update(cx, |state, cx| {
7809                    crate::input::set_language_config(state.language_name(), rules.clone(), cx);
7810                    state.enter(
7811                        &Enter {
7812                            secondary: false,
7813                            shift: false,
7814                        },
7815                        window,
7816                        cx,
7817                    );
7818                });
7819            });
7820            assert_cursors(&mut cx, &view.input, expected);
7821        }
7822    }
7823
7824    #[gpui::test]
7825    fn test_custom_language_config(cx: &mut TestAppContext) {
7826        let view = InputView::<EditorMode>::new(cx);
7827        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7828        cx.update(|_window, cx| {
7829            view.input.update(cx, |state, cx| {
7830                crate::input::set_language_config(
7831                    state.language_name(),
7832                    crate::input::language_config::LanguageConfig::default()
7833                        .brackets([crate::input::BracketPair::new("«", "»")])
7834                        .auto_closing_pairs([crate::input::AutoClosingPair::new("«", "»")]),
7835                    cx,
7836                );
7837            });
7838        });
7839        // Custom pair closes.
7840        setup_cursors(&mut cx, &view.input, "|");
7841        cx.update(|window, cx| {
7842            view.input.update(cx, |state, cx| {
7843                state.replace_text_in_range(None, "«", window, cx);
7844            });
7845        });
7846        assert_cursors(&mut cx, &view.input, "«|»");
7847        // Default `(` no longer pairs under custom rules.
7848        cx.update(|window, cx| {
7849            view.input.update(cx, |state, cx| {
7850                state.set_value("\n", window, cx);
7851                state.set_selected_range(0..0, cx);
7852                state.replace_text_in_range(None, "(", window, cx);
7853            });
7854        });
7855        assert_cursors(&mut cx, &view.input, "(|");
7856    }
7857
7858    #[gpui::test]
7859    fn test_skip_records_no_history(cx: &mut TestAppContext) {
7860        let view = InputView::<EditorMode>::new(cx);
7861        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7862        setup_cursors(&mut cx, &view.input, "(a|)");
7863        cx.update(|window, cx| {
7864            view.input.update(cx, |state, cx| {
7865                // Type a normal char (recorded), then skip over `)`.
7866                state.replace_text_in_range(None, "b", window, cx);
7867            });
7868        });
7869        assert_cursors(&mut cx, &view.input, "(ab|)");
7870        cx.update(|window, cx| {
7871            view.input.update(cx, |state, cx| {
7872                state.replace_text_in_range(None, ")", window, cx);
7873            });
7874        });
7875        // Skip is a pure cursor move: no history entry of its own.
7876        assert_cursors(&mut cx, &view.input, "(ab)|");
7877        // Undo removes `b`, never exposing a transient `())`.
7878        cx.update(|window, cx| {
7879            view.input
7880                .update(cx, |state, cx| state.undo(&Undo, window, cx));
7881        });
7882        assert_cursors(&mut cx, &view.input, "(a|)");
7883    }
7884
7885    #[gpui::test]
7886    fn test_pair_insert_undo_removes_both(cx: &mut TestAppContext) {
7887        let view = InputView::<EditorMode>::new(cx);
7888        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7889        setup_cursors(&mut cx, &view.input, "|");
7890        cx.update(|window, cx| {
7891            view.input.update(cx, |state, cx| {
7892                state.replace_text_in_range(None, "(", window, cx);
7893            });
7894        });
7895        assert_cursors(&mut cx, &view.input, "(|)");
7896        // One undo removes the whole pair (typing coalescing).
7897        cx.update(|window, cx| {
7898            view.input
7899                .update(cx, |state, cx| state.undo(&Undo, window, cx));
7900        });
7901        assert_cursors(&mut cx, &view.input, "|");
7902    }
7903
7904    #[gpui::test]
7905    fn test_escaped_quote_does_not_skip(cx: &mut TestAppContext) {
7906        let view = InputView::<EditorMode>::new(cx);
7907        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7908        // Odd backslashes: the typed quote is escaped, insert literally.
7909        setup_cursors(&mut cx, &view.input, "\"hello\\|\"");
7910        cx.update(|window, cx| {
7911            view.input.update(cx, |state, cx| {
7912                state.replace_text_in_range(None, "\"", window, cx);
7913            });
7914        });
7915        assert_cursors(&mut cx, &view.input, "\"hello\\\"|\"");
7916    }
7917
7918    #[gpui::test]
7919    fn test_even_backslashes_still_skip(cx: &mut TestAppContext) {
7920        let view = InputView::<EditorMode>::new(cx);
7921        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7922        // Even backslashes: quote is not escaped, skip over the terminator.
7923        setup_cursors(&mut cx, &view.input, "\"hello\\\\|\"");
7924        cx.update(|window, cx| {
7925            view.input.update(cx, |state, cx| {
7926                state.replace_text_in_range(None, "\"", window, cx);
7927            });
7928        });
7929        assert_cursors(&mut cx, &view.input, "\"hello\\\\\"|");
7930    }
7931
7932    #[gpui::test]
7933    fn test_editor_options_survive_configuration_replacement(cx: &mut TestAppContext) {
7934        use crate::input::{BracketPair, LanguageConfig};
7935
7936        let view = InputView::<EditorMode>::new(cx);
7937        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7938        cx.update(|window, cx| {
7939            view.input.update(cx, |state, cx| {
7940                // Language changes update rules without changing editor preferences.
7941                state.set_smart_indent(false, window, cx);
7942                crate::input::set_language_config(
7943                    state.language_name(),
7944                    LanguageConfig::default().brackets([BracketPair::new("«", "»")]),
7945                    cx,
7946                );
7947            });
7948        });
7949        view.input.read_with(&cx, |state, _| {
7950            assert!(!state.mode.is_smart_indent(), "editor option preserved");
7951            assert!(state.mode.is_auto_close());
7952            let pairs = &state.mode.language_config().unwrap().brackets;
7953            assert_eq!(pairs.len(), 1, "pairs follow configuration");
7954            assert_eq!(pairs[0].open.as_ref(), "«");
7955        });
7956    }
7957
7958    struct CommentAllProvider;
7959    impl crate::input::SyntaxContextProvider for CommentAllProvider {
7960        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
7961            crate::input::SyntaxContext::Comment
7962        }
7963    }
7964
7965    struct StringAllProvider;
7966    impl crate::input::SyntaxContextProvider for StringAllProvider {
7967        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
7968            crate::input::SyntaxContext::String
7969        }
7970    }
7971
7972    #[gpui::test]
7973    fn test_smart_indent_suppressed_in_strings(cx: &mut TestAppContext) {
7974        use std::rc::Rc;
7975
7976        let view = InputView::<EditorMode>::new(cx);
7977        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7978        cx.update(|_window, cx| {
7979            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
7980        });
7981        // Trigger `{` inside a string: no extra level, base indent only.
7982        setup_cursors(&mut cx, &view.input, "  x = {|");
7983        cx.update(|window, cx| {
7984            view.input.update(cx, |state, cx| {
7985                state.enter(
7986                    &Enter {
7987                        secondary: false,
7988                        shift: false,
7989                    },
7990                    window,
7991                    cx,
7992                );
7993            });
7994        });
7995        assert_cursors(&mut cx, &view.input, "  x = {\n  |");
7996    }
7997
7998    #[gpui::test]
7999    fn test_split_suppressed_in_strings(cx: &mut TestAppContext) {
8000        use std::rc::Rc;
8001
8002        let view = InputView::<EditorMode>::new(cx);
8003        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8004        cx.update(|_window, cx| {
8005            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
8006        });
8007        // No three-way split inside strings: plain newline instead.
8008        setup_cursors(&mut cx, &view.input, "{|}");
8009        cx.update(|window, cx| {
8010            view.input.update(cx, |state, cx| {
8011                state.enter(
8012                    &Enter {
8013                        secondary: false,
8014                        shift: false,
8015                    },
8016                    window,
8017                    cx,
8018                );
8019            });
8020        });
8021        assert_cursors(&mut cx, &view.input, "{\n|}");
8022    }
8023
8024    #[gpui::test]
8025    fn test_comment_context_disables_pairing(cx: &mut TestAppContext) {
8026        use std::rc::Rc;
8027
8028        let view = InputView::<EditorMode>::new(cx);
8029        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8030        cx.update(|_window, cx| {
8031            set_test_syntax_provider(Rc::new(CommentAllProvider), cx);
8032        });
8033        setup_cursors(&mut cx, &view.input, "|");
8034        cx.update(|window, cx| {
8035            view.input.update(cx, |state, cx| {
8036                state.replace_text_in_range(None, "(", window, cx);
8037            });
8038        });
8039        // In comments every character is literal.
8040        assert_cursors(&mut cx, &view.input, "(|");
8041    }
8042
8043    #[gpui::test]
8044    fn test_backspace_deletes_empty_pair(cx: &mut TestAppContext) {
8045        let view = InputView::<EditorMode>::new(cx);
8046        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8047        setup_cursors(&mut cx, &view.input, "{|}");
8048        cx.update(|window, cx| {
8049            view.input
8050                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
8051        });
8052        assert_cursors(&mut cx, &view.input, "|");
8053    }
8054
8055    #[gpui::test]
8056    fn test_backspace_keeps_closer_with_content(cx: &mut TestAppContext) {
8057        let view = InputView::<EditorMode>::new(cx);
8058        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8059        setup_cursors(&mut cx, &view.input, "{d|}");
8060        cx.update(|window, cx| {
8061            view.input
8062                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
8063        });
8064        // Only `d` is deleted, the closing brace stays.
8065        assert_cursors(&mut cx, &view.input, "{|}");
8066    }
8067
8068    #[gpui::test]
8069    fn test_enter_splits_brackets(cx: &mut TestAppContext) {
8070        let view = InputView::<EditorMode>::new(cx);
8071        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8072        setup_cursors(&mut cx, &view.input, "{|}");
8073        cx.update(|window, cx| {
8074            view.input.update(cx, |state, cx| {
8075                state.enter(
8076                    &Enter {
8077                        secondary: false,
8078                        shift: false,
8079                    },
8080                    window,
8081                    cx,
8082                );
8083            });
8084        });
8085        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
8086        // Selection must be collapsed: typing next must insert, not replace.
8087        view.input.read_with(&cx, |state, _| {
8088            assert!(state.active_selection().is_empty());
8089        });
8090        cx.update(|window, cx| {
8091            view.input.update(cx, |state, cx| {
8092                state.replace_text_in_range(None, "x", window, cx);
8093            });
8094        });
8095        assert_cursors(&mut cx, &view.input, "{\n  x|\n}");
8096    }
8097
8098    #[gpui::test]
8099    fn test_enter_smart_indent_after_brace(cx: &mut TestAppContext) {
8100        let view = InputView::<EditorMode>::new(cx);
8101        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8102        setup_cursors(&mut cx, &view.input, "fn main() {|");
8103        cx.update(|window, cx| {
8104            view.input.update(cx, |state, cx| {
8105                state.enter(
8106                    &Enter {
8107                        secondary: false,
8108                        shift: false,
8109                    },
8110                    window,
8111                    cx,
8112                );
8113            });
8114        });
8115        assert_cursors(&mut cx, &view.input, "fn main() {\n  |");
8116    }
8117
8118    #[gpui::test]
8119    fn test_block_indent_tracks_all_preceding_edits(cx: &mut TestAppContext) {
8120        let view = multi_line(cx);
8121        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8122        setup_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
8123        cx.update(|window, cx| {
8124            view.input
8125                .update(cx, |state, cx| state.indent(true, window, cx));
8126        });
8127        assert_cursors(&mut cx, &view.input, "  |ab\n  |cd\n  |ef");
8128        cx.update(|window, cx| {
8129            view.input
8130                .update(cx, |state, cx| state.outdent(true, window, cx));
8131        });
8132        assert_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
8133    }
8134
8135    #[gpui::test]
8136    fn test_block_outdent_clamps_cursor_inside_indent(cx: &mut TestAppContext) {
8137        let view = multi_line(cx);
8138        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8139        setup_cursors(&mut cx, &view.input, "ab\n | cd");
8140        cx.update(|window, cx| {
8141            view.input
8142                .update(cx, |state, cx| state.outdent(true, window, cx));
8143        });
8144        assert_cursors(&mut cx, &view.input, "ab\n|cd");
8145    }
8146
8147    #[gpui::test]
8148    fn test_ime_restores_original_selection(cx: &mut TestAppContext) {
8149        let view = InputView::build(cx, |state| state);
8150        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8151        cx.update(|window, cx| {
8152            view.input.update(cx, |state, cx| {
8153                state.set_value("abc", window, cx);
8154                state.set_selected_range(1..2, cx);
8155                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
8156                state.replace_text_in_range(None, "你", window, cx);
8157                state.undo(&Undo, window, cx);
8158                assert_eq!(state.value(), "abc");
8159                assert_eq!(state.selected_range(), 1..2);
8160                state.redo(&Redo, window, cx);
8161                assert_eq!(state.selected_range(), 4..4);
8162            });
8163        });
8164    }
8165
8166    #[gpui::test]
8167    fn test_noop_does_not_change_redo_selection(cx: &mut TestAppContext) {
8168        let view = multi_line(cx);
8169        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8170        cx.update(|window, cx| {
8171            view.input.update(cx, |state, cx| {
8172                state.set_value("abc", window, cx);
8173                state.set_selected_range(1..1, cx);
8174                state.replace_text_in_range(None, "X", window, cx);
8175                state.set_selected_range(0..0, cx);
8176                state.replace_text_in_range(None, "", window, cx);
8177                state.undo(&Undo, window, cx);
8178                state.redo(&Redo, window, cx);
8179                assert_eq!(state.value(), "aXbc");
8180                assert_eq!(state.selected_range(), 2..2);
8181            });
8182        });
8183    }
8184
8185    #[gpui::test]
8186    fn test_multi_cursor_insert_text(cx: &mut TestAppContext) {
8187        let view = multi_line(cx);
8188        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8189        let input = view.input;
8190
8191        setup_cursors(&mut cx, &input, "|hello |world|");
8192        cx.update(|window, cx| {
8193            input.update(cx, |state, cx| {
8194                state.replace_text_in_range(None, ">>>", window, cx);
8195            });
8196        });
8197        assert_cursors(&mut cx, &input, ">>>|hello >>>|world>>>|");
8198    }
8199
8200    #[gpui::test]
8201    fn test_multi_cursor_delete_backward(cx: &mut TestAppContext) {
8202        let view = multi_line(cx);
8203        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8204        let input = view.input;
8205
8206        setup_cursors(&mut cx, &input, "|islands| cars|");
8207        cx.update(|window, cx| {
8208            input.update(cx, |state, cx| {
8209                state.backspace(&Backspace, window, cx);
8210            });
8211        });
8212        // The first cursor has nothing to delete. The others delete an `s`.
8213        assert_cursors(&mut cx, &input, "|island| car|");
8214    }
8215
8216    #[gpui::test]
8217    fn test_multi_cursor_delete_forward_merges(cx: &mut TestAppContext) {
8218        let view = multi_line(cx);
8219        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8220        let input = view.input;
8221
8222        setup_cursors(&mut cx, &input, "hello| |world");
8223        cx.update(|window, cx| {
8224            input.update(cx, |state, cx| {
8225                state.delete(&Delete, window, cx);
8226            });
8227        });
8228        // Adjacent deletions merge into a single cursor.
8229        assert_cursors(&mut cx, &input, "hello|orld");
8230    }
8231
8232    #[gpui::test]
8233    fn test_multi_cursor_multiline_insert_and_delete(cx: &mut TestAppContext) {
8234        let view = multi_line(cx);
8235        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8236        let input = view.input;
8237
8238        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
8239        cx.update(|window, cx| {
8240            input.update(cx, |state, cx| {
8241                state.replace_text_in_range(None, "a", window, cx);
8242            });
8243        });
8244        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
8245
8246        // The whole multi-edit insert is a single undo transaction.
8247        input.read_with(&cx, |state, _| {
8248            assert_eq!(state.undo_manager.undo_count(), 1);
8249        });
8250
8251        cx.update(|window, cx| {
8252            input.update(cx, |state, cx| {
8253                state.backspace(&Backspace, window, cx);
8254            });
8255        });
8256        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
8257    }
8258
8259    #[gpui::test]
8260    fn test_add_cursor_below_preserves_column(cx: &mut TestAppContext) {
8261        let view = multi_line(cx);
8262        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8263        let input = view.input;
8264
8265        setup_cursors(&mut cx, &input, "ab|cd\nabcd");
8266        cx.update(|window, cx| {
8267            input.update(cx, |state, cx| {
8268                state.add_cursor_below(&AddCursorBelow, window, cx);
8269            });
8270        });
8271        assert_cursors(&mut cx, &input, "ab|cd\nab|cd");
8272    }
8273
8274    #[gpui::test]
8275    fn test_add_cursor_at_rejects_duplicates(cx: &mut TestAppContext) {
8276        let view = multi_line(cx);
8277        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8278        let input = view.input;
8279
8280        setup_cursors(&mut cx, &input, "he|llo");
8281        cx.update(|_, cx| {
8282            input.update(cx, |state, cx| {
8283                // Duplicate of the existing cursor is rejected.
8284                state.add_cursor_at(2, cx);
8285                assert_eq!(state.selections.len(), 1);
8286                // A distinct offset adds a cursor.
8287                state.add_cursor_at(4, cx);
8288                assert_eq!(state.selections.len(), 2);
8289            });
8290        });
8291    }
8292
8293    #[gpui::test]
8294    fn test_multi_cursor_undo_redo_restores_selections(cx: &mut TestAppContext) {
8295        let view = multi_line(cx);
8296        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8297        let input = view.input;
8298
8299        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
8300        cx.update(|window, cx| {
8301            input.update(cx, |state, cx| {
8302                state.replace_text_in_range(None, "a", window, cx);
8303            });
8304        });
8305        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
8306
8307        cx.update(|window, cx| {
8308            input.update(cx, |state, cx| {
8309                state.undo(&Undo, window, cx);
8310            });
8311        });
8312        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
8313
8314        cx.update(|window, cx| {
8315            input.update(cx, |state, cx| {
8316                state.redo(&Redo, window, cx);
8317            });
8318        });
8319        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
8320    }
8321
8322    #[gpui::test]
8323    fn test_multi_cursor_undo_redo_different_line_lengths(cx: &mut TestAppContext) {
8324        let view = multi_line(cx);
8325        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8326        let input = view.input;
8327
8328        setup_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
8329        cx.update(|window, cx| {
8330            input.update(cx, |state, cx| {
8331                state.replace_text_in_range(None, "a", window, cx);
8332            });
8333        });
8334        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
8335
8336        cx.update(|window, cx| {
8337            input.update(cx, |state, cx| {
8338                state.undo(&Undo, window, cx);
8339            });
8340        });
8341        assert_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
8342
8343        cx.update(|window, cx| {
8344            input.update(cx, |state, cx| {
8345                state.redo(&Redo, window, cx);
8346            });
8347        });
8348        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
8349    }
8350
8351    #[gpui::test]
8352    fn test_multi_cursor_undo_multiple_inserts(cx: &mut TestAppContext) {
8353        let view = multi_line(cx);
8354        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8355        let input = view.input;
8356
8357        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
8358        for ch in ['a', 'b', 'c'] {
8359            cx.update(|window, cx| {
8360                input.update(cx, |state, cx| {
8361                    state.replace_text_in_range(None, &ch.to_string(), window, cx);
8362                });
8363            });
8364        }
8365        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
8366
8367        // The repeated keystrokes form one typing gesture.
8368        cx.update(|window, cx| {
8369            input.update(cx, |state, cx| {
8370                assert_eq!(state.undo_manager.undo_count(), 1);
8371                state.undo(&Undo, window, cx);
8372            });
8373        });
8374        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
8375
8376        cx.update(|window, cx| {
8377            input.update(cx, |state, cx| {
8378                state.redo(&Redo, window, cx);
8379            });
8380        });
8381        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
8382    }
8383
8384    #[gpui::test]
8385    fn test_multi_cursor_backspace_run_is_one_undo(cx: &mut TestAppContext) {
8386        let view = multi_line(cx);
8387        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8388        let input = view.input;
8389
8390        setup_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
8391        for _ in 0..3 {
8392            cx.update(|window, cx| {
8393                input.update(cx, |state, cx| {
8394                    state.backspace(&Backspace, window, cx);
8395                });
8396            });
8397        }
8398        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
8399
8400        cx.update(|window, cx| {
8401            input.update(cx, |state, cx| {
8402                assert_eq!(state.undo_manager.undo_count(), 1);
8403                state.undo(&Undo, window, cx);
8404            });
8405        });
8406        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
8407    }
8408
8409    #[gpui::test]
8410    fn test_adding_a_cursor_splits_the_typing_gesture(cx: &mut TestAppContext) {
8411        let view = multi_line(cx);
8412        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8413        let input = view.input;
8414
8415        setup_cursors(&mut cx, &input, "|1\n2\n3");
8416        cx.update(|window, cx| {
8417            input.update(cx, |state, cx| {
8418                state.replace_text_in_range(None, "a", window, cx);
8419                state.add_cursor_below(&AddCursorBelow, window, cx);
8420                state.replace_text_in_range(None, "b", window, cx);
8421            });
8422        });
8423        assert_cursors(&mut cx, &input, "ab|1\n2b|\n3");
8424
8425        // The keystroke after the cursor was added is its own undo entry.
8426        cx.update(|window, cx| {
8427            input.update(cx, |state, cx| {
8428                state.undo(&Undo, window, cx);
8429            });
8430        });
8431        assert_cursors(&mut cx, &input, "a|1\n2|\n3");
8432    }
8433
8434    #[gpui::test]
8435    fn test_multi_cursor_indent_is_one_undo(cx: &mut TestAppContext) {
8436        let view = multi_line(cx);
8437        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8438        let input = view.input;
8439
8440        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
8441        cx.update(|window, cx| {
8442            input.update(cx, |state, cx| {
8443                state.indent_inline(&IndentInline, window, cx);
8444                assert_eq!(state.undo_manager.undo_count(), 1);
8445                state.undo(&Undo, window, cx);
8446            });
8447        });
8448        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
8449    }
8450
8451    #[gpui::test]
8452    fn test_multi_cursor_indent_then_outdent_roundtrips(cx: &mut TestAppContext) {
8453        let view = multi_line(cx);
8454        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8455        let input = view.input;
8456
8457        // Cursors at line starts.
8458        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
8459        cx.update(|window, cx| {
8460            input.update(cx, |state, cx| {
8461                state.indent(false, window, cx);
8462            });
8463        });
8464        assert_cursors(&mut cx, &input, "  |1\n  |2\n  |3");
8465
8466        cx.update(|window, cx| {
8467            input.update(cx, |state, cx| {
8468                state.outdent(false, window, cx);
8469            });
8470        });
8471        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
8472    }
8473
8474    #[gpui::test]
8475    fn test_inline_outdent_only_removes_line_indentation(cx: &mut TestAppContext) {
8476        let view = multi_line(cx);
8477        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8478        let input = view.input;
8479
8480        // A mid-line indent lands at the cursor, and the outdent does not
8481        // take it back: it only ever removes leading line indentation.
8482        setup_cursors(&mut cx, &input, "1|2\n1|2");
8483        cx.update(|window, cx| {
8484            input.update(cx, |state, cx| {
8485                state.indent(false, window, cx);
8486            });
8487        });
8488        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
8489
8490        cx.update(|window, cx| {
8491            input.update(cx, |state, cx| {
8492                state.outdent(false, window, cx);
8493            });
8494        });
8495        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
8496
8497        // A line with leading indentation loses that, wherever the cursor is.
8498        setup_cursors(&mut cx, &input, "  1|2\n  1|2");
8499        cx.update(|window, cx| {
8500            input.update(cx, |state, cx| {
8501                state.outdent(false, window, cx);
8502            });
8503        });
8504        assert_cursors(&mut cx, &input, "1|2\n1|2");
8505    }
8506
8507    #[gpui::test]
8508    fn test_readonly_multi_cursor_commands_leave_state_unchanged(cx: &mut TestAppContext) {
8509        let view = multi_line(cx);
8510        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8511        let input = view.input;
8512
8513        setup_cursors(&mut cx, &input, "a|b\nc|d");
8514        cx.update(|window, cx| {
8515            input.update(cx, |state, cx| {
8516                let before: Vec<_> = state.selections.iter().copied().collect();
8517                state.set_readonly(true, cx);
8518                state.backspace(&Backspace, window, cx);
8519                state.indent_inline(&IndentInline, window, cx);
8520
8521                assert_eq!(state.value(), "ab\ncd\n");
8522                assert_eq!(state.selections.iter().copied().collect::<Vec<_>>(), before);
8523            });
8524        });
8525    }
8526
8527    #[gpui::test]
8528    fn test_multi_cursor_edit_preserves_the_active_cursor(cx: &mut TestAppContext) {
8529        let view = multi_line(cx);
8530        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8531        let input = view.input;
8532
8533        setup_cursors(&mut cx, &input, "x|\n|y");
8534        cx.update(|window, cx| {
8535            input.update(cx, |state, cx| {
8536                let mut selections: Vec<_> = state.selections.iter().copied().collect();
8537                selections.swap(0, 1);
8538                state.selections.replace_all(selections);
8539                let active_id = state.active_selection().id;
8540                state.replace_text_in_range(None, "!", window, cx);
8541                assert_eq!(state.active_selection().id, active_id);
8542            });
8543        });
8544    }
8545
8546    #[gpui::test]
8547    fn test_block_indent_outdent_with_selection(cx: &mut TestAppContext) {
8548        let view = multi_line(cx);
8549        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8550        let input = view.input;
8551
8552        cx.update(|window, cx| {
8553            input.update(cx, |state, cx| {
8554                state.set_value("line1\nline2\nline3", window, cx);
8555                let id = state.selections.generate_id();
8556                state
8557                    .selections
8558                    .replace_all(vec![CursorSelection::new(id, 0, 17)]);
8559                cx.notify();
8560            });
8561        });
8562
8563        cx.update(|window, cx| {
8564            input.update(cx, |state, cx| {
8565                state.indent(true, window, cx);
8566            });
8567        });
8568        input.read_with(&cx, |state, _| {
8569            assert_eq!(state.text.to_string(), "  line1\n  line2\n  line3");
8570        });
8571
8572        cx.update(|window, cx| {
8573            input.update(cx, |state, cx| {
8574                state.outdent(true, window, cx);
8575            });
8576        });
8577        input.read_with(&cx, |state, _| {
8578            assert_eq!(state.text.to_string(), "line1\nline2\nline3");
8579        });
8580    }
8581
8582    #[gpui::test]
8583    fn test_multi_cursor_word_movement(cx: &mut TestAppContext) {
8584        let view = multi_line(cx);
8585        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8586        let input = view.input;
8587
8588        setup_cursors(
8589            &mut cx,
8590            &input,
8591            "on|e two three\none t|wo three\non|e two three",
8592        );
8593
8594        cx.update(|window, cx| {
8595            input.update(cx, |state, cx| {
8596                state.move_to_next_word(&MoveToNextWord, window, cx);
8597            });
8598        });
8599        assert_cursors(
8600            &mut cx,
8601            &input,
8602            "one| two three\none two| three\none| two three",
8603        );
8604
8605        cx.update(|window, cx| {
8606            input.update(cx, |state, cx| {
8607                state.move_to_previous_word(&MoveToPreviousWord, window, cx);
8608            });
8609        });
8610        assert_cursors(
8611            &mut cx,
8612            &input,
8613            "|one two three\none |two three\n|one two three",
8614        );
8615
8616        // Move to end/start of document collapses to a single cursor.
8617        cx.update(|window, cx| {
8618            input.update(cx, |state, cx| {
8619                state.move_to_end(&MoveToEnd, window, cx);
8620            });
8621        });
8622        input.read_with(&cx, |state, _| {
8623            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
8624            assert_eq!(cursors, vec![state.text.len()]);
8625        });
8626
8627        cx.update(|window, cx| {
8628            input.update(cx, |state, cx| {
8629                state.move_to_start(&MoveToStart, window, cx);
8630            });
8631        });
8632        input.read_with(&cx, |state, _| {
8633            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
8634            assert_eq!(cursors, vec![0]);
8635        });
8636    }
8637
8638    #[gpui::test]
8639    fn test_multi_cursor_selection_commands(cx: &mut TestAppContext) {
8640        let view = multi_line(cx);
8641        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8642        let input = view.input;
8643
8644        setup_cursors(
8645            &mut cx,
8646            &input,
8647            "on|e two three\none t|wo three\non|e two three",
8648        );
8649        cx.update(|window, cx| {
8650            input.update(cx, |state, cx| {
8651                state.select_to_start_of_line(&SelectToStartOfLine, window, cx);
8652            });
8653        });
8654        assert_cursors(
8655            &mut cx,
8656            &input,
8657            "|one two three\n|one two three\n|one two three",
8658        );
8659
8660        // Select to document start collapses to the active cursor only.
8661        setup_cursors(
8662            &mut cx,
8663            &input,
8664            "on|e two three\none t|wo three\non|e two three",
8665        );
8666        cx.update(|window, cx| {
8667            input.update(cx, |state, cx| {
8668                state.select_to_start(&SelectToStart, window, cx);
8669            });
8670        });
8671        input.read_with(&cx, |state, _| {
8672            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
8673            assert_eq!(cursors, vec![0]);
8674        });
8675    }
8676
8677    #[gpui::test]
8678    fn test_multi_cursor_replace_selection(cx: &mut TestAppContext) {
8679        let view = multi_line(cx);
8680        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8681        let input = view.input;
8682
8683        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
8684        cx.update(|window, cx| {
8685            input.update(cx, |state, cx| {
8686                state.select_right(&SelectRight, window, cx);
8687            });
8688        });
8689        input.read_with(&cx, |state, _| {
8690            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
8691            assert_eq!(ranges, vec![(0, 1), (2, 3), (4, 5)]);
8692        });
8693
8694        cx.update(|window, cx| {
8695            input.update(cx, |state, cx| {
8696                state.replace_text_in_range(None, "x", window, cx);
8697            });
8698        });
8699        assert_cursors(&mut cx, &input, "x|\nx|\nx|");
8700    }
8701
8702    #[gpui::test]
8703    fn test_multi_cursor_escape_collapses(cx: &mut TestAppContext) {
8704        let view = multi_line(cx);
8705        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8706        let input = view.input;
8707
8708        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
8709        cx.update(|window, cx| {
8710            input.update(cx, |state, cx| {
8711                state.escape(&Escape, window, cx);
8712            });
8713        });
8714        input.read_with(&cx, |state, _| {
8715            assert_eq!(state.selections.len(), 1);
8716        });
8717    }
8718
8719    #[gpui::test]
8720    fn test_build_columnar_selection(cx: &mut TestAppContext) {
8721        let view = multi_line(cx);
8722        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8723        let input = view.input;
8724
8725        setup_cursors(&mut cx, &input, "abcd\nabcd\nabcd");
8726        cx.update(|_, cx| {
8727            input.update(cx, |state, cx| {
8728                // From row 0 col 1 to row 2 col 3.
8729                state.build_columnar_selection(
8730                    ColumnarPoint::new(1, 0),
8731                    ColumnarPoint::new(13, 0),
8732                    cx,
8733                );
8734                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
8735                assert_eq!(ranges, vec![(1, 3), (6, 8), (11, 13)]);
8736            });
8737        });
8738    }
8739
8740    #[gpui::test]
8741    fn test_columnar_selection_keeps_width_over_short_rows(cx: &mut TestAppContext) {
8742        let view = multi_line(cx);
8743        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8744        let input = view.input;
8745
8746        setup_cursors(&mut cx, &input, "abcdef\nab\nabcdef");
8747        cx.update(|_, cx| {
8748            input.update(cx, |state, cx| {
8749                // Ends on the short row, 3 columns past its end: the block still spans
8750                // columns 1..5, and only that row is clipped to what it has.
8751                state.build_columnar_selection(
8752                    ColumnarPoint::new(1, 0),
8753                    ColumnarPoint::new(9, 3),
8754                    cx,
8755                );
8756                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
8757                assert_eq!(ranges, vec![(1, 5), (8, 9)]);
8758            });
8759        });
8760    }
8761
8762    #[gpui::test]
8763    fn test_multi_cursor_paste_distributes_lines(cx: &mut TestAppContext) {
8764        let view = multi_line(cx);
8765        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8766        let input = view.input;
8767
8768        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
8769        cx.update(|_, cx| {
8770            cx.write_to_clipboard(ClipboardItem::new_string("x\ny\nz".to_string()));
8771        });
8772        cx.update(|window, cx| {
8773            input.update(cx, |state, cx| {
8774                state.paste(&Paste, window, cx);
8775            });
8776        });
8777        // One clipboard line per cursor.
8778        assert_cursors(&mut cx, &input, "x|1\ny|2\nz|3");
8779    }
8780}
8781
8782/// Methods that only a single-line input offers.
8783impl InputBaseState<crate::input::InputMode> {
8784    /// Create a single-line text input state.
8785    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
8786        Self::new_in_mode(window, cx)
8787    }
8788
8789    /// Set a custom step function of the [`super::NumberInput`].
8790    ///
8791    /// The `f` receives the current value and the [`StepAction`], and returns
8792    /// the step to apply, so the step can vary with the value.
8793    ///
8794    /// # Example
8795    ///
8796    /// ```ignore
8797    /// // At the boundary 1.0 the step is 0.1 going down and 0.5 going up.
8798    /// InputState::new(window, cx).step_by(|value, action, _cx| match action {
8799    ///     StepAction::Increment => if value < 1.0 { 0.1 } else { 0.5 },
8800    ///     StepAction::Decrement => if value <= 1.0 { 0.1 } else { 0.5 },
8801    /// })
8802    /// ```
8803    pub fn step_by(mut self, f: impl Fn(f64, StepAction, &mut App) -> f64 + 'static) -> Self {
8804        self.number_step = Some(NumberStep::by_value(f));
8805        self
8806    }
8807
8808    /// Set with password masked state.
8809    pub fn masked(mut self, masked: bool) -> Self {
8810        self.masked = masked;
8811        self
8812    }
8813
8814    /// Set the password masked state of the input field.
8815    pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
8816        self.masked = masked;
8817        cx.notify();
8818    }
8819
8820    /// Set the regular expression pattern of the input field.
8821    pub fn pattern(mut self, pattern: regex::Regex) -> Self {
8822        self.pattern = Some(pattern);
8823        self
8824    }
8825
8826    /// Set the regular expression pattern of the input field with reference.
8827    pub fn set_pattern(
8828        &mut self,
8829        pattern: regex::Regex,
8830        _window: &mut Window,
8831        _cx: &mut Context<Self>,
8832    ) {
8833        self.pattern = Some(pattern);
8834    }
8835
8836    /// Set the validation function of the input field.
8837    pub fn validate(mut self, f: impl Fn(&str, &mut App) -> bool + 'static) -> Self {
8838        self.validate = Some(Box::new(f));
8839        self
8840    }
8841
8842    pub fn set_validator(
8843        &mut self,
8844        validate: impl Fn(&str, &mut App) -> bool + 'static,
8845        _cx: &mut Context<Self>,
8846    ) {
8847        self.validate = Some(Box::new(validate));
8848    }
8849
8850    /// Set the step value of the [`super::NumberInput`] for increment/decrement.
8851    ///
8852    /// If any of `step`, `min`, `max` is set, the [`super::NumberInput`] will
8853    /// update the value internally (step by `step`, default 1, clamp to the
8854    /// `min`/`max` range and emit [`InputEvent::Change`]) instead of emitting
8855    /// [`super::NumberInputEvent::Step`].
8856    ///
8857    /// See also [`Self::step_by`] to calculate the step value
8858    /// based on the current value.
8859    pub fn step(mut self, step: impl Into<NumberStep>) -> Self {
8860        self.number_step = Some(step.into());
8861        self
8862    }
8863
8864    /// Set the minimum value of the [`super::NumberInput`].
8865    ///
8866    /// The value will be clamped to the minimum value on stepping and on
8867    /// blur (only if the clamped value passes the `pattern`/`validate` check).
8868    /// See also [`Self::step`].
8869    pub fn min(mut self, min: f64) -> Self {
8870        self.number_min = Some(min);
8871        self
8872    }
8873
8874    /// Set the maximum value of the [`super::NumberInput`].
8875    ///
8876    /// The value will be clamped to the maximum value on stepping and on
8877    /// blur (only if the clamped value passes the `pattern`/`validate` check).
8878    /// See also [`Self::step`].
8879    pub fn max(mut self, max: f64) -> Self {
8880        self.number_max = Some(max);
8881        self
8882    }
8883
8884    /// Update the step value after construction, `None` to fall back to
8885    /// emitting [`super::NumberInputEvent::Step`] (if `min`, `max` are unset).
8886    ///
8887    /// See [`Self::step`] and [`Self::step_by`].
8888    pub fn set_step(
8889        &mut self,
8890        step: impl Into<Option<NumberStep>>,
8891        _: &mut Window,
8892        _: &mut Context<Self>,
8893    ) {
8894        self.number_step = step.into();
8895    }
8896
8897    /// Update the minimum value after construction. See [`Self::min`].
8898    pub fn set_min(&mut self, min: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
8899        self.number_min = min;
8900    }
8901
8902    /// Update the maximum value after construction. See [`Self::max`].
8903    pub fn set_max(&mut self, max: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
8904        self.number_max = max;
8905    }
8906
8907    /// Set true to show spinner at the input right.
8908    pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
8909        self.loading = loading;
8910        cx.notify();
8911    }
8912}
8913
8914/// Methods shared by the two multi-line modes, and reachable on neither a
8915/// single-line input nor anything else.
8916impl<M: crate::input::MultiLineMode> InputBaseState<M> {
8917    /// Set this input is searchable, default is false (Default true for Code Editor).
8918    #[doc(hidden)]
8919    pub fn searchable(mut self, searchable: bool) -> Self {
8920        self.searchable = searchable;
8921        self
8922    }
8923
8924    pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
8925        self.searchable = searchable;
8926        cx.notify();
8927    }
8928
8929    /// Set the soft wrap mode, default is true.
8930    #[doc(hidden)]
8931    pub fn soft_wrap(mut self, wrap: bool) -> Self {
8932        self.soft_wrap = wrap;
8933        self
8934    }
8935
8936    /// Update the soft wrap mode, default is true.
8937    pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
8938        self.soft_wrap = wrap;
8939        if wrap {
8940            let wrap_width = self
8941                .last_layout
8942                .as_ref()
8943                .and_then(|b| b.wrap_width)
8944                .unwrap_or(self.input_bounds.size.width);
8945
8946            self.display_map.on_layout_changed(Some(wrap_width), cx);
8947
8948            // Reset scroll to left 0
8949            let mut offset = self.scroll_handle.offset();
8950            offset.x = px(0.);
8951            self.scroll_handle.set_offset(offset);
8952        } else {
8953            self.display_map.on_layout_changed(None, cx);
8954        }
8955        cx.notify();
8956    }
8957
8958    /// Set how soft-wrapped continuation lines are indented, default is [`WrappingIndent::Same`]
8959    #[doc(hidden)]
8960    pub fn wrapping_indent(mut self, wrapping_indent: WrappingIndent) -> Self {
8961        self.wrapping_indent = wrapping_indent;
8962        self
8963    }
8964
8965    /// Update how soft-wrapped continuation lines are indented.
8966    pub fn set_wrapping_indent(
8967        &mut self,
8968        wrapping_indent: WrappingIndent,
8969        _: &mut Window,
8970        cx: &mut Context<Self>,
8971    ) {
8972        self.wrapping_indent = wrapping_indent;
8973        self.display_map.set_wrapping_indent(wrapping_indent, cx);
8974        cx.notify();
8975    }
8976}
8977
8978/// Methods that only ordinary multi-line text offers.
8979impl InputBaseState<crate::input::TextareaMode> {
8980    /// Create a multi-line text state.
8981    ///
8982    /// Being multi-line is carried by the mode, not by the layout, so the
8983    /// default plain-text layout needs no adjustment here.
8984    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
8985        Self::new_in_mode(window, cx)
8986    }
8987
8988    pub fn set_auto_grow(&mut self, min_rows: usize, max_rows: usize, cx: &mut Context<Self>) {
8989        self.mode = LayoutMode::auto_grow(min_rows, max_rows.max(min_rows));
8990        cx.notify();
8991    }
8992
8993    /// Set the number of rows for the multi-line Textarea.
8994    ///
8995    /// This is only used when `multi_line` is set to true.
8996    ///
8997    /// default: 2
8998    #[doc(hidden)]
8999    pub fn rows(mut self, rows: usize) -> Self {
9000        match &mut self.mode {
9001            LayoutMode::PlainText { rows: r, .. } | LayoutMode::CodeEditor { rows: r, .. } => {
9002                *r = rows
9003            }
9004            LayoutMode::AutoGrow {
9005                max_rows: max_r,
9006                rows: r,
9007                ..
9008            } => {
9009                *r = rows;
9010                *max_r = rows;
9011            }
9012        }
9013        self
9014    }
9015
9016    pub fn set_rows(&mut self, rows: usize, cx: &mut Context<Self>) {
9017        match &mut self.mode {
9018            LayoutMode::PlainText { rows: value, .. }
9019            | LayoutMode::CodeEditor { rows: value, .. } => *value = rows,
9020            LayoutMode::AutoGrow {
9021                rows: value,
9022                max_rows,
9023                ..
9024            } => {
9025                *value = rows;
9026                *max_rows = rows;
9027            }
9028        }
9029        cx.notify();
9030    }
9031
9032    /// Grow with the content from `min_rows` through `max_rows`.
9033    pub fn auto_grow(mut self, min_rows: usize, max_rows: usize) -> Self {
9034        self.mode = LayoutMode::auto_grow(min_rows, max_rows);
9035        self
9036    }
9037}
9038
9039/// Methods that only a source-code editor offers.
9040impl InputBaseState<crate::input::EditorMode> {
9041    /// Create a source-code editor state.
9042    ///
9043    /// Default options: line numbers on, tab size 2 with soft tabs, indent
9044    /// guides on, multi-line, and search enabled. Set the language for syntax
9045    /// highlighting with [`Self::language`]; without one the text is shown
9046    /// unhighlighted.
9047    ///
9048    /// The editor aims at simple code editing or display, not at being a
9049    /// full-featured code editor. It offers syntax highlighting, auto indent,
9050    /// line numbers, and handles large text up to about 50K lines.
9051    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
9052        let mut state = Self::new_in_mode(window, cx);
9053        state.mode = LayoutMode::code_editor(super::EditorLanguage::new(cx));
9054        state.searchable = true;
9055        state
9056    }
9057
9058    /// Set the language to highlight, e.g. `"rust"`.
9059    ///
9060    /// See [`Self::set_highlighter`] to change it after construction.
9061    pub fn language(mut self, language: impl Into<SharedString>) -> Self {
9062        if let LayoutMode::CodeEditor {
9063            language: l,
9064            highlighter,
9065            ..
9066        } = &mut self.mode
9067        {
9068            l.set_name(language.into());
9069            *highlighter.borrow_mut() = None;
9070        }
9071        self
9072    }
9073
9074    /// The current language name, e.g. `"rust"`.
9075    pub fn language_name(&self) -> SharedString {
9076        match &self.mode {
9077            LayoutMode::CodeEditor { language, .. } => language.name(),
9078            _ => SharedString::default(),
9079        }
9080    }
9081
9082    /// Set enable/disable code folding.
9083    ///
9084    /// Default: true
9085    #[doc(hidden)]
9086    pub fn folding(mut self, folding: bool) -> Self {
9087        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
9088            *f = folding;
9089        }
9090        self
9091    }
9092
9093    /// Set code folding at runtime.
9094    ///
9095    /// When disabling, all existing folds are cleared.
9096    pub fn set_folding(&mut self, folding: bool, _: &mut Window, cx: &mut Context<Self>) {
9097        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
9098            *f = folding;
9099        }
9100        if !folding {
9101            self.display_map.clear_folds();
9102        }
9103        cx.notify();
9104    }
9105
9106    /// Unfold any folded ranges that hide the given position.
9107    ///
9108    /// Use this to reveal a position before acting on it (e.g. before
9109    /// [`Self::set_cursor_position`], which stops at a fold boundary),
9110    /// without touching folds elsewhere in the buffer. Fold candidates are
9111    /// kept, so the opened ranges can be folded again from the gutter.
9112    ///
9113    /// A fold keeps its own first and last line visible, so a position on
9114    /// either of them opens nothing. Nested folds all open, since opening
9115    /// only the outermost would leave the position hidden.
9116    ///
9117    /// Returns whether any fold was opened.
9118    pub fn unfold_at(&mut self, position: impl Into<Position>, cx: &mut Context<Self>) -> bool {
9119        let offset = self.text.position_to_offset(&position.into());
9120        let line = self.text.offset_to_point(offset).row;
9121        // A fold hides start_line + 1 ..= end_line - 1, so a line is hidden
9122        // exactly when some folded range strictly contains it.
9123        let covering: Vec<usize> = self
9124            .display_map
9125            .folded_ranges()
9126            .iter()
9127            .filter(|fold| line > fold.start_line && line < fold.end_line)
9128            .map(|fold| fold.start_line)
9129            .collect();
9130        if covering.is_empty() {
9131            return false;
9132        }
9133
9134        for start_line in covering {
9135            self.display_map.set_folded(start_line, false);
9136        }
9137        cx.notify();
9138        true
9139    }
9140
9141    /// Set enable/disable line number.
9142    #[doc(hidden)]
9143    pub fn line_number(mut self, line_number: bool) -> Self {
9144        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
9145            *l = line_number;
9146        }
9147        self
9148    }
9149
9150    /// Set line number.
9151    pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
9152        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
9153            *l = line_number;
9154        }
9155        cx.notify();
9156    }
9157
9158    /// Set enable/disable automatic closing brackets and quotes.
9159    ///
9160    /// When enabled, typing an opener from [`LanguageConfig::auto_closing_pairs`](crate::input::language_config::LanguageConfig::auto_closing_pairs) inserts the
9161    /// matching closer and places the cursor inside. Typing a closer that is
9162    /// already present just moves past it. Default: true
9163    #[doc(hidden)]
9164    pub fn auto_close(mut self, auto_close: bool) -> Self {
9165        self.mode.set_auto_close(auto_close);
9166        self
9167    }
9168
9169    /// Set automatic closing brackets and quotes at runtime.
9170    pub fn set_auto_close(&mut self, auto_close: bool, _: &mut Window, cx: &mut Context<Self>) {
9171        self.mode.set_auto_close(auto_close);
9172        cx.notify();
9173    }
9174
9175    /// Set enable/disable smart indent on Enter.
9176    ///
9177    /// When enabled, Enter uses structural brackets and
9178    /// [`LanguageConfig::indentation_rules`](crate::input::language_config::LanguageConfig::indentation_rules) to choose indentation.
9179    /// Default: true
9180    #[doc(hidden)]
9181    pub fn smart_indent(mut self, smart_indent: bool) -> Self {
9182        self.mode.set_smart_indent(smart_indent);
9183        self
9184    }
9185
9186    /// Set smart indent on Enter at runtime.
9187    pub fn set_smart_indent(&mut self, smart_indent: bool, _: &mut Window, cx: &mut Context<Self>) {
9188        self.mode.set_smart_indent(smart_indent);
9189        cx.notify();
9190    }
9191}