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, ScrollEvent, ScrollHandle, SharedString, StatefulInteractiveElement as _, Styled as _,
11    Subscription, UTF16Selection, WeakFocusHandle, Window, actions, div, point,
12    prelude::FluentBuilder as _, px,
13};
14use gpui::{Autocapitalize, TextInputAction, TextInputConfiguration};
15use ropey::{Rope, RopeSlice};
16use serde::Deserialize;
17use std::borrow::Cow;
18use std::cell::Cell;
19use std::ops::Range;
20use std::rc::Rc;
21use sum_tree::Bias;
22use unicode_segmentation::*;
23
24use super::{
25    DiagnosticSet, DisplayMap, InputContextMenuCapabilities, InputEditorStyle,
26    InputHighlighterFactory, MASK_CHAR, MaskPattern, NativeMenu, NumberStep, WrappingIndent,
27    blink_cursor::BlinkCursor,
28    change::Change,
29    cursor::{CursorSelection, Selections},
30    element::{
31        EditorScrollbar, EditorScrollbarSnapshot, LongestLineKey, TextElement,
32        clamp_horizontal_scroll_offset,
33    },
34    kind::InputModeKind,
35    mask_pattern::normalize_number_input,
36    mode::LayoutMode,
37    undo_manager::{EditIntent, UndoManager},
38};
39use crate::actions::{SelectDown, SelectLeft, SelectRight, SelectUp};
40use crate::input::blink_cursor::CURSOR_WIDTH;
41use crate::input::movement::MoveDirection;
42use crate::input::{
43    InputExtras as _, Position, RopeExt as _, element::RIGHT_MARGIN, layout::LastLayout,
44};
45use crate::overscroll::Overscroll;
46use crate::{AutoScroll, StepAction, TestSupportExt as _};
47
48pub(super) const EDITOR_ELEMENT_ID: &str = "input-state";
49
50/// Vertical clearance to retain when revealing a text position.
51pub(crate) enum ScrollPadding {
52    Minimal,
53    SurroundingLines,
54}
55
56#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
57#[action(namespace = input, no_json)]
58pub struct Enter {
59    /// Is confirm with secondary.
60    pub secondary: bool,
61    /// Whether the Shift modifier was held when Enter was pressed.
62    pub shift: bool,
63}
64
65impl Enter {
66    /// Returns true if `action` is a primary `Enter` action (`secondary: false`),
67    /// regardless of whether Shift was held.
68    pub fn is_primary(action: &dyn Action) -> bool {
69        action.partial_eq(&Enter {
70            secondary: false,
71            shift: false,
72        }) || action.partial_eq(&Enter {
73            secondary: false,
74            shift: true,
75        })
76    }
77}
78
79actions!(
80    input,
81    [
82        ActivateToken,
83        Backspace,
84        Delete,
85        DeleteToBeginningOfLine,
86        DeleteToEndOfLine,
87        DeleteToPreviousWordStart,
88        DeleteToNextWordEnd,
89        Indent,
90        Outdent,
91        IndentInline,
92        OutdentInline,
93        MoveUp,
94        MoveDown,
95        MoveLeft,
96        MoveRight,
97        MoveHome,
98        MoveEnd,
99        MovePageUp,
100        MovePageDown,
101        AddCursorAbove,
102        AddCursorBelow,
103        SelectAll,
104        SelectToStartOfLine,
105        SelectToEndOfLine,
106        SelectToStart,
107        SelectToEnd,
108        SelectToPreviousWordStart,
109        SelectToNextWordEnd,
110        ShowCharacterPalette,
111        Copy,
112        Cut,
113        Paste,
114        Undo,
115        Redo,
116        MoveToStartOfLine,
117        MoveToEndOfLine,
118        MoveToStart,
119        MoveToEnd,
120        MoveToPreviousWord,
121        MoveToNextWord,
122        Escape,
123        ToggleCodeActions,
124        ShowCompletions,
125        Search,
126        Replace,
127        GoToDefinition,
128    ]
129);
130
131#[derive(Clone)]
132pub enum InputEvent {
133    Change,
134    PressEnter { secondary: bool, shift: bool },
135    Focus,
136    Blur,
137}
138
139pub(super) const CONTEXT: &str = "Input";
140
141pub(crate) fn init(cx: &mut App) {
142    cx.bind_keys([
143        KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
144        KeyBinding::new("shift-backspace", Backspace, Some(CONTEXT)),
145        #[cfg(target_os = "macos")]
146        KeyBinding::new("ctrl-backspace", Backspace, Some(CONTEXT)),
147        KeyBinding::new("delete", Delete, Some(CONTEXT)),
148        KeyBinding::new("shift-delete", Delete, Some(CONTEXT)),
149        #[cfg(target_os = "macos")]
150        KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
151        #[cfg(target_os = "macos")]
152        KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
153        #[cfg(target_os = "macos")]
154        KeyBinding::new("alt-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
155        #[cfg(not(target_os = "macos"))]
156        KeyBinding::new("ctrl-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
157        #[cfg(target_os = "macos")]
158        KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)),
159        #[cfg(not(target_os = "macos"))]
160        KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)),
161        KeyBinding::new(
162            "enter",
163            Enter {
164                secondary: false,
165                shift: false,
166            },
167            Some(CONTEXT),
168        ),
169        KeyBinding::new(
170            "shift-enter",
171            Enter {
172                secondary: false,
173                shift: true,
174            },
175            Some(CONTEXT),
176        ),
177        KeyBinding::new(
178            if cx.platform_info().is_mac() {
179                "cmd-enter"
180            } else {
181                "ctrl-enter"
182            },
183            Enter {
184                secondary: true,
185                shift: false,
186            },
187            Some(CONTEXT),
188        ),
189        KeyBinding::new("escape", Escape, Some(CONTEXT)),
190        KeyBinding::new("up", MoveUp, Some(CONTEXT)),
191        KeyBinding::new("down", MoveDown, Some(CONTEXT)),
192        KeyBinding::new("left", MoveLeft, Some(CONTEXT)),
193        KeyBinding::new("right", MoveRight, Some(CONTEXT)),
194        KeyBinding::new("pageup", MovePageUp, Some(CONTEXT)),
195        KeyBinding::new("pagedown", MovePageDown, Some(CONTEXT)),
196        KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
197        KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
198        #[cfg(target_os = "macos")]
199        KeyBinding::new("cmd-]", Indent, Some(CONTEXT)),
200        #[cfg(not(target_os = "macos"))]
201        KeyBinding::new("ctrl-]", Indent, Some(CONTEXT)),
202        #[cfg(target_os = "macos")]
203        KeyBinding::new("cmd-[", Outdent, Some(CONTEXT)),
204        #[cfg(not(target_os = "macos"))]
205        KeyBinding::new("ctrl-[", Outdent, Some(CONTEXT)),
206        KeyBinding::new("shift-left", SelectLeft, Some(CONTEXT)),
207        KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
208        KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
209        KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
210        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
211        KeyBinding::new("shift-alt-left", SelectLeft, Some(CONTEXT)),
212        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
213        KeyBinding::new("shift-alt-right", SelectRight, Some(CONTEXT)),
214        // Avoid Ctrl+Alt+arrows on Linux, where desktops may reserve them.
215        #[cfg(target_os = "macos")]
216        KeyBinding::new("cmd-alt-up", AddCursorAbove, Some(CONTEXT)),
217        #[cfg(target_os = "macos")]
218        KeyBinding::new("cmd-alt-down", AddCursorBelow, Some(CONTEXT)),
219        #[cfg(target_os = "windows")]
220        KeyBinding::new("ctrl-alt-up", AddCursorAbove, Some(CONTEXT)),
221        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
222        KeyBinding::new("shift-alt-up", AddCursorAbove, Some(CONTEXT)),
223        #[cfg(target_os = "windows")]
224        KeyBinding::new("ctrl-alt-down", AddCursorBelow, Some(CONTEXT)),
225        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
226        KeyBinding::new("shift-alt-down", AddCursorBelow, Some(CONTEXT)),
227        KeyBinding::new("home", MoveHome, Some(CONTEXT)),
228        KeyBinding::new("end", MoveEnd, Some(CONTEXT)),
229        #[cfg(not(target_os = "macos"))]
230        KeyBinding::new("ctrl-home", MoveToStart, Some(CONTEXT)),
231        #[cfg(not(target_os = "macos"))]
232        KeyBinding::new("ctrl-end", MoveToEnd, Some(CONTEXT)),
233        #[cfg(not(target_os = "macos"))]
234        KeyBinding::new("ctrl-shift-home", SelectToStart, Some(CONTEXT)),
235        #[cfg(not(target_os = "macos"))]
236        KeyBinding::new("ctrl-shift-end", SelectToEnd, Some(CONTEXT)),
237        KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
238        KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
239        #[cfg(target_os = "macos")]
240        KeyBinding::new("ctrl-shift-a", SelectToStartOfLine, Some(CONTEXT)),
241        #[cfg(target_os = "macos")]
242        KeyBinding::new("ctrl-shift-e", SelectToEndOfLine, Some(CONTEXT)),
243        #[cfg(target_os = "macos")]
244        KeyBinding::new("shift-cmd-left", SelectToStartOfLine, Some(CONTEXT)),
245        #[cfg(target_os = "macos")]
246        KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
247        #[cfg(any(target_os = "macos", target_os = "linux"))]
248        KeyBinding::new("alt-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
249        #[cfg(not(target_os = "macos"))]
250        KeyBinding::new("ctrl-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
251        #[cfg(any(target_os = "macos", target_os = "linux"))]
252        KeyBinding::new("alt-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
253        #[cfg(not(target_os = "macos"))]
254        KeyBinding::new("ctrl-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
255        #[cfg(target_os = "macos")]
256        KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
257        #[cfg(target_os = "macos")]
258        KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
259        #[cfg(not(target_os = "macos"))]
260        KeyBinding::new("ctrl-a", SelectAll, Some(CONTEXT)),
261        #[cfg(target_os = "macos")]
262        KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
263        #[cfg(not(target_os = "macos"))]
264        KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
265        #[cfg(target_os = "macos")]
266        KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
267        #[cfg(not(target_os = "macos"))]
268        KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
269        #[cfg(target_os = "macos")]
270        KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
271        #[cfg(not(target_os = "macos"))]
272        KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
273        #[cfg(target_os = "macos")]
274        KeyBinding::new("ctrl-a", MoveHome, Some(CONTEXT)),
275        #[cfg(target_os = "macos")]
276        KeyBinding::new("cmd-left", MoveHome, Some(CONTEXT)),
277        #[cfg(target_os = "macos")]
278        KeyBinding::new("ctrl-e", MoveEnd, Some(CONTEXT)),
279        #[cfg(target_os = "macos")]
280        KeyBinding::new("cmd-right", MoveEnd, Some(CONTEXT)),
281        #[cfg(target_os = "macos")]
282        KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
283        #[cfg(target_os = "macos")]
284        KeyBinding::new("cmd-shift-z", Redo, Some(CONTEXT)),
285        #[cfg(target_os = "macos")]
286        KeyBinding::new("cmd-up", MoveToStart, Some(CONTEXT)),
287        #[cfg(target_os = "macos")]
288        KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
289        #[cfg(target_os = "macos")]
290        KeyBinding::new("alt-left", MoveToPreviousWord, Some(CONTEXT)),
291        #[cfg(target_os = "macos")]
292        KeyBinding::new("alt-right", MoveToNextWord, Some(CONTEXT)),
293        #[cfg(not(target_os = "macos"))]
294        KeyBinding::new("ctrl-left", MoveToPreviousWord, Some(CONTEXT)),
295        #[cfg(not(target_os = "macos"))]
296        KeyBinding::new("ctrl-right", MoveToNextWord, Some(CONTEXT)),
297        #[cfg(target_os = "macos")]
298        KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
299        #[cfg(target_os = "macos")]
300        KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
301        #[cfg(not(target_os = "macos"))]
302        KeyBinding::new("ctrl-z", Undo, Some(CONTEXT)),
303        #[cfg(not(target_os = "macos"))]
304        KeyBinding::new("ctrl-y", Redo, Some(CONTEXT)),
305        #[cfg(target_os = "macos")]
306        KeyBinding::new("cmd-.", ToggleCodeActions, Some(CONTEXT)),
307        #[cfg(not(target_os = "macos"))]
308        KeyBinding::new("ctrl-.", ToggleCodeActions, Some(CONTEXT)),
309        #[cfg(target_os = "macos")]
310        KeyBinding::new("cmd-f", Search, Some(CONTEXT)),
311        #[cfg(not(target_os = "macos"))]
312        KeyBinding::new("ctrl-f", Search, Some(CONTEXT)),
313        #[cfg(target_os = "macos")]
314        KeyBinding::new("cmd-shift-f", Replace, Some(CONTEXT)),
315        #[cfg(not(target_os = "macos"))]
316        KeyBinding::new("ctrl-h", Replace, Some(CONTEXT)),
317    ]);
318}
319
320/// A mouse position resolved for a columnar (block) selection.
321///
322/// A position past the end of a short row has no glyph to land on, so `offset` clips to
323/// that row's end and `columns_past_line_end` keeps what the clip dropped. Together they
324/// say which column the pointer really reached, which is what a block spanning rows of
325/// different lengths has to be measured in.
326#[derive(Debug, Clone, Copy, PartialEq, Eq)]
327pub(super) struct ColumnarPoint {
328    offset: usize,
329    columns_past_line_end: usize,
330}
331
332impl ColumnarPoint {
333    pub(super) fn new(offset: usize, columns_past_line_end: usize) -> Self {
334        Self {
335            offset,
336            columns_past_line_end,
337        }
338    }
339}
340
341/// The document and selections a paste was asked to replace, taken when the
342/// `Paste` action ran so a clipboard read that resolves later can tell
343/// whether it still applies. See [`InputBaseState::paste_target`].
344#[derive(Debug, PartialEq)]
345struct PasteTarget {
346    document_revision: u64,
347    selections: Vec<CursorSelection>,
348}
349
350/// Caret positioning geometry from the input's latest prepaint.
351///
352/// Unlike `last_layout` (published at paint time), this is stashed during
353/// prepaint so caret-anchored popups can position themselves from the
354/// current frame's geometry in their deferred prepaint, instead of the
355/// previous frame's paint.
356#[derive(Clone, Copy, Debug)]
357#[doc(hidden)]
358pub struct PrepaintCaretGeometry {
359    cursor_bounds: Bounds<Pixels>,
360    line_height: Pixels,
361    input_bounds: Bounds<Pixels>,
362    scroll_offset: Point<Pixels>,
363    /// A resize owns visibility while its requested repaint settles.
364    pub(super) resize_reveal_owned: bool,
365}
366
367impl PrepaintCaretGeometry {
368    pub(super) fn new(
369        cursor_bounds: Bounds<Pixels>,
370        line_height: Pixels,
371        input_bounds: Bounds<Pixels>,
372        scroll_offset: Point<Pixels>,
373    ) -> Self {
374        Self {
375            cursor_bounds,
376            line_height,
377            input_bounds,
378            scroll_offset,
379            resize_reveal_owned: false,
380        }
381    }
382
383    /// The active caret's bounds from the fresh prepaint layout.
384    pub fn cursor_bounds(&self) -> Bounds<Pixels> {
385        self.cursor_bounds
386    }
387
388    /// Line height from the fresh prepaint layout.
389    pub fn line_height(&self) -> Pixels {
390        self.line_height
391    }
392
393    /// The input container bounds from the fresh prepaint.
394    pub fn input_bounds(&self) -> Bounds<Pixels> {
395        self.input_bounds
396    }
397
398    /// The scroll offset that will be in effect after paint, clamped like
399    /// `InputBaseState::update_scroll_offset` clamps it.
400    pub fn scroll_offset(&self) -> Point<Pixels> {
401        self.scroll_offset
402    }
403}
404
405/// The shared text-editing engine behind [`crate::input::InputState`],
406/// [`crate::input::TextareaState`] and [`crate::input::EditorState`].
407///
408/// `M` is the mode marker: it carries no data and only decides which methods
409/// exist, so an ordinary input cannot reach the editor's language features.
410///
411/// The three states are type aliases of this one, which is why this name is
412/// public: an alias is only as usable as the type behind it, so hiding this
413/// would leave `InputState` unable to do anything. Prefer naming the aliases
414/// — write `InputState`, not `InputBaseState<InputMode>`.
415pub struct InputBaseState<M: InputModeKind> {
416    /// State only this mode needs. See [`InputModeKind::Extras`].
417    pub(crate) extras: M::Extras,
418    pub(super) focus_handle: FocusHandle,
419    selection_focus: Option<WeakFocusHandle>,
420    pub(super) mode: LayoutMode,
421    pub(super) text: Rope,
422    pub(super) display_map: DisplayMap,
423    pub(super) undo_manager: UndoManager,
424    pub(super) inline_tokens: Option<Box<super::inline_tokens::InlineTokenStore>>,
425    pub(super) pending_token: Option<super::InlineToken>,
426    pub(super) replaying_history: bool,
427    pub(super) validated_token_edit: bool,
428    pub(super) document_revision: u64,
429    pub(super) token_presentation: super::InlineTokenPresentation,
430    pub(super) token_layout_cache: Option<Box<super::token_presentation::TokenLayoutCache>>,
431    /// Real per-frame bounds of the placed token elements, keyed by token
432    /// start offset; used for hover payloads and stale-hover reconciliation.
433    pub(super) token_bounds: std::collections::HashMap<usize, Bounds<Pixels>>,
434    /// The currently hovered token: its entered span, last measured bounds,
435    /// and UTF-16 range as of entry. Retained so hover exit can still be
436    /// delivered when the token is removed, replaced, scrolled out, or
437    /// disabled.
438    pub(super) hovered_token: Option<super::token_presentation::HoverSnapshot>,
439    pub(super) pending_token_hover_exits: Vec<super::token_presentation::HoverSnapshot>,
440    /// The start offset of a pressed token, with the document revision and
441    /// pointer position at the press.
442    pub(super) pressed_token: Option<(usize, u64, Point<Pixels>)>,
443    pub(super) search_session: super::SearchSession,
444    /// Advances every time search is explicitly invoked. See
445    /// [`InputBaseState::search_activation_revision`].
446    pub(super) search_activation_revision: u64,
447    pub(super) searchable: bool,
448    pub(super) replaceable: bool,
449    pub(super) soft_wrap: bool,
450    pub(super) wrapping_indent: WrappingIndent,
451    pub(super) scroll_beyond_last_line: Option<usize>,
452    pub(super) cursor_surrounding_lines: Option<usize>,
453    pub(super) blink_cursor: Entity<BlinkCursor>,
454    pub(super) loading: bool,
455    /// The cursors and selections.
456    ///
457    /// Always contains at least one selection where index 0 is the active cursor.
458    pub(super) selections: Selections,
459    /// Range for save the selected word, use to keep word range when drag move.
460    pub(super) selected_word_range: Option<CursorSelection>,
461    /// The marked range is the temporary insert text on IME typing.
462    pub(super) ime_marked_range: Option<CursorSelection>,
463    pub(super) last_layout: Option<LastLayout>,
464    /// Fresh caret geometry stashed during prepaint for caret-anchored popups.
465    ///
466    /// Popups are positioned in their deferred prepaint, which runs after the
467    /// input's prepaint but before its paint, so they read this instead of
468    /// the previous frame's `last_layout`.
469    pub(crate) prepaint_caret_geometry: Option<PrepaintCaretGeometry>,
470    pub(super) last_cursor: Option<usize>,
471    /// The input container bounds
472    pub(super) input_bounds: Bounds<Pixels>,
473    /// The text bounds
474    pub(super) last_bounds: Option<Bounds<Pixels>>,
475    /// The fully visible part of the window this input last asked its scroll
476    /// containers to reveal it in, while focused. `None` while unfocused.
477    pub(super) revealed_viewport: Option<Bounds<Pixels>>,
478    pub(super) last_selected_range: Option<CursorSelection>,
479    pub(super) selecting: bool,
480    /// Anchor point of an in-progress columnar (block) selection.
481    pub(super) column_select_start: Option<ColumnarPoint>,
482    /// The selection a long press made, with its handles and edit menu.
483    pub(super) touch_selection: super::touch::TouchSelection,
484    pub(crate) disabled: bool,
485    pub(crate) readonly: bool,
486    pub(crate) text_align: TextAlign,
487    pub(super) masked: bool,
488    pub(super) clean_on_escape: bool,
489    pub(super) submit_on_enter: bool,
490    /// Overrides for [`Self::text_input_configuration`].
491    pub(super) input_action: Option<TextInputAction>,
492    pub(super) autocorrect: Option<bool>,
493    pub(super) autocapitalize: Option<Autocapitalize>,
494    pub(super) show_whitespaces: bool,
495    /// This flag tells the renderer to prefer the end of the current visual line.
496    pub(crate) cursor_line_end_affinity: bool,
497    pub(super) pattern: Option<regex::Regex>,
498    pub(super) validate: Option<Box<dyn Fn(&str, &mut App) -> bool + 'static>>,
499    /// The step strategy for [`super::NumberInput`] to increment/decrement.
500    /// See [`Self::step`] and [`Self::step_by`].
501    pub(crate) number_step: Option<NumberStep>,
502    /// The minimum value for [`super::NumberInput`]. See [`Self::min`].
503    pub(crate) number_min: Option<f64>,
504    /// The maximum value for [`super::NumberInput`]. See [`Self::max`].
505    pub(crate) number_max: Option<f64>,
506    pub(crate) scroll_handle: ScrollHandle,
507    /// Whether the text stretches past its ends, see [`Self::scroll_bounce`].
508    scroll_bounce: bool,
509    /// The text's stretch past an end, while it lasts.
510    pub(super) overscroll: Overscroll,
511    /// The deferred scroll offset to apply on next layout.
512    pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,
513    /// The size of the scrollable content.
514    pub(crate) scroll_size: gpui::Size<Pixels>,
515    pub(super) editor_scrollbar_snapshot: Cell<Option<EditorScrollbarSnapshot>>,
516    /// The widest measured unwrapped line width and what it was measured for.
517    pub(super) longest_line_width: Cell<Option<(LongestLineKey, Pixels)>>,
518    pub(super) editor_paddings: Edges<Pixels>,
519    /// The style this state paints with: what was projected onto it, with
520    /// every colour left unset resolved from the palette that is current. It
521    /// is rebuilt at the top of every render, which is what keeps it current
522    /// when the palette changes after the state was built.
523    pub(super) editor_style: InputEditorStyle,
524    /// What a consumer projected, kept verbatim so that resolution never
525    /// consumes its own output: resolving in place would fill the unset
526    /// colours once and then never see them as unset again, which is the same
527    /// freeze in a different place.
528    projected_editor_style: InputEditorStyle,
529    accessibility: Option<InputAccessibility>,
530    /// What paints in place of Base's editor scrollbar, when a consumer
531    /// supplies its own.
532    pub(super) scrollbar_renderer: Option<crate::input::EditorScrollbarRenderer>,
533
534    /// The mask pattern for formatting the input text
535    pub(crate) mask_pattern: MaskPattern,
536    /// Whether the `mask_pattern` was explicitly set (via [`Self::mask_pattern`]
537    /// or [`Self::set_mask_pattern`]), to let [`super::NumberInput`] only apply
538    /// its default mask when the user has not made an explicit choice.
539    pub(super) mask_pattern_set: bool,
540    pub(super) placeholder: SharedString,
541
542    /// Diagnostic currently requested by pointer hover; applications render it.
543    pub(super) diagnostic_popover: Option<Rc<crate::input::DiagnosticEntry>>,
544
545    context_menu_handler: Option<
546        Rc<dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App)>,
547    >,
548    pending_context_menu: Option<(Point<Pixels>, usize)>,
549
550    /// Whether the context menu that shows on right-click is enabled.
551    ///
552    pub(super) enable_context_menu: bool,
553
554    /// A flag to indicate if we are currently inserting a completion item.
555    pub(super) completion_inserting: bool,
556    pub(super) overlay_action_handler: Option<
557        Rc<
558            dyn Fn(
559                super::InputOverlayKind,
560                Box<dyn Action>,
561                &mut Window,
562                &mut Context<InputBaseState<M>>,
563            ) -> bool,
564        >,
565    >,
566
567    /// A flag to indicate if we have a pending update to the text.
568    ///
569    /// If true, will call some update (for example LSP, Syntax Highlight) before render.
570    _pending_update: bool,
571    /// A flag to indicate if we should ignore the next completion event.
572    pub(super) silent_replace_text: bool,
573    /// A flag to indicate if we should emit InputEvents.
574    pub(super) emit_events: bool,
575
576    _subscriptions: Vec<Subscription>,
577
578    pub(super) auto_scroll: AutoScroll,
579}
580
581/// Accessibility presentation for the editor's existing keyboard-focus boundary.
582///
583/// Frames and their addon controls keep their own identities. This projection
584/// does not create a focus handle or register another text Tab stop. The engine
585/// supplies the live value and guards accessibility actions against its current
586/// disabled, read-only and masked state.
587#[derive(Clone, Debug)]
588pub struct InputAccessibility {
589    role: Option<gpui::Role>,
590    label: Option<SharedString>,
591    placeholder: Option<SharedString>,
592    accessibility_id: Option<SharedString>,
593    value_exposed: bool,
594}
595
596impl InputAccessibility {
597    pub fn new(role: Option<gpui::Role>) -> Self {
598        Self {
599            role,
600            label: None,
601            placeholder: None,
602            accessibility_id: None,
603            value_exposed: true,
604        }
605    }
606
607    pub fn with_label(mut self, label: Option<SharedString>) -> Self {
608        self.label = label;
609        self
610    }
611
612    pub fn with_placeholder(mut self, placeholder: Option<SharedString>) -> Self {
613        self.placeholder = placeholder;
614        self
615    }
616
617    pub fn with_accessibility_id(mut self, id: Option<SharedString>) -> Self {
618        self.accessibility_id = id;
619        self
620    }
621
622    /// Suppress values for secret content even when the mask is revealed.
623    pub fn with_value_exposed(mut self, exposed: bool) -> Self {
624        self.value_exposed = exposed;
625        self
626    }
627
628    pub fn role(&self) -> Option<gpui::Role> {
629        self.role
630    }
631
632    pub fn label(&self) -> Option<&SharedString> {
633        self.label.as_ref()
634    }
635
636    pub fn placeholder(&self) -> Option<&SharedString> {
637        self.placeholder.as_ref()
638    }
639
640    pub fn accessibility_id(&self) -> Option<&SharedString> {
641        self.accessibility_id.as_ref()
642    }
643
644    pub fn value_exposed(&self) -> bool {
645        self.value_exposed
646    }
647}
648
649/// Read-only styling data exposed to presentation facades.
650///
651/// The fields are private and read through the methods below, so that a new
652/// one can be added without breaking the facades.
653#[derive(Clone)]
654pub struct InputPresentation {
655    focus_handle: FocusHandle,
656    disabled: bool,
657    readonly: bool,
658    loading: bool,
659    masked: bool,
660    multi_line: bool,
661    code_editor: bool,
662    text_align: TextAlign,
663    placeholder: SharedString,
664    mask_placeholder: Option<String>,
665}
666
667impl InputPresentation {
668    pub fn focus_handle(&self) -> &FocusHandle {
669        &self.focus_handle
670    }
671
672    pub fn is_disabled(&self) -> bool {
673        self.disabled
674    }
675
676    pub fn is_readonly(&self) -> bool {
677        self.readonly
678    }
679
680    /// Returns true if the user is allowed to change the text.
681    ///
682    /// See also: [`InputBaseState::is_editable`].
683    pub fn is_editable(&self) -> bool {
684        !self.disabled && !self.readonly
685    }
686
687    pub fn is_loading(&self) -> bool {
688        self.loading
689    }
690
691    pub fn is_masked(&self) -> bool {
692        self.masked
693    }
694
695    pub fn is_multi_line(&self) -> bool {
696        self.multi_line
697    }
698
699    pub fn is_code_editor(&self) -> bool {
700        self.code_editor
701    }
702
703    pub fn text_align(&self) -> TextAlign {
704        self.text_align
705    }
706
707    pub fn placeholder(&self) -> &SharedString {
708        &self.placeholder
709    }
710
711    /// The placeholder derived from the mask pattern, e.g.: `(___) ___-____`.
712    pub fn mask_placeholder(&self) -> Option<&str> {
713        self.mask_placeholder.as_deref()
714    }
715}
716
717impl<M: InputModeKind> EventEmitter<InputEvent> for InputBaseState<M> {}
718
719impl<M: InputModeKind> InputBaseState<M> {
720    #[doc(hidden)]
721    pub fn cursor_layout(&self) -> Option<(Bounds<Pixels>, Pixels)> {
722        let layout = self.last_layout.as_ref()?;
723        Some((layout.cursor_bounds?, layout.line_height))
724    }
725
726    /// Caret geometry from the latest prepaint, for caret-anchored popups.
727    ///
728    /// Popups positioned in their deferred prepaint read this to anchor to
729    /// the current frame's caret instead of the previous frame's paint.
730    #[doc(hidden)]
731    pub fn prepaint_caret_geometry(&self) -> Option<PrepaintCaretGeometry> {
732        self.prepaint_caret_geometry
733    }
734
735    pub fn input_bounds(&self) -> Bounds<Pixels> {
736        self.input_bounds
737    }
738
739    pub fn text_bounds(&self) -> Option<Bounds<Pixels>> {
740        self.last_bounds
741    }
742
743    /// The cached display map for querying wrapped rows and display positions.
744    ///
745    /// Reading this does not prepare text or force layout. Before the first
746    /// layout, [`DisplayMap::wrap_row_count`] can be zero; prepared empty text
747    /// has one row. The count describes content, independent of minimum rows,
748    /// auto-grow limits and viewport height.
749    ///
750    /// Text edits use the cached wrapping parameters. Layout refreshes the
751    /// cache after width or font changes; consume that count on a subsequent
752    /// render, or use [`Self::prewrap`] to prepare it before the first layout.
753    pub fn display_map(&self) -> &DisplayMap {
754        &self.display_map
755    }
756
757    /// Wrap the text for `font`, `font_size` and `width` before the first
758    /// layout, so that [`DisplayMap::wrap_row_count`] is known on the first frame.
759    ///
760    /// Call it after setting the value and the wrapping indent. `width` is the
761    /// width of the input's text area: its width less its padding, and less
762    /// the line-number gutter for an editor with line numbers. It wraps as
763    /// layout does, a little short of that width and with the state's wrapping
764    /// indent. Once the input has been laid out this does nothing, and layout
765    /// keeps the wrap up to date. The font and font size must match the input's
766    /// actual text style.
767    pub fn prewrap(
768        &mut self,
769        font: gpui::Font,
770        font_size: Pixels,
771        width: Pixels,
772        cx: &mut Context<Self>,
773    ) {
774        if self.last_layout.is_some() {
775            return;
776        }
777        let wrap_width = (width - RIGHT_MARGIN).max(px(1.));
778        self.display_map.set_font(font, font_size, cx);
779        self.display_map
780            .set_tab_size(self.mode.tab_size().tab_size, cx);
781        self.display_map
782            .on_layout_changed(self.soft_wrap.then_some(wrap_width), cx);
783        self.display_map
784            .set_wrapping_indent(self.wrapping_indent, cx);
785        self.display_map.ensure_text_prepared(&self.text, cx);
786    }
787
788    pub fn diagnostic_popover(&self) -> Option<Rc<crate::input::DiagnosticEntry>> {
789        self.diagnostic_popover.clone()
790    }
791
792    pub fn presentation(&self) -> InputPresentation {
793        InputPresentation {
794            focus_handle: self.focus_handle.clone(),
795            disabled: self.disabled,
796            readonly: self.readonly,
797            loading: self.loading,
798            masked: self.masked,
799            multi_line: self.is_multi_line(),
800            code_editor: self.is_code_editor(),
801            text_align: self.text_align,
802            placeholder: self.placeholder.clone(),
803            mask_placeholder: self.mask_pattern.placeholder(),
804        }
805    }
806
807    /// Whether this input spans more than one line.
808    ///
809    /// Answered by the mode marker, which is fixed when the state is built.
810    /// [`LayoutMode`] holds the row counts and growth policy, not the kind.
811    #[inline]
812    /// Whether this input paints scrollbars.
813    ///
814    /// Only a multi-line input can scroll: a single-line input keeps its
815    /// caret in view by moving its own offset, and never has a viewport a
816    /// user could drag. Adding the editor scrollbar to every input put a
817    /// thumb inside every text field, which is a control the field does not
818    /// have.
819    pub(crate) fn shows_scrollbar(&self) -> bool {
820        self.is_multi_line()
821    }
822
823    pub fn is_multi_line(&self) -> bool {
824        M::MULTI_LINE
825    }
826
827    /// Whether this input is a single-line text field. See [`Self::is_multi_line`].
828    #[inline]
829    pub fn is_single_line(&self) -> bool {
830        !M::MULTI_LINE
831    }
832
833    /// Whether this input is a source-code editor.
834    #[inline]
835    pub fn is_code_editor(&self) -> bool {
836        M::CODE_EDITOR
837    }
838
839    /// Whether the user is allowed to copy the selection out.
840    ///
841    /// A masked input keeps its value out of the clipboard.
842    pub fn is_copyable(&self) -> bool {
843        self.selections.iter().any(|sel| !sel.is_empty()) && !self.masked
844    }
845
846    pub fn context_menu_capabilities(&self) -> InputContextMenuCapabilities {
847        let (go_to_definition, code_actions) = self.extras.context_menu_capabilities();
848        InputContextMenuCapabilities::new()
849            .disabled(self.disabled)
850            .readonly(self.readonly)
851            .code_editor(self.is_code_editor())
852            .selection(!self.active_selection().is_empty())
853            .masked(self.masked)
854            .go_to_definition(go_to_definition)
855            .code_actions(code_actions)
856    }
857
858    pub fn set_text_align(&mut self, text_align: TextAlign, cx: &mut Context<Self>) {
859        if !self.is_single_line() || self.text_align == text_align {
860            return;
861        }
862
863        self.text_align = text_align;
864        cx.notify();
865    }
866
867    /// Flip the password mask.
868    ///
869    /// Setting the mask is a single-line method, but flipping it stays here:
870    /// the reveal button is rendered from the generic path, and it can only be
871    /// switched on through [`crate::input::InputState`] anyway.
872    pub fn toggle_masked(&mut self, _: &mut Window, cx: &mut Context<Self>) {
873        self.masked = !self.masked;
874        cx.notify();
875    }
876
877    pub fn on_context_menu(
878        &mut self,
879        handler: Rc<
880            dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App),
881        >,
882    ) {
883        self.context_menu_handler = Some(handler);
884    }
885
886    /// Keeps the selection highlighted while this popup or one of its children has focus.
887    ///
888    /// This does not transfer keyboard focus or keep the caret visible. The
889    /// association holds only a weak focus handle. Pass `None` to clear it.
890    pub fn set_selection_focus(&mut self, focus: Option<FocusHandle>, cx: &mut Context<Self>) {
891        self.selection_focus = focus.as_ref().map(FocusHandle::downgrade);
892        cx.notify();
893    }
894
895    /// Whether the input or its associated selection popup has focus.
896    pub fn has_selection_focus(&self, window: &Window, cx: &App) -> bool {
897        self.focus_handle.is_focused(window)
898            || self
899                .selection_focus
900                .as_ref()
901                .and_then(WeakFocusHandle::upgrade)
902                .is_some_and(|focus| focus.is_focused(window) || focus.contains_focused(window, cx))
903    }
904
905    /// Build the engine. Each mode's own `new` sets its layout on top of this.
906    fn new_in_mode(window: &mut Window, cx: &mut Context<Self>) -> Self {
907        let focus_handle = cx.focus_handle().tab_stop(true);
908        let blink_cursor = cx.new(|_| BlinkCursor::new());
909        let undo_manager = UndoManager::new();
910
911        let _subscriptions = vec![
912            // Key bindings can consume events before on_key_down. Observe input
913            // before action dispatch so every keystroke resets the blink delay.
914            cx.intercept_keystrokes({
915                let focus_handle = focus_handle.clone();
916                let blink_cursor = blink_cursor.downgrade();
917                move |_, window, cx| {
918                    if focus_handle.is_focused(window) {
919                        _ = blink_cursor.update(cx, |cursor, cx| cursor.pause(cx));
920                    }
921                }
922            }),
923            // Observe the blink cursor to repaint the view when it changes.
924            cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
925            // Blink the cursor when the window is active, pause when it's not.
926            cx.observe_window_activation(window, |input, window, cx| {
927                if window.is_window_active() {
928                    let focus_handle = input.focus_handle.clone();
929                    if focus_handle.is_focused(window) {
930                        input.blink_cursor.update(cx, |blink_cursor, cx| {
931                            blink_cursor.start(cx);
932                        });
933                    }
934                }
935            }),
936            cx.on_focus(&focus_handle, window, Self::on_focus),
937            cx.on_blur(&focus_handle, window, Self::on_blur),
938        ];
939
940        let text_style = window.text_style();
941
942        Self {
943            extras: M::Extras::default(),
944            focus_handle: focus_handle.clone(),
945            selection_focus: None,
946            text: "".into(),
947            display_map: DisplayMap::new(text_style.font(), window.rem_size(), None),
948            search_session: super::SearchSession::default(),
949            search_activation_revision: 0,
950            searchable: false,
951            replaceable: true,
952            soft_wrap: true,
953            wrapping_indent: WrappingIndent::default(),
954            scroll_beyond_last_line: None,
955            cursor_surrounding_lines: None,
956            blink_cursor,
957            undo_manager,
958            inline_tokens: None,
959            pending_token: None,
960            replaying_history: false,
961            validated_token_edit: false,
962            document_revision: 0,
963            token_presentation: Default::default(),
964            token_bounds: Default::default(),
965            hovered_token: None,
966            pending_token_hover_exits: Vec::new(),
967            token_layout_cache: None,
968            pressed_token: None,
969            selections: Selections::default(),
970            selected_word_range: None,
971            ime_marked_range: None,
972            input_bounds: Bounds::default(),
973            selecting: false,
974            touch_selection: Default::default(),
975            disabled: false,
976            readonly: false,
977            text_align: TextAlign::Left,
978            masked: false,
979            clean_on_escape: false,
980            submit_on_enter: false,
981            input_action: None,
982            autocorrect: None,
983            autocapitalize: None,
984            show_whitespaces: false,
985            loading: false,
986            pattern: None,
987            validate: None,
988            number_step: Some(NumberStep::Fixed(1.)),
989            number_min: None,
990            number_max: None,
991            mode: LayoutMode::default(),
992            last_layout: None,
993            prepaint_caret_geometry: None,
994            last_bounds: None,
995            revealed_viewport: None,
996            last_selected_range: None,
997            column_select_start: None,
998            last_cursor: None,
999            scroll_handle: ScrollHandle::new(),
1000            scroll_bounce: crate::ScrollBounce::enabled_by_default(),
1001            overscroll: Overscroll::default(),
1002            scroll_size: gpui::size(px(0.), px(0.)),
1003            editor_scrollbar_snapshot: Cell::new(None),
1004            longest_line_width: Cell::new(None),
1005            editor_paddings: Edges::default(),
1006            deferred_scroll_offset: None,
1007            placeholder: SharedString::default(),
1008            mask_pattern: MaskPattern::default(),
1009            mask_pattern_set: false,
1010            editor_style: InputEditorStyle::default(),
1011            projected_editor_style: InputEditorStyle::default(),
1012            accessibility: None,
1013            scrollbar_renderer: None,
1014            diagnostic_popover: None,
1015            context_menu_handler: None,
1016            pending_context_menu: None,
1017            enable_context_menu: true,
1018            completion_inserting: false,
1019            overlay_action_handler: None,
1020            silent_replace_text: false,
1021            emit_events: true,
1022            _subscriptions,
1023            _pending_update: false,
1024            cursor_line_end_affinity: false,
1025            auto_scroll: AutoScroll::default(),
1026        }
1027    }
1028
1029    /// Sets whether the context menu that shows on right-click is enabled.
1030    ///
1031    /// The context menu is enabled by default. Disabling it turns off the
1032    /// right-click menu entirely, including a custom menu set on the element.
1033    /// A custom menu already replaces the built-in one, so keep this enabled
1034    /// to show only your own items.
1035    pub fn context_menu(mut self, enable: bool) -> Self {
1036        self.enable_context_menu = enable;
1037        self
1038    }
1039
1040    pub fn set_context_menu_enabled(&mut self, enabled: bool) {
1041        self.enable_context_menu = enabled;
1042    }
1043
1044    /// Set whether search UI allows replacement, default is true.
1045    #[doc(hidden)]
1046    pub fn replaceable(mut self, allow: bool) -> Self {
1047        self.replaceable = allow;
1048        self
1049    }
1050
1051    /// Set placeholder
1052    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1053        self.placeholder = placeholder.into();
1054        self
1055    }
1056
1057    /// Set highlighter language for for [`LayoutMode::CodeEditor`] mode.
1058    pub fn set_highlighter(
1059        &mut self,
1060        new_language: impl Into<SharedString>,
1061        cx: &mut Context<Self>,
1062    ) {
1063        match &mut self.mode {
1064            LayoutMode::CodeEditor {
1065                language,
1066                highlighter,
1067                ..
1068            } => {
1069                language.set_name(new_language.into());
1070                *highlighter.borrow_mut() = None;
1071            }
1072            _ => {}
1073        }
1074        self.refresh(cx);
1075    }
1076
1077    fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
1078        match &mut self.mode {
1079            LayoutMode::CodeEditor { highlighter, .. } => {
1080                *highlighter.borrow_mut() = None;
1081            }
1082            _ => {}
1083        }
1084        cx.notify();
1085    }
1086
1087    /// Install the parser/highlighter adapter used by code-editor mode.
1088    pub fn set_highlighter_factory(
1089        &mut self,
1090        factory: InputHighlighterFactory,
1091        cx: &mut Context<Self>,
1092    ) {
1093        self.mode.set_highlighter_factory(factory);
1094        self._pending_update = true;
1095        self.token_layout_cache = None;
1096        cx.notify();
1097    }
1098
1099    /// Install a default adapter without replacing an application-provided one.
1100    pub fn ensure_highlighter_factory(&mut self, factory: InputHighlighterFactory) {
1101        self.mode.ensure_highlighter_factory(factory);
1102    }
1103
1104    pub fn set_editor_style(&mut self, style: InputEditorStyle) {
1105        self.editor_style = style.clone();
1106        self.projected_editor_style = style;
1107    }
1108
1109    /// Project semantics onto the editor, never onto a second focus owner.
1110    pub fn set_accessibility(&mut self, accessibility: InputAccessibility) {
1111        self.accessibility = Some(accessibility);
1112    }
1113
1114    fn handle_accessibility_focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1115        if !self.disabled {
1116            self.focus(window, cx);
1117        }
1118    }
1119
1120    fn handle_accessibility_set_value(
1121        &mut self,
1122        data: Option<&gpui::accesskit::ActionData>,
1123        window: &mut Window,
1124        cx: &mut Context<Self>,
1125    ) {
1126        let Some(gpui::accesskit::ActionData::Value(value)) = data else {
1127            return;
1128        };
1129        // The action may have been queued before editability changed.
1130        if self.is_editable() {
1131            self.replace_all(value.to_string(), window, cx);
1132        }
1133    }
1134
1135    /// Set presentation padding for multi-line text and its scrollbar layout.
1136    #[doc(hidden)]
1137    pub fn set_editor_paddings(&mut self, paddings: Edges<Pixels>) {
1138        self.editor_paddings = paddings;
1139    }
1140
1141    /// Supply the element painted in place of the editor's scrollbar, or
1142    /// `None` to keep Base's. The renderer is called where the editor paints
1143    /// its scrollbar, with that frame's [`EditorScrollbarContext`]. Like
1144    /// [`Self::set_editor_style`], this does not notify, so it can be called
1145    /// while rendering.
1146    ///
1147    /// [`EditorScrollbarContext`]: crate::input::EditorScrollbarContext
1148    pub fn set_scrollbar_renderer(
1149        &mut self,
1150        renderer: Option<crate::input::EditorScrollbarRenderer>,
1151    ) {
1152        self.scrollbar_renderer = renderer;
1153    }
1154
1155    pub fn apply_highlighter_fold_candidates(
1156        &mut self,
1157        candidates: Vec<crate::input::FoldRange>,
1158        cx: &mut Context<Self>,
1159    ) {
1160        if self.mode.is_folding() {
1161            self.display_map.set_fold_candidates(candidates);
1162        }
1163        cx.notify();
1164    }
1165
1166    #[inline]
1167    pub fn diagnostics(&self) -> Option<&DiagnosticSet> {
1168        self.mode.diagnostics()
1169    }
1170
1171    #[inline]
1172    pub fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
1173        self.mode.diagnostics_mut()
1174    }
1175
1176    /// Set placeholder
1177    pub fn set_placeholder(
1178        &mut self,
1179        placeholder: impl Into<SharedString>,
1180        _: &mut Window,
1181        cx: &mut Context<Self>,
1182    ) {
1183        self.placeholder = placeholder.into();
1184        cx.notify();
1185    }
1186
1187    /// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.
1188    ///
1189    /// Returns:
1190    ///
1191    /// - The index of the line (zero-based) containing the offset.
1192    /// - The index of the sub-line (zero-based) within the line containing the offset.
1193    /// - The position of the offset.
1194    pub(super) fn line_and_position_for_offset(
1195        &self,
1196        offset: usize,
1197    ) -> (usize, usize, Option<Point<Pixels>>) {
1198        let Some(last_layout) = &self.last_layout else {
1199            return (0, 0, None);
1200        };
1201        let line_height = last_layout.line_height;
1202
1203        let mut y_offset = last_layout.visible_top;
1204        for (vi, line) in last_layout.lines.iter().enumerate() {
1205            let prev_lines_offset = last_layout.visible_line_byte_offsets[vi];
1206            let local_offset = offset.saturating_sub(prev_lines_offset);
1207            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
1208                let sub_line_index = (pos.y / line_height) as usize;
1209                let adjusted_pos = point(pos.x + last_layout.line_number_width, pos.y + y_offset);
1210                return (vi, sub_line_index, Some(adjusted_pos));
1211            }
1212
1213            y_offset += line.size(line_height).height;
1214        }
1215        (0, 0, None)
1216    }
1217
1218    /// Set the value of the input field: plain text, or [`InputContent`] to
1219    /// restore text together with its inline tokens. Editing history is
1220    /// cleared and no [`InputEvent::Change`] is emitted.
1221    ///
1222    /// For single-line inputs the caret is placed at the end of the text while
1223    /// the view is scrolled back to the start, so a long value shows its
1224    /// beginning instead of its tail (matching HTML `<input>`). Multi-line
1225    /// inputs reset the selection to `0..0`.
1226    pub fn set_value(
1227        &mut self,
1228        value: impl Into<super::InputContent>,
1229        window: &mut Window,
1230        cx: &mut Context<Self>,
1231    ) {
1232        let content = value.into();
1233        self.inline_tokens = None;
1234        self.undo_manager.set_ignoring(true);
1235        self.emit_events = false;
1236        self.replace_text(content.text().clone(), window, cx);
1237        self.install_tokens(content);
1238        self.undo_manager.set_ignoring(false);
1239        self.emit_events = true;
1240
1241        self.reset_selection();
1242        self.reset_lsp_state();
1243        self.reset_scroll_to_start();
1244
1245        self.undo_manager.clear();
1246        cx.notify();
1247    }
1248
1249    /// Replace the entire text content while preserving undo history.
1250    ///
1251    /// Unlike [`set_value`](Self::set_value), this method records the
1252    /// replacement in the undo stack, allowing the user to undo/redo
1253    /// the change. The selection is placed at the end of the new text
1254    /// for single-line inputs, or cleared (0..0) for multi-line inputs.
1255    ///
1256    /// Use this when programmatically replacing the full text but the
1257    /// user should still be able to undo the operation — e.g. formatting.
1258    pub fn replace_all(
1259        &mut self,
1260        text: impl Into<SharedString>,
1261        window: &mut Window,
1262        cx: &mut Context<Self>,
1263    ) {
1264        self.replace_text(text, window, cx);
1265        self.reset_selection();
1266        self.reset_lsp_state();
1267        self.reset_scroll_to_start();
1268
1269        cx.notify();
1270    }
1271
1272    /// Perform `f` with the user-facing edit restrictions lifted.
1273    ///
1274    /// The `disabled` and `readonly` modes only reject the changes made by the
1275    /// user, the programmatic APIs must always be able to update the text.
1276    pub(super) fn with_edits_allowed(&mut self, f: impl FnOnce(&mut Self)) {
1277        let (was_disabled, was_readonly) = (self.disabled, self.readonly);
1278        (self.disabled, self.readonly) = (false, false);
1279        f(self);
1280        (self.disabled, self.readonly) = (was_disabled, was_readonly);
1281    }
1282
1283    /// Insert text at the current cursor position.
1284    ///
1285    /// And the cursor will be moved to the end of inserted text.
1286    pub fn insert(
1287        &mut self,
1288        text: impl Into<SharedString>,
1289        window: &mut Window,
1290        cx: &mut Context<Self>,
1291    ) {
1292        let text: SharedString = text.into();
1293        self.with_edits_allowed(|this| {
1294            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1295            let range_utf16 = this.range_to_utf16(&(this.cursor()..this.cursor()));
1296            this.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
1297            let end = this.active_selection().end;
1298            this.set_cursor_to(end);
1299        });
1300    }
1301
1302    /// Replace text at the current cursor position.
1303    ///
1304    /// And the cursor will be moved to the end of replaced text.
1305    pub fn replace(
1306        &mut self,
1307        text: impl Into<SharedString>,
1308        window: &mut Window,
1309        cx: &mut Context<Self>,
1310    ) {
1311        let text: SharedString = text.into();
1312        self.with_edits_allowed(|this| {
1313            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1314            this.replace_text_in_range_silent(None, &text, window, cx);
1315            let end = this.active_selection().end;
1316            this.set_cursor_to(end);
1317        });
1318    }
1319
1320    fn replace_text(
1321        &mut self,
1322        text: impl Into<SharedString>,
1323        window: &mut Window,
1324        cx: &mut Context<Self>,
1325    ) {
1326        let text: SharedString = text.into();
1327        self.with_edits_allowed(|this| {
1328            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1329            let range = 0..this.text.chars().map(|c| c.len_utf16()).sum();
1330            this.replace_text_in_range_silent(Some(range), &text, window, cx);
1331            this.reset_highlighter(cx);
1332        });
1333    }
1334
1335    fn reset_selection(&mut self) {
1336        self.selections.remove_all_but_active();
1337
1338        // For single-line inputs the caret is placed at the end of the text
1339        // (matching HTML `<input>`); multi-line inputs reset the selection to
1340        // `0..0`.
1341        if self.is_single_line() {
1342            let end = self.text.len();
1343            self.set_cursor_to(end);
1344        } else {
1345            self.active_selection_mut().clear();
1346        }
1347    }
1348
1349    fn reset_lsp_state(&mut self) {
1350        if self.is_code_editor() {
1351            self._pending_update = true;
1352            M::reset_language_features(self);
1353        }
1354    }
1355
1356    fn reset_scroll_to_start(&mut self) {
1357        // Move scroll to the start. For single-line the caret is at the end, so
1358        // override the cursor-follow scroll for the next painted frame to keep
1359        // the start visible; the deferred offset is consumed during that paint.
1360        self.scroll_handle.set_offset(point(px(0.), px(0.)));
1361        if self.is_single_line() {
1362            self.deferred_scroll_offset = Some(point(px(0.), px(0.)));
1363        }
1364    }
1365
1366    /// Set with disabled mode.
1367    ///
1368    /// See also: [`Self::set_disabled`].
1369    #[allow(unused)]
1370    pub(crate) fn disabled(mut self, disabled: bool) -> Self {
1371        self.disabled = disabled;
1372        self
1373    }
1374
1375    pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
1376        if self.disabled == disabled {
1377            return;
1378        }
1379
1380        self.disabled = disabled;
1381        if disabled {
1382            M::hide_context_menu(self, cx);
1383            M::clear_inline_completion(self, cx);
1384        }
1385        cx.notify();
1386    }
1387
1388    /// Set with read-only mode.
1389    ///
1390    /// Unlike [`Self::disabled`], a read-only input keeps the normal appearance,
1391    /// focus, cursor, selection and copy behavior, it only rejects any change
1392    /// of the text made by the user.
1393    ///
1394    /// See also: [`Self::set_readonly`].
1395    #[allow(unused)]
1396    pub(crate) fn readonly(mut self, readonly: bool) -> Self {
1397        self.readonly = readonly;
1398        self
1399    }
1400
1401    pub fn set_readonly(&mut self, readonly: bool, cx: &mut Context<Self>) {
1402        if self.readonly == readonly {
1403            return;
1404        }
1405
1406        self.readonly = readonly;
1407        if readonly {
1408            M::hide_context_menu(self, cx);
1409            M::clear_inline_completion(self, cx);
1410            self.search_session.replace_mode = false;
1411        }
1412        cx.notify();
1413    }
1414
1415    /// Returns true if the user is allowed to change the text.
1416    ///
1417    /// This is false when the input is `disabled` or `readonly`, the programmatic
1418    /// APIs (e.g.: [`Self::set_value`], [`Self::insert`]) are not limited by this.
1419    pub fn is_editable(&self) -> bool {
1420        !self.disabled && !self.readonly
1421    }
1422
1423    /// Set true to clear the input by pressing Escape key.
1424    ///
1425    /// Not on Android, where Escape is also the back button.
1426    pub fn clean_on_escape(mut self) -> Self {
1427        self.clean_on_escape = true;
1428        self
1429    }
1430
1431    pub fn set_clean_on_escape(&mut self, clean: bool) {
1432        self.clean_on_escape = clean;
1433    }
1434
1435    /// Set true to treat `Enter` as a submit action in multi-line mode,
1436    /// while `Shift+Enter` inserts a newline.
1437    ///
1438    /// Default is `false` (both `Enter` and `Shift+Enter` insert a newline).
1439    #[doc(hidden)]
1440    pub fn submit_on_enter(mut self, submit: bool) -> Self {
1441        self.submit_on_enter = submit;
1442        self
1443    }
1444
1445    pub fn set_submit_on_enter(&mut self, submit: bool, cx: &mut Context<Self>) {
1446        self.submit_on_enter = submit;
1447        cx.notify();
1448    }
1449
1450    /// Set the action a software keyboard shows on its enter key, like Search
1451    /// or Send. Pressing it still emits [`InputEvent::PressEnter`].
1452    ///
1453    /// Default is [`TextInputAction::Done`] for a single-line input (and a
1454    /// multi-line one that submits on enter), and [`TextInputAction::Enter`]
1455    /// (a line break) for a multi-line input.
1456    pub fn input_action(mut self, action: TextInputAction) -> Self {
1457        self.input_action = Some(action);
1458        self
1459    }
1460
1461    /// Set whether the platform may autocorrect the text and offer word
1462    /// suggestions.
1463    ///
1464    /// Default is `false`. Enable this explicitly for fields that accept prose;
1465    /// masking or revealing text does not change the setting.
1466    pub fn autocorrect(mut self, autocorrect: bool) -> Self {
1467        self.autocorrect = Some(autocorrect);
1468        self
1469    }
1470
1471    /// Set how a software keyboard capitalizes the text.
1472    ///
1473    /// Default is [`Autocapitalize::None`]. Enable capitalization explicitly for
1474    /// fields where it is appropriate, independently of autocorrection.
1475    pub fn autocapitalize(mut self, autocapitalize: Autocapitalize) -> Self {
1476        self.autocapitalize = Some(autocapitalize);
1477        self
1478    }
1479
1480    /// The text assistance a platform IME (a software keyboard) should give
1481    /// this input, from [`Self::input_action`], [`Self::autocorrect`] and
1482    /// [`Self::autocapitalize`] or the defaults they describe.
1483    pub fn text_input_configuration(&self) -> TextInputConfiguration {
1484        // Masking is a presentation choice, not a reliable content type: a
1485        // revealed password must not acquire text assistance. Keep assistance
1486        // opt-in for structured fields such as passwords, email and URLs.
1487        let autocorrect = self.autocorrect.unwrap_or(false);
1488        let input_action =
1489            self.input_action
1490                .unwrap_or(if self.is_multi_line() && !self.submit_on_enter {
1491                    TextInputAction::Enter
1492                } else {
1493                    TextInputAction::Done
1494                });
1495        TextInputConfiguration {
1496            autocorrect,
1497            autocapitalize: self.autocapitalize.unwrap_or(Autocapitalize::None),
1498            suggestions: autocorrect,
1499            input_action,
1500        }
1501    }
1502
1503    /// Set whether to show whitespace characters.
1504    #[doc(hidden)]
1505    pub fn show_whitespaces(mut self, show: bool) -> Self {
1506        self.show_whitespaces = show;
1507        self
1508    }
1509
1510    /// Update whether to show whitespace characters.
1511    pub fn set_show_whitespaces(&mut self, show: bool, _: &mut Window, cx: &mut Context<Self>) {
1512        self.show_whitespaces = show;
1513        cx.notify();
1514    }
1515
1516    /// Empty rows reserved below the last line of content ("scroll
1517    /// beyond last line"), code-editor mode only. Mirrors VSCode's
1518    /// `editor.scrollBeyondLastLine` / Zed's `scroll_beyond_last_line`.
1519    ///
1520    /// - `None` (default): half the viewport, floored at
1521    ///   [`BOTTOM_MARGIN_ROWS`] line-heights.
1522    /// - `Some(0)`: no trailing space; the cursor sits flush with the
1523    ///   last row at scroll-max.
1524    /// - `Some(n)`: exactly `n` rows.
1525    pub fn scroll_beyond_last_line(mut self, rows: Option<usize>) -> Self {
1526        self.scroll_beyond_last_line = rows;
1527        self
1528    }
1529
1530    /// Update [`Self::scroll_beyond_last_line`] after construction.
1531    pub fn set_scroll_beyond_last_line(
1532        &mut self,
1533        rows: Option<usize>,
1534        _: &mut Window,
1535        cx: &mut Context<Self>,
1536    ) {
1537        if self.scroll_beyond_last_line == rows {
1538            return;
1539        }
1540        self.scroll_beyond_last_line = rows;
1541        cx.notify();
1542    }
1543
1544    /// Minimum number of lines the cursor is kept clear of the viewport's
1545    /// top/bottom edge before auto-scroll engages. Mirrors VSCode's
1546    /// `editor.cursorSurroundingLines` / Zed's `vertical_scroll_margin`.
1547    /// Orthogonal to [`Self::scroll_beyond_last_line`], which sizes the
1548    /// empty region; this controls the cursor's resting distance from the
1549    /// edge.
1550    ///
1551    /// - `None` (default): [`BOTTOM_MARGIN_ROWS`] lines, falling back to
1552    ///   one line on small viewports.
1553    /// - `Some(n)`: requests `n` lines, clamped to half the viewport.
1554    ///
1555    /// In a constrained viewport, edge clearance yields to the cursor's
1556    /// complete visual row so surrounding space cannot clip the text being edited.
1557    pub fn cursor_surrounding_lines(mut self, lines: Option<usize>) -> Self {
1558        self.cursor_surrounding_lines = lines;
1559        self
1560    }
1561
1562    /// Update [`Self::cursor_surrounding_lines`] after construction.
1563    pub fn set_cursor_surrounding_lines(
1564        &mut self,
1565        lines: Option<usize>,
1566        _: &mut Window,
1567        cx: &mut Context<Self>,
1568    ) {
1569        if self.cursor_surrounding_lines == lines {
1570            return;
1571        }
1572        self.cursor_surrounding_lines = lines;
1573        cx.notify();
1574    }
1575
1576    /// Set the default value of the input field.
1577    pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {
1578        let text: SharedString = value.into();
1579        self.text = Rope::from(self.normalize_input(&text).as_ref());
1580        if let Some(diagnostics) = self.mode.diagnostics_mut() {
1581            diagnostics.reset(&self.text)
1582        }
1583        // Note: We can't call display_map.set_text here because it needs cx.
1584        // The text will be set during prepare_if_need in element.rs
1585        self._pending_update = true;
1586        self
1587    }
1588
1589    /// Return the value of the input field as an owned string.
1590    ///
1591    /// The string is materialized on each call. See [`Self::text`] for the
1592    /// [`Rope`] the state owns, which is borrowed and costs nothing to read.
1593    pub fn value(&self) -> SharedString {
1594        SharedString::new(self.text.to_string())
1595    }
1596
1597    /// Return the portion of the value within the input field that
1598    /// is selected by the user, as an owned string.
1599    ///
1600    /// The string is materialized on each call. See [`Self::selected_text`]
1601    /// for the same selection borrowed out of the [`Rope`] the state owns.
1602    pub fn selected_value(&self) -> SharedString {
1603        SharedString::new(self.selected_text().to_string())
1604    }
1605
1606    /// Return the value without mask.
1607    pub fn unmask_value(&self) -> SharedString {
1608        self.mask_pattern.unmask(&self.text.to_string()).into()
1609    }
1610
1611    /// Kept so existing render paths keep compiling.
1612    ///
1613    /// Configuration used to be collected by a facade and applied here; the
1614    /// state now configures itself, so this does nothing and can be deleted at
1615    /// the call site.
1616    #[doc(hidden)]
1617    pub fn prepare(&mut self, _: &mut Window, _: &mut Context<Self>) {}
1618
1619    /// Return the text [`Rope`] of the input field.
1620    ///
1621    /// Borrowed from the state, so reading even a large document copies
1622    /// nothing. See [`Self::value`] when an owned string is wanted.
1623    pub fn text(&self) -> &Rope {
1624        &self.text
1625    }
1626
1627    /// Return the (0-based) [`Position`] of the cursor.
1628    pub fn cursor_position(&self) -> Position {
1629        let offset = self.cursor();
1630        self.text.offset_to_position(offset)
1631    }
1632
1633    /// Set (0-based) [`Position`] of the cursor.
1634    ///
1635    /// This will move the cursor to the specified line and column, and update the selection range.
1636    pub fn set_cursor_position(
1637        &mut self,
1638        position: impl Into<Position>,
1639        window: &mut Window,
1640        cx: &mut Context<Self>,
1641    ) {
1642        let position: Position = position.into();
1643        let offset = self.text.position_to_offset(&position);
1644
1645        self.move_to(offset, None, cx);
1646        self.update_preferred_column();
1647        self.focus(window, cx);
1648    }
1649
1650    /// Focus the input field.
1651    pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1652        self.focus_handle.focus(window, cx);
1653        self.blink_cursor.update(cx, |cursor, cx| {
1654            cursor.start(cx);
1655        });
1656    }
1657
1658    /// Refresh the input, so the next render re-runs syntax highlighting and
1659    /// the LSP providers, not just a redraw.
1660    ///
1661    /// Assigning the `lsp` providers (or other render-affecting state) at
1662    /// runtime does not take effect until the text next changes. Call this
1663    /// afterwards to force the refresh on the next render.
1664    ///
1665    /// ```ignore
1666    /// input.update(cx, |state, cx| {
1667    ///     state.extras.lsp.hover_provider = Some(provider);
1668    ///     state.refresh(cx);
1669    /// });
1670    /// ```
1671    pub fn refresh(&mut self, cx: &mut Context<Self>) {
1672        self._pending_update = true;
1673        cx.notify();
1674    }
1675
1676    pub(super) fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
1677        self.undo_manager.break_transaction_coalescing();
1678        self.select_all_cursors_to(|s, sel| s.previous_boundary(sel.cursor_offset()), cx);
1679    }
1680
1681    pub(super) fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
1682        self.select_all_cursors_to(|s, sel| s.next_boundary(sel.cursor_offset()), cx);
1683    }
1684
1685    pub(super) fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
1686        if self.is_single_line() {
1687            return;
1688        }
1689        self.select_all_cursors_to_with_affinity(
1690            |s, sel| {
1691                s.vertical_selection_target(
1692                    sel.cursor_offset(),
1693                    sel.column_anchor,
1694                    s.line_end_affinity_for(sel),
1695                    -1,
1696                )
1697            },
1698            true,
1699            cx,
1700        );
1701    }
1702
1703    pub(super) fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
1704        if self.is_single_line() {
1705            return;
1706        }
1707        self.select_all_cursors_to_with_affinity(
1708            |s, sel| {
1709                s.vertical_selection_target(
1710                    sel.cursor_offset(),
1711                    sel.column_anchor,
1712                    s.line_end_affinity_for(sel),
1713                    1,
1714                )
1715            },
1716            true,
1717            cx,
1718        );
1719    }
1720
1721    pub(super) fn on_action_select_all(
1722        &mut self,
1723        _: &SelectAll,
1724        window: &mut Window,
1725        cx: &mut Context<Self>,
1726    ) {
1727        self.select_all(window, cx);
1728    }
1729
1730    pub(super) fn select_to_start(
1731        &mut self,
1732        _: &SelectToStart,
1733        _: &mut Window,
1734        cx: &mut Context<Self>,
1735    ) {
1736        self.undo_manager.break_transaction_coalescing();
1737        self.select_all_cursors_to(|_, _| 0, cx);
1738    }
1739
1740    pub(super) fn select_to_end(
1741        &mut self,
1742        _: &SelectToEnd,
1743        _: &mut Window,
1744        cx: &mut Context<Self>,
1745    ) {
1746        self.undo_manager.break_transaction_coalescing();
1747        let end = self.text.len();
1748        self.select_all_cursors_to(move |_, _| end, cx);
1749    }
1750
1751    pub(super) fn select_to_start_of_line(
1752        &mut self,
1753        _: &SelectToStartOfLine,
1754        _: &mut Window,
1755        cx: &mut Context<Self>,
1756    ) {
1757        self.undo_manager.break_transaction_coalescing();
1758        self.select_all_cursors_to(
1759            |s, sel| s.start_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1760            cx,
1761        );
1762    }
1763
1764    pub(super) fn select_to_end_of_line(
1765        &mut self,
1766        _: &SelectToEndOfLine,
1767        _: &mut Window,
1768        cx: &mut Context<Self>,
1769    ) {
1770        self.undo_manager.break_transaction_coalescing();
1771        self.select_all_cursors_to_with_affinity(
1772            |s, sel| {
1773                (
1774                    s.end_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1775                    true,
1776                )
1777            },
1778            false,
1779            cx,
1780        );
1781    }
1782
1783    pub(super) fn select_to_previous_word(
1784        &mut self,
1785        _: &SelectToPreviousWordStart,
1786        _: &mut Window,
1787        cx: &mut Context<Self>,
1788    ) {
1789        self.undo_manager.break_transaction_coalescing();
1790        self.select_all_cursors_to(
1791            |s, sel| s.previous_start_of_word_at(sel.cursor_offset()),
1792            cx,
1793        );
1794    }
1795
1796    pub(super) fn select_to_next_word(
1797        &mut self,
1798        _: &SelectToNextWordEnd,
1799        _: &mut Window,
1800        cx: &mut Context<Self>,
1801    ) {
1802        self.undo_manager.break_transaction_coalescing();
1803        self.select_all_cursors_to(|s, sel| s.next_end_of_word_at(sel.cursor_offset()), cx);
1804    }
1805
1806    /// Return the start offset of the previous word.
1807    /// Return the previous start offset of the word before `offset`.
1808    pub(super) fn previous_start_of_word_at(&self, offset: usize) -> usize {
1809        if self.masked {
1810            // The mask replaces every character, so the displayed text has no
1811            // word boundaries to move or delete by. Collapse the word to the
1812            // whole text.
1813            return 0;
1814        }
1815
1816        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1817        // FIXME: Avoid to_string
1818        let left_part = self.text.slice(0..offset).to_string();
1819
1820        let target = UnicodeSegmentation::split_word_bound_indices(left_part.as_str())
1821            .rfind(|(_, s)| !s.trim_start().is_empty())
1822            .map(|(i, _)| i)
1823            .unwrap_or(0);
1824        self.token_boundary(target, Bias::Left)
1825    }
1826
1827    /// Return the next end offset of the word after `offset`.
1828    pub(super) fn next_end_of_word_at(&self, offset: usize) -> usize {
1829        if self.masked {
1830            // See `previous_start_of_word_at`.
1831            return self.text.len();
1832        }
1833
1834        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1835        let right_part = self.text.slice(offset..self.text.len()).to_string();
1836
1837        let target = UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
1838            .find(|(_, s)| !s.trim_start().is_empty())
1839            .map(|(i, s)| offset + i + s.len())
1840            .unwrap_or(self.text.len());
1841        self.token_boundary(target, Bias::Right)
1842    }
1843
1844    /// Get start of line byte offset for the given `offset`.
1845    ///
1846    /// When soft wrap is active, first press goes to visual line start,
1847    /// second press (already at visual start) goes to logical line start.
1848    pub(super) fn start_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1849        if self.is_single_line() {
1850            return 0;
1851        }
1852
1853        let row = self.text.offset_to_point(offset).row;
1854        let logical_start = self.text.line_start_offset(row);
1855
1856        if self.soft_wrap && self.is_code_editor() {
1857            let wrap_point = self
1858                .display_map
1859                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1860            if let Some(line) = self.display_map.line(row)
1861                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1862            {
1863                let visual_start = logical_start + range.start;
1864                if offset != visual_start {
1865                    return visual_start;
1866                }
1867            }
1868        }
1869
1870        logical_start
1871    }
1872
1873    /// Get end of line byte offset for the given `offset`.
1874    ///
1875    /// When soft wrap is active, first press goes to visual line end,
1876    /// second press (already at visual end) goes to logical line end.
1877    pub(super) fn end_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1878        if self.is_single_line() {
1879            return self.text.len();
1880        }
1881
1882        let row = self.text.offset_to_point(offset).row;
1883        let logical_start = self.text.line_start_offset(row);
1884        let logical_end = self.text.line_end_offset(row);
1885
1886        if self.soft_wrap && self.is_code_editor() {
1887            // Use the row the caret is drawn on: at a wrap boundary the raw offset would name
1888            // the next row, and a second End press would keep walking down instead of falling
1889            // through to the logical line end.
1890            let wrap_point = self
1891                .display_map
1892                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1893            if let Some(line) = self.display_map.line(row)
1894                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1895            {
1896                let visual_end = logical_start + range.end;
1897                if offset != visual_end {
1898                    return visual_end;
1899                }
1900            }
1901        }
1902
1903        logical_end
1904    }
1905
1906    /// Get indent string of next line.
1907    ///
1908    /// To get current and next line indent, to return more depth one.
1909    pub(super) fn indent_of_next_line(&mut self) -> String {
1910        self.indent_of_next_line_at(self.cursor())
1911    }
1912
1913    /// Get indent string of the next line, relative to the given `offset`.
1914    pub(super) fn indent_of_next_line_at(&mut self, offset: usize) -> String {
1915        if self.is_single_line() {
1916            return "".into();
1917        }
1918
1919        let mut current_indent = String::new();
1920        let mut next_indent = String::new();
1921        let line_end_affinity = self.line_end_affinity_at(offset);
1922        let current_line_start_pos = self.start_of_line_at(offset, line_end_affinity);
1923        let next_line_start_pos = self.end_of_line_at(offset, line_end_affinity);
1924        for c in self.text.slice(current_line_start_pos..).chars() {
1925            if !c.is_whitespace() {
1926                break;
1927            }
1928            if c == '\n' || c == '\r' {
1929                break;
1930            }
1931            current_indent.push(c);
1932        }
1933
1934        for c in self.text.slice(next_line_start_pos..).chars() {
1935            if !c.is_whitespace() {
1936                break;
1937            }
1938            if c == '\n' || c == '\r' {
1939                break;
1940            }
1941            next_indent.push(c);
1942        }
1943
1944        if next_indent.len() > current_indent.len() {
1945            return next_indent;
1946        }
1947
1948        // Smart indent: one extra level after lines ending with a trigger.
1949        // Skipped inside strings and comments: the trigger is literal text.
1950        if let Some(rules) = self.mode.language_config() {
1951            if self.mode.is_smart_indent()
1952                && M::editing_syntax_context(self, offset) == crate::input::SyntaxContext::Code
1953            {
1954                let line_before: String = self
1955                    .text
1956                    .slice(current_line_start_pos..offset)
1957                    .chars()
1958                    .collect();
1959                if rules.opens_indent(line_before.trim_end()) {
1960                    let tab = self.mode.tab_size();
1961                    if tab.hard_tabs {
1962                        current_indent.push('\t');
1963                    } else {
1964                        for _ in 0..tab.tab_size {
1965                            current_indent.push(' ');
1966                        }
1967                    }
1968                } else {
1969                    let line_after = self.text.slice(offset..next_line_start_pos).to_string();
1970                    if rules.closes_indent(&line_after) {
1971                        let tab = self.mode.tab_size();
1972                        if current_indent.ends_with('\t') {
1973                            current_indent.pop();
1974                        } else {
1975                            for _ in 0..tab.tab_size {
1976                                if !current_indent.ends_with(' ') {
1977                                    break;
1978                                }
1979                                current_indent.pop();
1980                            }
1981                        }
1982                    }
1983                }
1984            }
1985        }
1986
1987        current_indent
1988    }
1989
1990    /// Delete every selection as one batch. Collapsed cursors are first
1991    /// expanded to a deletion range by `collapsed_target` and non-empty
1992    /// selections delete their own range.
1993    ///
1994    /// `collapsed_intent` is the intent to record when every cursor is
1995    /// collapsed, which is what makes a run of single-character deletes undo
1996    /// as one gesture. Deleting a real selection is always atomic.
1997    fn delete_selections(
1998        &mut self,
1999        silent: bool,
2000        collapsed_intent: EditIntent,
2001        mut collapsed_target: impl FnMut(&mut Self, usize) -> Range<usize>,
2002        window: &mut Window,
2003        cx: &mut Context<Self>,
2004    ) {
2005        if !self.is_editable() {
2006            return;
2007        }
2008        let cursors: Vec<CursorSelection> = self.selections.iter().copied().collect();
2009        let intent = if cursors.iter().all(|sel| sel.is_empty()) {
2010            collapsed_intent
2011        } else {
2012            EditIntent::Atomic
2013        };
2014        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(cursors.len());
2015        for sel in &cursors {
2016            let range = if sel.is_empty() {
2017                collapsed_target(self, sel.cursor_offset())
2018            } else {
2019                sel.start..sel.end
2020            };
2021            let (start, end) = (range.start.min(range.end), range.start.max(range.end));
2022            let mut selection = *sel;
2023            selection.start = start;
2024            selection.end = end;
2025            new_selections.push(selection);
2026        }
2027        // Capture the user's selections before expanding or merging deletion
2028        // ranges. The edit ranges cannot reconstruct their original carets.
2029        self.undo_manager.begin_transaction_with(intent);
2030        self.undo_manager
2031            .record_selections(cursors.clone(), cursors.clone());
2032        self.selections.replace_all(new_selections);
2033        self.undo_manager.set_pending_intent(intent);
2034
2035        let was_silent = self.silent_replace_text;
2036        self.silent_replace_text = silent;
2037        self.replace_text_in_range(None, "", window, cx);
2038        self.silent_replace_text = was_silent;
2039        self.undo_manager
2040            .record_selections(cursors, self.selections.iter().copied().collect());
2041        self.undo_manager.commit_transaction();
2042        self.pause_blink_cursor(cx);
2043    }
2044
2045    /// Decide pairing against the pre-edit text, before an opening quote can
2046    /// change its own syntax context. Ordinary characters never query syntax.
2047    fn auto_close_target(&self, range: &Range<usize>, text: &str) -> Option<(usize, SharedString)> {
2048        if self.silent_replace_text
2049            || self.ime_marked_range.is_some()
2050            || !self.selections.is_single()
2051            || !self.active_selection().is_empty()
2052            || !range.is_empty()
2053            || range.start != self.cursor()
2054            || text.chars().count() != 1
2055            || !self.mode.is_auto_close()
2056        {
2057            return None;
2058        }
2059        let rules = self.mode.language_config()?;
2060        if self
2061            .text
2062            .chars_at(range.start)
2063            .next()
2064            .is_some_and(|c| !c.is_whitespace() && !rules.auto_close_before.contains(c))
2065        {
2066            return None;
2067        }
2068        for (open, close, not_in) in rules.closing_pairs() {
2069            let Some(prefix) = open.strip_suffix(text) else {
2070                continue;
2071            };
2072            if !self.text_before_matches(range.start, prefix) {
2073                continue;
2074            }
2075            let start = range.start - prefix.len();
2076            if open == close
2077                && (self.is_escaped_at(start)
2078                    || self
2079                        .text
2080                        .chars_at(start)
2081                        .reversed()
2082                        .next()
2083                        .is_some_and(|c| c.is_alphanumeric() || c == '_'))
2084            {
2085                continue;
2086            }
2087            if not_in.contains(&M::editing_syntax_context(self, start)) {
2088                continue;
2089            }
2090            return Some((open.len(), close.into()));
2091        }
2092        None
2093    }
2094
2095    fn text_before_matches(&self, offset: usize, text: &str) -> bool {
2096        offset >= text.len()
2097            && self.text.is_char_boundary(offset - text.len())
2098            && self.text.slice(offset - text.len()..offset) == text
2099    }
2100
2101    fn text_after_matches(&self, offset: usize, text: &str) -> bool {
2102        offset + text.len() <= self.text.len()
2103            && self.text.is_char_boundary(offset + text.len())
2104            && self.text.slice(offset..offset + text.len()) == text
2105    }
2106
2107    fn is_escaped_at(&self, offset: usize) -> bool {
2108        self.text
2109            .chars_at(offset)
2110            .reversed()
2111            .take_while(|c| *c == '\\')
2112            .count()
2113            % 2
2114            == 1
2115    }
2116
2117    /// Cursor target when a typed closer should skip over an existing one.
2118    ///
2119    /// Returns `Some(offset)` when `new_text` is a single closer from the
2120    /// active [`LanguageConfig`](crate::input::language_config::LanguageConfig) that already follows the collapsed cursor and is
2121    /// not escaped. The caller then moves the cursor without editing text or
2122    /// history. `None` means insert normally.
2123    fn skip_over_target(&self, new_text: &str) -> Option<usize> {
2124        if !self.mode.is_auto_close() || new_text.chars().count() != 1 {
2125            return None;
2126        }
2127        let rules = self.mode.language_config()?;
2128        let cursor = self.cursor();
2129        for (open, close, not_in) in rules.closing_pairs() {
2130            for (index, _) in close.char_indices() {
2131                if !close[index..].starts_with(new_text)
2132                    || !self.text_before_matches(cursor, &close[..index])
2133                    || !self.text_after_matches(cursor, &close[index..])
2134                {
2135                    continue;
2136                }
2137                let context = M::editing_syntax_context(self, cursor);
2138                let generated =
2139                    self.mode
2140                        .auto_closed_pairs()
2141                        .contains_closer(cursor, index, close.len());
2142                if !generated
2143                    && not_in.contains(&context)
2144                    && !(context == crate::input::SyntaxContext::String && open == close)
2145                {
2146                    continue;
2147                }
2148                if self.is_escaped_at(cursor - index) {
2149                    continue;
2150                }
2151                return Some(cursor + new_text.len());
2152            }
2153        }
2154        None
2155    }
2156
2157    pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
2158        // Nothing to delete at the start of the text. Propagate so an ancestor
2159        // (e.g. a command palette navigating back a level) can act on it.
2160        // This is harmless when nothing upstream is bound to backspace. With multiple
2161        // cursors the others can still delete, so only the lone-cursor case
2162        // propagates.
2163        if self.selections.is_single() && self.active_selection().is_empty() && self.cursor() == 0 {
2164            cx.propagate();
2165            return;
2166        }
2167
2168        // Pair deletion follows automatic closing rules, independently of
2169        // structural brackets used by Enter.
2170        if self.mode.is_auto_close()
2171            && self.selections.is_single()
2172            && self.active_selection().is_empty()
2173        {
2174            let off = self.cursor();
2175            let deletion = self.mode.language_config().and_then(|rules| {
2176                rules.closing_pairs().find_map(|(open, close, not_in)| {
2177                    if !self.text_before_matches(off, open) || !self.text_after_matches(off, close)
2178                    {
2179                        return None;
2180                    }
2181                    let start = off - open.len();
2182                    let generated = self
2183                        .mode
2184                        .auto_closed_pairs()
2185                        .contains(start..off, off..off + close.len());
2186                    if self.is_escaped_at(start)
2187                        || (!generated && not_in.contains(&M::editing_syntax_context(self, start)))
2188                    {
2189                        return None;
2190                    }
2191                    Some(start..off + close.len())
2192                })
2193            });
2194            if let Some(range) = deletion {
2195                let utf16 = self.range_to_utf16(&range);
2196                self.replace_text_in_range_silent(Some(utf16), "", window, cx);
2197                return;
2198            }
2199        }
2200
2201        self.delete_selections(
2202            false,
2203            EditIntent::Backspace,
2204            |s, offset| s.previous_boundary(offset)..offset,
2205            window,
2206            cx,
2207        );
2208    }
2209
2210    pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
2211        self.delete_selections(
2212            false,
2213            EditIntent::DeleteForward,
2214            |s, offset| offset..s.next_boundary(offset),
2215            window,
2216            cx,
2217        );
2218    }
2219
2220    pub(super) fn delete_to_beginning_of_line(
2221        &mut self,
2222        _: &DeleteToBeginningOfLine,
2223        window: &mut Window,
2224        cx: &mut Context<Self>,
2225    ) {
2226        self.delete_selections(
2227            true,
2228            EditIntent::Atomic,
2229            |s, offset| {
2230                let mut start = s.start_of_line_at(offset, s.line_end_affinity_at(offset));
2231                if start == offset {
2232                    start = start.saturating_sub(1);
2233                }
2234                start..offset
2235            },
2236            window,
2237            cx,
2238        );
2239    }
2240
2241    pub(super) fn delete_to_end_of_line(
2242        &mut self,
2243        _: &DeleteToEndOfLine,
2244        window: &mut Window,
2245        cx: &mut Context<Self>,
2246    ) {
2247        self.delete_selections(
2248            true,
2249            EditIntent::Atomic,
2250            |s, offset| {
2251                let mut end = s.end_of_line_at(offset, s.line_end_affinity_at(offset));
2252                if end == offset {
2253                    end = (end + 1).clamp(0, s.text.len());
2254                }
2255                offset..end
2256            },
2257            window,
2258            cx,
2259        );
2260    }
2261
2262    pub(super) fn delete_previous_word(
2263        &mut self,
2264        _: &DeleteToPreviousWordStart,
2265        window: &mut Window,
2266        cx: &mut Context<Self>,
2267    ) {
2268        self.delete_selections(
2269            true,
2270            EditIntent::Atomic,
2271            |s, offset| s.previous_start_of_word_at(offset)..offset,
2272            window,
2273            cx,
2274        );
2275    }
2276
2277    pub(super) fn delete_next_word(
2278        &mut self,
2279        _: &DeleteToNextWordEnd,
2280        window: &mut Window,
2281        cx: &mut Context<Self>,
2282    ) {
2283        self.delete_selections(
2284            true,
2285            EditIntent::Atomic,
2286            |s, offset| offset..s.next_end_of_word_at(offset),
2287            window,
2288            cx,
2289        );
2290    }
2291
2292    pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {
2293        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2294            return;
2295        }
2296
2297        // Clear inline completion on enter (user chose not to accept it)
2298        if M::has_inline_completion(self) {
2299            M::clear_inline_completion(self, cx);
2300        }
2301
2302        // In multi-line mode with `submit_on_enter` enabled, a plain `Enter`
2303        // (without Shift) is treated as submit: propagate the action and emit
2304        // PressEnter without inserting a newline. `Shift+Enter` still inserts
2305        // a newline.
2306        let insert_newline = self.is_multi_line() && (!self.submit_on_enter || action.shift);
2307
2308        if insert_newline {
2309            if !self.selections.is_single() {
2310                // Insert a newline (with per-line indent) at every cursor.
2311                self.selections.merge_overlapping();
2312                let selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
2313                let mut edits: Vec<(Range<usize>, String)> = Vec::with_capacity(selections.len());
2314                for sel in &selections {
2315                    let indent = if self.is_code_editor() {
2316                        self.indent_of_next_line_at(sel.cursor_offset())
2317                    } else {
2318                        String::new()
2319                    };
2320                    edits.push((sel.start..sel.end, format!("\n{}", indent)));
2321                }
2322                edits.sort_by_key(|(range, _)| range.start);
2323                self.replace_text_in_ranges(&edits, window, cx);
2324                self.pause_blink_cursor(cx);
2325            } else {
2326                // Bracket split: Enter between `{|}` produces `{\n  \n}`
2327                // with the cursor on the middle line. Controlled by smart_indent,
2328                // independently of automatic closing.
2329                // Skipped inside strings and comments: splitting there would
2330                // restructure literal text.
2331                let mut split = false;
2332                if self.is_code_editor() && self.active_selection().is_empty() {
2333                    if let Some(rules) = self.mode.language_config() {
2334                        if self.mode.is_smart_indent()
2335                            && M::editing_syntax_context(self, self.cursor())
2336                                == crate::input::SyntaxContext::Code
2337                        {
2338                            let off = self.cursor();
2339                            let is_pair = rules.brackets.iter().any(|p| {
2340                                !p.open.is_empty()
2341                                    && !p.close.is_empty()
2342                                    && p.open != p.close
2343                                    && self.text_before_matches(off, p.open.as_ref())
2344                                    && self.text_after_matches(off, p.close.as_ref())
2345                            });
2346                            if is_pair {
2347                                // Base indent: leading whitespace only.
2348                                let line_end_affinity = self.line_end_affinity_at(off);
2349                                let line_start = self.start_of_line_at(off, line_end_affinity);
2350                                let mut indent = String::new();
2351                                for c in self.text.slice(line_start..).chars() {
2352                                    if !c.is_whitespace() || c == '\n' || c == '\r' {
2353                                        break;
2354                                    }
2355                                    indent.push(c);
2356                                }
2357                                // Extra level only with smart indent enabled.
2358                                let mut inner = indent.clone();
2359                                if self.mode.is_smart_indent() {
2360                                    let tab = self.mode.tab_size();
2361                                    if tab.hard_tabs {
2362                                        inner.push('\t');
2363                                    } else {
2364                                        for _ in 0..tab.tab_size {
2365                                            inner.push(' ');
2366                                        }
2367                                    }
2368                                }
2369                                let new_line_text = format!("\n{inner}\n{indent}");
2370                                self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2371                                self.set_cursor_to(off + 1 + inner.len());
2372                                self.update_preferred_column();
2373                                let cursors = self.selections.iter().copied().collect::<Vec<_>>();
2374                                self.undo_manager
2375                                    .record_selections(cursors.clone(), cursors);
2376                                self.pause_blink_cursor(cx);
2377                                split = true;
2378                            }
2379                        }
2380                    }
2381                }
2382                if !split {
2383                    // Get current line indent
2384                    let indent = if self.is_code_editor() {
2385                        self.indent_of_next_line()
2386                    } else {
2387                        "".to_string()
2388                    };
2389
2390                    // Add newline and indent
2391                    let new_line_text = format!("\n{}", indent);
2392                    self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2393                    self.pause_blink_cursor(cx);
2394                }
2395            }
2396        } else {
2397            // Single line input or submit-on-enter: just emit the event
2398            // (e.g.: in a dialog to confirm, or a chat textarea to send).
2399            self.undo_manager.break_transaction_coalescing();
2400            cx.propagate();
2401        }
2402
2403        cx.emit(InputEvent::PressEnter {
2404            secondary: action.secondary,
2405            shift: action.shift,
2406        });
2407    }
2408
2409    pub fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2410        self.replace_text("", window, cx);
2411        self.set_selection(0, 0);
2412        self.scroll_to(0, None, cx);
2413    }
2414
2415    pub(super) fn escape(&mut self, action: &Escape, window: &mut Window, cx: &mut Context<Self>) {
2416        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2417            return;
2418        }
2419
2420        // Escape also dismisses a request whose popup has not arrived yet.
2421        M::hide_context_menu(self, cx);
2422
2423        // Collapse extra cursors back to the active one first.
2424        if !self.selections.is_single() {
2425            self.undo_manager.break_transaction_coalescing();
2426            self.selections.remove_all_but_active();
2427            cx.notify();
2428            return;
2429        }
2430
2431        // Clear inline completion on escape
2432        if M::has_inline_completion(self) {
2433            M::clear_inline_completion(self, cx);
2434            return; // Consume the escape, don't propagate
2435        }
2436        M::clear_inline_completion(self, cx);
2437
2438        // The handles and the edit menu are the topmost surface to dismiss.
2439        if self.touch_selection().is_some() {
2440            self.dismiss_touch_selection(cx);
2441            return;
2442        }
2443
2444        if self.ime_marked_range.is_some() {
2445            self.unmark_text(window, cx);
2446        }
2447
2448        // Android delivers its back button as escape, and back leaves a field's
2449        // text alone: it closes what is open or navigates.
2450        if self.clean_on_escape && !cfg!(target_os = "android") {
2451            return self.clean(window, cx);
2452        }
2453
2454        cx.propagate();
2455    }
2456
2457    /// Show the right-click context menu as a native OS menu.
2458    pub(crate) fn handle_right_click_menu(
2459        &mut self,
2460        position: Point<Pixels>,
2461        offset: usize,
2462        window: &mut Window,
2463        cx: &mut Context<Self>,
2464    ) {
2465        if self.disabled {
2466            return;
2467        }
2468        if crate::GlobalState::is_in_deferred_context(cx) {
2469            return;
2470        }
2471
2472        if !self.active_selection().contains(offset) {
2473            self.move_to(offset, None, cx);
2474        }
2475
2476        if self.is_code_editor() {
2477            M::on_hover_definition(self, offset, window, cx);
2478        }
2479
2480        if let Some(handler) = self.context_menu_handler.clone() {
2481            let capabilities = self.context_menu_capabilities();
2482            let input = cx.weak_entity();
2483            // The handler runs with the input released, so a custom builder can
2484            // read it (cursor, selection, text) to build its items.
2485            window.defer(cx, move |window, cx| {
2486                if input.upgrade().is_none() {
2487                    return;
2488                }
2489                let previous_focus = window.focused(cx);
2490                handler(NativeMenu::new(), capabilities, position, window, cx);
2491                // A drawn menu takes focus synchronously; native menus leave it
2492                // on the input. Capture it here rather than in the handler.
2493                input
2494                    .update(cx, |this, cx| {
2495                        if window.focused(cx) == previous_focus
2496                            && !this.focus_handle.is_focused(window)
2497                            && this.has_selection_focus(window, cx)
2498                        {
2499                            // A custom handler can reuse an already-focused popup.
2500                            return;
2501                        }
2502                        let menu_focus = window.focused(cx).filter(|focus| {
2503                            Some(focus) != previous_focus.as_ref() && *focus != this.focus_handle
2504                        });
2505                        this.set_selection_focus(menu_focus, cx);
2506                    })
2507                    .ok();
2508            });
2509        }
2510    }
2511
2512    pub(super) fn add_cursor_above(
2513        &mut self,
2514        _: &AddCursorAbove,
2515        _: &mut Window,
2516        cx: &mut Context<Self>,
2517    ) {
2518        self.add_cursor_vertical(-1, cx);
2519    }
2520
2521    pub(super) fn add_cursor_below(
2522        &mut self,
2523        _: &AddCursorBelow,
2524        _: &mut Window,
2525        cx: &mut Context<Self>,
2526    ) {
2527        self.add_cursor_vertical(1, cx);
2528    }
2529
2530    /// Add a new cursor one display line above (`move_lines < 0`) or below each
2531    /// existing cursor, preserving the column. Cursors that would not move (at
2532    /// the first/last display row) or that would duplicate an existing cursor
2533    /// are skipped.
2534    fn add_cursor_vertical(&mut self, move_lines: isize, cx: &mut Context<Self>) {
2535        if !self.is_multi_line() {
2536            return;
2537        }
2538
2539        self.pause_blink_cursor(cx);
2540        // Changing the cursor set ends the editing gesture that came before it.
2541        self.undo_manager.break_transaction_coalescing();
2542
2543        let sources: Vec<(usize, Option<(Pixels, usize)>, bool)> = self
2544            .selections
2545            .iter()
2546            .map(|sel| {
2547                (
2548                    sel.cursor_offset(),
2549                    sel.column_anchor,
2550                    self.line_end_affinity_for(sel),
2551                )
2552            })
2553            .collect();
2554        let mut offsets: std::collections::HashSet<usize> =
2555            sources.iter().map(|(offset, _, _)| *offset).collect();
2556
2557        let mut newest: Option<usize> = None;
2558        for (offset, anchor, line_end_affinity) in sources {
2559            let anchor = anchor.or_else(|| self.preferred_column_for(offset));
2560            let (target, _) = self.vertical_target(offset, anchor, line_end_affinity, move_lines);
2561            if target == offset || offsets.contains(&target) {
2562                continue;
2563            }
2564            offsets.insert(target);
2565            let id = self.selections.generate_id();
2566            let mut cursor = CursorSelection::new(id, target, target);
2567            cursor.column_anchor = anchor;
2568            self.selections.add(cursor);
2569            newest = Some(target);
2570        }
2571
2572        if let Some(newest) = newest {
2573            self.scroll_to(newest, None, cx);
2574        }
2575        cx.notify();
2576    }
2577
2578    /// Add an additional collapsed cursor at `offset`.
2579    ///
2580    /// Rejected when `offset` lands inside an existing selection or exactly on
2581    /// an existing cursor.
2582    pub(super) fn add_cursor_at(&mut self, offset: usize, cx: &mut Context<Self>) {
2583        if !self.is_multi_line() {
2584            return;
2585        }
2586
2587        for sel in self.selections.iter() {
2588            if sel.contains(offset) {
2589                return;
2590            }
2591            if sel.is_collapsed() && sel.cursor_offset() == offset {
2592                return;
2593            }
2594        }
2595
2596        self.undo_manager.break_transaction_coalescing();
2597        let id = self.selections.generate_id();
2598        let mut selection = CursorSelection::new(id, offset, offset);
2599        selection.column_anchor = self.preferred_column_for(offset);
2600        self.selections.add(selection);
2601        cx.notify();
2602    }
2603
2604    /// Build a columnar (block) selection spanning the rows between the two
2605    /// points, one selection per display row at the same column span.
2606    ///
2607    /// The span keeps the columns the pointer sat past the end of a short row, so a row
2608    /// that runs out of text does not narrow the block for the rows that do reach that
2609    /// far. Each row is then clipped to its own end, selecting as much of the span as it
2610    /// actually has.
2611    pub(super) fn build_columnar_selection(
2612        &mut self,
2613        start: ColumnarPoint,
2614        end: ColumnarPoint,
2615        cx: &mut Context<Self>,
2616    ) {
2617        if !self.is_multi_line() {
2618            return;
2619        }
2620
2621        self.undo_manager.break_transaction_coalescing();
2622
2623        let (start_row, start_col) = self.columnar_row_column(start);
2624        let (end_row, end_col) = self.columnar_row_column(end);
2625        let (start_row, end_row) = (start_row.min(end_row), start_row.max(end_row));
2626        let (start_col, end_col) = (start_col.min(end_col), start_col.max(end_col));
2627
2628        let mut new_selections = Vec::with_capacity(end_row - start_row + 1);
2629        for row in start_row..=end_row {
2630            let sel_start = self
2631                .display_map
2632                .display_row_column_to_offset(row, start_col);
2633            let sel_end = self.display_map.display_row_column_to_offset(row, end_col);
2634            let id = self.selections.generate_id();
2635            let sel_start = self.text.clip_offset(sel_start, Bias::Left);
2636            let sel_end = self.text.clip_offset(sel_end, Bias::Left);
2637            new_selections.push(CursorSelection::new(id, sel_start, sel_end));
2638        }
2639
2640        self.selections.replace_all(new_selections);
2641        cx.notify();
2642    }
2643
2644    /// Resolve a columnar point to the display row it is on and the column it reaches,
2645    /// counting the columns beyond the row's end so a short row keeps the full span.
2646    fn columnar_row_column(&self, point: ColumnarPoint) -> (usize, usize) {
2647        let display_point = self.display_map.offset_to_wrap_display_point(point.offset);
2648        let row = self
2649            .display_map
2650            .wrap_row_to_display_row(display_point.row)
2651            .unwrap_or_else(|| {
2652                self.display_map
2653                    .nearest_visible_display_row(display_point.row)
2654            });
2655
2656        (row, display_point.column + point.columns_past_line_end)
2657    }
2658
2659    pub(super) fn on_mouse_down(
2660        &mut self,
2661        event: &MouseDownEvent,
2662        window: &mut Window,
2663        cx: &mut Context<Self>,
2664    ) {
2665        // A token that consumed the press (a double click selecting it) must
2666        // not also place the caret.
2667        if window.default_prevented() {
2668            return;
2669        }
2670        // A tap asks for the virtual keyboard. Focus alone does not when it
2671        // moves from another input, or stays here, after the user hid it.
2672        if event.button == MouseButton::Left
2673            && crate::GlobalState::is_touch_press(cx)
2674            && !self.disabled
2675            && !self.readonly
2676        {
2677            window.request_virtual_keyboard();
2678        }
2679        self.undo_manager.break_transaction_coalescing();
2680        // Treat every press like a cursor move: take focus now rather than
2681        // after GPUI's focus observer runs, show the caret at once and restart
2682        // the idle delay, whatever phase the blink was in.
2683        if !self.disabled {
2684            self.focus(window, cx);
2685            self.pause_blink_cursor(cx);
2686        }
2687        // Input has its own text selection; suppress the window-level text
2688        // selection (Root) so it does not start a drag from here.
2689        crate::global_state::GlobalState::suppress_text_selection(cx);
2690
2691        // Clear inline completion on any mouse interaction
2692        M::clear_inline_completion(self, cx);
2693        // A tap on the touch selection asks for its menu back. Any other press
2694        // places the caret or starts a new drag, and the handles and the menu
2695        // no longer belong to what is selected.
2696        if event.button == MouseButton::Left
2697            && event.click_count == 1
2698            && self.reopen_edit_menu_at(event.position, cx)
2699        {
2700            return;
2701        }
2702        self.dismiss_touch_selection(cx);
2703
2704        // If there have IME marked range and is empty (Means pressed Esc to abort IME typing)
2705        // Clear the marked range.
2706        if let Some(ime_marked_range) = &self.ime_marked_range {
2707            if ime_marked_range.len() == 0 {
2708                self.ime_marked_range = None;
2709            }
2710        }
2711
2712        self.selecting = true;
2713        let (offset, line_end_affinity, columns_past_line_end) =
2714            self.resolve_mouse_position(event.position);
2715
2716        if M::on_click(self, event, offset, window, cx) {
2717            return;
2718        }
2719
2720        // Triple click to select line
2721        if event.button == MouseButton::Left && event.click_count >= 3 {
2722            self.select_line(offset, window, cx);
2723            return;
2724        }
2725
2726        // Double click to select word
2727        if event.button == MouseButton::Left && event.click_count == 2 {
2728            self.select_word(offset, window, cx);
2729            // A double tap is touch's other way to select a word, and it
2730            // gets the handles and the menu like a long press does.
2731            if crate::GlobalState::is_touch_press(cx) {
2732                self.keep_touch_selection(cx);
2733            }
2734            return;
2735        }
2736
2737        // Show Mouse context menu
2738        if event.button == MouseButton::Right {
2739            if self.enable_context_menu {
2740                if !self.active_selection().contains(offset) {
2741                    self.move_to(offset, None, cx);
2742                }
2743                self.pending_context_menu = Some((event.position, offset));
2744            }
2745            return;
2746        }
2747
2748        // Multi-cursor placement, multi-line only.
2749        if self.is_multi_line() && event.button == MouseButton::Left {
2750            if event.modifiers.alt
2751                && (event.modifiers.shift || (cfg!(target_os = "linux") && event.modifiers.control))
2752            {
2753                // Alt+Shift starts a block; Linux also accepts Ghostty's Ctrl+Alt.
2754                // Mark selecting so the drag handler extends the block.
2755                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2756                self.selecting = true;
2757                self.move_to_with_affinity(offset, None, line_end_affinity, cx);
2758                return;
2759            } else if event.modifiers.alt {
2760                self.add_cursor_at(offset, cx);
2761                // Keep click-to-add behavior, but use this press as the block
2762                // anchor if the user continues dragging with the left button.
2763                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2764                return;
2765            }
2766        }
2767
2768        if event.modifiers.shift {
2769            self.select_to_with_affinity(offset, line_end_affinity, cx);
2770        } else {
2771            self.move_to_with_affinity(offset, None, line_end_affinity, cx)
2772        }
2773    }
2774
2775    pub(super) fn on_mouse_up(
2776        &mut self,
2777        event: &MouseUpEvent,
2778        window: &mut Window,
2779        cx: &mut Context<Self>,
2780    ) {
2781        if event.button == MouseButton::Right {
2782            if let Some((position, offset)) = self.pending_context_menu.take() {
2783                self.handle_right_click_menu(position, offset, window, cx);
2784            }
2785        }
2786        if self.active_selection().is_empty() {
2787            self.active_selection_mut().reversed = false;
2788        }
2789        self.selecting = false;
2790        self.selected_word_range = None;
2791        self.column_select_start = None;
2792        self.auto_scroll.stop();
2793    }
2794
2795    pub(super) fn on_mouse_move(
2796        &mut self,
2797        event: &MouseMoveEvent,
2798        window: &mut Window,
2799        cx: &mut Context<Self>,
2800    ) {
2801        // Check if mouse is within bounds
2802        let within_bounds = self
2803            .last_bounds
2804            .as_ref()
2805            .map(|bounds| bounds.contains(&event.position))
2806            .unwrap_or(false);
2807
2808        if !within_bounds {
2809            // Clear hover when mouse leaves the input
2810            M::clear_hover_state(self, cx);
2811            return;
2812        }
2813
2814        // Show diagnostic popover on mouse move
2815        let (offset, _) = self.index_for_mouse_position(event.position);
2816        M::on_mouse_move(self, offset, event, window, cx);
2817
2818        if self.is_code_editor() {
2819            // Keep the current popover while the pointer stays on the same
2820            // diagnostic, so moving over a squiggle neither repaints nor
2821            // rebuilds the popover.
2822            match self
2823                .mode
2824                .diagnostics()
2825                .and_then(|set| set.for_offset(offset))
2826            {
2827                Some(diagnostic) => {
2828                    if self.diagnostic_popover.as_deref() != Some(diagnostic) {
2829                        self.diagnostic_popover = Some(Rc::new(diagnostic.clone()));
2830                        cx.notify();
2831                    }
2832                }
2833                None => {
2834                    if self.diagnostic_popover.take().is_some() {
2835                        cx.notify();
2836                    }
2837                }
2838            }
2839        }
2840    }
2841
2842    pub(super) fn on_scroll_wheel(
2843        &mut self,
2844        event: &ScrollEvent,
2845        window: &mut Window,
2846        cx: &mut Context<Self>,
2847    ) {
2848        let line_height = self
2849            .last_layout
2850            .as_ref()
2851            .map(|layout| layout.line_height)
2852            .unwrap_or(window.line_height());
2853        let delta = event.delta.pixel_delta(line_height);
2854
2855        let old_offset = self.scroll_handle.offset();
2856        let precise = matches!(event.delta, gpui::ScrollDelta::Pixels(_));
2857        if !precise || !self.scroll_bounce || cx.reduce_motion() || !self.overflows_vertically() {
2858            let displaced = self.overscroll.offset() != 0.;
2859            self.overscroll.reset();
2860            if displaced {
2861                cx.notify();
2862            }
2863        }
2864        let bounces = self.scroll_bounce
2865            && precise
2866            && !cx.reduce_motion()
2867            && delta.y.abs() >= delta.x.abs()
2868            && self.overflows_vertically();
2869        if bounces {
2870            let mut overscroll = std::mem::take(&mut self.overscroll);
2871            let stretched = overscroll.packet(
2872                delta.y.as_f32(),
2873                event.touch_phase,
2874                self.input_bounds.size.height.as_f32(),
2875                cx.background_executor().now(),
2876                |dy| {
2877                    let before = self.scroll_handle.offset();
2878                    self.update_scroll_offset(
2879                        Some(point(before.x + delta.x, before.y + px(dy))),
2880                        cx,
2881                    );
2882                    (self.scroll_handle.offset().y - before.y).as_f32()
2883                },
2884            );
2885            self.overscroll = overscroll;
2886            if stretched {
2887                cx.notify();
2888            }
2889        } else {
2890            // Axis drift must not drop the end of a vertical drag.
2891            if matches!(
2892                event.touch_phase,
2893                gpui::TouchPhase::Ended | gpui::TouchPhase::Cancelled
2894            ) && self.overscroll.offset() != 0.
2895            {
2896                self.overscroll.packet(
2897                    0.,
2898                    event.touch_phase,
2899                    self.input_bounds.size.height.as_f32(),
2900                    cx.background_executor().now(),
2901                    |_| 0.,
2902                );
2903                cx.notify();
2904            }
2905            self.update_scroll_offset(Some(old_offset + delta), cx);
2906        }
2907
2908        // Text that has room to scroll keeps a scroll over it even where the
2909        // text ends, so reaching the end does not scroll the region around the
2910        // input; text that fits leaves the scroll to that region.
2911        if self.scroll_handle.offset() != old_offset || self.overflows_along(delta) {
2912            cx.stop_propagation();
2913        }
2914
2915        if self.diagnostic_popover.take().is_some() {
2916            cx.notify();
2917        }
2918        // The handles follow the text; the menu would sit over whatever
2919        // scrolls underneath it, so it steps aside until the finger lifts.
2920        self.edit_menu_on_scroll(event.touch_phase, cx);
2921    }
2922
2923    /// Whether the text takes a wheel event moving by `delta` from the
2924    /// scroll areas around it: it has room to scroll along the axis the
2925    /// event mostly moves on. Unlike [`Self::overflows_along`], an event that
2926    /// moves on neither axis, such as a gesture's start or end, goes to text
2927    /// that can scroll vertically.
2928    pub(super) fn takes_scroll(&self, delta: Point<Pixels>) -> bool {
2929        if delta.y.abs() >= delta.x.abs() {
2930            self.overflows_vertically()
2931        } else {
2932            self.scroll_size.width > self.input_bounds.size.width
2933        }
2934    }
2935
2936    /// Whether the text has room to scroll along the axis `delta` mostly
2937    /// moves on. A trackpad swipe drifts a little off its axis, so the drift
2938    /// alone does not claim the scroll.
2939    fn overflows_along(&self, delta: Point<Pixels>) -> bool {
2940        if delta.y.abs() >= delta.x.abs() {
2941            delta.y != px(0.) && self.overflows_vertically()
2942        } else {
2943            self.scroll_size.width > self.input_bounds.size.width
2944        }
2945    }
2946
2947    /// Whether multi-line text is taller than the input.
2948    fn overflows_vertically(&self) -> bool {
2949        self.is_multi_line() && self.scroll_size.height > self.input_bounds.size.height
2950    }
2951
2952    pub(super) fn update_scroll_offset(
2953        &mut self,
2954        offset: Option<Point<Pixels>>,
2955        cx: &mut Context<Self>,
2956    ) {
2957        let mut offset = offset.unwrap_or(self.scroll_handle.offset());
2958        let safe_y_range =
2959            (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
2960
2961        offset.y = if self.is_single_line() {
2962            px(0.)
2963        } else {
2964            offset.y.clamp(safe_y_range.start, safe_y_range.end)
2965        };
2966        offset.x = clamp_horizontal_scroll_offset(
2967            offset.x,
2968            self.scroll_size.width,
2969            self.input_bounds.size.width,
2970            self.text_align,
2971        );
2972        if self.scroll_handle.offset() != offset {
2973            self.scroll_handle.set_offset(offset);
2974            cx.notify();
2975        }
2976    }
2977
2978    /// Scroll to make the given offset visible.
2979    ///
2980    /// If `direction` is Some, will keep edges at the same side.
2981    pub(crate) fn scroll_to(
2982        &mut self,
2983        offset: usize,
2984        direction: Option<MoveDirection>,
2985        cx: &mut Context<Self>,
2986    ) {
2987        let padding = if direction.is_some() {
2988            ScrollPadding::SurroundingLines
2989        } else {
2990            ScrollPadding::Minimal
2991        };
2992        self.scroll_to_with_padding(offset, direction, padding, cx);
2993    }
2994
2995    /// Reveal an offset with independently chosen direction restriction and padding.
2996    /// Search uses surrounding lines without restricting movement to match order.
2997    pub(crate) fn scroll_to_with_padding(
2998        &mut self,
2999        offset: usize,
3000        direction: Option<MoveDirection>,
3001        padding: ScrollPadding,
3002        cx: &mut Context<Self>,
3003    ) {
3004        let Some(last_layout) = self.last_layout.as_ref() else {
3005            return;
3006        };
3007        let Some(bounds) = self.last_bounds.as_ref() else {
3008            return;
3009        };
3010
3011        let mut scroll_offset = self.scroll_handle.offset();
3012        let was_offset = scroll_offset;
3013        let line_height = last_layout.line_height;
3014
3015        let point = self.text.offset_to_point(offset);
3016
3017        let row = point.row;
3018
3019        // Resolve the wrapped row even when the target is outside the last layout.
3020        let display_row = self
3021            .display_map
3022            .offset_to_display_row(offset, self.line_end_affinity_at(offset));
3023        let row_offset_y = line_height * display_row;
3024
3025        // For Right alignment use 0 margin: the cursor indicator is clamped inside bounds
3026        // in layout_cursors, so shifting the text here would cause a first-click visual jump.
3027        let safety_margin = match last_layout.text_align {
3028            TextAlign::Left => RIGHT_MARGIN,
3029            TextAlign::Right => px(0.),
3030            TextAlign::Center => CURSOR_WIDTH,
3031        };
3032        if let Some(vi) = last_layout
3033            .visible_buffer_lines
3034            .iter()
3035            .position(|&line| line == row)
3036        {
3037            let line = &last_layout.lines[vi];
3038            let local_offset = offset.saturating_sub(last_layout.visible_line_byte_offsets[vi]);
3039            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
3040                let bounds_width = bounds.size.width - last_layout.line_number_width;
3041                let col_offset_x = pos.x;
3042                if col_offset_x - safety_margin < -scroll_offset.x {
3043                    // If the position is out of the visible area, scroll to make it visible
3044                    scroll_offset.x = -col_offset_x + safety_margin;
3045                } else if col_offset_x + safety_margin > -scroll_offset.x + bounds_width {
3046                    scroll_offset.x = -(col_offset_x - bounds_width + safety_margin);
3047                }
3048            }
3049        }
3050
3051        // Scroll the row into view. Use the same edge clearance helper as
3052        // `TextElement::layout_cursors` so both scroll-into-view paths agree
3053        // (a mismatch flickered on `Down` at end-of-buffer with a small
3054        // `cursor_surrounding_lines` override).
3055        let edge_height =
3056            if matches!(padding, ScrollPadding::SurroundingLines) && self.is_code_editor() {
3057                super::element::cursor_surrounding_padding(
3058                    self.mode.is_auto_grow(),
3059                    self.cursor_surrounding_lines,
3060                    super::element::viewport_visible_lines(bounds.size.height, line_height),
3061                    line_height,
3062                )
3063            } else {
3064                px(0.)
3065            };
3066        scroll_offset.y = super::element::reveal_row(
3067            scroll_offset.y,
3068            row_offset_y,
3069            line_height,
3070            bounds.size.height,
3071            edge_height,
3072        );
3073
3074        // Avoid necessary scroll, when it was already in the correct position.
3075        if direction == Some(MoveDirection::Up) {
3076            scroll_offset.y = scroll_offset.y.max(was_offset.y);
3077        } else if direction == Some(MoveDirection::Down) {
3078            scroll_offset.y = scroll_offset.y.min(was_offset.y);
3079        }
3080
3081        // Clamp the deferred target into the same safe range that
3082        // `update_scroll_offset` enforces on persist, so paint never shows an
3083        // over-scrolled frame before the post-paint clamp pulls it back.
3084        let safe_y_min = (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.));
3085        scroll_offset.x = scroll_offset.x.min(px(0.));
3086        scroll_offset.y = scroll_offset.y.clamp(safe_y_min, px(0.));
3087        self.deferred_scroll_offset = Some(scroll_offset);
3088        cx.notify();
3089    }
3090
3091    pub(super) fn show_character_palette(
3092        &mut self,
3093        _: &ShowCharacterPalette,
3094        window: &mut Window,
3095        _: &mut Context<Self>,
3096    ) {
3097        window.show_character_palette();
3098    }
3099
3100    /// The text of every non-empty selection, in document order.
3101    fn selected_texts(&self) -> Vec<String> {
3102        let mut selections: Vec<CursorSelection> = self
3103            .selections
3104            .iter()
3105            .copied()
3106            .filter(|sel| !sel.is_empty())
3107            .collect();
3108        selections.sort_by_key(|sel| sel.start);
3109        selections
3110            .iter()
3111            .map(|sel| self.text.slice(*sel).to_string())
3112            .collect()
3113    }
3114
3115    pub(super) fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
3116        if !self.is_copyable() {
3117            return;
3118        }
3119
3120        let texts = self.selected_texts();
3121
3122        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
3123    }
3124
3125    pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
3126        if !self.is_copyable() {
3127            return;
3128        }
3129
3130        let texts = self.selected_texts();
3131
3132        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
3133
3134        self.undo_manager.set_pending_intent(EditIntent::Atomic);
3135        self.replace_text_in_range_silent(None, "", window, cx);
3136    }
3137
3138    pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
3139        if !self.is_editable() {
3140            return;
3141        }
3142        if let Some(clipboard) = cx.read_from_clipboard() {
3143            self.insert_clipboard(clipboard, window, cx);
3144            return;
3145        }
3146        // The synchronous read is empty on platforms whose clipboard is
3147        // asynchronous and permission-gated (the web), so fall back to the
3148        // real read. It has to start here, still inside the user activation
3149        // that dispatched `Paste`, or the browser refuses the read.
3150        let read = cx.read_from_clipboard_async();
3151        let target = self.paste_target();
3152        cx.spawn_in(window, async move |this, cx| match read.await {
3153            Ok(Some(clipboard)) => {
3154                this.update_in(cx, |this, window, cx| {
3155                    // The read can sit behind a permission prompt for as
3156                    // long as the user likes. If they edited, moved the
3157                    // caret or left the input meanwhile, the paste would
3158                    // land where they no longer mean it to; drop it, as the
3159                    // browser's own paste event only reaches the focused
3160                    // input too.
3161                    if this.is_editable()
3162                        && this.focus_handle.is_focused(window)
3163                        && this.paste_target() == target
3164                    {
3165                        this.insert_clipboard(clipboard, window, cx);
3166                    }
3167                })
3168                .ok();
3169            }
3170            Ok(None) => {}
3171            Err(error) => tracing::warn!("failed to read the clipboard for paste: {error}"),
3172        })
3173        .detach();
3174    }
3175
3176    /// Where a paste would go right now: the document as edited so far and
3177    /// the selections it would replace. Two equal targets mean an edit made
3178    /// for one still applies to the other.
3179    fn paste_target(&self) -> PasteTarget {
3180        PasteTarget {
3181            document_revision: self.document_revision,
3182            selections: self.selections.iter().copied().collect(),
3183        }
3184    }
3185
3186    /// Inserts the clipboard text the way `Paste` does: one atomic edit,
3187    /// newlines dropped on a single-line input, one line per selection when
3188    /// the counts match on a multi-line one. A clipboard without text (an
3189    /// image, say) is left alone rather than replacing the selection with
3190    /// nothing.
3191    fn insert_clipboard(
3192        &mut self,
3193        clipboard: ClipboardItem,
3194        window: &mut Window,
3195        cx: &mut Context<Self>,
3196    ) {
3197        let Some(mut new_text) = clipboard.text().filter(|text| !text.is_empty()) else {
3198            return;
3199        };
3200        // A paste is one atomic edit, never part of a typing run.
3201        self.undo_manager.set_pending_intent(EditIntent::Atomic);
3202
3203        if !self.is_multi_line() {
3204            new_text = new_text.replace('\n', "");
3205            self.replace_text_in_range_silent(None, &new_text, window, cx);
3206            self.scroll_to(self.cursor(), None, cx);
3207            return;
3208        }
3209
3210        // Distribute one clipboard line per selection when the counts match.
3211        // Otherwise insert the whole clipboard text at each cursor.
3212        if !self.selections.is_single() {
3213            self.selections.merge_overlapping();
3214        }
3215        let lines: Vec<String> = new_text.split('\n').map(|s| s.to_string()).collect();
3216        let count = self.selections.len();
3217        if count > 1 && lines.len() == count {
3218            let mut selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
3219            selections.sort_by_key(|sel| sel.start);
3220            let edits: Vec<(Range<usize>, String)> = selections
3221                .iter()
3222                .zip(lines)
3223                .map(|(sel, line)| (sel.start..sel.end, line))
3224                .collect();
3225            self.replace_text_in_ranges(&edits, window, cx);
3226        } else {
3227            self.replace_text_in_range_silent(None, &new_text, window, cx);
3228        }
3229        self.scroll_to(self.cursor(), None, cx);
3230    }
3231
3232    /// The intent of a batch the caller did not label: inserting text at
3233    /// collapsed cursors is typing, anything else stands on its own.
3234    fn typing_intent(&self, edits: &[(Range<usize>, String)], new_text: &str) -> EditIntent {
3235        if !new_text.is_empty()
3236            && !new_text.contains(['\n', '\r'])
3237            && edits.iter().all(|(range, _)| range.is_empty())
3238        {
3239            EditIntent::Typing
3240        } else {
3241            EditIntent::Atomic
3242        }
3243    }
3244
3245    /// Where a cursor stood before an edit made with this intent.
3246    ///
3247    /// Backspace and forward delete expand a collapsed cursor over the text
3248    /// they are about to remove, so the recorded cursor has to collapse back to
3249    /// the side it came from for undo to restore it where the user left it.
3250    fn collapse_for_intent(
3251        intent: EditIntent,
3252        mut selection: CursorSelection,
3253        range: &Range<usize>,
3254    ) -> CursorSelection {
3255        match intent {
3256            EditIntent::Backspace => selection.place_at(range.end, None),
3257            EditIntent::DeleteForward => selection.place_at(range.start, None),
3258            EditIntent::Typing | EditIntent::Atomic => {}
3259        }
3260        selection
3261    }
3262
3263    /// Whether an edit rewrites the character before a collapsed cursor, as the
3264    /// macOS Korean IME does on each keystroke instead of marking text.
3265    fn rewrites_typed_char(
3266        &self,
3267        selection: CursorSelection,
3268        range: &Range<usize>,
3269        old_text: &str,
3270        new_text: &str,
3271    ) -> bool {
3272        !self.silent_replace_text
3273            && selection.is_collapsed()
3274            && selection.cursor_offset() == range.end
3275            && old_text.chars().count() == 1
3276            && !old_text.contains(['\n', '\r'])
3277            && !new_text.contains(['\n', '\r'])
3278    }
3279
3280    fn push_history(
3281        &mut self,
3282        text: &Rope,
3283        range: &Range<usize>,
3284        new_text: &str,
3285        requested_intent: Option<EditIntent>,
3286        selection_before: CursorSelection,
3287        selection_after: Option<CursorSelection>,
3288    ) -> bool {
3289        self.document_revision = self.document_revision.wrapping_add(1);
3290        let token_delta = self.edit_tokens(range, new_text.len());
3291        if self.undo_manager.is_ignoring() {
3292            return false;
3293        }
3294
3295        let range =
3296            text.clip_offset(range.start, Bias::Left)..text.clip_offset(range.end, Bias::Right);
3297        let old_text = text.slice(range.clone()).to_string();
3298        let new_range = range.start..range.start + new_text.len();
3299
3300        let intent = requested_intent.unwrap_or_else(|| {
3301            if range.is_empty()
3302                && old_text.is_empty()
3303                && !new_text.is_empty()
3304                && !new_text.contains(['\n', '\r'])
3305                || self.rewrites_typed_char(selection_before, &range, &old_text, new_text)
3306            {
3307                EditIntent::Typing
3308            } else {
3309                EditIntent::Atomic
3310            }
3311        });
3312
3313        let selection_before = Self::collapse_for_intent(intent, selection_before, &range);
3314        let selection_after =
3315            selection_after.unwrap_or_else(|| (new_range.end..new_range.end).into());
3316
3317        let open_transaction = self.undo_manager.has_open_transaction();
3318        let mut change = Change::new(range, &old_text, new_range, new_text);
3319        change.token_delta = token_delta;
3320        let recorded = self.undo_manager.record_transaction(change, intent);
3321        // A batch records its own cursor sets. This covers a change that is a
3322        // transaction on its own.
3323        if recorded && !open_transaction {
3324            self.undo_manager
3325                .record_selections(vec![selection_before], vec![selection_after]);
3326        }
3327        recorded
3328    }
3329
3330    pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
3331        self.replay_history(true, window, cx);
3332    }
3333
3334    pub(super) fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
3335        self.replay_history(false, window, cx);
3336    }
3337
3338    fn replay_history(&mut self, undo: bool, window: &mut Window, cx: &mut Context<Self>) {
3339        if !self.is_editable() {
3340            return;
3341        }
3342        let replay = if undo {
3343            self.undo_manager.undo()
3344        } else {
3345            self.undo_manager.redo()
3346        };
3347        let Some(replay) = replay else {
3348            return;
3349        };
3350        let token_aware = self.inline_tokens.is_some()
3351            || replay
3352                .changes
3353                .iter()
3354                .any(|change| change.token_delta.is_some());
3355        let emit_events = self.emit_events;
3356        if token_aware {
3357            self.emit_events = false;
3358        }
3359        self.replaying_history = token_aware;
3360        self.undo_manager.set_ignoring(true);
3361        for change in &replay.changes {
3362            let (range, text) = if undo {
3363                (change.new_range.into(), &change.old_text)
3364            } else {
3365                (change.old_range.into(), &change.new_text)
3366            };
3367            let range_utf16 = self.range_to_utf16(&range);
3368            self.replace_text_in_range_silent(Some(range_utf16), text, window, cx);
3369            self.replay_tokens(&range, text.len(), change.token_delta.as_deref(), undo);
3370        }
3371        self.restore_selections(replay.selections);
3372        self.mode
3373            .restore_auto_closed_pairs(replay.auto_closed_pairs.unwrap_or_default());
3374        if token_aware {
3375            // Token replay bypasses IME normalization; a stale marked range
3376            // would otherwise describe text that no longer exists.
3377            self.ime_marked_range = None;
3378        }
3379        self.undo_manager.set_ignoring(false);
3380        self.replaying_history = false;
3381        self.emit_events = emit_events;
3382        if token_aware && emit_events {
3383            cx.emit(InputEvent::Change);
3384        }
3385        cx.notify();
3386    }
3387
3388    /// Restore a set of selections captured in a transaction, clamping offsets
3389    /// to the current text length. `None` leaves the current selections as the
3390    /// replay left them.
3391    fn restore_selections(&mut self, selections: Option<Vec<CursorSelection>>) {
3392        let Some(selections) = selections else {
3393            return;
3394        };
3395
3396        let len = self.text.len();
3397        let restored: Vec<CursorSelection> = selections
3398            .into_iter()
3399            .map(|mut sel| {
3400                sel.start = sel.start.min(len);
3401                sel.end = sel.end.min(len);
3402                sel
3403            })
3404            .collect();
3405        self.selections.replace_all(restored);
3406        self.selections.merge_overlapping();
3407    }
3408
3409    /// Get byte offset of the cursor.
3410    ///
3411    /// The offset is the UTF-8 offset.
3412    pub fn cursor(&self) -> usize {
3413        if let Some(ime_marked_range) = &self.ime_marked_range {
3414            return ime_marked_range.end;
3415        }
3416
3417        self.selections.active().cursor_offset()
3418    }
3419
3420    /// Returns the active selection.
3421    pub(super) fn active_selection(&self) -> &CursorSelection {
3422        self.selections.active()
3423    }
3424
3425    /// Returns a mutable reference to the active selection.
3426    pub(super) fn active_selection_mut(&mut self) -> &mut CursorSelection {
3427        self.selections.active_mut()
3428    }
3429
3430    /// Sets the active selection to the given range, keeping its `reversed`
3431    /// and `column_anchor` state untouched.
3432    pub(super) fn set_selection(&mut self, start: usize, end: usize) {
3433        let range = self.normalize_token_range(start..end);
3434        let (start, end) = (range.start, range.end);
3435        let active = self.active_selection_mut();
3436        active.start = start;
3437        active.end = end;
3438    }
3439
3440    /// Collapses the active selection to a cursor at the given offset,
3441    /// clearing `reversed`.
3442    pub(super) fn set_cursor_to(&mut self, offset: usize) {
3443        let offset = self.token_boundary(offset, Bias::Right);
3444        let active = self.active_selection_mut();
3445        active.start = offset;
3446        active.end = offset;
3447        active.reversed = false;
3448    }
3449
3450    /// Visible row range in the last laid-out viewport, `None` before first layout.
3451    pub fn visible_row_range(&self) -> Option<std::ops::Range<usize>> {
3452        self.last_layout.as_ref().map(|l| l.visible_range.clone())
3453    }
3454
3455    /// Current scroll offset of the editor viewport.
3456    pub fn scroll_offset(&self) -> gpui::Point<gpui::Pixels> {
3457        self.scroll_handle.offset()
3458    }
3459
3460    /// Set scroll offset of the editor viewport.
3461    ///
3462    /// The offset will be clamped to the valid range, and applied after the next layout.
3463    pub fn set_scroll_offset(&mut self, offset: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
3464        self.deferred_scroll_offset = Some(offset);
3465        cx.notify();
3466    }
3467
3468    /// Laid-out line height; `None` before first layout.
3469    pub fn line_height(&self) -> Option<gpui::Pixels> {
3470        self.last_layout.as_ref().map(|l| l.line_height)
3471    }
3472
3473    /// Returns the active selection as a byte range into the text.
3474    ///
3475    /// With multiple cursors, this reads only the active selection.
3476    ///
3477    /// The range is empty (`start == end`) when no text is selected; in
3478    /// that case the offset equals `cursor()`. Byte offsets are measured
3479    /// in the underlying rope's byte units.
3480    pub fn selected_range(&self) -> std::ops::Range<usize> {
3481        (*self.selections.active()).into()
3482    }
3483
3484    pub fn select_all(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3485        self.undo_manager.break_transaction_coalescing();
3486        self.selections.remove_all_but_active();
3487        self.set_selection(0, self.text.len());
3488        cx.notify();
3489    }
3490
3491    /// Set the selected range using UTF-8 byte offsets, removing additional cursors.
3492    ///
3493    /// Non-empty ranges expand to character boundaries. Empty ranges remain empty and are
3494    /// clipped to the preceding character boundary.
3495    pub fn set_selected_range(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3496        let range = self.normalize_token_range(range);
3497        let end_bias = if range.start == range.end {
3498            Bias::Left
3499        } else {
3500            Bias::Right
3501        };
3502        let start = self.text.clip_offset(range.start, Bias::Left);
3503        let end = self.text.clip_offset(range.end, end_bias);
3504
3505        self.move_to(start, None, cx);
3506        self.active_selection_mut().reversed = false;
3507        self.selected_word_range = None;
3508        self.select_to(end, cx);
3509    }
3510
3511    /// Add `range` as an additional selection, keeping the existing ones —
3512    /// the programmatic equivalent of Alt-clicking another cursor into the
3513    /// text.
3514    ///
3515    /// The range is clipped to the text and expanded to character
3516    /// boundaries, like [`Self::set_selected_range`]. A range already
3517    /// covered by a selection adds nothing. Overlapping selections are merged.
3518    /// On a single-line input there is no second cursor to add, so the call
3519    /// replaces the selection instead.
3520    /// The view scrolls to the new selection.
3521    pub fn add_selection(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3522        if !self.is_multi_line() {
3523            self.set_selected_range(range, cx);
3524            return;
3525        }
3526
3527        let range = self.normalize_token_range(range);
3528        let end_bias = if range.start == range.end {
3529            Bias::Left
3530        } else {
3531            Bias::Right
3532        };
3533        let start = self.cursor_boundary(range.start, Bias::Left);
3534        // Match set_selected_range: expand UTF-8 boundaries first, then place
3535        // the selection endpoint without splitting a CRLF newline.
3536        let end = self.cursor_boundary(self.text.clip_offset(range.end, end_bias), Bias::Left);
3537
3538        for sel in self.selections.iter() {
3539            if sel.start <= start && end <= sel.end {
3540                return;
3541            }
3542        }
3543
3544        self.undo_manager.break_transaction_coalescing();
3545        self.selected_word_range = None;
3546        self.pause_blink_cursor(cx);
3547        M::hide_context_menu(self, cx);
3548        M::clear_inline_completion(self, cx);
3549        let id = self.selections.generate_id();
3550        let mut selection = CursorSelection::new(id, start, end);
3551        selection.column_anchor = self.preferred_column_for(end);
3552        self.selections.add(selection);
3553        self.selections.merge_overlapping();
3554        self.scroll_to(end, None, cx);
3555        cx.notify();
3556    }
3557
3558    /// Resolve a mouse position to a byte offset in the text.
3559    ///
3560    /// Also reports the caret's line-end affinity for that offset: `true` when the position
3561    /// landed on the wrap boundary of a non-final visual row, meaning the caret belongs at the
3562    /// end of that row rather than at the start of the next one. Callers that place or extend a
3563    /// selection must pass it on, or clicking past the last glyph of a wrapped row leaves a
3564    /// caret one row below the pointer.
3565    pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> (usize, bool) {
3566        let (offset, line_end_affinity, _) = self.resolve_mouse_position(position);
3567        (offset, line_end_affinity)
3568    }
3569
3570    /// Resolve a mouse position to a text offset, the caret affinity to use for it, and
3571    /// how many columns the pointer sits past the end of that row.
3572    ///
3573    /// Only a columnar selection needs the third value; everywhere else a position past
3574    /// the end of a row means the end of that row, and
3575    /// [`Self::index_for_mouse_position`] is the call to make.
3576    pub(super) fn resolve_mouse_position(&self, position: Point<Pixels>) -> (usize, bool, usize) {
3577        // If the text is empty, always return 0
3578        if self.text.len() == 0 {
3579            return (0, false, 0);
3580        }
3581
3582        let (Some(bounds), Some(last_layout)) =
3583            (self.last_bounds.as_ref(), self.last_layout.as_ref())
3584        else {
3585            return (0, false, 0);
3586        };
3587
3588        let line_height = last_layout.line_height;
3589        let line_number_width = last_layout.line_number_width;
3590
3591        // TIP: About the IBeam cursor
3592        //
3593        // If cursor style is IBeam, the mouse mouse position is in the middle of the cursor (This is special in OS)
3594
3595        // The position is relative to the bounds of the text input
3596        //
3597        // bounds.origin:
3598        //
3599        // - included the input padding.
3600        // - included the scroll offset.
3601        let inner_position = position - bounds.origin - point(line_number_width, px(0.));
3602
3603        let mut y_offset = last_layout.visible_top;
3604        // Position relative to the last line walked, kept for a pointer that ends up
3605        // below every line.
3606        let mut last_line_pos = None;
3607
3608        // Traverse visible buffer lines (compact, no hidden entries)
3609        for (vi, (line_layout, _buffer_line)) in last_layout
3610            .lines
3611            .iter()
3612            .zip(last_layout.visible_buffer_lines.iter())
3613            .enumerate()
3614        {
3615            let line_start_offset = last_layout.visible_line_byte_offsets[vi];
3616
3617            // Calculate line origin for this display row
3618            let line_origin = point(px(0.), y_offset);
3619            let pos = inner_position - line_origin;
3620
3621            // Return offset by use closest_index_for_x if is single line mode.
3622            if self.is_single_line() {
3623                let local_index = line_layout.closest_index_for_x(pos.x, last_layout);
3624                // A single line never wraps, so there is no boundary to disambiguate.
3625                return (
3626                    self.resolve_index(line_start_offset + local_index),
3627                    false,
3628                    0,
3629                );
3630            }
3631
3632            // Check if mouse is in this line's bounds
3633            if let Some((local_index, line_end_affinity)) =
3634                line_layout.closest_index_for_position(pos, last_layout)
3635            {
3636                return (
3637                    self.resolve_index(line_start_offset + local_index),
3638                    line_end_affinity,
3639                    line_layout.columns_past_line_end(pos, last_layout),
3640                );
3641            } else if pos.y < px(0.) {
3642                // Mouse is above this line, return start of this line
3643                return (self.resolve_index(line_start_offset), false, 0);
3644            }
3645
3646            y_offset += line_layout.size(line_height).height;
3647            last_line_pos = Some(pos);
3648        }
3649
3650        // Clamp to the last laid-out row. Returning the end of the whole document
3651        // would select unseen text before drag autoscroll has reached it.
3652        let last_position = last_layout
3653            .lines
3654            .last()
3655            .zip(last_line_pos)
3656            .zip(last_layout.visible_line_byte_offsets.last())
3657            .map(|((line_layout, pos), line_start)| {
3658                let last_row_top = (line_layout.size(line_height).height - line_height).max(px(0.));
3659                let pos = point(pos.x, last_row_top);
3660                (
3661                    self.resolve_index(line_start + line_layout.len()),
3662                    false,
3663                    line_layout.columns_past_line_end(pos, last_layout),
3664                )
3665            });
3666
3667        last_position.unwrap_or((self.text.len(), false, 0))
3668    }
3669
3670    /// Map a display byte index back to a text offset, undoing the mask expansion when the input
3671    /// is masked.
3672    fn resolve_index(&self, index: usize) -> usize {
3673        if self.masked {
3674            self.text.char_index_to_offset(index / MASK_CHAR.len_utf8())
3675        } else {
3676            index.min(self.text.len())
3677        }
3678    }
3679
3680    /// Returns a y offsetted point for the line origin.
3681    /// Select the text from the current cursor position to the given offset.
3682    ///
3683    /// The offset is the UTF-8 offset.
3684    ///
3685    /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
3686    /// Extend a single selection so its moving end lands at `offset`, flipping
3687    /// `reversed` when the ends cross. When a sticky `word_range` is given the
3688    /// selection is kept covering it.
3689    fn extend_selection(
3690        sel: &mut CursorSelection,
3691        offset: usize,
3692        word_range: Option<CursorSelection>,
3693    ) {
3694        if sel.reversed {
3695            sel.start = offset;
3696        } else {
3697            sel.end = offset;
3698        }
3699
3700        if sel.end < sel.start {
3701            sel.reversed = !sel.reversed;
3702            std::mem::swap(&mut sel.start, &mut sel.end);
3703        }
3704
3705        if let Some(word_range) = word_range {
3706            if sel.start > word_range.start {
3707                sel.start = word_range.start;
3708            }
3709            if sel.end < word_range.end {
3710                sel.end = word_range.end;
3711            }
3712        }
3713    }
3714
3715    /// Extend only the active selection to `offset`. Used by mouse drag.
3716    pub(crate) fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
3717        self.select_to_with_affinity(offset, false, cx);
3718    }
3719
3720    /// Like [`Self::select_to`], but also carries the caret's line-end affinity.
3721    ///
3722    /// See [`Self::move_to_with_affinity`] for why the affinity travels with the offset. Note
3723    /// that plain [`Self::select_to`] clears the affinity: every offset it is given came from
3724    /// the text rather than from a visual position, so the caret has no reason to keep sticking
3725    /// to the end of a wrapped row.
3726    pub(crate) fn select_to_with_affinity(
3727        &mut self,
3728        offset: usize,
3729        line_end_affinity: bool,
3730        cx: &mut Context<Self>,
3731    ) {
3732        M::clear_inline_completion(self, cx);
3733
3734        self.cursor_line_end_affinity = line_end_affinity;
3735        let offset = self.cursor_boundary(offset, Bias::Left);
3736        let word_range = self.selected_word_range;
3737        Self::extend_selection(self.active_selection_mut(), offset, word_range);
3738        let range = self.normalize_token_range(self.selected_range());
3739        self.set_selection(range.start, range.end);
3740
3741        if self.active_selection().is_empty() {
3742            self.update_preferred_column();
3743        }
3744        cx.notify()
3745    }
3746
3747    /// Extend every selection to the offset produced by `f`, then merge any
3748    /// selections that now overlap. Used by keyboard selection commands.
3749    fn select_all_cursors_to(
3750        &mut self,
3751        f: impl Fn(&Self, &CursorSelection) -> usize,
3752        cx: &mut Context<Self>,
3753    ) {
3754        self.select_all_cursors_to_with_affinity(|s, sel| (f(s, sel), false), false, cx);
3755    }
3756
3757    /// Extend selections with caret affinity, preserving their column anchors even
3758    /// when vertical movement collapses a selection to a cursor.
3759    fn select_all_cursors_to_with_affinity(
3760        &mut self,
3761        f: impl Fn(&Self, &CursorSelection) -> (usize, bool),
3762        preserve_column: bool,
3763        cx: &mut Context<Self>,
3764    ) {
3765        self.pause_blink_cursor(cx);
3766        self.undo_manager.break_transaction_coalescing();
3767        M::clear_inline_completion(self, cx);
3768
3769        let mut active_affinity = false;
3770        let new_selections: Vec<CursorSelection> = self
3771            .selections
3772            .iter()
3773            .map(|sel| {
3774                let (offset, affinity) = f(self, sel);
3775                if sel.id == self.active_selection().id {
3776                    active_affinity = affinity;
3777                }
3778                let offset = self.cursor_boundary(offset, Bias::Left);
3779                let mut new_sel = *sel;
3780                Self::extend_selection(&mut new_sel, offset, None);
3781                let range = self.normalize_token_range(new_sel.start..new_sel.end);
3782                new_sel.start = range.start;
3783                new_sel.end = range.end;
3784                if !preserve_column {
3785                    new_sel.column_anchor =
3786                        self.preferred_column_for_with_affinity(new_sel.cursor_offset(), affinity);
3787                } else if new_sel.column_anchor.is_none() {
3788                    new_sel.column_anchor = self.preferred_column_for_with_affinity(
3789                        sel.cursor_offset(),
3790                        self.line_end_affinity_for(sel),
3791                    );
3792                }
3793                new_sel
3794            })
3795            .collect();
3796        self.cursor_line_end_affinity = active_affinity;
3797        self.selections.replace_all(new_selections);
3798        self.selections.merge_overlapping();
3799
3800        self.scroll_to(self.cursor(), None, cx);
3801        cx.notify()
3802    }
3803
3804    /// Unselects the currently selected text.
3805    pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3806        self.undo_manager.break_transaction_coalescing();
3807        let offset = self.cursor();
3808        self.set_cursor_to(offset);
3809        cx.notify()
3810    }
3811
3812    #[inline]
3813    pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {
3814        self.text.offset_utf16_to_offset(offset)
3815    }
3816
3817    #[inline]
3818    pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {
3819        self.text.offset_to_offset_utf16(offset)
3820    }
3821
3822    #[inline]
3823    pub(crate) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
3824        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
3825    }
3826
3827    #[inline]
3828    pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
3829        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
3830    }
3831
3832    /// If offset falls on a hidden (folded) line, clamp backward to the end of
3833    /// the fold header line (last visible position before the fold).
3834    fn clamp_offset_to_visible_backward(&self, offset: usize) -> usize {
3835        let line = self.text.offset_to_point(offset).row;
3836        if self.display_map.is_buffer_line_hidden(line) {
3837            for fold in self.display_map.folded_ranges() {
3838                if line > fold.start_line && line <= fold.end_line {
3839                    return self.text.line_end_offset(fold.start_line);
3840                }
3841            }
3842        }
3843        offset
3844    }
3845
3846    /// If offset falls on a hidden (folded) line, clamp forward to the start of
3847    /// the fold end line (first visible position after the fold).
3848    fn clamp_offset_to_visible_forward(&self, offset: usize) -> usize {
3849        let line = self.text.offset_to_point(offset).row;
3850        if self.display_map.is_buffer_line_hidden(line) {
3851            for fold in self.display_map.folded_ranges() {
3852                if line > fold.start_line && line <= fold.end_line {
3853                    return self.text.line_start_offset(fold.end_line);
3854                }
3855            }
3856        }
3857        offset
3858    }
3859
3860    /// Clip a cursor/selection offset without splitting a CRLF newline. This does
3861    /// not alter the rope or its byte/UTF-16 conversion used for exact source APIs.
3862    pub(super) fn cursor_boundary(&self, offset: usize, bias: Bias) -> usize {
3863        let offset = self.token_boundary(self.text.clip_offset(offset, bias), bias);
3864        if offset > 0
3865            && self.text.char_at(offset - 1) == Some('\r')
3866            && self.text.char_at(offset) == Some('\n')
3867        {
3868            if bias == Bias::Left {
3869                offset - 1
3870            } else {
3871                offset + 1
3872            }
3873        } else {
3874            offset
3875        }
3876    }
3877
3878    pub(super) fn previous_boundary(&self, offset: usize) -> usize {
3879        let offset = self.cursor_boundary(offset.saturating_sub(1), Bias::Left);
3880        self.clamp_offset_to_visible_backward(offset)
3881    }
3882
3883    pub(super) fn next_boundary(&self, offset: usize) -> usize {
3884        let offset = self.cursor_boundary(offset.saturating_add(1), Bias::Right);
3885        self.clamp_offset_to_visible_forward(offset)
3886    }
3887
3888    /// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
3889    pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
3890        (self.focus_handle.is_focused(window) || M::is_context_menu_open(self, cx))
3891            && !self.disabled
3892            && self.blink_cursor.read(cx).visible()
3893            && window.is_window_active()
3894    }
3895
3896    fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3897        self.blink_cursor.update(cx, |cursor, cx| {
3898            cursor.start(cx);
3899        });
3900        cx.emit(InputEvent::Focus);
3901    }
3902
3903    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3904        self.revealed_viewport = None;
3905        if M::is_context_menu_open(self, cx) {
3906            return;
3907        }
3908
3909        M::hide_context_menu(self, cx);
3910        self.undo_manager.break_transaction_coalescing();
3911
3912        // NOTE: Do not cancel select, when blur.
3913        // Because maybe user want to copy the selected text by AppMenuBar (will take focus handle).
3914
3915        M::clear_hover_state(self, cx);
3916        self.diagnostic_popover = None;
3917        M::clear_inline_completion(self, cx);
3918        self.dismiss_touch_selection(cx);
3919        self.blink_cursor.update(cx, |cursor, cx| {
3920            cursor.stop(cx);
3921        });
3922        self.clamp_number_value(window, cx);
3923        cx.emit(InputEvent::Blur);
3924        cx.notify();
3925    }
3926
3927    /// Clamp the number value to the `min`/`max` range, used on blur.
3928    ///
3929    /// Out-of-range values are allowed while typing (e.g. `1` is an
3930    /// intermediate state of `15` when min is 10), and clamped on blur.
3931    fn clamp_number_value(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3932        if !self.is_single_line() {
3933            return;
3934        }
3935        if !matches!(self.mask_pattern, MaskPattern::Number { .. }) {
3936            return;
3937        }
3938        if self.number_min.is_none() && self.number_max.is_none() {
3939            return;
3940        }
3941
3942        let Ok(value) = self.unmask_value().parse::<f64>() else {
3943            return;
3944        };
3945
3946        let clamped = match (self.number_min, self.number_max) {
3947            (Some(min), _) if value < min => min,
3948            (_, Some(max)) if value > max => max,
3949            _ => return,
3950        };
3951
3952        // The clamped value must pass the `pattern`/`validate` check,
3953        // otherwise keep the value as is.
3954        let new_text = clamped.to_string();
3955        if !self.is_valid_input(&new_text, cx) {
3956            return;
3957        }
3958
3959        let range = self.range_to_utf16(&(0..self.text.len()));
3960        self.replace_text_in_range_silent(Some(range), &new_text, window, cx);
3961    }
3962
3963    pub(super) fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
3964        self.blink_cursor.update(cx, |cursor, cx| {
3965            cursor.pause(cx);
3966        });
3967    }
3968
3969    pub(super) fn on_drag_move(
3970        &mut self,
3971        event: &MouseMoveEvent,
3972        window: &mut Window,
3973        cx: &mut Context<Self>,
3974    ) {
3975        if self.text.len() == 0 {
3976            return;
3977        }
3978
3979        if self.last_layout.is_none() {
3980            return;
3981        }
3982
3983        if !self.focus_handle.is_focused(window) {
3984            return;
3985        }
3986
3987        if !self.selecting {
3988            return;
3989        }
3990
3991        self.auto_scroll.last_drag_position = Some(event.position);
3992        let (offset, line_end_affinity, columns_past_line_end) =
3993            self.resolve_mouse_position(event.position);
3994        if let Some(start) = self.column_select_start {
3995            let end = ColumnarPoint::new(offset, columns_past_line_end);
3996            self.build_columnar_selection(start, end, cx);
3997        } else {
3998            self.select_to_with_affinity(offset, line_end_affinity, cx);
3999        }
4000
4001        if !self.is_single_line() {
4002            let delta = AutoScroll::compute_delta(event.position.y, self.input_bounds);
4003            // Input's ScrollHandle uses negative-y-is-down; negate the positive-towards-bottom delta.
4004            let scroll_delta = delta.map(|d| -d);
4005            self.auto_scroll.set(scroll_delta, cx, |delta, state, cx| {
4006                let current = state.scroll_handle.offset();
4007                state.update_scroll_offset(Some(point(current.x, current.y + delta)), cx);
4008                if let Some(pos) = state.auto_scroll.last_drag_position {
4009                    let (offset, line_end_affinity, columns_past_line_end) =
4010                        state.resolve_mouse_position(pos);
4011                    if let Some(start) = state.column_select_start {
4012                        let end = ColumnarPoint::new(offset, columns_past_line_end);
4013                        state.build_columnar_selection(start, end, cx);
4014                    } else {
4015                        state.select_to_with_affinity(offset, line_end_affinity, cx);
4016                    }
4017                }
4018            });
4019        }
4020    }
4021
4022    /// Normalize the inserted text before applying it to the input.
4023    ///
4024    /// For number inputs (with [`MaskPattern::Number`]), this converts
4025    /// full-width number characters into their ASCII equivalents,
4026    /// e.g. `12。5` -> `12.5`.
4027    pub(super) fn normalize_input<'a>(&self, new_text: &'a str) -> Cow<'a, str> {
4028        let normalized = if matches!(self.mask_pattern, MaskPattern::Number { .. }) {
4029            normalize_number_input(new_text)
4030        } else {
4031            Cow::Borrowed(new_text)
4032        };
4033
4034        if self.is_single_line() && normalized.contains(['\n', '\r']) {
4035            Cow::Owned(normalized.replace(['\n', '\r'], ""))
4036        } else {
4037            normalized
4038        }
4039    }
4040
4041    pub(crate) fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {
4042        if new_text.is_empty() {
4043            return true;
4044        }
4045
4046        if let Some(validate) = &self.validate {
4047            if !validate(new_text, cx) {
4048                return false;
4049            }
4050        }
4051
4052        if !self.mask_pattern.is_valid(new_text) {
4053            return false;
4054        }
4055
4056        let Some(pattern) = &self.pattern else {
4057            return true;
4058        };
4059
4060        pattern.is_match(new_text)
4061    }
4062
4063    /// Set the mask pattern for formatting the input text.
4064    ///
4065    /// The pattern can contain:
4066    /// - 9: Any digit or dot
4067    /// - A: Any letter
4068    /// - *: Any character
4069    /// - Other characters will be treated as literal mask characters
4070    ///
4071    /// Example: "(999)999-999" for phone numbers
4072    pub fn mask_pattern(mut self, pattern: impl Into<MaskPattern>) -> Self {
4073        self.mask_pattern = pattern.into();
4074        self.mask_pattern_set = true;
4075        if let Some(placeholder) = self.mask_pattern.placeholder() {
4076            self.placeholder = placeholder.into();
4077        }
4078        self
4079    }
4080
4081    pub fn set_mask_pattern(
4082        &mut self,
4083        pattern: impl Into<MaskPattern>,
4084        _: &mut Window,
4085        cx: &mut Context<Self>,
4086    ) {
4087        self.mask_pattern = pattern.into();
4088        self.mask_pattern_set = true;
4089        if let Some(placeholder) = self.mask_pattern.placeholder() {
4090            self.placeholder = placeholder.into();
4091        }
4092        cx.notify();
4093    }
4094
4095    /// Apply the default numeric mask unless the caller explicitly selected a mask.
4096    pub fn ensure_number_mask(&mut self) {
4097        if self.mask_pattern_set {
4098            return;
4099        }
4100        self.mask_pattern = MaskPattern::Number {
4101            separator: None,
4102            fraction: None,
4103        };
4104    }
4105
4106    pub(super) fn set_input_bounds(&mut self, new_bounds: Bounds<Pixels>, cx: &mut Context<Self>) {
4107        let wrap_width_changed = self.input_bounds.size.width != new_bounds.size.width;
4108        self.input_bounds = new_bounds;
4109
4110        // Update display_map wrap_width if changed.
4111        if let Some(last_layout) = self.last_layout.as_ref() {
4112            if wrap_width_changed {
4113                let wrap_width = if !self.soft_wrap {
4114                    // None to disable wrapping (will use Pixels::MAX)
4115                    None
4116                } else {
4117                    last_layout.wrap_width
4118                };
4119
4120                self.display_map.on_layout_changed(wrap_width, cx);
4121                if self.is_multi_line() {
4122                    self.mode.update_auto_grow(&self.display_map);
4123                }
4124                cx.notify();
4125            }
4126        }
4127    }
4128
4129    /// Return the active selection's text, borrowed out of the [`Rope`]
4130    /// the state owns.
4131    ///
4132    /// See [`Self::selected_value`] when an owned string is wanted.
4133    pub fn selected_text(&self) -> RopeSlice<'_> {
4134        let range_utf16 = self.range_to_utf16(&self.selected_range());
4135        let range = self.range_from_utf16(&range_utf16);
4136        self.text.slice(range)
4137    }
4138
4139    /// Return the rendered bounds for a UTF-8 byte range in the current input contents.
4140    ///
4141    /// Returns `None` when the requested range is not currently laid out or visible.
4142    pub fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
4143        let Some(last_layout) = self.last_layout.as_ref() else {
4144            return None;
4145        };
4146
4147        let Some(last_bounds) = self.last_bounds else {
4148            return None;
4149        };
4150
4151        let (_, _, start_pos) = self.line_and_position_for_offset(range.start);
4152        let (_, _, end_pos) = self.line_and_position_for_offset(range.end);
4153
4154        let Some(start_pos) = start_pos else {
4155            return None;
4156        };
4157        let Some(end_pos) = end_pos else {
4158            return None;
4159        };
4160
4161        Some(Bounds::from_corners(
4162            last_bounds.origin + start_pos,
4163            last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),
4164        ))
4165    }
4166
4167    /// Replace text in range in silent.
4168    ///
4169    /// This will not trigger any UI interaction, such as auto-completion.
4170    pub(crate) fn replace_text_in_range_silent(
4171        &mut self,
4172        range_utf16: Option<Range<usize>>,
4173        new_text: &str,
4174        window: &mut Window,
4175        cx: &mut Context<Self>,
4176    ) {
4177        self.silent_replace_text = true;
4178        self.replace_text_in_range(range_utf16, new_text, window, cx);
4179        self.silent_replace_text = false;
4180    }
4181
4182    /// Apply a batch of edits as one atomic history transaction.
4183    ///
4184    /// `edits` are `(byte range in the current pre-edit document, replacement)`
4185    /// pairs.
4186    pub(crate) fn replace_text_in_ranges(
4187        &mut self,
4188        edits: &[(Range<usize>, String)],
4189        window: &mut Window,
4190        cx: &mut Context<Self>,
4191    ) {
4192        if !self.is_editable() || edits.is_empty() {
4193            return;
4194        }
4195
4196        // Every edit invalidates provider responses for the previous document,
4197        // including deletion and indentation which do not trigger completion.
4198        M::hide_context_menu(self, cx);
4199        M::clear_inline_completion(self, cx);
4200
4201        // Sort descending by start so applying front-of-vec first edits the
4202        // highest offsets first, leaving lower offsets unchanged.
4203        let mut sorted: Vec<(Range<usize>, &str)> = edits
4204            .iter()
4205            .map(|(range, text)| (self.normalize_token_range(range.clone()), text.as_str()))
4206            .collect();
4207        sorted.sort_by_key(|edit| std::cmp::Reverse(edit.0.start));
4208        if !self.token_spans().is_empty() {
4209            let mut merged: Vec<(Range<usize>, &str)> = Vec::with_capacity(sorted.len());
4210            for (range, text) in sorted {
4211                if let Some(last) = merged
4212                    .last_mut()
4213                    .filter(|last| range.end > last.0.start || range == last.0)
4214                {
4215                    last.0.start = range.start.min(last.0.start);
4216                    last.0.end = range.end.max(last.0.end);
4217                } else {
4218                    merged.push((range, text));
4219                }
4220            }
4221            sorted = merged;
4222        }
4223
4224        #[cfg(debug_assertions)]
4225        for pair in sorted.windows(2) {
4226            debug_assert!(
4227                pair[1].0.end <= pair[0].0.start,
4228                "replace_text_in_ranges requires disjoint ranges"
4229            );
4230        }
4231
4232        // Wrap multiple edits in one explicit transaction so they undo as a
4233        // unit. A single edit records directly, which keeps it eligible for
4234        // the undo manager's typing coalescing.
4235        let requested_intent = self.undo_manager.take_pending_intent();
4236        let selection_before = *self.active_selection();
4237        let original_selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
4238        // Snapshot the cursors before applying, so undo can restore them. A
4239        // delete has already expanded them over the text it removes, so they
4240        // collapse back to where the user left them.
4241        let selections_before: Vec<CursorSelection> = self
4242            .selections
4243            .iter()
4244            .map(|selection| {
4245                Self::collapse_for_intent(
4246                    requested_intent.unwrap_or(EditIntent::Atomic),
4247                    *selection,
4248                    &(selection.start..selection.end),
4249                )
4250            })
4251            .collect();
4252        let group = sorted.len() > 1;
4253        if group {
4254            self.undo_manager.begin_transaction();
4255        }
4256
4257        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
4258        let mut recorded = false;
4259        // Several edits reach the highlighter as one batch, so it reparses
4260        // once for the change instead of once per edit.
4261        let mut highlighter_edits = Vec::new();
4262        for (range, new_text) in &sorted {
4263            let old_text = self.text.clone();
4264            self.mode.adjust_auto_closed_pair(range, new_text.len());
4265            self.text.replace(range.clone(), new_text);
4266
4267            M::adjust_annotations(self, range, new_text.len());
4268            recorded |= self.push_history(
4269                &old_text,
4270                range,
4271                new_text,
4272                requested_intent,
4273                selection_before,
4274                None,
4275            );
4276
4277            // Incremental, single-range updates must run per edit.
4278            self.display_map
4279                .adjust_folds_for_edit(&old_text, range, new_text);
4280            self.display_map
4281                .on_text_changed(&self.text, range, &Rope::from(*new_text), cx);
4282
4283            if group {
4284                if self.is_code_editor() {
4285                    let edit =
4286                        super::mode::replacement_input_edit(&old_text, &self.text, range, new_text);
4287                    highlighter_edits.push((edit, self.text.clone()));
4288                }
4289            } else {
4290                self.mode.update_highlighter(
4291                    super::mode::HighlighterUpdate {
4292                        selected_range: range,
4293                        old_text: &old_text,
4294                        new_text: &self.text,
4295                        change_text: new_text,
4296                        force: true,
4297                    },
4298                    window,
4299                    cx,
4300                );
4301
4302                self.update_fold_candidates_incremental(range, new_text);
4303            }
4304        }
4305
4306        if group {
4307            self.mode
4308                .update_highlighter_batch::<M>(&highlighter_edits, window, cx);
4309
4310            // Fold candidates are read from the reparsed document, so each
4311            // edit is located where it ended up once every edit was applied.
4312            // `sorted` runs from the highest offset down.
4313            let mut shift = 0isize;
4314            for (range, new_text) in sorted.iter().rev() {
4315                let start = (range.start as isize + shift) as usize;
4316                self.update_fold_candidates_incremental(&(start..start), new_text);
4317                shift += new_text.len() as isize - range.len() as isize;
4318            }
4319
4320            self.undo_manager.commit_transaction();
4321        }
4322
4323        // One observable update per batch instead of one per edit.
4324        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4325            diagnostics.reset(&self.text)
4326        }
4327        M::refresh_language_features(self, window, cx);
4328        self.update_search(cx);
4329
4330        // Compute the resulting cursors.
4331        // One collapsed cursor per edit, at the end of its inserted text.
4332        let mut ascending: Vec<(Range<usize>, &str)> = sorted.clone();
4333        ascending.sort_by_key(|edit| edit.0.start);
4334        let text_len = self.text.len();
4335        let mut delta: isize = 0;
4336        let mut edit_results = Vec::with_capacity(ascending.len());
4337        for (range, new_text) in &ascending {
4338            let offset = ((range.start as isize + delta) as usize + new_text.len()).min(text_len);
4339            edit_results.push((range.clone(), offset));
4340            delta += new_text.len() as isize - (range.end as isize - range.start as isize);
4341        }
4342
4343        let mut used = vec![false; edit_results.len()];
4344        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(ascending.len());
4345        for selection in original_selections {
4346            if let Ok(index) = edit_results
4347                .binary_search_by_key(&(selection.start, selection.end), |(range, _)| {
4348                    (range.start, range.end)
4349                })
4350                && !used[index]
4351            {
4352                let offset = edit_results[index].1;
4353                used[index] = true;
4354                let mut selection = selection;
4355                selection.place_at(offset, None);
4356                new_selections.push(selection);
4357            }
4358        }
4359        for (index, (_, offset)) in edit_results.into_iter().enumerate() {
4360            if !used[index] {
4361                new_selections.push(CursorSelection::new(
4362                    self.selections.generate_id(),
4363                    offset,
4364                    offset,
4365                ));
4366            }
4367        }
4368        self.selections.replace_all(new_selections);
4369        self.selections.merge_overlapping();
4370
4371        // Record the cursor snapshots for undo/redo restore.
4372        let selections_after: Vec<CursorSelection> = self.selections.iter().copied().collect();
4373        if recorded {
4374            self.undo_manager.record_auto_closed_pairs(
4375                auto_closed_pairs_before,
4376                self.mode.auto_closed_pairs().clone(),
4377            );
4378            self.undo_manager
4379                .record_selections(selections_before, selections_after);
4380        }
4381
4382        self.ime_marked_range.take();
4383        self.update_preferred_column();
4384        self.dismiss_touch_selection(cx);
4385        if self.is_multi_line() {
4386            self.mode.update_auto_grow(&self.display_map);
4387        }
4388        if self.emit_events {
4389            cx.emit(InputEvent::Change);
4390        }
4391        cx.notify();
4392    }
4393
4394    /// Update fold candidates from tree-sitter syntax tree (full extraction).
4395    /// Used only on initial load or language changes.
4396    fn update_fold_candidates(&mut self) {
4397        if !self.mode.is_folding() {
4398            return;
4399        }
4400
4401        let Some(highlighter_rc) = self.mode.highlighter() else {
4402            return;
4403        };
4404
4405        let highlighter = highlighter_rc.borrow();
4406        let Some(highlighter) = highlighter.as_ref() else {
4407            return;
4408        };
4409
4410        let fold_ranges = highlighter.fold_ranges(&self.text);
4411        self.display_map.set_fold_candidates(fold_ranges);
4412    }
4413
4414    /// Incrementally update fold candidates after a text edit.
4415    /// Only traverses the edited region of the syntax tree instead of the full tree.
4416    fn update_fold_candidates_incremental(&mut self, edit_range: &Range<usize>, new_text: &str) {
4417        if !self.mode.is_folding() {
4418            return;
4419        }
4420
4421        let Some(highlighter_rc) = self.mode.highlighter() else {
4422            return;
4423        };
4424
4425        let highlighter = highlighter_rc.borrow();
4426        let Some(highlighter) = highlighter.as_ref() else {
4427            return;
4428        };
4429
4430        // The new byte range in the updated text after the edit
4431        let new_end = edit_range.start + new_text.len();
4432        self.display_map.update_fold_candidates_for_edit(
4433            |range, text| highlighter.fold_ranges_for_edit(range, text),
4434            edit_range.start..new_end,
4435            &self.text,
4436        );
4437    }
4438}
4439
4440impl<M: InputModeKind> EntityInputHandler for InputBaseState<M> {
4441    fn text_for_range(
4442        &mut self,
4443        range_utf16: Range<usize>,
4444        adjusted_range: &mut Option<Range<usize>>,
4445        _window: &mut Window,
4446        _cx: &mut Context<Self>,
4447    ) -> Option<String> {
4448        let range = self.range_from_utf16(&range_utf16);
4449        adjusted_range.replace(self.range_to_utf16(&range));
4450        Some(self.text.slice(range).to_string())
4451    }
4452
4453    fn selected_text_range(
4454        &mut self,
4455        _ignore_disabled_input: bool,
4456        _window: &mut Window,
4457        _cx: &mut Context<Self>,
4458    ) -> Option<UTF16Selection> {
4459        Some(UTF16Selection {
4460            range: self.range_to_utf16(&self.selected_range()),
4461            reversed: self.active_selection().reversed,
4462        })
4463    }
4464
4465    fn marked_text_range(
4466        &self,
4467        _window: &mut Window,
4468        _cx: &mut Context<Self>,
4469    ) -> Option<Range<usize>> {
4470        self.ime_marked_range
4471            .map(|range| self.range_to_utf16(&range.into()))
4472    }
4473
4474    fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
4475        self.ime_marked_range = None;
4476        self.undo_manager.commit_transaction();
4477    }
4478
4479    /// Replace text in range.
4480    ///
4481    /// - If the new text is invalid, it will not be replaced.
4482    /// - If `range_utf16` is not provided, the current selected range will be used.
4483    fn replace_text_in_range(
4484        &mut self,
4485        range_utf16: Option<Range<usize>>,
4486        new_text: &str,
4487        window: &mut Window,
4488        cx: &mut Context<Self>,
4489    ) {
4490        let requested_intent = self.undo_manager.take_pending_intent();
4491        if !self.is_editable() {
4492            return;
4493        }
4494        let selection_before = *self.active_selection();
4495        // Committing a composition ends the transaction it opened, whether or
4496        // not the platform follows up with `unmark_text`.
4497        let ends_composition = self.ime_marked_range.is_some();
4498
4499        self.pause_blink_cursor(cx);
4500
4501        // NOTE: The normalization keeps the UTF-16 length, but may change the
4502        // UTF-8 byte length, so all the byte-offset calculations below must
4503        // use the normalized text.
4504        let new_text = if self.replaying_history {
4505            Cow::Borrowed(new_text)
4506        } else {
4507            self.normalize_input(new_text)
4508        };
4509        let new_text: &str = &new_text;
4510
4511        let range = range_utf16
4512            .as_ref()
4513            .map(|range_utf16| self.range_from_utf16(range_utf16))
4514            .or(self.ime_marked_range.map(|range| {
4515                let range = self.range_to_utf16(&(range.start..range.end));
4516                self.range_from_utf16(&range)
4517            }))
4518            .unwrap_or(self.selected_range());
4519        let range = self.normalize_token_range(range);
4520
4521        // Skip-over as a pure cursor move: a typed closer that already follows
4522        // the cursor moves past it without touching text or history, so Undo
4523        // never exposes a transient duplicate. Only for interactive single
4524        // keystrokes (current selection, no IME mark, not silent replays).
4525        // `take_pending_intent` above already consumed any request.
4526        if range.is_empty()
4527            && range.start == self.cursor()
4528            && self.ime_marked_range.is_none()
4529            && !self.silent_replace_text
4530            && self.is_code_editor()
4531            && self.selections.is_single()
4532            && self.active_selection().is_empty()
4533        {
4534            if let Some(target) = self.skip_over_target(new_text) {
4535                self.set_cursor_to(target);
4536                self.update_preferred_column();
4537                cx.notify();
4538                return;
4539            }
4540        }
4541
4542        if self.is_multi_line() {
4543            let multi_cursor = range_utf16.is_none()
4544                && self.ime_marked_range.is_none()
4545                && !self.selections.is_single();
4546            if multi_cursor {
4547                self.selections.merge_overlapping();
4548                let mut edits: Vec<(Range<usize>, String)> = self
4549                    .selections
4550                    .iter()
4551                    .map(|sel| (sel.start..sel.end, new_text.to_string()))
4552                    .collect();
4553                edits.sort_by_key(|(range, _)| range.start);
4554                // One keystroke across several cursors is one batch, committed
4555                // with the intent of the keystroke so a run of them coalesces
4556                // into one undo just like single-cursor typing.
4557                let intent =
4558                    requested_intent.unwrap_or_else(|| self.typing_intent(&edits, new_text));
4559                self.undo_manager.begin_transaction_with(intent);
4560                self.undo_manager.set_pending_intent(intent);
4561                self.replace_text_in_ranges(&edits, window, cx);
4562                self.undo_manager.commit_transaction();
4563            } else {
4564                if range_utf16.is_some() {
4565                    self.selections.remove_all_but_active();
4566                }
4567                if let Some(intent) = requested_intent {
4568                    self.undo_manager.set_pending_intent(intent);
4569                }
4570                if let Some((open_len, closer)) = self.auto_close_target(&range, new_text) {
4571                    // One edit keeps the pair atomic even at undo coalescing limits.
4572                    let replacement = format!("{new_text}{closer}");
4573                    self.replace_text_in_ranges(&[(range.clone(), replacement)], window, cx);
4574                    let cursor = range.start + new_text.len();
4575                    self.mode.track_auto_closed_pair(
4576                        cursor - open_len..cursor,
4577                        cursor..cursor + closer.len(),
4578                    );
4579                    self.undo_manager
4580                        .record_auto_closed_pairs_after(self.mode.auto_closed_pairs().clone());
4581                    self.set_cursor_to(cursor);
4582                    self.update_preferred_column();
4583                    self.undo_manager.record_selections(
4584                        vec![selection_before],
4585                        self.selections.iter().copied().collect(),
4586                    );
4587                } else {
4588                    self.replace_text_in_ranges(
4589                        &[(range.clone(), new_text.to_string())],
4590                        window,
4591                        cx,
4592                    );
4593                }
4594            }
4595            if ends_composition {
4596                self.undo_manager.commit_transaction();
4597            }
4598
4599            if !self.silent_replace_text {
4600                M::on_text_typed(self, &range, new_text, window, cx);
4601            }
4602            return;
4603        }
4604
4605        // Single-line path
4606        let old_text = self.text.clone();
4607        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4608        self.text.replace(range.clone(), new_text);
4609
4610        let mut new_offset = (range.start + new_text.len()).min(self.text.len());
4611
4612        // True if the mask has changed the text, e.g. regrouping the
4613        // separators or completing a leading dot.
4614        let mut mask_changed = false;
4615
4616        if self.is_single_line() {
4617            let pending_text = self.text.to_string();
4618            // Check if the new text is valid.
4619            //
4620            // Only reject the edit if the old text was valid, to avoid
4621            // trapping a pre-existing invalid text (e.g. a `default_value`
4622            // that does not conform), the user can still edit to fix it.
4623            if !self.replaying_history
4624                && !self.validated_token_edit
4625                && !self.is_valid_input(&pending_text, cx)
4626                && self.is_valid_input(&old_text.to_string(), cx)
4627            {
4628                self.text = old_text;
4629                return;
4630            }
4631
4632            if !self.replaying_history && !self.mask_pattern.is_none() {
4633                let mask_text = self.mask_pattern.mask(&pending_text);
4634                mask_changed = mask_text.as_str() != pending_text;
4635                self.text = Rope::from(mask_text.as_str());
4636                let new_text_len =
4637                    (new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
4638                new_offset = (range.start + new_text_len).min(mask_text.len());
4639            }
4640        }
4641
4642        if mask_changed {
4643            // Masking rewrites the whole document, so ranges recorded against
4644            // the old text no longer point at anything.
4645            M::reset_annotations(self);
4646        } else {
4647            M::adjust_annotations(self, &range, new_text.len());
4648        }
4649        if mask_changed {
4650            // A segment-based history entry no longer matches the masked
4651            // document, record a whole-document change instead, so that
4652            // undo/redo can restore the text exactly.
4653            self.push_history(
4654                &old_text,
4655                &(0..old_text.len()),
4656                &self.text.to_string(),
4657                Some(EditIntent::Atomic),
4658                selection_before,
4659                Some((new_offset..new_offset).into()),
4660            );
4661        } else {
4662            self.push_history(
4663                &old_text,
4664                &range,
4665                &new_text,
4666                requested_intent,
4667                selection_before,
4668                None,
4669            );
4670        }
4671        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4672            diagnostics.reset(&self.text)
4673        }
4674        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4675        self.display_map
4676            .adjust_folds_for_edit(&old_text, &range, new_text);
4677        self.display_map
4678            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4679
4680        self.mode.update_highlighter::<M>(
4681            super::mode::HighlighterUpdate {
4682                selected_range: &range,
4683                old_text: &old_text,
4684                new_text: &self.text,
4685                change_text: &new_text,
4686                force: true,
4687            },
4688            window,
4689            cx,
4690        );
4691
4692        self.update_fold_candidates_incremental(&range, new_text);
4693        M::refresh_language_features(self, window, cx);
4694        self.set_cursor_to(new_offset);
4695        self.ime_marked_range.take();
4696        // A commit ends the IME composition: macOS delivers `insertText:` for
4697        // the confirmed candidate without a following `unmarkText`, so close
4698        // the transaction here. Leaving it open would keep merging every later
4699        // edit into the same change, which then carries the text and selection
4700        // of the first composition.
4701        if ends_composition {
4702            self.undo_manager
4703                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4704            self.undo_manager.commit_transaction();
4705        }
4706        self.update_preferred_column();
4707        self.update_search(cx);
4708        self.dismiss_touch_selection(cx);
4709        if self.is_multi_line() {
4710            self.mode.update_auto_grow(&self.display_map);
4711        }
4712        if !self.silent_replace_text {
4713            M::on_text_typed(self, &range, &new_text, window, cx);
4714        }
4715        if self.emit_events {
4716            cx.emit(InputEvent::Change);
4717        }
4718        cx.notify();
4719    }
4720
4721    /// Mark text is the IME temporary insert on typing.
4722    fn replace_and_mark_text_in_range(
4723        &mut self,
4724        range_utf16: Option<Range<usize>>,
4725        new_text: &str,
4726        new_selected_range_utf16: Option<Range<usize>>,
4727        window: &mut Window,
4728        cx: &mut Context<Self>,
4729    ) {
4730        let requested_intent = self.undo_manager.take_pending_intent();
4731        if !self.is_editable() {
4732            return;
4733        }
4734        let selection_before = *self.active_selection();
4735
4736        let starts_composition = self.ime_marked_range.is_none();
4737        if starts_composition {
4738            // Even a canceled preedit separates the typing gestures on either
4739            // side; its no-op transaction must not reconnect those gestures.
4740            self.undo_manager.break_transaction_coalescing();
4741            self.undo_manager.begin_transaction();
4742        }
4743
4744        // Collapse any extra cursors so we never leave stale secondary cursors behind.
4745        self.selections.remove_all_but_active();
4746
4747        M::reset_language_features(self);
4748
4749        // See the same NOTE in `replace_text_in_range`.
4750        let new_text = if self.replaying_history {
4751            Cow::Borrowed(new_text)
4752        } else {
4753            self.normalize_input(new_text)
4754        };
4755        let new_text: &str = &new_text;
4756
4757        let range = range_utf16
4758            .as_ref()
4759            .map(|range_utf16| self.range_from_utf16(range_utf16))
4760            .or(self.ime_marked_range.map(|range| {
4761                let range = self.range_to_utf16(&(range.start..range.end));
4762                self.range_from_utf16(&range)
4763            }))
4764            .unwrap_or(self.selected_range());
4765        let range = self.normalize_token_range(range);
4766
4767        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
4768        let old_text = self.text.clone();
4769        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4770        self.text.replace(range.clone(), new_text);
4771
4772        if self.is_single_line() {
4773            let pending_text = self.text.to_string();
4774            // See the same NOTE in `replace_text_in_range`.
4775            if !self.replaying_history
4776                && !self.validated_token_edit
4777                && !self.is_valid_input(&pending_text, cx)
4778                && self.is_valid_input(&old_text.to_string(), cx)
4779            {
4780                self.text = old_text;
4781                if starts_composition {
4782                    self.undo_manager.commit_transaction();
4783                }
4784                return;
4785            }
4786        }
4787
4788        M::adjust_annotations(self, &range, new_text.len());
4789        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4790            diagnostics.reset(&self.text)
4791        }
4792        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4793        self.display_map
4794            .adjust_folds_for_edit(&old_text, &range, new_text);
4795        self.display_map
4796            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4797
4798        self.mode.update_highlighter::<M>(
4799            super::mode::HighlighterUpdate {
4800                selected_range: &range,
4801                old_text: &old_text,
4802                new_text: &self.text,
4803                change_text: &new_text,
4804                force: true,
4805            },
4806            window,
4807            cx,
4808        );
4809
4810        self.update_fold_candidates_incremental(&range, new_text);
4811        M::refresh_language_features(self, window, cx);
4812        if new_text.is_empty() {
4813            // Cancel selection, when cancel IME input.
4814            self.set_cursor_to(range.start);
4815            self.ime_marked_range = None;
4816        } else {
4817            self.ime_marked_range = Some((range.start..range.start + new_text.len()).into());
4818            let new_range = new_selected_range_utf16
4819                .as_ref()
4820                .map(|range_utf16| {
4821                    let new_text = Rope::from(new_text);
4822                    range.start + new_text.offset_utf16_to_offset(range_utf16.start)
4823                        ..range.start + new_text.offset_utf16_to_offset(range_utf16.end)
4824                })
4825                .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
4826            self.set_selection(new_range.start, new_range.end);
4827        }
4828        if self.is_multi_line() {
4829            self.mode.update_auto_grow(&self.display_map);
4830        }
4831        if self.push_history(
4832            &old_text,
4833            &range,
4834            new_text,
4835            requested_intent,
4836            selection_before,
4837            Some(*self.active_selection()),
4838        ) {
4839            self.undo_manager
4840                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4841            self.undo_manager.record_auto_closed_pairs(
4842                auto_closed_pairs_before,
4843                self.mode.auto_closed_pairs().clone(),
4844            );
4845        }
4846        if new_text.is_empty() {
4847            self.undo_manager.commit_transaction();
4848        }
4849        cx.notify();
4850    }
4851
4852    /// Used to position IME candidates.
4853    fn bounds_for_range(
4854        &mut self,
4855        range_utf16: Range<usize>,
4856        bounds: Bounds<Pixels>,
4857        _window: &mut Window,
4858        _cx: &mut Context<Self>,
4859    ) -> Option<Bounds<Pixels>> {
4860        let last_layout = self.last_layout.as_ref()?;
4861        let line_height = last_layout.line_height;
4862        let line_number_width = last_layout.line_number_width;
4863        let range = self.range_from_utf16(&range_utf16);
4864
4865        let mut start_origin = None;
4866        let mut end_origin = None;
4867        let line_number_origin = point(line_number_width, px(0.));
4868        let mut y_offset = last_layout.visible_top;
4869
4870        for (vi, line) in last_layout.lines.iter().enumerate() {
4871            if start_origin.is_some() && end_origin.is_some() {
4872                break;
4873            }
4874
4875            let index_offset = last_layout.visible_line_byte_offsets[vi];
4876
4877            if start_origin.is_none() {
4878                if let Some(p) = line.position_for_index(
4879                    range.start.saturating_sub(index_offset),
4880                    last_layout,
4881                    false,
4882                ) {
4883                    start_origin = Some(p + point(px(0.), y_offset));
4884                }
4885            }
4886
4887            if end_origin.is_none() {
4888                if let Some(p) = line.position_for_index(
4889                    range.end.saturating_sub(index_offset),
4890                    last_layout,
4891                    false,
4892                ) {
4893                    end_origin = Some(p + point(px(0.), y_offset));
4894                }
4895            }
4896
4897            y_offset += line.size(line_height).height;
4898        }
4899
4900        let start_origin = start_origin.or_else(|| {
4901            let offset = self.last_cursor.or(Some(self.cursor()))?;
4902            let (_, _, origin) = self.line_and_position_for_offset(offset);
4903            origin.map(|origin| origin - line_number_origin)
4904        })?;
4905        let mut end_origin = end_origin.unwrap_or(start_origin);
4906        // Ensure at same line.
4907        end_origin.y = start_origin.y;
4908
4909        Some(Bounds::from_corners(
4910            bounds.origin + line_number_origin + start_origin,
4911            // + line_height for show IME panel under the cursor line.
4912            bounds.origin + line_number_origin + point(end_origin.x, end_origin.y + line_height),
4913        ))
4914    }
4915
4916    fn character_index_for_point(
4917        &mut self,
4918        point: gpui::Point<Pixels>,
4919        _window: &mut Window,
4920        _cx: &mut Context<Self>,
4921    ) -> Option<usize> {
4922        let last_layout = self.last_layout.as_ref()?;
4923        let line_point = self.last_bounds?.localize(&point)?;
4924
4925        for (vi, line) in last_layout.lines.iter().enumerate() {
4926            let offset = last_layout.visible_line_byte_offsets[vi];
4927            if let Some(utf8_index) = line.index_for_position(line_point, last_layout) {
4928                return Some(self.offset_to_utf16(offset + utf8_index));
4929            }
4930        }
4931
4932        None
4933    }
4934
4935    fn text_input_configuration(
4936        &mut self,
4937        _window: &mut Window,
4938        _cx: &mut Context<Self>,
4939    ) -> TextInputConfiguration {
4940        InputBaseState::text_input_configuration(self)
4941    }
4942}
4943
4944impl<M: InputModeKind> Focusable for InputBaseState<M> {
4945    fn focus_handle(&self, _cx: &App) -> FocusHandle {
4946        self.focus_handle.clone()
4947    }
4948}
4949
4950impl<M: InputModeKind> Render for InputBaseState<M> {
4951    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
4952        // Before anything reads it: the element resolves this style during
4953        // layout and paint, and both happen after this call in the same frame.
4954        self.editor_style = self
4955            .projected_editor_style
4956            .resolved(&crate::Theme::global(cx).tokens);
4957        let entity = cx.entity();
4958        if self._pending_update {
4959            self.mode.update_highlighter::<M>(
4960                super::mode::HighlighterUpdate {
4961                    selected_range: &(0..0),
4962                    old_text: &self.text,
4963                    new_text: &self.text,
4964                    change_text: "",
4965                    force: false,
4966                },
4967                window,
4968                cx,
4969            );
4970
4971            self.update_fold_candidates();
4972            M::refresh_language_features(self, window, cx);
4973            self._pending_update = false;
4974        }
4975
4976        let element = div()
4977            .id(EDITOR_ELEMENT_ID)
4978            .key_context(CONTEXT)
4979            .when_some(self.accessibility.as_ref(), |this, accessibility| {
4980                this.when_some(accessibility.role, |this, role| this.role(role))
4981                    .when_some(accessibility.label.clone(), |this, label| {
4982                        this.aria_label(label)
4983                    })
4984                    .when_some(accessibility.placeholder.clone(), |this, placeholder| {
4985                        this.aria_placeholder(placeholder)
4986                    })
4987                    .when_some(accessibility.accessibility_id.clone(), |this, id| {
4988                        this.accessibility_id(id)
4989                    })
4990                    .when(
4991                        accessibility.value_exposed
4992                            && !self.masked
4993                            && (window.is_a11y_active() || cfg!(feature = "test-support")),
4994                        |this| this.aria_value(self.text.to_string()),
4995                    )
4996                    .on_a11y_action(gpui::AccessibleAction::Focus, {
4997                        let entity = entity.clone();
4998                        move |_, window, cx| {
4999                            entity.update(cx, |state, cx| {
5000                                state.handle_accessibility_focus(window, cx)
5001                            });
5002                        }
5003                    })
5004                    .when(self.is_editable(), |this| {
5005                        this.on_a11y_action(gpui::AccessibleAction::SetValue, {
5006                            let entity = entity.clone();
5007                            move |data, window, cx| {
5008                                entity.update(cx, |state, cx| {
5009                                    state.handle_accessibility_set_value(data, window, cx)
5010                                });
5011                            }
5012                        })
5013                    })
5014            })
5015            .when(self.is_editable(), |this| {
5016                this.on_action(window.listener_for(&entity, InputBaseState::backspace))
5017                    .on_action(window.listener_for(&entity, InputBaseState::delete))
5018                    .on_action(
5019                        window.listener_for(&entity, InputBaseState::delete_to_beginning_of_line),
5020                    )
5021                    .on_action(window.listener_for(&entity, InputBaseState::delete_to_end_of_line))
5022                    .on_action(window.listener_for(&entity, InputBaseState::delete_previous_word))
5023                    .on_action(window.listener_for(&entity, InputBaseState::delete_next_word))
5024                    .on_action(window.listener_for(&entity, InputBaseState::enter))
5025                    .on_action(window.listener_for(&entity, InputBaseState::escape))
5026                    .on_action(window.listener_for(&entity, InputBaseState::paste))
5027                    .on_action(window.listener_for(&entity, InputBaseState::cut))
5028                    .on_action(window.listener_for(&entity, InputBaseState::undo))
5029                    .on_action(window.listener_for(&entity, InputBaseState::redo))
5030                    .when(self.is_multi_line(), |this| {
5031                        this.on_action(window.listener_for(&entity, InputBaseState::indent_inline))
5032                            .on_action(window.listener_for(&entity, InputBaseState::outdent_inline))
5033                            .on_action(window.listener_for(&entity, InputBaseState::indent_block))
5034                            .on_action(window.listener_for(&entity, InputBaseState::outdent_block))
5035                    })
5036            })
5037            .on_action(window.listener_for(&entity, InputBaseState::left))
5038            .on_action(window.listener_for(&entity, InputBaseState::right))
5039            .on_action(window.listener_for(&entity, InputBaseState::select_left))
5040            .on_action(window.listener_for(&entity, InputBaseState::select_right))
5041            .when(self.is_multi_line(), |this| {
5042                this.on_action(window.listener_for(&entity, InputBaseState::up))
5043                    .on_action(window.listener_for(&entity, InputBaseState::down))
5044                    .on_action(window.listener_for(&entity, InputBaseState::select_up))
5045                    .on_action(window.listener_for(&entity, InputBaseState::select_down))
5046                    .on_action(window.listener_for(&entity, InputBaseState::page_up))
5047                    .on_action(window.listener_for(&entity, InputBaseState::page_down))
5048                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_above))
5049                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_below))
5050            })
5051            .on_action(window.listener_for(&entity, InputBaseState::on_action_select_all))
5052            .on_action(window.listener_for(&entity, InputBaseState::select_to_start_of_line))
5053            .on_action(window.listener_for(&entity, InputBaseState::select_to_end_of_line))
5054            .on_action(window.listener_for(&entity, InputBaseState::select_to_previous_word))
5055            .on_action(window.listener_for(&entity, InputBaseState::select_to_next_word))
5056            .on_action(window.listener_for(&entity, InputBaseState::home))
5057            .on_action(window.listener_for(&entity, InputBaseState::end))
5058            .on_action(window.listener_for(&entity, InputBaseState::move_to_start))
5059            .on_action(window.listener_for(&entity, InputBaseState::move_to_end))
5060            .on_action(window.listener_for(&entity, InputBaseState::move_to_previous_word))
5061            .on_action(window.listener_for(&entity, InputBaseState::move_to_next_word))
5062            .on_action(window.listener_for(&entity, InputBaseState::select_to_start))
5063            .on_action(window.listener_for(&entity, InputBaseState::select_to_end))
5064            .on_action(window.listener_for(&entity, InputBaseState::show_character_palette))
5065            .on_action({
5066                let entity = entity.clone();
5067                move |_: &ActivateToken, window, cx| {
5068                    let state = entity.read(cx);
5069                    let activation = state
5070                        .token_spans()
5071                        .iter()
5072                        .find(|span| span.range() == state.selected_range())
5073                        .and_then(|span| {
5074                            state.range_to_bounds(&span.range()).and_then(|bounds| {
5075                                state.token_activation(
5076                                    span.range().start,
5077                                    bounds,
5078                                    gpui::ClickEvent::Keyboard(gpui::KeyboardClickEvent {
5079                                        bounds,
5080                                        ..Default::default()
5081                                    }),
5082                                )
5083                            })
5084                        });
5085                    if let Some((listener, event)) = activation {
5086                        listener(&event, window, cx);
5087                    }
5088                }
5089            })
5090            .on_action(window.listener_for(&entity, InputBaseState::copy))
5091            .on_action(window.listener_for(&entity, InputBaseState::on_action_search))
5092            .on_action(window.listener_for(&entity, InputBaseState::on_action_replace))
5093            .on_mouse_down(
5094                MouseButton::Left,
5095                window.listener_for(&entity, InputBaseState::on_mouse_down),
5096            )
5097            .on_mouse_down(
5098                MouseButton::Right,
5099                window.listener_for(&entity, InputBaseState::on_mouse_down),
5100            )
5101            .on_mouse_up(
5102                MouseButton::Left,
5103                window.listener_for(&entity, InputBaseState::on_mouse_up),
5104            )
5105            .on_mouse_up(
5106                MouseButton::Right,
5107                window.listener_for(&entity, InputBaseState::on_mouse_up),
5108            )
5109            .on_mouse_move(window.listener_for(&entity, InputBaseState::on_mouse_move))
5110            .on_scroll(window.listener_for(&entity, InputBaseState::on_scroll_wheel))
5111            .when(self.is_multi_line() && !self.disabled, |this| {
5112                this.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
5113            })
5114            .when(!self.disabled, |this| {
5115                if self.is_multi_line() && window.modifiers().alt {
5116                    this.cursor_crosshair()
5117                } else {
5118                    this.cursor_text()
5119                }
5120            })
5121            .flex_1()
5122            // A single line fills the frame and sits at its vertical center,
5123            // so the frame needs no layout of its own to hold it. It is never
5124            // shorter than its line, though: this root clips (overflow-x
5125            // hidden clips both axes), and a frame whose padding leaves less
5126            // than a line, like Component's Input, would cut off descenders.
5127            .map(|this| {
5128                if self.is_multi_line() {
5129                    this.h_full()
5130                } else {
5131                    this.min_h_full().flex().items_center()
5132                }
5133            })
5134            .flex_grow_1()
5135            .overflow_x_hidden()
5136            .when(self.is_multi_line(), |this| {
5137                this.pt(self.editor_paddings.top)
5138                    .pr(self.editor_paddings.right)
5139                    .pb(self.editor_paddings.bottom)
5140                    .pl(self.editor_paddings.left)
5141            })
5142            .child(TextElement::new(entity.clone()).placeholder(self.placeholder.clone()))
5143            .when(self.shows_scrollbar(), |this| {
5144                this.child(EditorScrollbar::new(entity.clone()))
5145            });
5146
5147        // Actions only one mode handles are registered by that mode, where
5148        // `Self` is concrete enough to name its own entity type.
5149        M::register_actions(element, &entity, window)
5150            .test_support()
5151            .track_focus(&self.focus_handle)
5152    }
5153}
5154
5155#[cfg(test)]
5156mod tests {
5157    use super::*;
5158
5159    #[gpui::test]
5160    fn accessibility_actions_recheck_live_editability(cx: &mut TestAppContext) {
5161        cx.update(crate::init);
5162        let (state, cx) = cx.add_window_view(|window, cx| {
5163            crate::input::InputState::new(window, cx).default_value("initial")
5164        });
5165        let changes = Rc::new(Cell::new(0));
5166        let observed = changes.clone();
5167        let _subscription = cx.update(|_, cx| {
5168            cx.subscribe(&state, move |_, event: &InputEvent, _| {
5169                if matches!(event, InputEvent::Change) {
5170                    observed.set(observed.get() + 1);
5171                }
5172            })
5173        });
5174        cx.update(|window, cx| {
5175            state.update(cx, |state, cx| {
5176                state.handle_accessibility_set_value(None, window, cx);
5177                state.handle_accessibility_focus(window, cx);
5178                assert!(state.focus_handle.is_focused(window));
5179            });
5180            window.draw(cx).clear(cx);
5181        });
5182        assert_eq!(state.read_with(cx, |state, _| state.value()), "initial");
5183        assert_eq!(changes.get(), 0);
5184
5185        let action = gpui::accesskit::ActionData::Value("updated🦀".into());
5186        cx.update(|window, cx| {
5187            state.update(cx, |state, cx| {
5188                state.handle_accessibility_set_value(Some(&action), window, cx)
5189            });
5190        });
5191        assert_eq!(state.read_with(cx, |state, _| state.value()), "updated🦀");
5192        assert_eq!(changes.get(), 1);
5193        for disabled in [false, true] {
5194            cx.update(|window, cx| {
5195                window.blur(cx);
5196                state.update(cx, |state, cx| {
5197                    state.set_disabled(disabled, cx);
5198                    state.set_readonly(!disabled, cx);
5199                    state.handle_accessibility_focus(window, cx);
5200                    assert_eq!(state.focus_handle.is_focused(window), !disabled);
5201                    let rejected = gpui::accesskit::ActionData::Value("rejected".into());
5202                    state.handle_accessibility_set_value(Some(&rejected), window, cx);
5203                });
5204            });
5205            assert_eq!(state.read_with(cx, |state, _| state.value()), "updated🦀");
5206            assert_eq!(changes.get(), 1);
5207        }
5208        cx.update(|window, cx| {
5209            state.update(cx, |state, cx| {
5210                state.set_disabled(false, cx);
5211                state.set_readonly(false, cx);
5212                state.handle_accessibility_focus(window, cx);
5213            });
5214            window.draw(cx).clear(cx);
5215            window.dispatch_action(Box::new(Undo), cx);
5216        });
5217        assert_eq!(state.read_with(cx, |state, _| state.value()), "initial");
5218        cx.update(|window, cx| {
5219            state.update(cx, |state, cx| {
5220                state.set_masked(true, window, cx);
5221                state.handle_accessibility_set_value(Some(&action), window, cx);
5222            });
5223        });
5224        assert_eq!(state.read_with(cx, |state, _| state.value()), "updated🦀");
5225    }
5226
5227    #[gpui::test]
5228    fn accessibility_set_value_preserves_exact_editor_text(cx: &mut TestAppContext) {
5229        cx.update(crate::init);
5230        let (editor, cx) = cx.add_window_view(|window, cx| {
5231            crate::input::EditorState::new(window, cx).language("rust")
5232        });
5233        let action = gpui::accesskit::ActionData::Value("(".into());
5234        cx.update(|window, cx| {
5235            editor.update(cx, |editor, cx| {
5236                editor.handle_accessibility_set_value(Some(&action), window, cx)
5237            });
5238        });
5239        assert_eq!(editor.read_with(cx, |editor, _| editor.value()), "(");
5240    }
5241
5242    #[gpui::test]
5243    fn revealing_a_password_keeps_text_assistance_disabled(cx: &mut TestAppContext) {
5244        cx.update(crate::init);
5245        cx.open_window(size(px(400.), px(100.)), |window, cx| {
5246            let state = cx.new(|cx| crate::input::InputState::new(window, cx).masked(true));
5247            let hidden = state.read(cx).text_input_configuration();
5248            assert!(!hidden.autocorrect && !hidden.suggestions);
5249            assert_eq!(hidden.autocapitalize, Autocapitalize::None);
5250
5251            // The component's reveal button uses this same state transition.
5252            state.update(cx, |state, cx| state.toggle_masked(window, cx));
5253            assert_eq!(state.read(cx).text_input_configuration(), hidden);
5254            state.update(cx, |state, cx| state.toggle_masked(window, cx));
5255            assert_eq!(state.read(cx).text_input_configuration(), hidden);
5256            gpui::EmptyView
5257        });
5258    }
5259
5260    #[gpui::test]
5261    fn text_input_configuration_follows_the_input(cx: &mut TestAppContext) {
5262        use crate::input::{InputState, TextareaState};
5263
5264        cx.update(crate::init);
5265        let mut states = None;
5266        cx.open_window(size(px(400.), px(100.)), |window, cx| {
5267            states = Some((
5268                cx.new(|cx| InputState::new(window, cx)),
5269                cx.new(|cx| InputState::new(window, cx).masked(true)),
5270                cx.new(|cx| TextareaState::new(window, cx)),
5271                cx.new(|cx| {
5272                    InputState::new(window, cx)
5273                        .input_action(TextInputAction::Search)
5274                        .autocorrect(true)
5275                        .autocapitalize(Autocapitalize::Words)
5276                }),
5277            ));
5278            gpui::EmptyView
5279        });
5280        let (plain, masked, textarea, search) = states.unwrap();
5281
5282        cx.read(|cx| {
5283            assert_eq!(
5284                plain.read(cx).text_input_configuration(),
5285                TextInputConfiguration {
5286                    input_action: TextInputAction::Done,
5287                    ..Default::default()
5288                }
5289            );
5290            assert_eq!(
5291                masked.read(cx).text_input_configuration(),
5292                TextInputConfiguration {
5293                    input_action: TextInputAction::Done,
5294                    ..Default::default()
5295                }
5296            );
5297            assert_eq!(
5298                textarea.read(cx).text_input_configuration(),
5299                TextInputConfiguration {
5300                    input_action: TextInputAction::Enter,
5301                    ..Default::default()
5302                }
5303            );
5304            let search = search.read(cx).text_input_configuration();
5305            assert_eq!(search.input_action, TextInputAction::Search);
5306            assert_eq!(search.autocapitalize, Autocapitalize::Words);
5307            assert!(search.autocorrect);
5308            assert!(search.suggestions);
5309        });
5310    }
5311
5312    #[gpui::test]
5313    fn test_multicursor_unicode_completion_uses_post_edit_start(cx: &mut TestAppContext) {
5314        use crate::input::{CompletionProvider, EditorState};
5315        use gpui::Task;
5316        use lsp_types::{CompletionContext, CompletionResponse};
5317        use std::cell::RefCell;
5318
5319        #[derive(Default)]
5320        struct Provider {
5321            starts: RefCell<Vec<usize>>,
5322            queries: RefCell<Vec<(usize, String)>>,
5323        }
5324
5325        impl CompletionProvider for Provider {
5326            fn is_completion_trigger(&self, offset: usize, _: &str, _: &mut App) -> bool {
5327                self.starts.borrow_mut().push(offset);
5328                true
5329            }
5330
5331            fn completions(
5332                &self,
5333                _: &Rope,
5334                offset: usize,
5335                trigger: CompletionContext,
5336                _: &mut Window,
5337                _: &mut App,
5338            ) -> Task<anyhow::Result<CompletionResponse>> {
5339                self.queries
5340                    .borrow_mut()
5341                    .push((offset, trigger.trigger_character.unwrap()));
5342                Task::ready(Ok(CompletionResponse::Array(vec![])))
5343            }
5344        }
5345
5346        cx.update(crate::init);
5347        let mut editor = None;
5348        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5349            editor = Some(cx.new(|cx| EditorState::new(window, cx).default_value("aa")));
5350            gpui::EmptyView
5351        });
5352        let editor = editor.unwrap();
5353        let provider = Rc::new(Provider::default());
5354        window
5355            .update(cx, |_, window, cx| {
5356                editor.update(cx, |state, cx| {
5357                    state.extras.lsp.completion_provider = Some(provider.clone());
5358                    state
5359                        .extras
5360                        .context_menu_content
5361                        .completion
5362                        .trigger_start_offset = Some(0);
5363                    state.extras.context_menu_content.completion.query = "aa".into();
5364                    state.set_cursor_to(1);
5365                    state.add_cursor_at(0, cx);
5366
5367                    state.replace_text_in_range(None, "中", window, cx);
5368
5369                    assert_eq!(state.value(), "中a中a");
5370                    assert_eq!(provider.starts.borrow().as_slice(), &[4]);
5371                    assert_eq!(provider.queries.borrow().as_slice(), &[(7, "中".into())]);
5372                });
5373            })
5374            .unwrap();
5375    }
5376
5377    #[gpui::test]
5378    fn test_inline_token_wrap_and_size_refresh(cx: &mut TestAppContext) {
5379        use crate::input::{InlineToken, InlineTokenPresentation};
5380        let width = Rc::new(Cell::new(4000.));
5381        let render_width = width.clone();
5382        let view = InputView::build_textarea(cx, |state| state.rows(4).default_value("@a@b"));
5383        view.window_handle
5384            .update(cx, |_, window, cx| {
5385                view.input.update(cx, |state, cx| {
5386                    state
5387                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5388                        .unwrap();
5389                    state
5390                        .replace_range_with_token(2..4, InlineToken::new("b", "@b"), window, cx)
5391                        .unwrap();
5392                    state.set_token_presentation(
5393                        InlineTokenPresentation::default()
5394                            .token(move |_, _, _| div().w(px(render_width.get()))),
5395                    );
5396                });
5397            })
5398            .unwrap();
5399        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5400        visual.update(|window, cx| window.draw(cx).clear(cx));
5401        view.input.read_with(&visual, |state, _| {
5402            assert_eq!(state.display_map.wrap_row_count(), 2);
5403            let first = state.range_to_bounds(&(0..2)).unwrap();
5404            let second = state.range_to_bounds(&(2..4)).unwrap();
5405            assert!(second.origin.y > first.origin.y);
5406            assert_eq!(
5407                state.last_layout.as_ref().unwrap().lines[0].wrapped_lines[0].len,
5408                2
5409            );
5410        });
5411        width.set(100.);
5412        view.input.update(&mut visual, |_, cx| cx.notify());
5413        visual.update(|window, cx| window.draw(cx).clear(cx));
5414        view.input.read_with(&visual, |state, _| {
5415            assert_eq!(state.display_map.wrap_row_count(), 1)
5416        });
5417    }
5418
5419    #[gpui::test]
5420    fn test_inline_token_geometry_and_reentrant_activation(cx: &mut TestAppContext) {
5421        use crate::input::{InlineToken, InlineTokenPresentation};
5422        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5423        let target = view.input.clone();
5424        view.window_handle
5425            .update(cx, |_, window, cx| {
5426                view.input.update(cx, |state, cx| {
5427                    state
5428                        .replace_range_with_token(
5429                            7..13,
5430                            InlineToken::new("a", "@alice"),
5431                            window,
5432                            cx,
5433                        )
5434                        .unwrap();
5435                    state.set_token_presentation(
5436                        InlineTokenPresentation::default()
5437                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5438                            .on_token_click(move |_, window, cx| {
5439                                target.update(cx, |state, cx| {
5440                                    state.set_value("activated", window, cx)
5441                                })
5442                            }),
5443                    );
5444                    state.focus(window, cx);
5445                    state.set_selected_range(7..13, cx);
5446                });
5447            })
5448            .unwrap();
5449        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5450        visual.update(|window, cx| window.draw(cx).clear(cx));
5451        visual.update(|window, cx| {
5452            let state = view.input.read(cx);
5453            let bounds = state.range_to_bounds(&(7..13)).unwrap();
5454            assert!((bounds.size.width - px(100.)).abs() < px(1.), "{bounds:?}");
5455            let (left, _) = state.index_for_mouse_position(bounds.origin + point(px(10.), px(5.)));
5456            let (right, _) = state.index_for_mouse_position(bounds.origin + point(px(90.), px(5.)));
5457            assert_eq!((left, right), (7, 13));
5458            window.dispatch_action(Box::new(ActivateToken), cx);
5459        });
5460        visual.run_until_parked();
5461        view.input.read_with(&visual, |state, _| {
5462            assert_eq!(state.value().as_ref(), "activated")
5463        });
5464    }
5465
5466    #[gpui::test]
5467    fn test_inline_token_click_selects_it(cx: &mut TestAppContext) {
5468        use crate::input::InlineToken;
5469        cx.update(crate::init);
5470        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5471        view.window_handle
5472            .update(cx, |_, window, cx| {
5473                view.input.update(cx, |state, cx| {
5474                    state
5475                        .replace_range_with_token(
5476                            7..13,
5477                            InlineToken::new("a", "@alice"),
5478                            window,
5479                            cx,
5480                        )
5481                        .unwrap();
5482                    state.set_selected_range(0..0, cx);
5483                });
5484            })
5485            .unwrap();
5486        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5487        visual.update(|window, cx| window.draw(cx).clear(cx));
5488        let bounds = view
5489            .input
5490            .read_with(&visual, |state, _| state.range_to_bounds(&(7..13)).unwrap());
5491        visual.simulate_click(bounds.center(), gpui::Modifiers::default());
5492        visual.run_until_parked();
5493        view.input.read_with(&visual, |state, _| {
5494            assert_eq!(state.selected_range(), 7..13, "a click selects the token");
5495        });
5496    }
5497
5498    #[gpui::test]
5499    fn test_inline_token_hover_reports_presence_without_selecting(cx: &mut TestAppContext) {
5500        use crate::input::{InlineToken, InlineTokenPresentation};
5501        use gpui::{Bounds, point, px, size};
5502        cx.update(crate::init);
5503        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5504        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
5505        view.window_handle
5506            .update(cx, |_, window, cx| {
5507                view.input.update(cx, |state, cx| {
5508                    state
5509                        .replace_range_with_token(
5510                            7..13,
5511                            InlineToken::new("a", "@alice"),
5512                            window,
5513                            cx,
5514                        )
5515                        .unwrap();
5516                    state.set_token_presentation(
5517                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5518                    );
5519                    state.set_selected_range(0..0, cx);
5520                });
5521            })
5522            .unwrap();
5523        view.window_handle
5524            .update(cx, |_, _, cx| {
5525                view.input.update(cx, |state, _| {
5526                    let (_, entered) = state
5527                        .token_hover(7, bounds, true, None)
5528                        .expect("hover enters an enabled token");
5529                    assert!(entered.is_hovered());
5530                    assert_eq!(entered.token().id().as_ref(), "a");
5531                    assert_eq!(entered.range(), 7..13);
5532                    assert_eq!(entered.bounds(), bounds);
5533                    let (_, left) = state
5534                        .token_hover(7, bounds, false, None)
5535                        .expect("hover leaves the same token");
5536                    assert!(!left.is_hovered());
5537                    assert_eq!(state.selected_range(), 0..0, "hover never selects");
5538                    assert!(state.token_hover(99, bounds, true, None).is_none());
5539                });
5540            })
5541            .unwrap();
5542
5543        // Disabled tokens suppress hover, matching click; readonly allows it.
5544        view.window_handle
5545            .update(cx, |_, _, cx| {
5546                view.input.update(cx, |state, cx| {
5547                    state.set_disabled(true, cx);
5548                });
5549            })
5550            .unwrap();
5551        view.window_handle
5552            .update(cx, |_, _, cx| {
5553                view.input.update(cx, |state, _| {
5554                    assert!(state.token_hover(7, bounds, true, None).is_none())
5555                })
5556            })
5557            .unwrap();
5558        view.window_handle
5559            .update(cx, |_, _, cx| {
5560                view.input.update(cx, |state, cx| {
5561                    state.set_disabled(false, cx);
5562                    state.set_readonly(true, cx);
5563                });
5564            })
5565            .unwrap();
5566        view.window_handle
5567            .update(cx, |_, _, cx| {
5568                view.input.update(cx, |state, _| {
5569                    assert!(state.token_hover(7, bounds, true, None).is_some())
5570                })
5571            })
5572            .unwrap();
5573
5574        // Without a hover listener there is no hover activation.
5575        view.window_handle
5576            .update(cx, |_, _, cx| {
5577                view.input.update(cx, |state, _| {
5578                    state.set_token_presentation(InlineTokenPresentation::default());
5579                });
5580            })
5581            .unwrap();
5582        view.window_handle
5583            .update(cx, |_, _, cx| {
5584                view.input.update(cx, |state, _| {
5585                    assert!(state.token_hover(7, bounds, true, None).is_none())
5586                })
5587            })
5588            .unwrap();
5589    }
5590
5591    #[gpui::test]
5592    fn test_inline_token_hover_exit_delivered_when_token_removed(cx: &mut TestAppContext) {
5593        use crate::input::{InlineToken, InlineTokenPresentation};
5594        use std::cell::RefCell;
5595        use std::rc::Rc;
5596        cx.update(crate::init);
5597        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5598        let events: Rc<RefCell<Vec<(SharedString, bool)>>> = Rc::new(RefCell::new(Vec::new()));
5599        let sink = events.clone();
5600        view.window_handle
5601            .update(cx, |_, window, cx| {
5602                view.input.update(cx, |state, cx| {
5603                    state
5604                        .replace_range_with_token(
5605                            7..13,
5606                            InlineToken::new("a", "@alice"),
5607                            window,
5608                            cx,
5609                        )
5610                        .unwrap();
5611                    state.set_token_presentation(
5612                        InlineTokenPresentation::default()
5613                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5614                            .on_token_hover(move |event, _, _| {
5615                                sink.borrow_mut()
5616                                    .push((event.token().id().clone(), event.is_hovered()));
5617                            }),
5618                    );
5619                });
5620            })
5621            .unwrap();
5622        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5623        visual.update(|window, cx| window.draw(cx).clear(cx));
5624        let known_bounds = view.input.read_with(&visual, |state, _| {
5625            state
5626                .token_bounds
5627                .get(&7)
5628                .copied()
5629                .expect("placed token bounds")
5630        });
5631        visual.simulate_mouse_move(known_bounds.center(), None, gpui::Modifiers::default());
5632        visual.run_until_parked();
5633        visual.update(|window, cx| window.draw(cx).clear(cx));
5634        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5635
5636        // Remove the token while the pointer is still over its stale row: the
5637        // exit must fire even though the token element that owns the hover
5638        // mask is gone.
5639        view.window_handle
5640            .update(cx, |_, window, cx| {
5641                view.input
5642                    .update(cx, |state, cx| state.set_value("no tokens", window, cx));
5643            })
5644            .unwrap();
5645        visual.run_until_parked();
5646        visual.update(|window, cx| window.draw(cx).clear(cx));
5647        assert_eq!(
5648            events.borrow().as_slice(),
5649            &[("a".into(), true), ("a".into(), false)]
5650        );
5651    }
5652
5653    #[gpui::test]
5654    fn test_inline_token_hover_bounds_stay_single_row_at_wrap_boundary(cx: &mut TestAppContext) {
5655        use crate::input::{InlineToken, InlineTokenPresentation};
5656        cx.update(crate::init);
5657        let view = InputView::build_textarea(cx, |state| {
5658            state
5659                .rows(4)
5660                .default_value("aaaa bbbb cccc dddd @alice eeee ffff")
5661        });
5662        view.window_handle
5663            .update(cx, |_, window, cx| {
5664                view.input.update(cx, |state, cx| {
5665                    state
5666                        .replace_range_with_token(
5667                            20..26,
5668                            InlineToken::new("a", "@alice"),
5669                            window,
5670                            cx,
5671                        )
5672                        .unwrap();
5673                    state.set_token_presentation(
5674                        InlineTokenPresentation::default()
5675                            .token(|_, _, _| div().w(px(60.)).h(px(20.))),
5676                    );
5677                });
5678            })
5679            .unwrap();
5680        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5681        visual.update(|window, cx| window.draw(cx).clear(cx));
5682        let (placed, ranged, line_height) = view.input.read_with(&visual, |state, _| {
5683            (
5684                state
5685                    .token_bounds
5686                    .get(&20)
5687                    .copied()
5688                    .expect("placed token bounds"),
5689                state.range_to_bounds(&(20..26)),
5690                state.last_layout.as_ref().unwrap().line_height,
5691            )
5692        });
5693        // The laid-out element always occupies exactly one row of height, even
5694        // when the range-extent path resolves the same end boundary onto the
5695        // next visual row at a soft-wrap edge.
5696        assert!(placed.size.height <= line_height + px(1.), "{placed:?}");
5697        assert!(
5698            placed.size.width <= px(61.) && placed.size.width > px(0.),
5699            "{placed:?}"
5700        );
5701        if let Some(ranged) = ranged {
5702            assert!(
5703                ranged.size.height >= placed.size.height - px(1.),
5704                "{ranged:?} vs {placed:?}"
5705            );
5706        }
5707    }
5708
5709    #[gpui::test]
5710    fn test_inline_token_hover_same_offset_replacement_exits_predecessor(cx: &mut TestAppContext) {
5711        use crate::input::{InlineToken, InlineTokenPresentation};
5712        use std::{cell::RefCell, rc::Rc};
5713        cx.update(crate::init);
5714        let view = InputView::build(cx, |state| state.default_value("@alice!"));
5715        let events = Rc::new(RefCell::new(Vec::new()));
5716        let sink = events.clone();
5717        view.window_handle
5718            .update(cx, |_, window, cx| {
5719                view.input.update(cx, |state, cx| {
5720                    state
5721                        .replace_range_with_token(0..6, InlineToken::new("a", "@alice"), window, cx)
5722                        .unwrap();
5723                    state.set_token_presentation(
5724                        InlineTokenPresentation::default()
5725                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5726                            .on_token_hover(move |event, _, _| {
5727                                sink.borrow_mut()
5728                                    .push((event.token().id().clone(), event.is_hovered()));
5729                            }),
5730                    );
5731                });
5732            })
5733            .unwrap();
5734        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5735        visual.update(|window, cx| window.draw(cx).clear(cx));
5736        let bounds = view
5737            .input
5738            .read_with(&visual, |state, _| state.token_bounds[&0]);
5739        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5740        visual.run_until_parked();
5741        visual.update(|window, cx| window.draw(cx).clear(cx));
5742        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5743
5744        view.window_handle
5745            .update(cx, |_, window, cx| {
5746                view.input.update(cx, |state, cx| {
5747                    state
5748                        .replace_range_with_token(0..6, InlineToken::new("b", "@alice"), window, cx)
5749                        .unwrap();
5750                });
5751            })
5752            .unwrap();
5753        visual.run_until_parked();
5754        visual.update(|window, cx| window.draw(cx).clear(cx));
5755        visual.simulate_mouse_move(
5756            bounds.center() + gpui::point(px(1.), px(0.)),
5757            None,
5758            gpui::Modifiers::default(),
5759        );
5760        visual.run_until_parked();
5761        visual.update(|window, cx| window.draw(cx).clear(cx));
5762        assert_eq!(
5763            events.borrow().as_slice(),
5764            &[("a".into(), true), ("a".into(), false), ("b".into(), true)]
5765        );
5766        // Disabling keeps the token mounted; re-enabling must reset GPUI's
5767        // retained hover state even with a stationary pointer.
5768        view.input
5769            .update(&mut visual, |state, cx| state.set_disabled(true, cx));
5770        visual.update(|window, cx| window.draw(cx).clear(cx));
5771        visual.run_until_parked();
5772        assert_eq!(events.borrow().last(), Some(&("b".into(), false)));
5773        view.input
5774            .update(&mut visual, |state, cx| state.set_disabled(false, cx));
5775        visual.update(|window, cx| window.draw(cx).clear(cx));
5776        visual.run_until_parked();
5777        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5778        visual.run_until_parked();
5779        assert_eq!(
5780            events.borrow().as_slice(),
5781            &[
5782                ("a".into(), true),
5783                ("a".into(), false),
5784                ("b".into(), true),
5785                ("b".into(), false),
5786                ("b".into(), true)
5787            ]
5788        );
5789        visual.simulate_mouse_move(
5790            bounds.bottom_right() + gpui::point(px(10.), px(10.)),
5791            None,
5792            gpui::Modifiers::default(),
5793        );
5794        visual.run_until_parked();
5795        assert_eq!(
5796            events.borrow().as_slice(),
5797            &[
5798                ("a".into(), true),
5799                ("a".into(), false),
5800                ("b".into(), true),
5801                ("b".into(), false),
5802                ("b".into(), true),
5803                ("b".into(), false)
5804            ]
5805        );
5806    }
5807
5808    #[gpui::test]
5809    fn test_inline_token_hover_older_exit_keeps_newer_snapshot(cx: &mut TestAppContext) {
5810        use crate::input::{InlineToken, InlineTokenPresentation};
5811        use gpui::{Bounds, point, px, size};
5812        cx.update(crate::init);
5813        let view = InputView::build(cx, |state| state.default_value("@a @b"));
5814        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
5815        view.window_handle
5816            .update(cx, |_, window, cx| {
5817                view.input.update(cx, |state, cx| {
5818                    state
5819                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5820                        .unwrap();
5821                    state
5822                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
5823                        .unwrap();
5824                    state.set_token_presentation(
5825                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5826                    );
5827                    // The pointer moves A -> B; GPUI dispatches B's enter
5828                    // before A's exit.
5829                    state
5830                        .token_hover(0, bounds, true, None)
5831                        .expect("hover enters A");
5832                    state
5833                        .token_hover(3, bounds, true, None)
5834                        .expect("hover enters B");
5835                    let (_, exit_a) = state.token_hover(0, bounds, false, None).expect("A exits");
5836                    assert!(!exit_a.is_hovered());
5837                    // B's snapshot must survive A's late exit.
5838                    assert_eq!(
5839                        state
5840                            .hovered_token
5841                            .as_ref()
5842                            .expect("B still hovered")
5843                            .span
5844                            .token()
5845                            .id()
5846                            .as_ref(),
5847                        "b"
5848                    );
5849                    // Removing B afterwards still produces B's reconciled exit.
5850                    state.set_value("no tokens", window, cx);
5851                    state.token_bounds.clear();
5852                    let (listener, exit_b) = state.reconcile_token_hover().expect("B must exit");
5853                    assert!(!exit_b.is_hovered());
5854                    assert_eq!(exit_b.token().id().as_ref(), "b");
5855                    drop(listener);
5856                });
5857            })
5858            .unwrap();
5859    }
5860
5861    #[gpui::test]
5862    fn test_inline_token_hover_late_exit_matches_replaced_token(cx: &mut TestAppContext) {
5863        use crate::input::{InlineToken, InlineTokenPresentation};
5864        cx.update(crate::init);
5865        let view = InputView::build(cx, |state| state.default_value("@a"));
5866        view.window_handle
5867            .update(cx, |_, window, cx| {
5868                view.input.update(cx, |state, cx| {
5869                    let a = InlineToken::new("a", "@a");
5870                    let b = InlineToken::new("b", "@a");
5871                    state
5872                        .replace_range_with_token(0..2, a.clone(), window, cx)
5873                        .unwrap();
5874                    state.set_token_presentation(
5875                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5876                    );
5877                    let bounds = gpui::Bounds::default();
5878                    state.token_hover(0, bounds, true, Some(&a)).unwrap();
5879                    state
5880                        .replace_range_with_token(0..2, b.clone(), window, cx)
5881                        .unwrap();
5882                    state.token_hover(0, bounds, true, Some(&b)).unwrap();
5883                    let (_, exit_a) = state.token_hover(0, bounds, false, Some(&a)).unwrap();
5884                    assert_eq!(exit_a.token().id().as_ref(), "a");
5885                    assert_eq!(state.hovered_token.as_ref().unwrap().span.token(), &b);
5886                    let (_, exit_b) = state.token_hover(0, bounds, false, Some(&b)).unwrap();
5887                    assert_eq!(exit_b.token().id().as_ref(), "b");
5888                    assert!(state.pending_token_hover_exits.is_empty());
5889                });
5890            })
5891            .unwrap();
5892    }
5893
5894    #[gpui::test]
5895    fn test_inline_token_hover_exit_keeps_entry_utf16_after_edit(cx: &mut TestAppContext) {
5896        use crate::input::{InlineToken, InlineTokenPresentation};
5897        use gpui::EntityInputHandler as _;
5898        cx.update(crate::init);
5899        let view = InputView::build(cx, |state| state.default_value("🙂 @a"));
5900        view.window_handle
5901            .update(cx, |_, window, cx| {
5902                view.input.update(cx, |state, cx| {
5903                    state
5904                        .replace_range_with_token(5..7, InlineToken::new("a", "@a"), window, cx)
5905                        .unwrap();
5906                    state.set_token_presentation(
5907                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5908                    );
5909                    let bounds = gpui::Bounds::default();
5910                    let (_, enter) = state.token_hover(5, bounds, true, None).unwrap();
5911                    assert_eq!(enter.range_utf16(), (3, 5));
5912                    state.replace_text_in_range(Some(0..2), "abcd", window, cx);
5913                    assert_eq!(state.token_spans()[0].range(), 5..7);
5914                    let (_, exit) = state.token_hover(5, bounds, false, None).unwrap();
5915                    assert_eq!(exit.range_utf16(), (3, 5));
5916                });
5917            })
5918            .unwrap();
5919    }
5920
5921    #[gpui::test]
5922    fn test_inline_token_hover_callback_edit_preserves_both_exits(cx: &mut TestAppContext) {
5923        use crate::input::{InlineToken, InlineTokenPresentation};
5924        cx.update(crate::init);
5925        let view = InputView::build(cx, |state| state.default_value("@a @b"));
5926        view.window_handle
5927            .update(cx, |_, window, cx| {
5928                view.input.update(cx, |state, cx| {
5929                    state
5930                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5931                        .unwrap();
5932                    state
5933                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
5934                        .unwrap();
5935                    state.set_token_presentation(
5936                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5937                    );
5938                    let bounds = gpui::Bounds::default();
5939                    state.token_hover(0, bounds, true, None).unwrap();
5940                    state.token_hover(3, bounds, true, None).unwrap();
5941                    // Model an edit in B's enter callback before A's queued exit.
5942                    state.set_value("gone", window, cx);
5943                    let (_, exit_a) = state.token_hover(0, bounds, false, None).unwrap();
5944                    assert_eq!(exit_a.token().id().as_ref(), "a");
5945                    let (_, exit_b) = state.reconcile_token_hover().unwrap();
5946                    assert_eq!(exit_b.token().id().as_ref(), "b");
5947                    assert!(state.token_hover(0, bounds, false, None).is_none());
5948                    assert!(state.reconcile_token_hover().is_none());
5949                });
5950            })
5951            .unwrap();
5952    }
5953
5954    #[gpui::test]
5955    fn test_inline_token_edit_history_and_validation(cx: &mut TestAppContext) {
5956        let view = InputView::build(cx, |state| state.default_value("问 @alice!"));
5957        view.window_handle
5958            .update(cx, |_, window, cx| {
5959                view.input.update(cx, |state, cx| {
5960                    let token =
5961                        crate::input::InlineToken::new("alice-1", "@alice").with_label("Alice");
5962                    state
5963                        .replace_range_with_token(4..10, token.clone(), window, cx)
5964                        .unwrap();
5965                    assert_eq!(state.value().as_ref(), "问 @alice!");
5966                    assert_eq!(state.tokens()[0].range(), 4..10);
5967                    assert_eq!(state.next_end_of_word_at(4), 10);
5968                    assert_eq!(state.previous_start_of_word_at(10), 4);
5969                    state.undo(&Undo, window, cx);
5970                    assert!(
5971                        state.tokens().is_empty(),
5972                        "identity-only association is undoable"
5973                    );
5974                    assert_eq!(state.value().as_ref(), "问 @alice!");
5975                    state.redo(&Redo, window, cx);
5976                    assert_eq!(state.tokens()[0].token(), &token);
5977                    state.set_selected_range(6..7, cx);
5978                    assert_eq!(state.selected_range(), 4..10);
5979                    state.replace("", window, cx);
5980                    assert_eq!(state.value().as_ref(), "问 !");
5981                    assert!(state.tokens().is_empty());
5982                    state.undo(&Undo, window, cx);
5983                    assert_eq!(state.tokens()[0].token(), &token);
5984                    state.set_selected_range(0..0, cx);
5985                    state.replace("🙂", window, cx);
5986                    assert_eq!(state.tokens()[0].range(), 8..14);
5987                    state.undo(&Undo, window, cx);
5988                    assert_eq!(state.tokens()[0].range(), 4..10);
5989                    let before = state.content();
5990                    state
5991                        .replace_range_with_token(0..0, token.clone(), window, cx)
5992                        .expect("the same reference may occur twice");
5993                    assert_eq!(state.value().as_ref(), "@alice问 @alice!");
5994                    assert_eq!(state.tokens().len(), 2);
5995                    assert_eq!(state.tokens()[1].token(), &token);
5996                    state.undo(&Undo, window, cx);
5997                    assert_eq!(state.content(), before);
5998                    state.set_value(before.text().clone(), window, cx);
5999                    assert!(state.tokens().is_empty());
6000                    assert!(!state.undo_manager.has_undos());
6001                });
6002            })
6003            .unwrap();
6004    }
6005
6006    #[gpui::test]
6007    fn test_inline_token_textarea_ime_and_modes(cx: &mut TestAppContext) {
6008        use crate::input::{InlineToken, InlineTokenError, InputContent};
6009        let view = InputView::build_textarea(cx, |state| state);
6010        view.window_handle
6011            .update(cx, |_, window, cx| {
6012                view.input.update(cx, |state, cx| {
6013                    let content = InputContent::new("@a@b\n后面")
6014                        .with_token(0..2, InlineToken::new("a", "@a"))
6015                        .unwrap()
6016                        .with_token(2..4, InlineToken::new("b", "@b"))
6017                        .unwrap();
6018                    state.set_value(content.clone(), window, cx);
6019                    state.set_selected_range(2..2, cx);
6020                    assert_eq!(state.previous_boundary(2), 0);
6021                    assert_eq!(state.next_boundary(2), 4);
6022                    state.replace_and_mark_text_in_range(Some(1..2), "中", Some(1..1), window, cx);
6023                    assert_eq!(state.value().as_ref(), "中@b\n后面");
6024                    assert_eq!(state.tokens().len(), 1);
6025                    assert_eq!(
6026                        state.replace_with_token(InlineToken::new("x", "x"), window, cx),
6027                        Err(InlineTokenError::CompositionActive)
6028                    );
6029                    state.replace_text_in_range(None, "中文", window, cx);
6030                    state.undo(&Undo, window, cx);
6031                    assert_eq!(state.content(), content);
6032                    state.redo(&Redo, window, cx);
6033                    assert_eq!(state.value().as_ref(), "中文@b\n后面");
6034                    state.undo(&Undo, window, cx);
6035                    state.replace_all("plain", window, cx);
6036                    assert!(state.tokens().is_empty());
6037                    state.undo(&Undo, window, cx);
6038                    assert_eq!(state.content(), content);
6039                });
6040            })
6041            .unwrap();
6042        let view = InputView::build(cx, |state| state);
6043        view.window_handle
6044            .update(cx, |_, window, cx| {
6045                view.input.update(cx, |state, cx| {
6046                    assert_eq!(
6047                        InputContent::new("a\u{301}")
6048                            .with_token(0..1, InlineToken::new("bad", "a"))
6049                            .err(),
6050                        Some(InlineTokenError::InvalidBoundary)
6051                    );
6052                    assert_eq!(
6053                        InputContent::new("@a@a")
6054                            .with_token(0..2, InlineToken::new("a", "@a"))
6055                            .unwrap()
6056                            .with_token(1..3, InlineToken::new("x", "a@"))
6057                            .err(),
6058                        Some(InlineTokenError::OverlappingTokens)
6059                    );
6060                    state
6061                        .replace_with_token(InlineToken::new("a", "@a"), window, cx)
6062                        .unwrap();
6063                    let before = state.content();
6064                    state.set_masked(true, window, cx);
6065                    assert!(!state.tokens_visible());
6066                    assert_eq!(
6067                        state.replace_with_token(InlineToken::new("b", "b"), window, cx),
6068                        Err(InlineTokenError::UnsupportedMode)
6069                    );
6070                    assert_eq!(state.content(), before);
6071                    state.undo(&Undo, window, cx);
6072                    assert!(state.tokens().is_empty());
6073                    state.redo(&Redo, window, cx);
6074                    assert_eq!(state.content(), before);
6075                    assert!(!state.tokens_visible());
6076                });
6077            })
6078            .unwrap();
6079    }
6080
6081    use crate::theme::Theme;
6082    use gpui::{TestAppContext, VisualTestContext, size};
6083
6084    use crate::input::{EditorMode, EditorState, InputMode, LanguageConfig, TabSize, TextareaMode};
6085
6086    fn set_test_syntax_provider(
6087        provider: Rc<dyn crate::input::SyntaxContextProvider>,
6088        cx: &mut App,
6089    ) {
6090        struct TestLanguages(Rc<dyn crate::input::SyntaxContextProvider>);
6091        impl crate::input::LanguageProvider for TestLanguages {
6092            fn syntax_context_provider(
6093                &self,
6094                _: &str,
6095            ) -> Option<Rc<dyn crate::input::SyntaxContextProvider>> {
6096                Some(self.0.clone())
6097            }
6098        }
6099        crate::input::set_language_provider(Rc::new(TestLanguages(provider)), cx);
6100    }
6101
6102    struct TestRoot<M: InputModeKind>(Entity<InputBaseState<M>>);
6103
6104    impl<M: InputModeKind> Render for TestRoot<M> {
6105        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6106            div().size_full().child(self.0.clone())
6107        }
6108    }
6109
6110    struct InputView<M: InputModeKind> {
6111        input: Entity<InputBaseState<M>>,
6112        window_handle: gpui::WindowHandle<TestRoot<M>>,
6113    }
6114
6115    /// Helper to open a state of one mode in a window for testing.
6116    impl<M: InputModeKind> InputView<M> {
6117        fn build_with(
6118            cx: &mut TestAppContext,
6119            make: impl FnOnce(&mut Window, &mut Context<InputBaseState<M>>) -> InputBaseState<M>
6120            + 'static,
6121        ) -> Self {
6122            let mut input: Option<Entity<InputBaseState<M>>> = None;
6123
6124            let window = cx.update(|cx| {
6125                cx.open_window(Default::default(), |window, cx| {
6126                    // Set up the theme first
6127                    cx.set_global(Theme::default());
6128                    // Initialize input keybindings
6129                    super::super::init(cx);
6130
6131                    input = Some(cx.new(|cx| make(window, cx)));
6132
6133                    cx.new(|_| TestRoot(input.clone().unwrap()))
6134                })
6135                .unwrap()
6136            });
6137
6138            Self {
6139                input: input.clone().unwrap(),
6140                window_handle: window,
6141            }
6142        }
6143    }
6144
6145    impl InputView<EditorMode> {
6146        /// An editor state, for the tests that exercise code-editor behavior.
6147        fn new(cx: &mut TestAppContext) -> Self {
6148            Self::build_editor(cx, |state| state)
6149        }
6150
6151        fn build_editor(
6152            cx: &mut TestAppContext,
6153            f: impl FnOnce(InputBaseState<EditorMode>) -> InputBaseState<EditorMode> + 'static,
6154        ) -> Self {
6155            Self::build_with(cx, move |window, cx| {
6156                f(crate::input::EditorState::new(window, cx).language("sql"))
6157            })
6158        }
6159    }
6160
6161    impl InputView<TextareaMode> {
6162        fn build_textarea(
6163            cx: &mut TestAppContext,
6164            f: impl FnOnce(InputBaseState<TextareaMode>) -> InputBaseState<TextareaMode> + 'static,
6165        ) -> Self {
6166            Self::build_with(cx, move |window, cx| {
6167                f(crate::input::TextareaState::new(window, cx))
6168            })
6169        }
6170    }
6171
6172    impl InputView<InputMode> {
6173        /// A single-line state, the default these tests were written against.
6174        fn build(
6175            cx: &mut TestAppContext,
6176            f: impl FnOnce(InputBaseState<InputMode>) -> InputBaseState<InputMode> + 'static,
6177        ) -> Self {
6178            Self::build_with(cx, move |window, cx| {
6179                f(crate::input::InputState::new(window, cx))
6180            })
6181        }
6182    }
6183
6184    /// Sends one trackpad packet straight to the input.
6185    fn swipe<M: InputModeKind>(
6186        view: &InputView<M>,
6187        cx: &mut VisualTestContext,
6188        dy: f32,
6189        phase: gpui::TouchPhase,
6190    ) {
6191        cx.update(|window, cx| {
6192            view.input.update(cx, |state, cx| {
6193                state.on_scroll_wheel(
6194                    &ScrollEvent {
6195                        delta: gpui::ScrollDelta::Pixels(point(px(0.), px(dy))),
6196                        touch_phase: phase,
6197                        ..Default::default()
6198                    },
6199                    window,
6200                    cx,
6201                )
6202            })
6203        });
6204    }
6205
6206    fn long_text() -> String {
6207        (0..60)
6208            .map(|n| format!("line {n}"))
6209            .collect::<Vec<_>>()
6210            .join("\n")
6211    }
6212
6213    #[gpui::test]
6214    fn test_scroll_bounce_stretches_past_the_end_and_springs_back(cx: &mut TestAppContext) {
6215        use gpui::TouchPhase::{Ended, Moved, Started};
6216
6217        let view = InputView::build_textarea(cx, |state| {
6218            state.scroll_bounce(true).default_value(long_text())
6219        });
6220        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6221        cx.update(|window, cx| window.draw(cx).clear(cx));
6222        let drawn_at = |cx: &mut VisualTestContext| {
6223            view.input
6224                .read_with(cx, |state, _| state.last_bounds.unwrap().origin.y)
6225        };
6226
6227        swipe(&view, &mut cx, -20., Started);
6228        swipe(&view, &mut cx, -100_000., Moved);
6229        let (end, stretch) = view.input.read_with(&cx, |state, _| {
6230            (state.scroll_handle.offset().y, state.overscroll.offset())
6231        });
6232        assert!(end < px(0.), "the text scrolled to its end");
6233        assert!(stretch < 0., "the rest stretched the edge: {stretch}");
6234        cx.update(|window, cx| window.draw(cx).clear(cx));
6235        let stretched_at = drawn_at(&mut cx);
6236
6237        swipe(&view, &mut cx, 0., Ended);
6238        for _ in 0..120 {
6239            cx.executor()
6240                .advance_clock(std::time::Duration::from_millis(16));
6241            cx.update(|window, cx| window.draw(cx).clear(cx));
6242        }
6243        view.input.read_with(&cx, |state, _| {
6244            assert_eq!(state.overscroll.offset(), 0., "the edge sprang back");
6245            assert_eq!(
6246                state.scroll_handle.offset().y,
6247                end,
6248                "scrolling stayed exact"
6249            );
6250        });
6251        let moved = (stretched_at - drawn_at(&mut cx)).as_f32();
6252        assert!(
6253            (moved - stretch).abs() < 0.001,
6254            "the stretch only moved where the text was drawn: {moved} vs {stretch}"
6255        );
6256    }
6257
6258    /// Swipes far past the end of the text and returns where the text
6259    /// scrolled to and how far its edge stretched.
6260    fn swipe_past_the_end(
6261        view: &InputView<TextareaMode>,
6262        cx: &mut VisualTestContext,
6263    ) -> (Pixels, f32) {
6264        use gpui::TouchPhase::{Moved, Started};
6265
6266        cx.update(|window, cx| window.draw(cx).clear(cx));
6267        swipe(view, cx, -20., Started);
6268        swipe(view, cx, -100_000., Moved);
6269        view.input.read_with(cx, |state, _| {
6270            (state.scroll_handle.offset().y, state.overscroll.offset())
6271        })
6272    }
6273
6274    #[gpui::test]
6275    fn test_scroll_bounce_stays_put_under_reduced_motion(cx: &mut TestAppContext) {
6276        cx.update(|cx| cx.set_reduce_motion(true));
6277        let view = InputView::build_textarea(cx, |state| {
6278            state.scroll_bounce(true).default_value(long_text())
6279        });
6280        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6281        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6282        assert!(end < px(0.), "the text still scrolls");
6283        assert_eq!(stretch, 0., "the edge does not stretch");
6284    }
6285
6286    #[gpui::test]
6287    fn test_scroll_bounce_follows_the_platform_by_default(cx: &mut TestAppContext) {
6288        let view = InputView::build_textarea(cx, |state| state.default_value(long_text()));
6289        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6290        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6291        assert!(end < px(0.), "the text scrolls");
6292        assert_eq!(
6293            stretch != 0.,
6294            crate::ScrollBounce::enabled_by_default(),
6295            "the edge stretches where the platform's scroll views do"
6296        );
6297    }
6298
6299    #[gpui::test]
6300    fn test_scroll_bounce_leaves_text_that_fits(cx: &mut TestAppContext) {
6301        let view = InputView::build_textarea(cx, |state| {
6302            state.scroll_bounce(true).default_value("one line")
6303        });
6304        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6305        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6306        assert_eq!((end, stretch), (px(0.), 0.));
6307    }
6308
6309    #[gpui::test]
6310    fn test_scroll_bounce_resets_when_replacement_text_fits(cx: &mut TestAppContext) {
6311        let view = InputView::build_textarea(cx, |state| {
6312            state.scroll_bounce(true).default_value(long_text())
6313        });
6314        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6315        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6316        assert!(stretch < 0.);
6317        cx.update(|window, cx| {
6318            view.input
6319                .update(cx, |state, cx| state.set_value("short", window, cx));
6320            window.draw(cx).clear(cx);
6321        });
6322        swipe(&view, &mut cx, 0., gpui::TouchPhase::Ended);
6323        view.input.read_with(&cx, |state, _| {
6324            assert_eq!(state.overscroll.offset(), 0.);
6325            assert_eq!(state.scroll_handle.offset().y, px(0.));
6326        });
6327    }
6328
6329    #[gpui::test]
6330    fn test_scroll_bounce_releases_when_the_end_packet_is_horizontal(cx: &mut TestAppContext) {
6331        let view = InputView::build_textarea(cx, |state| {
6332            state.scroll_bounce(true).default_value(long_text())
6333        });
6334        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6335        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6336        assert!(stretch < 0.);
6337        cx.update(|window, cx| {
6338            view.input.update(cx, |state, cx| {
6339                state.on_scroll_wheel(
6340                    &ScrollEvent {
6341                        delta: gpui::ScrollDelta::Pixels(point(px(1.), px(0.))),
6342                        touch_phase: gpui::TouchPhase::Ended,
6343                        ..Default::default()
6344                    },
6345                    window,
6346                    cx,
6347                );
6348            });
6349        });
6350        for _ in 0..120 {
6351            cx.executor()
6352                .advance_clock(std::time::Duration::from_millis(16));
6353            cx.update(|window, cx| window.draw(cx).clear(cx));
6354        }
6355        assert_eq!(
6356            view.input
6357                .read_with(&cx, |state, _| state.overscroll.offset()),
6358            0.
6359        );
6360    }
6361
6362    #[gpui::test]
6363    fn test_scroll_bounce_ordinary_mouse_wheel_does_not_stretch(cx: &mut TestAppContext) {
6364        let view = InputView::build_textarea(cx, |state| {
6365            state.scroll_bounce(true).default_value(long_text())
6366        });
6367        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6368        cx.update(|window, cx| {
6369            window.draw(cx).clear(cx);
6370            view.input.update(cx, |state, cx| {
6371                state.on_scroll_wheel(
6372                    &ScrollEvent {
6373                        delta: gpui::ScrollDelta::Lines(point(0., -100_000.)),
6374                        touch_phase: gpui::TouchPhase::Moved,
6375                        ..Default::default()
6376                    },
6377                    window,
6378                    cx,
6379                );
6380                assert!(state.scroll_handle.offset().y < px(0.));
6381                assert_eq!(state.overscroll.offset(), 0.);
6382            });
6383        });
6384    }
6385
6386    #[gpui::test]
6387    fn test_scroll_bounce_new_wheel_input_interrupts_the_return(cx: &mut TestAppContext) {
6388        let view = InputView::build_textarea(cx, |state| {
6389            state.scroll_bounce(true).default_value(long_text())
6390        });
6391        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6392        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6393        assert!(stretch < 0.);
6394        swipe(&view, &mut cx, 0., gpui::TouchPhase::Ended);
6395        cx.executor()
6396            .advance_clock(std::time::Duration::from_millis(16));
6397        cx.update(|window, cx| {
6398            window.draw(cx).clear(cx);
6399            view.input.update(cx, |state, cx| {
6400                state.on_scroll_wheel(
6401                    &ScrollEvent {
6402                        delta: gpui::ScrollDelta::Lines(point(0., 2.)),
6403                        touch_phase: gpui::TouchPhase::Moved,
6404                        ..Default::default()
6405                    },
6406                    window,
6407                    cx,
6408                );
6409                assert!(state.scroll_handle.offset().y > end);
6410                assert_eq!(state.overscroll.offset(), 0.);
6411            });
6412        });
6413    }
6414
6415    #[gpui::test]
6416    fn test_turning_scroll_bounce_off_drops_the_stretch(cx: &mut TestAppContext) {
6417        let view = InputView::build_textarea(cx, |state| {
6418            state.scroll_bounce(true).default_value(long_text())
6419        });
6420        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6421        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6422        assert!(stretch < 0., "the edge stretched: {stretch}");
6423        view.input
6424            .update(&mut cx, |state, cx| state.set_scroll_bounce(false, cx));
6425        assert_eq!(
6426            view.input
6427                .read_with(&cx, |state, _| state.overscroll.offset()),
6428            0.
6429        );
6430    }
6431
6432    /// The frame is laid out by the application, which should not have to
6433    /// center a single line inside it.
6434    #[gpui::test]
6435    fn single_line_is_centered_in_a_taller_frame(cx: &mut TestAppContext) {
6436        struct Frame(Entity<InputBaseState<InputMode>>);
6437        impl Render for Frame {
6438            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6439                crate::input::InputBase::new("frame")
6440                    .h(px(60.))
6441                    .child(crate::input::Input::new(&self.0))
6442            }
6443        }
6444
6445        cx.update(crate::init);
6446        let mut input = None;
6447        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6448            let state = cx.new(|cx| crate::input::InputState::new(window, cx).default_value("a"));
6449            input = Some(state.clone());
6450            Frame(state)
6451        });
6452        let input = input.unwrap();
6453        let mut cx = VisualTestContext::from_window(window.into(), cx);
6454        cx.update(|window, cx| window.draw(cx).clear(cx));
6455
6456        input.read_with(&cx, |state, _| {
6457            let line = state.input_bounds;
6458            assert_eq!(line.center().y, px(30.), "line {line:?}");
6459        });
6460    }
6461
6462    #[gpui::test]
6463    fn test_noop_scroll_notifies_diagnostic_dismissal(cx: &mut TestAppContext) {
6464        use std::{cell::Cell, rc::Rc};
6465
6466        cx.update(crate::init);
6467        let mut input = None;
6468        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6469            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
6470            gpui::EmptyView
6471        });
6472        let input = input.unwrap();
6473        input.update(cx, |state, cx| {
6474            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
6475            state.scroll_size = size(px(100.), px(20.));
6476            state.present_diagnostic(crate::input::DiagnosticEntry::default(), cx);
6477        });
6478        let notifications = Rc::new(Cell::new(0));
6479        let count = notifications.clone();
6480        let _subscription =
6481            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6482
6483        window
6484            .update(cx, |_, window, cx| {
6485                input.update(cx, |state, cx| {
6486                    state.on_scroll_wheel(
6487                        &ScrollEvent {
6488                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
6489                            ..Default::default()
6490                        },
6491                        window,
6492                        cx,
6493                    );
6494                    assert_eq!(state.scroll_handle.offset(), Point::default());
6495                    assert!(state.diagnostic_popover().is_none());
6496                });
6497            })
6498            .unwrap();
6499        assert_eq!(
6500            notifications.get(),
6501            1,
6502            "clearing a diagnostic must notify even when scrolling is clamped"
6503        );
6504        window
6505            .update(cx, |_, window, cx| {
6506                input.update(cx, |state, cx| {
6507                    state.on_scroll_wheel(
6508                        &ScrollEvent {
6509                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
6510                            ..Default::default()
6511                        },
6512                        window,
6513                        cx,
6514                    );
6515                });
6516            })
6517            .unwrap();
6518        assert_eq!(notifications.get(), 1, "an unchanged input must stay quiet");
6519    }
6520
6521    #[gpui::test]
6522    fn test_cursor_layout_consumer_updates_after_selection(cx: &mut TestAppContext) {
6523        use std::{cell::Cell, rc::Rc};
6524        struct Panel {
6525            input: Entity<crate::input::EditorState>,
6526            observed: Rc<Cell<Option<(Bounds<Pixels>, Pixels)>>>,
6527        }
6528        impl Render for Panel {
6529            fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
6530                self.observed.set(self.input.read(cx).cursor_layout());
6531                div().size_full().child(self.input.clone())
6532            }
6533        }
6534        struct Root(Entity<Panel>);
6535        impl Render for Root {
6536            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6537                div().size_full().child(
6538                    self.0
6539                        .clone()
6540                        .cached(gpui::StyleRefinement::default().size_full()),
6541                )
6542            }
6543        }
6544        cx.update(crate::init);
6545        let observed = Rc::new(Cell::new(None));
6546        let mut input = None;
6547        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6548            let state = cx.new(|cx| {
6549                let mut state = crate::input::EditorState::new(window, cx);
6550                state.set_value("abcdefgh", window, cx);
6551                state
6552            });
6553            input = Some(state.clone());
6554            Root(cx.new(|_| Panel {
6555                input: state,
6556                observed: observed.clone(),
6557            }))
6558        });
6559        cx.run_until_parked();
6560        let input = input.unwrap();
6561        let before = input.read_with(cx, |state, _| state.cursor_layout());
6562        // The caret geometry only exists once the input has painted, and a
6563        // notify sent from inside a draw marks the view dirty without asking
6564        // for another frame, so the consumer reads the geometry on the next
6565        // invalidation rather than during the paint that produced it.
6566        input.update(cx, |_, cx| cx.notify());
6567        cx.run_until_parked();
6568        assert_eq!(observed.get(), before);
6569        window
6570            .update(cx, |_, _, cx| {
6571                input.update(cx, |state, cx| state.select_to_with_affinity(3, false, cx));
6572            })
6573            .unwrap();
6574        cx.run_until_parked();
6575        window.update(cx, |_, _, cx| cx.notify()).unwrap();
6576        let after = input.read_with(cx, |state, _| state.cursor_layout());
6577        assert_ne!(after, before, "the caret must actually move");
6578        assert_eq!(
6579            observed.get(),
6580            after,
6581            "render consumers must receive the newly painted caret geometry"
6582        );
6583    }
6584
6585    #[gpui::test]
6586    fn test_input_scroll_offset_notifies_only_on_change(cx: &mut TestAppContext) {
6587        use std::{cell::Cell, rc::Rc};
6588
6589        cx.update(crate::init);
6590        let mut input = None;
6591        let _window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6592            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
6593            gpui::EmptyView
6594        });
6595        let input = input.unwrap();
6596        let notifications = Rc::new(Cell::new(0));
6597        let count = notifications.clone();
6598        let _subscription =
6599            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6600
6601        input.update(cx, |state, _| {
6602            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
6603            state.scroll_size = size(px(300.), px(20.));
6604        });
6605        for target in [
6606            None,
6607            Some(point(px(0.), px(0.))),
6608            Some(point(px(20.), px(50.))),
6609        ] {
6610            input.update(cx, |state, cx| state.update_scroll_offset(target, cx));
6611            assert_eq!(
6612                notifications.get(),
6613                0,
6614                "an unchanged clamped offset must stay quiet"
6615            );
6616        }
6617        input.update(cx, |state, cx| {
6618            state.update_scroll_offset(Some(point(px(-40.), px(0.))), cx);
6619            assert_eq!(state.scroll_handle.offset(), point(px(-40.), px(0.)));
6620        });
6621        assert_eq!(notifications.get(), 1, "a scroll must notify");
6622        input.update(cx, |state, cx| {
6623            state.update_scroll_offset(Some(point(px(-400.), px(50.))), cx);
6624            assert_eq!(state.scroll_handle.offset(), point(px(-200.), px(0.)));
6625        });
6626        assert_eq!(notifications.get(), 2);
6627        input.update(cx, |state, cx| {
6628            state.update_scroll_offset(Some(point(px(-500.), px(0.))), cx);
6629        });
6630        assert_eq!(
6631            notifications.get(),
6632            2,
6633            "clamping to the current offset must stay quiet"
6634        );
6635        input.update(cx, |state, cx| {
6636            state.scroll_size.width = px(110.);
6637            state.update_scroll_offset(None, cx);
6638            assert_eq!(state.scroll_handle.offset(), point(px(-10.), px(0.)));
6639        });
6640        assert_eq!(
6641            notifications.get(),
6642            3,
6643            "a smaller scroll range must clamp and notify"
6644        );
6645    }
6646
6647    #[gpui::test]
6648    fn test_set_line_number_notifies_only_on_change(cx: &mut TestAppContext) {
6649        use std::{cell::Cell, rc::Rc};
6650
6651        cx.update(crate::init);
6652        let mut input = None;
6653        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6654            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
6655            gpui::EmptyView
6656        });
6657        let input = input.unwrap();
6658        let shown = input.read_with(cx, |state, _| state.mode.line_number());
6659        let notifications = Rc::new(Cell::new(0));
6660        let count = notifications.clone();
6661        let _subscription =
6662            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6663
6664        window
6665            .update(cx, |_, window, cx| {
6666                input.update(cx, |state, cx| state.set_line_number(shown, window, cx))
6667            })
6668            .unwrap();
6669        assert_eq!(notifications.get(), 0);
6670
6671        window
6672            .update(cx, |_, window, cx| {
6673                input.update(cx, |state, cx| state.set_line_number(!shown, window, cx))
6674            })
6675            .unwrap();
6676        assert_eq!(notifications.get(), 1);
6677        assert_eq!(
6678            input.read_with(cx, |state, _| state.mode.line_number()),
6679            !shown
6680        );
6681    }
6682
6683    #[gpui::test]
6684    fn test_set_value_on_unfocused_input_stays_quiet(cx: &mut TestAppContext) {
6685        use std::{cell::Cell, rc::Rc};
6686
6687        cx.update(crate::init);
6688        let mut input = None;
6689        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6690            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
6691            gpui::EmptyView
6692        });
6693        let input = input.unwrap();
6694        cx.run_until_parked();
6695
6696        let notifications = Rc::new(Cell::new(0));
6697        let count = notifications.clone();
6698        let _subscription =
6699            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6700        cx.run_until_parked();
6701
6702        // Seeding a form is a write, so it notifies once. The input is not
6703        // focused and draws no caret, so nothing may notify after that.
6704        window
6705            .update(cx, |_, window, cx| {
6706                input.update(cx, |state, cx| state.set_value("seeded", window, cx));
6707            })
6708            .unwrap();
6709        cx.run_until_parked();
6710        let settled = notifications.get();
6711
6712        cx.executor()
6713            .advance_clock(std::time::Duration::from_secs(3));
6714        cx.run_until_parked();
6715
6716        assert_eq!(
6717            notifications.get(),
6718            settled,
6719            "an unfocused input is blinking, and every blink repaints the view it is in"
6720        );
6721    }
6722
6723    #[gpui::test]
6724    fn test_input_does_not_invalidate_cached_parent_during_paint(cx: &mut TestAppContext) {
6725        use std::{cell::Cell, rc::Rc};
6726
6727        struct Panel {
6728            input: Entity<crate::input::InputState>,
6729            renders: Rc<Cell<usize>>,
6730        }
6731        impl Render for Panel {
6732            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6733                self.renders.set(self.renders.get() + 1);
6734                div().size_full().child(self.input.clone())
6735            }
6736        }
6737        struct Root(Entity<Panel>);
6738        impl Render for Root {
6739            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6740                div().size_full().child(
6741                    self.0
6742                        .clone()
6743                        .cached(gpui::StyleRefinement::default().size_full()),
6744                )
6745            }
6746        }
6747
6748        cx.update(crate::init);
6749        let renders = Rc::new(Cell::new(0));
6750        let mut input = None;
6751        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6752            let state = cx.new(|cx| crate::input::InputState::new(window, cx));
6753            input = Some(state.clone());
6754            Root(cx.new(|_| Panel {
6755                input: state,
6756                renders: renders.clone(),
6757            }))
6758        });
6759        cx.run_until_parked();
6760        let before = renders.get();
6761        assert!(before > 0);
6762        for _ in 0..60 {
6763            window.update(cx, |_, _, cx| cx.notify()).unwrap();
6764        }
6765        assert_eq!(
6766            renders.get(),
6767            before,
6768            "an idle input must not invalidate its cached parent"
6769        );
6770
6771        let input = input.unwrap();
6772        window
6773            .update(cx, |_, window, cx| {
6774                input.update(cx, |state, cx| state.set_value("changed", window, cx));
6775            })
6776            .unwrap();
6777        assert!(
6778            renders.get() > before,
6779            "text changes must invalidate the parent"
6780        );
6781        // The first paint reports the changed text extent to layout consumers.
6782        window.update(cx, |_, _, cx| cx.notify()).unwrap();
6783        let before = renders.get();
6784        for _ in 0..60 {
6785            window.update(cx, |_, _, cx| cx.notify()).unwrap();
6786        }
6787        assert_eq!(renders.get(), before, "the edited input must settle again");
6788
6789        cx.simulate_window_resize(window.into(), size(px(240.), px(100.)));
6790        cx.run_until_parked();
6791        assert!(renders.get() > before, "resizing must invalidate layout");
6792    }
6793
6794    #[gpui::test]
6795    fn textarea_cursor_treats_crlf_as_one_newline(cx: &mut TestAppContext) {
6796        cx.update(crate::init);
6797        let view = InputView::build_textarea(cx, |state| state);
6798        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6799        cx.update(|window, cx| {
6800            view.input.update(cx, |state, cx| {
6801                for original in ["\r\nlast", "\u{feff}first\r\nlast\n", "first\nlast\r\n"] {
6802                    state.set_value(original, window, cx);
6803                    let newline = original.find('\n').unwrap();
6804                    let end = if original.as_bytes()[newline.saturating_sub(1)] == b'\r' {
6805                        newline - 1
6806                    } else {
6807                        newline
6808                    };
6809                    state.end(&MoveEnd, window, cx);
6810                    assert_eq!(state.cursor(), end);
6811                    state.right(&MoveRight, window, cx);
6812                    assert_eq!(state.cursor(), newline + 1);
6813                    state.left(&MoveLeft, window, cx);
6814                    assert_eq!(state.cursor(), end);
6815                    state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
6816                    assert_eq!(state.selected_range(), end..end);
6817                    state.move_to_end(&MoveToEnd, window, cx);
6818                    assert_eq!(state.cursor(), original.len());
6819                    state.move_to_start(&MoveToStart, window, cx);
6820                    assert_eq!(state.cursor(), 0);
6821                    assert_eq!(state.value().as_bytes(), original.as_bytes());
6822                }
6823                // A lone CR is an ordinary character; it must not skip its neighbour.
6824                state.set_value("a\rb", window, cx);
6825                state.right(&MoveRight, window, cx);
6826                assert_eq!(state.cursor(), 1);
6827                state.right(&MoveRight, window, cx);
6828                assert_eq!(state.cursor(), 2);
6829            })
6830        });
6831    }
6832
6833    #[gpui::test]
6834    fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) {
6835        cx.update(crate::init);
6836
6837        // A single-line input keeps its caret in view by moving its own offset;
6838        // it has no viewport to drag, so a scrollbar in a text field is a
6839        // control that does not exist.
6840        let single = InputView::build(cx, |state| state);
6841        single
6842            .input
6843            .update(cx, |state, _| assert!(!state.shows_scrollbar()));
6844
6845        let multi = InputView::build_textarea(cx, |state| state);
6846        multi
6847            .input
6848            .update(cx, |state, _| assert!(state.shows_scrollbar()));
6849    }
6850
6851    #[gpui::test]
6852    fn context_menu_handler_is_deferred_and_respects_disabled(cx: &mut TestAppContext) {
6853        use std::{cell::Cell, rc::Rc};
6854        cx.update(crate::init);
6855        let input_view = InputView::new(cx);
6856        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6857        let input = input_view.input;
6858        let calls = Rc::new(Cell::new(0usize));
6859        let items = Rc::new(Cell::new(0usize));
6860        let menu_focus = cx.update(|_, cx| cx.focus_handle());
6861
6862        cx.update(|window, cx| {
6863            input.update(cx, |state, cx| {
6864                let calls2 = calls.clone();
6865                let items2 = items.clone();
6866                let menu_focus = menu_focus.clone();
6867                state.on_context_menu(Rc::new(move |menu, _, _, window, cx| {
6868                    calls2.set(calls2.get() + 1);
6869                    items2.set(menu.items.len());
6870                    menu_focus.focus(window, cx);
6871                }));
6872                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
6873            })
6874        });
6875        assert_eq!(calls.get(), 1);
6876        assert_eq!(items.get(), 0);
6877        input.read_with(&cx, |state, _| {
6878            assert_eq!(
6879                state
6880                    .selection_focus
6881                    .as_ref()
6882                    .and_then(WeakFocusHandle::upgrade)
6883                    .as_ref(),
6884                Some(&menu_focus),
6885                "the menu callback must retain selection without reborrowing its input"
6886            );
6887        });
6888
6889        // A custom handler may keep using the same, already-focused menu.
6890        cx.update(|window, cx| {
6891            input.update(cx, |state, cx| {
6892                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
6893            });
6894        });
6895        assert_eq!(calls.get(), 2);
6896        cx.update(|window, cx| {
6897            assert!(input.read(cx).has_selection_focus(window, cx));
6898        });
6899
6900        cx.update(|window, cx| {
6901            input.update(cx, |state, cx| {
6902                state.disabled = true;
6903                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
6904            })
6905        });
6906        assert_eq!(calls.get(), 2);
6907    }
6908
6909    #[gpui::test]
6910    fn context_menu_handler_can_read_its_input(cx: &mut TestAppContext) {
6911        use std::{cell::Cell, rc::Rc};
6912        cx.update(crate::init);
6913        let input_view = InputView::new(cx);
6914        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6915        let input = input_view.input;
6916        let cursor = Rc::new(Cell::new(None));
6917
6918        cx.update(|window, cx| {
6919            input.update(cx, |state, cx| {
6920                state.set_value("hello", window, cx);
6921                let weak = cx.weak_entity();
6922                let cursor = cursor.clone();
6923                state.on_context_menu(Rc::new(move |_, _, _, _, cx| {
6924                    cursor.set(weak.upgrade().map(|input| input.read(cx).cursor()));
6925                }));
6926                state.handle_right_click_menu(point(px(0.), px(0.)), 2, window, cx);
6927            })
6928        });
6929        assert_eq!(cursor.get(), Some(2));
6930    }
6931
6932    #[gpui::test]
6933    fn test_readonly_rejects_user_edits_only(cx: &mut TestAppContext) {
6934        let input_view = InputView::new(cx);
6935        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6936        let input = input_view.input;
6937
6938        cx.update(|window, cx| {
6939            input.update(cx, |state, cx| {
6940                state.set_value("hello", window, cx);
6941                state.set_readonly(true, cx);
6942            });
6943        });
6944
6945        cx.update(|_, cx| {
6946            input.read_with(cx, |state, _| {
6947                assert!(!state.is_editable());
6948                assert!(!state.is_replaceable());
6949            });
6950        });
6951
6952        // Typing (and IME) goes through the input handler, it must be rejected.
6953        cx.update(|window, cx| {
6954            input.update(cx, |state, cx| {
6955                state.replace_text_in_range(None, " world", window, cx);
6956                state.replace_and_mark_text_in_range(None, "あ", None, window, cx);
6957            });
6958        });
6959        cx.update(|_, cx| {
6960            input.read_with(cx, |state, _| assert_eq!(state.value(), "hello"));
6961        });
6962
6963        // The programmatic APIs are not limited by the readonly mode.
6964        cx.update(|window, cx| {
6965            input.update(cx, |state, cx| {
6966                state.insert(" world", window, cx);
6967                state.set_value("changed", window, cx);
6968            });
6969        });
6970        cx.update(|_, cx| {
6971            input.read_with(cx, |state, _| assert_eq!(state.value(), "changed"));
6972        });
6973
6974        // And the user can edit again after leaving the readonly mode.
6975        // The caret is at the start, because `set_value` has reset the selection.
6976        cx.update(|window, cx| {
6977            input.update(cx, |state, cx| {
6978                state.set_readonly(false, cx);
6979                state.replace_text_in_range(None, "!", window, cx);
6980            });
6981        });
6982        cx.update(|_, cx| {
6983            input.read_with(cx, |state, _| {
6984                assert!(state.is_editable());
6985                assert_eq!(state.value(), "!changed");
6986            });
6987        });
6988    }
6989
6990    /// Regression test: editing at a caret that sits far outside the viewport
6991    /// must reveal it in one frame. The cursor-follow in `layout_cursors` runs
6992    /// only on the frame the selection changes, so its old one-line step left
6993    /// the caret offscreen after typing at the end of a long paste.
6994    #[gpui::test]
6995    fn test_edit_reveals_far_offscreen_caret(cx: &mut TestAppContext) {
6996        let mut input = None;
6997        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
6998            cx.set_global(Theme::default());
6999            super::super::init(cx);
7000            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 6));
7001            input = Some(state.clone());
7002            TestRoot(state)
7003        });
7004        let mut cx = VisualTestContext::from_window(window.into(), cx);
7005        let input = input.unwrap();
7006
7007        let text: String = (1..=100)
7008            .map(|i| format!("line {i}"))
7009            .collect::<Vec<_>>()
7010            .join("\n");
7011        cx.update(|window, cx| {
7012            input.update(cx, |state, cx| {
7013                state.set_value(text, window, cx);
7014                let end = state.text.len();
7015                state.set_selection(end, end);
7016            });
7017        });
7018        cx.run_until_parked();
7019
7020        // The user scrolled back to the top to read the pasted text.
7021        cx.update(|_, cx| {
7022            input.update(cx, |state, cx| {
7023                state.scroll_handle.set_offset(point(px(0.), px(0.)));
7024                cx.notify();
7025            });
7026        });
7027        cx.run_until_parked();
7028
7029        cx.update(|window, cx| {
7030            input.update(cx, |state, cx| {
7031                state.replace_text_in_range(None, "X", window, cx);
7032            });
7033        });
7034        cx.run_until_parked();
7035
7036        cx.update(|_, cx| {
7037            input.read_with(cx, |state, _| {
7038                assert!(state.value().ends_with("line 100X"));
7039                let (caret, _) = state.cursor_layout().expect("caret painted");
7040                let viewport = state.input_bounds();
7041                let scroll_y = state.scroll_handle.offset().y;
7042                let top = caret.origin.y - viewport.origin.y + scroll_y;
7043                assert!(
7044                    top >= px(0.) && top + caret.size.height <= viewport.size.height,
7045                    "caret top {top:?} outside viewport height {:?} (scroll {scroll_y:?})",
7046                    viewport.size.height,
7047                );
7048            });
7049        });
7050    }
7051
7052    /// Regression test: changing the auto-grow limits keeps the rows fitted to
7053    /// the text. `set_auto_grow` used to start over at `min_rows`, and the rows
7054    /// were counted again only on the next edit or wrap width change.
7055    #[gpui::test]
7056    fn test_set_auto_grow_keeps_rows_fitted(cx: &mut TestAppContext) {
7057        let mut input = None;
7058        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
7059            cx.set_global(Theme::default());
7060            super::super::init(cx);
7061            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 10));
7062            input = Some(state.clone());
7063            TestRoot(state)
7064        });
7065        let mut cx = VisualTestContext::from_window(window.into(), cx);
7066        let input = input.unwrap();
7067
7068        let text: String = (1..=8)
7069            .map(|i| format!("line {i}"))
7070            .collect::<Vec<_>>()
7071            .join("\n");
7072        cx.update(|window, cx| {
7073            input.update(cx, |state, cx| state.set_value(text, window, cx));
7074        });
7075        cx.run_until_parked();
7076        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 8)));
7077
7078        // A larger maximum keeps all eight rows.
7079        cx.update(|_, cx| input.update(cx, |state, cx| state.set_auto_grow(1, 12, cx)));
7080        cx.run_until_parked();
7081        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 8)));
7082
7083        // A smaller maximum caps them.
7084        cx.update(|_, cx| input.update(cx, |state, cx| state.set_auto_grow(1, 4, cx)));
7085        cx.run_until_parked();
7086        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 4)));
7087    }
7088
7089    /// Regression test: `scroll_to` at end-of-buffer must produce a deferred
7090    /// scroll target within the safe scroll range, so the painted frame
7091    /// matches what `update_scroll_offset` persists (no jitter). A small
7092    /// `cursor_surrounding_lines` override used to mismatch the hardcoded
7093    /// 3-line edge clearance in `scroll_to`, overshooting `safe_y_min`.
7094    #[gpui::test]
7095    fn test_scroll_to_eob_does_not_overshoot_safe_range(cx: &mut TestAppContext) {
7096        let input_view = InputView::new(cx);
7097        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7098        let input = input_view.input;
7099
7100        // JetBrains-style: 1 trailing empty row + 1-line cursor surrounding.
7101        cx.update(|window, cx| {
7102            input.update(cx, |state, cx| {
7103                state.set_scroll_beyond_last_line(Some(1), window, cx);
7104                state.set_cursor_surrounding_lines(Some(1), window, cx);
7105                let text: String = (1..=50)
7106                    .map(|i| format!("line {i}"))
7107                    .collect::<Vec<_>>()
7108                    .join("\n");
7109                state.set_value(text, window, cx);
7110            });
7111        });
7112        cx.run_until_parked();
7113
7114        // Sanity: paint populated `scroll_size` and `input_bounds` — without
7115        // these, `safe_y_min` below collapses to 0 and the assertion is vacuous.
7116        cx.update(|_, cx| {
7117            input.read_with(cx, |state, _| {
7118                assert!(
7119                    state.scroll_size.height > px(0.),
7120                    "scroll_size not populated by initial paint"
7121                );
7122                assert!(
7123                    state.input_bounds.size.height > px(0.),
7124                    "input_bounds not populated by initial paint"
7125                );
7126            });
7127        });
7128
7129        // Move cursor to end with downward direction — same code path as a
7130        // `Down` keystroke at EOB. `scroll_to` runs synchronously inside
7131        // `move_to`; inspect `deferred_scroll_offset` in the same closure
7132        // before the next paint consumes and clears it.
7133        cx.update(|_, cx| {
7134            input.update(cx, |state, cx| {
7135                let end = state.text.len();
7136                state.move_to(end, Some(MoveDirection::Down), cx);
7137
7138                let deferred = state
7139                    .deferred_scroll_offset
7140                    .expect("scroll_to should populate deferred_scroll_offset");
7141                let safe_y_min =
7142                    (-state.scroll_size.height + state.input_bounds.size.height).min(px(0.));
7143
7144                assert!(
7145                    deferred.y >= safe_y_min,
7146                    "deferred_scroll_offset.y = {:?} below safe_y_min = {:?} \
7147                     — paint would jitter (Bug C regression)",
7148                    deferred.y,
7149                    safe_y_min,
7150                );
7151            });
7152        });
7153    }
7154
7155    #[gpui::test]
7156    fn test_next_search_match_reveals_with_padding_after_manual_scroll(cx: &mut TestAppContext) {
7157        assert_search_reveals_with_padding_after_manual_scroll(false, cx);
7158    }
7159
7160    #[gpui::test]
7161    fn test_previous_search_match_reveals_with_padding_after_manual_scroll(
7162        cx: &mut TestAppContext,
7163    ) {
7164        assert_search_reveals_with_padding_after_manual_scroll(true, cx);
7165    }
7166
7167    fn assert_search_reveals_with_padding_after_manual_scroll(
7168        previous: bool,
7169        cx: &mut TestAppContext,
7170    ) {
7171        let input_view = InputView::new(cx);
7172        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7173        let input = input_view.input;
7174        let text = (0..160)
7175            .map(|row| {
7176                if matches!(row, 20 | 60 | 100) {
7177                    format!("match on row {row}")
7178                } else {
7179                    format!("line {row}")
7180                }
7181            })
7182            .collect::<Vec<_>>()
7183            .join("\n");
7184        let start = text.match_indices("match").nth(1).unwrap().0;
7185        let expected_match = start..start + "match".len();
7186        cx.update(|window, cx| {
7187            input.update(cx, |state, cx| {
7188                state.set_cursor_surrounding_lines(Some(3), window, cx);
7189                state.set_value(text, window, cx);
7190                state.set_search_query("match", true, cx);
7191                if previous {
7192                    state.search_session.matcher.next();
7193                    state.search_session.matcher.next();
7194                }
7195            });
7196        });
7197        cx.run_until_parked();
7198        cx.update(|_, cx| {
7199            input.update(cx, |state, cx| {
7200                let line_height = state.last_layout.as_ref().unwrap().line_height;
7201                let y = if previous { px(0.) } else { -line_height * 80. };
7202                state.set_scroll_offset(point(px(0.), y), cx);
7203            });
7204        });
7205        cx.run_until_parked();
7206        input.read_with(&cx, |state, _| {
7207            let visible = state.visible_row_range().unwrap();
7208            if previous {
7209                assert!(visible.end <= 60, "target must be below the viewport");
7210            } else {
7211                assert!(visible.start > 60, "target must be above the viewport");
7212            }
7213        });
7214        cx.update(|_, cx| {
7215            input.update(cx, |state, cx| {
7216                let range = if previous {
7217                    state.previous_search_match(cx)
7218                } else {
7219                    state.next_search_match(cx)
7220                };
7221                assert_eq!(range, Some(expected_match));
7222                assert_eq!(state.search_session.matcher.label(), "2/3");
7223            });
7224        });
7225        cx.run_until_parked();
7226        input.read_with(&cx, |state, _| {
7227            assert!(state.visible_row_range().unwrap().contains(&60));
7228            let line_height = state.last_layout.as_ref().unwrap().line_height;
7229            let target_y = line_height * 60. + state.scroll_handle.offset().y;
7230            // Three lines of edge clearance include the matched line itself.
7231            assert!(target_y >= line_height * 2. - px(0.1));
7232            assert!(
7233                target_y + line_height * 3.
7234                    <= state.last_bounds.as_ref().unwrap().size.height + px(0.1),
7235                "search must preserve the configured surrounding-line padding"
7236            );
7237        });
7238    }
7239
7240    /// A host that wants the search shortcut for its own search UI.
7241    struct SearchHost {
7242        editor: Entity<InputBaseState<EditorMode>>,
7243        search_requests: Rc<Cell<usize>>,
7244    }
7245
7246    impl Render for SearchHost {
7247        fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
7248            let search_requests = self.search_requests.clone();
7249            div()
7250                .size_full()
7251                .on_action(cx.listener(move |_, _: &Search, _, _| {
7252                    search_requests.set(search_requests.get() + 1);
7253                }))
7254                .child(self.editor.clone())
7255        }
7256    }
7257
7258    /// Opens an editor inside [`SearchHost`], focused, and presses the search
7259    /// shortcut once. Returns the editor and the host's request count.
7260    fn press_search_shortcut(
7261        cx: &mut TestAppContext,
7262        searchable: bool,
7263    ) -> (Entity<InputBaseState<EditorMode>>, Rc<Cell<usize>>) {
7264        let search_requests = Rc::new(Cell::new(0));
7265        let mut editor = None;
7266        let window = cx.update(|cx| {
7267            cx.open_window(Default::default(), |window, cx| {
7268                cx.set_global(Theme::default());
7269                super::super::init(cx);
7270                let state =
7271                    cx.new(|cx| crate::input::EditorState::new(window, cx).searchable(searchable));
7272                editor = Some(state.clone());
7273                cx.new(|_| SearchHost {
7274                    editor: state,
7275                    search_requests: search_requests.clone(),
7276                })
7277            })
7278            .unwrap()
7279        });
7280        let editor = editor.unwrap();
7281        let mut cx = VisualTestContext::from_window(window.into(), cx);
7282        cx.update(|window, cx| {
7283            editor.update(cx, |state, cx| state.focus(window, cx));
7284        });
7285        cx.run_until_parked();
7286        #[cfg(target_os = "macos")]
7287        cx.simulate_keystrokes("cmd-f");
7288        #[cfg(not(target_os = "macos"))]
7289        cx.simulate_keystrokes("ctrl-f");
7290        cx.run_until_parked();
7291        (editor, search_requests)
7292    }
7293
7294    #[gpui::test]
7295    fn test_search_shortcut_reaches_the_host_when_not_searchable(cx: &mut TestAppContext) {
7296        let (editor, search_requests) = press_search_shortcut(cx, false);
7297        assert_eq!(search_requests.get(), 1);
7298        editor.read_with(cx, |state, _| {
7299            assert!(!state.search_session().open);
7300            assert!(!state.search_session().is_active());
7301        });
7302    }
7303
7304    #[gpui::test]
7305    fn test_search_shortcut_opens_the_panel_when_searchable(cx: &mut TestAppContext) {
7306        let (editor, search_requests) = press_search_shortcut(cx, true);
7307        assert_eq!(search_requests.get(), 0);
7308        editor.read_with(cx, |state, _| {
7309            assert!(state.search_session().open);
7310            assert!(state.search_session().is_active());
7311        });
7312    }
7313
7314    #[gpui::test]
7315    fn test_set_search_query_highlights_without_the_panel(cx: &mut TestAppContext) {
7316        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
7317        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7318        let input = input_view.input;
7319        cx.update(|window, cx| {
7320            input.update(cx, |state, cx| {
7321                state.set_value("foo bar foo", window, cx);
7322                state.set_search_query("foo", true, cx);
7323            });
7324        });
7325        cx.run_until_parked();
7326        input.read_with(&cx, |state, _| {
7327            let session = state.search_session();
7328            assert!(session.is_active());
7329            assert!(!session.open);
7330            assert_eq!(session.matcher.len(), 2);
7331        });
7332        cx.update(|_, cx| {
7333            input.update(cx, |state, cx| state.close_search(cx));
7334        });
7335        input.read_with(&cx, |state, _| {
7336            assert!(!state.search_session().is_active());
7337        });
7338    }
7339
7340    /// A closed search skips rescanning on edits, so every path that reads
7341    /// the matches again must see the edited text, not the text at close.
7342    #[gpui::test]
7343    fn test_closed_search_resyncs_matches_after_edits(cx: &mut TestAppContext) {
7344        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
7345        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7346        let input = input_view.input;
7347        cx.update(|window, cx| {
7348            input.update(cx, |state, cx| {
7349                state.set_value("foo bar foo", window, cx);
7350                state.set_search_query("foo", true, cx);
7351                state.close_search(cx);
7352
7353                state.set_value("bar foo", window, cx);
7354                assert_eq!(state.next_search_match(cx), Some(4..7));
7355
7356                state.set_value("foo foo foo", window, cx);
7357                state.set_search_query("foo", true, cx);
7358                assert_eq!(state.search_session().matcher.len(), 3);
7359            });
7360        });
7361    }
7362
7363    #[gpui::test]
7364    fn test_search_reveals_offscreen_wrapped_match(cx: &mut TestAppContext) {
7365        let input_view = InputView::new(cx);
7366        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7367        let input = input_view.input;
7368        let text = format!(
7369            "match\n{}\n{}match\n{}",
7370            "line\n".repeat(80),
7371            "wrapped text ".repeat(500),
7372            "line\n".repeat(80)
7373        );
7374        cx.update(|window, cx| {
7375            input.update(cx, |state, cx| {
7376                state.set_cursor_surrounding_lines(Some(3), window, cx);
7377                state.set_value(text, window, cx);
7378                state.set_search_query("match", true, cx);
7379            });
7380        });
7381        cx.run_until_parked();
7382        cx.update(|_, cx| {
7383            input.update(cx, |state, cx| {
7384                state.next_search_match(cx).unwrap();
7385            });
7386        });
7387        cx.run_until_parked();
7388        input.read_with(&cx, |state, _| {
7389            let range = state.search_session.matcher.matched_ranges()[1].clone();
7390            let layout = state.last_layout.as_ref().unwrap();
7391            let (_, _, position) = state.line_and_position_for_offset(range.end);
7392            let y = position.expect("wrapped match must be laid out").y
7393                + state.scroll_handle.offset().y;
7394            assert!(y >= layout.line_height * 2. - px(0.1));
7395            assert!(
7396                y + layout.line_height * 3. <= state.last_bounds.unwrap().size.height + px(0.1),
7397                "wrapped match must retain surrounding display rows"
7398            );
7399        });
7400    }
7401
7402    #[gpui::test]
7403    fn test_number_step(cx: &mut TestAppContext) {
7404        let input = InputView::build(cx, |state| state).input;
7405
7406        cx.update(|cx| {
7407            input.update(cx, |_state, cx| {
7408                assert_eq!(
7409                    NumberStep::from(5.).value(123., StepAction::Increment, cx),
7410                    5.
7411                );
7412
7413                // The step can differ by direction at a boundary: at 1.0 it
7414                // is 0.1 going down and 0.5 going up.
7415                let step = NumberStep::by_value(|value, action, _cx| {
7416                    let below = match action {
7417                        StepAction::Increment => value < 1.0,
7418                        StepAction::Decrement => value <= 1.0,
7419                    };
7420                    if below { 0.1 } else { 0.5 }
7421                });
7422                assert_eq!(step.value(0.5, StepAction::Increment, cx), 0.1);
7423                assert_eq!(step.value(1.0, StepAction::Increment, cx), 0.5);
7424                assert_eq!(step.value(1.0, StepAction::Decrement, cx), 0.1);
7425                assert_eq!(step.value(2.0, StepAction::Decrement, cx), 0.5);
7426            });
7427        });
7428    }
7429
7430    #[gpui::test]
7431    fn test_number_input_normalization(cx: &mut TestAppContext) {
7432        let input_view = InputView::build(cx, |state| {
7433            state.mask_pattern(MaskPattern::Number {
7434                separator: None,
7435                fraction: None,
7436            })
7437        });
7438        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7439        let input = input_view.input;
7440
7441        // Full-width digits and the ideographic full stop are normalized,
7442        // and the cursor is at the end (in normalized bytes, not the
7443        // original 12 bytes).
7444        cx.update(|window, cx| {
7445            input.update(cx, |state, cx| {
7446                state.replace_text_in_range(None, "12。5", window, cx);
7447            });
7448        });
7449        cx.run_until_parked();
7450        cx.update(|_, cx| {
7451            input.read_with(cx, |state, _| {
7452                assert_eq!(state.value(), "12.5");
7453                let cursor: Range<usize> = state.selected_range();
7454                assert_eq!(cursor, 4..4);
7455            });
7456        });
7457
7458        // Non-numeric input is rejected.
7459        cx.update(|window, cx| {
7460            input.update(cx, |state, cx| {
7461                state.replace_text_in_range(None, "abc", window, cx);
7462            });
7463        });
7464        cx.run_until_parked();
7465        cx.update(|_, cx| {
7466            input.read_with(cx, |state, _| {
7467                assert_eq!(state.value(), "12.5");
7468            });
7469        });
7470
7471        // A bare leading dot is kept as-is (normalized from the ideographic
7472        // full stop), not completed to "0.", so it stays editable.
7473        cx.update(|window, cx| {
7474            input.update(cx, |state, cx| {
7475                let range = state.range_to_utf16(&(0..state.text.len()));
7476                state.replace_text_in_range(Some(range), "。", window, cx);
7477            });
7478        });
7479        cx.run_until_parked();
7480        cx.update(|_, cx| {
7481            input.read_with(cx, |state, _| {
7482                assert_eq!(state.value(), ".");
7483                let cursor: Range<usize> = state.selected_range();
7484                assert_eq!(cursor, 1..1);
7485            });
7486        });
7487    }
7488
7489    #[gpui::test]
7490    fn test_mask_pattern_accepts_non_ascii_text(cx: &mut TestAppContext) {
7491        let input_view = InputView::build(cx, |state| state.mask_pattern("9999年99月"));
7492        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7493        let input = input_view.input;
7494
7495        cx.update(|window, cx| {
7496            input.update(cx, |state, cx| {
7497                state.replace_text_in_range(None, "2024年12月", window, cx);
7498            });
7499        });
7500        cx.run_until_parked();
7501        cx.update(|_, cx| {
7502            input.read_with(cx, |state, _| {
7503                assert_eq!(state.value(), "2024年12月");
7504            });
7505        });
7506    }
7507
7508    #[gpui::test]
7509    fn test_number_input_normalization_with_separator(cx: &mut TestAppContext) {
7510        let input_view = InputView::build(cx, |state| {
7511            state.mask_pattern(MaskPattern::Number {
7512                separator: Some(','),
7513                fraction: Some(2),
7514            })
7515        });
7516        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7517        let input = input_view.input;
7518
7519        cx.update(|window, cx| {
7520            input.update(cx, |state, cx| {
7521                state.replace_text_in_range(None, "1234", window, cx);
7522            });
7523        });
7524        cx.run_until_parked();
7525        cx.update(|_, cx| {
7526            input.read_with(cx, |state, _| {
7527                assert_eq!(state.value(), "1,234");
7528                assert_eq!(state.unmask_value(), "1234");
7529            });
7530        });
7531    }
7532
7533    #[gpui::test]
7534    fn test_number_input_clamp_on_blur(cx: &mut TestAppContext) {
7535        let input_view = InputView::build(cx, |state| {
7536            state
7537                .mask_pattern(MaskPattern::Number {
7538                    separator: None,
7539                    fraction: None,
7540                })
7541                .min(10.)
7542                .max(100.)
7543        });
7544        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7545        let input = input_view.input;
7546
7547        // Out-of-range values are allowed while typing, and clamped on blur.
7548        cx.update(|window, cx| {
7549            input.update(cx, |state, cx| {
7550                state.replace_text_in_range(None, "1000", window, cx);
7551                assert_eq!(state.value(), "1000");
7552                state.clamp_number_value(window, cx);
7553                assert_eq!(state.value(), "100");
7554
7555                let range = state.range_to_utf16(&(0..state.text.len()));
7556                state.replace_text_in_range(Some(range), "1", window, cx);
7557                assert_eq!(state.value(), "1");
7558                state.clamp_number_value(window, cx);
7559                assert_eq!(state.value(), "10");
7560            });
7561        });
7562    }
7563
7564    #[gpui::test]
7565    fn test_number_input_undo_with_mask(cx: &mut TestAppContext) {
7566        let input_view = InputView::build(cx, |state| {
7567            state.mask_pattern(MaskPattern::Number {
7568                separator: Some(','),
7569                fraction: None,
7570            })
7571        });
7572        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7573        let input = input_view.input;
7574
7575        // When the mask changes the text (regrouping separators), a
7576        // whole-document change is recorded, so undo/redo can restore it.
7577        cx.update(|window, cx| {
7578            input.update(cx, |state, cx| {
7579                state.replace_text_in_range(None, "1234", window, cx);
7580                assert_eq!(state.value(), "1,234");
7581                state.replace_text_in_range(None, "5", window, cx);
7582                assert_eq!(state.value(), "12,345");
7583
7584                // Each whole-document mask rewrite is an atomic undo step.
7585                // Before the whole-document history fix, undo produced a
7586                // corrupted value like "1,2344".
7587                state.undo(&Undo, window, cx);
7588                assert_eq!(state.value(), "1,234");
7589                state.undo(&Undo, window, cx);
7590                assert_eq!(state.value(), "");
7591                state.redo(&Redo, window, cx);
7592                assert_eq!(state.value(), "1,234");
7593                state.redo(&Redo, window, cx);
7594                assert_eq!(state.value(), "12,345");
7595            });
7596        });
7597    }
7598
7599    #[gpui::test]
7600    fn test_undo_manager_coalesces_adjacent_typing_transactions(cx: &mut TestAppContext) {
7601        let input_view = InputView::build(cx, |state| state);
7602        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7603        let input = input_view.input;
7604
7605        cx.update(|window, cx| {
7606            input.update(cx, |state, cx| {
7607                state.replace_text_in_range(None, "a", window, cx);
7608                state.replace_text_in_range(None, "b", window, cx);
7609                assert_eq!(state.value(), "ab");
7610
7611                state.undo(&Undo, window, cx);
7612                assert_eq!(state.value(), "");
7613            });
7614        });
7615    }
7616
7617    #[gpui::test]
7618    fn test_undo_manager_cursor_movement_splits_typing(cx: &mut TestAppContext) {
7619        let input_view = InputView::build(cx, |state| state);
7620        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7621        let input = input_view.input;
7622
7623        cx.update(|window, cx| {
7624            input.update(cx, |state, cx| {
7625                state.replace_text_in_range(None, "a", window, cx);
7626                state.replace_text_in_range(None, "b", window, cx);
7627                state.left(&MoveLeft, window, cx);
7628                state.replace_text_in_range(None, "x", window, cx);
7629                assert_eq!(state.value(), "axb");
7630
7631                state.undo(&Undo, window, cx);
7632                assert_eq!(state.value(), "ab");
7633                state.undo(&Undo, window, cx);
7634                assert_eq!(state.value(), "");
7635            });
7636        });
7637    }
7638
7639    #[gpui::test]
7640    fn test_undo_manager_splits_backward_and_forward_delete(cx: &mut TestAppContext) {
7641        let input_view = InputView::build(cx, |state| state);
7642        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7643        let input = input_view.input;
7644
7645        cx.update(|window, cx| {
7646            input.update(cx, |state, cx| {
7647                state.set_value("abcd", window, cx);
7648                state.set_selected_range(2..2, cx);
7649                state.backspace(&Backspace, window, cx);
7650                state.delete(&Delete, window, cx);
7651                assert_eq!(state.value(), "ad");
7652
7653                state.undo(&Undo, window, cx);
7654                assert_eq!(state.value(), "acd");
7655                state.undo(&Undo, window, cx);
7656                assert_eq!(state.value(), "abcd");
7657            });
7658        });
7659    }
7660
7661    #[gpui::test]
7662    fn test_undo_manager_coalesces_directional_character_deletes(cx: &mut TestAppContext) {
7663        let input_view = InputView::build(cx, |state| state);
7664        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7665        let input = input_view.input;
7666
7667        cx.update(|window, cx| {
7668            input.update(cx, |state, cx| {
7669                state.set_value("abcd", window, cx);
7670                state.backspace(&Backspace, window, cx);
7671                state.backspace(&Backspace, window, cx);
7672                assert_eq!(state.value(), "ab");
7673                state.undo(&Undo, window, cx);
7674                assert_eq!(state.value(), "abcd");
7675                assert_eq!(state.selected_range(), 4..4);
7676
7677                state.set_value("abcd", window, cx);
7678                state.set_selected_range(1..1, cx);
7679                state.delete(&Delete, window, cx);
7680                state.delete(&Delete, window, cx);
7681                assert_eq!(state.value(), "ad");
7682                state.undo(&Undo, window, cx);
7683                assert_eq!(state.value(), "abcd");
7684                assert_eq!(state.selected_range(), 1..1);
7685            });
7686        });
7687    }
7688
7689    #[gpui::test]
7690    fn test_undo_manager_atomic_paste_isolated_from_typing(cx: &mut TestAppContext) {
7691        let input_view = InputView::build(cx, |state| state);
7692        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7693        let input = input_view.input;
7694
7695        cx.update(|window, cx| {
7696            cx.write_to_clipboard(ClipboardItem::new_string("P".to_string()));
7697            input.update(cx, |state, cx| {
7698                state.replace_text_in_range(None, "a", window, cx);
7699                state.paste(&Paste, window, cx);
7700                state.replace_text_in_range(None, "b", window, cx);
7701                assert_eq!(state.value(), "aPb");
7702
7703                state.undo(&Undo, window, cx);
7704                assert_eq!(state.value(), "aP");
7705                state.undo(&Undo, window, cx);
7706                assert_eq!(state.value(), "a");
7707                state.undo(&Undo, window, cx);
7708                assert_eq!(state.value(), "");
7709            });
7710        });
7711    }
7712
7713    #[gpui::test]
7714    fn test_undo_manager_programmatic_insert_is_atomic(cx: &mut TestAppContext) {
7715        let input_view = InputView::build(cx, |state| state);
7716        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7717        let input = input_view.input;
7718
7719        cx.update(|window, cx| {
7720            input.update(cx, |state, cx| {
7721                state.replace_text_in_range(None, "a", window, cx);
7722                state.insert("P", window, cx);
7723                state.replace_text_in_range(None, "b", window, cx);
7724                assert_eq!(state.value(), "aPb");
7725
7726                state.undo(&Undo, window, cx);
7727                assert_eq!(state.value(), "aP");
7728                state.undo(&Undo, window, cx);
7729                assert_eq!(state.value(), "a");
7730            });
7731        });
7732    }
7733
7734    #[gpui::test]
7735    fn test_undo_manager_selection_round_trip_splits_typing(cx: &mut TestAppContext) {
7736        let input_view = InputView::build(cx, |state| state);
7737        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7738        let input = input_view.input;
7739
7740        cx.update(|window, cx| {
7741            input.update(cx, |state, cx| {
7742                state.replace_text_in_range(None, "a", window, cx);
7743                state.replace_text_in_range(None, "b", window, cx);
7744                state.select_all(window, cx);
7745                state.unselect(window, cx);
7746                state.replace_text_in_range(None, "c", window, cx);
7747
7748                state.undo(&Undo, window, cx);
7749                assert_eq!(state.value(), "ab");
7750                state.undo(&Undo, window, cx);
7751                assert_eq!(state.value(), "");
7752            });
7753        });
7754    }
7755
7756    #[gpui::test]
7757    fn test_undo_manager_enter_is_atomic(cx: &mut TestAppContext) {
7758        let input_view = InputView::build_textarea(cx, |state| state);
7759        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7760        let input = input_view.input;
7761
7762        cx.update(|window, cx| {
7763            input.update(cx, |state, cx| {
7764                state.replace_text_in_range(None, "a", window, cx);
7765                state.enter(
7766                    &Enter {
7767                        secondary: false,
7768                        shift: false,
7769                    },
7770                    window,
7771                    cx,
7772                );
7773                state.replace_text_in_range(None, "b", window, cx);
7774
7775                state.undo(&Undo, window, cx);
7776                assert_eq!(state.value(), "a\n");
7777                state.undo(&Undo, window, cx);
7778                assert_eq!(state.value(), "a");
7779                state.undo(&Undo, window, cx);
7780                assert_eq!(state.value(), "");
7781            });
7782        });
7783    }
7784
7785    #[gpui::test]
7786    fn test_undo_manager_single_line_return_commits_the_typing_session(cx: &mut TestAppContext) {
7787        let input_view = InputView::build(cx, |state| state);
7788        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7789        let input = input_view.input;
7790
7791        cx.update(|window, cx| {
7792            input.update(cx, |state, cx| {
7793                for part in ["a", "b", "c"] {
7794                    state.replace_text_in_range(None, part, window, cx);
7795                }
7796                state.enter(
7797                    &Enter {
7798                        secondary: false,
7799                        shift: false,
7800                    },
7801                    window,
7802                    cx,
7803                );
7804                for part in ["d", "e", "f"] {
7805                    state.replace_text_in_range(None, part, window, cx);
7806                }
7807                assert_eq!(state.value(), "abcdef");
7808
7809                state.undo(&Undo, window, cx);
7810                assert_eq!(state.value(), "abc");
7811                state.undo(&Undo, window, cx);
7812                assert_eq!(state.value(), "");
7813            });
7814        });
7815    }
7816
7817    #[gpui::test]
7818    fn test_undo_manager_submit_on_enter_commits_the_textarea_session(cx: &mut TestAppContext) {
7819        let input_view = InputView::build_textarea(cx, |state| state.submit_on_enter(true));
7820        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7821        let input = input_view.input;
7822
7823        cx.update(|window, cx| {
7824            input.update(cx, |state, cx| {
7825                state.replace_text_in_range(None, "before submit", window, cx);
7826                state.enter(
7827                    &Enter {
7828                        secondary: false,
7829                        shift: false,
7830                    },
7831                    window,
7832                    cx,
7833                );
7834                state.replace_text_in_range(None, " after submit", window, cx);
7835                assert_eq!(state.value(), "before submit after submit");
7836
7837                state.undo(&Undo, window, cx);
7838                assert_eq!(state.value(), "before submit");
7839                state.undo(&Undo, window, cx);
7840                assert_eq!(state.value(), "");
7841            });
7842        });
7843    }
7844
7845    #[gpui::test]
7846    fn test_undo_manager_blur_commits_the_typing_session(cx: &mut TestAppContext) {
7847        let input_view = InputView::build(cx, |state| state);
7848        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7849        let input = input_view.input;
7850
7851        cx.update(|window, cx| {
7852            input.update(cx, |state, cx| {
7853                state.replace_text_in_range(None, "before blur", window, cx);
7854                state.on_blur(window, cx);
7855                state.on_focus(window, cx);
7856                state.replace_text_in_range(None, " after focus", window, cx);
7857                assert_eq!(state.value(), "before blur after focus");
7858
7859                state.undo(&Undo, window, cx);
7860                assert_eq!(state.value(), "before blur");
7861                state.undo(&Undo, window, cx);
7862                assert_eq!(state.value(), "");
7863            });
7864        });
7865    }
7866
7867    #[gpui::test]
7868    fn test_undo_manager_keeps_rapid_lines_in_distinct_transactions(cx: &mut TestAppContext) {
7869        let input_view = InputView::build_textarea(cx, |state| state);
7870        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7871        let input = input_view.input;
7872
7873        cx.update(|window, cx| {
7874            input.update(cx, |state, cx| {
7875                let enter = Enter {
7876                    secondary: false,
7877                    shift: false,
7878                };
7879                state.replace_text_in_range(None, "a", window, cx);
7880                state.enter(&enter, window, cx);
7881                state.replace_text_in_range(None, "b", window, cx);
7882                state.enter(&enter, window, cx);
7883                state.replace_text_in_range(None, "c", window, cx);
7884                assert_eq!(state.value(), "a\nb\nc");
7885
7886                for expected in ["a\nb\n", "a\nb", "a\n", "a", ""] {
7887                    state.undo(&Undo, window, cx);
7888                    assert_eq!(state.value(), expected);
7889                }
7890            });
7891        });
7892    }
7893
7894    #[gpui::test]
7895    fn test_undo_manager_coalesces_long_unicode_typing_without_a_timer(cx: &mut TestAppContext) {
7896        let input_view = InputView::build_textarea(cx, |state| state);
7897        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7898        let input = input_view.input;
7899        let parts = [
7900            "The ",
7901            "quick ",
7902            "brown fox, ",
7903            "你好,世界 ",
7904            "🦀 jumps over 13 lazy dogs.",
7905        ];
7906        let expected = parts.concat();
7907
7908        cx.update(|window, cx| {
7909            input.update(cx, |state, cx| {
7910                for part in parts {
7911                    state.replace_text_in_range(None, part, window, cx);
7912                }
7913                assert_eq!(state.value(), expected);
7914
7915                state.undo(&Undo, window, cx);
7916                assert_eq!(state.value(), "");
7917                state.redo(&Redo, window, cx);
7918                assert_eq!(state.value(), expected);
7919            });
7920        });
7921    }
7922
7923    #[gpui::test]
7924    fn test_undo_manager_long_multiline_sequence_has_structural_boundaries(
7925        cx: &mut TestAppContext,
7926    ) {
7927        let input_view = InputView::build_textarea(cx, |state| state);
7928        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7929        let input = input_view.input;
7930        let enter = Enter {
7931            secondary: false,
7932            shift: false,
7933        };
7934
7935        cx.update(|window, cx| {
7936            input.update(cx, |state, cx| {
7937                for (index, line) in [
7938                    "first line with punctuation!",
7939                    "第二行包含 Unicode 🦀",
7940                    "third line has several words",
7941                ]
7942                .into_iter()
7943                .enumerate()
7944                {
7945                    for chunk in line.split_inclusive(' ') {
7946                        state.replace_text_in_range(None, chunk, window, cx);
7947                    }
7948                    if index < 2 {
7949                        state.enter(&enter, window, cx);
7950                    }
7951                }
7952
7953                assert_eq!(
7954                    state.value(),
7955                    "first line with punctuation!\n第二行包含 Unicode 🦀\nthird line has several words"
7956                );
7957                state.undo(&Undo, window, cx);
7958                assert_eq!(
7959                    state.value(),
7960                    "first line with punctuation!\n第二行包含 Unicode 🦀\n"
7961                );
7962                state.undo(&Undo, window, cx);
7963                assert_eq!(
7964                    state.value(),
7965                    "first line with punctuation!\n第二行包含 Unicode 🦀"
7966                );
7967                state.undo(&Undo, window, cx);
7968                assert_eq!(state.value(), "first line with punctuation!\n");
7969            });
7970        });
7971    }
7972
7973    #[gpui::test]
7974    fn test_masked_input_keeps_its_value_out_of_the_clipboard(cx: &mut TestAppContext) {
7975        let input_view = InputView::build(cx, |state| state);
7976        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7977        let input = input_view.input;
7978
7979        cx.update(|window, cx| {
7980            input.update(cx, |state, cx| {
7981                state.set_value("hunter2", window, cx);
7982                state.set_masked(true, window, cx);
7983                state.select_all(window, cx);
7984                cx.write_to_clipboard(ClipboardItem::new_string("sentinel".into()));
7985
7986                state.copy(&Copy, window, cx);
7987                assert_eq!(
7988                    cx.read_from_clipboard().and_then(|item| item.text()),
7989                    Some("sentinel".to_string())
7990                );
7991
7992                // Cut neither copies nor deletes.
7993                state.cut(&Cut, window, cx);
7994                assert_eq!(state.value(), "hunter2");
7995                assert_eq!(
7996                    cx.read_from_clipboard().and_then(|item| item.text()),
7997                    Some("sentinel".to_string())
7998                );
7999
8000                // Revealing the value restores both.
8001                state.set_masked(false, window, cx);
8002                state.copy(&Copy, window, cx);
8003                assert_eq!(
8004                    cx.read_from_clipboard().and_then(|item| item.text()),
8005                    Some("hunter2".to_string())
8006                );
8007            });
8008        });
8009    }
8010
8011    #[gpui::test]
8012    fn test_masked_input_collapses_word_boundaries(cx: &mut TestAppContext) {
8013        let input_view = InputView::build(cx, |state| state);
8014        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8015        let input = input_view.input;
8016
8017        cx.update(|window, cx| {
8018            input.update(cx, |state, cx| {
8019                state.set_value("aaa bbb ccc", window, cx);
8020                state.set_masked(true, window, cx);
8021                state.set_selected_range(7..7, cx);
8022
8023                // The mask hides word boundaries, so a word delete takes
8024                // everything before the caret and leaves the rest.
8025                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
8026                assert_eq!(state.value(), " ccc");
8027                assert_eq!(state.selected_range(), 0..0);
8028
8029                state.delete_next_word(&DeleteToNextWordEnd, window, cx);
8030                assert_eq!(state.value(), "");
8031
8032                // A double click takes the whole value, not one word.
8033                state.set_value("aaa bbb ccc", window, cx);
8034                state.select_word(9, window, cx);
8035                assert_eq!(state.selected_range(), 0..11);
8036
8037                // Unmasked, the same delete only takes one word.
8038                state.set_masked(false, window, cx);
8039                state.set_value("aaa bbb ccc", window, cx);
8040                state.set_selected_range(11..11, cx);
8041                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
8042                assert_eq!(state.value(), "aaa bbb ");
8043
8044                state.set_value("aaa bbb ccc", window, cx);
8045                state.select_word(9, window, cx);
8046                assert_eq!(state.selected_range(), 8..11);
8047            });
8048        });
8049    }
8050
8051    #[gpui::test]
8052    fn test_masked_input_disables_the_copy_context_menu_items(cx: &mut TestAppContext) {
8053        let input_view = InputView::build(cx, |state| state);
8054        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8055        let input = input_view.input;
8056
8057        cx.update(|window, cx| {
8058            input.update(cx, |state, cx| {
8059                state.set_value("hunter2", window, cx);
8060                state.select_all(window, cx);
8061                assert!(state.context_menu_capabilities().is_copyable());
8062
8063                state.set_masked(true, window, cx);
8064                let capabilities = state.context_menu_capabilities();
8065                assert!(capabilities.is_masked());
8066                assert!(capabilities.has_selection());
8067                assert!(!capabilities.is_copyable());
8068            });
8069        });
8070    }
8071
8072    #[gpui::test]
8073    fn test_undo_manager_cut_and_repeated_pastes_are_distinct_transactions(
8074        cx: &mut TestAppContext,
8075    ) {
8076        let input_view = InputView::build(cx, |state| state);
8077        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8078        let input = input_view.input;
8079
8080        cx.update(|window, cx| {
8081            input.update(cx, |state, cx| {
8082                state.replace_text_in_range(None, "alpha beta gamma", window, cx);
8083                state.set_selected_range(6..10, cx);
8084                state.cut(&Cut, window, cx);
8085                assert_eq!(state.value(), "alpha  gamma");
8086
8087                state.paste(&Paste, window, cx);
8088                state.paste(&Paste, window, cx);
8089                assert_eq!(state.value(), "alpha betabeta gamma");
8090
8091                state.undo(&Undo, window, cx);
8092                assert_eq!(state.value(), "alpha beta gamma");
8093                state.undo(&Undo, window, cx);
8094                assert_eq!(state.value(), "alpha  gamma");
8095                state.undo(&Undo, window, cx);
8096                assert_eq!(state.value(), "alpha beta gamma");
8097            });
8098        });
8099    }
8100
8101    #[gpui::test]
8102    fn test_undo_manager_word_and_line_deletes_do_not_coalesce(cx: &mut TestAppContext) {
8103        let input_view = InputView::build_textarea(cx, |state| state);
8104        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8105        let input = input_view.input;
8106
8107        cx.update(|window, cx| {
8108            input.update(cx, |state, cx| {
8109                state.set_value("one two three\nfour five", window, cx);
8110                state.set_selected_range(13..13, cx);
8111                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
8112                assert_eq!(state.value(), "one two \nfour five");
8113                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
8114                assert_eq!(state.value(), "one two four five");
8115
8116                state.undo(&Undo, window, cx);
8117                assert_eq!(state.value(), "one two \nfour five");
8118                state.undo(&Undo, window, cx);
8119                assert_eq!(state.value(), "one two three\nfour five");
8120            });
8121        });
8122    }
8123
8124    #[gpui::test]
8125    fn test_undo_manager_multiline_replacement_is_one_atomic_transaction(cx: &mut TestAppContext) {
8126        let input_view = InputView::build_textarea(cx, |state| state);
8127        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8128        let input = input_view.input;
8129
8130        cx.update(|window, cx| {
8131            input.update(cx, |state, cx| {
8132                state.replace_text_in_range(None, "before", window, cx);
8133                state.set_selected_range(0..6, cx);
8134                state.replace_text_in_range(None, "line one\nline two\n第三行", window, cx);
8135                assert_eq!(state.value(), "line one\nline two\n第三行");
8136
8137                state.undo(&Undo, window, cx);
8138                assert_eq!(state.value(), "before");
8139                assert_eq!(state.selected_range(), 0..6);
8140                state.redo(&Redo, window, cx);
8141                assert_eq!(state.value(), "line one\nline two\n第三行");
8142            });
8143        });
8144    }
8145
8146    #[gpui::test]
8147    fn test_undo_manager_composition_isolated_from_long_typing(cx: &mut TestAppContext) {
8148        let input_view = InputView::build(cx, |state| state);
8149        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8150        let input = input_view.input;
8151
8152        cx.update(|window, cx| {
8153            input.update(cx, |state, cx| {
8154                state.replace_text_in_range(None, "prefix ", window, cx);
8155                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
8156                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
8157                state.replace_and_mark_text_in_range(None, "你", None, window, cx);
8158                state.unmark_text(window, cx);
8159                state.replace_text_in_range(None, " suffix", window, cx);
8160                assert_eq!(state.value(), "prefix 你 suffix");
8161
8162                state.undo(&Undo, window, cx);
8163                assert_eq!(state.value(), "prefix 你");
8164                state.undo(&Undo, window, cx);
8165                assert_eq!(state.value(), "prefix ");
8166                state.undo(&Undo, window, cx);
8167                assert_eq!(state.value(), "");
8168            });
8169        });
8170    }
8171
8172    #[gpui::test]
8173    fn test_undo_manager_selected_replacement_is_atomic(cx: &mut TestAppContext) {
8174        let input_view = InputView::build(cx, |state| state);
8175        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8176        let input = input_view.input;
8177
8178        cx.update(|window, cx| {
8179            input.update(cx, |state, cx| {
8180                state.replace_text_in_range(None, "abc", window, cx);
8181                state.set_selected_range(1..2, cx);
8182                state.replace_text_in_range(None, "X", window, cx);
8183                state.replace_text_in_range(None, "z", window, cx);
8184                assert_eq!(state.value(), "aXzc");
8185
8186                state.undo(&Undo, window, cx);
8187                assert_eq!(state.value(), "aXc");
8188                state.undo(&Undo, window, cx);
8189                assert_eq!(state.value(), "abc");
8190                state.undo(&Undo, window, cx);
8191                assert_eq!(state.value(), "");
8192            });
8193        });
8194    }
8195
8196    #[gpui::test]
8197    fn test_number_input_leading_dot_editable(cx: &mut TestAppContext) {
8198        let input_view = InputView::build(cx, |state| {
8199            state.mask_pattern(MaskPattern::Number {
8200                separator: None,
8201                fraction: None,
8202            })
8203        });
8204        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8205        let input = input_view.input;
8206
8207        cx.update(|window, cx| {
8208            input.update(cx, |state, cx| {
8209                state.replace_text_in_range(None, "1.2", window, cx);
8210
8211                // Delete the integer part "1": the value keeps the leading dot
8212                // (".2"), not completed to "0.2", so the digits before the dot
8213                // stay editable.
8214                let range = state.range_to_utf16(&(0..1));
8215                state.replace_text_in_range(Some(range), "", window, cx);
8216                assert_eq!(state.value(), ".2");
8217                let cursor: Range<usize> = state.selected_range();
8218                assert_eq!(cursor, 0..0);
8219
8220                // The user can type a new integer part.
8221                state.replace_text_in_range(Some(0..0), "3", window, cx);
8222                assert_eq!(state.value(), "3.2");
8223            });
8224        });
8225    }
8226
8227    #[gpui::test]
8228    fn test_number_input_escape_invalid_text(cx: &mut TestAppContext) {
8229        // A pre-existing invalid text (e.g. a `default_value` that does not
8230        // conform) must not trap the user, the edit is allowed to fix it.
8231        let input_view = InputView::build(cx, |state| {
8232            state
8233                .mask_pattern(MaskPattern::Number {
8234                    separator: None,
8235                    fraction: None,
8236                })
8237                .default_value("1,234")
8238        });
8239        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8240        let input = input_view.input;
8241
8242        cx.update(|window, cx| {
8243            input.update(cx, |state, cx| {
8244                // Delete the last char, the pending text "1,23" is still
8245                // invalid, but the edit is allowed since the old text was
8246                // already invalid.
8247                let range = state.range_to_utf16(&(4..5));
8248                state.replace_text_in_range(Some(range), "", window, cx);
8249                assert_eq!(state.value(), "1,23");
8250
8251                // Once the text becomes valid, the validation works as usual.
8252                let range = state.range_to_utf16(&(1..2));
8253                state.replace_text_in_range(Some(range), "", window, cx);
8254                assert_eq!(state.value(), "123");
8255                state.replace_text_in_range(None, "a", window, cx);
8256                assert_eq!(state.value(), "123");
8257            });
8258        });
8259    }
8260
8261    /// After `set_value` on a single-line input the caret sits at the end (like
8262    /// HTML `<input>`), yet the view is scrolled back to the start so a long
8263    /// value shows its beginning instead of its tail.
8264    #[gpui::test]
8265    fn test_set_value_single_line_caret_at_end_view_at_start(cx: &mut TestAppContext) {
8266        let input_view = InputView::build(cx, |state| state);
8267        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8268        let input = input_view.input;
8269
8270        // Long enough to overflow any reasonable single-line input width.
8271        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
8272        let len = value.len();
8273
8274        // Right after `set_value`, before the next paint consumes the deferred
8275        // offset: caret is at the end, and the view is forced back to the start.
8276        cx.update(|window, cx| {
8277            input.update(cx, |state, cx| {
8278                state.set_value(value.clone(), window, cx);
8279
8280                assert_eq!(
8281                    state.selected_range(),
8282                    len..len,
8283                    "single-line caret should be at the end after set_value"
8284                );
8285                assert_eq!(
8286                    state.deferred_scroll_offset,
8287                    Some(point(px(0.), px(0.))),
8288                    "the view should be forced back to the start"
8289                );
8290            });
8291        });
8292
8293        // After a paint, the steady-state view stays at the start (x == 0) even
8294        // though the caret is at the far end.
8295        cx.run_until_parked();
8296        cx.update(|_, cx| {
8297            input.read_with(cx, |state, _| {
8298                assert!(
8299                    state.scroll_size.width > state.input_bounds.size.width,
8300                    "value must overflow the input width or this test is vacuous"
8301                );
8302                assert_eq!(
8303                    state.scroll_handle.offset().x,
8304                    px(0.),
8305                    "long value should display from its start, not its tail"
8306                );
8307            });
8308        });
8309    }
8310
8311    /// `replace_all` on a single-line input replaces the text, puts the
8312    /// caret at the end, and — like `set_value` — snaps the view back to the
8313    /// start so a long value shows its beginning instead of its tail.
8314    #[gpui::test]
8315    fn test_replace_all_single_line(cx: &mut TestAppContext) {
8316        let input_view = InputView::build(cx, |state| state);
8317        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8318        let input = input_view.input;
8319
8320        // Long enough to overflow any reasonable single-line input width.
8321        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
8322        let len = value.len();
8323
8324        // Right after `replace_all`, before the next paint consumes the
8325        // deferred offset: caret is at the end, and the view is forced back
8326        // to the start.
8327        cx.update(|window, cx| {
8328            input.update(cx, |state, cx| {
8329                state.set_value("hello", window, cx);
8330                state.replace_all(value.clone(), window, cx);
8331                assert_eq!(state.value(), value);
8332                assert_eq!(
8333                    state.selected_range(),
8334                    len..len,
8335                    "single-line caret should be at the end after replace_all"
8336                );
8337                assert_eq!(
8338                    state.scroll_handle.offset(),
8339                    point(px(0.), px(0.)),
8340                    "the scroll offset should be reset to the start"
8341                );
8342                assert_eq!(
8343                    state.deferred_scroll_offset,
8344                    Some(point(px(0.), px(0.))),
8345                    "single-line should set a deferred scroll offset to keep the start visible"
8346                );
8347            });
8348        });
8349
8350        // After a paint, the steady-state view stays at the start (x == 0)
8351        // even though the caret is at the far end.
8352        cx.run_until_parked();
8353        cx.update(|_, cx| {
8354            input.read_with(cx, |state, _| {
8355                assert!(
8356                    state.scroll_size.width > state.input_bounds.size.width,
8357                    "value must overflow the input width or this test is vacuous"
8358                );
8359                assert_eq!(
8360                    state.scroll_handle.offset().x,
8361                    px(0.),
8362                    "long value should display from its start, not its tail"
8363                );
8364            });
8365        });
8366    }
8367
8368    #[gpui::test]
8369    fn test_single_line_removes_newlines(cx: &mut TestAppContext) {
8370        let input_view = InputView::build(cx, |state| state.default_value("default\nvalue"));
8371        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8372        let input = input_view.input;
8373
8374        cx.update(|window, cx| {
8375            input.update(cx, |state, cx| {
8376                assert_eq!(state.value(), "defaultvalue");
8377
8378                state.set_value("first\nsecond\r\nthird\rfourth", window, cx);
8379                assert_eq!(state.value(), "firstsecondthirdfourth");
8380
8381                state.set_value("", window, cx);
8382                state.insert("a\nb", window, cx);
8383                assert_eq!(state.value(), "ab");
8384            });
8385
8386            cx.write_to_clipboard(ClipboardItem::new_string("a\r\nb\nc\rd".to_string()));
8387            input.update(cx, |state, cx| {
8388                state.set_value("", window, cx);
8389                state.paste(&Paste, window, cx);
8390                assert_eq!(state.value(), "abcd");
8391            });
8392        });
8393
8394        cx.run_until_parked();
8395    }
8396
8397    /// `replace_all` on a multi-line (non-code-editor) input clears the
8398    /// selection to `0..0` and resets the scroll offset, but does not set a
8399    /// deferred scroll offset (single-line only).
8400    #[gpui::test]
8401    fn test_replace_all_multi_line(cx: &mut TestAppContext) {
8402        let input_view = InputView::build_textarea(cx, |state| state);
8403        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8404        let input = input_view.input;
8405
8406        cx.update(|window, cx| {
8407            input.update(cx, |state, cx| {
8408                state.set_value("foo\nbar", window, cx);
8409                state.replace_all("baz\nqux", window, cx);
8410                assert_eq!(state.value(), "baz\nqux");
8411                assert_eq!(
8412                    state.selected_range(),
8413                    0..0,
8414                    "multi-line selection should be cleared after replace_all"
8415                );
8416                assert_eq!(
8417                    state.scroll_handle.offset(),
8418                    point(px(0.), px(0.)),
8419                    "the scroll offset should be reset to the start"
8420                );
8421                assert!(
8422                    state.deferred_scroll_offset.is_none(),
8423                    "multi-line should not set a deferred scroll offset"
8424                );
8425            });
8426        });
8427    }
8428
8429    /// Unlike `set_value`, `replace_all` records the change so the user can
8430    /// undo it back to the previous text and redo to the new text.
8431    #[gpui::test]
8432    fn test_replace_all_preserves_undo_history(cx: &mut TestAppContext) {
8433        let input_view = InputView::build(cx, |state| state);
8434        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8435        let input = input_view.input;
8436
8437        cx.update(|window, cx| {
8438            input.update(cx, |state, cx| {
8439                // Seed with a value and clear history so the baseline is clean.
8440                state.set_value("first", window, cx);
8441                assert!(
8442                    !state.undo_manager.has_undos(),
8443                    "history should be empty after set_value"
8444                );
8445
8446                // replace_all records a single undoable change.
8447                state.replace_all("second", window, cx);
8448                assert_eq!(state.value(), "second");
8449                assert!(
8450                    state.undo_manager.has_undos(),
8451                    "replace_all should record an undo step"
8452                );
8453
8454                // Undo restores the previous text.
8455                state.undo(&Undo, window, cx);
8456                assert_eq!(state.value(), "first");
8457
8458                // Redo reapplies the replacement.
8459                state.redo(&Redo, window, cx);
8460                assert_eq!(state.value(), "second");
8461            });
8462        });
8463    }
8464
8465    /// `replace_all` on a code editor marks a pending update and resets LSP
8466    /// state, so diagnostics/completions refresh against the new text.
8467    #[gpui::test]
8468    fn test_replace_all_code_editor(cx: &mut TestAppContext) {
8469        let input_view = InputView::new(cx);
8470        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8471        let input = input_view.input;
8472
8473        cx.update(|window, cx| {
8474            input.update(cx, |state, cx| {
8475                // Plant a pending-update flag and some LSP state to verify reset.
8476                state.set_value("select 1", window, cx);
8477                state._pending_update = false;
8478
8479                state.replace_all("select 2", window, cx);
8480                assert_eq!(state.value(), "select 2");
8481                assert!(
8482                    state._pending_update,
8483                    "replace_all on a code editor should request a pending update"
8484                );
8485            });
8486        });
8487    }
8488
8489    #[gpui::test]
8490    fn test_set_selected_range(cx: &mut TestAppContext) {
8491        let input_view = InputView::build(cx, |state| state.default_value("hello world"));
8492        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8493        let input = input_view.input;
8494
8495        cx.update(|_, cx| {
8496            input.update(cx, |s, cx| {
8497                s.set_selected_range(0..5, cx);
8498                assert_eq!(s.selected_range(), 0..5);
8499                assert_eq!(s.selected_text().to_string(), "hello");
8500
8501                s.set_selected_range(6..11, cx);
8502                assert_eq!(s.selected_text().to_string(), "world");
8503
8504                // clamped + collapsed
8505                s.set_selected_range(100..100, cx);
8506                assert_eq!(s.selected_range(), 11..11);
8507            });
8508        });
8509    }
8510
8511    /// A single-edit batch round-trips through undo/redo.
8512    #[gpui::test]
8513    fn test_replace_text_in_ranges_single_edit(cx: &mut TestAppContext) {
8514        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
8515        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8516        let input = input_view.input;
8517
8518        cx.update(|window, cx| {
8519            input.update(cx, |s, cx| {
8520                s.replace_text_in_ranges(&[(0..5, "HELLO".to_string())], window, cx);
8521                assert_eq!(s.value(), "HELLO world");
8522
8523                s.undo(&Undo, window, cx);
8524                assert_eq!(s.value(), "hello world");
8525
8526                s.redo(&Redo, window, cx);
8527                assert_eq!(s.value(), "HELLO world");
8528            });
8529        });
8530    }
8531
8532    /// A single undo restores the exact original text and a single redo
8533    /// re-applies all edits, verifying the back-to-front application ordering.
8534    #[gpui::test]
8535    fn test_replace_text_in_ranges_multi_edit_transaction(cx: &mut TestAppContext) {
8536        let input_view = InputView::build_textarea(cx, |state| state.default_value("aaa bbb ccc"));
8537        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8538        let input = input_view.input;
8539
8540        cx.update(|window, cx| {
8541            input.update(cx, |s, cx| {
8542                // Two edits at different positions, given in pre-edit
8543                // coordinates and in arbitrary (non-sorted) order.
8544                s.replace_text_in_ranges(
8545                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
8546                    window,
8547                    cx,
8548                );
8549                assert_eq!(s.value(), "X bbb Y");
8550
8551                // One collapsed cursor per edit, at the end of each inserted text.
8552                let cursors: Vec<usize> =
8553                    s.selections.iter().map(|sel| sel.cursor_offset()).collect();
8554                assert_eq!(cursors, vec![1, 7]);
8555
8556                // The whole batch is a single undo transaction.
8557                assert_eq!(s.undo_manager.undo_count(), 1);
8558
8559                // One undo restores the exact original text.
8560                s.undo(&Undo, window, cx);
8561                assert_eq!(s.value(), "aaa bbb ccc");
8562
8563                // One redo re-applies all edits.
8564                s.redo(&Redo, window, cx);
8565                assert_eq!(s.value(), "X bbb Y");
8566            });
8567        });
8568    }
8569
8570    /// A multi-edit change reaches the highlighter once, with every edit in
8571    /// application order and the text right after each, which is what the
8572    /// default `update_batch` replays through `update` one edit at a time.
8573    #[gpui::test]
8574    fn test_replace_text_in_ranges_drives_the_highlighter_once(cx: &mut TestAppContext) {
8575        use crate::input::{FoldRange, HighlightStyleResolver, InputEdit, InputHighlighter};
8576        use gpui::HighlightStyle;
8577        use std::cell::RefCell;
8578
8579        struct RecordingHighlighter(Rc<RefCell<Vec<Vec<String>>>>);
8580
8581        impl InputHighlighter for RecordingHighlighter {
8582            fn language(&self) -> SharedString {
8583                "recording".into()
8584            }
8585
8586            fn update(
8587                &mut self,
8588                _: Option<InputEdit>,
8589                text: &Rope,
8590                _: bool,
8591                _: &mut Window,
8592                _: &mut Context<EditorState>,
8593            ) {
8594                self.0.borrow_mut().push(vec![text.to_string()]);
8595            }
8596
8597            fn update_batch(
8598                &mut self,
8599                edits: &[(InputEdit, Rope)],
8600                _: bool,
8601                _: &mut Window,
8602                _: &mut Context<EditorState>,
8603            ) {
8604                let texts = edits.iter().map(|(_, text)| text.to_string()).collect();
8605                self.0.borrow_mut().push(texts);
8606            }
8607
8608            fn styles(
8609                &self,
8610                range: &Range<usize>,
8611                _: &dyn HighlightStyleResolver,
8612            ) -> Vec<(Range<usize>, HighlightStyle)> {
8613                vec![(range.clone(), HighlightStyle::default())]
8614            }
8615
8616            fn fold_ranges(&self, _: &Rope) -> Vec<FoldRange> {
8617                Vec::new()
8618            }
8619        }
8620
8621        let updates = Rc::new(RefCell::new(Vec::new()));
8622        let input_view = InputView::new(cx);
8623        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8624        let input = input_view.input;
8625
8626        cx.update(|window, cx| {
8627            input.update(cx, |s, cx| {
8628                s.set_value("aaa bbb ccc", window, cx);
8629                let recorded = updates.clone();
8630                s.set_highlighter_factory(
8631                    Rc::new(move |_: &str| {
8632                        let highlighter: Box<dyn InputHighlighter> =
8633                            Box::new(RecordingHighlighter(recorded.clone()));
8634                        Some(highlighter)
8635                    }),
8636                    cx,
8637                );
8638                s.replace_text_in_ranges(
8639                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
8640                    window,
8641                    cx,
8642                );
8643            });
8644        });
8645
8646        assert_eq!(
8647            *updates.borrow(),
8648            vec![vec!["aaa bbb Y".to_string(), "X bbb Y".to_string()]]
8649        );
8650    }
8651
8652    /// An IME composition (marking then commit) undoes as a single unit.
8653    #[gpui::test]
8654    fn test_ime_composition_undoes_as_one_unit(cx: &mut TestAppContext) {
8655        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
8656        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8657        let input = input_view.input;
8658
8659        cx.update(|window, cx| {
8660            input.update(cx, |s, cx| {
8661                // Simulate an IME composition: mark, refine, then commit.
8662                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
8663                s.replace_and_mark_text_in_range(None, "ni", Some(2..2), window, cx);
8664                s.replace_text_in_range(None, "你", window, cx);
8665                assert_eq!(s.value(), "你");
8666
8667                // The entire composition is one undo transaction.
8668                assert_eq!(s.undo_manager.undo_count(), 1);
8669
8670                s.undo(&Undo, window, cx);
8671                assert_eq!(s.value(), "");
8672
8673                s.redo(&Redo, window, cx);
8674                assert_eq!(s.value(), "你");
8675            });
8676        });
8677    }
8678
8679    /// A keystroke right after a committed composition must be its own undo
8680    /// entry, not merged into the (finalized) composition transaction.
8681    #[gpui::test]
8682    fn test_edit_after_composition_is_separate_undo(cx: &mut TestAppContext) {
8683        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
8684        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8685        let input = input_view.input;
8686
8687        cx.update(|window, cx| {
8688            input.update(cx, |s, cx| {
8689                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
8690                s.replace_text_in_range(None, "你", window, cx);
8691                assert_eq!(s.value(), "你");
8692                assert_eq!(s.undo_manager.undo_count(), 1);
8693
8694                // Typing after the commit is a distinct transaction.
8695                s.replace_text_in_range(None, "x", window, cx);
8696                assert_eq!(s.value(), "你x");
8697                assert_eq!(s.undo_manager.undo_count(), 2);
8698
8699                s.undo(&Undo, window, cx);
8700                assert_eq!(s.value(), "你");
8701                s.undo(&Undo, window, cx);
8702                assert_eq!(s.value(), "");
8703            });
8704        });
8705    }
8706
8707    /// Canceling a composition via `unmark_text` closes its transaction so it
8708    /// does not leak and swallow a later edit.
8709    #[gpui::test]
8710    fn test_composition_cancel_via_unmark_does_not_leak(cx: &mut TestAppContext) {
8711        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
8712        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8713        let input = input_view.input;
8714
8715        cx.update(|window, cx| {
8716            input.update(cx, |s, cx| {
8717                // Start a composition, then cancel it via unmark.
8718                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
8719                s.unmark_text(window, cx);
8720                let after_cancel = s.undo_manager.undo_count();
8721
8722                // A later edit is recorded independently.
8723                s.replace_text_in_range(None, "x", window, cx);
8724                assert_eq!(s.undo_manager.undo_count(), after_cancel + 1);
8725            });
8726        });
8727    }
8728
8729    #[gpui::test]
8730    fn test_set_selected_range_clips_to_utf8_boundaries(cx: &mut TestAppContext) {
8731        let input_view = InputView::build(cx, |state| state.default_value("éx"));
8732        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8733        let input = input_view.input;
8734
8735        cx.update(|window, cx| {
8736            input.update(cx, |state, cx| {
8737                state.set_selected_range(0..1, cx);
8738                assert_eq!(state.selected_range(), 0..2);
8739                state.copy(&Copy, window, cx);
8740
8741                state.set_selected_range(1..1, cx);
8742                assert_eq!(state.selected_range(), 0..0);
8743            });
8744        });
8745    }
8746
8747    #[gpui::test]
8748    fn test_ime_selection_is_relative_to_replacement_start(cx: &mut TestAppContext) {
8749        let input_view = InputView::build(cx, |state| state.default_value("你好 "));
8750        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8751        let input = input_view.input;
8752
8753        cx.update(|window, cx| {
8754            input.update(cx, |state, cx| {
8755                state.set_selected_range(7..7, cx);
8756                state.replace_and_mark_text_in_range(None, "s", Some(1..1), window, cx);
8757                state.replace_and_mark_text_in_range(None, "sh", Some(2..2), window, cx);
8758
8759                assert_eq!(state.value(), "你好 sh");
8760                assert_eq!(state.selected_range(), 9..9);
8761                assert_eq!(state.ime_marked_range, Some((7..9).into()));
8762            });
8763        });
8764    }
8765
8766    #[gpui::test]
8767    fn test_undo_manager_composition_is_one_undo_group(cx: &mut TestAppContext) {
8768        let input_view = InputView::build(cx, |state| state);
8769        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8770        let input = input_view.input;
8771
8772        cx.update(|window, cx| {
8773            input.update(cx, |state, cx| {
8774                state.set_value("a", window, cx);
8775                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
8776                state.replace_and_mark_text_in_range(None, "sh", None, window, cx);
8777                state.replace_text_in_range(None, "是", window, cx);
8778                assert_eq!(state.value(), "a是");
8779
8780                state.undo(&Undo, window, cx);
8781                assert_eq!(state.value(), "a");
8782                state.redo(&Redo, window, cx);
8783                assert_eq!(state.value(), "a是");
8784            });
8785        });
8786    }
8787
8788    #[gpui::test]
8789    fn test_undo_manager_consecutive_compositions_are_separate_groups(cx: &mut TestAppContext) {
8790        let input_view = InputView::build(cx, |state| state);
8791        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8792        let input = input_view.input;
8793
8794        cx.update(|window, cx| {
8795            input.update(cx, |state, cx| {
8796                // First composition: "jin" -> "今天"
8797                state.replace_and_mark_text_in_range(None, "j", None, window, cx);
8798                state.replace_and_mark_text_in_range(None, "jin", None, window, cx);
8799                state.replace_text_in_range(None, "今天", window, cx);
8800                // Second composition: "wo" -> "我们"
8801                state.replace_and_mark_text_in_range(None, "w", None, window, cx);
8802                state.replace_and_mark_text_in_range(None, "wo", None, window, cx);
8803                state.replace_text_in_range(None, "我们", window, cx);
8804                assert_eq!(state.value(), "今天我们");
8805                assert_eq!(state.selected_range(), 12..12);
8806
8807                state.undo(&Undo, window, cx);
8808                assert_eq!(state.value(), "今天");
8809                assert_eq!(state.selected_range(), 6..6);
8810
8811                state.undo(&Undo, window, cx);
8812                assert_eq!(state.value(), "");
8813                assert_eq!(state.selected_range(), 0..0);
8814
8815                state.redo(&Redo, window, cx);
8816                assert_eq!(state.value(), "今天");
8817                assert_eq!(state.selected_range(), 6..6);
8818
8819                state.redo(&Redo, window, cx);
8820                assert_eq!(state.value(), "今天我们");
8821                assert_eq!(state.selected_range(), 12..12);
8822            });
8823        });
8824    }
8825
8826    #[gpui::test]
8827    fn test_undo_manager_typing_after_composition_is_a_separate_group(cx: &mut TestAppContext) {
8828        let input_view = InputView::build(cx, |state| state);
8829        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8830        let input = input_view.input;
8831
8832        cx.update(|window, cx| {
8833            input.update(cx, |state, cx| {
8834                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
8835                state.replace_text_in_range(None, "你", window, cx);
8836                state.undo_manager.set_pending_intent(EditIntent::Typing);
8837                state.replace_text_in_range(None, "a", window, cx);
8838                state.undo_manager.set_pending_intent(EditIntent::Typing);
8839                state.replace_text_in_range(None, "b", window, cx);
8840                assert_eq!(state.value(), "你ab");
8841
8842                state.undo(&Undo, window, cx);
8843                assert_eq!(state.value(), "你");
8844
8845                state.undo(&Undo, window, cx);
8846                assert_eq!(state.value(), "");
8847            });
8848        });
8849    }
8850
8851    #[gpui::test]
8852    fn test_undo_manager_composition_cancel_leaves_no_entry(cx: &mut TestAppContext) {
8853        let input_view = InputView::build(cx, |state| state);
8854        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8855        let input = input_view.input;
8856
8857        cx.update(|window, cx| {
8858            input.update(cx, |state, cx| {
8859                state.set_value("a", window, cx);
8860                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
8861                state.replace_and_mark_text_in_range(None, "", None, window, cx);
8862
8863                assert_eq!(state.value(), "a");
8864                assert!(!state.undo_manager.has_undos());
8865            });
8866        });
8867    }
8868
8869    /// The macOS Korean IME rewrites the character it just inserted.
8870    #[gpui::test]
8871    fn test_undo_manager_ime_rewrites_of_typed_char_are_one_group(cx: &mut TestAppContext) {
8872        let input_view = InputView::build(cx, |state| state);
8873        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8874        let input = input_view.input;
8875
8876        cx.update(|window, cx| {
8877            input.update(cx, |state, cx| {
8878                state.set_value("é ", window, cx);
8879                state.set_selected_range(3..3, cx);
8880                // g k s r m f: "ㅎ" -> "하" -> "한", then "ㄱ" -> "그" -> "글"
8881                state.replace_text_in_range(None, "ㅎ", window, cx);
8882                state.replace_text_in_range(Some(2..3), "하", window, cx);
8883                state.replace_text_in_range(Some(2..3), "한", window, cx);
8884                state.replace_text_in_range(None, "ㄱ", window, cx);
8885                state.replace_text_in_range(Some(3..4), "그", window, cx);
8886                state.replace_text_in_range(Some(3..4), "글", window, cx);
8887                assert_eq!(state.value(), "é 한글");
8888                assert_eq!(state.undo_manager.undo_count(), 1);
8889
8890                state.undo(&Undo, window, cx);
8891                assert_eq!(state.value(), "é ");
8892                assert_eq!(state.selected_range(), 3..3);
8893
8894                state.redo(&Redo, window, cx);
8895                assert_eq!(state.value(), "é 한글");
8896                assert_eq!(state.selected_range(), 9..9);
8897            });
8898        });
8899    }
8900
8901    #[gpui::test]
8902    fn test_undo_manager_rewrite_after_cursor_movement_is_separate(cx: &mut TestAppContext) {
8903        let input_view = InputView::build(cx, |state| state);
8904        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8905        let input = input_view.input;
8906
8907        cx.update(|window, cx| {
8908            input.update(cx, |state, cx| {
8909                state.replace_text_in_range(None, "c", window, cx);
8910                state.replace_text_in_range(None, "e", window, cx);
8911                state.left(&MoveLeft, window, cx);
8912                state.right(&MoveRight, window, cx);
8913                state.replace_text_in_range(Some(1..2), "é", window, cx);
8914                assert_eq!(state.value(), "cé");
8915
8916                state.undo(&Undo, window, cx);
8917                assert_eq!(state.value(), "ce");
8918                state.undo(&Undo, window, cx);
8919                assert_eq!(state.value(), "");
8920            });
8921        });
8922    }
8923
8924    #[gpui::test]
8925    fn test_undo_manager_selection_restored_by_undo_and_redo(cx: &mut TestAppContext) {
8926        let input_view = InputView::build(cx, |state| state);
8927        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8928        let input = input_view.input;
8929
8930        cx.update(|window, cx| {
8931            input.update(cx, |state, cx| {
8932                state.set_value("abc", window, cx);
8933                state.set_selected_range(1..2, cx);
8934                state.replace_text_in_range(None, "X", window, cx);
8935
8936                state.undo(&Undo, window, cx);
8937                assert_eq!(state.value(), "abc");
8938                assert_eq!(state.selected_range(), 1..2);
8939
8940                state.redo(&Redo, window, cx);
8941                assert_eq!(state.value(), "aXc");
8942                assert_eq!(state.selected_range(), 2..2);
8943            });
8944        });
8945    }
8946
8947    #[gpui::test]
8948    fn test_undo_manager_forward_delete_restores_cursor(cx: &mut TestAppContext) {
8949        let input_view = InputView::build(cx, |state| state);
8950        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8951        let input = input_view.input;
8952
8953        cx.update(|window, cx| {
8954            input.update(cx, |state, cx| {
8955                state.set_value("abc", window, cx);
8956                state.set_selected_range(1..1, cx);
8957                state.delete(&Delete, window, cx);
8958
8959                state.undo(&Undo, window, cx);
8960                assert_eq!(state.value(), "abc");
8961                assert_eq!(state.selected_range(), 1..1);
8962            });
8963        });
8964    }
8965
8966    #[gpui::test]
8967    fn test_undo_manager_selection_movement_preserves_redo(cx: &mut TestAppContext) {
8968        let input_view = InputView::build(cx, |state| state);
8969        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8970        let input = input_view.input;
8971
8972        cx.update(|window, cx| {
8973            input.update(cx, |state, cx| {
8974                state.replace_text_in_range(None, "ab", window, cx);
8975                state.undo(&Undo, window, cx);
8976                state.set_selected_range(0..0, cx);
8977                state.redo(&Redo, window, cx);
8978                assert_eq!(state.value(), "ab");
8979
8980                state.undo(&Undo, window, cx);
8981                state.replace_text_in_range(None, "x", window, cx);
8982                state.redo(&Redo, window, cx);
8983                assert_eq!(state.value(), "x");
8984            });
8985        });
8986    }
8987
8988    #[gpui::test]
8989    fn test_undo_manager_noop_edit_preserves_redo(cx: &mut TestAppContext) {
8990        let input_view = InputView::build(cx, |state| state);
8991        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8992        let input = input_view.input;
8993
8994        cx.update(|window, cx| {
8995            input.update(cx, |state, cx| {
8996                state.replace_text_in_range(None, "a", window, cx);
8997                state.move_to(0, None, cx);
8998                state.replace_text_in_range(None, "", window, cx);
8999                state.undo(&Undo, window, cx);
9000                assert_eq!(state.value(), "");
9001                state.redo(&Redo, window, cx);
9002                assert_eq!(state.value(), "a");
9003                assert_eq!(state.cursor(), 1);
9004            });
9005        });
9006    }
9007
9008    #[gpui::test]
9009    fn test_cursor_round_trip_stops_typing_coalescing(cx: &mut TestAppContext) {
9010        let input_view = InputView::build(cx, |state| state);
9011        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9012        let input = input_view.input;
9013
9014        cx.update(|window, cx| {
9015            input.update(cx, |state, cx| {
9016                state.replace_text_in_range(None, "a", window, cx);
9017                state.left(&MoveLeft, window, cx);
9018                state.right(&MoveRight, window, cx);
9019                state.replace_text_in_range(None, "b", window, cx);
9020
9021                state.undo(&Undo, window, cx);
9022                assert_eq!(state.value(), "a");
9023                state.undo(&Undo, window, cx);
9024                assert_eq!(state.value(), "");
9025            });
9026        });
9027    }
9028
9029    #[gpui::test]
9030    fn test_undo_manager_noop_edit_breaks_coalescing_without_clearing_history(
9031        cx: &mut TestAppContext,
9032    ) {
9033        let input_view = InputView::build(cx, |state| state);
9034        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9035        let input = input_view.input;
9036
9037        cx.update(|window, cx| {
9038            input.update(cx, |state, cx| {
9039                state.replace_text_in_range(None, "alpha", window, cx);
9040                state.replace_text_in_range(None, "", window, cx);
9041                state.replace_text_in_range(None, "beta", window, cx);
9042                assert_eq!(state.value(), "alphabeta");
9043
9044                state.undo(&Undo, window, cx);
9045                assert_eq!(state.value(), "alpha");
9046                state.undo(&Undo, window, cx);
9047                assert_eq!(state.value(), "");
9048            });
9049        });
9050    }
9051
9052    #[gpui::test]
9053    fn test_undo_manager_masked_redo_restores_actual_cursor(cx: &mut TestAppContext) {
9054        let input_view = InputView::build(cx, |state| {
9055            state.mask_pattern(MaskPattern::Number {
9056                separator: Some(','),
9057                fraction: None,
9058            })
9059        });
9060        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9061        let input = input_view.input;
9062
9063        cx.update(|window, cx| {
9064            input.update(cx, |state, cx| {
9065                state.set_value("12345", window, cx);
9066                state.set_selected_range(2..2, cx);
9067                state.replace_text_in_range(None, "9", window, cx);
9068                let selection_after_edit = state.selected_range();
9069                assert_ne!(selection_after_edit.end, state.value().len());
9070
9071                state.undo(&Undo, window, cx);
9072                state.redo(&Redo, window, cx);
9073                assert_eq!(state.selected_range(), selection_after_edit);
9074            });
9075        });
9076    }
9077
9078    /// Unfolding at a position opens exactly the folds hiding it.
9079    ///
9080    /// A fold keeps its own first and last line visible, so a position on
9081    /// either of them opens nothing. Nested folds all open at once, sibling
9082    /// folds stay closed, and the opened ranges stay fold candidates.
9083    #[gpui::test]
9084    fn test_unfold_at(cx: &mut TestAppContext) {
9085        use crate::input::{FoldRange, Position};
9086
9087        let view = InputView::<EditorMode>::new(cx);
9088        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9089        let input = view.input;
9090
9091        // An outer fold over lines 0..=5, a fold nested inside it, and a
9092        // sibling fold that must never be touched.
9093        cx.update(|window, cx| {
9094            input.update(cx, |state, cx| {
9095                state.set_value("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl", window, cx);
9096                state.apply_highlighter_fold_candidates(
9097                    vec![
9098                        FoldRange::new(0, 5),
9099                        FoldRange::new(2, 4),
9100                        FoldRange::new(7, 10),
9101                    ],
9102                    cx,
9103                );
9104                state.display_map.set_folded(0, true);
9105                state.display_map.set_folded(2, true);
9106                state.display_map.set_folded(7, true);
9107            });
9108        });
9109
9110        // The outer fold's own first and last line stay visible, so neither
9111        // position opens anything.
9112        for line in [0, 5] {
9113            cx.update(|_, cx| {
9114                input.update(cx, |state, cx| {
9115                    assert!(!state.display_map.is_buffer_line_hidden(line));
9116                    assert!(!state.unfold_at(Position::new(line as u32, 0), cx));
9117                });
9118                input.read_with(cx, |state, _| {
9119                    assert!(state.display_map.is_folded_at(0));
9120                    assert!(state.display_map.is_folded_at(2));
9121                    assert!(state.display_map.is_folded_at(7));
9122                });
9123            });
9124        }
9125
9126        // Line 3 is hidden by both the outer and the nested fold, so both
9127        // open; the sibling fold does not.
9128        cx.update(|_, cx| {
9129            input.update(cx, |state, cx| {
9130                assert!(state.unfold_at(Position::new(3, 0), cx));
9131            });
9132            input.read_with(cx, |state, _| {
9133                assert!(!state.display_map.is_buffer_line_hidden(3));
9134                assert!(!state.display_map.is_folded_at(0));
9135                assert!(!state.display_map.is_folded_at(2));
9136                assert!(state.display_map.is_folded_at(7));
9137                // The opened ranges are still candidates for refolding.
9138                assert!(state.display_map.is_fold_candidate(0));
9139                assert!(state.display_map.is_fold_candidate(2));
9140            });
9141        });
9142
9143        // Nothing is hidden there any more, so a second call is a no-op.
9144        cx.update(|_, cx| {
9145            input.update(cx, |state, cx| {
9146                assert!(!state.unfold_at(Position::new(3, 0), cx));
9147            });
9148        });
9149    }
9150
9151    /// Losing focus hides the hover popover but keeps the decorations.
9152    ///
9153    /// Both used to be dropped by one call, so clicking away threw away
9154    /// decorations the application had installed and never asked to remove.
9155    #[gpui::test]
9156    fn test_blur_keeps_decorations(cx: &mut TestAppContext) {
9157        let view = InputView::<EditorMode>::new(cx);
9158        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9159        let input = view.input;
9160
9161        cx.update(|window, cx| {
9162            input.update(cx, |state, cx| {
9163                state.set_value("select 1", window, cx);
9164                let _collection = state.create_decorations_collection(
9165                    vec![crate::input::TextDecoration::new(
9166                        0..6,
9167                        gpui::HighlightStyle {
9168                            font_weight: Some(gpui::FontWeight::BOLD),
9169                            ..Default::default()
9170                        },
9171                    )],
9172                    cx,
9173                );
9174                state.present_hover(
9175                    0..6,
9176                    lsp_types::Hover {
9177                        contents: lsp_types::HoverContents::Scalar(
9178                            lsp_types::MarkedString::String("docs".into()),
9179                        ),
9180                        range: None,
9181                    },
9182                    cx,
9183                );
9184                assert!(state.hover_popover().is_some());
9185
9186                state.on_blur(window, cx);
9187
9188                assert!(
9189                    state.hover_popover().is_none(),
9190                    "blur should hide the hover popover"
9191                );
9192                let decorations = state.extras.decoration_layers();
9193                assert!(
9194                    decorations.iter().any(|layer| !layer.is_empty()),
9195                    "blur must not discard decorations"
9196                );
9197            });
9198        });
9199    }
9200
9201    /// The mode marker is the only source of truth for the kind of input.
9202    ///
9203    /// An auto-growing textarea capped at one row used to report itself as
9204    /// single-line, because the answer was derived from the row counts.
9205    #[gpui::test]
9206    fn test_kind_does_not_follow_the_row_count(cx: &mut TestAppContext) {
9207        let view = InputView::build_textarea(cx, |state| state.auto_grow(1, 1));
9208        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9209        view.input.read_with(&mut cx, |state, _| {
9210            assert!(state.is_multi_line());
9211            assert!(!state.is_single_line());
9212            assert!(!state.is_code_editor());
9213        });
9214    }
9215
9216    #[gpui::test]
9217    fn test_textarea_prewrap_prepares_content_rows_before_layout(cx: &mut TestAppContext) {
9218        cx.update(crate::init);
9219        for (value, expected_rows) in [("", 1), ("one\ntwo", 2)] {
9220            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9221                let font = window.text_style().font();
9222                cx.new(|cx| {
9223                    let mut state = crate::input::TextareaState::new(window, cx)
9224                        .rows(12)
9225                        .default_value(value);
9226                    assert_eq!(state.display_map().wrap_row_count(), 0);
9227                    state.prewrap(font, px(14.), px(120.), cx);
9228                    assert_eq!(state.display_map().wrap_row_count(), expected_rows);
9229                    assert_eq!(state.value(), value);
9230                    state
9231                });
9232                gpui::EmptyView
9233            });
9234        }
9235    }
9236
9237    #[gpui::test]
9238    fn test_textarea_prewrap_respects_soft_wrap_before_layout(cx: &mut TestAppContext) {
9239        cx.update(crate::init);
9240        let value = "select id, name, email, phone from users where active\nlimit 10";
9241        for soft_wrap in [true, false] {
9242            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9243                let font = window.text_style().font();
9244                cx.new(|cx| {
9245                    let mut state = crate::input::TextareaState::new(window, cx)
9246                        .rows(1)
9247                        .soft_wrap(soft_wrap)
9248                        .default_value(value);
9249                    state.prewrap(font, px(14.), px(120.), cx);
9250                    let rows = state.display_map().wrap_row_count();
9251                    if soft_wrap {
9252                        assert!(rows > 2, "the first logical line must wrap");
9253                    } else {
9254                        assert_eq!(rows, 2);
9255                    }
9256                    assert_eq!(state.value(), value);
9257                    state
9258                });
9259                gpui::EmptyView
9260            });
9261        }
9262    }
9263
9264    #[gpui::test]
9265    fn test_prewrap_counts_wrapped_rows_before_the_first_paint(cx: &mut TestAppContext) {
9266        cx.update(crate::init);
9267        let text = "select id, name, email, phone from users where active\nlimit 10";
9268        for (soft_wrap, wraps) in [(true, true), (false, false)] {
9269            let mut input = None;
9270            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9271                let font = window.text_style().font();
9272                let editor = cx.new(|cx| {
9273                    let mut state = crate::input::EditorState::new(window, cx)
9274                        .soft_wrap(soft_wrap)
9275                        .default_value(text);
9276                    state.prewrap(font, px(14.), px(120.), cx);
9277                    state
9278                });
9279                input = Some(editor);
9280                gpui::EmptyView
9281            });
9282            let rows = input
9283                .unwrap()
9284                .read_with(cx, |state, _| state.display_map().wrap_row_count());
9285            // Two lines; at 120px the first wraps unless soft wrap is off.
9286            assert_eq!(rows > 2, wraps, "soft_wrap({soft_wrap}): {rows} rows");
9287            if !wraps {
9288                assert_eq!(rows, 2);
9289            }
9290        }
9291    }
9292
9293    #[gpui::test]
9294    fn test_prewrap_uses_the_configured_tab_stops(cx: &mut TestAppContext) {
9295        cx.update(crate::init);
9296        cx.open_window(size(px(400.), px(100.)), |window, cx| {
9297            let font = window.text_style().font();
9298            let font_size = px(14.);
9299            let font_id = window.text_system().resolve_font(&font);
9300            let space_width = window.text_system().layout_width(font_id, font_size, ' ');
9301            cx.new(|cx| {
9302                let mut state = crate::input::EditorState::new(window, cx)
9303                    .tab_size(TabSize {
9304                        tab_size: 4,
9305                        hard_tabs: true,
9306                    })
9307                    .default_value("\t\t");
9308                for (tab_size, expected_rows) in [(4, 2), (2, 1), (8, 2)] {
9309                    state.set_tab_size(
9310                        TabSize {
9311                            tab_size,
9312                            hard_tabs: true,
9313                        },
9314                        cx,
9315                    );
9316                    state.prewrap(font.clone(), font_size, space_width * 5. + RIGHT_MARGIN, cx);
9317                    assert_eq!(state.display_map().wrap_row_count(), expected_rows);
9318                }
9319                state
9320            });
9321            gpui::EmptyView
9322        });
9323    }
9324
9325    /// Prewrap counts with the state's wrapping indent, as the first layout
9326    /// does: continuation rows indented as deep as their line take more rows.
9327    #[gpui::test]
9328    fn test_prewrap_wraps_with_the_wrapping_indent(cx: &mut TestAppContext) {
9329        cx.update(crate::init);
9330        let text = "            select id, name, email, phone from users where active";
9331        let mut rows = Vec::new();
9332        for indent in [WrappingIndent::Same, WrappingIndent::None] {
9333            let mut input = None;
9334            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9335                let font = window.text_style().font();
9336                input = Some(cx.new(|cx| {
9337                    let mut state = crate::input::EditorState::new(window, cx)
9338                        .wrapping_indent(indent)
9339                        .default_value(text);
9340                    state.prewrap(font, px(14.), px(200.), cx);
9341                    state
9342                }));
9343                gpui::EmptyView
9344            });
9345            rows.push(
9346                input
9347                    .unwrap()
9348                    .read_with(cx, |state, _| state.display_map().wrap_row_count()),
9349            );
9350        }
9351        assert!(rows[0] > rows[1], "rows with Same, None: {rows:?}");
9352    }
9353
9354    /// Once the editor is laid out, layout owns the wrap and prewrap leaves it.
9355    #[gpui::test]
9356    fn test_prewrap_does_nothing_once_laid_out(cx: &mut TestAppContext) {
9357        struct Frame(Entity<crate::input::EditorState>);
9358        impl Render for Frame {
9359            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
9360                crate::input::InputBase::new("frame")
9361                    .h(px(100.))
9362                    .child(crate::input::Editor::new(&self.0))
9363            }
9364        }
9365
9366        cx.update(crate::init);
9367        let text = "select id, name, email, phone from users where active\nlimit 10";
9368        let mut input = None;
9369        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
9370            let state = cx.new(|cx| crate::input::EditorState::new(window, cx).default_value(text));
9371            input = Some(state.clone());
9372            Frame(state)
9373        });
9374        let input = input.unwrap();
9375        let mut cx = VisualTestContext::from_window(window.into(), cx);
9376        cx.update(|window, cx| window.draw(cx).clear(cx));
9377
9378        let laid_out = input.read_with(&cx, |state, _| state.display_map().wrap_row_count());
9379        assert!(laid_out >= 2, "laid out to {laid_out} rows");
9380        let font = cx.update(|window, _| window.text_style().font());
9381        input.update(&mut cx, |state, cx| {
9382            state.prewrap(font, px(14.), px(60.), cx)
9383        });
9384        assert_eq!(
9385            input.read_with(&cx, |state, _| state.display_map().wrap_row_count()),
9386            laid_out
9387        );
9388    }
9389
9390    /// Soft wrap is on by default, for every mode that can wrap.
9391    ///
9392    /// The default lives in the shared constructor, where a mode-specific
9393    /// `new` can silently fail to restore it; this pins it down.
9394    #[gpui::test]
9395    fn test_soft_wrap_is_enabled_by_default(cx: &mut TestAppContext) {
9396        let textarea = InputView::build_textarea(cx, |state| state);
9397        let mut textarea_cx = VisualTestContext::from_window(textarea.window_handle.into(), cx);
9398        textarea
9399            .input
9400            .read_with(&mut textarea_cx, |state, _| assert!(state.soft_wrap));
9401
9402        let editor = InputView::<EditorMode>::new(cx);
9403        let mut editor_cx = VisualTestContext::from_window(editor.window_handle.into(), cx);
9404        editor
9405            .input
9406            .read_with(&mut editor_cx, |state, _| assert!(state.soft_wrap));
9407    }
9408
9409    /// Parse a cursor spec into `(text, cursor_offsets)`. Non-empty lines are
9410    /// joined with `\n` plus a trailing `\n`. `|` marks a cursor. Leading
9411    /// whitespace is kept, so a spec can express indentation.
9412    fn parse_cursor_spec(input: &str) -> (String, Vec<usize>) {
9413        let mut full_text = String::new();
9414        let mut cursor_offsets = Vec::new();
9415        let non_empty_lines: Vec<&str> = input.lines().filter(|l| !l.is_empty()).collect();
9416
9417        for (line_idx, line) in non_empty_lines.iter().enumerate() {
9418            let mut positions = Vec::new();
9419            let mut text = String::new();
9420            for ch in line.chars() {
9421                if ch == '|' {
9422                    positions.push(text.len());
9423                } else {
9424                    text.push(ch);
9425                }
9426            }
9427
9428            if line_idx > 0 {
9429                full_text.push('\n');
9430            }
9431            let line_start = full_text.len();
9432            for pos in positions {
9433                cursor_offsets.push(line_start + pos);
9434            }
9435            full_text.push_str(&text);
9436        }
9437        full_text.push('\n');
9438
9439        (full_text, cursor_offsets)
9440    }
9441
9442    /// Build a multi-line input for multi-cursor tests.
9443    fn multi_line(cx: &mut TestAppContext) -> InputView<TextareaMode> {
9444        InputView::build_textarea(cx, |state| state)
9445    }
9446
9447    /// Set the text and cursor positions from a spec (see [`parse_cursor_spec`]).
9448    fn setup_cursors<M: InputModeKind>(
9449        cx: &mut VisualTestContext,
9450        input: &Entity<InputBaseState<M>>,
9451        spec: &str,
9452    ) {
9453        let (full_text, offsets) = parse_cursor_spec(spec);
9454        cx.update(|window, cx| {
9455            input.update(cx, |state, cx| {
9456                state.set_value(&full_text, window, cx);
9457                let selections = offsets
9458                    .into_iter()
9459                    .map(|offset| {
9460                        CursorSelection::new(state.selections.generate_id(), offset, offset)
9461                    })
9462                    .collect();
9463                state.selections.replace_all(selections);
9464                cx.notify();
9465            });
9466        });
9467    }
9468
9469    /// Assert the text and cursor positions match a spec.
9470    #[track_caller]
9471    fn assert_cursors<M: InputModeKind>(
9472        cx: &mut VisualTestContext,
9473        input: &Entity<InputBaseState<M>>,
9474        spec: &str,
9475    ) {
9476        let (expected_text, mut expected_cursors) = parse_cursor_spec(spec);
9477        expected_cursors.sort();
9478
9479        let (actual_text, mut actual_cursors) = input.read_with(cx, |state, _| {
9480            (
9481                state.text.to_string(),
9482                state
9483                    .selections
9484                    .iter()
9485                    .map(|s| s.cursor_offset())
9486                    .collect::<Vec<_>>(),
9487            )
9488        });
9489        actual_cursors.sort();
9490
9491        assert_eq!(
9492            actual_text, expected_text,
9493            "Text mismatch:\nExpected: {expected_text:?}\nActual:   {actual_text:?}"
9494        );
9495        assert_eq!(
9496            actual_cursors, expected_cursors,
9497            "Cursor mismatch:\nExpected: {expected_cursors:?}\nActual:   {actual_cursors:?}"
9498        );
9499    }
9500
9501    #[gpui::test]
9502    fn test_alt_drag_selects_a_block_and_replaces_each_row(cx: &mut TestAppContext) {
9503        cx.update(crate::init);
9504        let view = InputView::<EditorMode>::new(cx);
9505        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9506        for modifiers in [
9507            gpui::Modifiers {
9508                alt: true,
9509                ..Default::default()
9510            },
9511            gpui::Modifiers {
9512                alt: true,
9513                shift: true,
9514                ..Default::default()
9515            },
9516            #[cfg(target_os = "linux")]
9517            gpui::Modifiers {
9518                alt: true,
9519                control: true,
9520                ..Default::default()
9521            },
9522        ] {
9523            setup_cursors(&mut cx, &view.input, "|abcd\nabcd\nabcd");
9524            cx.update(|window, cx| {
9525                view.input.update(cx, |state, cx| state.focus(window, cx));
9526            });
9527            let (start, end) = view.input.read_with(&cx, |state, _| {
9528                let layout = state.last_layout.as_ref().unwrap();
9529                let origin = state.last_bounds.unwrap().origin;
9530                let position = |row: usize, col| {
9531                    let local = layout.lines[row]
9532                        .position_for_index(col, layout, false)
9533                        .unwrap();
9534                    origin
9535                        + point(
9536                            layout.line_number_width + local.x,
9537                            layout.line_height * (row as f32 + 0.5),
9538                        )
9539                };
9540                (position(0, 1), position(2, 3))
9541            });
9542            // Land inside the third column instead of exactly on its trailing
9543            // glyph boundary, where pixel rounding can select the next column.
9544            let end = point(end.x - px(0.5), end.y);
9545            // A cached Ctrl-hover definition must not steal a column gesture.
9546            cx.update(|_, cx| {
9547                view.input.update(cx, |state, _| {
9548                    state.extras.hover_definition.update(
9549                        0..4,
9550                        vec![lsp_types::LocationLink {
9551                            origin_selection_range: None,
9552                            target_uri: "file:///tmp/column-selection.rs".parse().unwrap(),
9553                            target_range: Default::default(),
9554                            target_selection_range: Default::default(),
9555                        }],
9556                    );
9557                });
9558            });
9559            cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
9560            cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
9561            cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
9562            view.input.read_with(&cx, |state, _| {
9563                let ranges: Vec<_> = state
9564                    .selections
9565                    .iter()
9566                    .map(|sel| sel.start..sel.end)
9567                    .collect();
9568                assert_eq!(ranges, vec![1..3, 6..8, 11..13]);
9569            });
9570            // Moving after release must leave the block intact.
9571            cx.simulate_mouse_move(start, None, modifiers);
9572            cx.simulate_keystrokes("x");
9573            assert_cursors(&mut cx, &view.input, "ax|d\nax|d\nax|d");
9574        }
9575    }
9576
9577    #[gpui::test]
9578    fn test_alt_drag_extends_upward_from_an_existing_cursor(cx: &mut TestAppContext) {
9579        cx.update(crate::init);
9580        let view = InputView::<EditorMode>::new(cx);
9581        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9582        setup_cursors(&mut cx, &view.input, "abcd\nabcd\na|bcd");
9583        cx.update(|window, cx| {
9584            view.input.update(cx, |state, cx| state.focus(window, cx));
9585        });
9586        let (start, end) = view.input.read_with(&cx, |state, _| {
9587            let layout = state.last_layout.as_ref().unwrap();
9588            let origin = state.last_bounds.unwrap().origin;
9589            let local = layout.lines[0]
9590                .position_for_index(1, layout, false)
9591                .unwrap();
9592            let x = layout.line_number_width + local.x;
9593            (
9594                origin + point(x, layout.line_height * 2.5),
9595                origin + point(x, layout.line_height * 0.5),
9596            )
9597        });
9598        let modifiers = gpui::Modifiers {
9599            alt: true,
9600            ..Default::default()
9601        };
9602        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
9603        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
9604        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
9605        assert_cursors(&mut cx, &view.input, "a|bcd\na|bcd\na|bcd");
9606    }
9607
9608    #[gpui::test]
9609    fn test_alt_drag_over_a_short_row_keeps_the_block_width(cx: &mut TestAppContext) {
9610        cx.update(crate::init);
9611        let view = InputView::<EditorMode>::new(cx);
9612        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9613        setup_cursors(&mut cx, &view.input, "abcdef\nab\nabcdef");
9614        cx.update(|window, cx| {
9615            view.input.update(cx, |state, cx| state.focus(window, cx));
9616        });
9617        // Drag from row 0 column 1 down to row 1, at the x of row 0's column 5. Row 1
9618        // only has two characters, so the pointer ends past its end.
9619        let (start, end) = view.input.read_with(&cx, |state, _| {
9620            let layout = state.last_layout.as_ref().unwrap();
9621            let origin = state.last_bounds.unwrap().origin;
9622            let x_of = |index| {
9623                layout.line_number_width
9624                    + layout.lines[0]
9625                        .position_for_index(index, layout, false)
9626                        .unwrap()
9627                        .x
9628            };
9629            (
9630                origin + point(x_of(1), layout.line_height * 0.5),
9631                origin + point(x_of(5), layout.line_height * 1.5),
9632            )
9633        });
9634        let modifiers = gpui::Modifiers {
9635            alt: true,
9636            shift: true,
9637            ..Default::default()
9638        };
9639        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
9640        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
9641        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
9642
9643        // The short row is clipped to its own end; the long row keeps the full span.
9644        view.input.read_with(&cx, |state, _| {
9645            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
9646            assert_eq!(ranges, vec![(1, 5), (8, 9)]);
9647        });
9648    }
9649
9650    #[gpui::test]
9651    fn test_alt_mouse_release_outside_editor_ends_column_selection(cx: &mut TestAppContext) {
9652        cx.update(crate::init);
9653        let view = InputView::<EditorMode>::new(cx);
9654        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9655        setup_cursors(&mut cx, &view.input, "|abcd\nabcd");
9656        let position = view.input.read_with(&cx, |state, _| {
9657            let layout = state.last_layout.as_ref().unwrap();
9658            state.last_bounds.unwrap().origin
9659                + point(layout.line_number_width + px(2.), layout.line_height * 0.5)
9660        });
9661        let modifiers = gpui::Modifiers {
9662            alt: true,
9663            ..Default::default()
9664        };
9665        cx.simulate_mouse_down(position, MouseButton::Left, modifiers);
9666        cx.simulate_mouse_up(point(px(-100.), px(-100.)), MouseButton::Left, modifiers);
9667        view.input.read_with(&cx, |state, _| {
9668            assert!(!state.selecting);
9669            assert!(state.column_select_start.is_none());
9670        });
9671    }
9672
9673    #[gpui::test]
9674    fn test_consumed_keystrokes_keep_cursor_visible(cx: &mut TestAppContext) {
9675        let view = InputView::<EditorMode>::new(cx);
9676        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9677        setup_cursors(&mut cx, &view.input, "a|b");
9678        cx.update(|window, cx| {
9679            view.input.update(cx, |state, cx| {
9680                state.focus(window, cx);
9681                state.pause_blink_cursor(cx);
9682            });
9683        });
9684        cx.run_until_parked();
9685        cx.executor()
9686            .advance_clock(std::time::Duration::from_millis(300));
9687        cx.run_until_parked();
9688        view.input.read_with(&cx, |state, cx| {
9689            assert!(!state.blink_cursor.read(cx).visible());
9690        });
9691        // Copy consumes its shortcut without editing text or moving selections.
9692        for _ in 0..5 {
9693            #[cfg(target_os = "macos")]
9694            cx.simulate_keystrokes("cmd-c");
9695            #[cfg(not(target_os = "macos"))]
9696            cx.simulate_keystrokes("ctrl-c");
9697            cx.run_until_parked();
9698            cx.executor()
9699                .advance_clock(std::time::Duration::from_millis(200));
9700            cx.run_until_parked();
9701            view.input.read_with(&cx, |state, cx| {
9702                assert!(state.blink_cursor.read(cx).visible());
9703            });
9704        }
9705    }
9706
9707    #[gpui::test]
9708    fn test_mouse_down_shows_cursor_like_a_cursor_move(cx: &mut TestAppContext) {
9709        cx.update(crate::init);
9710        let view = InputView::<EditorMode>::new(cx);
9711        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9712        setup_cursors(&mut cx, &view.input, "abcd|efgh");
9713        let position = |offset: Pixels, cx: &mut VisualTestContext| {
9714            view.input.read_with(cx, |state, _| {
9715                let layout = state.last_layout.as_ref().unwrap();
9716                state.last_bounds.unwrap().origin
9717                    + point(layout.line_number_width + offset, layout.line_height * 0.5)
9718            })
9719        };
9720        let visible = |cx: &mut VisualTestContext| {
9721            view.input
9722                .read_with(cx, |state, cx| state.blink_cursor.read(cx).visible())
9723        };
9724
9725        // The press itself shows the caret, without waiting for the focus
9726        // observer, and the idle delay holds it there like a cursor move.
9727        let first = position(px(2.), &mut cx);
9728        cx.simulate_mouse_down(first, MouseButton::Left, Default::default());
9729        assert!(visible(&mut cx));
9730        cx.simulate_mouse_up(first, MouseButton::Left, Default::default());
9731        cx.run_until_parked();
9732
9733        // Pressing elsewhere in the focused input, in the hidden phase of the
9734        // blink, shows the caret again at the press.
9735        cx.executor()
9736            .advance_clock(std::time::Duration::from_millis(400));
9737        cx.run_until_parked();
9738        assert!(!visible(&mut cx));
9739        let second = position(px(30.), &mut cx);
9740        cx.simulate_mouse_down(second, MouseButton::Left, Default::default());
9741        assert!(visible(&mut cx));
9742        cx.executor()
9743            .advance_clock(std::time::Duration::from_millis(250));
9744        cx.run_until_parked();
9745        assert!(visible(&mut cx));
9746        cx.simulate_mouse_up(second, MouseButton::Left, Default::default());
9747    }
9748
9749    #[gpui::test]
9750    fn test_multi_cursor_actions_reveal_hidden_carets(cx: &mut TestAppContext) {
9751        let view = InputView::<EditorMode>::new(cx);
9752        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9753        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
9754        // Start each action in the hidden phase without depending on a
9755        // key-down listener: actions and text input also arrive directly.
9756        for action in 0..6 {
9757            cx.update(|_, cx| {
9758                view.input.update(cx, |state, cx| {
9759                    state.blink_cursor = cx.new(|_| BlinkCursor::new());
9760                    state.blink_cursor.update(cx, |cursor, cx| cursor.start(cx));
9761                });
9762            });
9763            cx.run_until_parked();
9764            cx.executor()
9765                .advance_clock(std::time::Duration::from_millis(500));
9766            cx.run_until_parked();
9767            view.input.read_with(&cx, |state, cx| {
9768                assert!(!state.blink_cursor.read(cx).visible(), "action {action}");
9769            });
9770
9771            cx.update(|window, cx| {
9772                view.input.update(cx, |state, cx| {
9773                    match action {
9774                        0 => state.add_cursor_above(&AddCursorAbove, window, cx),
9775                        1 => state.add_cursor_below(&AddCursorBelow, window, cx),
9776                        2 => state.select_up(&SelectUp, window, cx),
9777                        3 => state.select_down(&SelectDown, window, cx),
9778                        4 => state.replace_text_in_range(None, "x", window, cx),
9779                        _ => state.backspace(&Backspace, window, cx),
9780                    }
9781                    assert!(state.blink_cursor.read(cx).visible(), "action {action}");
9782                });
9783            });
9784        }
9785    }
9786
9787    #[gpui::test]
9788    fn test_multi_cursor_keyboard_dispatch(cx: &mut TestAppContext) {
9789        let view = InputView::<EditorMode>::new(cx);
9790        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9791        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
9792        cx.update(|window, cx| {
9793            view.input.update(cx, |state, cx| state.focus(window, cx));
9794        });
9795        #[cfg(target_os = "macos")]
9796        cx.simulate_keystrokes("cmd-alt-up");
9797        #[cfg(target_os = "windows")]
9798        cx.simulate_keystrokes("ctrl-alt-up");
9799        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
9800        cx.simulate_keystrokes("alt-shift-up");
9801        view.input
9802            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
9803        #[cfg(target_os = "macos")]
9804        cx.simulate_keystrokes("cmd-alt-down");
9805        #[cfg(target_os = "windows")]
9806        cx.simulate_keystrokes("ctrl-alt-down");
9807        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
9808        cx.simulate_keystrokes("alt-shift-down");
9809        view.input
9810            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 3));
9811        cx.simulate_keystrokes("x");
9812        assert_cursors(&mut cx, &view.input, "ax|b\nax|b\nax|b");
9813    }
9814
9815    #[gpui::test]
9816    fn test_multi_cursor_platform_word_selection_dispatch(cx: &mut TestAppContext) {
9817        let view = InputView::<EditorMode>::new(cx);
9818        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9819        setup_cursors(&mut cx, &view.input, "one |two\none |two");
9820        cx.update(|window, cx| {
9821            view.input.update(cx, |state, cx| state.focus(window, cx));
9822        });
9823        #[cfg(any(target_os = "macos", target_os = "linux"))]
9824        cx.simulate_keystrokes("alt-shift-right");
9825        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
9826        cx.simulate_keystrokes("ctrl-shift-right");
9827        cx.simulate_keystrokes("x");
9828        assert_cursors(&mut cx, &view.input, "one x|\none x|");
9829        setup_cursors(&mut cx, &view.input, "one two|\none two|");
9830        #[cfg(any(target_os = "macos", target_os = "linux"))]
9831        cx.simulate_keystrokes("alt-shift-left");
9832        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
9833        cx.simulate_keystrokes("ctrl-shift-left");
9834        cx.simulate_keystrokes("x");
9835        assert_cursors(&mut cx, &view.input, "one x|\none x|");
9836    }
9837
9838    #[cfg(not(target_os = "macos"))]
9839    #[gpui::test]
9840    fn test_multi_cursor_horizontal_selection_dispatch(cx: &mut TestAppContext) {
9841        let view = InputView::<EditorMode>::new(cx);
9842        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9843        setup_cursors(&mut cx, &view.input, "ab\na|b");
9844        cx.update(|window, cx| {
9845            view.input.update(cx, |state, cx| state.focus(window, cx));
9846        });
9847        #[cfg(target_os = "windows")]
9848        cx.simulate_keystrokes("ctrl-alt-up");
9849        #[cfg(not(target_os = "windows"))]
9850        cx.simulate_keystrokes("alt-shift-up");
9851        #[cfg(target_os = "linux")]
9852        cx.simulate_keystrokes("shift-right");
9853        #[cfg(not(target_os = "linux"))]
9854        cx.simulate_keystrokes("alt-shift-right");
9855        view.input.read_with(&cx, |state, _| {
9856            assert_eq!(
9857                state
9858                    .selections
9859                    .iter()
9860                    .map(|s| s.start..s.end)
9861                    .collect::<Vec<_>>(),
9862                vec![4..5, 1..2]
9863            );
9864        });
9865        #[cfg(target_os = "linux")]
9866        cx.simulate_keystrokes("shift-left shift-left");
9867        #[cfg(not(target_os = "linux"))]
9868        cx.simulate_keystrokes("alt-shift-left alt-shift-left");
9869        view.input.read_with(&cx, |state, _| {
9870            assert_eq!(
9871                state
9872                    .selections
9873                    .iter()
9874                    .map(|s| s.start..s.end)
9875                    .collect::<Vec<_>>(),
9876                vec![3..4, 0..1]
9877            );
9878        });
9879        cx.simulate_keystrokes("x");
9880        assert_cursors(&mut cx, &view.input, "x|b\nx|b");
9881    }
9882
9883    #[gpui::test]
9884    fn test_multi_cursor_alt_click_dispatch(cx: &mut TestAppContext) {
9885        cx.update(crate::init);
9886        let view = InputView::<EditorMode>::new(cx);
9887        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9888        setup_cursors(&mut cx, &view.input, "a|b\nab\nab");
9889        cx.update(|window, cx| {
9890            view.input.update(cx, |state, cx| state.focus(window, cx));
9891        });
9892        let position = view.input.read_with(&cx, |state, _| {
9893            let bounds = state.last_bounds.unwrap();
9894            let layout = state.last_layout.as_ref().unwrap();
9895            bounds.origin + point(layout.line_number_width + px(2.), layout.line_height * 1.5)
9896        });
9897        cx.simulate_click(
9898            position,
9899            gpui::Modifiers {
9900                alt: true,
9901                ..Default::default()
9902            },
9903        );
9904        view.input
9905            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
9906    }
9907
9908    #[gpui::test]
9909    fn test_add_selection(cx: &mut TestAppContext) {
9910        let view = InputView::<EditorMode>::new(cx);
9911        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9912        cx.update(|window, cx| {
9913            view.input.update(cx, |state, cx| {
9914                state.focus(window, cx);
9915                state.set_value("foo bar foo", window, cx);
9916                state.set_selected_range(0..3, cx);
9917                // The programmatic second cursor: the next occurrence.
9918                state.add_selection(8..11, cx);
9919                // Adding a range that is already selected changes nothing.
9920                state.add_selection(0..3, cx);
9921            });
9922        });
9923        view.input.read_with(&cx, |state, _| {
9924            let mut ranges: Vec<Range<usize>> =
9925                state.selections.iter().map(|s| s.start..s.end).collect();
9926            ranges.sort_by_key(|r| r.start);
9927            assert_eq!(ranges, vec![0..3, 8..11]);
9928        });
9929        // Typing replaces every selection at once.
9930        cx.simulate_keystrokes("x");
9931        view.input.read_with(&cx, |state, _| {
9932            assert_eq!(state.text.to_string(), "x bar x")
9933        });
9934    }
9935
9936    #[gpui::test]
9937    fn test_add_selection_preserves_crlf_boundaries(cx: &mut TestAppContext) {
9938        let view = InputView::build_textarea(cx, |state| state.default_value("a\r\nb"));
9939        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9940        cx.update(|window, cx| {
9941            view.input.update(cx, |state, cx| {
9942                state.focus(window, cx);
9943                state.set_selected_range(4..4, cx);
9944                state.add_selection(2..2, cx);
9945                let ranges: Vec<_> = state.selections.iter().map(|s| s.start..s.end).collect();
9946                assert_eq!(ranges, vec![4..4, 1..1]);
9947            });
9948        });
9949        cx.simulate_keystrokes("x");
9950        view.input.read_with(&cx, |state, _| {
9951            assert_eq!(state.value(), "ax\r\nbx");
9952        });
9953        cx.update(|window, cx| {
9954            view.input.update(cx, |state, cx| {
9955                state.set_value("a\r\nb", window, cx);
9956                state.set_selected_range(4..4, cx);
9957                state.add_selection(2..3, cx);
9958                state.cut(&Cut, window, cx);
9959                assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "\r\n");
9960                assert_eq!(state.value(), "ab");
9961            });
9962        });
9963    }
9964
9965    #[gpui::test]
9966    fn test_add_selection_merges_overlaps_before_copy_and_cut(cx: &mut TestAppContext) {
9967        let view = InputView::<EditorMode>::new(cx);
9968        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9969        cx.update(|window, cx| {
9970            view.input.update(cx, |state, cx| {
9971                // Cover partial overlaps, a containing range, and a bridge
9972                // between two previously disjoint selections.
9973                for additions in [vec![2..5], vec![0..5], vec![4..5, 2..4]] {
9974                    state.set_value("abcdef", window, cx);
9975                    state.set_selected_range(0..3, cx);
9976                    let active_id = state.active_selection().id;
9977                    for range in additions {
9978                        state.add_selection(range, cx);
9979                    }
9980                    assert_eq!(state.active_selection().id, active_id);
9981                    assert_eq!(state.selected_range(), 0..5);
9982                    assert_eq!(state.selections.len(), 1);
9983                    state.copy(&Copy, window, cx);
9984                    assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "abcde");
9985                    state.cut(&Cut, window, cx);
9986                    assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "abcde");
9987                    assert_eq!(state.value(), "f");
9988                    state.undo(&Undo, window, cx);
9989                    assert_eq!(state.value(), "abcdef");
9990                    assert_eq!(state.selected_range(), 0..5);
9991                }
9992            });
9993        });
9994    }
9995
9996    #[gpui::test]
9997    fn test_add_selection_clears_inline_completion(cx: &mut TestAppContext) {
9998        use crate::input::CompletionProvider;
9999        use gpui::Task;
10000        use lsp_types::{
10001            CompletionContext, CompletionResponse, InlineCompletionContext, InlineCompletionItem,
10002            InlineCompletionResponse,
10003        };
10004        use std::time::Duration;
10005
10006        struct Provider(Rc<Cell<usize>>);
10007
10008        impl CompletionProvider for Provider {
10009            fn is_completion_trigger(&self, _: usize, _: &str, _: &mut App) -> bool {
10010                false
10011            }
10012
10013            fn completions(
10014                &self,
10015                _: &Rope,
10016                _: usize,
10017                _: CompletionContext,
10018                _: &mut Window,
10019                _: &mut App,
10020            ) -> Task<anyhow::Result<CompletionResponse>> {
10021                Task::ready(Ok(CompletionResponse::Array(vec![])))
10022            }
10023
10024            fn inline_completion(
10025                &self,
10026                _: &Rope,
10027                _: usize,
10028                _: InlineCompletionContext,
10029                _: &mut Window,
10030                _: &mut App,
10031            ) -> Task<anyhow::Result<InlineCompletionResponse>> {
10032                self.0.set(self.0.get() + 1);
10033                Task::ready(Ok(InlineCompletionResponse::Array(vec![])))
10034            }
10035        }
10036
10037        let view = InputView::<EditorMode>::new(cx);
10038        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10039        let calls = Rc::new(Cell::new(0));
10040        cx.update(|window, cx| {
10041            view.input.update(cx, |state, cx| {
10042                state.focus(window, cx);
10043                state.set_value("aa\naa", window, cx);
10044                state.set_selected_range(2..2, cx);
10045                state.extras.inline_completion.item = Some(InlineCompletionItem {
10046                    insert_text: "suggestion".into(),
10047                    filter_text: None,
10048                    range: None,
10049                    command: None,
10050                    insert_text_format: None,
10051                });
10052                state.add_selection(5..5, cx);
10053                assert!(!state.has_inline_completion());
10054                state.indent_inline(&IndentInline, window, cx);
10055                assert_eq!(state.value(), "aa  \naa  ");
10056                assert_eq!(state.selections.len(), 2);
10057
10058                // The active cursor stays put, so only canceling the task
10059                // prevents this pending request from fetching a suggestion.
10060                state.set_value("aa\naa", window, cx);
10061                state.set_selected_range(2..2, cx);
10062                state.extras.lsp.completion_provider = Some(Rc::new(Provider(calls.clone())));
10063                state.schedule_inline_completion(window, cx);
10064            });
10065        });
10066        cx.run_until_parked();
10067        cx.update(|_, cx| {
10068            view.input
10069                .update(cx, |state, cx| state.add_selection(5..5, cx));
10070        });
10071        cx.executor().advance_clock(Duration::from_secs(1));
10072        cx.run_until_parked();
10073        assert_eq!(calls.get(), 0);
10074        view.input.read_with(&cx, |state, _| {
10075            assert!(!state.has_inline_completion());
10076            assert_eq!(state.selections.len(), 2);
10077            assert_eq!(state.value(), "aa\naa");
10078        });
10079    }
10080
10081    #[gpui::test]
10082    fn test_word_delete_undo_restores_caret(cx: &mut TestAppContext) {
10083        let view = multi_line(cx);
10084        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10085        setup_cursors(&mut cx, &view.input, "hello|");
10086        cx.update(|window, cx| {
10087            view.input.update(cx, |state, cx| {
10088                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
10089                state.undo(&Undo, window, cx);
10090                assert_eq!(state.selected_range(), 5..5);
10091            });
10092        });
10093    }
10094
10095    #[gpui::test]
10096    fn test_merged_delete_undo_restores_all_carets(cx: &mut TestAppContext) {
10097        let view = multi_line(cx);
10098        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10099        setup_cursors(&mut cx, &view.input, "a|b|c");
10100        cx.update(|window, cx| {
10101            view.input.update(cx, |state, cx| {
10102                state.backspace(&Backspace, window, cx);
10103                state.undo(&Undo, window, cx);
10104            });
10105        });
10106        assert_cursors(&mut cx, &view.input, "a|b|c");
10107    }
10108
10109    #[gpui::test]
10110    fn test_shift_end_respects_soft_wrap_end(cx: &mut TestAppContext) {
10111        let view = InputView::<EditorMode>::new(cx);
10112        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10113        cx.update(|window, cx| {
10114            view.input.update(cx, |state, cx| {
10115                state.set_value("abcdef ".repeat(30), window, cx);
10116                state.display_map.on_layout_changed(Some(px(60.)), cx);
10117                let line = state.display_map.line(0).unwrap();
10118                assert!(line.wrapped_lines.len() > 1);
10119                let boundary = line.wrapped_lines[0].end;
10120                state.move_to_with_affinity(boundary, None, true, cx);
10121                state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
10122                assert_eq!(state.selected_range(), boundary..state.text.len());
10123            });
10124        });
10125    }
10126
10127    #[gpui::test]
10128    fn test_outdent_unindented_unicode_is_unchanged(cx: &mut TestAppContext) {
10129        let view = multi_line(cx);
10130        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10131        setup_cursors(&mut cx, &view.input, "|你好\n|世界");
10132        cx.update(|window, cx| {
10133            view.input.update(cx, |state, cx| {
10134                state.outdent(false, window, cx);
10135                state.outdent(true, window, cx);
10136            });
10137        });
10138        assert_cursors(&mut cx, &view.input, "|你好\n|世界");
10139    }
10140
10141    #[gpui::test]
10142    fn test_column_selection_stays_on_unicode_boundaries(cx: &mut TestAppContext) {
10143        let view = multi_line(cx);
10144        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10145        setup_cursors(&mut cx, &view.input, "ab\n你好\ncd");
10146        cx.update(|window, cx| {
10147            view.input.update(cx, |state, cx| {
10148                state.build_columnar_selection(
10149                    ColumnarPoint::new(1, 0),
10150                    ColumnarPoint::new(11, 0),
10151                    cx,
10152                );
10153                let text = state.value();
10154                for sel in state.selections.iter() {
10155                    assert!(text.is_char_boundary(sel.start));
10156                    assert!(text.is_char_boundary(sel.end));
10157                }
10158                state.replace_text_in_range(None, "X", window, cx);
10159            });
10160        });
10161    }
10162
10163    #[gpui::test]
10164    fn test_editor_decorations_follow_typing(cx: &mut TestAppContext) {
10165        let view = InputView::<EditorMode>::new(cx);
10166        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10167        cx.update(|window, cx| {
10168            view.input.update(cx, |state, cx| {
10169                state.set_value("abc def", window, cx);
10170                state.create_decorations_collection(
10171                    vec![crate::input::TextDecoration::new(
10172                        4..7,
10173                        gpui::HighlightStyle::default(),
10174                    )],
10175                    cx,
10176                );
10177                state.set_selected_range(0..0, cx);
10178                state.replace_text_in_range(None, "X", window, cx);
10179                let layers = state.extras.decoration_layers();
10180                assert_eq!(layers.into_iter().flatten().next().unwrap().range, 5..8);
10181            });
10182        });
10183    }
10184
10185    #[gpui::test]
10186    fn test_backspace_preserves_escaped_quote_terminator(cx: &mut TestAppContext) {
10187        let view = InputView::<EditorMode>::new(cx);
10188        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10189        setup_cursors(&mut cx, &view.input, r#"{"s":"\"|"}"#);
10190        cx.update(|window, cx| {
10191            view.input.update(cx, |state, cx| {
10192                set_test_syntax_provider(std::rc::Rc::new(StringAllProvider), cx);
10193                state.backspace(&Backspace, window, cx);
10194            });
10195        });
10196        assert_cursors(&mut cx, &view.input, r#"{"s":"\|"}"#);
10197    }
10198
10199    #[gpui::test]
10200    fn test_ordinary_typing_does_not_query_syntax(cx: &mut TestAppContext) {
10201        struct UnexpectedQuery;
10202        impl crate::input::SyntaxContextProvider for UnexpectedQuery {
10203            fn context_at(&self, _: &ropey::Rope, _: usize) -> crate::input::SyntaxContext {
10204                panic!("ordinary typing must not query syntax");
10205            }
10206        }
10207        let view = InputView::<EditorMode>::new(cx);
10208        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10209        setup_cursors(&mut cx, &view.input, "|");
10210        cx.update(|window, cx| {
10211            view.input.update(cx, |state, cx| {
10212                set_test_syntax_provider(std::rc::Rc::new(UnexpectedQuery), cx);
10213                for c in ["a", "b", "c"] {
10214                    state.replace_text_in_range(None, c, window, cx);
10215                }
10216            });
10217        });
10218        assert_cursors(&mut cx, &view.input, "abc|");
10219    }
10220
10221    #[gpui::test]
10222    fn test_auto_close_is_atomic_at_history_limit(cx: &mut TestAppContext) {
10223        let view = InputView::<EditorMode>::new(cx);
10224        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10225        setup_cursors(&mut cx, &view.input, "|");
10226        cx.update(|window, cx| {
10227            view.input.update(cx, |state, cx| {
10228                for _ in 0..999 {
10229                    state.replace_text_in_range(None, "a", window, cx);
10230                }
10231                state.replace_text_in_range(None, "(", window, cx);
10232                state.undo(&Undo, window, cx);
10233                assert!(!state.text.to_string().contains('('));
10234                assert!(!state.text.to_string().contains(')'));
10235                state.redo(&Redo, window, cx);
10236            });
10237        });
10238        assert_cursors(&mut cx, &view.input, &format!("{}(|)", "a".repeat(999)));
10239    }
10240
10241    #[gpui::test]
10242    fn test_comment_closer_is_inserted_literally(cx: &mut TestAppContext) {
10243        let view = InputView::<EditorMode>::new(cx);
10244        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10245        setup_cursors(&mut cx, &view.input, "// (|)");
10246        cx.update(|window, cx| {
10247            view.input.update(cx, |state, cx| {
10248                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10249                state.replace_text_in_range(None, ")", window, cx);
10250            });
10251        });
10252        assert_cursors(&mut cx, &view.input, "// ()|)");
10253    }
10254
10255    #[gpui::test]
10256    fn test_explicit_range_skip_does_not_delete_existing_closer_on_undo(cx: &mut TestAppContext) {
10257        let view = InputView::<EditorMode>::new(cx);
10258        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10259        setup_cursors(&mut cx, &view.input, "(a|)");
10260        let before = view.input.read_with(&cx, |state, _| state.text.to_string());
10261        cx.update(|window, cx| {
10262            view.input.update(cx, |state, cx| {
10263                state.replace_text_in_range(Some(2..2), ")", window, cx);
10264            });
10265        });
10266        assert_cursors(&mut cx, &view.input, "(a)|");
10267        cx.update(|window, cx| {
10268            view.input
10269                .update(cx, |state, cx| state.undo(&Undo, window, cx));
10270        });
10271        view.input.read_with(&cx, |state, _| {
10272            assert_eq!(
10273                state.text.to_string(),
10274                before,
10275                "skip must not remove an existing closer on undo"
10276            );
10277        });
10278    }
10279
10280    #[gpui::test]
10281    fn test_pair_redo_restores_interior_cursor(cx: &mut TestAppContext) {
10282        let view = InputView::<EditorMode>::new(cx);
10283        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10284        setup_cursors(&mut cx, &view.input, "|");
10285        cx.update(|window, cx| {
10286            view.input.update(cx, |state, cx| {
10287                state.replace_text_in_range(None, "(", window, cx);
10288            });
10289        });
10290        assert_cursors(&mut cx, &view.input, "(|)");
10291        cx.update(|window, cx| {
10292            view.input.update(cx, |state, cx| {
10293                state.undo(&Undo, window, cx);
10294                state.redo(&Redo, window, cx);
10295            });
10296        });
10297        assert_cursors(&mut cx, &view.input, "(|)");
10298    }
10299
10300    #[gpui::test]
10301    fn test_pair_enter_redo_restores_interior_cursor(cx: &mut TestAppContext) {
10302        let view = InputView::<EditorMode>::new(cx);
10303        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10304        setup_cursors(&mut cx, &view.input, "{|}");
10305        cx.update(|window, cx| {
10306            view.input.update(cx, |state, cx| {
10307                state.enter(
10308                    &Enter {
10309                        secondary: false,
10310                        shift: false,
10311                    },
10312                    window,
10313                    cx,
10314                );
10315            });
10316        });
10317        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10318        cx.update(|window, cx| {
10319            view.input.update(cx, |state, cx| {
10320                state.undo(&Undo, window, cx);
10321            });
10322        });
10323        assert_cursors(&mut cx, &view.input, "{|}");
10324        cx.update(|window, cx| {
10325            view.input
10326                .update(cx, |state, cx| state.redo(&Redo, window, cx));
10327        });
10328        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10329    }
10330
10331    #[gpui::test]
10332    fn test_auto_close_parens(cx: &mut TestAppContext) {
10333        let view = InputView::<EditorMode>::new(cx);
10334        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10335        setup_cursors(&mut cx, &view.input, "|");
10336        cx.update(|window, cx| {
10337            view.input.update(cx, |state, cx| {
10338                state.replace_text_in_range(None, "(", window, cx);
10339            });
10340        });
10341        assert_cursors(&mut cx, &view.input, "(|)");
10342        // Selection must be collapsed: typing next must insert, not replace.
10343        cx.update(|window, cx| {
10344            view.input.update(cx, |state, cx| {
10345                state.replace_text_in_range(None, "x", window, cx);
10346            });
10347        });
10348        assert_cursors(&mut cx, &view.input, "(x|)");
10349    }
10350
10351    #[gpui::test]
10352    fn test_auto_close_skip_over_closer(cx: &mut TestAppContext) {
10353        let view = InputView::<EditorMode>::new(cx);
10354        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10355        setup_cursors(&mut cx, &view.input, "(|)");
10356        cx.update(|window, cx| {
10357            view.input.update(cx, |state, cx| {
10358                state.replace_text_in_range(None, ")", window, cx);
10359            });
10360        });
10361        // No duplicate closer, cursor moves past the existing one.
10362        assert_cursors(&mut cx, &view.input, "()|");
10363        // Selection must be collapsed, not covering blank space.
10364        view.input.read_with(&cx, |state, _| {
10365            assert!(state.active_selection().is_empty());
10366        });
10367    }
10368
10369    #[gpui::test]
10370    fn test_auto_close_no_pair_inside_word(cx: &mut TestAppContext) {
10371        let view = InputView::<EditorMode>::new(cx);
10372        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10373        setup_cursors(&mut cx, &view.input, "don|");
10374        cx.update(|window, cx| {
10375            view.input.update(cx, |state, cx| {
10376                state.replace_text_in_range(None, "'", window, cx);
10377            });
10378        });
10379        // Contraction: no auto-close.
10380        assert_cursors(&mut cx, &view.input, "don'|");
10381    }
10382
10383    #[gpui::test]
10384    fn test_auto_close_quote_after_cjk(cx: &mut TestAppContext) {
10385        let view = InputView::<EditorMode>::new(cx);
10386        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10387        // Unicode-safe previous-char lookup: must not panic on multi-byte prefix.
10388        // CJK counts as word-like, so no pairing — single quote inserted.
10389        setup_cursors(&mut cx, &view.input, "中|");
10390        cx.update(|window, cx| {
10391            view.input.update(cx, |state, cx| {
10392                state.replace_text_in_range(None, "'", window, cx);
10393            });
10394        });
10395        assert_cursors(&mut cx, &view.input, "中'|");
10396    }
10397
10398    #[gpui::test]
10399    fn test_auto_close_closer_skips_at_string_end(cx: &mut TestAppContext) {
10400        let view = InputView::<EditorMode>::new(cx);
10401        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10402        // Cursor before existing closing quote: typing `"` skips past it.
10403        setup_cursors(&mut cx, &view.input, "\"hello|\"");
10404        cx.update(|window, cx| {
10405            view.input.update(cx, |state, cx| {
10406                state.replace_text_in_range(None, "\"", window, cx);
10407            });
10408        });
10409        assert_cursors(&mut cx, &view.input, "\"hello\"|");
10410        view.input.read_with(&cx, |state, _| {
10411            assert!(state.active_selection().is_empty());
10412        });
10413    }
10414
10415    #[gpui::test]
10416    fn test_backspace_pair_with_cjk_prefix(cx: &mut TestAppContext) {
10417        let view = InputView::<EditorMode>::new(cx);
10418        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10419        // UTF-16 conversion: deleting `()` after CJK must not touch neighbors.
10420        setup_cursors(&mut cx, &view.input, "中(|)abc");
10421        cx.update(|window, cx| {
10422            view.input
10423                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
10424        });
10425        assert_cursors(&mut cx, &view.input, "中|abc");
10426    }
10427
10428    #[gpui::test]
10429    fn test_enter_split_respects_smart_indent_off(cx: &mut TestAppContext) {
10430        let view = InputView::<EditorMode>::new(cx);
10431        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10432        setup_cursors(&mut cx, &view.input, "{|}");
10433        cx.update(|window, cx| {
10434            view.input.update(cx, |state, cx| {
10435                state.set_smart_indent(false, window, cx);
10436                state.enter(
10437                    &Enter {
10438                        secondary: false,
10439                        shift: false,
10440                    },
10441                    window,
10442                    cx,
10443                );
10444            });
10445        });
10446        // Smart indent controls both structural splitting and extra indentation.
10447        assert_cursors(&mut cx, &view.input, "{\n|}");
10448    }
10449
10450    #[gpui::test]
10451    fn test_enter_split_is_independent_of_auto_close(cx: &mut TestAppContext) {
10452        let view = InputView::<EditorMode>::new(cx);
10453        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10454        setup_cursors(&mut cx, &view.input, "{|}");
10455        cx.update(|window, cx| {
10456            view.input.update(cx, |state, cx| {
10457                state.set_auto_close(false, window, cx);
10458                state.enter(
10459                    &Enter {
10460                        secondary: false,
10461                        shift: false,
10462                    },
10463                    window,
10464                    cx,
10465                );
10466            });
10467        });
10468        // Disabling automatic insertion does not disable structural indentation.
10469        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10470    }
10471
10472    #[gpui::test]
10473    fn test_language_config_applies_before_render_and_on_language_change(cx: &mut TestAppContext) {
10474        use crate::input::{AutoClosingPair, set_language_config};
10475        let view = InputView::<EditorMode>::new(cx);
10476        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10477        cx.update(|window, cx| {
10478            set_language_config(
10479                "alpha",
10480                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
10481                cx,
10482            );
10483            set_language_config(
10484                "beta",
10485                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("‹", "›")]),
10486                cx,
10487            );
10488            let first = cx.new(|cx| EditorState::new(window, cx).language("ALPHA"));
10489            let second = cx.new(|cx| EditorState::new(window, cx).language("beta"));
10490            first.update(cx, |state, cx| {
10491                state.replace_text_in_range(None, "«", window, cx);
10492                assert_eq!(state.text().to_string(), "«»");
10493                state.set_value("", window, cx);
10494                state.set_smart_indent(false, window, cx);
10495                state.set_highlighter("beta", cx);
10496                assert!(!state.mode.is_smart_indent());
10497                state.replace_text_in_range(None, "‹", window, cx);
10498                assert_eq!(state.text().to_string(), "‹›");
10499                state.set_value("", window, cx);
10500                state.set_highlighter("unknown", cx);
10501                state.replace_text_in_range(None, "(", window, cx);
10502                assert_eq!(state.text().to_string(), "()");
10503            });
10504            second.update(cx, |state, cx| {
10505                state.replace_text_in_range(None, "‹", window, cx);
10506                assert_eq!(state.text().to_string(), "‹›");
10507            });
10508        });
10509    }
10510
10511    #[gpui::test]
10512    fn test_language_service_replacement_updates_syntax_without_render(cx: &mut TestAppContext) {
10513        use crate::input::{LanguageProvider, SyntaxContextProvider, set_language_provider};
10514        struct StringLanguages(Rc<Cell<usize>>);
10515        impl LanguageProvider for StringLanguages {
10516            fn syntax_context_provider(&self, _: &str) -> Option<Rc<dyn SyntaxContextProvider>> {
10517                self.0.set(self.0.get() + 1);
10518                Some(Rc::new(StringAllProvider))
10519            }
10520        }
10521        struct CodeLanguages;
10522        impl LanguageProvider for CodeLanguages {}
10523        let view = InputView::<EditorMode>::new(cx);
10524        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10525        cx.update(|window, cx| {
10526            let creations = Rc::new(Cell::new(0));
10527            set_language_provider(Rc::new(StringLanguages(creations.clone())), cx);
10528            view.input.update(cx, |state, cx| {
10529                state.replace_text_in_range(None, "(", window, cx);
10530                state.replace_text_in_range(None, "[", window, cx);
10531                assert_eq!(state.text().to_string(), "([");
10532                assert_eq!(
10533                    creations.get(),
10534                    1,
10535                    "retain the document provider between edits"
10536                );
10537            });
10538            set_language_provider(Rc::new(CodeLanguages), cx);
10539            view.input.update(cx, |state, cx| {
10540                state.replace_text_in_range(None, "{", window, cx);
10541                assert_eq!(state.text().to_string(), "([{}");
10542            });
10543        });
10544    }
10545
10546    #[gpui::test]
10547    fn test_language_config_updates_existing_editors(cx: &mut TestAppContext) {
10548        use crate::input::{AutoClosingPair, set_language_config};
10549        let view = InputView::<EditorMode>::new(cx);
10550        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10551        setup_cursors(&mut cx, &view.input, "|");
10552        cx.update(|window, cx| {
10553            view.input.update(cx, |state, cx| {
10554                state.set_highlighter("CUSTOM", cx);
10555                state.set_auto_close(false, window, cx);
10556                state.set_smart_indent(false, window, cx);
10557            });
10558            set_language_config(
10559                "custom",
10560                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
10561                cx,
10562            );
10563            // Batched registrations must update every affected language.
10564            set_language_config("other", LanguageConfig::default(), cx);
10565            view.input.update(cx, |state, cx| {
10566                assert!(!state.mode.is_auto_close());
10567                assert!(!state.mode.is_smart_indent());
10568                state.set_auto_close(true, window, cx);
10569                state.replace_text_in_range(None, "«", window, cx);
10570            });
10571        });
10572        assert_cursors(&mut cx, &view.input, "«|»");
10573    }
10574
10575    #[gpui::test]
10576    fn test_auto_close_before_is_language_configurable(cx: &mut TestAppContext) {
10577        let view = InputView::<EditorMode>::new(cx);
10578        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10579        setup_cursors(&mut cx, &view.input, "|word");
10580        cx.update(|window, cx| {
10581            view.input.update(cx, |state, cx| {
10582                state.replace_text_in_range(None, "(", window, cx)
10583            });
10584        });
10585        assert_cursors(&mut cx, &view.input, "(|word");
10586        setup_cursors(&mut cx, &view.input, "|word");
10587        cx.update(|window, cx| {
10588            view.input.update(cx, |state, cx| {
10589                crate::input::set_language_config(
10590                    state.language_name(),
10591                    LanguageConfig::default().auto_close_before("w"),
10592                    cx,
10593                );
10594                state.replace_text_in_range(None, "(", window, cx);
10595            });
10596        });
10597        assert_cursors(&mut cx, &view.input, "(|)word");
10598    }
10599
10600    #[gpui::test]
10601    fn test_pair_context_restrictions_are_per_pair(cx: &mut TestAppContext) {
10602        use crate::input::{AutoClosingPair, SyntaxContext};
10603        let view = InputView::<EditorMode>::new(cx);
10604        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10605        setup_cursors(&mut cx, &view.input, "|");
10606        cx.update(|window, cx| {
10607            view.input.update(cx, |state, cx| {
10608                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10609                crate::input::set_language_config(
10610                    state.language_name(),
10611                    LanguageConfig::default().auto_closing_pairs([
10612                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::String]),
10613                        AutoClosingPair::new("[", "]").not_in([SyntaxContext::Comment]),
10614                    ]),
10615                    cx,
10616                );
10617                state.replace_text_in_range(None, "(", window, cx);
10618                state.replace_text_in_range(None, "[", window, cx);
10619            });
10620        });
10621        assert_cursors(&mut cx, &view.input, "([|)");
10622    }
10623
10624    #[gpui::test]
10625    fn test_multichar_delimiters_insert_delete_and_skip(cx: &mut TestAppContext) {
10626        use crate::input::{AutoClosingPair, SyntaxContext};
10627        struct BlockComment;
10628        impl crate::input::SyntaxContextProvider for BlockComment {
10629            fn context_at(&self, text: &ropey::Rope, _: usize) -> SyntaxContext {
10630                if text.to_string().contains("/*") {
10631                    SyntaxContext::Comment
10632                } else {
10633                    SyntaxContext::Code
10634                }
10635            }
10636        }
10637        let view = InputView::<EditorMode>::new(cx);
10638        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10639        setup_cursors(&mut cx, &view.input, "|");
10640        cx.update(|window, cx| {
10641            view.input.update(cx, |state, cx| {
10642                set_test_syntax_provider(std::rc::Rc::new(BlockComment), cx);
10643                crate::input::set_language_config(
10644                    state.language_name(),
10645                    LanguageConfig::default()
10646                        .auto_closing_pairs([AutoClosingPair::new("/*", "*/")
10647                            .not_in([SyntaxContext::String, SyntaxContext::Comment])]),
10648                    cx,
10649                );
10650                state.replace_text_in_range(None, "/", window, cx);
10651                state.replace_text_in_range(None, "*", window, cx);
10652            });
10653        });
10654        assert_cursors(&mut cx, &view.input, "/*|*/");
10655        cx.update(|window, cx| {
10656            view.input
10657                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
10658        });
10659        assert_cursors(&mut cx, &view.input, "|");
10660        cx.update(|window, cx| {
10661            view.input.update(cx, |state, cx| {
10662                state.undo(&Undo, window, cx);
10663                state.replace_text_in_range(None, "*", window, cx);
10664                state.replace_text_in_range(None, "/", window, cx);
10665            });
10666        });
10667        assert_cursors(&mut cx, &view.input, "/**/|");
10668    }
10669
10670    #[gpui::test]
10671    fn test_auto_closed_pairs_fallback_and_explicit_disable(cx: &mut TestAppContext) {
10672        use crate::input::BracketPair;
10673        let view = InputView::<EditorMode>::new(cx);
10674        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10675        setup_cursors(&mut cx, &view.input, "|");
10676        cx.update(|window, cx| {
10677            view.input.update(cx, |state, cx| {
10678                let mut rules = LanguageConfig::default().brackets([BracketPair::new("«", "»")]);
10679                rules.auto_closing_pairs = None;
10680                crate::input::set_language_config(state.language_name(), rules.clone(), cx);
10681                state.replace_text_in_range(None, "«", window, cx);
10682                crate::input::set_language_config(
10683                    state.language_name(),
10684                    rules.auto_closing_pairs([]),
10685                    cx,
10686                );
10687                state.replace_text_in_range(None, "«", window, cx);
10688            });
10689        });
10690        assert_cursors(&mut cx, &view.input, "««|»");
10691    }
10692
10693    #[gpui::test]
10694    fn test_undo_open_composition_preserves_generated_pairs(cx: &mut TestAppContext) {
10695        use crate::input::{AutoClosingPair, SyntaxContext};
10696        let view = InputView::<EditorMode>::new(cx);
10697        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10698        setup_cursors(&mut cx, &view.input, "|");
10699        cx.update(|window, cx| {
10700            view.input.update(cx, |state, cx| {
10701                crate::input::set_language_config(
10702                    state.language_name(),
10703                    LanguageConfig::default()
10704                        .auto_closing_pairs([
10705                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
10706                        ]),
10707                    cx,
10708                );
10709                state.replace_text_in_range(None, "/", window, cx);
10710                state.replace_text_in_range(None, "*", window, cx);
10711                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10712                state.replace_and_mark_text_in_range(None, "x", Some(1..1), window, cx);
10713                state.undo(&Undo, window, cx);
10714                state.replace_text_in_range(None, "*", window, cx);
10715                state.replace_text_in_range(None, "/", window, cx);
10716            });
10717        });
10718        assert_cursors(&mut cx, &view.input, "/**/|");
10719    }
10720
10721    #[gpui::test]
10722    fn test_undo_does_not_promote_literal_comment_delimiters(cx: &mut TestAppContext) {
10723        use crate::input::{AutoClosingPair, SyntaxContext};
10724        let view = InputView::<EditorMode>::new(cx);
10725        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10726        setup_cursors(&mut cx, &view.input, "/*|*/");
10727        cx.update(|window, cx| {
10728            view.input.update(cx, |state, cx| {
10729                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10730                crate::input::set_language_config(
10731                    state.language_name(),
10732                    LanguageConfig::default()
10733                        .auto_closing_pairs([
10734                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
10735                        ]),
10736                    cx,
10737                );
10738                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
10739                state.undo(&Undo, window, cx);
10740                state.backspace(&Backspace, window, cx);
10741            });
10742        });
10743        assert_cursors(&mut cx, &view.input, "/|*/");
10744    }
10745
10746    #[gpui::test]
10747    fn test_multiple_generated_pairs_retain_their_identity(cx: &mut TestAppContext) {
10748        use crate::input::{AutoClosingPair, SyntaxContext};
10749        struct CommentScope;
10750        impl crate::input::SyntaxContextProvider for CommentScope {
10751            fn context_at(&self, text: &ropey::Rope, offset: usize) -> SyntaxContext {
10752                if text
10753                    .to_string()
10754                    .find("*/")
10755                    .is_some_and(|end| offset < end + 2)
10756                {
10757                    SyntaxContext::Comment
10758                } else {
10759                    SyntaxContext::Code
10760                }
10761            }
10762        }
10763        let view = InputView::<EditorMode>::new(cx);
10764        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10765        setup_cursors(&mut cx, &view.input, "|");
10766        cx.update(|window, cx| {
10767            view.input.update(cx, |state, cx| {
10768                set_test_syntax_provider(std::rc::Rc::new(CommentScope), cx);
10769                crate::input::set_language_config(
10770                    state.language_name(),
10771                    LanguageConfig::default().auto_closing_pairs([
10772                        AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment]),
10773                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::Comment]),
10774                    ]),
10775                    cx,
10776                );
10777                state.replace_text_in_range(None, "/", window, cx);
10778                state.replace_text_in_range(None, "*", window, cx);
10779                state.set_selected_range(4..4, cx);
10780                state.replace_text_in_range(None, "(", window, cx);
10781                state.set_selected_range(2..2, cx);
10782                state.replace_text_in_range(None, "*", window, cx);
10783                state.replace_text_in_range(None, "/", window, cx);
10784            });
10785        });
10786        assert_cursors(&mut cx, &view.input, "/**/|()");
10787        cx.update(|window, cx| {
10788            view.input.update(cx, |state, cx| {
10789                state.set_selected_range(2..2, cx);
10790                state.backspace(&Backspace, window, cx);
10791                state.undo(&Undo, window, cx);
10792                state.redo(&Redo, window, cx);
10793            });
10794        });
10795        assert_cursors(&mut cx, &view.input, "|()");
10796    }
10797
10798    #[gpui::test]
10799    fn test_replacing_generated_opener_invalidates_the_pair(cx: &mut TestAppContext) {
10800        use crate::input::{AutoClosingPair, SyntaxContext};
10801        let view = InputView::<EditorMode>::new(cx);
10802        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10803        setup_cursors(&mut cx, &view.input, "|");
10804        cx.update(|window, cx| {
10805            view.input.update(cx, |state, cx| {
10806                crate::input::set_language_config(
10807                    state.language_name(),
10808                    LanguageConfig::default()
10809                        .auto_closing_pairs([
10810                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
10811                        ]),
10812                    cx,
10813                );
10814                state.replace_text_in_range(None, "/", window, cx);
10815                state.replace_text_in_range(None, "*", window, cx);
10816                state.replace_text_in_range(Some(0..2), "//", window, cx);
10817                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10818                state.replace_text_in_range(None, "*", window, cx);
10819            });
10820        });
10821        assert_cursors(&mut cx, &view.input, "//*|*/");
10822    }
10823
10824    #[gpui::test]
10825    fn test_configuration_preserves_compiled_regex_options(cx: &mut TestAppContext) {
10826        use crate::input::IndentationRules;
10827        let view = InputView::<EditorMode>::new(cx);
10828        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10829        setup_cursors(&mut cx, &view.input, "BEGIN|");
10830        cx.update(|window, cx| {
10831            view.input.update(cx, |state, cx| {
10832                let rules = |insensitive| {
10833                    LanguageConfig::default().indentation_rules(IndentationRules::new(
10834                        regex::RegexBuilder::new("begin$")
10835                            .case_insensitive(insensitive)
10836                            .build()
10837                            .unwrap(),
10838                        regex::Regex::new("^end").unwrap(),
10839                    ))
10840                };
10841                crate::input::set_language_config(state.language_name(), rules(false), cx);
10842                crate::input::set_language_config(state.language_name(), rules(true), cx);
10843                state.enter(
10844                    &Enter {
10845                        secondary: false,
10846                        shift: false,
10847                    },
10848                    window,
10849                    cx,
10850                );
10851            });
10852        });
10853        assert_cursors(&mut cx, &view.input, "BEGIN\n  |");
10854    }
10855
10856    #[gpui::test]
10857    fn test_indent_patterns_are_applied_on_enter(cx: &mut TestAppContext) {
10858        use crate::input::IndentationRules;
10859        let view = InputView::<EditorMode>::new(cx);
10860        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10861        let rules = LanguageConfig::default().indentation_rules(IndentationRules::new(
10862            regex::Regex::new(r"\bbegin\s*$").unwrap(),
10863            regex::Regex::new(r"^\s*end\b").unwrap(),
10864        ));
10865        for (before, expected) in [("begin|", "begin\n  |"), ("  |end", "  \n|end")] {
10866            setup_cursors(&mut cx, &view.input, before);
10867            cx.update(|window, cx| {
10868                view.input.update(cx, |state, cx| {
10869                    crate::input::set_language_config(state.language_name(), rules.clone(), cx);
10870                    state.enter(
10871                        &Enter {
10872                            secondary: false,
10873                            shift: false,
10874                        },
10875                        window,
10876                        cx,
10877                    );
10878                });
10879            });
10880            assert_cursors(&mut cx, &view.input, expected);
10881        }
10882    }
10883
10884    #[gpui::test]
10885    fn test_custom_language_config(cx: &mut TestAppContext) {
10886        let view = InputView::<EditorMode>::new(cx);
10887        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10888        cx.update(|_window, cx| {
10889            view.input.update(cx, |state, cx| {
10890                crate::input::set_language_config(
10891                    state.language_name(),
10892                    crate::input::language_config::LanguageConfig::default()
10893                        .brackets([crate::input::BracketPair::new("«", "»")])
10894                        .auto_closing_pairs([crate::input::AutoClosingPair::new("«", "»")]),
10895                    cx,
10896                );
10897            });
10898        });
10899        // Custom pair closes.
10900        setup_cursors(&mut cx, &view.input, "|");
10901        cx.update(|window, cx| {
10902            view.input.update(cx, |state, cx| {
10903                state.replace_text_in_range(None, "«", window, cx);
10904            });
10905        });
10906        assert_cursors(&mut cx, &view.input, "«|»");
10907        // Default `(` no longer pairs under custom rules.
10908        cx.update(|window, cx| {
10909            view.input.update(cx, |state, cx| {
10910                state.set_value("\n", window, cx);
10911                state.set_selected_range(0..0, cx);
10912                state.replace_text_in_range(None, "(", window, cx);
10913            });
10914        });
10915        assert_cursors(&mut cx, &view.input, "(|");
10916    }
10917
10918    #[gpui::test]
10919    fn test_skip_records_no_history(cx: &mut TestAppContext) {
10920        let view = InputView::<EditorMode>::new(cx);
10921        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10922        setup_cursors(&mut cx, &view.input, "(a|)");
10923        cx.update(|window, cx| {
10924            view.input.update(cx, |state, cx| {
10925                // Type a normal char (recorded), then skip over `)`.
10926                state.replace_text_in_range(None, "b", window, cx);
10927            });
10928        });
10929        assert_cursors(&mut cx, &view.input, "(ab|)");
10930        cx.update(|window, cx| {
10931            view.input.update(cx, |state, cx| {
10932                state.replace_text_in_range(None, ")", window, cx);
10933            });
10934        });
10935        // Skip is a pure cursor move: no history entry of its own.
10936        assert_cursors(&mut cx, &view.input, "(ab)|");
10937        // Undo removes `b`, never exposing a transient `())`.
10938        cx.update(|window, cx| {
10939            view.input
10940                .update(cx, |state, cx| state.undo(&Undo, window, cx));
10941        });
10942        assert_cursors(&mut cx, &view.input, "(a|)");
10943    }
10944
10945    #[gpui::test]
10946    fn test_pair_insert_undo_removes_both(cx: &mut TestAppContext) {
10947        let view = InputView::<EditorMode>::new(cx);
10948        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10949        setup_cursors(&mut cx, &view.input, "|");
10950        cx.update(|window, cx| {
10951            view.input.update(cx, |state, cx| {
10952                state.replace_text_in_range(None, "(", window, cx);
10953            });
10954        });
10955        assert_cursors(&mut cx, &view.input, "(|)");
10956        // One undo removes the whole pair (typing coalescing).
10957        cx.update(|window, cx| {
10958            view.input
10959                .update(cx, |state, cx| state.undo(&Undo, window, cx));
10960        });
10961        assert_cursors(&mut cx, &view.input, "|");
10962    }
10963
10964    #[gpui::test]
10965    fn test_escaped_quote_does_not_skip(cx: &mut TestAppContext) {
10966        let view = InputView::<EditorMode>::new(cx);
10967        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10968        // Odd backslashes: the typed quote is escaped, insert literally.
10969        setup_cursors(&mut cx, &view.input, "\"hello\\|\"");
10970        cx.update(|window, cx| {
10971            view.input.update(cx, |state, cx| {
10972                state.replace_text_in_range(None, "\"", window, cx);
10973            });
10974        });
10975        assert_cursors(&mut cx, &view.input, "\"hello\\\"|\"");
10976    }
10977
10978    #[gpui::test]
10979    fn test_even_backslashes_still_skip(cx: &mut TestAppContext) {
10980        let view = InputView::<EditorMode>::new(cx);
10981        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10982        // Even backslashes: quote is not escaped, skip over the terminator.
10983        setup_cursors(&mut cx, &view.input, "\"hello\\\\|\"");
10984        cx.update(|window, cx| {
10985            view.input.update(cx, |state, cx| {
10986                state.replace_text_in_range(None, "\"", window, cx);
10987            });
10988        });
10989        assert_cursors(&mut cx, &view.input, "\"hello\\\\\"|");
10990    }
10991
10992    #[gpui::test]
10993    fn test_editor_options_survive_configuration_replacement(cx: &mut TestAppContext) {
10994        use crate::input::{BracketPair, LanguageConfig};
10995
10996        let view = InputView::<EditorMode>::new(cx);
10997        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10998        cx.update(|window, cx| {
10999            view.input.update(cx, |state, cx| {
11000                // Language changes update rules without changing editor preferences.
11001                state.set_smart_indent(false, window, cx);
11002                crate::input::set_language_config(
11003                    state.language_name(),
11004                    LanguageConfig::default().brackets([BracketPair::new("«", "»")]),
11005                    cx,
11006                );
11007            });
11008        });
11009        view.input.read_with(&cx, |state, _| {
11010            assert!(!state.mode.is_smart_indent(), "editor option preserved");
11011            assert!(state.mode.is_auto_close());
11012            let pairs = &state.mode.language_config().unwrap().brackets;
11013            assert_eq!(pairs.len(), 1, "pairs follow configuration");
11014            assert_eq!(pairs[0].open.as_ref(), "«");
11015        });
11016    }
11017
11018    struct CommentAllProvider;
11019    impl crate::input::SyntaxContextProvider for CommentAllProvider {
11020        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
11021            crate::input::SyntaxContext::Comment
11022        }
11023    }
11024
11025    struct StringAllProvider;
11026    impl crate::input::SyntaxContextProvider for StringAllProvider {
11027        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
11028            crate::input::SyntaxContext::String
11029        }
11030    }
11031
11032    #[gpui::test]
11033    fn test_smart_indent_suppressed_in_strings(cx: &mut TestAppContext) {
11034        use std::rc::Rc;
11035
11036        let view = InputView::<EditorMode>::new(cx);
11037        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11038        cx.update(|_window, cx| {
11039            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
11040        });
11041        // Trigger `{` inside a string: no extra level, base indent only.
11042        setup_cursors(&mut cx, &view.input, "  x = {|");
11043        cx.update(|window, cx| {
11044            view.input.update(cx, |state, cx| {
11045                state.enter(
11046                    &Enter {
11047                        secondary: false,
11048                        shift: false,
11049                    },
11050                    window,
11051                    cx,
11052                );
11053            });
11054        });
11055        assert_cursors(&mut cx, &view.input, "  x = {\n  |");
11056    }
11057
11058    #[gpui::test]
11059    fn test_split_suppressed_in_strings(cx: &mut TestAppContext) {
11060        use std::rc::Rc;
11061
11062        let view = InputView::<EditorMode>::new(cx);
11063        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11064        cx.update(|_window, cx| {
11065            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
11066        });
11067        // No three-way split inside strings: plain newline instead.
11068        setup_cursors(&mut cx, &view.input, "{|}");
11069        cx.update(|window, cx| {
11070            view.input.update(cx, |state, cx| {
11071                state.enter(
11072                    &Enter {
11073                        secondary: false,
11074                        shift: false,
11075                    },
11076                    window,
11077                    cx,
11078                );
11079            });
11080        });
11081        assert_cursors(&mut cx, &view.input, "{\n|}");
11082    }
11083
11084    #[gpui::test]
11085    fn test_comment_context_disables_pairing(cx: &mut TestAppContext) {
11086        use std::rc::Rc;
11087
11088        let view = InputView::<EditorMode>::new(cx);
11089        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11090        cx.update(|_window, cx| {
11091            set_test_syntax_provider(Rc::new(CommentAllProvider), cx);
11092        });
11093        setup_cursors(&mut cx, &view.input, "|");
11094        cx.update(|window, cx| {
11095            view.input.update(cx, |state, cx| {
11096                state.replace_text_in_range(None, "(", window, cx);
11097            });
11098        });
11099        // In comments every character is literal.
11100        assert_cursors(&mut cx, &view.input, "(|");
11101    }
11102
11103    #[gpui::test]
11104    fn test_backspace_deletes_empty_pair(cx: &mut TestAppContext) {
11105        let view = InputView::<EditorMode>::new(cx);
11106        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11107        setup_cursors(&mut cx, &view.input, "{|}");
11108        cx.update(|window, cx| {
11109            view.input
11110                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
11111        });
11112        assert_cursors(&mut cx, &view.input, "|");
11113    }
11114
11115    #[gpui::test]
11116    fn test_backspace_keeps_closer_with_content(cx: &mut TestAppContext) {
11117        let view = InputView::<EditorMode>::new(cx);
11118        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11119        setup_cursors(&mut cx, &view.input, "{d|}");
11120        cx.update(|window, cx| {
11121            view.input
11122                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
11123        });
11124        // Only `d` is deleted, the closing brace stays.
11125        assert_cursors(&mut cx, &view.input, "{|}");
11126    }
11127
11128    #[gpui::test]
11129    fn test_enter_splits_brackets(cx: &mut TestAppContext) {
11130        let view = InputView::<EditorMode>::new(cx);
11131        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11132        setup_cursors(&mut cx, &view.input, "{|}");
11133        cx.update(|window, cx| {
11134            view.input.update(cx, |state, cx| {
11135                state.enter(
11136                    &Enter {
11137                        secondary: false,
11138                        shift: false,
11139                    },
11140                    window,
11141                    cx,
11142                );
11143            });
11144        });
11145        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
11146        // Selection must be collapsed: typing next must insert, not replace.
11147        view.input.read_with(&cx, |state, _| {
11148            assert!(state.active_selection().is_empty());
11149        });
11150        cx.update(|window, cx| {
11151            view.input.update(cx, |state, cx| {
11152                state.replace_text_in_range(None, "x", window, cx);
11153            });
11154        });
11155        assert_cursors(&mut cx, &view.input, "{\n  x|\n}");
11156    }
11157
11158    #[gpui::test]
11159    fn test_enter_smart_indent_after_brace(cx: &mut TestAppContext) {
11160        let view = InputView::<EditorMode>::new(cx);
11161        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11162        setup_cursors(&mut cx, &view.input, "fn main() {|");
11163        cx.update(|window, cx| {
11164            view.input.update(cx, |state, cx| {
11165                state.enter(
11166                    &Enter {
11167                        secondary: false,
11168                        shift: false,
11169                    },
11170                    window,
11171                    cx,
11172                );
11173            });
11174        });
11175        assert_cursors(&mut cx, &view.input, "fn main() {\n  |");
11176    }
11177
11178    #[gpui::test]
11179    fn test_block_indent_tracks_all_preceding_edits(cx: &mut TestAppContext) {
11180        let view = multi_line(cx);
11181        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11182        setup_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
11183        cx.update(|window, cx| {
11184            view.input
11185                .update(cx, |state, cx| state.indent(true, window, cx));
11186        });
11187        assert_cursors(&mut cx, &view.input, "  |ab\n  |cd\n  |ef");
11188        cx.update(|window, cx| {
11189            view.input
11190                .update(cx, |state, cx| state.outdent(true, window, cx));
11191        });
11192        assert_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
11193    }
11194
11195    #[gpui::test]
11196    fn test_block_outdent_clamps_cursor_inside_indent(cx: &mut TestAppContext) {
11197        let view = multi_line(cx);
11198        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11199        setup_cursors(&mut cx, &view.input, "ab\n | cd");
11200        cx.update(|window, cx| {
11201            view.input
11202                .update(cx, |state, cx| state.outdent(true, window, cx));
11203        });
11204        assert_cursors(&mut cx, &view.input, "ab\n|cd");
11205    }
11206
11207    #[gpui::test]
11208    fn test_ime_restores_original_selection(cx: &mut TestAppContext) {
11209        let view = InputView::build(cx, |state| state);
11210        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11211        cx.update(|window, cx| {
11212            view.input.update(cx, |state, cx| {
11213                state.set_value("abc", window, cx);
11214                state.set_selected_range(1..2, cx);
11215                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
11216                state.replace_text_in_range(None, "你", window, cx);
11217                state.undo(&Undo, window, cx);
11218                assert_eq!(state.value(), "abc");
11219                assert_eq!(state.selected_range(), 1..2);
11220                state.redo(&Redo, window, cx);
11221                assert_eq!(state.selected_range(), 4..4);
11222            });
11223        });
11224    }
11225
11226    #[gpui::test]
11227    fn test_noop_does_not_change_redo_selection(cx: &mut TestAppContext) {
11228        let view = multi_line(cx);
11229        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11230        cx.update(|window, cx| {
11231            view.input.update(cx, |state, cx| {
11232                state.set_value("abc", window, cx);
11233                state.set_selected_range(1..1, cx);
11234                state.replace_text_in_range(None, "X", window, cx);
11235                state.set_selected_range(0..0, cx);
11236                state.replace_text_in_range(None, "", window, cx);
11237                state.undo(&Undo, window, cx);
11238                state.redo(&Redo, window, cx);
11239                assert_eq!(state.value(), "aXbc");
11240                assert_eq!(state.selected_range(), 2..2);
11241            });
11242        });
11243    }
11244
11245    #[gpui::test]
11246    fn test_multi_cursor_insert_text(cx: &mut TestAppContext) {
11247        let view = multi_line(cx);
11248        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11249        let input = view.input;
11250
11251        setup_cursors(&mut cx, &input, "|hello |world|");
11252        cx.update(|window, cx| {
11253            input.update(cx, |state, cx| {
11254                state.replace_text_in_range(None, ">>>", window, cx);
11255            });
11256        });
11257        assert_cursors(&mut cx, &input, ">>>|hello >>>|world>>>|");
11258    }
11259
11260    #[gpui::test]
11261    fn test_multi_cursor_delete_backward(cx: &mut TestAppContext) {
11262        let view = multi_line(cx);
11263        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11264        let input = view.input;
11265
11266        setup_cursors(&mut cx, &input, "|islands| cars|");
11267        cx.update(|window, cx| {
11268            input.update(cx, |state, cx| {
11269                state.backspace(&Backspace, window, cx);
11270            });
11271        });
11272        // The first cursor has nothing to delete. The others delete an `s`.
11273        assert_cursors(&mut cx, &input, "|island| car|");
11274    }
11275
11276    #[gpui::test]
11277    fn test_multi_cursor_delete_forward_merges(cx: &mut TestAppContext) {
11278        let view = multi_line(cx);
11279        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11280        let input = view.input;
11281
11282        setup_cursors(&mut cx, &input, "hello| |world");
11283        cx.update(|window, cx| {
11284            input.update(cx, |state, cx| {
11285                state.delete(&Delete, window, cx);
11286            });
11287        });
11288        // Adjacent deletions merge into a single cursor.
11289        assert_cursors(&mut cx, &input, "hello|orld");
11290    }
11291
11292    #[gpui::test]
11293    fn test_multi_cursor_multiline_insert_and_delete(cx: &mut TestAppContext) {
11294        let view = multi_line(cx);
11295        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11296        let input = view.input;
11297
11298        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11299        cx.update(|window, cx| {
11300            input.update(cx, |state, cx| {
11301                state.replace_text_in_range(None, "a", window, cx);
11302            });
11303        });
11304        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11305
11306        // The whole multi-edit insert is a single undo transaction.
11307        input.read_with(&cx, |state, _| {
11308            assert_eq!(state.undo_manager.undo_count(), 1);
11309        });
11310
11311        cx.update(|window, cx| {
11312            input.update(cx, |state, cx| {
11313                state.backspace(&Backspace, window, cx);
11314            });
11315        });
11316        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11317    }
11318
11319    #[gpui::test]
11320    fn test_add_cursor_below_preserves_column(cx: &mut TestAppContext) {
11321        let view = multi_line(cx);
11322        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11323        let input = view.input;
11324
11325        setup_cursors(&mut cx, &input, "ab|cd\nabcd");
11326        cx.update(|window, cx| {
11327            input.update(cx, |state, cx| {
11328                state.add_cursor_below(&AddCursorBelow, window, cx);
11329            });
11330        });
11331        assert_cursors(&mut cx, &input, "ab|cd\nab|cd");
11332    }
11333
11334    #[gpui::test]
11335    fn test_add_cursor_at_rejects_duplicates(cx: &mut TestAppContext) {
11336        let view = multi_line(cx);
11337        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11338        let input = view.input;
11339
11340        setup_cursors(&mut cx, &input, "he|llo");
11341        cx.update(|_, cx| {
11342            input.update(cx, |state, cx| {
11343                // Duplicate of the existing cursor is rejected.
11344                state.add_cursor_at(2, cx);
11345                assert_eq!(state.selections.len(), 1);
11346                // A distinct offset adds a cursor.
11347                state.add_cursor_at(4, cx);
11348                assert_eq!(state.selections.len(), 2);
11349            });
11350        });
11351    }
11352
11353    #[gpui::test]
11354    fn test_multi_cursor_undo_redo_restores_selections(cx: &mut TestAppContext) {
11355        let view = multi_line(cx);
11356        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11357        let input = view.input;
11358
11359        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11360        cx.update(|window, cx| {
11361            input.update(cx, |state, cx| {
11362                state.replace_text_in_range(None, "a", window, cx);
11363            });
11364        });
11365        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11366
11367        cx.update(|window, cx| {
11368            input.update(cx, |state, cx| {
11369                state.undo(&Undo, window, cx);
11370            });
11371        });
11372        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11373
11374        cx.update(|window, cx| {
11375            input.update(cx, |state, cx| {
11376                state.redo(&Redo, window, cx);
11377            });
11378        });
11379        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11380    }
11381
11382    #[gpui::test]
11383    fn test_multi_cursor_undo_redo_different_line_lengths(cx: &mut TestAppContext) {
11384        let view = multi_line(cx);
11385        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11386        let input = view.input;
11387
11388        setup_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
11389        cx.update(|window, cx| {
11390            input.update(cx, |state, cx| {
11391                state.replace_text_in_range(None, "a", window, cx);
11392            });
11393        });
11394        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
11395
11396        cx.update(|window, cx| {
11397            input.update(cx, |state, cx| {
11398                state.undo(&Undo, window, cx);
11399            });
11400        });
11401        assert_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
11402
11403        cx.update(|window, cx| {
11404            input.update(cx, |state, cx| {
11405                state.redo(&Redo, window, cx);
11406            });
11407        });
11408        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
11409    }
11410
11411    #[gpui::test]
11412    fn test_multi_cursor_undo_multiple_inserts(cx: &mut TestAppContext) {
11413        let view = multi_line(cx);
11414        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11415        let input = view.input;
11416
11417        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11418        for ch in ['a', 'b', 'c'] {
11419            cx.update(|window, cx| {
11420                input.update(cx, |state, cx| {
11421                    state.replace_text_in_range(None, &ch.to_string(), window, cx);
11422                });
11423            });
11424        }
11425        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
11426
11427        // The repeated keystrokes form one typing gesture.
11428        cx.update(|window, cx| {
11429            input.update(cx, |state, cx| {
11430                assert_eq!(state.undo_manager.undo_count(), 1);
11431                state.undo(&Undo, window, cx);
11432            });
11433        });
11434        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11435
11436        cx.update(|window, cx| {
11437            input.update(cx, |state, cx| {
11438                state.redo(&Redo, window, cx);
11439            });
11440        });
11441        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
11442    }
11443
11444    #[gpui::test]
11445    fn test_multi_cursor_backspace_run_is_one_undo(cx: &mut TestAppContext) {
11446        let view = multi_line(cx);
11447        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11448        let input = view.input;
11449
11450        setup_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
11451        for _ in 0..3 {
11452            cx.update(|window, cx| {
11453                input.update(cx, |state, cx| {
11454                    state.backspace(&Backspace, window, cx);
11455                });
11456            });
11457        }
11458        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11459
11460        cx.update(|window, cx| {
11461            input.update(cx, |state, cx| {
11462                assert_eq!(state.undo_manager.undo_count(), 1);
11463                state.undo(&Undo, window, cx);
11464            });
11465        });
11466        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
11467    }
11468
11469    #[gpui::test]
11470    fn test_adding_a_cursor_splits_the_typing_gesture(cx: &mut TestAppContext) {
11471        let view = multi_line(cx);
11472        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11473        let input = view.input;
11474
11475        setup_cursors(&mut cx, &input, "|1\n2\n3");
11476        cx.update(|window, cx| {
11477            input.update(cx, |state, cx| {
11478                state.replace_text_in_range(None, "a", window, cx);
11479                state.add_cursor_below(&AddCursorBelow, window, cx);
11480                state.replace_text_in_range(None, "b", window, cx);
11481            });
11482        });
11483        assert_cursors(&mut cx, &input, "ab|1\n2b|\n3");
11484
11485        // The keystroke after the cursor was added is its own undo entry.
11486        cx.update(|window, cx| {
11487            input.update(cx, |state, cx| {
11488                state.undo(&Undo, window, cx);
11489            });
11490        });
11491        assert_cursors(&mut cx, &input, "a|1\n2|\n3");
11492    }
11493
11494    #[gpui::test]
11495    fn test_multi_cursor_indent_is_one_undo(cx: &mut TestAppContext) {
11496        let view = multi_line(cx);
11497        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11498        let input = view.input;
11499
11500        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11501        cx.update(|window, cx| {
11502            input.update(cx, |state, cx| {
11503                state.indent_inline(&IndentInline, window, cx);
11504                assert_eq!(state.undo_manager.undo_count(), 1);
11505                state.undo(&Undo, window, cx);
11506            });
11507        });
11508        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11509    }
11510
11511    #[gpui::test]
11512    fn test_multi_cursor_indent_then_outdent_roundtrips(cx: &mut TestAppContext) {
11513        let view = multi_line(cx);
11514        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11515        let input = view.input;
11516
11517        // Cursors at line starts.
11518        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11519        cx.update(|window, cx| {
11520            input.update(cx, |state, cx| {
11521                state.indent(false, window, cx);
11522            });
11523        });
11524        assert_cursors(&mut cx, &input, "  |1\n  |2\n  |3");
11525
11526        cx.update(|window, cx| {
11527            input.update(cx, |state, cx| {
11528                state.outdent(false, window, cx);
11529            });
11530        });
11531        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11532    }
11533
11534    #[gpui::test]
11535    fn test_inline_outdent_only_removes_line_indentation(cx: &mut TestAppContext) {
11536        let view = multi_line(cx);
11537        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11538        let input = view.input;
11539
11540        // A mid-line indent lands at the cursor, and the outdent does not
11541        // take it back: it only ever removes leading line indentation.
11542        setup_cursors(&mut cx, &input, "1|2\n1|2");
11543        cx.update(|window, cx| {
11544            input.update(cx, |state, cx| {
11545                state.indent(false, window, cx);
11546            });
11547        });
11548        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
11549
11550        cx.update(|window, cx| {
11551            input.update(cx, |state, cx| {
11552                state.outdent(false, window, cx);
11553            });
11554        });
11555        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
11556
11557        // A line with leading indentation loses that, wherever the cursor is.
11558        setup_cursors(&mut cx, &input, "  1|2\n  1|2");
11559        cx.update(|window, cx| {
11560            input.update(cx, |state, cx| {
11561                state.outdent(false, window, cx);
11562            });
11563        });
11564        assert_cursors(&mut cx, &input, "1|2\n1|2");
11565    }
11566
11567    #[gpui::test]
11568    fn test_readonly_multi_cursor_commands_leave_state_unchanged(cx: &mut TestAppContext) {
11569        let view = multi_line(cx);
11570        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11571        let input = view.input;
11572
11573        setup_cursors(&mut cx, &input, "a|b\nc|d");
11574        cx.update(|window, cx| {
11575            input.update(cx, |state, cx| {
11576                let before: Vec<_> = state.selections.iter().copied().collect();
11577                state.set_readonly(true, cx);
11578                state.backspace(&Backspace, window, cx);
11579                state.indent_inline(&IndentInline, window, cx);
11580
11581                assert_eq!(state.value(), "ab\ncd\n");
11582                assert_eq!(state.selections.iter().copied().collect::<Vec<_>>(), before);
11583            });
11584        });
11585    }
11586
11587    #[gpui::test]
11588    fn test_multi_cursor_edit_preserves_the_active_cursor(cx: &mut TestAppContext) {
11589        let view = multi_line(cx);
11590        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11591        let input = view.input;
11592
11593        setup_cursors(&mut cx, &input, "x|\n|y");
11594        cx.update(|window, cx| {
11595            input.update(cx, |state, cx| {
11596                let mut selections: Vec<_> = state.selections.iter().copied().collect();
11597                selections.swap(0, 1);
11598                state.selections.replace_all(selections);
11599                let active_id = state.active_selection().id;
11600                state.replace_text_in_range(None, "!", window, cx);
11601                assert_eq!(state.active_selection().id, active_id);
11602            });
11603        });
11604    }
11605
11606    #[gpui::test]
11607    fn test_block_indent_outdent_with_selection(cx: &mut TestAppContext) {
11608        let view = multi_line(cx);
11609        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11610        let input = view.input;
11611
11612        cx.update(|window, cx| {
11613            input.update(cx, |state, cx| {
11614                state.set_value("line1\nline2\nline3", window, cx);
11615                let id = state.selections.generate_id();
11616                state
11617                    .selections
11618                    .replace_all(vec![CursorSelection::new(id, 0, 17)]);
11619                cx.notify();
11620            });
11621        });
11622
11623        cx.update(|window, cx| {
11624            input.update(cx, |state, cx| {
11625                state.indent(true, window, cx);
11626            });
11627        });
11628        input.read_with(&cx, |state, _| {
11629            assert_eq!(state.text.to_string(), "  line1\n  line2\n  line3");
11630        });
11631
11632        cx.update(|window, cx| {
11633            input.update(cx, |state, cx| {
11634                state.outdent(true, window, cx);
11635            });
11636        });
11637        input.read_with(&cx, |state, _| {
11638            assert_eq!(state.text.to_string(), "line1\nline2\nline3");
11639        });
11640    }
11641
11642    #[gpui::test]
11643    fn test_multi_cursor_word_movement(cx: &mut TestAppContext) {
11644        let view = multi_line(cx);
11645        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11646        let input = view.input;
11647
11648        setup_cursors(
11649            &mut cx,
11650            &input,
11651            "on|e two three\none t|wo three\non|e two three",
11652        );
11653
11654        cx.update(|window, cx| {
11655            input.update(cx, |state, cx| {
11656                state.move_to_next_word(&MoveToNextWord, window, cx);
11657            });
11658        });
11659        assert_cursors(
11660            &mut cx,
11661            &input,
11662            "one| two three\none two| three\none| two three",
11663        );
11664
11665        cx.update(|window, cx| {
11666            input.update(cx, |state, cx| {
11667                state.move_to_previous_word(&MoveToPreviousWord, window, cx);
11668            });
11669        });
11670        assert_cursors(
11671            &mut cx,
11672            &input,
11673            "|one two three\none |two three\n|one two three",
11674        );
11675
11676        // Move to end/start of document collapses to a single cursor.
11677        cx.update(|window, cx| {
11678            input.update(cx, |state, cx| {
11679                state.move_to_end(&MoveToEnd, window, cx);
11680            });
11681        });
11682        input.read_with(&cx, |state, _| {
11683            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
11684            assert_eq!(cursors, vec![state.text.len()]);
11685        });
11686
11687        cx.update(|window, cx| {
11688            input.update(cx, |state, cx| {
11689                state.move_to_start(&MoveToStart, window, cx);
11690            });
11691        });
11692        input.read_with(&cx, |state, _| {
11693            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
11694            assert_eq!(cursors, vec![0]);
11695        });
11696    }
11697
11698    #[gpui::test]
11699    fn test_multi_cursor_selection_commands(cx: &mut TestAppContext) {
11700        let view = multi_line(cx);
11701        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11702        let input = view.input;
11703
11704        setup_cursors(
11705            &mut cx,
11706            &input,
11707            "on|e two three\none t|wo three\non|e two three",
11708        );
11709        cx.update(|window, cx| {
11710            input.update(cx, |state, cx| {
11711                state.select_to_start_of_line(&SelectToStartOfLine, window, cx);
11712            });
11713        });
11714        assert_cursors(
11715            &mut cx,
11716            &input,
11717            "|one two three\n|one two three\n|one two three",
11718        );
11719
11720        // Select to document start collapses to the active cursor only.
11721        setup_cursors(
11722            &mut cx,
11723            &input,
11724            "on|e two three\none t|wo three\non|e two three",
11725        );
11726        cx.update(|window, cx| {
11727            input.update(cx, |state, cx| {
11728                state.select_to_start(&SelectToStart, window, cx);
11729            });
11730        });
11731        input.read_with(&cx, |state, _| {
11732            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
11733            assert_eq!(cursors, vec![0]);
11734        });
11735    }
11736
11737    #[gpui::test]
11738    fn test_multi_cursor_replace_selection(cx: &mut TestAppContext) {
11739        let view = multi_line(cx);
11740        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11741        let input = view.input;
11742
11743        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
11744        cx.update(|window, cx| {
11745            input.update(cx, |state, cx| {
11746                state.select_right(&SelectRight, window, cx);
11747            });
11748        });
11749        input.read_with(&cx, |state, _| {
11750            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
11751            assert_eq!(ranges, vec![(0, 1), (2, 3), (4, 5)]);
11752        });
11753
11754        cx.update(|window, cx| {
11755            input.update(cx, |state, cx| {
11756                state.replace_text_in_range(None, "x", window, cx);
11757            });
11758        });
11759        assert_cursors(&mut cx, &input, "x|\nx|\nx|");
11760    }
11761
11762    #[gpui::test]
11763    fn test_multi_cursor_escape_collapses(cx: &mut TestAppContext) {
11764        let view = multi_line(cx);
11765        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11766        let input = view.input;
11767
11768        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
11769        cx.update(|window, cx| {
11770            input.update(cx, |state, cx| {
11771                state.escape(&Escape, window, cx);
11772            });
11773        });
11774        input.read_with(&cx, |state, _| {
11775            assert_eq!(state.selections.len(), 1);
11776        });
11777    }
11778
11779    #[gpui::test]
11780    fn test_build_columnar_selection(cx: &mut TestAppContext) {
11781        let view = multi_line(cx);
11782        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11783        let input = view.input;
11784
11785        setup_cursors(&mut cx, &input, "abcd\nabcd\nabcd");
11786        cx.update(|_, cx| {
11787            input.update(cx, |state, cx| {
11788                // From row 0 col 1 to row 2 col 3.
11789                state.build_columnar_selection(
11790                    ColumnarPoint::new(1, 0),
11791                    ColumnarPoint::new(13, 0),
11792                    cx,
11793                );
11794                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
11795                assert_eq!(ranges, vec![(1, 3), (6, 8), (11, 13)]);
11796            });
11797        });
11798    }
11799
11800    #[gpui::test]
11801    fn test_columnar_selection_keeps_width_over_short_rows(cx: &mut TestAppContext) {
11802        let view = multi_line(cx);
11803        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11804        let input = view.input;
11805
11806        setup_cursors(&mut cx, &input, "abcdef\nab\nabcdef");
11807        cx.update(|_, cx| {
11808            input.update(cx, |state, cx| {
11809                // Ends on the short row, 3 columns past its end: the block still spans
11810                // columns 1..5, and only that row is clipped to what it has.
11811                state.build_columnar_selection(
11812                    ColumnarPoint::new(1, 0),
11813                    ColumnarPoint::new(9, 3),
11814                    cx,
11815                );
11816                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
11817                assert_eq!(ranges, vec![(1, 5), (8, 9)]);
11818            });
11819        });
11820    }
11821
11822    #[gpui::test]
11823    fn test_multi_cursor_paste_distributes_lines(cx: &mut TestAppContext) {
11824        let view = multi_line(cx);
11825        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11826        let input = view.input;
11827
11828        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11829        cx.update(|_, cx| {
11830            cx.write_to_clipboard(ClipboardItem::new_string("x\ny\nz".to_string()));
11831        });
11832        cx.update(|window, cx| {
11833            input.update(cx, |state, cx| {
11834                state.paste(&Paste, window, cx);
11835            });
11836        });
11837        // One clipboard line per cursor.
11838        assert_cursors(&mut cx, &input, "x|1\ny|2\nz|3");
11839    }
11840
11841    #[gpui::test]
11842    fn test_paste_without_text_leaves_the_selection_alone(cx: &mut TestAppContext) {
11843        use gpui::{ClipboardEntry, Image, ImageFormat};
11844
11845        let input_view = InputView::build(cx, |state| state);
11846        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
11847        let input = input_view.input;
11848
11849        cx.update(|window, cx| {
11850            input.update(cx, |state, cx| {
11851                state.set_value("hello world", window, cx);
11852                state.select_all(window, cx);
11853                cx.write_to_clipboard(ClipboardItem {
11854                    entries: vec![ClipboardEntry::Image(Image::from_bytes(
11855                        ImageFormat::Png,
11856                        vec![0x89, b'P', b'N', b'G'],
11857                    ))],
11858                });
11859
11860                // An image-only clipboard must not replace the selection
11861                // with nothing.
11862                state.paste(&Paste, window, cx);
11863                assert_eq!(state.value(), "hello world");
11864                assert_eq!(state.selected_range(), 0..11);
11865            });
11866        });
11867    }
11868
11869    #[gpui::test]
11870    fn test_paste_target_tracks_edits_and_selections(cx: &mut TestAppContext) {
11871        let view = multi_line(cx);
11872        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11873        let input = view.input;
11874
11875        setup_cursors(&mut cx, &input, "ab|c");
11876        let target = input.read_with(&cx, |state, _| state.paste_target());
11877        // Nothing happened: a paste asked for then still applies.
11878        assert_eq!(
11879            input.read_with(&cx, |state, _| state.paste_target()),
11880            target
11881        );
11882
11883        // Moving the caret changes where the paste would go.
11884        cx.update(|_, cx| {
11885            input.update(cx, |state, cx| {
11886                let id = state.selections.generate_id();
11887                state
11888                    .selections
11889                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
11890                cx.notify();
11891            });
11892        });
11893        let moved = input.read_with(&cx, |state, _| state.paste_target());
11894        assert_ne!(moved, target);
11895
11896        // Editing the text changes it too, even with the caret put back.
11897        cx.update(|window, cx| {
11898            input.update(cx, |state, cx| {
11899                state.replace_text_in_range_silent(None, "x", window, cx);
11900                let id = state.selections.generate_id();
11901                state
11902                    .selections
11903                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
11904            });
11905        });
11906        assert_ne!(input.read_with(&cx, |state, _| state.paste_target()), moved);
11907    }
11908
11909    #[gpui::test]
11910    fn test_ime_candidate_bounds_fallback_before_repaint(cx: &mut TestAppContext) {
11911        let input_view = InputView::build_textarea(cx, |state| {
11912            state.default_value("这是一段已经输入的中文文字")
11913        });
11914        let mut visual = VisualTestContext::from_window(input_view.window_handle.into(), cx);
11915        visual.update(|window, cx| window.draw(cx).clear(cx));
11916
11917        visual.update(|window, cx| {
11918            input_view.input.update(cx, |state, cx| {
11919                state.focus(window, cx);
11920                let len = state.text.len();
11921                state.set_cursor_to(len);
11922            });
11923        });
11924        visual.update(|window, cx| window.draw(cx).clear(cx));
11925
11926        visual.update(|window, cx| {
11927            input_view.input.update(cx, |state, cx| {
11928                let bounds = state.text_bounds().unwrap_or_default();
11929                let before = state.selected_text_range(false, window, cx).unwrap().range;
11930                let previous = state
11931                    .bounds_for_range(before.end..before.end, bounds, window, cx)
11932                    .unwrap();
11933
11934                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
11935
11936                let after = state.selected_text_range(false, window, cx).unwrap().range;
11937                let pending = state
11938                    .bounds_for_range(after.end..after.end, bounds, window, cx)
11939                    .unwrap();
11940
11941                assert!(
11942                    (pending.origin.x - previous.origin.x).abs() < px(40.0),
11943                    "pending origin.x {:?} must remain close to previous origin.x {:?}",
11944                    pending.origin.x,
11945                    previous.origin.x
11946                );
11947
11948                let range_bounds = state
11949                    .bounds_for_range(before.end..after.end, bounds, window, cx)
11950                    .unwrap();
11951                assert!(
11952                    (range_bounds.origin.x - previous.origin.x).abs() < px(40.0),
11953                    "range_bounds origin.x {:?} must remain close to previous origin.x {:?}",
11954                    range_bounds.origin.x,
11955                    previous.origin.x
11956                );
11957                assert!(range_bounds.size.width >= px(0.0));
11958            });
11959        });
11960    }
11961}
11962
11963/// Methods that only a single-line input offers.
11964impl InputBaseState<crate::input::InputMode> {
11965    /// Create a single-line text input state.
11966    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
11967        Self::new_in_mode(window, cx)
11968    }
11969
11970    /// Set a custom step function of the [`super::NumberInput`].
11971    ///
11972    /// The `f` receives the current value and the [`StepAction`], and returns
11973    /// the step to apply, so the step can vary with the value.
11974    ///
11975    /// # Example
11976    ///
11977    /// ```ignore
11978    /// // At the boundary 1.0 the step is 0.1 going down and 0.5 going up.
11979    /// InputState::new(window, cx).step_by(|value, action, _cx| match action {
11980    ///     StepAction::Increment => if value < 1.0 { 0.1 } else { 0.5 },
11981    ///     StepAction::Decrement => if value <= 1.0 { 0.1 } else { 0.5 },
11982    /// })
11983    /// ```
11984    pub fn step_by(mut self, f: impl Fn(f64, StepAction, &mut App) -> f64 + 'static) -> Self {
11985        self.number_step = Some(NumberStep::by_value(f));
11986        self
11987    }
11988
11989    /// Set with password masked state.
11990    pub fn masked(mut self, masked: bool) -> Self {
11991        self.masked = masked;
11992        self
11993    }
11994
11995    /// Set the password masked state of the input field.
11996    pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
11997        self.masked = masked;
11998        cx.notify();
11999    }
12000
12001    /// Set the regular expression pattern of the input field.
12002    pub fn pattern(mut self, pattern: regex::Regex) -> Self {
12003        self.pattern = Some(pattern);
12004        self
12005    }
12006
12007    /// Set the regular expression pattern of the input field with reference.
12008    pub fn set_pattern(
12009        &mut self,
12010        pattern: regex::Regex,
12011        _window: &mut Window,
12012        _cx: &mut Context<Self>,
12013    ) {
12014        self.pattern = Some(pattern);
12015    }
12016
12017    /// Set the validation function of the input field.
12018    pub fn validate(mut self, f: impl Fn(&str, &mut App) -> bool + 'static) -> Self {
12019        self.validate = Some(Box::new(f));
12020        self
12021    }
12022
12023    pub fn set_validator(
12024        &mut self,
12025        validate: impl Fn(&str, &mut App) -> bool + 'static,
12026        _cx: &mut Context<Self>,
12027    ) {
12028        self.validate = Some(Box::new(validate));
12029    }
12030
12031    /// Set the step value of the [`super::NumberInput`] for increment/decrement.
12032    ///
12033    /// If any of `step`, `min`, `max` is set, the [`super::NumberInput`] will
12034    /// update the value internally (step by `step`, default 1, clamp to the
12035    /// `min`/`max` range and emit [`InputEvent::Change`]) instead of emitting
12036    /// [`super::NumberInputEvent::Step`].
12037    ///
12038    /// See also [`Self::step_by`] to calculate the step value
12039    /// based on the current value.
12040    pub fn step(mut self, step: impl Into<NumberStep>) -> Self {
12041        self.number_step = Some(step.into());
12042        self
12043    }
12044
12045    /// Set the minimum value of the [`super::NumberInput`].
12046    ///
12047    /// The value will be clamped to the minimum value on stepping and on
12048    /// blur (only if the clamped value passes the `pattern`/`validate` check).
12049    /// See also [`Self::step`].
12050    pub fn min(mut self, min: f64) -> Self {
12051        self.number_min = Some(min);
12052        self
12053    }
12054
12055    /// Set the maximum value of the [`super::NumberInput`].
12056    ///
12057    /// The value will be clamped to the maximum value on stepping and on
12058    /// blur (only if the clamped value passes the `pattern`/`validate` check).
12059    /// See also [`Self::step`].
12060    pub fn max(mut self, max: f64) -> Self {
12061        self.number_max = Some(max);
12062        self
12063    }
12064
12065    /// Update the step value after construction, `None` to fall back to
12066    /// emitting [`super::NumberInputEvent::Step`] (if `min`, `max` are unset).
12067    ///
12068    /// See [`Self::step`] and [`Self::step_by`].
12069    pub fn set_step(
12070        &mut self,
12071        step: impl Into<Option<NumberStep>>,
12072        _: &mut Window,
12073        _: &mut Context<Self>,
12074    ) {
12075        self.number_step = step.into();
12076    }
12077
12078    /// Update the minimum value after construction. See [`Self::min`].
12079    pub fn set_min(&mut self, min: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
12080        self.number_min = min;
12081    }
12082
12083    /// Update the maximum value after construction. See [`Self::max`].
12084    pub fn set_max(&mut self, max: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
12085        self.number_max = max;
12086    }
12087
12088    /// Set true to show spinner at the input right.
12089    pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
12090        self.loading = loading;
12091        cx.notify();
12092    }
12093}
12094
12095/// Methods shared by the two multi-line modes, and reachable on neither a
12096/// single-line input nor anything else.
12097impl<M: crate::input::MultiLineMode> InputBaseState<M> {
12098    /// Set whether text taller than the input stretches past its top and
12099    /// bottom and springs back when precise trackpad or Magic Mouse scrolling
12100    /// or its momentum runs into an end. Ordinary line-wheel scrolling does
12101    /// not bounce. The default follows
12102    /// [`crate::ScrollBounce::enabled_by_default`]: on on iOS, Android and
12103    /// macOS, off elsewhere. Under reduced motion the text stops at its ends
12104    /// without stretching.
12105    pub fn scroll_bounce(mut self, scroll_bounce: bool) -> Self {
12106        self.scroll_bounce = scroll_bounce;
12107        self
12108    }
12109
12110    /// Update whether the text stretches past its ends, see
12111    /// [`InputBaseState::scroll_bounce`]. Turning it off drops a stretch in
12112    /// progress.
12113    pub fn set_scroll_bounce(&mut self, scroll_bounce: bool, cx: &mut Context<Self>) {
12114        if self.scroll_bounce != scroll_bounce {
12115            self.scroll_bounce = scroll_bounce;
12116            self.overscroll.reset();
12117            cx.notify();
12118        }
12119    }
12120
12121    /// Whether the built-in search panel and its shortcut are enabled. Off by
12122    /// default, on for the code editor.
12123    ///
12124    /// This only concerns the panel. An input that is not searchable still
12125    /// answers [`InputBaseState::set_search_query`] and the other search
12126    /// methods, and lets `Ctrl-F` / `Cmd-F` bubble up to its ancestors, so an
12127    /// application can put its own search UI on top of the same engine.
12128    pub fn searchable(mut self, searchable: bool) -> Self {
12129        self.searchable = searchable;
12130        self
12131    }
12132
12133    /// See [`InputBaseState::searchable`].
12134    pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
12135        self.searchable = searchable;
12136        cx.notify();
12137    }
12138
12139    /// Set the soft wrap mode, default is true.
12140    #[doc(hidden)]
12141    pub fn soft_wrap(mut self, wrap: bool) -> Self {
12142        self.soft_wrap = wrap;
12143        self
12144    }
12145
12146    /// Update the soft wrap mode, default is true.
12147    pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
12148        self.soft_wrap = wrap;
12149        if wrap {
12150            let wrap_width = self
12151                .last_layout
12152                .as_ref()
12153                .and_then(|b| b.wrap_width)
12154                .unwrap_or(self.input_bounds.size.width);
12155
12156            self.display_map.on_layout_changed(Some(wrap_width), cx);
12157
12158            // Reset scroll to left 0
12159            let mut offset = self.scroll_handle.offset();
12160            offset.x = px(0.);
12161            self.scroll_handle.set_offset(offset);
12162        } else {
12163            self.display_map.on_layout_changed(None, cx);
12164        }
12165        cx.notify();
12166    }
12167
12168    /// Set how soft-wrapped continuation lines are indented, default is [`WrappingIndent::Same`]
12169    #[doc(hidden)]
12170    pub fn wrapping_indent(mut self, wrapping_indent: WrappingIndent) -> Self {
12171        self.wrapping_indent = wrapping_indent;
12172        self
12173    }
12174
12175    /// Update how soft-wrapped continuation lines are indented.
12176    pub fn set_wrapping_indent(
12177        &mut self,
12178        wrapping_indent: WrappingIndent,
12179        _: &mut Window,
12180        cx: &mut Context<Self>,
12181    ) {
12182        self.wrapping_indent = wrapping_indent;
12183        self.display_map.set_wrapping_indent(wrapping_indent, cx);
12184        cx.notify();
12185    }
12186}
12187
12188/// Methods that only ordinary multi-line text offers.
12189impl InputBaseState<crate::input::TextareaMode> {
12190    /// Create a multi-line text state.
12191    ///
12192    /// Being multi-line is carried by the mode, not by the layout, so the
12193    /// default plain-text layout needs no adjustment here.
12194    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
12195        Self::new_in_mode(window, cx)
12196    }
12197
12198    pub fn set_auto_grow(&mut self, min_rows: usize, max_rows: usize, cx: &mut Context<Self>) {
12199        self.mode = LayoutMode::auto_grow(min_rows, max_rows.max(min_rows));
12200        self.mode.update_auto_grow(&self.display_map);
12201        cx.notify();
12202    }
12203
12204    /// Set the number of rows for the multi-line Textarea.
12205    ///
12206    /// This is only used when `multi_line` is set to true. The input is at least
12207    /// this many lines tall.
12208    ///
12209    /// default: 1
12210    #[doc(hidden)]
12211    pub fn rows(mut self, rows: usize) -> Self {
12212        match &mut self.mode {
12213            LayoutMode::PlainText { rows: r, .. } | LayoutMode::CodeEditor { rows: r, .. } => {
12214                *r = rows
12215            }
12216            LayoutMode::AutoGrow {
12217                max_rows: max_r,
12218                rows: r,
12219                ..
12220            } => {
12221                *r = rows;
12222                *max_r = rows;
12223            }
12224        }
12225        self
12226    }
12227
12228    pub fn set_rows(&mut self, rows: usize, cx: &mut Context<Self>) {
12229        match &mut self.mode {
12230            LayoutMode::PlainText { rows: value, .. }
12231            | LayoutMode::CodeEditor { rows: value, .. } => *value = rows,
12232            LayoutMode::AutoGrow {
12233                rows: value,
12234                max_rows,
12235                ..
12236            } => {
12237                *value = rows;
12238                *max_rows = rows;
12239            }
12240        }
12241        cx.notify();
12242    }
12243
12244    /// Grow with the content from `min_rows` through `max_rows`.
12245    pub fn auto_grow(mut self, min_rows: usize, max_rows: usize) -> Self {
12246        self.mode = LayoutMode::auto_grow(min_rows, max_rows);
12247        self
12248    }
12249}
12250
12251/// Methods that only a source-code editor offers.
12252impl InputBaseState<crate::input::EditorMode> {
12253    /// Create a source-code editor state.
12254    ///
12255    /// Default options: line numbers on, tab size 2 with soft tabs, indent
12256    /// guides on, multi-line, and search enabled. Set the language for syntax
12257    /// highlighting with [`Self::language`]; without one the text is shown
12258    /// unhighlighted.
12259    ///
12260    /// The editor aims at simple code editing or display, not at being a
12261    /// full-featured code editor. It offers syntax highlighting, auto indent,
12262    /// line numbers, and handles large text up to about 50K lines.
12263    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
12264        let mut state = Self::new_in_mode(window, cx);
12265        state.mode = LayoutMode::code_editor(super::EditorLanguage::new(cx));
12266        state.searchable = true;
12267        state
12268    }
12269
12270    /// Set the language to highlight, e.g. `"rust"`.
12271    ///
12272    /// See [`Self::set_highlighter`] to change it after construction.
12273    pub fn language(mut self, language: impl Into<SharedString>) -> Self {
12274        if let LayoutMode::CodeEditor {
12275            language: l,
12276            highlighter,
12277            ..
12278        } = &mut self.mode
12279        {
12280            l.set_name(language.into());
12281            *highlighter.borrow_mut() = None;
12282        }
12283        self
12284    }
12285
12286    /// The current language name, e.g. `"rust"`.
12287    pub fn language_name(&self) -> SharedString {
12288        match &self.mode {
12289            LayoutMode::CodeEditor { language, .. } => language.name(),
12290            _ => SharedString::default(),
12291        }
12292    }
12293
12294    /// Set enable/disable code folding.
12295    ///
12296    /// Default: true
12297    #[doc(hidden)]
12298    pub fn folding(mut self, folding: bool) -> Self {
12299        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
12300            *f = folding;
12301        }
12302        self
12303    }
12304
12305    /// Set code folding at runtime.
12306    ///
12307    /// When disabling, all existing folds are cleared.
12308    pub fn set_folding(&mut self, folding: bool, _: &mut Window, cx: &mut Context<Self>) {
12309        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
12310            *f = folding;
12311        }
12312        if !folding {
12313            self.display_map.clear_folds();
12314        }
12315        cx.notify();
12316    }
12317
12318    /// Unfold any folded ranges that hide the given position.
12319    ///
12320    /// Use this to reveal a position before acting on it (e.g. before
12321    /// [`Self::set_cursor_position`], which stops at a fold boundary),
12322    /// without touching folds elsewhere in the buffer. Fold candidates are
12323    /// kept, so the opened ranges can be folded again from the gutter.
12324    ///
12325    /// A fold keeps its own first and last line visible, so a position on
12326    /// either of them opens nothing. Nested folds all open, since opening
12327    /// only the outermost would leave the position hidden.
12328    ///
12329    /// Returns whether any fold was opened.
12330    pub fn unfold_at(&mut self, position: impl Into<Position>, cx: &mut Context<Self>) -> bool {
12331        let offset = self.text.position_to_offset(&position.into());
12332        let line = self.text.offset_to_point(offset).row;
12333        // A fold hides start_line + 1 ..= end_line - 1, so a line is hidden
12334        // exactly when some folded range strictly contains it.
12335        let covering: Vec<usize> = self
12336            .display_map
12337            .folded_ranges()
12338            .iter()
12339            .filter(|fold| line > fold.start_line && line < fold.end_line)
12340            .map(|fold| fold.start_line)
12341            .collect();
12342        if covering.is_empty() {
12343            return false;
12344        }
12345
12346        for start_line in covering {
12347            self.display_map.set_folded(start_line, false);
12348        }
12349        cx.notify();
12350        true
12351    }
12352
12353    /// Set enable/disable line number.
12354    #[doc(hidden)]
12355    pub fn line_number(mut self, line_number: bool) -> Self {
12356        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
12357            *l = line_number;
12358        }
12359        self
12360    }
12361
12362    /// Set line number.
12363    pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
12364        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
12365            if *l == line_number {
12366                return;
12367            }
12368            *l = line_number;
12369        }
12370        cx.notify();
12371    }
12372
12373    /// Set enable/disable automatic closing brackets and quotes.
12374    ///
12375    /// When enabled, typing an opener from [`LanguageConfig::auto_closing_pairs`](crate::input::language_config::LanguageConfig::auto_closing_pairs) inserts the
12376    /// matching closer and places the cursor inside. Typing a closer that is
12377    /// already present just moves past it. Default: true
12378    #[doc(hidden)]
12379    pub fn auto_close(mut self, auto_close: bool) -> Self {
12380        self.mode.set_auto_close(auto_close);
12381        self
12382    }
12383
12384    /// Set automatic closing brackets and quotes at runtime.
12385    pub fn set_auto_close(&mut self, auto_close: bool, _: &mut Window, cx: &mut Context<Self>) {
12386        self.mode.set_auto_close(auto_close);
12387        cx.notify();
12388    }
12389
12390    /// Set enable/disable smart indent on Enter.
12391    ///
12392    /// When enabled, Enter uses structural brackets and
12393    /// [`LanguageConfig::indentation_rules`](crate::input::language_config::LanguageConfig::indentation_rules) to choose indentation.
12394    /// Default: true
12395    #[doc(hidden)]
12396    pub fn smart_indent(mut self, smart_indent: bool) -> Self {
12397        self.mode.set_smart_indent(smart_indent);
12398        self
12399    }
12400
12401    /// Set smart indent on Enter at runtime.
12402    pub fn set_smart_indent(&mut self, smart_indent: bool, _: &mut Window, cx: &mut Context<Self>) {
12403        self.mode.set_smart_indent(smart_indent);
12404        cx.notify();
12405    }
12406}