Skip to main content

gpui_base/input/base/
state.rs

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