Skip to main content

gpui_base/input/base/
state.rs

1//! A text input field that allows the user to enter text.
2//!
3//! Based on the `Input` example from the `gpui` crate.
4//! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs
5use gpui::TextAlign;
6use gpui::{
7    Action, App, AppContext, Bounds, ClipboardItem, Context, Edges, Entity, EntityInputHandler,
8    EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding,
9    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point,
10    Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription,
11    UTF16Selection, WeakFocusHandle, Window, actions, div, point, prelude::FluentBuilder as _, px,
12};
13use ropey::{Rope, RopeSlice};
14use serde::Deserialize;
15use std::borrow::Cow;
16use std::cell::Cell;
17use std::ops::Range;
18use std::rc::Rc;
19use sum_tree::Bias;
20use unicode_segmentation::*;
21
22use super::{
23    DiagnosticSet, DisplayMap, InputContextMenuCapabilities, InputEditorStyle,
24    InputHighlighterFactory, MASK_CHAR, MaskPattern, NativeMenu, NumberStep, WrappingIndent,
25    blink_cursor::BlinkCursor,
26    change::Change,
27    cursor::{CursorSelection, Selections},
28    element::{
29        EditorScrollbar, EditorScrollbarSnapshot, LongestLineKey, TextElement,
30        clamp_horizontal_scroll_offset,
31    },
32    kind::InputModeKind,
33    mask_pattern::normalize_number_input,
34    mode::LayoutMode,
35    undo_manager::{EditIntent, UndoManager},
36};
37use crate::actions::{SelectDown, SelectLeft, SelectRight, SelectUp};
38use crate::input::blink_cursor::CURSOR_WIDTH;
39use crate::input::movement::MoveDirection;
40use crate::input::{
41    InputExtras as _, Position, RopeExt as _, element::RIGHT_MARGIN, layout::LastLayout,
42};
43use crate::{AutoScroll, StepAction};
44
45/// Vertical clearance to retain when revealing a text position.
46pub(crate) enum ScrollPadding {
47    Minimal,
48    SurroundingLines,
49}
50
51#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
52#[action(namespace = input, no_json)]
53pub struct Enter {
54    /// Is confirm with secondary.
55    pub secondary: bool,
56    /// Whether the Shift modifier was held when Enter was pressed.
57    pub shift: bool,
58}
59
60impl Enter {
61    /// Returns true if `action` is a primary `Enter` action (`secondary: false`),
62    /// regardless of whether Shift was held.
63    pub fn is_primary(action: &dyn Action) -> bool {
64        action.partial_eq(&Enter {
65            secondary: false,
66            shift: false,
67        }) || action.partial_eq(&Enter {
68            secondary: false,
69            shift: true,
70        })
71    }
72}
73
74actions!(
75    input,
76    [
77        ActivateToken,
78        Backspace,
79        Delete,
80        DeleteToBeginningOfLine,
81        DeleteToEndOfLine,
82        DeleteToPreviousWordStart,
83        DeleteToNextWordEnd,
84        Indent,
85        Outdent,
86        IndentInline,
87        OutdentInline,
88        MoveUp,
89        MoveDown,
90        MoveLeft,
91        MoveRight,
92        MoveHome,
93        MoveEnd,
94        MovePageUp,
95        MovePageDown,
96        AddCursorAbove,
97        AddCursorBelow,
98        SelectAll,
99        SelectToStartOfLine,
100        SelectToEndOfLine,
101        SelectToStart,
102        SelectToEnd,
103        SelectToPreviousWordStart,
104        SelectToNextWordEnd,
105        ShowCharacterPalette,
106        Copy,
107        Cut,
108        Paste,
109        Undo,
110        Redo,
111        MoveToStartOfLine,
112        MoveToEndOfLine,
113        MoveToStart,
114        MoveToEnd,
115        MoveToPreviousWord,
116        MoveToNextWord,
117        Escape,
118        ToggleCodeActions,
119        ShowCompletions,
120        Search,
121        Replace,
122        GoToDefinition,
123    ]
124);
125
126#[derive(Clone)]
127pub enum InputEvent {
128    Change,
129    PressEnter { secondary: bool, shift: bool },
130    Focus,
131    Blur,
132}
133
134pub(super) const CONTEXT: &str = "Input";
135
136pub(crate) fn init(cx: &mut App) {
137    cx.bind_keys([
138        KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
139        KeyBinding::new("shift-backspace", Backspace, Some(CONTEXT)),
140        #[cfg(target_os = "macos")]
141        KeyBinding::new("ctrl-backspace", Backspace, Some(CONTEXT)),
142        KeyBinding::new("delete", Delete, Some(CONTEXT)),
143        KeyBinding::new("shift-delete", Delete, Some(CONTEXT)),
144        #[cfg(target_os = "macos")]
145        KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
146        #[cfg(target_os = "macos")]
147        KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
148        #[cfg(target_os = "macos")]
149        KeyBinding::new("alt-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
150        #[cfg(not(target_os = "macos"))]
151        KeyBinding::new("ctrl-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
152        #[cfg(target_os = "macos")]
153        KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)),
154        #[cfg(not(target_os = "macos"))]
155        KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)),
156        KeyBinding::new(
157            "enter",
158            Enter {
159                secondary: false,
160                shift: false,
161            },
162            Some(CONTEXT),
163        ),
164        KeyBinding::new(
165            "shift-enter",
166            Enter {
167                secondary: false,
168                shift: true,
169            },
170            Some(CONTEXT),
171        ),
172        KeyBinding::new(
173            "secondary-enter",
174            Enter {
175                secondary: true,
176                shift: false,
177            },
178            Some(CONTEXT),
179        ),
180        KeyBinding::new("escape", Escape, Some(CONTEXT)),
181        KeyBinding::new("up", MoveUp, Some(CONTEXT)),
182        KeyBinding::new("down", MoveDown, Some(CONTEXT)),
183        KeyBinding::new("left", MoveLeft, Some(CONTEXT)),
184        KeyBinding::new("right", MoveRight, Some(CONTEXT)),
185        KeyBinding::new("pageup", MovePageUp, Some(CONTEXT)),
186        KeyBinding::new("pagedown", MovePageDown, Some(CONTEXT)),
187        KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
188        KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
189        #[cfg(target_os = "macos")]
190        KeyBinding::new("cmd-]", Indent, Some(CONTEXT)),
191        #[cfg(not(target_os = "macos"))]
192        KeyBinding::new("ctrl-]", Indent, Some(CONTEXT)),
193        #[cfg(target_os = "macos")]
194        KeyBinding::new("cmd-[", Outdent, Some(CONTEXT)),
195        #[cfg(not(target_os = "macos"))]
196        KeyBinding::new("ctrl-[", Outdent, Some(CONTEXT)),
197        KeyBinding::new("shift-left", SelectLeft, Some(CONTEXT)),
198        KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
199        KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
200        KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
201        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
202        KeyBinding::new("shift-alt-left", SelectLeft, Some(CONTEXT)),
203        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
204        KeyBinding::new("shift-alt-right", SelectRight, Some(CONTEXT)),
205        // Avoid Ctrl+Alt+arrows on Linux, where desktops may reserve them.
206        #[cfg(target_os = "macos")]
207        KeyBinding::new("cmd-alt-up", AddCursorAbove, Some(CONTEXT)),
208        #[cfg(target_os = "macos")]
209        KeyBinding::new("cmd-alt-down", AddCursorBelow, Some(CONTEXT)),
210        #[cfg(target_os = "windows")]
211        KeyBinding::new("ctrl-alt-up", AddCursorAbove, Some(CONTEXT)),
212        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
213        KeyBinding::new("shift-alt-up", AddCursorAbove, Some(CONTEXT)),
214        #[cfg(target_os = "windows")]
215        KeyBinding::new("ctrl-alt-down", AddCursorBelow, Some(CONTEXT)),
216        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
217        KeyBinding::new("shift-alt-down", AddCursorBelow, Some(CONTEXT)),
218        KeyBinding::new("home", MoveHome, Some(CONTEXT)),
219        KeyBinding::new("end", MoveEnd, Some(CONTEXT)),
220        #[cfg(not(target_os = "macos"))]
221        KeyBinding::new("ctrl-home", MoveToStart, Some(CONTEXT)),
222        #[cfg(not(target_os = "macos"))]
223        KeyBinding::new("ctrl-end", MoveToEnd, Some(CONTEXT)),
224        #[cfg(not(target_os = "macos"))]
225        KeyBinding::new("ctrl-shift-home", SelectToStart, Some(CONTEXT)),
226        #[cfg(not(target_os = "macos"))]
227        KeyBinding::new("ctrl-shift-end", SelectToEnd, Some(CONTEXT)),
228        KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
229        KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
230        #[cfg(target_os = "macos")]
231        KeyBinding::new("ctrl-shift-a", SelectToStartOfLine, Some(CONTEXT)),
232        #[cfg(target_os = "macos")]
233        KeyBinding::new("ctrl-shift-e", SelectToEndOfLine, Some(CONTEXT)),
234        #[cfg(target_os = "macos")]
235        KeyBinding::new("shift-cmd-left", SelectToStartOfLine, Some(CONTEXT)),
236        #[cfg(target_os = "macos")]
237        KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
238        #[cfg(any(target_os = "macos", target_os = "linux"))]
239        KeyBinding::new("alt-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
240        #[cfg(not(target_os = "macos"))]
241        KeyBinding::new("ctrl-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
242        #[cfg(any(target_os = "macos", target_os = "linux"))]
243        KeyBinding::new("alt-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
244        #[cfg(not(target_os = "macos"))]
245        KeyBinding::new("ctrl-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
246        #[cfg(target_os = "macos")]
247        KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
248        #[cfg(target_os = "macos")]
249        KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
250        #[cfg(not(target_os = "macos"))]
251        KeyBinding::new("ctrl-a", SelectAll, Some(CONTEXT)),
252        #[cfg(target_os = "macos")]
253        KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
254        #[cfg(not(target_os = "macos"))]
255        KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
256        #[cfg(target_os = "macos")]
257        KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
258        #[cfg(not(target_os = "macos"))]
259        KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
260        #[cfg(target_os = "macos")]
261        KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
262        #[cfg(not(target_os = "macos"))]
263        KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
264        #[cfg(target_os = "macos")]
265        KeyBinding::new("ctrl-a", MoveHome, Some(CONTEXT)),
266        #[cfg(target_os = "macos")]
267        KeyBinding::new("cmd-left", MoveHome, Some(CONTEXT)),
268        #[cfg(target_os = "macos")]
269        KeyBinding::new("ctrl-e", MoveEnd, Some(CONTEXT)),
270        #[cfg(target_os = "macos")]
271        KeyBinding::new("cmd-right", MoveEnd, Some(CONTEXT)),
272        #[cfg(target_os = "macos")]
273        KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
274        #[cfg(target_os = "macos")]
275        KeyBinding::new("cmd-shift-z", Redo, Some(CONTEXT)),
276        #[cfg(target_os = "macos")]
277        KeyBinding::new("cmd-up", MoveToStart, Some(CONTEXT)),
278        #[cfg(target_os = "macos")]
279        KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
280        #[cfg(target_os = "macos")]
281        KeyBinding::new("alt-left", MoveToPreviousWord, Some(CONTEXT)),
282        #[cfg(target_os = "macos")]
283        KeyBinding::new("alt-right", MoveToNextWord, Some(CONTEXT)),
284        #[cfg(not(target_os = "macos"))]
285        KeyBinding::new("ctrl-left", MoveToPreviousWord, Some(CONTEXT)),
286        #[cfg(not(target_os = "macos"))]
287        KeyBinding::new("ctrl-right", MoveToNextWord, Some(CONTEXT)),
288        #[cfg(target_os = "macos")]
289        KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
290        #[cfg(target_os = "macos")]
291        KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
292        #[cfg(not(target_os = "macos"))]
293        KeyBinding::new("ctrl-z", Undo, Some(CONTEXT)),
294        #[cfg(not(target_os = "macos"))]
295        KeyBinding::new("ctrl-y", Redo, Some(CONTEXT)),
296        #[cfg(target_os = "macos")]
297        KeyBinding::new("cmd-.", ToggleCodeActions, Some(CONTEXT)),
298        #[cfg(not(target_os = "macos"))]
299        KeyBinding::new("ctrl-.", ToggleCodeActions, Some(CONTEXT)),
300        #[cfg(target_os = "macos")]
301        KeyBinding::new("cmd-f", Search, Some(CONTEXT)),
302        #[cfg(not(target_os = "macos"))]
303        KeyBinding::new("ctrl-f", Search, Some(CONTEXT)),
304        #[cfg(target_os = "macos")]
305        KeyBinding::new("cmd-shift-f", Replace, Some(CONTEXT)),
306        #[cfg(not(target_os = "macos"))]
307        KeyBinding::new("ctrl-h", Replace, Some(CONTEXT)),
308    ]);
309}
310
311/// A mouse position resolved for a columnar (block) selection.
312///
313/// A position past the end of a short row has no glyph to land on, so `offset` clips to
314/// that row's end and `columns_past_line_end` keeps what the clip dropped. Together they
315/// say which column the pointer really reached, which is what a block spanning rows of
316/// different lengths has to be measured in.
317#[derive(Debug, Clone, Copy, PartialEq, Eq)]
318pub(super) struct ColumnarPoint {
319    offset: usize,
320    columns_past_line_end: usize,
321}
322
323impl ColumnarPoint {
324    pub(super) fn new(offset: usize, columns_past_line_end: usize) -> Self {
325        Self {
326            offset,
327            columns_past_line_end,
328        }
329    }
330}
331
332/// The document and selections a paste was asked to replace, taken when the
333/// `Paste` action ran so a clipboard read that resolves later can tell
334/// whether it still applies. See [`InputBaseState::paste_target`].
335#[derive(Debug, PartialEq)]
336struct PasteTarget {
337    document_revision: u64,
338    selections: Vec<CursorSelection>,
339}
340
341/// Caret positioning geometry from the input's latest prepaint.
342///
343/// Unlike `last_layout` (published at paint time), this is stashed during
344/// prepaint so caret-anchored popups can position themselves from the
345/// current frame's geometry in their deferred prepaint, instead of the
346/// previous frame's paint.
347#[derive(Clone, Copy, Debug)]
348#[doc(hidden)]
349pub struct PrepaintCaretGeometry {
350    cursor_bounds: Bounds<Pixels>,
351    line_height: Pixels,
352    input_bounds: Bounds<Pixels>,
353    scroll_offset: Point<Pixels>,
354}
355
356impl PrepaintCaretGeometry {
357    pub(super) fn new(
358        cursor_bounds: Bounds<Pixels>,
359        line_height: Pixels,
360        input_bounds: Bounds<Pixels>,
361        scroll_offset: Point<Pixels>,
362    ) -> Self {
363        Self {
364            cursor_bounds,
365            line_height,
366            input_bounds,
367            scroll_offset,
368        }
369    }
370
371    /// The active caret's bounds from the fresh prepaint layout.
372    pub fn cursor_bounds(&self) -> Bounds<Pixels> {
373        self.cursor_bounds
374    }
375
376    /// Line height from the fresh prepaint layout.
377    pub fn line_height(&self) -> Pixels {
378        self.line_height
379    }
380
381    /// The input container bounds from the fresh prepaint.
382    pub fn input_bounds(&self) -> Bounds<Pixels> {
383        self.input_bounds
384    }
385
386    /// The scroll offset that will be in effect after paint, clamped like
387    /// `InputBaseState::update_scroll_offset` clamps it.
388    pub fn scroll_offset(&self) -> Point<Pixels> {
389        self.scroll_offset
390    }
391}
392
393/// The shared text-editing engine behind [`crate::input::InputState`],
394/// [`crate::input::TextareaState`] and [`crate::input::EditorState`].
395///
396/// `M` is the mode marker: it carries no data and only decides which methods
397/// exist, so an ordinary input cannot reach the editor's language features.
398///
399/// The three states are type aliases of this one, which is why this name is
400/// public: an alias is only as usable as the type behind it, so hiding this
401/// would leave `InputState` unable to do anything. Prefer naming the aliases
402/// — write `InputState`, not `InputBaseState<InputMode>`.
403pub struct InputBaseState<M: InputModeKind> {
404    /// State only this mode needs. See [`InputModeKind::Extras`].
405    pub(crate) extras: M::Extras,
406    pub(super) focus_handle: FocusHandle,
407    selection_focus: Option<WeakFocusHandle>,
408    pub(super) mode: LayoutMode,
409    pub(super) text: Rope,
410    pub(super) display_map: DisplayMap,
411    pub(super) undo_manager: UndoManager,
412    pub(super) inline_tokens: Option<Box<super::inline_tokens::InlineTokenStore>>,
413    pub(super) pending_token: Option<super::InlineToken>,
414    pub(super) replaying_history: bool,
415    pub(super) validated_token_edit: bool,
416    pub(super) document_revision: u64,
417    pub(super) token_presentation: super::InlineTokenPresentation,
418    pub(super) token_layout_cache: Option<Box<super::token_presentation::TokenLayoutCache>>,
419    /// Real per-frame bounds of the placed token elements, keyed by token
420    /// start offset; used for hover payloads and stale-hover reconciliation.
421    pub(super) token_bounds: std::collections::HashMap<usize, Bounds<Pixels>>,
422    /// The currently hovered token: its entered span, last measured bounds,
423    /// and UTF-16 range as of entry. Retained so hover exit can still be
424    /// delivered when the token is removed, replaced, scrolled out, or
425    /// disabled.
426    pub(super) hovered_token: Option<super::token_presentation::HoverSnapshot>,
427    pub(super) pending_token_hover_exits: Vec<super::token_presentation::HoverSnapshot>,
428    /// The start offset of a pressed token, with the document revision and
429    /// pointer position at the press.
430    pub(super) pressed_token: Option<(usize, u64, Point<Pixels>)>,
431    pub(super) search_session: super::SearchSession,
432    /// Advances every time search is explicitly invoked. See
433    /// [`InputBaseState::search_activation_revision`].
434    pub(super) search_activation_revision: u64,
435    pub(super) searchable: bool,
436    pub(super) replaceable: bool,
437    pub(super) soft_wrap: bool,
438    pub(super) wrapping_indent: WrappingIndent,
439    pub(super) scroll_beyond_last_line: Option<usize>,
440    pub(super) cursor_surrounding_lines: Option<usize>,
441    pub(super) blink_cursor: Entity<BlinkCursor>,
442    pub(super) loading: bool,
443    /// The cursors and selections.
444    ///
445    /// Always contains at least one selection where index 0 is the active cursor.
446    pub(super) selections: Selections,
447    /// Range for save the selected word, use to keep word range when drag move.
448    pub(super) selected_word_range: Option<CursorSelection>,
449    /// The marked range is the temporary insert text on IME typing.
450    pub(super) ime_marked_range: Option<CursorSelection>,
451    pub(super) last_layout: Option<LastLayout>,
452    /// Fresh caret geometry stashed during prepaint for caret-anchored popups.
453    ///
454    /// Popups are positioned in their deferred prepaint, which runs after the
455    /// input's prepaint but before its paint, so they read this instead of
456    /// the previous frame's `last_layout`.
457    pub(crate) prepaint_caret_geometry: Option<PrepaintCaretGeometry>,
458    pub(super) last_cursor: Option<usize>,
459    /// The input container bounds
460    pub(super) input_bounds: Bounds<Pixels>,
461    /// The text bounds
462    pub(super) last_bounds: Option<Bounds<Pixels>>,
463    pub(super) last_selected_range: Option<CursorSelection>,
464    pub(super) selecting: bool,
465    /// Anchor point of an in-progress columnar (block) selection.
466    pub(super) column_select_start: Option<ColumnarPoint>,
467    /// The selection a long press made, with its handles and edit menu.
468    pub(super) touch_selection: super::touch::TouchSelection,
469    pub(crate) disabled: bool,
470    pub(crate) readonly: bool,
471    pub(crate) text_align: TextAlign,
472    pub(super) masked: bool,
473    pub(super) clean_on_escape: bool,
474    pub(super) submit_on_enter: bool,
475    pub(super) show_whitespaces: bool,
476    /// This flag tells the renderer to prefer the end of the current visual line.
477    pub(crate) cursor_line_end_affinity: bool,
478    pub(super) pattern: Option<regex::Regex>,
479    pub(super) validate: Option<Box<dyn Fn(&str, &mut App) -> bool + 'static>>,
480    /// The step strategy for [`super::NumberInput`] to increment/decrement.
481    /// See [`Self::step`] and [`Self::step_by`].
482    pub(crate) number_step: Option<NumberStep>,
483    /// The minimum value for [`super::NumberInput`]. See [`Self::min`].
484    pub(crate) number_min: Option<f64>,
485    /// The maximum value for [`super::NumberInput`]. See [`Self::max`].
486    pub(crate) number_max: Option<f64>,
487    pub(crate) scroll_handle: ScrollHandle,
488    /// The deferred scroll offset to apply on next layout.
489    pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,
490    /// The size of the scrollable content.
491    pub(crate) scroll_size: gpui::Size<Pixels>,
492    pub(super) editor_scrollbar_snapshot: Cell<Option<EditorScrollbarSnapshot>>,
493    /// The unwrapped width of the longest line and what it was measured for.
494    pub(super) longest_line_width: Cell<Option<(LongestLineKey, Pixels)>>,
495    pub(super) editor_paddings: Edges<Pixels>,
496    /// The style this state paints with: what was projected onto it, with
497    /// every colour left unset resolved from the palette that is current. It
498    /// is rebuilt at the top of every render, which is what keeps it current
499    /// when the palette changes after the state was built.
500    pub(super) editor_style: InputEditorStyle,
501    /// What a consumer projected, kept verbatim so that resolution never
502    /// consumes its own output: resolving in place would fill the unset
503    /// colours once and then never see them as unset again, which is the same
504    /// freeze in a different place.
505    projected_editor_style: InputEditorStyle,
506
507    /// The mask pattern for formatting the input text
508    pub(crate) mask_pattern: MaskPattern,
509    /// Whether the `mask_pattern` was explicitly set (via [`Self::mask_pattern`]
510    /// or [`Self::set_mask_pattern`]), to let [`super::NumberInput`] only apply
511    /// its default mask when the user has not made an explicit choice.
512    pub(super) mask_pattern_set: bool,
513    pub(super) placeholder: SharedString,
514
515    /// Diagnostic currently requested by pointer hover; applications render it.
516    pub(super) diagnostic_popover: Option<Rc<crate::input::DiagnosticEntry>>,
517
518    context_menu_handler: Option<
519        Rc<dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App)>,
520    >,
521    pending_context_menu: Option<(Point<Pixels>, usize)>,
522
523    /// Whether the context menu that shows on right-click is enabled.
524    ///
525    pub(super) enable_context_menu: bool,
526
527    /// A flag to indicate if we are currently inserting a completion item.
528    pub(super) completion_inserting: bool,
529    pub(super) overlay_action_handler: Option<
530        Rc<
531            dyn Fn(
532                super::InputOverlayKind,
533                Box<dyn Action>,
534                &mut Window,
535                &mut Context<InputBaseState<M>>,
536            ) -> bool,
537        >,
538    >,
539
540    /// A flag to indicate if we have a pending update to the text.
541    ///
542    /// If true, will call some update (for example LSP, Syntax Highlight) before render.
543    _pending_update: bool,
544    /// A flag to indicate if we should ignore the next completion event.
545    pub(super) silent_replace_text: bool,
546    /// A flag to indicate if we should emit InputEvents.
547    pub(super) emit_events: bool,
548
549    _subscriptions: Vec<Subscription>,
550
551    pub(super) auto_scroll: AutoScroll,
552}
553
554/// Read-only styling data exposed to presentation facades.
555///
556/// The fields are private and read through the methods below, so that a new
557/// one can be added without breaking the facades.
558#[derive(Clone)]
559pub struct InputPresentation {
560    focus_handle: FocusHandle,
561    disabled: bool,
562    readonly: bool,
563    loading: bool,
564    masked: bool,
565    multi_line: bool,
566    code_editor: bool,
567    text_align: TextAlign,
568    placeholder: SharedString,
569    mask_placeholder: Option<String>,
570}
571
572impl InputPresentation {
573    pub fn focus_handle(&self) -> &FocusHandle {
574        &self.focus_handle
575    }
576
577    pub fn is_disabled(&self) -> bool {
578        self.disabled
579    }
580
581    pub fn is_readonly(&self) -> bool {
582        self.readonly
583    }
584
585    /// Returns true if the user is allowed to change the text.
586    ///
587    /// See also: [`InputBaseState::is_editable`].
588    pub fn is_editable(&self) -> bool {
589        !self.disabled && !self.readonly
590    }
591
592    pub fn is_loading(&self) -> bool {
593        self.loading
594    }
595
596    pub fn is_masked(&self) -> bool {
597        self.masked
598    }
599
600    pub fn is_multi_line(&self) -> bool {
601        self.multi_line
602    }
603
604    pub fn is_code_editor(&self) -> bool {
605        self.code_editor
606    }
607
608    pub fn text_align(&self) -> TextAlign {
609        self.text_align
610    }
611
612    pub fn placeholder(&self) -> &SharedString {
613        &self.placeholder
614    }
615
616    /// The placeholder derived from the mask pattern, e.g.: `(___) ___-____`.
617    pub fn mask_placeholder(&self) -> Option<&str> {
618        self.mask_placeholder.as_deref()
619    }
620}
621
622impl<M: InputModeKind> EventEmitter<InputEvent> for InputBaseState<M> {}
623
624impl<M: InputModeKind> InputBaseState<M> {
625    #[doc(hidden)]
626    pub fn cursor_layout(&self) -> Option<(Bounds<Pixels>, Pixels)> {
627        let layout = self.last_layout.as_ref()?;
628        Some((layout.cursor_bounds?, layout.line_height))
629    }
630
631    /// Caret geometry from the latest prepaint, for caret-anchored popups.
632    ///
633    /// Popups positioned in their deferred prepaint read this to anchor to
634    /// the current frame's caret instead of the previous frame's paint.
635    #[doc(hidden)]
636    pub fn prepaint_caret_geometry(&self) -> Option<PrepaintCaretGeometry> {
637        self.prepaint_caret_geometry
638    }
639
640    pub fn input_bounds(&self) -> Bounds<Pixels> {
641        self.input_bounds
642    }
643
644    pub fn text_bounds(&self) -> Option<Bounds<Pixels>> {
645        self.last_bounds
646    }
647
648    pub fn diagnostic_popover(&self) -> Option<Rc<crate::input::DiagnosticEntry>> {
649        self.diagnostic_popover.clone()
650    }
651
652    pub fn presentation(&self) -> InputPresentation {
653        InputPresentation {
654            focus_handle: self.focus_handle.clone(),
655            disabled: self.disabled,
656            readonly: self.readonly,
657            loading: self.loading,
658            masked: self.masked,
659            multi_line: self.is_multi_line(),
660            code_editor: self.is_code_editor(),
661            text_align: self.text_align,
662            placeholder: self.placeholder.clone(),
663            mask_placeholder: self.mask_pattern.placeholder(),
664        }
665    }
666
667    /// Whether this input spans more than one line.
668    ///
669    /// Answered by the mode marker, which is fixed when the state is built.
670    /// [`LayoutMode`] holds the row counts and growth policy, not the kind.
671    #[inline]
672    /// Whether this input paints scrollbars.
673    ///
674    /// Only a multi-line input can scroll: a single-line input keeps its
675    /// caret in view by moving its own offset, and never has a viewport a
676    /// user could drag. Adding the editor scrollbar to every input put a
677    /// thumb inside every text field, which is a control the field does not
678    /// have.
679    pub(crate) fn shows_scrollbar(&self) -> bool {
680        self.is_multi_line()
681    }
682
683    pub fn is_multi_line(&self) -> bool {
684        M::MULTI_LINE
685    }
686
687    /// Whether this input is a single-line text field. See [`Self::is_multi_line`].
688    #[inline]
689    pub fn is_single_line(&self) -> bool {
690        !M::MULTI_LINE
691    }
692
693    /// Whether this input is a source-code editor.
694    #[inline]
695    pub fn is_code_editor(&self) -> bool {
696        M::CODE_EDITOR
697    }
698
699    /// Whether the user is allowed to copy the selection out.
700    ///
701    /// A masked input keeps its value out of the clipboard.
702    pub fn is_copyable(&self) -> bool {
703        self.selections.iter().any(|sel| !sel.is_empty()) && !self.masked
704    }
705
706    pub fn context_menu_capabilities(&self) -> InputContextMenuCapabilities {
707        let (go_to_definition, code_actions) = self.extras.context_menu_capabilities();
708        InputContextMenuCapabilities::new()
709            .disabled(self.disabled)
710            .readonly(self.readonly)
711            .code_editor(self.is_code_editor())
712            .selection(!self.active_selection().is_empty())
713            .masked(self.masked)
714            .go_to_definition(go_to_definition)
715            .code_actions(code_actions)
716    }
717
718    pub fn set_text_align(&mut self, text_align: TextAlign, cx: &mut Context<Self>) {
719        if !self.is_single_line() || self.text_align == text_align {
720            return;
721        }
722
723        self.text_align = text_align;
724        cx.notify();
725    }
726
727    /// Flip the password mask.
728    ///
729    /// Setting the mask is a single-line method, but flipping it stays here:
730    /// the reveal button is rendered from the generic path, and it can only be
731    /// switched on through [`crate::input::InputState`] anyway.
732    pub fn toggle_masked(&mut self, _: &mut Window, cx: &mut Context<Self>) {
733        self.masked = !self.masked;
734        cx.notify();
735    }
736
737    pub fn on_context_menu(
738        &mut self,
739        handler: Rc<
740            dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App),
741        >,
742    ) {
743        self.context_menu_handler = Some(handler);
744    }
745
746    /// Keeps the selection highlighted while this popup or one of its children has focus.
747    ///
748    /// This does not transfer keyboard focus or keep the caret visible. The
749    /// association holds only a weak focus handle. Pass `None` to clear it.
750    pub fn set_selection_focus(&mut self, focus: Option<FocusHandle>, cx: &mut Context<Self>) {
751        self.selection_focus = focus.as_ref().map(FocusHandle::downgrade);
752        cx.notify();
753    }
754
755    /// Whether the input or its associated selection popup has focus.
756    pub fn has_selection_focus(&self, window: &Window, cx: &App) -> bool {
757        self.focus_handle.is_focused(window)
758            || self
759                .selection_focus
760                .as_ref()
761                .and_then(WeakFocusHandle::upgrade)
762                .is_some_and(|focus| focus.is_focused(window) || focus.contains_focused(window, cx))
763    }
764
765    /// Build the engine. Each mode's own `new` sets its layout on top of this.
766    fn new_in_mode(window: &mut Window, cx: &mut Context<Self>) -> Self {
767        let focus_handle = cx.focus_handle().tab_stop(true);
768        let blink_cursor = cx.new(|_| BlinkCursor::new());
769        let undo_manager = UndoManager::new();
770
771        let _subscriptions = vec![
772            // Key bindings can consume events before on_key_down. Observe input
773            // before action dispatch so every keystroke resets the blink delay.
774            cx.intercept_keystrokes({
775                let focus_handle = focus_handle.clone();
776                let blink_cursor = blink_cursor.downgrade();
777                move |_, window, cx| {
778                    if focus_handle.is_focused(window) {
779                        _ = blink_cursor.update(cx, |cursor, cx| cursor.pause(cx));
780                    }
781                }
782            }),
783            // Observe the blink cursor to repaint the view when it changes.
784            cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
785            // Blink the cursor when the window is active, pause when it's not.
786            cx.observe_window_activation(window, |input, window, cx| {
787                if window.is_window_active() {
788                    let focus_handle = input.focus_handle.clone();
789                    if focus_handle.is_focused(window) {
790                        input.blink_cursor.update(cx, |blink_cursor, cx| {
791                            blink_cursor.start(cx);
792                        });
793                    }
794                }
795            }),
796            cx.on_focus(&focus_handle, window, Self::on_focus),
797            cx.on_blur(&focus_handle, window, Self::on_blur),
798        ];
799
800        let text_style = window.text_style();
801
802        Self {
803            extras: M::Extras::default(),
804            focus_handle: focus_handle.clone(),
805            selection_focus: None,
806            text: "".into(),
807            display_map: DisplayMap::new(text_style.font(), window.rem_size(), None),
808            search_session: super::SearchSession::default(),
809            search_activation_revision: 0,
810            searchable: false,
811            replaceable: true,
812            soft_wrap: true,
813            wrapping_indent: WrappingIndent::default(),
814            scroll_beyond_last_line: None,
815            cursor_surrounding_lines: None,
816            blink_cursor,
817            undo_manager,
818            inline_tokens: None,
819            pending_token: None,
820            replaying_history: false,
821            validated_token_edit: false,
822            document_revision: 0,
823            token_presentation: Default::default(),
824            token_bounds: Default::default(),
825            hovered_token: None,
826            pending_token_hover_exits: Vec::new(),
827            token_layout_cache: None,
828            pressed_token: None,
829            selections: Selections::default(),
830            selected_word_range: None,
831            ime_marked_range: None,
832            input_bounds: Bounds::default(),
833            selecting: false,
834            touch_selection: Default::default(),
835            disabled: false,
836            readonly: false,
837            text_align: TextAlign::Left,
838            masked: false,
839            clean_on_escape: false,
840            submit_on_enter: false,
841            show_whitespaces: false,
842            loading: false,
843            pattern: None,
844            validate: None,
845            number_step: Some(NumberStep::Fixed(1.)),
846            number_min: None,
847            number_max: None,
848            mode: LayoutMode::default(),
849            last_layout: None,
850            prepaint_caret_geometry: None,
851            last_bounds: None,
852            last_selected_range: None,
853            column_select_start: None,
854            last_cursor: None,
855            scroll_handle: ScrollHandle::new(),
856            scroll_size: gpui::size(px(0.), px(0.)),
857            editor_scrollbar_snapshot: Cell::new(None),
858            longest_line_width: Cell::new(None),
859            editor_paddings: Edges::default(),
860            deferred_scroll_offset: None,
861            placeholder: SharedString::default(),
862            mask_pattern: MaskPattern::default(),
863            mask_pattern_set: false,
864            editor_style: InputEditorStyle::default(),
865            projected_editor_style: InputEditorStyle::default(),
866            diagnostic_popover: None,
867            context_menu_handler: None,
868            pending_context_menu: None,
869            enable_context_menu: true,
870            completion_inserting: false,
871            overlay_action_handler: None,
872            silent_replace_text: false,
873            emit_events: true,
874            _subscriptions,
875            _pending_update: false,
876            cursor_line_end_affinity: false,
877            auto_scroll: AutoScroll::default(),
878        }
879    }
880
881    /// Sets whether the context menu that shows on right-click is enabled.
882    ///
883    /// The context menu is enabled by default. Disabling it turns off the
884    /// right-click menu entirely, including a custom menu set on the element.
885    /// A custom menu already replaces the built-in one, so keep this enabled
886    /// to show only your own items.
887    pub fn context_menu(mut self, enable: bool) -> Self {
888        self.enable_context_menu = enable;
889        self
890    }
891
892    pub fn set_context_menu_enabled(&mut self, enabled: bool) {
893        self.enable_context_menu = enabled;
894    }
895
896    /// Set whether search UI allows replacement, default is true.
897    #[doc(hidden)]
898    pub fn replaceable(mut self, allow: bool) -> Self {
899        self.replaceable = allow;
900        self
901    }
902
903    /// Set placeholder
904    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
905        self.placeholder = placeholder.into();
906        self
907    }
908
909    /// Set highlighter language for for [`LayoutMode::CodeEditor`] mode.
910    pub fn set_highlighter(
911        &mut self,
912        new_language: impl Into<SharedString>,
913        cx: &mut Context<Self>,
914    ) {
915        match &mut self.mode {
916            LayoutMode::CodeEditor {
917                language,
918                highlighter,
919                ..
920            } => {
921                language.set_name(new_language.into());
922                *highlighter.borrow_mut() = None;
923            }
924            _ => {}
925        }
926        self.refresh(cx);
927    }
928
929    fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
930        match &mut self.mode {
931            LayoutMode::CodeEditor { highlighter, .. } => {
932                *highlighter.borrow_mut() = None;
933            }
934            _ => {}
935        }
936        cx.notify();
937    }
938
939    /// Install the parser/highlighter adapter used by code-editor mode.
940    pub fn set_highlighter_factory(
941        &mut self,
942        factory: InputHighlighterFactory,
943        cx: &mut Context<Self>,
944    ) {
945        self.mode.set_highlighter_factory(factory);
946        self._pending_update = true;
947        self.token_layout_cache = None;
948        cx.notify();
949    }
950
951    /// Install a default adapter without replacing an application-provided one.
952    pub fn ensure_highlighter_factory(&mut self, factory: InputHighlighterFactory) {
953        self.mode.ensure_highlighter_factory(factory);
954    }
955
956    pub fn set_editor_style(&mut self, style: InputEditorStyle) {
957        self.editor_style = style.clone();
958        self.projected_editor_style = style;
959    }
960
961    /// Set presentation padding for multi-line text and its scrollbar layout.
962    #[doc(hidden)]
963    pub fn set_editor_paddings(&mut self, paddings: Edges<Pixels>) {
964        self.editor_paddings = paddings;
965    }
966
967    pub fn apply_highlighter_fold_candidates(
968        &mut self,
969        candidates: Vec<crate::input::FoldRange>,
970        cx: &mut Context<Self>,
971    ) {
972        if self.mode.is_folding() {
973            self.display_map.set_fold_candidates(candidates);
974        }
975        cx.notify();
976    }
977
978    #[inline]
979    pub fn diagnostics(&self) -> Option<&DiagnosticSet> {
980        self.mode.diagnostics()
981    }
982
983    #[inline]
984    pub fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
985        self.mode.diagnostics_mut()
986    }
987
988    /// Set placeholder
989    pub fn set_placeholder(
990        &mut self,
991        placeholder: impl Into<SharedString>,
992        _: &mut Window,
993        cx: &mut Context<Self>,
994    ) {
995        self.placeholder = placeholder.into();
996        cx.notify();
997    }
998
999    /// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.
1000    ///
1001    /// Returns:
1002    ///
1003    /// - The index of the line (zero-based) containing the offset.
1004    /// - The index of the sub-line (zero-based) within the line containing the offset.
1005    /// - The position of the offset.
1006    pub(super) fn line_and_position_for_offset(
1007        &self,
1008        offset: usize,
1009    ) -> (usize, usize, Option<Point<Pixels>>) {
1010        let Some(last_layout) = &self.last_layout else {
1011            return (0, 0, None);
1012        };
1013        let line_height = last_layout.line_height;
1014
1015        let mut y_offset = last_layout.visible_top;
1016        for (vi, line) in last_layout.lines.iter().enumerate() {
1017            let prev_lines_offset = last_layout.visible_line_byte_offsets[vi];
1018            let local_offset = offset.saturating_sub(prev_lines_offset);
1019            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
1020                let sub_line_index = (pos.y / line_height) as usize;
1021                let adjusted_pos = point(pos.x + last_layout.line_number_width, pos.y + y_offset);
1022                return (vi, sub_line_index, Some(adjusted_pos));
1023            }
1024
1025            y_offset += line.size(line_height).height;
1026        }
1027        (0, 0, None)
1028    }
1029
1030    /// Set the value of the input field: plain text, or [`InputContent`] to
1031    /// restore text together with its inline tokens. Editing history is
1032    /// cleared and no [`InputEvent::Change`] is emitted.
1033    ///
1034    /// For single-line inputs the caret is placed at the end of the text while
1035    /// the view is scrolled back to the start, so a long value shows its
1036    /// beginning instead of its tail (matching HTML `<input>`). Multi-line
1037    /// inputs reset the selection to `0..0`.
1038    pub fn set_value(
1039        &mut self,
1040        value: impl Into<super::InputContent>,
1041        window: &mut Window,
1042        cx: &mut Context<Self>,
1043    ) {
1044        let content = value.into();
1045        self.inline_tokens = None;
1046        self.undo_manager.set_ignoring(true);
1047        self.emit_events = false;
1048        self.replace_text(content.text().clone(), window, cx);
1049        self.install_tokens(content);
1050        self.undo_manager.set_ignoring(false);
1051        self.emit_events = true;
1052
1053        self.reset_selection();
1054        self.reset_lsp_state();
1055        self.reset_scroll_to_start();
1056
1057        self.undo_manager.clear();
1058        cx.notify();
1059    }
1060
1061    /// Replace the entire text content while preserving undo history.
1062    ///
1063    /// Unlike [`set_value`](Self::set_value), this method records the
1064    /// replacement in the undo stack, allowing the user to undo/redo
1065    /// the change. The selection is placed at the end of the new text
1066    /// for single-line inputs, or cleared (0..0) for multi-line inputs.
1067    ///
1068    /// Use this when programmatically replacing the full text but the
1069    /// user should still be able to undo the operation — e.g. formatting.
1070    pub fn replace_all(
1071        &mut self,
1072        text: impl Into<SharedString>,
1073        window: &mut Window,
1074        cx: &mut Context<Self>,
1075    ) {
1076        self.replace_text(text, window, cx);
1077        self.reset_selection();
1078        self.reset_lsp_state();
1079        self.reset_scroll_to_start();
1080
1081        cx.notify();
1082    }
1083
1084    /// Perform `f` with the user-facing edit restrictions lifted.
1085    ///
1086    /// The `disabled` and `readonly` modes only reject the changes made by the
1087    /// user, the programmatic APIs must always be able to update the text.
1088    pub(super) fn with_edits_allowed(&mut self, f: impl FnOnce(&mut Self)) {
1089        let (was_disabled, was_readonly) = (self.disabled, self.readonly);
1090        (self.disabled, self.readonly) = (false, false);
1091        f(self);
1092        (self.disabled, self.readonly) = (was_disabled, was_readonly);
1093    }
1094
1095    /// Insert text at the current cursor position.
1096    ///
1097    /// And the cursor will be moved to the end of inserted text.
1098    pub fn insert(
1099        &mut self,
1100        text: impl Into<SharedString>,
1101        window: &mut Window,
1102        cx: &mut Context<Self>,
1103    ) {
1104        let text: SharedString = text.into();
1105        self.with_edits_allowed(|this| {
1106            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1107            let range_utf16 = this.range_to_utf16(&(this.cursor()..this.cursor()));
1108            this.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
1109            let end = this.active_selection().end;
1110            this.set_cursor_to(end);
1111        });
1112    }
1113
1114    /// Replace text at the current cursor position.
1115    ///
1116    /// And the cursor will be moved to the end of replaced text.
1117    pub fn replace(
1118        &mut self,
1119        text: impl Into<SharedString>,
1120        window: &mut Window,
1121        cx: &mut Context<Self>,
1122    ) {
1123        let text: SharedString = text.into();
1124        self.with_edits_allowed(|this| {
1125            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1126            this.replace_text_in_range_silent(None, &text, window, cx);
1127            let end = this.active_selection().end;
1128            this.set_cursor_to(end);
1129        });
1130    }
1131
1132    fn replace_text(
1133        &mut self,
1134        text: impl Into<SharedString>,
1135        window: &mut Window,
1136        cx: &mut Context<Self>,
1137    ) {
1138        let text: SharedString = text.into();
1139        self.with_edits_allowed(|this| {
1140            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1141            let range = 0..this.text.chars().map(|c| c.len_utf16()).sum();
1142            this.replace_text_in_range_silent(Some(range), &text, window, cx);
1143            this.reset_highlighter(cx);
1144        });
1145    }
1146
1147    fn reset_selection(&mut self) {
1148        self.selections.remove_all_but_active();
1149
1150        // For single-line inputs the caret is placed at the end of the text
1151        // (matching HTML `<input>`); multi-line inputs reset the selection to
1152        // `0..0`.
1153        if self.is_single_line() {
1154            let end = self.text.len();
1155            self.set_cursor_to(end);
1156        } else {
1157            self.active_selection_mut().clear();
1158        }
1159    }
1160
1161    fn reset_lsp_state(&mut self) {
1162        if self.is_code_editor() {
1163            self._pending_update = true;
1164            M::reset_language_features(self);
1165        }
1166    }
1167
1168    fn reset_scroll_to_start(&mut self) {
1169        // Move scroll to the start. For single-line the caret is at the end, so
1170        // override the cursor-follow scroll for the next painted frame to keep
1171        // the start visible; the deferred offset is consumed during that paint.
1172        self.scroll_handle.set_offset(point(px(0.), px(0.)));
1173        if self.is_single_line() {
1174            self.deferred_scroll_offset = Some(point(px(0.), px(0.)));
1175        }
1176    }
1177
1178    /// Set with disabled mode.
1179    ///
1180    /// See also: [`Self::set_disabled`].
1181    #[allow(unused)]
1182    pub(crate) fn disabled(mut self, disabled: bool) -> Self {
1183        self.disabled = disabled;
1184        self
1185    }
1186
1187    pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
1188        if self.disabled == disabled {
1189            return;
1190        }
1191
1192        self.disabled = disabled;
1193        if disabled {
1194            M::hide_context_menu(self, cx);
1195            M::clear_inline_completion(self, cx);
1196        }
1197        cx.notify();
1198    }
1199
1200    /// Set with read-only mode.
1201    ///
1202    /// Unlike [`Self::disabled`], a read-only input keeps the normal appearance,
1203    /// focus, cursor, selection and copy behavior, it only rejects any change
1204    /// of the text made by the user.
1205    ///
1206    /// See also: [`Self::set_readonly`].
1207    #[allow(unused)]
1208    pub(crate) fn readonly(mut self, readonly: bool) -> Self {
1209        self.readonly = readonly;
1210        self
1211    }
1212
1213    pub fn set_readonly(&mut self, readonly: bool, cx: &mut Context<Self>) {
1214        if self.readonly == readonly {
1215            return;
1216        }
1217
1218        self.readonly = readonly;
1219        if readonly {
1220            M::hide_context_menu(self, cx);
1221            M::clear_inline_completion(self, cx);
1222            self.search_session.replace_mode = false;
1223        }
1224        cx.notify();
1225    }
1226
1227    /// Returns true if the user is allowed to change the text.
1228    ///
1229    /// This is false when the input is `disabled` or `readonly`, the programmatic
1230    /// APIs (e.g.: [`Self::set_value`], [`Self::insert`]) are not limited by this.
1231    pub fn is_editable(&self) -> bool {
1232        !self.disabled && !self.readonly
1233    }
1234
1235    /// Set true to clear the input by pressing Escape key.
1236    pub fn clean_on_escape(mut self) -> Self {
1237        self.clean_on_escape = true;
1238        self
1239    }
1240
1241    pub fn set_clean_on_escape(&mut self, clean: bool) {
1242        self.clean_on_escape = clean;
1243    }
1244
1245    /// Set true to treat `Enter` as a submit action in multi-line mode,
1246    /// while `Shift+Enter` inserts a newline.
1247    ///
1248    /// Default is `false` (both `Enter` and `Shift+Enter` insert a newline).
1249    #[doc(hidden)]
1250    pub fn submit_on_enter(mut self, submit: bool) -> Self {
1251        self.submit_on_enter = submit;
1252        self
1253    }
1254
1255    pub fn set_submit_on_enter(&mut self, submit: bool, cx: &mut Context<Self>) {
1256        self.submit_on_enter = submit;
1257        cx.notify();
1258    }
1259
1260    /// Set whether to show whitespace characters.
1261    #[doc(hidden)]
1262    pub fn show_whitespaces(mut self, show: bool) -> Self {
1263        self.show_whitespaces = show;
1264        self
1265    }
1266
1267    /// Update whether to show whitespace characters.
1268    pub fn set_show_whitespaces(&mut self, show: bool, _: &mut Window, cx: &mut Context<Self>) {
1269        self.show_whitespaces = show;
1270        cx.notify();
1271    }
1272
1273    /// Empty rows reserved below the last line of content ("scroll
1274    /// beyond last line"), code-editor mode only. Mirrors VSCode's
1275    /// `editor.scrollBeyondLastLine` / Zed's `scroll_beyond_last_line`.
1276    ///
1277    /// - `None` (default): half the viewport, floored at
1278    ///   [`BOTTOM_MARGIN_ROWS`] line-heights.
1279    /// - `Some(0)`: no trailing space; the cursor sits flush with the
1280    ///   last row at scroll-max.
1281    /// - `Some(n)`: exactly `n` rows.
1282    pub fn scroll_beyond_last_line(mut self, rows: Option<usize>) -> Self {
1283        self.scroll_beyond_last_line = rows;
1284        self
1285    }
1286
1287    /// Update [`Self::scroll_beyond_last_line`] after construction.
1288    pub fn set_scroll_beyond_last_line(
1289        &mut self,
1290        rows: Option<usize>,
1291        _: &mut Window,
1292        cx: &mut Context<Self>,
1293    ) {
1294        if self.scroll_beyond_last_line == rows {
1295            return;
1296        }
1297        self.scroll_beyond_last_line = rows;
1298        cx.notify();
1299    }
1300
1301    /// Minimum number of lines the cursor is kept clear of the viewport's
1302    /// top/bottom edge before auto-scroll engages. Mirrors VSCode's
1303    /// `editor.cursorSurroundingLines` / Zed's `vertical_scroll_margin`.
1304    /// Orthogonal to [`Self::scroll_beyond_last_line`], which sizes the
1305    /// empty region; this controls the cursor's resting distance from the
1306    /// edge.
1307    ///
1308    /// - `None` (default): [`BOTTOM_MARGIN_ROWS`] lines, falling back to
1309    ///   one line on small viewports.
1310    /// - `Some(n)`: exactly `n` lines, clamped to half the viewport.
1311    pub fn cursor_surrounding_lines(mut self, lines: Option<usize>) -> Self {
1312        self.cursor_surrounding_lines = lines;
1313        self
1314    }
1315
1316    /// Update [`Self::cursor_surrounding_lines`] after construction.
1317    pub fn set_cursor_surrounding_lines(
1318        &mut self,
1319        lines: Option<usize>,
1320        _: &mut Window,
1321        cx: &mut Context<Self>,
1322    ) {
1323        if self.cursor_surrounding_lines == lines {
1324            return;
1325        }
1326        self.cursor_surrounding_lines = lines;
1327        cx.notify();
1328    }
1329
1330    /// Set the default value of the input field.
1331    pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {
1332        let text: SharedString = value.into();
1333        self.text = Rope::from(self.normalize_input(&text).as_ref());
1334        if let Some(diagnostics) = self.mode.diagnostics_mut() {
1335            diagnostics.reset(&self.text)
1336        }
1337        // Note: We can't call display_map.set_text here because it needs cx.
1338        // The text will be set during prepare_if_need in element.rs
1339        self._pending_update = true;
1340        self
1341    }
1342
1343    /// Return the value of the input field as an owned string.
1344    ///
1345    /// The string is materialized on each call. See [`Self::text`] for the
1346    /// [`Rope`] the state owns, which is borrowed and costs nothing to read.
1347    pub fn value(&self) -> SharedString {
1348        SharedString::new(self.text.to_string())
1349    }
1350
1351    /// Return the portion of the value within the input field that
1352    /// is selected by the user, as an owned string.
1353    ///
1354    /// The string is materialized on each call. See [`Self::selected_text`]
1355    /// for the same selection borrowed out of the [`Rope`] the state owns.
1356    pub fn selected_value(&self) -> SharedString {
1357        SharedString::new(self.selected_text().to_string())
1358    }
1359
1360    /// Return the value without mask.
1361    pub fn unmask_value(&self) -> SharedString {
1362        self.mask_pattern.unmask(&self.text.to_string()).into()
1363    }
1364
1365    /// Kept so existing render paths keep compiling.
1366    ///
1367    /// Configuration used to be collected by a facade and applied here; the
1368    /// state now configures itself, so this does nothing and can be deleted at
1369    /// the call site.
1370    #[doc(hidden)]
1371    pub fn prepare(&mut self, _: &mut Window, _: &mut Context<Self>) {}
1372
1373    /// Return the text [`Rope`] of the input field.
1374    ///
1375    /// Borrowed from the state, so reading even a large document copies
1376    /// nothing. See [`Self::value`] when an owned string is wanted.
1377    pub fn text(&self) -> &Rope {
1378        &self.text
1379    }
1380
1381    /// Return the (0-based) [`Position`] of the cursor.
1382    pub fn cursor_position(&self) -> Position {
1383        let offset = self.cursor();
1384        self.text.offset_to_position(offset)
1385    }
1386
1387    /// Set (0-based) [`Position`] of the cursor.
1388    ///
1389    /// This will move the cursor to the specified line and column, and update the selection range.
1390    pub fn set_cursor_position(
1391        &mut self,
1392        position: impl Into<Position>,
1393        window: &mut Window,
1394        cx: &mut Context<Self>,
1395    ) {
1396        let position: Position = position.into();
1397        let offset = self.text.position_to_offset(&position);
1398
1399        self.move_to(offset, None, cx);
1400        self.update_preferred_column();
1401        self.focus(window, cx);
1402    }
1403
1404    /// Focus the input field.
1405    pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1406        self.focus_handle.focus(window, cx);
1407        self.blink_cursor.update(cx, |cursor, cx| {
1408            cursor.start(cx);
1409        });
1410    }
1411
1412    /// Refresh the input, so the next render re-runs syntax highlighting and
1413    /// the LSP providers, not just a redraw.
1414    ///
1415    /// Assigning the `lsp` providers (or other render-affecting state) at
1416    /// runtime does not take effect until the text next changes. Call this
1417    /// afterwards to force the refresh on the next render.
1418    ///
1419    /// ```ignore
1420    /// input.update(cx, |state, cx| {
1421    ///     state.extras.lsp.hover_provider = Some(provider);
1422    ///     state.refresh(cx);
1423    /// });
1424    /// ```
1425    pub fn refresh(&mut self, cx: &mut Context<Self>) {
1426        self._pending_update = true;
1427        cx.notify();
1428    }
1429
1430    pub(super) fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
1431        self.undo_manager.break_transaction_coalescing();
1432        self.select_all_cursors_to(|s, sel| s.previous_boundary(sel.cursor_offset()), cx);
1433    }
1434
1435    pub(super) fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
1436        self.select_all_cursors_to(|s, sel| s.next_boundary(sel.cursor_offset()), cx);
1437    }
1438
1439    pub(super) fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
1440        if self.is_single_line() {
1441            return;
1442        }
1443        self.select_all_cursors_to_with_affinity(
1444            |s, sel| {
1445                s.vertical_selection_target(
1446                    sel.cursor_offset(),
1447                    sel.column_anchor,
1448                    s.line_end_affinity_for(sel),
1449                    -1,
1450                )
1451            },
1452            true,
1453            cx,
1454        );
1455    }
1456
1457    pub(super) fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
1458        if self.is_single_line() {
1459            return;
1460        }
1461        self.select_all_cursors_to_with_affinity(
1462            |s, sel| {
1463                s.vertical_selection_target(
1464                    sel.cursor_offset(),
1465                    sel.column_anchor,
1466                    s.line_end_affinity_for(sel),
1467                    1,
1468                )
1469            },
1470            true,
1471            cx,
1472        );
1473    }
1474
1475    pub(super) fn on_action_select_all(
1476        &mut self,
1477        _: &SelectAll,
1478        window: &mut Window,
1479        cx: &mut Context<Self>,
1480    ) {
1481        self.select_all(window, cx);
1482    }
1483
1484    pub(super) fn select_to_start(
1485        &mut self,
1486        _: &SelectToStart,
1487        _: &mut Window,
1488        cx: &mut Context<Self>,
1489    ) {
1490        self.undo_manager.break_transaction_coalescing();
1491        self.select_all_cursors_to(|_, _| 0, cx);
1492    }
1493
1494    pub(super) fn select_to_end(
1495        &mut self,
1496        _: &SelectToEnd,
1497        _: &mut Window,
1498        cx: &mut Context<Self>,
1499    ) {
1500        self.undo_manager.break_transaction_coalescing();
1501        let end = self.text.len();
1502        self.select_all_cursors_to(move |_, _| end, cx);
1503    }
1504
1505    pub(super) fn select_to_start_of_line(
1506        &mut self,
1507        _: &SelectToStartOfLine,
1508        _: &mut Window,
1509        cx: &mut Context<Self>,
1510    ) {
1511        self.undo_manager.break_transaction_coalescing();
1512        self.select_all_cursors_to(
1513            |s, sel| s.start_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1514            cx,
1515        );
1516    }
1517
1518    pub(super) fn select_to_end_of_line(
1519        &mut self,
1520        _: &SelectToEndOfLine,
1521        _: &mut Window,
1522        cx: &mut Context<Self>,
1523    ) {
1524        self.undo_manager.break_transaction_coalescing();
1525        self.select_all_cursors_to_with_affinity(
1526            |s, sel| {
1527                (
1528                    s.end_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1529                    true,
1530                )
1531            },
1532            false,
1533            cx,
1534        );
1535    }
1536
1537    pub(super) fn select_to_previous_word(
1538        &mut self,
1539        _: &SelectToPreviousWordStart,
1540        _: &mut Window,
1541        cx: &mut Context<Self>,
1542    ) {
1543        self.undo_manager.break_transaction_coalescing();
1544        self.select_all_cursors_to(
1545            |s, sel| s.previous_start_of_word_at(sel.cursor_offset()),
1546            cx,
1547        );
1548    }
1549
1550    pub(super) fn select_to_next_word(
1551        &mut self,
1552        _: &SelectToNextWordEnd,
1553        _: &mut Window,
1554        cx: &mut Context<Self>,
1555    ) {
1556        self.undo_manager.break_transaction_coalescing();
1557        self.select_all_cursors_to(|s, sel| s.next_end_of_word_at(sel.cursor_offset()), cx);
1558    }
1559
1560    /// Return the start offset of the previous word.
1561    /// Return the previous start offset of the word before `offset`.
1562    pub(super) fn previous_start_of_word_at(&self, offset: usize) -> usize {
1563        if self.masked {
1564            // The mask replaces every character, so the displayed text has no
1565            // word boundaries to move or delete by. Collapse the word to the
1566            // whole text.
1567            return 0;
1568        }
1569
1570        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1571        // FIXME: Avoid to_string
1572        let left_part = self.text.slice(0..offset).to_string();
1573
1574        let target = UnicodeSegmentation::split_word_bound_indices(left_part.as_str())
1575            .rfind(|(_, s)| !s.trim_start().is_empty())
1576            .map(|(i, _)| i)
1577            .unwrap_or(0);
1578        self.token_boundary(target, Bias::Left)
1579    }
1580
1581    /// Return the next end offset of the word after `offset`.
1582    pub(super) fn next_end_of_word_at(&self, offset: usize) -> usize {
1583        if self.masked {
1584            // See `previous_start_of_word_at`.
1585            return self.text.len();
1586        }
1587
1588        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1589        let right_part = self.text.slice(offset..self.text.len()).to_string();
1590
1591        let target = UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
1592            .find(|(_, s)| !s.trim_start().is_empty())
1593            .map(|(i, s)| offset + i + s.len())
1594            .unwrap_or(self.text.len());
1595        self.token_boundary(target, Bias::Right)
1596    }
1597
1598    /// Get start of line byte offset for the given `offset`.
1599    ///
1600    /// When soft wrap is active, first press goes to visual line start,
1601    /// second press (already at visual start) goes to logical line start.
1602    pub(super) fn start_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1603        if self.is_single_line() {
1604            return 0;
1605        }
1606
1607        let row = self.text.offset_to_point(offset).row;
1608        let logical_start = self.text.line_start_offset(row);
1609
1610        if self.soft_wrap && self.is_code_editor() {
1611            let wrap_point = self
1612                .display_map
1613                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1614            if let Some(line) = self.display_map.line(row)
1615                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1616            {
1617                let visual_start = logical_start + range.start;
1618                if offset != visual_start {
1619                    return visual_start;
1620                }
1621            }
1622        }
1623
1624        logical_start
1625    }
1626
1627    /// Get end of line byte offset for the given `offset`.
1628    ///
1629    /// When soft wrap is active, first press goes to visual line end,
1630    /// second press (already at visual end) goes to logical line end.
1631    pub(super) fn end_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1632        if self.is_single_line() {
1633            return self.text.len();
1634        }
1635
1636        let row = self.text.offset_to_point(offset).row;
1637        let logical_start = self.text.line_start_offset(row);
1638        let logical_end = self.text.line_end_offset(row);
1639
1640        if self.soft_wrap && self.is_code_editor() {
1641            // Use the row the caret is drawn on: at a wrap boundary the raw offset would name
1642            // the next row, and a second End press would keep walking down instead of falling
1643            // through to the logical line end.
1644            let wrap_point = self
1645                .display_map
1646                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1647            if let Some(line) = self.display_map.line(row)
1648                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1649            {
1650                let visual_end = logical_start + range.end;
1651                if offset != visual_end {
1652                    return visual_end;
1653                }
1654            }
1655        }
1656
1657        logical_end
1658    }
1659
1660    /// Get indent string of next line.
1661    ///
1662    /// To get current and next line indent, to return more depth one.
1663    pub(super) fn indent_of_next_line(&mut self) -> String {
1664        self.indent_of_next_line_at(self.cursor())
1665    }
1666
1667    /// Get indent string of the next line, relative to the given `offset`.
1668    pub(super) fn indent_of_next_line_at(&mut self, offset: usize) -> String {
1669        if self.is_single_line() {
1670            return "".into();
1671        }
1672
1673        let mut current_indent = String::new();
1674        let mut next_indent = String::new();
1675        let line_end_affinity = self.line_end_affinity_at(offset);
1676        let current_line_start_pos = self.start_of_line_at(offset, line_end_affinity);
1677        let next_line_start_pos = self.end_of_line_at(offset, line_end_affinity);
1678        for c in self.text.slice(current_line_start_pos..).chars() {
1679            if !c.is_whitespace() {
1680                break;
1681            }
1682            if c == '\n' || c == '\r' {
1683                break;
1684            }
1685            current_indent.push(c);
1686        }
1687
1688        for c in self.text.slice(next_line_start_pos..).chars() {
1689            if !c.is_whitespace() {
1690                break;
1691            }
1692            if c == '\n' || c == '\r' {
1693                break;
1694            }
1695            next_indent.push(c);
1696        }
1697
1698        if next_indent.len() > current_indent.len() {
1699            return next_indent;
1700        }
1701
1702        // Smart indent: one extra level after lines ending with a trigger.
1703        // Skipped inside strings and comments: the trigger is literal text.
1704        if let Some(rules) = self.mode.language_config() {
1705            if self.mode.is_smart_indent()
1706                && M::editing_syntax_context(self, offset) == crate::input::SyntaxContext::Code
1707            {
1708                let line_before: String = self
1709                    .text
1710                    .slice(current_line_start_pos..offset)
1711                    .chars()
1712                    .collect();
1713                if rules.opens_indent(line_before.trim_end()) {
1714                    let tab = self.mode.tab_size();
1715                    if tab.hard_tabs {
1716                        current_indent.push('\t');
1717                    } else {
1718                        for _ in 0..tab.tab_size {
1719                            current_indent.push(' ');
1720                        }
1721                    }
1722                } else {
1723                    let line_after = self.text.slice(offset..next_line_start_pos).to_string();
1724                    if rules.closes_indent(&line_after) {
1725                        let tab = self.mode.tab_size();
1726                        if current_indent.ends_with('\t') {
1727                            current_indent.pop();
1728                        } else {
1729                            for _ in 0..tab.tab_size {
1730                                if !current_indent.ends_with(' ') {
1731                                    break;
1732                                }
1733                                current_indent.pop();
1734                            }
1735                        }
1736                    }
1737                }
1738            }
1739        }
1740
1741        current_indent
1742    }
1743
1744    /// Delete every selection as one batch. Collapsed cursors are first
1745    /// expanded to a deletion range by `collapsed_target` and non-empty
1746    /// selections delete their own range.
1747    ///
1748    /// `collapsed_intent` is the intent to record when every cursor is
1749    /// collapsed, which is what makes a run of single-character deletes undo
1750    /// as one gesture. Deleting a real selection is always atomic.
1751    fn delete_selections(
1752        &mut self,
1753        silent: bool,
1754        collapsed_intent: EditIntent,
1755        mut collapsed_target: impl FnMut(&mut Self, usize) -> Range<usize>,
1756        window: &mut Window,
1757        cx: &mut Context<Self>,
1758    ) {
1759        if !self.is_editable() {
1760            return;
1761        }
1762        let cursors: Vec<CursorSelection> = self.selections.iter().copied().collect();
1763        let intent = if cursors.iter().all(|sel| sel.is_empty()) {
1764            collapsed_intent
1765        } else {
1766            EditIntent::Atomic
1767        };
1768        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(cursors.len());
1769        for sel in &cursors {
1770            let range = if sel.is_empty() {
1771                collapsed_target(self, sel.cursor_offset())
1772            } else {
1773                sel.start..sel.end
1774            };
1775            let (start, end) = (range.start.min(range.end), range.start.max(range.end));
1776            let mut selection = *sel;
1777            selection.start = start;
1778            selection.end = end;
1779            new_selections.push(selection);
1780        }
1781        // Capture the user's selections before expanding or merging deletion
1782        // ranges. The edit ranges cannot reconstruct their original carets.
1783        self.undo_manager.begin_transaction_with(intent);
1784        self.undo_manager
1785            .record_selections(cursors.clone(), cursors.clone());
1786        self.selections.replace_all(new_selections);
1787        self.undo_manager.set_pending_intent(intent);
1788
1789        let was_silent = self.silent_replace_text;
1790        self.silent_replace_text = silent;
1791        self.replace_text_in_range(None, "", window, cx);
1792        self.silent_replace_text = was_silent;
1793        self.undo_manager
1794            .record_selections(cursors, self.selections.iter().copied().collect());
1795        self.undo_manager.commit_transaction();
1796        self.pause_blink_cursor(cx);
1797    }
1798
1799    /// Decide pairing against the pre-edit text, before an opening quote can
1800    /// change its own syntax context. Ordinary characters never query syntax.
1801    fn auto_close_target(&self, range: &Range<usize>, text: &str) -> Option<(usize, SharedString)> {
1802        if self.silent_replace_text
1803            || self.ime_marked_range.is_some()
1804            || !self.selections.is_single()
1805            || !self.active_selection().is_empty()
1806            || !range.is_empty()
1807            || range.start != self.cursor()
1808            || text.chars().count() != 1
1809            || !self.mode.is_auto_close()
1810        {
1811            return None;
1812        }
1813        let rules = self.mode.language_config()?;
1814        if self
1815            .text
1816            .chars_at(range.start)
1817            .next()
1818            .is_some_and(|c| !c.is_whitespace() && !rules.auto_close_before.contains(c))
1819        {
1820            return None;
1821        }
1822        for (open, close, not_in) in rules.closing_pairs() {
1823            let Some(prefix) = open.strip_suffix(text) else {
1824                continue;
1825            };
1826            if !self.text_before_matches(range.start, prefix) {
1827                continue;
1828            }
1829            let start = range.start - prefix.len();
1830            if open == close
1831                && (self.is_escaped_at(start)
1832                    || self
1833                        .text
1834                        .chars_at(start)
1835                        .reversed()
1836                        .next()
1837                        .is_some_and(|c| c.is_alphanumeric() || c == '_'))
1838            {
1839                continue;
1840            }
1841            if not_in.contains(&M::editing_syntax_context(self, start)) {
1842                continue;
1843            }
1844            return Some((open.len(), close.into()));
1845        }
1846        None
1847    }
1848
1849    fn text_before_matches(&self, offset: usize, text: &str) -> bool {
1850        offset >= text.len()
1851            && self.text.is_char_boundary(offset - text.len())
1852            && self.text.slice(offset - text.len()..offset) == text
1853    }
1854
1855    fn text_after_matches(&self, offset: usize, text: &str) -> bool {
1856        offset + text.len() <= self.text.len()
1857            && self.text.is_char_boundary(offset + text.len())
1858            && self.text.slice(offset..offset + text.len()) == text
1859    }
1860
1861    fn is_escaped_at(&self, offset: usize) -> bool {
1862        self.text
1863            .chars_at(offset)
1864            .reversed()
1865            .take_while(|c| *c == '\\')
1866            .count()
1867            % 2
1868            == 1
1869    }
1870
1871    /// Cursor target when a typed closer should skip over an existing one.
1872    ///
1873    /// Returns `Some(offset)` when `new_text` is a single closer from the
1874    /// active [`LanguageConfig`](crate::input::language_config::LanguageConfig) that already follows the collapsed cursor and is
1875    /// not escaped. The caller then moves the cursor without editing text or
1876    /// history. `None` means insert normally.
1877    fn skip_over_target(&self, new_text: &str) -> Option<usize> {
1878        if !self.mode.is_auto_close() || new_text.chars().count() != 1 {
1879            return None;
1880        }
1881        let rules = self.mode.language_config()?;
1882        let cursor = self.cursor();
1883        for (open, close, not_in) in rules.closing_pairs() {
1884            for (index, _) in close.char_indices() {
1885                if !close[index..].starts_with(new_text)
1886                    || !self.text_before_matches(cursor, &close[..index])
1887                    || !self.text_after_matches(cursor, &close[index..])
1888                {
1889                    continue;
1890                }
1891                let context = M::editing_syntax_context(self, cursor);
1892                let generated =
1893                    self.mode
1894                        .auto_closed_pairs()
1895                        .contains_closer(cursor, index, close.len());
1896                if !generated
1897                    && not_in.contains(&context)
1898                    && !(context == crate::input::SyntaxContext::String && open == close)
1899                {
1900                    continue;
1901                }
1902                if self.is_escaped_at(cursor - index) {
1903                    continue;
1904                }
1905                return Some(cursor + new_text.len());
1906            }
1907        }
1908        None
1909    }
1910
1911    pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
1912        // Nothing to delete at the start of the text. Propagate so an ancestor
1913        // (e.g. a command palette navigating back a level) can act on it.
1914        // This is harmless when nothing upstream is bound to backspace. With multiple
1915        // cursors the others can still delete, so only the lone-cursor case
1916        // propagates.
1917        if self.selections.is_single() && self.active_selection().is_empty() && self.cursor() == 0 {
1918            cx.propagate();
1919            return;
1920        }
1921
1922        // Pair deletion follows automatic closing rules, independently of
1923        // structural brackets used by Enter.
1924        if self.mode.is_auto_close()
1925            && self.selections.is_single()
1926            && self.active_selection().is_empty()
1927        {
1928            let off = self.cursor();
1929            let deletion = self.mode.language_config().and_then(|rules| {
1930                rules.closing_pairs().find_map(|(open, close, not_in)| {
1931                    if !self.text_before_matches(off, open) || !self.text_after_matches(off, close)
1932                    {
1933                        return None;
1934                    }
1935                    let start = off - open.len();
1936                    let generated = self
1937                        .mode
1938                        .auto_closed_pairs()
1939                        .contains(start..off, off..off + close.len());
1940                    if self.is_escaped_at(start)
1941                        || (!generated && not_in.contains(&M::editing_syntax_context(self, start)))
1942                    {
1943                        return None;
1944                    }
1945                    Some(start..off + close.len())
1946                })
1947            });
1948            if let Some(range) = deletion {
1949                let utf16 = self.range_to_utf16(&range);
1950                self.replace_text_in_range_silent(Some(utf16), "", window, cx);
1951                return;
1952            }
1953        }
1954
1955        self.delete_selections(
1956            false,
1957            EditIntent::Backspace,
1958            |s, offset| s.previous_boundary(offset)..offset,
1959            window,
1960            cx,
1961        );
1962    }
1963
1964    pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
1965        self.delete_selections(
1966            false,
1967            EditIntent::DeleteForward,
1968            |s, offset| offset..s.next_boundary(offset),
1969            window,
1970            cx,
1971        );
1972    }
1973
1974    pub(super) fn delete_to_beginning_of_line(
1975        &mut self,
1976        _: &DeleteToBeginningOfLine,
1977        window: &mut Window,
1978        cx: &mut Context<Self>,
1979    ) {
1980        self.delete_selections(
1981            true,
1982            EditIntent::Atomic,
1983            |s, offset| {
1984                let mut start = s.start_of_line_at(offset, s.line_end_affinity_at(offset));
1985                if start == offset {
1986                    start = start.saturating_sub(1);
1987                }
1988                start..offset
1989            },
1990            window,
1991            cx,
1992        );
1993    }
1994
1995    pub(super) fn delete_to_end_of_line(
1996        &mut self,
1997        _: &DeleteToEndOfLine,
1998        window: &mut Window,
1999        cx: &mut Context<Self>,
2000    ) {
2001        self.delete_selections(
2002            true,
2003            EditIntent::Atomic,
2004            |s, offset| {
2005                let mut end = s.end_of_line_at(offset, s.line_end_affinity_at(offset));
2006                if end == offset {
2007                    end = (end + 1).clamp(0, s.text.len());
2008                }
2009                offset..end
2010            },
2011            window,
2012            cx,
2013        );
2014    }
2015
2016    pub(super) fn delete_previous_word(
2017        &mut self,
2018        _: &DeleteToPreviousWordStart,
2019        window: &mut Window,
2020        cx: &mut Context<Self>,
2021    ) {
2022        self.delete_selections(
2023            true,
2024            EditIntent::Atomic,
2025            |s, offset| s.previous_start_of_word_at(offset)..offset,
2026            window,
2027            cx,
2028        );
2029    }
2030
2031    pub(super) fn delete_next_word(
2032        &mut self,
2033        _: &DeleteToNextWordEnd,
2034        window: &mut Window,
2035        cx: &mut Context<Self>,
2036    ) {
2037        self.delete_selections(
2038            true,
2039            EditIntent::Atomic,
2040            |s, offset| offset..s.next_end_of_word_at(offset),
2041            window,
2042            cx,
2043        );
2044    }
2045
2046    pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {
2047        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2048            return;
2049        }
2050
2051        // Clear inline completion on enter (user chose not to accept it)
2052        if M::has_inline_completion(self) {
2053            M::clear_inline_completion(self, cx);
2054        }
2055
2056        // In multi-line mode with `submit_on_enter` enabled, a plain `Enter`
2057        // (without Shift) is treated as submit: propagate the action and emit
2058        // PressEnter without inserting a newline. `Shift+Enter` still inserts
2059        // a newline.
2060        let insert_newline = self.is_multi_line() && (!self.submit_on_enter || action.shift);
2061
2062        if insert_newline {
2063            if !self.selections.is_single() {
2064                // Insert a newline (with per-line indent) at every cursor.
2065                self.selections.merge_overlapping();
2066                let selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
2067                let mut edits: Vec<(Range<usize>, String)> = Vec::with_capacity(selections.len());
2068                for sel in &selections {
2069                    let indent = if self.is_code_editor() {
2070                        self.indent_of_next_line_at(sel.cursor_offset())
2071                    } else {
2072                        String::new()
2073                    };
2074                    edits.push((sel.start..sel.end, format!("\n{}", indent)));
2075                }
2076                edits.sort_by_key(|(range, _)| range.start);
2077                self.replace_text_in_ranges(&edits, window, cx);
2078                self.pause_blink_cursor(cx);
2079            } else {
2080                // Bracket split: Enter between `{|}` produces `{\n  \n}`
2081                // with the cursor on the middle line. Controlled by smart_indent,
2082                // independently of automatic closing.
2083                // Skipped inside strings and comments: splitting there would
2084                // restructure literal text.
2085                let mut split = false;
2086                if self.is_code_editor() && self.active_selection().is_empty() {
2087                    if let Some(rules) = self.mode.language_config() {
2088                        if self.mode.is_smart_indent()
2089                            && M::editing_syntax_context(self, self.cursor())
2090                                == crate::input::SyntaxContext::Code
2091                        {
2092                            let off = self.cursor();
2093                            let is_pair = rules.brackets.iter().any(|p| {
2094                                !p.open.is_empty()
2095                                    && !p.close.is_empty()
2096                                    && p.open != p.close
2097                                    && self.text_before_matches(off, p.open.as_ref())
2098                                    && self.text_after_matches(off, p.close.as_ref())
2099                            });
2100                            if is_pair {
2101                                // Base indent: leading whitespace only.
2102                                let line_end_affinity = self.line_end_affinity_at(off);
2103                                let line_start = self.start_of_line_at(off, line_end_affinity);
2104                                let mut indent = String::new();
2105                                for c in self.text.slice(line_start..).chars() {
2106                                    if !c.is_whitespace() || c == '\n' || c == '\r' {
2107                                        break;
2108                                    }
2109                                    indent.push(c);
2110                                }
2111                                // Extra level only with smart indent enabled.
2112                                let mut inner = indent.clone();
2113                                if self.mode.is_smart_indent() {
2114                                    let tab = self.mode.tab_size();
2115                                    if tab.hard_tabs {
2116                                        inner.push('\t');
2117                                    } else {
2118                                        for _ in 0..tab.tab_size {
2119                                            inner.push(' ');
2120                                        }
2121                                    }
2122                                }
2123                                let new_line_text = format!("\n{inner}\n{indent}");
2124                                self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2125                                self.set_cursor_to(off + 1 + inner.len());
2126                                self.update_preferred_column();
2127                                let cursors = self.selections.iter().copied().collect::<Vec<_>>();
2128                                self.undo_manager
2129                                    .record_selections(cursors.clone(), cursors);
2130                                self.pause_blink_cursor(cx);
2131                                split = true;
2132                            }
2133                        }
2134                    }
2135                }
2136                if !split {
2137                    // Get current line indent
2138                    let indent = if self.is_code_editor() {
2139                        self.indent_of_next_line()
2140                    } else {
2141                        "".to_string()
2142                    };
2143
2144                    // Add newline and indent
2145                    let new_line_text = format!("\n{}", indent);
2146                    self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2147                    self.pause_blink_cursor(cx);
2148                }
2149            }
2150        } else {
2151            // Single line input or submit-on-enter: just emit the event
2152            // (e.g.: in a dialog to confirm, or a chat textarea to send).
2153            self.undo_manager.break_transaction_coalescing();
2154            cx.propagate();
2155        }
2156
2157        cx.emit(InputEvent::PressEnter {
2158            secondary: action.secondary,
2159            shift: action.shift,
2160        });
2161    }
2162
2163    pub fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2164        self.replace_text("", window, cx);
2165        self.set_selection(0, 0);
2166        self.scroll_to(0, None, cx);
2167    }
2168
2169    pub(super) fn escape(&mut self, action: &Escape, window: &mut Window, cx: &mut Context<Self>) {
2170        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2171            return;
2172        }
2173
2174        // Escape also dismisses a request whose popup has not arrived yet.
2175        M::hide_context_menu(self, cx);
2176
2177        // Collapse extra cursors back to the active one first.
2178        if !self.selections.is_single() {
2179            self.undo_manager.break_transaction_coalescing();
2180            self.selections.remove_all_but_active();
2181            cx.notify();
2182            return;
2183        }
2184
2185        // Clear inline completion on escape
2186        if M::has_inline_completion(self) {
2187            M::clear_inline_completion(self, cx);
2188            return; // Consume the escape, don't propagate
2189        }
2190        M::clear_inline_completion(self, cx);
2191
2192        // The handles and the edit menu are the topmost surface to dismiss.
2193        if self.touch_selection().is_some() {
2194            self.dismiss_touch_selection(cx);
2195            return;
2196        }
2197
2198        if self.ime_marked_range.is_some() {
2199            self.unmark_text(window, cx);
2200        }
2201
2202        if self.clean_on_escape {
2203            return self.clean(window, cx);
2204        }
2205
2206        cx.propagate();
2207    }
2208
2209    /// Show the right-click context menu as a native OS menu.
2210    pub(crate) fn handle_right_click_menu(
2211        &mut self,
2212        position: Point<Pixels>,
2213        offset: usize,
2214        window: &mut Window,
2215        cx: &mut Context<Self>,
2216    ) {
2217        if self.disabled {
2218            return;
2219        }
2220        if crate::GlobalState::is_in_deferred_context(cx) {
2221            return;
2222        }
2223
2224        if !self.active_selection().contains(offset) {
2225            self.move_to(offset, None, cx);
2226        }
2227
2228        if self.is_code_editor() {
2229            M::on_hover_definition(self, offset, window, cx);
2230        }
2231
2232        if let Some(handler) = self.context_menu_handler.clone() {
2233            let capabilities = self.context_menu_capabilities();
2234            let input = cx.weak_entity();
2235            // The handler runs with the input released, so a custom builder can
2236            // read it (cursor, selection, text) to build its items.
2237            window.defer(cx, move |window, cx| {
2238                if input.upgrade().is_none() {
2239                    return;
2240                }
2241                let previous_focus = window.focused(cx);
2242                handler(NativeMenu::new(), capabilities, position, window, cx);
2243                // A drawn menu takes focus synchronously; native menus leave it
2244                // on the input. Capture it here rather than in the handler.
2245                input
2246                    .update(cx, |this, cx| {
2247                        if window.focused(cx) == previous_focus
2248                            && !this.focus_handle.is_focused(window)
2249                            && this.has_selection_focus(window, cx)
2250                        {
2251                            // A custom handler can reuse an already-focused popup.
2252                            return;
2253                        }
2254                        let menu_focus = window.focused(cx).filter(|focus| {
2255                            Some(focus) != previous_focus.as_ref() && *focus != this.focus_handle
2256                        });
2257                        this.set_selection_focus(menu_focus, cx);
2258                    })
2259                    .ok();
2260            });
2261        }
2262    }
2263
2264    pub(super) fn add_cursor_above(
2265        &mut self,
2266        _: &AddCursorAbove,
2267        _: &mut Window,
2268        cx: &mut Context<Self>,
2269    ) {
2270        self.add_cursor_vertical(-1, cx);
2271    }
2272
2273    pub(super) fn add_cursor_below(
2274        &mut self,
2275        _: &AddCursorBelow,
2276        _: &mut Window,
2277        cx: &mut Context<Self>,
2278    ) {
2279        self.add_cursor_vertical(1, cx);
2280    }
2281
2282    /// Add a new cursor one display line above (`move_lines < 0`) or below each
2283    /// existing cursor, preserving the column. Cursors that would not move (at
2284    /// the first/last display row) or that would duplicate an existing cursor
2285    /// are skipped.
2286    fn add_cursor_vertical(&mut self, move_lines: isize, cx: &mut Context<Self>) {
2287        if !self.is_multi_line() {
2288            return;
2289        }
2290
2291        self.pause_blink_cursor(cx);
2292        // Changing the cursor set ends the editing gesture that came before it.
2293        self.undo_manager.break_transaction_coalescing();
2294
2295        let sources: Vec<(usize, Option<(Pixels, usize)>, bool)> = self
2296            .selections
2297            .iter()
2298            .map(|sel| {
2299                (
2300                    sel.cursor_offset(),
2301                    sel.column_anchor,
2302                    self.line_end_affinity_for(sel),
2303                )
2304            })
2305            .collect();
2306        let mut offsets: std::collections::HashSet<usize> =
2307            sources.iter().map(|(offset, _, _)| *offset).collect();
2308
2309        let mut newest: Option<usize> = None;
2310        for (offset, anchor, line_end_affinity) in sources {
2311            let anchor = anchor.or_else(|| self.preferred_column_for(offset));
2312            let (target, _) = self.vertical_target(offset, anchor, line_end_affinity, move_lines);
2313            if target == offset || offsets.contains(&target) {
2314                continue;
2315            }
2316            offsets.insert(target);
2317            let id = self.selections.generate_id();
2318            let mut cursor = CursorSelection::new(id, target, target);
2319            cursor.column_anchor = anchor;
2320            self.selections.add(cursor);
2321            newest = Some(target);
2322        }
2323
2324        if let Some(newest) = newest {
2325            self.scroll_to(newest, None, cx);
2326        }
2327        cx.notify();
2328    }
2329
2330    /// Add an additional collapsed cursor at `offset`.
2331    ///
2332    /// Rejected when `offset` lands inside an existing selection or exactly on
2333    /// an existing cursor.
2334    pub(super) fn add_cursor_at(&mut self, offset: usize, cx: &mut Context<Self>) {
2335        if !self.is_multi_line() {
2336            return;
2337        }
2338
2339        for sel in self.selections.iter() {
2340            if sel.contains(offset) {
2341                return;
2342            }
2343            if sel.is_collapsed() && sel.cursor_offset() == offset {
2344                return;
2345            }
2346        }
2347
2348        self.undo_manager.break_transaction_coalescing();
2349        let id = self.selections.generate_id();
2350        let mut selection = CursorSelection::new(id, offset, offset);
2351        selection.column_anchor = self.preferred_column_for(offset);
2352        self.selections.add(selection);
2353        cx.notify();
2354    }
2355
2356    /// Build a columnar (block) selection spanning the rows between the two
2357    /// points, one selection per display row at the same column span.
2358    ///
2359    /// The span keeps the columns the pointer sat past the end of a short row, so a row
2360    /// that runs out of text does not narrow the block for the rows that do reach that
2361    /// far. Each row is then clipped to its own end, selecting as much of the span as it
2362    /// actually has.
2363    pub(super) fn build_columnar_selection(
2364        &mut self,
2365        start: ColumnarPoint,
2366        end: ColumnarPoint,
2367        cx: &mut Context<Self>,
2368    ) {
2369        if !self.is_multi_line() {
2370            return;
2371        }
2372
2373        self.undo_manager.break_transaction_coalescing();
2374
2375        let (start_row, start_col) = self.columnar_row_column(start);
2376        let (end_row, end_col) = self.columnar_row_column(end);
2377        let (start_row, end_row) = (start_row.min(end_row), start_row.max(end_row));
2378        let (start_col, end_col) = (start_col.min(end_col), start_col.max(end_col));
2379
2380        let mut new_selections = Vec::with_capacity(end_row - start_row + 1);
2381        for row in start_row..=end_row {
2382            let sel_start = self
2383                .display_map
2384                .display_row_column_to_offset(row, start_col);
2385            let sel_end = self.display_map.display_row_column_to_offset(row, end_col);
2386            let id = self.selections.generate_id();
2387            let sel_start = self.text.clip_offset(sel_start, Bias::Left);
2388            let sel_end = self.text.clip_offset(sel_end, Bias::Left);
2389            new_selections.push(CursorSelection::new(id, sel_start, sel_end));
2390        }
2391
2392        self.selections.replace_all(new_selections);
2393        cx.notify();
2394    }
2395
2396    /// Resolve a columnar point to the display row it is on and the column it reaches,
2397    /// counting the columns beyond the row's end so a short row keeps the full span.
2398    fn columnar_row_column(&self, point: ColumnarPoint) -> (usize, usize) {
2399        let display_point = self.display_map.offset_to_wrap_display_point(point.offset);
2400        let row = self
2401            .display_map
2402            .wrap_row_to_display_row(display_point.row)
2403            .unwrap_or_else(|| {
2404                self.display_map
2405                    .nearest_visible_display_row(display_point.row)
2406            });
2407
2408        (row, display_point.column + point.columns_past_line_end)
2409    }
2410
2411    pub(super) fn on_mouse_down(
2412        &mut self,
2413        event: &MouseDownEvent,
2414        window: &mut Window,
2415        cx: &mut Context<Self>,
2416    ) {
2417        // A token that consumed the press (a double click selecting it) must
2418        // not also place the caret.
2419        if window.default_prevented() {
2420            return;
2421        }
2422        self.undo_manager.break_transaction_coalescing();
2423        // Input has its own text selection; suppress the window-level text
2424        // selection (Root) so it does not start a drag from here.
2425        crate::global_state::GlobalState::suppress_text_selection(cx);
2426
2427        // Clear inline completion on any mouse interaction
2428        M::clear_inline_completion(self, cx);
2429        // A tap on the touch selection asks for its menu back. Any other press
2430        // places the caret or starts a new drag, and the handles and the menu
2431        // no longer belong to what is selected.
2432        if event.button == MouseButton::Left
2433            && event.click_count == 1
2434            && self.reopen_edit_menu_at(event.position, cx)
2435        {
2436            return;
2437        }
2438        self.dismiss_touch_selection(cx);
2439
2440        // If there have IME marked range and is empty (Means pressed Esc to abort IME typing)
2441        // Clear the marked range.
2442        if let Some(ime_marked_range) = &self.ime_marked_range {
2443            if ime_marked_range.len() == 0 {
2444                self.ime_marked_range = None;
2445            }
2446        }
2447
2448        self.selecting = true;
2449        let (offset, line_end_affinity, columns_past_line_end) =
2450            self.resolve_mouse_position(event.position);
2451
2452        if M::on_click(self, event, offset, window, cx) {
2453            return;
2454        }
2455
2456        // Triple click to select line
2457        if event.button == MouseButton::Left && event.click_count >= 3 {
2458            self.select_line(offset, window, cx);
2459            return;
2460        }
2461
2462        // Double click to select word
2463        if event.button == MouseButton::Left && event.click_count == 2 {
2464            self.select_word(offset, window, cx);
2465            // A double tap is touch's other way to select a word, and it
2466            // gets the handles and the menu like a long press does.
2467            if crate::GlobalState::is_touch_press(cx) {
2468                self.keep_touch_selection(cx);
2469            }
2470            return;
2471        }
2472
2473        // Show Mouse context menu
2474        if event.button == MouseButton::Right {
2475            if self.enable_context_menu {
2476                if !self.active_selection().contains(offset) {
2477                    self.move_to(offset, None, cx);
2478                }
2479                self.pending_context_menu = Some((event.position, offset));
2480            }
2481            return;
2482        }
2483
2484        // Multi-cursor placement, multi-line only.
2485        if self.is_multi_line() && event.button == MouseButton::Left {
2486            if event.modifiers.alt
2487                && (event.modifiers.shift || (cfg!(target_os = "linux") && event.modifiers.control))
2488            {
2489                // Alt+Shift starts a block; Linux also accepts Ghostty's Ctrl+Alt.
2490                // Mark selecting so the drag handler extends the block.
2491                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2492                self.selecting = true;
2493                self.move_to_with_affinity(offset, None, line_end_affinity, cx);
2494                return;
2495            } else if event.modifiers.alt {
2496                self.add_cursor_at(offset, cx);
2497                // Keep click-to-add behavior, but use this press as the block
2498                // anchor if the user continues dragging with the left button.
2499                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2500                return;
2501            }
2502        }
2503
2504        if event.modifiers.shift {
2505            self.select_to_with_affinity(offset, line_end_affinity, cx);
2506        } else {
2507            self.move_to_with_affinity(offset, None, line_end_affinity, cx)
2508        }
2509    }
2510
2511    pub(super) fn on_mouse_up(
2512        &mut self,
2513        event: &MouseUpEvent,
2514        window: &mut Window,
2515        cx: &mut Context<Self>,
2516    ) {
2517        if event.button == MouseButton::Right {
2518            if let Some((position, offset)) = self.pending_context_menu.take() {
2519                self.handle_right_click_menu(position, offset, window, cx);
2520            }
2521        }
2522        if self.active_selection().is_empty() {
2523            self.active_selection_mut().reversed = false;
2524        }
2525        self.selecting = false;
2526        self.selected_word_range = None;
2527        self.column_select_start = None;
2528        self.auto_scroll.stop();
2529    }
2530
2531    pub(super) fn on_mouse_move(
2532        &mut self,
2533        event: &MouseMoveEvent,
2534        window: &mut Window,
2535        cx: &mut Context<Self>,
2536    ) {
2537        // Check if mouse is within bounds
2538        let within_bounds = self
2539            .last_bounds
2540            .as_ref()
2541            .map(|bounds| bounds.contains(&event.position))
2542            .unwrap_or(false);
2543
2544        if !within_bounds {
2545            // Clear hover when mouse leaves the input
2546            M::clear_hover_state(self, cx);
2547            return;
2548        }
2549
2550        // Show diagnostic popover on mouse move
2551        let (offset, _) = self.index_for_mouse_position(event.position);
2552        M::on_mouse_move(self, offset, event, window, cx);
2553
2554        if self.is_code_editor() {
2555            // Keep the current popover while the pointer stays on the same
2556            // diagnostic, so moving over a squiggle neither repaints nor
2557            // rebuilds the popover.
2558            match self
2559                .mode
2560                .diagnostics()
2561                .and_then(|set| set.for_offset(offset))
2562            {
2563                Some(diagnostic) => {
2564                    if self.diagnostic_popover.as_deref() != Some(diagnostic) {
2565                        self.diagnostic_popover = Some(Rc::new(diagnostic.clone()));
2566                        cx.notify();
2567                    }
2568                }
2569                None => {
2570                    if self.diagnostic_popover.take().is_some() {
2571                        cx.notify();
2572                    }
2573                }
2574            }
2575        }
2576    }
2577
2578    pub(super) fn on_scroll_wheel(
2579        &mut self,
2580        event: &ScrollWheelEvent,
2581        window: &mut Window,
2582        cx: &mut Context<Self>,
2583    ) {
2584        let line_height = self
2585            .last_layout
2586            .as_ref()
2587            .map(|layout| layout.line_height)
2588            .unwrap_or(window.line_height());
2589        let delta = event.delta.pixel_delta(line_height);
2590
2591        let old_offset = self.scroll_handle.offset();
2592        self.update_scroll_offset(Some(old_offset + delta), cx);
2593
2594        // Only stop propagation if the offset actually changed
2595        if self.scroll_handle.offset() != old_offset {
2596            cx.stop_propagation();
2597        }
2598
2599        if self.diagnostic_popover.take().is_some() {
2600            cx.notify();
2601        }
2602        // The handles follow the text; the menu would sit over whatever
2603        // scrolls underneath it, so it steps aside until the finger lifts.
2604        self.edit_menu_on_scroll(event.touch_phase, cx);
2605    }
2606
2607    pub(super) fn update_scroll_offset(
2608        &mut self,
2609        offset: Option<Point<Pixels>>,
2610        cx: &mut Context<Self>,
2611    ) {
2612        let mut offset = offset.unwrap_or(self.scroll_handle.offset());
2613        let safe_y_range =
2614            (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
2615
2616        offset.y = if self.is_single_line() {
2617            px(0.)
2618        } else {
2619            offset.y.clamp(safe_y_range.start, safe_y_range.end)
2620        };
2621        offset.x = clamp_horizontal_scroll_offset(
2622            offset.x,
2623            self.scroll_size.width,
2624            self.input_bounds.size.width,
2625            self.text_align,
2626        );
2627        if self.scroll_handle.offset() != offset {
2628            self.scroll_handle.set_offset(offset);
2629            cx.notify();
2630        }
2631    }
2632
2633    /// Scroll to make the given offset visible.
2634    ///
2635    /// If `direction` is Some, will keep edges at the same side.
2636    pub(crate) fn scroll_to(
2637        &mut self,
2638        offset: usize,
2639        direction: Option<MoveDirection>,
2640        cx: &mut Context<Self>,
2641    ) {
2642        let padding = if direction.is_some() {
2643            ScrollPadding::SurroundingLines
2644        } else {
2645            ScrollPadding::Minimal
2646        };
2647        self.scroll_to_with_padding(offset, direction, padding, cx);
2648    }
2649
2650    /// Reveal an offset with independently chosen direction restriction and padding.
2651    /// Search uses surrounding lines without restricting movement to match order.
2652    pub(crate) fn scroll_to_with_padding(
2653        &mut self,
2654        offset: usize,
2655        direction: Option<MoveDirection>,
2656        padding: ScrollPadding,
2657        cx: &mut Context<Self>,
2658    ) {
2659        let Some(last_layout) = self.last_layout.as_ref() else {
2660            return;
2661        };
2662        let Some(bounds) = self.last_bounds.as_ref() else {
2663            return;
2664        };
2665
2666        let mut scroll_offset = self.scroll_handle.offset();
2667        let was_offset = scroll_offset;
2668        let line_height = last_layout.line_height;
2669
2670        let point = self.text.offset_to_point(offset);
2671
2672        let row = point.row;
2673
2674        // Resolve the wrapped row even when the target is outside the last layout.
2675        let display_pos = self
2676            .display_map
2677            .buffer_pos_to_display_pos(crate::input::BufferPoint::new(row, point.column));
2678        let row_offset_y = line_height * display_pos.row;
2679
2680        // For Right alignment use 0 margin: the cursor indicator is clamped inside bounds
2681        // in layout_cursors, so shifting the text here would cause a first-click visual jump.
2682        let safety_margin = match last_layout.text_align {
2683            TextAlign::Left => RIGHT_MARGIN,
2684            TextAlign::Right => px(0.),
2685            TextAlign::Center => CURSOR_WIDTH,
2686        };
2687        if let Some(vi) = last_layout
2688            .visible_buffer_lines
2689            .iter()
2690            .position(|&line| line == row)
2691        {
2692            let line = &last_layout.lines[vi];
2693            let local_offset = offset.saturating_sub(last_layout.visible_line_byte_offsets[vi]);
2694            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
2695                let bounds_width = bounds.size.width - last_layout.line_number_width;
2696                let col_offset_x = pos.x;
2697                if col_offset_x - safety_margin < -scroll_offset.x {
2698                    // If the position is out of the visible area, scroll to make it visible
2699                    scroll_offset.x = -col_offset_x + safety_margin;
2700                } else if col_offset_x + safety_margin > -scroll_offset.x + bounds_width {
2701                    scroll_offset.x = -(col_offset_x - bounds_width + safety_margin);
2702                }
2703            }
2704        }
2705
2706        // Scroll the row into view. Use the same edge clearance helper as
2707        // `TextElement::layout_cursors` so both scroll-into-view paths agree
2708        // (a mismatch flickered on `Down` at end-of-buffer with a small
2709        // `cursor_surrounding_lines` override).
2710        let edge_height =
2711            if matches!(padding, ScrollPadding::SurroundingLines) && self.is_code_editor() {
2712                super::element::cursor_surrounding_padding(
2713                    self.mode.is_auto_grow(),
2714                    self.cursor_surrounding_lines,
2715                    super::element::viewport_visible_lines(bounds.size.height, line_height),
2716                    line_height,
2717                )
2718            } else {
2719                line_height
2720            };
2721        if row_offset_y - edge_height + line_height < -scroll_offset.y {
2722            // Scroll up
2723            scroll_offset.y = -row_offset_y + edge_height - line_height;
2724        } else if row_offset_y + edge_height > -scroll_offset.y + bounds.size.height {
2725            // Scroll down
2726            scroll_offset.y = -(row_offset_y - bounds.size.height + edge_height);
2727        }
2728
2729        // Avoid necessary scroll, when it was already in the correct position.
2730        if direction == Some(MoveDirection::Up) {
2731            scroll_offset.y = scroll_offset.y.max(was_offset.y);
2732        } else if direction == Some(MoveDirection::Down) {
2733            scroll_offset.y = scroll_offset.y.min(was_offset.y);
2734        }
2735
2736        // Clamp the deferred target into the same safe range that
2737        // `update_scroll_offset` enforces on persist, so paint never shows an
2738        // over-scrolled frame before the post-paint clamp pulls it back.
2739        let safe_y_min = (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.));
2740        scroll_offset.x = scroll_offset.x.min(px(0.));
2741        scroll_offset.y = scroll_offset.y.clamp(safe_y_min, px(0.));
2742        self.deferred_scroll_offset = Some(scroll_offset);
2743        cx.notify();
2744    }
2745
2746    pub(super) fn show_character_palette(
2747        &mut self,
2748        _: &ShowCharacterPalette,
2749        window: &mut Window,
2750        _: &mut Context<Self>,
2751    ) {
2752        window.show_character_palette();
2753    }
2754
2755    /// The text of every non-empty selection, in document order.
2756    fn selected_texts(&self) -> Vec<String> {
2757        let mut selections: Vec<CursorSelection> = self
2758            .selections
2759            .iter()
2760            .copied()
2761            .filter(|sel| !sel.is_empty())
2762            .collect();
2763        selections.sort_by_key(|sel| sel.start);
2764        selections
2765            .iter()
2766            .map(|sel| self.text.slice(*sel).to_string())
2767            .collect()
2768    }
2769
2770    pub(super) fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
2771        if !self.is_copyable() {
2772            return;
2773        }
2774
2775        let texts = self.selected_texts();
2776
2777        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
2778    }
2779
2780    pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
2781        if !self.is_copyable() {
2782            return;
2783        }
2784
2785        let texts = self.selected_texts();
2786
2787        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
2788
2789        self.undo_manager.set_pending_intent(EditIntent::Atomic);
2790        self.replace_text_in_range_silent(None, "", window, cx);
2791    }
2792
2793    pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
2794        if !self.is_editable() {
2795            return;
2796        }
2797        if let Some(clipboard) = cx.read_from_clipboard() {
2798            self.insert_clipboard(clipboard, window, cx);
2799            return;
2800        }
2801        // The synchronous read is empty on platforms whose clipboard is
2802        // asynchronous and permission-gated (the web), so fall back to the
2803        // real read. It has to start here, still inside the user activation
2804        // that dispatched `Paste`, or the browser refuses the read.
2805        let read = cx.read_from_clipboard_async();
2806        let target = self.paste_target();
2807        cx.spawn_in(window, async move |this, cx| match read.await {
2808            Ok(Some(clipboard)) => {
2809                this.update_in(cx, |this, window, cx| {
2810                    // The read can sit behind a permission prompt for as
2811                    // long as the user likes. If they edited, moved the
2812                    // caret or left the input meanwhile, the paste would
2813                    // land where they no longer mean it to; drop it, as the
2814                    // browser's own paste event only reaches the focused
2815                    // input too.
2816                    if this.is_editable()
2817                        && this.focus_handle.is_focused(window)
2818                        && this.paste_target() == target
2819                    {
2820                        this.insert_clipboard(clipboard, window, cx);
2821                    }
2822                })
2823                .ok();
2824            }
2825            Ok(None) => {}
2826            Err(error) => tracing::warn!("failed to read the clipboard for paste: {error}"),
2827        })
2828        .detach();
2829    }
2830
2831    /// Where a paste would go right now: the document as edited so far and
2832    /// the selections it would replace. Two equal targets mean an edit made
2833    /// for one still applies to the other.
2834    fn paste_target(&self) -> PasteTarget {
2835        PasteTarget {
2836            document_revision: self.document_revision,
2837            selections: self.selections.iter().copied().collect(),
2838        }
2839    }
2840
2841    /// Inserts the clipboard text the way `Paste` does: one atomic edit,
2842    /// newlines dropped on a single-line input, one line per selection when
2843    /// the counts match on a multi-line one. A clipboard without text (an
2844    /// image, say) is left alone rather than replacing the selection with
2845    /// nothing.
2846    fn insert_clipboard(
2847        &mut self,
2848        clipboard: ClipboardItem,
2849        window: &mut Window,
2850        cx: &mut Context<Self>,
2851    ) {
2852        let Some(mut new_text) = clipboard.text().filter(|text| !text.is_empty()) else {
2853            return;
2854        };
2855        // A paste is one atomic edit, never part of a typing run.
2856        self.undo_manager.set_pending_intent(EditIntent::Atomic);
2857
2858        if !self.is_multi_line() {
2859            new_text = new_text.replace('\n', "");
2860            self.replace_text_in_range_silent(None, &new_text, window, cx);
2861            self.scroll_to(self.cursor(), None, cx);
2862            return;
2863        }
2864
2865        // Distribute one clipboard line per selection when the counts match.
2866        // Otherwise insert the whole clipboard text at each cursor.
2867        if !self.selections.is_single() {
2868            self.selections.merge_overlapping();
2869        }
2870        let lines: Vec<String> = new_text.split('\n').map(|s| s.to_string()).collect();
2871        let count = self.selections.len();
2872        if count > 1 && lines.len() == count {
2873            let mut selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
2874            selections.sort_by_key(|sel| sel.start);
2875            let edits: Vec<(Range<usize>, String)> = selections
2876                .iter()
2877                .zip(lines)
2878                .map(|(sel, line)| (sel.start..sel.end, line))
2879                .collect();
2880            self.replace_text_in_ranges(&edits, window, cx);
2881        } else {
2882            self.replace_text_in_range_silent(None, &new_text, window, cx);
2883        }
2884        self.scroll_to(self.cursor(), None, cx);
2885    }
2886
2887    /// The intent of a batch the caller did not label: inserting text at
2888    /// collapsed cursors is typing, anything else stands on its own.
2889    fn typing_intent(&self, edits: &[(Range<usize>, String)], new_text: &str) -> EditIntent {
2890        if !new_text.is_empty()
2891            && !new_text.contains(['\n', '\r'])
2892            && edits.iter().all(|(range, _)| range.is_empty())
2893        {
2894            EditIntent::Typing
2895        } else {
2896            EditIntent::Atomic
2897        }
2898    }
2899
2900    /// Where a cursor stood before an edit made with this intent.
2901    ///
2902    /// Backspace and forward delete expand a collapsed cursor over the text
2903    /// they are about to remove, so the recorded cursor has to collapse back to
2904    /// the side it came from for undo to restore it where the user left it.
2905    fn collapse_for_intent(
2906        intent: EditIntent,
2907        mut selection: CursorSelection,
2908        range: &Range<usize>,
2909    ) -> CursorSelection {
2910        match intent {
2911            EditIntent::Backspace => selection.place_at(range.end, None),
2912            EditIntent::DeleteForward => selection.place_at(range.start, None),
2913            EditIntent::Typing | EditIntent::Atomic => {}
2914        }
2915        selection
2916    }
2917
2918    /// Whether an edit rewrites the character before a collapsed cursor, as the
2919    /// macOS Korean IME does on each keystroke instead of marking text.
2920    fn rewrites_typed_char(
2921        &self,
2922        selection: CursorSelection,
2923        range: &Range<usize>,
2924        old_text: &str,
2925        new_text: &str,
2926    ) -> bool {
2927        !self.silent_replace_text
2928            && selection.is_collapsed()
2929            && selection.cursor_offset() == range.end
2930            && old_text.chars().count() == 1
2931            && !old_text.contains(['\n', '\r'])
2932            && !new_text.contains(['\n', '\r'])
2933    }
2934
2935    fn push_history(
2936        &mut self,
2937        text: &Rope,
2938        range: &Range<usize>,
2939        new_text: &str,
2940        requested_intent: Option<EditIntent>,
2941        selection_before: CursorSelection,
2942        selection_after: Option<CursorSelection>,
2943    ) -> bool {
2944        self.document_revision = self.document_revision.wrapping_add(1);
2945        let token_delta = self.edit_tokens(range, new_text.len());
2946        if self.undo_manager.is_ignoring() {
2947            return false;
2948        }
2949
2950        let range =
2951            text.clip_offset(range.start, Bias::Left)..text.clip_offset(range.end, Bias::Right);
2952        let old_text = text.slice(range.clone()).to_string();
2953        let new_range = range.start..range.start + new_text.len();
2954
2955        let intent = requested_intent.unwrap_or_else(|| {
2956            if range.is_empty()
2957                && old_text.is_empty()
2958                && !new_text.is_empty()
2959                && !new_text.contains(['\n', '\r'])
2960                || self.rewrites_typed_char(selection_before, &range, &old_text, new_text)
2961            {
2962                EditIntent::Typing
2963            } else {
2964                EditIntent::Atomic
2965            }
2966        });
2967
2968        let selection_before = Self::collapse_for_intent(intent, selection_before, &range);
2969        let selection_after =
2970            selection_after.unwrap_or_else(|| (new_range.end..new_range.end).into());
2971
2972        let open_transaction = self.undo_manager.has_open_transaction();
2973        let mut change = Change::new(range, &old_text, new_range, new_text);
2974        change.token_delta = token_delta;
2975        let recorded = self.undo_manager.record_transaction(change, intent);
2976        // A batch records its own cursor sets. This covers a change that is a
2977        // transaction on its own.
2978        if recorded && !open_transaction {
2979            self.undo_manager
2980                .record_selections(vec![selection_before], vec![selection_after]);
2981        }
2982        recorded
2983    }
2984
2985    pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
2986        self.replay_history(true, window, cx);
2987    }
2988
2989    pub(super) fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
2990        self.replay_history(false, window, cx);
2991    }
2992
2993    fn replay_history(&mut self, undo: bool, window: &mut Window, cx: &mut Context<Self>) {
2994        if !self.is_editable() {
2995            return;
2996        }
2997        let replay = if undo {
2998            self.undo_manager.undo()
2999        } else {
3000            self.undo_manager.redo()
3001        };
3002        let Some(replay) = replay else {
3003            return;
3004        };
3005        let token_aware = self.inline_tokens.is_some()
3006            || replay
3007                .changes
3008                .iter()
3009                .any(|change| change.token_delta.is_some());
3010        let emit_events = self.emit_events;
3011        if token_aware {
3012            self.emit_events = false;
3013        }
3014        self.replaying_history = token_aware;
3015        self.undo_manager.set_ignoring(true);
3016        for change in &replay.changes {
3017            let (range, text) = if undo {
3018                (change.new_range.into(), &change.old_text)
3019            } else {
3020                (change.old_range.into(), &change.new_text)
3021            };
3022            let range_utf16 = self.range_to_utf16(&range);
3023            self.replace_text_in_range_silent(Some(range_utf16), text, window, cx);
3024            self.replay_tokens(&range, text.len(), change.token_delta.as_deref(), undo);
3025        }
3026        self.restore_selections(replay.selections);
3027        self.mode
3028            .restore_auto_closed_pairs(replay.auto_closed_pairs.unwrap_or_default());
3029        if token_aware {
3030            // Token replay bypasses IME normalization; a stale marked range
3031            // would otherwise describe text that no longer exists.
3032            self.ime_marked_range = None;
3033        }
3034        self.undo_manager.set_ignoring(false);
3035        self.replaying_history = false;
3036        self.emit_events = emit_events;
3037        if token_aware && emit_events {
3038            cx.emit(InputEvent::Change);
3039        }
3040        cx.notify();
3041    }
3042
3043    /// Restore a set of selections captured in a transaction, clamping offsets
3044    /// to the current text length. `None` leaves the current selections as the
3045    /// replay left them.
3046    fn restore_selections(&mut self, selections: Option<Vec<CursorSelection>>) {
3047        let Some(selections) = selections else {
3048            return;
3049        };
3050
3051        let len = self.text.len();
3052        let restored: Vec<CursorSelection> = selections
3053            .into_iter()
3054            .map(|mut sel| {
3055                sel.start = sel.start.min(len);
3056                sel.end = sel.end.min(len);
3057                sel
3058            })
3059            .collect();
3060        self.selections.replace_all(restored);
3061        self.selections.merge_overlapping();
3062    }
3063
3064    /// Get byte offset of the cursor.
3065    ///
3066    /// The offset is the UTF-8 offset.
3067    pub fn cursor(&self) -> usize {
3068        if let Some(ime_marked_range) = &self.ime_marked_range {
3069            return ime_marked_range.end;
3070        }
3071
3072        self.selections.active().cursor_offset()
3073    }
3074
3075    /// Returns the active selection.
3076    pub(super) fn active_selection(&self) -> &CursorSelection {
3077        self.selections.active()
3078    }
3079
3080    /// Returns a mutable reference to the active selection.
3081    pub(super) fn active_selection_mut(&mut self) -> &mut CursorSelection {
3082        self.selections.active_mut()
3083    }
3084
3085    /// Sets the active selection to the given range, keeping its `reversed`
3086    /// and `column_anchor` state untouched.
3087    pub(super) fn set_selection(&mut self, start: usize, end: usize) {
3088        let range = self.normalize_token_range(start..end);
3089        let (start, end) = (range.start, range.end);
3090        let active = self.active_selection_mut();
3091        active.start = start;
3092        active.end = end;
3093    }
3094
3095    /// Collapses the active selection to a cursor at the given offset,
3096    /// clearing `reversed`.
3097    pub(super) fn set_cursor_to(&mut self, offset: usize) {
3098        let offset = self.token_boundary(offset, Bias::Right);
3099        let active = self.active_selection_mut();
3100        active.start = offset;
3101        active.end = offset;
3102        active.reversed = false;
3103    }
3104
3105    /// Visible row range in the last laid-out viewport, `None` before first layout.
3106    pub fn visible_row_range(&self) -> Option<std::ops::Range<usize>> {
3107        self.last_layout.as_ref().map(|l| l.visible_range.clone())
3108    }
3109
3110    /// Current scroll offset of the editor viewport.
3111    pub fn scroll_offset(&self) -> gpui::Point<gpui::Pixels> {
3112        self.scroll_handle.offset()
3113    }
3114
3115    /// Set scroll offset of the editor viewport.
3116    ///
3117    /// The offset will be clamped to the valid range, and applied after the next layout.
3118    pub fn set_scroll_offset(&mut self, offset: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
3119        self.deferred_scroll_offset = Some(offset);
3120        cx.notify();
3121    }
3122
3123    /// Laid-out line height; `None` before first layout.
3124    pub fn line_height(&self) -> Option<gpui::Pixels> {
3125        self.last_layout.as_ref().map(|l| l.line_height)
3126    }
3127
3128    /// Returns the active selection as a byte range into the text.
3129    ///
3130    /// With multiple cursors, this reads only the active selection.
3131    ///
3132    /// The range is empty (`start == end`) when no text is selected; in
3133    /// that case the offset equals `cursor()`. Byte offsets are measured
3134    /// in the underlying rope's byte units.
3135    pub fn selected_range(&self) -> std::ops::Range<usize> {
3136        (*self.selections.active()).into()
3137    }
3138
3139    pub fn select_all(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3140        self.undo_manager.break_transaction_coalescing();
3141        self.selections.remove_all_but_active();
3142        self.set_selection(0, self.text.len());
3143        cx.notify();
3144    }
3145
3146    /// Set the selected range using UTF-8 byte offsets, removing additional cursors.
3147    ///
3148    /// Non-empty ranges expand to character boundaries. Empty ranges remain empty and are
3149    /// clipped to the preceding character boundary.
3150    pub fn set_selected_range(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3151        let range = self.normalize_token_range(range);
3152        let end_bias = if range.start == range.end {
3153            Bias::Left
3154        } else {
3155            Bias::Right
3156        };
3157        let start = self.text.clip_offset(range.start, Bias::Left);
3158        let end = self.text.clip_offset(range.end, end_bias);
3159
3160        self.move_to(start, None, cx);
3161        self.active_selection_mut().reversed = false;
3162        self.selected_word_range = None;
3163        self.select_to(end, cx);
3164    }
3165
3166    /// Add `range` as an additional selection, keeping the existing ones —
3167    /// the programmatic equivalent of Alt-clicking another cursor into the
3168    /// text.
3169    ///
3170    /// The range is clipped to the text and expanded to character
3171    /// boundaries, like [`Self::set_selected_range`]. A range already
3172    /// covered by a selection adds nothing. Overlapping selections are merged.
3173    /// On a single-line input there is no second cursor to add, so the call
3174    /// replaces the selection instead.
3175    /// The view scrolls to the new selection.
3176    pub fn add_selection(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3177        if !self.is_multi_line() {
3178            self.set_selected_range(range, cx);
3179            return;
3180        }
3181
3182        let range = self.normalize_token_range(range);
3183        let end_bias = if range.start == range.end {
3184            Bias::Left
3185        } else {
3186            Bias::Right
3187        };
3188        let start = self.cursor_boundary(range.start, Bias::Left);
3189        // Match set_selected_range: expand UTF-8 boundaries first, then place
3190        // the selection endpoint without splitting a CRLF newline.
3191        let end = self.cursor_boundary(self.text.clip_offset(range.end, end_bias), Bias::Left);
3192
3193        for sel in self.selections.iter() {
3194            if sel.start <= start && end <= sel.end {
3195                return;
3196            }
3197        }
3198
3199        self.undo_manager.break_transaction_coalescing();
3200        self.selected_word_range = None;
3201        self.pause_blink_cursor(cx);
3202        M::hide_context_menu(self, cx);
3203        M::clear_inline_completion(self, cx);
3204        let id = self.selections.generate_id();
3205        let mut selection = CursorSelection::new(id, start, end);
3206        selection.column_anchor = self.preferred_column_for(end);
3207        self.selections.add(selection);
3208        self.selections.merge_overlapping();
3209        self.scroll_to(end, None, cx);
3210        cx.notify();
3211    }
3212
3213    /// Resolve a mouse position to a byte offset in the text.
3214    ///
3215    /// Also reports the caret's line-end affinity for that offset: `true` when the position
3216    /// landed on the wrap boundary of a non-final visual row, meaning the caret belongs at the
3217    /// end of that row rather than at the start of the next one. Callers that place or extend a
3218    /// selection must pass it on, or clicking past the last glyph of a wrapped row leaves a
3219    /// caret one row below the pointer.
3220    pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> (usize, bool) {
3221        let (offset, line_end_affinity, _) = self.resolve_mouse_position(position);
3222        (offset, line_end_affinity)
3223    }
3224
3225    /// Resolve a mouse position to a text offset, the caret affinity to use for it, and
3226    /// how many columns the pointer sits past the end of that row.
3227    ///
3228    /// Only a columnar selection needs the third value; everywhere else a position past
3229    /// the end of a row means the end of that row, and
3230    /// [`Self::index_for_mouse_position`] is the call to make.
3231    pub(super) fn resolve_mouse_position(&self, position: Point<Pixels>) -> (usize, bool, usize) {
3232        // If the text is empty, always return 0
3233        if self.text.len() == 0 {
3234            return (0, false, 0);
3235        }
3236
3237        let (Some(bounds), Some(last_layout)) =
3238            (self.last_bounds.as_ref(), self.last_layout.as_ref())
3239        else {
3240            return (0, false, 0);
3241        };
3242
3243        let line_height = last_layout.line_height;
3244        let line_number_width = last_layout.line_number_width;
3245
3246        // TIP: About the IBeam cursor
3247        //
3248        // If cursor style is IBeam, the mouse mouse position is in the middle of the cursor (This is special in OS)
3249
3250        // The position is relative to the bounds of the text input
3251        //
3252        // bounds.origin:
3253        //
3254        // - included the input padding.
3255        // - included the scroll offset.
3256        let inner_position = position - bounds.origin - point(line_number_width, px(0.));
3257
3258        let mut y_offset = last_layout.visible_top;
3259        // Position relative to the last line walked, kept for a pointer that ends up
3260        // below every line.
3261        let mut last_line_pos = None;
3262
3263        // Traverse visible buffer lines (compact, no hidden entries)
3264        for (vi, (line_layout, _buffer_line)) in last_layout
3265            .lines
3266            .iter()
3267            .zip(last_layout.visible_buffer_lines.iter())
3268            .enumerate()
3269        {
3270            let line_start_offset = last_layout.visible_line_byte_offsets[vi];
3271
3272            // Calculate line origin for this display row
3273            let line_origin = point(px(0.), y_offset);
3274            let pos = inner_position - line_origin;
3275
3276            // Return offset by use closest_index_for_x if is single line mode.
3277            if self.is_single_line() {
3278                let local_index = line_layout.closest_index_for_x(pos.x, last_layout);
3279                // A single line never wraps, so there is no boundary to disambiguate.
3280                return (
3281                    self.resolve_index(line_start_offset + local_index),
3282                    false,
3283                    0,
3284                );
3285            }
3286
3287            // Check if mouse is in this line's bounds
3288            if let Some((local_index, line_end_affinity)) =
3289                line_layout.closest_index_for_position(pos, last_layout)
3290            {
3291                return (
3292                    self.resolve_index(line_start_offset + local_index),
3293                    line_end_affinity,
3294                    line_layout.columns_past_line_end(pos, last_layout),
3295                );
3296            } else if pos.y < px(0.) {
3297                // Mouse is above this line, return start of this line
3298                return (self.resolve_index(line_start_offset), false, 0);
3299            }
3300
3301            y_offset += line_layout.size(line_height).height;
3302            last_line_pos = Some(pos);
3303        }
3304
3305        // Clamp to the last laid-out row. Returning the end of the whole document
3306        // would select unseen text before drag autoscroll has reached it.
3307        let last_position = last_layout
3308            .lines
3309            .last()
3310            .zip(last_line_pos)
3311            .zip(last_layout.visible_line_byte_offsets.last())
3312            .map(|((line_layout, pos), line_start)| {
3313                let last_row_top = (line_layout.size(line_height).height - line_height).max(px(0.));
3314                let pos = point(pos.x, last_row_top);
3315                (
3316                    self.resolve_index(line_start + line_layout.len()),
3317                    false,
3318                    line_layout.columns_past_line_end(pos, last_layout),
3319                )
3320            });
3321
3322        last_position.unwrap_or((self.text.len(), false, 0))
3323    }
3324
3325    /// Map a display byte index back to a text offset, undoing the mask expansion when the input
3326    /// is masked.
3327    fn resolve_index(&self, index: usize) -> usize {
3328        if self.masked {
3329            self.text.char_index_to_offset(index / MASK_CHAR.len_utf8())
3330        } else {
3331            index.min(self.text.len())
3332        }
3333    }
3334
3335    /// Returns a y offsetted point for the line origin.
3336    /// Select the text from the current cursor position to the given offset.
3337    ///
3338    /// The offset is the UTF-8 offset.
3339    ///
3340    /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
3341    /// Extend a single selection so its moving end lands at `offset`, flipping
3342    /// `reversed` when the ends cross. When a sticky `word_range` is given the
3343    /// selection is kept covering it.
3344    fn extend_selection(
3345        sel: &mut CursorSelection,
3346        offset: usize,
3347        word_range: Option<CursorSelection>,
3348    ) {
3349        if sel.reversed {
3350            sel.start = offset;
3351        } else {
3352            sel.end = offset;
3353        }
3354
3355        if sel.end < sel.start {
3356            sel.reversed = !sel.reversed;
3357            std::mem::swap(&mut sel.start, &mut sel.end);
3358        }
3359
3360        if let Some(word_range) = word_range {
3361            if sel.start > word_range.start {
3362                sel.start = word_range.start;
3363            }
3364            if sel.end < word_range.end {
3365                sel.end = word_range.end;
3366            }
3367        }
3368    }
3369
3370    /// Extend only the active selection to `offset`. Used by mouse drag.
3371    pub(crate) fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
3372        self.select_to_with_affinity(offset, false, cx);
3373    }
3374
3375    /// Like [`Self::select_to`], but also carries the caret's line-end affinity.
3376    ///
3377    /// See [`Self::move_to_with_affinity`] for why the affinity travels with the offset. Note
3378    /// that plain [`Self::select_to`] clears the affinity: every offset it is given came from
3379    /// the text rather than from a visual position, so the caret has no reason to keep sticking
3380    /// to the end of a wrapped row.
3381    pub(crate) fn select_to_with_affinity(
3382        &mut self,
3383        offset: usize,
3384        line_end_affinity: bool,
3385        cx: &mut Context<Self>,
3386    ) {
3387        M::clear_inline_completion(self, cx);
3388
3389        self.cursor_line_end_affinity = line_end_affinity;
3390        let offset = self.cursor_boundary(offset, Bias::Left);
3391        let word_range = self.selected_word_range;
3392        Self::extend_selection(self.active_selection_mut(), offset, word_range);
3393        let range = self.normalize_token_range(self.selected_range());
3394        self.set_selection(range.start, range.end);
3395
3396        if self.active_selection().is_empty() {
3397            self.update_preferred_column();
3398        }
3399        cx.notify()
3400    }
3401
3402    /// Extend every selection to the offset produced by `f`, then merge any
3403    /// selections that now overlap. Used by keyboard selection commands.
3404    fn select_all_cursors_to(
3405        &mut self,
3406        f: impl Fn(&Self, &CursorSelection) -> usize,
3407        cx: &mut Context<Self>,
3408    ) {
3409        self.select_all_cursors_to_with_affinity(|s, sel| (f(s, sel), false), false, cx);
3410    }
3411
3412    /// Extend selections with caret affinity, preserving their column anchors even
3413    /// when vertical movement collapses a selection to a cursor.
3414    fn select_all_cursors_to_with_affinity(
3415        &mut self,
3416        f: impl Fn(&Self, &CursorSelection) -> (usize, bool),
3417        preserve_column: bool,
3418        cx: &mut Context<Self>,
3419    ) {
3420        self.pause_blink_cursor(cx);
3421        self.undo_manager.break_transaction_coalescing();
3422        M::clear_inline_completion(self, cx);
3423
3424        let mut active_affinity = false;
3425        let new_selections: Vec<CursorSelection> = self
3426            .selections
3427            .iter()
3428            .map(|sel| {
3429                let (offset, affinity) = f(self, sel);
3430                if sel.id == self.active_selection().id {
3431                    active_affinity = affinity;
3432                }
3433                let offset = self.cursor_boundary(offset, Bias::Left);
3434                let mut new_sel = *sel;
3435                Self::extend_selection(&mut new_sel, offset, None);
3436                let range = self.normalize_token_range(new_sel.start..new_sel.end);
3437                new_sel.start = range.start;
3438                new_sel.end = range.end;
3439                if !preserve_column {
3440                    new_sel.column_anchor =
3441                        self.preferred_column_for_with_affinity(new_sel.cursor_offset(), affinity);
3442                } else if new_sel.column_anchor.is_none() {
3443                    new_sel.column_anchor = self.preferred_column_for_with_affinity(
3444                        sel.cursor_offset(),
3445                        self.line_end_affinity_for(sel),
3446                    );
3447                }
3448                new_sel
3449            })
3450            .collect();
3451        self.cursor_line_end_affinity = active_affinity;
3452        self.selections.replace_all(new_selections);
3453        self.selections.merge_overlapping();
3454
3455        self.scroll_to(self.cursor(), None, cx);
3456        cx.notify()
3457    }
3458
3459    /// Unselects the currently selected text.
3460    pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3461        self.undo_manager.break_transaction_coalescing();
3462        let offset = self.cursor();
3463        self.set_cursor_to(offset);
3464        cx.notify()
3465    }
3466
3467    #[inline]
3468    pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {
3469        self.text.offset_utf16_to_offset(offset)
3470    }
3471
3472    #[inline]
3473    pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {
3474        self.text.offset_to_offset_utf16(offset)
3475    }
3476
3477    #[inline]
3478    pub(crate) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
3479        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
3480    }
3481
3482    #[inline]
3483    pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
3484        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
3485    }
3486
3487    /// If offset falls on a hidden (folded) line, clamp backward to the end of
3488    /// the fold header line (last visible position before the fold).
3489    fn clamp_offset_to_visible_backward(&self, offset: usize) -> usize {
3490        let line = self.text.offset_to_point(offset).row;
3491        if self.display_map.is_buffer_line_hidden(line) {
3492            for fold in self.display_map.folded_ranges() {
3493                if line > fold.start_line && line <= fold.end_line {
3494                    return self.text.line_end_offset(fold.start_line);
3495                }
3496            }
3497        }
3498        offset
3499    }
3500
3501    /// If offset falls on a hidden (folded) line, clamp forward to the start of
3502    /// the fold end line (first visible position after the fold).
3503    fn clamp_offset_to_visible_forward(&self, offset: usize) -> usize {
3504        let line = self.text.offset_to_point(offset).row;
3505        if self.display_map.is_buffer_line_hidden(line) {
3506            for fold in self.display_map.folded_ranges() {
3507                if line > fold.start_line && line <= fold.end_line {
3508                    return self.text.line_start_offset(fold.end_line);
3509                }
3510            }
3511        }
3512        offset
3513    }
3514
3515    /// Clip a cursor/selection offset without splitting a CRLF newline. This does
3516    /// not alter the rope or its byte/UTF-16 conversion used for exact source APIs.
3517    pub(super) fn cursor_boundary(&self, offset: usize, bias: Bias) -> usize {
3518        let offset = self.token_boundary(self.text.clip_offset(offset, bias), bias);
3519        if offset > 0
3520            && self.text.char_at(offset - 1) == Some('\r')
3521            && self.text.char_at(offset) == Some('\n')
3522        {
3523            if bias == Bias::Left {
3524                offset - 1
3525            } else {
3526                offset + 1
3527            }
3528        } else {
3529            offset
3530        }
3531    }
3532
3533    pub(super) fn previous_boundary(&self, offset: usize) -> usize {
3534        let offset = self.cursor_boundary(offset.saturating_sub(1), Bias::Left);
3535        self.clamp_offset_to_visible_backward(offset)
3536    }
3537
3538    pub(super) fn next_boundary(&self, offset: usize) -> usize {
3539        let offset = self.cursor_boundary(offset.saturating_add(1), Bias::Right);
3540        self.clamp_offset_to_visible_forward(offset)
3541    }
3542
3543    /// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
3544    pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
3545        (self.focus_handle.is_focused(window) || M::is_context_menu_open(self, cx))
3546            && !self.disabled
3547            && self.blink_cursor.read(cx).visible()
3548            && window.is_window_active()
3549    }
3550
3551    fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3552        self.blink_cursor.update(cx, |cursor, cx| {
3553            cursor.start(cx);
3554        });
3555        cx.emit(InputEvent::Focus);
3556    }
3557
3558    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3559        if M::is_context_menu_open(self, cx) {
3560            return;
3561        }
3562
3563        M::hide_context_menu(self, cx);
3564        self.undo_manager.break_transaction_coalescing();
3565
3566        // NOTE: Do not cancel select, when blur.
3567        // Because maybe user want to copy the selected text by AppMenuBar (will take focus handle).
3568
3569        M::clear_hover_state(self, cx);
3570        self.diagnostic_popover = None;
3571        M::clear_inline_completion(self, cx);
3572        self.dismiss_touch_selection(cx);
3573        self.blink_cursor.update(cx, |cursor, cx| {
3574            cursor.stop(cx);
3575        });
3576        self.clamp_number_value(window, cx);
3577        cx.emit(InputEvent::Blur);
3578        cx.notify();
3579    }
3580
3581    /// Clamp the number value to the `min`/`max` range, used on blur.
3582    ///
3583    /// Out-of-range values are allowed while typing (e.g. `1` is an
3584    /// intermediate state of `15` when min is 10), and clamped on blur.
3585    fn clamp_number_value(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3586        if !self.is_single_line() {
3587            return;
3588        }
3589        if !matches!(self.mask_pattern, MaskPattern::Number { .. }) {
3590            return;
3591        }
3592        if self.number_min.is_none() && self.number_max.is_none() {
3593            return;
3594        }
3595
3596        let Ok(value) = self.unmask_value().parse::<f64>() else {
3597            return;
3598        };
3599
3600        let clamped = match (self.number_min, self.number_max) {
3601            (Some(min), _) if value < min => min,
3602            (_, Some(max)) if value > max => max,
3603            _ => return,
3604        };
3605
3606        // The clamped value must pass the `pattern`/`validate` check,
3607        // otherwise keep the value as is.
3608        let new_text = clamped.to_string();
3609        if !self.is_valid_input(&new_text, cx) {
3610            return;
3611        }
3612
3613        let range = self.range_to_utf16(&(0..self.text.len()));
3614        self.replace_text_in_range_silent(Some(range), &new_text, window, cx);
3615    }
3616
3617    pub(super) fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
3618        self.blink_cursor.update(cx, |cursor, cx| {
3619            cursor.pause(cx);
3620        });
3621    }
3622
3623    pub(super) fn on_drag_move(
3624        &mut self,
3625        event: &MouseMoveEvent,
3626        window: &mut Window,
3627        cx: &mut Context<Self>,
3628    ) {
3629        if self.text.len() == 0 {
3630            return;
3631        }
3632
3633        if self.last_layout.is_none() {
3634            return;
3635        }
3636
3637        if !self.focus_handle.is_focused(window) {
3638            return;
3639        }
3640
3641        if !self.selecting {
3642            return;
3643        }
3644
3645        self.auto_scroll.last_drag_position = Some(event.position);
3646        let (offset, line_end_affinity, columns_past_line_end) =
3647            self.resolve_mouse_position(event.position);
3648        if let Some(start) = self.column_select_start {
3649            let end = ColumnarPoint::new(offset, columns_past_line_end);
3650            self.build_columnar_selection(start, end, cx);
3651        } else {
3652            self.select_to_with_affinity(offset, line_end_affinity, cx);
3653        }
3654
3655        if !self.is_single_line() {
3656            let delta = AutoScroll::compute_delta(event.position.y, self.input_bounds);
3657            // Input's ScrollHandle uses negative-y-is-down; negate the positive-towards-bottom delta.
3658            let scroll_delta = delta.map(|d| -d);
3659            self.auto_scroll.set(scroll_delta, cx, |delta, state, cx| {
3660                let current = state.scroll_handle.offset();
3661                state.update_scroll_offset(Some(point(current.x, current.y + delta)), cx);
3662                if let Some(pos) = state.auto_scroll.last_drag_position {
3663                    let (offset, line_end_affinity, columns_past_line_end) =
3664                        state.resolve_mouse_position(pos);
3665                    if let Some(start) = state.column_select_start {
3666                        let end = ColumnarPoint::new(offset, columns_past_line_end);
3667                        state.build_columnar_selection(start, end, cx);
3668                    } else {
3669                        state.select_to_with_affinity(offset, line_end_affinity, cx);
3670                    }
3671                }
3672            });
3673        }
3674    }
3675
3676    /// Normalize the inserted text before applying it to the input.
3677    ///
3678    /// For number inputs (with [`MaskPattern::Number`]), this converts
3679    /// full-width number characters into their ASCII equivalents,
3680    /// e.g. `12。5` -> `12.5`.
3681    pub(super) fn normalize_input<'a>(&self, new_text: &'a str) -> Cow<'a, str> {
3682        let normalized = if matches!(self.mask_pattern, MaskPattern::Number { .. }) {
3683            normalize_number_input(new_text)
3684        } else {
3685            Cow::Borrowed(new_text)
3686        };
3687
3688        if self.is_single_line() && normalized.contains(['\n', '\r']) {
3689            Cow::Owned(normalized.replace(['\n', '\r'], ""))
3690        } else {
3691            normalized
3692        }
3693    }
3694
3695    pub(crate) fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {
3696        if new_text.is_empty() {
3697            return true;
3698        }
3699
3700        if let Some(validate) = &self.validate {
3701            if !validate(new_text, cx) {
3702                return false;
3703            }
3704        }
3705
3706        if !self.mask_pattern.is_valid(new_text) {
3707            return false;
3708        }
3709
3710        let Some(pattern) = &self.pattern else {
3711            return true;
3712        };
3713
3714        pattern.is_match(new_text)
3715    }
3716
3717    /// Set the mask pattern for formatting the input text.
3718    ///
3719    /// The pattern can contain:
3720    /// - 9: Any digit or dot
3721    /// - A: Any letter
3722    /// - *: Any character
3723    /// - Other characters will be treated as literal mask characters
3724    ///
3725    /// Example: "(999)999-999" for phone numbers
3726    pub fn mask_pattern(mut self, pattern: impl Into<MaskPattern>) -> Self {
3727        self.mask_pattern = pattern.into();
3728        self.mask_pattern_set = true;
3729        if let Some(placeholder) = self.mask_pattern.placeholder() {
3730            self.placeholder = placeholder.into();
3731        }
3732        self
3733    }
3734
3735    pub fn set_mask_pattern(
3736        &mut self,
3737        pattern: impl Into<MaskPattern>,
3738        _: &mut Window,
3739        cx: &mut Context<Self>,
3740    ) {
3741        self.mask_pattern = pattern.into();
3742        self.mask_pattern_set = true;
3743        if let Some(placeholder) = self.mask_pattern.placeholder() {
3744            self.placeholder = placeholder.into();
3745        }
3746        cx.notify();
3747    }
3748
3749    /// Apply the default numeric mask unless the caller explicitly selected a mask.
3750    pub fn ensure_number_mask(&mut self) {
3751        if self.mask_pattern_set {
3752            return;
3753        }
3754        self.mask_pattern = MaskPattern::Number {
3755            separator: None,
3756            fraction: None,
3757        };
3758    }
3759
3760    pub(super) fn set_input_bounds(&mut self, new_bounds: Bounds<Pixels>, cx: &mut Context<Self>) {
3761        let wrap_width_changed = self.input_bounds.size.width != new_bounds.size.width;
3762        self.input_bounds = new_bounds;
3763
3764        // Update display_map wrap_width if changed.
3765        if let Some(last_layout) = self.last_layout.as_ref() {
3766            if wrap_width_changed {
3767                let wrap_width = if !self.soft_wrap {
3768                    // None to disable wrapping (will use Pixels::MAX)
3769                    None
3770                } else {
3771                    last_layout.wrap_width
3772                };
3773
3774                self.display_map.on_layout_changed(wrap_width, cx);
3775                if self.is_multi_line() {
3776                    self.mode.update_auto_grow(&self.display_map);
3777                }
3778                cx.notify();
3779            }
3780        }
3781    }
3782
3783    /// Return the active selection's text, borrowed out of the [`Rope`]
3784    /// the state owns.
3785    ///
3786    /// See [`Self::selected_value`] when an owned string is wanted.
3787    pub fn selected_text(&self) -> RopeSlice<'_> {
3788        let range_utf16 = self.range_to_utf16(&self.selected_range());
3789        let range = self.range_from_utf16(&range_utf16);
3790        self.text.slice(range)
3791    }
3792
3793    /// Return the rendered bounds for a UTF-8 byte range in the current input contents.
3794    ///
3795    /// Returns `None` when the requested range is not currently laid out or visible.
3796    pub fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
3797        let Some(last_layout) = self.last_layout.as_ref() else {
3798            return None;
3799        };
3800
3801        let Some(last_bounds) = self.last_bounds else {
3802            return None;
3803        };
3804
3805        let (_, _, start_pos) = self.line_and_position_for_offset(range.start);
3806        let (_, _, end_pos) = self.line_and_position_for_offset(range.end);
3807
3808        let Some(start_pos) = start_pos else {
3809            return None;
3810        };
3811        let Some(end_pos) = end_pos else {
3812            return None;
3813        };
3814
3815        Some(Bounds::from_corners(
3816            last_bounds.origin + start_pos,
3817            last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),
3818        ))
3819    }
3820
3821    /// Replace text in range in silent.
3822    ///
3823    /// This will not trigger any UI interaction, such as auto-completion.
3824    pub(crate) fn replace_text_in_range_silent(
3825        &mut self,
3826        range_utf16: Option<Range<usize>>,
3827        new_text: &str,
3828        window: &mut Window,
3829        cx: &mut Context<Self>,
3830    ) {
3831        self.silent_replace_text = true;
3832        self.replace_text_in_range(range_utf16, new_text, window, cx);
3833        self.silent_replace_text = false;
3834    }
3835
3836    /// Apply a batch of edits as one atomic history transaction.
3837    ///
3838    /// `edits` are `(byte range in the current pre-edit document, replacement)`
3839    /// pairs.
3840    pub(crate) fn replace_text_in_ranges(
3841        &mut self,
3842        edits: &[(Range<usize>, String)],
3843        window: &mut Window,
3844        cx: &mut Context<Self>,
3845    ) {
3846        if !self.is_editable() || edits.is_empty() {
3847            return;
3848        }
3849
3850        // Every edit invalidates provider responses for the previous document,
3851        // including deletion and indentation which do not trigger completion.
3852        M::hide_context_menu(self, cx);
3853        M::clear_inline_completion(self, cx);
3854
3855        // Sort descending by start so applying front-of-vec first edits the
3856        // highest offsets first, leaving lower offsets unchanged.
3857        let mut sorted: Vec<(Range<usize>, &str)> = edits
3858            .iter()
3859            .map(|(range, text)| (self.normalize_token_range(range.clone()), text.as_str()))
3860            .collect();
3861        sorted.sort_by_key(|edit| std::cmp::Reverse(edit.0.start));
3862        if !self.token_spans().is_empty() {
3863            let mut merged: Vec<(Range<usize>, &str)> = Vec::with_capacity(sorted.len());
3864            for (range, text) in sorted {
3865                if let Some(last) = merged
3866                    .last_mut()
3867                    .filter(|last| range.end > last.0.start || range == last.0)
3868                {
3869                    last.0.start = range.start.min(last.0.start);
3870                    last.0.end = range.end.max(last.0.end);
3871                } else {
3872                    merged.push((range, text));
3873                }
3874            }
3875            sorted = merged;
3876        }
3877
3878        #[cfg(debug_assertions)]
3879        for pair in sorted.windows(2) {
3880            debug_assert!(
3881                pair[1].0.end <= pair[0].0.start,
3882                "replace_text_in_ranges requires disjoint ranges"
3883            );
3884        }
3885
3886        // Wrap multiple edits in one explicit transaction so they undo as a
3887        // unit. A single edit records directly, which keeps it eligible for
3888        // the undo manager's typing coalescing.
3889        let requested_intent = self.undo_manager.take_pending_intent();
3890        let selection_before = *self.active_selection();
3891        let original_selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
3892        // Snapshot the cursors before applying, so undo can restore them. A
3893        // delete has already expanded them over the text it removes, so they
3894        // collapse back to where the user left them.
3895        let selections_before: Vec<CursorSelection> = self
3896            .selections
3897            .iter()
3898            .map(|selection| {
3899                Self::collapse_for_intent(
3900                    requested_intent.unwrap_or(EditIntent::Atomic),
3901                    *selection,
3902                    &(selection.start..selection.end),
3903                )
3904            })
3905            .collect();
3906        let group = sorted.len() > 1;
3907        if group {
3908            self.undo_manager.begin_transaction();
3909        }
3910
3911        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
3912        let mut recorded = false;
3913        // Several edits reach the highlighter as one batch, so it reparses
3914        // once for the change instead of once per edit.
3915        let mut highlighter_edits = Vec::new();
3916        for (range, new_text) in &sorted {
3917            let old_text = self.text.clone();
3918            self.mode.adjust_auto_closed_pair(range, new_text.len());
3919            self.text.replace(range.clone(), new_text);
3920
3921            M::adjust_annotations(self, range, new_text.len());
3922            recorded |= self.push_history(
3923                &old_text,
3924                range,
3925                new_text,
3926                requested_intent,
3927                selection_before,
3928                None,
3929            );
3930
3931            // Incremental, single-range updates must run per edit.
3932            self.display_map
3933                .adjust_folds_for_edit(&old_text, range, new_text);
3934            self.display_map
3935                .on_text_changed(&self.text, range, &Rope::from(*new_text), cx);
3936
3937            if group {
3938                if self.is_code_editor() {
3939                    let edit =
3940                        super::mode::replacement_input_edit(&old_text, &self.text, range, new_text);
3941                    highlighter_edits.push((edit, self.text.clone()));
3942                }
3943            } else {
3944                self.mode.update_highlighter(
3945                    super::mode::HighlighterUpdate {
3946                        selected_range: range,
3947                        old_text: &old_text,
3948                        new_text: &self.text,
3949                        change_text: new_text,
3950                        force: true,
3951                    },
3952                    window,
3953                    cx,
3954                );
3955
3956                self.update_fold_candidates_incremental(range, new_text);
3957            }
3958        }
3959
3960        if group {
3961            self.mode
3962                .update_highlighter_batch::<M>(&highlighter_edits, window, cx);
3963
3964            // Fold candidates are read from the reparsed document, so each
3965            // edit is located where it ended up once every edit was applied.
3966            // `sorted` runs from the highest offset down.
3967            let mut shift = 0isize;
3968            for (range, new_text) in sorted.iter().rev() {
3969                let start = (range.start as isize + shift) as usize;
3970                self.update_fold_candidates_incremental(&(start..start), new_text);
3971                shift += new_text.len() as isize - range.len() as isize;
3972            }
3973
3974            self.undo_manager.commit_transaction();
3975        }
3976
3977        // One observable update per batch instead of one per edit.
3978        if let Some(diagnostics) = self.mode.diagnostics_mut() {
3979            diagnostics.reset(&self.text)
3980        }
3981        M::refresh_language_features(self, window, cx);
3982        self.update_search(cx);
3983
3984        // Compute the resulting cursors.
3985        // One collapsed cursor per edit, at the end of its inserted text.
3986        let mut ascending: Vec<(Range<usize>, &str)> = sorted.clone();
3987        ascending.sort_by_key(|edit| edit.0.start);
3988        let text_len = self.text.len();
3989        let mut delta: isize = 0;
3990        let mut edit_results = Vec::with_capacity(ascending.len());
3991        for (range, new_text) in &ascending {
3992            let offset = ((range.start as isize + delta) as usize + new_text.len()).min(text_len);
3993            edit_results.push((range.clone(), offset));
3994            delta += new_text.len() as isize - (range.end as isize - range.start as isize);
3995        }
3996
3997        let mut used = vec![false; edit_results.len()];
3998        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(ascending.len());
3999        for selection in original_selections {
4000            if let Ok(index) = edit_results
4001                .binary_search_by_key(&(selection.start, selection.end), |(range, _)| {
4002                    (range.start, range.end)
4003                })
4004                && !used[index]
4005            {
4006                let offset = edit_results[index].1;
4007                used[index] = true;
4008                let mut selection = selection;
4009                selection.place_at(offset, None);
4010                new_selections.push(selection);
4011            }
4012        }
4013        for (index, (_, offset)) in edit_results.into_iter().enumerate() {
4014            if !used[index] {
4015                new_selections.push(CursorSelection::new(
4016                    self.selections.generate_id(),
4017                    offset,
4018                    offset,
4019                ));
4020            }
4021        }
4022        self.selections.replace_all(new_selections);
4023        self.selections.merge_overlapping();
4024
4025        // Record the cursor snapshots for undo/redo restore.
4026        let selections_after: Vec<CursorSelection> = self.selections.iter().copied().collect();
4027        if recorded {
4028            self.undo_manager.record_auto_closed_pairs(
4029                auto_closed_pairs_before,
4030                self.mode.auto_closed_pairs().clone(),
4031            );
4032            self.undo_manager
4033                .record_selections(selections_before, selections_after);
4034        }
4035
4036        self.ime_marked_range.take();
4037        self.update_preferred_column();
4038        self.dismiss_touch_selection(cx);
4039        if self.is_multi_line() {
4040            self.mode.update_auto_grow(&self.display_map);
4041        }
4042        if self.emit_events {
4043            cx.emit(InputEvent::Change);
4044        }
4045        cx.notify();
4046    }
4047
4048    /// Update fold candidates from tree-sitter syntax tree (full extraction).
4049    /// Used only on initial load or language changes.
4050    fn update_fold_candidates(&mut self) {
4051        if !self.mode.is_folding() {
4052            return;
4053        }
4054
4055        let Some(highlighter_rc) = self.mode.highlighter() else {
4056            return;
4057        };
4058
4059        let highlighter = highlighter_rc.borrow();
4060        let Some(highlighter) = highlighter.as_ref() else {
4061            return;
4062        };
4063
4064        let fold_ranges = highlighter.fold_ranges(&self.text);
4065        self.display_map.set_fold_candidates(fold_ranges);
4066    }
4067
4068    /// Incrementally update fold candidates after a text edit.
4069    /// Only traverses the edited region of the syntax tree instead of the full tree.
4070    fn update_fold_candidates_incremental(&mut self, edit_range: &Range<usize>, new_text: &str) {
4071        if !self.mode.is_folding() {
4072            return;
4073        }
4074
4075        let Some(highlighter_rc) = self.mode.highlighter() else {
4076            return;
4077        };
4078
4079        let highlighter = highlighter_rc.borrow();
4080        let Some(highlighter) = highlighter.as_ref() else {
4081            return;
4082        };
4083
4084        // The new byte range in the updated text after the edit
4085        let new_end = edit_range.start + new_text.len();
4086        self.display_map.update_fold_candidates_for_edit(
4087            |range, text| highlighter.fold_ranges_for_edit(range, text),
4088            edit_range.start..new_end,
4089            &self.text,
4090        );
4091    }
4092}
4093
4094impl<M: InputModeKind> EntityInputHandler for InputBaseState<M> {
4095    fn text_for_range(
4096        &mut self,
4097        range_utf16: Range<usize>,
4098        adjusted_range: &mut Option<Range<usize>>,
4099        _window: &mut Window,
4100        _cx: &mut Context<Self>,
4101    ) -> Option<String> {
4102        let range = self.range_from_utf16(&range_utf16);
4103        adjusted_range.replace(self.range_to_utf16(&range));
4104        Some(self.text.slice(range).to_string())
4105    }
4106
4107    fn selected_text_range(
4108        &mut self,
4109        _ignore_disabled_input: bool,
4110        _window: &mut Window,
4111        _cx: &mut Context<Self>,
4112    ) -> Option<UTF16Selection> {
4113        Some(UTF16Selection {
4114            range: self.range_to_utf16(&self.selected_range()),
4115            reversed: self.active_selection().reversed,
4116        })
4117    }
4118
4119    fn marked_text_range(
4120        &self,
4121        _window: &mut Window,
4122        _cx: &mut Context<Self>,
4123    ) -> Option<Range<usize>> {
4124        self.ime_marked_range
4125            .map(|range| self.range_to_utf16(&range.into()))
4126    }
4127
4128    fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
4129        self.ime_marked_range = None;
4130        self.undo_manager.commit_transaction();
4131    }
4132
4133    /// Replace text in range.
4134    ///
4135    /// - If the new text is invalid, it will not be replaced.
4136    /// - If `range_utf16` is not provided, the current selected range will be used.
4137    fn replace_text_in_range(
4138        &mut self,
4139        range_utf16: Option<Range<usize>>,
4140        new_text: &str,
4141        window: &mut Window,
4142        cx: &mut Context<Self>,
4143    ) {
4144        let requested_intent = self.undo_manager.take_pending_intent();
4145        if !self.is_editable() {
4146            return;
4147        }
4148        let selection_before = *self.active_selection();
4149        // Committing a composition ends the transaction it opened, whether or
4150        // not the platform follows up with `unmark_text`.
4151        let ends_composition = self.ime_marked_range.is_some();
4152
4153        self.pause_blink_cursor(cx);
4154
4155        // NOTE: The normalization keeps the UTF-16 length, but may change the
4156        // UTF-8 byte length, so all the byte-offset calculations below must
4157        // use the normalized text.
4158        let new_text = if self.replaying_history {
4159            Cow::Borrowed(new_text)
4160        } else {
4161            self.normalize_input(new_text)
4162        };
4163        let new_text: &str = &new_text;
4164
4165        let range = range_utf16
4166            .as_ref()
4167            .map(|range_utf16| self.range_from_utf16(range_utf16))
4168            .or(self.ime_marked_range.map(|range| {
4169                let range = self.range_to_utf16(&(range.start..range.end));
4170                self.range_from_utf16(&range)
4171            }))
4172            .unwrap_or(self.selected_range());
4173        let range = self.normalize_token_range(range);
4174
4175        // Skip-over as a pure cursor move: a typed closer that already follows
4176        // the cursor moves past it without touching text or history, so Undo
4177        // never exposes a transient duplicate. Only for interactive single
4178        // keystrokes (current selection, no IME mark, not silent replays).
4179        // `take_pending_intent` above already consumed any request.
4180        if range.is_empty()
4181            && range.start == self.cursor()
4182            && self.ime_marked_range.is_none()
4183            && !self.silent_replace_text
4184            && self.is_code_editor()
4185            && self.selections.is_single()
4186            && self.active_selection().is_empty()
4187        {
4188            if let Some(target) = self.skip_over_target(new_text) {
4189                self.set_cursor_to(target);
4190                self.update_preferred_column();
4191                cx.notify();
4192                return;
4193            }
4194        }
4195
4196        if self.is_multi_line() {
4197            let multi_cursor = range_utf16.is_none()
4198                && self.ime_marked_range.is_none()
4199                && !self.selections.is_single();
4200            if multi_cursor {
4201                self.selections.merge_overlapping();
4202                let mut edits: Vec<(Range<usize>, String)> = self
4203                    .selections
4204                    .iter()
4205                    .map(|sel| (sel.start..sel.end, new_text.to_string()))
4206                    .collect();
4207                edits.sort_by_key(|(range, _)| range.start);
4208                // One keystroke across several cursors is one batch, committed
4209                // with the intent of the keystroke so a run of them coalesces
4210                // into one undo just like single-cursor typing.
4211                let intent =
4212                    requested_intent.unwrap_or_else(|| self.typing_intent(&edits, new_text));
4213                self.undo_manager.begin_transaction_with(intent);
4214                self.undo_manager.set_pending_intent(intent);
4215                self.replace_text_in_ranges(&edits, window, cx);
4216                self.undo_manager.commit_transaction();
4217            } else {
4218                if range_utf16.is_some() {
4219                    self.selections.remove_all_but_active();
4220                }
4221                if let Some(intent) = requested_intent {
4222                    self.undo_manager.set_pending_intent(intent);
4223                }
4224                if let Some((open_len, closer)) = self.auto_close_target(&range, new_text) {
4225                    // One edit keeps the pair atomic even at undo coalescing limits.
4226                    let replacement = format!("{new_text}{closer}");
4227                    self.replace_text_in_ranges(&[(range.clone(), replacement)], window, cx);
4228                    let cursor = range.start + new_text.len();
4229                    self.mode.track_auto_closed_pair(
4230                        cursor - open_len..cursor,
4231                        cursor..cursor + closer.len(),
4232                    );
4233                    self.undo_manager
4234                        .record_auto_closed_pairs_after(self.mode.auto_closed_pairs().clone());
4235                    self.set_cursor_to(cursor);
4236                    self.update_preferred_column();
4237                    self.undo_manager.record_selections(
4238                        vec![selection_before],
4239                        self.selections.iter().copied().collect(),
4240                    );
4241                } else {
4242                    self.replace_text_in_ranges(
4243                        &[(range.clone(), new_text.to_string())],
4244                        window,
4245                        cx,
4246                    );
4247                }
4248            }
4249            if ends_composition {
4250                self.undo_manager.commit_transaction();
4251            }
4252
4253            if !self.silent_replace_text {
4254                M::on_text_typed(self, &range, new_text, window, cx);
4255            }
4256            return;
4257        }
4258
4259        // Single-line path
4260        let old_text = self.text.clone();
4261        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4262        self.text.replace(range.clone(), new_text);
4263
4264        let mut new_offset = (range.start + new_text.len()).min(self.text.len());
4265
4266        // True if the mask has changed the text, e.g. regrouping the
4267        // separators or completing a leading dot.
4268        let mut mask_changed = false;
4269
4270        if self.is_single_line() {
4271            let pending_text = self.text.to_string();
4272            // Check if the new text is valid.
4273            //
4274            // Only reject the edit if the old text was valid, to avoid
4275            // trapping a pre-existing invalid text (e.g. a `default_value`
4276            // that does not conform), the user can still edit to fix it.
4277            if !self.replaying_history
4278                && !self.validated_token_edit
4279                && !self.is_valid_input(&pending_text, cx)
4280                && self.is_valid_input(&old_text.to_string(), cx)
4281            {
4282                self.text = old_text;
4283                return;
4284            }
4285
4286            if !self.replaying_history && !self.mask_pattern.is_none() {
4287                let mask_text = self.mask_pattern.mask(&pending_text);
4288                mask_changed = mask_text.as_str() != pending_text;
4289                self.text = Rope::from(mask_text.as_str());
4290                let new_text_len =
4291                    (new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
4292                new_offset = (range.start + new_text_len).min(mask_text.len());
4293            }
4294        }
4295
4296        if mask_changed {
4297            // Masking rewrites the whole document, so ranges recorded against
4298            // the old text no longer point at anything.
4299            M::reset_annotations(self);
4300        } else {
4301            M::adjust_annotations(self, &range, new_text.len());
4302        }
4303        if mask_changed {
4304            // A segment-based history entry no longer matches the masked
4305            // document, record a whole-document change instead, so that
4306            // undo/redo can restore the text exactly.
4307            self.push_history(
4308                &old_text,
4309                &(0..old_text.len()),
4310                &self.text.to_string(),
4311                Some(EditIntent::Atomic),
4312                selection_before,
4313                Some((new_offset..new_offset).into()),
4314            );
4315        } else {
4316            self.push_history(
4317                &old_text,
4318                &range,
4319                &new_text,
4320                requested_intent,
4321                selection_before,
4322                None,
4323            );
4324        }
4325        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4326            diagnostics.reset(&self.text)
4327        }
4328        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4329        self.display_map
4330            .adjust_folds_for_edit(&old_text, &range, new_text);
4331        self.display_map
4332            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4333
4334        self.mode.update_highlighter::<M>(
4335            super::mode::HighlighterUpdate {
4336                selected_range: &range,
4337                old_text: &old_text,
4338                new_text: &self.text,
4339                change_text: &new_text,
4340                force: true,
4341            },
4342            window,
4343            cx,
4344        );
4345
4346        self.update_fold_candidates_incremental(&range, new_text);
4347        M::refresh_language_features(self, window, cx);
4348        self.set_cursor_to(new_offset);
4349        self.ime_marked_range.take();
4350        // A commit ends the IME composition: macOS delivers `insertText:` for
4351        // the confirmed candidate without a following `unmarkText`, so close
4352        // the transaction here. Leaving it open would keep merging every later
4353        // edit into the same change, which then carries the text and selection
4354        // of the first composition.
4355        if ends_composition {
4356            self.undo_manager
4357                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4358            self.undo_manager.commit_transaction();
4359        }
4360        self.update_preferred_column();
4361        self.update_search(cx);
4362        self.dismiss_touch_selection(cx);
4363        if self.is_multi_line() {
4364            self.mode.update_auto_grow(&self.display_map);
4365        }
4366        if !self.silent_replace_text {
4367            M::on_text_typed(self, &range, &new_text, window, cx);
4368        }
4369        if self.emit_events {
4370            cx.emit(InputEvent::Change);
4371        }
4372        cx.notify();
4373    }
4374
4375    /// Mark text is the IME temporary insert on typing.
4376    fn replace_and_mark_text_in_range(
4377        &mut self,
4378        range_utf16: Option<Range<usize>>,
4379        new_text: &str,
4380        new_selected_range_utf16: Option<Range<usize>>,
4381        window: &mut Window,
4382        cx: &mut Context<Self>,
4383    ) {
4384        let requested_intent = self.undo_manager.take_pending_intent();
4385        if !self.is_editable() {
4386            return;
4387        }
4388        let selection_before = *self.active_selection();
4389
4390        let starts_composition = self.ime_marked_range.is_none();
4391        if starts_composition {
4392            // Even a canceled preedit separates the typing gestures on either
4393            // side; its no-op transaction must not reconnect those gestures.
4394            self.undo_manager.break_transaction_coalescing();
4395            self.undo_manager.begin_transaction();
4396        }
4397
4398        // Collapse any extra cursors so we never leave stale secondary cursors behind.
4399        self.selections.remove_all_but_active();
4400
4401        M::reset_language_features(self);
4402
4403        // See the same NOTE in `replace_text_in_range`.
4404        let new_text = if self.replaying_history {
4405            Cow::Borrowed(new_text)
4406        } else {
4407            self.normalize_input(new_text)
4408        };
4409        let new_text: &str = &new_text;
4410
4411        let range = range_utf16
4412            .as_ref()
4413            .map(|range_utf16| self.range_from_utf16(range_utf16))
4414            .or(self.ime_marked_range.map(|range| {
4415                let range = self.range_to_utf16(&(range.start..range.end));
4416                self.range_from_utf16(&range)
4417            }))
4418            .unwrap_or(self.selected_range());
4419        let range = self.normalize_token_range(range);
4420
4421        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
4422        let old_text = self.text.clone();
4423        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4424        self.text.replace(range.clone(), new_text);
4425
4426        if self.is_single_line() {
4427            let pending_text = self.text.to_string();
4428            // See the same NOTE in `replace_text_in_range`.
4429            if !self.replaying_history
4430                && !self.validated_token_edit
4431                && !self.is_valid_input(&pending_text, cx)
4432                && self.is_valid_input(&old_text.to_string(), cx)
4433            {
4434                self.text = old_text;
4435                if starts_composition {
4436                    self.undo_manager.commit_transaction();
4437                }
4438                return;
4439            }
4440        }
4441
4442        M::adjust_annotations(self, &range, new_text.len());
4443        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4444            diagnostics.reset(&self.text)
4445        }
4446        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4447        self.display_map
4448            .adjust_folds_for_edit(&old_text, &range, new_text);
4449        self.display_map
4450            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4451
4452        self.mode.update_highlighter::<M>(
4453            super::mode::HighlighterUpdate {
4454                selected_range: &range,
4455                old_text: &old_text,
4456                new_text: &self.text,
4457                change_text: &new_text,
4458                force: true,
4459            },
4460            window,
4461            cx,
4462        );
4463
4464        self.update_fold_candidates_incremental(&range, new_text);
4465        M::refresh_language_features(self, window, cx);
4466        if new_text.is_empty() {
4467            // Cancel selection, when cancel IME input.
4468            self.set_cursor_to(range.start);
4469            self.ime_marked_range = None;
4470        } else {
4471            self.ime_marked_range = Some((range.start..range.start + new_text.len()).into());
4472            let new_range = new_selected_range_utf16
4473                .as_ref()
4474                .map(|range_utf16| {
4475                    let new_text = Rope::from(new_text);
4476                    range.start + new_text.offset_utf16_to_offset(range_utf16.start)
4477                        ..range.start + new_text.offset_utf16_to_offset(range_utf16.end)
4478                })
4479                .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
4480            self.set_selection(new_range.start, new_range.end);
4481        }
4482        if self.is_multi_line() {
4483            self.mode.update_auto_grow(&self.display_map);
4484        }
4485        if self.push_history(
4486            &old_text,
4487            &range,
4488            new_text,
4489            requested_intent,
4490            selection_before,
4491            Some(*self.active_selection()),
4492        ) {
4493            self.undo_manager
4494                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4495            self.undo_manager.record_auto_closed_pairs(
4496                auto_closed_pairs_before,
4497                self.mode.auto_closed_pairs().clone(),
4498            );
4499        }
4500        if new_text.is_empty() {
4501            self.undo_manager.commit_transaction();
4502        }
4503        cx.notify();
4504    }
4505
4506    /// Used to position IME candidates.
4507    fn bounds_for_range(
4508        &mut self,
4509        range_utf16: Range<usize>,
4510        bounds: Bounds<Pixels>,
4511        _window: &mut Window,
4512        _cx: &mut Context<Self>,
4513    ) -> Option<Bounds<Pixels>> {
4514        let last_layout = self.last_layout.as_ref()?;
4515        let line_height = last_layout.line_height;
4516        let line_number_width = last_layout.line_number_width;
4517        let range = self.range_from_utf16(&range_utf16);
4518
4519        let mut start_origin = None;
4520        let mut end_origin = None;
4521        let line_number_origin = point(line_number_width, px(0.));
4522        let mut y_offset = last_layout.visible_top;
4523
4524        for (vi, line) in last_layout.lines.iter().enumerate() {
4525            if start_origin.is_some() && end_origin.is_some() {
4526                break;
4527            }
4528
4529            let index_offset = last_layout.visible_line_byte_offsets[vi];
4530
4531            if start_origin.is_none() {
4532                if let Some(p) = line.position_for_index(
4533                    range.start.saturating_sub(index_offset),
4534                    last_layout,
4535                    false,
4536                ) {
4537                    start_origin = Some(p + point(px(0.), y_offset));
4538                }
4539            }
4540
4541            if end_origin.is_none() {
4542                if let Some(p) = line.position_for_index(
4543                    range.end.saturating_sub(index_offset),
4544                    last_layout,
4545                    false,
4546                ) {
4547                    end_origin = Some(p + point(px(0.), y_offset));
4548                }
4549            }
4550
4551            y_offset += line.size(line_height).height;
4552        }
4553
4554        let start_origin = start_origin.or_else(|| {
4555            let offset = self.last_cursor.or(Some(self.cursor()))?;
4556            let (_, _, origin) = self.line_and_position_for_offset(offset);
4557            origin.map(|origin| origin - line_number_origin)
4558        })?;
4559        let mut end_origin = end_origin.unwrap_or(start_origin);
4560        // Ensure at same line.
4561        end_origin.y = start_origin.y;
4562
4563        Some(Bounds::from_corners(
4564            bounds.origin + line_number_origin + start_origin,
4565            // + line_height for show IME panel under the cursor line.
4566            bounds.origin + line_number_origin + point(end_origin.x, end_origin.y + line_height),
4567        ))
4568    }
4569
4570    fn character_index_for_point(
4571        &mut self,
4572        point: gpui::Point<Pixels>,
4573        _window: &mut Window,
4574        _cx: &mut Context<Self>,
4575    ) -> Option<usize> {
4576        let last_layout = self.last_layout.as_ref()?;
4577        let line_point = self.last_bounds?.localize(&point)?;
4578
4579        for (vi, line) in last_layout.lines.iter().enumerate() {
4580            let offset = last_layout.visible_line_byte_offsets[vi];
4581            if let Some(utf8_index) = line.index_for_position(line_point, last_layout) {
4582                return Some(self.offset_to_utf16(offset + utf8_index));
4583            }
4584        }
4585
4586        None
4587    }
4588}
4589
4590impl<M: InputModeKind> Focusable for InputBaseState<M> {
4591    fn focus_handle(&self, _cx: &App) -> FocusHandle {
4592        self.focus_handle.clone()
4593    }
4594}
4595
4596impl<M: InputModeKind> Render for InputBaseState<M> {
4597    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
4598        // Before anything reads it: the element resolves this style during
4599        // layout and paint, and both happen after this call in the same frame.
4600        self.editor_style = self
4601            .projected_editor_style
4602            .resolved(&crate::Theme::global(cx).tokens);
4603        let entity = cx.entity();
4604        if self._pending_update {
4605            self.mode.update_highlighter::<M>(
4606                super::mode::HighlighterUpdate {
4607                    selected_range: &(0..0),
4608                    old_text: &self.text,
4609                    new_text: &self.text,
4610                    change_text: "",
4611                    force: false,
4612                },
4613                window,
4614                cx,
4615            );
4616
4617            self.update_fold_candidates();
4618            M::refresh_language_features(self, window, cx);
4619            self._pending_update = false;
4620        }
4621
4622        let element = div()
4623            .id("input-state")
4624            .key_context(CONTEXT)
4625            .track_focus(&self.focus_handle)
4626            .when(self.is_editable(), |this| {
4627                this.on_action(window.listener_for(&entity, InputBaseState::backspace))
4628                    .on_action(window.listener_for(&entity, InputBaseState::delete))
4629                    .on_action(
4630                        window.listener_for(&entity, InputBaseState::delete_to_beginning_of_line),
4631                    )
4632                    .on_action(window.listener_for(&entity, InputBaseState::delete_to_end_of_line))
4633                    .on_action(window.listener_for(&entity, InputBaseState::delete_previous_word))
4634                    .on_action(window.listener_for(&entity, InputBaseState::delete_next_word))
4635                    .on_action(window.listener_for(&entity, InputBaseState::enter))
4636                    .on_action(window.listener_for(&entity, InputBaseState::escape))
4637                    .on_action(window.listener_for(&entity, InputBaseState::paste))
4638                    .on_action(window.listener_for(&entity, InputBaseState::cut))
4639                    .on_action(window.listener_for(&entity, InputBaseState::undo))
4640                    .on_action(window.listener_for(&entity, InputBaseState::redo))
4641                    .when(self.is_multi_line(), |this| {
4642                        this.on_action(window.listener_for(&entity, InputBaseState::indent_inline))
4643                            .on_action(window.listener_for(&entity, InputBaseState::outdent_inline))
4644                            .on_action(window.listener_for(&entity, InputBaseState::indent_block))
4645                            .on_action(window.listener_for(&entity, InputBaseState::outdent_block))
4646                    })
4647            })
4648            .on_action(window.listener_for(&entity, InputBaseState::left))
4649            .on_action(window.listener_for(&entity, InputBaseState::right))
4650            .on_action(window.listener_for(&entity, InputBaseState::select_left))
4651            .on_action(window.listener_for(&entity, InputBaseState::select_right))
4652            .when(self.is_multi_line(), |this| {
4653                this.on_action(window.listener_for(&entity, InputBaseState::up))
4654                    .on_action(window.listener_for(&entity, InputBaseState::down))
4655                    .on_action(window.listener_for(&entity, InputBaseState::select_up))
4656                    .on_action(window.listener_for(&entity, InputBaseState::select_down))
4657                    .on_action(window.listener_for(&entity, InputBaseState::page_up))
4658                    .on_action(window.listener_for(&entity, InputBaseState::page_down))
4659                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_above))
4660                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_below))
4661            })
4662            .on_action(window.listener_for(&entity, InputBaseState::on_action_select_all))
4663            .on_action(window.listener_for(&entity, InputBaseState::select_to_start_of_line))
4664            .on_action(window.listener_for(&entity, InputBaseState::select_to_end_of_line))
4665            .on_action(window.listener_for(&entity, InputBaseState::select_to_previous_word))
4666            .on_action(window.listener_for(&entity, InputBaseState::select_to_next_word))
4667            .on_action(window.listener_for(&entity, InputBaseState::home))
4668            .on_action(window.listener_for(&entity, InputBaseState::end))
4669            .on_action(window.listener_for(&entity, InputBaseState::move_to_start))
4670            .on_action(window.listener_for(&entity, InputBaseState::move_to_end))
4671            .on_action(window.listener_for(&entity, InputBaseState::move_to_previous_word))
4672            .on_action(window.listener_for(&entity, InputBaseState::move_to_next_word))
4673            .on_action(window.listener_for(&entity, InputBaseState::select_to_start))
4674            .on_action(window.listener_for(&entity, InputBaseState::select_to_end))
4675            .on_action(window.listener_for(&entity, InputBaseState::show_character_palette))
4676            .on_action({
4677                let entity = entity.clone();
4678                move |_: &ActivateToken, window, cx| {
4679                    let state = entity.read(cx);
4680                    let activation = state
4681                        .token_spans()
4682                        .iter()
4683                        .find(|span| span.range() == state.selected_range())
4684                        .and_then(|span| {
4685                            state.range_to_bounds(&span.range()).and_then(|bounds| {
4686                                state.token_activation(
4687                                    span.range().start,
4688                                    bounds,
4689                                    gpui::ClickEvent::Keyboard(gpui::KeyboardClickEvent {
4690                                        bounds,
4691                                        ..Default::default()
4692                                    }),
4693                                )
4694                            })
4695                        });
4696                    if let Some((listener, event)) = activation {
4697                        listener(&event, window, cx);
4698                    }
4699                }
4700            })
4701            .on_action(window.listener_for(&entity, InputBaseState::copy))
4702            .on_action(window.listener_for(&entity, InputBaseState::on_action_search))
4703            .on_action(window.listener_for(&entity, InputBaseState::on_action_replace))
4704            .on_mouse_down(
4705                MouseButton::Left,
4706                window.listener_for(&entity, InputBaseState::on_mouse_down),
4707            )
4708            .on_mouse_down(
4709                MouseButton::Right,
4710                window.listener_for(&entity, InputBaseState::on_mouse_down),
4711            )
4712            .on_mouse_up(
4713                MouseButton::Left,
4714                window.listener_for(&entity, InputBaseState::on_mouse_up),
4715            )
4716            .on_mouse_up(
4717                MouseButton::Right,
4718                window.listener_for(&entity, InputBaseState::on_mouse_up),
4719            )
4720            .on_mouse_move(window.listener_for(&entity, InputBaseState::on_mouse_move))
4721            .on_scroll_wheel(window.listener_for(&entity, InputBaseState::on_scroll_wheel))
4722            .when(self.is_multi_line() && !self.disabled, |this| {
4723                this.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
4724            })
4725            .when(!self.disabled, |this| {
4726                if self.is_multi_line() && window.modifiers().alt {
4727                    this.cursor_crosshair()
4728                } else {
4729                    this.cursor_text()
4730                }
4731            })
4732            .flex_1()
4733            // A single line fills the frame and sits at its vertical center,
4734            // so the frame needs no layout of its own to hold it. It is never
4735            // shorter than its line, though: this root clips (overflow-x
4736            // hidden clips both axes), and a frame whose padding leaves less
4737            // than a line, like Component's Input, would cut off descenders.
4738            .map(|this| {
4739                if self.is_multi_line() {
4740                    this.h_full()
4741                } else {
4742                    this.min_h_full().flex().items_center()
4743                }
4744            })
4745            .flex_grow_1()
4746            .overflow_x_hidden()
4747            .when(self.is_multi_line(), |this| {
4748                this.pt(self.editor_paddings.top)
4749                    .pr(self.editor_paddings.right)
4750                    .pb(self.editor_paddings.bottom)
4751                    .pl(self.editor_paddings.left)
4752            })
4753            .child(TextElement::new(entity.clone()).placeholder(self.placeholder.clone()))
4754            .when(self.shows_scrollbar(), |this| {
4755                this.child(EditorScrollbar::new(entity.clone()))
4756            });
4757
4758        // Actions only one mode handles are registered by that mode, where
4759        // `Self` is concrete enough to name its own entity type.
4760        M::register_actions(element, &entity, window)
4761    }
4762}
4763
4764#[cfg(test)]
4765mod tests {
4766    use super::*;
4767
4768    #[gpui::test]
4769    fn test_multicursor_unicode_completion_uses_post_edit_start(cx: &mut TestAppContext) {
4770        use crate::input::{CompletionProvider, EditorState};
4771        use gpui::Task;
4772        use lsp_types::{CompletionContext, CompletionResponse};
4773        use std::cell::RefCell;
4774
4775        #[derive(Default)]
4776        struct Provider {
4777            starts: RefCell<Vec<usize>>,
4778            queries: RefCell<Vec<(usize, String)>>,
4779        }
4780
4781        impl CompletionProvider for Provider {
4782            fn is_completion_trigger(&self, offset: usize, _: &str, _: &mut App) -> bool {
4783                self.starts.borrow_mut().push(offset);
4784                true
4785            }
4786
4787            fn completions(
4788                &self,
4789                _: &Rope,
4790                offset: usize,
4791                trigger: CompletionContext,
4792                _: &mut Window,
4793                _: &mut App,
4794            ) -> Task<anyhow::Result<CompletionResponse>> {
4795                self.queries
4796                    .borrow_mut()
4797                    .push((offset, trigger.trigger_character.unwrap()));
4798                Task::ready(Ok(CompletionResponse::Array(vec![])))
4799            }
4800        }
4801
4802        cx.update(crate::init);
4803        let mut editor = None;
4804        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
4805            editor = Some(cx.new(|cx| EditorState::new(window, cx).default_value("aa")));
4806            gpui::EmptyView
4807        });
4808        let editor = editor.unwrap();
4809        let provider = Rc::new(Provider::default());
4810        window
4811            .update(cx, |_, window, cx| {
4812                editor.update(cx, |state, cx| {
4813                    state.extras.lsp.completion_provider = Some(provider.clone());
4814                    state
4815                        .extras
4816                        .context_menu_content
4817                        .completion
4818                        .trigger_start_offset = Some(0);
4819                    state.extras.context_menu_content.completion.query = "aa".into();
4820                    state.set_cursor_to(1);
4821                    state.add_cursor_at(0, cx);
4822
4823                    state.replace_text_in_range(None, "中", window, cx);
4824
4825                    assert_eq!(state.value(), "中a中a");
4826                    assert_eq!(provider.starts.borrow().as_slice(), &[4]);
4827                    assert_eq!(provider.queries.borrow().as_slice(), &[(7, "中".into())]);
4828                });
4829            })
4830            .unwrap();
4831    }
4832
4833    #[gpui::test]
4834    fn test_inline_token_wrap_and_size_refresh(cx: &mut TestAppContext) {
4835        use crate::input::{InlineToken, InlineTokenPresentation};
4836        let width = Rc::new(Cell::new(4000.));
4837        let render_width = width.clone();
4838        let view = InputView::build_textarea(cx, |state| state.rows(4).default_value("@a@b"));
4839        view.window_handle
4840            .update(cx, |_, window, cx| {
4841                view.input.update(cx, |state, cx| {
4842                    state
4843                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
4844                        .unwrap();
4845                    state
4846                        .replace_range_with_token(2..4, InlineToken::new("b", "@b"), window, cx)
4847                        .unwrap();
4848                    state.set_token_presentation(
4849                        InlineTokenPresentation::default()
4850                            .token(move |_, _, _| div().w(px(render_width.get()))),
4851                    );
4852                });
4853            })
4854            .unwrap();
4855        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
4856        visual.update(|window, cx| window.draw(cx).clear(cx));
4857        view.input.read_with(&visual, |state, _| {
4858            assert_eq!(state.display_map.wrap_row_count(), 2);
4859            let first = state.range_to_bounds(&(0..2)).unwrap();
4860            let second = state.range_to_bounds(&(2..4)).unwrap();
4861            assert!(second.origin.y > first.origin.y);
4862            assert_eq!(
4863                state.last_layout.as_ref().unwrap().lines[0].wrapped_lines[0].len,
4864                2
4865            );
4866        });
4867        width.set(100.);
4868        view.input.update(&mut visual, |_, cx| cx.notify());
4869        visual.update(|window, cx| window.draw(cx).clear(cx));
4870        view.input.read_with(&visual, |state, _| {
4871            assert_eq!(state.display_map.wrap_row_count(), 1)
4872        });
4873    }
4874
4875    #[gpui::test]
4876    fn test_inline_token_geometry_and_reentrant_activation(cx: &mut TestAppContext) {
4877        use crate::input::{InlineToken, InlineTokenPresentation};
4878        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
4879        let target = view.input.clone();
4880        view.window_handle
4881            .update(cx, |_, window, cx| {
4882                view.input.update(cx, |state, cx| {
4883                    state
4884                        .replace_range_with_token(
4885                            7..13,
4886                            InlineToken::new("a", "@alice"),
4887                            window,
4888                            cx,
4889                        )
4890                        .unwrap();
4891                    state.set_token_presentation(
4892                        InlineTokenPresentation::default()
4893                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
4894                            .on_token_click(move |_, window, cx| {
4895                                target.update(cx, |state, cx| {
4896                                    state.set_value("activated", window, cx)
4897                                })
4898                            }),
4899                    );
4900                    state.focus(window, cx);
4901                    state.set_selected_range(7..13, cx);
4902                });
4903            })
4904            .unwrap();
4905        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
4906        visual.update(|window, cx| window.draw(cx).clear(cx));
4907        visual.update(|window, cx| {
4908            let state = view.input.read(cx);
4909            let bounds = state.range_to_bounds(&(7..13)).unwrap();
4910            assert!((bounds.size.width - px(100.)).abs() < px(1.), "{bounds:?}");
4911            let (left, _) = state.index_for_mouse_position(bounds.origin + point(px(10.), px(5.)));
4912            let (right, _) = state.index_for_mouse_position(bounds.origin + point(px(90.), px(5.)));
4913            assert_eq!((left, right), (7, 13));
4914            window.dispatch_action(Box::new(ActivateToken), cx);
4915        });
4916        visual.run_until_parked();
4917        view.input.read_with(&visual, |state, _| {
4918            assert_eq!(state.value().as_ref(), "activated")
4919        });
4920    }
4921
4922    #[gpui::test]
4923    fn test_inline_token_click_selects_it(cx: &mut TestAppContext) {
4924        use crate::input::InlineToken;
4925        cx.update(crate::init);
4926        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
4927        view.window_handle
4928            .update(cx, |_, window, cx| {
4929                view.input.update(cx, |state, cx| {
4930                    state
4931                        .replace_range_with_token(
4932                            7..13,
4933                            InlineToken::new("a", "@alice"),
4934                            window,
4935                            cx,
4936                        )
4937                        .unwrap();
4938                    state.set_selected_range(0..0, cx);
4939                });
4940            })
4941            .unwrap();
4942        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
4943        visual.update(|window, cx| window.draw(cx).clear(cx));
4944        let bounds = view
4945            .input
4946            .read_with(&visual, |state, _| state.range_to_bounds(&(7..13)).unwrap());
4947        visual.simulate_click(bounds.center(), gpui::Modifiers::default());
4948        visual.run_until_parked();
4949        view.input.read_with(&visual, |state, _| {
4950            assert_eq!(state.selected_range(), 7..13, "a click selects the token");
4951        });
4952    }
4953
4954    #[gpui::test]
4955    fn test_inline_token_hover_reports_presence_without_selecting(cx: &mut TestAppContext) {
4956        use crate::input::{InlineToken, InlineTokenPresentation};
4957        use gpui::{Bounds, point, px, size};
4958        cx.update(crate::init);
4959        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
4960        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
4961        view.window_handle
4962            .update(cx, |_, window, cx| {
4963                view.input.update(cx, |state, cx| {
4964                    state
4965                        .replace_range_with_token(
4966                            7..13,
4967                            InlineToken::new("a", "@alice"),
4968                            window,
4969                            cx,
4970                        )
4971                        .unwrap();
4972                    state.set_token_presentation(
4973                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
4974                    );
4975                    state.set_selected_range(0..0, cx);
4976                });
4977            })
4978            .unwrap();
4979        view.window_handle
4980            .update(cx, |_, _, cx| {
4981                view.input.update(cx, |state, _| {
4982                    let (_, entered) = state
4983                        .token_hover(7, bounds, true, None)
4984                        .expect("hover enters an enabled token");
4985                    assert!(entered.is_hovered());
4986                    assert_eq!(entered.token().id().as_ref(), "a");
4987                    assert_eq!(entered.range(), 7..13);
4988                    assert_eq!(entered.bounds(), bounds);
4989                    let (_, left) = state
4990                        .token_hover(7, bounds, false, None)
4991                        .expect("hover leaves the same token");
4992                    assert!(!left.is_hovered());
4993                    assert_eq!(state.selected_range(), 0..0, "hover never selects");
4994                    assert!(state.token_hover(99, bounds, true, None).is_none());
4995                });
4996            })
4997            .unwrap();
4998
4999        // Disabled tokens suppress hover, matching click; readonly allows it.
5000        view.window_handle
5001            .update(cx, |_, _, cx| {
5002                view.input.update(cx, |state, cx| {
5003                    state.set_disabled(true, cx);
5004                });
5005            })
5006            .unwrap();
5007        view.window_handle
5008            .update(cx, |_, _, cx| {
5009                view.input.update(cx, |state, _| {
5010                    assert!(state.token_hover(7, bounds, true, None).is_none())
5011                })
5012            })
5013            .unwrap();
5014        view.window_handle
5015            .update(cx, |_, _, cx| {
5016                view.input.update(cx, |state, cx| {
5017                    state.set_disabled(false, cx);
5018                    state.set_readonly(true, cx);
5019                });
5020            })
5021            .unwrap();
5022        view.window_handle
5023            .update(cx, |_, _, cx| {
5024                view.input.update(cx, |state, _| {
5025                    assert!(state.token_hover(7, bounds, true, None).is_some())
5026                })
5027            })
5028            .unwrap();
5029
5030        // Without a hover listener there is no hover activation.
5031        view.window_handle
5032            .update(cx, |_, _, cx| {
5033                view.input.update(cx, |state, _| {
5034                    state.set_token_presentation(InlineTokenPresentation::default());
5035                });
5036            })
5037            .unwrap();
5038        view.window_handle
5039            .update(cx, |_, _, cx| {
5040                view.input.update(cx, |state, _| {
5041                    assert!(state.token_hover(7, bounds, true, None).is_none())
5042                })
5043            })
5044            .unwrap();
5045    }
5046
5047    #[gpui::test]
5048    fn test_inline_token_hover_exit_delivered_when_token_removed(cx: &mut TestAppContext) {
5049        use crate::input::{InlineToken, InlineTokenPresentation};
5050        use std::cell::RefCell;
5051        use std::rc::Rc;
5052        cx.update(crate::init);
5053        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5054        let events: Rc<RefCell<Vec<(SharedString, bool)>>> = Rc::new(RefCell::new(Vec::new()));
5055        let sink = events.clone();
5056        view.window_handle
5057            .update(cx, |_, window, cx| {
5058                view.input.update(cx, |state, cx| {
5059                    state
5060                        .replace_range_with_token(
5061                            7..13,
5062                            InlineToken::new("a", "@alice"),
5063                            window,
5064                            cx,
5065                        )
5066                        .unwrap();
5067                    state.set_token_presentation(
5068                        InlineTokenPresentation::default()
5069                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5070                            .on_token_hover(move |event, _, _| {
5071                                sink.borrow_mut()
5072                                    .push((event.token().id().clone(), event.is_hovered()));
5073                            }),
5074                    );
5075                });
5076            })
5077            .unwrap();
5078        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5079        visual.update(|window, cx| window.draw(cx).clear(cx));
5080        let known_bounds = view.input.read_with(&visual, |state, _| {
5081            state
5082                .token_bounds
5083                .get(&7)
5084                .copied()
5085                .expect("placed token bounds")
5086        });
5087        visual.simulate_mouse_move(known_bounds.center(), None, gpui::Modifiers::default());
5088        visual.run_until_parked();
5089        visual.update(|window, cx| window.draw(cx).clear(cx));
5090        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5091
5092        // Remove the token while the pointer is still over its stale row: the
5093        // exit must fire even though the token element that owns the hover
5094        // mask is gone.
5095        view.window_handle
5096            .update(cx, |_, window, cx| {
5097                view.input
5098                    .update(cx, |state, cx| state.set_value("no tokens", window, cx));
5099            })
5100            .unwrap();
5101        visual.run_until_parked();
5102        visual.update(|window, cx| window.draw(cx).clear(cx));
5103        assert_eq!(
5104            events.borrow().as_slice(),
5105            &[("a".into(), true), ("a".into(), false)]
5106        );
5107    }
5108
5109    #[gpui::test]
5110    fn test_inline_token_hover_bounds_stay_single_row_at_wrap_boundary(cx: &mut TestAppContext) {
5111        use crate::input::{InlineToken, InlineTokenPresentation};
5112        cx.update(crate::init);
5113        let view = InputView::build_textarea(cx, |state| {
5114            state
5115                .rows(4)
5116                .default_value("aaaa bbbb cccc dddd @alice eeee ffff")
5117        });
5118        view.window_handle
5119            .update(cx, |_, window, cx| {
5120                view.input.update(cx, |state, cx| {
5121                    state
5122                        .replace_range_with_token(
5123                            20..26,
5124                            InlineToken::new("a", "@alice"),
5125                            window,
5126                            cx,
5127                        )
5128                        .unwrap();
5129                    state.set_token_presentation(
5130                        InlineTokenPresentation::default()
5131                            .token(|_, _, _| div().w(px(60.)).h(px(20.))),
5132                    );
5133                });
5134            })
5135            .unwrap();
5136        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5137        visual.update(|window, cx| window.draw(cx).clear(cx));
5138        let (placed, ranged, line_height) = view.input.read_with(&visual, |state, _| {
5139            (
5140                state
5141                    .token_bounds
5142                    .get(&20)
5143                    .copied()
5144                    .expect("placed token bounds"),
5145                state.range_to_bounds(&(20..26)),
5146                state.last_layout.as_ref().unwrap().line_height,
5147            )
5148        });
5149        // The laid-out element always occupies exactly one row of height, even
5150        // when the range-extent path resolves the same end boundary onto the
5151        // next visual row at a soft-wrap edge.
5152        assert!(placed.size.height <= line_height + px(1.), "{placed:?}");
5153        assert!(
5154            placed.size.width <= px(61.) && placed.size.width > px(0.),
5155            "{placed:?}"
5156        );
5157        if let Some(ranged) = ranged {
5158            assert!(
5159                ranged.size.height >= placed.size.height - px(1.),
5160                "{ranged:?} vs {placed:?}"
5161            );
5162        }
5163    }
5164
5165    #[gpui::test]
5166    fn test_inline_token_hover_same_offset_replacement_exits_predecessor(cx: &mut TestAppContext) {
5167        use crate::input::{InlineToken, InlineTokenPresentation};
5168        use std::{cell::RefCell, rc::Rc};
5169        cx.update(crate::init);
5170        let view = InputView::build(cx, |state| state.default_value("@alice!"));
5171        let events = Rc::new(RefCell::new(Vec::new()));
5172        let sink = events.clone();
5173        view.window_handle
5174            .update(cx, |_, window, cx| {
5175                view.input.update(cx, |state, cx| {
5176                    state
5177                        .replace_range_with_token(0..6, InlineToken::new("a", "@alice"), window, cx)
5178                        .unwrap();
5179                    state.set_token_presentation(
5180                        InlineTokenPresentation::default()
5181                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5182                            .on_token_hover(move |event, _, _| {
5183                                sink.borrow_mut()
5184                                    .push((event.token().id().clone(), event.is_hovered()));
5185                            }),
5186                    );
5187                });
5188            })
5189            .unwrap();
5190        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5191        visual.update(|window, cx| window.draw(cx).clear(cx));
5192        let bounds = view
5193            .input
5194            .read_with(&visual, |state, _| state.token_bounds[&0]);
5195        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5196        visual.run_until_parked();
5197        visual.update(|window, cx| window.draw(cx).clear(cx));
5198        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5199
5200        view.window_handle
5201            .update(cx, |_, window, cx| {
5202                view.input.update(cx, |state, cx| {
5203                    state
5204                        .replace_range_with_token(0..6, InlineToken::new("b", "@alice"), window, cx)
5205                        .unwrap();
5206                });
5207            })
5208            .unwrap();
5209        visual.run_until_parked();
5210        visual.update(|window, cx| window.draw(cx).clear(cx));
5211        visual.simulate_mouse_move(
5212            bounds.center() + gpui::point(px(1.), px(0.)),
5213            None,
5214            gpui::Modifiers::default(),
5215        );
5216        visual.run_until_parked();
5217        visual.update(|window, cx| window.draw(cx).clear(cx));
5218        assert_eq!(
5219            events.borrow().as_slice(),
5220            &[("a".into(), true), ("a".into(), false), ("b".into(), true)]
5221        );
5222        // Disabling keeps the token mounted; re-enabling must reset GPUI's
5223        // retained hover state even with a stationary pointer.
5224        view.input
5225            .update(&mut visual, |state, cx| state.set_disabled(true, cx));
5226        visual.update(|window, cx| window.draw(cx).clear(cx));
5227        visual.run_until_parked();
5228        assert_eq!(events.borrow().last(), Some(&("b".into(), false)));
5229        view.input
5230            .update(&mut visual, |state, cx| state.set_disabled(false, cx));
5231        visual.update(|window, cx| window.draw(cx).clear(cx));
5232        visual.run_until_parked();
5233        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5234        visual.run_until_parked();
5235        assert_eq!(
5236            events.borrow().as_slice(),
5237            &[
5238                ("a".into(), true),
5239                ("a".into(), false),
5240                ("b".into(), true),
5241                ("b".into(), false),
5242                ("b".into(), true)
5243            ]
5244        );
5245        visual.simulate_mouse_move(
5246            bounds.bottom_right() + gpui::point(px(10.), px(10.)),
5247            None,
5248            gpui::Modifiers::default(),
5249        );
5250        visual.run_until_parked();
5251        assert_eq!(
5252            events.borrow().as_slice(),
5253            &[
5254                ("a".into(), true),
5255                ("a".into(), false),
5256                ("b".into(), true),
5257                ("b".into(), false),
5258                ("b".into(), true),
5259                ("b".into(), false)
5260            ]
5261        );
5262    }
5263
5264    #[gpui::test]
5265    fn test_inline_token_hover_older_exit_keeps_newer_snapshot(cx: &mut TestAppContext) {
5266        use crate::input::{InlineToken, InlineTokenPresentation};
5267        use gpui::{Bounds, point, px, size};
5268        cx.update(crate::init);
5269        let view = InputView::build(cx, |state| state.default_value("@a @b"));
5270        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
5271        view.window_handle
5272            .update(cx, |_, window, cx| {
5273                view.input.update(cx, |state, cx| {
5274                    state
5275                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5276                        .unwrap();
5277                    state
5278                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
5279                        .unwrap();
5280                    state.set_token_presentation(
5281                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5282                    );
5283                    // The pointer moves A -> B; GPUI dispatches B's enter
5284                    // before A's exit.
5285                    state
5286                        .token_hover(0, bounds, true, None)
5287                        .expect("hover enters A");
5288                    state
5289                        .token_hover(3, bounds, true, None)
5290                        .expect("hover enters B");
5291                    let (_, exit_a) = state.token_hover(0, bounds, false, None).expect("A exits");
5292                    assert!(!exit_a.is_hovered());
5293                    // B's snapshot must survive A's late exit.
5294                    assert_eq!(
5295                        state
5296                            .hovered_token
5297                            .as_ref()
5298                            .expect("B still hovered")
5299                            .span
5300                            .token()
5301                            .id()
5302                            .as_ref(),
5303                        "b"
5304                    );
5305                    // Removing B afterwards still produces B's reconciled exit.
5306                    state.set_value("no tokens", window, cx);
5307                    state.token_bounds.clear();
5308                    let (listener, exit_b) = state.reconcile_token_hover().expect("B must exit");
5309                    assert!(!exit_b.is_hovered());
5310                    assert_eq!(exit_b.token().id().as_ref(), "b");
5311                    drop(listener);
5312                });
5313            })
5314            .unwrap();
5315    }
5316
5317    #[gpui::test]
5318    fn test_inline_token_hover_late_exit_matches_replaced_token(cx: &mut TestAppContext) {
5319        use crate::input::{InlineToken, InlineTokenPresentation};
5320        cx.update(crate::init);
5321        let view = InputView::build(cx, |state| state.default_value("@a"));
5322        view.window_handle
5323            .update(cx, |_, window, cx| {
5324                view.input.update(cx, |state, cx| {
5325                    let a = InlineToken::new("a", "@a");
5326                    let b = InlineToken::new("b", "@a");
5327                    state
5328                        .replace_range_with_token(0..2, a.clone(), window, cx)
5329                        .unwrap();
5330                    state.set_token_presentation(
5331                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5332                    );
5333                    let bounds = gpui::Bounds::default();
5334                    state.token_hover(0, bounds, true, Some(&a)).unwrap();
5335                    state
5336                        .replace_range_with_token(0..2, b.clone(), window, cx)
5337                        .unwrap();
5338                    state.token_hover(0, bounds, true, Some(&b)).unwrap();
5339                    let (_, exit_a) = state.token_hover(0, bounds, false, Some(&a)).unwrap();
5340                    assert_eq!(exit_a.token().id().as_ref(), "a");
5341                    assert_eq!(state.hovered_token.as_ref().unwrap().span.token(), &b);
5342                    let (_, exit_b) = state.token_hover(0, bounds, false, Some(&b)).unwrap();
5343                    assert_eq!(exit_b.token().id().as_ref(), "b");
5344                    assert!(state.pending_token_hover_exits.is_empty());
5345                });
5346            })
5347            .unwrap();
5348    }
5349
5350    #[gpui::test]
5351    fn test_inline_token_hover_exit_keeps_entry_utf16_after_edit(cx: &mut TestAppContext) {
5352        use crate::input::{InlineToken, InlineTokenPresentation};
5353        use gpui::EntityInputHandler as _;
5354        cx.update(crate::init);
5355        let view = InputView::build(cx, |state| state.default_value("🙂 @a"));
5356        view.window_handle
5357            .update(cx, |_, window, cx| {
5358                view.input.update(cx, |state, cx| {
5359                    state
5360                        .replace_range_with_token(5..7, InlineToken::new("a", "@a"), window, cx)
5361                        .unwrap();
5362                    state.set_token_presentation(
5363                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5364                    );
5365                    let bounds = gpui::Bounds::default();
5366                    let (_, enter) = state.token_hover(5, bounds, true, None).unwrap();
5367                    assert_eq!(enter.range_utf16(), (3, 5));
5368                    state.replace_text_in_range(Some(0..2), "abcd", window, cx);
5369                    assert_eq!(state.token_spans()[0].range(), 5..7);
5370                    let (_, exit) = state.token_hover(5, bounds, false, None).unwrap();
5371                    assert_eq!(exit.range_utf16(), (3, 5));
5372                });
5373            })
5374            .unwrap();
5375    }
5376
5377    #[gpui::test]
5378    fn test_inline_token_hover_callback_edit_preserves_both_exits(cx: &mut TestAppContext) {
5379        use crate::input::{InlineToken, InlineTokenPresentation};
5380        cx.update(crate::init);
5381        let view = InputView::build(cx, |state| state.default_value("@a @b"));
5382        view.window_handle
5383            .update(cx, |_, window, cx| {
5384                view.input.update(cx, |state, cx| {
5385                    state
5386                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5387                        .unwrap();
5388                    state
5389                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
5390                        .unwrap();
5391                    state.set_token_presentation(
5392                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5393                    );
5394                    let bounds = gpui::Bounds::default();
5395                    state.token_hover(0, bounds, true, None).unwrap();
5396                    state.token_hover(3, bounds, true, None).unwrap();
5397                    // Model an edit in B's enter callback before A's queued exit.
5398                    state.set_value("gone", window, cx);
5399                    let (_, exit_a) = state.token_hover(0, bounds, false, None).unwrap();
5400                    assert_eq!(exit_a.token().id().as_ref(), "a");
5401                    let (_, exit_b) = state.reconcile_token_hover().unwrap();
5402                    assert_eq!(exit_b.token().id().as_ref(), "b");
5403                    assert!(state.token_hover(0, bounds, false, None).is_none());
5404                    assert!(state.reconcile_token_hover().is_none());
5405                });
5406            })
5407            .unwrap();
5408    }
5409
5410    #[gpui::test]
5411    fn test_inline_token_edit_history_and_validation(cx: &mut TestAppContext) {
5412        let view = InputView::build(cx, |state| state.default_value("问 @alice!"));
5413        view.window_handle
5414            .update(cx, |_, window, cx| {
5415                view.input.update(cx, |state, cx| {
5416                    let token =
5417                        crate::input::InlineToken::new("alice-1", "@alice").with_label("Alice");
5418                    state
5419                        .replace_range_with_token(4..10, token.clone(), window, cx)
5420                        .unwrap();
5421                    assert_eq!(state.value().as_ref(), "问 @alice!");
5422                    assert_eq!(state.tokens()[0].range(), 4..10);
5423                    assert_eq!(state.next_end_of_word_at(4), 10);
5424                    assert_eq!(state.previous_start_of_word_at(10), 4);
5425                    state.undo(&Undo, window, cx);
5426                    assert!(
5427                        state.tokens().is_empty(),
5428                        "identity-only association is undoable"
5429                    );
5430                    assert_eq!(state.value().as_ref(), "问 @alice!");
5431                    state.redo(&Redo, window, cx);
5432                    assert_eq!(state.tokens()[0].token(), &token);
5433                    state.set_selected_range(6..7, cx);
5434                    assert_eq!(state.selected_range(), 4..10);
5435                    state.replace("", window, cx);
5436                    assert_eq!(state.value().as_ref(), "问 !");
5437                    assert!(state.tokens().is_empty());
5438                    state.undo(&Undo, window, cx);
5439                    assert_eq!(state.tokens()[0].token(), &token);
5440                    state.set_selected_range(0..0, cx);
5441                    state.replace("🙂", window, cx);
5442                    assert_eq!(state.tokens()[0].range(), 8..14);
5443                    state.undo(&Undo, window, cx);
5444                    assert_eq!(state.tokens()[0].range(), 4..10);
5445                    let before = state.content();
5446                    state
5447                        .replace_range_with_token(0..0, token.clone(), window, cx)
5448                        .expect("the same reference may occur twice");
5449                    assert_eq!(state.value().as_ref(), "@alice问 @alice!");
5450                    assert_eq!(state.tokens().len(), 2);
5451                    assert_eq!(state.tokens()[1].token(), &token);
5452                    state.undo(&Undo, window, cx);
5453                    assert_eq!(state.content(), before);
5454                    state.set_value(before.text().clone(), window, cx);
5455                    assert!(state.tokens().is_empty());
5456                    assert!(!state.undo_manager.has_undos());
5457                });
5458            })
5459            .unwrap();
5460    }
5461
5462    #[gpui::test]
5463    fn test_inline_token_textarea_ime_and_modes(cx: &mut TestAppContext) {
5464        use crate::input::{InlineToken, InlineTokenError, InputContent};
5465        let view = InputView::build_textarea(cx, |state| state);
5466        view.window_handle
5467            .update(cx, |_, window, cx| {
5468                view.input.update(cx, |state, cx| {
5469                    let content = InputContent::new("@a@b\n后面")
5470                        .with_token(0..2, InlineToken::new("a", "@a"))
5471                        .unwrap()
5472                        .with_token(2..4, InlineToken::new("b", "@b"))
5473                        .unwrap();
5474                    state.set_value(content.clone(), window, cx);
5475                    state.set_selected_range(2..2, cx);
5476                    assert_eq!(state.previous_boundary(2), 0);
5477                    assert_eq!(state.next_boundary(2), 4);
5478                    state.replace_and_mark_text_in_range(Some(1..2), "中", Some(1..1), window, cx);
5479                    assert_eq!(state.value().as_ref(), "中@b\n后面");
5480                    assert_eq!(state.tokens().len(), 1);
5481                    assert_eq!(
5482                        state.replace_with_token(InlineToken::new("x", "x"), window, cx),
5483                        Err(InlineTokenError::CompositionActive)
5484                    );
5485                    state.replace_text_in_range(None, "中文", window, cx);
5486                    state.undo(&Undo, window, cx);
5487                    assert_eq!(state.content(), content);
5488                    state.redo(&Redo, window, cx);
5489                    assert_eq!(state.value().as_ref(), "中文@b\n后面");
5490                    state.undo(&Undo, window, cx);
5491                    state.replace_all("plain", window, cx);
5492                    assert!(state.tokens().is_empty());
5493                    state.undo(&Undo, window, cx);
5494                    assert_eq!(state.content(), content);
5495                });
5496            })
5497            .unwrap();
5498        let view = InputView::build(cx, |state| state);
5499        view.window_handle
5500            .update(cx, |_, window, cx| {
5501                view.input.update(cx, |state, cx| {
5502                    assert_eq!(
5503                        InputContent::new("a\u{301}")
5504                            .with_token(0..1, InlineToken::new("bad", "a"))
5505                            .err(),
5506                        Some(InlineTokenError::InvalidBoundary)
5507                    );
5508                    assert_eq!(
5509                        InputContent::new("@a@a")
5510                            .with_token(0..2, InlineToken::new("a", "@a"))
5511                            .unwrap()
5512                            .with_token(1..3, InlineToken::new("x", "a@"))
5513                            .err(),
5514                        Some(InlineTokenError::OverlappingTokens)
5515                    );
5516                    state
5517                        .replace_with_token(InlineToken::new("a", "@a"), window, cx)
5518                        .unwrap();
5519                    let before = state.content();
5520                    state.set_masked(true, window, cx);
5521                    assert!(!state.tokens_visible());
5522                    assert_eq!(
5523                        state.replace_with_token(InlineToken::new("b", "b"), window, cx),
5524                        Err(InlineTokenError::UnsupportedMode)
5525                    );
5526                    assert_eq!(state.content(), before);
5527                    state.undo(&Undo, window, cx);
5528                    assert!(state.tokens().is_empty());
5529                    state.redo(&Redo, window, cx);
5530                    assert_eq!(state.content(), before);
5531                    assert!(!state.tokens_visible());
5532                });
5533            })
5534            .unwrap();
5535    }
5536
5537    use crate::theme::Theme;
5538    use gpui::{TestAppContext, VisualTestContext, size};
5539
5540    use crate::input::{EditorMode, EditorState, InputMode, LanguageConfig, TextareaMode};
5541
5542    fn set_test_syntax_provider(
5543        provider: Rc<dyn crate::input::SyntaxContextProvider>,
5544        cx: &mut App,
5545    ) {
5546        struct TestLanguages(Rc<dyn crate::input::SyntaxContextProvider>);
5547        impl crate::input::LanguageProvider for TestLanguages {
5548            fn syntax_context_provider(
5549                &self,
5550                _: &str,
5551            ) -> Option<Rc<dyn crate::input::SyntaxContextProvider>> {
5552                Some(self.0.clone())
5553            }
5554        }
5555        crate::input::set_language_provider(Rc::new(TestLanguages(provider)), cx);
5556    }
5557
5558    struct TestRoot<M: InputModeKind>(Entity<InputBaseState<M>>);
5559
5560    impl<M: InputModeKind> Render for TestRoot<M> {
5561        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5562            div().size_full().child(self.0.clone())
5563        }
5564    }
5565
5566    struct InputView<M: InputModeKind> {
5567        input: Entity<InputBaseState<M>>,
5568        window_handle: gpui::WindowHandle<TestRoot<M>>,
5569    }
5570
5571    /// Helper to open a state of one mode in a window for testing.
5572    impl<M: InputModeKind> InputView<M> {
5573        fn build_with(
5574            cx: &mut TestAppContext,
5575            make: impl FnOnce(&mut Window, &mut Context<InputBaseState<M>>) -> InputBaseState<M>
5576            + 'static,
5577        ) -> Self {
5578            let mut input: Option<Entity<InputBaseState<M>>> = None;
5579
5580            let window = cx.update(|cx| {
5581                cx.open_window(Default::default(), |window, cx| {
5582                    // Set up the theme first
5583                    cx.set_global(Theme::default());
5584                    // Initialize input keybindings
5585                    super::super::init(cx);
5586
5587                    input = Some(cx.new(|cx| make(window, cx)));
5588
5589                    cx.new(|_| TestRoot(input.clone().unwrap()))
5590                })
5591                .unwrap()
5592            });
5593
5594            Self {
5595                input: input.clone().unwrap(),
5596                window_handle: window,
5597            }
5598        }
5599    }
5600
5601    impl InputView<EditorMode> {
5602        /// An editor state, for the tests that exercise code-editor behavior.
5603        fn new(cx: &mut TestAppContext) -> Self {
5604            Self::build_editor(cx, |state| state)
5605        }
5606
5607        fn build_editor(
5608            cx: &mut TestAppContext,
5609            f: impl FnOnce(InputBaseState<EditorMode>) -> InputBaseState<EditorMode> + 'static,
5610        ) -> Self {
5611            Self::build_with(cx, move |window, cx| {
5612                f(crate::input::EditorState::new(window, cx).language("sql"))
5613            })
5614        }
5615    }
5616
5617    impl InputView<TextareaMode> {
5618        fn build_textarea(
5619            cx: &mut TestAppContext,
5620            f: impl FnOnce(InputBaseState<TextareaMode>) -> InputBaseState<TextareaMode> + 'static,
5621        ) -> Self {
5622            Self::build_with(cx, move |window, cx| {
5623                f(crate::input::TextareaState::new(window, cx))
5624            })
5625        }
5626    }
5627
5628    impl InputView<InputMode> {
5629        /// A single-line state, the default these tests were written against.
5630        fn build(
5631            cx: &mut TestAppContext,
5632            f: impl FnOnce(InputBaseState<InputMode>) -> InputBaseState<InputMode> + 'static,
5633        ) -> Self {
5634            Self::build_with(cx, move |window, cx| {
5635                f(crate::input::InputState::new(window, cx))
5636            })
5637        }
5638    }
5639
5640    /// The frame is laid out by the application, which should not have to
5641    /// center a single line inside it.
5642    #[gpui::test]
5643    fn single_line_is_centered_in_a_taller_frame(cx: &mut TestAppContext) {
5644        struct Frame(Entity<InputBaseState<InputMode>>);
5645        impl Render for Frame {
5646            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5647                crate::input::InputBase::new("frame")
5648                    .h(px(60.))
5649                    .child(crate::input::Input::new(&self.0))
5650            }
5651        }
5652
5653        cx.update(crate::init);
5654        let mut input = None;
5655        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5656            let state = cx.new(|cx| crate::input::InputState::new(window, cx).default_value("a"));
5657            input = Some(state.clone());
5658            Frame(state)
5659        });
5660        let input = input.unwrap();
5661        let mut cx = VisualTestContext::from_window(window.into(), cx);
5662        cx.update(|window, cx| window.draw(cx).clear(cx));
5663
5664        input.read_with(&cx, |state, _| {
5665            let line = state.input_bounds;
5666            assert_eq!(line.center().y, px(30.), "line {line:?}");
5667        });
5668    }
5669
5670    #[gpui::test]
5671    fn test_noop_scroll_notifies_diagnostic_dismissal(cx: &mut TestAppContext) {
5672        use std::{cell::Cell, rc::Rc};
5673
5674        cx.update(crate::init);
5675        let mut input = None;
5676        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5677            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
5678            gpui::EmptyView
5679        });
5680        let input = input.unwrap();
5681        input.update(cx, |state, cx| {
5682            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
5683            state.scroll_size = size(px(100.), px(20.));
5684            state.present_diagnostic(crate::input::DiagnosticEntry::default(), cx);
5685        });
5686        let notifications = Rc::new(Cell::new(0));
5687        let count = notifications.clone();
5688        let _subscription =
5689            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
5690
5691        window
5692            .update(cx, |_, window, cx| {
5693                input.update(cx, |state, cx| {
5694                    state.on_scroll_wheel(
5695                        &ScrollWheelEvent {
5696                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
5697                            ..Default::default()
5698                        },
5699                        window,
5700                        cx,
5701                    );
5702                    assert_eq!(state.scroll_handle.offset(), Point::default());
5703                    assert!(state.diagnostic_popover().is_none());
5704                });
5705            })
5706            .unwrap();
5707        assert_eq!(
5708            notifications.get(),
5709            1,
5710            "clearing a diagnostic must notify even when scrolling is clamped"
5711        );
5712        window
5713            .update(cx, |_, window, cx| {
5714                input.update(cx, |state, cx| {
5715                    state.on_scroll_wheel(
5716                        &ScrollWheelEvent {
5717                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
5718                            ..Default::default()
5719                        },
5720                        window,
5721                        cx,
5722                    );
5723                });
5724            })
5725            .unwrap();
5726        assert_eq!(notifications.get(), 1, "an unchanged input must stay quiet");
5727    }
5728
5729    #[gpui::test]
5730    fn test_cursor_layout_consumer_updates_after_selection(cx: &mut TestAppContext) {
5731        use std::{cell::Cell, rc::Rc};
5732        struct Panel {
5733            input: Entity<crate::input::EditorState>,
5734            observed: Rc<Cell<Option<(Bounds<Pixels>, Pixels)>>>,
5735        }
5736        impl Render for Panel {
5737            fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
5738                self.observed.set(self.input.read(cx).cursor_layout());
5739                div().size_full().child(self.input.clone())
5740            }
5741        }
5742        struct Root(Entity<Panel>);
5743        impl Render for Root {
5744            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5745                div().size_full().child(
5746                    self.0
5747                        .clone()
5748                        .cached(gpui::StyleRefinement::default().size_full()),
5749                )
5750            }
5751        }
5752        cx.update(crate::init);
5753        let observed = Rc::new(Cell::new(None));
5754        let mut input = None;
5755        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5756            let state = cx.new(|cx| {
5757                let mut state = crate::input::EditorState::new(window, cx);
5758                state.set_value("abcdefgh", window, cx);
5759                state
5760            });
5761            input = Some(state.clone());
5762            Root(cx.new(|_| Panel {
5763                input: state,
5764                observed: observed.clone(),
5765            }))
5766        });
5767        cx.run_until_parked();
5768        let input = input.unwrap();
5769        let before = input.read_with(cx, |state, _| state.cursor_layout());
5770        // The caret geometry only exists once the input has painted, and a
5771        // notify sent from inside a draw marks the view dirty without asking
5772        // for another frame, so the consumer reads the geometry on the next
5773        // invalidation rather than during the paint that produced it.
5774        input.update(cx, |_, cx| cx.notify());
5775        cx.run_until_parked();
5776        assert_eq!(observed.get(), before);
5777        window
5778            .update(cx, |_, _, cx| {
5779                input.update(cx, |state, cx| state.select_to_with_affinity(3, false, cx));
5780            })
5781            .unwrap();
5782        cx.run_until_parked();
5783        window.update(cx, |_, _, cx| cx.notify()).unwrap();
5784        let after = input.read_with(cx, |state, _| state.cursor_layout());
5785        assert_ne!(after, before, "the caret must actually move");
5786        assert_eq!(
5787            observed.get(),
5788            after,
5789            "render consumers must receive the newly painted caret geometry"
5790        );
5791    }
5792
5793    #[gpui::test]
5794    fn test_input_scroll_offset_notifies_only_on_change(cx: &mut TestAppContext) {
5795        use std::{cell::Cell, rc::Rc};
5796
5797        cx.update(crate::init);
5798        let mut input = None;
5799        let _window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5800            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
5801            gpui::EmptyView
5802        });
5803        let input = input.unwrap();
5804        let notifications = Rc::new(Cell::new(0));
5805        let count = notifications.clone();
5806        let _subscription =
5807            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
5808
5809        input.update(cx, |state, _| {
5810            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
5811            state.scroll_size = size(px(300.), px(20.));
5812        });
5813        for target in [
5814            None,
5815            Some(point(px(0.), px(0.))),
5816            Some(point(px(20.), px(50.))),
5817        ] {
5818            input.update(cx, |state, cx| state.update_scroll_offset(target, cx));
5819            assert_eq!(
5820                notifications.get(),
5821                0,
5822                "an unchanged clamped offset must stay quiet"
5823            );
5824        }
5825        input.update(cx, |state, cx| {
5826            state.update_scroll_offset(Some(point(px(-40.), px(0.))), cx);
5827            assert_eq!(state.scroll_handle.offset(), point(px(-40.), px(0.)));
5828        });
5829        assert_eq!(notifications.get(), 1, "a scroll must notify");
5830        input.update(cx, |state, cx| {
5831            state.update_scroll_offset(Some(point(px(-400.), px(50.))), cx);
5832            assert_eq!(state.scroll_handle.offset(), point(px(-200.), px(0.)));
5833        });
5834        assert_eq!(notifications.get(), 2);
5835        input.update(cx, |state, cx| {
5836            state.update_scroll_offset(Some(point(px(-500.), px(0.))), cx);
5837        });
5838        assert_eq!(
5839            notifications.get(),
5840            2,
5841            "clamping to the current offset must stay quiet"
5842        );
5843        input.update(cx, |state, cx| {
5844            state.scroll_size.width = px(110.);
5845            state.update_scroll_offset(None, cx);
5846            assert_eq!(state.scroll_handle.offset(), point(px(-10.), px(0.)));
5847        });
5848        assert_eq!(
5849            notifications.get(),
5850            3,
5851            "a smaller scroll range must clamp and notify"
5852        );
5853    }
5854
5855    #[gpui::test]
5856    fn test_set_value_on_unfocused_input_stays_quiet(cx: &mut TestAppContext) {
5857        use std::{cell::Cell, rc::Rc};
5858
5859        cx.update(crate::init);
5860        let mut input = None;
5861        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5862            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
5863            gpui::EmptyView
5864        });
5865        let input = input.unwrap();
5866        cx.run_until_parked();
5867
5868        let notifications = Rc::new(Cell::new(0));
5869        let count = notifications.clone();
5870        let _subscription =
5871            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
5872        cx.run_until_parked();
5873
5874        // Seeding a form is a write, so it notifies once. The input is not
5875        // focused and draws no caret, so nothing may notify after that.
5876        window
5877            .update(cx, |_, window, cx| {
5878                input.update(cx, |state, cx| state.set_value("seeded", window, cx));
5879            })
5880            .unwrap();
5881        cx.run_until_parked();
5882        let settled = notifications.get();
5883
5884        cx.executor()
5885            .advance_clock(std::time::Duration::from_secs(3));
5886        cx.run_until_parked();
5887
5888        assert_eq!(
5889            notifications.get(),
5890            settled,
5891            "an unfocused input is blinking, and every blink repaints the view it is in"
5892        );
5893    }
5894
5895    #[gpui::test]
5896    fn test_input_does_not_invalidate_cached_parent_during_paint(cx: &mut TestAppContext) {
5897        use std::{cell::Cell, rc::Rc};
5898
5899        struct Panel {
5900            input: Entity<crate::input::InputState>,
5901            renders: Rc<Cell<usize>>,
5902        }
5903        impl Render for Panel {
5904            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5905                self.renders.set(self.renders.get() + 1);
5906                div().size_full().child(self.input.clone())
5907            }
5908        }
5909        struct Root(Entity<Panel>);
5910        impl Render for Root {
5911            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5912                div().size_full().child(
5913                    self.0
5914                        .clone()
5915                        .cached(gpui::StyleRefinement::default().size_full()),
5916                )
5917            }
5918        }
5919
5920        cx.update(crate::init);
5921        let renders = Rc::new(Cell::new(0));
5922        let mut input = None;
5923        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5924            let state = cx.new(|cx| crate::input::InputState::new(window, cx));
5925            input = Some(state.clone());
5926            Root(cx.new(|_| Panel {
5927                input: state,
5928                renders: renders.clone(),
5929            }))
5930        });
5931        cx.run_until_parked();
5932        let before = renders.get();
5933        assert!(before > 0);
5934        for _ in 0..60 {
5935            window.update(cx, |_, _, cx| cx.notify()).unwrap();
5936        }
5937        assert_eq!(
5938            renders.get(),
5939            before,
5940            "an idle input must not invalidate its cached parent"
5941        );
5942
5943        let input = input.unwrap();
5944        window
5945            .update(cx, |_, window, cx| {
5946                input.update(cx, |state, cx| state.set_value("changed", window, cx));
5947            })
5948            .unwrap();
5949        assert!(
5950            renders.get() > before,
5951            "text changes must invalidate the parent"
5952        );
5953        // The first paint reports the changed text extent to layout consumers.
5954        window.update(cx, |_, _, cx| cx.notify()).unwrap();
5955        let before = renders.get();
5956        for _ in 0..60 {
5957            window.update(cx, |_, _, cx| cx.notify()).unwrap();
5958        }
5959        assert_eq!(renders.get(), before, "the edited input must settle again");
5960
5961        cx.simulate_window_resize(window.into(), size(px(240.), px(100.)));
5962        cx.run_until_parked();
5963        assert!(renders.get() > before, "resizing must invalidate layout");
5964    }
5965
5966    #[gpui::test]
5967    fn textarea_cursor_treats_crlf_as_one_newline(cx: &mut TestAppContext) {
5968        cx.update(crate::init);
5969        let view = InputView::build_textarea(cx, |state| state);
5970        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
5971        cx.update(|window, cx| {
5972            view.input.update(cx, |state, cx| {
5973                for original in ["\r\nlast", "\u{feff}first\r\nlast\n", "first\nlast\r\n"] {
5974                    state.set_value(original, window, cx);
5975                    let newline = original.find('\n').unwrap();
5976                    let end = if original.as_bytes()[newline.saturating_sub(1)] == b'\r' {
5977                        newline - 1
5978                    } else {
5979                        newline
5980                    };
5981                    state.end(&MoveEnd, window, cx);
5982                    assert_eq!(state.cursor(), end);
5983                    state.right(&MoveRight, window, cx);
5984                    assert_eq!(state.cursor(), newline + 1);
5985                    state.left(&MoveLeft, window, cx);
5986                    assert_eq!(state.cursor(), end);
5987                    state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
5988                    assert_eq!(state.selected_range(), end..end);
5989                    state.move_to_end(&MoveToEnd, window, cx);
5990                    assert_eq!(state.cursor(), original.len());
5991                    state.move_to_start(&MoveToStart, window, cx);
5992                    assert_eq!(state.cursor(), 0);
5993                    assert_eq!(state.value().as_bytes(), original.as_bytes());
5994                }
5995                // A lone CR is an ordinary character; it must not skip its neighbour.
5996                state.set_value("a\rb", window, cx);
5997                state.right(&MoveRight, window, cx);
5998                assert_eq!(state.cursor(), 1);
5999                state.right(&MoveRight, window, cx);
6000                assert_eq!(state.cursor(), 2);
6001            })
6002        });
6003    }
6004
6005    #[gpui::test]
6006    fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) {
6007        cx.update(crate::init);
6008
6009        // A single-line input keeps its caret in view by moving its own offset;
6010        // it has no viewport to drag, so a scrollbar in a text field is a
6011        // control that does not exist.
6012        let single = InputView::build(cx, |state| state);
6013        single
6014            .input
6015            .update(cx, |state, _| assert!(!state.shows_scrollbar()));
6016
6017        let multi = InputView::build_textarea(cx, |state| state);
6018        multi
6019            .input
6020            .update(cx, |state, _| assert!(state.shows_scrollbar()));
6021    }
6022
6023    #[gpui::test]
6024    fn context_menu_handler_is_deferred_and_respects_disabled(cx: &mut TestAppContext) {
6025        use std::{cell::Cell, rc::Rc};
6026        cx.update(crate::init);
6027        let input_view = InputView::new(cx);
6028        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6029        let input = input_view.input;
6030        let calls = Rc::new(Cell::new(0usize));
6031        let items = Rc::new(Cell::new(0usize));
6032        let menu_focus = cx.update(|_, cx| cx.focus_handle());
6033
6034        cx.update(|window, cx| {
6035            input.update(cx, |state, cx| {
6036                let calls2 = calls.clone();
6037                let items2 = items.clone();
6038                let menu_focus = menu_focus.clone();
6039                state.on_context_menu(Rc::new(move |menu, _, _, window, cx| {
6040                    calls2.set(calls2.get() + 1);
6041                    items2.set(menu.items.len());
6042                    menu_focus.focus(window, cx);
6043                }));
6044                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
6045            })
6046        });
6047        assert_eq!(calls.get(), 1);
6048        assert_eq!(items.get(), 0);
6049        input.read_with(&cx, |state, _| {
6050            assert_eq!(
6051                state
6052                    .selection_focus
6053                    .as_ref()
6054                    .and_then(WeakFocusHandle::upgrade)
6055                    .as_ref(),
6056                Some(&menu_focus),
6057                "the menu callback must retain selection without reborrowing its input"
6058            );
6059        });
6060
6061        // A custom handler may keep using the same, already-focused menu.
6062        cx.update(|window, cx| {
6063            input.update(cx, |state, cx| {
6064                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
6065            });
6066        });
6067        assert_eq!(calls.get(), 2);
6068        cx.update(|window, cx| {
6069            assert!(input.read(cx).has_selection_focus(window, cx));
6070        });
6071
6072        cx.update(|window, cx| {
6073            input.update(cx, |state, cx| {
6074                state.disabled = true;
6075                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
6076            })
6077        });
6078        assert_eq!(calls.get(), 2);
6079    }
6080
6081    #[gpui::test]
6082    fn context_menu_handler_can_read_its_input(cx: &mut TestAppContext) {
6083        use std::{cell::Cell, rc::Rc};
6084        cx.update(crate::init);
6085        let input_view = InputView::new(cx);
6086        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6087        let input = input_view.input;
6088        let cursor = Rc::new(Cell::new(None));
6089
6090        cx.update(|window, cx| {
6091            input.update(cx, |state, cx| {
6092                state.set_value("hello", window, cx);
6093                let weak = cx.weak_entity();
6094                let cursor = cursor.clone();
6095                state.on_context_menu(Rc::new(move |_, _, _, _, cx| {
6096                    cursor.set(weak.upgrade().map(|input| input.read(cx).cursor()));
6097                }));
6098                state.handle_right_click_menu(point(px(0.), px(0.)), 2, window, cx);
6099            })
6100        });
6101        assert_eq!(cursor.get(), Some(2));
6102    }
6103
6104    #[gpui::test]
6105    fn test_readonly_rejects_user_edits_only(cx: &mut TestAppContext) {
6106        let input_view = InputView::new(cx);
6107        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6108        let input = input_view.input;
6109
6110        cx.update(|window, cx| {
6111            input.update(cx, |state, cx| {
6112                state.set_value("hello", window, cx);
6113                state.set_readonly(true, cx);
6114            });
6115        });
6116
6117        cx.update(|_, cx| {
6118            input.read_with(cx, |state, _| {
6119                assert!(!state.is_editable());
6120                assert!(!state.is_replaceable());
6121            });
6122        });
6123
6124        // Typing (and IME) goes through the input handler, it must be rejected.
6125        cx.update(|window, cx| {
6126            input.update(cx, |state, cx| {
6127                state.replace_text_in_range(None, " world", window, cx);
6128                state.replace_and_mark_text_in_range(None, "あ", None, window, cx);
6129            });
6130        });
6131        cx.update(|_, cx| {
6132            input.read_with(cx, |state, _| assert_eq!(state.value(), "hello"));
6133        });
6134
6135        // The programmatic APIs are not limited by the readonly mode.
6136        cx.update(|window, cx| {
6137            input.update(cx, |state, cx| {
6138                state.insert(" world", window, cx);
6139                state.set_value("changed", window, cx);
6140            });
6141        });
6142        cx.update(|_, cx| {
6143            input.read_with(cx, |state, _| assert_eq!(state.value(), "changed"));
6144        });
6145
6146        // And the user can edit again after leaving the readonly mode.
6147        // The caret is at the start, because `set_value` has reset the selection.
6148        cx.update(|window, cx| {
6149            input.update(cx, |state, cx| {
6150                state.set_readonly(false, cx);
6151                state.replace_text_in_range(None, "!", window, cx);
6152            });
6153        });
6154        cx.update(|_, cx| {
6155            input.read_with(cx, |state, _| {
6156                assert!(state.is_editable());
6157                assert_eq!(state.value(), "!changed");
6158            });
6159        });
6160    }
6161
6162    /// Regression test: editing at a caret that sits far outside the viewport
6163    /// must reveal it in one frame. The cursor-follow in `layout_cursors` runs
6164    /// only on the frame the selection changes, so its old one-line step left
6165    /// the caret offscreen after typing at the end of a long paste.
6166    #[gpui::test]
6167    fn test_edit_reveals_far_offscreen_caret(cx: &mut TestAppContext) {
6168        let mut input = None;
6169        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
6170            cx.set_global(Theme::default());
6171            super::super::init(cx);
6172            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 6));
6173            input = Some(state.clone());
6174            TestRoot(state)
6175        });
6176        let mut cx = VisualTestContext::from_window(window.into(), cx);
6177        let input = input.unwrap();
6178
6179        let text: String = (1..=100)
6180            .map(|i| format!("line {i}"))
6181            .collect::<Vec<_>>()
6182            .join("\n");
6183        cx.update(|window, cx| {
6184            input.update(cx, |state, cx| {
6185                state.set_value(text, window, cx);
6186                let end = state.text.len();
6187                state.set_selection(end, end);
6188            });
6189        });
6190        cx.run_until_parked();
6191
6192        // The user scrolled back to the top to read the pasted text.
6193        cx.update(|_, cx| {
6194            input.update(cx, |state, cx| {
6195                state.scroll_handle.set_offset(point(px(0.), px(0.)));
6196                cx.notify();
6197            });
6198        });
6199        cx.run_until_parked();
6200
6201        cx.update(|window, cx| {
6202            input.update(cx, |state, cx| {
6203                state.replace_text_in_range(None, "X", window, cx);
6204            });
6205        });
6206        cx.run_until_parked();
6207
6208        cx.update(|_, cx| {
6209            input.read_with(cx, |state, _| {
6210                assert!(state.value().ends_with("line 100X"));
6211                let (caret, _) = state.cursor_layout().expect("caret painted");
6212                let viewport = state.input_bounds();
6213                let scroll_y = state.scroll_handle.offset().y;
6214                let top = caret.origin.y - viewport.origin.y + scroll_y;
6215                assert!(
6216                    top >= px(0.) && top + caret.size.height <= viewport.size.height,
6217                    "caret top {top:?} outside viewport height {:?} (scroll {scroll_y:?})",
6218                    viewport.size.height,
6219                );
6220            });
6221        });
6222    }
6223
6224    /// Regression test: changing the auto-grow limits keeps the rows fitted to
6225    /// the text. `set_auto_grow` used to start over at `min_rows`, and the rows
6226    /// were counted again only on the next edit or wrap width change.
6227    #[gpui::test]
6228    fn test_set_auto_grow_keeps_rows_fitted(cx: &mut TestAppContext) {
6229        let mut input = None;
6230        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
6231            cx.set_global(Theme::default());
6232            super::super::init(cx);
6233            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 10));
6234            input = Some(state.clone());
6235            TestRoot(state)
6236        });
6237        let mut cx = VisualTestContext::from_window(window.into(), cx);
6238        let input = input.unwrap();
6239
6240        let text: String = (1..=8)
6241            .map(|i| format!("line {i}"))
6242            .collect::<Vec<_>>()
6243            .join("\n");
6244        cx.update(|window, cx| {
6245            input.update(cx, |state, cx| state.set_value(text, window, cx));
6246        });
6247        cx.run_until_parked();
6248        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 8)));
6249
6250        // A larger maximum keeps all eight rows.
6251        cx.update(|_, cx| input.update(cx, |state, cx| state.set_auto_grow(1, 12, cx)));
6252        cx.run_until_parked();
6253        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 8)));
6254
6255        // A smaller maximum caps them.
6256        cx.update(|_, cx| input.update(cx, |state, cx| state.set_auto_grow(1, 4, cx)));
6257        cx.run_until_parked();
6258        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 4)));
6259    }
6260
6261    /// Regression test: `scroll_to` at end-of-buffer must produce a deferred
6262    /// scroll target within the safe scroll range, so the painted frame
6263    /// matches what `update_scroll_offset` persists (no jitter). A small
6264    /// `cursor_surrounding_lines` override used to mismatch the hardcoded
6265    /// 3-line edge clearance in `scroll_to`, overshooting `safe_y_min`.
6266    #[gpui::test]
6267    fn test_scroll_to_eob_does_not_overshoot_safe_range(cx: &mut TestAppContext) {
6268        let input_view = InputView::new(cx);
6269        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6270        let input = input_view.input;
6271
6272        // JetBrains-style: 1 trailing empty row + 1-line cursor surrounding.
6273        cx.update(|window, cx| {
6274            input.update(cx, |state, cx| {
6275                state.set_scroll_beyond_last_line(Some(1), window, cx);
6276                state.set_cursor_surrounding_lines(Some(1), window, cx);
6277                let text: String = (1..=50)
6278                    .map(|i| format!("line {i}"))
6279                    .collect::<Vec<_>>()
6280                    .join("\n");
6281                state.set_value(text, window, cx);
6282            });
6283        });
6284        cx.run_until_parked();
6285
6286        // Sanity: paint populated `scroll_size` and `input_bounds` — without
6287        // these, `safe_y_min` below collapses to 0 and the assertion is vacuous.
6288        cx.update(|_, cx| {
6289            input.read_with(cx, |state, _| {
6290                assert!(
6291                    state.scroll_size.height > px(0.),
6292                    "scroll_size not populated by initial paint"
6293                );
6294                assert!(
6295                    state.input_bounds.size.height > px(0.),
6296                    "input_bounds not populated by initial paint"
6297                );
6298            });
6299        });
6300
6301        // Move cursor to end with downward direction — same code path as a
6302        // `Down` keystroke at EOB. `scroll_to` runs synchronously inside
6303        // `move_to`; inspect `deferred_scroll_offset` in the same closure
6304        // before the next paint consumes and clears it.
6305        cx.update(|_, cx| {
6306            input.update(cx, |state, cx| {
6307                let end = state.text.len();
6308                state.move_to(end, Some(MoveDirection::Down), cx);
6309
6310                let deferred = state
6311                    .deferred_scroll_offset
6312                    .expect("scroll_to should populate deferred_scroll_offset");
6313                let safe_y_min =
6314                    (-state.scroll_size.height + state.input_bounds.size.height).min(px(0.));
6315
6316                assert!(
6317                    deferred.y >= safe_y_min,
6318                    "deferred_scroll_offset.y = {:?} below safe_y_min = {:?} \
6319                     — paint would jitter (Bug C regression)",
6320                    deferred.y,
6321                    safe_y_min,
6322                );
6323            });
6324        });
6325    }
6326
6327    #[gpui::test]
6328    fn test_next_search_match_reveals_with_padding_after_manual_scroll(cx: &mut TestAppContext) {
6329        assert_search_reveals_with_padding_after_manual_scroll(false, cx);
6330    }
6331
6332    #[gpui::test]
6333    fn test_previous_search_match_reveals_with_padding_after_manual_scroll(
6334        cx: &mut TestAppContext,
6335    ) {
6336        assert_search_reveals_with_padding_after_manual_scroll(true, cx);
6337    }
6338
6339    fn assert_search_reveals_with_padding_after_manual_scroll(
6340        previous: bool,
6341        cx: &mut TestAppContext,
6342    ) {
6343        let input_view = InputView::new(cx);
6344        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6345        let input = input_view.input;
6346        let text = (0..160)
6347            .map(|row| {
6348                if matches!(row, 20 | 60 | 100) {
6349                    format!("match on row {row}")
6350                } else {
6351                    format!("line {row}")
6352                }
6353            })
6354            .collect::<Vec<_>>()
6355            .join("\n");
6356        let start = text.match_indices("match").nth(1).unwrap().0;
6357        let expected_match = start..start + "match".len();
6358        cx.update(|window, cx| {
6359            input.update(cx, |state, cx| {
6360                state.set_cursor_surrounding_lines(Some(3), window, cx);
6361                state.set_value(text, window, cx);
6362                state.set_search_query("match", true, cx);
6363                if previous {
6364                    state.search_session.matcher.next();
6365                    state.search_session.matcher.next();
6366                }
6367            });
6368        });
6369        cx.run_until_parked();
6370        cx.update(|_, cx| {
6371            input.update(cx, |state, cx| {
6372                let line_height = state.last_layout.as_ref().unwrap().line_height;
6373                let y = if previous { px(0.) } else { -line_height * 80. };
6374                state.set_scroll_offset(point(px(0.), y), cx);
6375            });
6376        });
6377        cx.run_until_parked();
6378        input.read_with(&cx, |state, _| {
6379            let visible = state.visible_row_range().unwrap();
6380            if previous {
6381                assert!(visible.end <= 60, "target must be below the viewport");
6382            } else {
6383                assert!(visible.start > 60, "target must be above the viewport");
6384            }
6385        });
6386        cx.update(|_, cx| {
6387            input.update(cx, |state, cx| {
6388                let range = if previous {
6389                    state.previous_search_match(cx)
6390                } else {
6391                    state.next_search_match(cx)
6392                };
6393                assert_eq!(range, Some(expected_match));
6394                assert_eq!(state.search_session.matcher.label(), "2/3");
6395            });
6396        });
6397        cx.run_until_parked();
6398        input.read_with(&cx, |state, _| {
6399            assert!(state.visible_row_range().unwrap().contains(&60));
6400            let line_height = state.last_layout.as_ref().unwrap().line_height;
6401            let target_y = line_height * 60. + state.scroll_handle.offset().y;
6402            // Three lines of edge clearance include the matched line itself.
6403            assert!(target_y >= line_height * 2. - px(0.1));
6404            assert!(
6405                target_y + line_height * 3.
6406                    <= state.last_bounds.as_ref().unwrap().size.height + px(0.1),
6407                "search must preserve the configured surrounding-line padding"
6408            );
6409        });
6410    }
6411
6412    /// A host that wants the search shortcut for its own search UI.
6413    struct SearchHost {
6414        editor: Entity<InputBaseState<EditorMode>>,
6415        search_requests: Rc<Cell<usize>>,
6416    }
6417
6418    impl Render for SearchHost {
6419        fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
6420            let search_requests = self.search_requests.clone();
6421            div()
6422                .size_full()
6423                .on_action(cx.listener(move |_, _: &Search, _, _| {
6424                    search_requests.set(search_requests.get() + 1);
6425                }))
6426                .child(self.editor.clone())
6427        }
6428    }
6429
6430    /// Opens an editor inside [`SearchHost`], focused, and presses the search
6431    /// shortcut once. Returns the editor and the host's request count.
6432    fn press_search_shortcut(
6433        cx: &mut TestAppContext,
6434        searchable: bool,
6435    ) -> (Entity<InputBaseState<EditorMode>>, Rc<Cell<usize>>) {
6436        let search_requests = Rc::new(Cell::new(0));
6437        let mut editor = None;
6438        let window = cx.update(|cx| {
6439            cx.open_window(Default::default(), |window, cx| {
6440                cx.set_global(Theme::default());
6441                super::super::init(cx);
6442                let state =
6443                    cx.new(|cx| crate::input::EditorState::new(window, cx).searchable(searchable));
6444                editor = Some(state.clone());
6445                cx.new(|_| SearchHost {
6446                    editor: state,
6447                    search_requests: search_requests.clone(),
6448                })
6449            })
6450            .unwrap()
6451        });
6452        let editor = editor.unwrap();
6453        let mut cx = VisualTestContext::from_window(window.into(), cx);
6454        cx.update(|window, cx| {
6455            editor.update(cx, |state, cx| state.focus(window, cx));
6456        });
6457        cx.run_until_parked();
6458        #[cfg(target_os = "macos")]
6459        cx.simulate_keystrokes("cmd-f");
6460        #[cfg(not(target_os = "macos"))]
6461        cx.simulate_keystrokes("ctrl-f");
6462        cx.run_until_parked();
6463        (editor, search_requests)
6464    }
6465
6466    #[gpui::test]
6467    fn test_search_shortcut_reaches_the_host_when_not_searchable(cx: &mut TestAppContext) {
6468        let (editor, search_requests) = press_search_shortcut(cx, false);
6469        assert_eq!(search_requests.get(), 1);
6470        editor.read_with(cx, |state, _| {
6471            assert!(!state.search_session().open);
6472            assert!(!state.search_session().is_active());
6473        });
6474    }
6475
6476    #[gpui::test]
6477    fn test_search_shortcut_opens_the_panel_when_searchable(cx: &mut TestAppContext) {
6478        let (editor, search_requests) = press_search_shortcut(cx, true);
6479        assert_eq!(search_requests.get(), 0);
6480        editor.read_with(cx, |state, _| {
6481            assert!(state.search_session().open);
6482            assert!(state.search_session().is_active());
6483        });
6484    }
6485
6486    #[gpui::test]
6487    fn test_set_search_query_highlights_without_the_panel(cx: &mut TestAppContext) {
6488        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
6489        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6490        let input = input_view.input;
6491        cx.update(|window, cx| {
6492            input.update(cx, |state, cx| {
6493                state.set_value("foo bar foo", window, cx);
6494                state.set_search_query("foo", true, cx);
6495            });
6496        });
6497        cx.run_until_parked();
6498        input.read_with(&cx, |state, _| {
6499            let session = state.search_session();
6500            assert!(session.is_active());
6501            assert!(!session.open);
6502            assert_eq!(session.matcher.len(), 2);
6503        });
6504        cx.update(|_, cx| {
6505            input.update(cx, |state, cx| state.close_search(cx));
6506        });
6507        input.read_with(&cx, |state, _| {
6508            assert!(!state.search_session().is_active());
6509        });
6510    }
6511
6512    /// A closed search skips rescanning on edits, so every path that reads
6513    /// the matches again must see the edited text, not the text at close.
6514    #[gpui::test]
6515    fn test_closed_search_resyncs_matches_after_edits(cx: &mut TestAppContext) {
6516        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
6517        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6518        let input = input_view.input;
6519        cx.update(|window, cx| {
6520            input.update(cx, |state, cx| {
6521                state.set_value("foo bar foo", window, cx);
6522                state.set_search_query("foo", true, cx);
6523                state.close_search(cx);
6524
6525                state.set_value("bar foo", window, cx);
6526                assert_eq!(state.next_search_match(cx), Some(4..7));
6527
6528                state.set_value("foo foo foo", window, cx);
6529                state.set_search_query("foo", true, cx);
6530                assert_eq!(state.search_session().matcher.len(), 3);
6531            });
6532        });
6533    }
6534
6535    #[gpui::test]
6536    fn test_search_reveals_offscreen_wrapped_match(cx: &mut TestAppContext) {
6537        let input_view = InputView::new(cx);
6538        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6539        let input = input_view.input;
6540        let text = format!(
6541            "match\n{}\n{}match\n{}",
6542            "line\n".repeat(80),
6543            "wrapped text ".repeat(500),
6544            "line\n".repeat(80)
6545        );
6546        cx.update(|window, cx| {
6547            input.update(cx, |state, cx| {
6548                state.set_cursor_surrounding_lines(Some(3), window, cx);
6549                state.set_value(text, window, cx);
6550                state.set_search_query("match", true, cx);
6551            });
6552        });
6553        cx.run_until_parked();
6554        cx.update(|_, cx| {
6555            input.update(cx, |state, cx| {
6556                state.next_search_match(cx).unwrap();
6557            });
6558        });
6559        cx.run_until_parked();
6560        input.read_with(&cx, |state, _| {
6561            let range = state.search_session.matcher.matched_ranges()[1].clone();
6562            let layout = state.last_layout.as_ref().unwrap();
6563            let (_, _, position) = state.line_and_position_for_offset(range.end);
6564            let y = position.expect("wrapped match must be laid out").y
6565                + state.scroll_handle.offset().y;
6566            assert!(y >= layout.line_height * 2. - px(0.1));
6567            assert!(
6568                y + layout.line_height * 3. <= state.last_bounds.unwrap().size.height + px(0.1),
6569                "wrapped match must retain surrounding display rows"
6570            );
6571        });
6572    }
6573
6574    #[gpui::test]
6575    fn test_number_step(cx: &mut TestAppContext) {
6576        let input = InputView::build(cx, |state| state).input;
6577
6578        cx.update(|cx| {
6579            input.update(cx, |_state, cx| {
6580                assert_eq!(
6581                    NumberStep::from(5.).value(123., StepAction::Increment, cx),
6582                    5.
6583                );
6584
6585                // The step can differ by direction at a boundary: at 1.0 it
6586                // is 0.1 going down and 0.5 going up.
6587                let step = NumberStep::by_value(|value, action, _cx| {
6588                    let below = match action {
6589                        StepAction::Increment => value < 1.0,
6590                        StepAction::Decrement => value <= 1.0,
6591                    };
6592                    if below { 0.1 } else { 0.5 }
6593                });
6594                assert_eq!(step.value(0.5, StepAction::Increment, cx), 0.1);
6595                assert_eq!(step.value(1.0, StepAction::Increment, cx), 0.5);
6596                assert_eq!(step.value(1.0, StepAction::Decrement, cx), 0.1);
6597                assert_eq!(step.value(2.0, StepAction::Decrement, cx), 0.5);
6598            });
6599        });
6600    }
6601
6602    #[gpui::test]
6603    fn test_number_input_normalization(cx: &mut TestAppContext) {
6604        let input_view = InputView::build(cx, |state| {
6605            state.mask_pattern(MaskPattern::Number {
6606                separator: None,
6607                fraction: None,
6608            })
6609        });
6610        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6611        let input = input_view.input;
6612
6613        // Full-width digits and the ideographic full stop are normalized,
6614        // and the cursor is at the end (in normalized bytes, not the
6615        // original 12 bytes).
6616        cx.update(|window, cx| {
6617            input.update(cx, |state, cx| {
6618                state.replace_text_in_range(None, "12。5", window, cx);
6619            });
6620        });
6621        cx.run_until_parked();
6622        cx.update(|_, cx| {
6623            input.read_with(cx, |state, _| {
6624                assert_eq!(state.value(), "12.5");
6625                let cursor: Range<usize> = state.selected_range();
6626                assert_eq!(cursor, 4..4);
6627            });
6628        });
6629
6630        // Non-numeric input is rejected.
6631        cx.update(|window, cx| {
6632            input.update(cx, |state, cx| {
6633                state.replace_text_in_range(None, "abc", window, cx);
6634            });
6635        });
6636        cx.run_until_parked();
6637        cx.update(|_, cx| {
6638            input.read_with(cx, |state, _| {
6639                assert_eq!(state.value(), "12.5");
6640            });
6641        });
6642
6643        // A bare leading dot is kept as-is (normalized from the ideographic
6644        // full stop), not completed to "0.", so it stays editable.
6645        cx.update(|window, cx| {
6646            input.update(cx, |state, cx| {
6647                let range = state.range_to_utf16(&(0..state.text.len()));
6648                state.replace_text_in_range(Some(range), "。", window, cx);
6649            });
6650        });
6651        cx.run_until_parked();
6652        cx.update(|_, cx| {
6653            input.read_with(cx, |state, _| {
6654                assert_eq!(state.value(), ".");
6655                let cursor: Range<usize> = state.selected_range();
6656                assert_eq!(cursor, 1..1);
6657            });
6658        });
6659    }
6660
6661    #[gpui::test]
6662    fn test_mask_pattern_accepts_non_ascii_text(cx: &mut TestAppContext) {
6663        let input_view = InputView::build(cx, |state| state.mask_pattern("9999年99月"));
6664        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6665        let input = input_view.input;
6666
6667        cx.update(|window, cx| {
6668            input.update(cx, |state, cx| {
6669                state.replace_text_in_range(None, "2024年12月", window, cx);
6670            });
6671        });
6672        cx.run_until_parked();
6673        cx.update(|_, cx| {
6674            input.read_with(cx, |state, _| {
6675                assert_eq!(state.value(), "2024年12月");
6676            });
6677        });
6678    }
6679
6680    #[gpui::test]
6681    fn test_number_input_normalization_with_separator(cx: &mut TestAppContext) {
6682        let input_view = InputView::build(cx, |state| {
6683            state.mask_pattern(MaskPattern::Number {
6684                separator: Some(','),
6685                fraction: Some(2),
6686            })
6687        });
6688        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6689        let input = input_view.input;
6690
6691        cx.update(|window, cx| {
6692            input.update(cx, |state, cx| {
6693                state.replace_text_in_range(None, "1234", window, cx);
6694            });
6695        });
6696        cx.run_until_parked();
6697        cx.update(|_, cx| {
6698            input.read_with(cx, |state, _| {
6699                assert_eq!(state.value(), "1,234");
6700                assert_eq!(state.unmask_value(), "1234");
6701            });
6702        });
6703    }
6704
6705    #[gpui::test]
6706    fn test_number_input_clamp_on_blur(cx: &mut TestAppContext) {
6707        let input_view = InputView::build(cx, |state| {
6708            state
6709                .mask_pattern(MaskPattern::Number {
6710                    separator: None,
6711                    fraction: None,
6712                })
6713                .min(10.)
6714                .max(100.)
6715        });
6716        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6717        let input = input_view.input;
6718
6719        // Out-of-range values are allowed while typing, and clamped on blur.
6720        cx.update(|window, cx| {
6721            input.update(cx, |state, cx| {
6722                state.replace_text_in_range(None, "1000", window, cx);
6723                assert_eq!(state.value(), "1000");
6724                state.clamp_number_value(window, cx);
6725                assert_eq!(state.value(), "100");
6726
6727                let range = state.range_to_utf16(&(0..state.text.len()));
6728                state.replace_text_in_range(Some(range), "1", window, cx);
6729                assert_eq!(state.value(), "1");
6730                state.clamp_number_value(window, cx);
6731                assert_eq!(state.value(), "10");
6732            });
6733        });
6734    }
6735
6736    #[gpui::test]
6737    fn test_number_input_undo_with_mask(cx: &mut TestAppContext) {
6738        let input_view = InputView::build(cx, |state| {
6739            state.mask_pattern(MaskPattern::Number {
6740                separator: Some(','),
6741                fraction: None,
6742            })
6743        });
6744        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6745        let input = input_view.input;
6746
6747        // When the mask changes the text (regrouping separators), a
6748        // whole-document change is recorded, so undo/redo can restore it.
6749        cx.update(|window, cx| {
6750            input.update(cx, |state, cx| {
6751                state.replace_text_in_range(None, "1234", window, cx);
6752                assert_eq!(state.value(), "1,234");
6753                state.replace_text_in_range(None, "5", window, cx);
6754                assert_eq!(state.value(), "12,345");
6755
6756                // Each whole-document mask rewrite is an atomic undo step.
6757                // Before the whole-document history fix, undo produced a
6758                // corrupted value like "1,2344".
6759                state.undo(&Undo, window, cx);
6760                assert_eq!(state.value(), "1,234");
6761                state.undo(&Undo, window, cx);
6762                assert_eq!(state.value(), "");
6763                state.redo(&Redo, window, cx);
6764                assert_eq!(state.value(), "1,234");
6765                state.redo(&Redo, window, cx);
6766                assert_eq!(state.value(), "12,345");
6767            });
6768        });
6769    }
6770
6771    #[gpui::test]
6772    fn test_undo_manager_coalesces_adjacent_typing_transactions(cx: &mut TestAppContext) {
6773        let input_view = InputView::build(cx, |state| state);
6774        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6775        let input = input_view.input;
6776
6777        cx.update(|window, cx| {
6778            input.update(cx, |state, cx| {
6779                state.replace_text_in_range(None, "a", window, cx);
6780                state.replace_text_in_range(None, "b", window, cx);
6781                assert_eq!(state.value(), "ab");
6782
6783                state.undo(&Undo, window, cx);
6784                assert_eq!(state.value(), "");
6785            });
6786        });
6787    }
6788
6789    #[gpui::test]
6790    fn test_undo_manager_cursor_movement_splits_typing(cx: &mut TestAppContext) {
6791        let input_view = InputView::build(cx, |state| state);
6792        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6793        let input = input_view.input;
6794
6795        cx.update(|window, cx| {
6796            input.update(cx, |state, cx| {
6797                state.replace_text_in_range(None, "a", window, cx);
6798                state.replace_text_in_range(None, "b", window, cx);
6799                state.left(&MoveLeft, window, cx);
6800                state.replace_text_in_range(None, "x", window, cx);
6801                assert_eq!(state.value(), "axb");
6802
6803                state.undo(&Undo, window, cx);
6804                assert_eq!(state.value(), "ab");
6805                state.undo(&Undo, window, cx);
6806                assert_eq!(state.value(), "");
6807            });
6808        });
6809    }
6810
6811    #[gpui::test]
6812    fn test_undo_manager_splits_backward_and_forward_delete(cx: &mut TestAppContext) {
6813        let input_view = InputView::build(cx, |state| state);
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                state.set_value("abcd", window, cx);
6820                state.set_selected_range(2..2, cx);
6821                state.backspace(&Backspace, window, cx);
6822                state.delete(&Delete, window, cx);
6823                assert_eq!(state.value(), "ad");
6824
6825                state.undo(&Undo, window, cx);
6826                assert_eq!(state.value(), "acd");
6827                state.undo(&Undo, window, cx);
6828                assert_eq!(state.value(), "abcd");
6829            });
6830        });
6831    }
6832
6833    #[gpui::test]
6834    fn test_undo_manager_coalesces_directional_character_deletes(cx: &mut TestAppContext) {
6835        let input_view = InputView::build(cx, |state| state);
6836        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6837        let input = input_view.input;
6838
6839        cx.update(|window, cx| {
6840            input.update(cx, |state, cx| {
6841                state.set_value("abcd", window, cx);
6842                state.backspace(&Backspace, window, cx);
6843                state.backspace(&Backspace, window, cx);
6844                assert_eq!(state.value(), "ab");
6845                state.undo(&Undo, window, cx);
6846                assert_eq!(state.value(), "abcd");
6847                assert_eq!(state.selected_range(), 4..4);
6848
6849                state.set_value("abcd", window, cx);
6850                state.set_selected_range(1..1, cx);
6851                state.delete(&Delete, window, cx);
6852                state.delete(&Delete, window, cx);
6853                assert_eq!(state.value(), "ad");
6854                state.undo(&Undo, window, cx);
6855                assert_eq!(state.value(), "abcd");
6856                assert_eq!(state.selected_range(), 1..1);
6857            });
6858        });
6859    }
6860
6861    #[gpui::test]
6862    fn test_undo_manager_atomic_paste_isolated_from_typing(cx: &mut TestAppContext) {
6863        let input_view = InputView::build(cx, |state| state);
6864        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6865        let input = input_view.input;
6866
6867        cx.update(|window, cx| {
6868            cx.write_to_clipboard(ClipboardItem::new_string("P".to_string()));
6869            input.update(cx, |state, cx| {
6870                state.replace_text_in_range(None, "a", window, cx);
6871                state.paste(&Paste, window, cx);
6872                state.replace_text_in_range(None, "b", window, cx);
6873                assert_eq!(state.value(), "aPb");
6874
6875                state.undo(&Undo, window, cx);
6876                assert_eq!(state.value(), "aP");
6877                state.undo(&Undo, window, cx);
6878                assert_eq!(state.value(), "a");
6879                state.undo(&Undo, window, cx);
6880                assert_eq!(state.value(), "");
6881            });
6882        });
6883    }
6884
6885    #[gpui::test]
6886    fn test_undo_manager_programmatic_insert_is_atomic(cx: &mut TestAppContext) {
6887        let input_view = InputView::build(cx, |state| state);
6888        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6889        let input = input_view.input;
6890
6891        cx.update(|window, cx| {
6892            input.update(cx, |state, cx| {
6893                state.replace_text_in_range(None, "a", window, cx);
6894                state.insert("P", window, cx);
6895                state.replace_text_in_range(None, "b", window, cx);
6896                assert_eq!(state.value(), "aPb");
6897
6898                state.undo(&Undo, window, cx);
6899                assert_eq!(state.value(), "aP");
6900                state.undo(&Undo, window, cx);
6901                assert_eq!(state.value(), "a");
6902            });
6903        });
6904    }
6905
6906    #[gpui::test]
6907    fn test_undo_manager_selection_round_trip_splits_typing(cx: &mut TestAppContext) {
6908        let input_view = InputView::build(cx, |state| state);
6909        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6910        let input = input_view.input;
6911
6912        cx.update(|window, cx| {
6913            input.update(cx, |state, cx| {
6914                state.replace_text_in_range(None, "a", window, cx);
6915                state.replace_text_in_range(None, "b", window, cx);
6916                state.select_all(window, cx);
6917                state.unselect(window, cx);
6918                state.replace_text_in_range(None, "c", window, cx);
6919
6920                state.undo(&Undo, window, cx);
6921                assert_eq!(state.value(), "ab");
6922                state.undo(&Undo, window, cx);
6923                assert_eq!(state.value(), "");
6924            });
6925        });
6926    }
6927
6928    #[gpui::test]
6929    fn test_undo_manager_enter_is_atomic(cx: &mut TestAppContext) {
6930        let input_view = InputView::build_textarea(cx, |state| state);
6931        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6932        let input = input_view.input;
6933
6934        cx.update(|window, cx| {
6935            input.update(cx, |state, cx| {
6936                state.replace_text_in_range(None, "a", window, cx);
6937                state.enter(
6938                    &Enter {
6939                        secondary: false,
6940                        shift: false,
6941                    },
6942                    window,
6943                    cx,
6944                );
6945                state.replace_text_in_range(None, "b", window, cx);
6946
6947                state.undo(&Undo, window, cx);
6948                assert_eq!(state.value(), "a\n");
6949                state.undo(&Undo, window, cx);
6950                assert_eq!(state.value(), "a");
6951                state.undo(&Undo, window, cx);
6952                assert_eq!(state.value(), "");
6953            });
6954        });
6955    }
6956
6957    #[gpui::test]
6958    fn test_undo_manager_single_line_return_commits_the_typing_session(cx: &mut TestAppContext) {
6959        let input_view = InputView::build(cx, |state| state);
6960        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6961        let input = input_view.input;
6962
6963        cx.update(|window, cx| {
6964            input.update(cx, |state, cx| {
6965                for part in ["a", "b", "c"] {
6966                    state.replace_text_in_range(None, part, window, cx);
6967                }
6968                state.enter(
6969                    &Enter {
6970                        secondary: false,
6971                        shift: false,
6972                    },
6973                    window,
6974                    cx,
6975                );
6976                for part in ["d", "e", "f"] {
6977                    state.replace_text_in_range(None, part, window, cx);
6978                }
6979                assert_eq!(state.value(), "abcdef");
6980
6981                state.undo(&Undo, window, cx);
6982                assert_eq!(state.value(), "abc");
6983                state.undo(&Undo, window, cx);
6984                assert_eq!(state.value(), "");
6985            });
6986        });
6987    }
6988
6989    #[gpui::test]
6990    fn test_undo_manager_submit_on_enter_commits_the_textarea_session(cx: &mut TestAppContext) {
6991        let input_view = InputView::build_textarea(cx, |state| state.submit_on_enter(true));
6992        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
6993        let input = input_view.input;
6994
6995        cx.update(|window, cx| {
6996            input.update(cx, |state, cx| {
6997                state.replace_text_in_range(None, "before submit", window, cx);
6998                state.enter(
6999                    &Enter {
7000                        secondary: false,
7001                        shift: false,
7002                    },
7003                    window,
7004                    cx,
7005                );
7006                state.replace_text_in_range(None, " after submit", window, cx);
7007                assert_eq!(state.value(), "before submit after submit");
7008
7009                state.undo(&Undo, window, cx);
7010                assert_eq!(state.value(), "before submit");
7011                state.undo(&Undo, window, cx);
7012                assert_eq!(state.value(), "");
7013            });
7014        });
7015    }
7016
7017    #[gpui::test]
7018    fn test_undo_manager_blur_commits_the_typing_session(cx: &mut TestAppContext) {
7019        let input_view = InputView::build(cx, |state| state);
7020        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7021        let input = input_view.input;
7022
7023        cx.update(|window, cx| {
7024            input.update(cx, |state, cx| {
7025                state.replace_text_in_range(None, "before blur", window, cx);
7026                state.on_blur(window, cx);
7027                state.on_focus(window, cx);
7028                state.replace_text_in_range(None, " after focus", window, cx);
7029                assert_eq!(state.value(), "before blur after focus");
7030
7031                state.undo(&Undo, window, cx);
7032                assert_eq!(state.value(), "before blur");
7033                state.undo(&Undo, window, cx);
7034                assert_eq!(state.value(), "");
7035            });
7036        });
7037    }
7038
7039    #[gpui::test]
7040    fn test_undo_manager_keeps_rapid_lines_in_distinct_transactions(cx: &mut TestAppContext) {
7041        let input_view = InputView::build_textarea(cx, |state| state);
7042        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7043        let input = input_view.input;
7044
7045        cx.update(|window, cx| {
7046            input.update(cx, |state, cx| {
7047                let enter = Enter {
7048                    secondary: false,
7049                    shift: false,
7050                };
7051                state.replace_text_in_range(None, "a", window, cx);
7052                state.enter(&enter, window, cx);
7053                state.replace_text_in_range(None, "b", window, cx);
7054                state.enter(&enter, window, cx);
7055                state.replace_text_in_range(None, "c", window, cx);
7056                assert_eq!(state.value(), "a\nb\nc");
7057
7058                for expected in ["a\nb\n", "a\nb", "a\n", "a", ""] {
7059                    state.undo(&Undo, window, cx);
7060                    assert_eq!(state.value(), expected);
7061                }
7062            });
7063        });
7064    }
7065
7066    #[gpui::test]
7067    fn test_undo_manager_coalesces_long_unicode_typing_without_a_timer(cx: &mut TestAppContext) {
7068        let input_view = InputView::build_textarea(cx, |state| state);
7069        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7070        let input = input_view.input;
7071        let parts = [
7072            "The ",
7073            "quick ",
7074            "brown fox, ",
7075            "你好,世界 ",
7076            "🦀 jumps over 13 lazy dogs.",
7077        ];
7078        let expected = parts.concat();
7079
7080        cx.update(|window, cx| {
7081            input.update(cx, |state, cx| {
7082                for part in parts {
7083                    state.replace_text_in_range(None, part, window, cx);
7084                }
7085                assert_eq!(state.value(), expected);
7086
7087                state.undo(&Undo, window, cx);
7088                assert_eq!(state.value(), "");
7089                state.redo(&Redo, window, cx);
7090                assert_eq!(state.value(), expected);
7091            });
7092        });
7093    }
7094
7095    #[gpui::test]
7096    fn test_undo_manager_long_multiline_sequence_has_structural_boundaries(
7097        cx: &mut TestAppContext,
7098    ) {
7099        let input_view = InputView::build_textarea(cx, |state| state);
7100        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7101        let input = input_view.input;
7102        let enter = Enter {
7103            secondary: false,
7104            shift: false,
7105        };
7106
7107        cx.update(|window, cx| {
7108            input.update(cx, |state, cx| {
7109                for (index, line) in [
7110                    "first line with punctuation!",
7111                    "第二行包含 Unicode 🦀",
7112                    "third line has several words",
7113                ]
7114                .into_iter()
7115                .enumerate()
7116                {
7117                    for chunk in line.split_inclusive(' ') {
7118                        state.replace_text_in_range(None, chunk, window, cx);
7119                    }
7120                    if index < 2 {
7121                        state.enter(&enter, window, cx);
7122                    }
7123                }
7124
7125                assert_eq!(
7126                    state.value(),
7127                    "first line with punctuation!\n第二行包含 Unicode 🦀\nthird line has several words"
7128                );
7129                state.undo(&Undo, window, cx);
7130                assert_eq!(
7131                    state.value(),
7132                    "first line with punctuation!\n第二行包含 Unicode 🦀\n"
7133                );
7134                state.undo(&Undo, window, cx);
7135                assert_eq!(
7136                    state.value(),
7137                    "first line with punctuation!\n第二行包含 Unicode 🦀"
7138                );
7139                state.undo(&Undo, window, cx);
7140                assert_eq!(state.value(), "first line with punctuation!\n");
7141            });
7142        });
7143    }
7144
7145    #[gpui::test]
7146    fn test_masked_input_keeps_its_value_out_of_the_clipboard(cx: &mut TestAppContext) {
7147        let input_view = InputView::build(cx, |state| state);
7148        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7149        let input = input_view.input;
7150
7151        cx.update(|window, cx| {
7152            input.update(cx, |state, cx| {
7153                state.set_value("hunter2", window, cx);
7154                state.set_masked(true, window, cx);
7155                state.select_all(window, cx);
7156                cx.write_to_clipboard(ClipboardItem::new_string("sentinel".into()));
7157
7158                state.copy(&Copy, window, cx);
7159                assert_eq!(
7160                    cx.read_from_clipboard().and_then(|item| item.text()),
7161                    Some("sentinel".to_string())
7162                );
7163
7164                // Cut neither copies nor deletes.
7165                state.cut(&Cut, window, cx);
7166                assert_eq!(state.value(), "hunter2");
7167                assert_eq!(
7168                    cx.read_from_clipboard().and_then(|item| item.text()),
7169                    Some("sentinel".to_string())
7170                );
7171
7172                // Revealing the value restores both.
7173                state.set_masked(false, window, cx);
7174                state.copy(&Copy, window, cx);
7175                assert_eq!(
7176                    cx.read_from_clipboard().and_then(|item| item.text()),
7177                    Some("hunter2".to_string())
7178                );
7179            });
7180        });
7181    }
7182
7183    #[gpui::test]
7184    fn test_masked_input_collapses_word_boundaries(cx: &mut TestAppContext) {
7185        let input_view = InputView::build(cx, |state| state);
7186        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7187        let input = input_view.input;
7188
7189        cx.update(|window, cx| {
7190            input.update(cx, |state, cx| {
7191                state.set_value("aaa bbb ccc", window, cx);
7192                state.set_masked(true, window, cx);
7193                state.set_selected_range(7..7, cx);
7194
7195                // The mask hides word boundaries, so a word delete takes
7196                // everything before the caret and leaves the rest.
7197                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
7198                assert_eq!(state.value(), " ccc");
7199                assert_eq!(state.selected_range(), 0..0);
7200
7201                state.delete_next_word(&DeleteToNextWordEnd, window, cx);
7202                assert_eq!(state.value(), "");
7203
7204                // A double click takes the whole value, not one word.
7205                state.set_value("aaa bbb ccc", window, cx);
7206                state.select_word(9, window, cx);
7207                assert_eq!(state.selected_range(), 0..11);
7208
7209                // Unmasked, the same delete only takes one word.
7210                state.set_masked(false, window, cx);
7211                state.set_value("aaa bbb ccc", window, cx);
7212                state.set_selected_range(11..11, cx);
7213                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
7214                assert_eq!(state.value(), "aaa bbb ");
7215
7216                state.set_value("aaa bbb ccc", window, cx);
7217                state.select_word(9, window, cx);
7218                assert_eq!(state.selected_range(), 8..11);
7219            });
7220        });
7221    }
7222
7223    #[gpui::test]
7224    fn test_masked_input_disables_the_copy_context_menu_items(cx: &mut TestAppContext) {
7225        let input_view = InputView::build(cx, |state| state);
7226        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7227        let input = input_view.input;
7228
7229        cx.update(|window, cx| {
7230            input.update(cx, |state, cx| {
7231                state.set_value("hunter2", window, cx);
7232                state.select_all(window, cx);
7233                assert!(state.context_menu_capabilities().is_copyable());
7234
7235                state.set_masked(true, window, cx);
7236                let capabilities = state.context_menu_capabilities();
7237                assert!(capabilities.is_masked());
7238                assert!(capabilities.has_selection());
7239                assert!(!capabilities.is_copyable());
7240            });
7241        });
7242    }
7243
7244    #[gpui::test]
7245    fn test_undo_manager_cut_and_repeated_pastes_are_distinct_transactions(
7246        cx: &mut TestAppContext,
7247    ) {
7248        let input_view = InputView::build(cx, |state| state);
7249        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7250        let input = input_view.input;
7251
7252        cx.update(|window, cx| {
7253            input.update(cx, |state, cx| {
7254                state.replace_text_in_range(None, "alpha beta gamma", window, cx);
7255                state.set_selected_range(6..10, cx);
7256                state.cut(&Cut, window, cx);
7257                assert_eq!(state.value(), "alpha  gamma");
7258
7259                state.paste(&Paste, window, cx);
7260                state.paste(&Paste, window, cx);
7261                assert_eq!(state.value(), "alpha betabeta gamma");
7262
7263                state.undo(&Undo, window, cx);
7264                assert_eq!(state.value(), "alpha beta gamma");
7265                state.undo(&Undo, window, cx);
7266                assert_eq!(state.value(), "alpha  gamma");
7267                state.undo(&Undo, window, cx);
7268                assert_eq!(state.value(), "alpha beta gamma");
7269            });
7270        });
7271    }
7272
7273    #[gpui::test]
7274    fn test_undo_manager_word_and_line_deletes_do_not_coalesce(cx: &mut TestAppContext) {
7275        let input_view = InputView::build_textarea(cx, |state| state);
7276        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7277        let input = input_view.input;
7278
7279        cx.update(|window, cx| {
7280            input.update(cx, |state, cx| {
7281                state.set_value("one two three\nfour five", window, cx);
7282                state.set_selected_range(13..13, cx);
7283                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
7284                assert_eq!(state.value(), "one two \nfour five");
7285                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
7286                assert_eq!(state.value(), "one two four five");
7287
7288                state.undo(&Undo, window, cx);
7289                assert_eq!(state.value(), "one two \nfour five");
7290                state.undo(&Undo, window, cx);
7291                assert_eq!(state.value(), "one two three\nfour five");
7292            });
7293        });
7294    }
7295
7296    #[gpui::test]
7297    fn test_undo_manager_multiline_replacement_is_one_atomic_transaction(cx: &mut TestAppContext) {
7298        let input_view = InputView::build_textarea(cx, |state| state);
7299        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7300        let input = input_view.input;
7301
7302        cx.update(|window, cx| {
7303            input.update(cx, |state, cx| {
7304                state.replace_text_in_range(None, "before", window, cx);
7305                state.set_selected_range(0..6, cx);
7306                state.replace_text_in_range(None, "line one\nline two\n第三行", window, cx);
7307                assert_eq!(state.value(), "line one\nline two\n第三行");
7308
7309                state.undo(&Undo, window, cx);
7310                assert_eq!(state.value(), "before");
7311                assert_eq!(state.selected_range(), 0..6);
7312                state.redo(&Redo, window, cx);
7313                assert_eq!(state.value(), "line one\nline two\n第三行");
7314            });
7315        });
7316    }
7317
7318    #[gpui::test]
7319    fn test_undo_manager_composition_isolated_from_long_typing(cx: &mut TestAppContext) {
7320        let input_view = InputView::build(cx, |state| state);
7321        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7322        let input = input_view.input;
7323
7324        cx.update(|window, cx| {
7325            input.update(cx, |state, cx| {
7326                state.replace_text_in_range(None, "prefix ", window, cx);
7327                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
7328                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
7329                state.replace_and_mark_text_in_range(None, "你", None, window, cx);
7330                state.unmark_text(window, cx);
7331                state.replace_text_in_range(None, " suffix", window, cx);
7332                assert_eq!(state.value(), "prefix 你 suffix");
7333
7334                state.undo(&Undo, window, cx);
7335                assert_eq!(state.value(), "prefix 你");
7336                state.undo(&Undo, window, cx);
7337                assert_eq!(state.value(), "prefix ");
7338                state.undo(&Undo, window, cx);
7339                assert_eq!(state.value(), "");
7340            });
7341        });
7342    }
7343
7344    #[gpui::test]
7345    fn test_undo_manager_selected_replacement_is_atomic(cx: &mut TestAppContext) {
7346        let input_view = InputView::build(cx, |state| state);
7347        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7348        let input = input_view.input;
7349
7350        cx.update(|window, cx| {
7351            input.update(cx, |state, cx| {
7352                state.replace_text_in_range(None, "abc", window, cx);
7353                state.set_selected_range(1..2, cx);
7354                state.replace_text_in_range(None, "X", window, cx);
7355                state.replace_text_in_range(None, "z", window, cx);
7356                assert_eq!(state.value(), "aXzc");
7357
7358                state.undo(&Undo, window, cx);
7359                assert_eq!(state.value(), "aXc");
7360                state.undo(&Undo, window, cx);
7361                assert_eq!(state.value(), "abc");
7362                state.undo(&Undo, window, cx);
7363                assert_eq!(state.value(), "");
7364            });
7365        });
7366    }
7367
7368    #[gpui::test]
7369    fn test_number_input_leading_dot_editable(cx: &mut TestAppContext) {
7370        let input_view = InputView::build(cx, |state| {
7371            state.mask_pattern(MaskPattern::Number {
7372                separator: None,
7373                fraction: None,
7374            })
7375        });
7376        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7377        let input = input_view.input;
7378
7379        cx.update(|window, cx| {
7380            input.update(cx, |state, cx| {
7381                state.replace_text_in_range(None, "1.2", window, cx);
7382
7383                // Delete the integer part "1": the value keeps the leading dot
7384                // (".2"), not completed to "0.2", so the digits before the dot
7385                // stay editable.
7386                let range = state.range_to_utf16(&(0..1));
7387                state.replace_text_in_range(Some(range), "", window, cx);
7388                assert_eq!(state.value(), ".2");
7389                let cursor: Range<usize> = state.selected_range();
7390                assert_eq!(cursor, 0..0);
7391
7392                // The user can type a new integer part.
7393                state.replace_text_in_range(Some(0..0), "3", window, cx);
7394                assert_eq!(state.value(), "3.2");
7395            });
7396        });
7397    }
7398
7399    #[gpui::test]
7400    fn test_number_input_escape_invalid_text(cx: &mut TestAppContext) {
7401        // A pre-existing invalid text (e.g. a `default_value` that does not
7402        // conform) must not trap the user, the edit is allowed to fix it.
7403        let input_view = InputView::build(cx, |state| {
7404            state
7405                .mask_pattern(MaskPattern::Number {
7406                    separator: None,
7407                    fraction: None,
7408                })
7409                .default_value("1,234")
7410        });
7411        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7412        let input = input_view.input;
7413
7414        cx.update(|window, cx| {
7415            input.update(cx, |state, cx| {
7416                // Delete the last char, the pending text "1,23" is still
7417                // invalid, but the edit is allowed since the old text was
7418                // already invalid.
7419                let range = state.range_to_utf16(&(4..5));
7420                state.replace_text_in_range(Some(range), "", window, cx);
7421                assert_eq!(state.value(), "1,23");
7422
7423                // Once the text becomes valid, the validation works as usual.
7424                let range = state.range_to_utf16(&(1..2));
7425                state.replace_text_in_range(Some(range), "", window, cx);
7426                assert_eq!(state.value(), "123");
7427                state.replace_text_in_range(None, "a", window, cx);
7428                assert_eq!(state.value(), "123");
7429            });
7430        });
7431    }
7432
7433    /// After `set_value` on a single-line input the caret sits at the end (like
7434    /// HTML `<input>`), yet the view is scrolled back to the start so a long
7435    /// value shows its beginning instead of its tail.
7436    #[gpui::test]
7437    fn test_set_value_single_line_caret_at_end_view_at_start(cx: &mut TestAppContext) {
7438        let input_view = InputView::build(cx, |state| state);
7439        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7440        let input = input_view.input;
7441
7442        // Long enough to overflow any reasonable single-line input width.
7443        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
7444        let len = value.len();
7445
7446        // Right after `set_value`, before the next paint consumes the deferred
7447        // offset: caret is at the end, and the view is forced back to the start.
7448        cx.update(|window, cx| {
7449            input.update(cx, |state, cx| {
7450                state.set_value(value.clone(), window, cx);
7451
7452                assert_eq!(
7453                    state.selected_range(),
7454                    len..len,
7455                    "single-line caret should be at the end after set_value"
7456                );
7457                assert_eq!(
7458                    state.deferred_scroll_offset,
7459                    Some(point(px(0.), px(0.))),
7460                    "the view should be forced back to the start"
7461                );
7462            });
7463        });
7464
7465        // After a paint, the steady-state view stays at the start (x == 0) even
7466        // though the caret is at the far end.
7467        cx.run_until_parked();
7468        cx.update(|_, cx| {
7469            input.read_with(cx, |state, _| {
7470                assert!(
7471                    state.scroll_size.width > state.input_bounds.size.width,
7472                    "value must overflow the input width or this test is vacuous"
7473                );
7474                assert_eq!(
7475                    state.scroll_handle.offset().x,
7476                    px(0.),
7477                    "long value should display from its start, not its tail"
7478                );
7479            });
7480        });
7481    }
7482
7483    /// `replace_all` on a single-line input replaces the text, puts the
7484    /// caret at the end, and — like `set_value` — snaps the view back to the
7485    /// start so a long value shows its beginning instead of its tail.
7486    #[gpui::test]
7487    fn test_replace_all_single_line(cx: &mut TestAppContext) {
7488        let input_view = InputView::build(cx, |state| state);
7489        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7490        let input = input_view.input;
7491
7492        // Long enough to overflow any reasonable single-line input width.
7493        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
7494        let len = value.len();
7495
7496        // Right after `replace_all`, before the next paint consumes the
7497        // deferred offset: caret is at the end, and the view is forced back
7498        // to the start.
7499        cx.update(|window, cx| {
7500            input.update(cx, |state, cx| {
7501                state.set_value("hello", window, cx);
7502                state.replace_all(value.clone(), window, cx);
7503                assert_eq!(state.value(), value);
7504                assert_eq!(
7505                    state.selected_range(),
7506                    len..len,
7507                    "single-line caret should be at the end after replace_all"
7508                );
7509                assert_eq!(
7510                    state.scroll_handle.offset(),
7511                    point(px(0.), px(0.)),
7512                    "the scroll offset should be reset to the start"
7513                );
7514                assert_eq!(
7515                    state.deferred_scroll_offset,
7516                    Some(point(px(0.), px(0.))),
7517                    "single-line should set a deferred scroll offset to keep the start visible"
7518                );
7519            });
7520        });
7521
7522        // After a paint, the steady-state view stays at the start (x == 0)
7523        // even though the caret is at the far end.
7524        cx.run_until_parked();
7525        cx.update(|_, cx| {
7526            input.read_with(cx, |state, _| {
7527                assert!(
7528                    state.scroll_size.width > state.input_bounds.size.width,
7529                    "value must overflow the input width or this test is vacuous"
7530                );
7531                assert_eq!(
7532                    state.scroll_handle.offset().x,
7533                    px(0.),
7534                    "long value should display from its start, not its tail"
7535                );
7536            });
7537        });
7538    }
7539
7540    #[gpui::test]
7541    fn test_single_line_removes_newlines(cx: &mut TestAppContext) {
7542        let input_view = InputView::build(cx, |state| state.default_value("default\nvalue"));
7543        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7544        let input = input_view.input;
7545
7546        cx.update(|window, cx| {
7547            input.update(cx, |state, cx| {
7548                assert_eq!(state.value(), "defaultvalue");
7549
7550                state.set_value("first\nsecond\r\nthird\rfourth", window, cx);
7551                assert_eq!(state.value(), "firstsecondthirdfourth");
7552
7553                state.set_value("", window, cx);
7554                state.insert("a\nb", window, cx);
7555                assert_eq!(state.value(), "ab");
7556            });
7557
7558            cx.write_to_clipboard(ClipboardItem::new_string("a\r\nb\nc\rd".to_string()));
7559            input.update(cx, |state, cx| {
7560                state.set_value("", window, cx);
7561                state.paste(&Paste, window, cx);
7562                assert_eq!(state.value(), "abcd");
7563            });
7564        });
7565
7566        cx.run_until_parked();
7567    }
7568
7569    /// `replace_all` on a multi-line (non-code-editor) input clears the
7570    /// selection to `0..0` and resets the scroll offset, but does not set a
7571    /// deferred scroll offset (single-line only).
7572    #[gpui::test]
7573    fn test_replace_all_multi_line(cx: &mut TestAppContext) {
7574        let input_view = InputView::build_textarea(cx, |state| state);
7575        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7576        let input = input_view.input;
7577
7578        cx.update(|window, cx| {
7579            input.update(cx, |state, cx| {
7580                state.set_value("foo\nbar", window, cx);
7581                state.replace_all("baz\nqux", window, cx);
7582                assert_eq!(state.value(), "baz\nqux");
7583                assert_eq!(
7584                    state.selected_range(),
7585                    0..0,
7586                    "multi-line selection should be cleared after replace_all"
7587                );
7588                assert_eq!(
7589                    state.scroll_handle.offset(),
7590                    point(px(0.), px(0.)),
7591                    "the scroll offset should be reset to the start"
7592                );
7593                assert!(
7594                    state.deferred_scroll_offset.is_none(),
7595                    "multi-line should not set a deferred scroll offset"
7596                );
7597            });
7598        });
7599    }
7600
7601    /// Unlike `set_value`, `replace_all` records the change so the user can
7602    /// undo it back to the previous text and redo to the new text.
7603    #[gpui::test]
7604    fn test_replace_all_preserves_undo_history(cx: &mut TestAppContext) {
7605        let input_view = InputView::build(cx, |state| state);
7606        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7607        let input = input_view.input;
7608
7609        cx.update(|window, cx| {
7610            input.update(cx, |state, cx| {
7611                // Seed with a value and clear history so the baseline is clean.
7612                state.set_value("first", window, cx);
7613                assert!(
7614                    !state.undo_manager.has_undos(),
7615                    "history should be empty after set_value"
7616                );
7617
7618                // replace_all records a single undoable change.
7619                state.replace_all("second", window, cx);
7620                assert_eq!(state.value(), "second");
7621                assert!(
7622                    state.undo_manager.has_undos(),
7623                    "replace_all should record an undo step"
7624                );
7625
7626                // Undo restores the previous text.
7627                state.undo(&Undo, window, cx);
7628                assert_eq!(state.value(), "first");
7629
7630                // Redo reapplies the replacement.
7631                state.redo(&Redo, window, cx);
7632                assert_eq!(state.value(), "second");
7633            });
7634        });
7635    }
7636
7637    /// `replace_all` on a code editor marks a pending update and resets LSP
7638    /// state, so diagnostics/completions refresh against the new text.
7639    #[gpui::test]
7640    fn test_replace_all_code_editor(cx: &mut TestAppContext) {
7641        let input_view = InputView::new(cx);
7642        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7643        let input = input_view.input;
7644
7645        cx.update(|window, cx| {
7646            input.update(cx, |state, cx| {
7647                // Plant a pending-update flag and some LSP state to verify reset.
7648                state.set_value("select 1", window, cx);
7649                state._pending_update = false;
7650
7651                state.replace_all("select 2", window, cx);
7652                assert_eq!(state.value(), "select 2");
7653                assert!(
7654                    state._pending_update,
7655                    "replace_all on a code editor should request a pending update"
7656                );
7657            });
7658        });
7659    }
7660
7661    #[gpui::test]
7662    fn test_set_selected_range(cx: &mut TestAppContext) {
7663        let input_view = InputView::build(cx, |state| state.default_value("hello world"));
7664        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7665        let input = input_view.input;
7666
7667        cx.update(|_, cx| {
7668            input.update(cx, |s, cx| {
7669                s.set_selected_range(0..5, cx);
7670                assert_eq!(s.selected_range(), 0..5);
7671                assert_eq!(s.selected_text().to_string(), "hello");
7672
7673                s.set_selected_range(6..11, cx);
7674                assert_eq!(s.selected_text().to_string(), "world");
7675
7676                // clamped + collapsed
7677                s.set_selected_range(100..100, cx);
7678                assert_eq!(s.selected_range(), 11..11);
7679            });
7680        });
7681    }
7682
7683    /// A single-edit batch round-trips through undo/redo.
7684    #[gpui::test]
7685    fn test_replace_text_in_ranges_single_edit(cx: &mut TestAppContext) {
7686        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
7687        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7688        let input = input_view.input;
7689
7690        cx.update(|window, cx| {
7691            input.update(cx, |s, cx| {
7692                s.replace_text_in_ranges(&[(0..5, "HELLO".to_string())], window, cx);
7693                assert_eq!(s.value(), "HELLO world");
7694
7695                s.undo(&Undo, window, cx);
7696                assert_eq!(s.value(), "hello world");
7697
7698                s.redo(&Redo, window, cx);
7699                assert_eq!(s.value(), "HELLO world");
7700            });
7701        });
7702    }
7703
7704    /// A single undo restores the exact original text and a single redo
7705    /// re-applies all edits, verifying the back-to-front application ordering.
7706    #[gpui::test]
7707    fn test_replace_text_in_ranges_multi_edit_transaction(cx: &mut TestAppContext) {
7708        let input_view = InputView::build_textarea(cx, |state| state.default_value("aaa bbb ccc"));
7709        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7710        let input = input_view.input;
7711
7712        cx.update(|window, cx| {
7713            input.update(cx, |s, cx| {
7714                // Two edits at different positions, given in pre-edit
7715                // coordinates and in arbitrary (non-sorted) order.
7716                s.replace_text_in_ranges(
7717                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
7718                    window,
7719                    cx,
7720                );
7721                assert_eq!(s.value(), "X bbb Y");
7722
7723                // One collapsed cursor per edit, at the end of each inserted text.
7724                let cursors: Vec<usize> =
7725                    s.selections.iter().map(|sel| sel.cursor_offset()).collect();
7726                assert_eq!(cursors, vec![1, 7]);
7727
7728                // The whole batch is a single undo transaction.
7729                assert_eq!(s.undo_manager.undo_count(), 1);
7730
7731                // One undo restores the exact original text.
7732                s.undo(&Undo, window, cx);
7733                assert_eq!(s.value(), "aaa bbb ccc");
7734
7735                // One redo re-applies all edits.
7736                s.redo(&Redo, window, cx);
7737                assert_eq!(s.value(), "X bbb Y");
7738            });
7739        });
7740    }
7741
7742    /// A multi-edit change reaches the highlighter once, with every edit in
7743    /// application order and the text right after each, which is what the
7744    /// default `update_batch` replays through `update` one edit at a time.
7745    #[gpui::test]
7746    fn test_replace_text_in_ranges_drives_the_highlighter_once(cx: &mut TestAppContext) {
7747        use crate::input::{FoldRange, HighlightStyleResolver, InputEdit, InputHighlighter};
7748        use gpui::HighlightStyle;
7749        use std::cell::RefCell;
7750
7751        struct RecordingHighlighter(Rc<RefCell<Vec<Vec<String>>>>);
7752
7753        impl InputHighlighter for RecordingHighlighter {
7754            fn language(&self) -> SharedString {
7755                "recording".into()
7756            }
7757
7758            fn update(
7759                &mut self,
7760                _: Option<InputEdit>,
7761                text: &Rope,
7762                _: bool,
7763                _: &mut Window,
7764                _: &mut Context<EditorState>,
7765            ) {
7766                self.0.borrow_mut().push(vec![text.to_string()]);
7767            }
7768
7769            fn update_batch(
7770                &mut self,
7771                edits: &[(InputEdit, Rope)],
7772                _: bool,
7773                _: &mut Window,
7774                _: &mut Context<EditorState>,
7775            ) {
7776                let texts = edits.iter().map(|(_, text)| text.to_string()).collect();
7777                self.0.borrow_mut().push(texts);
7778            }
7779
7780            fn styles(
7781                &self,
7782                range: &Range<usize>,
7783                _: &dyn HighlightStyleResolver,
7784            ) -> Vec<(Range<usize>, HighlightStyle)> {
7785                vec![(range.clone(), HighlightStyle::default())]
7786            }
7787
7788            fn fold_ranges(&self, _: &Rope) -> Vec<FoldRange> {
7789                Vec::new()
7790            }
7791        }
7792
7793        let updates = Rc::new(RefCell::new(Vec::new()));
7794        let input_view = InputView::new(cx);
7795        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7796        let input = input_view.input;
7797
7798        cx.update(|window, cx| {
7799            input.update(cx, |s, cx| {
7800                s.set_value("aaa bbb ccc", window, cx);
7801                let recorded = updates.clone();
7802                s.set_highlighter_factory(
7803                    Rc::new(move |_: &str| {
7804                        let highlighter: Box<dyn InputHighlighter> =
7805                            Box::new(RecordingHighlighter(recorded.clone()));
7806                        Some(highlighter)
7807                    }),
7808                    cx,
7809                );
7810                s.replace_text_in_ranges(
7811                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
7812                    window,
7813                    cx,
7814                );
7815            });
7816        });
7817
7818        assert_eq!(
7819            *updates.borrow(),
7820            vec![vec!["aaa bbb Y".to_string(), "X bbb Y".to_string()]]
7821        );
7822    }
7823
7824    /// An IME composition (marking then commit) undoes as a single unit.
7825    #[gpui::test]
7826    fn test_ime_composition_undoes_as_one_unit(cx: &mut TestAppContext) {
7827        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
7828        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7829        let input = input_view.input;
7830
7831        cx.update(|window, cx| {
7832            input.update(cx, |s, cx| {
7833                // Simulate an IME composition: mark, refine, then commit.
7834                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
7835                s.replace_and_mark_text_in_range(None, "ni", Some(2..2), window, cx);
7836                s.replace_text_in_range(None, "你", window, cx);
7837                assert_eq!(s.value(), "你");
7838
7839                // The entire composition is one undo transaction.
7840                assert_eq!(s.undo_manager.undo_count(), 1);
7841
7842                s.undo(&Undo, window, cx);
7843                assert_eq!(s.value(), "");
7844
7845                s.redo(&Redo, window, cx);
7846                assert_eq!(s.value(), "你");
7847            });
7848        });
7849    }
7850
7851    /// A keystroke right after a committed composition must be its own undo
7852    /// entry, not merged into the (finalized) composition transaction.
7853    #[gpui::test]
7854    fn test_edit_after_composition_is_separate_undo(cx: &mut TestAppContext) {
7855        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
7856        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7857        let input = input_view.input;
7858
7859        cx.update(|window, cx| {
7860            input.update(cx, |s, cx| {
7861                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
7862                s.replace_text_in_range(None, "你", window, cx);
7863                assert_eq!(s.value(), "你");
7864                assert_eq!(s.undo_manager.undo_count(), 1);
7865
7866                // Typing after the commit is a distinct transaction.
7867                s.replace_text_in_range(None, "x", window, cx);
7868                assert_eq!(s.value(), "你x");
7869                assert_eq!(s.undo_manager.undo_count(), 2);
7870
7871                s.undo(&Undo, window, cx);
7872                assert_eq!(s.value(), "你");
7873                s.undo(&Undo, window, cx);
7874                assert_eq!(s.value(), "");
7875            });
7876        });
7877    }
7878
7879    /// Canceling a composition via `unmark_text` closes its transaction so it
7880    /// does not leak and swallow a later edit.
7881    #[gpui::test]
7882    fn test_composition_cancel_via_unmark_does_not_leak(cx: &mut TestAppContext) {
7883        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
7884        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7885        let input = input_view.input;
7886
7887        cx.update(|window, cx| {
7888            input.update(cx, |s, cx| {
7889                // Start a composition, then cancel it via unmark.
7890                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
7891                s.unmark_text(window, cx);
7892                let after_cancel = s.undo_manager.undo_count();
7893
7894                // A later edit is recorded independently.
7895                s.replace_text_in_range(None, "x", window, cx);
7896                assert_eq!(s.undo_manager.undo_count(), after_cancel + 1);
7897            });
7898        });
7899    }
7900
7901    #[gpui::test]
7902    fn test_set_selected_range_clips_to_utf8_boundaries(cx: &mut TestAppContext) {
7903        let input_view = InputView::build(cx, |state| state.default_value("éx"));
7904        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7905        let input = input_view.input;
7906
7907        cx.update(|window, cx| {
7908            input.update(cx, |state, cx| {
7909                state.set_selected_range(0..1, cx);
7910                assert_eq!(state.selected_range(), 0..2);
7911                state.copy(&Copy, window, cx);
7912
7913                state.set_selected_range(1..1, cx);
7914                assert_eq!(state.selected_range(), 0..0);
7915            });
7916        });
7917    }
7918
7919    #[gpui::test]
7920    fn test_ime_selection_is_relative_to_replacement_start(cx: &mut TestAppContext) {
7921        let input_view = InputView::build(cx, |state| state.default_value("你好 "));
7922        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7923        let input = input_view.input;
7924
7925        cx.update(|window, cx| {
7926            input.update(cx, |state, cx| {
7927                state.set_selected_range(7..7, cx);
7928                state.replace_and_mark_text_in_range(None, "s", Some(1..1), window, cx);
7929                state.replace_and_mark_text_in_range(None, "sh", Some(2..2), window, cx);
7930
7931                assert_eq!(state.value(), "你好 sh");
7932                assert_eq!(state.selected_range(), 9..9);
7933                assert_eq!(state.ime_marked_range, Some((7..9).into()));
7934            });
7935        });
7936    }
7937
7938    #[gpui::test]
7939    fn test_undo_manager_composition_is_one_undo_group(cx: &mut TestAppContext) {
7940        let input_view = InputView::build(cx, |state| state);
7941        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7942        let input = input_view.input;
7943
7944        cx.update(|window, cx| {
7945            input.update(cx, |state, cx| {
7946                state.set_value("a", window, cx);
7947                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
7948                state.replace_and_mark_text_in_range(None, "sh", None, window, cx);
7949                state.replace_text_in_range(None, "是", window, cx);
7950                assert_eq!(state.value(), "a是");
7951
7952                state.undo(&Undo, window, cx);
7953                assert_eq!(state.value(), "a");
7954                state.redo(&Redo, window, cx);
7955                assert_eq!(state.value(), "a是");
7956            });
7957        });
7958    }
7959
7960    #[gpui::test]
7961    fn test_undo_manager_consecutive_compositions_are_separate_groups(cx: &mut TestAppContext) {
7962        let input_view = InputView::build(cx, |state| state);
7963        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7964        let input = input_view.input;
7965
7966        cx.update(|window, cx| {
7967            input.update(cx, |state, cx| {
7968                // First composition: "jin" -> "今天"
7969                state.replace_and_mark_text_in_range(None, "j", None, window, cx);
7970                state.replace_and_mark_text_in_range(None, "jin", None, window, cx);
7971                state.replace_text_in_range(None, "今天", window, cx);
7972                // Second composition: "wo" -> "我们"
7973                state.replace_and_mark_text_in_range(None, "w", None, window, cx);
7974                state.replace_and_mark_text_in_range(None, "wo", None, window, cx);
7975                state.replace_text_in_range(None, "我们", window, cx);
7976                assert_eq!(state.value(), "今天我们");
7977                assert_eq!(state.selected_range(), 12..12);
7978
7979                state.undo(&Undo, window, cx);
7980                assert_eq!(state.value(), "今天");
7981                assert_eq!(state.selected_range(), 6..6);
7982
7983                state.undo(&Undo, window, cx);
7984                assert_eq!(state.value(), "");
7985                assert_eq!(state.selected_range(), 0..0);
7986
7987                state.redo(&Redo, window, cx);
7988                assert_eq!(state.value(), "今天");
7989                assert_eq!(state.selected_range(), 6..6);
7990
7991                state.redo(&Redo, window, cx);
7992                assert_eq!(state.value(), "今天我们");
7993                assert_eq!(state.selected_range(), 12..12);
7994            });
7995        });
7996    }
7997
7998    #[gpui::test]
7999    fn test_undo_manager_typing_after_composition_is_a_separate_group(cx: &mut TestAppContext) {
8000        let input_view = InputView::build(cx, |state| state);
8001        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8002        let input = input_view.input;
8003
8004        cx.update(|window, cx| {
8005            input.update(cx, |state, cx| {
8006                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
8007                state.replace_text_in_range(None, "你", window, cx);
8008                state.undo_manager.set_pending_intent(EditIntent::Typing);
8009                state.replace_text_in_range(None, "a", window, cx);
8010                state.undo_manager.set_pending_intent(EditIntent::Typing);
8011                state.replace_text_in_range(None, "b", window, cx);
8012                assert_eq!(state.value(), "你ab");
8013
8014                state.undo(&Undo, window, cx);
8015                assert_eq!(state.value(), "你");
8016
8017                state.undo(&Undo, window, cx);
8018                assert_eq!(state.value(), "");
8019            });
8020        });
8021    }
8022
8023    #[gpui::test]
8024    fn test_undo_manager_composition_cancel_leaves_no_entry(cx: &mut TestAppContext) {
8025        let input_view = InputView::build(cx, |state| state);
8026        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8027        let input = input_view.input;
8028
8029        cx.update(|window, cx| {
8030            input.update(cx, |state, cx| {
8031                state.set_value("a", window, cx);
8032                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
8033                state.replace_and_mark_text_in_range(None, "", None, window, cx);
8034
8035                assert_eq!(state.value(), "a");
8036                assert!(!state.undo_manager.has_undos());
8037            });
8038        });
8039    }
8040
8041    /// The macOS Korean IME rewrites the character it just inserted.
8042    #[gpui::test]
8043    fn test_undo_manager_ime_rewrites_of_typed_char_are_one_group(cx: &mut TestAppContext) {
8044        let input_view = InputView::build(cx, |state| state);
8045        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8046        let input = input_view.input;
8047
8048        cx.update(|window, cx| {
8049            input.update(cx, |state, cx| {
8050                state.set_value("é ", window, cx);
8051                state.set_selected_range(3..3, cx);
8052                // g k s r m f: "ㅎ" -> "하" -> "한", then "ㄱ" -> "그" -> "글"
8053                state.replace_text_in_range(None, "ㅎ", window, cx);
8054                state.replace_text_in_range(Some(2..3), "하", window, cx);
8055                state.replace_text_in_range(Some(2..3), "한", window, cx);
8056                state.replace_text_in_range(None, "ㄱ", window, cx);
8057                state.replace_text_in_range(Some(3..4), "그", window, cx);
8058                state.replace_text_in_range(Some(3..4), "글", window, cx);
8059                assert_eq!(state.value(), "é 한글");
8060                assert_eq!(state.undo_manager.undo_count(), 1);
8061
8062                state.undo(&Undo, window, cx);
8063                assert_eq!(state.value(), "é ");
8064                assert_eq!(state.selected_range(), 3..3);
8065
8066                state.redo(&Redo, window, cx);
8067                assert_eq!(state.value(), "é 한글");
8068                assert_eq!(state.selected_range(), 9..9);
8069            });
8070        });
8071    }
8072
8073    #[gpui::test]
8074    fn test_undo_manager_rewrite_after_cursor_movement_is_separate(cx: &mut TestAppContext) {
8075        let input_view = InputView::build(cx, |state| state);
8076        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8077        let input = input_view.input;
8078
8079        cx.update(|window, cx| {
8080            input.update(cx, |state, cx| {
8081                state.replace_text_in_range(None, "c", window, cx);
8082                state.replace_text_in_range(None, "e", window, cx);
8083                state.left(&MoveLeft, window, cx);
8084                state.right(&MoveRight, window, cx);
8085                state.replace_text_in_range(Some(1..2), "é", window, cx);
8086                assert_eq!(state.value(), "cé");
8087
8088                state.undo(&Undo, window, cx);
8089                assert_eq!(state.value(), "ce");
8090                state.undo(&Undo, window, cx);
8091                assert_eq!(state.value(), "");
8092            });
8093        });
8094    }
8095
8096    #[gpui::test]
8097    fn test_undo_manager_selection_restored_by_undo_and_redo(cx: &mut TestAppContext) {
8098        let input_view = InputView::build(cx, |state| state);
8099        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8100        let input = input_view.input;
8101
8102        cx.update(|window, cx| {
8103            input.update(cx, |state, cx| {
8104                state.set_value("abc", window, cx);
8105                state.set_selected_range(1..2, cx);
8106                state.replace_text_in_range(None, "X", window, cx);
8107
8108                state.undo(&Undo, window, cx);
8109                assert_eq!(state.value(), "abc");
8110                assert_eq!(state.selected_range(), 1..2);
8111
8112                state.redo(&Redo, window, cx);
8113                assert_eq!(state.value(), "aXc");
8114                assert_eq!(state.selected_range(), 2..2);
8115            });
8116        });
8117    }
8118
8119    #[gpui::test]
8120    fn test_undo_manager_forward_delete_restores_cursor(cx: &mut TestAppContext) {
8121        let input_view = InputView::build(cx, |state| state);
8122        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8123        let input = input_view.input;
8124
8125        cx.update(|window, cx| {
8126            input.update(cx, |state, cx| {
8127                state.set_value("abc", window, cx);
8128                state.set_selected_range(1..1, cx);
8129                state.delete(&Delete, window, cx);
8130
8131                state.undo(&Undo, window, cx);
8132                assert_eq!(state.value(), "abc");
8133                assert_eq!(state.selected_range(), 1..1);
8134            });
8135        });
8136    }
8137
8138    #[gpui::test]
8139    fn test_undo_manager_selection_movement_preserves_redo(cx: &mut TestAppContext) {
8140        let input_view = InputView::build(cx, |state| state);
8141        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8142        let input = input_view.input;
8143
8144        cx.update(|window, cx| {
8145            input.update(cx, |state, cx| {
8146                state.replace_text_in_range(None, "ab", window, cx);
8147                state.undo(&Undo, window, cx);
8148                state.set_selected_range(0..0, cx);
8149                state.redo(&Redo, window, cx);
8150                assert_eq!(state.value(), "ab");
8151
8152                state.undo(&Undo, window, cx);
8153                state.replace_text_in_range(None, "x", window, cx);
8154                state.redo(&Redo, window, cx);
8155                assert_eq!(state.value(), "x");
8156            });
8157        });
8158    }
8159
8160    #[gpui::test]
8161    fn test_undo_manager_noop_edit_preserves_redo(cx: &mut TestAppContext) {
8162        let input_view = InputView::build(cx, |state| state);
8163        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8164        let input = input_view.input;
8165
8166        cx.update(|window, cx| {
8167            input.update(cx, |state, cx| {
8168                state.replace_text_in_range(None, "a", window, cx);
8169                state.move_to(0, None, cx);
8170                state.replace_text_in_range(None, "", window, cx);
8171                state.undo(&Undo, window, cx);
8172                assert_eq!(state.value(), "");
8173                state.redo(&Redo, window, cx);
8174                assert_eq!(state.value(), "a");
8175                assert_eq!(state.cursor(), 1);
8176            });
8177        });
8178    }
8179
8180    #[gpui::test]
8181    fn test_cursor_round_trip_stops_typing_coalescing(cx: &mut TestAppContext) {
8182        let input_view = InputView::build(cx, |state| state);
8183        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8184        let input = input_view.input;
8185
8186        cx.update(|window, cx| {
8187            input.update(cx, |state, cx| {
8188                state.replace_text_in_range(None, "a", window, cx);
8189                state.left(&MoveLeft, window, cx);
8190                state.right(&MoveRight, window, cx);
8191                state.replace_text_in_range(None, "b", window, cx);
8192
8193                state.undo(&Undo, window, cx);
8194                assert_eq!(state.value(), "a");
8195                state.undo(&Undo, window, cx);
8196                assert_eq!(state.value(), "");
8197            });
8198        });
8199    }
8200
8201    #[gpui::test]
8202    fn test_undo_manager_noop_edit_breaks_coalescing_without_clearing_history(
8203        cx: &mut TestAppContext,
8204    ) {
8205        let input_view = InputView::build(cx, |state| state);
8206        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8207        let input = input_view.input;
8208
8209        cx.update(|window, cx| {
8210            input.update(cx, |state, cx| {
8211                state.replace_text_in_range(None, "alpha", window, cx);
8212                state.replace_text_in_range(None, "", window, cx);
8213                state.replace_text_in_range(None, "beta", window, cx);
8214                assert_eq!(state.value(), "alphabeta");
8215
8216                state.undo(&Undo, window, cx);
8217                assert_eq!(state.value(), "alpha");
8218                state.undo(&Undo, window, cx);
8219                assert_eq!(state.value(), "");
8220            });
8221        });
8222    }
8223
8224    #[gpui::test]
8225    fn test_undo_manager_masked_redo_restores_actual_cursor(cx: &mut TestAppContext) {
8226        let input_view = InputView::build(cx, |state| {
8227            state.mask_pattern(MaskPattern::Number {
8228                separator: Some(','),
8229                fraction: None,
8230            })
8231        });
8232        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8233        let input = input_view.input;
8234
8235        cx.update(|window, cx| {
8236            input.update(cx, |state, cx| {
8237                state.set_value("12345", window, cx);
8238                state.set_selected_range(2..2, cx);
8239                state.replace_text_in_range(None, "9", window, cx);
8240                let selection_after_edit = state.selected_range();
8241                assert_ne!(selection_after_edit.end, state.value().len());
8242
8243                state.undo(&Undo, window, cx);
8244                state.redo(&Redo, window, cx);
8245                assert_eq!(state.selected_range(), selection_after_edit);
8246            });
8247        });
8248    }
8249
8250    /// Unfolding at a position opens exactly the folds hiding it.
8251    ///
8252    /// A fold keeps its own first and last line visible, so a position on
8253    /// either of them opens nothing. Nested folds all open at once, sibling
8254    /// folds stay closed, and the opened ranges stay fold candidates.
8255    #[gpui::test]
8256    fn test_unfold_at(cx: &mut TestAppContext) {
8257        use crate::input::{FoldRange, Position};
8258
8259        let view = InputView::<EditorMode>::new(cx);
8260        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8261        let input = view.input;
8262
8263        // An outer fold over lines 0..=5, a fold nested inside it, and a
8264        // sibling fold that must never be touched.
8265        cx.update(|window, cx| {
8266            input.update(cx, |state, cx| {
8267                state.set_value("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl", window, cx);
8268                state.apply_highlighter_fold_candidates(
8269                    vec![
8270                        FoldRange::new(0, 5),
8271                        FoldRange::new(2, 4),
8272                        FoldRange::new(7, 10),
8273                    ],
8274                    cx,
8275                );
8276                state.display_map.set_folded(0, true);
8277                state.display_map.set_folded(2, true);
8278                state.display_map.set_folded(7, true);
8279            });
8280        });
8281
8282        // The outer fold's own first and last line stay visible, so neither
8283        // position opens anything.
8284        for line in [0, 5] {
8285            cx.update(|_, cx| {
8286                input.update(cx, |state, cx| {
8287                    assert!(!state.display_map.is_buffer_line_hidden(line));
8288                    assert!(!state.unfold_at(Position::new(line as u32, 0), cx));
8289                });
8290                input.read_with(cx, |state, _| {
8291                    assert!(state.display_map.is_folded_at(0));
8292                    assert!(state.display_map.is_folded_at(2));
8293                    assert!(state.display_map.is_folded_at(7));
8294                });
8295            });
8296        }
8297
8298        // Line 3 is hidden by both the outer and the nested fold, so both
8299        // open; the sibling fold does not.
8300        cx.update(|_, cx| {
8301            input.update(cx, |state, cx| {
8302                assert!(state.unfold_at(Position::new(3, 0), cx));
8303            });
8304            input.read_with(cx, |state, _| {
8305                assert!(!state.display_map.is_buffer_line_hidden(3));
8306                assert!(!state.display_map.is_folded_at(0));
8307                assert!(!state.display_map.is_folded_at(2));
8308                assert!(state.display_map.is_folded_at(7));
8309                // The opened ranges are still candidates for refolding.
8310                assert!(state.display_map.is_fold_candidate(0));
8311                assert!(state.display_map.is_fold_candidate(2));
8312            });
8313        });
8314
8315        // Nothing is hidden there any more, so a second call is a no-op.
8316        cx.update(|_, cx| {
8317            input.update(cx, |state, cx| {
8318                assert!(!state.unfold_at(Position::new(3, 0), cx));
8319            });
8320        });
8321    }
8322
8323    /// Losing focus hides the hover popover but keeps the decorations.
8324    ///
8325    /// Both used to be dropped by one call, so clicking away threw away
8326    /// decorations the application had installed and never asked to remove.
8327    #[gpui::test]
8328    fn test_blur_keeps_decorations(cx: &mut TestAppContext) {
8329        let view = InputView::<EditorMode>::new(cx);
8330        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8331        let input = view.input;
8332
8333        cx.update(|window, cx| {
8334            input.update(cx, |state, cx| {
8335                state.set_value("select 1", window, cx);
8336                let _collection = state.create_decorations_collection(
8337                    vec![crate::input::TextDecoration::new(
8338                        0..6,
8339                        gpui::HighlightStyle {
8340                            font_weight: Some(gpui::FontWeight::BOLD),
8341                            ..Default::default()
8342                        },
8343                    )],
8344                    cx,
8345                );
8346                state.present_hover(
8347                    0..6,
8348                    lsp_types::Hover {
8349                        contents: lsp_types::HoverContents::Scalar(
8350                            lsp_types::MarkedString::String("docs".into()),
8351                        ),
8352                        range: None,
8353                    },
8354                    cx,
8355                );
8356                assert!(state.hover_popover().is_some());
8357
8358                state.on_blur(window, cx);
8359
8360                assert!(
8361                    state.hover_popover().is_none(),
8362                    "blur should hide the hover popover"
8363                );
8364                let decorations = state.extras.decoration_layers();
8365                assert!(
8366                    decorations.iter().any(|layer| !layer.is_empty()),
8367                    "blur must not discard decorations"
8368                );
8369            });
8370        });
8371    }
8372
8373    /// The mode marker is the only source of truth for the kind of input.
8374    ///
8375    /// An auto-growing textarea capped at one row used to report itself as
8376    /// single-line, because the answer was derived from the row counts.
8377    #[gpui::test]
8378    fn test_kind_does_not_follow_the_row_count(cx: &mut TestAppContext) {
8379        let view = InputView::build_textarea(cx, |state| state.auto_grow(1, 1));
8380        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8381        view.input.read_with(&mut cx, |state, _| {
8382            assert!(state.is_multi_line());
8383            assert!(!state.is_single_line());
8384            assert!(!state.is_code_editor());
8385        });
8386    }
8387
8388    /// Soft wrap is on by default, for every mode that can wrap.
8389    ///
8390    /// The default lives in the shared constructor, where a mode-specific
8391    /// `new` can silently fail to restore it; this pins it down.
8392    #[gpui::test]
8393    fn test_soft_wrap_is_enabled_by_default(cx: &mut TestAppContext) {
8394        let textarea = InputView::build_textarea(cx, |state| state);
8395        let mut textarea_cx = VisualTestContext::from_window(textarea.window_handle.into(), cx);
8396        textarea
8397            .input
8398            .read_with(&mut textarea_cx, |state, _| assert!(state.soft_wrap));
8399
8400        let editor = InputView::<EditorMode>::new(cx);
8401        let mut editor_cx = VisualTestContext::from_window(editor.window_handle.into(), cx);
8402        editor
8403            .input
8404            .read_with(&mut editor_cx, |state, _| assert!(state.soft_wrap));
8405    }
8406
8407    /// Parse a cursor spec into `(text, cursor_offsets)`. Non-empty lines are
8408    /// joined with `\n` plus a trailing `\n`. `|` marks a cursor. Leading
8409    /// whitespace is kept, so a spec can express indentation.
8410    fn parse_cursor_spec(input: &str) -> (String, Vec<usize>) {
8411        let mut full_text = String::new();
8412        let mut cursor_offsets = Vec::new();
8413        let non_empty_lines: Vec<&str> = input.lines().filter(|l| !l.is_empty()).collect();
8414
8415        for (line_idx, line) in non_empty_lines.iter().enumerate() {
8416            let mut positions = Vec::new();
8417            let mut text = String::new();
8418            for ch in line.chars() {
8419                if ch == '|' {
8420                    positions.push(text.len());
8421                } else {
8422                    text.push(ch);
8423                }
8424            }
8425
8426            if line_idx > 0 {
8427                full_text.push('\n');
8428            }
8429            let line_start = full_text.len();
8430            for pos in positions {
8431                cursor_offsets.push(line_start + pos);
8432            }
8433            full_text.push_str(&text);
8434        }
8435        full_text.push('\n');
8436
8437        (full_text, cursor_offsets)
8438    }
8439
8440    /// Build a multi-line input for multi-cursor tests.
8441    fn multi_line(cx: &mut TestAppContext) -> InputView<TextareaMode> {
8442        InputView::build_textarea(cx, |state| state)
8443    }
8444
8445    /// Set the text and cursor positions from a spec (see [`parse_cursor_spec`]).
8446    fn setup_cursors<M: InputModeKind>(
8447        cx: &mut VisualTestContext,
8448        input: &Entity<InputBaseState<M>>,
8449        spec: &str,
8450    ) {
8451        let (full_text, offsets) = parse_cursor_spec(spec);
8452        cx.update(|window, cx| {
8453            input.update(cx, |state, cx| {
8454                state.set_value(&full_text, window, cx);
8455                let selections = offsets
8456                    .into_iter()
8457                    .map(|offset| {
8458                        CursorSelection::new(state.selections.generate_id(), offset, offset)
8459                    })
8460                    .collect();
8461                state.selections.replace_all(selections);
8462                cx.notify();
8463            });
8464        });
8465    }
8466
8467    /// Assert the text and cursor positions match a spec.
8468    #[track_caller]
8469    fn assert_cursors<M: InputModeKind>(
8470        cx: &mut VisualTestContext,
8471        input: &Entity<InputBaseState<M>>,
8472        spec: &str,
8473    ) {
8474        let (expected_text, mut expected_cursors) = parse_cursor_spec(spec);
8475        expected_cursors.sort();
8476
8477        let (actual_text, mut actual_cursors) = input.read_with(cx, |state, _| {
8478            (
8479                state.text.to_string(),
8480                state
8481                    .selections
8482                    .iter()
8483                    .map(|s| s.cursor_offset())
8484                    .collect::<Vec<_>>(),
8485            )
8486        });
8487        actual_cursors.sort();
8488
8489        assert_eq!(
8490            actual_text, expected_text,
8491            "Text mismatch:\nExpected: {expected_text:?}\nActual:   {actual_text:?}"
8492        );
8493        assert_eq!(
8494            actual_cursors, expected_cursors,
8495            "Cursor mismatch:\nExpected: {expected_cursors:?}\nActual:   {actual_cursors:?}"
8496        );
8497    }
8498
8499    #[gpui::test]
8500    fn test_alt_drag_selects_a_block_and_replaces_each_row(cx: &mut TestAppContext) {
8501        cx.update(crate::init);
8502        let view = InputView::<EditorMode>::new(cx);
8503        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8504        for modifiers in [
8505            gpui::Modifiers {
8506                alt: true,
8507                ..Default::default()
8508            },
8509            gpui::Modifiers {
8510                alt: true,
8511                shift: true,
8512                ..Default::default()
8513            },
8514            #[cfg(target_os = "linux")]
8515            gpui::Modifiers {
8516                alt: true,
8517                control: true,
8518                ..Default::default()
8519            },
8520        ] {
8521            setup_cursors(&mut cx, &view.input, "|abcd\nabcd\nabcd");
8522            cx.update(|window, cx| {
8523                view.input.update(cx, |state, cx| state.focus(window, cx));
8524            });
8525            let (start, end) = view.input.read_with(&cx, |state, _| {
8526                let layout = state.last_layout.as_ref().unwrap();
8527                let origin = state.last_bounds.unwrap().origin;
8528                let position = |row: usize, col| {
8529                    let local = layout.lines[row]
8530                        .position_for_index(col, layout, false)
8531                        .unwrap();
8532                    origin
8533                        + point(
8534                            layout.line_number_width + local.x,
8535                            layout.line_height * (row as f32 + 0.5),
8536                        )
8537                };
8538                (position(0, 1), position(2, 3))
8539            });
8540            // Land inside the third column instead of exactly on its trailing
8541            // glyph boundary, where pixel rounding can select the next column.
8542            let end = point(end.x - px(0.5), end.y);
8543            // A cached Ctrl-hover definition must not steal a column gesture.
8544            cx.update(|_, cx| {
8545                view.input.update(cx, |state, _| {
8546                    state.extras.hover_definition.update(
8547                        0..4,
8548                        vec![lsp_types::LocationLink {
8549                            origin_selection_range: None,
8550                            target_uri: "file:///tmp/column-selection.rs".parse().unwrap(),
8551                            target_range: Default::default(),
8552                            target_selection_range: Default::default(),
8553                        }],
8554                    );
8555                });
8556            });
8557            cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
8558            cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
8559            cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
8560            view.input.read_with(&cx, |state, _| {
8561                let ranges: Vec<_> = state
8562                    .selections
8563                    .iter()
8564                    .map(|sel| sel.start..sel.end)
8565                    .collect();
8566                assert_eq!(ranges, vec![1..3, 6..8, 11..13]);
8567            });
8568            // Moving after release must leave the block intact.
8569            cx.simulate_mouse_move(start, None, modifiers);
8570            cx.simulate_keystrokes("x");
8571            assert_cursors(&mut cx, &view.input, "ax|d\nax|d\nax|d");
8572        }
8573    }
8574
8575    #[gpui::test]
8576    fn test_alt_drag_extends_upward_from_an_existing_cursor(cx: &mut TestAppContext) {
8577        cx.update(crate::init);
8578        let view = InputView::<EditorMode>::new(cx);
8579        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8580        setup_cursors(&mut cx, &view.input, "abcd\nabcd\na|bcd");
8581        cx.update(|window, cx| {
8582            view.input.update(cx, |state, cx| state.focus(window, cx));
8583        });
8584        let (start, end) = view.input.read_with(&cx, |state, _| {
8585            let layout = state.last_layout.as_ref().unwrap();
8586            let origin = state.last_bounds.unwrap().origin;
8587            let local = layout.lines[0]
8588                .position_for_index(1, layout, false)
8589                .unwrap();
8590            let x = layout.line_number_width + local.x;
8591            (
8592                origin + point(x, layout.line_height * 2.5),
8593                origin + point(x, layout.line_height * 0.5),
8594            )
8595        });
8596        let modifiers = gpui::Modifiers {
8597            alt: true,
8598            ..Default::default()
8599        };
8600        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
8601        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
8602        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
8603        assert_cursors(&mut cx, &view.input, "a|bcd\na|bcd\na|bcd");
8604    }
8605
8606    #[gpui::test]
8607    fn test_alt_drag_over_a_short_row_keeps_the_block_width(cx: &mut TestAppContext) {
8608        cx.update(crate::init);
8609        let view = InputView::<EditorMode>::new(cx);
8610        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8611        setup_cursors(&mut cx, &view.input, "abcdef\nab\nabcdef");
8612        cx.update(|window, cx| {
8613            view.input.update(cx, |state, cx| state.focus(window, cx));
8614        });
8615        // Drag from row 0 column 1 down to row 1, at the x of row 0's column 5. Row 1
8616        // only has two characters, so the pointer ends past its end.
8617        let (start, end) = view.input.read_with(&cx, |state, _| {
8618            let layout = state.last_layout.as_ref().unwrap();
8619            let origin = state.last_bounds.unwrap().origin;
8620            let x_of = |index| {
8621                layout.line_number_width
8622                    + layout.lines[0]
8623                        .position_for_index(index, layout, false)
8624                        .unwrap()
8625                        .x
8626            };
8627            (
8628                origin + point(x_of(1), layout.line_height * 0.5),
8629                origin + point(x_of(5), layout.line_height * 1.5),
8630            )
8631        });
8632        let modifiers = gpui::Modifiers {
8633            alt: true,
8634            shift: true,
8635            ..Default::default()
8636        };
8637        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
8638        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
8639        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
8640
8641        // The short row is clipped to its own end; the long row keeps the full span.
8642        view.input.read_with(&cx, |state, _| {
8643            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
8644            assert_eq!(ranges, vec![(1, 5), (8, 9)]);
8645        });
8646    }
8647
8648    #[gpui::test]
8649    fn test_alt_mouse_release_outside_editor_ends_column_selection(cx: &mut TestAppContext) {
8650        cx.update(crate::init);
8651        let view = InputView::<EditorMode>::new(cx);
8652        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8653        setup_cursors(&mut cx, &view.input, "|abcd\nabcd");
8654        let position = view.input.read_with(&cx, |state, _| {
8655            let layout = state.last_layout.as_ref().unwrap();
8656            state.last_bounds.unwrap().origin
8657                + point(layout.line_number_width + px(2.), layout.line_height * 0.5)
8658        });
8659        let modifiers = gpui::Modifiers {
8660            alt: true,
8661            ..Default::default()
8662        };
8663        cx.simulate_mouse_down(position, MouseButton::Left, modifiers);
8664        cx.simulate_mouse_up(point(px(-100.), px(-100.)), MouseButton::Left, modifiers);
8665        view.input.read_with(&cx, |state, _| {
8666            assert!(!state.selecting);
8667            assert!(state.column_select_start.is_none());
8668        });
8669    }
8670
8671    #[gpui::test]
8672    fn test_consumed_keystrokes_keep_cursor_visible(cx: &mut TestAppContext) {
8673        let view = InputView::<EditorMode>::new(cx);
8674        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8675        setup_cursors(&mut cx, &view.input, "a|b");
8676        cx.update(|window, cx| {
8677            view.input.update(cx, |state, cx| {
8678                state.focus(window, cx);
8679                state.pause_blink_cursor(cx);
8680            });
8681        });
8682        cx.run_until_parked();
8683        cx.executor()
8684            .advance_clock(std::time::Duration::from_millis(300));
8685        cx.run_until_parked();
8686        view.input.read_with(&cx, |state, cx| {
8687            assert!(!state.blink_cursor.read(cx).visible());
8688        });
8689        // Copy consumes its shortcut without editing text or moving selections.
8690        for _ in 0..5 {
8691            #[cfg(target_os = "macos")]
8692            cx.simulate_keystrokes("cmd-c");
8693            #[cfg(not(target_os = "macos"))]
8694            cx.simulate_keystrokes("ctrl-c");
8695            cx.run_until_parked();
8696            cx.executor()
8697                .advance_clock(std::time::Duration::from_millis(200));
8698            cx.run_until_parked();
8699            view.input.read_with(&cx, |state, cx| {
8700                assert!(state.blink_cursor.read(cx).visible());
8701            });
8702        }
8703    }
8704
8705    #[gpui::test]
8706    fn test_multi_cursor_actions_reveal_hidden_carets(cx: &mut TestAppContext) {
8707        let view = InputView::<EditorMode>::new(cx);
8708        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8709        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
8710        // Start each action in the hidden phase without depending on a
8711        // key-down listener: actions and text input also arrive directly.
8712        for action in 0..6 {
8713            cx.update(|_, cx| {
8714                view.input.update(cx, |state, cx| {
8715                    state.blink_cursor = cx.new(|_| BlinkCursor::new());
8716                    state.blink_cursor.update(cx, |cursor, cx| cursor.start(cx));
8717                });
8718            });
8719            cx.run_until_parked();
8720            cx.executor()
8721                .advance_clock(std::time::Duration::from_millis(500));
8722            cx.run_until_parked();
8723            view.input.read_with(&cx, |state, cx| {
8724                assert!(!state.blink_cursor.read(cx).visible(), "action {action}");
8725            });
8726
8727            cx.update(|window, cx| {
8728                view.input.update(cx, |state, cx| {
8729                    match action {
8730                        0 => state.add_cursor_above(&AddCursorAbove, window, cx),
8731                        1 => state.add_cursor_below(&AddCursorBelow, window, cx),
8732                        2 => state.select_up(&SelectUp, window, cx),
8733                        3 => state.select_down(&SelectDown, window, cx),
8734                        4 => state.replace_text_in_range(None, "x", window, cx),
8735                        _ => state.backspace(&Backspace, window, cx),
8736                    }
8737                    assert!(state.blink_cursor.read(cx).visible(), "action {action}");
8738                });
8739            });
8740        }
8741    }
8742
8743    #[gpui::test]
8744    fn test_multi_cursor_keyboard_dispatch(cx: &mut TestAppContext) {
8745        let view = InputView::<EditorMode>::new(cx);
8746        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8747        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
8748        cx.update(|window, cx| {
8749            view.input.update(cx, |state, cx| state.focus(window, cx));
8750        });
8751        #[cfg(target_os = "macos")]
8752        cx.simulate_keystrokes("cmd-alt-up");
8753        #[cfg(target_os = "windows")]
8754        cx.simulate_keystrokes("ctrl-alt-up");
8755        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
8756        cx.simulate_keystrokes("alt-shift-up");
8757        view.input
8758            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
8759        #[cfg(target_os = "macos")]
8760        cx.simulate_keystrokes("cmd-alt-down");
8761        #[cfg(target_os = "windows")]
8762        cx.simulate_keystrokes("ctrl-alt-down");
8763        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
8764        cx.simulate_keystrokes("alt-shift-down");
8765        view.input
8766            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 3));
8767        cx.simulate_keystrokes("x");
8768        assert_cursors(&mut cx, &view.input, "ax|b\nax|b\nax|b");
8769    }
8770
8771    #[gpui::test]
8772    fn test_multi_cursor_platform_word_selection_dispatch(cx: &mut TestAppContext) {
8773        let view = InputView::<EditorMode>::new(cx);
8774        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8775        setup_cursors(&mut cx, &view.input, "one |two\none |two");
8776        cx.update(|window, cx| {
8777            view.input.update(cx, |state, cx| state.focus(window, cx));
8778        });
8779        #[cfg(any(target_os = "macos", target_os = "linux"))]
8780        cx.simulate_keystrokes("alt-shift-right");
8781        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
8782        cx.simulate_keystrokes("ctrl-shift-right");
8783        cx.simulate_keystrokes("x");
8784        assert_cursors(&mut cx, &view.input, "one x|\none x|");
8785        setup_cursors(&mut cx, &view.input, "one two|\none two|");
8786        #[cfg(any(target_os = "macos", target_os = "linux"))]
8787        cx.simulate_keystrokes("alt-shift-left");
8788        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
8789        cx.simulate_keystrokes("ctrl-shift-left");
8790        cx.simulate_keystrokes("x");
8791        assert_cursors(&mut cx, &view.input, "one x|\none x|");
8792    }
8793
8794    #[cfg(not(target_os = "macos"))]
8795    #[gpui::test]
8796    fn test_multi_cursor_horizontal_selection_dispatch(cx: &mut TestAppContext) {
8797        let view = InputView::<EditorMode>::new(cx);
8798        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8799        setup_cursors(&mut cx, &view.input, "ab\na|b");
8800        cx.update(|window, cx| {
8801            view.input.update(cx, |state, cx| state.focus(window, cx));
8802        });
8803        #[cfg(target_os = "windows")]
8804        cx.simulate_keystrokes("ctrl-alt-up");
8805        #[cfg(not(target_os = "windows"))]
8806        cx.simulate_keystrokes("alt-shift-up");
8807        #[cfg(target_os = "linux")]
8808        cx.simulate_keystrokes("shift-right");
8809        #[cfg(not(target_os = "linux"))]
8810        cx.simulate_keystrokes("alt-shift-right");
8811        view.input.read_with(&cx, |state, _| {
8812            assert_eq!(
8813                state
8814                    .selections
8815                    .iter()
8816                    .map(|s| s.start..s.end)
8817                    .collect::<Vec<_>>(),
8818                vec![4..5, 1..2]
8819            );
8820        });
8821        #[cfg(target_os = "linux")]
8822        cx.simulate_keystrokes("shift-left shift-left");
8823        #[cfg(not(target_os = "linux"))]
8824        cx.simulate_keystrokes("alt-shift-left alt-shift-left");
8825        view.input.read_with(&cx, |state, _| {
8826            assert_eq!(
8827                state
8828                    .selections
8829                    .iter()
8830                    .map(|s| s.start..s.end)
8831                    .collect::<Vec<_>>(),
8832                vec![3..4, 0..1]
8833            );
8834        });
8835        cx.simulate_keystrokes("x");
8836        assert_cursors(&mut cx, &view.input, "x|b\nx|b");
8837    }
8838
8839    #[gpui::test]
8840    fn test_multi_cursor_alt_click_dispatch(cx: &mut TestAppContext) {
8841        cx.update(crate::init);
8842        let view = InputView::<EditorMode>::new(cx);
8843        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8844        setup_cursors(&mut cx, &view.input, "a|b\nab\nab");
8845        cx.update(|window, cx| {
8846            view.input.update(cx, |state, cx| state.focus(window, cx));
8847        });
8848        let position = view.input.read_with(&cx, |state, _| {
8849            let bounds = state.last_bounds.unwrap();
8850            let layout = state.last_layout.as_ref().unwrap();
8851            bounds.origin + point(layout.line_number_width + px(2.), layout.line_height * 1.5)
8852        });
8853        cx.simulate_click(
8854            position,
8855            gpui::Modifiers {
8856                alt: true,
8857                ..Default::default()
8858            },
8859        );
8860        view.input
8861            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
8862    }
8863
8864    #[gpui::test]
8865    fn test_add_selection(cx: &mut TestAppContext) {
8866        let view = InputView::<EditorMode>::new(cx);
8867        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8868        cx.update(|window, cx| {
8869            view.input.update(cx, |state, cx| {
8870                state.focus(window, cx);
8871                state.set_value("foo bar foo", window, cx);
8872                state.set_selected_range(0..3, cx);
8873                // The programmatic second cursor: the next occurrence.
8874                state.add_selection(8..11, cx);
8875                // Adding a range that is already selected changes nothing.
8876                state.add_selection(0..3, cx);
8877            });
8878        });
8879        view.input.read_with(&cx, |state, _| {
8880            let mut ranges: Vec<Range<usize>> =
8881                state.selections.iter().map(|s| s.start..s.end).collect();
8882            ranges.sort_by_key(|r| r.start);
8883            assert_eq!(ranges, vec![0..3, 8..11]);
8884        });
8885        // Typing replaces every selection at once.
8886        cx.simulate_keystrokes("x");
8887        view.input.read_with(&cx, |state, _| {
8888            assert_eq!(state.text.to_string(), "x bar x")
8889        });
8890    }
8891
8892    #[gpui::test]
8893    fn test_add_selection_preserves_crlf_boundaries(cx: &mut TestAppContext) {
8894        let view = InputView::build_textarea(cx, |state| state.default_value("a\r\nb"));
8895        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8896        cx.update(|window, cx| {
8897            view.input.update(cx, |state, cx| {
8898                state.focus(window, cx);
8899                state.set_selected_range(4..4, cx);
8900                state.add_selection(2..2, cx);
8901                let ranges: Vec<_> = state.selections.iter().map(|s| s.start..s.end).collect();
8902                assert_eq!(ranges, vec![4..4, 1..1]);
8903            });
8904        });
8905        cx.simulate_keystrokes("x");
8906        view.input.read_with(&cx, |state, _| {
8907            assert_eq!(state.value(), "ax\r\nbx");
8908        });
8909        cx.update(|window, cx| {
8910            view.input.update(cx, |state, cx| {
8911                state.set_value("a\r\nb", window, cx);
8912                state.set_selected_range(4..4, cx);
8913                state.add_selection(2..3, cx);
8914                state.cut(&Cut, window, cx);
8915                assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "\r\n");
8916                assert_eq!(state.value(), "ab");
8917            });
8918        });
8919    }
8920
8921    #[gpui::test]
8922    fn test_add_selection_merges_overlaps_before_copy_and_cut(cx: &mut TestAppContext) {
8923        let view = InputView::<EditorMode>::new(cx);
8924        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8925        cx.update(|window, cx| {
8926            view.input.update(cx, |state, cx| {
8927                // Cover partial overlaps, a containing range, and a bridge
8928                // between two previously disjoint selections.
8929                for additions in [vec![2..5], vec![0..5], vec![4..5, 2..4]] {
8930                    state.set_value("abcdef", window, cx);
8931                    state.set_selected_range(0..3, cx);
8932                    let active_id = state.active_selection().id;
8933                    for range in additions {
8934                        state.add_selection(range, cx);
8935                    }
8936                    assert_eq!(state.active_selection().id, active_id);
8937                    assert_eq!(state.selected_range(), 0..5);
8938                    assert_eq!(state.selections.len(), 1);
8939                    state.copy(&Copy, window, cx);
8940                    assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "abcde");
8941                    state.cut(&Cut, window, cx);
8942                    assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "abcde");
8943                    assert_eq!(state.value(), "f");
8944                    state.undo(&Undo, window, cx);
8945                    assert_eq!(state.value(), "abcdef");
8946                    assert_eq!(state.selected_range(), 0..5);
8947                }
8948            });
8949        });
8950    }
8951
8952    #[gpui::test]
8953    fn test_add_selection_clears_inline_completion(cx: &mut TestAppContext) {
8954        use crate::input::CompletionProvider;
8955        use gpui::Task;
8956        use lsp_types::{
8957            CompletionContext, CompletionResponse, InlineCompletionContext, InlineCompletionItem,
8958            InlineCompletionResponse,
8959        };
8960        use std::time::Duration;
8961
8962        struct Provider(Rc<Cell<usize>>);
8963
8964        impl CompletionProvider for Provider {
8965            fn is_completion_trigger(&self, _: usize, _: &str, _: &mut App) -> bool {
8966                false
8967            }
8968
8969            fn completions(
8970                &self,
8971                _: &Rope,
8972                _: usize,
8973                _: CompletionContext,
8974                _: &mut Window,
8975                _: &mut App,
8976            ) -> Task<anyhow::Result<CompletionResponse>> {
8977                Task::ready(Ok(CompletionResponse::Array(vec![])))
8978            }
8979
8980            fn inline_completion(
8981                &self,
8982                _: &Rope,
8983                _: usize,
8984                _: InlineCompletionContext,
8985                _: &mut Window,
8986                _: &mut App,
8987            ) -> Task<anyhow::Result<InlineCompletionResponse>> {
8988                self.0.set(self.0.get() + 1);
8989                Task::ready(Ok(InlineCompletionResponse::Array(vec![])))
8990            }
8991        }
8992
8993        let view = InputView::<EditorMode>::new(cx);
8994        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
8995        let calls = Rc::new(Cell::new(0));
8996        cx.update(|window, cx| {
8997            view.input.update(cx, |state, cx| {
8998                state.focus(window, cx);
8999                state.set_value("aa\naa", window, cx);
9000                state.set_selected_range(2..2, cx);
9001                state.extras.inline_completion.item = Some(InlineCompletionItem {
9002                    insert_text: "suggestion".into(),
9003                    filter_text: None,
9004                    range: None,
9005                    command: None,
9006                    insert_text_format: None,
9007                });
9008                state.add_selection(5..5, cx);
9009                assert!(!state.has_inline_completion());
9010                state.indent_inline(&IndentInline, window, cx);
9011                assert_eq!(state.value(), "aa  \naa  ");
9012                assert_eq!(state.selections.len(), 2);
9013
9014                // The active cursor stays put, so only canceling the task
9015                // prevents this pending request from fetching a suggestion.
9016                state.set_value("aa\naa", window, cx);
9017                state.set_selected_range(2..2, cx);
9018                state.extras.lsp.completion_provider = Some(Rc::new(Provider(calls.clone())));
9019                state.schedule_inline_completion(window, cx);
9020            });
9021        });
9022        cx.run_until_parked();
9023        cx.update(|_, cx| {
9024            view.input
9025                .update(cx, |state, cx| state.add_selection(5..5, cx));
9026        });
9027        cx.executor().advance_clock(Duration::from_secs(1));
9028        cx.run_until_parked();
9029        assert_eq!(calls.get(), 0);
9030        view.input.read_with(&cx, |state, _| {
9031            assert!(!state.has_inline_completion());
9032            assert_eq!(state.selections.len(), 2);
9033            assert_eq!(state.value(), "aa\naa");
9034        });
9035    }
9036
9037    #[gpui::test]
9038    fn test_word_delete_undo_restores_caret(cx: &mut TestAppContext) {
9039        let view = multi_line(cx);
9040        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9041        setup_cursors(&mut cx, &view.input, "hello|");
9042        cx.update(|window, cx| {
9043            view.input.update(cx, |state, cx| {
9044                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
9045                state.undo(&Undo, window, cx);
9046                assert_eq!(state.selected_range(), 5..5);
9047            });
9048        });
9049    }
9050
9051    #[gpui::test]
9052    fn test_merged_delete_undo_restores_all_carets(cx: &mut TestAppContext) {
9053        let view = multi_line(cx);
9054        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9055        setup_cursors(&mut cx, &view.input, "a|b|c");
9056        cx.update(|window, cx| {
9057            view.input.update(cx, |state, cx| {
9058                state.backspace(&Backspace, window, cx);
9059                state.undo(&Undo, window, cx);
9060            });
9061        });
9062        assert_cursors(&mut cx, &view.input, "a|b|c");
9063    }
9064
9065    #[gpui::test]
9066    fn test_shift_end_respects_soft_wrap_end(cx: &mut TestAppContext) {
9067        let view = InputView::<EditorMode>::new(cx);
9068        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9069        cx.update(|window, cx| {
9070            view.input.update(cx, |state, cx| {
9071                state.set_value("abcdef ".repeat(30), window, cx);
9072                state.display_map.on_layout_changed(Some(px(60.)), cx);
9073                let line = state.display_map.line(0).unwrap();
9074                assert!(line.wrapped_lines.len() > 1);
9075                let boundary = line.wrapped_lines[0].end;
9076                state.move_to_with_affinity(boundary, None, true, cx);
9077                state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
9078                assert_eq!(state.selected_range(), boundary..state.text.len());
9079            });
9080        });
9081    }
9082
9083    #[gpui::test]
9084    fn test_outdent_unindented_unicode_is_unchanged(cx: &mut TestAppContext) {
9085        let view = multi_line(cx);
9086        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9087        setup_cursors(&mut cx, &view.input, "|你好\n|世界");
9088        cx.update(|window, cx| {
9089            view.input.update(cx, |state, cx| {
9090                state.outdent(false, window, cx);
9091                state.outdent(true, window, cx);
9092            });
9093        });
9094        assert_cursors(&mut cx, &view.input, "|你好\n|世界");
9095    }
9096
9097    #[gpui::test]
9098    fn test_column_selection_stays_on_unicode_boundaries(cx: &mut TestAppContext) {
9099        let view = multi_line(cx);
9100        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9101        setup_cursors(&mut cx, &view.input, "ab\n你好\ncd");
9102        cx.update(|window, cx| {
9103            view.input.update(cx, |state, cx| {
9104                state.build_columnar_selection(
9105                    ColumnarPoint::new(1, 0),
9106                    ColumnarPoint::new(11, 0),
9107                    cx,
9108                );
9109                let text = state.value();
9110                for sel in state.selections.iter() {
9111                    assert!(text.is_char_boundary(sel.start));
9112                    assert!(text.is_char_boundary(sel.end));
9113                }
9114                state.replace_text_in_range(None, "X", window, cx);
9115            });
9116        });
9117    }
9118
9119    #[gpui::test]
9120    fn test_editor_decorations_follow_typing(cx: &mut TestAppContext) {
9121        let view = InputView::<EditorMode>::new(cx);
9122        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9123        cx.update(|window, cx| {
9124            view.input.update(cx, |state, cx| {
9125                state.set_value("abc def", window, cx);
9126                state.create_decorations_collection(
9127                    vec![crate::input::TextDecoration::new(
9128                        4..7,
9129                        gpui::HighlightStyle::default(),
9130                    )],
9131                    cx,
9132                );
9133                state.set_selected_range(0..0, cx);
9134                state.replace_text_in_range(None, "X", window, cx);
9135                let layers = state.extras.decoration_layers();
9136                assert_eq!(layers.into_iter().flatten().next().unwrap().range, 5..8);
9137            });
9138        });
9139    }
9140
9141    #[gpui::test]
9142    fn test_backspace_preserves_escaped_quote_terminator(cx: &mut TestAppContext) {
9143        let view = InputView::<EditorMode>::new(cx);
9144        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9145        setup_cursors(&mut cx, &view.input, r#"{"s":"\"|"}"#);
9146        cx.update(|window, cx| {
9147            view.input.update(cx, |state, cx| {
9148                set_test_syntax_provider(std::rc::Rc::new(StringAllProvider), cx);
9149                state.backspace(&Backspace, window, cx);
9150            });
9151        });
9152        assert_cursors(&mut cx, &view.input, r#"{"s":"\|"}"#);
9153    }
9154
9155    #[gpui::test]
9156    fn test_ordinary_typing_does_not_query_syntax(cx: &mut TestAppContext) {
9157        struct UnexpectedQuery;
9158        impl crate::input::SyntaxContextProvider for UnexpectedQuery {
9159            fn context_at(&self, _: &ropey::Rope, _: usize) -> crate::input::SyntaxContext {
9160                panic!("ordinary typing must not query syntax");
9161            }
9162        }
9163        let view = InputView::<EditorMode>::new(cx);
9164        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9165        setup_cursors(&mut cx, &view.input, "|");
9166        cx.update(|window, cx| {
9167            view.input.update(cx, |state, cx| {
9168                set_test_syntax_provider(std::rc::Rc::new(UnexpectedQuery), cx);
9169                for c in ["a", "b", "c"] {
9170                    state.replace_text_in_range(None, c, window, cx);
9171                }
9172            });
9173        });
9174        assert_cursors(&mut cx, &view.input, "abc|");
9175    }
9176
9177    #[gpui::test]
9178    fn test_auto_close_is_atomic_at_history_limit(cx: &mut TestAppContext) {
9179        let view = InputView::<EditorMode>::new(cx);
9180        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9181        setup_cursors(&mut cx, &view.input, "|");
9182        cx.update(|window, cx| {
9183            view.input.update(cx, |state, cx| {
9184                for _ in 0..999 {
9185                    state.replace_text_in_range(None, "a", window, cx);
9186                }
9187                state.replace_text_in_range(None, "(", window, cx);
9188                state.undo(&Undo, window, cx);
9189                assert!(!state.text.to_string().contains('('));
9190                assert!(!state.text.to_string().contains(')'));
9191                state.redo(&Redo, window, cx);
9192            });
9193        });
9194        assert_cursors(&mut cx, &view.input, &format!("{}(|)", "a".repeat(999)));
9195    }
9196
9197    #[gpui::test]
9198    fn test_comment_closer_is_inserted_literally(cx: &mut TestAppContext) {
9199        let view = InputView::<EditorMode>::new(cx);
9200        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9201        setup_cursors(&mut cx, &view.input, "// (|)");
9202        cx.update(|window, cx| {
9203            view.input.update(cx, |state, cx| {
9204                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
9205                state.replace_text_in_range(None, ")", window, cx);
9206            });
9207        });
9208        assert_cursors(&mut cx, &view.input, "// ()|)");
9209    }
9210
9211    #[gpui::test]
9212    fn test_explicit_range_skip_does_not_delete_existing_closer_on_undo(cx: &mut TestAppContext) {
9213        let view = InputView::<EditorMode>::new(cx);
9214        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9215        setup_cursors(&mut cx, &view.input, "(a|)");
9216        let before = view.input.read_with(&cx, |state, _| state.text.to_string());
9217        cx.update(|window, cx| {
9218            view.input.update(cx, |state, cx| {
9219                state.replace_text_in_range(Some(2..2), ")", window, cx);
9220            });
9221        });
9222        assert_cursors(&mut cx, &view.input, "(a)|");
9223        cx.update(|window, cx| {
9224            view.input
9225                .update(cx, |state, cx| state.undo(&Undo, window, cx));
9226        });
9227        view.input.read_with(&cx, |state, _| {
9228            assert_eq!(
9229                state.text.to_string(),
9230                before,
9231                "skip must not remove an existing closer on undo"
9232            );
9233        });
9234    }
9235
9236    #[gpui::test]
9237    fn test_pair_redo_restores_interior_cursor(cx: &mut TestAppContext) {
9238        let view = InputView::<EditorMode>::new(cx);
9239        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9240        setup_cursors(&mut cx, &view.input, "|");
9241        cx.update(|window, cx| {
9242            view.input.update(cx, |state, cx| {
9243                state.replace_text_in_range(None, "(", window, cx);
9244            });
9245        });
9246        assert_cursors(&mut cx, &view.input, "(|)");
9247        cx.update(|window, cx| {
9248            view.input.update(cx, |state, cx| {
9249                state.undo(&Undo, window, cx);
9250                state.redo(&Redo, window, cx);
9251            });
9252        });
9253        assert_cursors(&mut cx, &view.input, "(|)");
9254    }
9255
9256    #[gpui::test]
9257    fn test_pair_enter_redo_restores_interior_cursor(cx: &mut TestAppContext) {
9258        let view = InputView::<EditorMode>::new(cx);
9259        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9260        setup_cursors(&mut cx, &view.input, "{|}");
9261        cx.update(|window, cx| {
9262            view.input.update(cx, |state, cx| {
9263                state.enter(
9264                    &Enter {
9265                        secondary: false,
9266                        shift: false,
9267                    },
9268                    window,
9269                    cx,
9270                );
9271            });
9272        });
9273        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
9274        cx.update(|window, cx| {
9275            view.input.update(cx, |state, cx| {
9276                state.undo(&Undo, window, cx);
9277            });
9278        });
9279        assert_cursors(&mut cx, &view.input, "{|}");
9280        cx.update(|window, cx| {
9281            view.input
9282                .update(cx, |state, cx| state.redo(&Redo, window, cx));
9283        });
9284        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
9285    }
9286
9287    #[gpui::test]
9288    fn test_auto_close_parens(cx: &mut TestAppContext) {
9289        let view = InputView::<EditorMode>::new(cx);
9290        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9291        setup_cursors(&mut cx, &view.input, "|");
9292        cx.update(|window, cx| {
9293            view.input.update(cx, |state, cx| {
9294                state.replace_text_in_range(None, "(", window, cx);
9295            });
9296        });
9297        assert_cursors(&mut cx, &view.input, "(|)");
9298        // Selection must be collapsed: typing next must insert, not replace.
9299        cx.update(|window, cx| {
9300            view.input.update(cx, |state, cx| {
9301                state.replace_text_in_range(None, "x", window, cx);
9302            });
9303        });
9304        assert_cursors(&mut cx, &view.input, "(x|)");
9305    }
9306
9307    #[gpui::test]
9308    fn test_auto_close_skip_over_closer(cx: &mut TestAppContext) {
9309        let view = InputView::<EditorMode>::new(cx);
9310        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9311        setup_cursors(&mut cx, &view.input, "(|)");
9312        cx.update(|window, cx| {
9313            view.input.update(cx, |state, cx| {
9314                state.replace_text_in_range(None, ")", window, cx);
9315            });
9316        });
9317        // No duplicate closer, cursor moves past the existing one.
9318        assert_cursors(&mut cx, &view.input, "()|");
9319        // Selection must be collapsed, not covering blank space.
9320        view.input.read_with(&cx, |state, _| {
9321            assert!(state.active_selection().is_empty());
9322        });
9323    }
9324
9325    #[gpui::test]
9326    fn test_auto_close_no_pair_inside_word(cx: &mut TestAppContext) {
9327        let view = InputView::<EditorMode>::new(cx);
9328        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9329        setup_cursors(&mut cx, &view.input, "don|");
9330        cx.update(|window, cx| {
9331            view.input.update(cx, |state, cx| {
9332                state.replace_text_in_range(None, "'", window, cx);
9333            });
9334        });
9335        // Contraction: no auto-close.
9336        assert_cursors(&mut cx, &view.input, "don'|");
9337    }
9338
9339    #[gpui::test]
9340    fn test_auto_close_quote_after_cjk(cx: &mut TestAppContext) {
9341        let view = InputView::<EditorMode>::new(cx);
9342        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9343        // Unicode-safe previous-char lookup: must not panic on multi-byte prefix.
9344        // CJK counts as word-like, so no pairing — single quote inserted.
9345        setup_cursors(&mut cx, &view.input, "中|");
9346        cx.update(|window, cx| {
9347            view.input.update(cx, |state, cx| {
9348                state.replace_text_in_range(None, "'", window, cx);
9349            });
9350        });
9351        assert_cursors(&mut cx, &view.input, "中'|");
9352    }
9353
9354    #[gpui::test]
9355    fn test_auto_close_closer_skips_at_string_end(cx: &mut TestAppContext) {
9356        let view = InputView::<EditorMode>::new(cx);
9357        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9358        // Cursor before existing closing quote: typing `"` skips past it.
9359        setup_cursors(&mut cx, &view.input, "\"hello|\"");
9360        cx.update(|window, cx| {
9361            view.input.update(cx, |state, cx| {
9362                state.replace_text_in_range(None, "\"", window, cx);
9363            });
9364        });
9365        assert_cursors(&mut cx, &view.input, "\"hello\"|");
9366        view.input.read_with(&cx, |state, _| {
9367            assert!(state.active_selection().is_empty());
9368        });
9369    }
9370
9371    #[gpui::test]
9372    fn test_backspace_pair_with_cjk_prefix(cx: &mut TestAppContext) {
9373        let view = InputView::<EditorMode>::new(cx);
9374        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9375        // UTF-16 conversion: deleting `()` after CJK must not touch neighbors.
9376        setup_cursors(&mut cx, &view.input, "中(|)abc");
9377        cx.update(|window, cx| {
9378            view.input
9379                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
9380        });
9381        assert_cursors(&mut cx, &view.input, "中|abc");
9382    }
9383
9384    #[gpui::test]
9385    fn test_enter_split_respects_smart_indent_off(cx: &mut TestAppContext) {
9386        let view = InputView::<EditorMode>::new(cx);
9387        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9388        setup_cursors(&mut cx, &view.input, "{|}");
9389        cx.update(|window, cx| {
9390            view.input.update(cx, |state, cx| {
9391                state.set_smart_indent(false, window, cx);
9392                state.enter(
9393                    &Enter {
9394                        secondary: false,
9395                        shift: false,
9396                    },
9397                    window,
9398                    cx,
9399                );
9400            });
9401        });
9402        // Smart indent controls both structural splitting and extra indentation.
9403        assert_cursors(&mut cx, &view.input, "{\n|}");
9404    }
9405
9406    #[gpui::test]
9407    fn test_enter_split_is_independent_of_auto_close(cx: &mut TestAppContext) {
9408        let view = InputView::<EditorMode>::new(cx);
9409        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9410        setup_cursors(&mut cx, &view.input, "{|}");
9411        cx.update(|window, cx| {
9412            view.input.update(cx, |state, cx| {
9413                state.set_auto_close(false, window, cx);
9414                state.enter(
9415                    &Enter {
9416                        secondary: false,
9417                        shift: false,
9418                    },
9419                    window,
9420                    cx,
9421                );
9422            });
9423        });
9424        // Disabling automatic insertion does not disable structural indentation.
9425        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
9426    }
9427
9428    #[gpui::test]
9429    fn test_language_config_applies_before_render_and_on_language_change(cx: &mut TestAppContext) {
9430        use crate::input::{AutoClosingPair, set_language_config};
9431        let view = InputView::<EditorMode>::new(cx);
9432        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9433        cx.update(|window, cx| {
9434            set_language_config(
9435                "alpha",
9436                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
9437                cx,
9438            );
9439            set_language_config(
9440                "beta",
9441                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("‹", "›")]),
9442                cx,
9443            );
9444            let first = cx.new(|cx| EditorState::new(window, cx).language("ALPHA"));
9445            let second = cx.new(|cx| EditorState::new(window, cx).language("beta"));
9446            first.update(cx, |state, cx| {
9447                state.replace_text_in_range(None, "«", window, cx);
9448                assert_eq!(state.text().to_string(), "«»");
9449                state.set_value("", window, cx);
9450                state.set_smart_indent(false, window, cx);
9451                state.set_highlighter("beta", cx);
9452                assert!(!state.mode.is_smart_indent());
9453                state.replace_text_in_range(None, "‹", window, cx);
9454                assert_eq!(state.text().to_string(), "‹›");
9455                state.set_value("", window, cx);
9456                state.set_highlighter("unknown", cx);
9457                state.replace_text_in_range(None, "(", window, cx);
9458                assert_eq!(state.text().to_string(), "()");
9459            });
9460            second.update(cx, |state, cx| {
9461                state.replace_text_in_range(None, "‹", window, cx);
9462                assert_eq!(state.text().to_string(), "‹›");
9463            });
9464        });
9465    }
9466
9467    #[gpui::test]
9468    fn test_language_service_replacement_updates_syntax_without_render(cx: &mut TestAppContext) {
9469        use crate::input::{LanguageProvider, SyntaxContextProvider, set_language_provider};
9470        struct StringLanguages(Rc<Cell<usize>>);
9471        impl LanguageProvider for StringLanguages {
9472            fn syntax_context_provider(&self, _: &str) -> Option<Rc<dyn SyntaxContextProvider>> {
9473                self.0.set(self.0.get() + 1);
9474                Some(Rc::new(StringAllProvider))
9475            }
9476        }
9477        struct CodeLanguages;
9478        impl LanguageProvider for CodeLanguages {}
9479        let view = InputView::<EditorMode>::new(cx);
9480        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9481        cx.update(|window, cx| {
9482            let creations = Rc::new(Cell::new(0));
9483            set_language_provider(Rc::new(StringLanguages(creations.clone())), cx);
9484            view.input.update(cx, |state, cx| {
9485                state.replace_text_in_range(None, "(", window, cx);
9486                state.replace_text_in_range(None, "[", window, cx);
9487                assert_eq!(state.text().to_string(), "([");
9488                assert_eq!(
9489                    creations.get(),
9490                    1,
9491                    "retain the document provider between edits"
9492                );
9493            });
9494            set_language_provider(Rc::new(CodeLanguages), cx);
9495            view.input.update(cx, |state, cx| {
9496                state.replace_text_in_range(None, "{", window, cx);
9497                assert_eq!(state.text().to_string(), "([{}");
9498            });
9499        });
9500    }
9501
9502    #[gpui::test]
9503    fn test_language_config_updates_existing_editors(cx: &mut TestAppContext) {
9504        use crate::input::{AutoClosingPair, set_language_config};
9505        let view = InputView::<EditorMode>::new(cx);
9506        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9507        setup_cursors(&mut cx, &view.input, "|");
9508        cx.update(|window, cx| {
9509            view.input.update(cx, |state, cx| {
9510                state.set_highlighter("CUSTOM", cx);
9511                state.set_auto_close(false, window, cx);
9512                state.set_smart_indent(false, window, cx);
9513            });
9514            set_language_config(
9515                "custom",
9516                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
9517                cx,
9518            );
9519            // Batched registrations must update every affected language.
9520            set_language_config("other", LanguageConfig::default(), cx);
9521            view.input.update(cx, |state, cx| {
9522                assert!(!state.mode.is_auto_close());
9523                assert!(!state.mode.is_smart_indent());
9524                state.set_auto_close(true, window, cx);
9525                state.replace_text_in_range(None, "«", window, cx);
9526            });
9527        });
9528        assert_cursors(&mut cx, &view.input, "«|»");
9529    }
9530
9531    #[gpui::test]
9532    fn test_auto_close_before_is_language_configurable(cx: &mut TestAppContext) {
9533        let view = InputView::<EditorMode>::new(cx);
9534        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9535        setup_cursors(&mut cx, &view.input, "|word");
9536        cx.update(|window, cx| {
9537            view.input.update(cx, |state, cx| {
9538                state.replace_text_in_range(None, "(", window, cx)
9539            });
9540        });
9541        assert_cursors(&mut cx, &view.input, "(|word");
9542        setup_cursors(&mut cx, &view.input, "|word");
9543        cx.update(|window, cx| {
9544            view.input.update(cx, |state, cx| {
9545                crate::input::set_language_config(
9546                    state.language_name(),
9547                    LanguageConfig::default().auto_close_before("w"),
9548                    cx,
9549                );
9550                state.replace_text_in_range(None, "(", window, cx);
9551            });
9552        });
9553        assert_cursors(&mut cx, &view.input, "(|)word");
9554    }
9555
9556    #[gpui::test]
9557    fn test_pair_context_restrictions_are_per_pair(cx: &mut TestAppContext) {
9558        use crate::input::{AutoClosingPair, SyntaxContext};
9559        let view = InputView::<EditorMode>::new(cx);
9560        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9561        setup_cursors(&mut cx, &view.input, "|");
9562        cx.update(|window, cx| {
9563            view.input.update(cx, |state, cx| {
9564                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
9565                crate::input::set_language_config(
9566                    state.language_name(),
9567                    LanguageConfig::default().auto_closing_pairs([
9568                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::String]),
9569                        AutoClosingPair::new("[", "]").not_in([SyntaxContext::Comment]),
9570                    ]),
9571                    cx,
9572                );
9573                state.replace_text_in_range(None, "(", window, cx);
9574                state.replace_text_in_range(None, "[", window, cx);
9575            });
9576        });
9577        assert_cursors(&mut cx, &view.input, "([|)");
9578    }
9579
9580    #[gpui::test]
9581    fn test_multichar_delimiters_insert_delete_and_skip(cx: &mut TestAppContext) {
9582        use crate::input::{AutoClosingPair, SyntaxContext};
9583        struct BlockComment;
9584        impl crate::input::SyntaxContextProvider for BlockComment {
9585            fn context_at(&self, text: &ropey::Rope, _: usize) -> SyntaxContext {
9586                if text.to_string().contains("/*") {
9587                    SyntaxContext::Comment
9588                } else {
9589                    SyntaxContext::Code
9590                }
9591            }
9592        }
9593        let view = InputView::<EditorMode>::new(cx);
9594        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9595        setup_cursors(&mut cx, &view.input, "|");
9596        cx.update(|window, cx| {
9597            view.input.update(cx, |state, cx| {
9598                set_test_syntax_provider(std::rc::Rc::new(BlockComment), cx);
9599                crate::input::set_language_config(
9600                    state.language_name(),
9601                    LanguageConfig::default()
9602                        .auto_closing_pairs([AutoClosingPair::new("/*", "*/")
9603                            .not_in([SyntaxContext::String, SyntaxContext::Comment])]),
9604                    cx,
9605                );
9606                state.replace_text_in_range(None, "/", window, cx);
9607                state.replace_text_in_range(None, "*", window, cx);
9608            });
9609        });
9610        assert_cursors(&mut cx, &view.input, "/*|*/");
9611        cx.update(|window, cx| {
9612            view.input
9613                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
9614        });
9615        assert_cursors(&mut cx, &view.input, "|");
9616        cx.update(|window, cx| {
9617            view.input.update(cx, |state, cx| {
9618                state.undo(&Undo, window, cx);
9619                state.replace_text_in_range(None, "*", window, cx);
9620                state.replace_text_in_range(None, "/", window, cx);
9621            });
9622        });
9623        assert_cursors(&mut cx, &view.input, "/**/|");
9624    }
9625
9626    #[gpui::test]
9627    fn test_auto_closed_pairs_fallback_and_explicit_disable(cx: &mut TestAppContext) {
9628        use crate::input::BracketPair;
9629        let view = InputView::<EditorMode>::new(cx);
9630        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9631        setup_cursors(&mut cx, &view.input, "|");
9632        cx.update(|window, cx| {
9633            view.input.update(cx, |state, cx| {
9634                let mut rules = LanguageConfig::default().brackets([BracketPair::new("«", "»")]);
9635                rules.auto_closing_pairs = None;
9636                crate::input::set_language_config(state.language_name(), rules.clone(), cx);
9637                state.replace_text_in_range(None, "«", window, cx);
9638                crate::input::set_language_config(
9639                    state.language_name(),
9640                    rules.auto_closing_pairs([]),
9641                    cx,
9642                );
9643                state.replace_text_in_range(None, "«", window, cx);
9644            });
9645        });
9646        assert_cursors(&mut cx, &view.input, "««|»");
9647    }
9648
9649    #[gpui::test]
9650    fn test_undo_open_composition_preserves_generated_pairs(cx: &mut TestAppContext) {
9651        use crate::input::{AutoClosingPair, SyntaxContext};
9652        let view = InputView::<EditorMode>::new(cx);
9653        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9654        setup_cursors(&mut cx, &view.input, "|");
9655        cx.update(|window, cx| {
9656            view.input.update(cx, |state, cx| {
9657                crate::input::set_language_config(
9658                    state.language_name(),
9659                    LanguageConfig::default()
9660                        .auto_closing_pairs([
9661                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
9662                        ]),
9663                    cx,
9664                );
9665                state.replace_text_in_range(None, "/", window, cx);
9666                state.replace_text_in_range(None, "*", window, cx);
9667                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
9668                state.replace_and_mark_text_in_range(None, "x", Some(1..1), window, cx);
9669                state.undo(&Undo, window, cx);
9670                state.replace_text_in_range(None, "*", window, cx);
9671                state.replace_text_in_range(None, "/", window, cx);
9672            });
9673        });
9674        assert_cursors(&mut cx, &view.input, "/**/|");
9675    }
9676
9677    #[gpui::test]
9678    fn test_undo_does_not_promote_literal_comment_delimiters(cx: &mut TestAppContext) {
9679        use crate::input::{AutoClosingPair, SyntaxContext};
9680        let view = InputView::<EditorMode>::new(cx);
9681        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9682        setup_cursors(&mut cx, &view.input, "/*|*/");
9683        cx.update(|window, cx| {
9684            view.input.update(cx, |state, cx| {
9685                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
9686                crate::input::set_language_config(
9687                    state.language_name(),
9688                    LanguageConfig::default()
9689                        .auto_closing_pairs([
9690                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
9691                        ]),
9692                    cx,
9693                );
9694                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
9695                state.undo(&Undo, window, cx);
9696                state.backspace(&Backspace, window, cx);
9697            });
9698        });
9699        assert_cursors(&mut cx, &view.input, "/|*/");
9700    }
9701
9702    #[gpui::test]
9703    fn test_multiple_generated_pairs_retain_their_identity(cx: &mut TestAppContext) {
9704        use crate::input::{AutoClosingPair, SyntaxContext};
9705        struct CommentScope;
9706        impl crate::input::SyntaxContextProvider for CommentScope {
9707            fn context_at(&self, text: &ropey::Rope, offset: usize) -> SyntaxContext {
9708                if text
9709                    .to_string()
9710                    .find("*/")
9711                    .is_some_and(|end| offset < end + 2)
9712                {
9713                    SyntaxContext::Comment
9714                } else {
9715                    SyntaxContext::Code
9716                }
9717            }
9718        }
9719        let view = InputView::<EditorMode>::new(cx);
9720        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9721        setup_cursors(&mut cx, &view.input, "|");
9722        cx.update(|window, cx| {
9723            view.input.update(cx, |state, cx| {
9724                set_test_syntax_provider(std::rc::Rc::new(CommentScope), cx);
9725                crate::input::set_language_config(
9726                    state.language_name(),
9727                    LanguageConfig::default().auto_closing_pairs([
9728                        AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment]),
9729                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::Comment]),
9730                    ]),
9731                    cx,
9732                );
9733                state.replace_text_in_range(None, "/", window, cx);
9734                state.replace_text_in_range(None, "*", window, cx);
9735                state.set_selected_range(4..4, cx);
9736                state.replace_text_in_range(None, "(", window, cx);
9737                state.set_selected_range(2..2, cx);
9738                state.replace_text_in_range(None, "*", window, cx);
9739                state.replace_text_in_range(None, "/", window, cx);
9740            });
9741        });
9742        assert_cursors(&mut cx, &view.input, "/**/|()");
9743        cx.update(|window, cx| {
9744            view.input.update(cx, |state, cx| {
9745                state.set_selected_range(2..2, cx);
9746                state.backspace(&Backspace, window, cx);
9747                state.undo(&Undo, window, cx);
9748                state.redo(&Redo, window, cx);
9749            });
9750        });
9751        assert_cursors(&mut cx, &view.input, "|()");
9752    }
9753
9754    #[gpui::test]
9755    fn test_replacing_generated_opener_invalidates_the_pair(cx: &mut TestAppContext) {
9756        use crate::input::{AutoClosingPair, SyntaxContext};
9757        let view = InputView::<EditorMode>::new(cx);
9758        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9759        setup_cursors(&mut cx, &view.input, "|");
9760        cx.update(|window, cx| {
9761            view.input.update(cx, |state, cx| {
9762                crate::input::set_language_config(
9763                    state.language_name(),
9764                    LanguageConfig::default()
9765                        .auto_closing_pairs([
9766                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
9767                        ]),
9768                    cx,
9769                );
9770                state.replace_text_in_range(None, "/", window, cx);
9771                state.replace_text_in_range(None, "*", window, cx);
9772                state.replace_text_in_range(Some(0..2), "//", window, cx);
9773                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
9774                state.replace_text_in_range(None, "*", window, cx);
9775            });
9776        });
9777        assert_cursors(&mut cx, &view.input, "//*|*/");
9778    }
9779
9780    #[gpui::test]
9781    fn test_configuration_preserves_compiled_regex_options(cx: &mut TestAppContext) {
9782        use crate::input::IndentationRules;
9783        let view = InputView::<EditorMode>::new(cx);
9784        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9785        setup_cursors(&mut cx, &view.input, "BEGIN|");
9786        cx.update(|window, cx| {
9787            view.input.update(cx, |state, cx| {
9788                let rules = |insensitive| {
9789                    LanguageConfig::default().indentation_rules(IndentationRules::new(
9790                        regex::RegexBuilder::new("begin$")
9791                            .case_insensitive(insensitive)
9792                            .build()
9793                            .unwrap(),
9794                        regex::Regex::new("^end").unwrap(),
9795                    ))
9796                };
9797                crate::input::set_language_config(state.language_name(), rules(false), cx);
9798                crate::input::set_language_config(state.language_name(), rules(true), cx);
9799                state.enter(
9800                    &Enter {
9801                        secondary: false,
9802                        shift: false,
9803                    },
9804                    window,
9805                    cx,
9806                );
9807            });
9808        });
9809        assert_cursors(&mut cx, &view.input, "BEGIN\n  |");
9810    }
9811
9812    #[gpui::test]
9813    fn test_indent_patterns_are_applied_on_enter(cx: &mut TestAppContext) {
9814        use crate::input::IndentationRules;
9815        let view = InputView::<EditorMode>::new(cx);
9816        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9817        let rules = LanguageConfig::default().indentation_rules(IndentationRules::new(
9818            regex::Regex::new(r"\bbegin\s*$").unwrap(),
9819            regex::Regex::new(r"^\s*end\b").unwrap(),
9820        ));
9821        for (before, expected) in [("begin|", "begin\n  |"), ("  |end", "  \n|end")] {
9822            setup_cursors(&mut cx, &view.input, before);
9823            cx.update(|window, cx| {
9824                view.input.update(cx, |state, cx| {
9825                    crate::input::set_language_config(state.language_name(), rules.clone(), cx);
9826                    state.enter(
9827                        &Enter {
9828                            secondary: false,
9829                            shift: false,
9830                        },
9831                        window,
9832                        cx,
9833                    );
9834                });
9835            });
9836            assert_cursors(&mut cx, &view.input, expected);
9837        }
9838    }
9839
9840    #[gpui::test]
9841    fn test_custom_language_config(cx: &mut TestAppContext) {
9842        let view = InputView::<EditorMode>::new(cx);
9843        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9844        cx.update(|_window, cx| {
9845            view.input.update(cx, |state, cx| {
9846                crate::input::set_language_config(
9847                    state.language_name(),
9848                    crate::input::language_config::LanguageConfig::default()
9849                        .brackets([crate::input::BracketPair::new("«", "»")])
9850                        .auto_closing_pairs([crate::input::AutoClosingPair::new("«", "»")]),
9851                    cx,
9852                );
9853            });
9854        });
9855        // Custom pair closes.
9856        setup_cursors(&mut cx, &view.input, "|");
9857        cx.update(|window, cx| {
9858            view.input.update(cx, |state, cx| {
9859                state.replace_text_in_range(None, "«", window, cx);
9860            });
9861        });
9862        assert_cursors(&mut cx, &view.input, "«|»");
9863        // Default `(` no longer pairs under custom rules.
9864        cx.update(|window, cx| {
9865            view.input.update(cx, |state, cx| {
9866                state.set_value("\n", window, cx);
9867                state.set_selected_range(0..0, cx);
9868                state.replace_text_in_range(None, "(", window, cx);
9869            });
9870        });
9871        assert_cursors(&mut cx, &view.input, "(|");
9872    }
9873
9874    #[gpui::test]
9875    fn test_skip_records_no_history(cx: &mut TestAppContext) {
9876        let view = InputView::<EditorMode>::new(cx);
9877        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9878        setup_cursors(&mut cx, &view.input, "(a|)");
9879        cx.update(|window, cx| {
9880            view.input.update(cx, |state, cx| {
9881                // Type a normal char (recorded), then skip over `)`.
9882                state.replace_text_in_range(None, "b", window, cx);
9883            });
9884        });
9885        assert_cursors(&mut cx, &view.input, "(ab|)");
9886        cx.update(|window, cx| {
9887            view.input.update(cx, |state, cx| {
9888                state.replace_text_in_range(None, ")", window, cx);
9889            });
9890        });
9891        // Skip is a pure cursor move: no history entry of its own.
9892        assert_cursors(&mut cx, &view.input, "(ab)|");
9893        // Undo removes `b`, never exposing a transient `())`.
9894        cx.update(|window, cx| {
9895            view.input
9896                .update(cx, |state, cx| state.undo(&Undo, window, cx));
9897        });
9898        assert_cursors(&mut cx, &view.input, "(a|)");
9899    }
9900
9901    #[gpui::test]
9902    fn test_pair_insert_undo_removes_both(cx: &mut TestAppContext) {
9903        let view = InputView::<EditorMode>::new(cx);
9904        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9905        setup_cursors(&mut cx, &view.input, "|");
9906        cx.update(|window, cx| {
9907            view.input.update(cx, |state, cx| {
9908                state.replace_text_in_range(None, "(", window, cx);
9909            });
9910        });
9911        assert_cursors(&mut cx, &view.input, "(|)");
9912        // One undo removes the whole pair (typing coalescing).
9913        cx.update(|window, cx| {
9914            view.input
9915                .update(cx, |state, cx| state.undo(&Undo, window, cx));
9916        });
9917        assert_cursors(&mut cx, &view.input, "|");
9918    }
9919
9920    #[gpui::test]
9921    fn test_escaped_quote_does_not_skip(cx: &mut TestAppContext) {
9922        let view = InputView::<EditorMode>::new(cx);
9923        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9924        // Odd backslashes: the typed quote is escaped, insert literally.
9925        setup_cursors(&mut cx, &view.input, "\"hello\\|\"");
9926        cx.update(|window, cx| {
9927            view.input.update(cx, |state, cx| {
9928                state.replace_text_in_range(None, "\"", window, cx);
9929            });
9930        });
9931        assert_cursors(&mut cx, &view.input, "\"hello\\\"|\"");
9932    }
9933
9934    #[gpui::test]
9935    fn test_even_backslashes_still_skip(cx: &mut TestAppContext) {
9936        let view = InputView::<EditorMode>::new(cx);
9937        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9938        // Even backslashes: quote is not escaped, skip over the terminator.
9939        setup_cursors(&mut cx, &view.input, "\"hello\\\\|\"");
9940        cx.update(|window, cx| {
9941            view.input.update(cx, |state, cx| {
9942                state.replace_text_in_range(None, "\"", window, cx);
9943            });
9944        });
9945        assert_cursors(&mut cx, &view.input, "\"hello\\\\\"|");
9946    }
9947
9948    #[gpui::test]
9949    fn test_editor_options_survive_configuration_replacement(cx: &mut TestAppContext) {
9950        use crate::input::{BracketPair, LanguageConfig};
9951
9952        let view = InputView::<EditorMode>::new(cx);
9953        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9954        cx.update(|window, cx| {
9955            view.input.update(cx, |state, cx| {
9956                // Language changes update rules without changing editor preferences.
9957                state.set_smart_indent(false, window, cx);
9958                crate::input::set_language_config(
9959                    state.language_name(),
9960                    LanguageConfig::default().brackets([BracketPair::new("«", "»")]),
9961                    cx,
9962                );
9963            });
9964        });
9965        view.input.read_with(&cx, |state, _| {
9966            assert!(!state.mode.is_smart_indent(), "editor option preserved");
9967            assert!(state.mode.is_auto_close());
9968            let pairs = &state.mode.language_config().unwrap().brackets;
9969            assert_eq!(pairs.len(), 1, "pairs follow configuration");
9970            assert_eq!(pairs[0].open.as_ref(), "«");
9971        });
9972    }
9973
9974    struct CommentAllProvider;
9975    impl crate::input::SyntaxContextProvider for CommentAllProvider {
9976        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
9977            crate::input::SyntaxContext::Comment
9978        }
9979    }
9980
9981    struct StringAllProvider;
9982    impl crate::input::SyntaxContextProvider for StringAllProvider {
9983        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
9984            crate::input::SyntaxContext::String
9985        }
9986    }
9987
9988    #[gpui::test]
9989    fn test_smart_indent_suppressed_in_strings(cx: &mut TestAppContext) {
9990        use std::rc::Rc;
9991
9992        let view = InputView::<EditorMode>::new(cx);
9993        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9994        cx.update(|_window, cx| {
9995            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
9996        });
9997        // Trigger `{` inside a string: no extra level, base indent only.
9998        setup_cursors(&mut cx, &view.input, "  x = {|");
9999        cx.update(|window, cx| {
10000            view.input.update(cx, |state, cx| {
10001                state.enter(
10002                    &Enter {
10003                        secondary: false,
10004                        shift: false,
10005                    },
10006                    window,
10007                    cx,
10008                );
10009            });
10010        });
10011        assert_cursors(&mut cx, &view.input, "  x = {\n  |");
10012    }
10013
10014    #[gpui::test]
10015    fn test_split_suppressed_in_strings(cx: &mut TestAppContext) {
10016        use std::rc::Rc;
10017
10018        let view = InputView::<EditorMode>::new(cx);
10019        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10020        cx.update(|_window, cx| {
10021            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
10022        });
10023        // No three-way split inside strings: plain newline instead.
10024        setup_cursors(&mut cx, &view.input, "{|}");
10025        cx.update(|window, cx| {
10026            view.input.update(cx, |state, cx| {
10027                state.enter(
10028                    &Enter {
10029                        secondary: false,
10030                        shift: false,
10031                    },
10032                    window,
10033                    cx,
10034                );
10035            });
10036        });
10037        assert_cursors(&mut cx, &view.input, "{\n|}");
10038    }
10039
10040    #[gpui::test]
10041    fn test_comment_context_disables_pairing(cx: &mut TestAppContext) {
10042        use std::rc::Rc;
10043
10044        let view = InputView::<EditorMode>::new(cx);
10045        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10046        cx.update(|_window, cx| {
10047            set_test_syntax_provider(Rc::new(CommentAllProvider), cx);
10048        });
10049        setup_cursors(&mut cx, &view.input, "|");
10050        cx.update(|window, cx| {
10051            view.input.update(cx, |state, cx| {
10052                state.replace_text_in_range(None, "(", window, cx);
10053            });
10054        });
10055        // In comments every character is literal.
10056        assert_cursors(&mut cx, &view.input, "(|");
10057    }
10058
10059    #[gpui::test]
10060    fn test_backspace_deletes_empty_pair(cx: &mut TestAppContext) {
10061        let view = InputView::<EditorMode>::new(cx);
10062        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10063        setup_cursors(&mut cx, &view.input, "{|}");
10064        cx.update(|window, cx| {
10065            view.input
10066                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
10067        });
10068        assert_cursors(&mut cx, &view.input, "|");
10069    }
10070
10071    #[gpui::test]
10072    fn test_backspace_keeps_closer_with_content(cx: &mut TestAppContext) {
10073        let view = InputView::<EditorMode>::new(cx);
10074        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10075        setup_cursors(&mut cx, &view.input, "{d|}");
10076        cx.update(|window, cx| {
10077            view.input
10078                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
10079        });
10080        // Only `d` is deleted, the closing brace stays.
10081        assert_cursors(&mut cx, &view.input, "{|}");
10082    }
10083
10084    #[gpui::test]
10085    fn test_enter_splits_brackets(cx: &mut TestAppContext) {
10086        let view = InputView::<EditorMode>::new(cx);
10087        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10088        setup_cursors(&mut cx, &view.input, "{|}");
10089        cx.update(|window, cx| {
10090            view.input.update(cx, |state, cx| {
10091                state.enter(
10092                    &Enter {
10093                        secondary: false,
10094                        shift: false,
10095                    },
10096                    window,
10097                    cx,
10098                );
10099            });
10100        });
10101        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10102        // Selection must be collapsed: typing next must insert, not replace.
10103        view.input.read_with(&cx, |state, _| {
10104            assert!(state.active_selection().is_empty());
10105        });
10106        cx.update(|window, cx| {
10107            view.input.update(cx, |state, cx| {
10108                state.replace_text_in_range(None, "x", window, cx);
10109            });
10110        });
10111        assert_cursors(&mut cx, &view.input, "{\n  x|\n}");
10112    }
10113
10114    #[gpui::test]
10115    fn test_enter_smart_indent_after_brace(cx: &mut TestAppContext) {
10116        let view = InputView::<EditorMode>::new(cx);
10117        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10118        setup_cursors(&mut cx, &view.input, "fn main() {|");
10119        cx.update(|window, cx| {
10120            view.input.update(cx, |state, cx| {
10121                state.enter(
10122                    &Enter {
10123                        secondary: false,
10124                        shift: false,
10125                    },
10126                    window,
10127                    cx,
10128                );
10129            });
10130        });
10131        assert_cursors(&mut cx, &view.input, "fn main() {\n  |");
10132    }
10133
10134    #[gpui::test]
10135    fn test_block_indent_tracks_all_preceding_edits(cx: &mut TestAppContext) {
10136        let view = multi_line(cx);
10137        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10138        setup_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
10139        cx.update(|window, cx| {
10140            view.input
10141                .update(cx, |state, cx| state.indent(true, window, cx));
10142        });
10143        assert_cursors(&mut cx, &view.input, "  |ab\n  |cd\n  |ef");
10144        cx.update(|window, cx| {
10145            view.input
10146                .update(cx, |state, cx| state.outdent(true, window, cx));
10147        });
10148        assert_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
10149    }
10150
10151    #[gpui::test]
10152    fn test_block_outdent_clamps_cursor_inside_indent(cx: &mut TestAppContext) {
10153        let view = multi_line(cx);
10154        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10155        setup_cursors(&mut cx, &view.input, "ab\n | cd");
10156        cx.update(|window, cx| {
10157            view.input
10158                .update(cx, |state, cx| state.outdent(true, window, cx));
10159        });
10160        assert_cursors(&mut cx, &view.input, "ab\n|cd");
10161    }
10162
10163    #[gpui::test]
10164    fn test_ime_restores_original_selection(cx: &mut TestAppContext) {
10165        let view = InputView::build(cx, |state| state);
10166        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10167        cx.update(|window, cx| {
10168            view.input.update(cx, |state, cx| {
10169                state.set_value("abc", window, cx);
10170                state.set_selected_range(1..2, cx);
10171                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
10172                state.replace_text_in_range(None, "你", window, cx);
10173                state.undo(&Undo, window, cx);
10174                assert_eq!(state.value(), "abc");
10175                assert_eq!(state.selected_range(), 1..2);
10176                state.redo(&Redo, window, cx);
10177                assert_eq!(state.selected_range(), 4..4);
10178            });
10179        });
10180    }
10181
10182    #[gpui::test]
10183    fn test_noop_does_not_change_redo_selection(cx: &mut TestAppContext) {
10184        let view = multi_line(cx);
10185        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10186        cx.update(|window, cx| {
10187            view.input.update(cx, |state, cx| {
10188                state.set_value("abc", window, cx);
10189                state.set_selected_range(1..1, cx);
10190                state.replace_text_in_range(None, "X", window, cx);
10191                state.set_selected_range(0..0, cx);
10192                state.replace_text_in_range(None, "", window, cx);
10193                state.undo(&Undo, window, cx);
10194                state.redo(&Redo, window, cx);
10195                assert_eq!(state.value(), "aXbc");
10196                assert_eq!(state.selected_range(), 2..2);
10197            });
10198        });
10199    }
10200
10201    #[gpui::test]
10202    fn test_multi_cursor_insert_text(cx: &mut TestAppContext) {
10203        let view = multi_line(cx);
10204        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10205        let input = view.input;
10206
10207        setup_cursors(&mut cx, &input, "|hello |world|");
10208        cx.update(|window, cx| {
10209            input.update(cx, |state, cx| {
10210                state.replace_text_in_range(None, ">>>", window, cx);
10211            });
10212        });
10213        assert_cursors(&mut cx, &input, ">>>|hello >>>|world>>>|");
10214    }
10215
10216    #[gpui::test]
10217    fn test_multi_cursor_delete_backward(cx: &mut TestAppContext) {
10218        let view = multi_line(cx);
10219        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10220        let input = view.input;
10221
10222        setup_cursors(&mut cx, &input, "|islands| cars|");
10223        cx.update(|window, cx| {
10224            input.update(cx, |state, cx| {
10225                state.backspace(&Backspace, window, cx);
10226            });
10227        });
10228        // The first cursor has nothing to delete. The others delete an `s`.
10229        assert_cursors(&mut cx, &input, "|island| car|");
10230    }
10231
10232    #[gpui::test]
10233    fn test_multi_cursor_delete_forward_merges(cx: &mut TestAppContext) {
10234        let view = multi_line(cx);
10235        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10236        let input = view.input;
10237
10238        setup_cursors(&mut cx, &input, "hello| |world");
10239        cx.update(|window, cx| {
10240            input.update(cx, |state, cx| {
10241                state.delete(&Delete, window, cx);
10242            });
10243        });
10244        // Adjacent deletions merge into a single cursor.
10245        assert_cursors(&mut cx, &input, "hello|orld");
10246    }
10247
10248    #[gpui::test]
10249    fn test_multi_cursor_multiline_insert_and_delete(cx: &mut TestAppContext) {
10250        let view = multi_line(cx);
10251        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10252        let input = view.input;
10253
10254        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
10255        cx.update(|window, cx| {
10256            input.update(cx, |state, cx| {
10257                state.replace_text_in_range(None, "a", window, cx);
10258            });
10259        });
10260        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
10261
10262        // The whole multi-edit insert is a single undo transaction.
10263        input.read_with(&cx, |state, _| {
10264            assert_eq!(state.undo_manager.undo_count(), 1);
10265        });
10266
10267        cx.update(|window, cx| {
10268            input.update(cx, |state, cx| {
10269                state.backspace(&Backspace, window, cx);
10270            });
10271        });
10272        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
10273    }
10274
10275    #[gpui::test]
10276    fn test_add_cursor_below_preserves_column(cx: &mut TestAppContext) {
10277        let view = multi_line(cx);
10278        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10279        let input = view.input;
10280
10281        setup_cursors(&mut cx, &input, "ab|cd\nabcd");
10282        cx.update(|window, cx| {
10283            input.update(cx, |state, cx| {
10284                state.add_cursor_below(&AddCursorBelow, window, cx);
10285            });
10286        });
10287        assert_cursors(&mut cx, &input, "ab|cd\nab|cd");
10288    }
10289
10290    #[gpui::test]
10291    fn test_add_cursor_at_rejects_duplicates(cx: &mut TestAppContext) {
10292        let view = multi_line(cx);
10293        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10294        let input = view.input;
10295
10296        setup_cursors(&mut cx, &input, "he|llo");
10297        cx.update(|_, cx| {
10298            input.update(cx, |state, cx| {
10299                // Duplicate of the existing cursor is rejected.
10300                state.add_cursor_at(2, cx);
10301                assert_eq!(state.selections.len(), 1);
10302                // A distinct offset adds a cursor.
10303                state.add_cursor_at(4, cx);
10304                assert_eq!(state.selections.len(), 2);
10305            });
10306        });
10307    }
10308
10309    #[gpui::test]
10310    fn test_multi_cursor_undo_redo_restores_selections(cx: &mut TestAppContext) {
10311        let view = multi_line(cx);
10312        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10313        let input = view.input;
10314
10315        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
10316        cx.update(|window, cx| {
10317            input.update(cx, |state, cx| {
10318                state.replace_text_in_range(None, "a", window, cx);
10319            });
10320        });
10321        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
10322
10323        cx.update(|window, cx| {
10324            input.update(cx, |state, cx| {
10325                state.undo(&Undo, window, cx);
10326            });
10327        });
10328        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
10329
10330        cx.update(|window, cx| {
10331            input.update(cx, |state, cx| {
10332                state.redo(&Redo, window, cx);
10333            });
10334        });
10335        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
10336    }
10337
10338    #[gpui::test]
10339    fn test_multi_cursor_undo_redo_different_line_lengths(cx: &mut TestAppContext) {
10340        let view = multi_line(cx);
10341        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10342        let input = view.input;
10343
10344        setup_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
10345        cx.update(|window, cx| {
10346            input.update(cx, |state, cx| {
10347                state.replace_text_in_range(None, "a", window, cx);
10348            });
10349        });
10350        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
10351
10352        cx.update(|window, cx| {
10353            input.update(cx, |state, cx| {
10354                state.undo(&Undo, window, cx);
10355            });
10356        });
10357        assert_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
10358
10359        cx.update(|window, cx| {
10360            input.update(cx, |state, cx| {
10361                state.redo(&Redo, window, cx);
10362            });
10363        });
10364        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
10365    }
10366
10367    #[gpui::test]
10368    fn test_multi_cursor_undo_multiple_inserts(cx: &mut TestAppContext) {
10369        let view = multi_line(cx);
10370        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10371        let input = view.input;
10372
10373        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
10374        for ch in ['a', 'b', 'c'] {
10375            cx.update(|window, cx| {
10376                input.update(cx, |state, cx| {
10377                    state.replace_text_in_range(None, &ch.to_string(), window, cx);
10378                });
10379            });
10380        }
10381        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
10382
10383        // The repeated keystrokes form one typing gesture.
10384        cx.update(|window, cx| {
10385            input.update(cx, |state, cx| {
10386                assert_eq!(state.undo_manager.undo_count(), 1);
10387                state.undo(&Undo, window, cx);
10388            });
10389        });
10390        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
10391
10392        cx.update(|window, cx| {
10393            input.update(cx, |state, cx| {
10394                state.redo(&Redo, window, cx);
10395            });
10396        });
10397        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
10398    }
10399
10400    #[gpui::test]
10401    fn test_multi_cursor_backspace_run_is_one_undo(cx: &mut TestAppContext) {
10402        let view = multi_line(cx);
10403        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10404        let input = view.input;
10405
10406        setup_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
10407        for _ in 0..3 {
10408            cx.update(|window, cx| {
10409                input.update(cx, |state, cx| {
10410                    state.backspace(&Backspace, window, cx);
10411                });
10412            });
10413        }
10414        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
10415
10416        cx.update(|window, cx| {
10417            input.update(cx, |state, cx| {
10418                assert_eq!(state.undo_manager.undo_count(), 1);
10419                state.undo(&Undo, window, cx);
10420            });
10421        });
10422        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
10423    }
10424
10425    #[gpui::test]
10426    fn test_adding_a_cursor_splits_the_typing_gesture(cx: &mut TestAppContext) {
10427        let view = multi_line(cx);
10428        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10429        let input = view.input;
10430
10431        setup_cursors(&mut cx, &input, "|1\n2\n3");
10432        cx.update(|window, cx| {
10433            input.update(cx, |state, cx| {
10434                state.replace_text_in_range(None, "a", window, cx);
10435                state.add_cursor_below(&AddCursorBelow, window, cx);
10436                state.replace_text_in_range(None, "b", window, cx);
10437            });
10438        });
10439        assert_cursors(&mut cx, &input, "ab|1\n2b|\n3");
10440
10441        // The keystroke after the cursor was added is its own undo entry.
10442        cx.update(|window, cx| {
10443            input.update(cx, |state, cx| {
10444                state.undo(&Undo, window, cx);
10445            });
10446        });
10447        assert_cursors(&mut cx, &input, "a|1\n2|\n3");
10448    }
10449
10450    #[gpui::test]
10451    fn test_multi_cursor_indent_is_one_undo(cx: &mut TestAppContext) {
10452        let view = multi_line(cx);
10453        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10454        let input = view.input;
10455
10456        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
10457        cx.update(|window, cx| {
10458            input.update(cx, |state, cx| {
10459                state.indent_inline(&IndentInline, window, cx);
10460                assert_eq!(state.undo_manager.undo_count(), 1);
10461                state.undo(&Undo, window, cx);
10462            });
10463        });
10464        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
10465    }
10466
10467    #[gpui::test]
10468    fn test_multi_cursor_indent_then_outdent_roundtrips(cx: &mut TestAppContext) {
10469        let view = multi_line(cx);
10470        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10471        let input = view.input;
10472
10473        // Cursors at line starts.
10474        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
10475        cx.update(|window, cx| {
10476            input.update(cx, |state, cx| {
10477                state.indent(false, window, cx);
10478            });
10479        });
10480        assert_cursors(&mut cx, &input, "  |1\n  |2\n  |3");
10481
10482        cx.update(|window, cx| {
10483            input.update(cx, |state, cx| {
10484                state.outdent(false, window, cx);
10485            });
10486        });
10487        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
10488    }
10489
10490    #[gpui::test]
10491    fn test_inline_outdent_only_removes_line_indentation(cx: &mut TestAppContext) {
10492        let view = multi_line(cx);
10493        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10494        let input = view.input;
10495
10496        // A mid-line indent lands at the cursor, and the outdent does not
10497        // take it back: it only ever removes leading line indentation.
10498        setup_cursors(&mut cx, &input, "1|2\n1|2");
10499        cx.update(|window, cx| {
10500            input.update(cx, |state, cx| {
10501                state.indent(false, window, cx);
10502            });
10503        });
10504        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
10505
10506        cx.update(|window, cx| {
10507            input.update(cx, |state, cx| {
10508                state.outdent(false, window, cx);
10509            });
10510        });
10511        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
10512
10513        // A line with leading indentation loses that, wherever the cursor is.
10514        setup_cursors(&mut cx, &input, "  1|2\n  1|2");
10515        cx.update(|window, cx| {
10516            input.update(cx, |state, cx| {
10517                state.outdent(false, window, cx);
10518            });
10519        });
10520        assert_cursors(&mut cx, &input, "1|2\n1|2");
10521    }
10522
10523    #[gpui::test]
10524    fn test_readonly_multi_cursor_commands_leave_state_unchanged(cx: &mut TestAppContext) {
10525        let view = multi_line(cx);
10526        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10527        let input = view.input;
10528
10529        setup_cursors(&mut cx, &input, "a|b\nc|d");
10530        cx.update(|window, cx| {
10531            input.update(cx, |state, cx| {
10532                let before: Vec<_> = state.selections.iter().copied().collect();
10533                state.set_readonly(true, cx);
10534                state.backspace(&Backspace, window, cx);
10535                state.indent_inline(&IndentInline, window, cx);
10536
10537                assert_eq!(state.value(), "ab\ncd\n");
10538                assert_eq!(state.selections.iter().copied().collect::<Vec<_>>(), before);
10539            });
10540        });
10541    }
10542
10543    #[gpui::test]
10544    fn test_multi_cursor_edit_preserves_the_active_cursor(cx: &mut TestAppContext) {
10545        let view = multi_line(cx);
10546        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10547        let input = view.input;
10548
10549        setup_cursors(&mut cx, &input, "x|\n|y");
10550        cx.update(|window, cx| {
10551            input.update(cx, |state, cx| {
10552                let mut selections: Vec<_> = state.selections.iter().copied().collect();
10553                selections.swap(0, 1);
10554                state.selections.replace_all(selections);
10555                let active_id = state.active_selection().id;
10556                state.replace_text_in_range(None, "!", window, cx);
10557                assert_eq!(state.active_selection().id, active_id);
10558            });
10559        });
10560    }
10561
10562    #[gpui::test]
10563    fn test_block_indent_outdent_with_selection(cx: &mut TestAppContext) {
10564        let view = multi_line(cx);
10565        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10566        let input = view.input;
10567
10568        cx.update(|window, cx| {
10569            input.update(cx, |state, cx| {
10570                state.set_value("line1\nline2\nline3", window, cx);
10571                let id = state.selections.generate_id();
10572                state
10573                    .selections
10574                    .replace_all(vec![CursorSelection::new(id, 0, 17)]);
10575                cx.notify();
10576            });
10577        });
10578
10579        cx.update(|window, cx| {
10580            input.update(cx, |state, cx| {
10581                state.indent(true, window, cx);
10582            });
10583        });
10584        input.read_with(&cx, |state, _| {
10585            assert_eq!(state.text.to_string(), "  line1\n  line2\n  line3");
10586        });
10587
10588        cx.update(|window, cx| {
10589            input.update(cx, |state, cx| {
10590                state.outdent(true, window, cx);
10591            });
10592        });
10593        input.read_with(&cx, |state, _| {
10594            assert_eq!(state.text.to_string(), "line1\nline2\nline3");
10595        });
10596    }
10597
10598    #[gpui::test]
10599    fn test_multi_cursor_word_movement(cx: &mut TestAppContext) {
10600        let view = multi_line(cx);
10601        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10602        let input = view.input;
10603
10604        setup_cursors(
10605            &mut cx,
10606            &input,
10607            "on|e two three\none t|wo three\non|e two three",
10608        );
10609
10610        cx.update(|window, cx| {
10611            input.update(cx, |state, cx| {
10612                state.move_to_next_word(&MoveToNextWord, window, cx);
10613            });
10614        });
10615        assert_cursors(
10616            &mut cx,
10617            &input,
10618            "one| two three\none two| three\none| two three",
10619        );
10620
10621        cx.update(|window, cx| {
10622            input.update(cx, |state, cx| {
10623                state.move_to_previous_word(&MoveToPreviousWord, window, cx);
10624            });
10625        });
10626        assert_cursors(
10627            &mut cx,
10628            &input,
10629            "|one two three\none |two three\n|one two three",
10630        );
10631
10632        // Move to end/start of document collapses to a single cursor.
10633        cx.update(|window, cx| {
10634            input.update(cx, |state, cx| {
10635                state.move_to_end(&MoveToEnd, window, cx);
10636            });
10637        });
10638        input.read_with(&cx, |state, _| {
10639            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
10640            assert_eq!(cursors, vec![state.text.len()]);
10641        });
10642
10643        cx.update(|window, cx| {
10644            input.update(cx, |state, cx| {
10645                state.move_to_start(&MoveToStart, window, cx);
10646            });
10647        });
10648        input.read_with(&cx, |state, _| {
10649            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
10650            assert_eq!(cursors, vec![0]);
10651        });
10652    }
10653
10654    #[gpui::test]
10655    fn test_multi_cursor_selection_commands(cx: &mut TestAppContext) {
10656        let view = multi_line(cx);
10657        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10658        let input = view.input;
10659
10660        setup_cursors(
10661            &mut cx,
10662            &input,
10663            "on|e two three\none t|wo three\non|e two three",
10664        );
10665        cx.update(|window, cx| {
10666            input.update(cx, |state, cx| {
10667                state.select_to_start_of_line(&SelectToStartOfLine, window, cx);
10668            });
10669        });
10670        assert_cursors(
10671            &mut cx,
10672            &input,
10673            "|one two three\n|one two three\n|one two three",
10674        );
10675
10676        // Select to document start collapses to the active cursor only.
10677        setup_cursors(
10678            &mut cx,
10679            &input,
10680            "on|e two three\none t|wo three\non|e two three",
10681        );
10682        cx.update(|window, cx| {
10683            input.update(cx, |state, cx| {
10684                state.select_to_start(&SelectToStart, window, cx);
10685            });
10686        });
10687        input.read_with(&cx, |state, _| {
10688            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
10689            assert_eq!(cursors, vec![0]);
10690        });
10691    }
10692
10693    #[gpui::test]
10694    fn test_multi_cursor_replace_selection(cx: &mut TestAppContext) {
10695        let view = multi_line(cx);
10696        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10697        let input = view.input;
10698
10699        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
10700        cx.update(|window, cx| {
10701            input.update(cx, |state, cx| {
10702                state.select_right(&SelectRight, window, cx);
10703            });
10704        });
10705        input.read_with(&cx, |state, _| {
10706            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
10707            assert_eq!(ranges, vec![(0, 1), (2, 3), (4, 5)]);
10708        });
10709
10710        cx.update(|window, cx| {
10711            input.update(cx, |state, cx| {
10712                state.replace_text_in_range(None, "x", window, cx);
10713            });
10714        });
10715        assert_cursors(&mut cx, &input, "x|\nx|\nx|");
10716    }
10717
10718    #[gpui::test]
10719    fn test_multi_cursor_escape_collapses(cx: &mut TestAppContext) {
10720        let view = multi_line(cx);
10721        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10722        let input = view.input;
10723
10724        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
10725        cx.update(|window, cx| {
10726            input.update(cx, |state, cx| {
10727                state.escape(&Escape, window, cx);
10728            });
10729        });
10730        input.read_with(&cx, |state, _| {
10731            assert_eq!(state.selections.len(), 1);
10732        });
10733    }
10734
10735    #[gpui::test]
10736    fn test_build_columnar_selection(cx: &mut TestAppContext) {
10737        let view = multi_line(cx);
10738        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10739        let input = view.input;
10740
10741        setup_cursors(&mut cx, &input, "abcd\nabcd\nabcd");
10742        cx.update(|_, cx| {
10743            input.update(cx, |state, cx| {
10744                // From row 0 col 1 to row 2 col 3.
10745                state.build_columnar_selection(
10746                    ColumnarPoint::new(1, 0),
10747                    ColumnarPoint::new(13, 0),
10748                    cx,
10749                );
10750                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
10751                assert_eq!(ranges, vec![(1, 3), (6, 8), (11, 13)]);
10752            });
10753        });
10754    }
10755
10756    #[gpui::test]
10757    fn test_columnar_selection_keeps_width_over_short_rows(cx: &mut TestAppContext) {
10758        let view = multi_line(cx);
10759        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10760        let input = view.input;
10761
10762        setup_cursors(&mut cx, &input, "abcdef\nab\nabcdef");
10763        cx.update(|_, cx| {
10764            input.update(cx, |state, cx| {
10765                // Ends on the short row, 3 columns past its end: the block still spans
10766                // columns 1..5, and only that row is clipped to what it has.
10767                state.build_columnar_selection(
10768                    ColumnarPoint::new(1, 0),
10769                    ColumnarPoint::new(9, 3),
10770                    cx,
10771                );
10772                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
10773                assert_eq!(ranges, vec![(1, 5), (8, 9)]);
10774            });
10775        });
10776    }
10777
10778    #[gpui::test]
10779    fn test_multi_cursor_paste_distributes_lines(cx: &mut TestAppContext) {
10780        let view = multi_line(cx);
10781        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10782        let input = view.input;
10783
10784        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
10785        cx.update(|_, cx| {
10786            cx.write_to_clipboard(ClipboardItem::new_string("x\ny\nz".to_string()));
10787        });
10788        cx.update(|window, cx| {
10789            input.update(cx, |state, cx| {
10790                state.paste(&Paste, window, cx);
10791            });
10792        });
10793        // One clipboard line per cursor.
10794        assert_cursors(&mut cx, &input, "x|1\ny|2\nz|3");
10795    }
10796
10797    #[gpui::test]
10798    fn test_paste_without_text_leaves_the_selection_alone(cx: &mut TestAppContext) {
10799        use gpui::{ClipboardEntry, Image, ImageFormat};
10800
10801        let input_view = InputView::build(cx, |state| state);
10802        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
10803        let input = input_view.input;
10804
10805        cx.update(|window, cx| {
10806            input.update(cx, |state, cx| {
10807                state.set_value("hello world", window, cx);
10808                state.select_all(window, cx);
10809                cx.write_to_clipboard(ClipboardItem {
10810                    entries: vec![ClipboardEntry::Image(Image::from_bytes(
10811                        ImageFormat::Png,
10812                        vec![0x89, b'P', b'N', b'G'],
10813                    ))],
10814                });
10815
10816                // An image-only clipboard must not replace the selection
10817                // with nothing.
10818                state.paste(&Paste, window, cx);
10819                assert_eq!(state.value(), "hello world");
10820                assert_eq!(state.selected_range(), 0..11);
10821            });
10822        });
10823    }
10824
10825    #[gpui::test]
10826    fn test_paste_target_tracks_edits_and_selections(cx: &mut TestAppContext) {
10827        let view = multi_line(cx);
10828        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10829        let input = view.input;
10830
10831        setup_cursors(&mut cx, &input, "ab|c");
10832        let target = input.read_with(&cx, |state, _| state.paste_target());
10833        // Nothing happened: a paste asked for then still applies.
10834        assert_eq!(
10835            input.read_with(&cx, |state, _| state.paste_target()),
10836            target
10837        );
10838
10839        // Moving the caret changes where the paste would go.
10840        cx.update(|_, cx| {
10841            input.update(cx, |state, cx| {
10842                let id = state.selections.generate_id();
10843                state
10844                    .selections
10845                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
10846                cx.notify();
10847            });
10848        });
10849        let moved = input.read_with(&cx, |state, _| state.paste_target());
10850        assert_ne!(moved, target);
10851
10852        // Editing the text changes it too, even with the caret put back.
10853        cx.update(|window, cx| {
10854            input.update(cx, |state, cx| {
10855                state.replace_text_in_range_silent(None, "x", window, cx);
10856                let id = state.selections.generate_id();
10857                state
10858                    .selections
10859                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
10860            });
10861        });
10862        assert_ne!(input.read_with(&cx, |state, _| state.paste_target()), moved);
10863    }
10864
10865    #[gpui::test]
10866    fn test_ime_candidate_bounds_fallback_before_repaint(cx: &mut TestAppContext) {
10867        let input_view = InputView::build_textarea(cx, |state| {
10868            state.default_value("这是一段已经输入的中文文字")
10869        });
10870        let mut visual = VisualTestContext::from_window(input_view.window_handle.into(), cx);
10871        visual.update(|window, cx| window.draw(cx).clear(cx));
10872
10873        visual.update(|window, cx| {
10874            input_view.input.update(cx, |state, cx| {
10875                state.focus(window, cx);
10876                let len = state.text.len();
10877                state.set_cursor_to(len);
10878            });
10879        });
10880        visual.update(|window, cx| window.draw(cx).clear(cx));
10881
10882        visual.update(|window, cx| {
10883            input_view.input.update(cx, |state, cx| {
10884                let bounds = state.text_bounds().unwrap_or_default();
10885                let before = state.selected_text_range(false, window, cx).unwrap().range;
10886                let previous = state
10887                    .bounds_for_range(before.end..before.end, bounds, window, cx)
10888                    .unwrap();
10889
10890                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
10891
10892                let after = state.selected_text_range(false, window, cx).unwrap().range;
10893                let pending = state
10894                    .bounds_for_range(after.end..after.end, bounds, window, cx)
10895                    .unwrap();
10896
10897                assert!(
10898                    (pending.origin.x - previous.origin.x).abs() < px(40.0),
10899                    "pending origin.x {:?} must remain close to previous origin.x {:?}",
10900                    pending.origin.x,
10901                    previous.origin.x
10902                );
10903
10904                let range_bounds = state
10905                    .bounds_for_range(before.end..after.end, bounds, window, cx)
10906                    .unwrap();
10907                assert!(
10908                    (range_bounds.origin.x - previous.origin.x).abs() < px(40.0),
10909                    "range_bounds origin.x {:?} must remain close to previous origin.x {:?}",
10910                    range_bounds.origin.x,
10911                    previous.origin.x
10912                );
10913                assert!(range_bounds.size.width >= px(0.0));
10914            });
10915        });
10916    }
10917}
10918
10919/// Methods that only a single-line input offers.
10920impl InputBaseState<crate::input::InputMode> {
10921    /// Create a single-line text input state.
10922    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
10923        Self::new_in_mode(window, cx)
10924    }
10925
10926    /// Set a custom step function of the [`super::NumberInput`].
10927    ///
10928    /// The `f` receives the current value and the [`StepAction`], and returns
10929    /// the step to apply, so the step can vary with the value.
10930    ///
10931    /// # Example
10932    ///
10933    /// ```ignore
10934    /// // At the boundary 1.0 the step is 0.1 going down and 0.5 going up.
10935    /// InputState::new(window, cx).step_by(|value, action, _cx| match action {
10936    ///     StepAction::Increment => if value < 1.0 { 0.1 } else { 0.5 },
10937    ///     StepAction::Decrement => if value <= 1.0 { 0.1 } else { 0.5 },
10938    /// })
10939    /// ```
10940    pub fn step_by(mut self, f: impl Fn(f64, StepAction, &mut App) -> f64 + 'static) -> Self {
10941        self.number_step = Some(NumberStep::by_value(f));
10942        self
10943    }
10944
10945    /// Set with password masked state.
10946    pub fn masked(mut self, masked: bool) -> Self {
10947        self.masked = masked;
10948        self
10949    }
10950
10951    /// Set the password masked state of the input field.
10952    pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
10953        self.masked = masked;
10954        cx.notify();
10955    }
10956
10957    /// Set the regular expression pattern of the input field.
10958    pub fn pattern(mut self, pattern: regex::Regex) -> Self {
10959        self.pattern = Some(pattern);
10960        self
10961    }
10962
10963    /// Set the regular expression pattern of the input field with reference.
10964    pub fn set_pattern(
10965        &mut self,
10966        pattern: regex::Regex,
10967        _window: &mut Window,
10968        _cx: &mut Context<Self>,
10969    ) {
10970        self.pattern = Some(pattern);
10971    }
10972
10973    /// Set the validation function of the input field.
10974    pub fn validate(mut self, f: impl Fn(&str, &mut App) -> bool + 'static) -> Self {
10975        self.validate = Some(Box::new(f));
10976        self
10977    }
10978
10979    pub fn set_validator(
10980        &mut self,
10981        validate: impl Fn(&str, &mut App) -> bool + 'static,
10982        _cx: &mut Context<Self>,
10983    ) {
10984        self.validate = Some(Box::new(validate));
10985    }
10986
10987    /// Set the step value of the [`super::NumberInput`] for increment/decrement.
10988    ///
10989    /// If any of `step`, `min`, `max` is set, the [`super::NumberInput`] will
10990    /// update the value internally (step by `step`, default 1, clamp to the
10991    /// `min`/`max` range and emit [`InputEvent::Change`]) instead of emitting
10992    /// [`super::NumberInputEvent::Step`].
10993    ///
10994    /// See also [`Self::step_by`] to calculate the step value
10995    /// based on the current value.
10996    pub fn step(mut self, step: impl Into<NumberStep>) -> Self {
10997        self.number_step = Some(step.into());
10998        self
10999    }
11000
11001    /// Set the minimum value of the [`super::NumberInput`].
11002    ///
11003    /// The value will be clamped to the minimum value on stepping and on
11004    /// blur (only if the clamped value passes the `pattern`/`validate` check).
11005    /// See also [`Self::step`].
11006    pub fn min(mut self, min: f64) -> Self {
11007        self.number_min = Some(min);
11008        self
11009    }
11010
11011    /// Set the maximum value of the [`super::NumberInput`].
11012    ///
11013    /// The value will be clamped to the maximum value on stepping and on
11014    /// blur (only if the clamped value passes the `pattern`/`validate` check).
11015    /// See also [`Self::step`].
11016    pub fn max(mut self, max: f64) -> Self {
11017        self.number_max = Some(max);
11018        self
11019    }
11020
11021    /// Update the step value after construction, `None` to fall back to
11022    /// emitting [`super::NumberInputEvent::Step`] (if `min`, `max` are unset).
11023    ///
11024    /// See [`Self::step`] and [`Self::step_by`].
11025    pub fn set_step(
11026        &mut self,
11027        step: impl Into<Option<NumberStep>>,
11028        _: &mut Window,
11029        _: &mut Context<Self>,
11030    ) {
11031        self.number_step = step.into();
11032    }
11033
11034    /// Update the minimum value after construction. See [`Self::min`].
11035    pub fn set_min(&mut self, min: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
11036        self.number_min = min;
11037    }
11038
11039    /// Update the maximum value after construction. See [`Self::max`].
11040    pub fn set_max(&mut self, max: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
11041        self.number_max = max;
11042    }
11043
11044    /// Set true to show spinner at the input right.
11045    pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
11046        self.loading = loading;
11047        cx.notify();
11048    }
11049}
11050
11051/// Methods shared by the two multi-line modes, and reachable on neither a
11052/// single-line input nor anything else.
11053impl<M: crate::input::MultiLineMode> InputBaseState<M> {
11054    /// Whether the built-in search panel and its shortcut are enabled. Off by
11055    /// default, on for the code editor.
11056    ///
11057    /// This only concerns the panel. An input that is not searchable still
11058    /// answers [`InputBaseState::set_search_query`] and the other search
11059    /// methods, and lets `Ctrl-F` / `Cmd-F` bubble up to its ancestors, so an
11060    /// application can put its own search UI on top of the same engine.
11061    pub fn searchable(mut self, searchable: bool) -> Self {
11062        self.searchable = searchable;
11063        self
11064    }
11065
11066    /// See [`InputBaseState::searchable`].
11067    pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
11068        self.searchable = searchable;
11069        cx.notify();
11070    }
11071
11072    /// Set the soft wrap mode, default is true.
11073    #[doc(hidden)]
11074    pub fn soft_wrap(mut self, wrap: bool) -> Self {
11075        self.soft_wrap = wrap;
11076        self
11077    }
11078
11079    /// Update the soft wrap mode, default is true.
11080    pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
11081        self.soft_wrap = wrap;
11082        if wrap {
11083            let wrap_width = self
11084                .last_layout
11085                .as_ref()
11086                .and_then(|b| b.wrap_width)
11087                .unwrap_or(self.input_bounds.size.width);
11088
11089            self.display_map.on_layout_changed(Some(wrap_width), cx);
11090
11091            // Reset scroll to left 0
11092            let mut offset = self.scroll_handle.offset();
11093            offset.x = px(0.);
11094            self.scroll_handle.set_offset(offset);
11095        } else {
11096            self.display_map.on_layout_changed(None, cx);
11097        }
11098        cx.notify();
11099    }
11100
11101    /// Set how soft-wrapped continuation lines are indented, default is [`WrappingIndent::Same`]
11102    #[doc(hidden)]
11103    pub fn wrapping_indent(mut self, wrapping_indent: WrappingIndent) -> Self {
11104        self.wrapping_indent = wrapping_indent;
11105        self
11106    }
11107
11108    /// Update how soft-wrapped continuation lines are indented.
11109    pub fn set_wrapping_indent(
11110        &mut self,
11111        wrapping_indent: WrappingIndent,
11112        _: &mut Window,
11113        cx: &mut Context<Self>,
11114    ) {
11115        self.wrapping_indent = wrapping_indent;
11116        self.display_map.set_wrapping_indent(wrapping_indent, cx);
11117        cx.notify();
11118    }
11119}
11120
11121/// Methods that only ordinary multi-line text offers.
11122impl InputBaseState<crate::input::TextareaMode> {
11123    /// Create a multi-line text state.
11124    ///
11125    /// Being multi-line is carried by the mode, not by the layout, so the
11126    /// default plain-text layout needs no adjustment here.
11127    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
11128        Self::new_in_mode(window, cx)
11129    }
11130
11131    pub fn set_auto_grow(&mut self, min_rows: usize, max_rows: usize, cx: &mut Context<Self>) {
11132        self.mode = LayoutMode::auto_grow(min_rows, max_rows.max(min_rows));
11133        self.mode.update_auto_grow(&self.display_map);
11134        cx.notify();
11135    }
11136
11137    /// Set the number of rows for the multi-line Textarea.
11138    ///
11139    /// This is only used when `multi_line` is set to true. The input is at least
11140    /// this many lines tall.
11141    ///
11142    /// default: 1
11143    #[doc(hidden)]
11144    pub fn rows(mut self, rows: usize) -> Self {
11145        match &mut self.mode {
11146            LayoutMode::PlainText { rows: r, .. } | LayoutMode::CodeEditor { rows: r, .. } => {
11147                *r = rows
11148            }
11149            LayoutMode::AutoGrow {
11150                max_rows: max_r,
11151                rows: r,
11152                ..
11153            } => {
11154                *r = rows;
11155                *max_r = rows;
11156            }
11157        }
11158        self
11159    }
11160
11161    pub fn set_rows(&mut self, rows: usize, cx: &mut Context<Self>) {
11162        match &mut self.mode {
11163            LayoutMode::PlainText { rows: value, .. }
11164            | LayoutMode::CodeEditor { rows: value, .. } => *value = rows,
11165            LayoutMode::AutoGrow {
11166                rows: value,
11167                max_rows,
11168                ..
11169            } => {
11170                *value = rows;
11171                *max_rows = rows;
11172            }
11173        }
11174        cx.notify();
11175    }
11176
11177    /// Grow with the content from `min_rows` through `max_rows`.
11178    pub fn auto_grow(mut self, min_rows: usize, max_rows: usize) -> Self {
11179        self.mode = LayoutMode::auto_grow(min_rows, max_rows);
11180        self
11181    }
11182}
11183
11184/// Methods that only a source-code editor offers.
11185impl InputBaseState<crate::input::EditorMode> {
11186    /// Create a source-code editor state.
11187    ///
11188    /// Default options: line numbers on, tab size 2 with soft tabs, indent
11189    /// guides on, multi-line, and search enabled. Set the language for syntax
11190    /// highlighting with [`Self::language`]; without one the text is shown
11191    /// unhighlighted.
11192    ///
11193    /// The editor aims at simple code editing or display, not at being a
11194    /// full-featured code editor. It offers syntax highlighting, auto indent,
11195    /// line numbers, and handles large text up to about 50K lines.
11196    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
11197        let mut state = Self::new_in_mode(window, cx);
11198        state.mode = LayoutMode::code_editor(super::EditorLanguage::new(cx));
11199        state.searchable = true;
11200        state
11201    }
11202
11203    /// Set the language to highlight, e.g. `"rust"`.
11204    ///
11205    /// See [`Self::set_highlighter`] to change it after construction.
11206    pub fn language(mut self, language: impl Into<SharedString>) -> Self {
11207        if let LayoutMode::CodeEditor {
11208            language: l,
11209            highlighter,
11210            ..
11211        } = &mut self.mode
11212        {
11213            l.set_name(language.into());
11214            *highlighter.borrow_mut() = None;
11215        }
11216        self
11217    }
11218
11219    /// The current language name, e.g. `"rust"`.
11220    pub fn language_name(&self) -> SharedString {
11221        match &self.mode {
11222            LayoutMode::CodeEditor { language, .. } => language.name(),
11223            _ => SharedString::default(),
11224        }
11225    }
11226
11227    /// Set enable/disable code folding.
11228    ///
11229    /// Default: true
11230    #[doc(hidden)]
11231    pub fn folding(mut self, folding: bool) -> Self {
11232        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
11233            *f = folding;
11234        }
11235        self
11236    }
11237
11238    /// Set code folding at runtime.
11239    ///
11240    /// When disabling, all existing folds are cleared.
11241    pub fn set_folding(&mut self, folding: bool, _: &mut Window, cx: &mut Context<Self>) {
11242        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
11243            *f = folding;
11244        }
11245        if !folding {
11246            self.display_map.clear_folds();
11247        }
11248        cx.notify();
11249    }
11250
11251    /// Unfold any folded ranges that hide the given position.
11252    ///
11253    /// Use this to reveal a position before acting on it (e.g. before
11254    /// [`Self::set_cursor_position`], which stops at a fold boundary),
11255    /// without touching folds elsewhere in the buffer. Fold candidates are
11256    /// kept, so the opened ranges can be folded again from the gutter.
11257    ///
11258    /// A fold keeps its own first and last line visible, so a position on
11259    /// either of them opens nothing. Nested folds all open, since opening
11260    /// only the outermost would leave the position hidden.
11261    ///
11262    /// Returns whether any fold was opened.
11263    pub fn unfold_at(&mut self, position: impl Into<Position>, cx: &mut Context<Self>) -> bool {
11264        let offset = self.text.position_to_offset(&position.into());
11265        let line = self.text.offset_to_point(offset).row;
11266        // A fold hides start_line + 1 ..= end_line - 1, so a line is hidden
11267        // exactly when some folded range strictly contains it.
11268        let covering: Vec<usize> = self
11269            .display_map
11270            .folded_ranges()
11271            .iter()
11272            .filter(|fold| line > fold.start_line && line < fold.end_line)
11273            .map(|fold| fold.start_line)
11274            .collect();
11275        if covering.is_empty() {
11276            return false;
11277        }
11278
11279        for start_line in covering {
11280            self.display_map.set_folded(start_line, false);
11281        }
11282        cx.notify();
11283        true
11284    }
11285
11286    /// Set enable/disable line number.
11287    #[doc(hidden)]
11288    pub fn line_number(mut self, line_number: bool) -> Self {
11289        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
11290            *l = line_number;
11291        }
11292        self
11293    }
11294
11295    /// Set line number.
11296    pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
11297        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
11298            *l = line_number;
11299        }
11300        cx.notify();
11301    }
11302
11303    /// Set enable/disable automatic closing brackets and quotes.
11304    ///
11305    /// When enabled, typing an opener from [`LanguageConfig::auto_closing_pairs`](crate::input::language_config::LanguageConfig::auto_closing_pairs) inserts the
11306    /// matching closer and places the cursor inside. Typing a closer that is
11307    /// already present just moves past it. Default: true
11308    #[doc(hidden)]
11309    pub fn auto_close(mut self, auto_close: bool) -> Self {
11310        self.mode.set_auto_close(auto_close);
11311        self
11312    }
11313
11314    /// Set automatic closing brackets and quotes at runtime.
11315    pub fn set_auto_close(&mut self, auto_close: bool, _: &mut Window, cx: &mut Context<Self>) {
11316        self.mode.set_auto_close(auto_close);
11317        cx.notify();
11318    }
11319
11320    /// Set enable/disable smart indent on Enter.
11321    ///
11322    /// When enabled, Enter uses structural brackets and
11323    /// [`LanguageConfig::indentation_rules`](crate::input::language_config::LanguageConfig::indentation_rules) to choose indentation.
11324    /// Default: true
11325    #[doc(hidden)]
11326    pub fn smart_indent(mut self, smart_indent: bool) -> Self {
11327        self.mode.set_smart_indent(smart_indent);
11328        self
11329    }
11330
11331    /// Set smart indent on Enter at runtime.
11332    pub fn set_smart_indent(&mut self, smart_indent: bool, _: &mut Window, cx: &mut Context<Self>) {
11333        self.mode.set_smart_indent(smart_indent);
11334        cx.notify();
11335    }
11336}