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