Skip to main content

gpui_base/input/base/
state.rs

1//! A text input field that allows the user to enter text.
2//!
3//! Based on the `Input` example from the `gpui` crate.
4//! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs
5use gpui::TextAlign;
6use gpui::{
7    Action, App, AppContext, Bounds, ClipboardItem, Context, Edges, Entity, EntityInputHandler,
8    EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding,
9    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point,
10    Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription,
11    UTF16Selection, Window, actions, div, point, prelude::FluentBuilder as _, px,
12};
13use ropey::{Rope, RopeSlice};
14use serde::Deserialize;
15use std::borrow::Cow;
16use std::cell::Cell;
17use std::ops::Range;
18use std::rc::Rc;
19use sum_tree::Bias;
20use unicode_segmentation::*;
21
22use super::{
23    DiagnosticSet, DisplayMap, InputContextMenuCapabilities, InputEditorStyle,
24    InputHighlighterFactory, MASK_CHAR, MaskPattern, NativeMenu, NumberStep, WrappingIndent,
25    blink_cursor::BlinkCursor,
26    change::Change,
27    cursor::{CursorSelection, Selections},
28    element::{
29        EditorScrollbar, EditorScrollbarSnapshot, LongestLineKey, TextElement,
30        clamp_horizontal_scroll_offset,
31    },
32    kind::InputModeKind,
33    mask_pattern::normalize_number_input,
34    mode::LayoutMode,
35    undo_manager::{EditIntent, UndoManager},
36};
37use crate::actions::{SelectDown, SelectLeft, SelectRight, SelectUp};
38use crate::input::blink_cursor::CURSOR_WIDTH;
39use crate::input::movement::MoveDirection;
40use crate::input::{
41    InputExtras as _, Position, RopeExt as _, element::RIGHT_MARGIN, layout::LastLayout,
42};
43use crate::{AutoScroll, StepAction};
44
45/// Vertical clearance to retain when revealing a text position.
46pub(crate) enum ScrollPadding {
47    Minimal,
48    SurroundingLines,
49}
50
51#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
52#[action(namespace = input, no_json)]
53pub struct Enter {
54    /// Is confirm with secondary.
55    pub secondary: bool,
56    /// Whether the Shift modifier was held when Enter was pressed.
57    pub shift: bool,
58}
59
60impl Enter {
61    /// Returns true if `action` is a primary `Enter` action (`secondary: false`),
62    /// regardless of whether Shift was held.
63    pub fn is_primary(action: &dyn Action) -> bool {
64        action.partial_eq(&Enter {
65            secondary: false,
66            shift: false,
67        }) || action.partial_eq(&Enter {
68            secondary: false,
69            shift: true,
70        })
71    }
72}
73
74actions!(
75    input,
76    [
77        ActivateToken,
78        Backspace,
79        Delete,
80        DeleteToBeginningOfLine,
81        DeleteToEndOfLine,
82        DeleteToPreviousWordStart,
83        DeleteToNextWordEnd,
84        Indent,
85        Outdent,
86        IndentInline,
87        OutdentInline,
88        MoveUp,
89        MoveDown,
90        MoveLeft,
91        MoveRight,
92        MoveHome,
93        MoveEnd,
94        MovePageUp,
95        MovePageDown,
96        AddCursorAbove,
97        AddCursorBelow,
98        SelectAll,
99        SelectToStartOfLine,
100        SelectToEndOfLine,
101        SelectToStart,
102        SelectToEnd,
103        SelectToPreviousWordStart,
104        SelectToNextWordEnd,
105        ShowCharacterPalette,
106        Copy,
107        Cut,
108        Paste,
109        Undo,
110        Redo,
111        MoveToStartOfLine,
112        MoveToEndOfLine,
113        MoveToStart,
114        MoveToEnd,
115        MoveToPreviousWord,
116        MoveToNextWord,
117        Escape,
118        ToggleCodeActions,
119        Search,
120        Replace,
121        GoToDefinition,
122    ]
123);
124
125#[derive(Clone)]
126pub enum InputEvent {
127    Change,
128    PressEnter { secondary: bool, shift: bool },
129    Focus,
130    Blur,
131}
132
133pub(super) const CONTEXT: &str = "Input";
134
135pub(crate) fn init(cx: &mut App) {
136    cx.bind_keys([
137        KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
138        KeyBinding::new("shift-backspace", Backspace, Some(CONTEXT)),
139        #[cfg(target_os = "macos")]
140        KeyBinding::new("ctrl-backspace", Backspace, Some(CONTEXT)),
141        KeyBinding::new("delete", Delete, Some(CONTEXT)),
142        KeyBinding::new("shift-delete", Delete, Some(CONTEXT)),
143        #[cfg(target_os = "macos")]
144        KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
145        #[cfg(target_os = "macos")]
146        KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
147        #[cfg(target_os = "macos")]
148        KeyBinding::new("alt-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
149        #[cfg(not(target_os = "macos"))]
150        KeyBinding::new("ctrl-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
151        #[cfg(target_os = "macos")]
152        KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)),
153        #[cfg(not(target_os = "macos"))]
154        KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)),
155        KeyBinding::new(
156            "enter",
157            Enter {
158                secondary: false,
159                shift: false,
160            },
161            Some(CONTEXT),
162        ),
163        KeyBinding::new(
164            "shift-enter",
165            Enter {
166                secondary: false,
167                shift: true,
168            },
169            Some(CONTEXT),
170        ),
171        KeyBinding::new(
172            "secondary-enter",
173            Enter {
174                secondary: true,
175                shift: false,
176            },
177            Some(CONTEXT),
178        ),
179        KeyBinding::new("escape", Escape, Some(CONTEXT)),
180        KeyBinding::new("up", MoveUp, Some(CONTEXT)),
181        KeyBinding::new("down", MoveDown, Some(CONTEXT)),
182        KeyBinding::new("left", MoveLeft, Some(CONTEXT)),
183        KeyBinding::new("right", MoveRight, Some(CONTEXT)),
184        KeyBinding::new("pageup", MovePageUp, Some(CONTEXT)),
185        KeyBinding::new("pagedown", MovePageDown, Some(CONTEXT)),
186        KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
187        KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
188        #[cfg(target_os = "macos")]
189        KeyBinding::new("cmd-]", Indent, Some(CONTEXT)),
190        #[cfg(not(target_os = "macos"))]
191        KeyBinding::new("ctrl-]", Indent, Some(CONTEXT)),
192        #[cfg(target_os = "macos")]
193        KeyBinding::new("cmd-[", Outdent, Some(CONTEXT)),
194        #[cfg(not(target_os = "macos"))]
195        KeyBinding::new("ctrl-[", Outdent, Some(CONTEXT)),
196        KeyBinding::new("shift-left", SelectLeft, Some(CONTEXT)),
197        KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
198        KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
199        KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
200        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
201        KeyBinding::new("shift-alt-left", SelectLeft, Some(CONTEXT)),
202        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
203        KeyBinding::new("shift-alt-right", SelectRight, Some(CONTEXT)),
204        // Avoid Ctrl+Alt+arrows on Linux, where desktops may reserve them.
205        #[cfg(target_os = "macos")]
206        KeyBinding::new("cmd-alt-up", AddCursorAbove, Some(CONTEXT)),
207        #[cfg(target_os = "macos")]
208        KeyBinding::new("cmd-alt-down", AddCursorBelow, Some(CONTEXT)),
209        #[cfg(target_os = "windows")]
210        KeyBinding::new("ctrl-alt-up", AddCursorAbove, Some(CONTEXT)),
211        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
212        KeyBinding::new("shift-alt-up", AddCursorAbove, Some(CONTEXT)),
213        #[cfg(target_os = "windows")]
214        KeyBinding::new("ctrl-alt-down", AddCursorBelow, Some(CONTEXT)),
215        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
216        KeyBinding::new("shift-alt-down", AddCursorBelow, Some(CONTEXT)),
217        KeyBinding::new("home", MoveHome, Some(CONTEXT)),
218        KeyBinding::new("end", MoveEnd, Some(CONTEXT)),
219        #[cfg(not(target_os = "macos"))]
220        KeyBinding::new("ctrl-home", MoveToStart, Some(CONTEXT)),
221        #[cfg(not(target_os = "macos"))]
222        KeyBinding::new("ctrl-end", MoveToEnd, Some(CONTEXT)),
223        #[cfg(not(target_os = "macos"))]
224        KeyBinding::new("ctrl-shift-home", SelectToStart, Some(CONTEXT)),
225        #[cfg(not(target_os = "macos"))]
226        KeyBinding::new("ctrl-shift-end", SelectToEnd, Some(CONTEXT)),
227        KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
228        KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
229        #[cfg(target_os = "macos")]
230        KeyBinding::new("ctrl-shift-a", SelectToStartOfLine, Some(CONTEXT)),
231        #[cfg(target_os = "macos")]
232        KeyBinding::new("ctrl-shift-e", SelectToEndOfLine, Some(CONTEXT)),
233        #[cfg(target_os = "macos")]
234        KeyBinding::new("shift-cmd-left", SelectToStartOfLine, Some(CONTEXT)),
235        #[cfg(target_os = "macos")]
236        KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
237        #[cfg(any(target_os = "macos", target_os = "linux"))]
238        KeyBinding::new("alt-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
239        #[cfg(not(target_os = "macos"))]
240        KeyBinding::new("ctrl-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
241        #[cfg(any(target_os = "macos", target_os = "linux"))]
242        KeyBinding::new("alt-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
243        #[cfg(not(target_os = "macos"))]
244        KeyBinding::new("ctrl-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
245        #[cfg(target_os = "macos")]
246        KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
247        #[cfg(target_os = "macos")]
248        KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
249        #[cfg(not(target_os = "macos"))]
250        KeyBinding::new("ctrl-a", SelectAll, Some(CONTEXT)),
251        #[cfg(target_os = "macos")]
252        KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
253        #[cfg(not(target_os = "macos"))]
254        KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
255        #[cfg(target_os = "macos")]
256        KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
257        #[cfg(not(target_os = "macos"))]
258        KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
259        #[cfg(target_os = "macos")]
260        KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
261        #[cfg(not(target_os = "macos"))]
262        KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
263        #[cfg(target_os = "macos")]
264        KeyBinding::new("ctrl-a", MoveHome, Some(CONTEXT)),
265        #[cfg(target_os = "macos")]
266        KeyBinding::new("cmd-left", MoveHome, Some(CONTEXT)),
267        #[cfg(target_os = "macos")]
268        KeyBinding::new("ctrl-e", MoveEnd, Some(CONTEXT)),
269        #[cfg(target_os = "macos")]
270        KeyBinding::new("cmd-right", MoveEnd, Some(CONTEXT)),
271        #[cfg(target_os = "macos")]
272        KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
273        #[cfg(target_os = "macos")]
274        KeyBinding::new("cmd-shift-z", Redo, Some(CONTEXT)),
275        #[cfg(target_os = "macos")]
276        KeyBinding::new("cmd-up", MoveToStart, Some(CONTEXT)),
277        #[cfg(target_os = "macos")]
278        KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
279        #[cfg(target_os = "macos")]
280        KeyBinding::new("alt-left", MoveToPreviousWord, Some(CONTEXT)),
281        #[cfg(target_os = "macos")]
282        KeyBinding::new("alt-right", MoveToNextWord, Some(CONTEXT)),
283        #[cfg(not(target_os = "macos"))]
284        KeyBinding::new("ctrl-left", MoveToPreviousWord, Some(CONTEXT)),
285        #[cfg(not(target_os = "macos"))]
286        KeyBinding::new("ctrl-right", MoveToNextWord, Some(CONTEXT)),
287        #[cfg(target_os = "macos")]
288        KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
289        #[cfg(target_os = "macos")]
290        KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
291        #[cfg(not(target_os = "macos"))]
292        KeyBinding::new("ctrl-z", Undo, Some(CONTEXT)),
293        #[cfg(not(target_os = "macos"))]
294        KeyBinding::new("ctrl-y", Redo, Some(CONTEXT)),
295        #[cfg(target_os = "macos")]
296        KeyBinding::new("cmd-.", ToggleCodeActions, Some(CONTEXT)),
297        #[cfg(not(target_os = "macos"))]
298        KeyBinding::new("ctrl-.", ToggleCodeActions, Some(CONTEXT)),
299        #[cfg(target_os = "macos")]
300        KeyBinding::new("cmd-f", Search, Some(CONTEXT)),
301        #[cfg(not(target_os = "macos"))]
302        KeyBinding::new("ctrl-f", Search, Some(CONTEXT)),
303        #[cfg(target_os = "macos")]
304        KeyBinding::new("cmd-shift-f", Replace, Some(CONTEXT)),
305        #[cfg(not(target_os = "macos"))]
306        KeyBinding::new("ctrl-h", Replace, Some(CONTEXT)),
307    ]);
308}
309
310/// A mouse position resolved for a columnar (block) selection.
311///
312/// A position past the end of a short row has no glyph to land on, so `offset` clips to
313/// that row's end and `columns_past_line_end` keeps what the clip dropped. Together they
314/// say which column the pointer really reached, which is what a block spanning rows of
315/// different lengths has to be measured in.
316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317pub(super) struct ColumnarPoint {
318    offset: usize,
319    columns_past_line_end: usize,
320}
321
322impl ColumnarPoint {
323    pub(super) fn new(offset: usize, columns_past_line_end: usize) -> Self {
324        Self {
325            offset,
326            columns_past_line_end,
327        }
328    }
329}
330
331/// The document and selections a paste was asked to replace, taken when the
332/// `Paste` action ran so a clipboard read that resolves later can tell
333/// whether it still applies. See [`InputBaseState::paste_target`].
334#[derive(Debug, PartialEq)]
335struct PasteTarget {
336    document_revision: u64,
337    selections: Vec<CursorSelection>,
338}
339
340/// The shared text-editing engine behind [`crate::input::InputState`],
341/// [`crate::input::TextareaState`] and [`crate::input::EditorState`].
342///
343/// `M` is the mode marker: it carries no data and only decides which methods
344/// exist, so an ordinary input cannot reach the editor's language features.
345///
346/// The three states are type aliases of this one, which is why this name is
347/// public: an alias is only as usable as the type behind it, so hiding this
348/// would leave `InputState` unable to do anything. Prefer naming the aliases
349/// — write `InputState`, not `InputBaseState<InputMode>`.
350pub struct InputBaseState<M: InputModeKind> {
351    /// State only this mode needs. See [`InputModeKind::Extras`].
352    pub(crate) extras: M::Extras,
353    pub(super) focus_handle: FocusHandle,
354    pub(super) mode: LayoutMode,
355    pub(super) text: Rope,
356    pub(super) display_map: DisplayMap,
357    pub(super) undo_manager: UndoManager,
358    pub(super) inline_tokens: Option<Box<super::inline_tokens::InlineTokenStore>>,
359    pub(super) pending_token: Option<super::InlineToken>,
360    pub(super) replaying_history: bool,
361    pub(super) validated_token_edit: bool,
362    pub(super) document_revision: u64,
363    pub(super) token_presentation: super::InlineTokenPresentation,
364    pub(super) token_layout_cache: Option<Box<super::token_presentation::TokenLayoutCache>>,
365    /// Real per-frame bounds of the placed token elements, keyed by token
366    /// start offset; used for hover payloads and stale-hover reconciliation.
367    pub(super) token_bounds: std::collections::HashMap<usize, Bounds<Pixels>>,
368    /// The currently hovered token: its entered span, last measured bounds,
369    /// and UTF-16 range as of entry. Retained so hover exit can still be
370    /// delivered when the token is removed, replaced, scrolled out, or
371    /// disabled.
372    pub(super) hovered_token: Option<super::token_presentation::HoverSnapshot>,
373    pub(super) pending_token_hover_exits: Vec<super::token_presentation::HoverSnapshot>,
374    /// The start offset of a pressed token, with the document revision and
375    /// pointer position at the press.
376    pub(super) pressed_token: Option<(usize, u64, Point<Pixels>)>,
377    pub(super) search_session: super::SearchSession,
378    /// Advances every time search is explicitly invoked. See
379    /// [`InputBaseState::search_activation_revision`].
380    pub(super) search_activation_revision: u64,
381    pub(super) searchable: bool,
382    pub(super) replaceable: bool,
383    pub(super) soft_wrap: bool,
384    pub(super) wrapping_indent: WrappingIndent,
385    pub(super) scroll_beyond_last_line: Option<usize>,
386    pub(super) cursor_surrounding_lines: Option<usize>,
387    pub(super) blink_cursor: Entity<BlinkCursor>,
388    pub(super) loading: bool,
389    /// The cursors and selections.
390    ///
391    /// Always contains at least one selection where index 0 is the active cursor.
392    pub(super) selections: Selections,
393    /// Range for save the selected word, use to keep word range when drag move.
394    pub(super) selected_word_range: Option<CursorSelection>,
395    /// The marked range is the temporary insert text on IME typing.
396    pub(super) ime_marked_range: Option<CursorSelection>,
397    pub(super) last_layout: Option<LastLayout>,
398    pub(super) last_cursor: Option<usize>,
399    /// The input container bounds
400    pub(super) input_bounds: Bounds<Pixels>,
401    /// The text bounds
402    pub(super) last_bounds: Option<Bounds<Pixels>>,
403    pub(super) last_selected_range: Option<CursorSelection>,
404    pub(super) selecting: bool,
405    /// Anchor point of an in-progress columnar (block) selection.
406    pub(super) column_select_start: Option<ColumnarPoint>,
407    /// The selection a long press made, with its handles and edit menu.
408    pub(super) touch_selection: super::touch::TouchSelection,
409    pub(crate) disabled: bool,
410    pub(crate) readonly: bool,
411    pub(crate) text_align: TextAlign,
412    pub(super) masked: bool,
413    pub(super) clean_on_escape: bool,
414    pub(super) submit_on_enter: bool,
415    pub(super) show_whitespaces: bool,
416    /// This flag tells the renderer to prefer the end of the current visual line.
417    pub(crate) cursor_line_end_affinity: bool,
418    pub(super) pattern: Option<regex::Regex>,
419    pub(super) validate: Option<Box<dyn Fn(&str, &mut App) -> bool + 'static>>,
420    /// The step strategy for [`super::NumberInput`] to increment/decrement.
421    /// See [`Self::step`] and [`Self::step_by`].
422    pub(crate) number_step: Option<NumberStep>,
423    /// The minimum value for [`super::NumberInput`]. See [`Self::min`].
424    pub(crate) number_min: Option<f64>,
425    /// The maximum value for [`super::NumberInput`]. See [`Self::max`].
426    pub(crate) number_max: Option<f64>,
427    pub(crate) scroll_handle: ScrollHandle,
428    /// The deferred scroll offset to apply on next layout.
429    pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,
430    /// The size of the scrollable content.
431    pub(crate) scroll_size: gpui::Size<Pixels>,
432    pub(super) editor_scrollbar_snapshot: Cell<Option<EditorScrollbarSnapshot>>,
433    /// The unwrapped width of the longest line and what it was measured for.
434    pub(super) longest_line_width: Cell<Option<(LongestLineKey, Pixels)>>,
435    pub(super) editor_paddings: Edges<Pixels>,
436    /// The style this state paints with: what was projected onto it, with
437    /// every colour left unset resolved from the palette that is current. It
438    /// is rebuilt at the top of every render, which is what keeps it current
439    /// when the palette changes after the state was built.
440    pub(super) editor_style: InputEditorStyle,
441    /// What a consumer projected, kept verbatim so that resolution never
442    /// consumes its own output: resolving in place would fill the unset
443    /// colours once and then never see them as unset again, which is the same
444    /// freeze in a different place.
445    projected_editor_style: InputEditorStyle,
446
447    /// The mask pattern for formatting the input text
448    pub(crate) mask_pattern: MaskPattern,
449    /// Whether the `mask_pattern` was explicitly set (via [`Self::mask_pattern`]
450    /// or [`Self::set_mask_pattern`]), to let [`super::NumberInput`] only apply
451    /// its default mask when the user has not made an explicit choice.
452    pub(super) mask_pattern_set: bool,
453    pub(super) placeholder: SharedString,
454
455    /// Diagnostic currently requested by pointer hover; applications render it.
456    pub(super) diagnostic_popover: Option<Rc<crate::input::DiagnosticEntry>>,
457
458    context_menu_handler: Option<
459        Rc<dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App)>,
460    >,
461    pending_context_menu: Option<(Point<Pixels>, usize)>,
462
463    /// Whether the context menu that shows on right-click is enabled.
464    ///
465    pub(super) enable_context_menu: bool,
466
467    /// A flag to indicate if we are currently inserting a completion item.
468    pub(super) completion_inserting: bool,
469    pub(super) overlay_action_handler: Option<
470        Rc<
471            dyn Fn(
472                super::InputOverlayKind,
473                Box<dyn Action>,
474                &mut Window,
475                &mut Context<InputBaseState<M>>,
476            ) -> bool,
477        >,
478    >,
479
480    /// A flag to indicate if we have a pending update to the text.
481    ///
482    /// If true, will call some update (for example LSP, Syntax Highlight) before render.
483    _pending_update: bool,
484    /// A flag to indicate if we should ignore the next completion event.
485    pub(super) silent_replace_text: bool,
486    /// A flag to indicate if we should emit InputEvents.
487    pub(super) emit_events: bool,
488
489    _subscriptions: Vec<Subscription>,
490
491    pub(super) auto_scroll: AutoScroll,
492}
493
494/// Read-only styling data exposed to presentation facades.
495///
496/// The fields are private and read through the methods below, so that a new
497/// one can be added without breaking the facades.
498#[derive(Clone)]
499pub struct InputPresentation {
500    focus_handle: FocusHandle,
501    disabled: bool,
502    readonly: bool,
503    loading: bool,
504    masked: bool,
505    multi_line: bool,
506    code_editor: bool,
507    text_align: TextAlign,
508    placeholder: SharedString,
509    mask_placeholder: Option<String>,
510}
511
512impl InputPresentation {
513    pub fn focus_handle(&self) -> &FocusHandle {
514        &self.focus_handle
515    }
516
517    pub fn is_disabled(&self) -> bool {
518        self.disabled
519    }
520
521    pub fn is_readonly(&self) -> bool {
522        self.readonly
523    }
524
525    /// Returns true if the user is allowed to change the text.
526    ///
527    /// See also: [`InputBaseState::is_editable`].
528    pub fn is_editable(&self) -> bool {
529        !self.disabled && !self.readonly
530    }
531
532    pub fn is_loading(&self) -> bool {
533        self.loading
534    }
535
536    pub fn is_masked(&self) -> bool {
537        self.masked
538    }
539
540    pub fn is_multi_line(&self) -> bool {
541        self.multi_line
542    }
543
544    pub fn is_code_editor(&self) -> bool {
545        self.code_editor
546    }
547
548    pub fn text_align(&self) -> TextAlign {
549        self.text_align
550    }
551
552    pub fn placeholder(&self) -> &SharedString {
553        &self.placeholder
554    }
555
556    /// The placeholder derived from the mask pattern, e.g.: `(___) ___-____`.
557    pub fn mask_placeholder(&self) -> Option<&str> {
558        self.mask_placeholder.as_deref()
559    }
560}
561
562impl<M: InputModeKind> EventEmitter<InputEvent> for InputBaseState<M> {}
563
564impl<M: InputModeKind> InputBaseState<M> {
565    #[doc(hidden)]
566    pub fn cursor_layout(&self) -> Option<(Bounds<Pixels>, Pixels)> {
567        let layout = self.last_layout.as_ref()?;
568        Some((layout.cursor_bounds?, layout.line_height))
569    }
570
571    pub fn input_bounds(&self) -> Bounds<Pixels> {
572        self.input_bounds
573    }
574
575    pub fn text_bounds(&self) -> Option<Bounds<Pixels>> {
576        self.last_bounds
577    }
578
579    pub fn diagnostic_popover(&self) -> Option<Rc<crate::input::DiagnosticEntry>> {
580        self.diagnostic_popover.clone()
581    }
582
583    pub fn presentation(&self) -> InputPresentation {
584        InputPresentation {
585            focus_handle: self.focus_handle.clone(),
586            disabled: self.disabled,
587            readonly: self.readonly,
588            loading: self.loading,
589            masked: self.masked,
590            multi_line: self.is_multi_line(),
591            code_editor: self.is_code_editor(),
592            text_align: self.text_align,
593            placeholder: self.placeholder.clone(),
594            mask_placeholder: self.mask_pattern.placeholder(),
595        }
596    }
597
598    /// Whether this input spans more than one line.
599    ///
600    /// Answered by the mode marker, which is fixed when the state is built.
601    /// [`LayoutMode`] holds the row counts and growth policy, not the kind.
602    #[inline]
603    /// Whether this input paints scrollbars.
604    ///
605    /// Only a multi-line input can scroll: a single-line input keeps its
606    /// caret in view by moving its own offset, and never has a viewport a
607    /// user could drag. Adding the editor scrollbar to every input put a
608    /// thumb inside every text field, which is a control the field does not
609    /// have.
610    pub(crate) fn shows_scrollbar(&self) -> bool {
611        self.is_multi_line()
612    }
613
614    pub fn is_multi_line(&self) -> bool {
615        M::MULTI_LINE
616    }
617
618    /// Whether this input is a single-line text field. See [`Self::is_multi_line`].
619    #[inline]
620    pub fn is_single_line(&self) -> bool {
621        !M::MULTI_LINE
622    }
623
624    /// Whether this input is a source-code editor.
625    #[inline]
626    pub fn is_code_editor(&self) -> bool {
627        M::CODE_EDITOR
628    }
629
630    /// Whether the user is allowed to copy the selection out.
631    ///
632    /// A masked input keeps its value out of the clipboard.
633    pub fn is_copyable(&self) -> bool {
634        self.selections.iter().any(|sel| !sel.is_empty()) && !self.masked
635    }
636
637    pub fn context_menu_capabilities(&self) -> InputContextMenuCapabilities {
638        let (go_to_definition, code_actions) = self.extras.context_menu_capabilities();
639        InputContextMenuCapabilities::new()
640            .disabled(self.disabled)
641            .readonly(self.readonly)
642            .code_editor(self.is_code_editor())
643            .selection(!self.active_selection().is_empty())
644            .masked(self.masked)
645            .go_to_definition(go_to_definition)
646            .code_actions(code_actions)
647    }
648
649    pub fn set_text_align(&mut self, text_align: TextAlign, cx: &mut Context<Self>) {
650        if !self.is_single_line() || self.text_align == text_align {
651            return;
652        }
653
654        self.text_align = text_align;
655        cx.notify();
656    }
657
658    /// Flip the password mask.
659    ///
660    /// Setting the mask is a single-line method, but flipping it stays here:
661    /// the reveal button is rendered from the generic path, and it can only be
662    /// switched on through [`crate::input::InputState`] anyway.
663    pub fn toggle_masked(&mut self, _: &mut Window, cx: &mut Context<Self>) {
664        self.masked = !self.masked;
665        cx.notify();
666    }
667
668    pub fn on_context_menu(
669        &mut self,
670        handler: Rc<
671            dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App),
672        >,
673    ) {
674        self.context_menu_handler = Some(handler);
675    }
676
677    /// Build the engine. Each mode's own `new` sets its layout on top of this.
678    fn new_in_mode(window: &mut Window, cx: &mut Context<Self>) -> Self {
679        let focus_handle = cx.focus_handle().tab_stop(true);
680        let blink_cursor = cx.new(|_| BlinkCursor::new());
681        let undo_manager = UndoManager::new();
682
683        let _subscriptions = vec![
684            // Key bindings can consume events before on_key_down. Observe input
685            // before action dispatch so every keystroke resets the blink delay.
686            cx.intercept_keystrokes({
687                let focus_handle = focus_handle.clone();
688                let blink_cursor = blink_cursor.downgrade();
689                move |_, window, cx| {
690                    if focus_handle.is_focused(window) {
691                        _ = blink_cursor.update(cx, |cursor, cx| cursor.pause(cx));
692                    }
693                }
694            }),
695            // Observe the blink cursor to repaint the view when it changes.
696            cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
697            // Blink the cursor when the window is active, pause when it's not.
698            cx.observe_window_activation(window, |input, window, cx| {
699                if window.is_window_active() {
700                    let focus_handle = input.focus_handle.clone();
701                    if focus_handle.is_focused(window) {
702                        input.blink_cursor.update(cx, |blink_cursor, cx| {
703                            blink_cursor.start(cx);
704                        });
705                    }
706                }
707            }),
708            cx.on_focus(&focus_handle, window, Self::on_focus),
709            cx.on_blur(&focus_handle, window, Self::on_blur),
710        ];
711
712        let text_style = window.text_style();
713
714        Self {
715            extras: M::Extras::default(),
716            focus_handle: focus_handle.clone(),
717            text: "".into(),
718            display_map: DisplayMap::new(text_style.font(), window.rem_size(), None),
719            search_session: super::SearchSession::default(),
720            search_activation_revision: 0,
721            searchable: false,
722            replaceable: true,
723            soft_wrap: true,
724            wrapping_indent: WrappingIndent::default(),
725            scroll_beyond_last_line: None,
726            cursor_surrounding_lines: None,
727            blink_cursor,
728            undo_manager,
729            inline_tokens: None,
730            pending_token: None,
731            replaying_history: false,
732            validated_token_edit: false,
733            document_revision: 0,
734            token_presentation: Default::default(),
735            token_bounds: Default::default(),
736            hovered_token: None,
737            pending_token_hover_exits: Vec::new(),
738            token_layout_cache: None,
739            pressed_token: None,
740            selections: Selections::default(),
741            selected_word_range: None,
742            ime_marked_range: None,
743            input_bounds: Bounds::default(),
744            selecting: false,
745            touch_selection: Default::default(),
746            disabled: false,
747            readonly: false,
748            text_align: TextAlign::Left,
749            masked: false,
750            clean_on_escape: false,
751            submit_on_enter: false,
752            show_whitespaces: false,
753            loading: false,
754            pattern: None,
755            validate: None,
756            number_step: Some(NumberStep::Fixed(1.)),
757            number_min: None,
758            number_max: None,
759            mode: LayoutMode::default(),
760            last_layout: None,
761            last_bounds: None,
762            last_selected_range: None,
763            column_select_start: None,
764            last_cursor: None,
765            scroll_handle: ScrollHandle::new(),
766            scroll_size: gpui::size(px(0.), px(0.)),
767            editor_scrollbar_snapshot: Cell::new(None),
768            longest_line_width: Cell::new(None),
769            editor_paddings: Edges::default(),
770            deferred_scroll_offset: None,
771            placeholder: SharedString::default(),
772            mask_pattern: MaskPattern::default(),
773            mask_pattern_set: false,
774            editor_style: InputEditorStyle::default(),
775            projected_editor_style: InputEditorStyle::default(),
776            diagnostic_popover: None,
777            context_menu_handler: None,
778            pending_context_menu: None,
779            enable_context_menu: true,
780            completion_inserting: false,
781            overlay_action_handler: None,
782            silent_replace_text: false,
783            emit_events: true,
784            _subscriptions,
785            _pending_update: false,
786            cursor_line_end_affinity: false,
787            auto_scroll: AutoScroll::default(),
788        }
789    }
790
791    /// Sets whether the context menu that shows on right-click is enabled.
792    ///
793    /// The context menu is enabled by default. Disabling it turns off the
794    /// right-click menu entirely, including a custom menu set on the element.
795    /// A custom menu already replaces the built-in one, so keep this enabled
796    /// to show only your own items.
797    pub fn context_menu(mut self, enable: bool) -> Self {
798        self.enable_context_menu = enable;
799        self
800    }
801
802    pub fn set_context_menu_enabled(&mut self, enabled: bool) {
803        self.enable_context_menu = enabled;
804    }
805
806    /// Set whether search UI allows replacement, default is true.
807    #[doc(hidden)]
808    pub fn replaceable(mut self, allow: bool) -> Self {
809        self.replaceable = allow;
810        self
811    }
812
813    /// Set placeholder
814    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
815        self.placeholder = placeholder.into();
816        self
817    }
818
819    /// Set highlighter language for for [`LayoutMode::CodeEditor`] mode.
820    pub fn set_highlighter(
821        &mut self,
822        new_language: impl Into<SharedString>,
823        cx: &mut Context<Self>,
824    ) {
825        match &mut self.mode {
826            LayoutMode::CodeEditor {
827                language,
828                highlighter,
829                ..
830            } => {
831                language.set_name(new_language.into());
832                *highlighter.borrow_mut() = None;
833            }
834            _ => {}
835        }
836        self.refresh(cx);
837    }
838
839    fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
840        match &mut self.mode {
841            LayoutMode::CodeEditor { highlighter, .. } => {
842                *highlighter.borrow_mut() = None;
843            }
844            _ => {}
845        }
846        cx.notify();
847    }
848
849    /// Install the parser/highlighter adapter used by code-editor mode.
850    pub fn set_highlighter_factory(
851        &mut self,
852        factory: InputHighlighterFactory,
853        cx: &mut Context<Self>,
854    ) {
855        self.mode.set_highlighter_factory(factory);
856        self._pending_update = true;
857        self.token_layout_cache = None;
858        cx.notify();
859    }
860
861    /// Install a default adapter without replacing an application-provided one.
862    pub fn ensure_highlighter_factory(&mut self, factory: InputHighlighterFactory) {
863        self.mode.ensure_highlighter_factory(factory);
864    }
865
866    pub fn set_editor_style(&mut self, style: InputEditorStyle) {
867        self.editor_style = style.clone();
868        self.projected_editor_style = style;
869    }
870
871    /// Set presentation padding for multi-line text and its scrollbar layout.
872    #[doc(hidden)]
873    pub fn set_editor_paddings(&mut self, paddings: Edges<Pixels>) {
874        self.editor_paddings = paddings;
875    }
876
877    pub fn apply_highlighter_fold_candidates(
878        &mut self,
879        candidates: Vec<crate::input::FoldRange>,
880        cx: &mut Context<Self>,
881    ) {
882        if self.mode.is_folding() {
883            self.display_map.set_fold_candidates(candidates);
884        }
885        cx.notify();
886    }
887
888    #[inline]
889    pub fn diagnostics(&self) -> Option<&DiagnosticSet> {
890        self.mode.diagnostics()
891    }
892
893    #[inline]
894    pub fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
895        self.mode.diagnostics_mut()
896    }
897
898    /// Set placeholder
899    pub fn set_placeholder(
900        &mut self,
901        placeholder: impl Into<SharedString>,
902        _: &mut Window,
903        cx: &mut Context<Self>,
904    ) {
905        self.placeholder = placeholder.into();
906        cx.notify();
907    }
908
909    /// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.
910    ///
911    /// Returns:
912    ///
913    /// - The index of the line (zero-based) containing the offset.
914    /// - The index of the sub-line (zero-based) within the line containing the offset.
915    /// - The position of the offset.
916    pub(super) fn line_and_position_for_offset(
917        &self,
918        offset: usize,
919    ) -> (usize, usize, Option<Point<Pixels>>) {
920        let Some(last_layout) = &self.last_layout else {
921            return (0, 0, None);
922        };
923        let line_height = last_layout.line_height;
924
925        let mut y_offset = last_layout.visible_top;
926        for (vi, line) in last_layout.lines.iter().enumerate() {
927            let prev_lines_offset = last_layout.visible_line_byte_offsets[vi];
928            let local_offset = offset.saturating_sub(prev_lines_offset);
929            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
930                let sub_line_index = (pos.y / line_height) as usize;
931                let adjusted_pos = point(pos.x + last_layout.line_number_width, pos.y + y_offset);
932                return (vi, sub_line_index, Some(adjusted_pos));
933            }
934
935            y_offset += line.size(line_height).height;
936        }
937        (0, 0, None)
938    }
939
940    /// Set the value of the input field: plain text, or [`InputContent`] to
941    /// restore text together with its inline tokens. Editing history is
942    /// cleared and no [`InputEvent::Change`] is emitted.
943    ///
944    /// For single-line inputs the caret is placed at the end of the text while
945    /// the view is scrolled back to the start, so a long value shows its
946    /// beginning instead of its tail (matching HTML `<input>`). Multi-line
947    /// inputs reset the selection to `0..0`.
948    pub fn set_value(
949        &mut self,
950        value: impl Into<super::InputContent>,
951        window: &mut Window,
952        cx: &mut Context<Self>,
953    ) {
954        let content = value.into();
955        self.inline_tokens = None;
956        self.undo_manager.set_ignoring(true);
957        self.emit_events = false;
958        self.replace_text(content.text().clone(), window, cx);
959        self.install_tokens(content);
960        self.undo_manager.set_ignoring(false);
961        self.emit_events = true;
962
963        self.reset_selection();
964        self.reset_lsp_state();
965        self.reset_scroll_to_start();
966
967        self.undo_manager.clear();
968        cx.notify();
969    }
970
971    /// Replace the entire text content while preserving undo history.
972    ///
973    /// Unlike [`set_value`](Self::set_value), this method records the
974    /// replacement in the undo stack, allowing the user to undo/redo
975    /// the change. The selection is placed at the end of the new text
976    /// for single-line inputs, or cleared (0..0) for multi-line inputs.
977    ///
978    /// Use this when programmatically replacing the full text but the
979    /// user should still be able to undo the operation — e.g. formatting.
980    pub fn replace_all(
981        &mut self,
982        text: impl Into<SharedString>,
983        window: &mut Window,
984        cx: &mut Context<Self>,
985    ) {
986        self.replace_text(text, window, cx);
987        self.reset_selection();
988        self.reset_lsp_state();
989        self.reset_scroll_to_start();
990
991        cx.notify();
992    }
993
994    /// Perform `f` with the user-facing edit restrictions lifted.
995    ///
996    /// The `disabled` and `readonly` modes only reject the changes made by the
997    /// user, the programmatic APIs must always be able to update the text.
998    pub(super) fn with_edits_allowed(&mut self, f: impl FnOnce(&mut Self)) {
999        let (was_disabled, was_readonly) = (self.disabled, self.readonly);
1000        (self.disabled, self.readonly) = (false, false);
1001        f(self);
1002        (self.disabled, self.readonly) = (was_disabled, was_readonly);
1003    }
1004
1005    /// Insert text at the current cursor position.
1006    ///
1007    /// And the cursor will be moved to the end of inserted text.
1008    pub fn insert(
1009        &mut self,
1010        text: impl Into<SharedString>,
1011        window: &mut Window,
1012        cx: &mut Context<Self>,
1013    ) {
1014        let text: SharedString = text.into();
1015        self.with_edits_allowed(|this| {
1016            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1017            let range_utf16 = this.range_to_utf16(&(this.cursor()..this.cursor()));
1018            this.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
1019            let end = this.active_selection().end;
1020            this.set_cursor_to(end);
1021        });
1022    }
1023
1024    /// Replace text at the current cursor position.
1025    ///
1026    /// And the cursor will be moved to the end of replaced text.
1027    pub fn replace(
1028        &mut self,
1029        text: impl Into<SharedString>,
1030        window: &mut Window,
1031        cx: &mut Context<Self>,
1032    ) {
1033        let text: SharedString = text.into();
1034        self.with_edits_allowed(|this| {
1035            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1036            this.replace_text_in_range_silent(None, &text, window, cx);
1037            let end = this.active_selection().end;
1038            this.set_cursor_to(end);
1039        });
1040    }
1041
1042    fn replace_text(
1043        &mut self,
1044        text: impl Into<SharedString>,
1045        window: &mut Window,
1046        cx: &mut Context<Self>,
1047    ) {
1048        let text: SharedString = text.into();
1049        self.with_edits_allowed(|this| {
1050            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1051            let range = 0..this.text.chars().map(|c| c.len_utf16()).sum();
1052            this.replace_text_in_range_silent(Some(range), &text, window, cx);
1053            this.reset_highlighter(cx);
1054        });
1055    }
1056
1057    fn reset_selection(&mut self) {
1058        self.selections.remove_all_but_active();
1059
1060        // For single-line inputs the caret is placed at the end of the text
1061        // (matching HTML `<input>`); multi-line inputs reset the selection to
1062        // `0..0`.
1063        if self.is_single_line() {
1064            let end = self.text.len();
1065            self.set_cursor_to(end);
1066        } else {
1067            self.active_selection_mut().clear();
1068        }
1069    }
1070
1071    fn reset_lsp_state(&mut self) {
1072        if self.is_code_editor() {
1073            self._pending_update = true;
1074            M::reset_language_features(self);
1075        }
1076    }
1077
1078    fn reset_scroll_to_start(&mut self) {
1079        // Move scroll to the start. For single-line the caret is at the end, so
1080        // override the cursor-follow scroll for the next painted frame to keep
1081        // the start visible; the deferred offset is consumed during that paint.
1082        self.scroll_handle.set_offset(point(px(0.), px(0.)));
1083        if self.is_single_line() {
1084            self.deferred_scroll_offset = Some(point(px(0.), px(0.)));
1085        }
1086    }
1087
1088    /// Set with disabled mode.
1089    ///
1090    /// See also: [`Self::set_disabled`].
1091    #[allow(unused)]
1092    pub(crate) fn disabled(mut self, disabled: bool) -> Self {
1093        self.disabled = disabled;
1094        self
1095    }
1096
1097    pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
1098        if self.disabled == disabled {
1099            return;
1100        }
1101
1102        self.disabled = disabled;
1103        if disabled {
1104            M::hide_context_menu(self, cx);
1105            M::clear_inline_completion(self, cx);
1106        }
1107        cx.notify();
1108    }
1109
1110    /// Set with read-only mode.
1111    ///
1112    /// Unlike [`Self::disabled`], a read-only input keeps the normal appearance,
1113    /// focus, cursor, selection and copy behavior, it only rejects any change
1114    /// of the text made by the user.
1115    ///
1116    /// See also: [`Self::set_readonly`].
1117    #[allow(unused)]
1118    pub(crate) fn readonly(mut self, readonly: bool) -> Self {
1119        self.readonly = readonly;
1120        self
1121    }
1122
1123    pub fn set_readonly(&mut self, readonly: bool, cx: &mut Context<Self>) {
1124        if self.readonly == readonly {
1125            return;
1126        }
1127
1128        self.readonly = readonly;
1129        if readonly {
1130            M::hide_context_menu(self, cx);
1131            M::clear_inline_completion(self, cx);
1132            self.search_session.replace_mode = false;
1133        }
1134        cx.notify();
1135    }
1136
1137    /// Returns true if the user is allowed to change the text.
1138    ///
1139    /// This is false when the input is `disabled` or `readonly`, the programmatic
1140    /// APIs (e.g.: [`Self::set_value`], [`Self::insert`]) are not limited by this.
1141    pub fn is_editable(&self) -> bool {
1142        !self.disabled && !self.readonly
1143    }
1144
1145    /// Set true to clear the input by pressing Escape key.
1146    pub fn clean_on_escape(mut self) -> Self {
1147        self.clean_on_escape = true;
1148        self
1149    }
1150
1151    pub fn set_clean_on_escape(&mut self, clean: bool) {
1152        self.clean_on_escape = clean;
1153    }
1154
1155    /// Set true to treat `Enter` as a submit action in multi-line mode,
1156    /// while `Shift+Enter` inserts a newline.
1157    ///
1158    /// Default is `false` (both `Enter` and `Shift+Enter` insert a newline).
1159    #[doc(hidden)]
1160    pub fn submit_on_enter(mut self, submit: bool) -> Self {
1161        self.submit_on_enter = submit;
1162        self
1163    }
1164
1165    pub fn set_submit_on_enter(&mut self, submit: bool, cx: &mut Context<Self>) {
1166        self.submit_on_enter = submit;
1167        cx.notify();
1168    }
1169
1170    /// Set whether to show whitespace characters.
1171    #[doc(hidden)]
1172    pub fn show_whitespaces(mut self, show: bool) -> Self {
1173        self.show_whitespaces = show;
1174        self
1175    }
1176
1177    /// Update whether to show whitespace characters.
1178    pub fn set_show_whitespaces(&mut self, show: bool, _: &mut Window, cx: &mut Context<Self>) {
1179        self.show_whitespaces = show;
1180        cx.notify();
1181    }
1182
1183    /// Empty rows reserved below the last line of content ("scroll
1184    /// beyond last line"), code-editor mode only. Mirrors VSCode's
1185    /// `editor.scrollBeyondLastLine` / Zed's `scroll_beyond_last_line`.
1186    ///
1187    /// - `None` (default): half the viewport, floored at
1188    ///   [`BOTTOM_MARGIN_ROWS`] line-heights.
1189    /// - `Some(0)`: no trailing space; the cursor sits flush with the
1190    ///   last row at scroll-max.
1191    /// - `Some(n)`: exactly `n` rows.
1192    pub fn scroll_beyond_last_line(mut self, rows: Option<usize>) -> Self {
1193        self.scroll_beyond_last_line = rows;
1194        self
1195    }
1196
1197    /// Update [`Self::scroll_beyond_last_line`] after construction.
1198    pub fn set_scroll_beyond_last_line(
1199        &mut self,
1200        rows: Option<usize>,
1201        _: &mut Window,
1202        cx: &mut Context<Self>,
1203    ) {
1204        if self.scroll_beyond_last_line == rows {
1205            return;
1206        }
1207        self.scroll_beyond_last_line = rows;
1208        cx.notify();
1209    }
1210
1211    /// Minimum number of lines the cursor is kept clear of the viewport's
1212    /// top/bottom edge before auto-scroll engages. Mirrors VSCode's
1213    /// `editor.cursorSurroundingLines` / Zed's `vertical_scroll_margin`.
1214    /// Orthogonal to [`Self::scroll_beyond_last_line`], which sizes the
1215    /// empty region; this controls the cursor's resting distance from the
1216    /// edge.
1217    ///
1218    /// - `None` (default): [`BOTTOM_MARGIN_ROWS`] lines, falling back to
1219    ///   one line on small viewports.
1220    /// - `Some(n)`: exactly `n` lines, clamped to half the viewport.
1221    pub fn cursor_surrounding_lines(mut self, lines: Option<usize>) -> Self {
1222        self.cursor_surrounding_lines = lines;
1223        self
1224    }
1225
1226    /// Update [`Self::cursor_surrounding_lines`] after construction.
1227    pub fn set_cursor_surrounding_lines(
1228        &mut self,
1229        lines: Option<usize>,
1230        _: &mut Window,
1231        cx: &mut Context<Self>,
1232    ) {
1233        if self.cursor_surrounding_lines == lines {
1234            return;
1235        }
1236        self.cursor_surrounding_lines = lines;
1237        cx.notify();
1238    }
1239
1240    /// Set the default value of the input field.
1241    pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {
1242        let text: SharedString = value.into();
1243        self.text = Rope::from(self.normalize_input(&text).as_ref());
1244        if let Some(diagnostics) = self.mode.diagnostics_mut() {
1245            diagnostics.reset(&self.text)
1246        }
1247        // Note: We can't call display_map.set_text here because it needs cx.
1248        // The text will be set during prepare_if_need in element.rs
1249        self._pending_update = true;
1250        self
1251    }
1252
1253    /// Return the value of the input field as an owned string.
1254    ///
1255    /// The string is materialized on each call. See [`Self::text`] for the
1256    /// [`Rope`] the state owns, which is borrowed and costs nothing to read.
1257    pub fn value(&self) -> SharedString {
1258        SharedString::new(self.text.to_string())
1259    }
1260
1261    /// Return the portion of the value within the input field that
1262    /// is selected by the user, as an owned string.
1263    ///
1264    /// The string is materialized on each call. See [`Self::selected_text`]
1265    /// for the same selection borrowed out of the [`Rope`] the state owns.
1266    pub fn selected_value(&self) -> SharedString {
1267        SharedString::new(self.selected_text().to_string())
1268    }
1269
1270    /// Return the value without mask.
1271    pub fn unmask_value(&self) -> SharedString {
1272        self.mask_pattern.unmask(&self.text.to_string()).into()
1273    }
1274
1275    /// Kept so existing render paths keep compiling.
1276    ///
1277    /// Configuration used to be collected by a facade and applied here; the
1278    /// state now configures itself, so this does nothing and can be deleted at
1279    /// the call site.
1280    #[doc(hidden)]
1281    pub fn prepare(&mut self, _: &mut Window, _: &mut Context<Self>) {}
1282
1283    /// Return the text [`Rope`] of the input field.
1284    ///
1285    /// Borrowed from the state, so reading even a large document copies
1286    /// nothing. See [`Self::value`] when an owned string is wanted.
1287    pub fn text(&self) -> &Rope {
1288        &self.text
1289    }
1290
1291    /// Return the (0-based) [`Position`] of the cursor.
1292    pub fn cursor_position(&self) -> Position {
1293        let offset = self.cursor();
1294        self.text.offset_to_position(offset)
1295    }
1296
1297    /// Set (0-based) [`Position`] of the cursor.
1298    ///
1299    /// This will move the cursor to the specified line and column, and update the selection range.
1300    pub fn set_cursor_position(
1301        &mut self,
1302        position: impl Into<Position>,
1303        window: &mut Window,
1304        cx: &mut Context<Self>,
1305    ) {
1306        let position: Position = position.into();
1307        let offset = self.text.position_to_offset(&position);
1308
1309        self.move_to(offset, None, cx);
1310        self.update_preferred_column();
1311        self.focus(window, cx);
1312    }
1313
1314    /// Focus the input field.
1315    pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1316        self.focus_handle.focus(window, cx);
1317        self.blink_cursor.update(cx, |cursor, cx| {
1318            cursor.start(cx);
1319        });
1320    }
1321
1322    /// Refresh the input, so the next render re-runs syntax highlighting and
1323    /// the LSP providers, not just a redraw.
1324    ///
1325    /// Assigning the `lsp` providers (or other render-affecting state) at
1326    /// runtime does not take effect until the text next changes. Call this
1327    /// afterwards to force the refresh on the next render.
1328    ///
1329    /// ```ignore
1330    /// input.update(cx, |state, cx| {
1331    ///     state.extras.lsp.hover_provider = Some(provider);
1332    ///     state.refresh(cx);
1333    /// });
1334    /// ```
1335    pub fn refresh(&mut self, cx: &mut Context<Self>) {
1336        self._pending_update = true;
1337        cx.notify();
1338    }
1339
1340    pub(super) fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
1341        self.undo_manager.break_transaction_coalescing();
1342        self.select_all_cursors_to(|s, sel| s.previous_boundary(sel.cursor_offset()), cx);
1343    }
1344
1345    pub(super) fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
1346        self.select_all_cursors_to(|s, sel| s.next_boundary(sel.cursor_offset()), cx);
1347    }
1348
1349    pub(super) fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
1350        if self.is_single_line() {
1351            return;
1352        }
1353        self.select_all_cursors_to_with_affinity(
1354            |s, sel| {
1355                s.vertical_selection_target(
1356                    sel.cursor_offset(),
1357                    sel.column_anchor,
1358                    s.line_end_affinity_for(sel),
1359                    -1,
1360                )
1361            },
1362            true,
1363            cx,
1364        );
1365    }
1366
1367    pub(super) fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
1368        if self.is_single_line() {
1369            return;
1370        }
1371        self.select_all_cursors_to_with_affinity(
1372            |s, sel| {
1373                s.vertical_selection_target(
1374                    sel.cursor_offset(),
1375                    sel.column_anchor,
1376                    s.line_end_affinity_for(sel),
1377                    1,
1378                )
1379            },
1380            true,
1381            cx,
1382        );
1383    }
1384
1385    pub(super) fn on_action_select_all(
1386        &mut self,
1387        _: &SelectAll,
1388        window: &mut Window,
1389        cx: &mut Context<Self>,
1390    ) {
1391        self.select_all(window, cx);
1392    }
1393
1394    pub(super) fn select_to_start(
1395        &mut self,
1396        _: &SelectToStart,
1397        _: &mut Window,
1398        cx: &mut Context<Self>,
1399    ) {
1400        self.undo_manager.break_transaction_coalescing();
1401        self.select_all_cursors_to(|_, _| 0, cx);
1402    }
1403
1404    pub(super) fn select_to_end(
1405        &mut self,
1406        _: &SelectToEnd,
1407        _: &mut Window,
1408        cx: &mut Context<Self>,
1409    ) {
1410        self.undo_manager.break_transaction_coalescing();
1411        let end = self.text.len();
1412        self.select_all_cursors_to(move |_, _| end, cx);
1413    }
1414
1415    pub(super) fn select_to_start_of_line(
1416        &mut self,
1417        _: &SelectToStartOfLine,
1418        _: &mut Window,
1419        cx: &mut Context<Self>,
1420    ) {
1421        self.undo_manager.break_transaction_coalescing();
1422        self.select_all_cursors_to(
1423            |s, sel| s.start_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1424            cx,
1425        );
1426    }
1427
1428    pub(super) fn select_to_end_of_line(
1429        &mut self,
1430        _: &SelectToEndOfLine,
1431        _: &mut Window,
1432        cx: &mut Context<Self>,
1433    ) {
1434        self.undo_manager.break_transaction_coalescing();
1435        self.select_all_cursors_to_with_affinity(
1436            |s, sel| {
1437                (
1438                    s.end_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1439                    true,
1440                )
1441            },
1442            false,
1443            cx,
1444        );
1445    }
1446
1447    pub(super) fn select_to_previous_word(
1448        &mut self,
1449        _: &SelectToPreviousWordStart,
1450        _: &mut Window,
1451        cx: &mut Context<Self>,
1452    ) {
1453        self.undo_manager.break_transaction_coalescing();
1454        self.select_all_cursors_to(
1455            |s, sel| s.previous_start_of_word_at(sel.cursor_offset()),
1456            cx,
1457        );
1458    }
1459
1460    pub(super) fn select_to_next_word(
1461        &mut self,
1462        _: &SelectToNextWordEnd,
1463        _: &mut Window,
1464        cx: &mut Context<Self>,
1465    ) {
1466        self.undo_manager.break_transaction_coalescing();
1467        self.select_all_cursors_to(|s, sel| s.next_end_of_word_at(sel.cursor_offset()), cx);
1468    }
1469
1470    /// Return the start offset of the previous word.
1471    /// Return the previous start offset of the word before `offset`.
1472    pub(super) fn previous_start_of_word_at(&self, offset: usize) -> usize {
1473        if self.masked {
1474            // The mask replaces every character, so the displayed text has no
1475            // word boundaries to move or delete by. Collapse the word to the
1476            // whole text.
1477            return 0;
1478        }
1479
1480        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1481        // FIXME: Avoid to_string
1482        let left_part = self.text.slice(0..offset).to_string();
1483
1484        let target = UnicodeSegmentation::split_word_bound_indices(left_part.as_str())
1485            .rfind(|(_, s)| !s.trim_start().is_empty())
1486            .map(|(i, _)| i)
1487            .unwrap_or(0);
1488        self.token_boundary(target, Bias::Left)
1489    }
1490
1491    /// Return the next end offset of the word after `offset`.
1492    pub(super) fn next_end_of_word_at(&self, offset: usize) -> usize {
1493        if self.masked {
1494            // See `previous_start_of_word_at`.
1495            return self.text.len();
1496        }
1497
1498        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1499        let right_part = self.text.slice(offset..self.text.len()).to_string();
1500
1501        let target = UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
1502            .find(|(_, s)| !s.trim_start().is_empty())
1503            .map(|(i, s)| offset + i + s.len())
1504            .unwrap_or(self.text.len());
1505        self.token_boundary(target, Bias::Right)
1506    }
1507
1508    /// Get start of line byte offset for the given `offset`.
1509    ///
1510    /// When soft wrap is active, first press goes to visual line start,
1511    /// second press (already at visual start) goes to logical line start.
1512    pub(super) fn start_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1513        if self.is_single_line() {
1514            return 0;
1515        }
1516
1517        let row = self.text.offset_to_point(offset).row;
1518        let logical_start = self.text.line_start_offset(row);
1519
1520        if self.soft_wrap && self.is_code_editor() {
1521            let wrap_point = self
1522                .display_map
1523                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1524            if let Some(line) = self.display_map.line(row)
1525                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1526            {
1527                let visual_start = logical_start + range.start;
1528                if offset != visual_start {
1529                    return visual_start;
1530                }
1531            }
1532        }
1533
1534        logical_start
1535    }
1536
1537    /// Get end of line byte offset for the given `offset`.
1538    ///
1539    /// When soft wrap is active, first press goes to visual line end,
1540    /// second press (already at visual end) goes to logical line end.
1541    pub(super) fn end_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1542        if self.is_single_line() {
1543            return self.text.len();
1544        }
1545
1546        let row = self.text.offset_to_point(offset).row;
1547        let logical_start = self.text.line_start_offset(row);
1548        let logical_end = self.text.line_end_offset(row);
1549
1550        if self.soft_wrap && self.is_code_editor() {
1551            // Use the row the caret is drawn on: at a wrap boundary the raw offset would name
1552            // the next row, and a second End press would keep walking down instead of falling
1553            // through to the logical line end.
1554            let wrap_point = self
1555                .display_map
1556                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1557            if let Some(line) = self.display_map.line(row)
1558                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1559            {
1560                let visual_end = logical_start + range.end;
1561                if offset != visual_end {
1562                    return visual_end;
1563                }
1564            }
1565        }
1566
1567        logical_end
1568    }
1569
1570    /// Get indent string of next line.
1571    ///
1572    /// To get current and next line indent, to return more depth one.
1573    pub(super) fn indent_of_next_line(&mut self) -> String {
1574        self.indent_of_next_line_at(self.cursor())
1575    }
1576
1577    /// Get indent string of the next line, relative to the given `offset`.
1578    pub(super) fn indent_of_next_line_at(&mut self, offset: usize) -> String {
1579        if self.is_single_line() {
1580            return "".into();
1581        }
1582
1583        let mut current_indent = String::new();
1584        let mut next_indent = String::new();
1585        let line_end_affinity = self.line_end_affinity_at(offset);
1586        let current_line_start_pos = self.start_of_line_at(offset, line_end_affinity);
1587        let next_line_start_pos = self.end_of_line_at(offset, line_end_affinity);
1588        for c in self.text.slice(current_line_start_pos..).chars() {
1589            if !c.is_whitespace() {
1590                break;
1591            }
1592            if c == '\n' || c == '\r' {
1593                break;
1594            }
1595            current_indent.push(c);
1596        }
1597
1598        for c in self.text.slice(next_line_start_pos..).chars() {
1599            if !c.is_whitespace() {
1600                break;
1601            }
1602            if c == '\n' || c == '\r' {
1603                break;
1604            }
1605            next_indent.push(c);
1606        }
1607
1608        if next_indent.len() > current_indent.len() {
1609            return next_indent;
1610        }
1611
1612        // Smart indent: one extra level after lines ending with a trigger.
1613        // Skipped inside strings and comments: the trigger is literal text.
1614        if let Some(rules) = self.mode.language_config() {
1615            if self.mode.is_smart_indent()
1616                && M::editing_syntax_context(self, offset) == crate::input::SyntaxContext::Code
1617            {
1618                let line_before: String = self
1619                    .text
1620                    .slice(current_line_start_pos..offset)
1621                    .chars()
1622                    .collect();
1623                if rules.opens_indent(line_before.trim_end()) {
1624                    let tab = self.mode.tab_size();
1625                    if tab.hard_tabs {
1626                        current_indent.push('\t');
1627                    } else {
1628                        for _ in 0..tab.tab_size {
1629                            current_indent.push(' ');
1630                        }
1631                    }
1632                } else {
1633                    let line_after = self.text.slice(offset..next_line_start_pos).to_string();
1634                    if rules.closes_indent(&line_after) {
1635                        let tab = self.mode.tab_size();
1636                        if current_indent.ends_with('\t') {
1637                            current_indent.pop();
1638                        } else {
1639                            for _ in 0..tab.tab_size {
1640                                if !current_indent.ends_with(' ') {
1641                                    break;
1642                                }
1643                                current_indent.pop();
1644                            }
1645                        }
1646                    }
1647                }
1648            }
1649        }
1650
1651        current_indent
1652    }
1653
1654    /// Delete every selection as one batch. Collapsed cursors are first
1655    /// expanded to a deletion range by `collapsed_target` and non-empty
1656    /// selections delete their own range.
1657    ///
1658    /// `collapsed_intent` is the intent to record when every cursor is
1659    /// collapsed, which is what makes a run of single-character deletes undo
1660    /// as one gesture. Deleting a real selection is always atomic.
1661    fn delete_selections(
1662        &mut self,
1663        silent: bool,
1664        collapsed_intent: EditIntent,
1665        mut collapsed_target: impl FnMut(&mut Self, usize) -> Range<usize>,
1666        window: &mut Window,
1667        cx: &mut Context<Self>,
1668    ) {
1669        if !self.is_editable() {
1670            return;
1671        }
1672        let cursors: Vec<CursorSelection> = self.selections.iter().copied().collect();
1673        let intent = if cursors.iter().all(|sel| sel.is_empty()) {
1674            collapsed_intent
1675        } else {
1676            EditIntent::Atomic
1677        };
1678        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(cursors.len());
1679        for sel in &cursors {
1680            let range = if sel.is_empty() {
1681                collapsed_target(self, sel.cursor_offset())
1682            } else {
1683                sel.start..sel.end
1684            };
1685            let (start, end) = (range.start.min(range.end), range.start.max(range.end));
1686            let mut selection = *sel;
1687            selection.start = start;
1688            selection.end = end;
1689            new_selections.push(selection);
1690        }
1691        // Capture the user's selections before expanding or merging deletion
1692        // ranges. The edit ranges cannot reconstruct their original carets.
1693        self.undo_manager.begin_transaction_with(intent);
1694        self.undo_manager
1695            .record_selections(cursors.clone(), cursors.clone());
1696        self.selections.replace_all(new_selections);
1697        self.undo_manager.set_pending_intent(intent);
1698
1699        let was_silent = self.silent_replace_text;
1700        self.silent_replace_text = silent;
1701        self.replace_text_in_range(None, "", window, cx);
1702        self.silent_replace_text = was_silent;
1703        self.undo_manager
1704            .record_selections(cursors, self.selections.iter().copied().collect());
1705        self.undo_manager.commit_transaction();
1706        self.pause_blink_cursor(cx);
1707    }
1708
1709    /// Decide pairing against the pre-edit text, before an opening quote can
1710    /// change its own syntax context. Ordinary characters never query syntax.
1711    fn auto_close_target(&self, range: &Range<usize>, text: &str) -> Option<(usize, SharedString)> {
1712        if self.silent_replace_text
1713            || self.ime_marked_range.is_some()
1714            || !self.selections.is_single()
1715            || !self.active_selection().is_empty()
1716            || !range.is_empty()
1717            || range.start != self.cursor()
1718            || text.chars().count() != 1
1719            || !self.mode.is_auto_close()
1720        {
1721            return None;
1722        }
1723        let rules = self.mode.language_config()?;
1724        if self
1725            .text
1726            .chars_at(range.start)
1727            .next()
1728            .is_some_and(|c| !c.is_whitespace() && !rules.auto_close_before.contains(c))
1729        {
1730            return None;
1731        }
1732        for (open, close, not_in) in rules.closing_pairs() {
1733            let Some(prefix) = open.strip_suffix(text) else {
1734                continue;
1735            };
1736            if !self.text_before_matches(range.start, prefix) {
1737                continue;
1738            }
1739            let start = range.start - prefix.len();
1740            if open == close
1741                && (self.is_escaped_at(start)
1742                    || self
1743                        .text
1744                        .chars_at(start)
1745                        .reversed()
1746                        .next()
1747                        .is_some_and(|c| c.is_alphanumeric() || c == '_'))
1748            {
1749                continue;
1750            }
1751            if not_in.contains(&M::editing_syntax_context(self, start)) {
1752                continue;
1753            }
1754            return Some((open.len(), close.into()));
1755        }
1756        None
1757    }
1758
1759    fn text_before_matches(&self, offset: usize, text: &str) -> bool {
1760        offset >= text.len()
1761            && self.text.is_char_boundary(offset - text.len())
1762            && self.text.slice(offset - text.len()..offset) == text
1763    }
1764
1765    fn text_after_matches(&self, offset: usize, text: &str) -> bool {
1766        offset + text.len() <= self.text.len()
1767            && self.text.is_char_boundary(offset + text.len())
1768            && self.text.slice(offset..offset + text.len()) == text
1769    }
1770
1771    fn is_escaped_at(&self, offset: usize) -> bool {
1772        self.text
1773            .chars_at(offset)
1774            .reversed()
1775            .take_while(|c| *c == '\\')
1776            .count()
1777            % 2
1778            == 1
1779    }
1780
1781    /// Cursor target when a typed closer should skip over an existing one.
1782    ///
1783    /// Returns `Some(offset)` when `new_text` is a single closer from the
1784    /// active [`LanguageConfig`](crate::input::language_config::LanguageConfig) that already follows the collapsed cursor and is
1785    /// not escaped. The caller then moves the cursor without editing text or
1786    /// history. `None` means insert normally.
1787    fn skip_over_target(&self, new_text: &str) -> Option<usize> {
1788        if !self.mode.is_auto_close() || new_text.chars().count() != 1 {
1789            return None;
1790        }
1791        let rules = self.mode.language_config()?;
1792        let cursor = self.cursor();
1793        for (open, close, not_in) in rules.closing_pairs() {
1794            for (index, _) in close.char_indices() {
1795                if !close[index..].starts_with(new_text)
1796                    || !self.text_before_matches(cursor, &close[..index])
1797                    || !self.text_after_matches(cursor, &close[index..])
1798                {
1799                    continue;
1800                }
1801                let context = M::editing_syntax_context(self, cursor);
1802                let generated =
1803                    self.mode
1804                        .auto_closed_pairs()
1805                        .contains_closer(cursor, index, close.len());
1806                if !generated
1807                    && not_in.contains(&context)
1808                    && !(context == crate::input::SyntaxContext::String && open == close)
1809                {
1810                    continue;
1811                }
1812                if self.is_escaped_at(cursor - index) {
1813                    continue;
1814                }
1815                return Some(cursor + new_text.len());
1816            }
1817        }
1818        None
1819    }
1820
1821    pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
1822        // Nothing to delete at the start of the text. Propagate so an ancestor
1823        // (e.g. a command palette navigating back a level) can act on it.
1824        // This is harmless when nothing upstream is bound to backspace. With multiple
1825        // cursors the others can still delete, so only the lone-cursor case
1826        // propagates.
1827        if self.selections.is_single() && self.active_selection().is_empty() && self.cursor() == 0 {
1828            cx.propagate();
1829            return;
1830        }
1831
1832        // Pair deletion follows automatic closing rules, independently of
1833        // structural brackets used by Enter.
1834        if self.mode.is_auto_close()
1835            && self.selections.is_single()
1836            && self.active_selection().is_empty()
1837        {
1838            let off = self.cursor();
1839            let deletion = self.mode.language_config().and_then(|rules| {
1840                rules.closing_pairs().find_map(|(open, close, not_in)| {
1841                    if !self.text_before_matches(off, open) || !self.text_after_matches(off, close)
1842                    {
1843                        return None;
1844                    }
1845                    let start = off - open.len();
1846                    let generated = self
1847                        .mode
1848                        .auto_closed_pairs()
1849                        .contains(start..off, off..off + close.len());
1850                    if self.is_escaped_at(start)
1851                        || (!generated && not_in.contains(&M::editing_syntax_context(self, start)))
1852                    {
1853                        return None;
1854                    }
1855                    Some(start..off + close.len())
1856                })
1857            });
1858            if let Some(range) = deletion {
1859                let utf16 = self.range_to_utf16(&range);
1860                self.replace_text_in_range_silent(Some(utf16), "", window, cx);
1861                return;
1862            }
1863        }
1864
1865        self.delete_selections(
1866            false,
1867            EditIntent::Backspace,
1868            |s, offset| s.previous_boundary(offset)..offset,
1869            window,
1870            cx,
1871        );
1872    }
1873
1874    pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
1875        self.delete_selections(
1876            false,
1877            EditIntent::DeleteForward,
1878            |s, offset| offset..s.next_boundary(offset),
1879            window,
1880            cx,
1881        );
1882    }
1883
1884    pub(super) fn delete_to_beginning_of_line(
1885        &mut self,
1886        _: &DeleteToBeginningOfLine,
1887        window: &mut Window,
1888        cx: &mut Context<Self>,
1889    ) {
1890        self.delete_selections(
1891            true,
1892            EditIntent::Atomic,
1893            |s, offset| {
1894                let mut start = s.start_of_line_at(offset, s.line_end_affinity_at(offset));
1895                if start == offset {
1896                    start = start.saturating_sub(1);
1897                }
1898                start..offset
1899            },
1900            window,
1901            cx,
1902        );
1903    }
1904
1905    pub(super) fn delete_to_end_of_line(
1906        &mut self,
1907        _: &DeleteToEndOfLine,
1908        window: &mut Window,
1909        cx: &mut Context<Self>,
1910    ) {
1911        self.delete_selections(
1912            true,
1913            EditIntent::Atomic,
1914            |s, offset| {
1915                let mut end = s.end_of_line_at(offset, s.line_end_affinity_at(offset));
1916                if end == offset {
1917                    end = (end + 1).clamp(0, s.text.len());
1918                }
1919                offset..end
1920            },
1921            window,
1922            cx,
1923        );
1924    }
1925
1926    pub(super) fn delete_previous_word(
1927        &mut self,
1928        _: &DeleteToPreviousWordStart,
1929        window: &mut Window,
1930        cx: &mut Context<Self>,
1931    ) {
1932        self.delete_selections(
1933            true,
1934            EditIntent::Atomic,
1935            |s, offset| s.previous_start_of_word_at(offset)..offset,
1936            window,
1937            cx,
1938        );
1939    }
1940
1941    pub(super) fn delete_next_word(
1942        &mut self,
1943        _: &DeleteToNextWordEnd,
1944        window: &mut Window,
1945        cx: &mut Context<Self>,
1946    ) {
1947        self.delete_selections(
1948            true,
1949            EditIntent::Atomic,
1950            |s, offset| offset..s.next_end_of_word_at(offset),
1951            window,
1952            cx,
1953        );
1954    }
1955
1956    pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {
1957        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
1958            return;
1959        }
1960
1961        // Clear inline completion on enter (user chose not to accept it)
1962        if M::has_inline_completion(self) {
1963            M::clear_inline_completion(self, cx);
1964        }
1965
1966        // In multi-line mode with `submit_on_enter` enabled, a plain `Enter`
1967        // (without Shift) is treated as submit: propagate the action and emit
1968        // PressEnter without inserting a newline. `Shift+Enter` still inserts
1969        // a newline.
1970        let insert_newline = self.is_multi_line() && (!self.submit_on_enter || action.shift);
1971
1972        if insert_newline {
1973            if !self.selections.is_single() {
1974                // Insert a newline (with per-line indent) at every cursor.
1975                self.selections.merge_overlapping();
1976                let selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
1977                let mut edits: Vec<(Range<usize>, String)> = Vec::with_capacity(selections.len());
1978                for sel in &selections {
1979                    let indent = if self.is_code_editor() {
1980                        self.indent_of_next_line_at(sel.cursor_offset())
1981                    } else {
1982                        String::new()
1983                    };
1984                    edits.push((sel.start..sel.end, format!("\n{}", indent)));
1985                }
1986                edits.sort_by_key(|(range, _)| range.start);
1987                self.replace_text_in_ranges(&edits, window, cx);
1988                self.pause_blink_cursor(cx);
1989            } else {
1990                // Bracket split: Enter between `{|}` produces `{\n  \n}`
1991                // with the cursor on the middle line. Controlled by smart_indent,
1992                // independently of automatic closing.
1993                // Skipped inside strings and comments: splitting there would
1994                // restructure literal text.
1995                let mut split = false;
1996                if self.is_code_editor() && self.active_selection().is_empty() {
1997                    if let Some(rules) = self.mode.language_config() {
1998                        if self.mode.is_smart_indent()
1999                            && M::editing_syntax_context(self, self.cursor())
2000                                == crate::input::SyntaxContext::Code
2001                        {
2002                            let off = self.cursor();
2003                            let is_pair = rules.brackets.iter().any(|p| {
2004                                !p.open.is_empty()
2005                                    && !p.close.is_empty()
2006                                    && p.open != p.close
2007                                    && self.text_before_matches(off, p.open.as_ref())
2008                                    && self.text_after_matches(off, p.close.as_ref())
2009                            });
2010                            if is_pair {
2011                                // Base indent: leading whitespace only.
2012                                let line_end_affinity = self.line_end_affinity_at(off);
2013                                let line_start = self.start_of_line_at(off, line_end_affinity);
2014                                let mut indent = String::new();
2015                                for c in self.text.slice(line_start..).chars() {
2016                                    if !c.is_whitespace() || c == '\n' || c == '\r' {
2017                                        break;
2018                                    }
2019                                    indent.push(c);
2020                                }
2021                                // Extra level only with smart indent enabled.
2022                                let mut inner = indent.clone();
2023                                if self.mode.is_smart_indent() {
2024                                    let tab = self.mode.tab_size();
2025                                    if tab.hard_tabs {
2026                                        inner.push('\t');
2027                                    } else {
2028                                        for _ in 0..tab.tab_size {
2029                                            inner.push(' ');
2030                                        }
2031                                    }
2032                                }
2033                                let new_line_text = format!("\n{inner}\n{indent}");
2034                                self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2035                                self.set_cursor_to(off + 1 + inner.len());
2036                                self.update_preferred_column();
2037                                let cursors = self.selections.iter().copied().collect::<Vec<_>>();
2038                                self.undo_manager
2039                                    .record_selections(cursors.clone(), cursors);
2040                                self.pause_blink_cursor(cx);
2041                                split = true;
2042                            }
2043                        }
2044                    }
2045                }
2046                if !split {
2047                    // Get current line indent
2048                    let indent = if self.is_code_editor() {
2049                        self.indent_of_next_line()
2050                    } else {
2051                        "".to_string()
2052                    };
2053
2054                    // Add newline and indent
2055                    let new_line_text = format!("\n{}", indent);
2056                    self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2057                    self.pause_blink_cursor(cx);
2058                }
2059            }
2060        } else {
2061            // Single line input or submit-on-enter: just emit the event
2062            // (e.g.: in a dialog to confirm, or a chat textarea to send).
2063            self.undo_manager.break_transaction_coalescing();
2064            cx.propagate();
2065        }
2066
2067        cx.emit(InputEvent::PressEnter {
2068            secondary: action.secondary,
2069            shift: action.shift,
2070        });
2071    }
2072
2073    pub fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2074        self.replace_text("", window, cx);
2075        self.set_selection(0, 0);
2076        self.scroll_to(0, None, cx);
2077    }
2078
2079    pub(super) fn escape(&mut self, action: &Escape, window: &mut Window, cx: &mut Context<Self>) {
2080        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2081            return;
2082        }
2083
2084        // Escape also dismisses a request whose popup has not arrived yet.
2085        M::hide_context_menu(self, cx);
2086
2087        // Collapse extra cursors back to the active one first.
2088        if !self.selections.is_single() {
2089            self.undo_manager.break_transaction_coalescing();
2090            self.selections.remove_all_but_active();
2091            cx.notify();
2092            return;
2093        }
2094
2095        // Clear inline completion on escape
2096        if M::has_inline_completion(self) {
2097            M::clear_inline_completion(self, cx);
2098            return; // Consume the escape, don't propagate
2099        }
2100        M::clear_inline_completion(self, cx);
2101
2102        // The handles and the edit menu are the topmost surface to dismiss.
2103        if self.touch_selection().is_some() {
2104            self.dismiss_touch_selection(cx);
2105            return;
2106        }
2107
2108        if self.ime_marked_range.is_some() {
2109            self.unmark_text(window, cx);
2110        }
2111
2112        if self.clean_on_escape {
2113            return self.clean(window, cx);
2114        }
2115
2116        cx.propagate();
2117    }
2118
2119    /// Show the right-click context menu as a native OS menu.
2120    pub(crate) fn handle_right_click_menu(
2121        &mut self,
2122        position: Point<Pixels>,
2123        offset: usize,
2124        window: &mut Window,
2125        cx: &mut Context<Self>,
2126    ) {
2127        if self.disabled {
2128            return;
2129        }
2130        if crate::GlobalState::is_in_deferred_context(cx) {
2131            return;
2132        }
2133
2134        if !self.active_selection().contains(offset) {
2135            self.move_to(offset, None, cx);
2136        }
2137
2138        if self.is_code_editor() {
2139            M::on_hover_definition(self, offset, window, cx);
2140        }
2141
2142        if let Some(handler) = self.context_menu_handler.clone() {
2143            let capabilities = self.context_menu_capabilities();
2144            cx.defer_in(window, move |_, window, cx| {
2145                handler(NativeMenu::new(), capabilities, position, window, cx);
2146            });
2147        }
2148    }
2149
2150    pub(super) fn add_cursor_above(
2151        &mut self,
2152        _: &AddCursorAbove,
2153        _: &mut Window,
2154        cx: &mut Context<Self>,
2155    ) {
2156        self.add_cursor_vertical(-1, cx);
2157    }
2158
2159    pub(super) fn add_cursor_below(
2160        &mut self,
2161        _: &AddCursorBelow,
2162        _: &mut Window,
2163        cx: &mut Context<Self>,
2164    ) {
2165        self.add_cursor_vertical(1, cx);
2166    }
2167
2168    /// Add a new cursor one display line above (`move_lines < 0`) or below each
2169    /// existing cursor, preserving the column. Cursors that would not move (at
2170    /// the first/last display row) or that would duplicate an existing cursor
2171    /// are skipped.
2172    fn add_cursor_vertical(&mut self, move_lines: isize, cx: &mut Context<Self>) {
2173        if !self.is_multi_line() {
2174            return;
2175        }
2176
2177        self.pause_blink_cursor(cx);
2178        // Changing the cursor set ends the editing gesture that came before it.
2179        self.undo_manager.break_transaction_coalescing();
2180
2181        let sources: Vec<(usize, Option<(Pixels, usize)>, bool)> = self
2182            .selections
2183            .iter()
2184            .map(|sel| {
2185                (
2186                    sel.cursor_offset(),
2187                    sel.column_anchor,
2188                    self.line_end_affinity_for(sel),
2189                )
2190            })
2191            .collect();
2192        let mut offsets: std::collections::HashSet<usize> =
2193            sources.iter().map(|(offset, _, _)| *offset).collect();
2194
2195        let mut newest: Option<usize> = None;
2196        for (offset, anchor, line_end_affinity) in sources {
2197            let anchor = anchor.or_else(|| self.preferred_column_for(offset));
2198            let (target, _) = self.vertical_target(offset, anchor, line_end_affinity, move_lines);
2199            if target == offset || offsets.contains(&target) {
2200                continue;
2201            }
2202            offsets.insert(target);
2203            let id = self.selections.generate_id();
2204            let mut cursor = CursorSelection::new(id, target, target);
2205            cursor.column_anchor = anchor;
2206            self.selections.add(cursor);
2207            newest = Some(target);
2208        }
2209
2210        if let Some(newest) = newest {
2211            self.scroll_to(newest, None, cx);
2212        }
2213        cx.notify();
2214    }
2215
2216    /// Add an additional collapsed cursor at `offset`.
2217    ///
2218    /// Rejected when `offset` lands inside an existing selection or exactly on
2219    /// an existing cursor.
2220    pub(super) fn add_cursor_at(&mut self, offset: usize, cx: &mut Context<Self>) {
2221        if !self.is_multi_line() {
2222            return;
2223        }
2224
2225        for sel in self.selections.iter() {
2226            if sel.contains(offset) {
2227                return;
2228            }
2229            if sel.is_collapsed() && sel.cursor_offset() == offset {
2230                return;
2231            }
2232        }
2233
2234        self.undo_manager.break_transaction_coalescing();
2235        let id = self.selections.generate_id();
2236        let mut selection = CursorSelection::new(id, offset, offset);
2237        selection.column_anchor = self.preferred_column_for(offset);
2238        self.selections.add(selection);
2239        cx.notify();
2240    }
2241
2242    /// Build a columnar (block) selection spanning the rows between the two
2243    /// points, one selection per display row at the same column span.
2244    ///
2245    /// The span keeps the columns the pointer sat past the end of a short row, so a row
2246    /// that runs out of text does not narrow the block for the rows that do reach that
2247    /// far. Each row is then clipped to its own end, selecting as much of the span as it
2248    /// actually has.
2249    pub(super) fn build_columnar_selection(
2250        &mut self,
2251        start: ColumnarPoint,
2252        end: ColumnarPoint,
2253        cx: &mut Context<Self>,
2254    ) {
2255        if !self.is_multi_line() {
2256            return;
2257        }
2258
2259        self.undo_manager.break_transaction_coalescing();
2260
2261        let (start_row, start_col) = self.columnar_row_column(start);
2262        let (end_row, end_col) = self.columnar_row_column(end);
2263        let (start_row, end_row) = (start_row.min(end_row), start_row.max(end_row));
2264        let (start_col, end_col) = (start_col.min(end_col), start_col.max(end_col));
2265
2266        let mut new_selections = Vec::with_capacity(end_row - start_row + 1);
2267        for row in start_row..=end_row {
2268            let sel_start = self
2269                .display_map
2270                .display_row_column_to_offset(row, start_col);
2271            let sel_end = self.display_map.display_row_column_to_offset(row, end_col);
2272            let id = self.selections.generate_id();
2273            let sel_start = self.text.clip_offset(sel_start, Bias::Left);
2274            let sel_end = self.text.clip_offset(sel_end, Bias::Left);
2275            new_selections.push(CursorSelection::new(id, sel_start, sel_end));
2276        }
2277
2278        self.selections.replace_all(new_selections);
2279        cx.notify();
2280    }
2281
2282    /// Resolve a columnar point to the display row it is on and the column it reaches,
2283    /// counting the columns beyond the row's end so a short row keeps the full span.
2284    fn columnar_row_column(&self, point: ColumnarPoint) -> (usize, usize) {
2285        let display_point = self.display_map.offset_to_wrap_display_point(point.offset);
2286        let row = self
2287            .display_map
2288            .wrap_row_to_display_row(display_point.row)
2289            .unwrap_or_else(|| {
2290                self.display_map
2291                    .nearest_visible_display_row(display_point.row)
2292            });
2293
2294        (row, display_point.column + point.columns_past_line_end)
2295    }
2296
2297    pub(super) fn on_mouse_down(
2298        &mut self,
2299        event: &MouseDownEvent,
2300        window: &mut Window,
2301        cx: &mut Context<Self>,
2302    ) {
2303        // A token that consumed the press (a double click selecting it) must
2304        // not also place the caret.
2305        if window.default_prevented() {
2306            return;
2307        }
2308        self.undo_manager.break_transaction_coalescing();
2309        // Input has its own text selection; suppress the window-level text
2310        // selection (Root) so it does not start a drag from here.
2311        crate::global_state::GlobalState::suppress_text_selection(cx);
2312
2313        // Clear inline completion on any mouse interaction
2314        M::clear_inline_completion(self, cx);
2315        // A tap on the touch selection asks for its menu back. Any other press
2316        // places the caret or starts a new drag, and the handles and the menu
2317        // no longer belong to what is selected.
2318        if event.button == MouseButton::Left
2319            && event.click_count == 1
2320            && self.reopen_edit_menu_at(event.position, cx)
2321        {
2322            return;
2323        }
2324        self.dismiss_touch_selection(cx);
2325
2326        // If there have IME marked range and is empty (Means pressed Esc to abort IME typing)
2327        // Clear the marked range.
2328        if let Some(ime_marked_range) = &self.ime_marked_range {
2329            if ime_marked_range.len() == 0 {
2330                self.ime_marked_range = None;
2331            }
2332        }
2333
2334        self.selecting = true;
2335        let (offset, line_end_affinity, columns_past_line_end) =
2336            self.resolve_mouse_position(event.position);
2337
2338        if M::on_click(self, event, offset, window, cx) {
2339            return;
2340        }
2341
2342        // Triple click to select line
2343        if event.button == MouseButton::Left && event.click_count >= 3 {
2344            self.select_line(offset, window, cx);
2345            return;
2346        }
2347
2348        // Double click to select word
2349        if event.button == MouseButton::Left && event.click_count == 2 {
2350            self.select_word(offset, window, cx);
2351            // A double tap is touch's other way to select a word, and it
2352            // gets the handles and the menu like a long press does.
2353            if crate::GlobalState::is_touch_press(cx) {
2354                self.keep_touch_selection(cx);
2355            }
2356            return;
2357        }
2358
2359        // Show Mouse context menu
2360        if event.button == MouseButton::Right {
2361            if self.enable_context_menu {
2362                if !self.active_selection().contains(offset) {
2363                    self.move_to(offset, None, cx);
2364                }
2365                self.pending_context_menu = Some((event.position, offset));
2366            }
2367            return;
2368        }
2369
2370        // Multi-cursor placement, multi-line only.
2371        if self.is_multi_line() && event.button == MouseButton::Left {
2372            if event.modifiers.alt
2373                && (event.modifiers.shift || (cfg!(target_os = "linux") && event.modifiers.control))
2374            {
2375                // Alt+Shift starts a block; Linux also accepts Ghostty's Ctrl+Alt.
2376                // Mark selecting so the drag handler extends the block.
2377                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2378                self.selecting = true;
2379                self.move_to_with_affinity(offset, None, line_end_affinity, cx);
2380                return;
2381            } else if event.modifiers.alt {
2382                self.add_cursor_at(offset, cx);
2383                // Keep click-to-add behavior, but use this press as the block
2384                // anchor if the user continues dragging with the left button.
2385                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2386                return;
2387            }
2388        }
2389
2390        if event.modifiers.shift {
2391            self.select_to_with_affinity(offset, line_end_affinity, cx);
2392        } else {
2393            self.move_to_with_affinity(offset, None, line_end_affinity, cx)
2394        }
2395    }
2396
2397    pub(super) fn on_mouse_up(
2398        &mut self,
2399        event: &MouseUpEvent,
2400        window: &mut Window,
2401        cx: &mut Context<Self>,
2402    ) {
2403        if event.button == MouseButton::Right {
2404            if let Some((position, offset)) = self.pending_context_menu.take() {
2405                self.handle_right_click_menu(position, offset, window, cx);
2406            }
2407        }
2408        if self.active_selection().is_empty() {
2409            self.active_selection_mut().reversed = false;
2410        }
2411        self.selecting = false;
2412        self.selected_word_range = None;
2413        self.column_select_start = None;
2414        self.auto_scroll.stop();
2415    }
2416
2417    pub(super) fn on_mouse_move(
2418        &mut self,
2419        event: &MouseMoveEvent,
2420        window: &mut Window,
2421        cx: &mut Context<Self>,
2422    ) {
2423        // Check if mouse is within bounds
2424        let within_bounds = self
2425            .last_bounds
2426            .as_ref()
2427            .map(|bounds| bounds.contains(&event.position))
2428            .unwrap_or(false);
2429
2430        if !within_bounds {
2431            // Clear hover when mouse leaves the input
2432            M::clear_hover_state(self, cx);
2433            return;
2434        }
2435
2436        // Show diagnostic popover on mouse move
2437        let (offset, _) = self.index_for_mouse_position(event.position);
2438        M::on_mouse_move(self, offset, event, window, cx);
2439
2440        if self.is_code_editor() {
2441            // Keep the current popover while the pointer stays on the same
2442            // diagnostic, so moving over a squiggle neither repaints nor
2443            // rebuilds the popover.
2444            match self
2445                .mode
2446                .diagnostics()
2447                .and_then(|set| set.for_offset(offset))
2448            {
2449                Some(diagnostic) => {
2450                    if self.diagnostic_popover.as_deref() != Some(diagnostic) {
2451                        self.diagnostic_popover = Some(Rc::new(diagnostic.clone()));
2452                        cx.notify();
2453                    }
2454                }
2455                None => {
2456                    if self.diagnostic_popover.take().is_some() {
2457                        cx.notify();
2458                    }
2459                }
2460            }
2461        }
2462    }
2463
2464    pub(super) fn on_scroll_wheel(
2465        &mut self,
2466        event: &ScrollWheelEvent,
2467        window: &mut Window,
2468        cx: &mut Context<Self>,
2469    ) {
2470        let line_height = self
2471            .last_layout
2472            .as_ref()
2473            .map(|layout| layout.line_height)
2474            .unwrap_or(window.line_height());
2475        let delta = event.delta.pixel_delta(line_height);
2476
2477        let old_offset = self.scroll_handle.offset();
2478        self.update_scroll_offset(Some(old_offset + delta), cx);
2479
2480        // Only stop propagation if the offset actually changed
2481        if self.scroll_handle.offset() != old_offset {
2482            cx.stop_propagation();
2483        }
2484
2485        if self.diagnostic_popover.take().is_some() {
2486            cx.notify();
2487        }
2488        // The handles follow the text; the menu would sit over whatever
2489        // scrolls underneath it, so it steps aside until the finger lifts.
2490        self.edit_menu_on_scroll(event.touch_phase, cx);
2491    }
2492
2493    pub(super) fn update_scroll_offset(
2494        &mut self,
2495        offset: Option<Point<Pixels>>,
2496        cx: &mut Context<Self>,
2497    ) {
2498        let mut offset = offset.unwrap_or(self.scroll_handle.offset());
2499        let safe_y_range =
2500            (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
2501
2502        offset.y = if self.is_single_line() {
2503            px(0.)
2504        } else {
2505            offset.y.clamp(safe_y_range.start, safe_y_range.end)
2506        };
2507        offset.x = clamp_horizontal_scroll_offset(
2508            offset.x,
2509            self.scroll_size.width,
2510            self.input_bounds.size.width,
2511            self.text_align,
2512        );
2513        if self.scroll_handle.offset() != offset {
2514            self.scroll_handle.set_offset(offset);
2515            cx.notify();
2516        }
2517    }
2518
2519    /// Scroll to make the given offset visible.
2520    ///
2521    /// If `direction` is Some, will keep edges at the same side.
2522    pub(crate) fn scroll_to(
2523        &mut self,
2524        offset: usize,
2525        direction: Option<MoveDirection>,
2526        cx: &mut Context<Self>,
2527    ) {
2528        let padding = if direction.is_some() {
2529            ScrollPadding::SurroundingLines
2530        } else {
2531            ScrollPadding::Minimal
2532        };
2533        self.scroll_to_with_padding(offset, direction, padding, cx);
2534    }
2535
2536    /// Reveal an offset with independently chosen direction restriction and padding.
2537    /// Search uses surrounding lines without restricting movement to match order.
2538    pub(crate) fn scroll_to_with_padding(
2539        &mut self,
2540        offset: usize,
2541        direction: Option<MoveDirection>,
2542        padding: ScrollPadding,
2543        cx: &mut Context<Self>,
2544    ) {
2545        let Some(last_layout) = self.last_layout.as_ref() else {
2546            return;
2547        };
2548        let Some(bounds) = self.last_bounds.as_ref() else {
2549            return;
2550        };
2551
2552        let mut scroll_offset = self.scroll_handle.offset();
2553        let was_offset = scroll_offset;
2554        let line_height = last_layout.line_height;
2555
2556        let point = self.text.offset_to_point(offset);
2557
2558        let row = point.row;
2559
2560        // Resolve the wrapped row even when the target is outside the last layout.
2561        let display_pos = self
2562            .display_map
2563            .buffer_pos_to_display_pos(crate::input::BufferPoint::new(row, point.column));
2564        let row_offset_y = line_height * display_pos.row;
2565
2566        // For Right alignment use 0 margin: the cursor indicator is clamped inside bounds
2567        // in layout_cursors, so shifting the text here would cause a first-click visual jump.
2568        let safety_margin = match last_layout.text_align {
2569            TextAlign::Left => RIGHT_MARGIN,
2570            TextAlign::Right => px(0.),
2571            TextAlign::Center => CURSOR_WIDTH,
2572        };
2573        if let Some(vi) = last_layout
2574            .visible_buffer_lines
2575            .iter()
2576            .position(|&line| line == row)
2577        {
2578            let line = &last_layout.lines[vi];
2579            let local_offset = offset.saturating_sub(last_layout.visible_line_byte_offsets[vi]);
2580            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
2581                let bounds_width = bounds.size.width - last_layout.line_number_width;
2582                let col_offset_x = pos.x;
2583                if col_offset_x - safety_margin < -scroll_offset.x {
2584                    // If the position is out of the visible area, scroll to make it visible
2585                    scroll_offset.x = -col_offset_x + safety_margin;
2586                } else if col_offset_x + safety_margin > -scroll_offset.x + bounds_width {
2587                    scroll_offset.x = -(col_offset_x - bounds_width + safety_margin);
2588                }
2589            }
2590        }
2591
2592        // Scroll the row into view. Use the same edge clearance helper as
2593        // `TextElement::layout_cursors` so both scroll-into-view paths agree
2594        // (a mismatch flickered on `Down` at end-of-buffer with a small
2595        // `cursor_surrounding_lines` override).
2596        let edge_height =
2597            if matches!(padding, ScrollPadding::SurroundingLines) && self.is_code_editor() {
2598                super::element::cursor_surrounding_padding(
2599                    self.mode.is_auto_grow(),
2600                    self.cursor_surrounding_lines,
2601                    super::element::viewport_visible_lines(bounds.size.height, line_height),
2602                    line_height,
2603                )
2604            } else {
2605                line_height
2606            };
2607        if row_offset_y - edge_height + line_height < -scroll_offset.y {
2608            // Scroll up
2609            scroll_offset.y = -row_offset_y + edge_height - line_height;
2610        } else if row_offset_y + edge_height > -scroll_offset.y + bounds.size.height {
2611            // Scroll down
2612            scroll_offset.y = -(row_offset_y - bounds.size.height + edge_height);
2613        }
2614
2615        // Avoid necessary scroll, when it was already in the correct position.
2616        if direction == Some(MoveDirection::Up) {
2617            scroll_offset.y = scroll_offset.y.max(was_offset.y);
2618        } else if direction == Some(MoveDirection::Down) {
2619            scroll_offset.y = scroll_offset.y.min(was_offset.y);
2620        }
2621
2622        // Clamp the deferred target into the same safe range that
2623        // `update_scroll_offset` enforces on persist, so paint never shows an
2624        // over-scrolled frame before the post-paint clamp pulls it back.
2625        let safe_y_min = (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.));
2626        scroll_offset.x = scroll_offset.x.min(px(0.));
2627        scroll_offset.y = scroll_offset.y.clamp(safe_y_min, px(0.));
2628        self.deferred_scroll_offset = Some(scroll_offset);
2629        cx.notify();
2630    }
2631
2632    pub(super) fn show_character_palette(
2633        &mut self,
2634        _: &ShowCharacterPalette,
2635        window: &mut Window,
2636        _: &mut Context<Self>,
2637    ) {
2638        window.show_character_palette();
2639    }
2640
2641    /// The text of every non-empty selection, in document order.
2642    fn selected_texts(&self) -> Vec<String> {
2643        let mut selections: Vec<CursorSelection> = self
2644            .selections
2645            .iter()
2646            .copied()
2647            .filter(|sel| !sel.is_empty())
2648            .collect();
2649        selections.sort_by_key(|sel| sel.start);
2650        selections
2651            .iter()
2652            .map(|sel| self.text.slice(*sel).to_string())
2653            .collect()
2654    }
2655
2656    pub(super) fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
2657        if !self.is_copyable() {
2658            return;
2659        }
2660
2661        let texts = self.selected_texts();
2662
2663        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
2664    }
2665
2666    pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
2667        if !self.is_copyable() {
2668            return;
2669        }
2670
2671        let texts = self.selected_texts();
2672
2673        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
2674
2675        self.undo_manager.set_pending_intent(EditIntent::Atomic);
2676        self.replace_text_in_range_silent(None, "", window, cx);
2677    }
2678
2679    pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
2680        if !self.is_editable() {
2681            return;
2682        }
2683        if let Some(clipboard) = cx.read_from_clipboard() {
2684            self.insert_clipboard(clipboard, window, cx);
2685            return;
2686        }
2687        // The synchronous read is empty on platforms whose clipboard is
2688        // asynchronous and permission-gated (the web), so fall back to the
2689        // real read. It has to start here, still inside the user activation
2690        // that dispatched `Paste`, or the browser refuses the read.
2691        let read = cx.read_from_clipboard_async();
2692        let target = self.paste_target();
2693        cx.spawn_in(window, async move |this, cx| match read.await {
2694            Ok(Some(clipboard)) => {
2695                this.update_in(cx, |this, window, cx| {
2696                    // The read can sit behind a permission prompt for as
2697                    // long as the user likes. If they edited, moved the
2698                    // caret or left the input meanwhile, the paste would
2699                    // land where they no longer mean it to; drop it, as the
2700                    // browser's own paste event only reaches the focused
2701                    // input too.
2702                    if this.is_editable()
2703                        && this.focus_handle.is_focused(window)
2704                        && this.paste_target() == target
2705                    {
2706                        this.insert_clipboard(clipboard, window, cx);
2707                    }
2708                })
2709                .ok();
2710            }
2711            Ok(None) => {}
2712            Err(error) => tracing::warn!("failed to read the clipboard for paste: {error}"),
2713        })
2714        .detach();
2715    }
2716
2717    /// Where a paste would go right now: the document as edited so far and
2718    /// the selections it would replace. Two equal targets mean an edit made
2719    /// for one still applies to the other.
2720    fn paste_target(&self) -> PasteTarget {
2721        PasteTarget {
2722            document_revision: self.document_revision,
2723            selections: self.selections.iter().copied().collect(),
2724        }
2725    }
2726
2727    /// Inserts the clipboard text the way `Paste` does: one atomic edit,
2728    /// newlines dropped on a single-line input, one line per selection when
2729    /// the counts match on a multi-line one. A clipboard without text (an
2730    /// image, say) is left alone rather than replacing the selection with
2731    /// nothing.
2732    fn insert_clipboard(
2733        &mut self,
2734        clipboard: ClipboardItem,
2735        window: &mut Window,
2736        cx: &mut Context<Self>,
2737    ) {
2738        let Some(mut new_text) = clipboard.text().filter(|text| !text.is_empty()) else {
2739            return;
2740        };
2741        // A paste is one atomic edit, never part of a typing run.
2742        self.undo_manager.set_pending_intent(EditIntent::Atomic);
2743
2744        if !self.is_multi_line() {
2745            new_text = new_text.replace('\n', "");
2746            self.replace_text_in_range_silent(None, &new_text, window, cx);
2747            self.scroll_to(self.cursor(), None, cx);
2748            return;
2749        }
2750
2751        // Distribute one clipboard line per selection when the counts match.
2752        // Otherwise insert the whole clipboard text at each cursor.
2753        if !self.selections.is_single() {
2754            self.selections.merge_overlapping();
2755        }
2756        let lines: Vec<String> = new_text.split('\n').map(|s| s.to_string()).collect();
2757        let count = self.selections.len();
2758        if count > 1 && lines.len() == count {
2759            let mut selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
2760            selections.sort_by_key(|sel| sel.start);
2761            let edits: Vec<(Range<usize>, String)> = selections
2762                .iter()
2763                .zip(lines)
2764                .map(|(sel, line)| (sel.start..sel.end, line))
2765                .collect();
2766            self.replace_text_in_ranges(&edits, window, cx);
2767        } else {
2768            self.replace_text_in_range_silent(None, &new_text, window, cx);
2769        }
2770        self.scroll_to(self.cursor(), None, cx);
2771    }
2772
2773    /// The intent of a batch the caller did not label: inserting text at
2774    /// collapsed cursors is typing, anything else stands on its own.
2775    fn typing_intent(&self, edits: &[(Range<usize>, String)], new_text: &str) -> EditIntent {
2776        if !new_text.is_empty()
2777            && !new_text.contains(['\n', '\r'])
2778            && edits.iter().all(|(range, _)| range.is_empty())
2779        {
2780            EditIntent::Typing
2781        } else {
2782            EditIntent::Atomic
2783        }
2784    }
2785
2786    /// Where a cursor stood before an edit made with this intent.
2787    ///
2788    /// Backspace and forward delete expand a collapsed cursor over the text
2789    /// they are about to remove, so the recorded cursor has to collapse back to
2790    /// the side it came from for undo to restore it where the user left it.
2791    fn collapse_for_intent(
2792        intent: EditIntent,
2793        mut selection: CursorSelection,
2794        range: &Range<usize>,
2795    ) -> CursorSelection {
2796        match intent {
2797            EditIntent::Backspace => selection.place_at(range.end, None),
2798            EditIntent::DeleteForward => selection.place_at(range.start, None),
2799            EditIntent::Typing | EditIntent::Atomic => {}
2800        }
2801        selection
2802    }
2803
2804    /// Whether an edit rewrites the character before a collapsed cursor, as the
2805    /// macOS Korean IME does on each keystroke instead of marking text.
2806    fn rewrites_typed_char(
2807        &self,
2808        selection: CursorSelection,
2809        range: &Range<usize>,
2810        old_text: &str,
2811        new_text: &str,
2812    ) -> bool {
2813        !self.silent_replace_text
2814            && selection.is_collapsed()
2815            && selection.cursor_offset() == range.end
2816            && old_text.chars().count() == 1
2817            && !old_text.contains(['\n', '\r'])
2818            && !new_text.contains(['\n', '\r'])
2819    }
2820
2821    fn push_history(
2822        &mut self,
2823        text: &Rope,
2824        range: &Range<usize>,
2825        new_text: &str,
2826        requested_intent: Option<EditIntent>,
2827        selection_before: CursorSelection,
2828        selection_after: Option<CursorSelection>,
2829    ) -> bool {
2830        self.document_revision = self.document_revision.wrapping_add(1);
2831        let token_delta = self.edit_tokens(range, new_text.len());
2832        if self.undo_manager.is_ignoring() {
2833            return false;
2834        }
2835
2836        let range =
2837            text.clip_offset(range.start, Bias::Left)..text.clip_offset(range.end, Bias::Right);
2838        let old_text = text.slice(range.clone()).to_string();
2839        let new_range = range.start..range.start + new_text.len();
2840
2841        let intent = requested_intent.unwrap_or_else(|| {
2842            if range.is_empty()
2843                && old_text.is_empty()
2844                && !new_text.is_empty()
2845                && !new_text.contains(['\n', '\r'])
2846                || self.rewrites_typed_char(selection_before, &range, &old_text, new_text)
2847            {
2848                EditIntent::Typing
2849            } else {
2850                EditIntent::Atomic
2851            }
2852        });
2853
2854        let selection_before = Self::collapse_for_intent(intent, selection_before, &range);
2855        let selection_after =
2856            selection_after.unwrap_or_else(|| (new_range.end..new_range.end).into());
2857
2858        let open_transaction = self.undo_manager.has_open_transaction();
2859        let mut change = Change::new(range, &old_text, new_range, new_text);
2860        change.token_delta = token_delta;
2861        let recorded = self.undo_manager.record_transaction(change, intent);
2862        // A batch records its own cursor sets. This covers a change that is a
2863        // transaction on its own.
2864        if recorded && !open_transaction {
2865            self.undo_manager
2866                .record_selections(vec![selection_before], vec![selection_after]);
2867        }
2868        recorded
2869    }
2870
2871    pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
2872        self.replay_history(true, window, cx);
2873    }
2874
2875    pub(super) fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
2876        self.replay_history(false, window, cx);
2877    }
2878
2879    fn replay_history(&mut self, undo: bool, window: &mut Window, cx: &mut Context<Self>) {
2880        if !self.is_editable() {
2881            return;
2882        }
2883        let replay = if undo {
2884            self.undo_manager.undo()
2885        } else {
2886            self.undo_manager.redo()
2887        };
2888        let Some(replay) = replay else {
2889            return;
2890        };
2891        let token_aware = self.inline_tokens.is_some()
2892            || replay
2893                .changes
2894                .iter()
2895                .any(|change| change.token_delta.is_some());
2896        let emit_events = self.emit_events;
2897        if token_aware {
2898            self.emit_events = false;
2899        }
2900        self.replaying_history = token_aware;
2901        self.undo_manager.set_ignoring(true);
2902        for change in &replay.changes {
2903            let (range, text) = if undo {
2904                (change.new_range.into(), &change.old_text)
2905            } else {
2906                (change.old_range.into(), &change.new_text)
2907            };
2908            let range_utf16 = self.range_to_utf16(&range);
2909            self.replace_text_in_range_silent(Some(range_utf16), text, window, cx);
2910            self.replay_tokens(&range, text.len(), change.token_delta.as_deref(), undo);
2911        }
2912        self.restore_selections(replay.selections);
2913        self.mode
2914            .restore_auto_closed_pairs(replay.auto_closed_pairs.unwrap_or_default());
2915        if token_aware {
2916            // Token replay bypasses IME normalization; a stale marked range
2917            // would otherwise describe text that no longer exists.
2918            self.ime_marked_range = None;
2919        }
2920        self.undo_manager.set_ignoring(false);
2921        self.replaying_history = false;
2922        self.emit_events = emit_events;
2923        if token_aware && emit_events {
2924            cx.emit(InputEvent::Change);
2925        }
2926        cx.notify();
2927    }
2928
2929    /// Restore a set of selections captured in a transaction, clamping offsets
2930    /// to the current text length. `None` leaves the current selections as the
2931    /// replay left them.
2932    fn restore_selections(&mut self, selections: Option<Vec<CursorSelection>>) {
2933        let Some(selections) = selections else {
2934            return;
2935        };
2936
2937        let len = self.text.len();
2938        let restored: Vec<CursorSelection> = selections
2939            .into_iter()
2940            .map(|mut sel| {
2941                sel.start = sel.start.min(len);
2942                sel.end = sel.end.min(len);
2943                sel
2944            })
2945            .collect();
2946        self.selections.replace_all(restored);
2947        self.selections.merge_overlapping();
2948    }
2949
2950    /// Get byte offset of the cursor.
2951    ///
2952    /// The offset is the UTF-8 offset.
2953    pub fn cursor(&self) -> usize {
2954        if let Some(ime_marked_range) = &self.ime_marked_range {
2955            return ime_marked_range.end;
2956        }
2957
2958        self.selections.active().cursor_offset()
2959    }
2960
2961    /// Returns the active selection.
2962    pub(super) fn active_selection(&self) -> &CursorSelection {
2963        self.selections.active()
2964    }
2965
2966    /// Returns a mutable reference to the active selection.
2967    pub(super) fn active_selection_mut(&mut self) -> &mut CursorSelection {
2968        self.selections.active_mut()
2969    }
2970
2971    /// Sets the active selection to the given range, keeping its `reversed`
2972    /// and `column_anchor` state untouched.
2973    pub(super) fn set_selection(&mut self, start: usize, end: usize) {
2974        let range = self.normalize_token_range(start..end);
2975        let (start, end) = (range.start, range.end);
2976        let active = self.active_selection_mut();
2977        active.start = start;
2978        active.end = end;
2979    }
2980
2981    /// Collapses the active selection to a cursor at the given offset,
2982    /// clearing `reversed`.
2983    pub(super) fn set_cursor_to(&mut self, offset: usize) {
2984        let offset = self.token_boundary(offset, Bias::Right);
2985        let active = self.active_selection_mut();
2986        active.start = offset;
2987        active.end = offset;
2988        active.reversed = false;
2989    }
2990
2991    /// Visible row range in the last laid-out viewport, `None` before first layout.
2992    pub fn visible_row_range(&self) -> Option<std::ops::Range<usize>> {
2993        self.last_layout.as_ref().map(|l| l.visible_range.clone())
2994    }
2995
2996    /// Current scroll offset of the editor viewport.
2997    pub fn scroll_offset(&self) -> gpui::Point<gpui::Pixels> {
2998        self.scroll_handle.offset()
2999    }
3000
3001    /// Set scroll offset of the editor viewport.
3002    ///
3003    /// The offset will be clamped to the valid range, and applied after the next layout.
3004    pub fn set_scroll_offset(&mut self, offset: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
3005        self.deferred_scroll_offset = Some(offset);
3006        cx.notify();
3007    }
3008
3009    /// Laid-out line height; `None` before first layout.
3010    pub fn line_height(&self) -> Option<gpui::Pixels> {
3011        self.last_layout.as_ref().map(|l| l.line_height)
3012    }
3013
3014    /// Returns the active selection as a byte range into the text.
3015    ///
3016    /// With multiple cursors, this reads only the active selection.
3017    ///
3018    /// The range is empty (`start == end`) when no text is selected; in
3019    /// that case the offset equals `cursor()`. Byte offsets are measured
3020    /// in the underlying rope's byte units.
3021    pub fn selected_range(&self) -> std::ops::Range<usize> {
3022        (*self.selections.active()).into()
3023    }
3024
3025    pub fn select_all(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3026        self.undo_manager.break_transaction_coalescing();
3027        self.selections.remove_all_but_active();
3028        self.set_selection(0, self.text.len());
3029        cx.notify();
3030    }
3031
3032    /// Set the selected range using UTF-8 byte offsets, removing additional cursors.
3033    ///
3034    /// Non-empty ranges expand to character boundaries. Empty ranges remain empty and are
3035    /// clipped to the preceding character boundary.
3036    pub fn set_selected_range(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3037        let range = self.normalize_token_range(range);
3038        let end_bias = if range.start == range.end {
3039            Bias::Left
3040        } else {
3041            Bias::Right
3042        };
3043        let start = self.text.clip_offset(range.start, Bias::Left);
3044        let end = self.text.clip_offset(range.end, end_bias);
3045
3046        self.move_to(start, None, cx);
3047        self.active_selection_mut().reversed = false;
3048        self.selected_word_range = None;
3049        self.select_to(end, cx);
3050    }
3051
3052    /// Resolve a mouse position to a byte offset in the text.
3053    ///
3054    /// Also reports the caret's line-end affinity for that offset: `true` when the position
3055    /// landed on the wrap boundary of a non-final visual row, meaning the caret belongs at the
3056    /// end of that row rather than at the start of the next one. Callers that place or extend a
3057    /// selection must pass it on, or clicking past the last glyph of a wrapped row leaves a
3058    /// caret one row below the pointer.
3059    pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> (usize, bool) {
3060        let (offset, line_end_affinity, _) = self.resolve_mouse_position(position);
3061        (offset, line_end_affinity)
3062    }
3063
3064    /// Resolve a mouse position to a text offset, the caret affinity to use for it, and
3065    /// how many columns the pointer sits past the end of that row.
3066    ///
3067    /// Only a columnar selection needs the third value; everywhere else a position past
3068    /// the end of a row means the end of that row, and
3069    /// [`Self::index_for_mouse_position`] is the call to make.
3070    pub(super) fn resolve_mouse_position(&self, position: Point<Pixels>) -> (usize, bool, usize) {
3071        // If the text is empty, always return 0
3072        if self.text.len() == 0 {
3073            return (0, false, 0);
3074        }
3075
3076        let (Some(bounds), Some(last_layout)) =
3077            (self.last_bounds.as_ref(), self.last_layout.as_ref())
3078        else {
3079            return (0, false, 0);
3080        };
3081
3082        let line_height = last_layout.line_height;
3083        let line_number_width = last_layout.line_number_width;
3084
3085        // TIP: About the IBeam cursor
3086        //
3087        // If cursor style is IBeam, the mouse mouse position is in the middle of the cursor (This is special in OS)
3088
3089        // The position is relative to the bounds of the text input
3090        //
3091        // bounds.origin:
3092        //
3093        // - included the input padding.
3094        // - included the scroll offset.
3095        let inner_position = position - bounds.origin - point(line_number_width, px(0.));
3096
3097        let mut y_offset = last_layout.visible_top;
3098        // Position relative to the last line walked, kept for a pointer that ends up
3099        // below every line.
3100        let mut last_line_pos = None;
3101
3102        // Traverse visible buffer lines (compact, no hidden entries)
3103        for (vi, (line_layout, _buffer_line)) in last_layout
3104            .lines
3105            .iter()
3106            .zip(last_layout.visible_buffer_lines.iter())
3107            .enumerate()
3108        {
3109            let line_start_offset = last_layout.visible_line_byte_offsets[vi];
3110
3111            // Calculate line origin for this display row
3112            let line_origin = point(px(0.), y_offset);
3113            let pos = inner_position - line_origin;
3114
3115            // Return offset by use closest_index_for_x if is single line mode.
3116            if self.is_single_line() {
3117                let local_index = line_layout.closest_index_for_x(pos.x, last_layout);
3118                // A single line never wraps, so there is no boundary to disambiguate.
3119                return (
3120                    self.resolve_index(line_start_offset + local_index),
3121                    false,
3122                    0,
3123                );
3124            }
3125
3126            // Check if mouse is in this line's bounds
3127            if let Some((local_index, line_end_affinity)) =
3128                line_layout.closest_index_for_position(pos, last_layout)
3129            {
3130                return (
3131                    self.resolve_index(line_start_offset + local_index),
3132                    line_end_affinity,
3133                    line_layout.columns_past_line_end(pos, last_layout),
3134                );
3135            } else if pos.y < px(0.) {
3136                // Mouse is above this line, return start of this line
3137                return (self.resolve_index(line_start_offset), false, 0);
3138            }
3139
3140            y_offset += line_layout.size(line_height).height;
3141            last_line_pos = Some(pos);
3142        }
3143
3144        // Clamp to the last laid-out row. Returning the end of the whole document
3145        // would select unseen text before drag autoscroll has reached it.
3146        let last_position = last_layout
3147            .lines
3148            .last()
3149            .zip(last_line_pos)
3150            .zip(last_layout.visible_line_byte_offsets.last())
3151            .map(|((line_layout, pos), line_start)| {
3152                let last_row_top = (line_layout.size(line_height).height - line_height).max(px(0.));
3153                let pos = point(pos.x, last_row_top);
3154                (
3155                    self.resolve_index(line_start + line_layout.len()),
3156                    false,
3157                    line_layout.columns_past_line_end(pos, last_layout),
3158                )
3159            });
3160
3161        last_position.unwrap_or((self.text.len(), false, 0))
3162    }
3163
3164    /// Map a display byte index back to a text offset, undoing the mask expansion when the input
3165    /// is masked.
3166    fn resolve_index(&self, index: usize) -> usize {
3167        if self.masked {
3168            self.text.char_index_to_offset(index / MASK_CHAR.len_utf8())
3169        } else {
3170            index.min(self.text.len())
3171        }
3172    }
3173
3174    /// Returns a y offsetted point for the line origin.
3175    /// Select the text from the current cursor position to the given offset.
3176    ///
3177    /// The offset is the UTF-8 offset.
3178    ///
3179    /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
3180    /// Extend a single selection so its moving end lands at `offset`, flipping
3181    /// `reversed` when the ends cross. When a sticky `word_range` is given the
3182    /// selection is kept covering it.
3183    fn extend_selection(
3184        sel: &mut CursorSelection,
3185        offset: usize,
3186        word_range: Option<CursorSelection>,
3187    ) {
3188        if sel.reversed {
3189            sel.start = offset;
3190        } else {
3191            sel.end = offset;
3192        }
3193
3194        if sel.end < sel.start {
3195            sel.reversed = !sel.reversed;
3196            std::mem::swap(&mut sel.start, &mut sel.end);
3197        }
3198
3199        if let Some(word_range) = word_range {
3200            if sel.start > word_range.start {
3201                sel.start = word_range.start;
3202            }
3203            if sel.end < word_range.end {
3204                sel.end = word_range.end;
3205            }
3206        }
3207    }
3208
3209    /// Extend only the active selection to `offset`. Used by mouse drag.
3210    pub(crate) fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
3211        self.select_to_with_affinity(offset, false, cx);
3212    }
3213
3214    /// Like [`Self::select_to`], but also carries the caret's line-end affinity.
3215    ///
3216    /// See [`Self::move_to_with_affinity`] for why the affinity travels with the offset. Note
3217    /// that plain [`Self::select_to`] clears the affinity: every offset it is given came from
3218    /// the text rather than from a visual position, so the caret has no reason to keep sticking
3219    /// to the end of a wrapped row.
3220    pub(crate) fn select_to_with_affinity(
3221        &mut self,
3222        offset: usize,
3223        line_end_affinity: bool,
3224        cx: &mut Context<Self>,
3225    ) {
3226        M::clear_inline_completion(self, cx);
3227
3228        self.cursor_line_end_affinity = line_end_affinity;
3229        let offset = self.cursor_boundary(offset, Bias::Left);
3230        let word_range = self.selected_word_range;
3231        Self::extend_selection(self.active_selection_mut(), offset, word_range);
3232        let range = self.normalize_token_range(self.selected_range());
3233        self.set_selection(range.start, range.end);
3234
3235        if self.active_selection().is_empty() {
3236            self.update_preferred_column();
3237        }
3238        cx.notify()
3239    }
3240
3241    /// Extend every selection to the offset produced by `f`, then merge any
3242    /// selections that now overlap. Used by keyboard selection commands.
3243    fn select_all_cursors_to(
3244        &mut self,
3245        f: impl Fn(&Self, &CursorSelection) -> usize,
3246        cx: &mut Context<Self>,
3247    ) {
3248        self.select_all_cursors_to_with_affinity(|s, sel| (f(s, sel), false), false, cx);
3249    }
3250
3251    /// Extend selections with caret affinity, preserving their column anchors even
3252    /// when vertical movement collapses a selection to a cursor.
3253    fn select_all_cursors_to_with_affinity(
3254        &mut self,
3255        f: impl Fn(&Self, &CursorSelection) -> (usize, bool),
3256        preserve_column: bool,
3257        cx: &mut Context<Self>,
3258    ) {
3259        self.pause_blink_cursor(cx);
3260        self.undo_manager.break_transaction_coalescing();
3261        M::clear_inline_completion(self, cx);
3262
3263        let mut active_affinity = false;
3264        let new_selections: Vec<CursorSelection> = self
3265            .selections
3266            .iter()
3267            .map(|sel| {
3268                let (offset, affinity) = f(self, sel);
3269                if sel.id == self.active_selection().id {
3270                    active_affinity = affinity;
3271                }
3272                let offset = self.cursor_boundary(offset, Bias::Left);
3273                let mut new_sel = *sel;
3274                Self::extend_selection(&mut new_sel, offset, None);
3275                let range = self.normalize_token_range(new_sel.start..new_sel.end);
3276                new_sel.start = range.start;
3277                new_sel.end = range.end;
3278                if !preserve_column {
3279                    new_sel.column_anchor =
3280                        self.preferred_column_for_with_affinity(new_sel.cursor_offset(), affinity);
3281                } else if new_sel.column_anchor.is_none() {
3282                    new_sel.column_anchor = self.preferred_column_for_with_affinity(
3283                        sel.cursor_offset(),
3284                        self.line_end_affinity_for(sel),
3285                    );
3286                }
3287                new_sel
3288            })
3289            .collect();
3290        self.cursor_line_end_affinity = active_affinity;
3291        self.selections.replace_all(new_selections);
3292        self.selections.merge_overlapping();
3293
3294        self.scroll_to(self.cursor(), None, cx);
3295        cx.notify()
3296    }
3297
3298    /// Unselects the currently selected text.
3299    pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3300        self.undo_manager.break_transaction_coalescing();
3301        let offset = self.cursor();
3302        self.set_cursor_to(offset);
3303        cx.notify()
3304    }
3305
3306    #[inline]
3307    pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {
3308        self.text.offset_utf16_to_offset(offset)
3309    }
3310
3311    #[inline]
3312    pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {
3313        self.text.offset_to_offset_utf16(offset)
3314    }
3315
3316    #[inline]
3317    pub(crate) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
3318        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
3319    }
3320
3321    #[inline]
3322    pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
3323        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
3324    }
3325
3326    /// If offset falls on a hidden (folded) line, clamp backward to the end of
3327    /// the fold header line (last visible position before the fold).
3328    fn clamp_offset_to_visible_backward(&self, offset: usize) -> usize {
3329        let line = self.text.offset_to_point(offset).row;
3330        if self.display_map.is_buffer_line_hidden(line) {
3331            for fold in self.display_map.folded_ranges() {
3332                if line > fold.start_line && line <= fold.end_line {
3333                    return self.text.line_end_offset(fold.start_line);
3334                }
3335            }
3336        }
3337        offset
3338    }
3339
3340    /// If offset falls on a hidden (folded) line, clamp forward to the start of
3341    /// the fold end line (first visible position after the fold).
3342    fn clamp_offset_to_visible_forward(&self, offset: usize) -> usize {
3343        let line = self.text.offset_to_point(offset).row;
3344        if self.display_map.is_buffer_line_hidden(line) {
3345            for fold in self.display_map.folded_ranges() {
3346                if line > fold.start_line && line <= fold.end_line {
3347                    return self.text.line_start_offset(fold.end_line);
3348                }
3349            }
3350        }
3351        offset
3352    }
3353
3354    /// Clip a cursor/selection offset without splitting a CRLF newline. This does
3355    /// not alter the rope or its byte/UTF-16 conversion used for exact source APIs.
3356    pub(super) fn cursor_boundary(&self, offset: usize, bias: Bias) -> usize {
3357        let offset = self.token_boundary(self.text.clip_offset(offset, bias), bias);
3358        if offset > 0
3359            && self.text.char_at(offset - 1) == Some('\r')
3360            && self.text.char_at(offset) == Some('\n')
3361        {
3362            if bias == Bias::Left {
3363                offset - 1
3364            } else {
3365                offset + 1
3366            }
3367        } else {
3368            offset
3369        }
3370    }
3371
3372    pub(super) fn previous_boundary(&self, offset: usize) -> usize {
3373        let offset = self.cursor_boundary(offset.saturating_sub(1), Bias::Left);
3374        self.clamp_offset_to_visible_backward(offset)
3375    }
3376
3377    pub(super) fn next_boundary(&self, offset: usize) -> usize {
3378        let offset = self.cursor_boundary(offset.saturating_add(1), Bias::Right);
3379        self.clamp_offset_to_visible_forward(offset)
3380    }
3381
3382    /// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
3383    pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
3384        (self.focus_handle.is_focused(window) || M::is_context_menu_open(self, cx))
3385            && !self.disabled
3386            && self.blink_cursor.read(cx).visible()
3387            && window.is_window_active()
3388    }
3389
3390    fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3391        self.blink_cursor.update(cx, |cursor, cx| {
3392            cursor.start(cx);
3393        });
3394        cx.emit(InputEvent::Focus);
3395    }
3396
3397    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3398        if M::is_context_menu_open(self, cx) {
3399            return;
3400        }
3401
3402        M::hide_context_menu(self, cx);
3403        self.undo_manager.break_transaction_coalescing();
3404
3405        // NOTE: Do not cancel select, when blur.
3406        // Because maybe user want to copy the selected text by AppMenuBar (will take focus handle).
3407
3408        M::clear_hover_state(self, cx);
3409        self.diagnostic_popover = None;
3410        M::clear_inline_completion(self, cx);
3411        self.dismiss_touch_selection(cx);
3412        self.blink_cursor.update(cx, |cursor, cx| {
3413            cursor.stop(cx);
3414        });
3415        self.clamp_number_value(window, cx);
3416        cx.emit(InputEvent::Blur);
3417        cx.notify();
3418    }
3419
3420    /// Clamp the number value to the `min`/`max` range, used on blur.
3421    ///
3422    /// Out-of-range values are allowed while typing (e.g. `1` is an
3423    /// intermediate state of `15` when min is 10), and clamped on blur.
3424    fn clamp_number_value(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3425        if !self.is_single_line() {
3426            return;
3427        }
3428        if !matches!(self.mask_pattern, MaskPattern::Number { .. }) {
3429            return;
3430        }
3431        if self.number_min.is_none() && self.number_max.is_none() {
3432            return;
3433        }
3434
3435        let Ok(value) = self.unmask_value().parse::<f64>() else {
3436            return;
3437        };
3438
3439        let clamped = match (self.number_min, self.number_max) {
3440            (Some(min), _) if value < min => min,
3441            (_, Some(max)) if value > max => max,
3442            _ => return,
3443        };
3444
3445        // The clamped value must pass the `pattern`/`validate` check,
3446        // otherwise keep the value as is.
3447        let new_text = clamped.to_string();
3448        if !self.is_valid_input(&new_text, cx) {
3449            return;
3450        }
3451
3452        let range = self.range_to_utf16(&(0..self.text.len()));
3453        self.replace_text_in_range_silent(Some(range), &new_text, window, cx);
3454    }
3455
3456    pub(super) fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
3457        self.blink_cursor.update(cx, |cursor, cx| {
3458            cursor.pause(cx);
3459        });
3460    }
3461
3462    pub(super) fn on_drag_move(
3463        &mut self,
3464        event: &MouseMoveEvent,
3465        window: &mut Window,
3466        cx: &mut Context<Self>,
3467    ) {
3468        if self.text.len() == 0 {
3469            return;
3470        }
3471
3472        if self.last_layout.is_none() {
3473            return;
3474        }
3475
3476        if !self.focus_handle.is_focused(window) {
3477            return;
3478        }
3479
3480        if !self.selecting {
3481            return;
3482        }
3483
3484        self.auto_scroll.last_drag_position = Some(event.position);
3485        let (offset, line_end_affinity, columns_past_line_end) =
3486            self.resolve_mouse_position(event.position);
3487        if let Some(start) = self.column_select_start {
3488            let end = ColumnarPoint::new(offset, columns_past_line_end);
3489            self.build_columnar_selection(start, end, cx);
3490        } else {
3491            self.select_to_with_affinity(offset, line_end_affinity, cx);
3492        }
3493
3494        if !self.is_single_line() {
3495            let delta = AutoScroll::compute_delta(event.position.y, self.input_bounds);
3496            // Input's ScrollHandle uses negative-y-is-down; negate the positive-towards-bottom delta.
3497            let scroll_delta = delta.map(|d| -d);
3498            self.auto_scroll.set(scroll_delta, cx, |delta, state, cx| {
3499                let current = state.scroll_handle.offset();
3500                state.update_scroll_offset(Some(point(current.x, current.y + delta)), cx);
3501                if let Some(pos) = state.auto_scroll.last_drag_position {
3502                    let (offset, line_end_affinity, columns_past_line_end) =
3503                        state.resolve_mouse_position(pos);
3504                    if let Some(start) = state.column_select_start {
3505                        let end = ColumnarPoint::new(offset, columns_past_line_end);
3506                        state.build_columnar_selection(start, end, cx);
3507                    } else {
3508                        state.select_to_with_affinity(offset, line_end_affinity, cx);
3509                    }
3510                }
3511            });
3512        }
3513    }
3514
3515    /// Normalize the inserted text before applying it to the input.
3516    ///
3517    /// For number inputs (with [`MaskPattern::Number`]), this converts
3518    /// full-width number characters into their ASCII equivalents,
3519    /// e.g. `12。5` -> `12.5`.
3520    pub(super) fn normalize_input<'a>(&self, new_text: &'a str) -> Cow<'a, str> {
3521        let normalized = if matches!(self.mask_pattern, MaskPattern::Number { .. }) {
3522            normalize_number_input(new_text)
3523        } else {
3524            Cow::Borrowed(new_text)
3525        };
3526
3527        if self.is_single_line() && normalized.contains(['\n', '\r']) {
3528            Cow::Owned(normalized.replace(['\n', '\r'], ""))
3529        } else {
3530            normalized
3531        }
3532    }
3533
3534    pub(crate) fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {
3535        if new_text.is_empty() {
3536            return true;
3537        }
3538
3539        if let Some(validate) = &self.validate {
3540            if !validate(new_text, cx) {
3541                return false;
3542            }
3543        }
3544
3545        if !self.mask_pattern.is_valid(new_text) {
3546            return false;
3547        }
3548
3549        let Some(pattern) = &self.pattern else {
3550            return true;
3551        };
3552
3553        pattern.is_match(new_text)
3554    }
3555
3556    /// Set the mask pattern for formatting the input text.
3557    ///
3558    /// The pattern can contain:
3559    /// - 9: Any digit or dot
3560    /// - A: Any letter
3561    /// - *: Any character
3562    /// - Other characters will be treated as literal mask characters
3563    ///
3564    /// Example: "(999)999-999" for phone numbers
3565    pub fn mask_pattern(mut self, pattern: impl Into<MaskPattern>) -> Self {
3566        self.mask_pattern = pattern.into();
3567        self.mask_pattern_set = true;
3568        if let Some(placeholder) = self.mask_pattern.placeholder() {
3569            self.placeholder = placeholder.into();
3570        }
3571        self
3572    }
3573
3574    pub fn set_mask_pattern(
3575        &mut self,
3576        pattern: impl Into<MaskPattern>,
3577        _: &mut Window,
3578        cx: &mut Context<Self>,
3579    ) {
3580        self.mask_pattern = pattern.into();
3581        self.mask_pattern_set = true;
3582        if let Some(placeholder) = self.mask_pattern.placeholder() {
3583            self.placeholder = placeholder.into();
3584        }
3585        cx.notify();
3586    }
3587
3588    /// Apply the default numeric mask unless the caller explicitly selected a mask.
3589    pub fn ensure_number_mask(&mut self) {
3590        if self.mask_pattern_set {
3591            return;
3592        }
3593        self.mask_pattern = MaskPattern::Number {
3594            separator: None,
3595            fraction: None,
3596        };
3597    }
3598
3599    pub(super) fn set_input_bounds(&mut self, new_bounds: Bounds<Pixels>, cx: &mut Context<Self>) {
3600        let wrap_width_changed = self.input_bounds.size.width != new_bounds.size.width;
3601        self.input_bounds = new_bounds;
3602
3603        // Update display_map wrap_width if changed.
3604        if let Some(last_layout) = self.last_layout.as_ref() {
3605            if wrap_width_changed {
3606                let wrap_width = if !self.soft_wrap {
3607                    // None to disable wrapping (will use Pixels::MAX)
3608                    None
3609                } else {
3610                    last_layout.wrap_width
3611                };
3612
3613                self.display_map.on_layout_changed(wrap_width, cx);
3614                if self.is_multi_line() {
3615                    self.mode.update_auto_grow(&self.display_map);
3616                }
3617                cx.notify();
3618            }
3619        }
3620    }
3621
3622    /// Return the active selection's text, borrowed out of the [`Rope`]
3623    /// the state owns.
3624    ///
3625    /// See [`Self::selected_value`] when an owned string is wanted.
3626    pub fn selected_text(&self) -> RopeSlice<'_> {
3627        let range_utf16 = self.range_to_utf16(&self.selected_range());
3628        let range = self.range_from_utf16(&range_utf16);
3629        self.text.slice(range)
3630    }
3631
3632    /// Return the rendered bounds for a UTF-8 byte range in the current input contents.
3633    ///
3634    /// Returns `None` when the requested range is not currently laid out or visible.
3635    pub fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
3636        let Some(last_layout) = self.last_layout.as_ref() else {
3637            return None;
3638        };
3639
3640        let Some(last_bounds) = self.last_bounds else {
3641            return None;
3642        };
3643
3644        let (_, _, start_pos) = self.line_and_position_for_offset(range.start);
3645        let (_, _, end_pos) = self.line_and_position_for_offset(range.end);
3646
3647        let Some(start_pos) = start_pos else {
3648            return None;
3649        };
3650        let Some(end_pos) = end_pos else {
3651            return None;
3652        };
3653
3654        Some(Bounds::from_corners(
3655            last_bounds.origin + start_pos,
3656            last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),
3657        ))
3658    }
3659
3660    /// Replace text in range in silent.
3661    ///
3662    /// This will not trigger any UI interaction, such as auto-completion.
3663    pub(crate) fn replace_text_in_range_silent(
3664        &mut self,
3665        range_utf16: Option<Range<usize>>,
3666        new_text: &str,
3667        window: &mut Window,
3668        cx: &mut Context<Self>,
3669    ) {
3670        self.silent_replace_text = true;
3671        self.replace_text_in_range(range_utf16, new_text, window, cx);
3672        self.silent_replace_text = false;
3673    }
3674
3675    /// Apply a batch of edits as one atomic history transaction.
3676    ///
3677    /// `edits` are `(byte range in the current pre-edit document, replacement)`
3678    /// pairs.
3679    pub(crate) fn replace_text_in_ranges(
3680        &mut self,
3681        edits: &[(Range<usize>, String)],
3682        window: &mut Window,
3683        cx: &mut Context<Self>,
3684    ) {
3685        if !self.is_editable() || edits.is_empty() {
3686            return;
3687        }
3688
3689        // Every edit invalidates provider responses for the previous document,
3690        // including deletion and indentation which do not trigger completion.
3691        M::hide_context_menu(self, cx);
3692        M::clear_inline_completion(self, cx);
3693
3694        // Sort descending by start so applying front-of-vec first edits the
3695        // highest offsets first, leaving lower offsets unchanged.
3696        let mut sorted: Vec<(Range<usize>, &str)> = edits
3697            .iter()
3698            .map(|(range, text)| (self.normalize_token_range(range.clone()), text.as_str()))
3699            .collect();
3700        sorted.sort_by_key(|edit| std::cmp::Reverse(edit.0.start));
3701        if !self.token_spans().is_empty() {
3702            let mut merged: Vec<(Range<usize>, &str)> = Vec::with_capacity(sorted.len());
3703            for (range, text) in sorted {
3704                if let Some(last) = merged
3705                    .last_mut()
3706                    .filter(|last| range.end > last.0.start || range == last.0)
3707                {
3708                    last.0.start = range.start.min(last.0.start);
3709                    last.0.end = range.end.max(last.0.end);
3710                } else {
3711                    merged.push((range, text));
3712                }
3713            }
3714            sorted = merged;
3715        }
3716
3717        #[cfg(debug_assertions)]
3718        for pair in sorted.windows(2) {
3719            debug_assert!(
3720                pair[1].0.end <= pair[0].0.start,
3721                "replace_text_in_ranges requires disjoint ranges"
3722            );
3723        }
3724
3725        // Wrap multiple edits in one explicit transaction so they undo as a
3726        // unit. A single edit records directly, which keeps it eligible for
3727        // the undo manager's typing coalescing.
3728        let requested_intent = self.undo_manager.take_pending_intent();
3729        let selection_before = *self.active_selection();
3730        let original_selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
3731        // Snapshot the cursors before applying, so undo can restore them. A
3732        // delete has already expanded them over the text it removes, so they
3733        // collapse back to where the user left them.
3734        let selections_before: Vec<CursorSelection> = self
3735            .selections
3736            .iter()
3737            .map(|selection| {
3738                Self::collapse_for_intent(
3739                    requested_intent.unwrap_or(EditIntent::Atomic),
3740                    *selection,
3741                    &(selection.start..selection.end),
3742                )
3743            })
3744            .collect();
3745        let group = sorted.len() > 1;
3746        if group {
3747            self.undo_manager.begin_transaction();
3748        }
3749
3750        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
3751        let mut recorded = false;
3752        // Several edits reach the highlighter as one batch, so it reparses
3753        // once for the change instead of once per edit.
3754        let mut highlighter_edits = Vec::new();
3755        for (range, new_text) in &sorted {
3756            let old_text = self.text.clone();
3757            self.mode.adjust_auto_closed_pair(range, new_text.len());
3758            self.text.replace(range.clone(), new_text);
3759
3760            M::adjust_annotations(self, range, new_text.len());
3761            recorded |= self.push_history(
3762                &old_text,
3763                range,
3764                new_text,
3765                requested_intent,
3766                selection_before,
3767                None,
3768            );
3769
3770            // Incremental, single-range updates must run per edit.
3771            self.display_map
3772                .adjust_folds_for_edit(&old_text, range, new_text);
3773            self.display_map
3774                .on_text_changed(&self.text, range, &Rope::from(*new_text), cx);
3775
3776            if group {
3777                if self.is_code_editor() {
3778                    let edit =
3779                        super::mode::replacement_input_edit(&old_text, &self.text, range, new_text);
3780                    highlighter_edits.push((edit, self.text.clone()));
3781                }
3782            } else {
3783                self.mode.update_highlighter(
3784                    super::mode::HighlighterUpdate {
3785                        selected_range: range,
3786                        old_text: &old_text,
3787                        new_text: &self.text,
3788                        change_text: new_text,
3789                        force: true,
3790                    },
3791                    window,
3792                    cx,
3793                );
3794
3795                self.update_fold_candidates_incremental(range, new_text);
3796            }
3797        }
3798
3799        if group {
3800            self.mode
3801                .update_highlighter_batch::<M>(&highlighter_edits, window, cx);
3802
3803            // Fold candidates are read from the reparsed document, so each
3804            // edit is located where it ended up once every edit was applied.
3805            // `sorted` runs from the highest offset down.
3806            let mut shift = 0isize;
3807            for (range, new_text) in sorted.iter().rev() {
3808                let start = (range.start as isize + shift) as usize;
3809                self.update_fold_candidates_incremental(&(start..start), new_text);
3810                shift += new_text.len() as isize - range.len() as isize;
3811            }
3812
3813            self.undo_manager.commit_transaction();
3814        }
3815
3816        // One observable update per batch instead of one per edit.
3817        if let Some(diagnostics) = self.mode.diagnostics_mut() {
3818            diagnostics.reset(&self.text)
3819        }
3820        M::refresh_language_features(self, window, cx);
3821        self.update_search(cx);
3822
3823        // Compute the resulting cursors.
3824        // One collapsed cursor per edit, at the end of its inserted text.
3825        let mut ascending: Vec<(Range<usize>, &str)> = sorted.clone();
3826        ascending.sort_by_key(|edit| edit.0.start);
3827        let text_len = self.text.len();
3828        let mut delta: isize = 0;
3829        let mut edit_results = Vec::with_capacity(ascending.len());
3830        for (range, new_text) in &ascending {
3831            let offset = ((range.start as isize + delta) as usize + new_text.len()).min(text_len);
3832            edit_results.push((range.clone(), offset));
3833            delta += new_text.len() as isize - (range.end as isize - range.start as isize);
3834        }
3835
3836        let mut used = vec![false; edit_results.len()];
3837        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(ascending.len());
3838        for selection in original_selections {
3839            if let Ok(index) = edit_results
3840                .binary_search_by_key(&(selection.start, selection.end), |(range, _)| {
3841                    (range.start, range.end)
3842                })
3843                && !used[index]
3844            {
3845                let offset = edit_results[index].1;
3846                used[index] = true;
3847                let mut selection = selection;
3848                selection.place_at(offset, None);
3849                new_selections.push(selection);
3850            }
3851        }
3852        for (index, (_, offset)) in edit_results.into_iter().enumerate() {
3853            if !used[index] {
3854                new_selections.push(CursorSelection::new(
3855                    self.selections.generate_id(),
3856                    offset,
3857                    offset,
3858                ));
3859            }
3860        }
3861        self.selections.replace_all(new_selections);
3862        self.selections.merge_overlapping();
3863
3864        // Record the cursor snapshots for undo/redo restore.
3865        let selections_after: Vec<CursorSelection> = self.selections.iter().copied().collect();
3866        if recorded {
3867            self.undo_manager.record_auto_closed_pairs(
3868                auto_closed_pairs_before,
3869                self.mode.auto_closed_pairs().clone(),
3870            );
3871            self.undo_manager
3872                .record_selections(selections_before, selections_after);
3873        }
3874
3875        self.ime_marked_range.take();
3876        self.update_preferred_column();
3877        self.dismiss_touch_selection(cx);
3878        if self.is_multi_line() {
3879            self.mode.update_auto_grow(&self.display_map);
3880        }
3881        if self.emit_events {
3882            cx.emit(InputEvent::Change);
3883        }
3884        cx.notify();
3885    }
3886
3887    /// Update fold candidates from tree-sitter syntax tree (full extraction).
3888    /// Used only on initial load or language changes.
3889    fn update_fold_candidates(&mut self) {
3890        if !self.mode.is_folding() {
3891            return;
3892        }
3893
3894        let Some(highlighter_rc) = self.mode.highlighter() else {
3895            return;
3896        };
3897
3898        let highlighter = highlighter_rc.borrow();
3899        let Some(highlighter) = highlighter.as_ref() else {
3900            return;
3901        };
3902
3903        let fold_ranges = highlighter.fold_ranges(&self.text);
3904        self.display_map.set_fold_candidates(fold_ranges);
3905    }
3906
3907    /// Incrementally update fold candidates after a text edit.
3908    /// Only traverses the edited region of the syntax tree instead of the full tree.
3909    fn update_fold_candidates_incremental(&mut self, edit_range: &Range<usize>, new_text: &str) {
3910        if !self.mode.is_folding() {
3911            return;
3912        }
3913
3914        let Some(highlighter_rc) = self.mode.highlighter() else {
3915            return;
3916        };
3917
3918        let highlighter = highlighter_rc.borrow();
3919        let Some(highlighter) = highlighter.as_ref() else {
3920            return;
3921        };
3922
3923        // The new byte range in the updated text after the edit
3924        let new_end = edit_range.start + new_text.len();
3925        self.display_map.update_fold_candidates_for_edit(
3926            |range, text| highlighter.fold_ranges_for_edit(range, text),
3927            edit_range.start..new_end,
3928            &self.text,
3929        );
3930    }
3931}
3932
3933impl<M: InputModeKind> EntityInputHandler for InputBaseState<M> {
3934    fn text_for_range(
3935        &mut self,
3936        range_utf16: Range<usize>,
3937        adjusted_range: &mut Option<Range<usize>>,
3938        _window: &mut Window,
3939        _cx: &mut Context<Self>,
3940    ) -> Option<String> {
3941        let range = self.range_from_utf16(&range_utf16);
3942        adjusted_range.replace(self.range_to_utf16(&range));
3943        Some(self.text.slice(range).to_string())
3944    }
3945
3946    fn selected_text_range(
3947        &mut self,
3948        _ignore_disabled_input: bool,
3949        _window: &mut Window,
3950        _cx: &mut Context<Self>,
3951    ) -> Option<UTF16Selection> {
3952        Some(UTF16Selection {
3953            range: self.range_to_utf16(&self.selected_range()),
3954            reversed: self.active_selection().reversed,
3955        })
3956    }
3957
3958    fn marked_text_range(
3959        &self,
3960        _window: &mut Window,
3961        _cx: &mut Context<Self>,
3962    ) -> Option<Range<usize>> {
3963        self.ime_marked_range
3964            .map(|range| self.range_to_utf16(&range.into()))
3965    }
3966
3967    fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
3968        self.ime_marked_range = None;
3969        self.undo_manager.commit_transaction();
3970    }
3971
3972    /// Replace text in range.
3973    ///
3974    /// - If the new text is invalid, it will not be replaced.
3975    /// - If `range_utf16` is not provided, the current selected range will be used.
3976    fn replace_text_in_range(
3977        &mut self,
3978        range_utf16: Option<Range<usize>>,
3979        new_text: &str,
3980        window: &mut Window,
3981        cx: &mut Context<Self>,
3982    ) {
3983        let requested_intent = self.undo_manager.take_pending_intent();
3984        if !self.is_editable() {
3985            return;
3986        }
3987        let selection_before = *self.active_selection();
3988        // Committing a composition ends the transaction it opened, whether or
3989        // not the platform follows up with `unmark_text`.
3990        let ends_composition = self.ime_marked_range.is_some();
3991
3992        self.pause_blink_cursor(cx);
3993
3994        // NOTE: The normalization keeps the UTF-16 length, but may change the
3995        // UTF-8 byte length, so all the byte-offset calculations below must
3996        // use the normalized text.
3997        let new_text = if self.replaying_history {
3998            Cow::Borrowed(new_text)
3999        } else {
4000            self.normalize_input(new_text)
4001        };
4002        let new_text: &str = &new_text;
4003
4004        let range = range_utf16
4005            .as_ref()
4006            .map(|range_utf16| self.range_from_utf16(range_utf16))
4007            .or(self.ime_marked_range.map(|range| {
4008                let range = self.range_to_utf16(&(range.start..range.end));
4009                self.range_from_utf16(&range)
4010            }))
4011            .unwrap_or(self.selected_range());
4012        let range = self.normalize_token_range(range);
4013
4014        // Skip-over as a pure cursor move: a typed closer that already follows
4015        // the cursor moves past it without touching text or history, so Undo
4016        // never exposes a transient duplicate. Only for interactive single
4017        // keystrokes (current selection, no IME mark, not silent replays).
4018        // `take_pending_intent` above already consumed any request.
4019        if range.is_empty()
4020            && range.start == self.cursor()
4021            && self.ime_marked_range.is_none()
4022            && !self.silent_replace_text
4023            && self.is_code_editor()
4024            && self.selections.is_single()
4025            && self.active_selection().is_empty()
4026        {
4027            if let Some(target) = self.skip_over_target(new_text) {
4028                self.set_cursor_to(target);
4029                self.update_preferred_column();
4030                cx.notify();
4031                return;
4032            }
4033        }
4034
4035        if self.is_multi_line() {
4036            let multi_cursor = range_utf16.is_none()
4037                && self.ime_marked_range.is_none()
4038                && !self.selections.is_single();
4039            if multi_cursor {
4040                self.selections.merge_overlapping();
4041                let mut edits: Vec<(Range<usize>, String)> = self
4042                    .selections
4043                    .iter()
4044                    .map(|sel| (sel.start..sel.end, new_text.to_string()))
4045                    .collect();
4046                edits.sort_by_key(|(range, _)| range.start);
4047                // One keystroke across several cursors is one batch, committed
4048                // with the intent of the keystroke so a run of them coalesces
4049                // into one undo just like single-cursor typing.
4050                let intent =
4051                    requested_intent.unwrap_or_else(|| self.typing_intent(&edits, new_text));
4052                self.undo_manager.begin_transaction_with(intent);
4053                self.undo_manager.set_pending_intent(intent);
4054                self.replace_text_in_ranges(&edits, window, cx);
4055                self.undo_manager.commit_transaction();
4056            } else {
4057                if range_utf16.is_some() {
4058                    self.selections.remove_all_but_active();
4059                }
4060                if let Some(intent) = requested_intent {
4061                    self.undo_manager.set_pending_intent(intent);
4062                }
4063                if let Some((open_len, closer)) = self.auto_close_target(&range, new_text) {
4064                    // One edit keeps the pair atomic even at undo coalescing limits.
4065                    let replacement = format!("{new_text}{closer}");
4066                    self.replace_text_in_ranges(&[(range.clone(), replacement)], window, cx);
4067                    let cursor = range.start + new_text.len();
4068                    self.mode.track_auto_closed_pair(
4069                        cursor - open_len..cursor,
4070                        cursor..cursor + closer.len(),
4071                    );
4072                    self.undo_manager
4073                        .record_auto_closed_pairs_after(self.mode.auto_closed_pairs().clone());
4074                    self.set_cursor_to(cursor);
4075                    self.update_preferred_column();
4076                    self.undo_manager.record_selections(
4077                        vec![selection_before],
4078                        self.selections.iter().copied().collect(),
4079                    );
4080                } else {
4081                    self.replace_text_in_ranges(
4082                        &[(range.clone(), new_text.to_string())],
4083                        window,
4084                        cx,
4085                    );
4086                }
4087            }
4088            if ends_composition {
4089                self.undo_manager.commit_transaction();
4090            }
4091
4092            if !self.silent_replace_text {
4093                M::on_text_typed(self, &range, new_text, window, cx);
4094            }
4095            return;
4096        }
4097
4098        // Single-line path
4099        let old_text = self.text.clone();
4100        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4101        self.text.replace(range.clone(), new_text);
4102
4103        let mut new_offset = (range.start + new_text.len()).min(self.text.len());
4104
4105        // True if the mask has changed the text, e.g. regrouping the
4106        // separators or completing a leading dot.
4107        let mut mask_changed = false;
4108
4109        if self.is_single_line() {
4110            let pending_text = self.text.to_string();
4111            // Check if the new text is valid.
4112            //
4113            // Only reject the edit if the old text was valid, to avoid
4114            // trapping a pre-existing invalid text (e.g. a `default_value`
4115            // that does not conform), the user can still edit to fix it.
4116            if !self.replaying_history
4117                && !self.validated_token_edit
4118                && !self.is_valid_input(&pending_text, cx)
4119                && self.is_valid_input(&old_text.to_string(), cx)
4120            {
4121                self.text = old_text;
4122                return;
4123            }
4124
4125            if !self.replaying_history && !self.mask_pattern.is_none() {
4126                let mask_text = self.mask_pattern.mask(&pending_text);
4127                mask_changed = mask_text.as_str() != pending_text;
4128                self.text = Rope::from(mask_text.as_str());
4129                let new_text_len =
4130                    (new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
4131                new_offset = (range.start + new_text_len).min(mask_text.len());
4132            }
4133        }
4134
4135        if mask_changed {
4136            // Masking rewrites the whole document, so ranges recorded against
4137            // the old text no longer point at anything.
4138            M::reset_annotations(self);
4139        } else {
4140            M::adjust_annotations(self, &range, new_text.len());
4141        }
4142        if mask_changed {
4143            // A segment-based history entry no longer matches the masked
4144            // document, record a whole-document change instead, so that
4145            // undo/redo can restore the text exactly.
4146            self.push_history(
4147                &old_text,
4148                &(0..old_text.len()),
4149                &self.text.to_string(),
4150                Some(EditIntent::Atomic),
4151                selection_before,
4152                Some((new_offset..new_offset).into()),
4153            );
4154        } else {
4155            self.push_history(
4156                &old_text,
4157                &range,
4158                &new_text,
4159                requested_intent,
4160                selection_before,
4161                None,
4162            );
4163        }
4164        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4165            diagnostics.reset(&self.text)
4166        }
4167        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4168        self.display_map
4169            .adjust_folds_for_edit(&old_text, &range, new_text);
4170        self.display_map
4171            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4172
4173        self.mode.update_highlighter::<M>(
4174            super::mode::HighlighterUpdate {
4175                selected_range: &range,
4176                old_text: &old_text,
4177                new_text: &self.text,
4178                change_text: &new_text,
4179                force: true,
4180            },
4181            window,
4182            cx,
4183        );
4184
4185        self.update_fold_candidates_incremental(&range, new_text);
4186        M::refresh_language_features(self, window, cx);
4187        self.set_cursor_to(new_offset);
4188        self.ime_marked_range.take();
4189        // A commit ends the IME composition: macOS delivers `insertText:` for
4190        // the confirmed candidate without a following `unmarkText`, so close
4191        // the transaction here. Leaving it open would keep merging every later
4192        // edit into the same change, which then carries the text and selection
4193        // of the first composition.
4194        if ends_composition {
4195            self.undo_manager
4196                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4197            self.undo_manager.commit_transaction();
4198        }
4199        self.update_preferred_column();
4200        self.update_search(cx);
4201        self.dismiss_touch_selection(cx);
4202        if self.is_multi_line() {
4203            self.mode.update_auto_grow(&self.display_map);
4204        }
4205        if !self.silent_replace_text {
4206            M::on_text_typed(self, &range, &new_text, window, cx);
4207        }
4208        if self.emit_events {
4209            cx.emit(InputEvent::Change);
4210        }
4211        cx.notify();
4212    }
4213
4214    /// Mark text is the IME temporary insert on typing.
4215    fn replace_and_mark_text_in_range(
4216        &mut self,
4217        range_utf16: Option<Range<usize>>,
4218        new_text: &str,
4219        new_selected_range_utf16: Option<Range<usize>>,
4220        window: &mut Window,
4221        cx: &mut Context<Self>,
4222    ) {
4223        let requested_intent = self.undo_manager.take_pending_intent();
4224        if !self.is_editable() {
4225            return;
4226        }
4227        let selection_before = *self.active_selection();
4228
4229        let starts_composition = self.ime_marked_range.is_none();
4230        if starts_composition {
4231            // Even a canceled preedit separates the typing gestures on either
4232            // side; its no-op transaction must not reconnect those gestures.
4233            self.undo_manager.break_transaction_coalescing();
4234            self.undo_manager.begin_transaction();
4235        }
4236
4237        // Collapse any extra cursors so we never leave stale secondary cursors behind.
4238        self.selections.remove_all_but_active();
4239
4240        M::reset_language_features(self);
4241
4242        // See the same NOTE in `replace_text_in_range`.
4243        let new_text = if self.replaying_history {
4244            Cow::Borrowed(new_text)
4245        } else {
4246            self.normalize_input(new_text)
4247        };
4248        let new_text: &str = &new_text;
4249
4250        let range = range_utf16
4251            .as_ref()
4252            .map(|range_utf16| self.range_from_utf16(range_utf16))
4253            .or(self.ime_marked_range.map(|range| {
4254                let range = self.range_to_utf16(&(range.start..range.end));
4255                self.range_from_utf16(&range)
4256            }))
4257            .unwrap_or(self.selected_range());
4258        let range = self.normalize_token_range(range);
4259
4260        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
4261        let old_text = self.text.clone();
4262        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4263        self.text.replace(range.clone(), new_text);
4264
4265        if self.is_single_line() {
4266            let pending_text = self.text.to_string();
4267            // See the same NOTE in `replace_text_in_range`.
4268            if !self.replaying_history
4269                && !self.validated_token_edit
4270                && !self.is_valid_input(&pending_text, cx)
4271                && self.is_valid_input(&old_text.to_string(), cx)
4272            {
4273                self.text = old_text;
4274                if starts_composition {
4275                    self.undo_manager.commit_transaction();
4276                }
4277                return;
4278            }
4279        }
4280
4281        M::adjust_annotations(self, &range, new_text.len());
4282        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4283            diagnostics.reset(&self.text)
4284        }
4285        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4286        self.display_map
4287            .adjust_folds_for_edit(&old_text, &range, new_text);
4288        self.display_map
4289            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4290
4291        self.mode.update_highlighter::<M>(
4292            super::mode::HighlighterUpdate {
4293                selected_range: &range,
4294                old_text: &old_text,
4295                new_text: &self.text,
4296                change_text: &new_text,
4297                force: true,
4298            },
4299            window,
4300            cx,
4301        );
4302
4303        self.update_fold_candidates_incremental(&range, new_text);
4304        M::refresh_language_features(self, window, cx);
4305        if new_text.is_empty() {
4306            // Cancel selection, when cancel IME input.
4307            self.set_cursor_to(range.start);
4308            self.ime_marked_range = None;
4309        } else {
4310            self.ime_marked_range = Some((range.start..range.start + new_text.len()).into());
4311            let new_range = new_selected_range_utf16
4312                .as_ref()
4313                .map(|range_utf16| {
4314                    let new_text = Rope::from(new_text);
4315                    range.start + new_text.offset_utf16_to_offset(range_utf16.start)
4316                        ..range.start + new_text.offset_utf16_to_offset(range_utf16.end)
4317                })
4318                .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
4319            self.set_selection(new_range.start, new_range.end);
4320        }
4321        if self.is_multi_line() {
4322            self.mode.update_auto_grow(&self.display_map);
4323        }
4324        if self.push_history(
4325            &old_text,
4326            &range,
4327            new_text,
4328            requested_intent,
4329            selection_before,
4330            Some(*self.active_selection()),
4331        ) {
4332            self.undo_manager
4333                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4334            self.undo_manager.record_auto_closed_pairs(
4335                auto_closed_pairs_before,
4336                self.mode.auto_closed_pairs().clone(),
4337            );
4338        }
4339        if new_text.is_empty() {
4340            self.undo_manager.commit_transaction();
4341        }
4342        cx.notify();
4343    }
4344
4345    /// Used to position IME candidates.
4346    fn bounds_for_range(
4347        &mut self,
4348        range_utf16: Range<usize>,
4349        bounds: Bounds<Pixels>,
4350        _window: &mut Window,
4351        _cx: &mut Context<Self>,
4352    ) -> Option<Bounds<Pixels>> {
4353        let last_layout = self.last_layout.as_ref()?;
4354        let line_height = last_layout.line_height;
4355        let line_number_width = last_layout.line_number_width;
4356        let range = self.range_from_utf16(&range_utf16);
4357
4358        let mut start_origin = None;
4359        let mut end_origin = None;
4360        let line_number_origin = point(line_number_width, px(0.));
4361        let mut y_offset = last_layout.visible_top;
4362
4363        for (vi, line) in last_layout.lines.iter().enumerate() {
4364            if start_origin.is_some() && end_origin.is_some() {
4365                break;
4366            }
4367
4368            let index_offset = last_layout.visible_line_byte_offsets[vi];
4369
4370            if start_origin.is_none() {
4371                if let Some(p) = line.position_for_index(
4372                    range.start.saturating_sub(index_offset),
4373                    last_layout,
4374                    false,
4375                ) {
4376                    start_origin = Some(p + point(px(0.), y_offset));
4377                }
4378            }
4379
4380            if end_origin.is_none() {
4381                if let Some(p) = line.position_for_index(
4382                    range.end.saturating_sub(index_offset),
4383                    last_layout,
4384                    false,
4385                ) {
4386                    end_origin = Some(p + point(px(0.), y_offset));
4387                }
4388            }
4389
4390            y_offset += line.size(line_height).height;
4391        }
4392
4393        let start_origin = start_origin.or_else(|| {
4394            let offset = self.last_cursor.or(Some(self.cursor()))?;
4395            let (_, _, origin) = self.line_and_position_for_offset(offset);
4396            origin.map(|origin| origin - line_number_origin)
4397        })?;
4398        let mut end_origin = end_origin.unwrap_or(start_origin);
4399        // Ensure at same line.
4400        end_origin.y = start_origin.y;
4401
4402        Some(Bounds::from_corners(
4403            bounds.origin + line_number_origin + start_origin,
4404            // + line_height for show IME panel under the cursor line.
4405            bounds.origin + line_number_origin + point(end_origin.x, end_origin.y + line_height),
4406        ))
4407    }
4408
4409    fn character_index_for_point(
4410        &mut self,
4411        point: gpui::Point<Pixels>,
4412        _window: &mut Window,
4413        _cx: &mut Context<Self>,
4414    ) -> Option<usize> {
4415        let last_layout = self.last_layout.as_ref()?;
4416        let line_point = self.last_bounds?.localize(&point)?;
4417
4418        for (vi, line) in last_layout.lines.iter().enumerate() {
4419            let offset = last_layout.visible_line_byte_offsets[vi];
4420            if let Some(utf8_index) = line.index_for_position(line_point, last_layout) {
4421                return Some(self.offset_to_utf16(offset + utf8_index));
4422            }
4423        }
4424
4425        None
4426    }
4427}
4428
4429impl<M: InputModeKind> Focusable for InputBaseState<M> {
4430    fn focus_handle(&self, _cx: &App) -> FocusHandle {
4431        self.focus_handle.clone()
4432    }
4433}
4434
4435impl<M: InputModeKind> Render for InputBaseState<M> {
4436    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
4437        // Before anything reads it: the element resolves this style during
4438        // layout and paint, and both happen after this call in the same frame.
4439        self.editor_style = self
4440            .projected_editor_style
4441            .resolved(&crate::Theme::global(cx).tokens);
4442        let entity = cx.entity();
4443        if self._pending_update {
4444            self.mode.update_highlighter::<M>(
4445                super::mode::HighlighterUpdate {
4446                    selected_range: &(0..0),
4447                    old_text: &self.text,
4448                    new_text: &self.text,
4449                    change_text: "",
4450                    force: false,
4451                },
4452                window,
4453                cx,
4454            );
4455
4456            self.update_fold_candidates();
4457            M::refresh_language_features(self, window, cx);
4458            self._pending_update = false;
4459        }
4460
4461        let element = div()
4462            .id("input-state")
4463            .key_context(CONTEXT)
4464            .track_focus(&self.focus_handle)
4465            .when(self.is_editable(), |this| {
4466                this.on_action(window.listener_for(&entity, InputBaseState::backspace))
4467                    .on_action(window.listener_for(&entity, InputBaseState::delete))
4468                    .on_action(
4469                        window.listener_for(&entity, InputBaseState::delete_to_beginning_of_line),
4470                    )
4471                    .on_action(window.listener_for(&entity, InputBaseState::delete_to_end_of_line))
4472                    .on_action(window.listener_for(&entity, InputBaseState::delete_previous_word))
4473                    .on_action(window.listener_for(&entity, InputBaseState::delete_next_word))
4474                    .on_action(window.listener_for(&entity, InputBaseState::enter))
4475                    .on_action(window.listener_for(&entity, InputBaseState::escape))
4476                    .on_action(window.listener_for(&entity, InputBaseState::paste))
4477                    .on_action(window.listener_for(&entity, InputBaseState::cut))
4478                    .on_action(window.listener_for(&entity, InputBaseState::undo))
4479                    .on_action(window.listener_for(&entity, InputBaseState::redo))
4480                    .when(self.is_multi_line(), |this| {
4481                        this.on_action(window.listener_for(&entity, InputBaseState::indent_inline))
4482                            .on_action(window.listener_for(&entity, InputBaseState::outdent_inline))
4483                            .on_action(window.listener_for(&entity, InputBaseState::indent_block))
4484                            .on_action(window.listener_for(&entity, InputBaseState::outdent_block))
4485                    })
4486            })
4487            .on_action(window.listener_for(&entity, InputBaseState::left))
4488            .on_action(window.listener_for(&entity, InputBaseState::right))
4489            .on_action(window.listener_for(&entity, InputBaseState::select_left))
4490            .on_action(window.listener_for(&entity, InputBaseState::select_right))
4491            .when(self.is_multi_line(), |this| {
4492                this.on_action(window.listener_for(&entity, InputBaseState::up))
4493                    .on_action(window.listener_for(&entity, InputBaseState::down))
4494                    .on_action(window.listener_for(&entity, InputBaseState::select_up))
4495                    .on_action(window.listener_for(&entity, InputBaseState::select_down))
4496                    .on_action(window.listener_for(&entity, InputBaseState::page_up))
4497                    .on_action(window.listener_for(&entity, InputBaseState::page_down))
4498                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_above))
4499                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_below))
4500            })
4501            .on_action(window.listener_for(&entity, InputBaseState::on_action_select_all))
4502            .on_action(window.listener_for(&entity, InputBaseState::select_to_start_of_line))
4503            .on_action(window.listener_for(&entity, InputBaseState::select_to_end_of_line))
4504            .on_action(window.listener_for(&entity, InputBaseState::select_to_previous_word))
4505            .on_action(window.listener_for(&entity, InputBaseState::select_to_next_word))
4506            .on_action(window.listener_for(&entity, InputBaseState::home))
4507            .on_action(window.listener_for(&entity, InputBaseState::end))
4508            .on_action(window.listener_for(&entity, InputBaseState::move_to_start))
4509            .on_action(window.listener_for(&entity, InputBaseState::move_to_end))
4510            .on_action(window.listener_for(&entity, InputBaseState::move_to_previous_word))
4511            .on_action(window.listener_for(&entity, InputBaseState::move_to_next_word))
4512            .on_action(window.listener_for(&entity, InputBaseState::select_to_start))
4513            .on_action(window.listener_for(&entity, InputBaseState::select_to_end))
4514            .on_action(window.listener_for(&entity, InputBaseState::show_character_palette))
4515            .on_action({
4516                let entity = entity.clone();
4517                move |_: &ActivateToken, window, cx| {
4518                    let state = entity.read(cx);
4519                    let activation = state
4520                        .token_spans()
4521                        .iter()
4522                        .find(|span| span.range() == state.selected_range())
4523                        .and_then(|span| {
4524                            state.range_to_bounds(&span.range()).and_then(|bounds| {
4525                                state.token_activation(
4526                                    span.range().start,
4527                                    bounds,
4528                                    gpui::ClickEvent::Keyboard(gpui::KeyboardClickEvent {
4529                                        bounds,
4530                                        ..Default::default()
4531                                    }),
4532                                )
4533                            })
4534                        });
4535                    if let Some((listener, event)) = activation {
4536                        listener(&event, window, cx);
4537                    }
4538                }
4539            })
4540            .on_action(window.listener_for(&entity, InputBaseState::copy))
4541            .on_action(window.listener_for(&entity, InputBaseState::on_action_search))
4542            .on_action(window.listener_for(&entity, InputBaseState::on_action_replace))
4543            .on_mouse_down(
4544                MouseButton::Left,
4545                window.listener_for(&entity, InputBaseState::on_mouse_down),
4546            )
4547            .on_mouse_down(
4548                MouseButton::Right,
4549                window.listener_for(&entity, InputBaseState::on_mouse_down),
4550            )
4551            .on_mouse_up(
4552                MouseButton::Left,
4553                window.listener_for(&entity, InputBaseState::on_mouse_up),
4554            )
4555            .on_mouse_up(
4556                MouseButton::Right,
4557                window.listener_for(&entity, InputBaseState::on_mouse_up),
4558            )
4559            .on_mouse_move(window.listener_for(&entity, InputBaseState::on_mouse_move))
4560            .on_scroll_wheel(window.listener_for(&entity, InputBaseState::on_scroll_wheel))
4561            .when(self.is_multi_line() && !self.disabled, |this| {
4562                this.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
4563            })
4564            .when(!self.disabled, |this| {
4565                if self.is_multi_line() && window.modifiers().alt {
4566                    this.cursor_crosshair()
4567                } else {
4568                    this.cursor_text()
4569                }
4570            })
4571            .flex_1()
4572            // A single line fills the frame and sits at its vertical center,
4573            // so the frame needs no layout of its own to hold it. It is never
4574            // shorter than its line, though: this root clips (overflow-x
4575            // hidden clips both axes), and a frame whose padding leaves less
4576            // than a line, like Component's Input, would cut off descenders.
4577            .map(|this| {
4578                if self.is_multi_line() {
4579                    this.h_full()
4580                } else {
4581                    this.min_h_full().flex().items_center()
4582                }
4583            })
4584            .flex_grow_1()
4585            .overflow_x_hidden()
4586            .when(self.is_multi_line(), |this| {
4587                this.pt(self.editor_paddings.top)
4588                    .pr(self.editor_paddings.right)
4589                    .pb(self.editor_paddings.bottom)
4590                    .pl(self.editor_paddings.left)
4591            })
4592            .child(TextElement::new(entity.clone()).placeholder(self.placeholder.clone()))
4593            .when(self.shows_scrollbar(), |this| {
4594                this.child(EditorScrollbar::new(entity.clone()))
4595            });
4596
4597        // Actions only one mode handles are registered by that mode, where
4598        // `Self` is concrete enough to name its own entity type.
4599        M::register_actions(element, &entity, window)
4600    }
4601}
4602
4603#[cfg(test)]
4604mod tests {
4605    use super::*;
4606
4607    #[gpui::test]
4608    fn test_multicursor_unicode_completion_uses_post_edit_start(cx: &mut TestAppContext) {
4609        use crate::input::{CompletionProvider, EditorState};
4610        use gpui::Task;
4611        use lsp_types::{CompletionContext, CompletionResponse};
4612        use std::cell::RefCell;
4613
4614        #[derive(Default)]
4615        struct Provider {
4616            starts: RefCell<Vec<usize>>,
4617            queries: RefCell<Vec<(usize, String)>>,
4618        }
4619
4620        impl CompletionProvider for Provider {
4621            fn is_completion_trigger(&self, offset: usize, _: &str, _: &mut App) -> bool {
4622                self.starts.borrow_mut().push(offset);
4623                true
4624            }
4625
4626            fn completions(
4627                &self,
4628                _: &Rope,
4629                offset: usize,
4630                trigger: CompletionContext,
4631                _: &mut Window,
4632                _: &mut App,
4633            ) -> Task<anyhow::Result<CompletionResponse>> {
4634                self.queries
4635                    .borrow_mut()
4636                    .push((offset, trigger.trigger_character.unwrap()));
4637                Task::ready(Ok(CompletionResponse::Array(vec![])))
4638            }
4639        }
4640
4641        cx.update(crate::init);
4642        let mut editor = None;
4643        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
4644            editor = Some(cx.new(|cx| EditorState::new(window, cx).default_value("aa")));
4645            gpui::EmptyView
4646        });
4647        let editor = editor.unwrap();
4648        let provider = Rc::new(Provider::default());
4649        window
4650            .update(cx, |_, window, cx| {
4651                editor.update(cx, |state, cx| {
4652                    state.extras.lsp.completion_provider = Some(provider.clone());
4653                    state
4654                        .extras
4655                        .context_menu_content
4656                        .completion
4657                        .trigger_start_offset = Some(0);
4658                    state.extras.context_menu_content.completion.query = "aa".into();
4659                    state.set_cursor_to(1);
4660                    state.add_cursor_at(0, cx);
4661
4662                    state.replace_text_in_range(None, "中", window, cx);
4663
4664                    assert_eq!(state.value(), "中a中a");
4665                    assert_eq!(provider.starts.borrow().as_slice(), &[4]);
4666                    assert_eq!(provider.queries.borrow().as_slice(), &[(7, "中".into())]);
4667                });
4668            })
4669            .unwrap();
4670    }
4671
4672    #[gpui::test]
4673    fn test_inline_token_wrap_and_size_refresh(cx: &mut TestAppContext) {
4674        use crate::input::{InlineToken, InlineTokenPresentation};
4675        let width = Rc::new(Cell::new(4000.));
4676        let render_width = width.clone();
4677        let view = InputView::build_textarea(cx, |state| state.rows(4).default_value("@a@b"));
4678        view.window_handle
4679            .update(cx, |_, window, cx| {
4680                view.input.update(cx, |state, cx| {
4681                    state
4682                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
4683                        .unwrap();
4684                    state
4685                        .replace_range_with_token(2..4, InlineToken::new("b", "@b"), window, cx)
4686                        .unwrap();
4687                    state.set_token_presentation(
4688                        InlineTokenPresentation::default()
4689                            .token(move |_, _, _| div().w(px(render_width.get()))),
4690                    );
4691                });
4692            })
4693            .unwrap();
4694        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
4695        visual.update(|window, cx| window.draw(cx).clear(cx));
4696        view.input.read_with(&visual, |state, _| {
4697            assert_eq!(state.display_map.wrap_row_count(), 2);
4698            let first = state.range_to_bounds(&(0..2)).unwrap();
4699            let second = state.range_to_bounds(&(2..4)).unwrap();
4700            assert!(second.origin.y > first.origin.y);
4701            assert_eq!(
4702                state.last_layout.as_ref().unwrap().lines[0].wrapped_lines[0].len,
4703                2
4704            );
4705        });
4706        width.set(100.);
4707        view.input.update(&mut visual, |_, cx| cx.notify());
4708        visual.update(|window, cx| window.draw(cx).clear(cx));
4709        view.input.read_with(&visual, |state, _| {
4710            assert_eq!(state.display_map.wrap_row_count(), 1)
4711        });
4712    }
4713
4714    #[gpui::test]
4715    fn test_inline_token_geometry_and_reentrant_activation(cx: &mut TestAppContext) {
4716        use crate::input::{InlineToken, InlineTokenPresentation};
4717        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
4718        let target = view.input.clone();
4719        view.window_handle
4720            .update(cx, |_, window, cx| {
4721                view.input.update(cx, |state, cx| {
4722                    state
4723                        .replace_range_with_token(
4724                            7..13,
4725                            InlineToken::new("a", "@alice"),
4726                            window,
4727                            cx,
4728                        )
4729                        .unwrap();
4730                    state.set_token_presentation(
4731                        InlineTokenPresentation::default()
4732                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
4733                            .on_token_click(move |_, window, cx| {
4734                                target.update(cx, |state, cx| {
4735                                    state.set_value("activated", window, cx)
4736                                })
4737                            }),
4738                    );
4739                    state.focus(window, cx);
4740                    state.set_selected_range(7..13, cx);
4741                });
4742            })
4743            .unwrap();
4744        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
4745        visual.update(|window, cx| window.draw(cx).clear(cx));
4746        visual.update(|window, cx| {
4747            let state = view.input.read(cx);
4748            let bounds = state.range_to_bounds(&(7..13)).unwrap();
4749            assert!((bounds.size.width - px(100.)).abs() < px(1.), "{bounds:?}");
4750            let (left, _) = state.index_for_mouse_position(bounds.origin + point(px(10.), px(5.)));
4751            let (right, _) = state.index_for_mouse_position(bounds.origin + point(px(90.), px(5.)));
4752            assert_eq!((left, right), (7, 13));
4753            window.dispatch_action(Box::new(ActivateToken), cx);
4754        });
4755        visual.run_until_parked();
4756        view.input.read_with(&visual, |state, _| {
4757            assert_eq!(state.value().as_ref(), "activated")
4758        });
4759    }
4760
4761    #[gpui::test]
4762    fn test_inline_token_click_selects_it(cx: &mut TestAppContext) {
4763        use crate::input::InlineToken;
4764        cx.update(crate::init);
4765        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
4766        view.window_handle
4767            .update(cx, |_, window, cx| {
4768                view.input.update(cx, |state, cx| {
4769                    state
4770                        .replace_range_with_token(
4771                            7..13,
4772                            InlineToken::new("a", "@alice"),
4773                            window,
4774                            cx,
4775                        )
4776                        .unwrap();
4777                    state.set_selected_range(0..0, cx);
4778                });
4779            })
4780            .unwrap();
4781        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
4782        visual.update(|window, cx| window.draw(cx).clear(cx));
4783        let bounds = view
4784            .input
4785            .read_with(&visual, |state, _| state.range_to_bounds(&(7..13)).unwrap());
4786        visual.simulate_click(bounds.center(), gpui::Modifiers::default());
4787        visual.run_until_parked();
4788        view.input.read_with(&visual, |state, _| {
4789            assert_eq!(state.selected_range(), 7..13, "a click selects the token");
4790        });
4791    }
4792
4793    #[gpui::test]
4794    fn test_inline_token_hover_reports_presence_without_selecting(cx: &mut TestAppContext) {
4795        use crate::input::{InlineToken, InlineTokenPresentation};
4796        use gpui::{Bounds, point, px, size};
4797        cx.update(crate::init);
4798        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
4799        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
4800        view.window_handle
4801            .update(cx, |_, window, cx| {
4802                view.input.update(cx, |state, cx| {
4803                    state
4804                        .replace_range_with_token(
4805                            7..13,
4806                            InlineToken::new("a", "@alice"),
4807                            window,
4808                            cx,
4809                        )
4810                        .unwrap();
4811                    state.set_token_presentation(
4812                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
4813                    );
4814                    state.set_selected_range(0..0, cx);
4815                });
4816            })
4817            .unwrap();
4818        view.window_handle
4819            .update(cx, |_, _, cx| {
4820                view.input.update(cx, |state, _| {
4821                    let (_, entered) = state
4822                        .token_hover(7, bounds, true, None)
4823                        .expect("hover enters an enabled token");
4824                    assert!(entered.is_hovered());
4825                    assert_eq!(entered.token().id().as_ref(), "a");
4826                    assert_eq!(entered.range(), 7..13);
4827                    assert_eq!(entered.bounds(), bounds);
4828                    let (_, left) = state
4829                        .token_hover(7, bounds, false, None)
4830                        .expect("hover leaves the same token");
4831                    assert!(!left.is_hovered());
4832                    assert_eq!(state.selected_range(), 0..0, "hover never selects");
4833                    assert!(state.token_hover(99, bounds, true, None).is_none());
4834                });
4835            })
4836            .unwrap();
4837
4838        // Disabled tokens suppress hover, matching click; readonly allows it.
4839        view.window_handle
4840            .update(cx, |_, _, cx| {
4841                view.input.update(cx, |state, cx| {
4842                    state.set_disabled(true, cx);
4843                });
4844            })
4845            .unwrap();
4846        view.window_handle
4847            .update(cx, |_, _, cx| {
4848                view.input.update(cx, |state, _| {
4849                    assert!(state.token_hover(7, bounds, true, None).is_none())
4850                })
4851            })
4852            .unwrap();
4853        view.window_handle
4854            .update(cx, |_, _, cx| {
4855                view.input.update(cx, |state, cx| {
4856                    state.set_disabled(false, cx);
4857                    state.set_readonly(true, cx);
4858                });
4859            })
4860            .unwrap();
4861        view.window_handle
4862            .update(cx, |_, _, cx| {
4863                view.input.update(cx, |state, _| {
4864                    assert!(state.token_hover(7, bounds, true, None).is_some())
4865                })
4866            })
4867            .unwrap();
4868
4869        // Without a hover listener there is no hover activation.
4870        view.window_handle
4871            .update(cx, |_, _, cx| {
4872                view.input.update(cx, |state, _| {
4873                    state.set_token_presentation(InlineTokenPresentation::default());
4874                });
4875            })
4876            .unwrap();
4877        view.window_handle
4878            .update(cx, |_, _, cx| {
4879                view.input.update(cx, |state, _| {
4880                    assert!(state.token_hover(7, bounds, true, None).is_none())
4881                })
4882            })
4883            .unwrap();
4884    }
4885
4886    #[gpui::test]
4887    fn test_inline_token_hover_exit_delivered_when_token_removed(cx: &mut TestAppContext) {
4888        use crate::input::{InlineToken, InlineTokenPresentation};
4889        use std::cell::RefCell;
4890        use std::rc::Rc;
4891        cx.update(crate::init);
4892        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
4893        let events: Rc<RefCell<Vec<(SharedString, bool)>>> = Rc::new(RefCell::new(Vec::new()));
4894        let sink = events.clone();
4895        view.window_handle
4896            .update(cx, |_, window, cx| {
4897                view.input.update(cx, |state, cx| {
4898                    state
4899                        .replace_range_with_token(
4900                            7..13,
4901                            InlineToken::new("a", "@alice"),
4902                            window,
4903                            cx,
4904                        )
4905                        .unwrap();
4906                    state.set_token_presentation(
4907                        InlineTokenPresentation::default()
4908                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
4909                            .on_token_hover(move |event, _, _| {
4910                                sink.borrow_mut()
4911                                    .push((event.token().id().clone(), event.is_hovered()));
4912                            }),
4913                    );
4914                });
4915            })
4916            .unwrap();
4917        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
4918        visual.update(|window, cx| window.draw(cx).clear(cx));
4919        let known_bounds = view.input.read_with(&visual, |state, _| {
4920            state
4921                .token_bounds
4922                .get(&7)
4923                .copied()
4924                .expect("placed token bounds")
4925        });
4926        visual.simulate_mouse_move(known_bounds.center(), None, gpui::Modifiers::default());
4927        visual.run_until_parked();
4928        visual.update(|window, cx| window.draw(cx).clear(cx));
4929        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
4930
4931        // Remove the token while the pointer is still over its stale row: the
4932        // exit must fire even though the token element that owns the hover
4933        // mask is gone.
4934        view.window_handle
4935            .update(cx, |_, window, cx| {
4936                view.input
4937                    .update(cx, |state, cx| state.set_value("no tokens", window, cx));
4938            })
4939            .unwrap();
4940        visual.run_until_parked();
4941        visual.update(|window, cx| window.draw(cx).clear(cx));
4942        assert_eq!(
4943            events.borrow().as_slice(),
4944            &[("a".into(), true), ("a".into(), false)]
4945        );
4946    }
4947
4948    #[gpui::test]
4949    fn test_inline_token_hover_bounds_stay_single_row_at_wrap_boundary(cx: &mut TestAppContext) {
4950        use crate::input::{InlineToken, InlineTokenPresentation};
4951        cx.update(crate::init);
4952        let view = InputView::build_textarea(cx, |state| {
4953            state
4954                .rows(4)
4955                .default_value("aaaa bbbb cccc dddd @alice eeee ffff")
4956        });
4957        view.window_handle
4958            .update(cx, |_, window, cx| {
4959                view.input.update(cx, |state, cx| {
4960                    state
4961                        .replace_range_with_token(
4962                            20..26,
4963                            InlineToken::new("a", "@alice"),
4964                            window,
4965                            cx,
4966                        )
4967                        .unwrap();
4968                    state.set_token_presentation(
4969                        InlineTokenPresentation::default()
4970                            .token(|_, _, _| div().w(px(60.)).h(px(20.))),
4971                    );
4972                });
4973            })
4974            .unwrap();
4975        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
4976        visual.update(|window, cx| window.draw(cx).clear(cx));
4977        let (placed, ranged, line_height) = view.input.read_with(&visual, |state, _| {
4978            (
4979                state
4980                    .token_bounds
4981                    .get(&20)
4982                    .copied()
4983                    .expect("placed token bounds"),
4984                state.range_to_bounds(&(20..26)),
4985                state.last_layout.as_ref().unwrap().line_height,
4986            )
4987        });
4988        // The laid-out element always occupies exactly one row of height, even
4989        // when the range-extent path resolves the same end boundary onto the
4990        // next visual row at a soft-wrap edge.
4991        assert!(placed.size.height <= line_height + px(1.), "{placed:?}");
4992        assert!(
4993            placed.size.width <= px(61.) && placed.size.width > px(0.),
4994            "{placed:?}"
4995        );
4996        if let Some(ranged) = ranged {
4997            assert!(
4998                ranged.size.height >= placed.size.height - px(1.),
4999                "{ranged:?} vs {placed:?}"
5000            );
5001        }
5002    }
5003
5004    #[gpui::test]
5005    fn test_inline_token_hover_same_offset_replacement_exits_predecessor(cx: &mut TestAppContext) {
5006        use crate::input::{InlineToken, InlineTokenPresentation};
5007        use std::{cell::RefCell, rc::Rc};
5008        cx.update(crate::init);
5009        let view = InputView::build(cx, |state| state.default_value("@alice!"));
5010        let events = Rc::new(RefCell::new(Vec::new()));
5011        let sink = events.clone();
5012        view.window_handle
5013            .update(cx, |_, window, cx| {
5014                view.input.update(cx, |state, cx| {
5015                    state
5016                        .replace_range_with_token(0..6, InlineToken::new("a", "@alice"), window, cx)
5017                        .unwrap();
5018                    state.set_token_presentation(
5019                        InlineTokenPresentation::default()
5020                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5021                            .on_token_hover(move |event, _, _| {
5022                                sink.borrow_mut()
5023                                    .push((event.token().id().clone(), event.is_hovered()));
5024                            }),
5025                    );
5026                });
5027            })
5028            .unwrap();
5029        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5030        visual.update(|window, cx| window.draw(cx).clear(cx));
5031        let bounds = view
5032            .input
5033            .read_with(&visual, |state, _| state.token_bounds[&0]);
5034        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5035        visual.run_until_parked();
5036        visual.update(|window, cx| window.draw(cx).clear(cx));
5037        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5038
5039        view.window_handle
5040            .update(cx, |_, window, cx| {
5041                view.input.update(cx, |state, cx| {
5042                    state
5043                        .replace_range_with_token(0..6, InlineToken::new("b", "@alice"), window, cx)
5044                        .unwrap();
5045                });
5046            })
5047            .unwrap();
5048        visual.run_until_parked();
5049        visual.update(|window, cx| window.draw(cx).clear(cx));
5050        visual.simulate_mouse_move(
5051            bounds.center() + gpui::point(px(1.), px(0.)),
5052            None,
5053            gpui::Modifiers::default(),
5054        );
5055        visual.run_until_parked();
5056        visual.update(|window, cx| window.draw(cx).clear(cx));
5057        assert_eq!(
5058            events.borrow().as_slice(),
5059            &[("a".into(), true), ("a".into(), false), ("b".into(), true)]
5060        );
5061        // Disabling keeps the token mounted; re-enabling must reset GPUI's
5062        // retained hover state even with a stationary pointer.
5063        view.input
5064            .update(&mut visual, |state, cx| state.set_disabled(true, cx));
5065        visual.update(|window, cx| window.draw(cx).clear(cx));
5066        visual.run_until_parked();
5067        assert_eq!(events.borrow().last(), Some(&("b".into(), false)));
5068        view.input
5069            .update(&mut visual, |state, cx| state.set_disabled(false, cx));
5070        visual.update(|window, cx| window.draw(cx).clear(cx));
5071        visual.run_until_parked();
5072        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5073        visual.run_until_parked();
5074        assert_eq!(
5075            events.borrow().as_slice(),
5076            &[
5077                ("a".into(), true),
5078                ("a".into(), false),
5079                ("b".into(), true),
5080                ("b".into(), false),
5081                ("b".into(), true)
5082            ]
5083        );
5084        visual.simulate_mouse_move(
5085            bounds.bottom_right() + gpui::point(px(10.), px(10.)),
5086            None,
5087            gpui::Modifiers::default(),
5088        );
5089        visual.run_until_parked();
5090        assert_eq!(
5091            events.borrow().as_slice(),
5092            &[
5093                ("a".into(), true),
5094                ("a".into(), false),
5095                ("b".into(), true),
5096                ("b".into(), false),
5097                ("b".into(), true),
5098                ("b".into(), false)
5099            ]
5100        );
5101    }
5102
5103    #[gpui::test]
5104    fn test_inline_token_hover_older_exit_keeps_newer_snapshot(cx: &mut TestAppContext) {
5105        use crate::input::{InlineToken, InlineTokenPresentation};
5106        use gpui::{Bounds, point, px, size};
5107        cx.update(crate::init);
5108        let view = InputView::build(cx, |state| state.default_value("@a @b"));
5109        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
5110        view.window_handle
5111            .update(cx, |_, window, cx| {
5112                view.input.update(cx, |state, cx| {
5113                    state
5114                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5115                        .unwrap();
5116                    state
5117                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
5118                        .unwrap();
5119                    state.set_token_presentation(
5120                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5121                    );
5122                    // The pointer moves A -> B; GPUI dispatches B's enter
5123                    // before A's exit.
5124                    state
5125                        .token_hover(0, bounds, true, None)
5126                        .expect("hover enters A");
5127                    state
5128                        .token_hover(3, bounds, true, None)
5129                        .expect("hover enters B");
5130                    let (_, exit_a) = state.token_hover(0, bounds, false, None).expect("A exits");
5131                    assert!(!exit_a.is_hovered());
5132                    // B's snapshot must survive A's late exit.
5133                    assert_eq!(
5134                        state
5135                            .hovered_token
5136                            .as_ref()
5137                            .expect("B still hovered")
5138                            .span
5139                            .token()
5140                            .id()
5141                            .as_ref(),
5142                        "b"
5143                    );
5144                    // Removing B afterwards still produces B's reconciled exit.
5145                    state.set_value("no tokens", window, cx);
5146                    state.token_bounds.clear();
5147                    let (listener, exit_b) = state.reconcile_token_hover().expect("B must exit");
5148                    assert!(!exit_b.is_hovered());
5149                    assert_eq!(exit_b.token().id().as_ref(), "b");
5150                    drop(listener);
5151                });
5152            })
5153            .unwrap();
5154    }
5155
5156    #[gpui::test]
5157    fn test_inline_token_hover_late_exit_matches_replaced_token(cx: &mut TestAppContext) {
5158        use crate::input::{InlineToken, InlineTokenPresentation};
5159        cx.update(crate::init);
5160        let view = InputView::build(cx, |state| state.default_value("@a"));
5161        view.window_handle
5162            .update(cx, |_, window, cx| {
5163                view.input.update(cx, |state, cx| {
5164                    let a = InlineToken::new("a", "@a");
5165                    let b = InlineToken::new("b", "@a");
5166                    state
5167                        .replace_range_with_token(0..2, a.clone(), window, cx)
5168                        .unwrap();
5169                    state.set_token_presentation(
5170                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5171                    );
5172                    let bounds = gpui::Bounds::default();
5173                    state.token_hover(0, bounds, true, Some(&a)).unwrap();
5174                    state
5175                        .replace_range_with_token(0..2, b.clone(), window, cx)
5176                        .unwrap();
5177                    state.token_hover(0, bounds, true, Some(&b)).unwrap();
5178                    let (_, exit_a) = state.token_hover(0, bounds, false, Some(&a)).unwrap();
5179                    assert_eq!(exit_a.token().id().as_ref(), "a");
5180                    assert_eq!(state.hovered_token.as_ref().unwrap().span.token(), &b);
5181                    let (_, exit_b) = state.token_hover(0, bounds, false, Some(&b)).unwrap();
5182                    assert_eq!(exit_b.token().id().as_ref(), "b");
5183                    assert!(state.pending_token_hover_exits.is_empty());
5184                });
5185            })
5186            .unwrap();
5187    }
5188
5189    #[gpui::test]
5190    fn test_inline_token_hover_exit_keeps_entry_utf16_after_edit(cx: &mut TestAppContext) {
5191        use crate::input::{InlineToken, InlineTokenPresentation};
5192        use gpui::EntityInputHandler as _;
5193        cx.update(crate::init);
5194        let view = InputView::build(cx, |state| state.default_value("🙂 @a"));
5195        view.window_handle
5196            .update(cx, |_, window, cx| {
5197                view.input.update(cx, |state, cx| {
5198                    state
5199                        .replace_range_with_token(5..7, InlineToken::new("a", "@a"), window, cx)
5200                        .unwrap();
5201                    state.set_token_presentation(
5202                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5203                    );
5204                    let bounds = gpui::Bounds::default();
5205                    let (_, enter) = state.token_hover(5, bounds, true, None).unwrap();
5206                    assert_eq!(enter.range_utf16(), (3, 5));
5207                    state.replace_text_in_range(Some(0..2), "abcd", window, cx);
5208                    assert_eq!(state.token_spans()[0].range(), 5..7);
5209                    let (_, exit) = state.token_hover(5, bounds, false, None).unwrap();
5210                    assert_eq!(exit.range_utf16(), (3, 5));
5211                });
5212            })
5213            .unwrap();
5214    }
5215
5216    #[gpui::test]
5217    fn test_inline_token_hover_callback_edit_preserves_both_exits(cx: &mut TestAppContext) {
5218        use crate::input::{InlineToken, InlineTokenPresentation};
5219        cx.update(crate::init);
5220        let view = InputView::build(cx, |state| state.default_value("@a @b"));
5221        view.window_handle
5222            .update(cx, |_, window, cx| {
5223                view.input.update(cx, |state, cx| {
5224                    state
5225                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5226                        .unwrap();
5227                    state
5228                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
5229                        .unwrap();
5230                    state.set_token_presentation(
5231                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5232                    );
5233                    let bounds = gpui::Bounds::default();
5234                    state.token_hover(0, bounds, true, None).unwrap();
5235                    state.token_hover(3, bounds, true, None).unwrap();
5236                    // Model an edit in B's enter callback before A's queued exit.
5237                    state.set_value("gone", window, cx);
5238                    let (_, exit_a) = state.token_hover(0, bounds, false, None).unwrap();
5239                    assert_eq!(exit_a.token().id().as_ref(), "a");
5240                    let (_, exit_b) = state.reconcile_token_hover().unwrap();
5241                    assert_eq!(exit_b.token().id().as_ref(), "b");
5242                    assert!(state.token_hover(0, bounds, false, None).is_none());
5243                    assert!(state.reconcile_token_hover().is_none());
5244                });
5245            })
5246            .unwrap();
5247    }
5248
5249    #[gpui::test]
5250    fn test_inline_token_edit_history_and_validation(cx: &mut TestAppContext) {
5251        let view = InputView::build(cx, |state| state.default_value("问 @alice!"));
5252        view.window_handle
5253            .update(cx, |_, window, cx| {
5254                view.input.update(cx, |state, cx| {
5255                    let token =
5256                        crate::input::InlineToken::new("alice-1", "@alice").with_label("Alice");
5257                    state
5258                        .replace_range_with_token(4..10, token.clone(), window, cx)
5259                        .unwrap();
5260                    assert_eq!(state.value().as_ref(), "问 @alice!");
5261                    assert_eq!(state.tokens()[0].range(), 4..10);
5262                    assert_eq!(state.next_end_of_word_at(4), 10);
5263                    assert_eq!(state.previous_start_of_word_at(10), 4);
5264                    state.undo(&Undo, window, cx);
5265                    assert!(
5266                        state.tokens().is_empty(),
5267                        "identity-only association is undoable"
5268                    );
5269                    assert_eq!(state.value().as_ref(), "问 @alice!");
5270                    state.redo(&Redo, window, cx);
5271                    assert_eq!(state.tokens()[0].token(), &token);
5272                    state.set_selected_range(6..7, cx);
5273                    assert_eq!(state.selected_range(), 4..10);
5274                    state.replace("", window, cx);
5275                    assert_eq!(state.value().as_ref(), "问 !");
5276                    assert!(state.tokens().is_empty());
5277                    state.undo(&Undo, window, cx);
5278                    assert_eq!(state.tokens()[0].token(), &token);
5279                    state.set_selected_range(0..0, cx);
5280                    state.replace("🙂", window, cx);
5281                    assert_eq!(state.tokens()[0].range(), 8..14);
5282                    state.undo(&Undo, window, cx);
5283                    assert_eq!(state.tokens()[0].range(), 4..10);
5284                    let before = state.content();
5285                    state
5286                        .replace_range_with_token(0..0, token.clone(), window, cx)
5287                        .expect("the same reference may occur twice");
5288                    assert_eq!(state.value().as_ref(), "@alice问 @alice!");
5289                    assert_eq!(state.tokens().len(), 2);
5290                    assert_eq!(state.tokens()[1].token(), &token);
5291                    state.undo(&Undo, window, cx);
5292                    assert_eq!(state.content(), before);
5293                    state.set_value(before.text().clone(), window, cx);
5294                    assert!(state.tokens().is_empty());
5295                    assert!(!state.undo_manager.has_undos());
5296                });
5297            })
5298            .unwrap();
5299    }
5300
5301    #[gpui::test]
5302    fn test_inline_token_textarea_ime_and_modes(cx: &mut TestAppContext) {
5303        use crate::input::{InlineToken, InlineTokenError, InputContent};
5304        let view = InputView::build_textarea(cx, |state| state);
5305        view.window_handle
5306            .update(cx, |_, window, cx| {
5307                view.input.update(cx, |state, cx| {
5308                    let content = InputContent::new("@a@b\n后面")
5309                        .with_token(0..2, InlineToken::new("a", "@a"))
5310                        .unwrap()
5311                        .with_token(2..4, InlineToken::new("b", "@b"))
5312                        .unwrap();
5313                    state.set_value(content.clone(), window, cx);
5314                    state.set_selected_range(2..2, cx);
5315                    assert_eq!(state.previous_boundary(2), 0);
5316                    assert_eq!(state.next_boundary(2), 4);
5317                    state.replace_and_mark_text_in_range(Some(1..2), "中", Some(1..1), window, cx);
5318                    assert_eq!(state.value().as_ref(), "中@b\n后面");
5319                    assert_eq!(state.tokens().len(), 1);
5320                    assert_eq!(
5321                        state.replace_with_token(InlineToken::new("x", "x"), window, cx),
5322                        Err(InlineTokenError::CompositionActive)
5323                    );
5324                    state.replace_text_in_range(None, "中文", window, cx);
5325                    state.undo(&Undo, window, cx);
5326                    assert_eq!(state.content(), content);
5327                    state.redo(&Redo, window, cx);
5328                    assert_eq!(state.value().as_ref(), "中文@b\n后面");
5329                    state.undo(&Undo, window, cx);
5330                    state.replace_all("plain", window, cx);
5331                    assert!(state.tokens().is_empty());
5332                    state.undo(&Undo, window, cx);
5333                    assert_eq!(state.content(), content);
5334                });
5335            })
5336            .unwrap();
5337        let view = InputView::build(cx, |state| state);
5338        view.window_handle
5339            .update(cx, |_, window, cx| {
5340                view.input.update(cx, |state, cx| {
5341                    assert_eq!(
5342                        InputContent::new("a\u{301}")
5343                            .with_token(0..1, InlineToken::new("bad", "a"))
5344                            .err(),
5345                        Some(InlineTokenError::InvalidBoundary)
5346                    );
5347                    assert_eq!(
5348                        InputContent::new("@a@a")
5349                            .with_token(0..2, InlineToken::new("a", "@a"))
5350                            .unwrap()
5351                            .with_token(1..3, InlineToken::new("x", "a@"))
5352                            .err(),
5353                        Some(InlineTokenError::OverlappingTokens)
5354                    );
5355                    state
5356                        .replace_with_token(InlineToken::new("a", "@a"), window, cx)
5357                        .unwrap();
5358                    let before = state.content();
5359                    state.set_masked(true, window, cx);
5360                    assert!(!state.tokens_visible());
5361                    assert_eq!(
5362                        state.replace_with_token(InlineToken::new("b", "b"), window, cx),
5363                        Err(InlineTokenError::UnsupportedMode)
5364                    );
5365                    assert_eq!(state.content(), before);
5366                    state.undo(&Undo, window, cx);
5367                    assert!(state.tokens().is_empty());
5368                    state.redo(&Redo, window, cx);
5369                    assert_eq!(state.content(), before);
5370                    assert!(!state.tokens_visible());
5371                });
5372            })
5373            .unwrap();
5374    }
5375
5376    use crate::theme::Theme;
5377    use gpui::{TestAppContext, VisualTestContext, size};
5378
5379    use crate::input::{EditorMode, EditorState, InputMode, LanguageConfig, TextareaMode};
5380
5381    fn set_test_syntax_provider(
5382        provider: Rc<dyn crate::input::SyntaxContextProvider>,
5383        cx: &mut App,
5384    ) {
5385        struct TestLanguages(Rc<dyn crate::input::SyntaxContextProvider>);
5386        impl crate::input::LanguageProvider for TestLanguages {
5387            fn syntax_context_provider(
5388                &self,
5389                _: &str,
5390            ) -> Option<Rc<dyn crate::input::SyntaxContextProvider>> {
5391                Some(self.0.clone())
5392            }
5393        }
5394        crate::input::set_language_provider(Rc::new(TestLanguages(provider)), cx);
5395    }
5396
5397    struct TestRoot<M: InputModeKind>(Entity<InputBaseState<M>>);
5398
5399    impl<M: InputModeKind> Render for TestRoot<M> {
5400        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5401            div().size_full().child(self.0.clone())
5402        }
5403    }
5404
5405    struct InputView<M: InputModeKind> {
5406        input: Entity<InputBaseState<M>>,
5407        window_handle: gpui::WindowHandle<TestRoot<M>>,
5408    }
5409
5410    /// Helper to open a state of one mode in a window for testing.
5411    impl<M: InputModeKind> InputView<M> {
5412        fn build_with(
5413            cx: &mut TestAppContext,
5414            make: impl FnOnce(&mut Window, &mut Context<InputBaseState<M>>) -> InputBaseState<M>
5415            + 'static,
5416        ) -> Self {
5417            let mut input: Option<Entity<InputBaseState<M>>> = None;
5418
5419            let window = cx.update(|cx| {
5420                cx.open_window(Default::default(), |window, cx| {
5421                    // Set up the theme first
5422                    cx.set_global(Theme::default());
5423                    // Initialize input keybindings
5424                    super::super::init(cx);
5425
5426                    input = Some(cx.new(|cx| make(window, cx)));
5427
5428                    cx.new(|_| TestRoot(input.clone().unwrap()))
5429                })
5430                .unwrap()
5431            });
5432
5433            Self {
5434                input: input.clone().unwrap(),
5435                window_handle: window,
5436            }
5437        }
5438    }
5439
5440    impl InputView<EditorMode> {
5441        /// An editor state, for the tests that exercise code-editor behavior.
5442        fn new(cx: &mut TestAppContext) -> Self {
5443            Self::build_editor(cx, |state| state)
5444        }
5445
5446        fn build_editor(
5447            cx: &mut TestAppContext,
5448            f: impl FnOnce(InputBaseState<EditorMode>) -> InputBaseState<EditorMode> + 'static,
5449        ) -> Self {
5450            Self::build_with(cx, move |window, cx| {
5451                f(crate::input::EditorState::new(window, cx).language("sql"))
5452            })
5453        }
5454    }
5455
5456    impl InputView<TextareaMode> {
5457        fn build_textarea(
5458            cx: &mut TestAppContext,
5459            f: impl FnOnce(InputBaseState<TextareaMode>) -> InputBaseState<TextareaMode> + 'static,
5460        ) -> Self {
5461            Self::build_with(cx, move |window, cx| {
5462                f(crate::input::TextareaState::new(window, cx))
5463            })
5464        }
5465    }
5466
5467    impl InputView<InputMode> {
5468        /// A single-line state, the default these tests were written against.
5469        fn build(
5470            cx: &mut TestAppContext,
5471            f: impl FnOnce(InputBaseState<InputMode>) -> InputBaseState<InputMode> + 'static,
5472        ) -> Self {
5473            Self::build_with(cx, move |window, cx| {
5474                f(crate::input::InputState::new(window, cx))
5475            })
5476        }
5477    }
5478
5479    /// The frame is laid out by the application, which should not have to
5480    /// center a single line inside it.
5481    #[gpui::test]
5482    fn single_line_is_centered_in_a_taller_frame(cx: &mut TestAppContext) {
5483        struct Frame(Entity<InputBaseState<InputMode>>);
5484        impl Render for Frame {
5485            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5486                crate::input::InputBase::new("frame")
5487                    .h(px(60.))
5488                    .child(crate::input::Input::new(&self.0))
5489            }
5490        }
5491
5492        cx.update(crate::init);
5493        let mut input = None;
5494        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5495            let state = cx.new(|cx| crate::input::InputState::new(window, cx).default_value("a"));
5496            input = Some(state.clone());
5497            Frame(state)
5498        });
5499        let input = input.unwrap();
5500        let mut cx = VisualTestContext::from_window(window.into(), cx);
5501        cx.update(|window, cx| window.draw(cx).clear(cx));
5502
5503        input.read_with(&cx, |state, _| {
5504            let line = state.input_bounds;
5505            assert_eq!(line.center().y, px(30.), "line {line:?}");
5506        });
5507    }
5508
5509    #[gpui::test]
5510    fn test_noop_scroll_notifies_diagnostic_dismissal(cx: &mut TestAppContext) {
5511        use std::{cell::Cell, rc::Rc};
5512
5513        cx.update(crate::init);
5514        let mut input = None;
5515        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5516            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
5517            gpui::EmptyView
5518        });
5519        let input = input.unwrap();
5520        input.update(cx, |state, cx| {
5521            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
5522            state.scroll_size = size(px(100.), px(20.));
5523            state.present_diagnostic(crate::input::DiagnosticEntry::default(), cx);
5524        });
5525        let notifications = Rc::new(Cell::new(0));
5526        let count = notifications.clone();
5527        let _subscription =
5528            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
5529
5530        window
5531            .update(cx, |_, window, cx| {
5532                input.update(cx, |state, cx| {
5533                    state.on_scroll_wheel(
5534                        &ScrollWheelEvent {
5535                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
5536                            ..Default::default()
5537                        },
5538                        window,
5539                        cx,
5540                    );
5541                    assert_eq!(state.scroll_handle.offset(), Point::default());
5542                    assert!(state.diagnostic_popover().is_none());
5543                });
5544            })
5545            .unwrap();
5546        assert_eq!(
5547            notifications.get(),
5548            1,
5549            "clearing a diagnostic must notify even when scrolling is clamped"
5550        );
5551        window
5552            .update(cx, |_, window, cx| {
5553                input.update(cx, |state, cx| {
5554                    state.on_scroll_wheel(
5555                        &ScrollWheelEvent {
5556                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
5557                            ..Default::default()
5558                        },
5559                        window,
5560                        cx,
5561                    );
5562                });
5563            })
5564            .unwrap();
5565        assert_eq!(notifications.get(), 1, "an unchanged input must stay quiet");
5566    }
5567
5568    #[gpui::test]
5569    fn test_cursor_layout_consumer_updates_after_selection(cx: &mut TestAppContext) {
5570        use std::{cell::Cell, rc::Rc};
5571        struct Panel {
5572            input: Entity<crate::input::EditorState>,
5573            observed: Rc<Cell<Option<(Bounds<Pixels>, Pixels)>>>,
5574        }
5575        impl Render for Panel {
5576            fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
5577                self.observed.set(self.input.read(cx).cursor_layout());
5578                div().size_full().child(self.input.clone())
5579            }
5580        }
5581        struct Root(Entity<Panel>);
5582        impl Render for Root {
5583            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5584                div().size_full().child(
5585                    self.0
5586                        .clone()
5587                        .cached(gpui::StyleRefinement::default().size_full()),
5588                )
5589            }
5590        }
5591        cx.update(crate::init);
5592        let observed = Rc::new(Cell::new(None));
5593        let mut input = None;
5594        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5595            let state = cx.new(|cx| {
5596                let mut state = crate::input::EditorState::new(window, cx);
5597                state.set_value("abcdefgh", window, cx);
5598                state
5599            });
5600            input = Some(state.clone());
5601            Root(cx.new(|_| Panel {
5602                input: state,
5603                observed: observed.clone(),
5604            }))
5605        });
5606        cx.run_until_parked();
5607        let input = input.unwrap();
5608        let before = input.read_with(cx, |state, _| state.cursor_layout());
5609        // The caret geometry only exists once the input has painted, and a
5610        // notify sent from inside a draw marks the view dirty without asking
5611        // for another frame, so the consumer reads the geometry on the next
5612        // invalidation rather than during the paint that produced it.
5613        input.update(cx, |_, cx| cx.notify());
5614        cx.run_until_parked();
5615        assert_eq!(observed.get(), before);
5616        window
5617            .update(cx, |_, _, cx| {
5618                input.update(cx, |state, cx| state.select_to_with_affinity(3, false, cx));
5619            })
5620            .unwrap();
5621        cx.run_until_parked();
5622        window.update(cx, |_, _, cx| cx.notify()).unwrap();
5623        let after = input.read_with(cx, |state, _| state.cursor_layout());
5624        assert_ne!(after, before, "the caret must actually move");
5625        assert_eq!(
5626            observed.get(),
5627            after,
5628            "render consumers must receive the newly painted caret geometry"
5629        );
5630    }
5631
5632    #[gpui::test]
5633    fn test_input_scroll_offset_notifies_only_on_change(cx: &mut TestAppContext) {
5634        use std::{cell::Cell, rc::Rc};
5635
5636        cx.update(crate::init);
5637        let mut input = None;
5638        let _window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5639            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
5640            gpui::EmptyView
5641        });
5642        let input = input.unwrap();
5643        let notifications = Rc::new(Cell::new(0));
5644        let count = notifications.clone();
5645        let _subscription =
5646            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
5647
5648        input.update(cx, |state, _| {
5649            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
5650            state.scroll_size = size(px(300.), px(20.));
5651        });
5652        for target in [
5653            None,
5654            Some(point(px(0.), px(0.))),
5655            Some(point(px(20.), px(50.))),
5656        ] {
5657            input.update(cx, |state, cx| state.update_scroll_offset(target, cx));
5658            assert_eq!(
5659                notifications.get(),
5660                0,
5661                "an unchanged clamped offset must stay quiet"
5662            );
5663        }
5664        input.update(cx, |state, cx| {
5665            state.update_scroll_offset(Some(point(px(-40.), px(0.))), cx);
5666            assert_eq!(state.scroll_handle.offset(), point(px(-40.), px(0.)));
5667        });
5668        assert_eq!(notifications.get(), 1, "a scroll must notify");
5669        input.update(cx, |state, cx| {
5670            state.update_scroll_offset(Some(point(px(-400.), px(50.))), cx);
5671            assert_eq!(state.scroll_handle.offset(), point(px(-200.), px(0.)));
5672        });
5673        assert_eq!(notifications.get(), 2);
5674        input.update(cx, |state, cx| {
5675            state.update_scroll_offset(Some(point(px(-500.), px(0.))), cx);
5676        });
5677        assert_eq!(
5678            notifications.get(),
5679            2,
5680            "clamping to the current offset must stay quiet"
5681        );
5682        input.update(cx, |state, cx| {
5683            state.scroll_size.width = px(110.);
5684            state.update_scroll_offset(None, cx);
5685            assert_eq!(state.scroll_handle.offset(), point(px(-10.), px(0.)));
5686        });
5687        assert_eq!(
5688            notifications.get(),
5689            3,
5690            "a smaller scroll range must clamp and notify"
5691        );
5692    }
5693
5694    #[gpui::test]
5695    fn test_set_value_on_unfocused_input_stays_quiet(cx: &mut TestAppContext) {
5696        use std::{cell::Cell, rc::Rc};
5697
5698        cx.update(crate::init);
5699        let mut input = None;
5700        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5701            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
5702            gpui::EmptyView
5703        });
5704        let input = input.unwrap();
5705        cx.run_until_parked();
5706
5707        let notifications = Rc::new(Cell::new(0));
5708        let count = notifications.clone();
5709        let _subscription =
5710            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
5711        cx.run_until_parked();
5712
5713        // Seeding a form is a write, so it notifies once. The input is not
5714        // focused and draws no caret, so nothing may notify after that.
5715        window
5716            .update(cx, |_, window, cx| {
5717                input.update(cx, |state, cx| state.set_value("seeded", window, cx));
5718            })
5719            .unwrap();
5720        cx.run_until_parked();
5721        let settled = notifications.get();
5722
5723        cx.executor()
5724            .advance_clock(std::time::Duration::from_secs(3));
5725        cx.run_until_parked();
5726
5727        assert_eq!(
5728            notifications.get(),
5729            settled,
5730            "an unfocused input is blinking, and every blink repaints the view it is in"
5731        );
5732    }
5733
5734    #[gpui::test]
5735    fn test_input_does_not_invalidate_cached_parent_during_paint(cx: &mut TestAppContext) {
5736        use std::{cell::Cell, rc::Rc};
5737
5738        struct Panel {
5739            input: Entity<crate::input::InputState>,
5740            renders: Rc<Cell<usize>>,
5741        }
5742        impl Render for Panel {
5743            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5744                self.renders.set(self.renders.get() + 1);
5745                div().size_full().child(self.input.clone())
5746            }
5747        }
5748        struct Root(Entity<Panel>);
5749        impl Render for Root {
5750            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5751                div().size_full().child(
5752                    self.0
5753                        .clone()
5754                        .cached(gpui::StyleRefinement::default().size_full()),
5755                )
5756            }
5757        }
5758
5759        cx.update(crate::init);
5760        let renders = Rc::new(Cell::new(0));
5761        let mut input = None;
5762        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5763            let state = cx.new(|cx| crate::input::InputState::new(window, cx));
5764            input = Some(state.clone());
5765            Root(cx.new(|_| Panel {
5766                input: state,
5767                renders: renders.clone(),
5768            }))
5769        });
5770        cx.run_until_parked();
5771        let before = renders.get();
5772        assert!(before > 0);
5773        for _ in 0..60 {
5774            window.update(cx, |_, _, cx| cx.notify()).unwrap();
5775        }
5776        assert_eq!(
5777            renders.get(),
5778            before,
5779            "an idle input must not invalidate its cached parent"
5780        );
5781
5782        let input = input.unwrap();
5783        window
5784            .update(cx, |_, window, cx| {
5785                input.update(cx, |state, cx| state.set_value("changed", window, cx));
5786            })
5787            .unwrap();
5788        assert!(
5789            renders.get() > before,
5790            "text changes must invalidate the parent"
5791        );
5792        // The first paint reports the changed text extent to layout consumers.
5793        window.update(cx, |_, _, cx| cx.notify()).unwrap();
5794        let before = renders.get();
5795        for _ in 0..60 {
5796            window.update(cx, |_, _, cx| cx.notify()).unwrap();
5797        }
5798        assert_eq!(renders.get(), before, "the edited input must settle again");
5799
5800        cx.simulate_window_resize(window.into(), size(px(240.), px(100.)));
5801        cx.run_until_parked();
5802        assert!(renders.get() > before, "resizing must invalidate layout");
5803    }
5804
5805    #[gpui::test]
5806    fn textarea_cursor_treats_crlf_as_one_newline(cx: &mut TestAppContext) {
5807        cx.update(crate::init);
5808        let view = InputView::build_textarea(cx, |state| state);
5809        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
5810        cx.update(|window, cx| {
5811            view.input.update(cx, |state, cx| {
5812                for original in ["\r\nlast", "\u{feff}first\r\nlast\n", "first\nlast\r\n"] {
5813                    state.set_value(original, window, cx);
5814                    let newline = original.find('\n').unwrap();
5815                    let end = if original.as_bytes()[newline.saturating_sub(1)] == b'\r' {
5816                        newline - 1
5817                    } else {
5818                        newline
5819                    };
5820                    state.end(&MoveEnd, window, cx);
5821                    assert_eq!(state.cursor(), end);
5822                    state.right(&MoveRight, window, cx);
5823                    assert_eq!(state.cursor(), newline + 1);
5824                    state.left(&MoveLeft, window, cx);
5825                    assert_eq!(state.cursor(), end);
5826                    state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
5827                    assert_eq!(state.selected_range(), end..end);
5828                    state.move_to_end(&MoveToEnd, window, cx);
5829                    assert_eq!(state.cursor(), original.len());
5830                    state.move_to_start(&MoveToStart, window, cx);
5831                    assert_eq!(state.cursor(), 0);
5832                    assert_eq!(state.value().as_bytes(), original.as_bytes());
5833                }
5834                // A lone CR is an ordinary character; it must not skip its neighbour.
5835                state.set_value("a\rb", window, cx);
5836                state.right(&MoveRight, window, cx);
5837                assert_eq!(state.cursor(), 1);
5838                state.right(&MoveRight, window, cx);
5839                assert_eq!(state.cursor(), 2);
5840            })
5841        });
5842    }
5843
5844    #[gpui::test]
5845    fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) {
5846        cx.update(crate::init);
5847
5848        // A single-line input keeps its caret in view by moving its own offset;
5849        // it has no viewport to drag, so a scrollbar in a text field is a
5850        // control that does not exist.
5851        let single = InputView::build(cx, |state| state);
5852        single
5853            .input
5854            .update(cx, |state, _| assert!(!state.shows_scrollbar()));
5855
5856        let multi = InputView::build_textarea(cx, |state| state);
5857        multi
5858            .input
5859            .update(cx, |state, _| assert!(state.shows_scrollbar()));
5860    }
5861
5862    #[gpui::test]
5863    fn context_menu_handler_is_deferred_and_respects_disabled(cx: &mut TestAppContext) {
5864        use std::{cell::Cell, rc::Rc};
5865        cx.update(crate::init);
5866        let input_view = InputView::new(cx);
5867        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5868        let input = input_view.input;
5869        let calls = Rc::new(Cell::new(0usize));
5870        let items = Rc::new(Cell::new(0usize));
5871
5872        cx.update(|window, cx| {
5873            input.update(cx, |state, cx| {
5874                let calls2 = calls.clone();
5875                let items2 = items.clone();
5876                state.on_context_menu(Rc::new(move |menu, _, _, _, _| {
5877                    calls2.set(calls2.get() + 1);
5878                    items2.set(menu.items.len());
5879                }));
5880                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
5881            })
5882        });
5883        assert_eq!(calls.get(), 1);
5884        assert_eq!(items.get(), 0);
5885
5886        cx.update(|window, cx| {
5887            input.update(cx, |state, cx| {
5888                state.disabled = true;
5889                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
5890            })
5891        });
5892        assert_eq!(calls.get(), 1);
5893    }
5894
5895    #[gpui::test]
5896    fn test_readonly_rejects_user_edits_only(cx: &mut TestAppContext) {
5897        let input_view = InputView::new(cx);
5898        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
5899        let input = input_view.input;
5900
5901        cx.update(|window, cx| {
5902            input.update(cx, |state, cx| {
5903                state.set_value("hello", window, cx);
5904                state.set_readonly(true, cx);
5905            });
5906        });
5907
5908        cx.update(|_, cx| {
5909            input.read_with(cx, |state, _| {
5910                assert!(!state.is_editable());
5911                assert!(!state.is_replaceable());
5912            });
5913        });
5914
5915        // Typing (and IME) goes through the input handler, it must be rejected.
5916        cx.update(|window, cx| {
5917            input.update(cx, |state, cx| {
5918                state.replace_text_in_range(None, " world", window, cx);
5919                state.replace_and_mark_text_in_range(None, "あ", None, window, cx);
5920            });
5921        });
5922        cx.update(|_, cx| {
5923            input.read_with(cx, |state, _| assert_eq!(state.value(), "hello"));
5924        });
5925
5926        // The programmatic APIs are not limited by the readonly mode.
5927        cx.update(|window, cx| {
5928            input.update(cx, |state, cx| {
5929                state.insert(" world", window, cx);
5930                state.set_value("changed", window, cx);
5931            });
5932        });
5933        cx.update(|_, cx| {
5934            input.read_with(cx, |state, _| assert_eq!(state.value(), "changed"));
5935        });
5936
5937        // And the user can edit again after leaving the readonly mode.
5938        // The caret is at the start, because `set_value` has reset the selection.
5939        cx.update(|window, cx| {
5940            input.update(cx, |state, cx| {
5941                state.set_readonly(false, cx);
5942                state.replace_text_in_range(None, "!", window, cx);
5943            });
5944        });
5945        cx.update(|_, cx| {
5946            input.read_with(cx, |state, _| {
5947                assert!(state.is_editable());
5948                assert_eq!(state.value(), "!changed");
5949            });
5950        });
5951    }
5952
5953    /// Regression test: editing at a caret that sits far outside the viewport
5954    /// must reveal it in one frame. The cursor-follow in `layout_cursors` runs
5955    /// only on the frame the selection changes, so its old one-line step left
5956    /// the caret offscreen after typing at the end of a long paste.
5957    #[gpui::test]
5958    fn test_edit_reveals_far_offscreen_caret(cx: &mut TestAppContext) {
5959        let mut input = None;
5960        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
5961            cx.set_global(Theme::default());
5962            super::super::init(cx);
5963            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 6));
5964            input = Some(state.clone());
5965            TestRoot(state)
5966        });
5967        let mut cx = VisualTestContext::from_window(window.into(), cx);
5968        let input = input.unwrap();
5969
5970        let text: String = (1..=100)
5971            .map(|i| format!("line {i}"))
5972            .collect::<Vec<_>>()
5973            .join("\n");
5974        cx.update(|window, cx| {
5975            input.update(cx, |state, cx| {
5976                state.set_value(text, window, cx);
5977                let end = state.text.len();
5978                state.set_selection(end, end);
5979            });
5980        });
5981        cx.run_until_parked();
5982
5983        // The user scrolled back to the top to read the pasted text.
5984        cx.update(|_, cx| {
5985            input.update(cx, |state, cx| {
5986                state.scroll_handle.set_offset(point(px(0.), px(0.)));
5987                cx.notify();
5988            });
5989        });
5990        cx.run_until_parked();
5991
5992        cx.update(|window, cx| {
5993            input.update(cx, |state, cx| {
5994                state.replace_text_in_range(None, "X", window, cx);
5995            });
5996        });
5997        cx.run_until_parked();
5998
5999        cx.update(|_, cx| {
6000            input.read_with(cx, |state, _| {
6001                assert!(state.value().ends_with("line 100X"));
6002                let (caret, _) = state.cursor_layout().expect("caret painted");
6003                let viewport = state.input_bounds();
6004                let scroll_y = state.scroll_handle.offset().y;
6005                let top = caret.origin.y - viewport.origin.y + scroll_y;
6006                assert!(
6007                    top >= px(0.) && top + caret.size.height <= viewport.size.height,
6008                    "caret top {top:?} outside viewport height {:?} (scroll {scroll_y:?})",
6009                    viewport.size.height,
6010                );
6011            });
6012        });
6013    }
6014
6015    /// Regression test: `scroll_to` at end-of-buffer must produce a deferred
6016    /// scroll target within the safe scroll range, so the painted frame
6017    /// matches what `update_scroll_offset` persists (no jitter). A small
6018    /// `cursor_surrounding_lines` override used to mismatch the hardcoded
6019    /// 3-line edge clearance in `scroll_to`, overshooting `safe_y_min`.
6020    #[gpui::test]
6021    fn test_scroll_to_eob_does_not_overshoot_safe_range(cx: &mut TestAppContext) {
6022        let input_view = InputView::new(cx);
6023        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6024        let input = input_view.input;
6025
6026        // JetBrains-style: 1 trailing empty row + 1-line cursor surrounding.
6027        cx.update(|window, cx| {
6028            input.update(cx, |state, cx| {
6029                state.set_scroll_beyond_last_line(Some(1), window, cx);
6030                state.set_cursor_surrounding_lines(Some(1), window, cx);
6031                let text: String = (1..=50)
6032                    .map(|i| format!("line {i}"))
6033                    .collect::<Vec<_>>()
6034                    .join("\n");
6035                state.set_value(text, window, cx);
6036            });
6037        });
6038        cx.run_until_parked();
6039
6040        // Sanity: paint populated `scroll_size` and `input_bounds` — without
6041        // these, `safe_y_min` below collapses to 0 and the assertion is vacuous.
6042        cx.update(|_, cx| {
6043            input.read_with(cx, |state, _| {
6044                assert!(
6045                    state.scroll_size.height > px(0.),
6046                    "scroll_size not populated by initial paint"
6047                );
6048                assert!(
6049                    state.input_bounds.size.height > px(0.),
6050                    "input_bounds not populated by initial paint"
6051                );
6052            });
6053        });
6054
6055        // Move cursor to end with downward direction — same code path as a
6056        // `Down` keystroke at EOB. `scroll_to` runs synchronously inside
6057        // `move_to`; inspect `deferred_scroll_offset` in the same closure
6058        // before the next paint consumes and clears it.
6059        cx.update(|_, cx| {
6060            input.update(cx, |state, cx| {
6061                let end = state.text.len();
6062                state.move_to(end, Some(MoveDirection::Down), cx);
6063
6064                let deferred = state
6065                    .deferred_scroll_offset
6066                    .expect("scroll_to should populate deferred_scroll_offset");
6067                let safe_y_min =
6068                    (-state.scroll_size.height + state.input_bounds.size.height).min(px(0.));
6069
6070                assert!(
6071                    deferred.y >= safe_y_min,
6072                    "deferred_scroll_offset.y = {:?} below safe_y_min = {:?} \
6073                     — paint would jitter (Bug C regression)",
6074                    deferred.y,
6075                    safe_y_min,
6076                );
6077            });
6078        });
6079    }
6080
6081    #[gpui::test]
6082    fn test_next_search_match_reveals_with_padding_after_manual_scroll(cx: &mut TestAppContext) {
6083        assert_search_reveals_with_padding_after_manual_scroll(false, cx);
6084    }
6085
6086    #[gpui::test]
6087    fn test_previous_search_match_reveals_with_padding_after_manual_scroll(
6088        cx: &mut TestAppContext,
6089    ) {
6090        assert_search_reveals_with_padding_after_manual_scroll(true, cx);
6091    }
6092
6093    fn assert_search_reveals_with_padding_after_manual_scroll(
6094        previous: bool,
6095        cx: &mut TestAppContext,
6096    ) {
6097        let input_view = InputView::new(cx);
6098        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6099        let input = input_view.input;
6100        let text = (0..160)
6101            .map(|row| {
6102                if matches!(row, 20 | 60 | 100) {
6103                    format!("match on row {row}")
6104                } else {
6105                    format!("line {row}")
6106                }
6107            })
6108            .collect::<Vec<_>>()
6109            .join("\n");
6110        let start = text.match_indices("match").nth(1).unwrap().0;
6111        let expected_match = start..start + "match".len();
6112        cx.update(|window, cx| {
6113            input.update(cx, |state, cx| {
6114                state.set_cursor_surrounding_lines(Some(3), window, cx);
6115                state.set_value(text, window, cx);
6116                state.set_search_query("match", true, cx);
6117                if previous {
6118                    state.search_session.matcher.next();
6119                    state.search_session.matcher.next();
6120                }
6121            });
6122        });
6123        cx.run_until_parked();
6124        cx.update(|_, cx| {
6125            input.update(cx, |state, cx| {
6126                let line_height = state.last_layout.as_ref().unwrap().line_height;
6127                let y = if previous { px(0.) } else { -line_height * 80. };
6128                state.set_scroll_offset(point(px(0.), y), cx);
6129            });
6130        });
6131        cx.run_until_parked();
6132        input.read_with(&cx, |state, _| {
6133            let visible = state.visible_row_range().unwrap();
6134            if previous {
6135                assert!(visible.end <= 60, "target must be below the viewport");
6136            } else {
6137                assert!(visible.start > 60, "target must be above the viewport");
6138            }
6139        });
6140        cx.update(|_, cx| {
6141            input.update(cx, |state, cx| {
6142                let range = if previous {
6143                    state.previous_search_match(cx)
6144                } else {
6145                    state.next_search_match(cx)
6146                };
6147                assert_eq!(range, Some(expected_match));
6148                assert_eq!(state.search_session.matcher.label(), "2/3");
6149            });
6150        });
6151        cx.run_until_parked();
6152        input.read_with(&cx, |state, _| {
6153            assert!(state.visible_row_range().unwrap().contains(&60));
6154            let line_height = state.last_layout.as_ref().unwrap().line_height;
6155            let target_y = line_height * 60. + state.scroll_handle.offset().y;
6156            // Three lines of edge clearance include the matched line itself.
6157            assert!(target_y >= line_height * 2. - px(0.1));
6158            assert!(
6159                target_y + line_height * 3.
6160                    <= state.last_bounds.as_ref().unwrap().size.height + px(0.1),
6161                "search must preserve the configured surrounding-line padding"
6162            );
6163        });
6164    }
6165
6166    /// A host that wants the search shortcut for its own search UI.
6167    struct SearchHost {
6168        editor: Entity<InputBaseState<EditorMode>>,
6169        search_requests: Rc<Cell<usize>>,
6170    }
6171
6172    impl Render for SearchHost {
6173        fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
6174            let search_requests = self.search_requests.clone();
6175            div()
6176                .size_full()
6177                .on_action(cx.listener(move |_, _: &Search, _, _| {
6178                    search_requests.set(search_requests.get() + 1);
6179                }))
6180                .child(self.editor.clone())
6181        }
6182    }
6183
6184    /// Opens an editor inside [`SearchHost`], focused, and presses the search
6185    /// shortcut once. Returns the editor and the host's request count.
6186    fn press_search_shortcut(
6187        cx: &mut TestAppContext,
6188        searchable: bool,
6189    ) -> (Entity<InputBaseState<EditorMode>>, Rc<Cell<usize>>) {
6190        let search_requests = Rc::new(Cell::new(0));
6191        let mut editor = None;
6192        let window = cx.update(|cx| {
6193            cx.open_window(Default::default(), |window, cx| {
6194                cx.set_global(Theme::default());
6195                super::super::init(cx);
6196                let state =
6197                    cx.new(|cx| crate::input::EditorState::new(window, cx).searchable(searchable));
6198                editor = Some(state.clone());
6199                cx.new(|_| SearchHost {
6200                    editor: state,
6201                    search_requests: search_requests.clone(),
6202                })
6203            })
6204            .unwrap()
6205        });
6206        let editor = editor.unwrap();
6207        let mut cx = VisualTestContext::from_window(window.into(), cx);
6208        cx.update(|window, cx| {
6209            editor.update(cx, |state, cx| state.focus(window, cx));
6210        });
6211        cx.run_until_parked();
6212        #[cfg(target_os = "macos")]
6213        cx.simulate_keystrokes("cmd-f");
6214        #[cfg(not(target_os = "macos"))]
6215        cx.simulate_keystrokes("ctrl-f");
6216        cx.run_until_parked();
6217        (editor, search_requests)
6218    }
6219
6220    #[gpui::test]
6221    fn test_search_shortcut_reaches_the_host_when_not_searchable(cx: &mut TestAppContext) {
6222        let (editor, search_requests) = press_search_shortcut(cx, false);
6223        assert_eq!(search_requests.get(), 1);
6224        editor.read_with(cx, |state, _| {
6225            assert!(!state.search_session().open);
6226            assert!(!state.search_session().is_active());
6227        });
6228    }
6229
6230    #[gpui::test]
6231    fn test_search_shortcut_opens_the_panel_when_searchable(cx: &mut TestAppContext) {
6232        let (editor, search_requests) = press_search_shortcut(cx, true);
6233        assert_eq!(search_requests.get(), 0);
6234        editor.read_with(cx, |state, _| {
6235            assert!(state.search_session().open);
6236            assert!(state.search_session().is_active());
6237        });
6238    }
6239
6240    #[gpui::test]
6241    fn test_set_search_query_highlights_without_the_panel(cx: &mut TestAppContext) {
6242        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
6243        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6244        let input = input_view.input;
6245        cx.update(|window, cx| {
6246            input.update(cx, |state, cx| {
6247                state.set_value("foo bar foo", window, cx);
6248                state.set_search_query("foo", true, cx);
6249            });
6250        });
6251        cx.run_until_parked();
6252        input.read_with(&cx, |state, _| {
6253            let session = state.search_session();
6254            assert!(session.is_active());
6255            assert!(!session.open);
6256            assert_eq!(session.matcher.len(), 2);
6257        });
6258        cx.update(|_, cx| {
6259            input.update(cx, |state, cx| state.close_search(cx));
6260        });
6261        input.read_with(&cx, |state, _| {
6262            assert!(!state.search_session().is_active());
6263        });
6264    }
6265
6266    /// A closed search skips rescanning on edits, so every path that reads
6267    /// the matches again must see the edited text, not the text at close.
6268    #[gpui::test]
6269    fn test_closed_search_resyncs_matches_after_edits(cx: &mut TestAppContext) {
6270        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
6271        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6272        let input = input_view.input;
6273        cx.update(|window, cx| {
6274            input.update(cx, |state, cx| {
6275                state.set_value("foo bar foo", window, cx);
6276                state.set_search_query("foo", true, cx);
6277                state.close_search(cx);
6278
6279                state.set_value("bar foo", window, cx);
6280                assert_eq!(state.next_search_match(cx), Some(4..7));
6281
6282                state.set_value("foo foo foo", window, cx);
6283                state.set_search_query("foo", true, cx);
6284                assert_eq!(state.search_session().matcher.len(), 3);
6285            });
6286        });
6287    }
6288
6289    #[gpui::test]
6290    fn test_search_reveals_offscreen_wrapped_match(cx: &mut TestAppContext) {
6291        let input_view = InputView::new(cx);
6292        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6293        let input = input_view.input;
6294        let text = format!(
6295            "match\n{}\n{}match\n{}",
6296            "line\n".repeat(80),
6297            "wrapped text ".repeat(500),
6298            "line\n".repeat(80)
6299        );
6300        cx.update(|window, cx| {
6301            input.update(cx, |state, cx| {
6302                state.set_cursor_surrounding_lines(Some(3), window, cx);
6303                state.set_value(text, window, cx);
6304                state.set_search_query("match", true, cx);
6305            });
6306        });
6307        cx.run_until_parked();
6308        cx.update(|_, cx| {
6309            input.update(cx, |state, cx| {
6310                state.next_search_match(cx).unwrap();
6311            });
6312        });
6313        cx.run_until_parked();
6314        input.read_with(&cx, |state, _| {
6315            let range = state.search_session.matcher.matched_ranges()[1].clone();
6316            let layout = state.last_layout.as_ref().unwrap();
6317            let (_, _, position) = state.line_and_position_for_offset(range.end);
6318            let y = position.expect("wrapped match must be laid out").y
6319                + state.scroll_handle.offset().y;
6320            assert!(y >= layout.line_height * 2. - px(0.1));
6321            assert!(
6322                y + layout.line_height * 3. <= state.last_bounds.unwrap().size.height + px(0.1),
6323                "wrapped match must retain surrounding display rows"
6324            );
6325        });
6326    }
6327
6328    #[gpui::test]
6329    fn test_number_step(cx: &mut TestAppContext) {
6330        let input = InputView::build(cx, |state| state).input;
6331
6332        cx.update(|cx| {
6333            input.update(cx, |_state, cx| {
6334                assert_eq!(
6335                    NumberStep::from(5.).value(123., StepAction::Increment, cx),
6336                    5.
6337                );
6338
6339                // The step can differ by direction at a boundary: at 1.0 it
6340                // is 0.1 going down and 0.5 going up.
6341                let step = NumberStep::by_value(|value, action, _cx| {
6342                    let below = match action {
6343                        StepAction::Increment => value < 1.0,
6344                        StepAction::Decrement => value <= 1.0,
6345                    };
6346                    if below { 0.1 } else { 0.5 }
6347                });
6348                assert_eq!(step.value(0.5, StepAction::Increment, cx), 0.1);
6349                assert_eq!(step.value(1.0, StepAction::Increment, cx), 0.5);
6350                assert_eq!(step.value(1.0, StepAction::Decrement, cx), 0.1);
6351                assert_eq!(step.value(2.0, StepAction::Decrement, cx), 0.5);
6352            });
6353        });
6354    }
6355
6356    #[gpui::test]
6357    fn test_number_input_normalization(cx: &mut TestAppContext) {
6358        let input_view = InputView::build(cx, |state| {
6359            state.mask_pattern(MaskPattern::Number {
6360                separator: None,
6361                fraction: None,
6362            })
6363        });
6364        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6365        let input = input_view.input;
6366
6367        // Full-width digits and the ideographic full stop are normalized,
6368        // and the cursor is at the end (in normalized bytes, not the
6369        // original 12 bytes).
6370        cx.update(|window, cx| {
6371            input.update(cx, |state, cx| {
6372                state.replace_text_in_range(None, "12。5", window, cx);
6373            });
6374        });
6375        cx.run_until_parked();
6376        cx.update(|_, cx| {
6377            input.read_with(cx, |state, _| {
6378                assert_eq!(state.value(), "12.5");
6379                let cursor: Range<usize> = state.selected_range();
6380                assert_eq!(cursor, 4..4);
6381            });
6382        });
6383
6384        // Non-numeric input is rejected.
6385        cx.update(|window, cx| {
6386            input.update(cx, |state, cx| {
6387                state.replace_text_in_range(None, "abc", window, cx);
6388            });
6389        });
6390        cx.run_until_parked();
6391        cx.update(|_, cx| {
6392            input.read_with(cx, |state, _| {
6393                assert_eq!(state.value(), "12.5");
6394            });
6395        });
6396
6397        // A bare leading dot is kept as-is (normalized from the ideographic
6398        // full stop), not completed to "0.", so it stays editable.
6399        cx.update(|window, cx| {
6400            input.update(cx, |state, cx| {
6401                let range = state.range_to_utf16(&(0..state.text.len()));
6402                state.replace_text_in_range(Some(range), "。", window, cx);
6403            });
6404        });
6405        cx.run_until_parked();
6406        cx.update(|_, cx| {
6407            input.read_with(cx, |state, _| {
6408                assert_eq!(state.value(), ".");
6409                let cursor: Range<usize> = state.selected_range();
6410                assert_eq!(cursor, 1..1);
6411            });
6412        });
6413    }
6414
6415    #[gpui::test]
6416    fn test_mask_pattern_accepts_non_ascii_text(cx: &mut TestAppContext) {
6417        let input_view = InputView::build(cx, |state| state.mask_pattern("9999年99月"));
6418        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6419        let input = input_view.input;
6420
6421        cx.update(|window, cx| {
6422            input.update(cx, |state, cx| {
6423                state.replace_text_in_range(None, "2024年12月", window, cx);
6424            });
6425        });
6426        cx.run_until_parked();
6427        cx.update(|_, cx| {
6428            input.read_with(cx, |state, _| {
6429                assert_eq!(state.value(), "2024年12月");
6430            });
6431        });
6432    }
6433
6434    #[gpui::test]
6435    fn test_number_input_normalization_with_separator(cx: &mut TestAppContext) {
6436        let input_view = InputView::build(cx, |state| {
6437            state.mask_pattern(MaskPattern::Number {
6438                separator: Some(','),
6439                fraction: Some(2),
6440            })
6441        });
6442        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6443        let input = input_view.input;
6444
6445        cx.update(|window, cx| {
6446            input.update(cx, |state, cx| {
6447                state.replace_text_in_range(None, "1234", window, cx);
6448            });
6449        });
6450        cx.run_until_parked();
6451        cx.update(|_, cx| {
6452            input.read_with(cx, |state, _| {
6453                assert_eq!(state.value(), "1,234");
6454                assert_eq!(state.unmask_value(), "1234");
6455            });
6456        });
6457    }
6458
6459    #[gpui::test]
6460    fn test_number_input_clamp_on_blur(cx: &mut TestAppContext) {
6461        let input_view = InputView::build(cx, |state| {
6462            state
6463                .mask_pattern(MaskPattern::Number {
6464                    separator: None,
6465                    fraction: None,
6466                })
6467                .min(10.)
6468                .max(100.)
6469        });
6470        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6471        let input = input_view.input;
6472
6473        // Out-of-range values are allowed while typing, and clamped on blur.
6474        cx.update(|window, cx| {
6475            input.update(cx, |state, cx| {
6476                state.replace_text_in_range(None, "1000", window, cx);
6477                assert_eq!(state.value(), "1000");
6478                state.clamp_number_value(window, cx);
6479                assert_eq!(state.value(), "100");
6480
6481                let range = state.range_to_utf16(&(0..state.text.len()));
6482                state.replace_text_in_range(Some(range), "1", window, cx);
6483                assert_eq!(state.value(), "1");
6484                state.clamp_number_value(window, cx);
6485                assert_eq!(state.value(), "10");
6486            });
6487        });
6488    }
6489
6490    #[gpui::test]
6491    fn test_number_input_undo_with_mask(cx: &mut TestAppContext) {
6492        let input_view = InputView::build(cx, |state| {
6493            state.mask_pattern(MaskPattern::Number {
6494                separator: Some(','),
6495                fraction: None,
6496            })
6497        });
6498        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6499        let input = input_view.input;
6500
6501        // When the mask changes the text (regrouping separators), a
6502        // whole-document change is recorded, so undo/redo can restore it.
6503        cx.update(|window, cx| {
6504            input.update(cx, |state, cx| {
6505                state.replace_text_in_range(None, "1234", window, cx);
6506                assert_eq!(state.value(), "1,234");
6507                state.replace_text_in_range(None, "5", window, cx);
6508                assert_eq!(state.value(), "12,345");
6509
6510                // Each whole-document mask rewrite is an atomic undo step.
6511                // Before the whole-document history fix, undo produced a
6512                // corrupted value like "1,2344".
6513                state.undo(&Undo, window, cx);
6514                assert_eq!(state.value(), "1,234");
6515                state.undo(&Undo, window, cx);
6516                assert_eq!(state.value(), "");
6517                state.redo(&Redo, window, cx);
6518                assert_eq!(state.value(), "1,234");
6519                state.redo(&Redo, window, cx);
6520                assert_eq!(state.value(), "12,345");
6521            });
6522        });
6523    }
6524
6525    #[gpui::test]
6526    fn test_undo_manager_coalesces_adjacent_typing_transactions(cx: &mut TestAppContext) {
6527        let input_view = InputView::build(cx, |state| state);
6528        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6529        let input = input_view.input;
6530
6531        cx.update(|window, cx| {
6532            input.update(cx, |state, cx| {
6533                state.replace_text_in_range(None, "a", window, cx);
6534                state.replace_text_in_range(None, "b", window, cx);
6535                assert_eq!(state.value(), "ab");
6536
6537                state.undo(&Undo, window, cx);
6538                assert_eq!(state.value(), "");
6539            });
6540        });
6541    }
6542
6543    #[gpui::test]
6544    fn test_undo_manager_cursor_movement_splits_typing(cx: &mut TestAppContext) {
6545        let input_view = InputView::build(cx, |state| state);
6546        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6547        let input = input_view.input;
6548
6549        cx.update(|window, cx| {
6550            input.update(cx, |state, cx| {
6551                state.replace_text_in_range(None, "a", window, cx);
6552                state.replace_text_in_range(None, "b", window, cx);
6553                state.left(&MoveLeft, window, cx);
6554                state.replace_text_in_range(None, "x", window, cx);
6555                assert_eq!(state.value(), "axb");
6556
6557                state.undo(&Undo, window, cx);
6558                assert_eq!(state.value(), "ab");
6559                state.undo(&Undo, window, cx);
6560                assert_eq!(state.value(), "");
6561            });
6562        });
6563    }
6564
6565    #[gpui::test]
6566    fn test_undo_manager_splits_backward_and_forward_delete(cx: &mut TestAppContext) {
6567        let input_view = InputView::build(cx, |state| state);
6568        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6569        let input = input_view.input;
6570
6571        cx.update(|window, cx| {
6572            input.update(cx, |state, cx| {
6573                state.set_value("abcd", window, cx);
6574                state.set_selected_range(2..2, cx);
6575                state.backspace(&Backspace, window, cx);
6576                state.delete(&Delete, window, cx);
6577                assert_eq!(state.value(), "ad");
6578
6579                state.undo(&Undo, window, cx);
6580                assert_eq!(state.value(), "acd");
6581                state.undo(&Undo, window, cx);
6582                assert_eq!(state.value(), "abcd");
6583            });
6584        });
6585    }
6586
6587    #[gpui::test]
6588    fn test_undo_manager_coalesces_directional_character_deletes(cx: &mut TestAppContext) {
6589        let input_view = InputView::build(cx, |state| state);
6590        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6591        let input = input_view.input;
6592
6593        cx.update(|window, cx| {
6594            input.update(cx, |state, cx| {
6595                state.set_value("abcd", window, cx);
6596                state.backspace(&Backspace, window, cx);
6597                state.backspace(&Backspace, window, cx);
6598                assert_eq!(state.value(), "ab");
6599                state.undo(&Undo, window, cx);
6600                assert_eq!(state.value(), "abcd");
6601                assert_eq!(state.selected_range(), 4..4);
6602
6603                state.set_value("abcd", window, cx);
6604                state.set_selected_range(1..1, cx);
6605                state.delete(&Delete, window, cx);
6606                state.delete(&Delete, window, cx);
6607                assert_eq!(state.value(), "ad");
6608                state.undo(&Undo, window, cx);
6609                assert_eq!(state.value(), "abcd");
6610                assert_eq!(state.selected_range(), 1..1);
6611            });
6612        });
6613    }
6614
6615    #[gpui::test]
6616    fn test_undo_manager_atomic_paste_isolated_from_typing(cx: &mut TestAppContext) {
6617        let input_view = InputView::build(cx, |state| state);
6618        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6619        let input = input_view.input;
6620
6621        cx.update(|window, cx| {
6622            cx.write_to_clipboard(ClipboardItem::new_string("P".to_string()));
6623            input.update(cx, |state, cx| {
6624                state.replace_text_in_range(None, "a", window, cx);
6625                state.paste(&Paste, window, cx);
6626                state.replace_text_in_range(None, "b", window, cx);
6627                assert_eq!(state.value(), "aPb");
6628
6629                state.undo(&Undo, window, cx);
6630                assert_eq!(state.value(), "aP");
6631                state.undo(&Undo, window, cx);
6632                assert_eq!(state.value(), "a");
6633                state.undo(&Undo, window, cx);
6634                assert_eq!(state.value(), "");
6635            });
6636        });
6637    }
6638
6639    #[gpui::test]
6640    fn test_undo_manager_programmatic_insert_is_atomic(cx: &mut TestAppContext) {
6641        let input_view = InputView::build(cx, |state| state);
6642        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6643        let input = input_view.input;
6644
6645        cx.update(|window, cx| {
6646            input.update(cx, |state, cx| {
6647                state.replace_text_in_range(None, "a", window, cx);
6648                state.insert("P", window, cx);
6649                state.replace_text_in_range(None, "b", window, cx);
6650                assert_eq!(state.value(), "aPb");
6651
6652                state.undo(&Undo, window, cx);
6653                assert_eq!(state.value(), "aP");
6654                state.undo(&Undo, window, cx);
6655                assert_eq!(state.value(), "a");
6656            });
6657        });
6658    }
6659
6660    #[gpui::test]
6661    fn test_undo_manager_selection_round_trip_splits_typing(cx: &mut TestAppContext) {
6662        let input_view = InputView::build(cx, |state| state);
6663        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6664        let input = input_view.input;
6665
6666        cx.update(|window, cx| {
6667            input.update(cx, |state, cx| {
6668                state.replace_text_in_range(None, "a", window, cx);
6669                state.replace_text_in_range(None, "b", window, cx);
6670                state.select_all(window, cx);
6671                state.unselect(window, cx);
6672                state.replace_text_in_range(None, "c", window, cx);
6673
6674                state.undo(&Undo, window, cx);
6675                assert_eq!(state.value(), "ab");
6676                state.undo(&Undo, window, cx);
6677                assert_eq!(state.value(), "");
6678            });
6679        });
6680    }
6681
6682    #[gpui::test]
6683    fn test_undo_manager_enter_is_atomic(cx: &mut TestAppContext) {
6684        let input_view = InputView::build_textarea(cx, |state| state);
6685        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6686        let input = input_view.input;
6687
6688        cx.update(|window, cx| {
6689            input.update(cx, |state, cx| {
6690                state.replace_text_in_range(None, "a", window, cx);
6691                state.enter(
6692                    &Enter {
6693                        secondary: false,
6694                        shift: false,
6695                    },
6696                    window,
6697                    cx,
6698                );
6699                state.replace_text_in_range(None, "b", window, cx);
6700
6701                state.undo(&Undo, window, cx);
6702                assert_eq!(state.value(), "a\n");
6703                state.undo(&Undo, window, cx);
6704                assert_eq!(state.value(), "a");
6705                state.undo(&Undo, window, cx);
6706                assert_eq!(state.value(), "");
6707            });
6708        });
6709    }
6710
6711    #[gpui::test]
6712    fn test_undo_manager_single_line_return_commits_the_typing_session(cx: &mut TestAppContext) {
6713        let input_view = InputView::build(cx, |state| state);
6714        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6715        let input = input_view.input;
6716
6717        cx.update(|window, cx| {
6718            input.update(cx, |state, cx| {
6719                for part in ["a", "b", "c"] {
6720                    state.replace_text_in_range(None, part, window, cx);
6721                }
6722                state.enter(
6723                    &Enter {
6724                        secondary: false,
6725                        shift: false,
6726                    },
6727                    window,
6728                    cx,
6729                );
6730                for part in ["d", "e", "f"] {
6731                    state.replace_text_in_range(None, part, window, cx);
6732                }
6733                assert_eq!(state.value(), "abcdef");
6734
6735                state.undo(&Undo, window, cx);
6736                assert_eq!(state.value(), "abc");
6737                state.undo(&Undo, window, cx);
6738                assert_eq!(state.value(), "");
6739            });
6740        });
6741    }
6742
6743    #[gpui::test]
6744    fn test_undo_manager_submit_on_enter_commits_the_textarea_session(cx: &mut TestAppContext) {
6745        let input_view = InputView::build_textarea(cx, |state| state.submit_on_enter(true));
6746        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6747        let input = input_view.input;
6748
6749        cx.update(|window, cx| {
6750            input.update(cx, |state, cx| {
6751                state.replace_text_in_range(None, "before submit", window, cx);
6752                state.enter(
6753                    &Enter {
6754                        secondary: false,
6755                        shift: false,
6756                    },
6757                    window,
6758                    cx,
6759                );
6760                state.replace_text_in_range(None, " after submit", window, cx);
6761                assert_eq!(state.value(), "before submit after submit");
6762
6763                state.undo(&Undo, window, cx);
6764                assert_eq!(state.value(), "before submit");
6765                state.undo(&Undo, window, cx);
6766                assert_eq!(state.value(), "");
6767            });
6768        });
6769    }
6770
6771    #[gpui::test]
6772    fn test_undo_manager_blur_commits_the_typing_session(cx: &mut TestAppContext) {
6773        let input_view = InputView::build(cx, |state| state);
6774        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6775        let input = input_view.input;
6776
6777        cx.update(|window, cx| {
6778            input.update(cx, |state, cx| {
6779                state.replace_text_in_range(None, "before blur", window, cx);
6780                state.on_blur(window, cx);
6781                state.on_focus(window, cx);
6782                state.replace_text_in_range(None, " after focus", window, cx);
6783                assert_eq!(state.value(), "before blur after focus");
6784
6785                state.undo(&Undo, window, cx);
6786                assert_eq!(state.value(), "before blur");
6787                state.undo(&Undo, window, cx);
6788                assert_eq!(state.value(), "");
6789            });
6790        });
6791    }
6792
6793    #[gpui::test]
6794    fn test_undo_manager_keeps_rapid_lines_in_distinct_transactions(cx: &mut TestAppContext) {
6795        let input_view = InputView::build_textarea(cx, |state| state);
6796        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6797        let input = input_view.input;
6798
6799        cx.update(|window, cx| {
6800            input.update(cx, |state, cx| {
6801                let enter = Enter {
6802                    secondary: false,
6803                    shift: false,
6804                };
6805                state.replace_text_in_range(None, "a", window, cx);
6806                state.enter(&enter, window, cx);
6807                state.replace_text_in_range(None, "b", window, cx);
6808                state.enter(&enter, window, cx);
6809                state.replace_text_in_range(None, "c", window, cx);
6810                assert_eq!(state.value(), "a\nb\nc");
6811
6812                for expected in ["a\nb\n", "a\nb", "a\n", "a", ""] {
6813                    state.undo(&Undo, window, cx);
6814                    assert_eq!(state.value(), expected);
6815                }
6816            });
6817        });
6818    }
6819
6820    #[gpui::test]
6821    fn test_undo_manager_coalesces_long_unicode_typing_without_a_timer(cx: &mut TestAppContext) {
6822        let input_view = InputView::build_textarea(cx, |state| state);
6823        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6824        let input = input_view.input;
6825        let parts = [
6826            "The ",
6827            "quick ",
6828            "brown fox, ",
6829            "你好,世界 ",
6830            "🦀 jumps over 13 lazy dogs.",
6831        ];
6832        let expected = parts.concat();
6833
6834        cx.update(|window, cx| {
6835            input.update(cx, |state, cx| {
6836                for part in parts {
6837                    state.replace_text_in_range(None, part, window, cx);
6838                }
6839                assert_eq!(state.value(), expected);
6840
6841                state.undo(&Undo, window, cx);
6842                assert_eq!(state.value(), "");
6843                state.redo(&Redo, window, cx);
6844                assert_eq!(state.value(), expected);
6845            });
6846        });
6847    }
6848
6849    #[gpui::test]
6850    fn test_undo_manager_long_multiline_sequence_has_structural_boundaries(
6851        cx: &mut TestAppContext,
6852    ) {
6853        let input_view = InputView::build_textarea(cx, |state| state);
6854        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6855        let input = input_view.input;
6856        let enter = Enter {
6857            secondary: false,
6858            shift: false,
6859        };
6860
6861        cx.update(|window, cx| {
6862            input.update(cx, |state, cx| {
6863                for (index, line) in [
6864                    "first line with punctuation!",
6865                    "第二行包含 Unicode 🦀",
6866                    "third line has several words",
6867                ]
6868                .into_iter()
6869                .enumerate()
6870                {
6871                    for chunk in line.split_inclusive(' ') {
6872                        state.replace_text_in_range(None, chunk, window, cx);
6873                    }
6874                    if index < 2 {
6875                        state.enter(&enter, window, cx);
6876                    }
6877                }
6878
6879                assert_eq!(
6880                    state.value(),
6881                    "first line with punctuation!\n第二行包含 Unicode 🦀\nthird line has several words"
6882                );
6883                state.undo(&Undo, window, cx);
6884                assert_eq!(
6885                    state.value(),
6886                    "first line with punctuation!\n第二行包含 Unicode 🦀\n"
6887                );
6888                state.undo(&Undo, window, cx);
6889                assert_eq!(
6890                    state.value(),
6891                    "first line with punctuation!\n第二行包含 Unicode 🦀"
6892                );
6893                state.undo(&Undo, window, cx);
6894                assert_eq!(state.value(), "first line with punctuation!\n");
6895            });
6896        });
6897    }
6898
6899    #[gpui::test]
6900    fn test_masked_input_keeps_its_value_out_of_the_clipboard(cx: &mut TestAppContext) {
6901        let input_view = InputView::build(cx, |state| state);
6902        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6903        let input = input_view.input;
6904
6905        cx.update(|window, cx| {
6906            input.update(cx, |state, cx| {
6907                state.set_value("hunter2", window, cx);
6908                state.set_masked(true, window, cx);
6909                state.select_all(window, cx);
6910                cx.write_to_clipboard(ClipboardItem::new_string("sentinel".into()));
6911
6912                state.copy(&Copy, window, cx);
6913                assert_eq!(
6914                    cx.read_from_clipboard().and_then(|item| item.text()),
6915                    Some("sentinel".to_string())
6916                );
6917
6918                // Cut neither copies nor deletes.
6919                state.cut(&Cut, window, cx);
6920                assert_eq!(state.value(), "hunter2");
6921                assert_eq!(
6922                    cx.read_from_clipboard().and_then(|item| item.text()),
6923                    Some("sentinel".to_string())
6924                );
6925
6926                // Revealing the value restores both.
6927                state.set_masked(false, window, cx);
6928                state.copy(&Copy, window, cx);
6929                assert_eq!(
6930                    cx.read_from_clipboard().and_then(|item| item.text()),
6931                    Some("hunter2".to_string())
6932                );
6933            });
6934        });
6935    }
6936
6937    #[gpui::test]
6938    fn test_masked_input_collapses_word_boundaries(cx: &mut TestAppContext) {
6939        let input_view = InputView::build(cx, |state| state);
6940        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6941        let input = input_view.input;
6942
6943        cx.update(|window, cx| {
6944            input.update(cx, |state, cx| {
6945                state.set_value("aaa bbb ccc", window, cx);
6946                state.set_masked(true, window, cx);
6947                state.set_selected_range(7..7, cx);
6948
6949                // The mask hides word boundaries, so a word delete takes
6950                // everything before the caret and leaves the rest.
6951                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
6952                assert_eq!(state.value(), " ccc");
6953                assert_eq!(state.selected_range(), 0..0);
6954
6955                state.delete_next_word(&DeleteToNextWordEnd, window, cx);
6956                assert_eq!(state.value(), "");
6957
6958                // A double click takes the whole value, not one word.
6959                state.set_value("aaa bbb ccc", window, cx);
6960                state.select_word(9, window, cx);
6961                assert_eq!(state.selected_range(), 0..11);
6962
6963                // Unmasked, the same delete only takes one word.
6964                state.set_masked(false, window, cx);
6965                state.set_value("aaa bbb ccc", window, cx);
6966                state.set_selected_range(11..11, cx);
6967                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
6968                assert_eq!(state.value(), "aaa bbb ");
6969
6970                state.set_value("aaa bbb ccc", window, cx);
6971                state.select_word(9, window, cx);
6972                assert_eq!(state.selected_range(), 8..11);
6973            });
6974        });
6975    }
6976
6977    #[gpui::test]
6978    fn test_masked_input_disables_the_copy_context_menu_items(cx: &mut TestAppContext) {
6979        let input_view = InputView::build(cx, |state| state);
6980        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6981        let input = input_view.input;
6982
6983        cx.update(|window, cx| {
6984            input.update(cx, |state, cx| {
6985                state.set_value("hunter2", window, cx);
6986                state.select_all(window, cx);
6987                assert!(state.context_menu_capabilities().is_copyable());
6988
6989                state.set_masked(true, window, cx);
6990                let capabilities = state.context_menu_capabilities();
6991                assert!(capabilities.is_masked());
6992                assert!(capabilities.has_selection());
6993                assert!(!capabilities.is_copyable());
6994            });
6995        });
6996    }
6997
6998    #[gpui::test]
6999    fn test_undo_manager_cut_and_repeated_pastes_are_distinct_transactions(
7000        cx: &mut TestAppContext,
7001    ) {
7002        let input_view = InputView::build(cx, |state| state);
7003        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7004        let input = input_view.input;
7005
7006        cx.update(|window, cx| {
7007            input.update(cx, |state, cx| {
7008                state.replace_text_in_range(None, "alpha beta gamma", window, cx);
7009                state.set_selected_range(6..10, cx);
7010                state.cut(&Cut, window, cx);
7011                assert_eq!(state.value(), "alpha  gamma");
7012
7013                state.paste(&Paste, window, cx);
7014                state.paste(&Paste, window, cx);
7015                assert_eq!(state.value(), "alpha betabeta gamma");
7016
7017                state.undo(&Undo, window, cx);
7018                assert_eq!(state.value(), "alpha beta gamma");
7019                state.undo(&Undo, window, cx);
7020                assert_eq!(state.value(), "alpha  gamma");
7021                state.undo(&Undo, window, cx);
7022                assert_eq!(state.value(), "alpha beta gamma");
7023            });
7024        });
7025    }
7026
7027    #[gpui::test]
7028    fn test_undo_manager_word_and_line_deletes_do_not_coalesce(cx: &mut TestAppContext) {
7029        let input_view = InputView::build_textarea(cx, |state| state);
7030        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7031        let input = input_view.input;
7032
7033        cx.update(|window, cx| {
7034            input.update(cx, |state, cx| {
7035                state.set_value("one two three\nfour five", window, cx);
7036                state.set_selected_range(13..13, cx);
7037                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
7038                assert_eq!(state.value(), "one two \nfour five");
7039                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
7040                assert_eq!(state.value(), "one two four five");
7041
7042                state.undo(&Undo, window, cx);
7043                assert_eq!(state.value(), "one two \nfour five");
7044                state.undo(&Undo, window, cx);
7045                assert_eq!(state.value(), "one two three\nfour five");
7046            });
7047        });
7048    }
7049
7050    #[gpui::test]
7051    fn test_undo_manager_multiline_replacement_is_one_atomic_transaction(cx: &mut TestAppContext) {
7052        let input_view = InputView::build_textarea(cx, |state| state);
7053        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7054        let input = input_view.input;
7055
7056        cx.update(|window, cx| {
7057            input.update(cx, |state, cx| {
7058                state.replace_text_in_range(None, "before", window, cx);
7059                state.set_selected_range(0..6, cx);
7060                state.replace_text_in_range(None, "line one\nline two\n第三行", window, cx);
7061                assert_eq!(state.value(), "line one\nline two\n第三行");
7062
7063                state.undo(&Undo, window, cx);
7064                assert_eq!(state.value(), "before");
7065                assert_eq!(state.selected_range(), 0..6);
7066                state.redo(&Redo, window, cx);
7067                assert_eq!(state.value(), "line one\nline two\n第三行");
7068            });
7069        });
7070    }
7071
7072    #[gpui::test]
7073    fn test_undo_manager_composition_isolated_from_long_typing(cx: &mut TestAppContext) {
7074        let input_view = InputView::build(cx, |state| state);
7075        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7076        let input = input_view.input;
7077
7078        cx.update(|window, cx| {
7079            input.update(cx, |state, cx| {
7080                state.replace_text_in_range(None, "prefix ", window, cx);
7081                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
7082                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
7083                state.replace_and_mark_text_in_range(None, "你", None, window, cx);
7084                state.unmark_text(window, cx);
7085                state.replace_text_in_range(None, " suffix", window, cx);
7086                assert_eq!(state.value(), "prefix 你 suffix");
7087
7088                state.undo(&Undo, window, cx);
7089                assert_eq!(state.value(), "prefix 你");
7090                state.undo(&Undo, window, cx);
7091                assert_eq!(state.value(), "prefix ");
7092                state.undo(&Undo, window, cx);
7093                assert_eq!(state.value(), "");
7094            });
7095        });
7096    }
7097
7098    #[gpui::test]
7099    fn test_undo_manager_selected_replacement_is_atomic(cx: &mut TestAppContext) {
7100        let input_view = InputView::build(cx, |state| state);
7101        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7102        let input = input_view.input;
7103
7104        cx.update(|window, cx| {
7105            input.update(cx, |state, cx| {
7106                state.replace_text_in_range(None, "abc", window, cx);
7107                state.set_selected_range(1..2, cx);
7108                state.replace_text_in_range(None, "X", window, cx);
7109                state.replace_text_in_range(None, "z", window, cx);
7110                assert_eq!(state.value(), "aXzc");
7111
7112                state.undo(&Undo, window, cx);
7113                assert_eq!(state.value(), "aXc");
7114                state.undo(&Undo, window, cx);
7115                assert_eq!(state.value(), "abc");
7116                state.undo(&Undo, window, cx);
7117                assert_eq!(state.value(), "");
7118            });
7119        });
7120    }
7121
7122    #[gpui::test]
7123    fn test_number_input_leading_dot_editable(cx: &mut TestAppContext) {
7124        let input_view = InputView::build(cx, |state| {
7125            state.mask_pattern(MaskPattern::Number {
7126                separator: None,
7127                fraction: None,
7128            })
7129        });
7130        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7131        let input = input_view.input;
7132
7133        cx.update(|window, cx| {
7134            input.update(cx, |state, cx| {
7135                state.replace_text_in_range(None, "1.2", window, cx);
7136
7137                // Delete the integer part "1": the value keeps the leading dot
7138                // (".2"), not completed to "0.2", so the digits before the dot
7139                // stay editable.
7140                let range = state.range_to_utf16(&(0..1));
7141                state.replace_text_in_range(Some(range), "", window, cx);
7142                assert_eq!(state.value(), ".2");
7143                let cursor: Range<usize> = state.selected_range();
7144                assert_eq!(cursor, 0..0);
7145
7146                // The user can type a new integer part.
7147                state.replace_text_in_range(Some(0..0), "3", window, cx);
7148                assert_eq!(state.value(), "3.2");
7149            });
7150        });
7151    }
7152
7153    #[gpui::test]
7154    fn test_number_input_escape_invalid_text(cx: &mut TestAppContext) {
7155        // A pre-existing invalid text (e.g. a `default_value` that does not
7156        // conform) must not trap the user, the edit is allowed to fix it.
7157        let input_view = InputView::build(cx, |state| {
7158            state
7159                .mask_pattern(MaskPattern::Number {
7160                    separator: None,
7161                    fraction: None,
7162                })
7163                .default_value("1,234")
7164        });
7165        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7166        let input = input_view.input;
7167
7168        cx.update(|window, cx| {
7169            input.update(cx, |state, cx| {
7170                // Delete the last char, the pending text "1,23" is still
7171                // invalid, but the edit is allowed since the old text was
7172                // already invalid.
7173                let range = state.range_to_utf16(&(4..5));
7174                state.replace_text_in_range(Some(range), "", window, cx);
7175                assert_eq!(state.value(), "1,23");
7176
7177                // Once the text becomes valid, the validation works as usual.
7178                let range = state.range_to_utf16(&(1..2));
7179                state.replace_text_in_range(Some(range), "", window, cx);
7180                assert_eq!(state.value(), "123");
7181                state.replace_text_in_range(None, "a", window, cx);
7182                assert_eq!(state.value(), "123");
7183            });
7184        });
7185    }
7186
7187    /// After `set_value` on a single-line input the caret sits at the end (like
7188    /// HTML `<input>`), yet the view is scrolled back to the start so a long
7189    /// value shows its beginning instead of its tail.
7190    #[gpui::test]
7191    fn test_set_value_single_line_caret_at_end_view_at_start(cx: &mut TestAppContext) {
7192        let input_view = InputView::build(cx, |state| state);
7193        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7194        let input = input_view.input;
7195
7196        // Long enough to overflow any reasonable single-line input width.
7197        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
7198        let len = value.len();
7199
7200        // Right after `set_value`, before the next paint consumes the deferred
7201        // offset: caret is at the end, and the view is forced back to the start.
7202        cx.update(|window, cx| {
7203            input.update(cx, |state, cx| {
7204                state.set_value(value.clone(), window, cx);
7205
7206                assert_eq!(
7207                    state.selected_range(),
7208                    len..len,
7209                    "single-line caret should be at the end after set_value"
7210                );
7211                assert_eq!(
7212                    state.deferred_scroll_offset,
7213                    Some(point(px(0.), px(0.))),
7214                    "the view should be forced back to the start"
7215                );
7216            });
7217        });
7218
7219        // After a paint, the steady-state view stays at the start (x == 0) even
7220        // though the caret is at the far end.
7221        cx.run_until_parked();
7222        cx.update(|_, cx| {
7223            input.read_with(cx, |state, _| {
7224                assert!(
7225                    state.scroll_size.width > state.input_bounds.size.width,
7226                    "value must overflow the input width or this test is vacuous"
7227                );
7228                assert_eq!(
7229                    state.scroll_handle.offset().x,
7230                    px(0.),
7231                    "long value should display from its start, not its tail"
7232                );
7233            });
7234        });
7235    }
7236
7237    /// `replace_all` on a single-line input replaces the text, puts the
7238    /// caret at the end, and — like `set_value` — snaps the view back to the
7239    /// start so a long value shows its beginning instead of its tail.
7240    #[gpui::test]
7241    fn test_replace_all_single_line(cx: &mut TestAppContext) {
7242        let input_view = InputView::build(cx, |state| state);
7243        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7244        let input = input_view.input;
7245
7246        // Long enough to overflow any reasonable single-line input width.
7247        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
7248        let len = value.len();
7249
7250        // Right after `replace_all`, before the next paint consumes the
7251        // deferred offset: caret is at the end, and the view is forced back
7252        // to the start.
7253        cx.update(|window, cx| {
7254            input.update(cx, |state, cx| {
7255                state.set_value("hello", window, cx);
7256                state.replace_all(value.clone(), window, cx);
7257                assert_eq!(state.value(), value);
7258                assert_eq!(
7259                    state.selected_range(),
7260                    len..len,
7261                    "single-line caret should be at the end after replace_all"
7262                );
7263                assert_eq!(
7264                    state.scroll_handle.offset(),
7265                    point(px(0.), px(0.)),
7266                    "the scroll offset should be reset to the start"
7267                );
7268                assert_eq!(
7269                    state.deferred_scroll_offset,
7270                    Some(point(px(0.), px(0.))),
7271                    "single-line should set a deferred scroll offset to keep the start visible"
7272                );
7273            });
7274        });
7275
7276        // After a paint, the steady-state view stays at the start (x == 0)
7277        // even though the caret is at the far end.
7278        cx.run_until_parked();
7279        cx.update(|_, cx| {
7280            input.read_with(cx, |state, _| {
7281                assert!(
7282                    state.scroll_size.width > state.input_bounds.size.width,
7283                    "value must overflow the input width or this test is vacuous"
7284                );
7285                assert_eq!(
7286                    state.scroll_handle.offset().x,
7287                    px(0.),
7288                    "long value should display from its start, not its tail"
7289                );
7290            });
7291        });
7292    }
7293
7294    #[gpui::test]
7295    fn test_single_line_removes_newlines(cx: &mut TestAppContext) {
7296        let input_view = InputView::build(cx, |state| state.default_value("default\nvalue"));
7297        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7298        let input = input_view.input;
7299
7300        cx.update(|window, cx| {
7301            input.update(cx, |state, cx| {
7302                assert_eq!(state.value(), "defaultvalue");
7303
7304                state.set_value("first\nsecond\r\nthird\rfourth", window, cx);
7305                assert_eq!(state.value(), "firstsecondthirdfourth");
7306
7307                state.set_value("", window, cx);
7308                state.insert("a\nb", window, cx);
7309                assert_eq!(state.value(), "ab");
7310            });
7311
7312            cx.write_to_clipboard(ClipboardItem::new_string("a\r\nb\nc\rd".to_string()));
7313            input.update(cx, |state, cx| {
7314                state.set_value("", window, cx);
7315                state.paste(&Paste, window, cx);
7316                assert_eq!(state.value(), "abcd");
7317            });
7318        });
7319
7320        cx.run_until_parked();
7321    }
7322
7323    /// `replace_all` on a multi-line (non-code-editor) input clears the
7324    /// selection to `0..0` and resets the scroll offset, but does not set a
7325    /// deferred scroll offset (single-line only).
7326    #[gpui::test]
7327    fn test_replace_all_multi_line(cx: &mut TestAppContext) {
7328        let input_view = InputView::build_textarea(cx, |state| state);
7329        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7330        let input = input_view.input;
7331
7332        cx.update(|window, cx| {
7333            input.update(cx, |state, cx| {
7334                state.set_value("foo\nbar", window, cx);
7335                state.replace_all("baz\nqux", window, cx);
7336                assert_eq!(state.value(), "baz\nqux");
7337                assert_eq!(
7338                    state.selected_range(),
7339                    0..0,
7340                    "multi-line selection should be cleared after replace_all"
7341                );
7342                assert_eq!(
7343                    state.scroll_handle.offset(),
7344                    point(px(0.), px(0.)),
7345                    "the scroll offset should be reset to the start"
7346                );
7347                assert!(
7348                    state.deferred_scroll_offset.is_none(),
7349                    "multi-line should not set a deferred scroll offset"
7350                );
7351            });
7352        });
7353    }
7354
7355    /// Unlike `set_value`, `replace_all` records the change so the user can
7356    /// undo it back to the previous text and redo to the new text.
7357    #[gpui::test]
7358    fn test_replace_all_preserves_undo_history(cx: &mut TestAppContext) {
7359        let input_view = InputView::build(cx, |state| state);
7360        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7361        let input = input_view.input;
7362
7363        cx.update(|window, cx| {
7364            input.update(cx, |state, cx| {
7365                // Seed with a value and clear history so the baseline is clean.
7366                state.set_value("first", window, cx);
7367                assert!(
7368                    !state.undo_manager.has_undos(),
7369                    "history should be empty after set_value"
7370                );
7371
7372                // replace_all records a single undoable change.
7373                state.replace_all("second", window, cx);
7374                assert_eq!(state.value(), "second");
7375                assert!(
7376                    state.undo_manager.has_undos(),
7377                    "replace_all should record an undo step"
7378                );
7379
7380                // Undo restores the previous text.
7381                state.undo(&Undo, window, cx);
7382                assert_eq!(state.value(), "first");
7383
7384                // Redo reapplies the replacement.
7385                state.redo(&Redo, window, cx);
7386                assert_eq!(state.value(), "second");
7387            });
7388        });
7389    }
7390
7391    /// `replace_all` on a code editor marks a pending update and resets LSP
7392    /// state, so diagnostics/completions refresh against the new text.
7393    #[gpui::test]
7394    fn test_replace_all_code_editor(cx: &mut TestAppContext) {
7395        let input_view = InputView::new(cx);
7396        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7397        let input = input_view.input;
7398
7399        cx.update(|window, cx| {
7400            input.update(cx, |state, cx| {
7401                // Plant a pending-update flag and some LSP state to verify reset.
7402                state.set_value("select 1", window, cx);
7403                state._pending_update = false;
7404
7405                state.replace_all("select 2", window, cx);
7406                assert_eq!(state.value(), "select 2");
7407                assert!(
7408                    state._pending_update,
7409                    "replace_all on a code editor should request a pending update"
7410                );
7411            });
7412        });
7413    }
7414
7415    #[gpui::test]
7416    fn test_set_selected_range(cx: &mut TestAppContext) {
7417        let input_view = InputView::build(cx, |state| state.default_value("hello world"));
7418        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7419        let input = input_view.input;
7420
7421        cx.update(|_, cx| {
7422            input.update(cx, |s, cx| {
7423                s.set_selected_range(0..5, cx);
7424                assert_eq!(s.selected_range(), 0..5);
7425                assert_eq!(s.selected_text().to_string(), "hello");
7426
7427                s.set_selected_range(6..11, cx);
7428                assert_eq!(s.selected_text().to_string(), "world");
7429
7430                // clamped + collapsed
7431                s.set_selected_range(100..100, cx);
7432                assert_eq!(s.selected_range(), 11..11);
7433            });
7434        });
7435    }
7436
7437    /// A single-edit batch round-trips through undo/redo.
7438    #[gpui::test]
7439    fn test_replace_text_in_ranges_single_edit(cx: &mut TestAppContext) {
7440        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
7441        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7442        let input = input_view.input;
7443
7444        cx.update(|window, cx| {
7445            input.update(cx, |s, cx| {
7446                s.replace_text_in_ranges(&[(0..5, "HELLO".to_string())], window, cx);
7447                assert_eq!(s.value(), "HELLO world");
7448
7449                s.undo(&Undo, window, cx);
7450                assert_eq!(s.value(), "hello world");
7451
7452                s.redo(&Redo, window, cx);
7453                assert_eq!(s.value(), "HELLO world");
7454            });
7455        });
7456    }
7457
7458    /// A single undo restores the exact original text and a single redo
7459    /// re-applies all edits, verifying the back-to-front application ordering.
7460    #[gpui::test]
7461    fn test_replace_text_in_ranges_multi_edit_transaction(cx: &mut TestAppContext) {
7462        let input_view = InputView::build_textarea(cx, |state| state.default_value("aaa bbb ccc"));
7463        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7464        let input = input_view.input;
7465
7466        cx.update(|window, cx| {
7467            input.update(cx, |s, cx| {
7468                // Two edits at different positions, given in pre-edit
7469                // coordinates and in arbitrary (non-sorted) order.
7470                s.replace_text_in_ranges(
7471                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
7472                    window,
7473                    cx,
7474                );
7475                assert_eq!(s.value(), "X bbb Y");
7476
7477                // One collapsed cursor per edit, at the end of each inserted text.
7478                let cursors: Vec<usize> =
7479                    s.selections.iter().map(|sel| sel.cursor_offset()).collect();
7480                assert_eq!(cursors, vec![1, 7]);
7481
7482                // The whole batch is a single undo transaction.
7483                assert_eq!(s.undo_manager.undo_count(), 1);
7484
7485                // One undo restores the exact original text.
7486                s.undo(&Undo, window, cx);
7487                assert_eq!(s.value(), "aaa bbb ccc");
7488
7489                // One redo re-applies all edits.
7490                s.redo(&Redo, window, cx);
7491                assert_eq!(s.value(), "X bbb Y");
7492            });
7493        });
7494    }
7495
7496    /// A multi-edit change reaches the highlighter once, with every edit in
7497    /// application order and the text right after each, which is what the
7498    /// default `update_batch` replays through `update` one edit at a time.
7499    #[gpui::test]
7500    fn test_replace_text_in_ranges_drives_the_highlighter_once(cx: &mut TestAppContext) {
7501        use crate::input::{FoldRange, HighlightStyleResolver, InputEdit, InputHighlighter};
7502        use gpui::HighlightStyle;
7503        use std::cell::RefCell;
7504
7505        struct RecordingHighlighter(Rc<RefCell<Vec<Vec<String>>>>);
7506
7507        impl InputHighlighter for RecordingHighlighter {
7508            fn language(&self) -> SharedString {
7509                "recording".into()
7510            }
7511
7512            fn update(
7513                &mut self,
7514                _: Option<InputEdit>,
7515                text: &Rope,
7516                _: bool,
7517                _: &mut Window,
7518                _: &mut Context<EditorState>,
7519            ) {
7520                self.0.borrow_mut().push(vec![text.to_string()]);
7521            }
7522
7523            fn update_batch(
7524                &mut self,
7525                edits: &[(InputEdit, Rope)],
7526                _: bool,
7527                _: &mut Window,
7528                _: &mut Context<EditorState>,
7529            ) {
7530                let texts = edits.iter().map(|(_, text)| text.to_string()).collect();
7531                self.0.borrow_mut().push(texts);
7532            }
7533
7534            fn styles(
7535                &self,
7536                range: &Range<usize>,
7537                _: &dyn HighlightStyleResolver,
7538            ) -> Vec<(Range<usize>, HighlightStyle)> {
7539                vec![(range.clone(), HighlightStyle::default())]
7540            }
7541
7542            fn fold_ranges(&self, _: &Rope) -> Vec<FoldRange> {
7543                Vec::new()
7544            }
7545        }
7546
7547        let updates = Rc::new(RefCell::new(Vec::new()));
7548        let input_view = InputView::new(cx);
7549        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7550        let input = input_view.input;
7551
7552        cx.update(|window, cx| {
7553            input.update(cx, |s, cx| {
7554                s.set_value("aaa bbb ccc", window, cx);
7555                let recorded = updates.clone();
7556                s.set_highlighter_factory(
7557                    Rc::new(move |_: &str| {
7558                        let highlighter: Box<dyn InputHighlighter> =
7559                            Box::new(RecordingHighlighter(recorded.clone()));
7560                        Some(highlighter)
7561                    }),
7562                    cx,
7563                );
7564                s.replace_text_in_ranges(
7565                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
7566                    window,
7567                    cx,
7568                );
7569            });
7570        });
7571
7572        assert_eq!(
7573            *updates.borrow(),
7574            vec![vec!["aaa bbb Y".to_string(), "X bbb Y".to_string()]]
7575        );
7576    }
7577
7578    /// An IME composition (marking then commit) undoes as a single unit.
7579    #[gpui::test]
7580    fn test_ime_composition_undoes_as_one_unit(cx: &mut TestAppContext) {
7581        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
7582        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7583        let input = input_view.input;
7584
7585        cx.update(|window, cx| {
7586            input.update(cx, |s, cx| {
7587                // Simulate an IME composition: mark, refine, then commit.
7588                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
7589                s.replace_and_mark_text_in_range(None, "ni", Some(2..2), window, cx);
7590                s.replace_text_in_range(None, "你", window, cx);
7591                assert_eq!(s.value(), "你");
7592
7593                // The entire composition is one undo transaction.
7594                assert_eq!(s.undo_manager.undo_count(), 1);
7595
7596                s.undo(&Undo, window, cx);
7597                assert_eq!(s.value(), "");
7598
7599                s.redo(&Redo, window, cx);
7600                assert_eq!(s.value(), "你");
7601            });
7602        });
7603    }
7604
7605    /// A keystroke right after a committed composition must be its own undo
7606    /// entry, not merged into the (finalized) composition transaction.
7607    #[gpui::test]
7608    fn test_edit_after_composition_is_separate_undo(cx: &mut TestAppContext) {
7609        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
7610        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7611        let input = input_view.input;
7612
7613        cx.update(|window, cx| {
7614            input.update(cx, |s, cx| {
7615                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
7616                s.replace_text_in_range(None, "你", window, cx);
7617                assert_eq!(s.value(), "你");
7618                assert_eq!(s.undo_manager.undo_count(), 1);
7619
7620                // Typing after the commit is a distinct transaction.
7621                s.replace_text_in_range(None, "x", window, cx);
7622                assert_eq!(s.value(), "你x");
7623                assert_eq!(s.undo_manager.undo_count(), 2);
7624
7625                s.undo(&Undo, window, cx);
7626                assert_eq!(s.value(), "你");
7627                s.undo(&Undo, window, cx);
7628                assert_eq!(s.value(), "");
7629            });
7630        });
7631    }
7632
7633    /// Canceling a composition via `unmark_text` closes its transaction so it
7634    /// does not leak and swallow a later edit.
7635    #[gpui::test]
7636    fn test_composition_cancel_via_unmark_does_not_leak(cx: &mut TestAppContext) {
7637        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
7638        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7639        let input = input_view.input;
7640
7641        cx.update(|window, cx| {
7642            input.update(cx, |s, cx| {
7643                // Start a composition, then cancel it via unmark.
7644                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
7645                s.unmark_text(window, cx);
7646                let after_cancel = s.undo_manager.undo_count();
7647
7648                // A later edit is recorded independently.
7649                s.replace_text_in_range(None, "x", window, cx);
7650                assert_eq!(s.undo_manager.undo_count(), after_cancel + 1);
7651            });
7652        });
7653    }
7654
7655    #[gpui::test]
7656    fn test_set_selected_range_clips_to_utf8_boundaries(cx: &mut TestAppContext) {
7657        let input_view = InputView::build(cx, |state| state.default_value("éx"));
7658        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7659        let input = input_view.input;
7660
7661        cx.update(|window, cx| {
7662            input.update(cx, |state, cx| {
7663                state.set_selected_range(0..1, cx);
7664                assert_eq!(state.selected_range(), 0..2);
7665                state.copy(&Copy, window, cx);
7666
7667                state.set_selected_range(1..1, cx);
7668                assert_eq!(state.selected_range(), 0..0);
7669            });
7670        });
7671    }
7672
7673    #[gpui::test]
7674    fn test_ime_selection_is_relative_to_replacement_start(cx: &mut TestAppContext) {
7675        let input_view = InputView::build(cx, |state| state.default_value("你好 "));
7676        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7677        let input = input_view.input;
7678
7679        cx.update(|window, cx| {
7680            input.update(cx, |state, cx| {
7681                state.set_selected_range(7..7, cx);
7682                state.replace_and_mark_text_in_range(None, "s", Some(1..1), window, cx);
7683                state.replace_and_mark_text_in_range(None, "sh", Some(2..2), window, cx);
7684
7685                assert_eq!(state.value(), "你好 sh");
7686                assert_eq!(state.selected_range(), 9..9);
7687                assert_eq!(state.ime_marked_range, Some((7..9).into()));
7688            });
7689        });
7690    }
7691
7692    #[gpui::test]
7693    fn test_undo_manager_composition_is_one_undo_group(cx: &mut TestAppContext) {
7694        let input_view = InputView::build(cx, |state| state);
7695        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7696        let input = input_view.input;
7697
7698        cx.update(|window, cx| {
7699            input.update(cx, |state, cx| {
7700                state.set_value("a", window, cx);
7701                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
7702                state.replace_and_mark_text_in_range(None, "sh", None, window, cx);
7703                state.replace_text_in_range(None, "是", window, cx);
7704                assert_eq!(state.value(), "a是");
7705
7706                state.undo(&Undo, window, cx);
7707                assert_eq!(state.value(), "a");
7708                state.redo(&Redo, window, cx);
7709                assert_eq!(state.value(), "a是");
7710            });
7711        });
7712    }
7713
7714    #[gpui::test]
7715    fn test_undo_manager_consecutive_compositions_are_separate_groups(cx: &mut TestAppContext) {
7716        let input_view = InputView::build(cx, |state| state);
7717        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7718        let input = input_view.input;
7719
7720        cx.update(|window, cx| {
7721            input.update(cx, |state, cx| {
7722                // First composition: "jin" -> "今天"
7723                state.replace_and_mark_text_in_range(None, "j", None, window, cx);
7724                state.replace_and_mark_text_in_range(None, "jin", None, window, cx);
7725                state.replace_text_in_range(None, "今天", window, cx);
7726                // Second composition: "wo" -> "我们"
7727                state.replace_and_mark_text_in_range(None, "w", None, window, cx);
7728                state.replace_and_mark_text_in_range(None, "wo", None, window, cx);
7729                state.replace_text_in_range(None, "我们", window, cx);
7730                assert_eq!(state.value(), "今天我们");
7731                assert_eq!(state.selected_range(), 12..12);
7732
7733                state.undo(&Undo, window, cx);
7734                assert_eq!(state.value(), "今天");
7735                assert_eq!(state.selected_range(), 6..6);
7736
7737                state.undo(&Undo, window, cx);
7738                assert_eq!(state.value(), "");
7739                assert_eq!(state.selected_range(), 0..0);
7740
7741                state.redo(&Redo, window, cx);
7742                assert_eq!(state.value(), "今天");
7743                assert_eq!(state.selected_range(), 6..6);
7744
7745                state.redo(&Redo, window, cx);
7746                assert_eq!(state.value(), "今天我们");
7747                assert_eq!(state.selected_range(), 12..12);
7748            });
7749        });
7750    }
7751
7752    #[gpui::test]
7753    fn test_undo_manager_typing_after_composition_is_a_separate_group(cx: &mut TestAppContext) {
7754        let input_view = InputView::build(cx, |state| state);
7755        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7756        let input = input_view.input;
7757
7758        cx.update(|window, cx| {
7759            input.update(cx, |state, cx| {
7760                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
7761                state.replace_text_in_range(None, "你", window, cx);
7762                state.undo_manager.set_pending_intent(EditIntent::Typing);
7763                state.replace_text_in_range(None, "a", window, cx);
7764                state.undo_manager.set_pending_intent(EditIntent::Typing);
7765                state.replace_text_in_range(None, "b", window, cx);
7766                assert_eq!(state.value(), "你ab");
7767
7768                state.undo(&Undo, window, cx);
7769                assert_eq!(state.value(), "你");
7770
7771                state.undo(&Undo, window, cx);
7772                assert_eq!(state.value(), "");
7773            });
7774        });
7775    }
7776
7777    #[gpui::test]
7778    fn test_undo_manager_composition_cancel_leaves_no_entry(cx: &mut TestAppContext) {
7779        let input_view = InputView::build(cx, |state| state);
7780        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7781        let input = input_view.input;
7782
7783        cx.update(|window, cx| {
7784            input.update(cx, |state, cx| {
7785                state.set_value("a", window, cx);
7786                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
7787                state.replace_and_mark_text_in_range(None, "", None, window, cx);
7788
7789                assert_eq!(state.value(), "a");
7790                assert!(!state.undo_manager.has_undos());
7791            });
7792        });
7793    }
7794
7795    /// The macOS Korean IME rewrites the character it just inserted.
7796    #[gpui::test]
7797    fn test_undo_manager_ime_rewrites_of_typed_char_are_one_group(cx: &mut TestAppContext) {
7798        let input_view = InputView::build(cx, |state| state);
7799        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7800        let input = input_view.input;
7801
7802        cx.update(|window, cx| {
7803            input.update(cx, |state, cx| {
7804                state.set_value("é ", window, cx);
7805                state.set_selected_range(3..3, cx);
7806                // g k s r m f: "ㅎ" -> "하" -> "한", then "ㄱ" -> "그" -> "글"
7807                state.replace_text_in_range(None, "ㅎ", window, cx);
7808                state.replace_text_in_range(Some(2..3), "하", window, cx);
7809                state.replace_text_in_range(Some(2..3), "한", window, cx);
7810                state.replace_text_in_range(None, "ㄱ", window, cx);
7811                state.replace_text_in_range(Some(3..4), "그", window, cx);
7812                state.replace_text_in_range(Some(3..4), "글", window, cx);
7813                assert_eq!(state.value(), "é 한글");
7814                assert_eq!(state.undo_manager.undo_count(), 1);
7815
7816                state.undo(&Undo, window, cx);
7817                assert_eq!(state.value(), "é ");
7818                assert_eq!(state.selected_range(), 3..3);
7819
7820                state.redo(&Redo, window, cx);
7821                assert_eq!(state.value(), "é 한글");
7822                assert_eq!(state.selected_range(), 9..9);
7823            });
7824        });
7825    }
7826
7827    #[gpui::test]
7828    fn test_undo_manager_rewrite_after_cursor_movement_is_separate(cx: &mut TestAppContext) {
7829        let input_view = InputView::build(cx, |state| state);
7830        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7831        let input = input_view.input;
7832
7833        cx.update(|window, cx| {
7834            input.update(cx, |state, cx| {
7835                state.replace_text_in_range(None, "c", window, cx);
7836                state.replace_text_in_range(None, "e", window, cx);
7837                state.left(&MoveLeft, window, cx);
7838                state.right(&MoveRight, window, cx);
7839                state.replace_text_in_range(Some(1..2), "é", window, cx);
7840                assert_eq!(state.value(), "cé");
7841
7842                state.undo(&Undo, window, cx);
7843                assert_eq!(state.value(), "ce");
7844                state.undo(&Undo, window, cx);
7845                assert_eq!(state.value(), "");
7846            });
7847        });
7848    }
7849
7850    #[gpui::test]
7851    fn test_undo_manager_selection_restored_by_undo_and_redo(cx: &mut TestAppContext) {
7852        let input_view = InputView::build(cx, |state| state);
7853        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7854        let input = input_view.input;
7855
7856        cx.update(|window, cx| {
7857            input.update(cx, |state, cx| {
7858                state.set_value("abc", window, cx);
7859                state.set_selected_range(1..2, cx);
7860                state.replace_text_in_range(None, "X", window, cx);
7861
7862                state.undo(&Undo, window, cx);
7863                assert_eq!(state.value(), "abc");
7864                assert_eq!(state.selected_range(), 1..2);
7865
7866                state.redo(&Redo, window, cx);
7867                assert_eq!(state.value(), "aXc");
7868                assert_eq!(state.selected_range(), 2..2);
7869            });
7870        });
7871    }
7872
7873    #[gpui::test]
7874    fn test_undo_manager_forward_delete_restores_cursor(cx: &mut TestAppContext) {
7875        let input_view = InputView::build(cx, |state| state);
7876        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7877        let input = input_view.input;
7878
7879        cx.update(|window, cx| {
7880            input.update(cx, |state, cx| {
7881                state.set_value("abc", window, cx);
7882                state.set_selected_range(1..1, cx);
7883                state.delete(&Delete, window, cx);
7884
7885                state.undo(&Undo, window, cx);
7886                assert_eq!(state.value(), "abc");
7887                assert_eq!(state.selected_range(), 1..1);
7888            });
7889        });
7890    }
7891
7892    #[gpui::test]
7893    fn test_undo_manager_selection_movement_preserves_redo(cx: &mut TestAppContext) {
7894        let input_view = InputView::build(cx, |state| state);
7895        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7896        let input = input_view.input;
7897
7898        cx.update(|window, cx| {
7899            input.update(cx, |state, cx| {
7900                state.replace_text_in_range(None, "ab", window, cx);
7901                state.undo(&Undo, window, cx);
7902                state.set_selected_range(0..0, cx);
7903                state.redo(&Redo, window, cx);
7904                assert_eq!(state.value(), "ab");
7905
7906                state.undo(&Undo, window, cx);
7907                state.replace_text_in_range(None, "x", window, cx);
7908                state.redo(&Redo, window, cx);
7909                assert_eq!(state.value(), "x");
7910            });
7911        });
7912    }
7913
7914    #[gpui::test]
7915    fn test_undo_manager_noop_edit_preserves_redo(cx: &mut TestAppContext) {
7916        let input_view = InputView::build(cx, |state| state);
7917        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7918        let input = input_view.input;
7919
7920        cx.update(|window, cx| {
7921            input.update(cx, |state, cx| {
7922                state.replace_text_in_range(None, "a", window, cx);
7923                state.move_to(0, None, cx);
7924                state.replace_text_in_range(None, "", window, cx);
7925                state.undo(&Undo, window, cx);
7926                assert_eq!(state.value(), "");
7927                state.redo(&Redo, window, cx);
7928                assert_eq!(state.value(), "a");
7929                assert_eq!(state.cursor(), 1);
7930            });
7931        });
7932    }
7933
7934    #[gpui::test]
7935    fn test_cursor_round_trip_stops_typing_coalescing(cx: &mut TestAppContext) {
7936        let input_view = InputView::build(cx, |state| state);
7937        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7938        let input = input_view.input;
7939
7940        cx.update(|window, cx| {
7941            input.update(cx, |state, cx| {
7942                state.replace_text_in_range(None, "a", window, cx);
7943                state.left(&MoveLeft, window, cx);
7944                state.right(&MoveRight, window, cx);
7945                state.replace_text_in_range(None, "b", window, cx);
7946
7947                state.undo(&Undo, window, cx);
7948                assert_eq!(state.value(), "a");
7949                state.undo(&Undo, window, cx);
7950                assert_eq!(state.value(), "");
7951            });
7952        });
7953    }
7954
7955    #[gpui::test]
7956    fn test_undo_manager_noop_edit_breaks_coalescing_without_clearing_history(
7957        cx: &mut TestAppContext,
7958    ) {
7959        let input_view = InputView::build(cx, |state| state);
7960        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7961        let input = input_view.input;
7962
7963        cx.update(|window, cx| {
7964            input.update(cx, |state, cx| {
7965                state.replace_text_in_range(None, "alpha", window, cx);
7966                state.replace_text_in_range(None, "", window, cx);
7967                state.replace_text_in_range(None, "beta", window, cx);
7968                assert_eq!(state.value(), "alphabeta");
7969
7970                state.undo(&Undo, window, cx);
7971                assert_eq!(state.value(), "alpha");
7972                state.undo(&Undo, window, cx);
7973                assert_eq!(state.value(), "");
7974            });
7975        });
7976    }
7977
7978    #[gpui::test]
7979    fn test_undo_manager_masked_redo_restores_actual_cursor(cx: &mut TestAppContext) {
7980        let input_view = InputView::build(cx, |state| {
7981            state.mask_pattern(MaskPattern::Number {
7982                separator: Some(','),
7983                fraction: None,
7984            })
7985        });
7986        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7987        let input = input_view.input;
7988
7989        cx.update(|window, cx| {
7990            input.update(cx, |state, cx| {
7991                state.set_value("12345", window, cx);
7992                state.set_selected_range(2..2, cx);
7993                state.replace_text_in_range(None, "9", window, cx);
7994                let selection_after_edit = state.selected_range();
7995                assert_ne!(selection_after_edit.end, state.value().len());
7996
7997                state.undo(&Undo, window, cx);
7998                state.redo(&Redo, window, cx);
7999                assert_eq!(state.selected_range(), selection_after_edit);
8000            });
8001        });
8002    }
8003
8004    /// Unfolding at a position opens exactly the folds hiding it.
8005    ///
8006    /// A fold keeps its own first and last line visible, so a position on
8007    /// either of them opens nothing. Nested folds all open at once, sibling
8008    /// folds stay closed, and the opened ranges stay fold candidates.
8009    #[gpui::test]
8010    fn test_unfold_at(cx: &mut TestAppContext) {
8011        use crate::input::{FoldRange, Position};
8012
8013        let view = InputView::<EditorMode>::new(cx);
8014        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8015        let input = view.input;
8016
8017        // An outer fold over lines 0..=5, a fold nested inside it, and a
8018        // sibling fold that must never be touched.
8019        cx.update(|window, cx| {
8020            input.update(cx, |state, cx| {
8021                state.set_value("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl", window, cx);
8022                state.apply_highlighter_fold_candidates(
8023                    vec![
8024                        FoldRange::new(0, 5),
8025                        FoldRange::new(2, 4),
8026                        FoldRange::new(7, 10),
8027                    ],
8028                    cx,
8029                );
8030                state.display_map.set_folded(0, true);
8031                state.display_map.set_folded(2, true);
8032                state.display_map.set_folded(7, true);
8033            });
8034        });
8035
8036        // The outer fold's own first and last line stay visible, so neither
8037        // position opens anything.
8038        for line in [0, 5] {
8039            cx.update(|_, cx| {
8040                input.update(cx, |state, cx| {
8041                    assert!(!state.display_map.is_buffer_line_hidden(line));
8042                    assert!(!state.unfold_at(Position::new(line as u32, 0), cx));
8043                });
8044                input.read_with(cx, |state, _| {
8045                    assert!(state.display_map.is_folded_at(0));
8046                    assert!(state.display_map.is_folded_at(2));
8047                    assert!(state.display_map.is_folded_at(7));
8048                });
8049            });
8050        }
8051
8052        // Line 3 is hidden by both the outer and the nested fold, so both
8053        // open; the sibling fold does not.
8054        cx.update(|_, cx| {
8055            input.update(cx, |state, cx| {
8056                assert!(state.unfold_at(Position::new(3, 0), cx));
8057            });
8058            input.read_with(cx, |state, _| {
8059                assert!(!state.display_map.is_buffer_line_hidden(3));
8060                assert!(!state.display_map.is_folded_at(0));
8061                assert!(!state.display_map.is_folded_at(2));
8062                assert!(state.display_map.is_folded_at(7));
8063                // The opened ranges are still candidates for refolding.
8064                assert!(state.display_map.is_fold_candidate(0));
8065                assert!(state.display_map.is_fold_candidate(2));
8066            });
8067        });
8068
8069        // Nothing is hidden there any more, so a second call is a no-op.
8070        cx.update(|_, cx| {
8071            input.update(cx, |state, cx| {
8072                assert!(!state.unfold_at(Position::new(3, 0), cx));
8073            });
8074        });
8075    }
8076
8077    /// Losing focus hides the hover popover but keeps the decorations.
8078    ///
8079    /// Both used to be dropped by one call, so clicking away threw away
8080    /// decorations the application had installed and never asked to remove.
8081    #[gpui::test]
8082    fn test_blur_keeps_decorations(cx: &mut TestAppContext) {
8083        let view = InputView::<EditorMode>::new(cx);
8084        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8085        let input = view.input;
8086
8087        cx.update(|window, cx| {
8088            input.update(cx, |state, cx| {
8089                state.set_value("select 1", window, cx);
8090                let _collection = state.create_decorations_collection(
8091                    vec![crate::input::TextDecoration::new(
8092                        0..6,
8093                        gpui::HighlightStyle {
8094                            font_weight: Some(gpui::FontWeight::BOLD),
8095                            ..Default::default()
8096                        },
8097                    )],
8098                    cx,
8099                );
8100                state.present_hover(
8101                    0..6,
8102                    lsp_types::Hover {
8103                        contents: lsp_types::HoverContents::Scalar(
8104                            lsp_types::MarkedString::String("docs".into()),
8105                        ),
8106                        range: None,
8107                    },
8108                    cx,
8109                );
8110                assert!(state.hover_popover().is_some());
8111
8112                state.on_blur(window, cx);
8113
8114                assert!(
8115                    state.hover_popover().is_none(),
8116                    "blur should hide the hover popover"
8117                );
8118                let decorations = state.extras.decoration_layers();
8119                assert!(
8120                    decorations.iter().any(|layer| !layer.is_empty()),
8121                    "blur must not discard decorations"
8122                );
8123            });
8124        });
8125    }
8126
8127    /// The mode marker is the only source of truth for the kind of input.
8128    ///
8129    /// An auto-growing textarea capped at one row used to report itself as
8130    /// single-line, because the answer was derived from the row counts.
8131    #[gpui::test]
8132    fn test_kind_does_not_follow_the_row_count(cx: &mut TestAppContext) {
8133        let view = InputView::build_textarea(cx, |state| state.auto_grow(1, 1));
8134        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8135        view.input.read_with(&mut cx, |state, _| {
8136            assert!(state.is_multi_line());
8137            assert!(!state.is_single_line());
8138            assert!(!state.is_code_editor());
8139        });
8140    }
8141
8142    /// Soft wrap is on by default, for every mode that can wrap.
8143    ///
8144    /// The default lives in the shared constructor, where a mode-specific
8145    /// `new` can silently fail to restore it; this pins it down.
8146    #[gpui::test]
8147    fn test_soft_wrap_is_enabled_by_default(cx: &mut TestAppContext) {
8148        let textarea = InputView::build_textarea(cx, |state| state);
8149        let mut textarea_cx = VisualTestContext::from_window(textarea.window_handle.into(), cx);
8150        textarea
8151            .input
8152            .read_with(&mut textarea_cx, |state, _| assert!(state.soft_wrap));
8153
8154        let editor = InputView::<EditorMode>::new(cx);
8155        let mut editor_cx = VisualTestContext::from_window(editor.window_handle.into(), cx);
8156        editor
8157            .input
8158            .read_with(&mut editor_cx, |state, _| assert!(state.soft_wrap));
8159    }
8160
8161    /// Parse a cursor spec into `(text, cursor_offsets)`. Non-empty lines are
8162    /// joined with `\n` plus a trailing `\n`. `|` marks a cursor. Leading
8163    /// whitespace is kept, so a spec can express indentation.
8164    fn parse_cursor_spec(input: &str) -> (String, Vec<usize>) {
8165        let mut full_text = String::new();
8166        let mut cursor_offsets = Vec::new();
8167        let non_empty_lines: Vec<&str> = input.lines().filter(|l| !l.is_empty()).collect();
8168
8169        for (line_idx, line) in non_empty_lines.iter().enumerate() {
8170            let mut positions = Vec::new();
8171            let mut text = String::new();
8172            for ch in line.chars() {
8173                if ch == '|' {
8174                    positions.push(text.len());
8175                } else {
8176                    text.push(ch);
8177                }
8178            }
8179
8180            if line_idx > 0 {
8181                full_text.push('\n');
8182            }
8183            let line_start = full_text.len();
8184            for pos in positions {
8185                cursor_offsets.push(line_start + pos);
8186            }
8187            full_text.push_str(&text);
8188        }
8189        full_text.push('\n');
8190
8191        (full_text, cursor_offsets)
8192    }
8193
8194    /// Build a multi-line input for multi-cursor tests.
8195    fn multi_line(cx: &mut TestAppContext) -> InputView<TextareaMode> {
8196        InputView::build_textarea(cx, |state| state)
8197    }
8198
8199    /// Set the text and cursor positions from a spec (see [`parse_cursor_spec`]).
8200    fn setup_cursors<M: InputModeKind>(
8201        cx: &mut VisualTestContext,
8202        input: &Entity<InputBaseState<M>>,
8203        spec: &str,
8204    ) {
8205        let (full_text, offsets) = parse_cursor_spec(spec);
8206        cx.update(|window, cx| {
8207            input.update(cx, |state, cx| {
8208                state.set_value(&full_text, window, cx);
8209                let selections = offsets
8210                    .into_iter()
8211                    .map(|offset| {
8212                        CursorSelection::new(state.selections.generate_id(), offset, offset)
8213                    })
8214                    .collect();
8215                state.selections.replace_all(selections);
8216                cx.notify();
8217            });
8218        });
8219    }
8220
8221    /// Assert the text and cursor positions match a spec.
8222    #[track_caller]
8223    fn assert_cursors<M: InputModeKind>(
8224        cx: &mut VisualTestContext,
8225        input: &Entity<InputBaseState<M>>,
8226        spec: &str,
8227    ) {
8228        let (expected_text, mut expected_cursors) = parse_cursor_spec(spec);
8229        expected_cursors.sort();
8230
8231        let (actual_text, mut actual_cursors) = input.read_with(cx, |state, _| {
8232            (
8233                state.text.to_string(),
8234                state
8235                    .selections
8236                    .iter()
8237                    .map(|s| s.cursor_offset())
8238                    .collect::<Vec<_>>(),
8239            )
8240        });
8241        actual_cursors.sort();
8242
8243        assert_eq!(
8244            actual_text, expected_text,
8245            "Text mismatch:\nExpected: {expected_text:?}\nActual:   {actual_text:?}"
8246        );
8247        assert_eq!(
8248            actual_cursors, expected_cursors,
8249            "Cursor mismatch:\nExpected: {expected_cursors:?}\nActual:   {actual_cursors:?}"
8250        );
8251    }
8252
8253    #[gpui::test]
8254    fn test_alt_drag_selects_a_block_and_replaces_each_row(cx: &mut TestAppContext) {
8255        cx.update(crate::init);
8256        let view = InputView::<EditorMode>::new(cx);
8257        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8258        for modifiers in [
8259            gpui::Modifiers {
8260                alt: true,
8261                ..Default::default()
8262            },
8263            gpui::Modifiers {
8264                alt: true,
8265                shift: true,
8266                ..Default::default()
8267            },
8268            #[cfg(target_os = "linux")]
8269            gpui::Modifiers {
8270                alt: true,
8271                control: true,
8272                ..Default::default()
8273            },
8274        ] {
8275            setup_cursors(&mut cx, &view.input, "|abcd\nabcd\nabcd");
8276            cx.update(|window, cx| {
8277                view.input.update(cx, |state, cx| state.focus(window, cx));
8278            });
8279            let (start, end) = view.input.read_with(&cx, |state, _| {
8280                let layout = state.last_layout.as_ref().unwrap();
8281                let origin = state.last_bounds.unwrap().origin;
8282                let position = |row: usize, col| {
8283                    let local = layout.lines[row]
8284                        .position_for_index(col, layout, false)
8285                        .unwrap();
8286                    origin
8287                        + point(
8288                            layout.line_number_width + local.x,
8289                            layout.line_height * (row as f32 + 0.5),
8290                        )
8291                };
8292                (position(0, 1), position(2, 3))
8293            });
8294            // Land inside the third column instead of exactly on its trailing
8295            // glyph boundary, where pixel rounding can select the next column.
8296            let end = point(end.x - px(0.5), end.y);
8297            // A cached Ctrl-hover definition must not steal a column gesture.
8298            cx.update(|_, cx| {
8299                view.input.update(cx, |state, _| {
8300                    state.extras.hover_definition.update(
8301                        0..4,
8302                        vec![lsp_types::LocationLink {
8303                            origin_selection_range: None,
8304                            target_uri: "file:///tmp/column-selection.rs".parse().unwrap(),
8305                            target_range: Default::default(),
8306                            target_selection_range: Default::default(),
8307                        }],
8308                    );
8309                });
8310            });
8311            cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
8312            cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
8313            cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
8314            view.input.read_with(&cx, |state, _| {
8315                let ranges: Vec<_> = state
8316                    .selections
8317                    .iter()
8318                    .map(|sel| sel.start..sel.end)
8319                    .collect();
8320                assert_eq!(ranges, vec![1..3, 6..8, 11..13]);
8321            });
8322            // Moving after release must leave the block intact.
8323            cx.simulate_mouse_move(start, None, modifiers);
8324            cx.simulate_keystrokes("x");
8325            assert_cursors(&mut cx, &view.input, "ax|d\nax|d\nax|d");
8326        }
8327    }
8328
8329    #[gpui::test]
8330    fn test_alt_drag_extends_upward_from_an_existing_cursor(cx: &mut TestAppContext) {
8331        cx.update(crate::init);
8332        let view = InputView::<EditorMode>::new(cx);
8333        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8334        setup_cursors(&mut cx, &view.input, "abcd\nabcd\na|bcd");
8335        cx.update(|window, cx| {
8336            view.input.update(cx, |state, cx| state.focus(window, cx));
8337        });
8338        let (start, end) = view.input.read_with(&cx, |state, _| {
8339            let layout = state.last_layout.as_ref().unwrap();
8340            let origin = state.last_bounds.unwrap().origin;
8341            let local = layout.lines[0]
8342                .position_for_index(1, layout, false)
8343                .unwrap();
8344            let x = layout.line_number_width + local.x;
8345            (
8346                origin + point(x, layout.line_height * 2.5),
8347                origin + point(x, layout.line_height * 0.5),
8348            )
8349        });
8350        let modifiers = gpui::Modifiers {
8351            alt: true,
8352            ..Default::default()
8353        };
8354        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
8355        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
8356        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
8357        assert_cursors(&mut cx, &view.input, "a|bcd\na|bcd\na|bcd");
8358    }
8359
8360    #[gpui::test]
8361    fn test_alt_drag_over_a_short_row_keeps_the_block_width(cx: &mut TestAppContext) {
8362        cx.update(crate::init);
8363        let view = InputView::<EditorMode>::new(cx);
8364        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8365        setup_cursors(&mut cx, &view.input, "abcdef\nab\nabcdef");
8366        cx.update(|window, cx| {
8367            view.input.update(cx, |state, cx| state.focus(window, cx));
8368        });
8369        // Drag from row 0 column 1 down to row 1, at the x of row 0's column 5. Row 1
8370        // only has two characters, so the pointer ends past its end.
8371        let (start, end) = view.input.read_with(&cx, |state, _| {
8372            let layout = state.last_layout.as_ref().unwrap();
8373            let origin = state.last_bounds.unwrap().origin;
8374            let x_of = |index| {
8375                layout.line_number_width
8376                    + layout.lines[0]
8377                        .position_for_index(index, layout, false)
8378                        .unwrap()
8379                        .x
8380            };
8381            (
8382                origin + point(x_of(1), layout.line_height * 0.5),
8383                origin + point(x_of(5), layout.line_height * 1.5),
8384            )
8385        });
8386        let modifiers = gpui::Modifiers {
8387            alt: true,
8388            shift: true,
8389            ..Default::default()
8390        };
8391        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
8392        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
8393        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
8394
8395        // The short row is clipped to its own end; the long row keeps the full span.
8396        view.input.read_with(&cx, |state, _| {
8397            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
8398            assert_eq!(ranges, vec![(1, 5), (8, 9)]);
8399        });
8400    }
8401
8402    #[gpui::test]
8403    fn test_alt_mouse_release_outside_editor_ends_column_selection(cx: &mut TestAppContext) {
8404        cx.update(crate::init);
8405        let view = InputView::<EditorMode>::new(cx);
8406        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8407        setup_cursors(&mut cx, &view.input, "|abcd\nabcd");
8408        let position = view.input.read_with(&cx, |state, _| {
8409            let layout = state.last_layout.as_ref().unwrap();
8410            state.last_bounds.unwrap().origin
8411                + point(layout.line_number_width + px(2.), layout.line_height * 0.5)
8412        });
8413        let modifiers = gpui::Modifiers {
8414            alt: true,
8415            ..Default::default()
8416        };
8417        cx.simulate_mouse_down(position, MouseButton::Left, modifiers);
8418        cx.simulate_mouse_up(point(px(-100.), px(-100.)), MouseButton::Left, modifiers);
8419        view.input.read_with(&cx, |state, _| {
8420            assert!(!state.selecting);
8421            assert!(state.column_select_start.is_none());
8422        });
8423    }
8424
8425    #[gpui::test]
8426    fn test_consumed_keystrokes_keep_cursor_visible(cx: &mut TestAppContext) {
8427        let view = InputView::<EditorMode>::new(cx);
8428        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8429        setup_cursors(&mut cx, &view.input, "a|b");
8430        cx.update(|window, cx| {
8431            view.input.update(cx, |state, cx| {
8432                state.focus(window, cx);
8433                state.pause_blink_cursor(cx);
8434            });
8435        });
8436        cx.run_until_parked();
8437        cx.executor()
8438            .advance_clock(std::time::Duration::from_millis(300));
8439        cx.run_until_parked();
8440        view.input.read_with(&cx, |state, cx| {
8441            assert!(!state.blink_cursor.read(cx).visible());
8442        });
8443        // Copy consumes its shortcut without editing text or moving selections.
8444        for _ in 0..5 {
8445            #[cfg(target_os = "macos")]
8446            cx.simulate_keystrokes("cmd-c");
8447            #[cfg(not(target_os = "macos"))]
8448            cx.simulate_keystrokes("ctrl-c");
8449            cx.run_until_parked();
8450            cx.executor()
8451                .advance_clock(std::time::Duration::from_millis(200));
8452            cx.run_until_parked();
8453            view.input.read_with(&cx, |state, cx| {
8454                assert!(state.blink_cursor.read(cx).visible());
8455            });
8456        }
8457    }
8458
8459    #[gpui::test]
8460    fn test_multi_cursor_actions_reveal_hidden_carets(cx: &mut TestAppContext) {
8461        let view = InputView::<EditorMode>::new(cx);
8462        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8463        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
8464        // Start each action in the hidden phase without depending on a
8465        // key-down listener: actions and text input also arrive directly.
8466        for action in 0..6 {
8467            cx.update(|_, cx| {
8468                view.input.update(cx, |state, cx| {
8469                    state.blink_cursor = cx.new(|_| BlinkCursor::new());
8470                    state.blink_cursor.update(cx, |cursor, cx| cursor.start(cx));
8471                });
8472            });
8473            cx.run_until_parked();
8474            cx.executor()
8475                .advance_clock(std::time::Duration::from_millis(500));
8476            cx.run_until_parked();
8477            view.input.read_with(&cx, |state, cx| {
8478                assert!(!state.blink_cursor.read(cx).visible(), "action {action}");
8479            });
8480
8481            cx.update(|window, cx| {
8482                view.input.update(cx, |state, cx| {
8483                    match action {
8484                        0 => state.add_cursor_above(&AddCursorAbove, window, cx),
8485                        1 => state.add_cursor_below(&AddCursorBelow, window, cx),
8486                        2 => state.select_up(&SelectUp, window, cx),
8487                        3 => state.select_down(&SelectDown, window, cx),
8488                        4 => state.replace_text_in_range(None, "x", window, cx),
8489                        _ => state.backspace(&Backspace, window, cx),
8490                    }
8491                    assert!(state.blink_cursor.read(cx).visible(), "action {action}");
8492                });
8493            });
8494        }
8495    }
8496
8497    #[gpui::test]
8498    fn test_multi_cursor_keyboard_dispatch(cx: &mut TestAppContext) {
8499        let view = InputView::<EditorMode>::new(cx);
8500        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8501        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
8502        cx.update(|window, cx| {
8503            view.input.update(cx, |state, cx| state.focus(window, cx));
8504        });
8505        #[cfg(target_os = "macos")]
8506        cx.simulate_keystrokes("cmd-alt-up");
8507        #[cfg(target_os = "windows")]
8508        cx.simulate_keystrokes("ctrl-alt-up");
8509        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
8510        cx.simulate_keystrokes("alt-shift-up");
8511        view.input
8512            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
8513        #[cfg(target_os = "macos")]
8514        cx.simulate_keystrokes("cmd-alt-down");
8515        #[cfg(target_os = "windows")]
8516        cx.simulate_keystrokes("ctrl-alt-down");
8517        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
8518        cx.simulate_keystrokes("alt-shift-down");
8519        view.input
8520            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 3));
8521        cx.simulate_keystrokes("x");
8522        assert_cursors(&mut cx, &view.input, "ax|b\nax|b\nax|b");
8523    }
8524
8525    #[gpui::test]
8526    fn test_multi_cursor_platform_word_selection_dispatch(cx: &mut TestAppContext) {
8527        let view = InputView::<EditorMode>::new(cx);
8528        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8529        setup_cursors(&mut cx, &view.input, "one |two\none |two");
8530        cx.update(|window, cx| {
8531            view.input.update(cx, |state, cx| state.focus(window, cx));
8532        });
8533        #[cfg(any(target_os = "macos", target_os = "linux"))]
8534        cx.simulate_keystrokes("alt-shift-right");
8535        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
8536        cx.simulate_keystrokes("ctrl-shift-right");
8537        cx.simulate_keystrokes("x");
8538        assert_cursors(&mut cx, &view.input, "one x|\none x|");
8539        setup_cursors(&mut cx, &view.input, "one two|\none two|");
8540        #[cfg(any(target_os = "macos", target_os = "linux"))]
8541        cx.simulate_keystrokes("alt-shift-left");
8542        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
8543        cx.simulate_keystrokes("ctrl-shift-left");
8544        cx.simulate_keystrokes("x");
8545        assert_cursors(&mut cx, &view.input, "one x|\none x|");
8546    }
8547
8548    #[cfg(not(target_os = "macos"))]
8549    #[gpui::test]
8550    fn test_multi_cursor_horizontal_selection_dispatch(cx: &mut TestAppContext) {
8551        let view = InputView::<EditorMode>::new(cx);
8552        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8553        setup_cursors(&mut cx, &view.input, "ab\na|b");
8554        cx.update(|window, cx| {
8555            view.input.update(cx, |state, cx| state.focus(window, cx));
8556        });
8557        #[cfg(target_os = "windows")]
8558        cx.simulate_keystrokes("ctrl-alt-up");
8559        #[cfg(not(target_os = "windows"))]
8560        cx.simulate_keystrokes("alt-shift-up");
8561        #[cfg(target_os = "linux")]
8562        cx.simulate_keystrokes("shift-right");
8563        #[cfg(not(target_os = "linux"))]
8564        cx.simulate_keystrokes("alt-shift-right");
8565        view.input.read_with(&cx, |state, _| {
8566            assert_eq!(
8567                state
8568                    .selections
8569                    .iter()
8570                    .map(|s| s.start..s.end)
8571                    .collect::<Vec<_>>(),
8572                vec![4..5, 1..2]
8573            );
8574        });
8575        #[cfg(target_os = "linux")]
8576        cx.simulate_keystrokes("shift-left shift-left");
8577        #[cfg(not(target_os = "linux"))]
8578        cx.simulate_keystrokes("alt-shift-left alt-shift-left");
8579        view.input.read_with(&cx, |state, _| {
8580            assert_eq!(
8581                state
8582                    .selections
8583                    .iter()
8584                    .map(|s| s.start..s.end)
8585                    .collect::<Vec<_>>(),
8586                vec![3..4, 0..1]
8587            );
8588        });
8589        cx.simulate_keystrokes("x");
8590        assert_cursors(&mut cx, &view.input, "x|b\nx|b");
8591    }
8592
8593    #[gpui::test]
8594    fn test_multi_cursor_alt_click_dispatch(cx: &mut TestAppContext) {
8595        cx.update(crate::init);
8596        let view = InputView::<EditorMode>::new(cx);
8597        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8598        setup_cursors(&mut cx, &view.input, "a|b\nab\nab");
8599        cx.update(|window, cx| {
8600            view.input.update(cx, |state, cx| state.focus(window, cx));
8601        });
8602        let position = view.input.read_with(&cx, |state, _| {
8603            let bounds = state.last_bounds.unwrap();
8604            let layout = state.last_layout.as_ref().unwrap();
8605            bounds.origin + point(layout.line_number_width + px(2.), layout.line_height * 1.5)
8606        });
8607        cx.simulate_click(
8608            position,
8609            gpui::Modifiers {
8610                alt: true,
8611                ..Default::default()
8612            },
8613        );
8614        view.input
8615            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
8616    }
8617
8618    #[gpui::test]
8619    fn test_word_delete_undo_restores_caret(cx: &mut TestAppContext) {
8620        let view = multi_line(cx);
8621        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8622        setup_cursors(&mut cx, &view.input, "hello|");
8623        cx.update(|window, cx| {
8624            view.input.update(cx, |state, cx| {
8625                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
8626                state.undo(&Undo, window, cx);
8627                assert_eq!(state.selected_range(), 5..5);
8628            });
8629        });
8630    }
8631
8632    #[gpui::test]
8633    fn test_merged_delete_undo_restores_all_carets(cx: &mut TestAppContext) {
8634        let view = multi_line(cx);
8635        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8636        setup_cursors(&mut cx, &view.input, "a|b|c");
8637        cx.update(|window, cx| {
8638            view.input.update(cx, |state, cx| {
8639                state.backspace(&Backspace, window, cx);
8640                state.undo(&Undo, window, cx);
8641            });
8642        });
8643        assert_cursors(&mut cx, &view.input, "a|b|c");
8644    }
8645
8646    #[gpui::test]
8647    fn test_shift_end_respects_soft_wrap_end(cx: &mut TestAppContext) {
8648        let view = InputView::<EditorMode>::new(cx);
8649        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8650        cx.update(|window, cx| {
8651            view.input.update(cx, |state, cx| {
8652                state.set_value("abcdef ".repeat(30), window, cx);
8653                state.display_map.on_layout_changed(Some(px(60.)), cx);
8654                let line = state.display_map.line(0).unwrap();
8655                assert!(line.wrapped_lines.len() > 1);
8656                let boundary = line.wrapped_lines[0].end;
8657                state.move_to_with_affinity(boundary, None, true, cx);
8658                state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
8659                assert_eq!(state.selected_range(), boundary..state.text.len());
8660            });
8661        });
8662    }
8663
8664    #[gpui::test]
8665    fn test_outdent_unindented_unicode_is_unchanged(cx: &mut TestAppContext) {
8666        let view = multi_line(cx);
8667        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8668        setup_cursors(&mut cx, &view.input, "|你好\n|世界");
8669        cx.update(|window, cx| {
8670            view.input.update(cx, |state, cx| {
8671                state.outdent(false, window, cx);
8672                state.outdent(true, window, cx);
8673            });
8674        });
8675        assert_cursors(&mut cx, &view.input, "|你好\n|世界");
8676    }
8677
8678    #[gpui::test]
8679    fn test_column_selection_stays_on_unicode_boundaries(cx: &mut TestAppContext) {
8680        let view = multi_line(cx);
8681        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8682        setup_cursors(&mut cx, &view.input, "ab\n你好\ncd");
8683        cx.update(|window, cx| {
8684            view.input.update(cx, |state, cx| {
8685                state.build_columnar_selection(
8686                    ColumnarPoint::new(1, 0),
8687                    ColumnarPoint::new(11, 0),
8688                    cx,
8689                );
8690                let text = state.value();
8691                for sel in state.selections.iter() {
8692                    assert!(text.is_char_boundary(sel.start));
8693                    assert!(text.is_char_boundary(sel.end));
8694                }
8695                state.replace_text_in_range(None, "X", window, cx);
8696            });
8697        });
8698    }
8699
8700    #[gpui::test]
8701    fn test_editor_decorations_follow_typing(cx: &mut TestAppContext) {
8702        let view = InputView::<EditorMode>::new(cx);
8703        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8704        cx.update(|window, cx| {
8705            view.input.update(cx, |state, cx| {
8706                state.set_value("abc def", window, cx);
8707                state.create_decorations_collection(
8708                    vec![crate::input::TextDecoration::new(
8709                        4..7,
8710                        gpui::HighlightStyle::default(),
8711                    )],
8712                    cx,
8713                );
8714                state.set_selected_range(0..0, cx);
8715                state.replace_text_in_range(None, "X", window, cx);
8716                let layers = state.extras.decoration_layers();
8717                assert_eq!(layers.into_iter().flatten().next().unwrap().range, 5..8);
8718            });
8719        });
8720    }
8721
8722    #[gpui::test]
8723    fn test_backspace_preserves_escaped_quote_terminator(cx: &mut TestAppContext) {
8724        let view = InputView::<EditorMode>::new(cx);
8725        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8726        setup_cursors(&mut cx, &view.input, r#"{"s":"\"|"}"#);
8727        cx.update(|window, cx| {
8728            view.input.update(cx, |state, cx| {
8729                set_test_syntax_provider(std::rc::Rc::new(StringAllProvider), cx);
8730                state.backspace(&Backspace, window, cx);
8731            });
8732        });
8733        assert_cursors(&mut cx, &view.input, r#"{"s":"\|"}"#);
8734    }
8735
8736    #[gpui::test]
8737    fn test_ordinary_typing_does_not_query_syntax(cx: &mut TestAppContext) {
8738        struct UnexpectedQuery;
8739        impl crate::input::SyntaxContextProvider for UnexpectedQuery {
8740            fn context_at(&self, _: &ropey::Rope, _: usize) -> crate::input::SyntaxContext {
8741                panic!("ordinary typing must not query syntax");
8742            }
8743        }
8744        let view = InputView::<EditorMode>::new(cx);
8745        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8746        setup_cursors(&mut cx, &view.input, "|");
8747        cx.update(|window, cx| {
8748            view.input.update(cx, |state, cx| {
8749                set_test_syntax_provider(std::rc::Rc::new(UnexpectedQuery), cx);
8750                for c in ["a", "b", "c"] {
8751                    state.replace_text_in_range(None, c, window, cx);
8752                }
8753            });
8754        });
8755        assert_cursors(&mut cx, &view.input, "abc|");
8756    }
8757
8758    #[gpui::test]
8759    fn test_auto_close_is_atomic_at_history_limit(cx: &mut TestAppContext) {
8760        let view = InputView::<EditorMode>::new(cx);
8761        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8762        setup_cursors(&mut cx, &view.input, "|");
8763        cx.update(|window, cx| {
8764            view.input.update(cx, |state, cx| {
8765                for _ in 0..999 {
8766                    state.replace_text_in_range(None, "a", window, cx);
8767                }
8768                state.replace_text_in_range(None, "(", window, cx);
8769                state.undo(&Undo, window, cx);
8770                assert!(!state.text.to_string().contains('('));
8771                assert!(!state.text.to_string().contains(')'));
8772                state.redo(&Redo, window, cx);
8773            });
8774        });
8775        assert_cursors(&mut cx, &view.input, &format!("{}(|)", "a".repeat(999)));
8776    }
8777
8778    #[gpui::test]
8779    fn test_comment_closer_is_inserted_literally(cx: &mut TestAppContext) {
8780        let view = InputView::<EditorMode>::new(cx);
8781        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8782        setup_cursors(&mut cx, &view.input, "// (|)");
8783        cx.update(|window, cx| {
8784            view.input.update(cx, |state, cx| {
8785                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
8786                state.replace_text_in_range(None, ")", window, cx);
8787            });
8788        });
8789        assert_cursors(&mut cx, &view.input, "// ()|)");
8790    }
8791
8792    #[gpui::test]
8793    fn test_explicit_range_skip_does_not_delete_existing_closer_on_undo(cx: &mut TestAppContext) {
8794        let view = InputView::<EditorMode>::new(cx);
8795        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8796        setup_cursors(&mut cx, &view.input, "(a|)");
8797        let before = view.input.read_with(&cx, |state, _| state.text.to_string());
8798        cx.update(|window, cx| {
8799            view.input.update(cx, |state, cx| {
8800                state.replace_text_in_range(Some(2..2), ")", window, cx);
8801            });
8802        });
8803        assert_cursors(&mut cx, &view.input, "(a)|");
8804        cx.update(|window, cx| {
8805            view.input
8806                .update(cx, |state, cx| state.undo(&Undo, window, cx));
8807        });
8808        view.input.read_with(&cx, |state, _| {
8809            assert_eq!(
8810                state.text.to_string(),
8811                before,
8812                "skip must not remove an existing closer on undo"
8813            );
8814        });
8815    }
8816
8817    #[gpui::test]
8818    fn test_pair_redo_restores_interior_cursor(cx: &mut TestAppContext) {
8819        let view = InputView::<EditorMode>::new(cx);
8820        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8821        setup_cursors(&mut cx, &view.input, "|");
8822        cx.update(|window, cx| {
8823            view.input.update(cx, |state, cx| {
8824                state.replace_text_in_range(None, "(", window, cx);
8825            });
8826        });
8827        assert_cursors(&mut cx, &view.input, "(|)");
8828        cx.update(|window, cx| {
8829            view.input.update(cx, |state, cx| {
8830                state.undo(&Undo, window, cx);
8831                state.redo(&Redo, window, cx);
8832            });
8833        });
8834        assert_cursors(&mut cx, &view.input, "(|)");
8835    }
8836
8837    #[gpui::test]
8838    fn test_pair_enter_redo_restores_interior_cursor(cx: &mut TestAppContext) {
8839        let view = InputView::<EditorMode>::new(cx);
8840        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8841        setup_cursors(&mut cx, &view.input, "{|}");
8842        cx.update(|window, cx| {
8843            view.input.update(cx, |state, cx| {
8844                state.enter(
8845                    &Enter {
8846                        secondary: false,
8847                        shift: false,
8848                    },
8849                    window,
8850                    cx,
8851                );
8852            });
8853        });
8854        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
8855        cx.update(|window, cx| {
8856            view.input.update(cx, |state, cx| {
8857                state.undo(&Undo, window, cx);
8858            });
8859        });
8860        assert_cursors(&mut cx, &view.input, "{|}");
8861        cx.update(|window, cx| {
8862            view.input
8863                .update(cx, |state, cx| state.redo(&Redo, window, cx));
8864        });
8865        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
8866    }
8867
8868    #[gpui::test]
8869    fn test_auto_close_parens(cx: &mut TestAppContext) {
8870        let view = InputView::<EditorMode>::new(cx);
8871        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8872        setup_cursors(&mut cx, &view.input, "|");
8873        cx.update(|window, cx| {
8874            view.input.update(cx, |state, cx| {
8875                state.replace_text_in_range(None, "(", window, cx);
8876            });
8877        });
8878        assert_cursors(&mut cx, &view.input, "(|)");
8879        // Selection must be collapsed: typing next must insert, not replace.
8880        cx.update(|window, cx| {
8881            view.input.update(cx, |state, cx| {
8882                state.replace_text_in_range(None, "x", window, cx);
8883            });
8884        });
8885        assert_cursors(&mut cx, &view.input, "(x|)");
8886    }
8887
8888    #[gpui::test]
8889    fn test_auto_close_skip_over_closer(cx: &mut TestAppContext) {
8890        let view = InputView::<EditorMode>::new(cx);
8891        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8892        setup_cursors(&mut cx, &view.input, "(|)");
8893        cx.update(|window, cx| {
8894            view.input.update(cx, |state, cx| {
8895                state.replace_text_in_range(None, ")", window, cx);
8896            });
8897        });
8898        // No duplicate closer, cursor moves past the existing one.
8899        assert_cursors(&mut cx, &view.input, "()|");
8900        // Selection must be collapsed, not covering blank space.
8901        view.input.read_with(&cx, |state, _| {
8902            assert!(state.active_selection().is_empty());
8903        });
8904    }
8905
8906    #[gpui::test]
8907    fn test_auto_close_no_pair_inside_word(cx: &mut TestAppContext) {
8908        let view = InputView::<EditorMode>::new(cx);
8909        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8910        setup_cursors(&mut cx, &view.input, "don|");
8911        cx.update(|window, cx| {
8912            view.input.update(cx, |state, cx| {
8913                state.replace_text_in_range(None, "'", window, cx);
8914            });
8915        });
8916        // Contraction: no auto-close.
8917        assert_cursors(&mut cx, &view.input, "don'|");
8918    }
8919
8920    #[gpui::test]
8921    fn test_auto_close_quote_after_cjk(cx: &mut TestAppContext) {
8922        let view = InputView::<EditorMode>::new(cx);
8923        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8924        // Unicode-safe previous-char lookup: must not panic on multi-byte prefix.
8925        // CJK counts as word-like, so no pairing — single quote inserted.
8926        setup_cursors(&mut cx, &view.input, "中|");
8927        cx.update(|window, cx| {
8928            view.input.update(cx, |state, cx| {
8929                state.replace_text_in_range(None, "'", window, cx);
8930            });
8931        });
8932        assert_cursors(&mut cx, &view.input, "中'|");
8933    }
8934
8935    #[gpui::test]
8936    fn test_auto_close_closer_skips_at_string_end(cx: &mut TestAppContext) {
8937        let view = InputView::<EditorMode>::new(cx);
8938        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8939        // Cursor before existing closing quote: typing `"` skips past it.
8940        setup_cursors(&mut cx, &view.input, "\"hello|\"");
8941        cx.update(|window, cx| {
8942            view.input.update(cx, |state, cx| {
8943                state.replace_text_in_range(None, "\"", window, cx);
8944            });
8945        });
8946        assert_cursors(&mut cx, &view.input, "\"hello\"|");
8947        view.input.read_with(&cx, |state, _| {
8948            assert!(state.active_selection().is_empty());
8949        });
8950    }
8951
8952    #[gpui::test]
8953    fn test_backspace_pair_with_cjk_prefix(cx: &mut TestAppContext) {
8954        let view = InputView::<EditorMode>::new(cx);
8955        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8956        // UTF-16 conversion: deleting `()` after CJK must not touch neighbors.
8957        setup_cursors(&mut cx, &view.input, "中(|)abc");
8958        cx.update(|window, cx| {
8959            view.input
8960                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
8961        });
8962        assert_cursors(&mut cx, &view.input, "中|abc");
8963    }
8964
8965    #[gpui::test]
8966    fn test_enter_split_respects_smart_indent_off(cx: &mut TestAppContext) {
8967        let view = InputView::<EditorMode>::new(cx);
8968        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8969        setup_cursors(&mut cx, &view.input, "{|}");
8970        cx.update(|window, cx| {
8971            view.input.update(cx, |state, cx| {
8972                state.set_smart_indent(false, window, cx);
8973                state.enter(
8974                    &Enter {
8975                        secondary: false,
8976                        shift: false,
8977                    },
8978                    window,
8979                    cx,
8980                );
8981            });
8982        });
8983        // Smart indent controls both structural splitting and extra indentation.
8984        assert_cursors(&mut cx, &view.input, "{\n|}");
8985    }
8986
8987    #[gpui::test]
8988    fn test_enter_split_is_independent_of_auto_close(cx: &mut TestAppContext) {
8989        let view = InputView::<EditorMode>::new(cx);
8990        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8991        setup_cursors(&mut cx, &view.input, "{|}");
8992        cx.update(|window, cx| {
8993            view.input.update(cx, |state, cx| {
8994                state.set_auto_close(false, window, cx);
8995                state.enter(
8996                    &Enter {
8997                        secondary: false,
8998                        shift: false,
8999                    },
9000                    window,
9001                    cx,
9002                );
9003            });
9004        });
9005        // Disabling automatic insertion does not disable structural indentation.
9006        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
9007    }
9008
9009    #[gpui::test]
9010    fn test_language_config_applies_before_render_and_on_language_change(cx: &mut TestAppContext) {
9011        use crate::input::{AutoClosingPair, set_language_config};
9012        let view = InputView::<EditorMode>::new(cx);
9013        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9014        cx.update(|window, cx| {
9015            set_language_config(
9016                "alpha",
9017                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
9018                cx,
9019            );
9020            set_language_config(
9021                "beta",
9022                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("‹", "›")]),
9023                cx,
9024            );
9025            let first = cx.new(|cx| EditorState::new(window, cx).language("ALPHA"));
9026            let second = cx.new(|cx| EditorState::new(window, cx).language("beta"));
9027            first.update(cx, |state, cx| {
9028                state.replace_text_in_range(None, "«", window, cx);
9029                assert_eq!(state.text().to_string(), "«»");
9030                state.set_value("", window, cx);
9031                state.set_smart_indent(false, window, cx);
9032                state.set_highlighter("beta", cx);
9033                assert!(!state.mode.is_smart_indent());
9034                state.replace_text_in_range(None, "‹", window, cx);
9035                assert_eq!(state.text().to_string(), "‹›");
9036                state.set_value("", window, cx);
9037                state.set_highlighter("unknown", cx);
9038                state.replace_text_in_range(None, "(", window, cx);
9039                assert_eq!(state.text().to_string(), "()");
9040            });
9041            second.update(cx, |state, cx| {
9042                state.replace_text_in_range(None, "‹", window, cx);
9043                assert_eq!(state.text().to_string(), "‹›");
9044            });
9045        });
9046    }
9047
9048    #[gpui::test]
9049    fn test_language_service_replacement_updates_syntax_without_render(cx: &mut TestAppContext) {
9050        use crate::input::{LanguageProvider, SyntaxContextProvider, set_language_provider};
9051        struct StringLanguages(Rc<Cell<usize>>);
9052        impl LanguageProvider for StringLanguages {
9053            fn syntax_context_provider(&self, _: &str) -> Option<Rc<dyn SyntaxContextProvider>> {
9054                self.0.set(self.0.get() + 1);
9055                Some(Rc::new(StringAllProvider))
9056            }
9057        }
9058        struct CodeLanguages;
9059        impl LanguageProvider for CodeLanguages {}
9060        let view = InputView::<EditorMode>::new(cx);
9061        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9062        cx.update(|window, cx| {
9063            let creations = Rc::new(Cell::new(0));
9064            set_language_provider(Rc::new(StringLanguages(creations.clone())), cx);
9065            view.input.update(cx, |state, cx| {
9066                state.replace_text_in_range(None, "(", window, cx);
9067                state.replace_text_in_range(None, "[", window, cx);
9068                assert_eq!(state.text().to_string(), "([");
9069                assert_eq!(
9070                    creations.get(),
9071                    1,
9072                    "retain the document provider between edits"
9073                );
9074            });
9075            set_language_provider(Rc::new(CodeLanguages), cx);
9076            view.input.update(cx, |state, cx| {
9077                state.replace_text_in_range(None, "{", window, cx);
9078                assert_eq!(state.text().to_string(), "([{}");
9079            });
9080        });
9081    }
9082
9083    #[gpui::test]
9084    fn test_language_config_updates_existing_editors(cx: &mut TestAppContext) {
9085        use crate::input::{AutoClosingPair, set_language_config};
9086        let view = InputView::<EditorMode>::new(cx);
9087        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9088        setup_cursors(&mut cx, &view.input, "|");
9089        cx.update(|window, cx| {
9090            view.input.update(cx, |state, cx| {
9091                state.set_highlighter("CUSTOM", cx);
9092                state.set_auto_close(false, window, cx);
9093                state.set_smart_indent(false, window, cx);
9094            });
9095            set_language_config(
9096                "custom",
9097                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
9098                cx,
9099            );
9100            // Batched registrations must update every affected language.
9101            set_language_config("other", LanguageConfig::default(), cx);
9102            view.input.update(cx, |state, cx| {
9103                assert!(!state.mode.is_auto_close());
9104                assert!(!state.mode.is_smart_indent());
9105                state.set_auto_close(true, window, cx);
9106                state.replace_text_in_range(None, "«", window, cx);
9107            });
9108        });
9109        assert_cursors(&mut cx, &view.input, "«|»");
9110    }
9111
9112    #[gpui::test]
9113    fn test_auto_close_before_is_language_configurable(cx: &mut TestAppContext) {
9114        let view = InputView::<EditorMode>::new(cx);
9115        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9116        setup_cursors(&mut cx, &view.input, "|word");
9117        cx.update(|window, cx| {
9118            view.input.update(cx, |state, cx| {
9119                state.replace_text_in_range(None, "(", window, cx)
9120            });
9121        });
9122        assert_cursors(&mut cx, &view.input, "(|word");
9123        setup_cursors(&mut cx, &view.input, "|word");
9124        cx.update(|window, cx| {
9125            view.input.update(cx, |state, cx| {
9126                crate::input::set_language_config(
9127                    state.language_name(),
9128                    LanguageConfig::default().auto_close_before("w"),
9129                    cx,
9130                );
9131                state.replace_text_in_range(None, "(", window, cx);
9132            });
9133        });
9134        assert_cursors(&mut cx, &view.input, "(|)word");
9135    }
9136
9137    #[gpui::test]
9138    fn test_pair_context_restrictions_are_per_pair(cx: &mut TestAppContext) {
9139        use crate::input::{AutoClosingPair, SyntaxContext};
9140        let view = InputView::<EditorMode>::new(cx);
9141        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9142        setup_cursors(&mut cx, &view.input, "|");
9143        cx.update(|window, cx| {
9144            view.input.update(cx, |state, cx| {
9145                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
9146                crate::input::set_language_config(
9147                    state.language_name(),
9148                    LanguageConfig::default().auto_closing_pairs([
9149                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::String]),
9150                        AutoClosingPair::new("[", "]").not_in([SyntaxContext::Comment]),
9151                    ]),
9152                    cx,
9153                );
9154                state.replace_text_in_range(None, "(", window, cx);
9155                state.replace_text_in_range(None, "[", window, cx);
9156            });
9157        });
9158        assert_cursors(&mut cx, &view.input, "([|)");
9159    }
9160
9161    #[gpui::test]
9162    fn test_multichar_delimiters_insert_delete_and_skip(cx: &mut TestAppContext) {
9163        use crate::input::{AutoClosingPair, SyntaxContext};
9164        struct BlockComment;
9165        impl crate::input::SyntaxContextProvider for BlockComment {
9166            fn context_at(&self, text: &ropey::Rope, _: usize) -> SyntaxContext {
9167                if text.to_string().contains("/*") {
9168                    SyntaxContext::Comment
9169                } else {
9170                    SyntaxContext::Code
9171                }
9172            }
9173        }
9174        let view = InputView::<EditorMode>::new(cx);
9175        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9176        setup_cursors(&mut cx, &view.input, "|");
9177        cx.update(|window, cx| {
9178            view.input.update(cx, |state, cx| {
9179                set_test_syntax_provider(std::rc::Rc::new(BlockComment), cx);
9180                crate::input::set_language_config(
9181                    state.language_name(),
9182                    LanguageConfig::default()
9183                        .auto_closing_pairs([AutoClosingPair::new("/*", "*/")
9184                            .not_in([SyntaxContext::String, SyntaxContext::Comment])]),
9185                    cx,
9186                );
9187                state.replace_text_in_range(None, "/", window, cx);
9188                state.replace_text_in_range(None, "*", window, cx);
9189            });
9190        });
9191        assert_cursors(&mut cx, &view.input, "/*|*/");
9192        cx.update(|window, cx| {
9193            view.input
9194                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
9195        });
9196        assert_cursors(&mut cx, &view.input, "|");
9197        cx.update(|window, cx| {
9198            view.input.update(cx, |state, cx| {
9199                state.undo(&Undo, window, cx);
9200                state.replace_text_in_range(None, "*", window, cx);
9201                state.replace_text_in_range(None, "/", window, cx);
9202            });
9203        });
9204        assert_cursors(&mut cx, &view.input, "/**/|");
9205    }
9206
9207    #[gpui::test]
9208    fn test_auto_closed_pairs_fallback_and_explicit_disable(cx: &mut TestAppContext) {
9209        use crate::input::BracketPair;
9210        let view = InputView::<EditorMode>::new(cx);
9211        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9212        setup_cursors(&mut cx, &view.input, "|");
9213        cx.update(|window, cx| {
9214            view.input.update(cx, |state, cx| {
9215                let mut rules = LanguageConfig::default().brackets([BracketPair::new("«", "»")]);
9216                rules.auto_closing_pairs = None;
9217                crate::input::set_language_config(state.language_name(), rules.clone(), cx);
9218                state.replace_text_in_range(None, "«", window, cx);
9219                crate::input::set_language_config(
9220                    state.language_name(),
9221                    rules.auto_closing_pairs([]),
9222                    cx,
9223                );
9224                state.replace_text_in_range(None, "«", window, cx);
9225            });
9226        });
9227        assert_cursors(&mut cx, &view.input, "««|»");
9228    }
9229
9230    #[gpui::test]
9231    fn test_undo_open_composition_preserves_generated_pairs(cx: &mut TestAppContext) {
9232        use crate::input::{AutoClosingPair, SyntaxContext};
9233        let view = InputView::<EditorMode>::new(cx);
9234        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9235        setup_cursors(&mut cx, &view.input, "|");
9236        cx.update(|window, cx| {
9237            view.input.update(cx, |state, cx| {
9238                crate::input::set_language_config(
9239                    state.language_name(),
9240                    LanguageConfig::default()
9241                        .auto_closing_pairs([
9242                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
9243                        ]),
9244                    cx,
9245                );
9246                state.replace_text_in_range(None, "/", window, cx);
9247                state.replace_text_in_range(None, "*", window, cx);
9248                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
9249                state.replace_and_mark_text_in_range(None, "x", Some(1..1), window, cx);
9250                state.undo(&Undo, window, cx);
9251                state.replace_text_in_range(None, "*", window, cx);
9252                state.replace_text_in_range(None, "/", window, cx);
9253            });
9254        });
9255        assert_cursors(&mut cx, &view.input, "/**/|");
9256    }
9257
9258    #[gpui::test]
9259    fn test_undo_does_not_promote_literal_comment_delimiters(cx: &mut TestAppContext) {
9260        use crate::input::{AutoClosingPair, SyntaxContext};
9261        let view = InputView::<EditorMode>::new(cx);
9262        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9263        setup_cursors(&mut cx, &view.input, "/*|*/");
9264        cx.update(|window, cx| {
9265            view.input.update(cx, |state, cx| {
9266                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
9267                crate::input::set_language_config(
9268                    state.language_name(),
9269                    LanguageConfig::default()
9270                        .auto_closing_pairs([
9271                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
9272                        ]),
9273                    cx,
9274                );
9275                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
9276                state.undo(&Undo, window, cx);
9277                state.backspace(&Backspace, window, cx);
9278            });
9279        });
9280        assert_cursors(&mut cx, &view.input, "/|*/");
9281    }
9282
9283    #[gpui::test]
9284    fn test_multiple_generated_pairs_retain_their_identity(cx: &mut TestAppContext) {
9285        use crate::input::{AutoClosingPair, SyntaxContext};
9286        struct CommentScope;
9287        impl crate::input::SyntaxContextProvider for CommentScope {
9288            fn context_at(&self, text: &ropey::Rope, offset: usize) -> SyntaxContext {
9289                if text
9290                    .to_string()
9291                    .find("*/")
9292                    .is_some_and(|end| offset < end + 2)
9293                {
9294                    SyntaxContext::Comment
9295                } else {
9296                    SyntaxContext::Code
9297                }
9298            }
9299        }
9300        let view = InputView::<EditorMode>::new(cx);
9301        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9302        setup_cursors(&mut cx, &view.input, "|");
9303        cx.update(|window, cx| {
9304            view.input.update(cx, |state, cx| {
9305                set_test_syntax_provider(std::rc::Rc::new(CommentScope), cx);
9306                crate::input::set_language_config(
9307                    state.language_name(),
9308                    LanguageConfig::default().auto_closing_pairs([
9309                        AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment]),
9310                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::Comment]),
9311                    ]),
9312                    cx,
9313                );
9314                state.replace_text_in_range(None, "/", window, cx);
9315                state.replace_text_in_range(None, "*", window, cx);
9316                state.set_selected_range(4..4, cx);
9317                state.replace_text_in_range(None, "(", window, cx);
9318                state.set_selected_range(2..2, cx);
9319                state.replace_text_in_range(None, "*", window, cx);
9320                state.replace_text_in_range(None, "/", window, cx);
9321            });
9322        });
9323        assert_cursors(&mut cx, &view.input, "/**/|()");
9324        cx.update(|window, cx| {
9325            view.input.update(cx, |state, cx| {
9326                state.set_selected_range(2..2, cx);
9327                state.backspace(&Backspace, window, cx);
9328                state.undo(&Undo, window, cx);
9329                state.redo(&Redo, window, cx);
9330            });
9331        });
9332        assert_cursors(&mut cx, &view.input, "|()");
9333    }
9334
9335    #[gpui::test]
9336    fn test_replacing_generated_opener_invalidates_the_pair(cx: &mut TestAppContext) {
9337        use crate::input::{AutoClosingPair, SyntaxContext};
9338        let view = InputView::<EditorMode>::new(cx);
9339        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9340        setup_cursors(&mut cx, &view.input, "|");
9341        cx.update(|window, cx| {
9342            view.input.update(cx, |state, cx| {
9343                crate::input::set_language_config(
9344                    state.language_name(),
9345                    LanguageConfig::default()
9346                        .auto_closing_pairs([
9347                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
9348                        ]),
9349                    cx,
9350                );
9351                state.replace_text_in_range(None, "/", window, cx);
9352                state.replace_text_in_range(None, "*", window, cx);
9353                state.replace_text_in_range(Some(0..2), "//", window, cx);
9354                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
9355                state.replace_text_in_range(None, "*", window, cx);
9356            });
9357        });
9358        assert_cursors(&mut cx, &view.input, "//*|*/");
9359    }
9360
9361    #[gpui::test]
9362    fn test_configuration_preserves_compiled_regex_options(cx: &mut TestAppContext) {
9363        use crate::input::IndentationRules;
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, "BEGIN|");
9367        cx.update(|window, cx| {
9368            view.input.update(cx, |state, cx| {
9369                let rules = |insensitive| {
9370                    LanguageConfig::default().indentation_rules(IndentationRules::new(
9371                        regex::RegexBuilder::new("begin$")
9372                            .case_insensitive(insensitive)
9373                            .build()
9374                            .unwrap(),
9375                        regex::Regex::new("^end").unwrap(),
9376                    ))
9377                };
9378                crate::input::set_language_config(state.language_name(), rules(false), cx);
9379                crate::input::set_language_config(state.language_name(), rules(true), cx);
9380                state.enter(
9381                    &Enter {
9382                        secondary: false,
9383                        shift: false,
9384                    },
9385                    window,
9386                    cx,
9387                );
9388            });
9389        });
9390        assert_cursors(&mut cx, &view.input, "BEGIN\n  |");
9391    }
9392
9393    #[gpui::test]
9394    fn test_indent_patterns_are_applied_on_enter(cx: &mut TestAppContext) {
9395        use crate::input::IndentationRules;
9396        let view = InputView::<EditorMode>::new(cx);
9397        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9398        let rules = LanguageConfig::default().indentation_rules(IndentationRules::new(
9399            regex::Regex::new(r"\bbegin\s*$").unwrap(),
9400            regex::Regex::new(r"^\s*end\b").unwrap(),
9401        ));
9402        for (before, expected) in [("begin|", "begin\n  |"), ("  |end", "  \n|end")] {
9403            setup_cursors(&mut cx, &view.input, before);
9404            cx.update(|window, cx| {
9405                view.input.update(cx, |state, cx| {
9406                    crate::input::set_language_config(state.language_name(), rules.clone(), cx);
9407                    state.enter(
9408                        &Enter {
9409                            secondary: false,
9410                            shift: false,
9411                        },
9412                        window,
9413                        cx,
9414                    );
9415                });
9416            });
9417            assert_cursors(&mut cx, &view.input, expected);
9418        }
9419    }
9420
9421    #[gpui::test]
9422    fn test_custom_language_config(cx: &mut TestAppContext) {
9423        let view = InputView::<EditorMode>::new(cx);
9424        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9425        cx.update(|_window, cx| {
9426            view.input.update(cx, |state, cx| {
9427                crate::input::set_language_config(
9428                    state.language_name(),
9429                    crate::input::language_config::LanguageConfig::default()
9430                        .brackets([crate::input::BracketPair::new("«", "»")])
9431                        .auto_closing_pairs([crate::input::AutoClosingPair::new("«", "»")]),
9432                    cx,
9433                );
9434            });
9435        });
9436        // Custom pair closes.
9437        setup_cursors(&mut cx, &view.input, "|");
9438        cx.update(|window, cx| {
9439            view.input.update(cx, |state, cx| {
9440                state.replace_text_in_range(None, "«", window, cx);
9441            });
9442        });
9443        assert_cursors(&mut cx, &view.input, "«|»");
9444        // Default `(` no longer pairs under custom rules.
9445        cx.update(|window, cx| {
9446            view.input.update(cx, |state, cx| {
9447                state.set_value("\n", window, cx);
9448                state.set_selected_range(0..0, cx);
9449                state.replace_text_in_range(None, "(", window, cx);
9450            });
9451        });
9452        assert_cursors(&mut cx, &view.input, "(|");
9453    }
9454
9455    #[gpui::test]
9456    fn test_skip_records_no_history(cx: &mut TestAppContext) {
9457        let view = InputView::<EditorMode>::new(cx);
9458        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9459        setup_cursors(&mut cx, &view.input, "(a|)");
9460        cx.update(|window, cx| {
9461            view.input.update(cx, |state, cx| {
9462                // Type a normal char (recorded), then skip over `)`.
9463                state.replace_text_in_range(None, "b", window, cx);
9464            });
9465        });
9466        assert_cursors(&mut cx, &view.input, "(ab|)");
9467        cx.update(|window, cx| {
9468            view.input.update(cx, |state, cx| {
9469                state.replace_text_in_range(None, ")", window, cx);
9470            });
9471        });
9472        // Skip is a pure cursor move: no history entry of its own.
9473        assert_cursors(&mut cx, &view.input, "(ab)|");
9474        // Undo removes `b`, never exposing a transient `())`.
9475        cx.update(|window, cx| {
9476            view.input
9477                .update(cx, |state, cx| state.undo(&Undo, window, cx));
9478        });
9479        assert_cursors(&mut cx, &view.input, "(a|)");
9480    }
9481
9482    #[gpui::test]
9483    fn test_pair_insert_undo_removes_both(cx: &mut TestAppContext) {
9484        let view = InputView::<EditorMode>::new(cx);
9485        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9486        setup_cursors(&mut cx, &view.input, "|");
9487        cx.update(|window, cx| {
9488            view.input.update(cx, |state, cx| {
9489                state.replace_text_in_range(None, "(", window, cx);
9490            });
9491        });
9492        assert_cursors(&mut cx, &view.input, "(|)");
9493        // One undo removes the whole pair (typing coalescing).
9494        cx.update(|window, cx| {
9495            view.input
9496                .update(cx, |state, cx| state.undo(&Undo, window, cx));
9497        });
9498        assert_cursors(&mut cx, &view.input, "|");
9499    }
9500
9501    #[gpui::test]
9502    fn test_escaped_quote_does_not_skip(cx: &mut TestAppContext) {
9503        let view = InputView::<EditorMode>::new(cx);
9504        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9505        // Odd backslashes: the typed quote is escaped, insert literally.
9506        setup_cursors(&mut cx, &view.input, "\"hello\\|\"");
9507        cx.update(|window, cx| {
9508            view.input.update(cx, |state, cx| {
9509                state.replace_text_in_range(None, "\"", window, cx);
9510            });
9511        });
9512        assert_cursors(&mut cx, &view.input, "\"hello\\\"|\"");
9513    }
9514
9515    #[gpui::test]
9516    fn test_even_backslashes_still_skip(cx: &mut TestAppContext) {
9517        let view = InputView::<EditorMode>::new(cx);
9518        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9519        // Even backslashes: quote is not escaped, skip over the terminator.
9520        setup_cursors(&mut cx, &view.input, "\"hello\\\\|\"");
9521        cx.update(|window, cx| {
9522            view.input.update(cx, |state, cx| {
9523                state.replace_text_in_range(None, "\"", window, cx);
9524            });
9525        });
9526        assert_cursors(&mut cx, &view.input, "\"hello\\\\\"|");
9527    }
9528
9529    #[gpui::test]
9530    fn test_editor_options_survive_configuration_replacement(cx: &mut TestAppContext) {
9531        use crate::input::{BracketPair, LanguageConfig};
9532
9533        let view = InputView::<EditorMode>::new(cx);
9534        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9535        cx.update(|window, cx| {
9536            view.input.update(cx, |state, cx| {
9537                // Language changes update rules without changing editor preferences.
9538                state.set_smart_indent(false, window, cx);
9539                crate::input::set_language_config(
9540                    state.language_name(),
9541                    LanguageConfig::default().brackets([BracketPair::new("«", "»")]),
9542                    cx,
9543                );
9544            });
9545        });
9546        view.input.read_with(&cx, |state, _| {
9547            assert!(!state.mode.is_smart_indent(), "editor option preserved");
9548            assert!(state.mode.is_auto_close());
9549            let pairs = &state.mode.language_config().unwrap().brackets;
9550            assert_eq!(pairs.len(), 1, "pairs follow configuration");
9551            assert_eq!(pairs[0].open.as_ref(), "«");
9552        });
9553    }
9554
9555    struct CommentAllProvider;
9556    impl crate::input::SyntaxContextProvider for CommentAllProvider {
9557        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
9558            crate::input::SyntaxContext::Comment
9559        }
9560    }
9561
9562    struct StringAllProvider;
9563    impl crate::input::SyntaxContextProvider for StringAllProvider {
9564        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
9565            crate::input::SyntaxContext::String
9566        }
9567    }
9568
9569    #[gpui::test]
9570    fn test_smart_indent_suppressed_in_strings(cx: &mut TestAppContext) {
9571        use std::rc::Rc;
9572
9573        let view = InputView::<EditorMode>::new(cx);
9574        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9575        cx.update(|_window, cx| {
9576            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
9577        });
9578        // Trigger `{` inside a string: no extra level, base indent only.
9579        setup_cursors(&mut cx, &view.input, "  x = {|");
9580        cx.update(|window, cx| {
9581            view.input.update(cx, |state, cx| {
9582                state.enter(
9583                    &Enter {
9584                        secondary: false,
9585                        shift: false,
9586                    },
9587                    window,
9588                    cx,
9589                );
9590            });
9591        });
9592        assert_cursors(&mut cx, &view.input, "  x = {\n  |");
9593    }
9594
9595    #[gpui::test]
9596    fn test_split_suppressed_in_strings(cx: &mut TestAppContext) {
9597        use std::rc::Rc;
9598
9599        let view = InputView::<EditorMode>::new(cx);
9600        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9601        cx.update(|_window, cx| {
9602            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
9603        });
9604        // No three-way split inside strings: plain newline instead.
9605        setup_cursors(&mut cx, &view.input, "{|}");
9606        cx.update(|window, cx| {
9607            view.input.update(cx, |state, cx| {
9608                state.enter(
9609                    &Enter {
9610                        secondary: false,
9611                        shift: false,
9612                    },
9613                    window,
9614                    cx,
9615                );
9616            });
9617        });
9618        assert_cursors(&mut cx, &view.input, "{\n|}");
9619    }
9620
9621    #[gpui::test]
9622    fn test_comment_context_disables_pairing(cx: &mut TestAppContext) {
9623        use std::rc::Rc;
9624
9625        let view = InputView::<EditorMode>::new(cx);
9626        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9627        cx.update(|_window, cx| {
9628            set_test_syntax_provider(Rc::new(CommentAllProvider), cx);
9629        });
9630        setup_cursors(&mut cx, &view.input, "|");
9631        cx.update(|window, cx| {
9632            view.input.update(cx, |state, cx| {
9633                state.replace_text_in_range(None, "(", window, cx);
9634            });
9635        });
9636        // In comments every character is literal.
9637        assert_cursors(&mut cx, &view.input, "(|");
9638    }
9639
9640    #[gpui::test]
9641    fn test_backspace_deletes_empty_pair(cx: &mut TestAppContext) {
9642        let view = InputView::<EditorMode>::new(cx);
9643        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9644        setup_cursors(&mut cx, &view.input, "{|}");
9645        cx.update(|window, cx| {
9646            view.input
9647                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
9648        });
9649        assert_cursors(&mut cx, &view.input, "|");
9650    }
9651
9652    #[gpui::test]
9653    fn test_backspace_keeps_closer_with_content(cx: &mut TestAppContext) {
9654        let view = InputView::<EditorMode>::new(cx);
9655        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9656        setup_cursors(&mut cx, &view.input, "{d|}");
9657        cx.update(|window, cx| {
9658            view.input
9659                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
9660        });
9661        // Only `d` is deleted, the closing brace stays.
9662        assert_cursors(&mut cx, &view.input, "{|}");
9663    }
9664
9665    #[gpui::test]
9666    fn test_enter_splits_brackets(cx: &mut TestAppContext) {
9667        let view = InputView::<EditorMode>::new(cx);
9668        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9669        setup_cursors(&mut cx, &view.input, "{|}");
9670        cx.update(|window, cx| {
9671            view.input.update(cx, |state, cx| {
9672                state.enter(
9673                    &Enter {
9674                        secondary: false,
9675                        shift: false,
9676                    },
9677                    window,
9678                    cx,
9679                );
9680            });
9681        });
9682        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
9683        // Selection must be collapsed: typing next must insert, not replace.
9684        view.input.read_with(&cx, |state, _| {
9685            assert!(state.active_selection().is_empty());
9686        });
9687        cx.update(|window, cx| {
9688            view.input.update(cx, |state, cx| {
9689                state.replace_text_in_range(None, "x", window, cx);
9690            });
9691        });
9692        assert_cursors(&mut cx, &view.input, "{\n  x|\n}");
9693    }
9694
9695    #[gpui::test]
9696    fn test_enter_smart_indent_after_brace(cx: &mut TestAppContext) {
9697        let view = InputView::<EditorMode>::new(cx);
9698        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9699        setup_cursors(&mut cx, &view.input, "fn main() {|");
9700        cx.update(|window, cx| {
9701            view.input.update(cx, |state, cx| {
9702                state.enter(
9703                    &Enter {
9704                        secondary: false,
9705                        shift: false,
9706                    },
9707                    window,
9708                    cx,
9709                );
9710            });
9711        });
9712        assert_cursors(&mut cx, &view.input, "fn main() {\n  |");
9713    }
9714
9715    #[gpui::test]
9716    fn test_block_indent_tracks_all_preceding_edits(cx: &mut TestAppContext) {
9717        let view = multi_line(cx);
9718        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9719        setup_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
9720        cx.update(|window, cx| {
9721            view.input
9722                .update(cx, |state, cx| state.indent(true, window, cx));
9723        });
9724        assert_cursors(&mut cx, &view.input, "  |ab\n  |cd\n  |ef");
9725        cx.update(|window, cx| {
9726            view.input
9727                .update(cx, |state, cx| state.outdent(true, window, cx));
9728        });
9729        assert_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
9730    }
9731
9732    #[gpui::test]
9733    fn test_block_outdent_clamps_cursor_inside_indent(cx: &mut TestAppContext) {
9734        let view = multi_line(cx);
9735        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9736        setup_cursors(&mut cx, &view.input, "ab\n | cd");
9737        cx.update(|window, cx| {
9738            view.input
9739                .update(cx, |state, cx| state.outdent(true, window, cx));
9740        });
9741        assert_cursors(&mut cx, &view.input, "ab\n|cd");
9742    }
9743
9744    #[gpui::test]
9745    fn test_ime_restores_original_selection(cx: &mut TestAppContext) {
9746        let view = InputView::build(cx, |state| state);
9747        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9748        cx.update(|window, cx| {
9749            view.input.update(cx, |state, cx| {
9750                state.set_value("abc", window, cx);
9751                state.set_selected_range(1..2, cx);
9752                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
9753                state.replace_text_in_range(None, "你", window, cx);
9754                state.undo(&Undo, window, cx);
9755                assert_eq!(state.value(), "abc");
9756                assert_eq!(state.selected_range(), 1..2);
9757                state.redo(&Redo, window, cx);
9758                assert_eq!(state.selected_range(), 4..4);
9759            });
9760        });
9761    }
9762
9763    #[gpui::test]
9764    fn test_noop_does_not_change_redo_selection(cx: &mut TestAppContext) {
9765        let view = multi_line(cx);
9766        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9767        cx.update(|window, cx| {
9768            view.input.update(cx, |state, cx| {
9769                state.set_value("abc", window, cx);
9770                state.set_selected_range(1..1, cx);
9771                state.replace_text_in_range(None, "X", window, cx);
9772                state.set_selected_range(0..0, cx);
9773                state.replace_text_in_range(None, "", window, cx);
9774                state.undo(&Undo, window, cx);
9775                state.redo(&Redo, window, cx);
9776                assert_eq!(state.value(), "aXbc");
9777                assert_eq!(state.selected_range(), 2..2);
9778            });
9779        });
9780    }
9781
9782    #[gpui::test]
9783    fn test_multi_cursor_insert_text(cx: &mut TestAppContext) {
9784        let view = multi_line(cx);
9785        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9786        let input = view.input;
9787
9788        setup_cursors(&mut cx, &input, "|hello |world|");
9789        cx.update(|window, cx| {
9790            input.update(cx, |state, cx| {
9791                state.replace_text_in_range(None, ">>>", window, cx);
9792            });
9793        });
9794        assert_cursors(&mut cx, &input, ">>>|hello >>>|world>>>|");
9795    }
9796
9797    #[gpui::test]
9798    fn test_multi_cursor_delete_backward(cx: &mut TestAppContext) {
9799        let view = multi_line(cx);
9800        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9801        let input = view.input;
9802
9803        setup_cursors(&mut cx, &input, "|islands| cars|");
9804        cx.update(|window, cx| {
9805            input.update(cx, |state, cx| {
9806                state.backspace(&Backspace, window, cx);
9807            });
9808        });
9809        // The first cursor has nothing to delete. The others delete an `s`.
9810        assert_cursors(&mut cx, &input, "|island| car|");
9811    }
9812
9813    #[gpui::test]
9814    fn test_multi_cursor_delete_forward_merges(cx: &mut TestAppContext) {
9815        let view = multi_line(cx);
9816        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9817        let input = view.input;
9818
9819        setup_cursors(&mut cx, &input, "hello| |world");
9820        cx.update(|window, cx| {
9821            input.update(cx, |state, cx| {
9822                state.delete(&Delete, window, cx);
9823            });
9824        });
9825        // Adjacent deletions merge into a single cursor.
9826        assert_cursors(&mut cx, &input, "hello|orld");
9827    }
9828
9829    #[gpui::test]
9830    fn test_multi_cursor_multiline_insert_and_delete(cx: &mut TestAppContext) {
9831        let view = multi_line(cx);
9832        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9833        let input = view.input;
9834
9835        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
9836        cx.update(|window, cx| {
9837            input.update(cx, |state, cx| {
9838                state.replace_text_in_range(None, "a", window, cx);
9839            });
9840        });
9841        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
9842
9843        // The whole multi-edit insert is a single undo transaction.
9844        input.read_with(&cx, |state, _| {
9845            assert_eq!(state.undo_manager.undo_count(), 1);
9846        });
9847
9848        cx.update(|window, cx| {
9849            input.update(cx, |state, cx| {
9850                state.backspace(&Backspace, window, cx);
9851            });
9852        });
9853        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
9854    }
9855
9856    #[gpui::test]
9857    fn test_add_cursor_below_preserves_column(cx: &mut TestAppContext) {
9858        let view = multi_line(cx);
9859        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9860        let input = view.input;
9861
9862        setup_cursors(&mut cx, &input, "ab|cd\nabcd");
9863        cx.update(|window, cx| {
9864            input.update(cx, |state, cx| {
9865                state.add_cursor_below(&AddCursorBelow, window, cx);
9866            });
9867        });
9868        assert_cursors(&mut cx, &input, "ab|cd\nab|cd");
9869    }
9870
9871    #[gpui::test]
9872    fn test_add_cursor_at_rejects_duplicates(cx: &mut TestAppContext) {
9873        let view = multi_line(cx);
9874        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9875        let input = view.input;
9876
9877        setup_cursors(&mut cx, &input, "he|llo");
9878        cx.update(|_, cx| {
9879            input.update(cx, |state, cx| {
9880                // Duplicate of the existing cursor is rejected.
9881                state.add_cursor_at(2, cx);
9882                assert_eq!(state.selections.len(), 1);
9883                // A distinct offset adds a cursor.
9884                state.add_cursor_at(4, cx);
9885                assert_eq!(state.selections.len(), 2);
9886            });
9887        });
9888    }
9889
9890    #[gpui::test]
9891    fn test_multi_cursor_undo_redo_restores_selections(cx: &mut TestAppContext) {
9892        let view = multi_line(cx);
9893        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9894        let input = view.input;
9895
9896        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
9897        cx.update(|window, cx| {
9898            input.update(cx, |state, cx| {
9899                state.replace_text_in_range(None, "a", window, cx);
9900            });
9901        });
9902        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
9903
9904        cx.update(|window, cx| {
9905            input.update(cx, |state, cx| {
9906                state.undo(&Undo, window, cx);
9907            });
9908        });
9909        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
9910
9911        cx.update(|window, cx| {
9912            input.update(cx, |state, cx| {
9913                state.redo(&Redo, window, cx);
9914            });
9915        });
9916        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
9917    }
9918
9919    #[gpui::test]
9920    fn test_multi_cursor_undo_redo_different_line_lengths(cx: &mut TestAppContext) {
9921        let view = multi_line(cx);
9922        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9923        let input = view.input;
9924
9925        setup_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
9926        cx.update(|window, cx| {
9927            input.update(cx, |state, cx| {
9928                state.replace_text_in_range(None, "a", window, cx);
9929            });
9930        });
9931        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
9932
9933        cx.update(|window, cx| {
9934            input.update(cx, |state, cx| {
9935                state.undo(&Undo, window, cx);
9936            });
9937        });
9938        assert_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
9939
9940        cx.update(|window, cx| {
9941            input.update(cx, |state, cx| {
9942                state.redo(&Redo, window, cx);
9943            });
9944        });
9945        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
9946    }
9947
9948    #[gpui::test]
9949    fn test_multi_cursor_undo_multiple_inserts(cx: &mut TestAppContext) {
9950        let view = multi_line(cx);
9951        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9952        let input = view.input;
9953
9954        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
9955        for ch in ['a', 'b', 'c'] {
9956            cx.update(|window, cx| {
9957                input.update(cx, |state, cx| {
9958                    state.replace_text_in_range(None, &ch.to_string(), window, cx);
9959                });
9960            });
9961        }
9962        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
9963
9964        // The repeated keystrokes form one typing gesture.
9965        cx.update(|window, cx| {
9966            input.update(cx, |state, cx| {
9967                assert_eq!(state.undo_manager.undo_count(), 1);
9968                state.undo(&Undo, window, cx);
9969            });
9970        });
9971        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
9972
9973        cx.update(|window, cx| {
9974            input.update(cx, |state, cx| {
9975                state.redo(&Redo, window, cx);
9976            });
9977        });
9978        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
9979    }
9980
9981    #[gpui::test]
9982    fn test_multi_cursor_backspace_run_is_one_undo(cx: &mut TestAppContext) {
9983        let view = multi_line(cx);
9984        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9985        let input = view.input;
9986
9987        setup_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
9988        for _ in 0..3 {
9989            cx.update(|window, cx| {
9990                input.update(cx, |state, cx| {
9991                    state.backspace(&Backspace, window, cx);
9992                });
9993            });
9994        }
9995        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
9996
9997        cx.update(|window, cx| {
9998            input.update(cx, |state, cx| {
9999                assert_eq!(state.undo_manager.undo_count(), 1);
10000                state.undo(&Undo, window, cx);
10001            });
10002        });
10003        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
10004    }
10005
10006    #[gpui::test]
10007    fn test_adding_a_cursor_splits_the_typing_gesture(cx: &mut TestAppContext) {
10008        let view = multi_line(cx);
10009        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10010        let input = view.input;
10011
10012        setup_cursors(&mut cx, &input, "|1\n2\n3");
10013        cx.update(|window, cx| {
10014            input.update(cx, |state, cx| {
10015                state.replace_text_in_range(None, "a", window, cx);
10016                state.add_cursor_below(&AddCursorBelow, window, cx);
10017                state.replace_text_in_range(None, "b", window, cx);
10018            });
10019        });
10020        assert_cursors(&mut cx, &input, "ab|1\n2b|\n3");
10021
10022        // The keystroke after the cursor was added is its own undo entry.
10023        cx.update(|window, cx| {
10024            input.update(cx, |state, cx| {
10025                state.undo(&Undo, window, cx);
10026            });
10027        });
10028        assert_cursors(&mut cx, &input, "a|1\n2|\n3");
10029    }
10030
10031    #[gpui::test]
10032    fn test_multi_cursor_indent_is_one_undo(cx: &mut TestAppContext) {
10033        let view = multi_line(cx);
10034        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10035        let input = view.input;
10036
10037        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
10038        cx.update(|window, cx| {
10039            input.update(cx, |state, cx| {
10040                state.indent_inline(&IndentInline, window, cx);
10041                assert_eq!(state.undo_manager.undo_count(), 1);
10042                state.undo(&Undo, window, cx);
10043            });
10044        });
10045        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
10046    }
10047
10048    #[gpui::test]
10049    fn test_multi_cursor_indent_then_outdent_roundtrips(cx: &mut TestAppContext) {
10050        let view = multi_line(cx);
10051        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10052        let input = view.input;
10053
10054        // Cursors at line starts.
10055        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
10056        cx.update(|window, cx| {
10057            input.update(cx, |state, cx| {
10058                state.indent(false, window, cx);
10059            });
10060        });
10061        assert_cursors(&mut cx, &input, "  |1\n  |2\n  |3");
10062
10063        cx.update(|window, cx| {
10064            input.update(cx, |state, cx| {
10065                state.outdent(false, window, cx);
10066            });
10067        });
10068        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
10069    }
10070
10071    #[gpui::test]
10072    fn test_inline_outdent_only_removes_line_indentation(cx: &mut TestAppContext) {
10073        let view = multi_line(cx);
10074        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10075        let input = view.input;
10076
10077        // A mid-line indent lands at the cursor, and the outdent does not
10078        // take it back: it only ever removes leading line indentation.
10079        setup_cursors(&mut cx, &input, "1|2\n1|2");
10080        cx.update(|window, cx| {
10081            input.update(cx, |state, cx| {
10082                state.indent(false, window, cx);
10083            });
10084        });
10085        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
10086
10087        cx.update(|window, cx| {
10088            input.update(cx, |state, cx| {
10089                state.outdent(false, window, cx);
10090            });
10091        });
10092        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
10093
10094        // A line with leading indentation loses that, wherever the cursor is.
10095        setup_cursors(&mut cx, &input, "  1|2\n  1|2");
10096        cx.update(|window, cx| {
10097            input.update(cx, |state, cx| {
10098                state.outdent(false, window, cx);
10099            });
10100        });
10101        assert_cursors(&mut cx, &input, "1|2\n1|2");
10102    }
10103
10104    #[gpui::test]
10105    fn test_readonly_multi_cursor_commands_leave_state_unchanged(cx: &mut TestAppContext) {
10106        let view = multi_line(cx);
10107        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10108        let input = view.input;
10109
10110        setup_cursors(&mut cx, &input, "a|b\nc|d");
10111        cx.update(|window, cx| {
10112            input.update(cx, |state, cx| {
10113                let before: Vec<_> = state.selections.iter().copied().collect();
10114                state.set_readonly(true, cx);
10115                state.backspace(&Backspace, window, cx);
10116                state.indent_inline(&IndentInline, window, cx);
10117
10118                assert_eq!(state.value(), "ab\ncd\n");
10119                assert_eq!(state.selections.iter().copied().collect::<Vec<_>>(), before);
10120            });
10121        });
10122    }
10123
10124    #[gpui::test]
10125    fn test_multi_cursor_edit_preserves_the_active_cursor(cx: &mut TestAppContext) {
10126        let view = multi_line(cx);
10127        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10128        let input = view.input;
10129
10130        setup_cursors(&mut cx, &input, "x|\n|y");
10131        cx.update(|window, cx| {
10132            input.update(cx, |state, cx| {
10133                let mut selections: Vec<_> = state.selections.iter().copied().collect();
10134                selections.swap(0, 1);
10135                state.selections.replace_all(selections);
10136                let active_id = state.active_selection().id;
10137                state.replace_text_in_range(None, "!", window, cx);
10138                assert_eq!(state.active_selection().id, active_id);
10139            });
10140        });
10141    }
10142
10143    #[gpui::test]
10144    fn test_block_indent_outdent_with_selection(cx: &mut TestAppContext) {
10145        let view = multi_line(cx);
10146        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10147        let input = view.input;
10148
10149        cx.update(|window, cx| {
10150            input.update(cx, |state, cx| {
10151                state.set_value("line1\nline2\nline3", window, cx);
10152                let id = state.selections.generate_id();
10153                state
10154                    .selections
10155                    .replace_all(vec![CursorSelection::new(id, 0, 17)]);
10156                cx.notify();
10157            });
10158        });
10159
10160        cx.update(|window, cx| {
10161            input.update(cx, |state, cx| {
10162                state.indent(true, window, cx);
10163            });
10164        });
10165        input.read_with(&cx, |state, _| {
10166            assert_eq!(state.text.to_string(), "  line1\n  line2\n  line3");
10167        });
10168
10169        cx.update(|window, cx| {
10170            input.update(cx, |state, cx| {
10171                state.outdent(true, window, cx);
10172            });
10173        });
10174        input.read_with(&cx, |state, _| {
10175            assert_eq!(state.text.to_string(), "line1\nline2\nline3");
10176        });
10177    }
10178
10179    #[gpui::test]
10180    fn test_multi_cursor_word_movement(cx: &mut TestAppContext) {
10181        let view = multi_line(cx);
10182        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10183        let input = view.input;
10184
10185        setup_cursors(
10186            &mut cx,
10187            &input,
10188            "on|e two three\none t|wo three\non|e two three",
10189        );
10190
10191        cx.update(|window, cx| {
10192            input.update(cx, |state, cx| {
10193                state.move_to_next_word(&MoveToNextWord, window, cx);
10194            });
10195        });
10196        assert_cursors(
10197            &mut cx,
10198            &input,
10199            "one| two three\none two| three\none| two three",
10200        );
10201
10202        cx.update(|window, cx| {
10203            input.update(cx, |state, cx| {
10204                state.move_to_previous_word(&MoveToPreviousWord, window, cx);
10205            });
10206        });
10207        assert_cursors(
10208            &mut cx,
10209            &input,
10210            "|one two three\none |two three\n|one two three",
10211        );
10212
10213        // Move to end/start of document collapses to a single cursor.
10214        cx.update(|window, cx| {
10215            input.update(cx, |state, cx| {
10216                state.move_to_end(&MoveToEnd, window, cx);
10217            });
10218        });
10219        input.read_with(&cx, |state, _| {
10220            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
10221            assert_eq!(cursors, vec![state.text.len()]);
10222        });
10223
10224        cx.update(|window, cx| {
10225            input.update(cx, |state, cx| {
10226                state.move_to_start(&MoveToStart, window, cx);
10227            });
10228        });
10229        input.read_with(&cx, |state, _| {
10230            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
10231            assert_eq!(cursors, vec![0]);
10232        });
10233    }
10234
10235    #[gpui::test]
10236    fn test_multi_cursor_selection_commands(cx: &mut TestAppContext) {
10237        let view = multi_line(cx);
10238        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10239        let input = view.input;
10240
10241        setup_cursors(
10242            &mut cx,
10243            &input,
10244            "on|e two three\none t|wo three\non|e two three",
10245        );
10246        cx.update(|window, cx| {
10247            input.update(cx, |state, cx| {
10248                state.select_to_start_of_line(&SelectToStartOfLine, window, cx);
10249            });
10250        });
10251        assert_cursors(
10252            &mut cx,
10253            &input,
10254            "|one two three\n|one two three\n|one two three",
10255        );
10256
10257        // Select to document start collapses to the active cursor only.
10258        setup_cursors(
10259            &mut cx,
10260            &input,
10261            "on|e two three\none t|wo three\non|e two three",
10262        );
10263        cx.update(|window, cx| {
10264            input.update(cx, |state, cx| {
10265                state.select_to_start(&SelectToStart, window, cx);
10266            });
10267        });
10268        input.read_with(&cx, |state, _| {
10269            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
10270            assert_eq!(cursors, vec![0]);
10271        });
10272    }
10273
10274    #[gpui::test]
10275    fn test_multi_cursor_replace_selection(cx: &mut TestAppContext) {
10276        let view = multi_line(cx);
10277        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10278        let input = view.input;
10279
10280        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
10281        cx.update(|window, cx| {
10282            input.update(cx, |state, cx| {
10283                state.select_right(&SelectRight, window, cx);
10284            });
10285        });
10286        input.read_with(&cx, |state, _| {
10287            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
10288            assert_eq!(ranges, vec![(0, 1), (2, 3), (4, 5)]);
10289        });
10290
10291        cx.update(|window, cx| {
10292            input.update(cx, |state, cx| {
10293                state.replace_text_in_range(None, "x", window, cx);
10294            });
10295        });
10296        assert_cursors(&mut cx, &input, "x|\nx|\nx|");
10297    }
10298
10299    #[gpui::test]
10300    fn test_multi_cursor_escape_collapses(cx: &mut TestAppContext) {
10301        let view = multi_line(cx);
10302        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10303        let input = view.input;
10304
10305        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
10306        cx.update(|window, cx| {
10307            input.update(cx, |state, cx| {
10308                state.escape(&Escape, window, cx);
10309            });
10310        });
10311        input.read_with(&cx, |state, _| {
10312            assert_eq!(state.selections.len(), 1);
10313        });
10314    }
10315
10316    #[gpui::test]
10317    fn test_build_columnar_selection(cx: &mut TestAppContext) {
10318        let view = multi_line(cx);
10319        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10320        let input = view.input;
10321
10322        setup_cursors(&mut cx, &input, "abcd\nabcd\nabcd");
10323        cx.update(|_, cx| {
10324            input.update(cx, |state, cx| {
10325                // From row 0 col 1 to row 2 col 3.
10326                state.build_columnar_selection(
10327                    ColumnarPoint::new(1, 0),
10328                    ColumnarPoint::new(13, 0),
10329                    cx,
10330                );
10331                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
10332                assert_eq!(ranges, vec![(1, 3), (6, 8), (11, 13)]);
10333            });
10334        });
10335    }
10336
10337    #[gpui::test]
10338    fn test_columnar_selection_keeps_width_over_short_rows(cx: &mut TestAppContext) {
10339        let view = multi_line(cx);
10340        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10341        let input = view.input;
10342
10343        setup_cursors(&mut cx, &input, "abcdef\nab\nabcdef");
10344        cx.update(|_, cx| {
10345            input.update(cx, |state, cx| {
10346                // Ends on the short row, 3 columns past its end: the block still spans
10347                // columns 1..5, and only that row is clipped to what it has.
10348                state.build_columnar_selection(
10349                    ColumnarPoint::new(1, 0),
10350                    ColumnarPoint::new(9, 3),
10351                    cx,
10352                );
10353                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
10354                assert_eq!(ranges, vec![(1, 5), (8, 9)]);
10355            });
10356        });
10357    }
10358
10359    #[gpui::test]
10360    fn test_multi_cursor_paste_distributes_lines(cx: &mut TestAppContext) {
10361        let view = multi_line(cx);
10362        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10363        let input = view.input;
10364
10365        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
10366        cx.update(|_, cx| {
10367            cx.write_to_clipboard(ClipboardItem::new_string("x\ny\nz".to_string()));
10368        });
10369        cx.update(|window, cx| {
10370            input.update(cx, |state, cx| {
10371                state.paste(&Paste, window, cx);
10372            });
10373        });
10374        // One clipboard line per cursor.
10375        assert_cursors(&mut cx, &input, "x|1\ny|2\nz|3");
10376    }
10377
10378    #[gpui::test]
10379    fn test_paste_without_text_leaves_the_selection_alone(cx: &mut TestAppContext) {
10380        use gpui::{ClipboardEntry, Image, ImageFormat};
10381
10382        let input_view = InputView::build(cx, |state| state);
10383        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
10384        let input = input_view.input;
10385
10386        cx.update(|window, cx| {
10387            input.update(cx, |state, cx| {
10388                state.set_value("hello world", window, cx);
10389                state.select_all(window, cx);
10390                cx.write_to_clipboard(ClipboardItem {
10391                    entries: vec![ClipboardEntry::Image(Image::from_bytes(
10392                        ImageFormat::Png,
10393                        vec![0x89, b'P', b'N', b'G'],
10394                    ))],
10395                });
10396
10397                // An image-only clipboard must not replace the selection
10398                // with nothing.
10399                state.paste(&Paste, window, cx);
10400                assert_eq!(state.value(), "hello world");
10401                assert_eq!(state.selected_range(), 0..11);
10402            });
10403        });
10404    }
10405
10406    #[gpui::test]
10407    fn test_paste_target_tracks_edits_and_selections(cx: &mut TestAppContext) {
10408        let view = multi_line(cx);
10409        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10410        let input = view.input;
10411
10412        setup_cursors(&mut cx, &input, "ab|c");
10413        let target = input.read_with(&cx, |state, _| state.paste_target());
10414        // Nothing happened: a paste asked for then still applies.
10415        assert_eq!(
10416            input.read_with(&cx, |state, _| state.paste_target()),
10417            target
10418        );
10419
10420        // Moving the caret changes where the paste would go.
10421        cx.update(|_, cx| {
10422            input.update(cx, |state, cx| {
10423                let id = state.selections.generate_id();
10424                state
10425                    .selections
10426                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
10427                cx.notify();
10428            });
10429        });
10430        let moved = input.read_with(&cx, |state, _| state.paste_target());
10431        assert_ne!(moved, target);
10432
10433        // Editing the text changes it too, even with the caret put back.
10434        cx.update(|window, cx| {
10435            input.update(cx, |state, cx| {
10436                state.replace_text_in_range_silent(None, "x", window, cx);
10437                let id = state.selections.generate_id();
10438                state
10439                    .selections
10440                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
10441            });
10442        });
10443        assert_ne!(input.read_with(&cx, |state, _| state.paste_target()), moved);
10444    }
10445
10446    #[gpui::test]
10447    fn test_ime_candidate_bounds_fallback_before_repaint(cx: &mut TestAppContext) {
10448        let input_view = InputView::build_textarea(cx, |state| {
10449            state.default_value("这是一段已经输入的中文文字")
10450        });
10451        let mut visual = VisualTestContext::from_window(input_view.window_handle.into(), cx);
10452        visual.update(|window, cx| window.draw(cx).clear(cx));
10453
10454        visual.update(|window, cx| {
10455            input_view.input.update(cx, |state, cx| {
10456                state.focus(window, cx);
10457                let len = state.text.len();
10458                state.set_cursor_to(len);
10459            });
10460        });
10461        visual.update(|window, cx| window.draw(cx).clear(cx));
10462
10463        visual.update(|window, cx| {
10464            input_view.input.update(cx, |state, cx| {
10465                let bounds = state.text_bounds().unwrap_or_default();
10466                let before = state.selected_text_range(false, window, cx).unwrap().range;
10467                let previous = state
10468                    .bounds_for_range(before.end..before.end, bounds, window, cx)
10469                    .unwrap();
10470
10471                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
10472
10473                let after = state.selected_text_range(false, window, cx).unwrap().range;
10474                let pending = state
10475                    .bounds_for_range(after.end..after.end, bounds, window, cx)
10476                    .unwrap();
10477
10478                assert!(
10479                    (pending.origin.x - previous.origin.x).abs() < px(40.0),
10480                    "pending origin.x {:?} must remain close to previous origin.x {:?}",
10481                    pending.origin.x,
10482                    previous.origin.x
10483                );
10484
10485                let range_bounds = state
10486                    .bounds_for_range(before.end..after.end, bounds, window, cx)
10487                    .unwrap();
10488                assert!(
10489                    (range_bounds.origin.x - previous.origin.x).abs() < px(40.0),
10490                    "range_bounds origin.x {:?} must remain close to previous origin.x {:?}",
10491                    range_bounds.origin.x,
10492                    previous.origin.x
10493                );
10494                assert!(range_bounds.size.width >= px(0.0));
10495            });
10496        });
10497    }
10498}
10499
10500/// Methods that only a single-line input offers.
10501impl InputBaseState<crate::input::InputMode> {
10502    /// Create a single-line text input state.
10503    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
10504        Self::new_in_mode(window, cx)
10505    }
10506
10507    /// Set a custom step function of the [`super::NumberInput`].
10508    ///
10509    /// The `f` receives the current value and the [`StepAction`], and returns
10510    /// the step to apply, so the step can vary with the value.
10511    ///
10512    /// # Example
10513    ///
10514    /// ```ignore
10515    /// // At the boundary 1.0 the step is 0.1 going down and 0.5 going up.
10516    /// InputState::new(window, cx).step_by(|value, action, _cx| match action {
10517    ///     StepAction::Increment => if value < 1.0 { 0.1 } else { 0.5 },
10518    ///     StepAction::Decrement => if value <= 1.0 { 0.1 } else { 0.5 },
10519    /// })
10520    /// ```
10521    pub fn step_by(mut self, f: impl Fn(f64, StepAction, &mut App) -> f64 + 'static) -> Self {
10522        self.number_step = Some(NumberStep::by_value(f));
10523        self
10524    }
10525
10526    /// Set with password masked state.
10527    pub fn masked(mut self, masked: bool) -> Self {
10528        self.masked = masked;
10529        self
10530    }
10531
10532    /// Set the password masked state of the input field.
10533    pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
10534        self.masked = masked;
10535        cx.notify();
10536    }
10537
10538    /// Set the regular expression pattern of the input field.
10539    pub fn pattern(mut self, pattern: regex::Regex) -> Self {
10540        self.pattern = Some(pattern);
10541        self
10542    }
10543
10544    /// Set the regular expression pattern of the input field with reference.
10545    pub fn set_pattern(
10546        &mut self,
10547        pattern: regex::Regex,
10548        _window: &mut Window,
10549        _cx: &mut Context<Self>,
10550    ) {
10551        self.pattern = Some(pattern);
10552    }
10553
10554    /// Set the validation function of the input field.
10555    pub fn validate(mut self, f: impl Fn(&str, &mut App) -> bool + 'static) -> Self {
10556        self.validate = Some(Box::new(f));
10557        self
10558    }
10559
10560    pub fn set_validator(
10561        &mut self,
10562        validate: impl Fn(&str, &mut App) -> bool + 'static,
10563        _cx: &mut Context<Self>,
10564    ) {
10565        self.validate = Some(Box::new(validate));
10566    }
10567
10568    /// Set the step value of the [`super::NumberInput`] for increment/decrement.
10569    ///
10570    /// If any of `step`, `min`, `max` is set, the [`super::NumberInput`] will
10571    /// update the value internally (step by `step`, default 1, clamp to the
10572    /// `min`/`max` range and emit [`InputEvent::Change`]) instead of emitting
10573    /// [`super::NumberInputEvent::Step`].
10574    ///
10575    /// See also [`Self::step_by`] to calculate the step value
10576    /// based on the current value.
10577    pub fn step(mut self, step: impl Into<NumberStep>) -> Self {
10578        self.number_step = Some(step.into());
10579        self
10580    }
10581
10582    /// Set the minimum value of the [`super::NumberInput`].
10583    ///
10584    /// The value will be clamped to the minimum value on stepping and on
10585    /// blur (only if the clamped value passes the `pattern`/`validate` check).
10586    /// See also [`Self::step`].
10587    pub fn min(mut self, min: f64) -> Self {
10588        self.number_min = Some(min);
10589        self
10590    }
10591
10592    /// Set the maximum value of the [`super::NumberInput`].
10593    ///
10594    /// The value will be clamped to the maximum value on stepping and on
10595    /// blur (only if the clamped value passes the `pattern`/`validate` check).
10596    /// See also [`Self::step`].
10597    pub fn max(mut self, max: f64) -> Self {
10598        self.number_max = Some(max);
10599        self
10600    }
10601
10602    /// Update the step value after construction, `None` to fall back to
10603    /// emitting [`super::NumberInputEvent::Step`] (if `min`, `max` are unset).
10604    ///
10605    /// See [`Self::step`] and [`Self::step_by`].
10606    pub fn set_step(
10607        &mut self,
10608        step: impl Into<Option<NumberStep>>,
10609        _: &mut Window,
10610        _: &mut Context<Self>,
10611    ) {
10612        self.number_step = step.into();
10613    }
10614
10615    /// Update the minimum value after construction. See [`Self::min`].
10616    pub fn set_min(&mut self, min: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
10617        self.number_min = min;
10618    }
10619
10620    /// Update the maximum value after construction. See [`Self::max`].
10621    pub fn set_max(&mut self, max: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
10622        self.number_max = max;
10623    }
10624
10625    /// Set true to show spinner at the input right.
10626    pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
10627        self.loading = loading;
10628        cx.notify();
10629    }
10630}
10631
10632/// Methods shared by the two multi-line modes, and reachable on neither a
10633/// single-line input nor anything else.
10634impl<M: crate::input::MultiLineMode> InputBaseState<M> {
10635    /// Whether the built-in search panel and its shortcut are enabled. Off by
10636    /// default, on for the code editor.
10637    ///
10638    /// This only concerns the panel. An input that is not searchable still
10639    /// answers [`InputBaseState::set_search_query`] and the other search
10640    /// methods, and lets `Ctrl-F` / `Cmd-F` bubble up to its ancestors, so an
10641    /// application can put its own search UI on top of the same engine.
10642    pub fn searchable(mut self, searchable: bool) -> Self {
10643        self.searchable = searchable;
10644        self
10645    }
10646
10647    /// See [`InputBaseState::searchable`].
10648    pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
10649        self.searchable = searchable;
10650        cx.notify();
10651    }
10652
10653    /// Set the soft wrap mode, default is true.
10654    #[doc(hidden)]
10655    pub fn soft_wrap(mut self, wrap: bool) -> Self {
10656        self.soft_wrap = wrap;
10657        self
10658    }
10659
10660    /// Update the soft wrap mode, default is true.
10661    pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
10662        self.soft_wrap = wrap;
10663        if wrap {
10664            let wrap_width = self
10665                .last_layout
10666                .as_ref()
10667                .and_then(|b| b.wrap_width)
10668                .unwrap_or(self.input_bounds.size.width);
10669
10670            self.display_map.on_layout_changed(Some(wrap_width), cx);
10671
10672            // Reset scroll to left 0
10673            let mut offset = self.scroll_handle.offset();
10674            offset.x = px(0.);
10675            self.scroll_handle.set_offset(offset);
10676        } else {
10677            self.display_map.on_layout_changed(None, cx);
10678        }
10679        cx.notify();
10680    }
10681
10682    /// Set how soft-wrapped continuation lines are indented, default is [`WrappingIndent::Same`]
10683    #[doc(hidden)]
10684    pub fn wrapping_indent(mut self, wrapping_indent: WrappingIndent) -> Self {
10685        self.wrapping_indent = wrapping_indent;
10686        self
10687    }
10688
10689    /// Update how soft-wrapped continuation lines are indented.
10690    pub fn set_wrapping_indent(
10691        &mut self,
10692        wrapping_indent: WrappingIndent,
10693        _: &mut Window,
10694        cx: &mut Context<Self>,
10695    ) {
10696        self.wrapping_indent = wrapping_indent;
10697        self.display_map.set_wrapping_indent(wrapping_indent, cx);
10698        cx.notify();
10699    }
10700}
10701
10702/// Methods that only ordinary multi-line text offers.
10703impl InputBaseState<crate::input::TextareaMode> {
10704    /// Create a multi-line text state.
10705    ///
10706    /// Being multi-line is carried by the mode, not by the layout, so the
10707    /// default plain-text layout needs no adjustment here.
10708    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
10709        Self::new_in_mode(window, cx)
10710    }
10711
10712    pub fn set_auto_grow(&mut self, min_rows: usize, max_rows: usize, cx: &mut Context<Self>) {
10713        self.mode = LayoutMode::auto_grow(min_rows, max_rows.max(min_rows));
10714        cx.notify();
10715    }
10716
10717    /// Set the number of rows for the multi-line Textarea.
10718    ///
10719    /// This is only used when `multi_line` is set to true. The input is at least
10720    /// this many lines tall.
10721    ///
10722    /// default: 1
10723    #[doc(hidden)]
10724    pub fn rows(mut self, rows: usize) -> Self {
10725        match &mut self.mode {
10726            LayoutMode::PlainText { rows: r, .. } | LayoutMode::CodeEditor { rows: r, .. } => {
10727                *r = rows
10728            }
10729            LayoutMode::AutoGrow {
10730                max_rows: max_r,
10731                rows: r,
10732                ..
10733            } => {
10734                *r = rows;
10735                *max_r = rows;
10736            }
10737        }
10738        self
10739    }
10740
10741    pub fn set_rows(&mut self, rows: usize, cx: &mut Context<Self>) {
10742        match &mut self.mode {
10743            LayoutMode::PlainText { rows: value, .. }
10744            | LayoutMode::CodeEditor { rows: value, .. } => *value = rows,
10745            LayoutMode::AutoGrow {
10746                rows: value,
10747                max_rows,
10748                ..
10749            } => {
10750                *value = rows;
10751                *max_rows = rows;
10752            }
10753        }
10754        cx.notify();
10755    }
10756
10757    /// Grow with the content from `min_rows` through `max_rows`.
10758    pub fn auto_grow(mut self, min_rows: usize, max_rows: usize) -> Self {
10759        self.mode = LayoutMode::auto_grow(min_rows, max_rows);
10760        self
10761    }
10762}
10763
10764/// Methods that only a source-code editor offers.
10765impl InputBaseState<crate::input::EditorMode> {
10766    /// Create a source-code editor state.
10767    ///
10768    /// Default options: line numbers on, tab size 2 with soft tabs, indent
10769    /// guides on, multi-line, and search enabled. Set the language for syntax
10770    /// highlighting with [`Self::language`]; without one the text is shown
10771    /// unhighlighted.
10772    ///
10773    /// The editor aims at simple code editing or display, not at being a
10774    /// full-featured code editor. It offers syntax highlighting, auto indent,
10775    /// line numbers, and handles large text up to about 50K lines.
10776    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
10777        let mut state = Self::new_in_mode(window, cx);
10778        state.mode = LayoutMode::code_editor(super::EditorLanguage::new(cx));
10779        state.searchable = true;
10780        state
10781    }
10782
10783    /// Set the language to highlight, e.g. `"rust"`.
10784    ///
10785    /// See [`Self::set_highlighter`] to change it after construction.
10786    pub fn language(mut self, language: impl Into<SharedString>) -> Self {
10787        if let LayoutMode::CodeEditor {
10788            language: l,
10789            highlighter,
10790            ..
10791        } = &mut self.mode
10792        {
10793            l.set_name(language.into());
10794            *highlighter.borrow_mut() = None;
10795        }
10796        self
10797    }
10798
10799    /// The current language name, e.g. `"rust"`.
10800    pub fn language_name(&self) -> SharedString {
10801        match &self.mode {
10802            LayoutMode::CodeEditor { language, .. } => language.name(),
10803            _ => SharedString::default(),
10804        }
10805    }
10806
10807    /// Set enable/disable code folding.
10808    ///
10809    /// Default: true
10810    #[doc(hidden)]
10811    pub fn folding(mut self, folding: bool) -> Self {
10812        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
10813            *f = folding;
10814        }
10815        self
10816    }
10817
10818    /// Set code folding at runtime.
10819    ///
10820    /// When disabling, all existing folds are cleared.
10821    pub fn set_folding(&mut self, folding: bool, _: &mut Window, cx: &mut Context<Self>) {
10822        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
10823            *f = folding;
10824        }
10825        if !folding {
10826            self.display_map.clear_folds();
10827        }
10828        cx.notify();
10829    }
10830
10831    /// Unfold any folded ranges that hide the given position.
10832    ///
10833    /// Use this to reveal a position before acting on it (e.g. before
10834    /// [`Self::set_cursor_position`], which stops at a fold boundary),
10835    /// without touching folds elsewhere in the buffer. Fold candidates are
10836    /// kept, so the opened ranges can be folded again from the gutter.
10837    ///
10838    /// A fold keeps its own first and last line visible, so a position on
10839    /// either of them opens nothing. Nested folds all open, since opening
10840    /// only the outermost would leave the position hidden.
10841    ///
10842    /// Returns whether any fold was opened.
10843    pub fn unfold_at(&mut self, position: impl Into<Position>, cx: &mut Context<Self>) -> bool {
10844        let offset = self.text.position_to_offset(&position.into());
10845        let line = self.text.offset_to_point(offset).row;
10846        // A fold hides start_line + 1 ..= end_line - 1, so a line is hidden
10847        // exactly when some folded range strictly contains it.
10848        let covering: Vec<usize> = self
10849            .display_map
10850            .folded_ranges()
10851            .iter()
10852            .filter(|fold| line > fold.start_line && line < fold.end_line)
10853            .map(|fold| fold.start_line)
10854            .collect();
10855        if covering.is_empty() {
10856            return false;
10857        }
10858
10859        for start_line in covering {
10860            self.display_map.set_folded(start_line, false);
10861        }
10862        cx.notify();
10863        true
10864    }
10865
10866    /// Set enable/disable line number.
10867    #[doc(hidden)]
10868    pub fn line_number(mut self, line_number: bool) -> Self {
10869        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
10870            *l = line_number;
10871        }
10872        self
10873    }
10874
10875    /// Set line number.
10876    pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
10877        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
10878            *l = line_number;
10879        }
10880        cx.notify();
10881    }
10882
10883    /// Set enable/disable automatic closing brackets and quotes.
10884    ///
10885    /// When enabled, typing an opener from [`LanguageConfig::auto_closing_pairs`](crate::input::language_config::LanguageConfig::auto_closing_pairs) inserts the
10886    /// matching closer and places the cursor inside. Typing a closer that is
10887    /// already present just moves past it. Default: true
10888    #[doc(hidden)]
10889    pub fn auto_close(mut self, auto_close: bool) -> Self {
10890        self.mode.set_auto_close(auto_close);
10891        self
10892    }
10893
10894    /// Set automatic closing brackets and quotes at runtime.
10895    pub fn set_auto_close(&mut self, auto_close: bool, _: &mut Window, cx: &mut Context<Self>) {
10896        self.mode.set_auto_close(auto_close);
10897        cx.notify();
10898    }
10899
10900    /// Set enable/disable smart indent on Enter.
10901    ///
10902    /// When enabled, Enter uses structural brackets and
10903    /// [`LanguageConfig::indentation_rules`](crate::input::language_config::LanguageConfig::indentation_rules) to choose indentation.
10904    /// Default: true
10905    #[doc(hidden)]
10906    pub fn smart_indent(mut self, smart_indent: bool) -> Self {
10907        self.mode.set_smart_indent(smart_indent);
10908        self
10909    }
10910
10911    /// Set smart indent on Enter at runtime.
10912    pub fn set_smart_indent(&mut self, smart_indent: bool, _: &mut Window, cx: &mut Context<Self>) {
10913        self.mode.set_smart_indent(smart_indent);
10914        cx.notify();
10915    }
10916}