Skip to main content

gpui_base/input/base/
state.rs

1//! A text input field that allows the user to enter text.
2//!
3//! Based on the `Input` example from the `gpui` crate.
4//! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs
5use gpui::TextAlign;
6use gpui::{
7    Action, App, AppContext, Bounds, ClipboardItem, Context, Edges, Entity, EntityInputHandler,
8    EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding,
9    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point,
10    Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription,
11    UTF16Selection, WeakFocusHandle, Window, actions, div, point, prelude::FluentBuilder as _, px,
12};
13use gpui::{Autocapitalize, TextInputAction, TextInputConfiguration};
14use ropey::{Rope, RopeSlice};
15use serde::Deserialize;
16use std::borrow::Cow;
17use std::cell::Cell;
18use std::ops::Range;
19use std::rc::Rc;
20use sum_tree::Bias;
21use unicode_segmentation::*;
22
23use super::{
24    DiagnosticSet, DisplayMap, InputContextMenuCapabilities, InputEditorStyle,
25    InputHighlighterFactory, MASK_CHAR, MaskPattern, NativeMenu, NumberStep, WrappingIndent,
26    blink_cursor::BlinkCursor,
27    change::Change,
28    cursor::{CursorSelection, Selections},
29    element::{
30        EditorScrollbar, EditorScrollbarSnapshot, LongestLineKey, TextElement,
31        clamp_horizontal_scroll_offset,
32    },
33    kind::InputModeKind,
34    mask_pattern::normalize_number_input,
35    mode::LayoutMode,
36    undo_manager::{EditIntent, UndoManager},
37};
38use crate::actions::{SelectDown, SelectLeft, SelectRight, SelectUp};
39use crate::input::blink_cursor::CURSOR_WIDTH;
40use crate::input::movement::MoveDirection;
41use crate::input::{
42    InputExtras as _, Position, RopeExt as _, element::RIGHT_MARGIN, layout::LastLayout,
43};
44use crate::overscroll::Overscroll;
45use crate::{AutoScroll, StepAction};
46
47/// Vertical clearance to retain when revealing a text position.
48pub(crate) enum ScrollPadding {
49    Minimal,
50    SurroundingLines,
51}
52
53#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
54#[action(namespace = input, no_json)]
55pub struct Enter {
56    /// Is confirm with secondary.
57    pub secondary: bool,
58    /// Whether the Shift modifier was held when Enter was pressed.
59    pub shift: bool,
60}
61
62impl Enter {
63    /// Returns true if `action` is a primary `Enter` action (`secondary: false`),
64    /// regardless of whether Shift was held.
65    pub fn is_primary(action: &dyn Action) -> bool {
66        action.partial_eq(&Enter {
67            secondary: false,
68            shift: false,
69        }) || action.partial_eq(&Enter {
70            secondary: false,
71            shift: true,
72        })
73    }
74}
75
76actions!(
77    input,
78    [
79        ActivateToken,
80        Backspace,
81        Delete,
82        DeleteToBeginningOfLine,
83        DeleteToEndOfLine,
84        DeleteToPreviousWordStart,
85        DeleteToNextWordEnd,
86        Indent,
87        Outdent,
88        IndentInline,
89        OutdentInline,
90        MoveUp,
91        MoveDown,
92        MoveLeft,
93        MoveRight,
94        MoveHome,
95        MoveEnd,
96        MovePageUp,
97        MovePageDown,
98        AddCursorAbove,
99        AddCursorBelow,
100        SelectAll,
101        SelectToStartOfLine,
102        SelectToEndOfLine,
103        SelectToStart,
104        SelectToEnd,
105        SelectToPreviousWordStart,
106        SelectToNextWordEnd,
107        ShowCharacterPalette,
108        Copy,
109        Cut,
110        Paste,
111        Undo,
112        Redo,
113        MoveToStartOfLine,
114        MoveToEndOfLine,
115        MoveToStart,
116        MoveToEnd,
117        MoveToPreviousWord,
118        MoveToNextWord,
119        Escape,
120        ToggleCodeActions,
121        ShowCompletions,
122        Search,
123        Replace,
124        GoToDefinition,
125    ]
126);
127
128#[derive(Clone)]
129pub enum InputEvent {
130    Change,
131    PressEnter { secondary: bool, shift: bool },
132    Focus,
133    Blur,
134}
135
136pub(super) const CONTEXT: &str = "Input";
137
138pub(crate) fn init(cx: &mut App) {
139    cx.bind_keys([
140        KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
141        KeyBinding::new("shift-backspace", Backspace, Some(CONTEXT)),
142        #[cfg(target_os = "macos")]
143        KeyBinding::new("ctrl-backspace", Backspace, Some(CONTEXT)),
144        KeyBinding::new("delete", Delete, Some(CONTEXT)),
145        KeyBinding::new("shift-delete", Delete, Some(CONTEXT)),
146        #[cfg(target_os = "macos")]
147        KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
148        #[cfg(target_os = "macos")]
149        KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
150        #[cfg(target_os = "macos")]
151        KeyBinding::new("alt-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
152        #[cfg(not(target_os = "macos"))]
153        KeyBinding::new("ctrl-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
154        #[cfg(target_os = "macos")]
155        KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)),
156        #[cfg(not(target_os = "macos"))]
157        KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)),
158        KeyBinding::new(
159            "enter",
160            Enter {
161                secondary: false,
162                shift: false,
163            },
164            Some(CONTEXT),
165        ),
166        KeyBinding::new(
167            "shift-enter",
168            Enter {
169                secondary: false,
170                shift: true,
171            },
172            Some(CONTEXT),
173        ),
174        KeyBinding::new(
175            "secondary-enter",
176            Enter {
177                secondary: true,
178                shift: false,
179            },
180            Some(CONTEXT),
181        ),
182        KeyBinding::new("escape", Escape, Some(CONTEXT)),
183        KeyBinding::new("up", MoveUp, Some(CONTEXT)),
184        KeyBinding::new("down", MoveDown, Some(CONTEXT)),
185        KeyBinding::new("left", MoveLeft, Some(CONTEXT)),
186        KeyBinding::new("right", MoveRight, Some(CONTEXT)),
187        KeyBinding::new("pageup", MovePageUp, Some(CONTEXT)),
188        KeyBinding::new("pagedown", MovePageDown, Some(CONTEXT)),
189        KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
190        KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
191        #[cfg(target_os = "macos")]
192        KeyBinding::new("cmd-]", Indent, Some(CONTEXT)),
193        #[cfg(not(target_os = "macos"))]
194        KeyBinding::new("ctrl-]", Indent, Some(CONTEXT)),
195        #[cfg(target_os = "macos")]
196        KeyBinding::new("cmd-[", Outdent, Some(CONTEXT)),
197        #[cfg(not(target_os = "macos"))]
198        KeyBinding::new("ctrl-[", Outdent, Some(CONTEXT)),
199        KeyBinding::new("shift-left", SelectLeft, Some(CONTEXT)),
200        KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
201        KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
202        KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
203        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
204        KeyBinding::new("shift-alt-left", SelectLeft, Some(CONTEXT)),
205        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
206        KeyBinding::new("shift-alt-right", SelectRight, Some(CONTEXT)),
207        // Avoid Ctrl+Alt+arrows on Linux, where desktops may reserve them.
208        #[cfg(target_os = "macos")]
209        KeyBinding::new("cmd-alt-up", AddCursorAbove, Some(CONTEXT)),
210        #[cfg(target_os = "macos")]
211        KeyBinding::new("cmd-alt-down", AddCursorBelow, Some(CONTEXT)),
212        #[cfg(target_os = "windows")]
213        KeyBinding::new("ctrl-alt-up", AddCursorAbove, Some(CONTEXT)),
214        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
215        KeyBinding::new("shift-alt-up", AddCursorAbove, Some(CONTEXT)),
216        #[cfg(target_os = "windows")]
217        KeyBinding::new("ctrl-alt-down", AddCursorBelow, Some(CONTEXT)),
218        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
219        KeyBinding::new("shift-alt-down", AddCursorBelow, Some(CONTEXT)),
220        KeyBinding::new("home", MoveHome, Some(CONTEXT)),
221        KeyBinding::new("end", MoveEnd, Some(CONTEXT)),
222        #[cfg(not(target_os = "macos"))]
223        KeyBinding::new("ctrl-home", MoveToStart, Some(CONTEXT)),
224        #[cfg(not(target_os = "macos"))]
225        KeyBinding::new("ctrl-end", MoveToEnd, Some(CONTEXT)),
226        #[cfg(not(target_os = "macos"))]
227        KeyBinding::new("ctrl-shift-home", SelectToStart, Some(CONTEXT)),
228        #[cfg(not(target_os = "macos"))]
229        KeyBinding::new("ctrl-shift-end", SelectToEnd, Some(CONTEXT)),
230        KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
231        KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
232        #[cfg(target_os = "macos")]
233        KeyBinding::new("ctrl-shift-a", SelectToStartOfLine, Some(CONTEXT)),
234        #[cfg(target_os = "macos")]
235        KeyBinding::new("ctrl-shift-e", SelectToEndOfLine, Some(CONTEXT)),
236        #[cfg(target_os = "macos")]
237        KeyBinding::new("shift-cmd-left", SelectToStartOfLine, Some(CONTEXT)),
238        #[cfg(target_os = "macos")]
239        KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
240        #[cfg(any(target_os = "macos", target_os = "linux"))]
241        KeyBinding::new("alt-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
242        #[cfg(not(target_os = "macos"))]
243        KeyBinding::new("ctrl-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
244        #[cfg(any(target_os = "macos", target_os = "linux"))]
245        KeyBinding::new("alt-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
246        #[cfg(not(target_os = "macos"))]
247        KeyBinding::new("ctrl-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
248        #[cfg(target_os = "macos")]
249        KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
250        #[cfg(target_os = "macos")]
251        KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
252        #[cfg(not(target_os = "macos"))]
253        KeyBinding::new("ctrl-a", SelectAll, Some(CONTEXT)),
254        #[cfg(target_os = "macos")]
255        KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
256        #[cfg(not(target_os = "macos"))]
257        KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
258        #[cfg(target_os = "macos")]
259        KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
260        #[cfg(not(target_os = "macos"))]
261        KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
262        #[cfg(target_os = "macos")]
263        KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
264        #[cfg(not(target_os = "macos"))]
265        KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
266        #[cfg(target_os = "macos")]
267        KeyBinding::new("ctrl-a", MoveHome, Some(CONTEXT)),
268        #[cfg(target_os = "macos")]
269        KeyBinding::new("cmd-left", MoveHome, Some(CONTEXT)),
270        #[cfg(target_os = "macos")]
271        KeyBinding::new("ctrl-e", MoveEnd, Some(CONTEXT)),
272        #[cfg(target_os = "macos")]
273        KeyBinding::new("cmd-right", MoveEnd, Some(CONTEXT)),
274        #[cfg(target_os = "macos")]
275        KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
276        #[cfg(target_os = "macos")]
277        KeyBinding::new("cmd-shift-z", Redo, Some(CONTEXT)),
278        #[cfg(target_os = "macos")]
279        KeyBinding::new("cmd-up", MoveToStart, Some(CONTEXT)),
280        #[cfg(target_os = "macos")]
281        KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
282        #[cfg(target_os = "macos")]
283        KeyBinding::new("alt-left", MoveToPreviousWord, Some(CONTEXT)),
284        #[cfg(target_os = "macos")]
285        KeyBinding::new("alt-right", MoveToNextWord, Some(CONTEXT)),
286        #[cfg(not(target_os = "macos"))]
287        KeyBinding::new("ctrl-left", MoveToPreviousWord, Some(CONTEXT)),
288        #[cfg(not(target_os = "macos"))]
289        KeyBinding::new("ctrl-right", MoveToNextWord, Some(CONTEXT)),
290        #[cfg(target_os = "macos")]
291        KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
292        #[cfg(target_os = "macos")]
293        KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
294        #[cfg(not(target_os = "macos"))]
295        KeyBinding::new("ctrl-z", Undo, Some(CONTEXT)),
296        #[cfg(not(target_os = "macos"))]
297        KeyBinding::new("ctrl-y", Redo, Some(CONTEXT)),
298        #[cfg(target_os = "macos")]
299        KeyBinding::new("cmd-.", ToggleCodeActions, Some(CONTEXT)),
300        #[cfg(not(target_os = "macos"))]
301        KeyBinding::new("ctrl-.", ToggleCodeActions, Some(CONTEXT)),
302        #[cfg(target_os = "macos")]
303        KeyBinding::new("cmd-f", Search, Some(CONTEXT)),
304        #[cfg(not(target_os = "macos"))]
305        KeyBinding::new("ctrl-f", Search, Some(CONTEXT)),
306        #[cfg(target_os = "macos")]
307        KeyBinding::new("cmd-shift-f", Replace, Some(CONTEXT)),
308        #[cfg(not(target_os = "macos"))]
309        KeyBinding::new("ctrl-h", Replace, Some(CONTEXT)),
310    ]);
311}
312
313/// A mouse position resolved for a columnar (block) selection.
314///
315/// A position past the end of a short row has no glyph to land on, so `offset` clips to
316/// that row's end and `columns_past_line_end` keeps what the clip dropped. Together they
317/// say which column the pointer really reached, which is what a block spanning rows of
318/// different lengths has to be measured in.
319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
320pub(super) struct ColumnarPoint {
321    offset: usize,
322    columns_past_line_end: usize,
323}
324
325impl ColumnarPoint {
326    pub(super) fn new(offset: usize, columns_past_line_end: usize) -> Self {
327        Self {
328            offset,
329            columns_past_line_end,
330        }
331    }
332}
333
334/// The document and selections a paste was asked to replace, taken when the
335/// `Paste` action ran so a clipboard read that resolves later can tell
336/// whether it still applies. See [`InputBaseState::paste_target`].
337#[derive(Debug, PartialEq)]
338struct PasteTarget {
339    document_revision: u64,
340    selections: Vec<CursorSelection>,
341}
342
343/// Caret positioning geometry from the input's latest prepaint.
344///
345/// Unlike `last_layout` (published at paint time), this is stashed during
346/// prepaint so caret-anchored popups can position themselves from the
347/// current frame's geometry in their deferred prepaint, instead of the
348/// previous frame's paint.
349#[derive(Clone, Copy, Debug)]
350#[doc(hidden)]
351pub struct PrepaintCaretGeometry {
352    cursor_bounds: Bounds<Pixels>,
353    line_height: Pixels,
354    input_bounds: Bounds<Pixels>,
355    scroll_offset: Point<Pixels>,
356    /// A resize owns visibility while its requested repaint settles.
357    pub(super) resize_reveal_owned: bool,
358}
359
360impl PrepaintCaretGeometry {
361    pub(super) fn new(
362        cursor_bounds: Bounds<Pixels>,
363        line_height: Pixels,
364        input_bounds: Bounds<Pixels>,
365        scroll_offset: Point<Pixels>,
366    ) -> Self {
367        Self {
368            cursor_bounds,
369            line_height,
370            input_bounds,
371            scroll_offset,
372            resize_reveal_owned: false,
373        }
374    }
375
376    /// The active caret's bounds from the fresh prepaint layout.
377    pub fn cursor_bounds(&self) -> Bounds<Pixels> {
378        self.cursor_bounds
379    }
380
381    /// Line height from the fresh prepaint layout.
382    pub fn line_height(&self) -> Pixels {
383        self.line_height
384    }
385
386    /// The input container bounds from the fresh prepaint.
387    pub fn input_bounds(&self) -> Bounds<Pixels> {
388        self.input_bounds
389    }
390
391    /// The scroll offset that will be in effect after paint, clamped like
392    /// `InputBaseState::update_scroll_offset` clamps it.
393    pub fn scroll_offset(&self) -> Point<Pixels> {
394        self.scroll_offset
395    }
396}
397
398/// The shared text-editing engine behind [`crate::input::InputState`],
399/// [`crate::input::TextareaState`] and [`crate::input::EditorState`].
400///
401/// `M` is the mode marker: it carries no data and only decides which methods
402/// exist, so an ordinary input cannot reach the editor's language features.
403///
404/// The three states are type aliases of this one, which is why this name is
405/// public: an alias is only as usable as the type behind it, so hiding this
406/// would leave `InputState` unable to do anything. Prefer naming the aliases
407/// — write `InputState`, not `InputBaseState<InputMode>`.
408pub struct InputBaseState<M: InputModeKind> {
409    /// State only this mode needs. See [`InputModeKind::Extras`].
410    pub(crate) extras: M::Extras,
411    pub(super) focus_handle: FocusHandle,
412    selection_focus: Option<WeakFocusHandle>,
413    pub(super) mode: LayoutMode,
414    pub(super) text: Rope,
415    pub(super) display_map: DisplayMap,
416    pub(super) undo_manager: UndoManager,
417    pub(super) inline_tokens: Option<Box<super::inline_tokens::InlineTokenStore>>,
418    pub(super) pending_token: Option<super::InlineToken>,
419    pub(super) replaying_history: bool,
420    pub(super) validated_token_edit: bool,
421    pub(super) document_revision: u64,
422    pub(super) token_presentation: super::InlineTokenPresentation,
423    pub(super) token_layout_cache: Option<Box<super::token_presentation::TokenLayoutCache>>,
424    /// Real per-frame bounds of the placed token elements, keyed by token
425    /// start offset; used for hover payloads and stale-hover reconciliation.
426    pub(super) token_bounds: std::collections::HashMap<usize, Bounds<Pixels>>,
427    /// The currently hovered token: its entered span, last measured bounds,
428    /// and UTF-16 range as of entry. Retained so hover exit can still be
429    /// delivered when the token is removed, replaced, scrolled out, or
430    /// disabled.
431    pub(super) hovered_token: Option<super::token_presentation::HoverSnapshot>,
432    pub(super) pending_token_hover_exits: Vec<super::token_presentation::HoverSnapshot>,
433    /// The start offset of a pressed token, with the document revision and
434    /// pointer position at the press.
435    pub(super) pressed_token: Option<(usize, u64, Point<Pixels>)>,
436    pub(super) search_session: super::SearchSession,
437    /// Advances every time search is explicitly invoked. See
438    /// [`InputBaseState::search_activation_revision`].
439    pub(super) search_activation_revision: u64,
440    pub(super) searchable: bool,
441    pub(super) replaceable: bool,
442    pub(super) soft_wrap: bool,
443    pub(super) wrapping_indent: WrappingIndent,
444    pub(super) scroll_beyond_last_line: Option<usize>,
445    pub(super) cursor_surrounding_lines: Option<usize>,
446    pub(super) blink_cursor: Entity<BlinkCursor>,
447    pub(super) loading: bool,
448    /// The cursors and selections.
449    ///
450    /// Always contains at least one selection where index 0 is the active cursor.
451    pub(super) selections: Selections,
452    /// Range for save the selected word, use to keep word range when drag move.
453    pub(super) selected_word_range: Option<CursorSelection>,
454    /// The marked range is the temporary insert text on IME typing.
455    pub(super) ime_marked_range: Option<CursorSelection>,
456    pub(super) last_layout: Option<LastLayout>,
457    /// Fresh caret geometry stashed during prepaint for caret-anchored popups.
458    ///
459    /// Popups are positioned in their deferred prepaint, which runs after the
460    /// input's prepaint but before its paint, so they read this instead of
461    /// the previous frame's `last_layout`.
462    pub(crate) prepaint_caret_geometry: Option<PrepaintCaretGeometry>,
463    pub(super) last_cursor: Option<usize>,
464    /// The input container bounds
465    pub(super) input_bounds: Bounds<Pixels>,
466    /// The text bounds
467    pub(super) last_bounds: Option<Bounds<Pixels>>,
468    pub(super) last_selected_range: Option<CursorSelection>,
469    pub(super) selecting: bool,
470    /// Anchor point of an in-progress columnar (block) selection.
471    pub(super) column_select_start: Option<ColumnarPoint>,
472    /// The selection a long press made, with its handles and edit menu.
473    pub(super) touch_selection: super::touch::TouchSelection,
474    pub(crate) disabled: bool,
475    pub(crate) readonly: bool,
476    pub(crate) text_align: TextAlign,
477    pub(super) masked: bool,
478    pub(super) clean_on_escape: bool,
479    pub(super) submit_on_enter: bool,
480    /// Overrides for [`Self::text_input_configuration`].
481    pub(super) input_action: Option<TextInputAction>,
482    pub(super) autocorrect: Option<bool>,
483    pub(super) autocapitalize: Option<Autocapitalize>,
484    pub(super) show_whitespaces: bool,
485    /// This flag tells the renderer to prefer the end of the current visual line.
486    pub(crate) cursor_line_end_affinity: bool,
487    pub(super) pattern: Option<regex::Regex>,
488    pub(super) validate: Option<Box<dyn Fn(&str, &mut App) -> bool + 'static>>,
489    /// The step strategy for [`super::NumberInput`] to increment/decrement.
490    /// See [`Self::step`] and [`Self::step_by`].
491    pub(crate) number_step: Option<NumberStep>,
492    /// The minimum value for [`super::NumberInput`]. See [`Self::min`].
493    pub(crate) number_min: Option<f64>,
494    /// The maximum value for [`super::NumberInput`]. See [`Self::max`].
495    pub(crate) number_max: Option<f64>,
496    pub(crate) scroll_handle: ScrollHandle,
497    /// Whether the text stretches past its ends, see [`Self::scroll_bounce`].
498    scroll_bounce: bool,
499    /// The text's stretch past an end, while it lasts.
500    pub(super) overscroll: Overscroll,
501    /// The deferred scroll offset to apply on next layout.
502    pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,
503    /// The size of the scrollable content.
504    pub(crate) scroll_size: gpui::Size<Pixels>,
505    pub(super) editor_scrollbar_snapshot: Cell<Option<EditorScrollbarSnapshot>>,
506    /// The unwrapped width of the longest line and what it was measured for.
507    pub(super) longest_line_width: Cell<Option<(LongestLineKey, Pixels)>>,
508    pub(super) editor_paddings: Edges<Pixels>,
509    /// The style this state paints with: what was projected onto it, with
510    /// every colour left unset resolved from the palette that is current. It
511    /// is rebuilt at the top of every render, which is what keeps it current
512    /// when the palette changes after the state was built.
513    pub(super) editor_style: InputEditorStyle,
514    /// What a consumer projected, kept verbatim so that resolution never
515    /// consumes its own output: resolving in place would fill the unset
516    /// colours once and then never see them as unset again, which is the same
517    /// freeze in a different place.
518    projected_editor_style: InputEditorStyle,
519
520    /// The mask pattern for formatting the input text
521    pub(crate) mask_pattern: MaskPattern,
522    /// Whether the `mask_pattern` was explicitly set (via [`Self::mask_pattern`]
523    /// or [`Self::set_mask_pattern`]), to let [`super::NumberInput`] only apply
524    /// its default mask when the user has not made an explicit choice.
525    pub(super) mask_pattern_set: bool,
526    pub(super) placeholder: SharedString,
527
528    /// Diagnostic currently requested by pointer hover; applications render it.
529    pub(super) diagnostic_popover: Option<Rc<crate::input::DiagnosticEntry>>,
530
531    context_menu_handler: Option<
532        Rc<dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App)>,
533    >,
534    pending_context_menu: Option<(Point<Pixels>, usize)>,
535
536    /// Whether the context menu that shows on right-click is enabled.
537    ///
538    pub(super) enable_context_menu: bool,
539
540    /// A flag to indicate if we are currently inserting a completion item.
541    pub(super) completion_inserting: bool,
542    pub(super) overlay_action_handler: Option<
543        Rc<
544            dyn Fn(
545                super::InputOverlayKind,
546                Box<dyn Action>,
547                &mut Window,
548                &mut Context<InputBaseState<M>>,
549            ) -> bool,
550        >,
551    >,
552
553    /// A flag to indicate if we have a pending update to the text.
554    ///
555    /// If true, will call some update (for example LSP, Syntax Highlight) before render.
556    _pending_update: bool,
557    /// A flag to indicate if we should ignore the next completion event.
558    pub(super) silent_replace_text: bool,
559    /// A flag to indicate if we should emit InputEvents.
560    pub(super) emit_events: bool,
561
562    _subscriptions: Vec<Subscription>,
563
564    pub(super) auto_scroll: AutoScroll,
565}
566
567/// Read-only styling data exposed to presentation facades.
568///
569/// The fields are private and read through the methods below, so that a new
570/// one can be added without breaking the facades.
571#[derive(Clone)]
572pub struct InputPresentation {
573    focus_handle: FocusHandle,
574    disabled: bool,
575    readonly: bool,
576    loading: bool,
577    masked: bool,
578    multi_line: bool,
579    code_editor: bool,
580    text_align: TextAlign,
581    placeholder: SharedString,
582    mask_placeholder: Option<String>,
583}
584
585impl InputPresentation {
586    pub fn focus_handle(&self) -> &FocusHandle {
587        &self.focus_handle
588    }
589
590    pub fn is_disabled(&self) -> bool {
591        self.disabled
592    }
593
594    pub fn is_readonly(&self) -> bool {
595        self.readonly
596    }
597
598    /// Returns true if the user is allowed to change the text.
599    ///
600    /// See also: [`InputBaseState::is_editable`].
601    pub fn is_editable(&self) -> bool {
602        !self.disabled && !self.readonly
603    }
604
605    pub fn is_loading(&self) -> bool {
606        self.loading
607    }
608
609    pub fn is_masked(&self) -> bool {
610        self.masked
611    }
612
613    pub fn is_multi_line(&self) -> bool {
614        self.multi_line
615    }
616
617    pub fn is_code_editor(&self) -> bool {
618        self.code_editor
619    }
620
621    pub fn text_align(&self) -> TextAlign {
622        self.text_align
623    }
624
625    pub fn placeholder(&self) -> &SharedString {
626        &self.placeholder
627    }
628
629    /// The placeholder derived from the mask pattern, e.g.: `(___) ___-____`.
630    pub fn mask_placeholder(&self) -> Option<&str> {
631        self.mask_placeholder.as_deref()
632    }
633}
634
635impl<M: InputModeKind> EventEmitter<InputEvent> for InputBaseState<M> {}
636
637impl<M: InputModeKind> InputBaseState<M> {
638    #[doc(hidden)]
639    pub fn cursor_layout(&self) -> Option<(Bounds<Pixels>, Pixels)> {
640        let layout = self.last_layout.as_ref()?;
641        Some((layout.cursor_bounds?, layout.line_height))
642    }
643
644    /// Caret geometry from the latest prepaint, for caret-anchored popups.
645    ///
646    /// Popups positioned in their deferred prepaint read this to anchor to
647    /// the current frame's caret instead of the previous frame's paint.
648    #[doc(hidden)]
649    pub fn prepaint_caret_geometry(&self) -> Option<PrepaintCaretGeometry> {
650        self.prepaint_caret_geometry
651    }
652
653    pub fn input_bounds(&self) -> Bounds<Pixels> {
654        self.input_bounds
655    }
656
657    pub fn text_bounds(&self) -> Option<Bounds<Pixels>> {
658        self.last_bounds
659    }
660
661    /// The cached display map for querying wrapped rows and display positions.
662    ///
663    /// Reading this does not prepare text or force layout. Before the first
664    /// layout, [`DisplayMap::wrap_row_count`] can be zero; prepared empty text
665    /// has one row. The count describes content, independent of minimum rows,
666    /// auto-grow limits and viewport height.
667    ///
668    /// Text edits use the cached wrapping parameters. Layout refreshes the
669    /// cache after width or font changes; consume that count on a subsequent
670    /// render, or use [`Self::prewrap`] to prepare it before the first layout.
671    pub fn display_map(&self) -> &DisplayMap {
672        &self.display_map
673    }
674
675    /// Wrap the text for `font`, `font_size` and `width` before the first
676    /// layout, so that [`DisplayMap::wrap_row_count`] is known on the first frame.
677    ///
678    /// Call it after setting the value and the wrapping indent. `width` is the
679    /// width of the input's text area: its width less its padding, and less
680    /// the line-number gutter for an editor with line numbers. It wraps as
681    /// layout does, a little short of that width and with the state's wrapping
682    /// indent. Once the input has been laid out this does nothing, and layout
683    /// keeps the wrap up to date. The font and font size must match the input's
684    /// actual text style.
685    pub fn prewrap(
686        &mut self,
687        font: gpui::Font,
688        font_size: Pixels,
689        width: Pixels,
690        cx: &mut Context<Self>,
691    ) {
692        if self.last_layout.is_some() {
693            return;
694        }
695        let wrap_width = (width - RIGHT_MARGIN).max(px(1.));
696        self.display_map.set_font(font, font_size, cx);
697        self.display_map
698            .on_layout_changed(self.soft_wrap.then_some(wrap_width), cx);
699        self.display_map
700            .set_wrapping_indent(self.wrapping_indent, cx);
701        self.display_map.ensure_text_prepared(&self.text, cx);
702    }
703
704    pub fn diagnostic_popover(&self) -> Option<Rc<crate::input::DiagnosticEntry>> {
705        self.diagnostic_popover.clone()
706    }
707
708    pub fn presentation(&self) -> InputPresentation {
709        InputPresentation {
710            focus_handle: self.focus_handle.clone(),
711            disabled: self.disabled,
712            readonly: self.readonly,
713            loading: self.loading,
714            masked: self.masked,
715            multi_line: self.is_multi_line(),
716            code_editor: self.is_code_editor(),
717            text_align: self.text_align,
718            placeholder: self.placeholder.clone(),
719            mask_placeholder: self.mask_pattern.placeholder(),
720        }
721    }
722
723    /// Whether this input spans more than one line.
724    ///
725    /// Answered by the mode marker, which is fixed when the state is built.
726    /// [`LayoutMode`] holds the row counts and growth policy, not the kind.
727    #[inline]
728    /// Whether this input paints scrollbars.
729    ///
730    /// Only a multi-line input can scroll: a single-line input keeps its
731    /// caret in view by moving its own offset, and never has a viewport a
732    /// user could drag. Adding the editor scrollbar to every input put a
733    /// thumb inside every text field, which is a control the field does not
734    /// have.
735    pub(crate) fn shows_scrollbar(&self) -> bool {
736        self.is_multi_line()
737    }
738
739    pub fn is_multi_line(&self) -> bool {
740        M::MULTI_LINE
741    }
742
743    /// Whether this input is a single-line text field. See [`Self::is_multi_line`].
744    #[inline]
745    pub fn is_single_line(&self) -> bool {
746        !M::MULTI_LINE
747    }
748
749    /// Whether this input is a source-code editor.
750    #[inline]
751    pub fn is_code_editor(&self) -> bool {
752        M::CODE_EDITOR
753    }
754
755    /// Whether the user is allowed to copy the selection out.
756    ///
757    /// A masked input keeps its value out of the clipboard.
758    pub fn is_copyable(&self) -> bool {
759        self.selections.iter().any(|sel| !sel.is_empty()) && !self.masked
760    }
761
762    pub fn context_menu_capabilities(&self) -> InputContextMenuCapabilities {
763        let (go_to_definition, code_actions) = self.extras.context_menu_capabilities();
764        InputContextMenuCapabilities::new()
765            .disabled(self.disabled)
766            .readonly(self.readonly)
767            .code_editor(self.is_code_editor())
768            .selection(!self.active_selection().is_empty())
769            .masked(self.masked)
770            .go_to_definition(go_to_definition)
771            .code_actions(code_actions)
772    }
773
774    pub fn set_text_align(&mut self, text_align: TextAlign, cx: &mut Context<Self>) {
775        if !self.is_single_line() || self.text_align == text_align {
776            return;
777        }
778
779        self.text_align = text_align;
780        cx.notify();
781    }
782
783    /// Flip the password mask.
784    ///
785    /// Setting the mask is a single-line method, but flipping it stays here:
786    /// the reveal button is rendered from the generic path, and it can only be
787    /// switched on through [`crate::input::InputState`] anyway.
788    pub fn toggle_masked(&mut self, _: &mut Window, cx: &mut Context<Self>) {
789        self.masked = !self.masked;
790        cx.notify();
791    }
792
793    pub fn on_context_menu(
794        &mut self,
795        handler: Rc<
796            dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App),
797        >,
798    ) {
799        self.context_menu_handler = Some(handler);
800    }
801
802    /// Keeps the selection highlighted while this popup or one of its children has focus.
803    ///
804    /// This does not transfer keyboard focus or keep the caret visible. The
805    /// association holds only a weak focus handle. Pass `None` to clear it.
806    pub fn set_selection_focus(&mut self, focus: Option<FocusHandle>, cx: &mut Context<Self>) {
807        self.selection_focus = focus.as_ref().map(FocusHandle::downgrade);
808        cx.notify();
809    }
810
811    /// Whether the input or its associated selection popup has focus.
812    pub fn has_selection_focus(&self, window: &Window, cx: &App) -> bool {
813        self.focus_handle.is_focused(window)
814            || self
815                .selection_focus
816                .as_ref()
817                .and_then(WeakFocusHandle::upgrade)
818                .is_some_and(|focus| focus.is_focused(window) || focus.contains_focused(window, cx))
819    }
820
821    /// Build the engine. Each mode's own `new` sets its layout on top of this.
822    fn new_in_mode(window: &mut Window, cx: &mut Context<Self>) -> Self {
823        let focus_handle = cx.focus_handle().tab_stop(true);
824        let blink_cursor = cx.new(|_| BlinkCursor::new());
825        let undo_manager = UndoManager::new();
826
827        let _subscriptions = vec![
828            // Key bindings can consume events before on_key_down. Observe input
829            // before action dispatch so every keystroke resets the blink delay.
830            cx.intercept_keystrokes({
831                let focus_handle = focus_handle.clone();
832                let blink_cursor = blink_cursor.downgrade();
833                move |_, window, cx| {
834                    if focus_handle.is_focused(window) {
835                        _ = blink_cursor.update(cx, |cursor, cx| cursor.pause(cx));
836                    }
837                }
838            }),
839            // Observe the blink cursor to repaint the view when it changes.
840            cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
841            // Blink the cursor when the window is active, pause when it's not.
842            cx.observe_window_activation(window, |input, window, cx| {
843                if window.is_window_active() {
844                    let focus_handle = input.focus_handle.clone();
845                    if focus_handle.is_focused(window) {
846                        input.blink_cursor.update(cx, |blink_cursor, cx| {
847                            blink_cursor.start(cx);
848                        });
849                    }
850                }
851            }),
852            cx.on_focus(&focus_handle, window, Self::on_focus),
853            cx.on_blur(&focus_handle, window, Self::on_blur),
854        ];
855
856        let text_style = window.text_style();
857
858        Self {
859            extras: M::Extras::default(),
860            focus_handle: focus_handle.clone(),
861            selection_focus: None,
862            text: "".into(),
863            display_map: DisplayMap::new(text_style.font(), window.rem_size(), None),
864            search_session: super::SearchSession::default(),
865            search_activation_revision: 0,
866            searchable: false,
867            replaceable: true,
868            soft_wrap: true,
869            wrapping_indent: WrappingIndent::default(),
870            scroll_beyond_last_line: None,
871            cursor_surrounding_lines: None,
872            blink_cursor,
873            undo_manager,
874            inline_tokens: None,
875            pending_token: None,
876            replaying_history: false,
877            validated_token_edit: false,
878            document_revision: 0,
879            token_presentation: Default::default(),
880            token_bounds: Default::default(),
881            hovered_token: None,
882            pending_token_hover_exits: Vec::new(),
883            token_layout_cache: None,
884            pressed_token: None,
885            selections: Selections::default(),
886            selected_word_range: None,
887            ime_marked_range: None,
888            input_bounds: Bounds::default(),
889            selecting: false,
890            touch_selection: Default::default(),
891            disabled: false,
892            readonly: false,
893            text_align: TextAlign::Left,
894            masked: false,
895            clean_on_escape: false,
896            submit_on_enter: false,
897            input_action: None,
898            autocorrect: None,
899            autocapitalize: None,
900            show_whitespaces: false,
901            loading: false,
902            pattern: None,
903            validate: None,
904            number_step: Some(NumberStep::Fixed(1.)),
905            number_min: None,
906            number_max: None,
907            mode: LayoutMode::default(),
908            last_layout: None,
909            prepaint_caret_geometry: None,
910            last_bounds: None,
911            last_selected_range: None,
912            column_select_start: None,
913            last_cursor: None,
914            scroll_handle: ScrollHandle::new(),
915            scroll_bounce: crate::ScrollBounce::enabled_by_default(),
916            overscroll: Overscroll::default(),
917            scroll_size: gpui::size(px(0.), px(0.)),
918            editor_scrollbar_snapshot: Cell::new(None),
919            longest_line_width: Cell::new(None),
920            editor_paddings: Edges::default(),
921            deferred_scroll_offset: None,
922            placeholder: SharedString::default(),
923            mask_pattern: MaskPattern::default(),
924            mask_pattern_set: false,
925            editor_style: InputEditorStyle::default(),
926            projected_editor_style: InputEditorStyle::default(),
927            diagnostic_popover: None,
928            context_menu_handler: None,
929            pending_context_menu: None,
930            enable_context_menu: true,
931            completion_inserting: false,
932            overlay_action_handler: None,
933            silent_replace_text: false,
934            emit_events: true,
935            _subscriptions,
936            _pending_update: false,
937            cursor_line_end_affinity: false,
938            auto_scroll: AutoScroll::default(),
939        }
940    }
941
942    /// Sets whether the context menu that shows on right-click is enabled.
943    ///
944    /// The context menu is enabled by default. Disabling it turns off the
945    /// right-click menu entirely, including a custom menu set on the element.
946    /// A custom menu already replaces the built-in one, so keep this enabled
947    /// to show only your own items.
948    pub fn context_menu(mut self, enable: bool) -> Self {
949        self.enable_context_menu = enable;
950        self
951    }
952
953    pub fn set_context_menu_enabled(&mut self, enabled: bool) {
954        self.enable_context_menu = enabled;
955    }
956
957    /// Set whether search UI allows replacement, default is true.
958    #[doc(hidden)]
959    pub fn replaceable(mut self, allow: bool) -> Self {
960        self.replaceable = allow;
961        self
962    }
963
964    /// Set placeholder
965    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
966        self.placeholder = placeholder.into();
967        self
968    }
969
970    /// Set highlighter language for for [`LayoutMode::CodeEditor`] mode.
971    pub fn set_highlighter(
972        &mut self,
973        new_language: impl Into<SharedString>,
974        cx: &mut Context<Self>,
975    ) {
976        match &mut self.mode {
977            LayoutMode::CodeEditor {
978                language,
979                highlighter,
980                ..
981            } => {
982                language.set_name(new_language.into());
983                *highlighter.borrow_mut() = None;
984            }
985            _ => {}
986        }
987        self.refresh(cx);
988    }
989
990    fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
991        match &mut self.mode {
992            LayoutMode::CodeEditor { highlighter, .. } => {
993                *highlighter.borrow_mut() = None;
994            }
995            _ => {}
996        }
997        cx.notify();
998    }
999
1000    /// Install the parser/highlighter adapter used by code-editor mode.
1001    pub fn set_highlighter_factory(
1002        &mut self,
1003        factory: InputHighlighterFactory,
1004        cx: &mut Context<Self>,
1005    ) {
1006        self.mode.set_highlighter_factory(factory);
1007        self._pending_update = true;
1008        self.token_layout_cache = None;
1009        cx.notify();
1010    }
1011
1012    /// Install a default adapter without replacing an application-provided one.
1013    pub fn ensure_highlighter_factory(&mut self, factory: InputHighlighterFactory) {
1014        self.mode.ensure_highlighter_factory(factory);
1015    }
1016
1017    pub fn set_editor_style(&mut self, style: InputEditorStyle) {
1018        self.editor_style = style.clone();
1019        self.projected_editor_style = style;
1020    }
1021
1022    /// Set presentation padding for multi-line text and its scrollbar layout.
1023    #[doc(hidden)]
1024    pub fn set_editor_paddings(&mut self, paddings: Edges<Pixels>) {
1025        self.editor_paddings = paddings;
1026    }
1027
1028    pub fn apply_highlighter_fold_candidates(
1029        &mut self,
1030        candidates: Vec<crate::input::FoldRange>,
1031        cx: &mut Context<Self>,
1032    ) {
1033        if self.mode.is_folding() {
1034            self.display_map.set_fold_candidates(candidates);
1035        }
1036        cx.notify();
1037    }
1038
1039    #[inline]
1040    pub fn diagnostics(&self) -> Option<&DiagnosticSet> {
1041        self.mode.diagnostics()
1042    }
1043
1044    #[inline]
1045    pub fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
1046        self.mode.diagnostics_mut()
1047    }
1048
1049    /// Set placeholder
1050    pub fn set_placeholder(
1051        &mut self,
1052        placeholder: impl Into<SharedString>,
1053        _: &mut Window,
1054        cx: &mut Context<Self>,
1055    ) {
1056        self.placeholder = placeholder.into();
1057        cx.notify();
1058    }
1059
1060    /// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.
1061    ///
1062    /// Returns:
1063    ///
1064    /// - The index of the line (zero-based) containing the offset.
1065    /// - The index of the sub-line (zero-based) within the line containing the offset.
1066    /// - The position of the offset.
1067    pub(super) fn line_and_position_for_offset(
1068        &self,
1069        offset: usize,
1070    ) -> (usize, usize, Option<Point<Pixels>>) {
1071        let Some(last_layout) = &self.last_layout else {
1072            return (0, 0, None);
1073        };
1074        let line_height = last_layout.line_height;
1075
1076        let mut y_offset = last_layout.visible_top;
1077        for (vi, line) in last_layout.lines.iter().enumerate() {
1078            let prev_lines_offset = last_layout.visible_line_byte_offsets[vi];
1079            let local_offset = offset.saturating_sub(prev_lines_offset);
1080            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
1081                let sub_line_index = (pos.y / line_height) as usize;
1082                let adjusted_pos = point(pos.x + last_layout.line_number_width, pos.y + y_offset);
1083                return (vi, sub_line_index, Some(adjusted_pos));
1084            }
1085
1086            y_offset += line.size(line_height).height;
1087        }
1088        (0, 0, None)
1089    }
1090
1091    /// Set the value of the input field: plain text, or [`InputContent`] to
1092    /// restore text together with its inline tokens. Editing history is
1093    /// cleared and no [`InputEvent::Change`] is emitted.
1094    ///
1095    /// For single-line inputs the caret is placed at the end of the text while
1096    /// the view is scrolled back to the start, so a long value shows its
1097    /// beginning instead of its tail (matching HTML `<input>`). Multi-line
1098    /// inputs reset the selection to `0..0`.
1099    pub fn set_value(
1100        &mut self,
1101        value: impl Into<super::InputContent>,
1102        window: &mut Window,
1103        cx: &mut Context<Self>,
1104    ) {
1105        let content = value.into();
1106        self.inline_tokens = None;
1107        self.undo_manager.set_ignoring(true);
1108        self.emit_events = false;
1109        self.replace_text(content.text().clone(), window, cx);
1110        self.install_tokens(content);
1111        self.undo_manager.set_ignoring(false);
1112        self.emit_events = true;
1113
1114        self.reset_selection();
1115        self.reset_lsp_state();
1116        self.reset_scroll_to_start();
1117
1118        self.undo_manager.clear();
1119        cx.notify();
1120    }
1121
1122    /// Replace the entire text content while preserving undo history.
1123    ///
1124    /// Unlike [`set_value`](Self::set_value), this method records the
1125    /// replacement in the undo stack, allowing the user to undo/redo
1126    /// the change. The selection is placed at the end of the new text
1127    /// for single-line inputs, or cleared (0..0) for multi-line inputs.
1128    ///
1129    /// Use this when programmatically replacing the full text but the
1130    /// user should still be able to undo the operation — e.g. formatting.
1131    pub fn replace_all(
1132        &mut self,
1133        text: impl Into<SharedString>,
1134        window: &mut Window,
1135        cx: &mut Context<Self>,
1136    ) {
1137        self.replace_text(text, window, cx);
1138        self.reset_selection();
1139        self.reset_lsp_state();
1140        self.reset_scroll_to_start();
1141
1142        cx.notify();
1143    }
1144
1145    /// Perform `f` with the user-facing edit restrictions lifted.
1146    ///
1147    /// The `disabled` and `readonly` modes only reject the changes made by the
1148    /// user, the programmatic APIs must always be able to update the text.
1149    pub(super) fn with_edits_allowed(&mut self, f: impl FnOnce(&mut Self)) {
1150        let (was_disabled, was_readonly) = (self.disabled, self.readonly);
1151        (self.disabled, self.readonly) = (false, false);
1152        f(self);
1153        (self.disabled, self.readonly) = (was_disabled, was_readonly);
1154    }
1155
1156    /// Insert text at the current cursor position.
1157    ///
1158    /// And the cursor will be moved to the end of inserted text.
1159    pub fn insert(
1160        &mut self,
1161        text: impl Into<SharedString>,
1162        window: &mut Window,
1163        cx: &mut Context<Self>,
1164    ) {
1165        let text: SharedString = text.into();
1166        self.with_edits_allowed(|this| {
1167            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1168            let range_utf16 = this.range_to_utf16(&(this.cursor()..this.cursor()));
1169            this.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
1170            let end = this.active_selection().end;
1171            this.set_cursor_to(end);
1172        });
1173    }
1174
1175    /// Replace text at the current cursor position.
1176    ///
1177    /// And the cursor will be moved to the end of replaced text.
1178    pub fn replace(
1179        &mut self,
1180        text: impl Into<SharedString>,
1181        window: &mut Window,
1182        cx: &mut Context<Self>,
1183    ) {
1184        let text: SharedString = text.into();
1185        self.with_edits_allowed(|this| {
1186            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1187            this.replace_text_in_range_silent(None, &text, window, cx);
1188            let end = this.active_selection().end;
1189            this.set_cursor_to(end);
1190        });
1191    }
1192
1193    fn replace_text(
1194        &mut self,
1195        text: impl Into<SharedString>,
1196        window: &mut Window,
1197        cx: &mut Context<Self>,
1198    ) {
1199        let text: SharedString = text.into();
1200        self.with_edits_allowed(|this| {
1201            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1202            let range = 0..this.text.chars().map(|c| c.len_utf16()).sum();
1203            this.replace_text_in_range_silent(Some(range), &text, window, cx);
1204            this.reset_highlighter(cx);
1205        });
1206    }
1207
1208    fn reset_selection(&mut self) {
1209        self.selections.remove_all_but_active();
1210
1211        // For single-line inputs the caret is placed at the end of the text
1212        // (matching HTML `<input>`); multi-line inputs reset the selection to
1213        // `0..0`.
1214        if self.is_single_line() {
1215            let end = self.text.len();
1216            self.set_cursor_to(end);
1217        } else {
1218            self.active_selection_mut().clear();
1219        }
1220    }
1221
1222    fn reset_lsp_state(&mut self) {
1223        if self.is_code_editor() {
1224            self._pending_update = true;
1225            M::reset_language_features(self);
1226        }
1227    }
1228
1229    fn reset_scroll_to_start(&mut self) {
1230        // Move scroll to the start. For single-line the caret is at the end, so
1231        // override the cursor-follow scroll for the next painted frame to keep
1232        // the start visible; the deferred offset is consumed during that paint.
1233        self.scroll_handle.set_offset(point(px(0.), px(0.)));
1234        if self.is_single_line() {
1235            self.deferred_scroll_offset = Some(point(px(0.), px(0.)));
1236        }
1237    }
1238
1239    /// Set with disabled mode.
1240    ///
1241    /// See also: [`Self::set_disabled`].
1242    #[allow(unused)]
1243    pub(crate) fn disabled(mut self, disabled: bool) -> Self {
1244        self.disabled = disabled;
1245        self
1246    }
1247
1248    pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
1249        if self.disabled == disabled {
1250            return;
1251        }
1252
1253        self.disabled = disabled;
1254        if disabled {
1255            M::hide_context_menu(self, cx);
1256            M::clear_inline_completion(self, cx);
1257        }
1258        cx.notify();
1259    }
1260
1261    /// Set with read-only mode.
1262    ///
1263    /// Unlike [`Self::disabled`], a read-only input keeps the normal appearance,
1264    /// focus, cursor, selection and copy behavior, it only rejects any change
1265    /// of the text made by the user.
1266    ///
1267    /// See also: [`Self::set_readonly`].
1268    #[allow(unused)]
1269    pub(crate) fn readonly(mut self, readonly: bool) -> Self {
1270        self.readonly = readonly;
1271        self
1272    }
1273
1274    pub fn set_readonly(&mut self, readonly: bool, cx: &mut Context<Self>) {
1275        if self.readonly == readonly {
1276            return;
1277        }
1278
1279        self.readonly = readonly;
1280        if readonly {
1281            M::hide_context_menu(self, cx);
1282            M::clear_inline_completion(self, cx);
1283            self.search_session.replace_mode = false;
1284        }
1285        cx.notify();
1286    }
1287
1288    /// Returns true if the user is allowed to change the text.
1289    ///
1290    /// This is false when the input is `disabled` or `readonly`, the programmatic
1291    /// APIs (e.g.: [`Self::set_value`], [`Self::insert`]) are not limited by this.
1292    pub fn is_editable(&self) -> bool {
1293        !self.disabled && !self.readonly
1294    }
1295
1296    /// Set true to clear the input by pressing Escape key.
1297    ///
1298    /// Not on Android, where Escape is also the back button.
1299    pub fn clean_on_escape(mut self) -> Self {
1300        self.clean_on_escape = true;
1301        self
1302    }
1303
1304    pub fn set_clean_on_escape(&mut self, clean: bool) {
1305        self.clean_on_escape = clean;
1306    }
1307
1308    /// Set true to treat `Enter` as a submit action in multi-line mode,
1309    /// while `Shift+Enter` inserts a newline.
1310    ///
1311    /// Default is `false` (both `Enter` and `Shift+Enter` insert a newline).
1312    #[doc(hidden)]
1313    pub fn submit_on_enter(mut self, submit: bool) -> Self {
1314        self.submit_on_enter = submit;
1315        self
1316    }
1317
1318    pub fn set_submit_on_enter(&mut self, submit: bool, cx: &mut Context<Self>) {
1319        self.submit_on_enter = submit;
1320        cx.notify();
1321    }
1322
1323    /// Set the action a software keyboard shows on its enter key, like Search
1324    /// or Send. Pressing it still emits [`InputEvent::PressEnter`].
1325    ///
1326    /// Default is [`TextInputAction::Done`] for a single-line input (and a
1327    /// multi-line one that submits on enter), and [`TextInputAction::Enter`]
1328    /// (a line break) for a multi-line input.
1329    pub fn input_action(mut self, action: TextInputAction) -> Self {
1330        self.input_action = Some(action);
1331        self
1332    }
1333
1334    /// Set whether the platform may autocorrect the text and offer word
1335    /// suggestions.
1336    ///
1337    /// Default is `false`. Enable this explicitly for fields that accept prose;
1338    /// masking or revealing text does not change the setting.
1339    pub fn autocorrect(mut self, autocorrect: bool) -> Self {
1340        self.autocorrect = Some(autocorrect);
1341        self
1342    }
1343
1344    /// Set how a software keyboard capitalizes the text.
1345    ///
1346    /// Default is [`Autocapitalize::None`]. Enable capitalization explicitly for
1347    /// fields where it is appropriate, independently of autocorrection.
1348    pub fn autocapitalize(mut self, autocapitalize: Autocapitalize) -> Self {
1349        self.autocapitalize = Some(autocapitalize);
1350        self
1351    }
1352
1353    /// The text assistance a platform IME (a software keyboard) should give
1354    /// this input, from [`Self::input_action`], [`Self::autocorrect`] and
1355    /// [`Self::autocapitalize`] or the defaults they describe.
1356    pub fn text_input_configuration(&self) -> TextInputConfiguration {
1357        // Masking is a presentation choice, not a reliable content type: a
1358        // revealed password must not acquire text assistance. Keep assistance
1359        // opt-in for structured fields such as passwords, email and URLs.
1360        let autocorrect = self.autocorrect.unwrap_or(false);
1361        let input_action =
1362            self.input_action
1363                .unwrap_or(if self.is_multi_line() && !self.submit_on_enter {
1364                    TextInputAction::Enter
1365                } else {
1366                    TextInputAction::Done
1367                });
1368        TextInputConfiguration {
1369            autocorrect,
1370            autocapitalize: self.autocapitalize.unwrap_or(Autocapitalize::None),
1371            suggestions: autocorrect,
1372            input_action,
1373        }
1374    }
1375
1376    /// Set whether to show whitespace characters.
1377    #[doc(hidden)]
1378    pub fn show_whitespaces(mut self, show: bool) -> Self {
1379        self.show_whitespaces = show;
1380        self
1381    }
1382
1383    /// Update whether to show whitespace characters.
1384    pub fn set_show_whitespaces(&mut self, show: bool, _: &mut Window, cx: &mut Context<Self>) {
1385        self.show_whitespaces = show;
1386        cx.notify();
1387    }
1388
1389    /// Empty rows reserved below the last line of content ("scroll
1390    /// beyond last line"), code-editor mode only. Mirrors VSCode's
1391    /// `editor.scrollBeyondLastLine` / Zed's `scroll_beyond_last_line`.
1392    ///
1393    /// - `None` (default): half the viewport, floored at
1394    ///   [`BOTTOM_MARGIN_ROWS`] line-heights.
1395    /// - `Some(0)`: no trailing space; the cursor sits flush with the
1396    ///   last row at scroll-max.
1397    /// - `Some(n)`: exactly `n` rows.
1398    pub fn scroll_beyond_last_line(mut self, rows: Option<usize>) -> Self {
1399        self.scroll_beyond_last_line = rows;
1400        self
1401    }
1402
1403    /// Update [`Self::scroll_beyond_last_line`] after construction.
1404    pub fn set_scroll_beyond_last_line(
1405        &mut self,
1406        rows: Option<usize>,
1407        _: &mut Window,
1408        cx: &mut Context<Self>,
1409    ) {
1410        if self.scroll_beyond_last_line == rows {
1411            return;
1412        }
1413        self.scroll_beyond_last_line = rows;
1414        cx.notify();
1415    }
1416
1417    /// Minimum number of lines the cursor is kept clear of the viewport's
1418    /// top/bottom edge before auto-scroll engages. Mirrors VSCode's
1419    /// `editor.cursorSurroundingLines` / Zed's `vertical_scroll_margin`.
1420    /// Orthogonal to [`Self::scroll_beyond_last_line`], which sizes the
1421    /// empty region; this controls the cursor's resting distance from the
1422    /// edge.
1423    ///
1424    /// - `None` (default): [`BOTTOM_MARGIN_ROWS`] lines, falling back to
1425    ///   one line on small viewports.
1426    /// - `Some(n)`: requests `n` lines, clamped to half the viewport.
1427    ///
1428    /// In a constrained viewport, edge clearance yields to the cursor's
1429    /// complete visual row so surrounding space cannot clip the text being edited.
1430    pub fn cursor_surrounding_lines(mut self, lines: Option<usize>) -> Self {
1431        self.cursor_surrounding_lines = lines;
1432        self
1433    }
1434
1435    /// Update [`Self::cursor_surrounding_lines`] after construction.
1436    pub fn set_cursor_surrounding_lines(
1437        &mut self,
1438        lines: Option<usize>,
1439        _: &mut Window,
1440        cx: &mut Context<Self>,
1441    ) {
1442        if self.cursor_surrounding_lines == lines {
1443            return;
1444        }
1445        self.cursor_surrounding_lines = lines;
1446        cx.notify();
1447    }
1448
1449    /// Set the default value of the input field.
1450    pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {
1451        let text: SharedString = value.into();
1452        self.text = Rope::from(self.normalize_input(&text).as_ref());
1453        if let Some(diagnostics) = self.mode.diagnostics_mut() {
1454            diagnostics.reset(&self.text)
1455        }
1456        // Note: We can't call display_map.set_text here because it needs cx.
1457        // The text will be set during prepare_if_need in element.rs
1458        self._pending_update = true;
1459        self
1460    }
1461
1462    /// Return the value of the input field as an owned string.
1463    ///
1464    /// The string is materialized on each call. See [`Self::text`] for the
1465    /// [`Rope`] the state owns, which is borrowed and costs nothing to read.
1466    pub fn value(&self) -> SharedString {
1467        SharedString::new(self.text.to_string())
1468    }
1469
1470    /// Return the portion of the value within the input field that
1471    /// is selected by the user, as an owned string.
1472    ///
1473    /// The string is materialized on each call. See [`Self::selected_text`]
1474    /// for the same selection borrowed out of the [`Rope`] the state owns.
1475    pub fn selected_value(&self) -> SharedString {
1476        SharedString::new(self.selected_text().to_string())
1477    }
1478
1479    /// Return the value without mask.
1480    pub fn unmask_value(&self) -> SharedString {
1481        self.mask_pattern.unmask(&self.text.to_string()).into()
1482    }
1483
1484    /// Kept so existing render paths keep compiling.
1485    ///
1486    /// Configuration used to be collected by a facade and applied here; the
1487    /// state now configures itself, so this does nothing and can be deleted at
1488    /// the call site.
1489    #[doc(hidden)]
1490    pub fn prepare(&mut self, _: &mut Window, _: &mut Context<Self>) {}
1491
1492    /// Return the text [`Rope`] of the input field.
1493    ///
1494    /// Borrowed from the state, so reading even a large document copies
1495    /// nothing. See [`Self::value`] when an owned string is wanted.
1496    pub fn text(&self) -> &Rope {
1497        &self.text
1498    }
1499
1500    /// Return the (0-based) [`Position`] of the cursor.
1501    pub fn cursor_position(&self) -> Position {
1502        let offset = self.cursor();
1503        self.text.offset_to_position(offset)
1504    }
1505
1506    /// Set (0-based) [`Position`] of the cursor.
1507    ///
1508    /// This will move the cursor to the specified line and column, and update the selection range.
1509    pub fn set_cursor_position(
1510        &mut self,
1511        position: impl Into<Position>,
1512        window: &mut Window,
1513        cx: &mut Context<Self>,
1514    ) {
1515        let position: Position = position.into();
1516        let offset = self.text.position_to_offset(&position);
1517
1518        self.move_to(offset, None, cx);
1519        self.update_preferred_column();
1520        self.focus(window, cx);
1521    }
1522
1523    /// Focus the input field.
1524    pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1525        self.focus_handle.focus(window, cx);
1526        self.blink_cursor.update(cx, |cursor, cx| {
1527            cursor.start(cx);
1528        });
1529    }
1530
1531    /// Refresh the input, so the next render re-runs syntax highlighting and
1532    /// the LSP providers, not just a redraw.
1533    ///
1534    /// Assigning the `lsp` providers (or other render-affecting state) at
1535    /// runtime does not take effect until the text next changes. Call this
1536    /// afterwards to force the refresh on the next render.
1537    ///
1538    /// ```ignore
1539    /// input.update(cx, |state, cx| {
1540    ///     state.extras.lsp.hover_provider = Some(provider);
1541    ///     state.refresh(cx);
1542    /// });
1543    /// ```
1544    pub fn refresh(&mut self, cx: &mut Context<Self>) {
1545        self._pending_update = true;
1546        cx.notify();
1547    }
1548
1549    pub(super) fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
1550        self.undo_manager.break_transaction_coalescing();
1551        self.select_all_cursors_to(|s, sel| s.previous_boundary(sel.cursor_offset()), cx);
1552    }
1553
1554    pub(super) fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
1555        self.select_all_cursors_to(|s, sel| s.next_boundary(sel.cursor_offset()), cx);
1556    }
1557
1558    pub(super) fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
1559        if self.is_single_line() {
1560            return;
1561        }
1562        self.select_all_cursors_to_with_affinity(
1563            |s, sel| {
1564                s.vertical_selection_target(
1565                    sel.cursor_offset(),
1566                    sel.column_anchor,
1567                    s.line_end_affinity_for(sel),
1568                    -1,
1569                )
1570            },
1571            true,
1572            cx,
1573        );
1574    }
1575
1576    pub(super) fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
1577        if self.is_single_line() {
1578            return;
1579        }
1580        self.select_all_cursors_to_with_affinity(
1581            |s, sel| {
1582                s.vertical_selection_target(
1583                    sel.cursor_offset(),
1584                    sel.column_anchor,
1585                    s.line_end_affinity_for(sel),
1586                    1,
1587                )
1588            },
1589            true,
1590            cx,
1591        );
1592    }
1593
1594    pub(super) fn on_action_select_all(
1595        &mut self,
1596        _: &SelectAll,
1597        window: &mut Window,
1598        cx: &mut Context<Self>,
1599    ) {
1600        self.select_all(window, cx);
1601    }
1602
1603    pub(super) fn select_to_start(
1604        &mut self,
1605        _: &SelectToStart,
1606        _: &mut Window,
1607        cx: &mut Context<Self>,
1608    ) {
1609        self.undo_manager.break_transaction_coalescing();
1610        self.select_all_cursors_to(|_, _| 0, cx);
1611    }
1612
1613    pub(super) fn select_to_end(
1614        &mut self,
1615        _: &SelectToEnd,
1616        _: &mut Window,
1617        cx: &mut Context<Self>,
1618    ) {
1619        self.undo_manager.break_transaction_coalescing();
1620        let end = self.text.len();
1621        self.select_all_cursors_to(move |_, _| end, cx);
1622    }
1623
1624    pub(super) fn select_to_start_of_line(
1625        &mut self,
1626        _: &SelectToStartOfLine,
1627        _: &mut Window,
1628        cx: &mut Context<Self>,
1629    ) {
1630        self.undo_manager.break_transaction_coalescing();
1631        self.select_all_cursors_to(
1632            |s, sel| s.start_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1633            cx,
1634        );
1635    }
1636
1637    pub(super) fn select_to_end_of_line(
1638        &mut self,
1639        _: &SelectToEndOfLine,
1640        _: &mut Window,
1641        cx: &mut Context<Self>,
1642    ) {
1643        self.undo_manager.break_transaction_coalescing();
1644        self.select_all_cursors_to_with_affinity(
1645            |s, sel| {
1646                (
1647                    s.end_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1648                    true,
1649                )
1650            },
1651            false,
1652            cx,
1653        );
1654    }
1655
1656    pub(super) fn select_to_previous_word(
1657        &mut self,
1658        _: &SelectToPreviousWordStart,
1659        _: &mut Window,
1660        cx: &mut Context<Self>,
1661    ) {
1662        self.undo_manager.break_transaction_coalescing();
1663        self.select_all_cursors_to(
1664            |s, sel| s.previous_start_of_word_at(sel.cursor_offset()),
1665            cx,
1666        );
1667    }
1668
1669    pub(super) fn select_to_next_word(
1670        &mut self,
1671        _: &SelectToNextWordEnd,
1672        _: &mut Window,
1673        cx: &mut Context<Self>,
1674    ) {
1675        self.undo_manager.break_transaction_coalescing();
1676        self.select_all_cursors_to(|s, sel| s.next_end_of_word_at(sel.cursor_offset()), cx);
1677    }
1678
1679    /// Return the start offset of the previous word.
1680    /// Return the previous start offset of the word before `offset`.
1681    pub(super) fn previous_start_of_word_at(&self, offset: usize) -> usize {
1682        if self.masked {
1683            // The mask replaces every character, so the displayed text has no
1684            // word boundaries to move or delete by. Collapse the word to the
1685            // whole text.
1686            return 0;
1687        }
1688
1689        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1690        // FIXME: Avoid to_string
1691        let left_part = self.text.slice(0..offset).to_string();
1692
1693        let target = UnicodeSegmentation::split_word_bound_indices(left_part.as_str())
1694            .rfind(|(_, s)| !s.trim_start().is_empty())
1695            .map(|(i, _)| i)
1696            .unwrap_or(0);
1697        self.token_boundary(target, Bias::Left)
1698    }
1699
1700    /// Return the next end offset of the word after `offset`.
1701    pub(super) fn next_end_of_word_at(&self, offset: usize) -> usize {
1702        if self.masked {
1703            // See `previous_start_of_word_at`.
1704            return self.text.len();
1705        }
1706
1707        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1708        let right_part = self.text.slice(offset..self.text.len()).to_string();
1709
1710        let target = UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
1711            .find(|(_, s)| !s.trim_start().is_empty())
1712            .map(|(i, s)| offset + i + s.len())
1713            .unwrap_or(self.text.len());
1714        self.token_boundary(target, Bias::Right)
1715    }
1716
1717    /// Get start of line byte offset for the given `offset`.
1718    ///
1719    /// When soft wrap is active, first press goes to visual line start,
1720    /// second press (already at visual start) goes to logical line start.
1721    pub(super) fn start_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1722        if self.is_single_line() {
1723            return 0;
1724        }
1725
1726        let row = self.text.offset_to_point(offset).row;
1727        let logical_start = self.text.line_start_offset(row);
1728
1729        if self.soft_wrap && self.is_code_editor() {
1730            let wrap_point = self
1731                .display_map
1732                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1733            if let Some(line) = self.display_map.line(row)
1734                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1735            {
1736                let visual_start = logical_start + range.start;
1737                if offset != visual_start {
1738                    return visual_start;
1739                }
1740            }
1741        }
1742
1743        logical_start
1744    }
1745
1746    /// Get end of line byte offset for the given `offset`.
1747    ///
1748    /// When soft wrap is active, first press goes to visual line end,
1749    /// second press (already at visual end) goes to logical line end.
1750    pub(super) fn end_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1751        if self.is_single_line() {
1752            return self.text.len();
1753        }
1754
1755        let row = self.text.offset_to_point(offset).row;
1756        let logical_start = self.text.line_start_offset(row);
1757        let logical_end = self.text.line_end_offset(row);
1758
1759        if self.soft_wrap && self.is_code_editor() {
1760            // Use the row the caret is drawn on: at a wrap boundary the raw offset would name
1761            // the next row, and a second End press would keep walking down instead of falling
1762            // through to the logical line end.
1763            let wrap_point = self
1764                .display_map
1765                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1766            if let Some(line) = self.display_map.line(row)
1767                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1768            {
1769                let visual_end = logical_start + range.end;
1770                if offset != visual_end {
1771                    return visual_end;
1772                }
1773            }
1774        }
1775
1776        logical_end
1777    }
1778
1779    /// Get indent string of next line.
1780    ///
1781    /// To get current and next line indent, to return more depth one.
1782    pub(super) fn indent_of_next_line(&mut self) -> String {
1783        self.indent_of_next_line_at(self.cursor())
1784    }
1785
1786    /// Get indent string of the next line, relative to the given `offset`.
1787    pub(super) fn indent_of_next_line_at(&mut self, offset: usize) -> String {
1788        if self.is_single_line() {
1789            return "".into();
1790        }
1791
1792        let mut current_indent = String::new();
1793        let mut next_indent = String::new();
1794        let line_end_affinity = self.line_end_affinity_at(offset);
1795        let current_line_start_pos = self.start_of_line_at(offset, line_end_affinity);
1796        let next_line_start_pos = self.end_of_line_at(offset, line_end_affinity);
1797        for c in self.text.slice(current_line_start_pos..).chars() {
1798            if !c.is_whitespace() {
1799                break;
1800            }
1801            if c == '\n' || c == '\r' {
1802                break;
1803            }
1804            current_indent.push(c);
1805        }
1806
1807        for c in self.text.slice(next_line_start_pos..).chars() {
1808            if !c.is_whitespace() {
1809                break;
1810            }
1811            if c == '\n' || c == '\r' {
1812                break;
1813            }
1814            next_indent.push(c);
1815        }
1816
1817        if next_indent.len() > current_indent.len() {
1818            return next_indent;
1819        }
1820
1821        // Smart indent: one extra level after lines ending with a trigger.
1822        // Skipped inside strings and comments: the trigger is literal text.
1823        if let Some(rules) = self.mode.language_config() {
1824            if self.mode.is_smart_indent()
1825                && M::editing_syntax_context(self, offset) == crate::input::SyntaxContext::Code
1826            {
1827                let line_before: String = self
1828                    .text
1829                    .slice(current_line_start_pos..offset)
1830                    .chars()
1831                    .collect();
1832                if rules.opens_indent(line_before.trim_end()) {
1833                    let tab = self.mode.tab_size();
1834                    if tab.hard_tabs {
1835                        current_indent.push('\t');
1836                    } else {
1837                        for _ in 0..tab.tab_size {
1838                            current_indent.push(' ');
1839                        }
1840                    }
1841                } else {
1842                    let line_after = self.text.slice(offset..next_line_start_pos).to_string();
1843                    if rules.closes_indent(&line_after) {
1844                        let tab = self.mode.tab_size();
1845                        if current_indent.ends_with('\t') {
1846                            current_indent.pop();
1847                        } else {
1848                            for _ in 0..tab.tab_size {
1849                                if !current_indent.ends_with(' ') {
1850                                    break;
1851                                }
1852                                current_indent.pop();
1853                            }
1854                        }
1855                    }
1856                }
1857            }
1858        }
1859
1860        current_indent
1861    }
1862
1863    /// Delete every selection as one batch. Collapsed cursors are first
1864    /// expanded to a deletion range by `collapsed_target` and non-empty
1865    /// selections delete their own range.
1866    ///
1867    /// `collapsed_intent` is the intent to record when every cursor is
1868    /// collapsed, which is what makes a run of single-character deletes undo
1869    /// as one gesture. Deleting a real selection is always atomic.
1870    fn delete_selections(
1871        &mut self,
1872        silent: bool,
1873        collapsed_intent: EditIntent,
1874        mut collapsed_target: impl FnMut(&mut Self, usize) -> Range<usize>,
1875        window: &mut Window,
1876        cx: &mut Context<Self>,
1877    ) {
1878        if !self.is_editable() {
1879            return;
1880        }
1881        let cursors: Vec<CursorSelection> = self.selections.iter().copied().collect();
1882        let intent = if cursors.iter().all(|sel| sel.is_empty()) {
1883            collapsed_intent
1884        } else {
1885            EditIntent::Atomic
1886        };
1887        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(cursors.len());
1888        for sel in &cursors {
1889            let range = if sel.is_empty() {
1890                collapsed_target(self, sel.cursor_offset())
1891            } else {
1892                sel.start..sel.end
1893            };
1894            let (start, end) = (range.start.min(range.end), range.start.max(range.end));
1895            let mut selection = *sel;
1896            selection.start = start;
1897            selection.end = end;
1898            new_selections.push(selection);
1899        }
1900        // Capture the user's selections before expanding or merging deletion
1901        // ranges. The edit ranges cannot reconstruct their original carets.
1902        self.undo_manager.begin_transaction_with(intent);
1903        self.undo_manager
1904            .record_selections(cursors.clone(), cursors.clone());
1905        self.selections.replace_all(new_selections);
1906        self.undo_manager.set_pending_intent(intent);
1907
1908        let was_silent = self.silent_replace_text;
1909        self.silent_replace_text = silent;
1910        self.replace_text_in_range(None, "", window, cx);
1911        self.silent_replace_text = was_silent;
1912        self.undo_manager
1913            .record_selections(cursors, self.selections.iter().copied().collect());
1914        self.undo_manager.commit_transaction();
1915        self.pause_blink_cursor(cx);
1916    }
1917
1918    /// Decide pairing against the pre-edit text, before an opening quote can
1919    /// change its own syntax context. Ordinary characters never query syntax.
1920    fn auto_close_target(&self, range: &Range<usize>, text: &str) -> Option<(usize, SharedString)> {
1921        if self.silent_replace_text
1922            || self.ime_marked_range.is_some()
1923            || !self.selections.is_single()
1924            || !self.active_selection().is_empty()
1925            || !range.is_empty()
1926            || range.start != self.cursor()
1927            || text.chars().count() != 1
1928            || !self.mode.is_auto_close()
1929        {
1930            return None;
1931        }
1932        let rules = self.mode.language_config()?;
1933        if self
1934            .text
1935            .chars_at(range.start)
1936            .next()
1937            .is_some_and(|c| !c.is_whitespace() && !rules.auto_close_before.contains(c))
1938        {
1939            return None;
1940        }
1941        for (open, close, not_in) in rules.closing_pairs() {
1942            let Some(prefix) = open.strip_suffix(text) else {
1943                continue;
1944            };
1945            if !self.text_before_matches(range.start, prefix) {
1946                continue;
1947            }
1948            let start = range.start - prefix.len();
1949            if open == close
1950                && (self.is_escaped_at(start)
1951                    || self
1952                        .text
1953                        .chars_at(start)
1954                        .reversed()
1955                        .next()
1956                        .is_some_and(|c| c.is_alphanumeric() || c == '_'))
1957            {
1958                continue;
1959            }
1960            if not_in.contains(&M::editing_syntax_context(self, start)) {
1961                continue;
1962            }
1963            return Some((open.len(), close.into()));
1964        }
1965        None
1966    }
1967
1968    fn text_before_matches(&self, offset: usize, text: &str) -> bool {
1969        offset >= text.len()
1970            && self.text.is_char_boundary(offset - text.len())
1971            && self.text.slice(offset - text.len()..offset) == text
1972    }
1973
1974    fn text_after_matches(&self, offset: usize, text: &str) -> bool {
1975        offset + text.len() <= self.text.len()
1976            && self.text.is_char_boundary(offset + text.len())
1977            && self.text.slice(offset..offset + text.len()) == text
1978    }
1979
1980    fn is_escaped_at(&self, offset: usize) -> bool {
1981        self.text
1982            .chars_at(offset)
1983            .reversed()
1984            .take_while(|c| *c == '\\')
1985            .count()
1986            % 2
1987            == 1
1988    }
1989
1990    /// Cursor target when a typed closer should skip over an existing one.
1991    ///
1992    /// Returns `Some(offset)` when `new_text` is a single closer from the
1993    /// active [`LanguageConfig`](crate::input::language_config::LanguageConfig) that already follows the collapsed cursor and is
1994    /// not escaped. The caller then moves the cursor without editing text or
1995    /// history. `None` means insert normally.
1996    fn skip_over_target(&self, new_text: &str) -> Option<usize> {
1997        if !self.mode.is_auto_close() || new_text.chars().count() != 1 {
1998            return None;
1999        }
2000        let rules = self.mode.language_config()?;
2001        let cursor = self.cursor();
2002        for (open, close, not_in) in rules.closing_pairs() {
2003            for (index, _) in close.char_indices() {
2004                if !close[index..].starts_with(new_text)
2005                    || !self.text_before_matches(cursor, &close[..index])
2006                    || !self.text_after_matches(cursor, &close[index..])
2007                {
2008                    continue;
2009                }
2010                let context = M::editing_syntax_context(self, cursor);
2011                let generated =
2012                    self.mode
2013                        .auto_closed_pairs()
2014                        .contains_closer(cursor, index, close.len());
2015                if !generated
2016                    && not_in.contains(&context)
2017                    && !(context == crate::input::SyntaxContext::String && open == close)
2018                {
2019                    continue;
2020                }
2021                if self.is_escaped_at(cursor - index) {
2022                    continue;
2023                }
2024                return Some(cursor + new_text.len());
2025            }
2026        }
2027        None
2028    }
2029
2030    pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
2031        // Nothing to delete at the start of the text. Propagate so an ancestor
2032        // (e.g. a command palette navigating back a level) can act on it.
2033        // This is harmless when nothing upstream is bound to backspace. With multiple
2034        // cursors the others can still delete, so only the lone-cursor case
2035        // propagates.
2036        if self.selections.is_single() && self.active_selection().is_empty() && self.cursor() == 0 {
2037            cx.propagate();
2038            return;
2039        }
2040
2041        // Pair deletion follows automatic closing rules, independently of
2042        // structural brackets used by Enter.
2043        if self.mode.is_auto_close()
2044            && self.selections.is_single()
2045            && self.active_selection().is_empty()
2046        {
2047            let off = self.cursor();
2048            let deletion = self.mode.language_config().and_then(|rules| {
2049                rules.closing_pairs().find_map(|(open, close, not_in)| {
2050                    if !self.text_before_matches(off, open) || !self.text_after_matches(off, close)
2051                    {
2052                        return None;
2053                    }
2054                    let start = off - open.len();
2055                    let generated = self
2056                        .mode
2057                        .auto_closed_pairs()
2058                        .contains(start..off, off..off + close.len());
2059                    if self.is_escaped_at(start)
2060                        || (!generated && not_in.contains(&M::editing_syntax_context(self, start)))
2061                    {
2062                        return None;
2063                    }
2064                    Some(start..off + close.len())
2065                })
2066            });
2067            if let Some(range) = deletion {
2068                let utf16 = self.range_to_utf16(&range);
2069                self.replace_text_in_range_silent(Some(utf16), "", window, cx);
2070                return;
2071            }
2072        }
2073
2074        self.delete_selections(
2075            false,
2076            EditIntent::Backspace,
2077            |s, offset| s.previous_boundary(offset)..offset,
2078            window,
2079            cx,
2080        );
2081    }
2082
2083    pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
2084        self.delete_selections(
2085            false,
2086            EditIntent::DeleteForward,
2087            |s, offset| offset..s.next_boundary(offset),
2088            window,
2089            cx,
2090        );
2091    }
2092
2093    pub(super) fn delete_to_beginning_of_line(
2094        &mut self,
2095        _: &DeleteToBeginningOfLine,
2096        window: &mut Window,
2097        cx: &mut Context<Self>,
2098    ) {
2099        self.delete_selections(
2100            true,
2101            EditIntent::Atomic,
2102            |s, offset| {
2103                let mut start = s.start_of_line_at(offset, s.line_end_affinity_at(offset));
2104                if start == offset {
2105                    start = start.saturating_sub(1);
2106                }
2107                start..offset
2108            },
2109            window,
2110            cx,
2111        );
2112    }
2113
2114    pub(super) fn delete_to_end_of_line(
2115        &mut self,
2116        _: &DeleteToEndOfLine,
2117        window: &mut Window,
2118        cx: &mut Context<Self>,
2119    ) {
2120        self.delete_selections(
2121            true,
2122            EditIntent::Atomic,
2123            |s, offset| {
2124                let mut end = s.end_of_line_at(offset, s.line_end_affinity_at(offset));
2125                if end == offset {
2126                    end = (end + 1).clamp(0, s.text.len());
2127                }
2128                offset..end
2129            },
2130            window,
2131            cx,
2132        );
2133    }
2134
2135    pub(super) fn delete_previous_word(
2136        &mut self,
2137        _: &DeleteToPreviousWordStart,
2138        window: &mut Window,
2139        cx: &mut Context<Self>,
2140    ) {
2141        self.delete_selections(
2142            true,
2143            EditIntent::Atomic,
2144            |s, offset| s.previous_start_of_word_at(offset)..offset,
2145            window,
2146            cx,
2147        );
2148    }
2149
2150    pub(super) fn delete_next_word(
2151        &mut self,
2152        _: &DeleteToNextWordEnd,
2153        window: &mut Window,
2154        cx: &mut Context<Self>,
2155    ) {
2156        self.delete_selections(
2157            true,
2158            EditIntent::Atomic,
2159            |s, offset| offset..s.next_end_of_word_at(offset),
2160            window,
2161            cx,
2162        );
2163    }
2164
2165    pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {
2166        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2167            return;
2168        }
2169
2170        // Clear inline completion on enter (user chose not to accept it)
2171        if M::has_inline_completion(self) {
2172            M::clear_inline_completion(self, cx);
2173        }
2174
2175        // In multi-line mode with `submit_on_enter` enabled, a plain `Enter`
2176        // (without Shift) is treated as submit: propagate the action and emit
2177        // PressEnter without inserting a newline. `Shift+Enter` still inserts
2178        // a newline.
2179        let insert_newline = self.is_multi_line() && (!self.submit_on_enter || action.shift);
2180
2181        if insert_newline {
2182            if !self.selections.is_single() {
2183                // Insert a newline (with per-line indent) at every cursor.
2184                self.selections.merge_overlapping();
2185                let selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
2186                let mut edits: Vec<(Range<usize>, String)> = Vec::with_capacity(selections.len());
2187                for sel in &selections {
2188                    let indent = if self.is_code_editor() {
2189                        self.indent_of_next_line_at(sel.cursor_offset())
2190                    } else {
2191                        String::new()
2192                    };
2193                    edits.push((sel.start..sel.end, format!("\n{}", indent)));
2194                }
2195                edits.sort_by_key(|(range, _)| range.start);
2196                self.replace_text_in_ranges(&edits, window, cx);
2197                self.pause_blink_cursor(cx);
2198            } else {
2199                // Bracket split: Enter between `{|}` produces `{\n  \n}`
2200                // with the cursor on the middle line. Controlled by smart_indent,
2201                // independently of automatic closing.
2202                // Skipped inside strings and comments: splitting there would
2203                // restructure literal text.
2204                let mut split = false;
2205                if self.is_code_editor() && self.active_selection().is_empty() {
2206                    if let Some(rules) = self.mode.language_config() {
2207                        if self.mode.is_smart_indent()
2208                            && M::editing_syntax_context(self, self.cursor())
2209                                == crate::input::SyntaxContext::Code
2210                        {
2211                            let off = self.cursor();
2212                            let is_pair = rules.brackets.iter().any(|p| {
2213                                !p.open.is_empty()
2214                                    && !p.close.is_empty()
2215                                    && p.open != p.close
2216                                    && self.text_before_matches(off, p.open.as_ref())
2217                                    && self.text_after_matches(off, p.close.as_ref())
2218                            });
2219                            if is_pair {
2220                                // Base indent: leading whitespace only.
2221                                let line_end_affinity = self.line_end_affinity_at(off);
2222                                let line_start = self.start_of_line_at(off, line_end_affinity);
2223                                let mut indent = String::new();
2224                                for c in self.text.slice(line_start..).chars() {
2225                                    if !c.is_whitespace() || c == '\n' || c == '\r' {
2226                                        break;
2227                                    }
2228                                    indent.push(c);
2229                                }
2230                                // Extra level only with smart indent enabled.
2231                                let mut inner = indent.clone();
2232                                if self.mode.is_smart_indent() {
2233                                    let tab = self.mode.tab_size();
2234                                    if tab.hard_tabs {
2235                                        inner.push('\t');
2236                                    } else {
2237                                        for _ in 0..tab.tab_size {
2238                                            inner.push(' ');
2239                                        }
2240                                    }
2241                                }
2242                                let new_line_text = format!("\n{inner}\n{indent}");
2243                                self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2244                                self.set_cursor_to(off + 1 + inner.len());
2245                                self.update_preferred_column();
2246                                let cursors = self.selections.iter().copied().collect::<Vec<_>>();
2247                                self.undo_manager
2248                                    .record_selections(cursors.clone(), cursors);
2249                                self.pause_blink_cursor(cx);
2250                                split = true;
2251                            }
2252                        }
2253                    }
2254                }
2255                if !split {
2256                    // Get current line indent
2257                    let indent = if self.is_code_editor() {
2258                        self.indent_of_next_line()
2259                    } else {
2260                        "".to_string()
2261                    };
2262
2263                    // Add newline and indent
2264                    let new_line_text = format!("\n{}", indent);
2265                    self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2266                    self.pause_blink_cursor(cx);
2267                }
2268            }
2269        } else {
2270            // Single line input or submit-on-enter: just emit the event
2271            // (e.g.: in a dialog to confirm, or a chat textarea to send).
2272            self.undo_manager.break_transaction_coalescing();
2273            cx.propagate();
2274        }
2275
2276        cx.emit(InputEvent::PressEnter {
2277            secondary: action.secondary,
2278            shift: action.shift,
2279        });
2280    }
2281
2282    pub fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2283        self.replace_text("", window, cx);
2284        self.set_selection(0, 0);
2285        self.scroll_to(0, None, cx);
2286    }
2287
2288    pub(super) fn escape(&mut self, action: &Escape, window: &mut Window, cx: &mut Context<Self>) {
2289        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2290            return;
2291        }
2292
2293        // Escape also dismisses a request whose popup has not arrived yet.
2294        M::hide_context_menu(self, cx);
2295
2296        // Collapse extra cursors back to the active one first.
2297        if !self.selections.is_single() {
2298            self.undo_manager.break_transaction_coalescing();
2299            self.selections.remove_all_but_active();
2300            cx.notify();
2301            return;
2302        }
2303
2304        // Clear inline completion on escape
2305        if M::has_inline_completion(self) {
2306            M::clear_inline_completion(self, cx);
2307            return; // Consume the escape, don't propagate
2308        }
2309        M::clear_inline_completion(self, cx);
2310
2311        // The handles and the edit menu are the topmost surface to dismiss.
2312        if self.touch_selection().is_some() {
2313            self.dismiss_touch_selection(cx);
2314            return;
2315        }
2316
2317        if self.ime_marked_range.is_some() {
2318            self.unmark_text(window, cx);
2319        }
2320
2321        // Android delivers its back button as escape, and back leaves a field's
2322        // text alone: it closes what is open or navigates.
2323        if self.clean_on_escape && !cfg!(target_os = "android") {
2324            return self.clean(window, cx);
2325        }
2326
2327        cx.propagate();
2328    }
2329
2330    /// Show the right-click context menu as a native OS menu.
2331    pub(crate) fn handle_right_click_menu(
2332        &mut self,
2333        position: Point<Pixels>,
2334        offset: usize,
2335        window: &mut Window,
2336        cx: &mut Context<Self>,
2337    ) {
2338        if self.disabled {
2339            return;
2340        }
2341        if crate::GlobalState::is_in_deferred_context(cx) {
2342            return;
2343        }
2344
2345        if !self.active_selection().contains(offset) {
2346            self.move_to(offset, None, cx);
2347        }
2348
2349        if self.is_code_editor() {
2350            M::on_hover_definition(self, offset, window, cx);
2351        }
2352
2353        if let Some(handler) = self.context_menu_handler.clone() {
2354            let capabilities = self.context_menu_capabilities();
2355            let input = cx.weak_entity();
2356            // The handler runs with the input released, so a custom builder can
2357            // read it (cursor, selection, text) to build its items.
2358            window.defer(cx, move |window, cx| {
2359                if input.upgrade().is_none() {
2360                    return;
2361                }
2362                let previous_focus = window.focused(cx);
2363                handler(NativeMenu::new(), capabilities, position, window, cx);
2364                // A drawn menu takes focus synchronously; native menus leave it
2365                // on the input. Capture it here rather than in the handler.
2366                input
2367                    .update(cx, |this, cx| {
2368                        if window.focused(cx) == previous_focus
2369                            && !this.focus_handle.is_focused(window)
2370                            && this.has_selection_focus(window, cx)
2371                        {
2372                            // A custom handler can reuse an already-focused popup.
2373                            return;
2374                        }
2375                        let menu_focus = window.focused(cx).filter(|focus| {
2376                            Some(focus) != previous_focus.as_ref() && *focus != this.focus_handle
2377                        });
2378                        this.set_selection_focus(menu_focus, cx);
2379                    })
2380                    .ok();
2381            });
2382        }
2383    }
2384
2385    pub(super) fn add_cursor_above(
2386        &mut self,
2387        _: &AddCursorAbove,
2388        _: &mut Window,
2389        cx: &mut Context<Self>,
2390    ) {
2391        self.add_cursor_vertical(-1, cx);
2392    }
2393
2394    pub(super) fn add_cursor_below(
2395        &mut self,
2396        _: &AddCursorBelow,
2397        _: &mut Window,
2398        cx: &mut Context<Self>,
2399    ) {
2400        self.add_cursor_vertical(1, cx);
2401    }
2402
2403    /// Add a new cursor one display line above (`move_lines < 0`) or below each
2404    /// existing cursor, preserving the column. Cursors that would not move (at
2405    /// the first/last display row) or that would duplicate an existing cursor
2406    /// are skipped.
2407    fn add_cursor_vertical(&mut self, move_lines: isize, cx: &mut Context<Self>) {
2408        if !self.is_multi_line() {
2409            return;
2410        }
2411
2412        self.pause_blink_cursor(cx);
2413        // Changing the cursor set ends the editing gesture that came before it.
2414        self.undo_manager.break_transaction_coalescing();
2415
2416        let sources: Vec<(usize, Option<(Pixels, usize)>, bool)> = self
2417            .selections
2418            .iter()
2419            .map(|sel| {
2420                (
2421                    sel.cursor_offset(),
2422                    sel.column_anchor,
2423                    self.line_end_affinity_for(sel),
2424                )
2425            })
2426            .collect();
2427        let mut offsets: std::collections::HashSet<usize> =
2428            sources.iter().map(|(offset, _, _)| *offset).collect();
2429
2430        let mut newest: Option<usize> = None;
2431        for (offset, anchor, line_end_affinity) in sources {
2432            let anchor = anchor.or_else(|| self.preferred_column_for(offset));
2433            let (target, _) = self.vertical_target(offset, anchor, line_end_affinity, move_lines);
2434            if target == offset || offsets.contains(&target) {
2435                continue;
2436            }
2437            offsets.insert(target);
2438            let id = self.selections.generate_id();
2439            let mut cursor = CursorSelection::new(id, target, target);
2440            cursor.column_anchor = anchor;
2441            self.selections.add(cursor);
2442            newest = Some(target);
2443        }
2444
2445        if let Some(newest) = newest {
2446            self.scroll_to(newest, None, cx);
2447        }
2448        cx.notify();
2449    }
2450
2451    /// Add an additional collapsed cursor at `offset`.
2452    ///
2453    /// Rejected when `offset` lands inside an existing selection or exactly on
2454    /// an existing cursor.
2455    pub(super) fn add_cursor_at(&mut self, offset: usize, cx: &mut Context<Self>) {
2456        if !self.is_multi_line() {
2457            return;
2458        }
2459
2460        for sel in self.selections.iter() {
2461            if sel.contains(offset) {
2462                return;
2463            }
2464            if sel.is_collapsed() && sel.cursor_offset() == offset {
2465                return;
2466            }
2467        }
2468
2469        self.undo_manager.break_transaction_coalescing();
2470        let id = self.selections.generate_id();
2471        let mut selection = CursorSelection::new(id, offset, offset);
2472        selection.column_anchor = self.preferred_column_for(offset);
2473        self.selections.add(selection);
2474        cx.notify();
2475    }
2476
2477    /// Build a columnar (block) selection spanning the rows between the two
2478    /// points, one selection per display row at the same column span.
2479    ///
2480    /// The span keeps the columns the pointer sat past the end of a short row, so a row
2481    /// that runs out of text does not narrow the block for the rows that do reach that
2482    /// far. Each row is then clipped to its own end, selecting as much of the span as it
2483    /// actually has.
2484    pub(super) fn build_columnar_selection(
2485        &mut self,
2486        start: ColumnarPoint,
2487        end: ColumnarPoint,
2488        cx: &mut Context<Self>,
2489    ) {
2490        if !self.is_multi_line() {
2491            return;
2492        }
2493
2494        self.undo_manager.break_transaction_coalescing();
2495
2496        let (start_row, start_col) = self.columnar_row_column(start);
2497        let (end_row, end_col) = self.columnar_row_column(end);
2498        let (start_row, end_row) = (start_row.min(end_row), start_row.max(end_row));
2499        let (start_col, end_col) = (start_col.min(end_col), start_col.max(end_col));
2500
2501        let mut new_selections = Vec::with_capacity(end_row - start_row + 1);
2502        for row in start_row..=end_row {
2503            let sel_start = self
2504                .display_map
2505                .display_row_column_to_offset(row, start_col);
2506            let sel_end = self.display_map.display_row_column_to_offset(row, end_col);
2507            let id = self.selections.generate_id();
2508            let sel_start = self.text.clip_offset(sel_start, Bias::Left);
2509            let sel_end = self.text.clip_offset(sel_end, Bias::Left);
2510            new_selections.push(CursorSelection::new(id, sel_start, sel_end));
2511        }
2512
2513        self.selections.replace_all(new_selections);
2514        cx.notify();
2515    }
2516
2517    /// Resolve a columnar point to the display row it is on and the column it reaches,
2518    /// counting the columns beyond the row's end so a short row keeps the full span.
2519    fn columnar_row_column(&self, point: ColumnarPoint) -> (usize, usize) {
2520        let display_point = self.display_map.offset_to_wrap_display_point(point.offset);
2521        let row = self
2522            .display_map
2523            .wrap_row_to_display_row(display_point.row)
2524            .unwrap_or_else(|| {
2525                self.display_map
2526                    .nearest_visible_display_row(display_point.row)
2527            });
2528
2529        (row, display_point.column + point.columns_past_line_end)
2530    }
2531
2532    pub(super) fn on_mouse_down(
2533        &mut self,
2534        event: &MouseDownEvent,
2535        window: &mut Window,
2536        cx: &mut Context<Self>,
2537    ) {
2538        // A token that consumed the press (a double click selecting it) must
2539        // not also place the caret.
2540        if window.default_prevented() {
2541            return;
2542        }
2543        self.undo_manager.break_transaction_coalescing();
2544        // Treat every press like a cursor move: take focus now rather than
2545        // after GPUI's focus observer runs, show the caret at once and restart
2546        // the idle delay, whatever phase the blink was in.
2547        if !self.disabled {
2548            self.focus(window, cx);
2549            self.pause_blink_cursor(cx);
2550        }
2551        // Input has its own text selection; suppress the window-level text
2552        // selection (Root) so it does not start a drag from here.
2553        crate::global_state::GlobalState::suppress_text_selection(cx);
2554
2555        // Clear inline completion on any mouse interaction
2556        M::clear_inline_completion(self, cx);
2557        // A tap on the touch selection asks for its menu back. Any other press
2558        // places the caret or starts a new drag, and the handles and the menu
2559        // no longer belong to what is selected.
2560        if event.button == MouseButton::Left
2561            && event.click_count == 1
2562            && self.reopen_edit_menu_at(event.position, cx)
2563        {
2564            return;
2565        }
2566        self.dismiss_touch_selection(cx);
2567
2568        // If there have IME marked range and is empty (Means pressed Esc to abort IME typing)
2569        // Clear the marked range.
2570        if let Some(ime_marked_range) = &self.ime_marked_range {
2571            if ime_marked_range.len() == 0 {
2572                self.ime_marked_range = None;
2573            }
2574        }
2575
2576        self.selecting = true;
2577        let (offset, line_end_affinity, columns_past_line_end) =
2578            self.resolve_mouse_position(event.position);
2579
2580        if M::on_click(self, event, offset, window, cx) {
2581            return;
2582        }
2583
2584        // Triple click to select line
2585        if event.button == MouseButton::Left && event.click_count >= 3 {
2586            self.select_line(offset, window, cx);
2587            return;
2588        }
2589
2590        // Double click to select word
2591        if event.button == MouseButton::Left && event.click_count == 2 {
2592            self.select_word(offset, window, cx);
2593            // A double tap is touch's other way to select a word, and it
2594            // gets the handles and the menu like a long press does.
2595            if crate::GlobalState::is_touch_press(cx) {
2596                self.keep_touch_selection(cx);
2597            }
2598            return;
2599        }
2600
2601        // Show Mouse context menu
2602        if event.button == MouseButton::Right {
2603            if self.enable_context_menu {
2604                if !self.active_selection().contains(offset) {
2605                    self.move_to(offset, None, cx);
2606                }
2607                self.pending_context_menu = Some((event.position, offset));
2608            }
2609            return;
2610        }
2611
2612        // Multi-cursor placement, multi-line only.
2613        if self.is_multi_line() && event.button == MouseButton::Left {
2614            if event.modifiers.alt
2615                && (event.modifiers.shift || (cfg!(target_os = "linux") && event.modifiers.control))
2616            {
2617                // Alt+Shift starts a block; Linux also accepts Ghostty's Ctrl+Alt.
2618                // Mark selecting so the drag handler extends the block.
2619                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2620                self.selecting = true;
2621                self.move_to_with_affinity(offset, None, line_end_affinity, cx);
2622                return;
2623            } else if event.modifiers.alt {
2624                self.add_cursor_at(offset, cx);
2625                // Keep click-to-add behavior, but use this press as the block
2626                // anchor if the user continues dragging with the left button.
2627                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2628                return;
2629            }
2630        }
2631
2632        if event.modifiers.shift {
2633            self.select_to_with_affinity(offset, line_end_affinity, cx);
2634        } else {
2635            self.move_to_with_affinity(offset, None, line_end_affinity, cx)
2636        }
2637    }
2638
2639    pub(super) fn on_mouse_up(
2640        &mut self,
2641        event: &MouseUpEvent,
2642        window: &mut Window,
2643        cx: &mut Context<Self>,
2644    ) {
2645        if event.button == MouseButton::Right {
2646            if let Some((position, offset)) = self.pending_context_menu.take() {
2647                self.handle_right_click_menu(position, offset, window, cx);
2648            }
2649        }
2650        if self.active_selection().is_empty() {
2651            self.active_selection_mut().reversed = false;
2652        }
2653        self.selecting = false;
2654        self.selected_word_range = None;
2655        self.column_select_start = None;
2656        self.auto_scroll.stop();
2657    }
2658
2659    pub(super) fn on_mouse_move(
2660        &mut self,
2661        event: &MouseMoveEvent,
2662        window: &mut Window,
2663        cx: &mut Context<Self>,
2664    ) {
2665        // Check if mouse is within bounds
2666        let within_bounds = self
2667            .last_bounds
2668            .as_ref()
2669            .map(|bounds| bounds.contains(&event.position))
2670            .unwrap_or(false);
2671
2672        if !within_bounds {
2673            // Clear hover when mouse leaves the input
2674            M::clear_hover_state(self, cx);
2675            return;
2676        }
2677
2678        // Show diagnostic popover on mouse move
2679        let (offset, _) = self.index_for_mouse_position(event.position);
2680        M::on_mouse_move(self, offset, event, window, cx);
2681
2682        if self.is_code_editor() {
2683            // Keep the current popover while the pointer stays on the same
2684            // diagnostic, so moving over a squiggle neither repaints nor
2685            // rebuilds the popover.
2686            match self
2687                .mode
2688                .diagnostics()
2689                .and_then(|set| set.for_offset(offset))
2690            {
2691                Some(diagnostic) => {
2692                    if self.diagnostic_popover.as_deref() != Some(diagnostic) {
2693                        self.diagnostic_popover = Some(Rc::new(diagnostic.clone()));
2694                        cx.notify();
2695                    }
2696                }
2697                None => {
2698                    if self.diagnostic_popover.take().is_some() {
2699                        cx.notify();
2700                    }
2701                }
2702            }
2703        }
2704    }
2705
2706    pub(super) fn on_scroll_wheel(
2707        &mut self,
2708        event: &ScrollWheelEvent,
2709        window: &mut Window,
2710        cx: &mut Context<Self>,
2711    ) {
2712        let line_height = self
2713            .last_layout
2714            .as_ref()
2715            .map(|layout| layout.line_height)
2716            .unwrap_or(window.line_height());
2717        let delta = event.delta.pixel_delta(line_height);
2718
2719        let old_offset = self.scroll_handle.offset();
2720        let precise = matches!(event.delta, gpui::ScrollDelta::Pixels(_));
2721        if !precise || !self.scroll_bounce || cx.reduce_motion() || !self.overflows_vertically() {
2722            let displaced = self.overscroll.offset() != 0.;
2723            self.overscroll.reset();
2724            if displaced {
2725                cx.notify();
2726            }
2727        }
2728        let bounces = self.scroll_bounce
2729            && precise
2730            && !cx.reduce_motion()
2731            && delta.y.abs() >= delta.x.abs()
2732            && self.overflows_vertically();
2733        if bounces {
2734            let mut overscroll = std::mem::take(&mut self.overscroll);
2735            let stretched = overscroll.packet(
2736                delta.y.as_f32(),
2737                event.touch_phase,
2738                self.input_bounds.size.height.as_f32(),
2739                cx.background_executor().now(),
2740                |dy| {
2741                    let before = self.scroll_handle.offset();
2742                    self.update_scroll_offset(
2743                        Some(point(before.x + delta.x, before.y + px(dy))),
2744                        cx,
2745                    );
2746                    (self.scroll_handle.offset().y - before.y).as_f32()
2747                },
2748            );
2749            self.overscroll = overscroll;
2750            if stretched {
2751                cx.notify();
2752            }
2753        } else {
2754            // Axis drift must not drop the end of a vertical drag.
2755            if matches!(
2756                event.touch_phase,
2757                gpui::TouchPhase::Ended | gpui::TouchPhase::Cancelled
2758            ) && self.overscroll.offset() != 0.
2759            {
2760                self.overscroll.packet(
2761                    0.,
2762                    event.touch_phase,
2763                    self.input_bounds.size.height.as_f32(),
2764                    cx.background_executor().now(),
2765                    |_| 0.,
2766                );
2767                cx.notify();
2768            }
2769            self.update_scroll_offset(Some(old_offset + delta), cx);
2770        }
2771
2772        // Text that has room to scroll keeps a scroll over it even where the
2773        // text ends, so reaching the end does not scroll the region around the
2774        // input; text that fits leaves the scroll to that region.
2775        if self.scroll_handle.offset() != old_offset || self.overflows_along(delta) {
2776            cx.stop_propagation();
2777        }
2778
2779        if self.diagnostic_popover.take().is_some() {
2780            cx.notify();
2781        }
2782        // The handles follow the text; the menu would sit over whatever
2783        // scrolls underneath it, so it steps aside until the finger lifts.
2784        self.edit_menu_on_scroll(event.touch_phase, cx);
2785    }
2786
2787    /// Whether the text takes a wheel event moving by `delta` from the
2788    /// scroll areas around it: it has room to scroll along the axis the
2789    /// event mostly moves on. Unlike [`Self::overflows_along`], an event that
2790    /// moves on neither axis, such as a gesture's start or end, goes to text
2791    /// that can scroll vertically.
2792    pub(super) fn takes_scroll(&self, delta: Point<Pixels>) -> bool {
2793        if delta.y.abs() >= delta.x.abs() {
2794            self.overflows_vertically()
2795        } else {
2796            self.scroll_size.width > self.input_bounds.size.width
2797        }
2798    }
2799
2800    /// Whether the text has room to scroll along the axis `delta` mostly
2801    /// moves on. A trackpad swipe drifts a little off its axis, so the drift
2802    /// alone does not claim the scroll.
2803    fn overflows_along(&self, delta: Point<Pixels>) -> bool {
2804        if delta.y.abs() >= delta.x.abs() {
2805            delta.y != px(0.) && self.overflows_vertically()
2806        } else {
2807            self.scroll_size.width > self.input_bounds.size.width
2808        }
2809    }
2810
2811    /// Whether multi-line text is taller than the input.
2812    fn overflows_vertically(&self) -> bool {
2813        self.is_multi_line() && self.scroll_size.height > self.input_bounds.size.height
2814    }
2815
2816    pub(super) fn update_scroll_offset(
2817        &mut self,
2818        offset: Option<Point<Pixels>>,
2819        cx: &mut Context<Self>,
2820    ) {
2821        let mut offset = offset.unwrap_or(self.scroll_handle.offset());
2822        let safe_y_range =
2823            (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
2824
2825        offset.y = if self.is_single_line() {
2826            px(0.)
2827        } else {
2828            offset.y.clamp(safe_y_range.start, safe_y_range.end)
2829        };
2830        offset.x = clamp_horizontal_scroll_offset(
2831            offset.x,
2832            self.scroll_size.width,
2833            self.input_bounds.size.width,
2834            self.text_align,
2835        );
2836        if self.scroll_handle.offset() != offset {
2837            self.scroll_handle.set_offset(offset);
2838            cx.notify();
2839        }
2840    }
2841
2842    /// Scroll to make the given offset visible.
2843    ///
2844    /// If `direction` is Some, will keep edges at the same side.
2845    pub(crate) fn scroll_to(
2846        &mut self,
2847        offset: usize,
2848        direction: Option<MoveDirection>,
2849        cx: &mut Context<Self>,
2850    ) {
2851        let padding = if direction.is_some() {
2852            ScrollPadding::SurroundingLines
2853        } else {
2854            ScrollPadding::Minimal
2855        };
2856        self.scroll_to_with_padding(offset, direction, padding, cx);
2857    }
2858
2859    /// Reveal an offset with independently chosen direction restriction and padding.
2860    /// Search uses surrounding lines without restricting movement to match order.
2861    pub(crate) fn scroll_to_with_padding(
2862        &mut self,
2863        offset: usize,
2864        direction: Option<MoveDirection>,
2865        padding: ScrollPadding,
2866        cx: &mut Context<Self>,
2867    ) {
2868        let Some(last_layout) = self.last_layout.as_ref() else {
2869            return;
2870        };
2871        let Some(bounds) = self.last_bounds.as_ref() else {
2872            return;
2873        };
2874
2875        let mut scroll_offset = self.scroll_handle.offset();
2876        let was_offset = scroll_offset;
2877        let line_height = last_layout.line_height;
2878
2879        let point = self.text.offset_to_point(offset);
2880
2881        let row = point.row;
2882
2883        // Resolve the wrapped row even when the target is outside the last layout.
2884        let display_row = self
2885            .display_map
2886            .offset_to_display_row(offset, self.line_end_affinity_at(offset));
2887        let row_offset_y = line_height * display_row;
2888
2889        // For Right alignment use 0 margin: the cursor indicator is clamped inside bounds
2890        // in layout_cursors, so shifting the text here would cause a first-click visual jump.
2891        let safety_margin = match last_layout.text_align {
2892            TextAlign::Left => RIGHT_MARGIN,
2893            TextAlign::Right => px(0.),
2894            TextAlign::Center => CURSOR_WIDTH,
2895        };
2896        if let Some(vi) = last_layout
2897            .visible_buffer_lines
2898            .iter()
2899            .position(|&line| line == row)
2900        {
2901            let line = &last_layout.lines[vi];
2902            let local_offset = offset.saturating_sub(last_layout.visible_line_byte_offsets[vi]);
2903            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
2904                let bounds_width = bounds.size.width - last_layout.line_number_width;
2905                let col_offset_x = pos.x;
2906                if col_offset_x - safety_margin < -scroll_offset.x {
2907                    // If the position is out of the visible area, scroll to make it visible
2908                    scroll_offset.x = -col_offset_x + safety_margin;
2909                } else if col_offset_x + safety_margin > -scroll_offset.x + bounds_width {
2910                    scroll_offset.x = -(col_offset_x - bounds_width + safety_margin);
2911                }
2912            }
2913        }
2914
2915        // Scroll the row into view. Use the same edge clearance helper as
2916        // `TextElement::layout_cursors` so both scroll-into-view paths agree
2917        // (a mismatch flickered on `Down` at end-of-buffer with a small
2918        // `cursor_surrounding_lines` override).
2919        let edge_height =
2920            if matches!(padding, ScrollPadding::SurroundingLines) && self.is_code_editor() {
2921                super::element::cursor_surrounding_padding(
2922                    self.mode.is_auto_grow(),
2923                    self.cursor_surrounding_lines,
2924                    super::element::viewport_visible_lines(bounds.size.height, line_height),
2925                    line_height,
2926                )
2927            } else {
2928                px(0.)
2929            };
2930        scroll_offset.y = super::element::reveal_row(
2931            scroll_offset.y,
2932            row_offset_y,
2933            line_height,
2934            bounds.size.height,
2935            edge_height,
2936        );
2937
2938        // Avoid necessary scroll, when it was already in the correct position.
2939        if direction == Some(MoveDirection::Up) {
2940            scroll_offset.y = scroll_offset.y.max(was_offset.y);
2941        } else if direction == Some(MoveDirection::Down) {
2942            scroll_offset.y = scroll_offset.y.min(was_offset.y);
2943        }
2944
2945        // Clamp the deferred target into the same safe range that
2946        // `update_scroll_offset` enforces on persist, so paint never shows an
2947        // over-scrolled frame before the post-paint clamp pulls it back.
2948        let safe_y_min = (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.));
2949        scroll_offset.x = scroll_offset.x.min(px(0.));
2950        scroll_offset.y = scroll_offset.y.clamp(safe_y_min, px(0.));
2951        self.deferred_scroll_offset = Some(scroll_offset);
2952        cx.notify();
2953    }
2954
2955    pub(super) fn show_character_palette(
2956        &mut self,
2957        _: &ShowCharacterPalette,
2958        window: &mut Window,
2959        _: &mut Context<Self>,
2960    ) {
2961        window.show_character_palette();
2962    }
2963
2964    /// The text of every non-empty selection, in document order.
2965    fn selected_texts(&self) -> Vec<String> {
2966        let mut selections: Vec<CursorSelection> = self
2967            .selections
2968            .iter()
2969            .copied()
2970            .filter(|sel| !sel.is_empty())
2971            .collect();
2972        selections.sort_by_key(|sel| sel.start);
2973        selections
2974            .iter()
2975            .map(|sel| self.text.slice(*sel).to_string())
2976            .collect()
2977    }
2978
2979    pub(super) fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
2980        if !self.is_copyable() {
2981            return;
2982        }
2983
2984        let texts = self.selected_texts();
2985
2986        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
2987    }
2988
2989    pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
2990        if !self.is_copyable() {
2991            return;
2992        }
2993
2994        let texts = self.selected_texts();
2995
2996        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
2997
2998        self.undo_manager.set_pending_intent(EditIntent::Atomic);
2999        self.replace_text_in_range_silent(None, "", window, cx);
3000    }
3001
3002    pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
3003        if !self.is_editable() {
3004            return;
3005        }
3006        if let Some(clipboard) = cx.read_from_clipboard() {
3007            self.insert_clipboard(clipboard, window, cx);
3008            return;
3009        }
3010        // The synchronous read is empty on platforms whose clipboard is
3011        // asynchronous and permission-gated (the web), so fall back to the
3012        // real read. It has to start here, still inside the user activation
3013        // that dispatched `Paste`, or the browser refuses the read.
3014        let read = cx.read_from_clipboard_async();
3015        let target = self.paste_target();
3016        cx.spawn_in(window, async move |this, cx| match read.await {
3017            Ok(Some(clipboard)) => {
3018                this.update_in(cx, |this, window, cx| {
3019                    // The read can sit behind a permission prompt for as
3020                    // long as the user likes. If they edited, moved the
3021                    // caret or left the input meanwhile, the paste would
3022                    // land where they no longer mean it to; drop it, as the
3023                    // browser's own paste event only reaches the focused
3024                    // input too.
3025                    if this.is_editable()
3026                        && this.focus_handle.is_focused(window)
3027                        && this.paste_target() == target
3028                    {
3029                        this.insert_clipboard(clipboard, window, cx);
3030                    }
3031                })
3032                .ok();
3033            }
3034            Ok(None) => {}
3035            Err(error) => tracing::warn!("failed to read the clipboard for paste: {error}"),
3036        })
3037        .detach();
3038    }
3039
3040    /// Where a paste would go right now: the document as edited so far and
3041    /// the selections it would replace. Two equal targets mean an edit made
3042    /// for one still applies to the other.
3043    fn paste_target(&self) -> PasteTarget {
3044        PasteTarget {
3045            document_revision: self.document_revision,
3046            selections: self.selections.iter().copied().collect(),
3047        }
3048    }
3049
3050    /// Inserts the clipboard text the way `Paste` does: one atomic edit,
3051    /// newlines dropped on a single-line input, one line per selection when
3052    /// the counts match on a multi-line one. A clipboard without text (an
3053    /// image, say) is left alone rather than replacing the selection with
3054    /// nothing.
3055    fn insert_clipboard(
3056        &mut self,
3057        clipboard: ClipboardItem,
3058        window: &mut Window,
3059        cx: &mut Context<Self>,
3060    ) {
3061        let Some(mut new_text) = clipboard.text().filter(|text| !text.is_empty()) else {
3062            return;
3063        };
3064        // A paste is one atomic edit, never part of a typing run.
3065        self.undo_manager.set_pending_intent(EditIntent::Atomic);
3066
3067        if !self.is_multi_line() {
3068            new_text = new_text.replace('\n', "");
3069            self.replace_text_in_range_silent(None, &new_text, window, cx);
3070            self.scroll_to(self.cursor(), None, cx);
3071            return;
3072        }
3073
3074        // Distribute one clipboard line per selection when the counts match.
3075        // Otherwise insert the whole clipboard text at each cursor.
3076        if !self.selections.is_single() {
3077            self.selections.merge_overlapping();
3078        }
3079        let lines: Vec<String> = new_text.split('\n').map(|s| s.to_string()).collect();
3080        let count = self.selections.len();
3081        if count > 1 && lines.len() == count {
3082            let mut selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
3083            selections.sort_by_key(|sel| sel.start);
3084            let edits: Vec<(Range<usize>, String)> = selections
3085                .iter()
3086                .zip(lines)
3087                .map(|(sel, line)| (sel.start..sel.end, line))
3088                .collect();
3089            self.replace_text_in_ranges(&edits, window, cx);
3090        } else {
3091            self.replace_text_in_range_silent(None, &new_text, window, cx);
3092        }
3093        self.scroll_to(self.cursor(), None, cx);
3094    }
3095
3096    /// The intent of a batch the caller did not label: inserting text at
3097    /// collapsed cursors is typing, anything else stands on its own.
3098    fn typing_intent(&self, edits: &[(Range<usize>, String)], new_text: &str) -> EditIntent {
3099        if !new_text.is_empty()
3100            && !new_text.contains(['\n', '\r'])
3101            && edits.iter().all(|(range, _)| range.is_empty())
3102        {
3103            EditIntent::Typing
3104        } else {
3105            EditIntent::Atomic
3106        }
3107    }
3108
3109    /// Where a cursor stood before an edit made with this intent.
3110    ///
3111    /// Backspace and forward delete expand a collapsed cursor over the text
3112    /// they are about to remove, so the recorded cursor has to collapse back to
3113    /// the side it came from for undo to restore it where the user left it.
3114    fn collapse_for_intent(
3115        intent: EditIntent,
3116        mut selection: CursorSelection,
3117        range: &Range<usize>,
3118    ) -> CursorSelection {
3119        match intent {
3120            EditIntent::Backspace => selection.place_at(range.end, None),
3121            EditIntent::DeleteForward => selection.place_at(range.start, None),
3122            EditIntent::Typing | EditIntent::Atomic => {}
3123        }
3124        selection
3125    }
3126
3127    /// Whether an edit rewrites the character before a collapsed cursor, as the
3128    /// macOS Korean IME does on each keystroke instead of marking text.
3129    fn rewrites_typed_char(
3130        &self,
3131        selection: CursorSelection,
3132        range: &Range<usize>,
3133        old_text: &str,
3134        new_text: &str,
3135    ) -> bool {
3136        !self.silent_replace_text
3137            && selection.is_collapsed()
3138            && selection.cursor_offset() == range.end
3139            && old_text.chars().count() == 1
3140            && !old_text.contains(['\n', '\r'])
3141            && !new_text.contains(['\n', '\r'])
3142    }
3143
3144    fn push_history(
3145        &mut self,
3146        text: &Rope,
3147        range: &Range<usize>,
3148        new_text: &str,
3149        requested_intent: Option<EditIntent>,
3150        selection_before: CursorSelection,
3151        selection_after: Option<CursorSelection>,
3152    ) -> bool {
3153        self.document_revision = self.document_revision.wrapping_add(1);
3154        let token_delta = self.edit_tokens(range, new_text.len());
3155        if self.undo_manager.is_ignoring() {
3156            return false;
3157        }
3158
3159        let range =
3160            text.clip_offset(range.start, Bias::Left)..text.clip_offset(range.end, Bias::Right);
3161        let old_text = text.slice(range.clone()).to_string();
3162        let new_range = range.start..range.start + new_text.len();
3163
3164        let intent = requested_intent.unwrap_or_else(|| {
3165            if range.is_empty()
3166                && old_text.is_empty()
3167                && !new_text.is_empty()
3168                && !new_text.contains(['\n', '\r'])
3169                || self.rewrites_typed_char(selection_before, &range, &old_text, new_text)
3170            {
3171                EditIntent::Typing
3172            } else {
3173                EditIntent::Atomic
3174            }
3175        });
3176
3177        let selection_before = Self::collapse_for_intent(intent, selection_before, &range);
3178        let selection_after =
3179            selection_after.unwrap_or_else(|| (new_range.end..new_range.end).into());
3180
3181        let open_transaction = self.undo_manager.has_open_transaction();
3182        let mut change = Change::new(range, &old_text, new_range, new_text);
3183        change.token_delta = token_delta;
3184        let recorded = self.undo_manager.record_transaction(change, intent);
3185        // A batch records its own cursor sets. This covers a change that is a
3186        // transaction on its own.
3187        if recorded && !open_transaction {
3188            self.undo_manager
3189                .record_selections(vec![selection_before], vec![selection_after]);
3190        }
3191        recorded
3192    }
3193
3194    pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
3195        self.replay_history(true, window, cx);
3196    }
3197
3198    pub(super) fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
3199        self.replay_history(false, window, cx);
3200    }
3201
3202    fn replay_history(&mut self, undo: bool, window: &mut Window, cx: &mut Context<Self>) {
3203        if !self.is_editable() {
3204            return;
3205        }
3206        let replay = if undo {
3207            self.undo_manager.undo()
3208        } else {
3209            self.undo_manager.redo()
3210        };
3211        let Some(replay) = replay else {
3212            return;
3213        };
3214        let token_aware = self.inline_tokens.is_some()
3215            || replay
3216                .changes
3217                .iter()
3218                .any(|change| change.token_delta.is_some());
3219        let emit_events = self.emit_events;
3220        if token_aware {
3221            self.emit_events = false;
3222        }
3223        self.replaying_history = token_aware;
3224        self.undo_manager.set_ignoring(true);
3225        for change in &replay.changes {
3226            let (range, text) = if undo {
3227                (change.new_range.into(), &change.old_text)
3228            } else {
3229                (change.old_range.into(), &change.new_text)
3230            };
3231            let range_utf16 = self.range_to_utf16(&range);
3232            self.replace_text_in_range_silent(Some(range_utf16), text, window, cx);
3233            self.replay_tokens(&range, text.len(), change.token_delta.as_deref(), undo);
3234        }
3235        self.restore_selections(replay.selections);
3236        self.mode
3237            .restore_auto_closed_pairs(replay.auto_closed_pairs.unwrap_or_default());
3238        if token_aware {
3239            // Token replay bypasses IME normalization; a stale marked range
3240            // would otherwise describe text that no longer exists.
3241            self.ime_marked_range = None;
3242        }
3243        self.undo_manager.set_ignoring(false);
3244        self.replaying_history = false;
3245        self.emit_events = emit_events;
3246        if token_aware && emit_events {
3247            cx.emit(InputEvent::Change);
3248        }
3249        cx.notify();
3250    }
3251
3252    /// Restore a set of selections captured in a transaction, clamping offsets
3253    /// to the current text length. `None` leaves the current selections as the
3254    /// replay left them.
3255    fn restore_selections(&mut self, selections: Option<Vec<CursorSelection>>) {
3256        let Some(selections) = selections else {
3257            return;
3258        };
3259
3260        let len = self.text.len();
3261        let restored: Vec<CursorSelection> = selections
3262            .into_iter()
3263            .map(|mut sel| {
3264                sel.start = sel.start.min(len);
3265                sel.end = sel.end.min(len);
3266                sel
3267            })
3268            .collect();
3269        self.selections.replace_all(restored);
3270        self.selections.merge_overlapping();
3271    }
3272
3273    /// Get byte offset of the cursor.
3274    ///
3275    /// The offset is the UTF-8 offset.
3276    pub fn cursor(&self) -> usize {
3277        if let Some(ime_marked_range) = &self.ime_marked_range {
3278            return ime_marked_range.end;
3279        }
3280
3281        self.selections.active().cursor_offset()
3282    }
3283
3284    /// Returns the active selection.
3285    pub(super) fn active_selection(&self) -> &CursorSelection {
3286        self.selections.active()
3287    }
3288
3289    /// Returns a mutable reference to the active selection.
3290    pub(super) fn active_selection_mut(&mut self) -> &mut CursorSelection {
3291        self.selections.active_mut()
3292    }
3293
3294    /// Sets the active selection to the given range, keeping its `reversed`
3295    /// and `column_anchor` state untouched.
3296    pub(super) fn set_selection(&mut self, start: usize, end: usize) {
3297        let range = self.normalize_token_range(start..end);
3298        let (start, end) = (range.start, range.end);
3299        let active = self.active_selection_mut();
3300        active.start = start;
3301        active.end = end;
3302    }
3303
3304    /// Collapses the active selection to a cursor at the given offset,
3305    /// clearing `reversed`.
3306    pub(super) fn set_cursor_to(&mut self, offset: usize) {
3307        let offset = self.token_boundary(offset, Bias::Right);
3308        let active = self.active_selection_mut();
3309        active.start = offset;
3310        active.end = offset;
3311        active.reversed = false;
3312    }
3313
3314    /// Visible row range in the last laid-out viewport, `None` before first layout.
3315    pub fn visible_row_range(&self) -> Option<std::ops::Range<usize>> {
3316        self.last_layout.as_ref().map(|l| l.visible_range.clone())
3317    }
3318
3319    /// Current scroll offset of the editor viewport.
3320    pub fn scroll_offset(&self) -> gpui::Point<gpui::Pixels> {
3321        self.scroll_handle.offset()
3322    }
3323
3324    /// Set scroll offset of the editor viewport.
3325    ///
3326    /// The offset will be clamped to the valid range, and applied after the next layout.
3327    pub fn set_scroll_offset(&mut self, offset: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
3328        self.deferred_scroll_offset = Some(offset);
3329        cx.notify();
3330    }
3331
3332    /// Laid-out line height; `None` before first layout.
3333    pub fn line_height(&self) -> Option<gpui::Pixels> {
3334        self.last_layout.as_ref().map(|l| l.line_height)
3335    }
3336
3337    /// Returns the active selection as a byte range into the text.
3338    ///
3339    /// With multiple cursors, this reads only the active selection.
3340    ///
3341    /// The range is empty (`start == end`) when no text is selected; in
3342    /// that case the offset equals `cursor()`. Byte offsets are measured
3343    /// in the underlying rope's byte units.
3344    pub fn selected_range(&self) -> std::ops::Range<usize> {
3345        (*self.selections.active()).into()
3346    }
3347
3348    pub fn select_all(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3349        self.undo_manager.break_transaction_coalescing();
3350        self.selections.remove_all_but_active();
3351        self.set_selection(0, self.text.len());
3352        cx.notify();
3353    }
3354
3355    /// Set the selected range using UTF-8 byte offsets, removing additional cursors.
3356    ///
3357    /// Non-empty ranges expand to character boundaries. Empty ranges remain empty and are
3358    /// clipped to the preceding character boundary.
3359    pub fn set_selected_range(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3360        let range = self.normalize_token_range(range);
3361        let end_bias = if range.start == range.end {
3362            Bias::Left
3363        } else {
3364            Bias::Right
3365        };
3366        let start = self.text.clip_offset(range.start, Bias::Left);
3367        let end = self.text.clip_offset(range.end, end_bias);
3368
3369        self.move_to(start, None, cx);
3370        self.active_selection_mut().reversed = false;
3371        self.selected_word_range = None;
3372        self.select_to(end, cx);
3373    }
3374
3375    /// Add `range` as an additional selection, keeping the existing ones —
3376    /// the programmatic equivalent of Alt-clicking another cursor into the
3377    /// text.
3378    ///
3379    /// The range is clipped to the text and expanded to character
3380    /// boundaries, like [`Self::set_selected_range`]. A range already
3381    /// covered by a selection adds nothing. Overlapping selections are merged.
3382    /// On a single-line input there is no second cursor to add, so the call
3383    /// replaces the selection instead.
3384    /// The view scrolls to the new selection.
3385    pub fn add_selection(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3386        if !self.is_multi_line() {
3387            self.set_selected_range(range, cx);
3388            return;
3389        }
3390
3391        let range = self.normalize_token_range(range);
3392        let end_bias = if range.start == range.end {
3393            Bias::Left
3394        } else {
3395            Bias::Right
3396        };
3397        let start = self.cursor_boundary(range.start, Bias::Left);
3398        // Match set_selected_range: expand UTF-8 boundaries first, then place
3399        // the selection endpoint without splitting a CRLF newline.
3400        let end = self.cursor_boundary(self.text.clip_offset(range.end, end_bias), Bias::Left);
3401
3402        for sel in self.selections.iter() {
3403            if sel.start <= start && end <= sel.end {
3404                return;
3405            }
3406        }
3407
3408        self.undo_manager.break_transaction_coalescing();
3409        self.selected_word_range = None;
3410        self.pause_blink_cursor(cx);
3411        M::hide_context_menu(self, cx);
3412        M::clear_inline_completion(self, cx);
3413        let id = self.selections.generate_id();
3414        let mut selection = CursorSelection::new(id, start, end);
3415        selection.column_anchor = self.preferred_column_for(end);
3416        self.selections.add(selection);
3417        self.selections.merge_overlapping();
3418        self.scroll_to(end, None, cx);
3419        cx.notify();
3420    }
3421
3422    /// Resolve a mouse position to a byte offset in the text.
3423    ///
3424    /// Also reports the caret's line-end affinity for that offset: `true` when the position
3425    /// landed on the wrap boundary of a non-final visual row, meaning the caret belongs at the
3426    /// end of that row rather than at the start of the next one. Callers that place or extend a
3427    /// selection must pass it on, or clicking past the last glyph of a wrapped row leaves a
3428    /// caret one row below the pointer.
3429    pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> (usize, bool) {
3430        let (offset, line_end_affinity, _) = self.resolve_mouse_position(position);
3431        (offset, line_end_affinity)
3432    }
3433
3434    /// Resolve a mouse position to a text offset, the caret affinity to use for it, and
3435    /// how many columns the pointer sits past the end of that row.
3436    ///
3437    /// Only a columnar selection needs the third value; everywhere else a position past
3438    /// the end of a row means the end of that row, and
3439    /// [`Self::index_for_mouse_position`] is the call to make.
3440    pub(super) fn resolve_mouse_position(&self, position: Point<Pixels>) -> (usize, bool, usize) {
3441        // If the text is empty, always return 0
3442        if self.text.len() == 0 {
3443            return (0, false, 0);
3444        }
3445
3446        let (Some(bounds), Some(last_layout)) =
3447            (self.last_bounds.as_ref(), self.last_layout.as_ref())
3448        else {
3449            return (0, false, 0);
3450        };
3451
3452        let line_height = last_layout.line_height;
3453        let line_number_width = last_layout.line_number_width;
3454
3455        // TIP: About the IBeam cursor
3456        //
3457        // If cursor style is IBeam, the mouse mouse position is in the middle of the cursor (This is special in OS)
3458
3459        // The position is relative to the bounds of the text input
3460        //
3461        // bounds.origin:
3462        //
3463        // - included the input padding.
3464        // - included the scroll offset.
3465        let inner_position = position - bounds.origin - point(line_number_width, px(0.));
3466
3467        let mut y_offset = last_layout.visible_top;
3468        // Position relative to the last line walked, kept for a pointer that ends up
3469        // below every line.
3470        let mut last_line_pos = None;
3471
3472        // Traverse visible buffer lines (compact, no hidden entries)
3473        for (vi, (line_layout, _buffer_line)) in last_layout
3474            .lines
3475            .iter()
3476            .zip(last_layout.visible_buffer_lines.iter())
3477            .enumerate()
3478        {
3479            let line_start_offset = last_layout.visible_line_byte_offsets[vi];
3480
3481            // Calculate line origin for this display row
3482            let line_origin = point(px(0.), y_offset);
3483            let pos = inner_position - line_origin;
3484
3485            // Return offset by use closest_index_for_x if is single line mode.
3486            if self.is_single_line() {
3487                let local_index = line_layout.closest_index_for_x(pos.x, last_layout);
3488                // A single line never wraps, so there is no boundary to disambiguate.
3489                return (
3490                    self.resolve_index(line_start_offset + local_index),
3491                    false,
3492                    0,
3493                );
3494            }
3495
3496            // Check if mouse is in this line's bounds
3497            if let Some((local_index, line_end_affinity)) =
3498                line_layout.closest_index_for_position(pos, last_layout)
3499            {
3500                return (
3501                    self.resolve_index(line_start_offset + local_index),
3502                    line_end_affinity,
3503                    line_layout.columns_past_line_end(pos, last_layout),
3504                );
3505            } else if pos.y < px(0.) {
3506                // Mouse is above this line, return start of this line
3507                return (self.resolve_index(line_start_offset), false, 0);
3508            }
3509
3510            y_offset += line_layout.size(line_height).height;
3511            last_line_pos = Some(pos);
3512        }
3513
3514        // Clamp to the last laid-out row. Returning the end of the whole document
3515        // would select unseen text before drag autoscroll has reached it.
3516        let last_position = last_layout
3517            .lines
3518            .last()
3519            .zip(last_line_pos)
3520            .zip(last_layout.visible_line_byte_offsets.last())
3521            .map(|((line_layout, pos), line_start)| {
3522                let last_row_top = (line_layout.size(line_height).height - line_height).max(px(0.));
3523                let pos = point(pos.x, last_row_top);
3524                (
3525                    self.resolve_index(line_start + line_layout.len()),
3526                    false,
3527                    line_layout.columns_past_line_end(pos, last_layout),
3528                )
3529            });
3530
3531        last_position.unwrap_or((self.text.len(), false, 0))
3532    }
3533
3534    /// Map a display byte index back to a text offset, undoing the mask expansion when the input
3535    /// is masked.
3536    fn resolve_index(&self, index: usize) -> usize {
3537        if self.masked {
3538            self.text.char_index_to_offset(index / MASK_CHAR.len_utf8())
3539        } else {
3540            index.min(self.text.len())
3541        }
3542    }
3543
3544    /// Returns a y offsetted point for the line origin.
3545    /// Select the text from the current cursor position to the given offset.
3546    ///
3547    /// The offset is the UTF-8 offset.
3548    ///
3549    /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
3550    /// Extend a single selection so its moving end lands at `offset`, flipping
3551    /// `reversed` when the ends cross. When a sticky `word_range` is given the
3552    /// selection is kept covering it.
3553    fn extend_selection(
3554        sel: &mut CursorSelection,
3555        offset: usize,
3556        word_range: Option<CursorSelection>,
3557    ) {
3558        if sel.reversed {
3559            sel.start = offset;
3560        } else {
3561            sel.end = offset;
3562        }
3563
3564        if sel.end < sel.start {
3565            sel.reversed = !sel.reversed;
3566            std::mem::swap(&mut sel.start, &mut sel.end);
3567        }
3568
3569        if let Some(word_range) = word_range {
3570            if sel.start > word_range.start {
3571                sel.start = word_range.start;
3572            }
3573            if sel.end < word_range.end {
3574                sel.end = word_range.end;
3575            }
3576        }
3577    }
3578
3579    /// Extend only the active selection to `offset`. Used by mouse drag.
3580    pub(crate) fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
3581        self.select_to_with_affinity(offset, false, cx);
3582    }
3583
3584    /// Like [`Self::select_to`], but also carries the caret's line-end affinity.
3585    ///
3586    /// See [`Self::move_to_with_affinity`] for why the affinity travels with the offset. Note
3587    /// that plain [`Self::select_to`] clears the affinity: every offset it is given came from
3588    /// the text rather than from a visual position, so the caret has no reason to keep sticking
3589    /// to the end of a wrapped row.
3590    pub(crate) fn select_to_with_affinity(
3591        &mut self,
3592        offset: usize,
3593        line_end_affinity: bool,
3594        cx: &mut Context<Self>,
3595    ) {
3596        M::clear_inline_completion(self, cx);
3597
3598        self.cursor_line_end_affinity = line_end_affinity;
3599        let offset = self.cursor_boundary(offset, Bias::Left);
3600        let word_range = self.selected_word_range;
3601        Self::extend_selection(self.active_selection_mut(), offset, word_range);
3602        let range = self.normalize_token_range(self.selected_range());
3603        self.set_selection(range.start, range.end);
3604
3605        if self.active_selection().is_empty() {
3606            self.update_preferred_column();
3607        }
3608        cx.notify()
3609    }
3610
3611    /// Extend every selection to the offset produced by `f`, then merge any
3612    /// selections that now overlap. Used by keyboard selection commands.
3613    fn select_all_cursors_to(
3614        &mut self,
3615        f: impl Fn(&Self, &CursorSelection) -> usize,
3616        cx: &mut Context<Self>,
3617    ) {
3618        self.select_all_cursors_to_with_affinity(|s, sel| (f(s, sel), false), false, cx);
3619    }
3620
3621    /// Extend selections with caret affinity, preserving their column anchors even
3622    /// when vertical movement collapses a selection to a cursor.
3623    fn select_all_cursors_to_with_affinity(
3624        &mut self,
3625        f: impl Fn(&Self, &CursorSelection) -> (usize, bool),
3626        preserve_column: bool,
3627        cx: &mut Context<Self>,
3628    ) {
3629        self.pause_blink_cursor(cx);
3630        self.undo_manager.break_transaction_coalescing();
3631        M::clear_inline_completion(self, cx);
3632
3633        let mut active_affinity = false;
3634        let new_selections: Vec<CursorSelection> = self
3635            .selections
3636            .iter()
3637            .map(|sel| {
3638                let (offset, affinity) = f(self, sel);
3639                if sel.id == self.active_selection().id {
3640                    active_affinity = affinity;
3641                }
3642                let offset = self.cursor_boundary(offset, Bias::Left);
3643                let mut new_sel = *sel;
3644                Self::extend_selection(&mut new_sel, offset, None);
3645                let range = self.normalize_token_range(new_sel.start..new_sel.end);
3646                new_sel.start = range.start;
3647                new_sel.end = range.end;
3648                if !preserve_column {
3649                    new_sel.column_anchor =
3650                        self.preferred_column_for_with_affinity(new_sel.cursor_offset(), affinity);
3651                } else if new_sel.column_anchor.is_none() {
3652                    new_sel.column_anchor = self.preferred_column_for_with_affinity(
3653                        sel.cursor_offset(),
3654                        self.line_end_affinity_for(sel),
3655                    );
3656                }
3657                new_sel
3658            })
3659            .collect();
3660        self.cursor_line_end_affinity = active_affinity;
3661        self.selections.replace_all(new_selections);
3662        self.selections.merge_overlapping();
3663
3664        self.scroll_to(self.cursor(), None, cx);
3665        cx.notify()
3666    }
3667
3668    /// Unselects the currently selected text.
3669    pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3670        self.undo_manager.break_transaction_coalescing();
3671        let offset = self.cursor();
3672        self.set_cursor_to(offset);
3673        cx.notify()
3674    }
3675
3676    #[inline]
3677    pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {
3678        self.text.offset_utf16_to_offset(offset)
3679    }
3680
3681    #[inline]
3682    pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {
3683        self.text.offset_to_offset_utf16(offset)
3684    }
3685
3686    #[inline]
3687    pub(crate) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
3688        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
3689    }
3690
3691    #[inline]
3692    pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
3693        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
3694    }
3695
3696    /// If offset falls on a hidden (folded) line, clamp backward to the end of
3697    /// the fold header line (last visible position before the fold).
3698    fn clamp_offset_to_visible_backward(&self, offset: usize) -> usize {
3699        let line = self.text.offset_to_point(offset).row;
3700        if self.display_map.is_buffer_line_hidden(line) {
3701            for fold in self.display_map.folded_ranges() {
3702                if line > fold.start_line && line <= fold.end_line {
3703                    return self.text.line_end_offset(fold.start_line);
3704                }
3705            }
3706        }
3707        offset
3708    }
3709
3710    /// If offset falls on a hidden (folded) line, clamp forward to the start of
3711    /// the fold end line (first visible position after the fold).
3712    fn clamp_offset_to_visible_forward(&self, offset: usize) -> usize {
3713        let line = self.text.offset_to_point(offset).row;
3714        if self.display_map.is_buffer_line_hidden(line) {
3715            for fold in self.display_map.folded_ranges() {
3716                if line > fold.start_line && line <= fold.end_line {
3717                    return self.text.line_start_offset(fold.end_line);
3718                }
3719            }
3720        }
3721        offset
3722    }
3723
3724    /// Clip a cursor/selection offset without splitting a CRLF newline. This does
3725    /// not alter the rope or its byte/UTF-16 conversion used for exact source APIs.
3726    pub(super) fn cursor_boundary(&self, offset: usize, bias: Bias) -> usize {
3727        let offset = self.token_boundary(self.text.clip_offset(offset, bias), bias);
3728        if offset > 0
3729            && self.text.char_at(offset - 1) == Some('\r')
3730            && self.text.char_at(offset) == Some('\n')
3731        {
3732            if bias == Bias::Left {
3733                offset - 1
3734            } else {
3735                offset + 1
3736            }
3737        } else {
3738            offset
3739        }
3740    }
3741
3742    pub(super) fn previous_boundary(&self, offset: usize) -> usize {
3743        let offset = self.cursor_boundary(offset.saturating_sub(1), Bias::Left);
3744        self.clamp_offset_to_visible_backward(offset)
3745    }
3746
3747    pub(super) fn next_boundary(&self, offset: usize) -> usize {
3748        let offset = self.cursor_boundary(offset.saturating_add(1), Bias::Right);
3749        self.clamp_offset_to_visible_forward(offset)
3750    }
3751
3752    /// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
3753    pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
3754        (self.focus_handle.is_focused(window) || M::is_context_menu_open(self, cx))
3755            && !self.disabled
3756            && self.blink_cursor.read(cx).visible()
3757            && window.is_window_active()
3758    }
3759
3760    fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3761        self.blink_cursor.update(cx, |cursor, cx| {
3762            cursor.start(cx);
3763        });
3764        cx.emit(InputEvent::Focus);
3765    }
3766
3767    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3768        if M::is_context_menu_open(self, cx) {
3769            return;
3770        }
3771
3772        M::hide_context_menu(self, cx);
3773        self.undo_manager.break_transaction_coalescing();
3774
3775        // NOTE: Do not cancel select, when blur.
3776        // Because maybe user want to copy the selected text by AppMenuBar (will take focus handle).
3777
3778        M::clear_hover_state(self, cx);
3779        self.diagnostic_popover = None;
3780        M::clear_inline_completion(self, cx);
3781        self.dismiss_touch_selection(cx);
3782        self.blink_cursor.update(cx, |cursor, cx| {
3783            cursor.stop(cx);
3784        });
3785        self.clamp_number_value(window, cx);
3786        cx.emit(InputEvent::Blur);
3787        cx.notify();
3788    }
3789
3790    /// Clamp the number value to the `min`/`max` range, used on blur.
3791    ///
3792    /// Out-of-range values are allowed while typing (e.g. `1` is an
3793    /// intermediate state of `15` when min is 10), and clamped on blur.
3794    fn clamp_number_value(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3795        if !self.is_single_line() {
3796            return;
3797        }
3798        if !matches!(self.mask_pattern, MaskPattern::Number { .. }) {
3799            return;
3800        }
3801        if self.number_min.is_none() && self.number_max.is_none() {
3802            return;
3803        }
3804
3805        let Ok(value) = self.unmask_value().parse::<f64>() else {
3806            return;
3807        };
3808
3809        let clamped = match (self.number_min, self.number_max) {
3810            (Some(min), _) if value < min => min,
3811            (_, Some(max)) if value > max => max,
3812            _ => return,
3813        };
3814
3815        // The clamped value must pass the `pattern`/`validate` check,
3816        // otherwise keep the value as is.
3817        let new_text = clamped.to_string();
3818        if !self.is_valid_input(&new_text, cx) {
3819            return;
3820        }
3821
3822        let range = self.range_to_utf16(&(0..self.text.len()));
3823        self.replace_text_in_range_silent(Some(range), &new_text, window, cx);
3824    }
3825
3826    pub(super) fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
3827        self.blink_cursor.update(cx, |cursor, cx| {
3828            cursor.pause(cx);
3829        });
3830    }
3831
3832    pub(super) fn on_drag_move(
3833        &mut self,
3834        event: &MouseMoveEvent,
3835        window: &mut Window,
3836        cx: &mut Context<Self>,
3837    ) {
3838        if self.text.len() == 0 {
3839            return;
3840        }
3841
3842        if self.last_layout.is_none() {
3843            return;
3844        }
3845
3846        if !self.focus_handle.is_focused(window) {
3847            return;
3848        }
3849
3850        if !self.selecting {
3851            return;
3852        }
3853
3854        self.auto_scroll.last_drag_position = Some(event.position);
3855        let (offset, line_end_affinity, columns_past_line_end) =
3856            self.resolve_mouse_position(event.position);
3857        if let Some(start) = self.column_select_start {
3858            let end = ColumnarPoint::new(offset, columns_past_line_end);
3859            self.build_columnar_selection(start, end, cx);
3860        } else {
3861            self.select_to_with_affinity(offset, line_end_affinity, cx);
3862        }
3863
3864        if !self.is_single_line() {
3865            let delta = AutoScroll::compute_delta(event.position.y, self.input_bounds);
3866            // Input's ScrollHandle uses negative-y-is-down; negate the positive-towards-bottom delta.
3867            let scroll_delta = delta.map(|d| -d);
3868            self.auto_scroll.set(scroll_delta, cx, |delta, state, cx| {
3869                let current = state.scroll_handle.offset();
3870                state.update_scroll_offset(Some(point(current.x, current.y + delta)), cx);
3871                if let Some(pos) = state.auto_scroll.last_drag_position {
3872                    let (offset, line_end_affinity, columns_past_line_end) =
3873                        state.resolve_mouse_position(pos);
3874                    if let Some(start) = state.column_select_start {
3875                        let end = ColumnarPoint::new(offset, columns_past_line_end);
3876                        state.build_columnar_selection(start, end, cx);
3877                    } else {
3878                        state.select_to_with_affinity(offset, line_end_affinity, cx);
3879                    }
3880                }
3881            });
3882        }
3883    }
3884
3885    /// Normalize the inserted text before applying it to the input.
3886    ///
3887    /// For number inputs (with [`MaskPattern::Number`]), this converts
3888    /// full-width number characters into their ASCII equivalents,
3889    /// e.g. `12。5` -> `12.5`.
3890    pub(super) fn normalize_input<'a>(&self, new_text: &'a str) -> Cow<'a, str> {
3891        let normalized = if matches!(self.mask_pattern, MaskPattern::Number { .. }) {
3892            normalize_number_input(new_text)
3893        } else {
3894            Cow::Borrowed(new_text)
3895        };
3896
3897        if self.is_single_line() && normalized.contains(['\n', '\r']) {
3898            Cow::Owned(normalized.replace(['\n', '\r'], ""))
3899        } else {
3900            normalized
3901        }
3902    }
3903
3904    pub(crate) fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {
3905        if new_text.is_empty() {
3906            return true;
3907        }
3908
3909        if let Some(validate) = &self.validate {
3910            if !validate(new_text, cx) {
3911                return false;
3912            }
3913        }
3914
3915        if !self.mask_pattern.is_valid(new_text) {
3916            return false;
3917        }
3918
3919        let Some(pattern) = &self.pattern else {
3920            return true;
3921        };
3922
3923        pattern.is_match(new_text)
3924    }
3925
3926    /// Set the mask pattern for formatting the input text.
3927    ///
3928    /// The pattern can contain:
3929    /// - 9: Any digit or dot
3930    /// - A: Any letter
3931    /// - *: Any character
3932    /// - Other characters will be treated as literal mask characters
3933    ///
3934    /// Example: "(999)999-999" for phone numbers
3935    pub fn mask_pattern(mut self, pattern: impl Into<MaskPattern>) -> Self {
3936        self.mask_pattern = pattern.into();
3937        self.mask_pattern_set = true;
3938        if let Some(placeholder) = self.mask_pattern.placeholder() {
3939            self.placeholder = placeholder.into();
3940        }
3941        self
3942    }
3943
3944    pub fn set_mask_pattern(
3945        &mut self,
3946        pattern: impl Into<MaskPattern>,
3947        _: &mut Window,
3948        cx: &mut Context<Self>,
3949    ) {
3950        self.mask_pattern = pattern.into();
3951        self.mask_pattern_set = true;
3952        if let Some(placeholder) = self.mask_pattern.placeholder() {
3953            self.placeholder = placeholder.into();
3954        }
3955        cx.notify();
3956    }
3957
3958    /// Apply the default numeric mask unless the caller explicitly selected a mask.
3959    pub fn ensure_number_mask(&mut self) {
3960        if self.mask_pattern_set {
3961            return;
3962        }
3963        self.mask_pattern = MaskPattern::Number {
3964            separator: None,
3965            fraction: None,
3966        };
3967    }
3968
3969    pub(super) fn set_input_bounds(&mut self, new_bounds: Bounds<Pixels>, cx: &mut Context<Self>) {
3970        let wrap_width_changed = self.input_bounds.size.width != new_bounds.size.width;
3971        self.input_bounds = new_bounds;
3972
3973        // Update display_map wrap_width if changed.
3974        if let Some(last_layout) = self.last_layout.as_ref() {
3975            if wrap_width_changed {
3976                let wrap_width = if !self.soft_wrap {
3977                    // None to disable wrapping (will use Pixels::MAX)
3978                    None
3979                } else {
3980                    last_layout.wrap_width
3981                };
3982
3983                self.display_map.on_layout_changed(wrap_width, cx);
3984                if self.is_multi_line() {
3985                    self.mode.update_auto_grow(&self.display_map);
3986                }
3987                cx.notify();
3988            }
3989        }
3990    }
3991
3992    /// Return the active selection's text, borrowed out of the [`Rope`]
3993    /// the state owns.
3994    ///
3995    /// See [`Self::selected_value`] when an owned string is wanted.
3996    pub fn selected_text(&self) -> RopeSlice<'_> {
3997        let range_utf16 = self.range_to_utf16(&self.selected_range());
3998        let range = self.range_from_utf16(&range_utf16);
3999        self.text.slice(range)
4000    }
4001
4002    /// Return the rendered bounds for a UTF-8 byte range in the current input contents.
4003    ///
4004    /// Returns `None` when the requested range is not currently laid out or visible.
4005    pub fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
4006        let Some(last_layout) = self.last_layout.as_ref() else {
4007            return None;
4008        };
4009
4010        let Some(last_bounds) = self.last_bounds else {
4011            return None;
4012        };
4013
4014        let (_, _, start_pos) = self.line_and_position_for_offset(range.start);
4015        let (_, _, end_pos) = self.line_and_position_for_offset(range.end);
4016
4017        let Some(start_pos) = start_pos else {
4018            return None;
4019        };
4020        let Some(end_pos) = end_pos else {
4021            return None;
4022        };
4023
4024        Some(Bounds::from_corners(
4025            last_bounds.origin + start_pos,
4026            last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),
4027        ))
4028    }
4029
4030    /// Replace text in range in silent.
4031    ///
4032    /// This will not trigger any UI interaction, such as auto-completion.
4033    pub(crate) fn replace_text_in_range_silent(
4034        &mut self,
4035        range_utf16: Option<Range<usize>>,
4036        new_text: &str,
4037        window: &mut Window,
4038        cx: &mut Context<Self>,
4039    ) {
4040        self.silent_replace_text = true;
4041        self.replace_text_in_range(range_utf16, new_text, window, cx);
4042        self.silent_replace_text = false;
4043    }
4044
4045    /// Apply a batch of edits as one atomic history transaction.
4046    ///
4047    /// `edits` are `(byte range in the current pre-edit document, replacement)`
4048    /// pairs.
4049    pub(crate) fn replace_text_in_ranges(
4050        &mut self,
4051        edits: &[(Range<usize>, String)],
4052        window: &mut Window,
4053        cx: &mut Context<Self>,
4054    ) {
4055        if !self.is_editable() || edits.is_empty() {
4056            return;
4057        }
4058
4059        // Every edit invalidates provider responses for the previous document,
4060        // including deletion and indentation which do not trigger completion.
4061        M::hide_context_menu(self, cx);
4062        M::clear_inline_completion(self, cx);
4063
4064        // Sort descending by start so applying front-of-vec first edits the
4065        // highest offsets first, leaving lower offsets unchanged.
4066        let mut sorted: Vec<(Range<usize>, &str)> = edits
4067            .iter()
4068            .map(|(range, text)| (self.normalize_token_range(range.clone()), text.as_str()))
4069            .collect();
4070        sorted.sort_by_key(|edit| std::cmp::Reverse(edit.0.start));
4071        if !self.token_spans().is_empty() {
4072            let mut merged: Vec<(Range<usize>, &str)> = Vec::with_capacity(sorted.len());
4073            for (range, text) in sorted {
4074                if let Some(last) = merged
4075                    .last_mut()
4076                    .filter(|last| range.end > last.0.start || range == last.0)
4077                {
4078                    last.0.start = range.start.min(last.0.start);
4079                    last.0.end = range.end.max(last.0.end);
4080                } else {
4081                    merged.push((range, text));
4082                }
4083            }
4084            sorted = merged;
4085        }
4086
4087        #[cfg(debug_assertions)]
4088        for pair in sorted.windows(2) {
4089            debug_assert!(
4090                pair[1].0.end <= pair[0].0.start,
4091                "replace_text_in_ranges requires disjoint ranges"
4092            );
4093        }
4094
4095        // Wrap multiple edits in one explicit transaction so they undo as a
4096        // unit. A single edit records directly, which keeps it eligible for
4097        // the undo manager's typing coalescing.
4098        let requested_intent = self.undo_manager.take_pending_intent();
4099        let selection_before = *self.active_selection();
4100        let original_selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
4101        // Snapshot the cursors before applying, so undo can restore them. A
4102        // delete has already expanded them over the text it removes, so they
4103        // collapse back to where the user left them.
4104        let selections_before: Vec<CursorSelection> = self
4105            .selections
4106            .iter()
4107            .map(|selection| {
4108                Self::collapse_for_intent(
4109                    requested_intent.unwrap_or(EditIntent::Atomic),
4110                    *selection,
4111                    &(selection.start..selection.end),
4112                )
4113            })
4114            .collect();
4115        let group = sorted.len() > 1;
4116        if group {
4117            self.undo_manager.begin_transaction();
4118        }
4119
4120        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
4121        let mut recorded = false;
4122        // Several edits reach the highlighter as one batch, so it reparses
4123        // once for the change instead of once per edit.
4124        let mut highlighter_edits = Vec::new();
4125        for (range, new_text) in &sorted {
4126            let old_text = self.text.clone();
4127            self.mode.adjust_auto_closed_pair(range, new_text.len());
4128            self.text.replace(range.clone(), new_text);
4129
4130            M::adjust_annotations(self, range, new_text.len());
4131            recorded |= self.push_history(
4132                &old_text,
4133                range,
4134                new_text,
4135                requested_intent,
4136                selection_before,
4137                None,
4138            );
4139
4140            // Incremental, single-range updates must run per edit.
4141            self.display_map
4142                .adjust_folds_for_edit(&old_text, range, new_text);
4143            self.display_map
4144                .on_text_changed(&self.text, range, &Rope::from(*new_text), cx);
4145
4146            if group {
4147                if self.is_code_editor() {
4148                    let edit =
4149                        super::mode::replacement_input_edit(&old_text, &self.text, range, new_text);
4150                    highlighter_edits.push((edit, self.text.clone()));
4151                }
4152            } else {
4153                self.mode.update_highlighter(
4154                    super::mode::HighlighterUpdate {
4155                        selected_range: range,
4156                        old_text: &old_text,
4157                        new_text: &self.text,
4158                        change_text: new_text,
4159                        force: true,
4160                    },
4161                    window,
4162                    cx,
4163                );
4164
4165                self.update_fold_candidates_incremental(range, new_text);
4166            }
4167        }
4168
4169        if group {
4170            self.mode
4171                .update_highlighter_batch::<M>(&highlighter_edits, window, cx);
4172
4173            // Fold candidates are read from the reparsed document, so each
4174            // edit is located where it ended up once every edit was applied.
4175            // `sorted` runs from the highest offset down.
4176            let mut shift = 0isize;
4177            for (range, new_text) in sorted.iter().rev() {
4178                let start = (range.start as isize + shift) as usize;
4179                self.update_fold_candidates_incremental(&(start..start), new_text);
4180                shift += new_text.len() as isize - range.len() as isize;
4181            }
4182
4183            self.undo_manager.commit_transaction();
4184        }
4185
4186        // One observable update per batch instead of one per edit.
4187        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4188            diagnostics.reset(&self.text)
4189        }
4190        M::refresh_language_features(self, window, cx);
4191        self.update_search(cx);
4192
4193        // Compute the resulting cursors.
4194        // One collapsed cursor per edit, at the end of its inserted text.
4195        let mut ascending: Vec<(Range<usize>, &str)> = sorted.clone();
4196        ascending.sort_by_key(|edit| edit.0.start);
4197        let text_len = self.text.len();
4198        let mut delta: isize = 0;
4199        let mut edit_results = Vec::with_capacity(ascending.len());
4200        for (range, new_text) in &ascending {
4201            let offset = ((range.start as isize + delta) as usize + new_text.len()).min(text_len);
4202            edit_results.push((range.clone(), offset));
4203            delta += new_text.len() as isize - (range.end as isize - range.start as isize);
4204        }
4205
4206        let mut used = vec![false; edit_results.len()];
4207        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(ascending.len());
4208        for selection in original_selections {
4209            if let Ok(index) = edit_results
4210                .binary_search_by_key(&(selection.start, selection.end), |(range, _)| {
4211                    (range.start, range.end)
4212                })
4213                && !used[index]
4214            {
4215                let offset = edit_results[index].1;
4216                used[index] = true;
4217                let mut selection = selection;
4218                selection.place_at(offset, None);
4219                new_selections.push(selection);
4220            }
4221        }
4222        for (index, (_, offset)) in edit_results.into_iter().enumerate() {
4223            if !used[index] {
4224                new_selections.push(CursorSelection::new(
4225                    self.selections.generate_id(),
4226                    offset,
4227                    offset,
4228                ));
4229            }
4230        }
4231        self.selections.replace_all(new_selections);
4232        self.selections.merge_overlapping();
4233
4234        // Record the cursor snapshots for undo/redo restore.
4235        let selections_after: Vec<CursorSelection> = self.selections.iter().copied().collect();
4236        if recorded {
4237            self.undo_manager.record_auto_closed_pairs(
4238                auto_closed_pairs_before,
4239                self.mode.auto_closed_pairs().clone(),
4240            );
4241            self.undo_manager
4242                .record_selections(selections_before, selections_after);
4243        }
4244
4245        self.ime_marked_range.take();
4246        self.update_preferred_column();
4247        self.dismiss_touch_selection(cx);
4248        if self.is_multi_line() {
4249            self.mode.update_auto_grow(&self.display_map);
4250        }
4251        if self.emit_events {
4252            cx.emit(InputEvent::Change);
4253        }
4254        cx.notify();
4255    }
4256
4257    /// Update fold candidates from tree-sitter syntax tree (full extraction).
4258    /// Used only on initial load or language changes.
4259    fn update_fold_candidates(&mut self) {
4260        if !self.mode.is_folding() {
4261            return;
4262        }
4263
4264        let Some(highlighter_rc) = self.mode.highlighter() else {
4265            return;
4266        };
4267
4268        let highlighter = highlighter_rc.borrow();
4269        let Some(highlighter) = highlighter.as_ref() else {
4270            return;
4271        };
4272
4273        let fold_ranges = highlighter.fold_ranges(&self.text);
4274        self.display_map.set_fold_candidates(fold_ranges);
4275    }
4276
4277    /// Incrementally update fold candidates after a text edit.
4278    /// Only traverses the edited region of the syntax tree instead of the full tree.
4279    fn update_fold_candidates_incremental(&mut self, edit_range: &Range<usize>, new_text: &str) {
4280        if !self.mode.is_folding() {
4281            return;
4282        }
4283
4284        let Some(highlighter_rc) = self.mode.highlighter() else {
4285            return;
4286        };
4287
4288        let highlighter = highlighter_rc.borrow();
4289        let Some(highlighter) = highlighter.as_ref() else {
4290            return;
4291        };
4292
4293        // The new byte range in the updated text after the edit
4294        let new_end = edit_range.start + new_text.len();
4295        self.display_map.update_fold_candidates_for_edit(
4296            |range, text| highlighter.fold_ranges_for_edit(range, text),
4297            edit_range.start..new_end,
4298            &self.text,
4299        );
4300    }
4301}
4302
4303impl<M: InputModeKind> EntityInputHandler for InputBaseState<M> {
4304    fn text_for_range(
4305        &mut self,
4306        range_utf16: Range<usize>,
4307        adjusted_range: &mut Option<Range<usize>>,
4308        _window: &mut Window,
4309        _cx: &mut Context<Self>,
4310    ) -> Option<String> {
4311        let range = self.range_from_utf16(&range_utf16);
4312        adjusted_range.replace(self.range_to_utf16(&range));
4313        Some(self.text.slice(range).to_string())
4314    }
4315
4316    fn selected_text_range(
4317        &mut self,
4318        _ignore_disabled_input: bool,
4319        _window: &mut Window,
4320        _cx: &mut Context<Self>,
4321    ) -> Option<UTF16Selection> {
4322        Some(UTF16Selection {
4323            range: self.range_to_utf16(&self.selected_range()),
4324            reversed: self.active_selection().reversed,
4325        })
4326    }
4327
4328    fn marked_text_range(
4329        &self,
4330        _window: &mut Window,
4331        _cx: &mut Context<Self>,
4332    ) -> Option<Range<usize>> {
4333        self.ime_marked_range
4334            .map(|range| self.range_to_utf16(&range.into()))
4335    }
4336
4337    fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
4338        self.ime_marked_range = None;
4339        self.undo_manager.commit_transaction();
4340    }
4341
4342    /// Replace text in range.
4343    ///
4344    /// - If the new text is invalid, it will not be replaced.
4345    /// - If `range_utf16` is not provided, the current selected range will be used.
4346    fn replace_text_in_range(
4347        &mut self,
4348        range_utf16: Option<Range<usize>>,
4349        new_text: &str,
4350        window: &mut Window,
4351        cx: &mut Context<Self>,
4352    ) {
4353        let requested_intent = self.undo_manager.take_pending_intent();
4354        if !self.is_editable() {
4355            return;
4356        }
4357        let selection_before = *self.active_selection();
4358        // Committing a composition ends the transaction it opened, whether or
4359        // not the platform follows up with `unmark_text`.
4360        let ends_composition = self.ime_marked_range.is_some();
4361
4362        self.pause_blink_cursor(cx);
4363
4364        // NOTE: The normalization keeps the UTF-16 length, but may change the
4365        // UTF-8 byte length, so all the byte-offset calculations below must
4366        // use the normalized text.
4367        let new_text = if self.replaying_history {
4368            Cow::Borrowed(new_text)
4369        } else {
4370            self.normalize_input(new_text)
4371        };
4372        let new_text: &str = &new_text;
4373
4374        let range = range_utf16
4375            .as_ref()
4376            .map(|range_utf16| self.range_from_utf16(range_utf16))
4377            .or(self.ime_marked_range.map(|range| {
4378                let range = self.range_to_utf16(&(range.start..range.end));
4379                self.range_from_utf16(&range)
4380            }))
4381            .unwrap_or(self.selected_range());
4382        let range = self.normalize_token_range(range);
4383
4384        // Skip-over as a pure cursor move: a typed closer that already follows
4385        // the cursor moves past it without touching text or history, so Undo
4386        // never exposes a transient duplicate. Only for interactive single
4387        // keystrokes (current selection, no IME mark, not silent replays).
4388        // `take_pending_intent` above already consumed any request.
4389        if range.is_empty()
4390            && range.start == self.cursor()
4391            && self.ime_marked_range.is_none()
4392            && !self.silent_replace_text
4393            && self.is_code_editor()
4394            && self.selections.is_single()
4395            && self.active_selection().is_empty()
4396        {
4397            if let Some(target) = self.skip_over_target(new_text) {
4398                self.set_cursor_to(target);
4399                self.update_preferred_column();
4400                cx.notify();
4401                return;
4402            }
4403        }
4404
4405        if self.is_multi_line() {
4406            let multi_cursor = range_utf16.is_none()
4407                && self.ime_marked_range.is_none()
4408                && !self.selections.is_single();
4409            if multi_cursor {
4410                self.selections.merge_overlapping();
4411                let mut edits: Vec<(Range<usize>, String)> = self
4412                    .selections
4413                    .iter()
4414                    .map(|sel| (sel.start..sel.end, new_text.to_string()))
4415                    .collect();
4416                edits.sort_by_key(|(range, _)| range.start);
4417                // One keystroke across several cursors is one batch, committed
4418                // with the intent of the keystroke so a run of them coalesces
4419                // into one undo just like single-cursor typing.
4420                let intent =
4421                    requested_intent.unwrap_or_else(|| self.typing_intent(&edits, new_text));
4422                self.undo_manager.begin_transaction_with(intent);
4423                self.undo_manager.set_pending_intent(intent);
4424                self.replace_text_in_ranges(&edits, window, cx);
4425                self.undo_manager.commit_transaction();
4426            } else {
4427                if range_utf16.is_some() {
4428                    self.selections.remove_all_but_active();
4429                }
4430                if let Some(intent) = requested_intent {
4431                    self.undo_manager.set_pending_intent(intent);
4432                }
4433                if let Some((open_len, closer)) = self.auto_close_target(&range, new_text) {
4434                    // One edit keeps the pair atomic even at undo coalescing limits.
4435                    let replacement = format!("{new_text}{closer}");
4436                    self.replace_text_in_ranges(&[(range.clone(), replacement)], window, cx);
4437                    let cursor = range.start + new_text.len();
4438                    self.mode.track_auto_closed_pair(
4439                        cursor - open_len..cursor,
4440                        cursor..cursor + closer.len(),
4441                    );
4442                    self.undo_manager
4443                        .record_auto_closed_pairs_after(self.mode.auto_closed_pairs().clone());
4444                    self.set_cursor_to(cursor);
4445                    self.update_preferred_column();
4446                    self.undo_manager.record_selections(
4447                        vec![selection_before],
4448                        self.selections.iter().copied().collect(),
4449                    );
4450                } else {
4451                    self.replace_text_in_ranges(
4452                        &[(range.clone(), new_text.to_string())],
4453                        window,
4454                        cx,
4455                    );
4456                }
4457            }
4458            if ends_composition {
4459                self.undo_manager.commit_transaction();
4460            }
4461
4462            if !self.silent_replace_text {
4463                M::on_text_typed(self, &range, new_text, window, cx);
4464            }
4465            return;
4466        }
4467
4468        // Single-line path
4469        let old_text = self.text.clone();
4470        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4471        self.text.replace(range.clone(), new_text);
4472
4473        let mut new_offset = (range.start + new_text.len()).min(self.text.len());
4474
4475        // True if the mask has changed the text, e.g. regrouping the
4476        // separators or completing a leading dot.
4477        let mut mask_changed = false;
4478
4479        if self.is_single_line() {
4480            let pending_text = self.text.to_string();
4481            // Check if the new text is valid.
4482            //
4483            // Only reject the edit if the old text was valid, to avoid
4484            // trapping a pre-existing invalid text (e.g. a `default_value`
4485            // that does not conform), the user can still edit to fix it.
4486            if !self.replaying_history
4487                && !self.validated_token_edit
4488                && !self.is_valid_input(&pending_text, cx)
4489                && self.is_valid_input(&old_text.to_string(), cx)
4490            {
4491                self.text = old_text;
4492                return;
4493            }
4494
4495            if !self.replaying_history && !self.mask_pattern.is_none() {
4496                let mask_text = self.mask_pattern.mask(&pending_text);
4497                mask_changed = mask_text.as_str() != pending_text;
4498                self.text = Rope::from(mask_text.as_str());
4499                let new_text_len =
4500                    (new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
4501                new_offset = (range.start + new_text_len).min(mask_text.len());
4502            }
4503        }
4504
4505        if mask_changed {
4506            // Masking rewrites the whole document, so ranges recorded against
4507            // the old text no longer point at anything.
4508            M::reset_annotations(self);
4509        } else {
4510            M::adjust_annotations(self, &range, new_text.len());
4511        }
4512        if mask_changed {
4513            // A segment-based history entry no longer matches the masked
4514            // document, record a whole-document change instead, so that
4515            // undo/redo can restore the text exactly.
4516            self.push_history(
4517                &old_text,
4518                &(0..old_text.len()),
4519                &self.text.to_string(),
4520                Some(EditIntent::Atomic),
4521                selection_before,
4522                Some((new_offset..new_offset).into()),
4523            );
4524        } else {
4525            self.push_history(
4526                &old_text,
4527                &range,
4528                &new_text,
4529                requested_intent,
4530                selection_before,
4531                None,
4532            );
4533        }
4534        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4535            diagnostics.reset(&self.text)
4536        }
4537        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4538        self.display_map
4539            .adjust_folds_for_edit(&old_text, &range, new_text);
4540        self.display_map
4541            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4542
4543        self.mode.update_highlighter::<M>(
4544            super::mode::HighlighterUpdate {
4545                selected_range: &range,
4546                old_text: &old_text,
4547                new_text: &self.text,
4548                change_text: &new_text,
4549                force: true,
4550            },
4551            window,
4552            cx,
4553        );
4554
4555        self.update_fold_candidates_incremental(&range, new_text);
4556        M::refresh_language_features(self, window, cx);
4557        self.set_cursor_to(new_offset);
4558        self.ime_marked_range.take();
4559        // A commit ends the IME composition: macOS delivers `insertText:` for
4560        // the confirmed candidate without a following `unmarkText`, so close
4561        // the transaction here. Leaving it open would keep merging every later
4562        // edit into the same change, which then carries the text and selection
4563        // of the first composition.
4564        if ends_composition {
4565            self.undo_manager
4566                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4567            self.undo_manager.commit_transaction();
4568        }
4569        self.update_preferred_column();
4570        self.update_search(cx);
4571        self.dismiss_touch_selection(cx);
4572        if self.is_multi_line() {
4573            self.mode.update_auto_grow(&self.display_map);
4574        }
4575        if !self.silent_replace_text {
4576            M::on_text_typed(self, &range, &new_text, window, cx);
4577        }
4578        if self.emit_events {
4579            cx.emit(InputEvent::Change);
4580        }
4581        cx.notify();
4582    }
4583
4584    /// Mark text is the IME temporary insert on typing.
4585    fn replace_and_mark_text_in_range(
4586        &mut self,
4587        range_utf16: Option<Range<usize>>,
4588        new_text: &str,
4589        new_selected_range_utf16: Option<Range<usize>>,
4590        window: &mut Window,
4591        cx: &mut Context<Self>,
4592    ) {
4593        let requested_intent = self.undo_manager.take_pending_intent();
4594        if !self.is_editable() {
4595            return;
4596        }
4597        let selection_before = *self.active_selection();
4598
4599        let starts_composition = self.ime_marked_range.is_none();
4600        if starts_composition {
4601            // Even a canceled preedit separates the typing gestures on either
4602            // side; its no-op transaction must not reconnect those gestures.
4603            self.undo_manager.break_transaction_coalescing();
4604            self.undo_manager.begin_transaction();
4605        }
4606
4607        // Collapse any extra cursors so we never leave stale secondary cursors behind.
4608        self.selections.remove_all_but_active();
4609
4610        M::reset_language_features(self);
4611
4612        // See the same NOTE in `replace_text_in_range`.
4613        let new_text = if self.replaying_history {
4614            Cow::Borrowed(new_text)
4615        } else {
4616            self.normalize_input(new_text)
4617        };
4618        let new_text: &str = &new_text;
4619
4620        let range = range_utf16
4621            .as_ref()
4622            .map(|range_utf16| self.range_from_utf16(range_utf16))
4623            .or(self.ime_marked_range.map(|range| {
4624                let range = self.range_to_utf16(&(range.start..range.end));
4625                self.range_from_utf16(&range)
4626            }))
4627            .unwrap_or(self.selected_range());
4628        let range = self.normalize_token_range(range);
4629
4630        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
4631        let old_text = self.text.clone();
4632        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4633        self.text.replace(range.clone(), new_text);
4634
4635        if self.is_single_line() {
4636            let pending_text = self.text.to_string();
4637            // See the same NOTE in `replace_text_in_range`.
4638            if !self.replaying_history
4639                && !self.validated_token_edit
4640                && !self.is_valid_input(&pending_text, cx)
4641                && self.is_valid_input(&old_text.to_string(), cx)
4642            {
4643                self.text = old_text;
4644                if starts_composition {
4645                    self.undo_manager.commit_transaction();
4646                }
4647                return;
4648            }
4649        }
4650
4651        M::adjust_annotations(self, &range, new_text.len());
4652        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4653            diagnostics.reset(&self.text)
4654        }
4655        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4656        self.display_map
4657            .adjust_folds_for_edit(&old_text, &range, new_text);
4658        self.display_map
4659            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4660
4661        self.mode.update_highlighter::<M>(
4662            super::mode::HighlighterUpdate {
4663                selected_range: &range,
4664                old_text: &old_text,
4665                new_text: &self.text,
4666                change_text: &new_text,
4667                force: true,
4668            },
4669            window,
4670            cx,
4671        );
4672
4673        self.update_fold_candidates_incremental(&range, new_text);
4674        M::refresh_language_features(self, window, cx);
4675        if new_text.is_empty() {
4676            // Cancel selection, when cancel IME input.
4677            self.set_cursor_to(range.start);
4678            self.ime_marked_range = None;
4679        } else {
4680            self.ime_marked_range = Some((range.start..range.start + new_text.len()).into());
4681            let new_range = new_selected_range_utf16
4682                .as_ref()
4683                .map(|range_utf16| {
4684                    let new_text = Rope::from(new_text);
4685                    range.start + new_text.offset_utf16_to_offset(range_utf16.start)
4686                        ..range.start + new_text.offset_utf16_to_offset(range_utf16.end)
4687                })
4688                .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
4689            self.set_selection(new_range.start, new_range.end);
4690        }
4691        if self.is_multi_line() {
4692            self.mode.update_auto_grow(&self.display_map);
4693        }
4694        if self.push_history(
4695            &old_text,
4696            &range,
4697            new_text,
4698            requested_intent,
4699            selection_before,
4700            Some(*self.active_selection()),
4701        ) {
4702            self.undo_manager
4703                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4704            self.undo_manager.record_auto_closed_pairs(
4705                auto_closed_pairs_before,
4706                self.mode.auto_closed_pairs().clone(),
4707            );
4708        }
4709        if new_text.is_empty() {
4710            self.undo_manager.commit_transaction();
4711        }
4712        cx.notify();
4713    }
4714
4715    /// Used to position IME candidates.
4716    fn bounds_for_range(
4717        &mut self,
4718        range_utf16: Range<usize>,
4719        bounds: Bounds<Pixels>,
4720        _window: &mut Window,
4721        _cx: &mut Context<Self>,
4722    ) -> Option<Bounds<Pixels>> {
4723        let last_layout = self.last_layout.as_ref()?;
4724        let line_height = last_layout.line_height;
4725        let line_number_width = last_layout.line_number_width;
4726        let range = self.range_from_utf16(&range_utf16);
4727
4728        let mut start_origin = None;
4729        let mut end_origin = None;
4730        let line_number_origin = point(line_number_width, px(0.));
4731        let mut y_offset = last_layout.visible_top;
4732
4733        for (vi, line) in last_layout.lines.iter().enumerate() {
4734            if start_origin.is_some() && end_origin.is_some() {
4735                break;
4736            }
4737
4738            let index_offset = last_layout.visible_line_byte_offsets[vi];
4739
4740            if start_origin.is_none() {
4741                if let Some(p) = line.position_for_index(
4742                    range.start.saturating_sub(index_offset),
4743                    last_layout,
4744                    false,
4745                ) {
4746                    start_origin = Some(p + point(px(0.), y_offset));
4747                }
4748            }
4749
4750            if end_origin.is_none() {
4751                if let Some(p) = line.position_for_index(
4752                    range.end.saturating_sub(index_offset),
4753                    last_layout,
4754                    false,
4755                ) {
4756                    end_origin = Some(p + point(px(0.), y_offset));
4757                }
4758            }
4759
4760            y_offset += line.size(line_height).height;
4761        }
4762
4763        let start_origin = start_origin.or_else(|| {
4764            let offset = self.last_cursor.or(Some(self.cursor()))?;
4765            let (_, _, origin) = self.line_and_position_for_offset(offset);
4766            origin.map(|origin| origin - line_number_origin)
4767        })?;
4768        let mut end_origin = end_origin.unwrap_or(start_origin);
4769        // Ensure at same line.
4770        end_origin.y = start_origin.y;
4771
4772        Some(Bounds::from_corners(
4773            bounds.origin + line_number_origin + start_origin,
4774            // + line_height for show IME panel under the cursor line.
4775            bounds.origin + line_number_origin + point(end_origin.x, end_origin.y + line_height),
4776        ))
4777    }
4778
4779    fn character_index_for_point(
4780        &mut self,
4781        point: gpui::Point<Pixels>,
4782        _window: &mut Window,
4783        _cx: &mut Context<Self>,
4784    ) -> Option<usize> {
4785        let last_layout = self.last_layout.as_ref()?;
4786        let line_point = self.last_bounds?.localize(&point)?;
4787
4788        for (vi, line) in last_layout.lines.iter().enumerate() {
4789            let offset = last_layout.visible_line_byte_offsets[vi];
4790            if let Some(utf8_index) = line.index_for_position(line_point, last_layout) {
4791                return Some(self.offset_to_utf16(offset + utf8_index));
4792            }
4793        }
4794
4795        None
4796    }
4797
4798    fn text_input_configuration(
4799        &mut self,
4800        _window: &mut Window,
4801        _cx: &mut Context<Self>,
4802    ) -> TextInputConfiguration {
4803        InputBaseState::text_input_configuration(self)
4804    }
4805}
4806
4807impl<M: InputModeKind> Focusable for InputBaseState<M> {
4808    fn focus_handle(&self, _cx: &App) -> FocusHandle {
4809        self.focus_handle.clone()
4810    }
4811}
4812
4813impl<M: InputModeKind> Render for InputBaseState<M> {
4814    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
4815        // Before anything reads it: the element resolves this style during
4816        // layout and paint, and both happen after this call in the same frame.
4817        self.editor_style = self
4818            .projected_editor_style
4819            .resolved(&crate::Theme::global(cx).tokens);
4820        let entity = cx.entity();
4821        if self._pending_update {
4822            self.mode.update_highlighter::<M>(
4823                super::mode::HighlighterUpdate {
4824                    selected_range: &(0..0),
4825                    old_text: &self.text,
4826                    new_text: &self.text,
4827                    change_text: "",
4828                    force: false,
4829                },
4830                window,
4831                cx,
4832            );
4833
4834            self.update_fold_candidates();
4835            M::refresh_language_features(self, window, cx);
4836            self._pending_update = false;
4837        }
4838
4839        let element = div()
4840            .id("input-state")
4841            .key_context(CONTEXT)
4842            .track_focus(&self.focus_handle)
4843            .when(self.is_editable(), |this| {
4844                this.on_action(window.listener_for(&entity, InputBaseState::backspace))
4845                    .on_action(window.listener_for(&entity, InputBaseState::delete))
4846                    .on_action(
4847                        window.listener_for(&entity, InputBaseState::delete_to_beginning_of_line),
4848                    )
4849                    .on_action(window.listener_for(&entity, InputBaseState::delete_to_end_of_line))
4850                    .on_action(window.listener_for(&entity, InputBaseState::delete_previous_word))
4851                    .on_action(window.listener_for(&entity, InputBaseState::delete_next_word))
4852                    .on_action(window.listener_for(&entity, InputBaseState::enter))
4853                    .on_action(window.listener_for(&entity, InputBaseState::escape))
4854                    .on_action(window.listener_for(&entity, InputBaseState::paste))
4855                    .on_action(window.listener_for(&entity, InputBaseState::cut))
4856                    .on_action(window.listener_for(&entity, InputBaseState::undo))
4857                    .on_action(window.listener_for(&entity, InputBaseState::redo))
4858                    .when(self.is_multi_line(), |this| {
4859                        this.on_action(window.listener_for(&entity, InputBaseState::indent_inline))
4860                            .on_action(window.listener_for(&entity, InputBaseState::outdent_inline))
4861                            .on_action(window.listener_for(&entity, InputBaseState::indent_block))
4862                            .on_action(window.listener_for(&entity, InputBaseState::outdent_block))
4863                    })
4864            })
4865            .on_action(window.listener_for(&entity, InputBaseState::left))
4866            .on_action(window.listener_for(&entity, InputBaseState::right))
4867            .on_action(window.listener_for(&entity, InputBaseState::select_left))
4868            .on_action(window.listener_for(&entity, InputBaseState::select_right))
4869            .when(self.is_multi_line(), |this| {
4870                this.on_action(window.listener_for(&entity, InputBaseState::up))
4871                    .on_action(window.listener_for(&entity, InputBaseState::down))
4872                    .on_action(window.listener_for(&entity, InputBaseState::select_up))
4873                    .on_action(window.listener_for(&entity, InputBaseState::select_down))
4874                    .on_action(window.listener_for(&entity, InputBaseState::page_up))
4875                    .on_action(window.listener_for(&entity, InputBaseState::page_down))
4876                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_above))
4877                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_below))
4878            })
4879            .on_action(window.listener_for(&entity, InputBaseState::on_action_select_all))
4880            .on_action(window.listener_for(&entity, InputBaseState::select_to_start_of_line))
4881            .on_action(window.listener_for(&entity, InputBaseState::select_to_end_of_line))
4882            .on_action(window.listener_for(&entity, InputBaseState::select_to_previous_word))
4883            .on_action(window.listener_for(&entity, InputBaseState::select_to_next_word))
4884            .on_action(window.listener_for(&entity, InputBaseState::home))
4885            .on_action(window.listener_for(&entity, InputBaseState::end))
4886            .on_action(window.listener_for(&entity, InputBaseState::move_to_start))
4887            .on_action(window.listener_for(&entity, InputBaseState::move_to_end))
4888            .on_action(window.listener_for(&entity, InputBaseState::move_to_previous_word))
4889            .on_action(window.listener_for(&entity, InputBaseState::move_to_next_word))
4890            .on_action(window.listener_for(&entity, InputBaseState::select_to_start))
4891            .on_action(window.listener_for(&entity, InputBaseState::select_to_end))
4892            .on_action(window.listener_for(&entity, InputBaseState::show_character_palette))
4893            .on_action({
4894                let entity = entity.clone();
4895                move |_: &ActivateToken, window, cx| {
4896                    let state = entity.read(cx);
4897                    let activation = state
4898                        .token_spans()
4899                        .iter()
4900                        .find(|span| span.range() == state.selected_range())
4901                        .and_then(|span| {
4902                            state.range_to_bounds(&span.range()).and_then(|bounds| {
4903                                state.token_activation(
4904                                    span.range().start,
4905                                    bounds,
4906                                    gpui::ClickEvent::Keyboard(gpui::KeyboardClickEvent {
4907                                        bounds,
4908                                        ..Default::default()
4909                                    }),
4910                                )
4911                            })
4912                        });
4913                    if let Some((listener, event)) = activation {
4914                        listener(&event, window, cx);
4915                    }
4916                }
4917            })
4918            .on_action(window.listener_for(&entity, InputBaseState::copy))
4919            .on_action(window.listener_for(&entity, InputBaseState::on_action_search))
4920            .on_action(window.listener_for(&entity, InputBaseState::on_action_replace))
4921            .on_mouse_down(
4922                MouseButton::Left,
4923                window.listener_for(&entity, InputBaseState::on_mouse_down),
4924            )
4925            .on_mouse_down(
4926                MouseButton::Right,
4927                window.listener_for(&entity, InputBaseState::on_mouse_down),
4928            )
4929            .on_mouse_up(
4930                MouseButton::Left,
4931                window.listener_for(&entity, InputBaseState::on_mouse_up),
4932            )
4933            .on_mouse_up(
4934                MouseButton::Right,
4935                window.listener_for(&entity, InputBaseState::on_mouse_up),
4936            )
4937            .on_mouse_move(window.listener_for(&entity, InputBaseState::on_mouse_move))
4938            .on_scroll_wheel(window.listener_for(&entity, InputBaseState::on_scroll_wheel))
4939            .when(self.is_multi_line() && !self.disabled, |this| {
4940                this.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
4941            })
4942            .when(!self.disabled, |this| {
4943                if self.is_multi_line() && window.modifiers().alt {
4944                    this.cursor_crosshair()
4945                } else {
4946                    this.cursor_text()
4947                }
4948            })
4949            .flex_1()
4950            // A single line fills the frame and sits at its vertical center,
4951            // so the frame needs no layout of its own to hold it. It is never
4952            // shorter than its line, though: this root clips (overflow-x
4953            // hidden clips both axes), and a frame whose padding leaves less
4954            // than a line, like Component's Input, would cut off descenders.
4955            .map(|this| {
4956                if self.is_multi_line() {
4957                    this.h_full()
4958                } else {
4959                    this.min_h_full().flex().items_center()
4960                }
4961            })
4962            .flex_grow_1()
4963            .overflow_x_hidden()
4964            .when(self.is_multi_line(), |this| {
4965                this.pt(self.editor_paddings.top)
4966                    .pr(self.editor_paddings.right)
4967                    .pb(self.editor_paddings.bottom)
4968                    .pl(self.editor_paddings.left)
4969            })
4970            .child(TextElement::new(entity.clone()).placeholder(self.placeholder.clone()))
4971            .when(self.shows_scrollbar(), |this| {
4972                this.child(EditorScrollbar::new(entity.clone()))
4973            });
4974
4975        // Actions only one mode handles are registered by that mode, where
4976        // `Self` is concrete enough to name its own entity type.
4977        M::register_actions(element, &entity, window)
4978    }
4979}
4980
4981#[cfg(test)]
4982mod tests {
4983    use super::*;
4984
4985    #[gpui::test]
4986    fn revealing_a_password_keeps_text_assistance_disabled(cx: &mut TestAppContext) {
4987        cx.update(crate::init);
4988        cx.open_window(size(px(400.), px(100.)), |window, cx| {
4989            let state = cx.new(|cx| crate::input::InputState::new(window, cx).masked(true));
4990            let hidden = state.read(cx).text_input_configuration();
4991            assert!(!hidden.autocorrect && !hidden.suggestions);
4992            assert_eq!(hidden.autocapitalize, Autocapitalize::None);
4993
4994            // The component's reveal button uses this same state transition.
4995            state.update(cx, |state, cx| state.toggle_masked(window, cx));
4996            assert_eq!(state.read(cx).text_input_configuration(), hidden);
4997            state.update(cx, |state, cx| state.toggle_masked(window, cx));
4998            assert_eq!(state.read(cx).text_input_configuration(), hidden);
4999            gpui::EmptyView
5000        });
5001    }
5002
5003    #[gpui::test]
5004    fn text_input_configuration_follows_the_input(cx: &mut TestAppContext) {
5005        use crate::input::{InputState, TextareaState};
5006
5007        cx.update(crate::init);
5008        let mut states = None;
5009        cx.open_window(size(px(400.), px(100.)), |window, cx| {
5010            states = Some((
5011                cx.new(|cx| InputState::new(window, cx)),
5012                cx.new(|cx| InputState::new(window, cx).masked(true)),
5013                cx.new(|cx| TextareaState::new(window, cx)),
5014                cx.new(|cx| {
5015                    InputState::new(window, cx)
5016                        .input_action(TextInputAction::Search)
5017                        .autocorrect(true)
5018                        .autocapitalize(Autocapitalize::Words)
5019                }),
5020            ));
5021            gpui::EmptyView
5022        });
5023        let (plain, masked, textarea, search) = states.unwrap();
5024
5025        cx.read(|cx| {
5026            assert_eq!(
5027                plain.read(cx).text_input_configuration(),
5028                TextInputConfiguration {
5029                    input_action: TextInputAction::Done,
5030                    ..Default::default()
5031                }
5032            );
5033            assert_eq!(
5034                masked.read(cx).text_input_configuration(),
5035                TextInputConfiguration {
5036                    input_action: TextInputAction::Done,
5037                    ..Default::default()
5038                }
5039            );
5040            assert_eq!(
5041                textarea.read(cx).text_input_configuration(),
5042                TextInputConfiguration {
5043                    input_action: TextInputAction::Enter,
5044                    ..Default::default()
5045                }
5046            );
5047            let search = search.read(cx).text_input_configuration();
5048            assert_eq!(search.input_action, TextInputAction::Search);
5049            assert_eq!(search.autocapitalize, Autocapitalize::Words);
5050            assert!(search.autocorrect);
5051            assert!(search.suggestions);
5052        });
5053    }
5054
5055    #[gpui::test]
5056    fn test_multicursor_unicode_completion_uses_post_edit_start(cx: &mut TestAppContext) {
5057        use crate::input::{CompletionProvider, EditorState};
5058        use gpui::Task;
5059        use lsp_types::{CompletionContext, CompletionResponse};
5060        use std::cell::RefCell;
5061
5062        #[derive(Default)]
5063        struct Provider {
5064            starts: RefCell<Vec<usize>>,
5065            queries: RefCell<Vec<(usize, String)>>,
5066        }
5067
5068        impl CompletionProvider for Provider {
5069            fn is_completion_trigger(&self, offset: usize, _: &str, _: &mut App) -> bool {
5070                self.starts.borrow_mut().push(offset);
5071                true
5072            }
5073
5074            fn completions(
5075                &self,
5076                _: &Rope,
5077                offset: usize,
5078                trigger: CompletionContext,
5079                _: &mut Window,
5080                _: &mut App,
5081            ) -> Task<anyhow::Result<CompletionResponse>> {
5082                self.queries
5083                    .borrow_mut()
5084                    .push((offset, trigger.trigger_character.unwrap()));
5085                Task::ready(Ok(CompletionResponse::Array(vec![])))
5086            }
5087        }
5088
5089        cx.update(crate::init);
5090        let mut editor = None;
5091        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5092            editor = Some(cx.new(|cx| EditorState::new(window, cx).default_value("aa")));
5093            gpui::EmptyView
5094        });
5095        let editor = editor.unwrap();
5096        let provider = Rc::new(Provider::default());
5097        window
5098            .update(cx, |_, window, cx| {
5099                editor.update(cx, |state, cx| {
5100                    state.extras.lsp.completion_provider = Some(provider.clone());
5101                    state
5102                        .extras
5103                        .context_menu_content
5104                        .completion
5105                        .trigger_start_offset = Some(0);
5106                    state.extras.context_menu_content.completion.query = "aa".into();
5107                    state.set_cursor_to(1);
5108                    state.add_cursor_at(0, cx);
5109
5110                    state.replace_text_in_range(None, "中", window, cx);
5111
5112                    assert_eq!(state.value(), "中a中a");
5113                    assert_eq!(provider.starts.borrow().as_slice(), &[4]);
5114                    assert_eq!(provider.queries.borrow().as_slice(), &[(7, "中".into())]);
5115                });
5116            })
5117            .unwrap();
5118    }
5119
5120    #[gpui::test]
5121    fn test_inline_token_wrap_and_size_refresh(cx: &mut TestAppContext) {
5122        use crate::input::{InlineToken, InlineTokenPresentation};
5123        let width = Rc::new(Cell::new(4000.));
5124        let render_width = width.clone();
5125        let view = InputView::build_textarea(cx, |state| state.rows(4).default_value("@a@b"));
5126        view.window_handle
5127            .update(cx, |_, window, cx| {
5128                view.input.update(cx, |state, cx| {
5129                    state
5130                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5131                        .unwrap();
5132                    state
5133                        .replace_range_with_token(2..4, InlineToken::new("b", "@b"), window, cx)
5134                        .unwrap();
5135                    state.set_token_presentation(
5136                        InlineTokenPresentation::default()
5137                            .token(move |_, _, _| div().w(px(render_width.get()))),
5138                    );
5139                });
5140            })
5141            .unwrap();
5142        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5143        visual.update(|window, cx| window.draw(cx).clear(cx));
5144        view.input.read_with(&visual, |state, _| {
5145            assert_eq!(state.display_map.wrap_row_count(), 2);
5146            let first = state.range_to_bounds(&(0..2)).unwrap();
5147            let second = state.range_to_bounds(&(2..4)).unwrap();
5148            assert!(second.origin.y > first.origin.y);
5149            assert_eq!(
5150                state.last_layout.as_ref().unwrap().lines[0].wrapped_lines[0].len,
5151                2
5152            );
5153        });
5154        width.set(100.);
5155        view.input.update(&mut visual, |_, cx| cx.notify());
5156        visual.update(|window, cx| window.draw(cx).clear(cx));
5157        view.input.read_with(&visual, |state, _| {
5158            assert_eq!(state.display_map.wrap_row_count(), 1)
5159        });
5160    }
5161
5162    #[gpui::test]
5163    fn test_inline_token_geometry_and_reentrant_activation(cx: &mut TestAppContext) {
5164        use crate::input::{InlineToken, InlineTokenPresentation};
5165        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5166        let target = view.input.clone();
5167        view.window_handle
5168            .update(cx, |_, window, cx| {
5169                view.input.update(cx, |state, cx| {
5170                    state
5171                        .replace_range_with_token(
5172                            7..13,
5173                            InlineToken::new("a", "@alice"),
5174                            window,
5175                            cx,
5176                        )
5177                        .unwrap();
5178                    state.set_token_presentation(
5179                        InlineTokenPresentation::default()
5180                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5181                            .on_token_click(move |_, window, cx| {
5182                                target.update(cx, |state, cx| {
5183                                    state.set_value("activated", window, cx)
5184                                })
5185                            }),
5186                    );
5187                    state.focus(window, cx);
5188                    state.set_selected_range(7..13, cx);
5189                });
5190            })
5191            .unwrap();
5192        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5193        visual.update(|window, cx| window.draw(cx).clear(cx));
5194        visual.update(|window, cx| {
5195            let state = view.input.read(cx);
5196            let bounds = state.range_to_bounds(&(7..13)).unwrap();
5197            assert!((bounds.size.width - px(100.)).abs() < px(1.), "{bounds:?}");
5198            let (left, _) = state.index_for_mouse_position(bounds.origin + point(px(10.), px(5.)));
5199            let (right, _) = state.index_for_mouse_position(bounds.origin + point(px(90.), px(5.)));
5200            assert_eq!((left, right), (7, 13));
5201            window.dispatch_action(Box::new(ActivateToken), cx);
5202        });
5203        visual.run_until_parked();
5204        view.input.read_with(&visual, |state, _| {
5205            assert_eq!(state.value().as_ref(), "activated")
5206        });
5207    }
5208
5209    #[gpui::test]
5210    fn test_inline_token_click_selects_it(cx: &mut TestAppContext) {
5211        use crate::input::InlineToken;
5212        cx.update(crate::init);
5213        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5214        view.window_handle
5215            .update(cx, |_, window, cx| {
5216                view.input.update(cx, |state, cx| {
5217                    state
5218                        .replace_range_with_token(
5219                            7..13,
5220                            InlineToken::new("a", "@alice"),
5221                            window,
5222                            cx,
5223                        )
5224                        .unwrap();
5225                    state.set_selected_range(0..0, cx);
5226                });
5227            })
5228            .unwrap();
5229        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5230        visual.update(|window, cx| window.draw(cx).clear(cx));
5231        let bounds = view
5232            .input
5233            .read_with(&visual, |state, _| state.range_to_bounds(&(7..13)).unwrap());
5234        visual.simulate_click(bounds.center(), gpui::Modifiers::default());
5235        visual.run_until_parked();
5236        view.input.read_with(&visual, |state, _| {
5237            assert_eq!(state.selected_range(), 7..13, "a click selects the token");
5238        });
5239    }
5240
5241    #[gpui::test]
5242    fn test_inline_token_hover_reports_presence_without_selecting(cx: &mut TestAppContext) {
5243        use crate::input::{InlineToken, InlineTokenPresentation};
5244        use gpui::{Bounds, point, px, size};
5245        cx.update(crate::init);
5246        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5247        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
5248        view.window_handle
5249            .update(cx, |_, window, cx| {
5250                view.input.update(cx, |state, cx| {
5251                    state
5252                        .replace_range_with_token(
5253                            7..13,
5254                            InlineToken::new("a", "@alice"),
5255                            window,
5256                            cx,
5257                        )
5258                        .unwrap();
5259                    state.set_token_presentation(
5260                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5261                    );
5262                    state.set_selected_range(0..0, cx);
5263                });
5264            })
5265            .unwrap();
5266        view.window_handle
5267            .update(cx, |_, _, cx| {
5268                view.input.update(cx, |state, _| {
5269                    let (_, entered) = state
5270                        .token_hover(7, bounds, true, None)
5271                        .expect("hover enters an enabled token");
5272                    assert!(entered.is_hovered());
5273                    assert_eq!(entered.token().id().as_ref(), "a");
5274                    assert_eq!(entered.range(), 7..13);
5275                    assert_eq!(entered.bounds(), bounds);
5276                    let (_, left) = state
5277                        .token_hover(7, bounds, false, None)
5278                        .expect("hover leaves the same token");
5279                    assert!(!left.is_hovered());
5280                    assert_eq!(state.selected_range(), 0..0, "hover never selects");
5281                    assert!(state.token_hover(99, bounds, true, None).is_none());
5282                });
5283            })
5284            .unwrap();
5285
5286        // Disabled tokens suppress hover, matching click; readonly allows it.
5287        view.window_handle
5288            .update(cx, |_, _, cx| {
5289                view.input.update(cx, |state, cx| {
5290                    state.set_disabled(true, cx);
5291                });
5292            })
5293            .unwrap();
5294        view.window_handle
5295            .update(cx, |_, _, cx| {
5296                view.input.update(cx, |state, _| {
5297                    assert!(state.token_hover(7, bounds, true, None).is_none())
5298                })
5299            })
5300            .unwrap();
5301        view.window_handle
5302            .update(cx, |_, _, cx| {
5303                view.input.update(cx, |state, cx| {
5304                    state.set_disabled(false, cx);
5305                    state.set_readonly(true, cx);
5306                });
5307            })
5308            .unwrap();
5309        view.window_handle
5310            .update(cx, |_, _, cx| {
5311                view.input.update(cx, |state, _| {
5312                    assert!(state.token_hover(7, bounds, true, None).is_some())
5313                })
5314            })
5315            .unwrap();
5316
5317        // Without a hover listener there is no hover activation.
5318        view.window_handle
5319            .update(cx, |_, _, cx| {
5320                view.input.update(cx, |state, _| {
5321                    state.set_token_presentation(InlineTokenPresentation::default());
5322                });
5323            })
5324            .unwrap();
5325        view.window_handle
5326            .update(cx, |_, _, cx| {
5327                view.input.update(cx, |state, _| {
5328                    assert!(state.token_hover(7, bounds, true, None).is_none())
5329                })
5330            })
5331            .unwrap();
5332    }
5333
5334    #[gpui::test]
5335    fn test_inline_token_hover_exit_delivered_when_token_removed(cx: &mut TestAppContext) {
5336        use crate::input::{InlineToken, InlineTokenPresentation};
5337        use std::cell::RefCell;
5338        use std::rc::Rc;
5339        cx.update(crate::init);
5340        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5341        let events: Rc<RefCell<Vec<(SharedString, bool)>>> = Rc::new(RefCell::new(Vec::new()));
5342        let sink = events.clone();
5343        view.window_handle
5344            .update(cx, |_, window, cx| {
5345                view.input.update(cx, |state, cx| {
5346                    state
5347                        .replace_range_with_token(
5348                            7..13,
5349                            InlineToken::new("a", "@alice"),
5350                            window,
5351                            cx,
5352                        )
5353                        .unwrap();
5354                    state.set_token_presentation(
5355                        InlineTokenPresentation::default()
5356                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5357                            .on_token_hover(move |event, _, _| {
5358                                sink.borrow_mut()
5359                                    .push((event.token().id().clone(), event.is_hovered()));
5360                            }),
5361                    );
5362                });
5363            })
5364            .unwrap();
5365        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5366        visual.update(|window, cx| window.draw(cx).clear(cx));
5367        let known_bounds = view.input.read_with(&visual, |state, _| {
5368            state
5369                .token_bounds
5370                .get(&7)
5371                .copied()
5372                .expect("placed token bounds")
5373        });
5374        visual.simulate_mouse_move(known_bounds.center(), None, gpui::Modifiers::default());
5375        visual.run_until_parked();
5376        visual.update(|window, cx| window.draw(cx).clear(cx));
5377        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5378
5379        // Remove the token while the pointer is still over its stale row: the
5380        // exit must fire even though the token element that owns the hover
5381        // mask is gone.
5382        view.window_handle
5383            .update(cx, |_, window, cx| {
5384                view.input
5385                    .update(cx, |state, cx| state.set_value("no tokens", window, cx));
5386            })
5387            .unwrap();
5388        visual.run_until_parked();
5389        visual.update(|window, cx| window.draw(cx).clear(cx));
5390        assert_eq!(
5391            events.borrow().as_slice(),
5392            &[("a".into(), true), ("a".into(), false)]
5393        );
5394    }
5395
5396    #[gpui::test]
5397    fn test_inline_token_hover_bounds_stay_single_row_at_wrap_boundary(cx: &mut TestAppContext) {
5398        use crate::input::{InlineToken, InlineTokenPresentation};
5399        cx.update(crate::init);
5400        let view = InputView::build_textarea(cx, |state| {
5401            state
5402                .rows(4)
5403                .default_value("aaaa bbbb cccc dddd @alice eeee ffff")
5404        });
5405        view.window_handle
5406            .update(cx, |_, window, cx| {
5407                view.input.update(cx, |state, cx| {
5408                    state
5409                        .replace_range_with_token(
5410                            20..26,
5411                            InlineToken::new("a", "@alice"),
5412                            window,
5413                            cx,
5414                        )
5415                        .unwrap();
5416                    state.set_token_presentation(
5417                        InlineTokenPresentation::default()
5418                            .token(|_, _, _| div().w(px(60.)).h(px(20.))),
5419                    );
5420                });
5421            })
5422            .unwrap();
5423        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5424        visual.update(|window, cx| window.draw(cx).clear(cx));
5425        let (placed, ranged, line_height) = view.input.read_with(&visual, |state, _| {
5426            (
5427                state
5428                    .token_bounds
5429                    .get(&20)
5430                    .copied()
5431                    .expect("placed token bounds"),
5432                state.range_to_bounds(&(20..26)),
5433                state.last_layout.as_ref().unwrap().line_height,
5434            )
5435        });
5436        // The laid-out element always occupies exactly one row of height, even
5437        // when the range-extent path resolves the same end boundary onto the
5438        // next visual row at a soft-wrap edge.
5439        assert!(placed.size.height <= line_height + px(1.), "{placed:?}");
5440        assert!(
5441            placed.size.width <= px(61.) && placed.size.width > px(0.),
5442            "{placed:?}"
5443        );
5444        if let Some(ranged) = ranged {
5445            assert!(
5446                ranged.size.height >= placed.size.height - px(1.),
5447                "{ranged:?} vs {placed:?}"
5448            );
5449        }
5450    }
5451
5452    #[gpui::test]
5453    fn test_inline_token_hover_same_offset_replacement_exits_predecessor(cx: &mut TestAppContext) {
5454        use crate::input::{InlineToken, InlineTokenPresentation};
5455        use std::{cell::RefCell, rc::Rc};
5456        cx.update(crate::init);
5457        let view = InputView::build(cx, |state| state.default_value("@alice!"));
5458        let events = Rc::new(RefCell::new(Vec::new()));
5459        let sink = events.clone();
5460        view.window_handle
5461            .update(cx, |_, window, cx| {
5462                view.input.update(cx, |state, cx| {
5463                    state
5464                        .replace_range_with_token(0..6, InlineToken::new("a", "@alice"), window, cx)
5465                        .unwrap();
5466                    state.set_token_presentation(
5467                        InlineTokenPresentation::default()
5468                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5469                            .on_token_hover(move |event, _, _| {
5470                                sink.borrow_mut()
5471                                    .push((event.token().id().clone(), event.is_hovered()));
5472                            }),
5473                    );
5474                });
5475            })
5476            .unwrap();
5477        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5478        visual.update(|window, cx| window.draw(cx).clear(cx));
5479        let bounds = view
5480            .input
5481            .read_with(&visual, |state, _| state.token_bounds[&0]);
5482        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5483        visual.run_until_parked();
5484        visual.update(|window, cx| window.draw(cx).clear(cx));
5485        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5486
5487        view.window_handle
5488            .update(cx, |_, window, cx| {
5489                view.input.update(cx, |state, cx| {
5490                    state
5491                        .replace_range_with_token(0..6, InlineToken::new("b", "@alice"), window, cx)
5492                        .unwrap();
5493                });
5494            })
5495            .unwrap();
5496        visual.run_until_parked();
5497        visual.update(|window, cx| window.draw(cx).clear(cx));
5498        visual.simulate_mouse_move(
5499            bounds.center() + gpui::point(px(1.), px(0.)),
5500            None,
5501            gpui::Modifiers::default(),
5502        );
5503        visual.run_until_parked();
5504        visual.update(|window, cx| window.draw(cx).clear(cx));
5505        assert_eq!(
5506            events.borrow().as_slice(),
5507            &[("a".into(), true), ("a".into(), false), ("b".into(), true)]
5508        );
5509        // Disabling keeps the token mounted; re-enabling must reset GPUI's
5510        // retained hover state even with a stationary pointer.
5511        view.input
5512            .update(&mut visual, |state, cx| state.set_disabled(true, cx));
5513        visual.update(|window, cx| window.draw(cx).clear(cx));
5514        visual.run_until_parked();
5515        assert_eq!(events.borrow().last(), Some(&("b".into(), false)));
5516        view.input
5517            .update(&mut visual, |state, cx| state.set_disabled(false, cx));
5518        visual.update(|window, cx| window.draw(cx).clear(cx));
5519        visual.run_until_parked();
5520        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5521        visual.run_until_parked();
5522        assert_eq!(
5523            events.borrow().as_slice(),
5524            &[
5525                ("a".into(), true),
5526                ("a".into(), false),
5527                ("b".into(), true),
5528                ("b".into(), false),
5529                ("b".into(), true)
5530            ]
5531        );
5532        visual.simulate_mouse_move(
5533            bounds.bottom_right() + gpui::point(px(10.), px(10.)),
5534            None,
5535            gpui::Modifiers::default(),
5536        );
5537        visual.run_until_parked();
5538        assert_eq!(
5539            events.borrow().as_slice(),
5540            &[
5541                ("a".into(), true),
5542                ("a".into(), false),
5543                ("b".into(), true),
5544                ("b".into(), false),
5545                ("b".into(), true),
5546                ("b".into(), false)
5547            ]
5548        );
5549    }
5550
5551    #[gpui::test]
5552    fn test_inline_token_hover_older_exit_keeps_newer_snapshot(cx: &mut TestAppContext) {
5553        use crate::input::{InlineToken, InlineTokenPresentation};
5554        use gpui::{Bounds, point, px, size};
5555        cx.update(crate::init);
5556        let view = InputView::build(cx, |state| state.default_value("@a @b"));
5557        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
5558        view.window_handle
5559            .update(cx, |_, window, cx| {
5560                view.input.update(cx, |state, cx| {
5561                    state
5562                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5563                        .unwrap();
5564                    state
5565                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
5566                        .unwrap();
5567                    state.set_token_presentation(
5568                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5569                    );
5570                    // The pointer moves A -> B; GPUI dispatches B's enter
5571                    // before A's exit.
5572                    state
5573                        .token_hover(0, bounds, true, None)
5574                        .expect("hover enters A");
5575                    state
5576                        .token_hover(3, bounds, true, None)
5577                        .expect("hover enters B");
5578                    let (_, exit_a) = state.token_hover(0, bounds, false, None).expect("A exits");
5579                    assert!(!exit_a.is_hovered());
5580                    // B's snapshot must survive A's late exit.
5581                    assert_eq!(
5582                        state
5583                            .hovered_token
5584                            .as_ref()
5585                            .expect("B still hovered")
5586                            .span
5587                            .token()
5588                            .id()
5589                            .as_ref(),
5590                        "b"
5591                    );
5592                    // Removing B afterwards still produces B's reconciled exit.
5593                    state.set_value("no tokens", window, cx);
5594                    state.token_bounds.clear();
5595                    let (listener, exit_b) = state.reconcile_token_hover().expect("B must exit");
5596                    assert!(!exit_b.is_hovered());
5597                    assert_eq!(exit_b.token().id().as_ref(), "b");
5598                    drop(listener);
5599                });
5600            })
5601            .unwrap();
5602    }
5603
5604    #[gpui::test]
5605    fn test_inline_token_hover_late_exit_matches_replaced_token(cx: &mut TestAppContext) {
5606        use crate::input::{InlineToken, InlineTokenPresentation};
5607        cx.update(crate::init);
5608        let view = InputView::build(cx, |state| state.default_value("@a"));
5609        view.window_handle
5610            .update(cx, |_, window, cx| {
5611                view.input.update(cx, |state, cx| {
5612                    let a = InlineToken::new("a", "@a");
5613                    let b = InlineToken::new("b", "@a");
5614                    state
5615                        .replace_range_with_token(0..2, a.clone(), window, cx)
5616                        .unwrap();
5617                    state.set_token_presentation(
5618                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5619                    );
5620                    let bounds = gpui::Bounds::default();
5621                    state.token_hover(0, bounds, true, Some(&a)).unwrap();
5622                    state
5623                        .replace_range_with_token(0..2, b.clone(), window, cx)
5624                        .unwrap();
5625                    state.token_hover(0, bounds, true, Some(&b)).unwrap();
5626                    let (_, exit_a) = state.token_hover(0, bounds, false, Some(&a)).unwrap();
5627                    assert_eq!(exit_a.token().id().as_ref(), "a");
5628                    assert_eq!(state.hovered_token.as_ref().unwrap().span.token(), &b);
5629                    let (_, exit_b) = state.token_hover(0, bounds, false, Some(&b)).unwrap();
5630                    assert_eq!(exit_b.token().id().as_ref(), "b");
5631                    assert!(state.pending_token_hover_exits.is_empty());
5632                });
5633            })
5634            .unwrap();
5635    }
5636
5637    #[gpui::test]
5638    fn test_inline_token_hover_exit_keeps_entry_utf16_after_edit(cx: &mut TestAppContext) {
5639        use crate::input::{InlineToken, InlineTokenPresentation};
5640        use gpui::EntityInputHandler as _;
5641        cx.update(crate::init);
5642        let view = InputView::build(cx, |state| state.default_value("🙂 @a"));
5643        view.window_handle
5644            .update(cx, |_, window, cx| {
5645                view.input.update(cx, |state, cx| {
5646                    state
5647                        .replace_range_with_token(5..7, InlineToken::new("a", "@a"), window, cx)
5648                        .unwrap();
5649                    state.set_token_presentation(
5650                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5651                    );
5652                    let bounds = gpui::Bounds::default();
5653                    let (_, enter) = state.token_hover(5, bounds, true, None).unwrap();
5654                    assert_eq!(enter.range_utf16(), (3, 5));
5655                    state.replace_text_in_range(Some(0..2), "abcd", window, cx);
5656                    assert_eq!(state.token_spans()[0].range(), 5..7);
5657                    let (_, exit) = state.token_hover(5, bounds, false, None).unwrap();
5658                    assert_eq!(exit.range_utf16(), (3, 5));
5659                });
5660            })
5661            .unwrap();
5662    }
5663
5664    #[gpui::test]
5665    fn test_inline_token_hover_callback_edit_preserves_both_exits(cx: &mut TestAppContext) {
5666        use crate::input::{InlineToken, InlineTokenPresentation};
5667        cx.update(crate::init);
5668        let view = InputView::build(cx, |state| state.default_value("@a @b"));
5669        view.window_handle
5670            .update(cx, |_, window, cx| {
5671                view.input.update(cx, |state, cx| {
5672                    state
5673                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5674                        .unwrap();
5675                    state
5676                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
5677                        .unwrap();
5678                    state.set_token_presentation(
5679                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5680                    );
5681                    let bounds = gpui::Bounds::default();
5682                    state.token_hover(0, bounds, true, None).unwrap();
5683                    state.token_hover(3, bounds, true, None).unwrap();
5684                    // Model an edit in B's enter callback before A's queued exit.
5685                    state.set_value("gone", window, cx);
5686                    let (_, exit_a) = state.token_hover(0, bounds, false, None).unwrap();
5687                    assert_eq!(exit_a.token().id().as_ref(), "a");
5688                    let (_, exit_b) = state.reconcile_token_hover().unwrap();
5689                    assert_eq!(exit_b.token().id().as_ref(), "b");
5690                    assert!(state.token_hover(0, bounds, false, None).is_none());
5691                    assert!(state.reconcile_token_hover().is_none());
5692                });
5693            })
5694            .unwrap();
5695    }
5696
5697    #[gpui::test]
5698    fn test_inline_token_edit_history_and_validation(cx: &mut TestAppContext) {
5699        let view = InputView::build(cx, |state| state.default_value("问 @alice!"));
5700        view.window_handle
5701            .update(cx, |_, window, cx| {
5702                view.input.update(cx, |state, cx| {
5703                    let token =
5704                        crate::input::InlineToken::new("alice-1", "@alice").with_label("Alice");
5705                    state
5706                        .replace_range_with_token(4..10, token.clone(), window, cx)
5707                        .unwrap();
5708                    assert_eq!(state.value().as_ref(), "问 @alice!");
5709                    assert_eq!(state.tokens()[0].range(), 4..10);
5710                    assert_eq!(state.next_end_of_word_at(4), 10);
5711                    assert_eq!(state.previous_start_of_word_at(10), 4);
5712                    state.undo(&Undo, window, cx);
5713                    assert!(
5714                        state.tokens().is_empty(),
5715                        "identity-only association is undoable"
5716                    );
5717                    assert_eq!(state.value().as_ref(), "问 @alice!");
5718                    state.redo(&Redo, window, cx);
5719                    assert_eq!(state.tokens()[0].token(), &token);
5720                    state.set_selected_range(6..7, cx);
5721                    assert_eq!(state.selected_range(), 4..10);
5722                    state.replace("", window, cx);
5723                    assert_eq!(state.value().as_ref(), "问 !");
5724                    assert!(state.tokens().is_empty());
5725                    state.undo(&Undo, window, cx);
5726                    assert_eq!(state.tokens()[0].token(), &token);
5727                    state.set_selected_range(0..0, cx);
5728                    state.replace("🙂", window, cx);
5729                    assert_eq!(state.tokens()[0].range(), 8..14);
5730                    state.undo(&Undo, window, cx);
5731                    assert_eq!(state.tokens()[0].range(), 4..10);
5732                    let before = state.content();
5733                    state
5734                        .replace_range_with_token(0..0, token.clone(), window, cx)
5735                        .expect("the same reference may occur twice");
5736                    assert_eq!(state.value().as_ref(), "@alice问 @alice!");
5737                    assert_eq!(state.tokens().len(), 2);
5738                    assert_eq!(state.tokens()[1].token(), &token);
5739                    state.undo(&Undo, window, cx);
5740                    assert_eq!(state.content(), before);
5741                    state.set_value(before.text().clone(), window, cx);
5742                    assert!(state.tokens().is_empty());
5743                    assert!(!state.undo_manager.has_undos());
5744                });
5745            })
5746            .unwrap();
5747    }
5748
5749    #[gpui::test]
5750    fn test_inline_token_textarea_ime_and_modes(cx: &mut TestAppContext) {
5751        use crate::input::{InlineToken, InlineTokenError, InputContent};
5752        let view = InputView::build_textarea(cx, |state| state);
5753        view.window_handle
5754            .update(cx, |_, window, cx| {
5755                view.input.update(cx, |state, cx| {
5756                    let content = InputContent::new("@a@b\n后面")
5757                        .with_token(0..2, InlineToken::new("a", "@a"))
5758                        .unwrap()
5759                        .with_token(2..4, InlineToken::new("b", "@b"))
5760                        .unwrap();
5761                    state.set_value(content.clone(), window, cx);
5762                    state.set_selected_range(2..2, cx);
5763                    assert_eq!(state.previous_boundary(2), 0);
5764                    assert_eq!(state.next_boundary(2), 4);
5765                    state.replace_and_mark_text_in_range(Some(1..2), "中", Some(1..1), window, cx);
5766                    assert_eq!(state.value().as_ref(), "中@b\n后面");
5767                    assert_eq!(state.tokens().len(), 1);
5768                    assert_eq!(
5769                        state.replace_with_token(InlineToken::new("x", "x"), window, cx),
5770                        Err(InlineTokenError::CompositionActive)
5771                    );
5772                    state.replace_text_in_range(None, "中文", window, cx);
5773                    state.undo(&Undo, window, cx);
5774                    assert_eq!(state.content(), content);
5775                    state.redo(&Redo, window, cx);
5776                    assert_eq!(state.value().as_ref(), "中文@b\n后面");
5777                    state.undo(&Undo, window, cx);
5778                    state.replace_all("plain", window, cx);
5779                    assert!(state.tokens().is_empty());
5780                    state.undo(&Undo, window, cx);
5781                    assert_eq!(state.content(), content);
5782                });
5783            })
5784            .unwrap();
5785        let view = InputView::build(cx, |state| state);
5786        view.window_handle
5787            .update(cx, |_, window, cx| {
5788                view.input.update(cx, |state, cx| {
5789                    assert_eq!(
5790                        InputContent::new("a\u{301}")
5791                            .with_token(0..1, InlineToken::new("bad", "a"))
5792                            .err(),
5793                        Some(InlineTokenError::InvalidBoundary)
5794                    );
5795                    assert_eq!(
5796                        InputContent::new("@a@a")
5797                            .with_token(0..2, InlineToken::new("a", "@a"))
5798                            .unwrap()
5799                            .with_token(1..3, InlineToken::new("x", "a@"))
5800                            .err(),
5801                        Some(InlineTokenError::OverlappingTokens)
5802                    );
5803                    state
5804                        .replace_with_token(InlineToken::new("a", "@a"), window, cx)
5805                        .unwrap();
5806                    let before = state.content();
5807                    state.set_masked(true, window, cx);
5808                    assert!(!state.tokens_visible());
5809                    assert_eq!(
5810                        state.replace_with_token(InlineToken::new("b", "b"), window, cx),
5811                        Err(InlineTokenError::UnsupportedMode)
5812                    );
5813                    assert_eq!(state.content(), before);
5814                    state.undo(&Undo, window, cx);
5815                    assert!(state.tokens().is_empty());
5816                    state.redo(&Redo, window, cx);
5817                    assert_eq!(state.content(), before);
5818                    assert!(!state.tokens_visible());
5819                });
5820            })
5821            .unwrap();
5822    }
5823
5824    use crate::theme::Theme;
5825    use gpui::{TestAppContext, VisualTestContext, size};
5826
5827    use crate::input::{EditorMode, EditorState, InputMode, LanguageConfig, TextareaMode};
5828
5829    fn set_test_syntax_provider(
5830        provider: Rc<dyn crate::input::SyntaxContextProvider>,
5831        cx: &mut App,
5832    ) {
5833        struct TestLanguages(Rc<dyn crate::input::SyntaxContextProvider>);
5834        impl crate::input::LanguageProvider for TestLanguages {
5835            fn syntax_context_provider(
5836                &self,
5837                _: &str,
5838            ) -> Option<Rc<dyn crate::input::SyntaxContextProvider>> {
5839                Some(self.0.clone())
5840            }
5841        }
5842        crate::input::set_language_provider(Rc::new(TestLanguages(provider)), cx);
5843    }
5844
5845    struct TestRoot<M: InputModeKind>(Entity<InputBaseState<M>>);
5846
5847    impl<M: InputModeKind> Render for TestRoot<M> {
5848        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5849            div().size_full().child(self.0.clone())
5850        }
5851    }
5852
5853    struct InputView<M: InputModeKind> {
5854        input: Entity<InputBaseState<M>>,
5855        window_handle: gpui::WindowHandle<TestRoot<M>>,
5856    }
5857
5858    /// Helper to open a state of one mode in a window for testing.
5859    impl<M: InputModeKind> InputView<M> {
5860        fn build_with(
5861            cx: &mut TestAppContext,
5862            make: impl FnOnce(&mut Window, &mut Context<InputBaseState<M>>) -> InputBaseState<M>
5863            + 'static,
5864        ) -> Self {
5865            let mut input: Option<Entity<InputBaseState<M>>> = None;
5866
5867            let window = cx.update(|cx| {
5868                cx.open_window(Default::default(), |window, cx| {
5869                    // Set up the theme first
5870                    cx.set_global(Theme::default());
5871                    // Initialize input keybindings
5872                    super::super::init(cx);
5873
5874                    input = Some(cx.new(|cx| make(window, cx)));
5875
5876                    cx.new(|_| TestRoot(input.clone().unwrap()))
5877                })
5878                .unwrap()
5879            });
5880
5881            Self {
5882                input: input.clone().unwrap(),
5883                window_handle: window,
5884            }
5885        }
5886    }
5887
5888    impl InputView<EditorMode> {
5889        /// An editor state, for the tests that exercise code-editor behavior.
5890        fn new(cx: &mut TestAppContext) -> Self {
5891            Self::build_editor(cx, |state| state)
5892        }
5893
5894        fn build_editor(
5895            cx: &mut TestAppContext,
5896            f: impl FnOnce(InputBaseState<EditorMode>) -> InputBaseState<EditorMode> + 'static,
5897        ) -> Self {
5898            Self::build_with(cx, move |window, cx| {
5899                f(crate::input::EditorState::new(window, cx).language("sql"))
5900            })
5901        }
5902    }
5903
5904    impl InputView<TextareaMode> {
5905        fn build_textarea(
5906            cx: &mut TestAppContext,
5907            f: impl FnOnce(InputBaseState<TextareaMode>) -> InputBaseState<TextareaMode> + 'static,
5908        ) -> Self {
5909            Self::build_with(cx, move |window, cx| {
5910                f(crate::input::TextareaState::new(window, cx))
5911            })
5912        }
5913    }
5914
5915    impl InputView<InputMode> {
5916        /// A single-line state, the default these tests were written against.
5917        fn build(
5918            cx: &mut TestAppContext,
5919            f: impl FnOnce(InputBaseState<InputMode>) -> InputBaseState<InputMode> + 'static,
5920        ) -> Self {
5921            Self::build_with(cx, move |window, cx| {
5922                f(crate::input::InputState::new(window, cx))
5923            })
5924        }
5925    }
5926
5927    /// Sends one trackpad packet straight to the input.
5928    fn swipe<M: InputModeKind>(
5929        view: &InputView<M>,
5930        cx: &mut VisualTestContext,
5931        dy: f32,
5932        phase: gpui::TouchPhase,
5933    ) {
5934        cx.update(|window, cx| {
5935            view.input.update(cx, |state, cx| {
5936                state.on_scroll_wheel(
5937                    &ScrollWheelEvent {
5938                        delta: gpui::ScrollDelta::Pixels(point(px(0.), px(dy))),
5939                        touch_phase: phase,
5940                        ..Default::default()
5941                    },
5942                    window,
5943                    cx,
5944                )
5945            })
5946        });
5947    }
5948
5949    fn long_text() -> String {
5950        (0..60)
5951            .map(|n| format!("line {n}"))
5952            .collect::<Vec<_>>()
5953            .join("\n")
5954    }
5955
5956    #[gpui::test]
5957    fn test_scroll_bounce_stretches_past_the_end_and_springs_back(cx: &mut TestAppContext) {
5958        use gpui::TouchPhase::{Ended, Moved, Started};
5959
5960        let view = InputView::build_textarea(cx, |state| {
5961            state.scroll_bounce(true).default_value(long_text())
5962        });
5963        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
5964        cx.update(|window, cx| window.draw(cx).clear(cx));
5965        let drawn_at = |cx: &mut VisualTestContext| {
5966            view.input
5967                .read_with(cx, |state, _| state.last_bounds.unwrap().origin.y)
5968        };
5969
5970        swipe(&view, &mut cx, -20., Started);
5971        swipe(&view, &mut cx, -100_000., Moved);
5972        let (end, stretch) = view.input.read_with(&cx, |state, _| {
5973            (state.scroll_handle.offset().y, state.overscroll.offset())
5974        });
5975        assert!(end < px(0.), "the text scrolled to its end");
5976        assert!(stretch < 0., "the rest stretched the edge: {stretch}");
5977        cx.update(|window, cx| window.draw(cx).clear(cx));
5978        let stretched_at = drawn_at(&mut cx);
5979
5980        swipe(&view, &mut cx, 0., Ended);
5981        for _ in 0..120 {
5982            cx.executor()
5983                .advance_clock(std::time::Duration::from_millis(16));
5984            cx.update(|window, cx| window.draw(cx).clear(cx));
5985        }
5986        view.input.read_with(&cx, |state, _| {
5987            assert_eq!(state.overscroll.offset(), 0., "the edge sprang back");
5988            assert_eq!(
5989                state.scroll_handle.offset().y,
5990                end,
5991                "scrolling stayed exact"
5992            );
5993        });
5994        let moved = (stretched_at - drawn_at(&mut cx)).as_f32();
5995        assert!(
5996            (moved - stretch).abs() < 0.001,
5997            "the stretch only moved where the text was drawn: {moved} vs {stretch}"
5998        );
5999    }
6000
6001    /// Swipes far past the end of the text and returns where the text
6002    /// scrolled to and how far its edge stretched.
6003    fn swipe_past_the_end(
6004        view: &InputView<TextareaMode>,
6005        cx: &mut VisualTestContext,
6006    ) -> (Pixels, f32) {
6007        use gpui::TouchPhase::{Moved, Started};
6008
6009        cx.update(|window, cx| window.draw(cx).clear(cx));
6010        swipe(view, cx, -20., Started);
6011        swipe(view, cx, -100_000., Moved);
6012        view.input.read_with(cx, |state, _| {
6013            (state.scroll_handle.offset().y, state.overscroll.offset())
6014        })
6015    }
6016
6017    #[gpui::test]
6018    fn test_scroll_bounce_stays_put_under_reduced_motion(cx: &mut TestAppContext) {
6019        cx.update(|cx| cx.set_reduce_motion(true));
6020        let view = InputView::build_textarea(cx, |state| {
6021            state.scroll_bounce(true).default_value(long_text())
6022        });
6023        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6024        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6025        assert!(end < px(0.), "the text still scrolls");
6026        assert_eq!(stretch, 0., "the edge does not stretch");
6027    }
6028
6029    #[gpui::test]
6030    fn test_scroll_bounce_follows_the_platform_by_default(cx: &mut TestAppContext) {
6031        let view = InputView::build_textarea(cx, |state| state.default_value(long_text()));
6032        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6033        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6034        assert!(end < px(0.), "the text scrolls");
6035        assert_eq!(
6036            stretch != 0.,
6037            crate::ScrollBounce::enabled_by_default(),
6038            "the edge stretches where the platform's scroll views do"
6039        );
6040    }
6041
6042    #[gpui::test]
6043    fn test_scroll_bounce_leaves_text_that_fits(cx: &mut TestAppContext) {
6044        let view = InputView::build_textarea(cx, |state| {
6045            state.scroll_bounce(true).default_value("one line")
6046        });
6047        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6048        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6049        assert_eq!((end, stretch), (px(0.), 0.));
6050    }
6051
6052    #[gpui::test]
6053    fn test_scroll_bounce_resets_when_replacement_text_fits(cx: &mut TestAppContext) {
6054        let view = InputView::build_textarea(cx, |state| {
6055            state.scroll_bounce(true).default_value(long_text())
6056        });
6057        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6058        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6059        assert!(stretch < 0.);
6060        cx.update(|window, cx| {
6061            view.input
6062                .update(cx, |state, cx| state.set_value("short", window, cx));
6063            window.draw(cx).clear(cx);
6064        });
6065        swipe(&view, &mut cx, 0., gpui::TouchPhase::Ended);
6066        view.input.read_with(&cx, |state, _| {
6067            assert_eq!(state.overscroll.offset(), 0.);
6068            assert_eq!(state.scroll_handle.offset().y, px(0.));
6069        });
6070    }
6071
6072    #[gpui::test]
6073    fn test_scroll_bounce_releases_when_the_end_packet_is_horizontal(cx: &mut TestAppContext) {
6074        let view = InputView::build_textarea(cx, |state| {
6075            state.scroll_bounce(true).default_value(long_text())
6076        });
6077        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6078        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6079        assert!(stretch < 0.);
6080        cx.update(|window, cx| {
6081            view.input.update(cx, |state, cx| {
6082                state.on_scroll_wheel(
6083                    &ScrollWheelEvent {
6084                        delta: gpui::ScrollDelta::Pixels(point(px(1.), px(0.))),
6085                        touch_phase: gpui::TouchPhase::Ended,
6086                        ..Default::default()
6087                    },
6088                    window,
6089                    cx,
6090                );
6091            });
6092        });
6093        for _ in 0..120 {
6094            cx.executor()
6095                .advance_clock(std::time::Duration::from_millis(16));
6096            cx.update(|window, cx| window.draw(cx).clear(cx));
6097        }
6098        assert_eq!(
6099            view.input
6100                .read_with(&cx, |state, _| state.overscroll.offset()),
6101            0.
6102        );
6103    }
6104
6105    #[gpui::test]
6106    fn test_scroll_bounce_ordinary_mouse_wheel_does_not_stretch(cx: &mut TestAppContext) {
6107        let view = InputView::build_textarea(cx, |state| {
6108            state.scroll_bounce(true).default_value(long_text())
6109        });
6110        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6111        cx.update(|window, cx| {
6112            window.draw(cx).clear(cx);
6113            view.input.update(cx, |state, cx| {
6114                state.on_scroll_wheel(
6115                    &ScrollWheelEvent {
6116                        delta: gpui::ScrollDelta::Lines(point(0., -100_000.)),
6117                        touch_phase: gpui::TouchPhase::Moved,
6118                        ..Default::default()
6119                    },
6120                    window,
6121                    cx,
6122                );
6123                assert!(state.scroll_handle.offset().y < px(0.));
6124                assert_eq!(state.overscroll.offset(), 0.);
6125            });
6126        });
6127    }
6128
6129    #[gpui::test]
6130    fn test_scroll_bounce_new_wheel_input_interrupts_the_return(cx: &mut TestAppContext) {
6131        let view = InputView::build_textarea(cx, |state| {
6132            state.scroll_bounce(true).default_value(long_text())
6133        });
6134        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6135        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6136        assert!(stretch < 0.);
6137        swipe(&view, &mut cx, 0., gpui::TouchPhase::Ended);
6138        cx.executor()
6139            .advance_clock(std::time::Duration::from_millis(16));
6140        cx.update(|window, cx| {
6141            window.draw(cx).clear(cx);
6142            view.input.update(cx, |state, cx| {
6143                state.on_scroll_wheel(
6144                    &ScrollWheelEvent {
6145                        delta: gpui::ScrollDelta::Lines(point(0., 2.)),
6146                        touch_phase: gpui::TouchPhase::Moved,
6147                        ..Default::default()
6148                    },
6149                    window,
6150                    cx,
6151                );
6152                assert!(state.scroll_handle.offset().y > end);
6153                assert_eq!(state.overscroll.offset(), 0.);
6154            });
6155        });
6156    }
6157
6158    #[gpui::test]
6159    fn test_turning_scroll_bounce_off_drops_the_stretch(cx: &mut TestAppContext) {
6160        let view = InputView::build_textarea(cx, |state| {
6161            state.scroll_bounce(true).default_value(long_text())
6162        });
6163        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6164        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6165        assert!(stretch < 0., "the edge stretched: {stretch}");
6166        view.input
6167            .update(&mut cx, |state, cx| state.set_scroll_bounce(false, cx));
6168        assert_eq!(
6169            view.input
6170                .read_with(&cx, |state, _| state.overscroll.offset()),
6171            0.
6172        );
6173    }
6174
6175    /// The frame is laid out by the application, which should not have to
6176    /// center a single line inside it.
6177    #[gpui::test]
6178    fn single_line_is_centered_in_a_taller_frame(cx: &mut TestAppContext) {
6179        struct Frame(Entity<InputBaseState<InputMode>>);
6180        impl Render for Frame {
6181            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6182                crate::input::InputBase::new("frame")
6183                    .h(px(60.))
6184                    .child(crate::input::Input::new(&self.0))
6185            }
6186        }
6187
6188        cx.update(crate::init);
6189        let mut input = None;
6190        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6191            let state = cx.new(|cx| crate::input::InputState::new(window, cx).default_value("a"));
6192            input = Some(state.clone());
6193            Frame(state)
6194        });
6195        let input = input.unwrap();
6196        let mut cx = VisualTestContext::from_window(window.into(), cx);
6197        cx.update(|window, cx| window.draw(cx).clear(cx));
6198
6199        input.read_with(&cx, |state, _| {
6200            let line = state.input_bounds;
6201            assert_eq!(line.center().y, px(30.), "line {line:?}");
6202        });
6203    }
6204
6205    #[gpui::test]
6206    fn test_noop_scroll_notifies_diagnostic_dismissal(cx: &mut TestAppContext) {
6207        use std::{cell::Cell, rc::Rc};
6208
6209        cx.update(crate::init);
6210        let mut input = None;
6211        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6212            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
6213            gpui::EmptyView
6214        });
6215        let input = input.unwrap();
6216        input.update(cx, |state, cx| {
6217            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
6218            state.scroll_size = size(px(100.), px(20.));
6219            state.present_diagnostic(crate::input::DiagnosticEntry::default(), cx);
6220        });
6221        let notifications = Rc::new(Cell::new(0));
6222        let count = notifications.clone();
6223        let _subscription =
6224            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6225
6226        window
6227            .update(cx, |_, window, cx| {
6228                input.update(cx, |state, cx| {
6229                    state.on_scroll_wheel(
6230                        &ScrollWheelEvent {
6231                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
6232                            ..Default::default()
6233                        },
6234                        window,
6235                        cx,
6236                    );
6237                    assert_eq!(state.scroll_handle.offset(), Point::default());
6238                    assert!(state.diagnostic_popover().is_none());
6239                });
6240            })
6241            .unwrap();
6242        assert_eq!(
6243            notifications.get(),
6244            1,
6245            "clearing a diagnostic must notify even when scrolling is clamped"
6246        );
6247        window
6248            .update(cx, |_, window, cx| {
6249                input.update(cx, |state, cx| {
6250                    state.on_scroll_wheel(
6251                        &ScrollWheelEvent {
6252                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
6253                            ..Default::default()
6254                        },
6255                        window,
6256                        cx,
6257                    );
6258                });
6259            })
6260            .unwrap();
6261        assert_eq!(notifications.get(), 1, "an unchanged input must stay quiet");
6262    }
6263
6264    #[gpui::test]
6265    fn test_cursor_layout_consumer_updates_after_selection(cx: &mut TestAppContext) {
6266        use std::{cell::Cell, rc::Rc};
6267        struct Panel {
6268            input: Entity<crate::input::EditorState>,
6269            observed: Rc<Cell<Option<(Bounds<Pixels>, Pixels)>>>,
6270        }
6271        impl Render for Panel {
6272            fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
6273                self.observed.set(self.input.read(cx).cursor_layout());
6274                div().size_full().child(self.input.clone())
6275            }
6276        }
6277        struct Root(Entity<Panel>);
6278        impl Render for Root {
6279            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6280                div().size_full().child(
6281                    self.0
6282                        .clone()
6283                        .cached(gpui::StyleRefinement::default().size_full()),
6284                )
6285            }
6286        }
6287        cx.update(crate::init);
6288        let observed = Rc::new(Cell::new(None));
6289        let mut input = None;
6290        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6291            let state = cx.new(|cx| {
6292                let mut state = crate::input::EditorState::new(window, cx);
6293                state.set_value("abcdefgh", window, cx);
6294                state
6295            });
6296            input = Some(state.clone());
6297            Root(cx.new(|_| Panel {
6298                input: state,
6299                observed: observed.clone(),
6300            }))
6301        });
6302        cx.run_until_parked();
6303        let input = input.unwrap();
6304        let before = input.read_with(cx, |state, _| state.cursor_layout());
6305        // The caret geometry only exists once the input has painted, and a
6306        // notify sent from inside a draw marks the view dirty without asking
6307        // for another frame, so the consumer reads the geometry on the next
6308        // invalidation rather than during the paint that produced it.
6309        input.update(cx, |_, cx| cx.notify());
6310        cx.run_until_parked();
6311        assert_eq!(observed.get(), before);
6312        window
6313            .update(cx, |_, _, cx| {
6314                input.update(cx, |state, cx| state.select_to_with_affinity(3, false, cx));
6315            })
6316            .unwrap();
6317        cx.run_until_parked();
6318        window.update(cx, |_, _, cx| cx.notify()).unwrap();
6319        let after = input.read_with(cx, |state, _| state.cursor_layout());
6320        assert_ne!(after, before, "the caret must actually move");
6321        assert_eq!(
6322            observed.get(),
6323            after,
6324            "render consumers must receive the newly painted caret geometry"
6325        );
6326    }
6327
6328    #[gpui::test]
6329    fn test_input_scroll_offset_notifies_only_on_change(cx: &mut TestAppContext) {
6330        use std::{cell::Cell, rc::Rc};
6331
6332        cx.update(crate::init);
6333        let mut input = None;
6334        let _window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6335            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
6336            gpui::EmptyView
6337        });
6338        let input = input.unwrap();
6339        let notifications = Rc::new(Cell::new(0));
6340        let count = notifications.clone();
6341        let _subscription =
6342            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6343
6344        input.update(cx, |state, _| {
6345            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
6346            state.scroll_size = size(px(300.), px(20.));
6347        });
6348        for target in [
6349            None,
6350            Some(point(px(0.), px(0.))),
6351            Some(point(px(20.), px(50.))),
6352        ] {
6353            input.update(cx, |state, cx| state.update_scroll_offset(target, cx));
6354            assert_eq!(
6355                notifications.get(),
6356                0,
6357                "an unchanged clamped offset must stay quiet"
6358            );
6359        }
6360        input.update(cx, |state, cx| {
6361            state.update_scroll_offset(Some(point(px(-40.), px(0.))), cx);
6362            assert_eq!(state.scroll_handle.offset(), point(px(-40.), px(0.)));
6363        });
6364        assert_eq!(notifications.get(), 1, "a scroll must notify");
6365        input.update(cx, |state, cx| {
6366            state.update_scroll_offset(Some(point(px(-400.), px(50.))), cx);
6367            assert_eq!(state.scroll_handle.offset(), point(px(-200.), px(0.)));
6368        });
6369        assert_eq!(notifications.get(), 2);
6370        input.update(cx, |state, cx| {
6371            state.update_scroll_offset(Some(point(px(-500.), px(0.))), cx);
6372        });
6373        assert_eq!(
6374            notifications.get(),
6375            2,
6376            "clamping to the current offset must stay quiet"
6377        );
6378        input.update(cx, |state, cx| {
6379            state.scroll_size.width = px(110.);
6380            state.update_scroll_offset(None, cx);
6381            assert_eq!(state.scroll_handle.offset(), point(px(-10.), px(0.)));
6382        });
6383        assert_eq!(
6384            notifications.get(),
6385            3,
6386            "a smaller scroll range must clamp and notify"
6387        );
6388    }
6389
6390    #[gpui::test]
6391    fn test_set_line_number_notifies_only_on_change(cx: &mut TestAppContext) {
6392        use std::{cell::Cell, rc::Rc};
6393
6394        cx.update(crate::init);
6395        let mut input = None;
6396        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6397            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
6398            gpui::EmptyView
6399        });
6400        let input = input.unwrap();
6401        let shown = input.read_with(cx, |state, _| state.mode.line_number());
6402        let notifications = Rc::new(Cell::new(0));
6403        let count = notifications.clone();
6404        let _subscription =
6405            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6406
6407        window
6408            .update(cx, |_, window, cx| {
6409                input.update(cx, |state, cx| state.set_line_number(shown, window, cx))
6410            })
6411            .unwrap();
6412        assert_eq!(notifications.get(), 0);
6413
6414        window
6415            .update(cx, |_, window, cx| {
6416                input.update(cx, |state, cx| state.set_line_number(!shown, window, cx))
6417            })
6418            .unwrap();
6419        assert_eq!(notifications.get(), 1);
6420        assert_eq!(
6421            input.read_with(cx, |state, _| state.mode.line_number()),
6422            !shown
6423        );
6424    }
6425
6426    #[gpui::test]
6427    fn test_set_value_on_unfocused_input_stays_quiet(cx: &mut TestAppContext) {
6428        use std::{cell::Cell, rc::Rc};
6429
6430        cx.update(crate::init);
6431        let mut input = None;
6432        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6433            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
6434            gpui::EmptyView
6435        });
6436        let input = input.unwrap();
6437        cx.run_until_parked();
6438
6439        let notifications = Rc::new(Cell::new(0));
6440        let count = notifications.clone();
6441        let _subscription =
6442            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6443        cx.run_until_parked();
6444
6445        // Seeding a form is a write, so it notifies once. The input is not
6446        // focused and draws no caret, so nothing may notify after that.
6447        window
6448            .update(cx, |_, window, cx| {
6449                input.update(cx, |state, cx| state.set_value("seeded", window, cx));
6450            })
6451            .unwrap();
6452        cx.run_until_parked();
6453        let settled = notifications.get();
6454
6455        cx.executor()
6456            .advance_clock(std::time::Duration::from_secs(3));
6457        cx.run_until_parked();
6458
6459        assert_eq!(
6460            notifications.get(),
6461            settled,
6462            "an unfocused input is blinking, and every blink repaints the view it is in"
6463        );
6464    }
6465
6466    #[gpui::test]
6467    fn test_input_does_not_invalidate_cached_parent_during_paint(cx: &mut TestAppContext) {
6468        use std::{cell::Cell, rc::Rc};
6469
6470        struct Panel {
6471            input: Entity<crate::input::InputState>,
6472            renders: Rc<Cell<usize>>,
6473        }
6474        impl Render for Panel {
6475            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6476                self.renders.set(self.renders.get() + 1);
6477                div().size_full().child(self.input.clone())
6478            }
6479        }
6480        struct Root(Entity<Panel>);
6481        impl Render for Root {
6482            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6483                div().size_full().child(
6484                    self.0
6485                        .clone()
6486                        .cached(gpui::StyleRefinement::default().size_full()),
6487                )
6488            }
6489        }
6490
6491        cx.update(crate::init);
6492        let renders = Rc::new(Cell::new(0));
6493        let mut input = None;
6494        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6495            let state = cx.new(|cx| crate::input::InputState::new(window, cx));
6496            input = Some(state.clone());
6497            Root(cx.new(|_| Panel {
6498                input: state,
6499                renders: renders.clone(),
6500            }))
6501        });
6502        cx.run_until_parked();
6503        let before = renders.get();
6504        assert!(before > 0);
6505        for _ in 0..60 {
6506            window.update(cx, |_, _, cx| cx.notify()).unwrap();
6507        }
6508        assert_eq!(
6509            renders.get(),
6510            before,
6511            "an idle input must not invalidate its cached parent"
6512        );
6513
6514        let input = input.unwrap();
6515        window
6516            .update(cx, |_, window, cx| {
6517                input.update(cx, |state, cx| state.set_value("changed", window, cx));
6518            })
6519            .unwrap();
6520        assert!(
6521            renders.get() > before,
6522            "text changes must invalidate the parent"
6523        );
6524        // The first paint reports the changed text extent to layout consumers.
6525        window.update(cx, |_, _, cx| cx.notify()).unwrap();
6526        let before = renders.get();
6527        for _ in 0..60 {
6528            window.update(cx, |_, _, cx| cx.notify()).unwrap();
6529        }
6530        assert_eq!(renders.get(), before, "the edited input must settle again");
6531
6532        cx.simulate_window_resize(window.into(), size(px(240.), px(100.)));
6533        cx.run_until_parked();
6534        assert!(renders.get() > before, "resizing must invalidate layout");
6535    }
6536
6537    #[gpui::test]
6538    fn textarea_cursor_treats_crlf_as_one_newline(cx: &mut TestAppContext) {
6539        cx.update(crate::init);
6540        let view = InputView::build_textarea(cx, |state| state);
6541        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6542        cx.update(|window, cx| {
6543            view.input.update(cx, |state, cx| {
6544                for original in ["\r\nlast", "\u{feff}first\r\nlast\n", "first\nlast\r\n"] {
6545                    state.set_value(original, window, cx);
6546                    let newline = original.find('\n').unwrap();
6547                    let end = if original.as_bytes()[newline.saturating_sub(1)] == b'\r' {
6548                        newline - 1
6549                    } else {
6550                        newline
6551                    };
6552                    state.end(&MoveEnd, window, cx);
6553                    assert_eq!(state.cursor(), end);
6554                    state.right(&MoveRight, window, cx);
6555                    assert_eq!(state.cursor(), newline + 1);
6556                    state.left(&MoveLeft, window, cx);
6557                    assert_eq!(state.cursor(), end);
6558                    state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
6559                    assert_eq!(state.selected_range(), end..end);
6560                    state.move_to_end(&MoveToEnd, window, cx);
6561                    assert_eq!(state.cursor(), original.len());
6562                    state.move_to_start(&MoveToStart, window, cx);
6563                    assert_eq!(state.cursor(), 0);
6564                    assert_eq!(state.value().as_bytes(), original.as_bytes());
6565                }
6566                // A lone CR is an ordinary character; it must not skip its neighbour.
6567                state.set_value("a\rb", window, cx);
6568                state.right(&MoveRight, window, cx);
6569                assert_eq!(state.cursor(), 1);
6570                state.right(&MoveRight, window, cx);
6571                assert_eq!(state.cursor(), 2);
6572            })
6573        });
6574    }
6575
6576    #[gpui::test]
6577    fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) {
6578        cx.update(crate::init);
6579
6580        // A single-line input keeps its caret in view by moving its own offset;
6581        // it has no viewport to drag, so a scrollbar in a text field is a
6582        // control that does not exist.
6583        let single = InputView::build(cx, |state| state);
6584        single
6585            .input
6586            .update(cx, |state, _| assert!(!state.shows_scrollbar()));
6587
6588        let multi = InputView::build_textarea(cx, |state| state);
6589        multi
6590            .input
6591            .update(cx, |state, _| assert!(state.shows_scrollbar()));
6592    }
6593
6594    #[gpui::test]
6595    fn context_menu_handler_is_deferred_and_respects_disabled(cx: &mut TestAppContext) {
6596        use std::{cell::Cell, rc::Rc};
6597        cx.update(crate::init);
6598        let input_view = InputView::new(cx);
6599        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6600        let input = input_view.input;
6601        let calls = Rc::new(Cell::new(0usize));
6602        let items = Rc::new(Cell::new(0usize));
6603        let menu_focus = cx.update(|_, cx| cx.focus_handle());
6604
6605        cx.update(|window, cx| {
6606            input.update(cx, |state, cx| {
6607                let calls2 = calls.clone();
6608                let items2 = items.clone();
6609                let menu_focus = menu_focus.clone();
6610                state.on_context_menu(Rc::new(move |menu, _, _, window, cx| {
6611                    calls2.set(calls2.get() + 1);
6612                    items2.set(menu.items.len());
6613                    menu_focus.focus(window, cx);
6614                }));
6615                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
6616            })
6617        });
6618        assert_eq!(calls.get(), 1);
6619        assert_eq!(items.get(), 0);
6620        input.read_with(&cx, |state, _| {
6621            assert_eq!(
6622                state
6623                    .selection_focus
6624                    .as_ref()
6625                    .and_then(WeakFocusHandle::upgrade)
6626                    .as_ref(),
6627                Some(&menu_focus),
6628                "the menu callback must retain selection without reborrowing its input"
6629            );
6630        });
6631
6632        // A custom handler may keep using the same, already-focused menu.
6633        cx.update(|window, cx| {
6634            input.update(cx, |state, cx| {
6635                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
6636            });
6637        });
6638        assert_eq!(calls.get(), 2);
6639        cx.update(|window, cx| {
6640            assert!(input.read(cx).has_selection_focus(window, cx));
6641        });
6642
6643        cx.update(|window, cx| {
6644            input.update(cx, |state, cx| {
6645                state.disabled = true;
6646                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
6647            })
6648        });
6649        assert_eq!(calls.get(), 2);
6650    }
6651
6652    #[gpui::test]
6653    fn context_menu_handler_can_read_its_input(cx: &mut TestAppContext) {
6654        use std::{cell::Cell, rc::Rc};
6655        cx.update(crate::init);
6656        let input_view = InputView::new(cx);
6657        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6658        let input = input_view.input;
6659        let cursor = Rc::new(Cell::new(None));
6660
6661        cx.update(|window, cx| {
6662            input.update(cx, |state, cx| {
6663                state.set_value("hello", window, cx);
6664                let weak = cx.weak_entity();
6665                let cursor = cursor.clone();
6666                state.on_context_menu(Rc::new(move |_, _, _, _, cx| {
6667                    cursor.set(weak.upgrade().map(|input| input.read(cx).cursor()));
6668                }));
6669                state.handle_right_click_menu(point(px(0.), px(0.)), 2, window, cx);
6670            })
6671        });
6672        assert_eq!(cursor.get(), Some(2));
6673    }
6674
6675    #[gpui::test]
6676    fn test_readonly_rejects_user_edits_only(cx: &mut TestAppContext) {
6677        let input_view = InputView::new(cx);
6678        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6679        let input = input_view.input;
6680
6681        cx.update(|window, cx| {
6682            input.update(cx, |state, cx| {
6683                state.set_value("hello", window, cx);
6684                state.set_readonly(true, cx);
6685            });
6686        });
6687
6688        cx.update(|_, cx| {
6689            input.read_with(cx, |state, _| {
6690                assert!(!state.is_editable());
6691                assert!(!state.is_replaceable());
6692            });
6693        });
6694
6695        // Typing (and IME) goes through the input handler, it must be rejected.
6696        cx.update(|window, cx| {
6697            input.update(cx, |state, cx| {
6698                state.replace_text_in_range(None, " world", window, cx);
6699                state.replace_and_mark_text_in_range(None, "あ", None, window, cx);
6700            });
6701        });
6702        cx.update(|_, cx| {
6703            input.read_with(cx, |state, _| assert_eq!(state.value(), "hello"));
6704        });
6705
6706        // The programmatic APIs are not limited by the readonly mode.
6707        cx.update(|window, cx| {
6708            input.update(cx, |state, cx| {
6709                state.insert(" world", window, cx);
6710                state.set_value("changed", window, cx);
6711            });
6712        });
6713        cx.update(|_, cx| {
6714            input.read_with(cx, |state, _| assert_eq!(state.value(), "changed"));
6715        });
6716
6717        // And the user can edit again after leaving the readonly mode.
6718        // The caret is at the start, because `set_value` has reset the selection.
6719        cx.update(|window, cx| {
6720            input.update(cx, |state, cx| {
6721                state.set_readonly(false, cx);
6722                state.replace_text_in_range(None, "!", window, cx);
6723            });
6724        });
6725        cx.update(|_, cx| {
6726            input.read_with(cx, |state, _| {
6727                assert!(state.is_editable());
6728                assert_eq!(state.value(), "!changed");
6729            });
6730        });
6731    }
6732
6733    /// Regression test: editing at a caret that sits far outside the viewport
6734    /// must reveal it in one frame. The cursor-follow in `layout_cursors` runs
6735    /// only on the frame the selection changes, so its old one-line step left
6736    /// the caret offscreen after typing at the end of a long paste.
6737    #[gpui::test]
6738    fn test_edit_reveals_far_offscreen_caret(cx: &mut TestAppContext) {
6739        let mut input = None;
6740        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
6741            cx.set_global(Theme::default());
6742            super::super::init(cx);
6743            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 6));
6744            input = Some(state.clone());
6745            TestRoot(state)
6746        });
6747        let mut cx = VisualTestContext::from_window(window.into(), cx);
6748        let input = input.unwrap();
6749
6750        let text: String = (1..=100)
6751            .map(|i| format!("line {i}"))
6752            .collect::<Vec<_>>()
6753            .join("\n");
6754        cx.update(|window, cx| {
6755            input.update(cx, |state, cx| {
6756                state.set_value(text, window, cx);
6757                let end = state.text.len();
6758                state.set_selection(end, end);
6759            });
6760        });
6761        cx.run_until_parked();
6762
6763        // The user scrolled back to the top to read the pasted text.
6764        cx.update(|_, cx| {
6765            input.update(cx, |state, cx| {
6766                state.scroll_handle.set_offset(point(px(0.), px(0.)));
6767                cx.notify();
6768            });
6769        });
6770        cx.run_until_parked();
6771
6772        cx.update(|window, cx| {
6773            input.update(cx, |state, cx| {
6774                state.replace_text_in_range(None, "X", window, cx);
6775            });
6776        });
6777        cx.run_until_parked();
6778
6779        cx.update(|_, cx| {
6780            input.read_with(cx, |state, _| {
6781                assert!(state.value().ends_with("line 100X"));
6782                let (caret, _) = state.cursor_layout().expect("caret painted");
6783                let viewport = state.input_bounds();
6784                let scroll_y = state.scroll_handle.offset().y;
6785                let top = caret.origin.y - viewport.origin.y + scroll_y;
6786                assert!(
6787                    top >= px(0.) && top + caret.size.height <= viewport.size.height,
6788                    "caret top {top:?} outside viewport height {:?} (scroll {scroll_y:?})",
6789                    viewport.size.height,
6790                );
6791            });
6792        });
6793    }
6794
6795    /// Regression test: changing the auto-grow limits keeps the rows fitted to
6796    /// the text. `set_auto_grow` used to start over at `min_rows`, and the rows
6797    /// were counted again only on the next edit or wrap width change.
6798    #[gpui::test]
6799    fn test_set_auto_grow_keeps_rows_fitted(cx: &mut TestAppContext) {
6800        let mut input = None;
6801        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
6802            cx.set_global(Theme::default());
6803            super::super::init(cx);
6804            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 10));
6805            input = Some(state.clone());
6806            TestRoot(state)
6807        });
6808        let mut cx = VisualTestContext::from_window(window.into(), cx);
6809        let input = input.unwrap();
6810
6811        let text: String = (1..=8)
6812            .map(|i| format!("line {i}"))
6813            .collect::<Vec<_>>()
6814            .join("\n");
6815        cx.update(|window, cx| {
6816            input.update(cx, |state, cx| state.set_value(text, window, cx));
6817        });
6818        cx.run_until_parked();
6819        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 8)));
6820
6821        // A larger maximum keeps all eight rows.
6822        cx.update(|_, cx| input.update(cx, |state, cx| state.set_auto_grow(1, 12, cx)));
6823        cx.run_until_parked();
6824        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 8)));
6825
6826        // A smaller maximum caps them.
6827        cx.update(|_, cx| input.update(cx, |state, cx| state.set_auto_grow(1, 4, cx)));
6828        cx.run_until_parked();
6829        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 4)));
6830    }
6831
6832    /// Regression test: `scroll_to` at end-of-buffer must produce a deferred
6833    /// scroll target within the safe scroll range, so the painted frame
6834    /// matches what `update_scroll_offset` persists (no jitter). A small
6835    /// `cursor_surrounding_lines` override used to mismatch the hardcoded
6836    /// 3-line edge clearance in `scroll_to`, overshooting `safe_y_min`.
6837    #[gpui::test]
6838    fn test_scroll_to_eob_does_not_overshoot_safe_range(cx: &mut TestAppContext) {
6839        let input_view = InputView::new(cx);
6840        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6841        let input = input_view.input;
6842
6843        // JetBrains-style: 1 trailing empty row + 1-line cursor surrounding.
6844        cx.update(|window, cx| {
6845            input.update(cx, |state, cx| {
6846                state.set_scroll_beyond_last_line(Some(1), window, cx);
6847                state.set_cursor_surrounding_lines(Some(1), window, cx);
6848                let text: String = (1..=50)
6849                    .map(|i| format!("line {i}"))
6850                    .collect::<Vec<_>>()
6851                    .join("\n");
6852                state.set_value(text, window, cx);
6853            });
6854        });
6855        cx.run_until_parked();
6856
6857        // Sanity: paint populated `scroll_size` and `input_bounds` — without
6858        // these, `safe_y_min` below collapses to 0 and the assertion is vacuous.
6859        cx.update(|_, cx| {
6860            input.read_with(cx, |state, _| {
6861                assert!(
6862                    state.scroll_size.height > px(0.),
6863                    "scroll_size not populated by initial paint"
6864                );
6865                assert!(
6866                    state.input_bounds.size.height > px(0.),
6867                    "input_bounds not populated by initial paint"
6868                );
6869            });
6870        });
6871
6872        // Move cursor to end with downward direction — same code path as a
6873        // `Down` keystroke at EOB. `scroll_to` runs synchronously inside
6874        // `move_to`; inspect `deferred_scroll_offset` in the same closure
6875        // before the next paint consumes and clears it.
6876        cx.update(|_, cx| {
6877            input.update(cx, |state, cx| {
6878                let end = state.text.len();
6879                state.move_to(end, Some(MoveDirection::Down), cx);
6880
6881                let deferred = state
6882                    .deferred_scroll_offset
6883                    .expect("scroll_to should populate deferred_scroll_offset");
6884                let safe_y_min =
6885                    (-state.scroll_size.height + state.input_bounds.size.height).min(px(0.));
6886
6887                assert!(
6888                    deferred.y >= safe_y_min,
6889                    "deferred_scroll_offset.y = {:?} below safe_y_min = {:?} \
6890                     — paint would jitter (Bug C regression)",
6891                    deferred.y,
6892                    safe_y_min,
6893                );
6894            });
6895        });
6896    }
6897
6898    #[gpui::test]
6899    fn test_next_search_match_reveals_with_padding_after_manual_scroll(cx: &mut TestAppContext) {
6900        assert_search_reveals_with_padding_after_manual_scroll(false, cx);
6901    }
6902
6903    #[gpui::test]
6904    fn test_previous_search_match_reveals_with_padding_after_manual_scroll(
6905        cx: &mut TestAppContext,
6906    ) {
6907        assert_search_reveals_with_padding_after_manual_scroll(true, cx);
6908    }
6909
6910    fn assert_search_reveals_with_padding_after_manual_scroll(
6911        previous: bool,
6912        cx: &mut TestAppContext,
6913    ) {
6914        let input_view = InputView::new(cx);
6915        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6916        let input = input_view.input;
6917        let text = (0..160)
6918            .map(|row| {
6919                if matches!(row, 20 | 60 | 100) {
6920                    format!("match on row {row}")
6921                } else {
6922                    format!("line {row}")
6923                }
6924            })
6925            .collect::<Vec<_>>()
6926            .join("\n");
6927        let start = text.match_indices("match").nth(1).unwrap().0;
6928        let expected_match = start..start + "match".len();
6929        cx.update(|window, cx| {
6930            input.update(cx, |state, cx| {
6931                state.set_cursor_surrounding_lines(Some(3), window, cx);
6932                state.set_value(text, window, cx);
6933                state.set_search_query("match", true, cx);
6934                if previous {
6935                    state.search_session.matcher.next();
6936                    state.search_session.matcher.next();
6937                }
6938            });
6939        });
6940        cx.run_until_parked();
6941        cx.update(|_, cx| {
6942            input.update(cx, |state, cx| {
6943                let line_height = state.last_layout.as_ref().unwrap().line_height;
6944                let y = if previous { px(0.) } else { -line_height * 80. };
6945                state.set_scroll_offset(point(px(0.), y), cx);
6946            });
6947        });
6948        cx.run_until_parked();
6949        input.read_with(&cx, |state, _| {
6950            let visible = state.visible_row_range().unwrap();
6951            if previous {
6952                assert!(visible.end <= 60, "target must be below the viewport");
6953            } else {
6954                assert!(visible.start > 60, "target must be above the viewport");
6955            }
6956        });
6957        cx.update(|_, cx| {
6958            input.update(cx, |state, cx| {
6959                let range = if previous {
6960                    state.previous_search_match(cx)
6961                } else {
6962                    state.next_search_match(cx)
6963                };
6964                assert_eq!(range, Some(expected_match));
6965                assert_eq!(state.search_session.matcher.label(), "2/3");
6966            });
6967        });
6968        cx.run_until_parked();
6969        input.read_with(&cx, |state, _| {
6970            assert!(state.visible_row_range().unwrap().contains(&60));
6971            let line_height = state.last_layout.as_ref().unwrap().line_height;
6972            let target_y = line_height * 60. + state.scroll_handle.offset().y;
6973            // Three lines of edge clearance include the matched line itself.
6974            assert!(target_y >= line_height * 2. - px(0.1));
6975            assert!(
6976                target_y + line_height * 3.
6977                    <= state.last_bounds.as_ref().unwrap().size.height + px(0.1),
6978                "search must preserve the configured surrounding-line padding"
6979            );
6980        });
6981    }
6982
6983    /// A host that wants the search shortcut for its own search UI.
6984    struct SearchHost {
6985        editor: Entity<InputBaseState<EditorMode>>,
6986        search_requests: Rc<Cell<usize>>,
6987    }
6988
6989    impl Render for SearchHost {
6990        fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
6991            let search_requests = self.search_requests.clone();
6992            div()
6993                .size_full()
6994                .on_action(cx.listener(move |_, _: &Search, _, _| {
6995                    search_requests.set(search_requests.get() + 1);
6996                }))
6997                .child(self.editor.clone())
6998        }
6999    }
7000
7001    /// Opens an editor inside [`SearchHost`], focused, and presses the search
7002    /// shortcut once. Returns the editor and the host's request count.
7003    fn press_search_shortcut(
7004        cx: &mut TestAppContext,
7005        searchable: bool,
7006    ) -> (Entity<InputBaseState<EditorMode>>, Rc<Cell<usize>>) {
7007        let search_requests = Rc::new(Cell::new(0));
7008        let mut editor = None;
7009        let window = cx.update(|cx| {
7010            cx.open_window(Default::default(), |window, cx| {
7011                cx.set_global(Theme::default());
7012                super::super::init(cx);
7013                let state =
7014                    cx.new(|cx| crate::input::EditorState::new(window, cx).searchable(searchable));
7015                editor = Some(state.clone());
7016                cx.new(|_| SearchHost {
7017                    editor: state,
7018                    search_requests: search_requests.clone(),
7019                })
7020            })
7021            .unwrap()
7022        });
7023        let editor = editor.unwrap();
7024        let mut cx = VisualTestContext::from_window(window.into(), cx);
7025        cx.update(|window, cx| {
7026            editor.update(cx, |state, cx| state.focus(window, cx));
7027        });
7028        cx.run_until_parked();
7029        #[cfg(target_os = "macos")]
7030        cx.simulate_keystrokes("cmd-f");
7031        #[cfg(not(target_os = "macos"))]
7032        cx.simulate_keystrokes("ctrl-f");
7033        cx.run_until_parked();
7034        (editor, search_requests)
7035    }
7036
7037    #[gpui::test]
7038    fn test_search_shortcut_reaches_the_host_when_not_searchable(cx: &mut TestAppContext) {
7039        let (editor, search_requests) = press_search_shortcut(cx, false);
7040        assert_eq!(search_requests.get(), 1);
7041        editor.read_with(cx, |state, _| {
7042            assert!(!state.search_session().open);
7043            assert!(!state.search_session().is_active());
7044        });
7045    }
7046
7047    #[gpui::test]
7048    fn test_search_shortcut_opens_the_panel_when_searchable(cx: &mut TestAppContext) {
7049        let (editor, search_requests) = press_search_shortcut(cx, true);
7050        assert_eq!(search_requests.get(), 0);
7051        editor.read_with(cx, |state, _| {
7052            assert!(state.search_session().open);
7053            assert!(state.search_session().is_active());
7054        });
7055    }
7056
7057    #[gpui::test]
7058    fn test_set_search_query_highlights_without_the_panel(cx: &mut TestAppContext) {
7059        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
7060        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7061        let input = input_view.input;
7062        cx.update(|window, cx| {
7063            input.update(cx, |state, cx| {
7064                state.set_value("foo bar foo", window, cx);
7065                state.set_search_query("foo", true, cx);
7066            });
7067        });
7068        cx.run_until_parked();
7069        input.read_with(&cx, |state, _| {
7070            let session = state.search_session();
7071            assert!(session.is_active());
7072            assert!(!session.open);
7073            assert_eq!(session.matcher.len(), 2);
7074        });
7075        cx.update(|_, cx| {
7076            input.update(cx, |state, cx| state.close_search(cx));
7077        });
7078        input.read_with(&cx, |state, _| {
7079            assert!(!state.search_session().is_active());
7080        });
7081    }
7082
7083    /// A closed search skips rescanning on edits, so every path that reads
7084    /// the matches again must see the edited text, not the text at close.
7085    #[gpui::test]
7086    fn test_closed_search_resyncs_matches_after_edits(cx: &mut TestAppContext) {
7087        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
7088        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7089        let input = input_view.input;
7090        cx.update(|window, cx| {
7091            input.update(cx, |state, cx| {
7092                state.set_value("foo bar foo", window, cx);
7093                state.set_search_query("foo", true, cx);
7094                state.close_search(cx);
7095
7096                state.set_value("bar foo", window, cx);
7097                assert_eq!(state.next_search_match(cx), Some(4..7));
7098
7099                state.set_value("foo foo foo", window, cx);
7100                state.set_search_query("foo", true, cx);
7101                assert_eq!(state.search_session().matcher.len(), 3);
7102            });
7103        });
7104    }
7105
7106    #[gpui::test]
7107    fn test_search_reveals_offscreen_wrapped_match(cx: &mut TestAppContext) {
7108        let input_view = InputView::new(cx);
7109        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7110        let input = input_view.input;
7111        let text = format!(
7112            "match\n{}\n{}match\n{}",
7113            "line\n".repeat(80),
7114            "wrapped text ".repeat(500),
7115            "line\n".repeat(80)
7116        );
7117        cx.update(|window, cx| {
7118            input.update(cx, |state, cx| {
7119                state.set_cursor_surrounding_lines(Some(3), window, cx);
7120                state.set_value(text, window, cx);
7121                state.set_search_query("match", true, cx);
7122            });
7123        });
7124        cx.run_until_parked();
7125        cx.update(|_, cx| {
7126            input.update(cx, |state, cx| {
7127                state.next_search_match(cx).unwrap();
7128            });
7129        });
7130        cx.run_until_parked();
7131        input.read_with(&cx, |state, _| {
7132            let range = state.search_session.matcher.matched_ranges()[1].clone();
7133            let layout = state.last_layout.as_ref().unwrap();
7134            let (_, _, position) = state.line_and_position_for_offset(range.end);
7135            let y = position.expect("wrapped match must be laid out").y
7136                + state.scroll_handle.offset().y;
7137            assert!(y >= layout.line_height * 2. - px(0.1));
7138            assert!(
7139                y + layout.line_height * 3. <= state.last_bounds.unwrap().size.height + px(0.1),
7140                "wrapped match must retain surrounding display rows"
7141            );
7142        });
7143    }
7144
7145    #[gpui::test]
7146    fn test_number_step(cx: &mut TestAppContext) {
7147        let input = InputView::build(cx, |state| state).input;
7148
7149        cx.update(|cx| {
7150            input.update(cx, |_state, cx| {
7151                assert_eq!(
7152                    NumberStep::from(5.).value(123., StepAction::Increment, cx),
7153                    5.
7154                );
7155
7156                // The step can differ by direction at a boundary: at 1.0 it
7157                // is 0.1 going down and 0.5 going up.
7158                let step = NumberStep::by_value(|value, action, _cx| {
7159                    let below = match action {
7160                        StepAction::Increment => value < 1.0,
7161                        StepAction::Decrement => value <= 1.0,
7162                    };
7163                    if below { 0.1 } else { 0.5 }
7164                });
7165                assert_eq!(step.value(0.5, StepAction::Increment, cx), 0.1);
7166                assert_eq!(step.value(1.0, StepAction::Increment, cx), 0.5);
7167                assert_eq!(step.value(1.0, StepAction::Decrement, cx), 0.1);
7168                assert_eq!(step.value(2.0, StepAction::Decrement, cx), 0.5);
7169            });
7170        });
7171    }
7172
7173    #[gpui::test]
7174    fn test_number_input_normalization(cx: &mut TestAppContext) {
7175        let input_view = InputView::build(cx, |state| {
7176            state.mask_pattern(MaskPattern::Number {
7177                separator: None,
7178                fraction: None,
7179            })
7180        });
7181        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7182        let input = input_view.input;
7183
7184        // Full-width digits and the ideographic full stop are normalized,
7185        // and the cursor is at the end (in normalized bytes, not the
7186        // original 12 bytes).
7187        cx.update(|window, cx| {
7188            input.update(cx, |state, cx| {
7189                state.replace_text_in_range(None, "12。5", window, cx);
7190            });
7191        });
7192        cx.run_until_parked();
7193        cx.update(|_, cx| {
7194            input.read_with(cx, |state, _| {
7195                assert_eq!(state.value(), "12.5");
7196                let cursor: Range<usize> = state.selected_range();
7197                assert_eq!(cursor, 4..4);
7198            });
7199        });
7200
7201        // Non-numeric input is rejected.
7202        cx.update(|window, cx| {
7203            input.update(cx, |state, cx| {
7204                state.replace_text_in_range(None, "abc", window, cx);
7205            });
7206        });
7207        cx.run_until_parked();
7208        cx.update(|_, cx| {
7209            input.read_with(cx, |state, _| {
7210                assert_eq!(state.value(), "12.5");
7211            });
7212        });
7213
7214        // A bare leading dot is kept as-is (normalized from the ideographic
7215        // full stop), not completed to "0.", so it stays editable.
7216        cx.update(|window, cx| {
7217            input.update(cx, |state, cx| {
7218                let range = state.range_to_utf16(&(0..state.text.len()));
7219                state.replace_text_in_range(Some(range), "。", window, cx);
7220            });
7221        });
7222        cx.run_until_parked();
7223        cx.update(|_, cx| {
7224            input.read_with(cx, |state, _| {
7225                assert_eq!(state.value(), ".");
7226                let cursor: Range<usize> = state.selected_range();
7227                assert_eq!(cursor, 1..1);
7228            });
7229        });
7230    }
7231
7232    #[gpui::test]
7233    fn test_mask_pattern_accepts_non_ascii_text(cx: &mut TestAppContext) {
7234        let input_view = InputView::build(cx, |state| state.mask_pattern("9999年99月"));
7235        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7236        let input = input_view.input;
7237
7238        cx.update(|window, cx| {
7239            input.update(cx, |state, cx| {
7240                state.replace_text_in_range(None, "2024年12月", window, cx);
7241            });
7242        });
7243        cx.run_until_parked();
7244        cx.update(|_, cx| {
7245            input.read_with(cx, |state, _| {
7246                assert_eq!(state.value(), "2024年12月");
7247            });
7248        });
7249    }
7250
7251    #[gpui::test]
7252    fn test_number_input_normalization_with_separator(cx: &mut TestAppContext) {
7253        let input_view = InputView::build(cx, |state| {
7254            state.mask_pattern(MaskPattern::Number {
7255                separator: Some(','),
7256                fraction: Some(2),
7257            })
7258        });
7259        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7260        let input = input_view.input;
7261
7262        cx.update(|window, cx| {
7263            input.update(cx, |state, cx| {
7264                state.replace_text_in_range(None, "1234", window, cx);
7265            });
7266        });
7267        cx.run_until_parked();
7268        cx.update(|_, cx| {
7269            input.read_with(cx, |state, _| {
7270                assert_eq!(state.value(), "1,234");
7271                assert_eq!(state.unmask_value(), "1234");
7272            });
7273        });
7274    }
7275
7276    #[gpui::test]
7277    fn test_number_input_clamp_on_blur(cx: &mut TestAppContext) {
7278        let input_view = InputView::build(cx, |state| {
7279            state
7280                .mask_pattern(MaskPattern::Number {
7281                    separator: None,
7282                    fraction: None,
7283                })
7284                .min(10.)
7285                .max(100.)
7286        });
7287        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7288        let input = input_view.input;
7289
7290        // Out-of-range values are allowed while typing, and clamped on blur.
7291        cx.update(|window, cx| {
7292            input.update(cx, |state, cx| {
7293                state.replace_text_in_range(None, "1000", window, cx);
7294                assert_eq!(state.value(), "1000");
7295                state.clamp_number_value(window, cx);
7296                assert_eq!(state.value(), "100");
7297
7298                let range = state.range_to_utf16(&(0..state.text.len()));
7299                state.replace_text_in_range(Some(range), "1", window, cx);
7300                assert_eq!(state.value(), "1");
7301                state.clamp_number_value(window, cx);
7302                assert_eq!(state.value(), "10");
7303            });
7304        });
7305    }
7306
7307    #[gpui::test]
7308    fn test_number_input_undo_with_mask(cx: &mut TestAppContext) {
7309        let input_view = InputView::build(cx, |state| {
7310            state.mask_pattern(MaskPattern::Number {
7311                separator: Some(','),
7312                fraction: None,
7313            })
7314        });
7315        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7316        let input = input_view.input;
7317
7318        // When the mask changes the text (regrouping separators), a
7319        // whole-document change is recorded, so undo/redo can restore it.
7320        cx.update(|window, cx| {
7321            input.update(cx, |state, cx| {
7322                state.replace_text_in_range(None, "1234", window, cx);
7323                assert_eq!(state.value(), "1,234");
7324                state.replace_text_in_range(None, "5", window, cx);
7325                assert_eq!(state.value(), "12,345");
7326
7327                // Each whole-document mask rewrite is an atomic undo step.
7328                // Before the whole-document history fix, undo produced a
7329                // corrupted value like "1,2344".
7330                state.undo(&Undo, window, cx);
7331                assert_eq!(state.value(), "1,234");
7332                state.undo(&Undo, window, cx);
7333                assert_eq!(state.value(), "");
7334                state.redo(&Redo, window, cx);
7335                assert_eq!(state.value(), "1,234");
7336                state.redo(&Redo, window, cx);
7337                assert_eq!(state.value(), "12,345");
7338            });
7339        });
7340    }
7341
7342    #[gpui::test]
7343    fn test_undo_manager_coalesces_adjacent_typing_transactions(cx: &mut TestAppContext) {
7344        let input_view = InputView::build(cx, |state| state);
7345        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7346        let input = input_view.input;
7347
7348        cx.update(|window, cx| {
7349            input.update(cx, |state, cx| {
7350                state.replace_text_in_range(None, "a", window, cx);
7351                state.replace_text_in_range(None, "b", window, cx);
7352                assert_eq!(state.value(), "ab");
7353
7354                state.undo(&Undo, window, cx);
7355                assert_eq!(state.value(), "");
7356            });
7357        });
7358    }
7359
7360    #[gpui::test]
7361    fn test_undo_manager_cursor_movement_splits_typing(cx: &mut TestAppContext) {
7362        let input_view = InputView::build(cx, |state| state);
7363        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7364        let input = input_view.input;
7365
7366        cx.update(|window, cx| {
7367            input.update(cx, |state, cx| {
7368                state.replace_text_in_range(None, "a", window, cx);
7369                state.replace_text_in_range(None, "b", window, cx);
7370                state.left(&MoveLeft, window, cx);
7371                state.replace_text_in_range(None, "x", window, cx);
7372                assert_eq!(state.value(), "axb");
7373
7374                state.undo(&Undo, window, cx);
7375                assert_eq!(state.value(), "ab");
7376                state.undo(&Undo, window, cx);
7377                assert_eq!(state.value(), "");
7378            });
7379        });
7380    }
7381
7382    #[gpui::test]
7383    fn test_undo_manager_splits_backward_and_forward_delete(cx: &mut TestAppContext) {
7384        let input_view = InputView::build(cx, |state| state);
7385        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7386        let input = input_view.input;
7387
7388        cx.update(|window, cx| {
7389            input.update(cx, |state, cx| {
7390                state.set_value("abcd", window, cx);
7391                state.set_selected_range(2..2, cx);
7392                state.backspace(&Backspace, window, cx);
7393                state.delete(&Delete, window, cx);
7394                assert_eq!(state.value(), "ad");
7395
7396                state.undo(&Undo, window, cx);
7397                assert_eq!(state.value(), "acd");
7398                state.undo(&Undo, window, cx);
7399                assert_eq!(state.value(), "abcd");
7400            });
7401        });
7402    }
7403
7404    #[gpui::test]
7405    fn test_undo_manager_coalesces_directional_character_deletes(cx: &mut TestAppContext) {
7406        let input_view = InputView::build(cx, |state| state);
7407        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7408        let input = input_view.input;
7409
7410        cx.update(|window, cx| {
7411            input.update(cx, |state, cx| {
7412                state.set_value("abcd", window, cx);
7413                state.backspace(&Backspace, window, cx);
7414                state.backspace(&Backspace, window, cx);
7415                assert_eq!(state.value(), "ab");
7416                state.undo(&Undo, window, cx);
7417                assert_eq!(state.value(), "abcd");
7418                assert_eq!(state.selected_range(), 4..4);
7419
7420                state.set_value("abcd", window, cx);
7421                state.set_selected_range(1..1, cx);
7422                state.delete(&Delete, window, cx);
7423                state.delete(&Delete, window, cx);
7424                assert_eq!(state.value(), "ad");
7425                state.undo(&Undo, window, cx);
7426                assert_eq!(state.value(), "abcd");
7427                assert_eq!(state.selected_range(), 1..1);
7428            });
7429        });
7430    }
7431
7432    #[gpui::test]
7433    fn test_undo_manager_atomic_paste_isolated_from_typing(cx: &mut TestAppContext) {
7434        let input_view = InputView::build(cx, |state| state);
7435        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7436        let input = input_view.input;
7437
7438        cx.update(|window, cx| {
7439            cx.write_to_clipboard(ClipboardItem::new_string("P".to_string()));
7440            input.update(cx, |state, cx| {
7441                state.replace_text_in_range(None, "a", window, cx);
7442                state.paste(&Paste, window, cx);
7443                state.replace_text_in_range(None, "b", window, cx);
7444                assert_eq!(state.value(), "aPb");
7445
7446                state.undo(&Undo, window, cx);
7447                assert_eq!(state.value(), "aP");
7448                state.undo(&Undo, window, cx);
7449                assert_eq!(state.value(), "a");
7450                state.undo(&Undo, window, cx);
7451                assert_eq!(state.value(), "");
7452            });
7453        });
7454    }
7455
7456    #[gpui::test]
7457    fn test_undo_manager_programmatic_insert_is_atomic(cx: &mut TestAppContext) {
7458        let input_view = InputView::build(cx, |state| state);
7459        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7460        let input = input_view.input;
7461
7462        cx.update(|window, cx| {
7463            input.update(cx, |state, cx| {
7464                state.replace_text_in_range(None, "a", window, cx);
7465                state.insert("P", window, cx);
7466                state.replace_text_in_range(None, "b", window, cx);
7467                assert_eq!(state.value(), "aPb");
7468
7469                state.undo(&Undo, window, cx);
7470                assert_eq!(state.value(), "aP");
7471                state.undo(&Undo, window, cx);
7472                assert_eq!(state.value(), "a");
7473            });
7474        });
7475    }
7476
7477    #[gpui::test]
7478    fn test_undo_manager_selection_round_trip_splits_typing(cx: &mut TestAppContext) {
7479        let input_view = InputView::build(cx, |state| state);
7480        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7481        let input = input_view.input;
7482
7483        cx.update(|window, cx| {
7484            input.update(cx, |state, cx| {
7485                state.replace_text_in_range(None, "a", window, cx);
7486                state.replace_text_in_range(None, "b", window, cx);
7487                state.select_all(window, cx);
7488                state.unselect(window, cx);
7489                state.replace_text_in_range(None, "c", window, cx);
7490
7491                state.undo(&Undo, window, cx);
7492                assert_eq!(state.value(), "ab");
7493                state.undo(&Undo, window, cx);
7494                assert_eq!(state.value(), "");
7495            });
7496        });
7497    }
7498
7499    #[gpui::test]
7500    fn test_undo_manager_enter_is_atomic(cx: &mut TestAppContext) {
7501        let input_view = InputView::build_textarea(cx, |state| state);
7502        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7503        let input = input_view.input;
7504
7505        cx.update(|window, cx| {
7506            input.update(cx, |state, cx| {
7507                state.replace_text_in_range(None, "a", window, cx);
7508                state.enter(
7509                    &Enter {
7510                        secondary: false,
7511                        shift: false,
7512                    },
7513                    window,
7514                    cx,
7515                );
7516                state.replace_text_in_range(None, "b", window, cx);
7517
7518                state.undo(&Undo, window, cx);
7519                assert_eq!(state.value(), "a\n");
7520                state.undo(&Undo, window, cx);
7521                assert_eq!(state.value(), "a");
7522                state.undo(&Undo, window, cx);
7523                assert_eq!(state.value(), "");
7524            });
7525        });
7526    }
7527
7528    #[gpui::test]
7529    fn test_undo_manager_single_line_return_commits_the_typing_session(cx: &mut TestAppContext) {
7530        let input_view = InputView::build(cx, |state| state);
7531        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7532        let input = input_view.input;
7533
7534        cx.update(|window, cx| {
7535            input.update(cx, |state, cx| {
7536                for part in ["a", "b", "c"] {
7537                    state.replace_text_in_range(None, part, window, cx);
7538                }
7539                state.enter(
7540                    &Enter {
7541                        secondary: false,
7542                        shift: false,
7543                    },
7544                    window,
7545                    cx,
7546                );
7547                for part in ["d", "e", "f"] {
7548                    state.replace_text_in_range(None, part, window, cx);
7549                }
7550                assert_eq!(state.value(), "abcdef");
7551
7552                state.undo(&Undo, window, cx);
7553                assert_eq!(state.value(), "abc");
7554                state.undo(&Undo, window, cx);
7555                assert_eq!(state.value(), "");
7556            });
7557        });
7558    }
7559
7560    #[gpui::test]
7561    fn test_undo_manager_submit_on_enter_commits_the_textarea_session(cx: &mut TestAppContext) {
7562        let input_view = InputView::build_textarea(cx, |state| state.submit_on_enter(true));
7563        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7564        let input = input_view.input;
7565
7566        cx.update(|window, cx| {
7567            input.update(cx, |state, cx| {
7568                state.replace_text_in_range(None, "before submit", window, cx);
7569                state.enter(
7570                    &Enter {
7571                        secondary: false,
7572                        shift: false,
7573                    },
7574                    window,
7575                    cx,
7576                );
7577                state.replace_text_in_range(None, " after submit", window, cx);
7578                assert_eq!(state.value(), "before submit after submit");
7579
7580                state.undo(&Undo, window, cx);
7581                assert_eq!(state.value(), "before submit");
7582                state.undo(&Undo, window, cx);
7583                assert_eq!(state.value(), "");
7584            });
7585        });
7586    }
7587
7588    #[gpui::test]
7589    fn test_undo_manager_blur_commits_the_typing_session(cx: &mut TestAppContext) {
7590        let input_view = InputView::build(cx, |state| state);
7591        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7592        let input = input_view.input;
7593
7594        cx.update(|window, cx| {
7595            input.update(cx, |state, cx| {
7596                state.replace_text_in_range(None, "before blur", window, cx);
7597                state.on_blur(window, cx);
7598                state.on_focus(window, cx);
7599                state.replace_text_in_range(None, " after focus", window, cx);
7600                assert_eq!(state.value(), "before blur after focus");
7601
7602                state.undo(&Undo, window, cx);
7603                assert_eq!(state.value(), "before blur");
7604                state.undo(&Undo, window, cx);
7605                assert_eq!(state.value(), "");
7606            });
7607        });
7608    }
7609
7610    #[gpui::test]
7611    fn test_undo_manager_keeps_rapid_lines_in_distinct_transactions(cx: &mut TestAppContext) {
7612        let input_view = InputView::build_textarea(cx, |state| state);
7613        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7614        let input = input_view.input;
7615
7616        cx.update(|window, cx| {
7617            input.update(cx, |state, cx| {
7618                let enter = Enter {
7619                    secondary: false,
7620                    shift: false,
7621                };
7622                state.replace_text_in_range(None, "a", window, cx);
7623                state.enter(&enter, window, cx);
7624                state.replace_text_in_range(None, "b", window, cx);
7625                state.enter(&enter, window, cx);
7626                state.replace_text_in_range(None, "c", window, cx);
7627                assert_eq!(state.value(), "a\nb\nc");
7628
7629                for expected in ["a\nb\n", "a\nb", "a\n", "a", ""] {
7630                    state.undo(&Undo, window, cx);
7631                    assert_eq!(state.value(), expected);
7632                }
7633            });
7634        });
7635    }
7636
7637    #[gpui::test]
7638    fn test_undo_manager_coalesces_long_unicode_typing_without_a_timer(cx: &mut TestAppContext) {
7639        let input_view = InputView::build_textarea(cx, |state| state);
7640        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7641        let input = input_view.input;
7642        let parts = [
7643            "The ",
7644            "quick ",
7645            "brown fox, ",
7646            "你好,世界 ",
7647            "🦀 jumps over 13 lazy dogs.",
7648        ];
7649        let expected = parts.concat();
7650
7651        cx.update(|window, cx| {
7652            input.update(cx, |state, cx| {
7653                for part in parts {
7654                    state.replace_text_in_range(None, part, window, cx);
7655                }
7656                assert_eq!(state.value(), expected);
7657
7658                state.undo(&Undo, window, cx);
7659                assert_eq!(state.value(), "");
7660                state.redo(&Redo, window, cx);
7661                assert_eq!(state.value(), expected);
7662            });
7663        });
7664    }
7665
7666    #[gpui::test]
7667    fn test_undo_manager_long_multiline_sequence_has_structural_boundaries(
7668        cx: &mut TestAppContext,
7669    ) {
7670        let input_view = InputView::build_textarea(cx, |state| state);
7671        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7672        let input = input_view.input;
7673        let enter = Enter {
7674            secondary: false,
7675            shift: false,
7676        };
7677
7678        cx.update(|window, cx| {
7679            input.update(cx, |state, cx| {
7680                for (index, line) in [
7681                    "first line with punctuation!",
7682                    "第二行包含 Unicode 🦀",
7683                    "third line has several words",
7684                ]
7685                .into_iter()
7686                .enumerate()
7687                {
7688                    for chunk in line.split_inclusive(' ') {
7689                        state.replace_text_in_range(None, chunk, window, cx);
7690                    }
7691                    if index < 2 {
7692                        state.enter(&enter, window, cx);
7693                    }
7694                }
7695
7696                assert_eq!(
7697                    state.value(),
7698                    "first line with punctuation!\n第二行包含 Unicode 🦀\nthird line has several words"
7699                );
7700                state.undo(&Undo, window, cx);
7701                assert_eq!(
7702                    state.value(),
7703                    "first line with punctuation!\n第二行包含 Unicode 🦀\n"
7704                );
7705                state.undo(&Undo, window, cx);
7706                assert_eq!(
7707                    state.value(),
7708                    "first line with punctuation!\n第二行包含 Unicode 🦀"
7709                );
7710                state.undo(&Undo, window, cx);
7711                assert_eq!(state.value(), "first line with punctuation!\n");
7712            });
7713        });
7714    }
7715
7716    #[gpui::test]
7717    fn test_masked_input_keeps_its_value_out_of_the_clipboard(cx: &mut TestAppContext) {
7718        let input_view = InputView::build(cx, |state| state);
7719        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7720        let input = input_view.input;
7721
7722        cx.update(|window, cx| {
7723            input.update(cx, |state, cx| {
7724                state.set_value("hunter2", window, cx);
7725                state.set_masked(true, window, cx);
7726                state.select_all(window, cx);
7727                cx.write_to_clipboard(ClipboardItem::new_string("sentinel".into()));
7728
7729                state.copy(&Copy, window, cx);
7730                assert_eq!(
7731                    cx.read_from_clipboard().and_then(|item| item.text()),
7732                    Some("sentinel".to_string())
7733                );
7734
7735                // Cut neither copies nor deletes.
7736                state.cut(&Cut, window, cx);
7737                assert_eq!(state.value(), "hunter2");
7738                assert_eq!(
7739                    cx.read_from_clipboard().and_then(|item| item.text()),
7740                    Some("sentinel".to_string())
7741                );
7742
7743                // Revealing the value restores both.
7744                state.set_masked(false, window, cx);
7745                state.copy(&Copy, window, cx);
7746                assert_eq!(
7747                    cx.read_from_clipboard().and_then(|item| item.text()),
7748                    Some("hunter2".to_string())
7749                );
7750            });
7751        });
7752    }
7753
7754    #[gpui::test]
7755    fn test_masked_input_collapses_word_boundaries(cx: &mut TestAppContext) {
7756        let input_view = InputView::build(cx, |state| state);
7757        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7758        let input = input_view.input;
7759
7760        cx.update(|window, cx| {
7761            input.update(cx, |state, cx| {
7762                state.set_value("aaa bbb ccc", window, cx);
7763                state.set_masked(true, window, cx);
7764                state.set_selected_range(7..7, cx);
7765
7766                // The mask hides word boundaries, so a word delete takes
7767                // everything before the caret and leaves the rest.
7768                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
7769                assert_eq!(state.value(), " ccc");
7770                assert_eq!(state.selected_range(), 0..0);
7771
7772                state.delete_next_word(&DeleteToNextWordEnd, window, cx);
7773                assert_eq!(state.value(), "");
7774
7775                // A double click takes the whole value, not one word.
7776                state.set_value("aaa bbb ccc", window, cx);
7777                state.select_word(9, window, cx);
7778                assert_eq!(state.selected_range(), 0..11);
7779
7780                // Unmasked, the same delete only takes one word.
7781                state.set_masked(false, window, cx);
7782                state.set_value("aaa bbb ccc", window, cx);
7783                state.set_selected_range(11..11, cx);
7784                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
7785                assert_eq!(state.value(), "aaa bbb ");
7786
7787                state.set_value("aaa bbb ccc", window, cx);
7788                state.select_word(9, window, cx);
7789                assert_eq!(state.selected_range(), 8..11);
7790            });
7791        });
7792    }
7793
7794    #[gpui::test]
7795    fn test_masked_input_disables_the_copy_context_menu_items(cx: &mut TestAppContext) {
7796        let input_view = InputView::build(cx, |state| state);
7797        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7798        let input = input_view.input;
7799
7800        cx.update(|window, cx| {
7801            input.update(cx, |state, cx| {
7802                state.set_value("hunter2", window, cx);
7803                state.select_all(window, cx);
7804                assert!(state.context_menu_capabilities().is_copyable());
7805
7806                state.set_masked(true, window, cx);
7807                let capabilities = state.context_menu_capabilities();
7808                assert!(capabilities.is_masked());
7809                assert!(capabilities.has_selection());
7810                assert!(!capabilities.is_copyable());
7811            });
7812        });
7813    }
7814
7815    #[gpui::test]
7816    fn test_undo_manager_cut_and_repeated_pastes_are_distinct_transactions(
7817        cx: &mut TestAppContext,
7818    ) {
7819        let input_view = InputView::build(cx, |state| state);
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, "alpha beta gamma", window, cx);
7826                state.set_selected_range(6..10, cx);
7827                state.cut(&Cut, window, cx);
7828                assert_eq!(state.value(), "alpha  gamma");
7829
7830                state.paste(&Paste, window, cx);
7831                state.paste(&Paste, window, cx);
7832                assert_eq!(state.value(), "alpha betabeta gamma");
7833
7834                state.undo(&Undo, window, cx);
7835                assert_eq!(state.value(), "alpha beta gamma");
7836                state.undo(&Undo, window, cx);
7837                assert_eq!(state.value(), "alpha  gamma");
7838                state.undo(&Undo, window, cx);
7839                assert_eq!(state.value(), "alpha beta gamma");
7840            });
7841        });
7842    }
7843
7844    #[gpui::test]
7845    fn test_undo_manager_word_and_line_deletes_do_not_coalesce(cx: &mut TestAppContext) {
7846        let input_view = InputView::build_textarea(cx, |state| state);
7847        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7848        let input = input_view.input;
7849
7850        cx.update(|window, cx| {
7851            input.update(cx, |state, cx| {
7852                state.set_value("one two three\nfour five", window, cx);
7853                state.set_selected_range(13..13, cx);
7854                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
7855                assert_eq!(state.value(), "one two \nfour five");
7856                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
7857                assert_eq!(state.value(), "one two four five");
7858
7859                state.undo(&Undo, window, cx);
7860                assert_eq!(state.value(), "one two \nfour five");
7861                state.undo(&Undo, window, cx);
7862                assert_eq!(state.value(), "one two three\nfour five");
7863            });
7864        });
7865    }
7866
7867    #[gpui::test]
7868    fn test_undo_manager_multiline_replacement_is_one_atomic_transaction(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                state.replace_text_in_range(None, "before", window, cx);
7876                state.set_selected_range(0..6, cx);
7877                state.replace_text_in_range(None, "line one\nline two\n第三行", window, cx);
7878                assert_eq!(state.value(), "line one\nline two\n第三行");
7879
7880                state.undo(&Undo, window, cx);
7881                assert_eq!(state.value(), "before");
7882                assert_eq!(state.selected_range(), 0..6);
7883                state.redo(&Redo, window, cx);
7884                assert_eq!(state.value(), "line one\nline two\n第三行");
7885            });
7886        });
7887    }
7888
7889    #[gpui::test]
7890    fn test_undo_manager_composition_isolated_from_long_typing(cx: &mut TestAppContext) {
7891        let input_view = InputView::build(cx, |state| state);
7892        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7893        let input = input_view.input;
7894
7895        cx.update(|window, cx| {
7896            input.update(cx, |state, cx| {
7897                state.replace_text_in_range(None, "prefix ", window, cx);
7898                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
7899                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
7900                state.replace_and_mark_text_in_range(None, "你", None, window, cx);
7901                state.unmark_text(window, cx);
7902                state.replace_text_in_range(None, " suffix", window, cx);
7903                assert_eq!(state.value(), "prefix 你 suffix");
7904
7905                state.undo(&Undo, window, cx);
7906                assert_eq!(state.value(), "prefix 你");
7907                state.undo(&Undo, window, cx);
7908                assert_eq!(state.value(), "prefix ");
7909                state.undo(&Undo, window, cx);
7910                assert_eq!(state.value(), "");
7911            });
7912        });
7913    }
7914
7915    #[gpui::test]
7916    fn test_undo_manager_selected_replacement_is_atomic(cx: &mut TestAppContext) {
7917        let input_view = InputView::build(cx, |state| state);
7918        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7919        let input = input_view.input;
7920
7921        cx.update(|window, cx| {
7922            input.update(cx, |state, cx| {
7923                state.replace_text_in_range(None, "abc", window, cx);
7924                state.set_selected_range(1..2, cx);
7925                state.replace_text_in_range(None, "X", window, cx);
7926                state.replace_text_in_range(None, "z", window, cx);
7927                assert_eq!(state.value(), "aXzc");
7928
7929                state.undo(&Undo, window, cx);
7930                assert_eq!(state.value(), "aXc");
7931                state.undo(&Undo, window, cx);
7932                assert_eq!(state.value(), "abc");
7933                state.undo(&Undo, window, cx);
7934                assert_eq!(state.value(), "");
7935            });
7936        });
7937    }
7938
7939    #[gpui::test]
7940    fn test_number_input_leading_dot_editable(cx: &mut TestAppContext) {
7941        let input_view = InputView::build(cx, |state| {
7942            state.mask_pattern(MaskPattern::Number {
7943                separator: None,
7944                fraction: None,
7945            })
7946        });
7947        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7948        let input = input_view.input;
7949
7950        cx.update(|window, cx| {
7951            input.update(cx, |state, cx| {
7952                state.replace_text_in_range(None, "1.2", window, cx);
7953
7954                // Delete the integer part "1": the value keeps the leading dot
7955                // (".2"), not completed to "0.2", so the digits before the dot
7956                // stay editable.
7957                let range = state.range_to_utf16(&(0..1));
7958                state.replace_text_in_range(Some(range), "", window, cx);
7959                assert_eq!(state.value(), ".2");
7960                let cursor: Range<usize> = state.selected_range();
7961                assert_eq!(cursor, 0..0);
7962
7963                // The user can type a new integer part.
7964                state.replace_text_in_range(Some(0..0), "3", window, cx);
7965                assert_eq!(state.value(), "3.2");
7966            });
7967        });
7968    }
7969
7970    #[gpui::test]
7971    fn test_number_input_escape_invalid_text(cx: &mut TestAppContext) {
7972        // A pre-existing invalid text (e.g. a `default_value` that does not
7973        // conform) must not trap the user, the edit is allowed to fix it.
7974        let input_view = InputView::build(cx, |state| {
7975            state
7976                .mask_pattern(MaskPattern::Number {
7977                    separator: None,
7978                    fraction: None,
7979                })
7980                .default_value("1,234")
7981        });
7982        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7983        let input = input_view.input;
7984
7985        cx.update(|window, cx| {
7986            input.update(cx, |state, cx| {
7987                // Delete the last char, the pending text "1,23" is still
7988                // invalid, but the edit is allowed since the old text was
7989                // already invalid.
7990                let range = state.range_to_utf16(&(4..5));
7991                state.replace_text_in_range(Some(range), "", window, cx);
7992                assert_eq!(state.value(), "1,23");
7993
7994                // Once the text becomes valid, the validation works as usual.
7995                let range = state.range_to_utf16(&(1..2));
7996                state.replace_text_in_range(Some(range), "", window, cx);
7997                assert_eq!(state.value(), "123");
7998                state.replace_text_in_range(None, "a", window, cx);
7999                assert_eq!(state.value(), "123");
8000            });
8001        });
8002    }
8003
8004    /// After `set_value` on a single-line input the caret sits at the end (like
8005    /// HTML `<input>`), yet the view is scrolled back to the start so a long
8006    /// value shows its beginning instead of its tail.
8007    #[gpui::test]
8008    fn test_set_value_single_line_caret_at_end_view_at_start(cx: &mut TestAppContext) {
8009        let input_view = InputView::build(cx, |state| state);
8010        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8011        let input = input_view.input;
8012
8013        // Long enough to overflow any reasonable single-line input width.
8014        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
8015        let len = value.len();
8016
8017        // Right after `set_value`, before the next paint consumes the deferred
8018        // offset: caret is at the end, and the view is forced back to the start.
8019        cx.update(|window, cx| {
8020            input.update(cx, |state, cx| {
8021                state.set_value(value.clone(), window, cx);
8022
8023                assert_eq!(
8024                    state.selected_range(),
8025                    len..len,
8026                    "single-line caret should be at the end after set_value"
8027                );
8028                assert_eq!(
8029                    state.deferred_scroll_offset,
8030                    Some(point(px(0.), px(0.))),
8031                    "the view should be forced back to the start"
8032                );
8033            });
8034        });
8035
8036        // After a paint, the steady-state view stays at the start (x == 0) even
8037        // though the caret is at the far end.
8038        cx.run_until_parked();
8039        cx.update(|_, cx| {
8040            input.read_with(cx, |state, _| {
8041                assert!(
8042                    state.scroll_size.width > state.input_bounds.size.width,
8043                    "value must overflow the input width or this test is vacuous"
8044                );
8045                assert_eq!(
8046                    state.scroll_handle.offset().x,
8047                    px(0.),
8048                    "long value should display from its start, not its tail"
8049                );
8050            });
8051        });
8052    }
8053
8054    /// `replace_all` on a single-line input replaces the text, puts the
8055    /// caret at the end, and — like `set_value` — snaps the view back to the
8056    /// start so a long value shows its beginning instead of its tail.
8057    #[gpui::test]
8058    fn test_replace_all_single_line(cx: &mut TestAppContext) {
8059        let input_view = InputView::build(cx, |state| state);
8060        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8061        let input = input_view.input;
8062
8063        // Long enough to overflow any reasonable single-line input width.
8064        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
8065        let len = value.len();
8066
8067        // Right after `replace_all`, before the next paint consumes the
8068        // deferred offset: caret is at the end, and the view is forced back
8069        // to the start.
8070        cx.update(|window, cx| {
8071            input.update(cx, |state, cx| {
8072                state.set_value("hello", window, cx);
8073                state.replace_all(value.clone(), window, cx);
8074                assert_eq!(state.value(), value);
8075                assert_eq!(
8076                    state.selected_range(),
8077                    len..len,
8078                    "single-line caret should be at the end after replace_all"
8079                );
8080                assert_eq!(
8081                    state.scroll_handle.offset(),
8082                    point(px(0.), px(0.)),
8083                    "the scroll offset should be reset to the start"
8084                );
8085                assert_eq!(
8086                    state.deferred_scroll_offset,
8087                    Some(point(px(0.), px(0.))),
8088                    "single-line should set a deferred scroll offset to keep the start visible"
8089                );
8090            });
8091        });
8092
8093        // After a paint, the steady-state view stays at the start (x == 0)
8094        // even though the caret is at the far end.
8095        cx.run_until_parked();
8096        cx.update(|_, cx| {
8097            input.read_with(cx, |state, _| {
8098                assert!(
8099                    state.scroll_size.width > state.input_bounds.size.width,
8100                    "value must overflow the input width or this test is vacuous"
8101                );
8102                assert_eq!(
8103                    state.scroll_handle.offset().x,
8104                    px(0.),
8105                    "long value should display from its start, not its tail"
8106                );
8107            });
8108        });
8109    }
8110
8111    #[gpui::test]
8112    fn test_single_line_removes_newlines(cx: &mut TestAppContext) {
8113        let input_view = InputView::build(cx, |state| state.default_value("default\nvalue"));
8114        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8115        let input = input_view.input;
8116
8117        cx.update(|window, cx| {
8118            input.update(cx, |state, cx| {
8119                assert_eq!(state.value(), "defaultvalue");
8120
8121                state.set_value("first\nsecond\r\nthird\rfourth", window, cx);
8122                assert_eq!(state.value(), "firstsecondthirdfourth");
8123
8124                state.set_value("", window, cx);
8125                state.insert("a\nb", window, cx);
8126                assert_eq!(state.value(), "ab");
8127            });
8128
8129            cx.write_to_clipboard(ClipboardItem::new_string("a\r\nb\nc\rd".to_string()));
8130            input.update(cx, |state, cx| {
8131                state.set_value("", window, cx);
8132                state.paste(&Paste, window, cx);
8133                assert_eq!(state.value(), "abcd");
8134            });
8135        });
8136
8137        cx.run_until_parked();
8138    }
8139
8140    /// `replace_all` on a multi-line (non-code-editor) input clears the
8141    /// selection to `0..0` and resets the scroll offset, but does not set a
8142    /// deferred scroll offset (single-line only).
8143    #[gpui::test]
8144    fn test_replace_all_multi_line(cx: &mut TestAppContext) {
8145        let input_view = InputView::build_textarea(cx, |state| state);
8146        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8147        let input = input_view.input;
8148
8149        cx.update(|window, cx| {
8150            input.update(cx, |state, cx| {
8151                state.set_value("foo\nbar", window, cx);
8152                state.replace_all("baz\nqux", window, cx);
8153                assert_eq!(state.value(), "baz\nqux");
8154                assert_eq!(
8155                    state.selected_range(),
8156                    0..0,
8157                    "multi-line selection should be cleared after replace_all"
8158                );
8159                assert_eq!(
8160                    state.scroll_handle.offset(),
8161                    point(px(0.), px(0.)),
8162                    "the scroll offset should be reset to the start"
8163                );
8164                assert!(
8165                    state.deferred_scroll_offset.is_none(),
8166                    "multi-line should not set a deferred scroll offset"
8167                );
8168            });
8169        });
8170    }
8171
8172    /// Unlike `set_value`, `replace_all` records the change so the user can
8173    /// undo it back to the previous text and redo to the new text.
8174    #[gpui::test]
8175    fn test_replace_all_preserves_undo_history(cx: &mut TestAppContext) {
8176        let input_view = InputView::build(cx, |state| state);
8177        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8178        let input = input_view.input;
8179
8180        cx.update(|window, cx| {
8181            input.update(cx, |state, cx| {
8182                // Seed with a value and clear history so the baseline is clean.
8183                state.set_value("first", window, cx);
8184                assert!(
8185                    !state.undo_manager.has_undos(),
8186                    "history should be empty after set_value"
8187                );
8188
8189                // replace_all records a single undoable change.
8190                state.replace_all("second", window, cx);
8191                assert_eq!(state.value(), "second");
8192                assert!(
8193                    state.undo_manager.has_undos(),
8194                    "replace_all should record an undo step"
8195                );
8196
8197                // Undo restores the previous text.
8198                state.undo(&Undo, window, cx);
8199                assert_eq!(state.value(), "first");
8200
8201                // Redo reapplies the replacement.
8202                state.redo(&Redo, window, cx);
8203                assert_eq!(state.value(), "second");
8204            });
8205        });
8206    }
8207
8208    /// `replace_all` on a code editor marks a pending update and resets LSP
8209    /// state, so diagnostics/completions refresh against the new text.
8210    #[gpui::test]
8211    fn test_replace_all_code_editor(cx: &mut TestAppContext) {
8212        let input_view = InputView::new(cx);
8213        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8214        let input = input_view.input;
8215
8216        cx.update(|window, cx| {
8217            input.update(cx, |state, cx| {
8218                // Plant a pending-update flag and some LSP state to verify reset.
8219                state.set_value("select 1", window, cx);
8220                state._pending_update = false;
8221
8222                state.replace_all("select 2", window, cx);
8223                assert_eq!(state.value(), "select 2");
8224                assert!(
8225                    state._pending_update,
8226                    "replace_all on a code editor should request a pending update"
8227                );
8228            });
8229        });
8230    }
8231
8232    #[gpui::test]
8233    fn test_set_selected_range(cx: &mut TestAppContext) {
8234        let input_view = InputView::build(cx, |state| state.default_value("hello world"));
8235        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8236        let input = input_view.input;
8237
8238        cx.update(|_, cx| {
8239            input.update(cx, |s, cx| {
8240                s.set_selected_range(0..5, cx);
8241                assert_eq!(s.selected_range(), 0..5);
8242                assert_eq!(s.selected_text().to_string(), "hello");
8243
8244                s.set_selected_range(6..11, cx);
8245                assert_eq!(s.selected_text().to_string(), "world");
8246
8247                // clamped + collapsed
8248                s.set_selected_range(100..100, cx);
8249                assert_eq!(s.selected_range(), 11..11);
8250            });
8251        });
8252    }
8253
8254    /// A single-edit batch round-trips through undo/redo.
8255    #[gpui::test]
8256    fn test_replace_text_in_ranges_single_edit(cx: &mut TestAppContext) {
8257        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
8258        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8259        let input = input_view.input;
8260
8261        cx.update(|window, cx| {
8262            input.update(cx, |s, cx| {
8263                s.replace_text_in_ranges(&[(0..5, "HELLO".to_string())], window, cx);
8264                assert_eq!(s.value(), "HELLO world");
8265
8266                s.undo(&Undo, window, cx);
8267                assert_eq!(s.value(), "hello world");
8268
8269                s.redo(&Redo, window, cx);
8270                assert_eq!(s.value(), "HELLO world");
8271            });
8272        });
8273    }
8274
8275    /// A single undo restores the exact original text and a single redo
8276    /// re-applies all edits, verifying the back-to-front application ordering.
8277    #[gpui::test]
8278    fn test_replace_text_in_ranges_multi_edit_transaction(cx: &mut TestAppContext) {
8279        let input_view = InputView::build_textarea(cx, |state| state.default_value("aaa bbb ccc"));
8280        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8281        let input = input_view.input;
8282
8283        cx.update(|window, cx| {
8284            input.update(cx, |s, cx| {
8285                // Two edits at different positions, given in pre-edit
8286                // coordinates and in arbitrary (non-sorted) order.
8287                s.replace_text_in_ranges(
8288                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
8289                    window,
8290                    cx,
8291                );
8292                assert_eq!(s.value(), "X bbb Y");
8293
8294                // One collapsed cursor per edit, at the end of each inserted text.
8295                let cursors: Vec<usize> =
8296                    s.selections.iter().map(|sel| sel.cursor_offset()).collect();
8297                assert_eq!(cursors, vec![1, 7]);
8298
8299                // The whole batch is a single undo transaction.
8300                assert_eq!(s.undo_manager.undo_count(), 1);
8301
8302                // One undo restores the exact original text.
8303                s.undo(&Undo, window, cx);
8304                assert_eq!(s.value(), "aaa bbb ccc");
8305
8306                // One redo re-applies all edits.
8307                s.redo(&Redo, window, cx);
8308                assert_eq!(s.value(), "X bbb Y");
8309            });
8310        });
8311    }
8312
8313    /// A multi-edit change reaches the highlighter once, with every edit in
8314    /// application order and the text right after each, which is what the
8315    /// default `update_batch` replays through `update` one edit at a time.
8316    #[gpui::test]
8317    fn test_replace_text_in_ranges_drives_the_highlighter_once(cx: &mut TestAppContext) {
8318        use crate::input::{FoldRange, HighlightStyleResolver, InputEdit, InputHighlighter};
8319        use gpui::HighlightStyle;
8320        use std::cell::RefCell;
8321
8322        struct RecordingHighlighter(Rc<RefCell<Vec<Vec<String>>>>);
8323
8324        impl InputHighlighter for RecordingHighlighter {
8325            fn language(&self) -> SharedString {
8326                "recording".into()
8327            }
8328
8329            fn update(
8330                &mut self,
8331                _: Option<InputEdit>,
8332                text: &Rope,
8333                _: bool,
8334                _: &mut Window,
8335                _: &mut Context<EditorState>,
8336            ) {
8337                self.0.borrow_mut().push(vec![text.to_string()]);
8338            }
8339
8340            fn update_batch(
8341                &mut self,
8342                edits: &[(InputEdit, Rope)],
8343                _: bool,
8344                _: &mut Window,
8345                _: &mut Context<EditorState>,
8346            ) {
8347                let texts = edits.iter().map(|(_, text)| text.to_string()).collect();
8348                self.0.borrow_mut().push(texts);
8349            }
8350
8351            fn styles(
8352                &self,
8353                range: &Range<usize>,
8354                _: &dyn HighlightStyleResolver,
8355            ) -> Vec<(Range<usize>, HighlightStyle)> {
8356                vec![(range.clone(), HighlightStyle::default())]
8357            }
8358
8359            fn fold_ranges(&self, _: &Rope) -> Vec<FoldRange> {
8360                Vec::new()
8361            }
8362        }
8363
8364        let updates = Rc::new(RefCell::new(Vec::new()));
8365        let input_view = InputView::new(cx);
8366        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8367        let input = input_view.input;
8368
8369        cx.update(|window, cx| {
8370            input.update(cx, |s, cx| {
8371                s.set_value("aaa bbb ccc", window, cx);
8372                let recorded = updates.clone();
8373                s.set_highlighter_factory(
8374                    Rc::new(move |_: &str| {
8375                        let highlighter: Box<dyn InputHighlighter> =
8376                            Box::new(RecordingHighlighter(recorded.clone()));
8377                        Some(highlighter)
8378                    }),
8379                    cx,
8380                );
8381                s.replace_text_in_ranges(
8382                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
8383                    window,
8384                    cx,
8385                );
8386            });
8387        });
8388
8389        assert_eq!(
8390            *updates.borrow(),
8391            vec![vec!["aaa bbb Y".to_string(), "X bbb Y".to_string()]]
8392        );
8393    }
8394
8395    /// An IME composition (marking then commit) undoes as a single unit.
8396    #[gpui::test]
8397    fn test_ime_composition_undoes_as_one_unit(cx: &mut TestAppContext) {
8398        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
8399        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8400        let input = input_view.input;
8401
8402        cx.update(|window, cx| {
8403            input.update(cx, |s, cx| {
8404                // Simulate an IME composition: mark, refine, then commit.
8405                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
8406                s.replace_and_mark_text_in_range(None, "ni", Some(2..2), window, cx);
8407                s.replace_text_in_range(None, "你", window, cx);
8408                assert_eq!(s.value(), "你");
8409
8410                // The entire composition is one undo transaction.
8411                assert_eq!(s.undo_manager.undo_count(), 1);
8412
8413                s.undo(&Undo, window, cx);
8414                assert_eq!(s.value(), "");
8415
8416                s.redo(&Redo, window, cx);
8417                assert_eq!(s.value(), "你");
8418            });
8419        });
8420    }
8421
8422    /// A keystroke right after a committed composition must be its own undo
8423    /// entry, not merged into the (finalized) composition transaction.
8424    #[gpui::test]
8425    fn test_edit_after_composition_is_separate_undo(cx: &mut TestAppContext) {
8426        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
8427        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8428        let input = input_view.input;
8429
8430        cx.update(|window, cx| {
8431            input.update(cx, |s, cx| {
8432                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
8433                s.replace_text_in_range(None, "你", window, cx);
8434                assert_eq!(s.value(), "你");
8435                assert_eq!(s.undo_manager.undo_count(), 1);
8436
8437                // Typing after the commit is a distinct transaction.
8438                s.replace_text_in_range(None, "x", window, cx);
8439                assert_eq!(s.value(), "你x");
8440                assert_eq!(s.undo_manager.undo_count(), 2);
8441
8442                s.undo(&Undo, window, cx);
8443                assert_eq!(s.value(), "你");
8444                s.undo(&Undo, window, cx);
8445                assert_eq!(s.value(), "");
8446            });
8447        });
8448    }
8449
8450    /// Canceling a composition via `unmark_text` closes its transaction so it
8451    /// does not leak and swallow a later edit.
8452    #[gpui::test]
8453    fn test_composition_cancel_via_unmark_does_not_leak(cx: &mut TestAppContext) {
8454        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
8455        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8456        let input = input_view.input;
8457
8458        cx.update(|window, cx| {
8459            input.update(cx, |s, cx| {
8460                // Start a composition, then cancel it via unmark.
8461                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
8462                s.unmark_text(window, cx);
8463                let after_cancel = s.undo_manager.undo_count();
8464
8465                // A later edit is recorded independently.
8466                s.replace_text_in_range(None, "x", window, cx);
8467                assert_eq!(s.undo_manager.undo_count(), after_cancel + 1);
8468            });
8469        });
8470    }
8471
8472    #[gpui::test]
8473    fn test_set_selected_range_clips_to_utf8_boundaries(cx: &mut TestAppContext) {
8474        let input_view = InputView::build(cx, |state| state.default_value("éx"));
8475        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8476        let input = input_view.input;
8477
8478        cx.update(|window, cx| {
8479            input.update(cx, |state, cx| {
8480                state.set_selected_range(0..1, cx);
8481                assert_eq!(state.selected_range(), 0..2);
8482                state.copy(&Copy, window, cx);
8483
8484                state.set_selected_range(1..1, cx);
8485                assert_eq!(state.selected_range(), 0..0);
8486            });
8487        });
8488    }
8489
8490    #[gpui::test]
8491    fn test_ime_selection_is_relative_to_replacement_start(cx: &mut TestAppContext) {
8492        let input_view = InputView::build(cx, |state| state.default_value("你好 "));
8493        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8494        let input = input_view.input;
8495
8496        cx.update(|window, cx| {
8497            input.update(cx, |state, cx| {
8498                state.set_selected_range(7..7, cx);
8499                state.replace_and_mark_text_in_range(None, "s", Some(1..1), window, cx);
8500                state.replace_and_mark_text_in_range(None, "sh", Some(2..2), window, cx);
8501
8502                assert_eq!(state.value(), "你好 sh");
8503                assert_eq!(state.selected_range(), 9..9);
8504                assert_eq!(state.ime_marked_range, Some((7..9).into()));
8505            });
8506        });
8507    }
8508
8509    #[gpui::test]
8510    fn test_undo_manager_composition_is_one_undo_group(cx: &mut TestAppContext) {
8511        let input_view = InputView::build(cx, |state| state);
8512        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8513        let input = input_view.input;
8514
8515        cx.update(|window, cx| {
8516            input.update(cx, |state, cx| {
8517                state.set_value("a", window, cx);
8518                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
8519                state.replace_and_mark_text_in_range(None, "sh", None, window, cx);
8520                state.replace_text_in_range(None, "是", window, cx);
8521                assert_eq!(state.value(), "a是");
8522
8523                state.undo(&Undo, window, cx);
8524                assert_eq!(state.value(), "a");
8525                state.redo(&Redo, window, cx);
8526                assert_eq!(state.value(), "a是");
8527            });
8528        });
8529    }
8530
8531    #[gpui::test]
8532    fn test_undo_manager_consecutive_compositions_are_separate_groups(cx: &mut TestAppContext) {
8533        let input_view = InputView::build(cx, |state| state);
8534        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8535        let input = input_view.input;
8536
8537        cx.update(|window, cx| {
8538            input.update(cx, |state, cx| {
8539                // First composition: "jin" -> "今天"
8540                state.replace_and_mark_text_in_range(None, "j", None, window, cx);
8541                state.replace_and_mark_text_in_range(None, "jin", None, window, cx);
8542                state.replace_text_in_range(None, "今天", window, cx);
8543                // Second composition: "wo" -> "我们"
8544                state.replace_and_mark_text_in_range(None, "w", None, window, cx);
8545                state.replace_and_mark_text_in_range(None, "wo", None, window, cx);
8546                state.replace_text_in_range(None, "我们", window, cx);
8547                assert_eq!(state.value(), "今天我们");
8548                assert_eq!(state.selected_range(), 12..12);
8549
8550                state.undo(&Undo, window, cx);
8551                assert_eq!(state.value(), "今天");
8552                assert_eq!(state.selected_range(), 6..6);
8553
8554                state.undo(&Undo, window, cx);
8555                assert_eq!(state.value(), "");
8556                assert_eq!(state.selected_range(), 0..0);
8557
8558                state.redo(&Redo, window, cx);
8559                assert_eq!(state.value(), "今天");
8560                assert_eq!(state.selected_range(), 6..6);
8561
8562                state.redo(&Redo, window, cx);
8563                assert_eq!(state.value(), "今天我们");
8564                assert_eq!(state.selected_range(), 12..12);
8565            });
8566        });
8567    }
8568
8569    #[gpui::test]
8570    fn test_undo_manager_typing_after_composition_is_a_separate_group(cx: &mut TestAppContext) {
8571        let input_view = InputView::build(cx, |state| state);
8572        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8573        let input = input_view.input;
8574
8575        cx.update(|window, cx| {
8576            input.update(cx, |state, cx| {
8577                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
8578                state.replace_text_in_range(None, "你", window, cx);
8579                state.undo_manager.set_pending_intent(EditIntent::Typing);
8580                state.replace_text_in_range(None, "a", window, cx);
8581                state.undo_manager.set_pending_intent(EditIntent::Typing);
8582                state.replace_text_in_range(None, "b", window, cx);
8583                assert_eq!(state.value(), "你ab");
8584
8585                state.undo(&Undo, window, cx);
8586                assert_eq!(state.value(), "你");
8587
8588                state.undo(&Undo, window, cx);
8589                assert_eq!(state.value(), "");
8590            });
8591        });
8592    }
8593
8594    #[gpui::test]
8595    fn test_undo_manager_composition_cancel_leaves_no_entry(cx: &mut TestAppContext) {
8596        let input_view = InputView::build(cx, |state| state);
8597        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8598        let input = input_view.input;
8599
8600        cx.update(|window, cx| {
8601            input.update(cx, |state, cx| {
8602                state.set_value("a", window, cx);
8603                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
8604                state.replace_and_mark_text_in_range(None, "", None, window, cx);
8605
8606                assert_eq!(state.value(), "a");
8607                assert!(!state.undo_manager.has_undos());
8608            });
8609        });
8610    }
8611
8612    /// The macOS Korean IME rewrites the character it just inserted.
8613    #[gpui::test]
8614    fn test_undo_manager_ime_rewrites_of_typed_char_are_one_group(cx: &mut TestAppContext) {
8615        let input_view = InputView::build(cx, |state| state);
8616        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8617        let input = input_view.input;
8618
8619        cx.update(|window, cx| {
8620            input.update(cx, |state, cx| {
8621                state.set_value("é ", window, cx);
8622                state.set_selected_range(3..3, cx);
8623                // g k s r m f: "ㅎ" -> "하" -> "한", then "ㄱ" -> "그" -> "글"
8624                state.replace_text_in_range(None, "ㅎ", window, cx);
8625                state.replace_text_in_range(Some(2..3), "하", window, cx);
8626                state.replace_text_in_range(Some(2..3), "한", window, cx);
8627                state.replace_text_in_range(None, "ㄱ", window, cx);
8628                state.replace_text_in_range(Some(3..4), "그", window, cx);
8629                state.replace_text_in_range(Some(3..4), "글", window, cx);
8630                assert_eq!(state.value(), "é 한글");
8631                assert_eq!(state.undo_manager.undo_count(), 1);
8632
8633                state.undo(&Undo, window, cx);
8634                assert_eq!(state.value(), "é ");
8635                assert_eq!(state.selected_range(), 3..3);
8636
8637                state.redo(&Redo, window, cx);
8638                assert_eq!(state.value(), "é 한글");
8639                assert_eq!(state.selected_range(), 9..9);
8640            });
8641        });
8642    }
8643
8644    #[gpui::test]
8645    fn test_undo_manager_rewrite_after_cursor_movement_is_separate(cx: &mut TestAppContext) {
8646        let input_view = InputView::build(cx, |state| state);
8647        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8648        let input = input_view.input;
8649
8650        cx.update(|window, cx| {
8651            input.update(cx, |state, cx| {
8652                state.replace_text_in_range(None, "c", window, cx);
8653                state.replace_text_in_range(None, "e", window, cx);
8654                state.left(&MoveLeft, window, cx);
8655                state.right(&MoveRight, window, cx);
8656                state.replace_text_in_range(Some(1..2), "é", window, cx);
8657                assert_eq!(state.value(), "cé");
8658
8659                state.undo(&Undo, window, cx);
8660                assert_eq!(state.value(), "ce");
8661                state.undo(&Undo, window, cx);
8662                assert_eq!(state.value(), "");
8663            });
8664        });
8665    }
8666
8667    #[gpui::test]
8668    fn test_undo_manager_selection_restored_by_undo_and_redo(cx: &mut TestAppContext) {
8669        let input_view = InputView::build(cx, |state| state);
8670        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8671        let input = input_view.input;
8672
8673        cx.update(|window, cx| {
8674            input.update(cx, |state, cx| {
8675                state.set_value("abc", window, cx);
8676                state.set_selected_range(1..2, cx);
8677                state.replace_text_in_range(None, "X", window, cx);
8678
8679                state.undo(&Undo, window, cx);
8680                assert_eq!(state.value(), "abc");
8681                assert_eq!(state.selected_range(), 1..2);
8682
8683                state.redo(&Redo, window, cx);
8684                assert_eq!(state.value(), "aXc");
8685                assert_eq!(state.selected_range(), 2..2);
8686            });
8687        });
8688    }
8689
8690    #[gpui::test]
8691    fn test_undo_manager_forward_delete_restores_cursor(cx: &mut TestAppContext) {
8692        let input_view = InputView::build(cx, |state| state);
8693        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8694        let input = input_view.input;
8695
8696        cx.update(|window, cx| {
8697            input.update(cx, |state, cx| {
8698                state.set_value("abc", window, cx);
8699                state.set_selected_range(1..1, cx);
8700                state.delete(&Delete, window, cx);
8701
8702                state.undo(&Undo, window, cx);
8703                assert_eq!(state.value(), "abc");
8704                assert_eq!(state.selected_range(), 1..1);
8705            });
8706        });
8707    }
8708
8709    #[gpui::test]
8710    fn test_undo_manager_selection_movement_preserves_redo(cx: &mut TestAppContext) {
8711        let input_view = InputView::build(cx, |state| state);
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, |state, cx| {
8717                state.replace_text_in_range(None, "ab", window, cx);
8718                state.undo(&Undo, window, cx);
8719                state.set_selected_range(0..0, cx);
8720                state.redo(&Redo, window, cx);
8721                assert_eq!(state.value(), "ab");
8722
8723                state.undo(&Undo, window, cx);
8724                state.replace_text_in_range(None, "x", window, cx);
8725                state.redo(&Redo, window, cx);
8726                assert_eq!(state.value(), "x");
8727            });
8728        });
8729    }
8730
8731    #[gpui::test]
8732    fn test_undo_manager_noop_edit_preserves_redo(cx: &mut TestAppContext) {
8733        let input_view = InputView::build(cx, |state| state);
8734        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8735        let input = input_view.input;
8736
8737        cx.update(|window, cx| {
8738            input.update(cx, |state, cx| {
8739                state.replace_text_in_range(None, "a", window, cx);
8740                state.move_to(0, None, cx);
8741                state.replace_text_in_range(None, "", window, cx);
8742                state.undo(&Undo, window, cx);
8743                assert_eq!(state.value(), "");
8744                state.redo(&Redo, window, cx);
8745                assert_eq!(state.value(), "a");
8746                assert_eq!(state.cursor(), 1);
8747            });
8748        });
8749    }
8750
8751    #[gpui::test]
8752    fn test_cursor_round_trip_stops_typing_coalescing(cx: &mut TestAppContext) {
8753        let input_view = InputView::build(cx, |state| state);
8754        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8755        let input = input_view.input;
8756
8757        cx.update(|window, cx| {
8758            input.update(cx, |state, cx| {
8759                state.replace_text_in_range(None, "a", window, cx);
8760                state.left(&MoveLeft, window, cx);
8761                state.right(&MoveRight, window, cx);
8762                state.replace_text_in_range(None, "b", window, cx);
8763
8764                state.undo(&Undo, window, cx);
8765                assert_eq!(state.value(), "a");
8766                state.undo(&Undo, window, cx);
8767                assert_eq!(state.value(), "");
8768            });
8769        });
8770    }
8771
8772    #[gpui::test]
8773    fn test_undo_manager_noop_edit_breaks_coalescing_without_clearing_history(
8774        cx: &mut TestAppContext,
8775    ) {
8776        let input_view = InputView::build(cx, |state| state);
8777        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8778        let input = input_view.input;
8779
8780        cx.update(|window, cx| {
8781            input.update(cx, |state, cx| {
8782                state.replace_text_in_range(None, "alpha", window, cx);
8783                state.replace_text_in_range(None, "", window, cx);
8784                state.replace_text_in_range(None, "beta", window, cx);
8785                assert_eq!(state.value(), "alphabeta");
8786
8787                state.undo(&Undo, window, cx);
8788                assert_eq!(state.value(), "alpha");
8789                state.undo(&Undo, window, cx);
8790                assert_eq!(state.value(), "");
8791            });
8792        });
8793    }
8794
8795    #[gpui::test]
8796    fn test_undo_manager_masked_redo_restores_actual_cursor(cx: &mut TestAppContext) {
8797        let input_view = InputView::build(cx, |state| {
8798            state.mask_pattern(MaskPattern::Number {
8799                separator: Some(','),
8800                fraction: None,
8801            })
8802        });
8803        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8804        let input = input_view.input;
8805
8806        cx.update(|window, cx| {
8807            input.update(cx, |state, cx| {
8808                state.set_value("12345", window, cx);
8809                state.set_selected_range(2..2, cx);
8810                state.replace_text_in_range(None, "9", window, cx);
8811                let selection_after_edit = state.selected_range();
8812                assert_ne!(selection_after_edit.end, state.value().len());
8813
8814                state.undo(&Undo, window, cx);
8815                state.redo(&Redo, window, cx);
8816                assert_eq!(state.selected_range(), selection_after_edit);
8817            });
8818        });
8819    }
8820
8821    /// Unfolding at a position opens exactly the folds hiding it.
8822    ///
8823    /// A fold keeps its own first and last line visible, so a position on
8824    /// either of them opens nothing. Nested folds all open at once, sibling
8825    /// folds stay closed, and the opened ranges stay fold candidates.
8826    #[gpui::test]
8827    fn test_unfold_at(cx: &mut TestAppContext) {
8828        use crate::input::{FoldRange, Position};
8829
8830        let view = InputView::<EditorMode>::new(cx);
8831        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8832        let input = view.input;
8833
8834        // An outer fold over lines 0..=5, a fold nested inside it, and a
8835        // sibling fold that must never be touched.
8836        cx.update(|window, cx| {
8837            input.update(cx, |state, cx| {
8838                state.set_value("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl", window, cx);
8839                state.apply_highlighter_fold_candidates(
8840                    vec![
8841                        FoldRange::new(0, 5),
8842                        FoldRange::new(2, 4),
8843                        FoldRange::new(7, 10),
8844                    ],
8845                    cx,
8846                );
8847                state.display_map.set_folded(0, true);
8848                state.display_map.set_folded(2, true);
8849                state.display_map.set_folded(7, true);
8850            });
8851        });
8852
8853        // The outer fold's own first and last line stay visible, so neither
8854        // position opens anything.
8855        for line in [0, 5] {
8856            cx.update(|_, cx| {
8857                input.update(cx, |state, cx| {
8858                    assert!(!state.display_map.is_buffer_line_hidden(line));
8859                    assert!(!state.unfold_at(Position::new(line as u32, 0), cx));
8860                });
8861                input.read_with(cx, |state, _| {
8862                    assert!(state.display_map.is_folded_at(0));
8863                    assert!(state.display_map.is_folded_at(2));
8864                    assert!(state.display_map.is_folded_at(7));
8865                });
8866            });
8867        }
8868
8869        // Line 3 is hidden by both the outer and the nested fold, so both
8870        // open; the sibling fold does not.
8871        cx.update(|_, cx| {
8872            input.update(cx, |state, cx| {
8873                assert!(state.unfold_at(Position::new(3, 0), cx));
8874            });
8875            input.read_with(cx, |state, _| {
8876                assert!(!state.display_map.is_buffer_line_hidden(3));
8877                assert!(!state.display_map.is_folded_at(0));
8878                assert!(!state.display_map.is_folded_at(2));
8879                assert!(state.display_map.is_folded_at(7));
8880                // The opened ranges are still candidates for refolding.
8881                assert!(state.display_map.is_fold_candidate(0));
8882                assert!(state.display_map.is_fold_candidate(2));
8883            });
8884        });
8885
8886        // Nothing is hidden there any more, so a second call is a no-op.
8887        cx.update(|_, cx| {
8888            input.update(cx, |state, cx| {
8889                assert!(!state.unfold_at(Position::new(3, 0), cx));
8890            });
8891        });
8892    }
8893
8894    /// Losing focus hides the hover popover but keeps the decorations.
8895    ///
8896    /// Both used to be dropped by one call, so clicking away threw away
8897    /// decorations the application had installed and never asked to remove.
8898    #[gpui::test]
8899    fn test_blur_keeps_decorations(cx: &mut TestAppContext) {
8900        let view = InputView::<EditorMode>::new(cx);
8901        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8902        let input = view.input;
8903
8904        cx.update(|window, cx| {
8905            input.update(cx, |state, cx| {
8906                state.set_value("select 1", window, cx);
8907                let _collection = state.create_decorations_collection(
8908                    vec![crate::input::TextDecoration::new(
8909                        0..6,
8910                        gpui::HighlightStyle {
8911                            font_weight: Some(gpui::FontWeight::BOLD),
8912                            ..Default::default()
8913                        },
8914                    )],
8915                    cx,
8916                );
8917                state.present_hover(
8918                    0..6,
8919                    lsp_types::Hover {
8920                        contents: lsp_types::HoverContents::Scalar(
8921                            lsp_types::MarkedString::String("docs".into()),
8922                        ),
8923                        range: None,
8924                    },
8925                    cx,
8926                );
8927                assert!(state.hover_popover().is_some());
8928
8929                state.on_blur(window, cx);
8930
8931                assert!(
8932                    state.hover_popover().is_none(),
8933                    "blur should hide the hover popover"
8934                );
8935                let decorations = state.extras.decoration_layers();
8936                assert!(
8937                    decorations.iter().any(|layer| !layer.is_empty()),
8938                    "blur must not discard decorations"
8939                );
8940            });
8941        });
8942    }
8943
8944    /// The mode marker is the only source of truth for the kind of input.
8945    ///
8946    /// An auto-growing textarea capped at one row used to report itself as
8947    /// single-line, because the answer was derived from the row counts.
8948    #[gpui::test]
8949    fn test_kind_does_not_follow_the_row_count(cx: &mut TestAppContext) {
8950        let view = InputView::build_textarea(cx, |state| state.auto_grow(1, 1));
8951        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8952        view.input.read_with(&mut cx, |state, _| {
8953            assert!(state.is_multi_line());
8954            assert!(!state.is_single_line());
8955            assert!(!state.is_code_editor());
8956        });
8957    }
8958
8959    #[gpui::test]
8960    fn test_textarea_prewrap_prepares_content_rows_before_layout(cx: &mut TestAppContext) {
8961        cx.update(crate::init);
8962        for (value, expected_rows) in [("", 1), ("one\ntwo", 2)] {
8963            cx.open_window(size(px(400.), px(100.)), |window, cx| {
8964                let font = window.text_style().font();
8965                cx.new(|cx| {
8966                    let mut state = crate::input::TextareaState::new(window, cx)
8967                        .rows(12)
8968                        .default_value(value);
8969                    assert_eq!(state.display_map().wrap_row_count(), 0);
8970                    state.prewrap(font, px(14.), px(120.), cx);
8971                    assert_eq!(state.display_map().wrap_row_count(), expected_rows);
8972                    assert_eq!(state.value(), value);
8973                    state
8974                });
8975                gpui::EmptyView
8976            });
8977        }
8978    }
8979
8980    #[gpui::test]
8981    fn test_textarea_prewrap_respects_soft_wrap_before_layout(cx: &mut TestAppContext) {
8982        cx.update(crate::init);
8983        let value = "select id, name, email, phone from users where active\nlimit 10";
8984        for soft_wrap in [true, false] {
8985            cx.open_window(size(px(400.), px(100.)), |window, cx| {
8986                let font = window.text_style().font();
8987                cx.new(|cx| {
8988                    let mut state = crate::input::TextareaState::new(window, cx)
8989                        .rows(1)
8990                        .soft_wrap(soft_wrap)
8991                        .default_value(value);
8992                    state.prewrap(font, px(14.), px(120.), cx);
8993                    let rows = state.display_map().wrap_row_count();
8994                    if soft_wrap {
8995                        assert!(rows > 2, "the first logical line must wrap");
8996                    } else {
8997                        assert_eq!(rows, 2);
8998                    }
8999                    assert_eq!(state.value(), value);
9000                    state
9001                });
9002                gpui::EmptyView
9003            });
9004        }
9005    }
9006
9007    #[gpui::test]
9008    fn test_prewrap_counts_wrapped_rows_before_the_first_paint(cx: &mut TestAppContext) {
9009        cx.update(crate::init);
9010        let text = "select id, name, email, phone from users where active\nlimit 10";
9011        for (soft_wrap, wraps) in [(true, true), (false, false)] {
9012            let mut input = None;
9013            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9014                let font = window.text_style().font();
9015                let editor = cx.new(|cx| {
9016                    let mut state = crate::input::EditorState::new(window, cx)
9017                        .soft_wrap(soft_wrap)
9018                        .default_value(text);
9019                    state.prewrap(font, px(14.), px(120.), cx);
9020                    state
9021                });
9022                input = Some(editor);
9023                gpui::EmptyView
9024            });
9025            let rows = input
9026                .unwrap()
9027                .read_with(cx, |state, _| state.display_map().wrap_row_count());
9028            // Two lines; at 120px the first wraps unless soft wrap is off.
9029            assert_eq!(rows > 2, wraps, "soft_wrap({soft_wrap}): {rows} rows");
9030            if !wraps {
9031                assert_eq!(rows, 2);
9032            }
9033        }
9034    }
9035
9036    /// Prewrap counts with the state's wrapping indent, as the first layout
9037    /// does: continuation rows indented as deep as their line take more rows.
9038    #[gpui::test]
9039    fn test_prewrap_wraps_with_the_wrapping_indent(cx: &mut TestAppContext) {
9040        cx.update(crate::init);
9041        let text = "            select id, name, email, phone from users where active";
9042        let mut rows = Vec::new();
9043        for indent in [WrappingIndent::Same, WrappingIndent::None] {
9044            let mut input = None;
9045            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9046                let font = window.text_style().font();
9047                input = Some(cx.new(|cx| {
9048                    let mut state = crate::input::EditorState::new(window, cx)
9049                        .wrapping_indent(indent)
9050                        .default_value(text);
9051                    state.prewrap(font, px(14.), px(200.), cx);
9052                    state
9053                }));
9054                gpui::EmptyView
9055            });
9056            rows.push(
9057                input
9058                    .unwrap()
9059                    .read_with(cx, |state, _| state.display_map().wrap_row_count()),
9060            );
9061        }
9062        assert!(rows[0] > rows[1], "rows with Same, None: {rows:?}");
9063    }
9064
9065    /// Once the editor is laid out, layout owns the wrap and prewrap leaves it.
9066    #[gpui::test]
9067    fn test_prewrap_does_nothing_once_laid_out(cx: &mut TestAppContext) {
9068        struct Frame(Entity<crate::input::EditorState>);
9069        impl Render for Frame {
9070            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
9071                crate::input::InputBase::new("frame")
9072                    .h(px(100.))
9073                    .child(crate::input::Editor::new(&self.0))
9074            }
9075        }
9076
9077        cx.update(crate::init);
9078        let text = "select id, name, email, phone from users where active\nlimit 10";
9079        let mut input = None;
9080        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
9081            let state = cx.new(|cx| crate::input::EditorState::new(window, cx).default_value(text));
9082            input = Some(state.clone());
9083            Frame(state)
9084        });
9085        let input = input.unwrap();
9086        let mut cx = VisualTestContext::from_window(window.into(), cx);
9087        cx.update(|window, cx| window.draw(cx).clear(cx));
9088
9089        let laid_out = input.read_with(&cx, |state, _| state.display_map().wrap_row_count());
9090        assert!(laid_out >= 2, "laid out to {laid_out} rows");
9091        let font = cx.update(|window, _| window.text_style().font());
9092        input.update(&mut cx, |state, cx| {
9093            state.prewrap(font, px(14.), px(60.), cx)
9094        });
9095        assert_eq!(
9096            input.read_with(&cx, |state, _| state.display_map().wrap_row_count()),
9097            laid_out
9098        );
9099    }
9100
9101    /// Soft wrap is on by default, for every mode that can wrap.
9102    ///
9103    /// The default lives in the shared constructor, where a mode-specific
9104    /// `new` can silently fail to restore it; this pins it down.
9105    #[gpui::test]
9106    fn test_soft_wrap_is_enabled_by_default(cx: &mut TestAppContext) {
9107        let textarea = InputView::build_textarea(cx, |state| state);
9108        let mut textarea_cx = VisualTestContext::from_window(textarea.window_handle.into(), cx);
9109        textarea
9110            .input
9111            .read_with(&mut textarea_cx, |state, _| assert!(state.soft_wrap));
9112
9113        let editor = InputView::<EditorMode>::new(cx);
9114        let mut editor_cx = VisualTestContext::from_window(editor.window_handle.into(), cx);
9115        editor
9116            .input
9117            .read_with(&mut editor_cx, |state, _| assert!(state.soft_wrap));
9118    }
9119
9120    /// Parse a cursor spec into `(text, cursor_offsets)`. Non-empty lines are
9121    /// joined with `\n` plus a trailing `\n`. `|` marks a cursor. Leading
9122    /// whitespace is kept, so a spec can express indentation.
9123    fn parse_cursor_spec(input: &str) -> (String, Vec<usize>) {
9124        let mut full_text = String::new();
9125        let mut cursor_offsets = Vec::new();
9126        let non_empty_lines: Vec<&str> = input.lines().filter(|l| !l.is_empty()).collect();
9127
9128        for (line_idx, line) in non_empty_lines.iter().enumerate() {
9129            let mut positions = Vec::new();
9130            let mut text = String::new();
9131            for ch in line.chars() {
9132                if ch == '|' {
9133                    positions.push(text.len());
9134                } else {
9135                    text.push(ch);
9136                }
9137            }
9138
9139            if line_idx > 0 {
9140                full_text.push('\n');
9141            }
9142            let line_start = full_text.len();
9143            for pos in positions {
9144                cursor_offsets.push(line_start + pos);
9145            }
9146            full_text.push_str(&text);
9147        }
9148        full_text.push('\n');
9149
9150        (full_text, cursor_offsets)
9151    }
9152
9153    /// Build a multi-line input for multi-cursor tests.
9154    fn multi_line(cx: &mut TestAppContext) -> InputView<TextareaMode> {
9155        InputView::build_textarea(cx, |state| state)
9156    }
9157
9158    /// Set the text and cursor positions from a spec (see [`parse_cursor_spec`]).
9159    fn setup_cursors<M: InputModeKind>(
9160        cx: &mut VisualTestContext,
9161        input: &Entity<InputBaseState<M>>,
9162        spec: &str,
9163    ) {
9164        let (full_text, offsets) = parse_cursor_spec(spec);
9165        cx.update(|window, cx| {
9166            input.update(cx, |state, cx| {
9167                state.set_value(&full_text, window, cx);
9168                let selections = offsets
9169                    .into_iter()
9170                    .map(|offset| {
9171                        CursorSelection::new(state.selections.generate_id(), offset, offset)
9172                    })
9173                    .collect();
9174                state.selections.replace_all(selections);
9175                cx.notify();
9176            });
9177        });
9178    }
9179
9180    /// Assert the text and cursor positions match a spec.
9181    #[track_caller]
9182    fn assert_cursors<M: InputModeKind>(
9183        cx: &mut VisualTestContext,
9184        input: &Entity<InputBaseState<M>>,
9185        spec: &str,
9186    ) {
9187        let (expected_text, mut expected_cursors) = parse_cursor_spec(spec);
9188        expected_cursors.sort();
9189
9190        let (actual_text, mut actual_cursors) = input.read_with(cx, |state, _| {
9191            (
9192                state.text.to_string(),
9193                state
9194                    .selections
9195                    .iter()
9196                    .map(|s| s.cursor_offset())
9197                    .collect::<Vec<_>>(),
9198            )
9199        });
9200        actual_cursors.sort();
9201
9202        assert_eq!(
9203            actual_text, expected_text,
9204            "Text mismatch:\nExpected: {expected_text:?}\nActual:   {actual_text:?}"
9205        );
9206        assert_eq!(
9207            actual_cursors, expected_cursors,
9208            "Cursor mismatch:\nExpected: {expected_cursors:?}\nActual:   {actual_cursors:?}"
9209        );
9210    }
9211
9212    #[gpui::test]
9213    fn test_alt_drag_selects_a_block_and_replaces_each_row(cx: &mut TestAppContext) {
9214        cx.update(crate::init);
9215        let view = InputView::<EditorMode>::new(cx);
9216        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9217        for modifiers in [
9218            gpui::Modifiers {
9219                alt: true,
9220                ..Default::default()
9221            },
9222            gpui::Modifiers {
9223                alt: true,
9224                shift: true,
9225                ..Default::default()
9226            },
9227            #[cfg(target_os = "linux")]
9228            gpui::Modifiers {
9229                alt: true,
9230                control: true,
9231                ..Default::default()
9232            },
9233        ] {
9234            setup_cursors(&mut cx, &view.input, "|abcd\nabcd\nabcd");
9235            cx.update(|window, cx| {
9236                view.input.update(cx, |state, cx| state.focus(window, cx));
9237            });
9238            let (start, end) = view.input.read_with(&cx, |state, _| {
9239                let layout = state.last_layout.as_ref().unwrap();
9240                let origin = state.last_bounds.unwrap().origin;
9241                let position = |row: usize, col| {
9242                    let local = layout.lines[row]
9243                        .position_for_index(col, layout, false)
9244                        .unwrap();
9245                    origin
9246                        + point(
9247                            layout.line_number_width + local.x,
9248                            layout.line_height * (row as f32 + 0.5),
9249                        )
9250                };
9251                (position(0, 1), position(2, 3))
9252            });
9253            // Land inside the third column instead of exactly on its trailing
9254            // glyph boundary, where pixel rounding can select the next column.
9255            let end = point(end.x - px(0.5), end.y);
9256            // A cached Ctrl-hover definition must not steal a column gesture.
9257            cx.update(|_, cx| {
9258                view.input.update(cx, |state, _| {
9259                    state.extras.hover_definition.update(
9260                        0..4,
9261                        vec![lsp_types::LocationLink {
9262                            origin_selection_range: None,
9263                            target_uri: "file:///tmp/column-selection.rs".parse().unwrap(),
9264                            target_range: Default::default(),
9265                            target_selection_range: Default::default(),
9266                        }],
9267                    );
9268                });
9269            });
9270            cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
9271            cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
9272            cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
9273            view.input.read_with(&cx, |state, _| {
9274                let ranges: Vec<_> = state
9275                    .selections
9276                    .iter()
9277                    .map(|sel| sel.start..sel.end)
9278                    .collect();
9279                assert_eq!(ranges, vec![1..3, 6..8, 11..13]);
9280            });
9281            // Moving after release must leave the block intact.
9282            cx.simulate_mouse_move(start, None, modifiers);
9283            cx.simulate_keystrokes("x");
9284            assert_cursors(&mut cx, &view.input, "ax|d\nax|d\nax|d");
9285        }
9286    }
9287
9288    #[gpui::test]
9289    fn test_alt_drag_extends_upward_from_an_existing_cursor(cx: &mut TestAppContext) {
9290        cx.update(crate::init);
9291        let view = InputView::<EditorMode>::new(cx);
9292        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9293        setup_cursors(&mut cx, &view.input, "abcd\nabcd\na|bcd");
9294        cx.update(|window, cx| {
9295            view.input.update(cx, |state, cx| state.focus(window, cx));
9296        });
9297        let (start, end) = view.input.read_with(&cx, |state, _| {
9298            let layout = state.last_layout.as_ref().unwrap();
9299            let origin = state.last_bounds.unwrap().origin;
9300            let local = layout.lines[0]
9301                .position_for_index(1, layout, false)
9302                .unwrap();
9303            let x = layout.line_number_width + local.x;
9304            (
9305                origin + point(x, layout.line_height * 2.5),
9306                origin + point(x, layout.line_height * 0.5),
9307            )
9308        });
9309        let modifiers = gpui::Modifiers {
9310            alt: true,
9311            ..Default::default()
9312        };
9313        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
9314        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
9315        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
9316        assert_cursors(&mut cx, &view.input, "a|bcd\na|bcd\na|bcd");
9317    }
9318
9319    #[gpui::test]
9320    fn test_alt_drag_over_a_short_row_keeps_the_block_width(cx: &mut TestAppContext) {
9321        cx.update(crate::init);
9322        let view = InputView::<EditorMode>::new(cx);
9323        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9324        setup_cursors(&mut cx, &view.input, "abcdef\nab\nabcdef");
9325        cx.update(|window, cx| {
9326            view.input.update(cx, |state, cx| state.focus(window, cx));
9327        });
9328        // Drag from row 0 column 1 down to row 1, at the x of row 0's column 5. Row 1
9329        // only has two characters, so the pointer ends past its end.
9330        let (start, end) = view.input.read_with(&cx, |state, _| {
9331            let layout = state.last_layout.as_ref().unwrap();
9332            let origin = state.last_bounds.unwrap().origin;
9333            let x_of = |index| {
9334                layout.line_number_width
9335                    + layout.lines[0]
9336                        .position_for_index(index, layout, false)
9337                        .unwrap()
9338                        .x
9339            };
9340            (
9341                origin + point(x_of(1), layout.line_height * 0.5),
9342                origin + point(x_of(5), layout.line_height * 1.5),
9343            )
9344        });
9345        let modifiers = gpui::Modifiers {
9346            alt: true,
9347            shift: true,
9348            ..Default::default()
9349        };
9350        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
9351        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
9352        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
9353
9354        // The short row is clipped to its own end; the long row keeps the full span.
9355        view.input.read_with(&cx, |state, _| {
9356            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
9357            assert_eq!(ranges, vec![(1, 5), (8, 9)]);
9358        });
9359    }
9360
9361    #[gpui::test]
9362    fn test_alt_mouse_release_outside_editor_ends_column_selection(cx: &mut TestAppContext) {
9363        cx.update(crate::init);
9364        let view = InputView::<EditorMode>::new(cx);
9365        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9366        setup_cursors(&mut cx, &view.input, "|abcd\nabcd");
9367        let position = view.input.read_with(&cx, |state, _| {
9368            let layout = state.last_layout.as_ref().unwrap();
9369            state.last_bounds.unwrap().origin
9370                + point(layout.line_number_width + px(2.), layout.line_height * 0.5)
9371        });
9372        let modifiers = gpui::Modifiers {
9373            alt: true,
9374            ..Default::default()
9375        };
9376        cx.simulate_mouse_down(position, MouseButton::Left, modifiers);
9377        cx.simulate_mouse_up(point(px(-100.), px(-100.)), MouseButton::Left, modifiers);
9378        view.input.read_with(&cx, |state, _| {
9379            assert!(!state.selecting);
9380            assert!(state.column_select_start.is_none());
9381        });
9382    }
9383
9384    #[gpui::test]
9385    fn test_consumed_keystrokes_keep_cursor_visible(cx: &mut TestAppContext) {
9386        let view = InputView::<EditorMode>::new(cx);
9387        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9388        setup_cursors(&mut cx, &view.input, "a|b");
9389        cx.update(|window, cx| {
9390            view.input.update(cx, |state, cx| {
9391                state.focus(window, cx);
9392                state.pause_blink_cursor(cx);
9393            });
9394        });
9395        cx.run_until_parked();
9396        cx.executor()
9397            .advance_clock(std::time::Duration::from_millis(300));
9398        cx.run_until_parked();
9399        view.input.read_with(&cx, |state, cx| {
9400            assert!(!state.blink_cursor.read(cx).visible());
9401        });
9402        // Copy consumes its shortcut without editing text or moving selections.
9403        for _ in 0..5 {
9404            #[cfg(target_os = "macos")]
9405            cx.simulate_keystrokes("cmd-c");
9406            #[cfg(not(target_os = "macos"))]
9407            cx.simulate_keystrokes("ctrl-c");
9408            cx.run_until_parked();
9409            cx.executor()
9410                .advance_clock(std::time::Duration::from_millis(200));
9411            cx.run_until_parked();
9412            view.input.read_with(&cx, |state, cx| {
9413                assert!(state.blink_cursor.read(cx).visible());
9414            });
9415        }
9416    }
9417
9418    #[gpui::test]
9419    fn test_mouse_down_shows_cursor_like_a_cursor_move(cx: &mut TestAppContext) {
9420        cx.update(crate::init);
9421        let view = InputView::<EditorMode>::new(cx);
9422        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9423        setup_cursors(&mut cx, &view.input, "abcd|efgh");
9424        let position = |offset: Pixels, cx: &mut VisualTestContext| {
9425            view.input.read_with(cx, |state, _| {
9426                let layout = state.last_layout.as_ref().unwrap();
9427                state.last_bounds.unwrap().origin
9428                    + point(layout.line_number_width + offset, layout.line_height * 0.5)
9429            })
9430        };
9431        let visible = |cx: &mut VisualTestContext| {
9432            view.input
9433                .read_with(cx, |state, cx| state.blink_cursor.read(cx).visible())
9434        };
9435
9436        // The press itself shows the caret, without waiting for the focus
9437        // observer, and the idle delay holds it there like a cursor move.
9438        let first = position(px(2.), &mut cx);
9439        cx.simulate_mouse_down(first, MouseButton::Left, Default::default());
9440        assert!(visible(&mut cx));
9441        cx.simulate_mouse_up(first, MouseButton::Left, Default::default());
9442        cx.run_until_parked();
9443
9444        // Pressing elsewhere in the focused input, in the hidden phase of the
9445        // blink, shows the caret again at the press.
9446        cx.executor()
9447            .advance_clock(std::time::Duration::from_millis(400));
9448        cx.run_until_parked();
9449        assert!(!visible(&mut cx));
9450        let second = position(px(30.), &mut cx);
9451        cx.simulate_mouse_down(second, MouseButton::Left, Default::default());
9452        assert!(visible(&mut cx));
9453        cx.executor()
9454            .advance_clock(std::time::Duration::from_millis(250));
9455        cx.run_until_parked();
9456        assert!(visible(&mut cx));
9457        cx.simulate_mouse_up(second, MouseButton::Left, Default::default());
9458    }
9459
9460    #[gpui::test]
9461    fn test_multi_cursor_actions_reveal_hidden_carets(cx: &mut TestAppContext) {
9462        let view = InputView::<EditorMode>::new(cx);
9463        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9464        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
9465        // Start each action in the hidden phase without depending on a
9466        // key-down listener: actions and text input also arrive directly.
9467        for action in 0..6 {
9468            cx.update(|_, cx| {
9469                view.input.update(cx, |state, cx| {
9470                    state.blink_cursor = cx.new(|_| BlinkCursor::new());
9471                    state.blink_cursor.update(cx, |cursor, cx| cursor.start(cx));
9472                });
9473            });
9474            cx.run_until_parked();
9475            cx.executor()
9476                .advance_clock(std::time::Duration::from_millis(500));
9477            cx.run_until_parked();
9478            view.input.read_with(&cx, |state, cx| {
9479                assert!(!state.blink_cursor.read(cx).visible(), "action {action}");
9480            });
9481
9482            cx.update(|window, cx| {
9483                view.input.update(cx, |state, cx| {
9484                    match action {
9485                        0 => state.add_cursor_above(&AddCursorAbove, window, cx),
9486                        1 => state.add_cursor_below(&AddCursorBelow, window, cx),
9487                        2 => state.select_up(&SelectUp, window, cx),
9488                        3 => state.select_down(&SelectDown, window, cx),
9489                        4 => state.replace_text_in_range(None, "x", window, cx),
9490                        _ => state.backspace(&Backspace, window, cx),
9491                    }
9492                    assert!(state.blink_cursor.read(cx).visible(), "action {action}");
9493                });
9494            });
9495        }
9496    }
9497
9498    #[gpui::test]
9499    fn test_multi_cursor_keyboard_dispatch(cx: &mut TestAppContext) {
9500        let view = InputView::<EditorMode>::new(cx);
9501        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9502        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
9503        cx.update(|window, cx| {
9504            view.input.update(cx, |state, cx| state.focus(window, cx));
9505        });
9506        #[cfg(target_os = "macos")]
9507        cx.simulate_keystrokes("cmd-alt-up");
9508        #[cfg(target_os = "windows")]
9509        cx.simulate_keystrokes("ctrl-alt-up");
9510        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
9511        cx.simulate_keystrokes("alt-shift-up");
9512        view.input
9513            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
9514        #[cfg(target_os = "macos")]
9515        cx.simulate_keystrokes("cmd-alt-down");
9516        #[cfg(target_os = "windows")]
9517        cx.simulate_keystrokes("ctrl-alt-down");
9518        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
9519        cx.simulate_keystrokes("alt-shift-down");
9520        view.input
9521            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 3));
9522        cx.simulate_keystrokes("x");
9523        assert_cursors(&mut cx, &view.input, "ax|b\nax|b\nax|b");
9524    }
9525
9526    #[gpui::test]
9527    fn test_multi_cursor_platform_word_selection_dispatch(cx: &mut TestAppContext) {
9528        let view = InputView::<EditorMode>::new(cx);
9529        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9530        setup_cursors(&mut cx, &view.input, "one |two\none |two");
9531        cx.update(|window, cx| {
9532            view.input.update(cx, |state, cx| state.focus(window, cx));
9533        });
9534        #[cfg(any(target_os = "macos", target_os = "linux"))]
9535        cx.simulate_keystrokes("alt-shift-right");
9536        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
9537        cx.simulate_keystrokes("ctrl-shift-right");
9538        cx.simulate_keystrokes("x");
9539        assert_cursors(&mut cx, &view.input, "one x|\none x|");
9540        setup_cursors(&mut cx, &view.input, "one two|\none two|");
9541        #[cfg(any(target_os = "macos", target_os = "linux"))]
9542        cx.simulate_keystrokes("alt-shift-left");
9543        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
9544        cx.simulate_keystrokes("ctrl-shift-left");
9545        cx.simulate_keystrokes("x");
9546        assert_cursors(&mut cx, &view.input, "one x|\none x|");
9547    }
9548
9549    #[cfg(not(target_os = "macos"))]
9550    #[gpui::test]
9551    fn test_multi_cursor_horizontal_selection_dispatch(cx: &mut TestAppContext) {
9552        let view = InputView::<EditorMode>::new(cx);
9553        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9554        setup_cursors(&mut cx, &view.input, "ab\na|b");
9555        cx.update(|window, cx| {
9556            view.input.update(cx, |state, cx| state.focus(window, cx));
9557        });
9558        #[cfg(target_os = "windows")]
9559        cx.simulate_keystrokes("ctrl-alt-up");
9560        #[cfg(not(target_os = "windows"))]
9561        cx.simulate_keystrokes("alt-shift-up");
9562        #[cfg(target_os = "linux")]
9563        cx.simulate_keystrokes("shift-right");
9564        #[cfg(not(target_os = "linux"))]
9565        cx.simulate_keystrokes("alt-shift-right");
9566        view.input.read_with(&cx, |state, _| {
9567            assert_eq!(
9568                state
9569                    .selections
9570                    .iter()
9571                    .map(|s| s.start..s.end)
9572                    .collect::<Vec<_>>(),
9573                vec![4..5, 1..2]
9574            );
9575        });
9576        #[cfg(target_os = "linux")]
9577        cx.simulate_keystrokes("shift-left shift-left");
9578        #[cfg(not(target_os = "linux"))]
9579        cx.simulate_keystrokes("alt-shift-left alt-shift-left");
9580        view.input.read_with(&cx, |state, _| {
9581            assert_eq!(
9582                state
9583                    .selections
9584                    .iter()
9585                    .map(|s| s.start..s.end)
9586                    .collect::<Vec<_>>(),
9587                vec![3..4, 0..1]
9588            );
9589        });
9590        cx.simulate_keystrokes("x");
9591        assert_cursors(&mut cx, &view.input, "x|b\nx|b");
9592    }
9593
9594    #[gpui::test]
9595    fn test_multi_cursor_alt_click_dispatch(cx: &mut TestAppContext) {
9596        cx.update(crate::init);
9597        let view = InputView::<EditorMode>::new(cx);
9598        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9599        setup_cursors(&mut cx, &view.input, "a|b\nab\nab");
9600        cx.update(|window, cx| {
9601            view.input.update(cx, |state, cx| state.focus(window, cx));
9602        });
9603        let position = view.input.read_with(&cx, |state, _| {
9604            let bounds = state.last_bounds.unwrap();
9605            let layout = state.last_layout.as_ref().unwrap();
9606            bounds.origin + point(layout.line_number_width + px(2.), layout.line_height * 1.5)
9607        });
9608        cx.simulate_click(
9609            position,
9610            gpui::Modifiers {
9611                alt: true,
9612                ..Default::default()
9613            },
9614        );
9615        view.input
9616            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
9617    }
9618
9619    #[gpui::test]
9620    fn test_add_selection(cx: &mut TestAppContext) {
9621        let view = InputView::<EditorMode>::new(cx);
9622        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9623        cx.update(|window, cx| {
9624            view.input.update(cx, |state, cx| {
9625                state.focus(window, cx);
9626                state.set_value("foo bar foo", window, cx);
9627                state.set_selected_range(0..3, cx);
9628                // The programmatic second cursor: the next occurrence.
9629                state.add_selection(8..11, cx);
9630                // Adding a range that is already selected changes nothing.
9631                state.add_selection(0..3, cx);
9632            });
9633        });
9634        view.input.read_with(&cx, |state, _| {
9635            let mut ranges: Vec<Range<usize>> =
9636                state.selections.iter().map(|s| s.start..s.end).collect();
9637            ranges.sort_by_key(|r| r.start);
9638            assert_eq!(ranges, vec![0..3, 8..11]);
9639        });
9640        // Typing replaces every selection at once.
9641        cx.simulate_keystrokes("x");
9642        view.input.read_with(&cx, |state, _| {
9643            assert_eq!(state.text.to_string(), "x bar x")
9644        });
9645    }
9646
9647    #[gpui::test]
9648    fn test_add_selection_preserves_crlf_boundaries(cx: &mut TestAppContext) {
9649        let view = InputView::build_textarea(cx, |state| state.default_value("a\r\nb"));
9650        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9651        cx.update(|window, cx| {
9652            view.input.update(cx, |state, cx| {
9653                state.focus(window, cx);
9654                state.set_selected_range(4..4, cx);
9655                state.add_selection(2..2, cx);
9656                let ranges: Vec<_> = state.selections.iter().map(|s| s.start..s.end).collect();
9657                assert_eq!(ranges, vec![4..4, 1..1]);
9658            });
9659        });
9660        cx.simulate_keystrokes("x");
9661        view.input.read_with(&cx, |state, _| {
9662            assert_eq!(state.value(), "ax\r\nbx");
9663        });
9664        cx.update(|window, cx| {
9665            view.input.update(cx, |state, cx| {
9666                state.set_value("a\r\nb", window, cx);
9667                state.set_selected_range(4..4, cx);
9668                state.add_selection(2..3, cx);
9669                state.cut(&Cut, window, cx);
9670                assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "\r\n");
9671                assert_eq!(state.value(), "ab");
9672            });
9673        });
9674    }
9675
9676    #[gpui::test]
9677    fn test_add_selection_merges_overlaps_before_copy_and_cut(cx: &mut TestAppContext) {
9678        let view = InputView::<EditorMode>::new(cx);
9679        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9680        cx.update(|window, cx| {
9681            view.input.update(cx, |state, cx| {
9682                // Cover partial overlaps, a containing range, and a bridge
9683                // between two previously disjoint selections.
9684                for additions in [vec![2..5], vec![0..5], vec![4..5, 2..4]] {
9685                    state.set_value("abcdef", window, cx);
9686                    state.set_selected_range(0..3, cx);
9687                    let active_id = state.active_selection().id;
9688                    for range in additions {
9689                        state.add_selection(range, cx);
9690                    }
9691                    assert_eq!(state.active_selection().id, active_id);
9692                    assert_eq!(state.selected_range(), 0..5);
9693                    assert_eq!(state.selections.len(), 1);
9694                    state.copy(&Copy, window, cx);
9695                    assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "abcde");
9696                    state.cut(&Cut, window, cx);
9697                    assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "abcde");
9698                    assert_eq!(state.value(), "f");
9699                    state.undo(&Undo, window, cx);
9700                    assert_eq!(state.value(), "abcdef");
9701                    assert_eq!(state.selected_range(), 0..5);
9702                }
9703            });
9704        });
9705    }
9706
9707    #[gpui::test]
9708    fn test_add_selection_clears_inline_completion(cx: &mut TestAppContext) {
9709        use crate::input::CompletionProvider;
9710        use gpui::Task;
9711        use lsp_types::{
9712            CompletionContext, CompletionResponse, InlineCompletionContext, InlineCompletionItem,
9713            InlineCompletionResponse,
9714        };
9715        use std::time::Duration;
9716
9717        struct Provider(Rc<Cell<usize>>);
9718
9719        impl CompletionProvider for Provider {
9720            fn is_completion_trigger(&self, _: usize, _: &str, _: &mut App) -> bool {
9721                false
9722            }
9723
9724            fn completions(
9725                &self,
9726                _: &Rope,
9727                _: usize,
9728                _: CompletionContext,
9729                _: &mut Window,
9730                _: &mut App,
9731            ) -> Task<anyhow::Result<CompletionResponse>> {
9732                Task::ready(Ok(CompletionResponse::Array(vec![])))
9733            }
9734
9735            fn inline_completion(
9736                &self,
9737                _: &Rope,
9738                _: usize,
9739                _: InlineCompletionContext,
9740                _: &mut Window,
9741                _: &mut App,
9742            ) -> Task<anyhow::Result<InlineCompletionResponse>> {
9743                self.0.set(self.0.get() + 1);
9744                Task::ready(Ok(InlineCompletionResponse::Array(vec![])))
9745            }
9746        }
9747
9748        let view = InputView::<EditorMode>::new(cx);
9749        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9750        let calls = Rc::new(Cell::new(0));
9751        cx.update(|window, cx| {
9752            view.input.update(cx, |state, cx| {
9753                state.focus(window, cx);
9754                state.set_value("aa\naa", window, cx);
9755                state.set_selected_range(2..2, cx);
9756                state.extras.inline_completion.item = Some(InlineCompletionItem {
9757                    insert_text: "suggestion".into(),
9758                    filter_text: None,
9759                    range: None,
9760                    command: None,
9761                    insert_text_format: None,
9762                });
9763                state.add_selection(5..5, cx);
9764                assert!(!state.has_inline_completion());
9765                state.indent_inline(&IndentInline, window, cx);
9766                assert_eq!(state.value(), "aa  \naa  ");
9767                assert_eq!(state.selections.len(), 2);
9768
9769                // The active cursor stays put, so only canceling the task
9770                // prevents this pending request from fetching a suggestion.
9771                state.set_value("aa\naa", window, cx);
9772                state.set_selected_range(2..2, cx);
9773                state.extras.lsp.completion_provider = Some(Rc::new(Provider(calls.clone())));
9774                state.schedule_inline_completion(window, cx);
9775            });
9776        });
9777        cx.run_until_parked();
9778        cx.update(|_, cx| {
9779            view.input
9780                .update(cx, |state, cx| state.add_selection(5..5, cx));
9781        });
9782        cx.executor().advance_clock(Duration::from_secs(1));
9783        cx.run_until_parked();
9784        assert_eq!(calls.get(), 0);
9785        view.input.read_with(&cx, |state, _| {
9786            assert!(!state.has_inline_completion());
9787            assert_eq!(state.selections.len(), 2);
9788            assert_eq!(state.value(), "aa\naa");
9789        });
9790    }
9791
9792    #[gpui::test]
9793    fn test_word_delete_undo_restores_caret(cx: &mut TestAppContext) {
9794        let view = multi_line(cx);
9795        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9796        setup_cursors(&mut cx, &view.input, "hello|");
9797        cx.update(|window, cx| {
9798            view.input.update(cx, |state, cx| {
9799                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
9800                state.undo(&Undo, window, cx);
9801                assert_eq!(state.selected_range(), 5..5);
9802            });
9803        });
9804    }
9805
9806    #[gpui::test]
9807    fn test_merged_delete_undo_restores_all_carets(cx: &mut TestAppContext) {
9808        let view = multi_line(cx);
9809        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9810        setup_cursors(&mut cx, &view.input, "a|b|c");
9811        cx.update(|window, cx| {
9812            view.input.update(cx, |state, cx| {
9813                state.backspace(&Backspace, window, cx);
9814                state.undo(&Undo, window, cx);
9815            });
9816        });
9817        assert_cursors(&mut cx, &view.input, "a|b|c");
9818    }
9819
9820    #[gpui::test]
9821    fn test_shift_end_respects_soft_wrap_end(cx: &mut TestAppContext) {
9822        let view = InputView::<EditorMode>::new(cx);
9823        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9824        cx.update(|window, cx| {
9825            view.input.update(cx, |state, cx| {
9826                state.set_value("abcdef ".repeat(30), window, cx);
9827                state.display_map.on_layout_changed(Some(px(60.)), cx);
9828                let line = state.display_map.line(0).unwrap();
9829                assert!(line.wrapped_lines.len() > 1);
9830                let boundary = line.wrapped_lines[0].end;
9831                state.move_to_with_affinity(boundary, None, true, cx);
9832                state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
9833                assert_eq!(state.selected_range(), boundary..state.text.len());
9834            });
9835        });
9836    }
9837
9838    #[gpui::test]
9839    fn test_outdent_unindented_unicode_is_unchanged(cx: &mut TestAppContext) {
9840        let view = multi_line(cx);
9841        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9842        setup_cursors(&mut cx, &view.input, "|你好\n|世界");
9843        cx.update(|window, cx| {
9844            view.input.update(cx, |state, cx| {
9845                state.outdent(false, window, cx);
9846                state.outdent(true, window, cx);
9847            });
9848        });
9849        assert_cursors(&mut cx, &view.input, "|你好\n|世界");
9850    }
9851
9852    #[gpui::test]
9853    fn test_column_selection_stays_on_unicode_boundaries(cx: &mut TestAppContext) {
9854        let view = multi_line(cx);
9855        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9856        setup_cursors(&mut cx, &view.input, "ab\n你好\ncd");
9857        cx.update(|window, cx| {
9858            view.input.update(cx, |state, cx| {
9859                state.build_columnar_selection(
9860                    ColumnarPoint::new(1, 0),
9861                    ColumnarPoint::new(11, 0),
9862                    cx,
9863                );
9864                let text = state.value();
9865                for sel in state.selections.iter() {
9866                    assert!(text.is_char_boundary(sel.start));
9867                    assert!(text.is_char_boundary(sel.end));
9868                }
9869                state.replace_text_in_range(None, "X", window, cx);
9870            });
9871        });
9872    }
9873
9874    #[gpui::test]
9875    fn test_editor_decorations_follow_typing(cx: &mut TestAppContext) {
9876        let view = InputView::<EditorMode>::new(cx);
9877        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9878        cx.update(|window, cx| {
9879            view.input.update(cx, |state, cx| {
9880                state.set_value("abc def", window, cx);
9881                state.create_decorations_collection(
9882                    vec![crate::input::TextDecoration::new(
9883                        4..7,
9884                        gpui::HighlightStyle::default(),
9885                    )],
9886                    cx,
9887                );
9888                state.set_selected_range(0..0, cx);
9889                state.replace_text_in_range(None, "X", window, cx);
9890                let layers = state.extras.decoration_layers();
9891                assert_eq!(layers.into_iter().flatten().next().unwrap().range, 5..8);
9892            });
9893        });
9894    }
9895
9896    #[gpui::test]
9897    fn test_backspace_preserves_escaped_quote_terminator(cx: &mut TestAppContext) {
9898        let view = InputView::<EditorMode>::new(cx);
9899        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9900        setup_cursors(&mut cx, &view.input, r#"{"s":"\"|"}"#);
9901        cx.update(|window, cx| {
9902            view.input.update(cx, |state, cx| {
9903                set_test_syntax_provider(std::rc::Rc::new(StringAllProvider), cx);
9904                state.backspace(&Backspace, window, cx);
9905            });
9906        });
9907        assert_cursors(&mut cx, &view.input, r#"{"s":"\|"}"#);
9908    }
9909
9910    #[gpui::test]
9911    fn test_ordinary_typing_does_not_query_syntax(cx: &mut TestAppContext) {
9912        struct UnexpectedQuery;
9913        impl crate::input::SyntaxContextProvider for UnexpectedQuery {
9914            fn context_at(&self, _: &ropey::Rope, _: usize) -> crate::input::SyntaxContext {
9915                panic!("ordinary typing must not query syntax");
9916            }
9917        }
9918        let view = InputView::<EditorMode>::new(cx);
9919        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9920        setup_cursors(&mut cx, &view.input, "|");
9921        cx.update(|window, cx| {
9922            view.input.update(cx, |state, cx| {
9923                set_test_syntax_provider(std::rc::Rc::new(UnexpectedQuery), cx);
9924                for c in ["a", "b", "c"] {
9925                    state.replace_text_in_range(None, c, window, cx);
9926                }
9927            });
9928        });
9929        assert_cursors(&mut cx, &view.input, "abc|");
9930    }
9931
9932    #[gpui::test]
9933    fn test_auto_close_is_atomic_at_history_limit(cx: &mut TestAppContext) {
9934        let view = InputView::<EditorMode>::new(cx);
9935        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9936        setup_cursors(&mut cx, &view.input, "|");
9937        cx.update(|window, cx| {
9938            view.input.update(cx, |state, cx| {
9939                for _ in 0..999 {
9940                    state.replace_text_in_range(None, "a", window, cx);
9941                }
9942                state.replace_text_in_range(None, "(", window, cx);
9943                state.undo(&Undo, window, cx);
9944                assert!(!state.text.to_string().contains('('));
9945                assert!(!state.text.to_string().contains(')'));
9946                state.redo(&Redo, window, cx);
9947            });
9948        });
9949        assert_cursors(&mut cx, &view.input, &format!("{}(|)", "a".repeat(999)));
9950    }
9951
9952    #[gpui::test]
9953    fn test_comment_closer_is_inserted_literally(cx: &mut TestAppContext) {
9954        let view = InputView::<EditorMode>::new(cx);
9955        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9956        setup_cursors(&mut cx, &view.input, "// (|)");
9957        cx.update(|window, cx| {
9958            view.input.update(cx, |state, cx| {
9959                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
9960                state.replace_text_in_range(None, ")", window, cx);
9961            });
9962        });
9963        assert_cursors(&mut cx, &view.input, "// ()|)");
9964    }
9965
9966    #[gpui::test]
9967    fn test_explicit_range_skip_does_not_delete_existing_closer_on_undo(cx: &mut TestAppContext) {
9968        let view = InputView::<EditorMode>::new(cx);
9969        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9970        setup_cursors(&mut cx, &view.input, "(a|)");
9971        let before = view.input.read_with(&cx, |state, _| state.text.to_string());
9972        cx.update(|window, cx| {
9973            view.input.update(cx, |state, cx| {
9974                state.replace_text_in_range(Some(2..2), ")", window, cx);
9975            });
9976        });
9977        assert_cursors(&mut cx, &view.input, "(a)|");
9978        cx.update(|window, cx| {
9979            view.input
9980                .update(cx, |state, cx| state.undo(&Undo, window, cx));
9981        });
9982        view.input.read_with(&cx, |state, _| {
9983            assert_eq!(
9984                state.text.to_string(),
9985                before,
9986                "skip must not remove an existing closer on undo"
9987            );
9988        });
9989    }
9990
9991    #[gpui::test]
9992    fn test_pair_redo_restores_interior_cursor(cx: &mut TestAppContext) {
9993        let view = InputView::<EditorMode>::new(cx);
9994        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9995        setup_cursors(&mut cx, &view.input, "|");
9996        cx.update(|window, cx| {
9997            view.input.update(cx, |state, cx| {
9998                state.replace_text_in_range(None, "(", window, cx);
9999            });
10000        });
10001        assert_cursors(&mut cx, &view.input, "(|)");
10002        cx.update(|window, cx| {
10003            view.input.update(cx, |state, cx| {
10004                state.undo(&Undo, window, cx);
10005                state.redo(&Redo, window, cx);
10006            });
10007        });
10008        assert_cursors(&mut cx, &view.input, "(|)");
10009    }
10010
10011    #[gpui::test]
10012    fn test_pair_enter_redo_restores_interior_cursor(cx: &mut TestAppContext) {
10013        let view = InputView::<EditorMode>::new(cx);
10014        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10015        setup_cursors(&mut cx, &view.input, "{|}");
10016        cx.update(|window, cx| {
10017            view.input.update(cx, |state, cx| {
10018                state.enter(
10019                    &Enter {
10020                        secondary: false,
10021                        shift: false,
10022                    },
10023                    window,
10024                    cx,
10025                );
10026            });
10027        });
10028        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10029        cx.update(|window, cx| {
10030            view.input.update(cx, |state, cx| {
10031                state.undo(&Undo, window, cx);
10032            });
10033        });
10034        assert_cursors(&mut cx, &view.input, "{|}");
10035        cx.update(|window, cx| {
10036            view.input
10037                .update(cx, |state, cx| state.redo(&Redo, window, cx));
10038        });
10039        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10040    }
10041
10042    #[gpui::test]
10043    fn test_auto_close_parens(cx: &mut TestAppContext) {
10044        let view = InputView::<EditorMode>::new(cx);
10045        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10046        setup_cursors(&mut cx, &view.input, "|");
10047        cx.update(|window, cx| {
10048            view.input.update(cx, |state, cx| {
10049                state.replace_text_in_range(None, "(", window, cx);
10050            });
10051        });
10052        assert_cursors(&mut cx, &view.input, "(|)");
10053        // Selection must be collapsed: typing next must insert, not replace.
10054        cx.update(|window, cx| {
10055            view.input.update(cx, |state, cx| {
10056                state.replace_text_in_range(None, "x", window, cx);
10057            });
10058        });
10059        assert_cursors(&mut cx, &view.input, "(x|)");
10060    }
10061
10062    #[gpui::test]
10063    fn test_auto_close_skip_over_closer(cx: &mut TestAppContext) {
10064        let view = InputView::<EditorMode>::new(cx);
10065        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10066        setup_cursors(&mut cx, &view.input, "(|)");
10067        cx.update(|window, cx| {
10068            view.input.update(cx, |state, cx| {
10069                state.replace_text_in_range(None, ")", window, cx);
10070            });
10071        });
10072        // No duplicate closer, cursor moves past the existing one.
10073        assert_cursors(&mut cx, &view.input, "()|");
10074        // Selection must be collapsed, not covering blank space.
10075        view.input.read_with(&cx, |state, _| {
10076            assert!(state.active_selection().is_empty());
10077        });
10078    }
10079
10080    #[gpui::test]
10081    fn test_auto_close_no_pair_inside_word(cx: &mut TestAppContext) {
10082        let view = InputView::<EditorMode>::new(cx);
10083        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10084        setup_cursors(&mut cx, &view.input, "don|");
10085        cx.update(|window, cx| {
10086            view.input.update(cx, |state, cx| {
10087                state.replace_text_in_range(None, "'", window, cx);
10088            });
10089        });
10090        // Contraction: no auto-close.
10091        assert_cursors(&mut cx, &view.input, "don'|");
10092    }
10093
10094    #[gpui::test]
10095    fn test_auto_close_quote_after_cjk(cx: &mut TestAppContext) {
10096        let view = InputView::<EditorMode>::new(cx);
10097        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10098        // Unicode-safe previous-char lookup: must not panic on multi-byte prefix.
10099        // CJK counts as word-like, so no pairing — single quote inserted.
10100        setup_cursors(&mut cx, &view.input, "中|");
10101        cx.update(|window, cx| {
10102            view.input.update(cx, |state, cx| {
10103                state.replace_text_in_range(None, "'", window, cx);
10104            });
10105        });
10106        assert_cursors(&mut cx, &view.input, "中'|");
10107    }
10108
10109    #[gpui::test]
10110    fn test_auto_close_closer_skips_at_string_end(cx: &mut TestAppContext) {
10111        let view = InputView::<EditorMode>::new(cx);
10112        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10113        // Cursor before existing closing quote: typing `"` skips past it.
10114        setup_cursors(&mut cx, &view.input, "\"hello|\"");
10115        cx.update(|window, cx| {
10116            view.input.update(cx, |state, cx| {
10117                state.replace_text_in_range(None, "\"", window, cx);
10118            });
10119        });
10120        assert_cursors(&mut cx, &view.input, "\"hello\"|");
10121        view.input.read_with(&cx, |state, _| {
10122            assert!(state.active_selection().is_empty());
10123        });
10124    }
10125
10126    #[gpui::test]
10127    fn test_backspace_pair_with_cjk_prefix(cx: &mut TestAppContext) {
10128        let view = InputView::<EditorMode>::new(cx);
10129        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10130        // UTF-16 conversion: deleting `()` after CJK must not touch neighbors.
10131        setup_cursors(&mut cx, &view.input, "中(|)abc");
10132        cx.update(|window, cx| {
10133            view.input
10134                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
10135        });
10136        assert_cursors(&mut cx, &view.input, "中|abc");
10137    }
10138
10139    #[gpui::test]
10140    fn test_enter_split_respects_smart_indent_off(cx: &mut TestAppContext) {
10141        let view = InputView::<EditorMode>::new(cx);
10142        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10143        setup_cursors(&mut cx, &view.input, "{|}");
10144        cx.update(|window, cx| {
10145            view.input.update(cx, |state, cx| {
10146                state.set_smart_indent(false, window, cx);
10147                state.enter(
10148                    &Enter {
10149                        secondary: false,
10150                        shift: false,
10151                    },
10152                    window,
10153                    cx,
10154                );
10155            });
10156        });
10157        // Smart indent controls both structural splitting and extra indentation.
10158        assert_cursors(&mut cx, &view.input, "{\n|}");
10159    }
10160
10161    #[gpui::test]
10162    fn test_enter_split_is_independent_of_auto_close(cx: &mut TestAppContext) {
10163        let view = InputView::<EditorMode>::new(cx);
10164        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10165        setup_cursors(&mut cx, &view.input, "{|}");
10166        cx.update(|window, cx| {
10167            view.input.update(cx, |state, cx| {
10168                state.set_auto_close(false, window, cx);
10169                state.enter(
10170                    &Enter {
10171                        secondary: false,
10172                        shift: false,
10173                    },
10174                    window,
10175                    cx,
10176                );
10177            });
10178        });
10179        // Disabling automatic insertion does not disable structural indentation.
10180        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10181    }
10182
10183    #[gpui::test]
10184    fn test_language_config_applies_before_render_and_on_language_change(cx: &mut TestAppContext) {
10185        use crate::input::{AutoClosingPair, set_language_config};
10186        let view = InputView::<EditorMode>::new(cx);
10187        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10188        cx.update(|window, cx| {
10189            set_language_config(
10190                "alpha",
10191                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
10192                cx,
10193            );
10194            set_language_config(
10195                "beta",
10196                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("‹", "›")]),
10197                cx,
10198            );
10199            let first = cx.new(|cx| EditorState::new(window, cx).language("ALPHA"));
10200            let second = cx.new(|cx| EditorState::new(window, cx).language("beta"));
10201            first.update(cx, |state, cx| {
10202                state.replace_text_in_range(None, "«", window, cx);
10203                assert_eq!(state.text().to_string(), "«»");
10204                state.set_value("", window, cx);
10205                state.set_smart_indent(false, window, cx);
10206                state.set_highlighter("beta", cx);
10207                assert!(!state.mode.is_smart_indent());
10208                state.replace_text_in_range(None, "‹", window, cx);
10209                assert_eq!(state.text().to_string(), "‹›");
10210                state.set_value("", window, cx);
10211                state.set_highlighter("unknown", cx);
10212                state.replace_text_in_range(None, "(", window, cx);
10213                assert_eq!(state.text().to_string(), "()");
10214            });
10215            second.update(cx, |state, cx| {
10216                state.replace_text_in_range(None, "‹", window, cx);
10217                assert_eq!(state.text().to_string(), "‹›");
10218            });
10219        });
10220    }
10221
10222    #[gpui::test]
10223    fn test_language_service_replacement_updates_syntax_without_render(cx: &mut TestAppContext) {
10224        use crate::input::{LanguageProvider, SyntaxContextProvider, set_language_provider};
10225        struct StringLanguages(Rc<Cell<usize>>);
10226        impl LanguageProvider for StringLanguages {
10227            fn syntax_context_provider(&self, _: &str) -> Option<Rc<dyn SyntaxContextProvider>> {
10228                self.0.set(self.0.get() + 1);
10229                Some(Rc::new(StringAllProvider))
10230            }
10231        }
10232        struct CodeLanguages;
10233        impl LanguageProvider for CodeLanguages {}
10234        let view = InputView::<EditorMode>::new(cx);
10235        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10236        cx.update(|window, cx| {
10237            let creations = Rc::new(Cell::new(0));
10238            set_language_provider(Rc::new(StringLanguages(creations.clone())), cx);
10239            view.input.update(cx, |state, cx| {
10240                state.replace_text_in_range(None, "(", window, cx);
10241                state.replace_text_in_range(None, "[", window, cx);
10242                assert_eq!(state.text().to_string(), "([");
10243                assert_eq!(
10244                    creations.get(),
10245                    1,
10246                    "retain the document provider between edits"
10247                );
10248            });
10249            set_language_provider(Rc::new(CodeLanguages), cx);
10250            view.input.update(cx, |state, cx| {
10251                state.replace_text_in_range(None, "{", window, cx);
10252                assert_eq!(state.text().to_string(), "([{}");
10253            });
10254        });
10255    }
10256
10257    #[gpui::test]
10258    fn test_language_config_updates_existing_editors(cx: &mut TestAppContext) {
10259        use crate::input::{AutoClosingPair, set_language_config};
10260        let view = InputView::<EditorMode>::new(cx);
10261        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10262        setup_cursors(&mut cx, &view.input, "|");
10263        cx.update(|window, cx| {
10264            view.input.update(cx, |state, cx| {
10265                state.set_highlighter("CUSTOM", cx);
10266                state.set_auto_close(false, window, cx);
10267                state.set_smart_indent(false, window, cx);
10268            });
10269            set_language_config(
10270                "custom",
10271                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
10272                cx,
10273            );
10274            // Batched registrations must update every affected language.
10275            set_language_config("other", LanguageConfig::default(), cx);
10276            view.input.update(cx, |state, cx| {
10277                assert!(!state.mode.is_auto_close());
10278                assert!(!state.mode.is_smart_indent());
10279                state.set_auto_close(true, window, cx);
10280                state.replace_text_in_range(None, "«", window, cx);
10281            });
10282        });
10283        assert_cursors(&mut cx, &view.input, "«|»");
10284    }
10285
10286    #[gpui::test]
10287    fn test_auto_close_before_is_language_configurable(cx: &mut TestAppContext) {
10288        let view = InputView::<EditorMode>::new(cx);
10289        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10290        setup_cursors(&mut cx, &view.input, "|word");
10291        cx.update(|window, cx| {
10292            view.input.update(cx, |state, cx| {
10293                state.replace_text_in_range(None, "(", window, cx)
10294            });
10295        });
10296        assert_cursors(&mut cx, &view.input, "(|word");
10297        setup_cursors(&mut cx, &view.input, "|word");
10298        cx.update(|window, cx| {
10299            view.input.update(cx, |state, cx| {
10300                crate::input::set_language_config(
10301                    state.language_name(),
10302                    LanguageConfig::default().auto_close_before("w"),
10303                    cx,
10304                );
10305                state.replace_text_in_range(None, "(", window, cx);
10306            });
10307        });
10308        assert_cursors(&mut cx, &view.input, "(|)word");
10309    }
10310
10311    #[gpui::test]
10312    fn test_pair_context_restrictions_are_per_pair(cx: &mut TestAppContext) {
10313        use crate::input::{AutoClosingPair, SyntaxContext};
10314        let view = InputView::<EditorMode>::new(cx);
10315        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10316        setup_cursors(&mut cx, &view.input, "|");
10317        cx.update(|window, cx| {
10318            view.input.update(cx, |state, cx| {
10319                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10320                crate::input::set_language_config(
10321                    state.language_name(),
10322                    LanguageConfig::default().auto_closing_pairs([
10323                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::String]),
10324                        AutoClosingPair::new("[", "]").not_in([SyntaxContext::Comment]),
10325                    ]),
10326                    cx,
10327                );
10328                state.replace_text_in_range(None, "(", window, cx);
10329                state.replace_text_in_range(None, "[", window, cx);
10330            });
10331        });
10332        assert_cursors(&mut cx, &view.input, "([|)");
10333    }
10334
10335    #[gpui::test]
10336    fn test_multichar_delimiters_insert_delete_and_skip(cx: &mut TestAppContext) {
10337        use crate::input::{AutoClosingPair, SyntaxContext};
10338        struct BlockComment;
10339        impl crate::input::SyntaxContextProvider for BlockComment {
10340            fn context_at(&self, text: &ropey::Rope, _: usize) -> SyntaxContext {
10341                if text.to_string().contains("/*") {
10342                    SyntaxContext::Comment
10343                } else {
10344                    SyntaxContext::Code
10345                }
10346            }
10347        }
10348        let view = InputView::<EditorMode>::new(cx);
10349        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10350        setup_cursors(&mut cx, &view.input, "|");
10351        cx.update(|window, cx| {
10352            view.input.update(cx, |state, cx| {
10353                set_test_syntax_provider(std::rc::Rc::new(BlockComment), cx);
10354                crate::input::set_language_config(
10355                    state.language_name(),
10356                    LanguageConfig::default()
10357                        .auto_closing_pairs([AutoClosingPair::new("/*", "*/")
10358                            .not_in([SyntaxContext::String, SyntaxContext::Comment])]),
10359                    cx,
10360                );
10361                state.replace_text_in_range(None, "/", window, cx);
10362                state.replace_text_in_range(None, "*", window, cx);
10363            });
10364        });
10365        assert_cursors(&mut cx, &view.input, "/*|*/");
10366        cx.update(|window, cx| {
10367            view.input
10368                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
10369        });
10370        assert_cursors(&mut cx, &view.input, "|");
10371        cx.update(|window, cx| {
10372            view.input.update(cx, |state, cx| {
10373                state.undo(&Undo, window, cx);
10374                state.replace_text_in_range(None, "*", window, cx);
10375                state.replace_text_in_range(None, "/", window, cx);
10376            });
10377        });
10378        assert_cursors(&mut cx, &view.input, "/**/|");
10379    }
10380
10381    #[gpui::test]
10382    fn test_auto_closed_pairs_fallback_and_explicit_disable(cx: &mut TestAppContext) {
10383        use crate::input::BracketPair;
10384        let view = InputView::<EditorMode>::new(cx);
10385        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10386        setup_cursors(&mut cx, &view.input, "|");
10387        cx.update(|window, cx| {
10388            view.input.update(cx, |state, cx| {
10389                let mut rules = LanguageConfig::default().brackets([BracketPair::new("«", "»")]);
10390                rules.auto_closing_pairs = None;
10391                crate::input::set_language_config(state.language_name(), rules.clone(), cx);
10392                state.replace_text_in_range(None, "«", window, cx);
10393                crate::input::set_language_config(
10394                    state.language_name(),
10395                    rules.auto_closing_pairs([]),
10396                    cx,
10397                );
10398                state.replace_text_in_range(None, "«", window, cx);
10399            });
10400        });
10401        assert_cursors(&mut cx, &view.input, "««|»");
10402    }
10403
10404    #[gpui::test]
10405    fn test_undo_open_composition_preserves_generated_pairs(cx: &mut TestAppContext) {
10406        use crate::input::{AutoClosingPair, SyntaxContext};
10407        let view = InputView::<EditorMode>::new(cx);
10408        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10409        setup_cursors(&mut cx, &view.input, "|");
10410        cx.update(|window, cx| {
10411            view.input.update(cx, |state, cx| {
10412                crate::input::set_language_config(
10413                    state.language_name(),
10414                    LanguageConfig::default()
10415                        .auto_closing_pairs([
10416                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
10417                        ]),
10418                    cx,
10419                );
10420                state.replace_text_in_range(None, "/", window, cx);
10421                state.replace_text_in_range(None, "*", window, cx);
10422                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10423                state.replace_and_mark_text_in_range(None, "x", Some(1..1), window, cx);
10424                state.undo(&Undo, window, cx);
10425                state.replace_text_in_range(None, "*", window, cx);
10426                state.replace_text_in_range(None, "/", window, cx);
10427            });
10428        });
10429        assert_cursors(&mut cx, &view.input, "/**/|");
10430    }
10431
10432    #[gpui::test]
10433    fn test_undo_does_not_promote_literal_comment_delimiters(cx: &mut TestAppContext) {
10434        use crate::input::{AutoClosingPair, SyntaxContext};
10435        let view = InputView::<EditorMode>::new(cx);
10436        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10437        setup_cursors(&mut cx, &view.input, "/*|*/");
10438        cx.update(|window, cx| {
10439            view.input.update(cx, |state, cx| {
10440                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10441                crate::input::set_language_config(
10442                    state.language_name(),
10443                    LanguageConfig::default()
10444                        .auto_closing_pairs([
10445                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
10446                        ]),
10447                    cx,
10448                );
10449                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
10450                state.undo(&Undo, window, cx);
10451                state.backspace(&Backspace, window, cx);
10452            });
10453        });
10454        assert_cursors(&mut cx, &view.input, "/|*/");
10455    }
10456
10457    #[gpui::test]
10458    fn test_multiple_generated_pairs_retain_their_identity(cx: &mut TestAppContext) {
10459        use crate::input::{AutoClosingPair, SyntaxContext};
10460        struct CommentScope;
10461        impl crate::input::SyntaxContextProvider for CommentScope {
10462            fn context_at(&self, text: &ropey::Rope, offset: usize) -> SyntaxContext {
10463                if text
10464                    .to_string()
10465                    .find("*/")
10466                    .is_some_and(|end| offset < end + 2)
10467                {
10468                    SyntaxContext::Comment
10469                } else {
10470                    SyntaxContext::Code
10471                }
10472            }
10473        }
10474        let view = InputView::<EditorMode>::new(cx);
10475        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10476        setup_cursors(&mut cx, &view.input, "|");
10477        cx.update(|window, cx| {
10478            view.input.update(cx, |state, cx| {
10479                set_test_syntax_provider(std::rc::Rc::new(CommentScope), cx);
10480                crate::input::set_language_config(
10481                    state.language_name(),
10482                    LanguageConfig::default().auto_closing_pairs([
10483                        AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment]),
10484                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::Comment]),
10485                    ]),
10486                    cx,
10487                );
10488                state.replace_text_in_range(None, "/", window, cx);
10489                state.replace_text_in_range(None, "*", window, cx);
10490                state.set_selected_range(4..4, cx);
10491                state.replace_text_in_range(None, "(", window, cx);
10492                state.set_selected_range(2..2, cx);
10493                state.replace_text_in_range(None, "*", window, cx);
10494                state.replace_text_in_range(None, "/", window, cx);
10495            });
10496        });
10497        assert_cursors(&mut cx, &view.input, "/**/|()");
10498        cx.update(|window, cx| {
10499            view.input.update(cx, |state, cx| {
10500                state.set_selected_range(2..2, cx);
10501                state.backspace(&Backspace, window, cx);
10502                state.undo(&Undo, window, cx);
10503                state.redo(&Redo, window, cx);
10504            });
10505        });
10506        assert_cursors(&mut cx, &view.input, "|()");
10507    }
10508
10509    #[gpui::test]
10510    fn test_replacing_generated_opener_invalidates_the_pair(cx: &mut TestAppContext) {
10511        use crate::input::{AutoClosingPair, SyntaxContext};
10512        let view = InputView::<EditorMode>::new(cx);
10513        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10514        setup_cursors(&mut cx, &view.input, "|");
10515        cx.update(|window, cx| {
10516            view.input.update(cx, |state, cx| {
10517                crate::input::set_language_config(
10518                    state.language_name(),
10519                    LanguageConfig::default()
10520                        .auto_closing_pairs([
10521                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
10522                        ]),
10523                    cx,
10524                );
10525                state.replace_text_in_range(None, "/", window, cx);
10526                state.replace_text_in_range(None, "*", window, cx);
10527                state.replace_text_in_range(Some(0..2), "//", window, cx);
10528                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10529                state.replace_text_in_range(None, "*", window, cx);
10530            });
10531        });
10532        assert_cursors(&mut cx, &view.input, "//*|*/");
10533    }
10534
10535    #[gpui::test]
10536    fn test_configuration_preserves_compiled_regex_options(cx: &mut TestAppContext) {
10537        use crate::input::IndentationRules;
10538        let view = InputView::<EditorMode>::new(cx);
10539        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10540        setup_cursors(&mut cx, &view.input, "BEGIN|");
10541        cx.update(|window, cx| {
10542            view.input.update(cx, |state, cx| {
10543                let rules = |insensitive| {
10544                    LanguageConfig::default().indentation_rules(IndentationRules::new(
10545                        regex::RegexBuilder::new("begin$")
10546                            .case_insensitive(insensitive)
10547                            .build()
10548                            .unwrap(),
10549                        regex::Regex::new("^end").unwrap(),
10550                    ))
10551                };
10552                crate::input::set_language_config(state.language_name(), rules(false), cx);
10553                crate::input::set_language_config(state.language_name(), rules(true), cx);
10554                state.enter(
10555                    &Enter {
10556                        secondary: false,
10557                        shift: false,
10558                    },
10559                    window,
10560                    cx,
10561                );
10562            });
10563        });
10564        assert_cursors(&mut cx, &view.input, "BEGIN\n  |");
10565    }
10566
10567    #[gpui::test]
10568    fn test_indent_patterns_are_applied_on_enter(cx: &mut TestAppContext) {
10569        use crate::input::IndentationRules;
10570        let view = InputView::<EditorMode>::new(cx);
10571        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10572        let rules = LanguageConfig::default().indentation_rules(IndentationRules::new(
10573            regex::Regex::new(r"\bbegin\s*$").unwrap(),
10574            regex::Regex::new(r"^\s*end\b").unwrap(),
10575        ));
10576        for (before, expected) in [("begin|", "begin\n  |"), ("  |end", "  \n|end")] {
10577            setup_cursors(&mut cx, &view.input, before);
10578            cx.update(|window, cx| {
10579                view.input.update(cx, |state, cx| {
10580                    crate::input::set_language_config(state.language_name(), rules.clone(), cx);
10581                    state.enter(
10582                        &Enter {
10583                            secondary: false,
10584                            shift: false,
10585                        },
10586                        window,
10587                        cx,
10588                    );
10589                });
10590            });
10591            assert_cursors(&mut cx, &view.input, expected);
10592        }
10593    }
10594
10595    #[gpui::test]
10596    fn test_custom_language_config(cx: &mut TestAppContext) {
10597        let view = InputView::<EditorMode>::new(cx);
10598        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10599        cx.update(|_window, cx| {
10600            view.input.update(cx, |state, cx| {
10601                crate::input::set_language_config(
10602                    state.language_name(),
10603                    crate::input::language_config::LanguageConfig::default()
10604                        .brackets([crate::input::BracketPair::new("«", "»")])
10605                        .auto_closing_pairs([crate::input::AutoClosingPair::new("«", "»")]),
10606                    cx,
10607                );
10608            });
10609        });
10610        // Custom pair closes.
10611        setup_cursors(&mut cx, &view.input, "|");
10612        cx.update(|window, cx| {
10613            view.input.update(cx, |state, cx| {
10614                state.replace_text_in_range(None, "«", window, cx);
10615            });
10616        });
10617        assert_cursors(&mut cx, &view.input, "«|»");
10618        // Default `(` no longer pairs under custom rules.
10619        cx.update(|window, cx| {
10620            view.input.update(cx, |state, cx| {
10621                state.set_value("\n", window, cx);
10622                state.set_selected_range(0..0, cx);
10623                state.replace_text_in_range(None, "(", window, cx);
10624            });
10625        });
10626        assert_cursors(&mut cx, &view.input, "(|");
10627    }
10628
10629    #[gpui::test]
10630    fn test_skip_records_no_history(cx: &mut TestAppContext) {
10631        let view = InputView::<EditorMode>::new(cx);
10632        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10633        setup_cursors(&mut cx, &view.input, "(a|)");
10634        cx.update(|window, cx| {
10635            view.input.update(cx, |state, cx| {
10636                // Type a normal char (recorded), then skip over `)`.
10637                state.replace_text_in_range(None, "b", window, cx);
10638            });
10639        });
10640        assert_cursors(&mut cx, &view.input, "(ab|)");
10641        cx.update(|window, cx| {
10642            view.input.update(cx, |state, cx| {
10643                state.replace_text_in_range(None, ")", window, cx);
10644            });
10645        });
10646        // Skip is a pure cursor move: no history entry of its own.
10647        assert_cursors(&mut cx, &view.input, "(ab)|");
10648        // Undo removes `b`, never exposing a transient `())`.
10649        cx.update(|window, cx| {
10650            view.input
10651                .update(cx, |state, cx| state.undo(&Undo, window, cx));
10652        });
10653        assert_cursors(&mut cx, &view.input, "(a|)");
10654    }
10655
10656    #[gpui::test]
10657    fn test_pair_insert_undo_removes_both(cx: &mut TestAppContext) {
10658        let view = InputView::<EditorMode>::new(cx);
10659        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10660        setup_cursors(&mut cx, &view.input, "|");
10661        cx.update(|window, cx| {
10662            view.input.update(cx, |state, cx| {
10663                state.replace_text_in_range(None, "(", window, cx);
10664            });
10665        });
10666        assert_cursors(&mut cx, &view.input, "(|)");
10667        // One undo removes the whole pair (typing coalescing).
10668        cx.update(|window, cx| {
10669            view.input
10670                .update(cx, |state, cx| state.undo(&Undo, window, cx));
10671        });
10672        assert_cursors(&mut cx, &view.input, "|");
10673    }
10674
10675    #[gpui::test]
10676    fn test_escaped_quote_does_not_skip(cx: &mut TestAppContext) {
10677        let view = InputView::<EditorMode>::new(cx);
10678        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10679        // Odd backslashes: the typed quote is escaped, insert literally.
10680        setup_cursors(&mut cx, &view.input, "\"hello\\|\"");
10681        cx.update(|window, cx| {
10682            view.input.update(cx, |state, cx| {
10683                state.replace_text_in_range(None, "\"", window, cx);
10684            });
10685        });
10686        assert_cursors(&mut cx, &view.input, "\"hello\\\"|\"");
10687    }
10688
10689    #[gpui::test]
10690    fn test_even_backslashes_still_skip(cx: &mut TestAppContext) {
10691        let view = InputView::<EditorMode>::new(cx);
10692        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10693        // Even backslashes: quote is not escaped, skip over the terminator.
10694        setup_cursors(&mut cx, &view.input, "\"hello\\\\|\"");
10695        cx.update(|window, cx| {
10696            view.input.update(cx, |state, cx| {
10697                state.replace_text_in_range(None, "\"", window, cx);
10698            });
10699        });
10700        assert_cursors(&mut cx, &view.input, "\"hello\\\\\"|");
10701    }
10702
10703    #[gpui::test]
10704    fn test_editor_options_survive_configuration_replacement(cx: &mut TestAppContext) {
10705        use crate::input::{BracketPair, LanguageConfig};
10706
10707        let view = InputView::<EditorMode>::new(cx);
10708        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10709        cx.update(|window, cx| {
10710            view.input.update(cx, |state, cx| {
10711                // Language changes update rules without changing editor preferences.
10712                state.set_smart_indent(false, window, cx);
10713                crate::input::set_language_config(
10714                    state.language_name(),
10715                    LanguageConfig::default().brackets([BracketPair::new("«", "»")]),
10716                    cx,
10717                );
10718            });
10719        });
10720        view.input.read_with(&cx, |state, _| {
10721            assert!(!state.mode.is_smart_indent(), "editor option preserved");
10722            assert!(state.mode.is_auto_close());
10723            let pairs = &state.mode.language_config().unwrap().brackets;
10724            assert_eq!(pairs.len(), 1, "pairs follow configuration");
10725            assert_eq!(pairs[0].open.as_ref(), "«");
10726        });
10727    }
10728
10729    struct CommentAllProvider;
10730    impl crate::input::SyntaxContextProvider for CommentAllProvider {
10731        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
10732            crate::input::SyntaxContext::Comment
10733        }
10734    }
10735
10736    struct StringAllProvider;
10737    impl crate::input::SyntaxContextProvider for StringAllProvider {
10738        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
10739            crate::input::SyntaxContext::String
10740        }
10741    }
10742
10743    #[gpui::test]
10744    fn test_smart_indent_suppressed_in_strings(cx: &mut TestAppContext) {
10745        use std::rc::Rc;
10746
10747        let view = InputView::<EditorMode>::new(cx);
10748        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10749        cx.update(|_window, cx| {
10750            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
10751        });
10752        // Trigger `{` inside a string: no extra level, base indent only.
10753        setup_cursors(&mut cx, &view.input, "  x = {|");
10754        cx.update(|window, cx| {
10755            view.input.update(cx, |state, cx| {
10756                state.enter(
10757                    &Enter {
10758                        secondary: false,
10759                        shift: false,
10760                    },
10761                    window,
10762                    cx,
10763                );
10764            });
10765        });
10766        assert_cursors(&mut cx, &view.input, "  x = {\n  |");
10767    }
10768
10769    #[gpui::test]
10770    fn test_split_suppressed_in_strings(cx: &mut TestAppContext) {
10771        use std::rc::Rc;
10772
10773        let view = InputView::<EditorMode>::new(cx);
10774        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10775        cx.update(|_window, cx| {
10776            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
10777        });
10778        // No three-way split inside strings: plain newline instead.
10779        setup_cursors(&mut cx, &view.input, "{|}");
10780        cx.update(|window, cx| {
10781            view.input.update(cx, |state, cx| {
10782                state.enter(
10783                    &Enter {
10784                        secondary: false,
10785                        shift: false,
10786                    },
10787                    window,
10788                    cx,
10789                );
10790            });
10791        });
10792        assert_cursors(&mut cx, &view.input, "{\n|}");
10793    }
10794
10795    #[gpui::test]
10796    fn test_comment_context_disables_pairing(cx: &mut TestAppContext) {
10797        use std::rc::Rc;
10798
10799        let view = InputView::<EditorMode>::new(cx);
10800        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10801        cx.update(|_window, cx| {
10802            set_test_syntax_provider(Rc::new(CommentAllProvider), cx);
10803        });
10804        setup_cursors(&mut cx, &view.input, "|");
10805        cx.update(|window, cx| {
10806            view.input.update(cx, |state, cx| {
10807                state.replace_text_in_range(None, "(", window, cx);
10808            });
10809        });
10810        // In comments every character is literal.
10811        assert_cursors(&mut cx, &view.input, "(|");
10812    }
10813
10814    #[gpui::test]
10815    fn test_backspace_deletes_empty_pair(cx: &mut TestAppContext) {
10816        let view = InputView::<EditorMode>::new(cx);
10817        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10818        setup_cursors(&mut cx, &view.input, "{|}");
10819        cx.update(|window, cx| {
10820            view.input
10821                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
10822        });
10823        assert_cursors(&mut cx, &view.input, "|");
10824    }
10825
10826    #[gpui::test]
10827    fn test_backspace_keeps_closer_with_content(cx: &mut TestAppContext) {
10828        let view = InputView::<EditorMode>::new(cx);
10829        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10830        setup_cursors(&mut cx, &view.input, "{d|}");
10831        cx.update(|window, cx| {
10832            view.input
10833                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
10834        });
10835        // Only `d` is deleted, the closing brace stays.
10836        assert_cursors(&mut cx, &view.input, "{|}");
10837    }
10838
10839    #[gpui::test]
10840    fn test_enter_splits_brackets(cx: &mut TestAppContext) {
10841        let view = InputView::<EditorMode>::new(cx);
10842        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10843        setup_cursors(&mut cx, &view.input, "{|}");
10844        cx.update(|window, cx| {
10845            view.input.update(cx, |state, cx| {
10846                state.enter(
10847                    &Enter {
10848                        secondary: false,
10849                        shift: false,
10850                    },
10851                    window,
10852                    cx,
10853                );
10854            });
10855        });
10856        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10857        // Selection must be collapsed: typing next must insert, not replace.
10858        view.input.read_with(&cx, |state, _| {
10859            assert!(state.active_selection().is_empty());
10860        });
10861        cx.update(|window, cx| {
10862            view.input.update(cx, |state, cx| {
10863                state.replace_text_in_range(None, "x", window, cx);
10864            });
10865        });
10866        assert_cursors(&mut cx, &view.input, "{\n  x|\n}");
10867    }
10868
10869    #[gpui::test]
10870    fn test_enter_smart_indent_after_brace(cx: &mut TestAppContext) {
10871        let view = InputView::<EditorMode>::new(cx);
10872        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10873        setup_cursors(&mut cx, &view.input, "fn main() {|");
10874        cx.update(|window, cx| {
10875            view.input.update(cx, |state, cx| {
10876                state.enter(
10877                    &Enter {
10878                        secondary: false,
10879                        shift: false,
10880                    },
10881                    window,
10882                    cx,
10883                );
10884            });
10885        });
10886        assert_cursors(&mut cx, &view.input, "fn main() {\n  |");
10887    }
10888
10889    #[gpui::test]
10890    fn test_block_indent_tracks_all_preceding_edits(cx: &mut TestAppContext) {
10891        let view = multi_line(cx);
10892        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10893        setup_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
10894        cx.update(|window, cx| {
10895            view.input
10896                .update(cx, |state, cx| state.indent(true, window, cx));
10897        });
10898        assert_cursors(&mut cx, &view.input, "  |ab\n  |cd\n  |ef");
10899        cx.update(|window, cx| {
10900            view.input
10901                .update(cx, |state, cx| state.outdent(true, window, cx));
10902        });
10903        assert_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
10904    }
10905
10906    #[gpui::test]
10907    fn test_block_outdent_clamps_cursor_inside_indent(cx: &mut TestAppContext) {
10908        let view = multi_line(cx);
10909        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10910        setup_cursors(&mut cx, &view.input, "ab\n | cd");
10911        cx.update(|window, cx| {
10912            view.input
10913                .update(cx, |state, cx| state.outdent(true, window, cx));
10914        });
10915        assert_cursors(&mut cx, &view.input, "ab\n|cd");
10916    }
10917
10918    #[gpui::test]
10919    fn test_ime_restores_original_selection(cx: &mut TestAppContext) {
10920        let view = InputView::build(cx, |state| state);
10921        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10922        cx.update(|window, cx| {
10923            view.input.update(cx, |state, cx| {
10924                state.set_value("abc", window, cx);
10925                state.set_selected_range(1..2, cx);
10926                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
10927                state.replace_text_in_range(None, "你", window, cx);
10928                state.undo(&Undo, window, cx);
10929                assert_eq!(state.value(), "abc");
10930                assert_eq!(state.selected_range(), 1..2);
10931                state.redo(&Redo, window, cx);
10932                assert_eq!(state.selected_range(), 4..4);
10933            });
10934        });
10935    }
10936
10937    #[gpui::test]
10938    fn test_noop_does_not_change_redo_selection(cx: &mut TestAppContext) {
10939        let view = multi_line(cx);
10940        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10941        cx.update(|window, cx| {
10942            view.input.update(cx, |state, cx| {
10943                state.set_value("abc", window, cx);
10944                state.set_selected_range(1..1, cx);
10945                state.replace_text_in_range(None, "X", window, cx);
10946                state.set_selected_range(0..0, cx);
10947                state.replace_text_in_range(None, "", window, cx);
10948                state.undo(&Undo, window, cx);
10949                state.redo(&Redo, window, cx);
10950                assert_eq!(state.value(), "aXbc");
10951                assert_eq!(state.selected_range(), 2..2);
10952            });
10953        });
10954    }
10955
10956    #[gpui::test]
10957    fn test_multi_cursor_insert_text(cx: &mut TestAppContext) {
10958        let view = multi_line(cx);
10959        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10960        let input = view.input;
10961
10962        setup_cursors(&mut cx, &input, "|hello |world|");
10963        cx.update(|window, cx| {
10964            input.update(cx, |state, cx| {
10965                state.replace_text_in_range(None, ">>>", window, cx);
10966            });
10967        });
10968        assert_cursors(&mut cx, &input, ">>>|hello >>>|world>>>|");
10969    }
10970
10971    #[gpui::test]
10972    fn test_multi_cursor_delete_backward(cx: &mut TestAppContext) {
10973        let view = multi_line(cx);
10974        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10975        let input = view.input;
10976
10977        setup_cursors(&mut cx, &input, "|islands| cars|");
10978        cx.update(|window, cx| {
10979            input.update(cx, |state, cx| {
10980                state.backspace(&Backspace, window, cx);
10981            });
10982        });
10983        // The first cursor has nothing to delete. The others delete an `s`.
10984        assert_cursors(&mut cx, &input, "|island| car|");
10985    }
10986
10987    #[gpui::test]
10988    fn test_multi_cursor_delete_forward_merges(cx: &mut TestAppContext) {
10989        let view = multi_line(cx);
10990        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10991        let input = view.input;
10992
10993        setup_cursors(&mut cx, &input, "hello| |world");
10994        cx.update(|window, cx| {
10995            input.update(cx, |state, cx| {
10996                state.delete(&Delete, window, cx);
10997            });
10998        });
10999        // Adjacent deletions merge into a single cursor.
11000        assert_cursors(&mut cx, &input, "hello|orld");
11001    }
11002
11003    #[gpui::test]
11004    fn test_multi_cursor_multiline_insert_and_delete(cx: &mut TestAppContext) {
11005        let view = multi_line(cx);
11006        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11007        let input = view.input;
11008
11009        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11010        cx.update(|window, cx| {
11011            input.update(cx, |state, cx| {
11012                state.replace_text_in_range(None, "a", window, cx);
11013            });
11014        });
11015        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11016
11017        // The whole multi-edit insert is a single undo transaction.
11018        input.read_with(&cx, |state, _| {
11019            assert_eq!(state.undo_manager.undo_count(), 1);
11020        });
11021
11022        cx.update(|window, cx| {
11023            input.update(cx, |state, cx| {
11024                state.backspace(&Backspace, window, cx);
11025            });
11026        });
11027        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11028    }
11029
11030    #[gpui::test]
11031    fn test_add_cursor_below_preserves_column(cx: &mut TestAppContext) {
11032        let view = multi_line(cx);
11033        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11034        let input = view.input;
11035
11036        setup_cursors(&mut cx, &input, "ab|cd\nabcd");
11037        cx.update(|window, cx| {
11038            input.update(cx, |state, cx| {
11039                state.add_cursor_below(&AddCursorBelow, window, cx);
11040            });
11041        });
11042        assert_cursors(&mut cx, &input, "ab|cd\nab|cd");
11043    }
11044
11045    #[gpui::test]
11046    fn test_add_cursor_at_rejects_duplicates(cx: &mut TestAppContext) {
11047        let view = multi_line(cx);
11048        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11049        let input = view.input;
11050
11051        setup_cursors(&mut cx, &input, "he|llo");
11052        cx.update(|_, cx| {
11053            input.update(cx, |state, cx| {
11054                // Duplicate of the existing cursor is rejected.
11055                state.add_cursor_at(2, cx);
11056                assert_eq!(state.selections.len(), 1);
11057                // A distinct offset adds a cursor.
11058                state.add_cursor_at(4, cx);
11059                assert_eq!(state.selections.len(), 2);
11060            });
11061        });
11062    }
11063
11064    #[gpui::test]
11065    fn test_multi_cursor_undo_redo_restores_selections(cx: &mut TestAppContext) {
11066        let view = multi_line(cx);
11067        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11068        let input = view.input;
11069
11070        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11071        cx.update(|window, cx| {
11072            input.update(cx, |state, cx| {
11073                state.replace_text_in_range(None, "a", window, cx);
11074            });
11075        });
11076        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11077
11078        cx.update(|window, cx| {
11079            input.update(cx, |state, cx| {
11080                state.undo(&Undo, window, cx);
11081            });
11082        });
11083        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11084
11085        cx.update(|window, cx| {
11086            input.update(cx, |state, cx| {
11087                state.redo(&Redo, window, cx);
11088            });
11089        });
11090        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11091    }
11092
11093    #[gpui::test]
11094    fn test_multi_cursor_undo_redo_different_line_lengths(cx: &mut TestAppContext) {
11095        let view = multi_line(cx);
11096        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11097        let input = view.input;
11098
11099        setup_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
11100        cx.update(|window, cx| {
11101            input.update(cx, |state, cx| {
11102                state.replace_text_in_range(None, "a", window, cx);
11103            });
11104        });
11105        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
11106
11107        cx.update(|window, cx| {
11108            input.update(cx, |state, cx| {
11109                state.undo(&Undo, window, cx);
11110            });
11111        });
11112        assert_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
11113
11114        cx.update(|window, cx| {
11115            input.update(cx, |state, cx| {
11116                state.redo(&Redo, window, cx);
11117            });
11118        });
11119        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
11120    }
11121
11122    #[gpui::test]
11123    fn test_multi_cursor_undo_multiple_inserts(cx: &mut TestAppContext) {
11124        let view = multi_line(cx);
11125        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11126        let input = view.input;
11127
11128        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11129        for ch in ['a', 'b', 'c'] {
11130            cx.update(|window, cx| {
11131                input.update(cx, |state, cx| {
11132                    state.replace_text_in_range(None, &ch.to_string(), window, cx);
11133                });
11134            });
11135        }
11136        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
11137
11138        // The repeated keystrokes form one typing gesture.
11139        cx.update(|window, cx| {
11140            input.update(cx, |state, cx| {
11141                assert_eq!(state.undo_manager.undo_count(), 1);
11142                state.undo(&Undo, window, cx);
11143            });
11144        });
11145        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11146
11147        cx.update(|window, cx| {
11148            input.update(cx, |state, cx| {
11149                state.redo(&Redo, window, cx);
11150            });
11151        });
11152        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
11153    }
11154
11155    #[gpui::test]
11156    fn test_multi_cursor_backspace_run_is_one_undo(cx: &mut TestAppContext) {
11157        let view = multi_line(cx);
11158        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11159        let input = view.input;
11160
11161        setup_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
11162        for _ in 0..3 {
11163            cx.update(|window, cx| {
11164                input.update(cx, |state, cx| {
11165                    state.backspace(&Backspace, window, cx);
11166                });
11167            });
11168        }
11169        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11170
11171        cx.update(|window, cx| {
11172            input.update(cx, |state, cx| {
11173                assert_eq!(state.undo_manager.undo_count(), 1);
11174                state.undo(&Undo, window, cx);
11175            });
11176        });
11177        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
11178    }
11179
11180    #[gpui::test]
11181    fn test_adding_a_cursor_splits_the_typing_gesture(cx: &mut TestAppContext) {
11182        let view = multi_line(cx);
11183        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11184        let input = view.input;
11185
11186        setup_cursors(&mut cx, &input, "|1\n2\n3");
11187        cx.update(|window, cx| {
11188            input.update(cx, |state, cx| {
11189                state.replace_text_in_range(None, "a", window, cx);
11190                state.add_cursor_below(&AddCursorBelow, window, cx);
11191                state.replace_text_in_range(None, "b", window, cx);
11192            });
11193        });
11194        assert_cursors(&mut cx, &input, "ab|1\n2b|\n3");
11195
11196        // The keystroke after the cursor was added is its own undo entry.
11197        cx.update(|window, cx| {
11198            input.update(cx, |state, cx| {
11199                state.undo(&Undo, window, cx);
11200            });
11201        });
11202        assert_cursors(&mut cx, &input, "a|1\n2|\n3");
11203    }
11204
11205    #[gpui::test]
11206    fn test_multi_cursor_indent_is_one_undo(cx: &mut TestAppContext) {
11207        let view = multi_line(cx);
11208        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11209        let input = view.input;
11210
11211        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11212        cx.update(|window, cx| {
11213            input.update(cx, |state, cx| {
11214                state.indent_inline(&IndentInline, window, cx);
11215                assert_eq!(state.undo_manager.undo_count(), 1);
11216                state.undo(&Undo, window, cx);
11217            });
11218        });
11219        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11220    }
11221
11222    #[gpui::test]
11223    fn test_multi_cursor_indent_then_outdent_roundtrips(cx: &mut TestAppContext) {
11224        let view = multi_line(cx);
11225        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11226        let input = view.input;
11227
11228        // Cursors at line starts.
11229        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11230        cx.update(|window, cx| {
11231            input.update(cx, |state, cx| {
11232                state.indent(false, window, cx);
11233            });
11234        });
11235        assert_cursors(&mut cx, &input, "  |1\n  |2\n  |3");
11236
11237        cx.update(|window, cx| {
11238            input.update(cx, |state, cx| {
11239                state.outdent(false, window, cx);
11240            });
11241        });
11242        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11243    }
11244
11245    #[gpui::test]
11246    fn test_inline_outdent_only_removes_line_indentation(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        // A mid-line indent lands at the cursor, and the outdent does not
11252        // take it back: it only ever removes leading line indentation.
11253        setup_cursors(&mut cx, &input, "1|2\n1|2");
11254        cx.update(|window, cx| {
11255            input.update(cx, |state, cx| {
11256                state.indent(false, window, cx);
11257            });
11258        });
11259        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
11260
11261        cx.update(|window, cx| {
11262            input.update(cx, |state, cx| {
11263                state.outdent(false, window, cx);
11264            });
11265        });
11266        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
11267
11268        // A line with leading indentation loses that, wherever the cursor is.
11269        setup_cursors(&mut cx, &input, "  1|2\n  1|2");
11270        cx.update(|window, cx| {
11271            input.update(cx, |state, cx| {
11272                state.outdent(false, window, cx);
11273            });
11274        });
11275        assert_cursors(&mut cx, &input, "1|2\n1|2");
11276    }
11277
11278    #[gpui::test]
11279    fn test_readonly_multi_cursor_commands_leave_state_unchanged(cx: &mut TestAppContext) {
11280        let view = multi_line(cx);
11281        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11282        let input = view.input;
11283
11284        setup_cursors(&mut cx, &input, "a|b\nc|d");
11285        cx.update(|window, cx| {
11286            input.update(cx, |state, cx| {
11287                let before: Vec<_> = state.selections.iter().copied().collect();
11288                state.set_readonly(true, cx);
11289                state.backspace(&Backspace, window, cx);
11290                state.indent_inline(&IndentInline, window, cx);
11291
11292                assert_eq!(state.value(), "ab\ncd\n");
11293                assert_eq!(state.selections.iter().copied().collect::<Vec<_>>(), before);
11294            });
11295        });
11296    }
11297
11298    #[gpui::test]
11299    fn test_multi_cursor_edit_preserves_the_active_cursor(cx: &mut TestAppContext) {
11300        let view = multi_line(cx);
11301        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11302        let input = view.input;
11303
11304        setup_cursors(&mut cx, &input, "x|\n|y");
11305        cx.update(|window, cx| {
11306            input.update(cx, |state, cx| {
11307                let mut selections: Vec<_> = state.selections.iter().copied().collect();
11308                selections.swap(0, 1);
11309                state.selections.replace_all(selections);
11310                let active_id = state.active_selection().id;
11311                state.replace_text_in_range(None, "!", window, cx);
11312                assert_eq!(state.active_selection().id, active_id);
11313            });
11314        });
11315    }
11316
11317    #[gpui::test]
11318    fn test_block_indent_outdent_with_selection(cx: &mut TestAppContext) {
11319        let view = multi_line(cx);
11320        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11321        let input = view.input;
11322
11323        cx.update(|window, cx| {
11324            input.update(cx, |state, cx| {
11325                state.set_value("line1\nline2\nline3", window, cx);
11326                let id = state.selections.generate_id();
11327                state
11328                    .selections
11329                    .replace_all(vec![CursorSelection::new(id, 0, 17)]);
11330                cx.notify();
11331            });
11332        });
11333
11334        cx.update(|window, cx| {
11335            input.update(cx, |state, cx| {
11336                state.indent(true, window, cx);
11337            });
11338        });
11339        input.read_with(&cx, |state, _| {
11340            assert_eq!(state.text.to_string(), "  line1\n  line2\n  line3");
11341        });
11342
11343        cx.update(|window, cx| {
11344            input.update(cx, |state, cx| {
11345                state.outdent(true, window, cx);
11346            });
11347        });
11348        input.read_with(&cx, |state, _| {
11349            assert_eq!(state.text.to_string(), "line1\nline2\nline3");
11350        });
11351    }
11352
11353    #[gpui::test]
11354    fn test_multi_cursor_word_movement(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(
11360            &mut cx,
11361            &input,
11362            "on|e two three\none t|wo three\non|e two three",
11363        );
11364
11365        cx.update(|window, cx| {
11366            input.update(cx, |state, cx| {
11367                state.move_to_next_word(&MoveToNextWord, window, cx);
11368            });
11369        });
11370        assert_cursors(
11371            &mut cx,
11372            &input,
11373            "one| two three\none two| three\none| two three",
11374        );
11375
11376        cx.update(|window, cx| {
11377            input.update(cx, |state, cx| {
11378                state.move_to_previous_word(&MoveToPreviousWord, window, cx);
11379            });
11380        });
11381        assert_cursors(
11382            &mut cx,
11383            &input,
11384            "|one two three\none |two three\n|one two three",
11385        );
11386
11387        // Move to end/start of document collapses to a single cursor.
11388        cx.update(|window, cx| {
11389            input.update(cx, |state, cx| {
11390                state.move_to_end(&MoveToEnd, window, cx);
11391            });
11392        });
11393        input.read_with(&cx, |state, _| {
11394            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
11395            assert_eq!(cursors, vec![state.text.len()]);
11396        });
11397
11398        cx.update(|window, cx| {
11399            input.update(cx, |state, cx| {
11400                state.move_to_start(&MoveToStart, window, cx);
11401            });
11402        });
11403        input.read_with(&cx, |state, _| {
11404            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
11405            assert_eq!(cursors, vec![0]);
11406        });
11407    }
11408
11409    #[gpui::test]
11410    fn test_multi_cursor_selection_commands(cx: &mut TestAppContext) {
11411        let view = multi_line(cx);
11412        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11413        let input = view.input;
11414
11415        setup_cursors(
11416            &mut cx,
11417            &input,
11418            "on|e two three\none t|wo three\non|e two three",
11419        );
11420        cx.update(|window, cx| {
11421            input.update(cx, |state, cx| {
11422                state.select_to_start_of_line(&SelectToStartOfLine, window, cx);
11423            });
11424        });
11425        assert_cursors(
11426            &mut cx,
11427            &input,
11428            "|one two three\n|one two three\n|one two three",
11429        );
11430
11431        // Select to document start collapses to the active cursor only.
11432        setup_cursors(
11433            &mut cx,
11434            &input,
11435            "on|e two three\none t|wo three\non|e two three",
11436        );
11437        cx.update(|window, cx| {
11438            input.update(cx, |state, cx| {
11439                state.select_to_start(&SelectToStart, window, cx);
11440            });
11441        });
11442        input.read_with(&cx, |state, _| {
11443            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
11444            assert_eq!(cursors, vec![0]);
11445        });
11446    }
11447
11448    #[gpui::test]
11449    fn test_multi_cursor_replace_selection(cx: &mut TestAppContext) {
11450        let view = multi_line(cx);
11451        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11452        let input = view.input;
11453
11454        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
11455        cx.update(|window, cx| {
11456            input.update(cx, |state, cx| {
11457                state.select_right(&SelectRight, window, cx);
11458            });
11459        });
11460        input.read_with(&cx, |state, _| {
11461            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
11462            assert_eq!(ranges, vec![(0, 1), (2, 3), (4, 5)]);
11463        });
11464
11465        cx.update(|window, cx| {
11466            input.update(cx, |state, cx| {
11467                state.replace_text_in_range(None, "x", window, cx);
11468            });
11469        });
11470        assert_cursors(&mut cx, &input, "x|\nx|\nx|");
11471    }
11472
11473    #[gpui::test]
11474    fn test_multi_cursor_escape_collapses(cx: &mut TestAppContext) {
11475        let view = multi_line(cx);
11476        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11477        let input = view.input;
11478
11479        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
11480        cx.update(|window, cx| {
11481            input.update(cx, |state, cx| {
11482                state.escape(&Escape, window, cx);
11483            });
11484        });
11485        input.read_with(&cx, |state, _| {
11486            assert_eq!(state.selections.len(), 1);
11487        });
11488    }
11489
11490    #[gpui::test]
11491    fn test_build_columnar_selection(cx: &mut TestAppContext) {
11492        let view = multi_line(cx);
11493        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11494        let input = view.input;
11495
11496        setup_cursors(&mut cx, &input, "abcd\nabcd\nabcd");
11497        cx.update(|_, cx| {
11498            input.update(cx, |state, cx| {
11499                // From row 0 col 1 to row 2 col 3.
11500                state.build_columnar_selection(
11501                    ColumnarPoint::new(1, 0),
11502                    ColumnarPoint::new(13, 0),
11503                    cx,
11504                );
11505                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
11506                assert_eq!(ranges, vec![(1, 3), (6, 8), (11, 13)]);
11507            });
11508        });
11509    }
11510
11511    #[gpui::test]
11512    fn test_columnar_selection_keeps_width_over_short_rows(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        setup_cursors(&mut cx, &input, "abcdef\nab\nabcdef");
11518        cx.update(|_, cx| {
11519            input.update(cx, |state, cx| {
11520                // Ends on the short row, 3 columns past its end: the block still spans
11521                // columns 1..5, and only that row is clipped to what it has.
11522                state.build_columnar_selection(
11523                    ColumnarPoint::new(1, 0),
11524                    ColumnarPoint::new(9, 3),
11525                    cx,
11526                );
11527                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
11528                assert_eq!(ranges, vec![(1, 5), (8, 9)]);
11529            });
11530        });
11531    }
11532
11533    #[gpui::test]
11534    fn test_multi_cursor_paste_distributes_lines(cx: &mut TestAppContext) {
11535        let view = multi_line(cx);
11536        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11537        let input = view.input;
11538
11539        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11540        cx.update(|_, cx| {
11541            cx.write_to_clipboard(ClipboardItem::new_string("x\ny\nz".to_string()));
11542        });
11543        cx.update(|window, cx| {
11544            input.update(cx, |state, cx| {
11545                state.paste(&Paste, window, cx);
11546            });
11547        });
11548        // One clipboard line per cursor.
11549        assert_cursors(&mut cx, &input, "x|1\ny|2\nz|3");
11550    }
11551
11552    #[gpui::test]
11553    fn test_paste_without_text_leaves_the_selection_alone(cx: &mut TestAppContext) {
11554        use gpui::{ClipboardEntry, Image, ImageFormat};
11555
11556        let input_view = InputView::build(cx, |state| state);
11557        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
11558        let input = input_view.input;
11559
11560        cx.update(|window, cx| {
11561            input.update(cx, |state, cx| {
11562                state.set_value("hello world", window, cx);
11563                state.select_all(window, cx);
11564                cx.write_to_clipboard(ClipboardItem {
11565                    entries: vec![ClipboardEntry::Image(Image::from_bytes(
11566                        ImageFormat::Png,
11567                        vec![0x89, b'P', b'N', b'G'],
11568                    ))],
11569                });
11570
11571                // An image-only clipboard must not replace the selection
11572                // with nothing.
11573                state.paste(&Paste, window, cx);
11574                assert_eq!(state.value(), "hello world");
11575                assert_eq!(state.selected_range(), 0..11);
11576            });
11577        });
11578    }
11579
11580    #[gpui::test]
11581    fn test_paste_target_tracks_edits_and_selections(cx: &mut TestAppContext) {
11582        let view = multi_line(cx);
11583        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11584        let input = view.input;
11585
11586        setup_cursors(&mut cx, &input, "ab|c");
11587        let target = input.read_with(&cx, |state, _| state.paste_target());
11588        // Nothing happened: a paste asked for then still applies.
11589        assert_eq!(
11590            input.read_with(&cx, |state, _| state.paste_target()),
11591            target
11592        );
11593
11594        // Moving the caret changes where the paste would go.
11595        cx.update(|_, cx| {
11596            input.update(cx, |state, cx| {
11597                let id = state.selections.generate_id();
11598                state
11599                    .selections
11600                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
11601                cx.notify();
11602            });
11603        });
11604        let moved = input.read_with(&cx, |state, _| state.paste_target());
11605        assert_ne!(moved, target);
11606
11607        // Editing the text changes it too, even with the caret put back.
11608        cx.update(|window, cx| {
11609            input.update(cx, |state, cx| {
11610                state.replace_text_in_range_silent(None, "x", window, cx);
11611                let id = state.selections.generate_id();
11612                state
11613                    .selections
11614                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
11615            });
11616        });
11617        assert_ne!(input.read_with(&cx, |state, _| state.paste_target()), moved);
11618    }
11619
11620    #[gpui::test]
11621    fn test_ime_candidate_bounds_fallback_before_repaint(cx: &mut TestAppContext) {
11622        let input_view = InputView::build_textarea(cx, |state| {
11623            state.default_value("这是一段已经输入的中文文字")
11624        });
11625        let mut visual = VisualTestContext::from_window(input_view.window_handle.into(), cx);
11626        visual.update(|window, cx| window.draw(cx).clear(cx));
11627
11628        visual.update(|window, cx| {
11629            input_view.input.update(cx, |state, cx| {
11630                state.focus(window, cx);
11631                let len = state.text.len();
11632                state.set_cursor_to(len);
11633            });
11634        });
11635        visual.update(|window, cx| window.draw(cx).clear(cx));
11636
11637        visual.update(|window, cx| {
11638            input_view.input.update(cx, |state, cx| {
11639                let bounds = state.text_bounds().unwrap_or_default();
11640                let before = state.selected_text_range(false, window, cx).unwrap().range;
11641                let previous = state
11642                    .bounds_for_range(before.end..before.end, bounds, window, cx)
11643                    .unwrap();
11644
11645                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
11646
11647                let after = state.selected_text_range(false, window, cx).unwrap().range;
11648                let pending = state
11649                    .bounds_for_range(after.end..after.end, bounds, window, cx)
11650                    .unwrap();
11651
11652                assert!(
11653                    (pending.origin.x - previous.origin.x).abs() < px(40.0),
11654                    "pending origin.x {:?} must remain close to previous origin.x {:?}",
11655                    pending.origin.x,
11656                    previous.origin.x
11657                );
11658
11659                let range_bounds = state
11660                    .bounds_for_range(before.end..after.end, bounds, window, cx)
11661                    .unwrap();
11662                assert!(
11663                    (range_bounds.origin.x - previous.origin.x).abs() < px(40.0),
11664                    "range_bounds origin.x {:?} must remain close to previous origin.x {:?}",
11665                    range_bounds.origin.x,
11666                    previous.origin.x
11667                );
11668                assert!(range_bounds.size.width >= px(0.0));
11669            });
11670        });
11671    }
11672}
11673
11674/// Methods that only a single-line input offers.
11675impl InputBaseState<crate::input::InputMode> {
11676    /// Create a single-line text input state.
11677    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
11678        Self::new_in_mode(window, cx)
11679    }
11680
11681    /// Set a custom step function of the [`super::NumberInput`].
11682    ///
11683    /// The `f` receives the current value and the [`StepAction`], and returns
11684    /// the step to apply, so the step can vary with the value.
11685    ///
11686    /// # Example
11687    ///
11688    /// ```ignore
11689    /// // At the boundary 1.0 the step is 0.1 going down and 0.5 going up.
11690    /// InputState::new(window, cx).step_by(|value, action, _cx| match action {
11691    ///     StepAction::Increment => if value < 1.0 { 0.1 } else { 0.5 },
11692    ///     StepAction::Decrement => if value <= 1.0 { 0.1 } else { 0.5 },
11693    /// })
11694    /// ```
11695    pub fn step_by(mut self, f: impl Fn(f64, StepAction, &mut App) -> f64 + 'static) -> Self {
11696        self.number_step = Some(NumberStep::by_value(f));
11697        self
11698    }
11699
11700    /// Set with password masked state.
11701    pub fn masked(mut self, masked: bool) -> Self {
11702        self.masked = masked;
11703        self
11704    }
11705
11706    /// Set the password masked state of the input field.
11707    pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
11708        self.masked = masked;
11709        cx.notify();
11710    }
11711
11712    /// Set the regular expression pattern of the input field.
11713    pub fn pattern(mut self, pattern: regex::Regex) -> Self {
11714        self.pattern = Some(pattern);
11715        self
11716    }
11717
11718    /// Set the regular expression pattern of the input field with reference.
11719    pub fn set_pattern(
11720        &mut self,
11721        pattern: regex::Regex,
11722        _window: &mut Window,
11723        _cx: &mut Context<Self>,
11724    ) {
11725        self.pattern = Some(pattern);
11726    }
11727
11728    /// Set the validation function of the input field.
11729    pub fn validate(mut self, f: impl Fn(&str, &mut App) -> bool + 'static) -> Self {
11730        self.validate = Some(Box::new(f));
11731        self
11732    }
11733
11734    pub fn set_validator(
11735        &mut self,
11736        validate: impl Fn(&str, &mut App) -> bool + 'static,
11737        _cx: &mut Context<Self>,
11738    ) {
11739        self.validate = Some(Box::new(validate));
11740    }
11741
11742    /// Set the step value of the [`super::NumberInput`] for increment/decrement.
11743    ///
11744    /// If any of `step`, `min`, `max` is set, the [`super::NumberInput`] will
11745    /// update the value internally (step by `step`, default 1, clamp to the
11746    /// `min`/`max` range and emit [`InputEvent::Change`]) instead of emitting
11747    /// [`super::NumberInputEvent::Step`].
11748    ///
11749    /// See also [`Self::step_by`] to calculate the step value
11750    /// based on the current value.
11751    pub fn step(mut self, step: impl Into<NumberStep>) -> Self {
11752        self.number_step = Some(step.into());
11753        self
11754    }
11755
11756    /// Set the minimum value of the [`super::NumberInput`].
11757    ///
11758    /// The value will be clamped to the minimum value on stepping and on
11759    /// blur (only if the clamped value passes the `pattern`/`validate` check).
11760    /// See also [`Self::step`].
11761    pub fn min(mut self, min: f64) -> Self {
11762        self.number_min = Some(min);
11763        self
11764    }
11765
11766    /// Set the maximum value of the [`super::NumberInput`].
11767    ///
11768    /// The value will be clamped to the maximum value on stepping and on
11769    /// blur (only if the clamped value passes the `pattern`/`validate` check).
11770    /// See also [`Self::step`].
11771    pub fn max(mut self, max: f64) -> Self {
11772        self.number_max = Some(max);
11773        self
11774    }
11775
11776    /// Update the step value after construction, `None` to fall back to
11777    /// emitting [`super::NumberInputEvent::Step`] (if `min`, `max` are unset).
11778    ///
11779    /// See [`Self::step`] and [`Self::step_by`].
11780    pub fn set_step(
11781        &mut self,
11782        step: impl Into<Option<NumberStep>>,
11783        _: &mut Window,
11784        _: &mut Context<Self>,
11785    ) {
11786        self.number_step = step.into();
11787    }
11788
11789    /// Update the minimum value after construction. See [`Self::min`].
11790    pub fn set_min(&mut self, min: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
11791        self.number_min = min;
11792    }
11793
11794    /// Update the maximum value after construction. See [`Self::max`].
11795    pub fn set_max(&mut self, max: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
11796        self.number_max = max;
11797    }
11798
11799    /// Set true to show spinner at the input right.
11800    pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
11801        self.loading = loading;
11802        cx.notify();
11803    }
11804}
11805
11806/// Methods shared by the two multi-line modes, and reachable on neither a
11807/// single-line input nor anything else.
11808impl<M: crate::input::MultiLineMode> InputBaseState<M> {
11809    /// Set whether text taller than the input stretches past its top and
11810    /// bottom and springs back when precise trackpad or Magic Mouse scrolling
11811    /// or its momentum runs into an end. Ordinary line-wheel scrolling does
11812    /// not bounce. The default follows
11813    /// [`crate::ScrollBounce::enabled_by_default`]: on on iOS, Android and
11814    /// macOS, off elsewhere. Under reduced motion the text stops at its ends
11815    /// without stretching.
11816    pub fn scroll_bounce(mut self, scroll_bounce: bool) -> Self {
11817        self.scroll_bounce = scroll_bounce;
11818        self
11819    }
11820
11821    /// Update whether the text stretches past its ends, see
11822    /// [`InputBaseState::scroll_bounce`]. Turning it off drops a stretch in
11823    /// progress.
11824    pub fn set_scroll_bounce(&mut self, scroll_bounce: bool, cx: &mut Context<Self>) {
11825        if self.scroll_bounce != scroll_bounce {
11826            self.scroll_bounce = scroll_bounce;
11827            self.overscroll.reset();
11828            cx.notify();
11829        }
11830    }
11831
11832    /// Whether the built-in search panel and its shortcut are enabled. Off by
11833    /// default, on for the code editor.
11834    ///
11835    /// This only concerns the panel. An input that is not searchable still
11836    /// answers [`InputBaseState::set_search_query`] and the other search
11837    /// methods, and lets `Ctrl-F` / `Cmd-F` bubble up to its ancestors, so an
11838    /// application can put its own search UI on top of the same engine.
11839    pub fn searchable(mut self, searchable: bool) -> Self {
11840        self.searchable = searchable;
11841        self
11842    }
11843
11844    /// See [`InputBaseState::searchable`].
11845    pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
11846        self.searchable = searchable;
11847        cx.notify();
11848    }
11849
11850    /// Set the soft wrap mode, default is true.
11851    #[doc(hidden)]
11852    pub fn soft_wrap(mut self, wrap: bool) -> Self {
11853        self.soft_wrap = wrap;
11854        self
11855    }
11856
11857    /// Update the soft wrap mode, default is true.
11858    pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
11859        self.soft_wrap = wrap;
11860        if wrap {
11861            let wrap_width = self
11862                .last_layout
11863                .as_ref()
11864                .and_then(|b| b.wrap_width)
11865                .unwrap_or(self.input_bounds.size.width);
11866
11867            self.display_map.on_layout_changed(Some(wrap_width), cx);
11868
11869            // Reset scroll to left 0
11870            let mut offset = self.scroll_handle.offset();
11871            offset.x = px(0.);
11872            self.scroll_handle.set_offset(offset);
11873        } else {
11874            self.display_map.on_layout_changed(None, cx);
11875        }
11876        cx.notify();
11877    }
11878
11879    /// Set how soft-wrapped continuation lines are indented, default is [`WrappingIndent::Same`]
11880    #[doc(hidden)]
11881    pub fn wrapping_indent(mut self, wrapping_indent: WrappingIndent) -> Self {
11882        self.wrapping_indent = wrapping_indent;
11883        self
11884    }
11885
11886    /// Update how soft-wrapped continuation lines are indented.
11887    pub fn set_wrapping_indent(
11888        &mut self,
11889        wrapping_indent: WrappingIndent,
11890        _: &mut Window,
11891        cx: &mut Context<Self>,
11892    ) {
11893        self.wrapping_indent = wrapping_indent;
11894        self.display_map.set_wrapping_indent(wrapping_indent, cx);
11895        cx.notify();
11896    }
11897}
11898
11899/// Methods that only ordinary multi-line text offers.
11900impl InputBaseState<crate::input::TextareaMode> {
11901    /// Create a multi-line text state.
11902    ///
11903    /// Being multi-line is carried by the mode, not by the layout, so the
11904    /// default plain-text layout needs no adjustment here.
11905    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
11906        Self::new_in_mode(window, cx)
11907    }
11908
11909    pub fn set_auto_grow(&mut self, min_rows: usize, max_rows: usize, cx: &mut Context<Self>) {
11910        self.mode = LayoutMode::auto_grow(min_rows, max_rows.max(min_rows));
11911        self.mode.update_auto_grow(&self.display_map);
11912        cx.notify();
11913    }
11914
11915    /// Set the number of rows for the multi-line Textarea.
11916    ///
11917    /// This is only used when `multi_line` is set to true. The input is at least
11918    /// this many lines tall.
11919    ///
11920    /// default: 1
11921    #[doc(hidden)]
11922    pub fn rows(mut self, rows: usize) -> Self {
11923        match &mut self.mode {
11924            LayoutMode::PlainText { rows: r, .. } | LayoutMode::CodeEditor { rows: r, .. } => {
11925                *r = rows
11926            }
11927            LayoutMode::AutoGrow {
11928                max_rows: max_r,
11929                rows: r,
11930                ..
11931            } => {
11932                *r = rows;
11933                *max_r = rows;
11934            }
11935        }
11936        self
11937    }
11938
11939    pub fn set_rows(&mut self, rows: usize, cx: &mut Context<Self>) {
11940        match &mut self.mode {
11941            LayoutMode::PlainText { rows: value, .. }
11942            | LayoutMode::CodeEditor { rows: value, .. } => *value = rows,
11943            LayoutMode::AutoGrow {
11944                rows: value,
11945                max_rows,
11946                ..
11947            } => {
11948                *value = rows;
11949                *max_rows = rows;
11950            }
11951        }
11952        cx.notify();
11953    }
11954
11955    /// Grow with the content from `min_rows` through `max_rows`.
11956    pub fn auto_grow(mut self, min_rows: usize, max_rows: usize) -> Self {
11957        self.mode = LayoutMode::auto_grow(min_rows, max_rows);
11958        self
11959    }
11960}
11961
11962/// Methods that only a source-code editor offers.
11963impl InputBaseState<crate::input::EditorMode> {
11964    /// Create a source-code editor state.
11965    ///
11966    /// Default options: line numbers on, tab size 2 with soft tabs, indent
11967    /// guides on, multi-line, and search enabled. Set the language for syntax
11968    /// highlighting with [`Self::language`]; without one the text is shown
11969    /// unhighlighted.
11970    ///
11971    /// The editor aims at simple code editing or display, not at being a
11972    /// full-featured code editor. It offers syntax highlighting, auto indent,
11973    /// line numbers, and handles large text up to about 50K lines.
11974    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
11975        let mut state = Self::new_in_mode(window, cx);
11976        state.mode = LayoutMode::code_editor(super::EditorLanguage::new(cx));
11977        state.searchable = true;
11978        state
11979    }
11980
11981    /// Set the language to highlight, e.g. `"rust"`.
11982    ///
11983    /// See [`Self::set_highlighter`] to change it after construction.
11984    pub fn language(mut self, language: impl Into<SharedString>) -> Self {
11985        if let LayoutMode::CodeEditor {
11986            language: l,
11987            highlighter,
11988            ..
11989        } = &mut self.mode
11990        {
11991            l.set_name(language.into());
11992            *highlighter.borrow_mut() = None;
11993        }
11994        self
11995    }
11996
11997    /// The current language name, e.g. `"rust"`.
11998    pub fn language_name(&self) -> SharedString {
11999        match &self.mode {
12000            LayoutMode::CodeEditor { language, .. } => language.name(),
12001            _ => SharedString::default(),
12002        }
12003    }
12004
12005    /// Set enable/disable code folding.
12006    ///
12007    /// Default: true
12008    #[doc(hidden)]
12009    pub fn folding(mut self, folding: bool) -> Self {
12010        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
12011            *f = folding;
12012        }
12013        self
12014    }
12015
12016    /// Set code folding at runtime.
12017    ///
12018    /// When disabling, all existing folds are cleared.
12019    pub fn set_folding(&mut self, folding: bool, _: &mut Window, cx: &mut Context<Self>) {
12020        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
12021            *f = folding;
12022        }
12023        if !folding {
12024            self.display_map.clear_folds();
12025        }
12026        cx.notify();
12027    }
12028
12029    /// Unfold any folded ranges that hide the given position.
12030    ///
12031    /// Use this to reveal a position before acting on it (e.g. before
12032    /// [`Self::set_cursor_position`], which stops at a fold boundary),
12033    /// without touching folds elsewhere in the buffer. Fold candidates are
12034    /// kept, so the opened ranges can be folded again from the gutter.
12035    ///
12036    /// A fold keeps its own first and last line visible, so a position on
12037    /// either of them opens nothing. Nested folds all open, since opening
12038    /// only the outermost would leave the position hidden.
12039    ///
12040    /// Returns whether any fold was opened.
12041    pub fn unfold_at(&mut self, position: impl Into<Position>, cx: &mut Context<Self>) -> bool {
12042        let offset = self.text.position_to_offset(&position.into());
12043        let line = self.text.offset_to_point(offset).row;
12044        // A fold hides start_line + 1 ..= end_line - 1, so a line is hidden
12045        // exactly when some folded range strictly contains it.
12046        let covering: Vec<usize> = self
12047            .display_map
12048            .folded_ranges()
12049            .iter()
12050            .filter(|fold| line > fold.start_line && line < fold.end_line)
12051            .map(|fold| fold.start_line)
12052            .collect();
12053        if covering.is_empty() {
12054            return false;
12055        }
12056
12057        for start_line in covering {
12058            self.display_map.set_folded(start_line, false);
12059        }
12060        cx.notify();
12061        true
12062    }
12063
12064    /// Set enable/disable line number.
12065    #[doc(hidden)]
12066    pub fn line_number(mut self, line_number: bool) -> Self {
12067        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
12068            *l = line_number;
12069        }
12070        self
12071    }
12072
12073    /// Set line number.
12074    pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
12075        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
12076            if *l == line_number {
12077                return;
12078            }
12079            *l = line_number;
12080        }
12081        cx.notify();
12082    }
12083
12084    /// Set enable/disable automatic closing brackets and quotes.
12085    ///
12086    /// When enabled, typing an opener from [`LanguageConfig::auto_closing_pairs`](crate::input::language_config::LanguageConfig::auto_closing_pairs) inserts the
12087    /// matching closer and places the cursor inside. Typing a closer that is
12088    /// already present just moves past it. Default: true
12089    #[doc(hidden)]
12090    pub fn auto_close(mut self, auto_close: bool) -> Self {
12091        self.mode.set_auto_close(auto_close);
12092        self
12093    }
12094
12095    /// Set automatic closing brackets and quotes at runtime.
12096    pub fn set_auto_close(&mut self, auto_close: bool, _: &mut Window, cx: &mut Context<Self>) {
12097        self.mode.set_auto_close(auto_close);
12098        cx.notify();
12099    }
12100
12101    /// Set enable/disable smart indent on Enter.
12102    ///
12103    /// When enabled, Enter uses structural brackets and
12104    /// [`LanguageConfig::indentation_rules`](crate::input::language_config::LanguageConfig::indentation_rules) to choose indentation.
12105    /// Default: true
12106    #[doc(hidden)]
12107    pub fn smart_indent(mut self, smart_indent: bool) -> Self {
12108        self.mode.set_smart_indent(smart_indent);
12109        self
12110    }
12111
12112    /// Set smart indent on Enter at runtime.
12113    pub fn set_smart_indent(&mut self, smart_indent: bool, _: &mut Window, cx: &mut Context<Self>) {
12114        self.mode.set_smart_indent(smart_indent);
12115        cx.notify();
12116    }
12117}