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