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, ScrollEvent, ScrollHandle, SharedString, StatefulInteractiveElement as _, Styled as _,
11    Subscription, UTF16Selection, WeakFocusHandle, Window, actions, div, point,
12    prelude::FluentBuilder as _, px,
13};
14use gpui::{Autocapitalize, TextInputAction, TextInputConfiguration};
15use ropey::{Rope, RopeSlice};
16use serde::Deserialize;
17use std::borrow::Cow;
18use std::cell::Cell;
19use std::ops::Range;
20use std::rc::Rc;
21use sum_tree::Bias;
22use unicode_segmentation::*;
23
24use super::{
25    DiagnosticSet, DisplayMap, InputContextMenuCapabilities, InputEditorStyle,
26    InputHighlighterFactory, MASK_CHAR, MaskPattern, NativeMenu, NumberStep, WrappingIndent,
27    blink_cursor::BlinkCursor,
28    change::Change,
29    cursor::{CursorSelection, Selections},
30    element::{
31        EditorScrollbar, EditorScrollbarSnapshot, LongestLineKey, OverviewRulerRows, TextElement,
32        clamp_horizontal_scroll_offset,
33    },
34    kind::InputModeKind,
35    mask_pattern::normalize_number_input,
36    mode::LayoutMode,
37    undo_manager::{EditIntent, UndoManager},
38};
39use crate::actions::{SelectDown, SelectLeft, SelectRight, SelectUp};
40use crate::input::blink_cursor::CURSOR_WIDTH;
41use crate::input::movement::MoveDirection;
42use crate::input::{
43    InputEdit, InputExtras as _, Position, RopeExt as _, element::RIGHT_MARGIN, layout::LastLayout,
44};
45use crate::overscroll::Overscroll;
46use crate::{AutoScroll, StepAction, TestSupportExt as _};
47
48pub(super) const EDITOR_ELEMENT_ID: &str = "input-state";
49
50/// Vertical clearance to retain when revealing a text position.
51pub(crate) enum ScrollPadding {
52    Minimal,
53    SurroundingLines,
54}
55
56#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
57#[action(namespace = input, no_json)]
58pub struct Enter {
59    /// Is confirm with secondary.
60    pub secondary: bool,
61    /// Whether the Shift modifier was held when Enter was pressed.
62    pub shift: bool,
63}
64
65impl Enter {
66    /// Returns true if `action` is a primary `Enter` action (`secondary: false`),
67    /// regardless of whether Shift was held.
68    pub fn is_primary(action: &dyn Action) -> bool {
69        action.partial_eq(&Enter {
70            secondary: false,
71            shift: false,
72        }) || action.partial_eq(&Enter {
73            secondary: false,
74            shift: true,
75        })
76    }
77}
78
79actions!(
80    input,
81    [
82        ActivateToken,
83        Backspace,
84        Delete,
85        DeleteToBeginningOfLine,
86        DeleteToEndOfLine,
87        DeleteToPreviousWordStart,
88        DeleteToNextWordEnd,
89        Indent,
90        Outdent,
91        IndentInline,
92        OutdentInline,
93        MoveUp,
94        MoveDown,
95        MoveLeft,
96        MoveRight,
97        MoveHome,
98        MoveEnd,
99        MovePageUp,
100        MovePageDown,
101        AddCursorAbove,
102        AddCursorBelow,
103        SelectAll,
104        SelectToStartOfLine,
105        SelectToEndOfLine,
106        SelectToStart,
107        SelectToEnd,
108        SelectToPreviousWordStart,
109        SelectToNextWordEnd,
110        ShowCharacterPalette,
111        Copy,
112        Cut,
113        Paste,
114        Undo,
115        Redo,
116        MoveToStartOfLine,
117        MoveToEndOfLine,
118        MoveToStart,
119        MoveToEnd,
120        MoveToPreviousWord,
121        MoveToNextWord,
122        Escape,
123        ToggleCodeActions,
124        ShowCompletions,
125        Search,
126        Replace,
127        GoToDefinition,
128    ]
129);
130
131/// An event emitted by an input.
132///
133/// Subscribe to [`InputEditsEvent`] for the immutable edits associated with a
134/// change. `Change` itself remains available for consumers that only need a
135/// notification.
136#[derive(Clone)]
137pub enum InputEvent {
138    Change,
139    PressEnter { secondary: bool, shift: bool },
140    Focus,
141    Blur,
142}
143
144/// An immutable batch of text edits emitted for one input change.
145///
146/// Each entry contains the edit and the document immediately after it, in
147/// application order. Unlike reading the input state in a `Change` subscriber,
148/// this snapshot remains associated with its event when updates are queued.
149#[derive(Clone)]
150pub struct InputEditsEvent {
151    edits: Vec<(InputEdit, Rope)>,
152}
153
154impl InputEditsEvent {
155    /// The edits and the text after each edit, in application order.
156    pub fn edits(&self) -> &[(InputEdit, Rope)] {
157        &self.edits
158    }
159}
160
161pub(super) const CONTEXT: &str = "Input";
162
163pub(crate) fn init(cx: &mut App) {
164    cx.bind_keys([
165        KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
166        KeyBinding::new("shift-backspace", Backspace, Some(CONTEXT)),
167        #[cfg(target_os = "macos")]
168        KeyBinding::new("ctrl-backspace", Backspace, Some(CONTEXT)),
169        KeyBinding::new("delete", Delete, Some(CONTEXT)),
170        KeyBinding::new("shift-delete", Delete, Some(CONTEXT)),
171        #[cfg(target_os = "macos")]
172        KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
173        #[cfg(target_os = "macos")]
174        KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
175        #[cfg(target_os = "macos")]
176        KeyBinding::new("alt-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
177        #[cfg(not(target_os = "macos"))]
178        KeyBinding::new("ctrl-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
179        #[cfg(target_os = "macos")]
180        KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)),
181        #[cfg(not(target_os = "macos"))]
182        KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)),
183        KeyBinding::new(
184            "enter",
185            Enter {
186                secondary: false,
187                shift: false,
188            },
189            Some(CONTEXT),
190        ),
191        KeyBinding::new(
192            "shift-enter",
193            Enter {
194                secondary: false,
195                shift: true,
196            },
197            Some(CONTEXT),
198        ),
199        KeyBinding::new(
200            if cx.platform_info().is_mac() {
201                "cmd-enter"
202            } else {
203                "ctrl-enter"
204            },
205            Enter {
206                secondary: true,
207                shift: false,
208            },
209            Some(CONTEXT),
210        ),
211        KeyBinding::new("escape", Escape, Some(CONTEXT)),
212        KeyBinding::new("up", MoveUp, Some(CONTEXT)),
213        KeyBinding::new("down", MoveDown, Some(CONTEXT)),
214        KeyBinding::new("left", MoveLeft, Some(CONTEXT)),
215        KeyBinding::new("right", MoveRight, Some(CONTEXT)),
216        KeyBinding::new("pageup", MovePageUp, Some(CONTEXT)),
217        KeyBinding::new("pagedown", MovePageDown, Some(CONTEXT)),
218        KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
219        KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
220        #[cfg(target_os = "macos")]
221        KeyBinding::new("cmd-]", Indent, Some(CONTEXT)),
222        #[cfg(not(target_os = "macos"))]
223        KeyBinding::new("ctrl-]", Indent, Some(CONTEXT)),
224        #[cfg(target_os = "macos")]
225        KeyBinding::new("cmd-[", Outdent, Some(CONTEXT)),
226        #[cfg(not(target_os = "macos"))]
227        KeyBinding::new("ctrl-[", Outdent, Some(CONTEXT)),
228        KeyBinding::new("shift-left", SelectLeft, Some(CONTEXT)),
229        KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
230        KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
231        KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
232        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
233        KeyBinding::new("shift-alt-left", SelectLeft, Some(CONTEXT)),
234        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
235        KeyBinding::new("shift-alt-right", SelectRight, Some(CONTEXT)),
236        // Avoid Ctrl+Alt+arrows on Linux, where desktops may reserve them.
237        #[cfg(target_os = "macos")]
238        KeyBinding::new("cmd-alt-up", AddCursorAbove, Some(CONTEXT)),
239        #[cfg(target_os = "macos")]
240        KeyBinding::new("cmd-alt-down", AddCursorBelow, Some(CONTEXT)),
241        #[cfg(target_os = "windows")]
242        KeyBinding::new("ctrl-alt-up", AddCursorAbove, Some(CONTEXT)),
243        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
244        KeyBinding::new("shift-alt-up", AddCursorAbove, Some(CONTEXT)),
245        #[cfg(target_os = "windows")]
246        KeyBinding::new("ctrl-alt-down", AddCursorBelow, Some(CONTEXT)),
247        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
248        KeyBinding::new("shift-alt-down", AddCursorBelow, Some(CONTEXT)),
249        KeyBinding::new("home", MoveHome, Some(CONTEXT)),
250        KeyBinding::new("end", MoveEnd, Some(CONTEXT)),
251        #[cfg(not(target_os = "macos"))]
252        KeyBinding::new("ctrl-home", MoveToStart, Some(CONTEXT)),
253        #[cfg(not(target_os = "macos"))]
254        KeyBinding::new("ctrl-end", MoveToEnd, Some(CONTEXT)),
255        #[cfg(not(target_os = "macos"))]
256        KeyBinding::new("ctrl-shift-home", SelectToStart, Some(CONTEXT)),
257        #[cfg(not(target_os = "macos"))]
258        KeyBinding::new("ctrl-shift-end", SelectToEnd, Some(CONTEXT)),
259        KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
260        KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
261        #[cfg(target_os = "macos")]
262        KeyBinding::new("ctrl-shift-a", SelectToStartOfLine, Some(CONTEXT)),
263        #[cfg(target_os = "macos")]
264        KeyBinding::new("ctrl-shift-e", SelectToEndOfLine, Some(CONTEXT)),
265        #[cfg(target_os = "macos")]
266        KeyBinding::new("shift-cmd-left", SelectToStartOfLine, Some(CONTEXT)),
267        #[cfg(target_os = "macos")]
268        KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
269        #[cfg(any(target_os = "macos", target_os = "linux"))]
270        KeyBinding::new("alt-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
271        #[cfg(not(target_os = "macos"))]
272        KeyBinding::new("ctrl-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
273        #[cfg(any(target_os = "macos", target_os = "linux"))]
274        KeyBinding::new("alt-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
275        #[cfg(not(target_os = "macos"))]
276        KeyBinding::new("ctrl-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
277        #[cfg(target_os = "macos")]
278        KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
279        #[cfg(target_os = "macos")]
280        KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
281        #[cfg(not(target_os = "macos"))]
282        KeyBinding::new("ctrl-a", SelectAll, Some(CONTEXT)),
283        #[cfg(target_os = "macos")]
284        KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
285        #[cfg(not(target_os = "macos"))]
286        KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
287        #[cfg(target_os = "macos")]
288        KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
289        #[cfg(not(target_os = "macos"))]
290        KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
291        #[cfg(target_os = "macos")]
292        KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
293        #[cfg(not(target_os = "macos"))]
294        KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
295        #[cfg(target_os = "macos")]
296        KeyBinding::new("ctrl-a", MoveHome, Some(CONTEXT)),
297        #[cfg(target_os = "macos")]
298        KeyBinding::new("cmd-left", MoveHome, Some(CONTEXT)),
299        #[cfg(target_os = "macos")]
300        KeyBinding::new("ctrl-e", MoveEnd, Some(CONTEXT)),
301        #[cfg(target_os = "macos")]
302        KeyBinding::new("cmd-right", MoveEnd, Some(CONTEXT)),
303        #[cfg(target_os = "macos")]
304        KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
305        #[cfg(target_os = "macos")]
306        KeyBinding::new("cmd-shift-z", Redo, Some(CONTEXT)),
307        #[cfg(target_os = "macos")]
308        KeyBinding::new("cmd-up", MoveToStart, Some(CONTEXT)),
309        #[cfg(target_os = "macos")]
310        KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
311        #[cfg(target_os = "macos")]
312        KeyBinding::new("alt-left", MoveToPreviousWord, Some(CONTEXT)),
313        #[cfg(target_os = "macos")]
314        KeyBinding::new("alt-right", MoveToNextWord, Some(CONTEXT)),
315        #[cfg(not(target_os = "macos"))]
316        KeyBinding::new("ctrl-left", MoveToPreviousWord, Some(CONTEXT)),
317        #[cfg(not(target_os = "macos"))]
318        KeyBinding::new("ctrl-right", MoveToNextWord, Some(CONTEXT)),
319        #[cfg(target_os = "macos")]
320        KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
321        #[cfg(target_os = "macos")]
322        KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
323        #[cfg(not(target_os = "macos"))]
324        KeyBinding::new("ctrl-z", Undo, Some(CONTEXT)),
325        #[cfg(not(target_os = "macos"))]
326        KeyBinding::new("ctrl-y", Redo, Some(CONTEXT)),
327        #[cfg(target_os = "macos")]
328        KeyBinding::new("cmd-.", ToggleCodeActions, Some(CONTEXT)),
329        #[cfg(not(target_os = "macos"))]
330        KeyBinding::new("ctrl-.", ToggleCodeActions, Some(CONTEXT)),
331        #[cfg(target_os = "macos")]
332        KeyBinding::new("cmd-f", Search, Some(CONTEXT)),
333        #[cfg(not(target_os = "macos"))]
334        KeyBinding::new("ctrl-f", Search, Some(CONTEXT)),
335        #[cfg(target_os = "macos")]
336        KeyBinding::new("cmd-shift-f", Replace, Some(CONTEXT)),
337        #[cfg(not(target_os = "macos"))]
338        KeyBinding::new("ctrl-h", Replace, Some(CONTEXT)),
339    ]);
340}
341
342/// A mouse position resolved for a columnar (block) selection.
343///
344/// A position past the end of a short row has no glyph to land on, so `offset` clips to
345/// that row's end and `columns_past_line_end` keeps what the clip dropped. Together they
346/// say which column the pointer really reached, which is what a block spanning rows of
347/// different lengths has to be measured in.
348#[derive(Debug, Clone, Copy, PartialEq, Eq)]
349pub(super) struct ColumnarPoint {
350    offset: usize,
351    columns_past_line_end: usize,
352}
353
354impl ColumnarPoint {
355    pub(super) fn new(offset: usize, columns_past_line_end: usize) -> Self {
356        Self {
357            offset,
358            columns_past_line_end,
359        }
360    }
361}
362
363/// The document and selections a paste was asked to replace, taken when the
364/// `Paste` action ran so a clipboard read that resolves later can tell
365/// whether it still applies. See [`InputBaseState::paste_target`].
366#[derive(Debug, PartialEq)]
367struct PasteTarget {
368    document_revision: u64,
369    selections: Vec<CursorSelection>,
370}
371
372/// Caret positioning geometry from the input's latest prepaint.
373///
374/// Unlike `last_layout` (published at paint time), this is stashed during
375/// prepaint so caret-anchored popups can position themselves from the
376/// current frame's geometry in their deferred prepaint, instead of the
377/// previous frame's paint.
378#[derive(Clone, Copy, Debug)]
379#[doc(hidden)]
380pub struct PrepaintCaretGeometry {
381    cursor_bounds: Bounds<Pixels>,
382    line_height: Pixels,
383    input_bounds: Bounds<Pixels>,
384    scroll_offset: Point<Pixels>,
385    /// A resize owns visibility while its requested repaint settles.
386    pub(super) resize_reveal_owned: bool,
387}
388
389impl PrepaintCaretGeometry {
390    pub(super) fn new(
391        cursor_bounds: Bounds<Pixels>,
392        line_height: Pixels,
393        input_bounds: Bounds<Pixels>,
394        scroll_offset: Point<Pixels>,
395    ) -> Self {
396        Self {
397            cursor_bounds,
398            line_height,
399            input_bounds,
400            scroll_offset,
401            resize_reveal_owned: false,
402        }
403    }
404
405    /// The active caret's bounds from the fresh prepaint layout.
406    pub fn cursor_bounds(&self) -> Bounds<Pixels> {
407        self.cursor_bounds
408    }
409
410    /// Line height from the fresh prepaint layout.
411    pub fn line_height(&self) -> Pixels {
412        self.line_height
413    }
414
415    /// The input container bounds from the fresh prepaint.
416    pub fn input_bounds(&self) -> Bounds<Pixels> {
417        self.input_bounds
418    }
419
420    /// The scroll offset that will be in effect after paint, clamped like
421    /// `InputBaseState::update_scroll_offset` clamps it.
422    pub fn scroll_offset(&self) -> Point<Pixels> {
423        self.scroll_offset
424    }
425}
426
427/// The shared text-editing engine behind [`crate::input::InputState`],
428/// [`crate::input::TextareaState`] and [`crate::input::EditorState`].
429///
430/// `M` is the mode marker: it carries no data and only decides which methods
431/// exist, so an ordinary input cannot reach the editor's language features.
432///
433/// The three states are type aliases of this one, which is why this name is
434/// public: an alias is only as usable as the type behind it, so hiding this
435/// would leave `InputState` unable to do anything. Prefer naming the aliases
436/// — write `InputState`, not `InputBaseState<InputMode>`.
437pub struct InputBaseState<M: InputModeKind> {
438    /// State only this mode needs. See [`InputModeKind::Extras`].
439    pub(crate) extras: M::Extras,
440    pub(super) focus_handle: FocusHandle,
441    selection_focus: Option<WeakFocusHandle>,
442    pub(super) mode: LayoutMode,
443    pub(super) text: Rope,
444    pub(super) display_map: DisplayMap,
445    pub(super) undo_manager: UndoManager,
446    pub(super) inline_tokens: Option<Box<super::inline_tokens::InlineTokenStore>>,
447    pub(super) pending_token: Option<super::InlineToken>,
448    pub(super) replaying_history: bool,
449    pub(super) validated_token_edit: bool,
450    pub(super) document_revision: u64,
451    pub(super) token_presentation: super::InlineTokenPresentation,
452    pub(super) token_layout_cache: Option<Box<super::token_presentation::TokenLayoutCache>>,
453    /// Real per-frame bounds of the placed token elements, keyed by token
454    /// start offset; used for hover payloads and stale-hover reconciliation.
455    pub(super) token_bounds: std::collections::HashMap<usize, Bounds<Pixels>>,
456    /// The currently hovered token: its entered span, last measured bounds,
457    /// and UTF-16 range as of entry. Retained so hover exit can still be
458    /// delivered when the token is removed, replaced, scrolled out, or
459    /// disabled.
460    pub(super) hovered_token: Option<super::token_presentation::HoverSnapshot>,
461    pub(super) pending_token_hover_exits: Vec<super::token_presentation::HoverSnapshot>,
462    /// The start offset of a pressed token, with the document revision and
463    /// pointer position at the press.
464    pub(super) pressed_token: Option<(usize, u64, Point<Pixels>)>,
465    pub(super) search_session: super::SearchSession,
466    /// Advances every time search is explicitly invoked. See
467    /// [`InputBaseState::search_activation_revision`].
468    pub(super) search_activation_revision: u64,
469    pub(super) searchable: bool,
470    pub(super) replaceable: bool,
471    pub(super) soft_wrap: bool,
472    pub(super) wrapping_indent: WrappingIndent,
473    pub(super) scroll_beyond_last_line: Option<usize>,
474    pub(super) cursor_surrounding_lines: Option<usize>,
475    pub(super) blink_cursor: Entity<BlinkCursor>,
476    pub(super) loading: bool,
477    /// The cursors and selections.
478    ///
479    /// Always contains at least one selection where index 0 is the active cursor.
480    pub(super) selections: Selections,
481    /// Range for save the selected word, use to keep word range when drag move.
482    pub(super) selected_word_range: Option<CursorSelection>,
483    /// The marked range is the temporary insert text on IME typing.
484    pub(super) ime_marked_range: Option<CursorSelection>,
485    /// Text before an unannounced IME composition, until the next change.
486    ime_original_text: Option<Rope>,
487    pub(super) last_layout: Option<LastLayout>,
488    /// Fresh caret geometry stashed during prepaint for caret-anchored popups.
489    ///
490    /// Popups are positioned in their deferred prepaint, which runs after the
491    /// input's prepaint but before its paint, so they read this instead of
492    /// the previous frame's `last_layout`.
493    pub(crate) prepaint_caret_geometry: Option<PrepaintCaretGeometry>,
494    pub(super) last_cursor: Option<usize>,
495    /// The input container bounds
496    pub(super) input_bounds: Bounds<Pixels>,
497    /// The text bounds
498    pub(super) last_bounds: Option<Bounds<Pixels>>,
499    /// The fully visible part of the window this input last asked its scroll
500    /// containers to reveal it in, while focused. `None` while unfocused.
501    pub(super) revealed_viewport: Option<Bounds<Pixels>>,
502    pub(super) last_selected_range: Option<CursorSelection>,
503    /// The current edit batch, copied into an event when a change is emitted.
504    pub(super) last_input_edits: Vec<(InputEdit, Rope)>,
505    pub(super) selecting: bool,
506    /// Anchor point of an in-progress columnar (block) selection.
507    pub(super) column_select_start: Option<ColumnarPoint>,
508    /// The selection a long press made, with its handles and edit menu.
509    pub(super) touch_selection: super::touch::TouchSelection,
510    pub(crate) disabled: bool,
511    pub(crate) readonly: bool,
512    pub(crate) text_align: TextAlign,
513    pub(super) masked: bool,
514    pub(super) clean_on_escape: bool,
515    pub(super) submit_on_enter: bool,
516    /// Overrides for [`Self::text_input_configuration`].
517    pub(super) input_action: Option<TextInputAction>,
518    pub(super) autocorrect: Option<bool>,
519    pub(super) autocapitalize: Option<Autocapitalize>,
520    pub(super) show_whitespaces: bool,
521    /// This flag tells the renderer to prefer the end of the current visual line.
522    pub(crate) cursor_line_end_affinity: bool,
523    pub(super) pattern: Option<regex::Regex>,
524    pub(super) validate: Option<Box<dyn Fn(&str, &mut App) -> bool + 'static>>,
525    /// The step strategy for [`super::NumberInput`] to increment/decrement.
526    /// See [`Self::step`] and [`Self::step_by`].
527    pub(crate) number_step: Option<NumberStep>,
528    /// The minimum value for [`super::NumberInput`]. See [`Self::min`].
529    pub(crate) number_min: Option<f64>,
530    /// The maximum value for [`super::NumberInput`]. See [`Self::max`].
531    pub(crate) number_max: Option<f64>,
532    pub(crate) scroll_handle: ScrollHandle,
533    /// Whether the text stretches past its ends, see [`Self::scroll_bounce`].
534    scroll_bounce: bool,
535    /// The text's stretch past an end, while it lasts.
536    pub(super) overscroll: Overscroll,
537    /// The deferred scroll offset to apply on next layout.
538    pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,
539    /// The size of the scrollable content.
540    pub(crate) scroll_size: gpui::Size<Pixels>,
541    pub(super) editor_scrollbar_snapshot: Cell<Option<EditorScrollbarSnapshot>>,
542    /// Overview ruler marks in display rows, kept between frames.
543    pub(super) overview_ruler_rows: std::cell::RefCell<OverviewRulerRows>,
544    /// The widest measured unwrapped line width and what it was measured for.
545    pub(super) longest_line_width: Cell<Option<(LongestLineKey, Pixels)>>,
546    pub(super) editor_paddings: Edges<Pixels>,
547    /// The style this state paints with: what was projected onto it, with
548    /// every colour left unset resolved from the palette that is current. It
549    /// is rebuilt at the top of every render, which is what keeps it current
550    /// when the palette changes after the state was built.
551    pub(super) editor_style: InputEditorStyle,
552    /// What a consumer projected, kept verbatim so that resolution never
553    /// consumes its own output: resolving in place would fill the unset
554    /// colours once and then never see them as unset again, which is the same
555    /// freeze in a different place.
556    projected_editor_style: InputEditorStyle,
557    accessibility: Option<InputAccessibility>,
558    /// What paints in place of Base's editor scrollbar, when a consumer
559    /// supplies its own.
560    pub(super) scrollbar_renderer: Option<crate::input::EditorScrollbarRenderer>,
561
562    /// The mask pattern for formatting the input text
563    pub(crate) mask_pattern: MaskPattern,
564    /// Whether the `mask_pattern` was explicitly set (via [`Self::mask_pattern`]
565    /// or [`Self::set_mask_pattern`]), to let [`super::NumberInput`] only apply
566    /// its default mask when the user has not made an explicit choice.
567    pub(super) mask_pattern_set: bool,
568    pub(super) placeholder: SharedString,
569
570    /// Diagnostic currently requested by pointer hover; applications render it.
571    pub(super) diagnostic_popover: Option<Rc<crate::input::DiagnosticEntry>>,
572
573    context_menu_handler: Option<
574        Rc<dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App)>,
575    >,
576    pending_context_menu: Option<(Point<Pixels>, usize)>,
577
578    /// Whether the context menu that shows on right-click is enabled.
579    ///
580    pub(super) enable_context_menu: bool,
581
582    /// A flag to indicate if we are currently inserting a completion item.
583    pub(super) completion_inserting: bool,
584    pub(super) overlay_action_handler: Option<
585        Rc<
586            dyn Fn(
587                super::InputOverlayKind,
588                Box<dyn Action>,
589                &mut Window,
590                &mut Context<InputBaseState<M>>,
591            ) -> bool,
592        >,
593    >,
594
595    /// A flag to indicate if we have a pending update to the text.
596    ///
597    /// If true, will call some update (for example LSP, Syntax Highlight) before render.
598    _pending_update: bool,
599    /// A flag to indicate if we should ignore the next completion event.
600    pub(super) silent_replace_text: bool,
601    /// A flag to indicate if we should emit InputEvents.
602    pub(super) emit_events: bool,
603
604    _subscriptions: Vec<Subscription>,
605
606    pub(super) auto_scroll: AutoScroll,
607}
608
609/// Accessibility presentation for the editor's existing keyboard-focus boundary.
610///
611/// Frames and their addon controls keep their own identities. This projection
612/// does not create a focus handle or register another text Tab stop. The engine
613/// supplies the live value and guards accessibility actions against its current
614/// disabled, read-only and masked state.
615#[derive(Clone, Debug)]
616pub struct InputAccessibility {
617    role: Option<gpui::Role>,
618    label: Option<SharedString>,
619    placeholder: Option<SharedString>,
620    accessibility_id: Option<SharedString>,
621    value_exposed: bool,
622}
623
624impl InputAccessibility {
625    pub fn new(role: Option<gpui::Role>) -> Self {
626        Self {
627            role,
628            label: None,
629            placeholder: None,
630            accessibility_id: None,
631            value_exposed: true,
632        }
633    }
634
635    pub fn with_label(mut self, label: Option<SharedString>) -> Self {
636        self.label = label;
637        self
638    }
639
640    pub fn with_placeholder(mut self, placeholder: Option<SharedString>) -> Self {
641        self.placeholder = placeholder;
642        self
643    }
644
645    pub fn with_accessibility_id(mut self, id: Option<SharedString>) -> Self {
646        self.accessibility_id = id;
647        self
648    }
649
650    /// Suppress values for secret content even when the mask is revealed.
651    pub fn with_value_exposed(mut self, exposed: bool) -> Self {
652        self.value_exposed = exposed;
653        self
654    }
655
656    pub fn role(&self) -> Option<gpui::Role> {
657        self.role
658    }
659
660    pub fn label(&self) -> Option<&SharedString> {
661        self.label.as_ref()
662    }
663
664    pub fn placeholder(&self) -> Option<&SharedString> {
665        self.placeholder.as_ref()
666    }
667
668    pub fn accessibility_id(&self) -> Option<&SharedString> {
669        self.accessibility_id.as_ref()
670    }
671
672    pub fn value_exposed(&self) -> bool {
673        self.value_exposed
674    }
675}
676
677/// Read-only styling data exposed to presentation facades.
678///
679/// The fields are private and read through the methods below, so that a new
680/// one can be added without breaking the facades.
681#[derive(Clone)]
682pub struct InputPresentation {
683    focus_handle: FocusHandle,
684    disabled: bool,
685    readonly: bool,
686    loading: bool,
687    masked: bool,
688    multi_line: bool,
689    code_editor: bool,
690    text_align: TextAlign,
691    placeholder: SharedString,
692    mask_placeholder: Option<String>,
693}
694
695impl InputPresentation {
696    pub fn focus_handle(&self) -> &FocusHandle {
697        &self.focus_handle
698    }
699
700    pub fn is_disabled(&self) -> bool {
701        self.disabled
702    }
703
704    pub fn is_readonly(&self) -> bool {
705        self.readonly
706    }
707
708    /// Returns true if the user is allowed to change the text.
709    ///
710    /// See also: [`InputBaseState::is_editable`].
711    pub fn is_editable(&self) -> bool {
712        !self.disabled && !self.readonly
713    }
714
715    pub fn is_loading(&self) -> bool {
716        self.loading
717    }
718
719    pub fn is_masked(&self) -> bool {
720        self.masked
721    }
722
723    pub fn is_multi_line(&self) -> bool {
724        self.multi_line
725    }
726
727    pub fn is_code_editor(&self) -> bool {
728        self.code_editor
729    }
730
731    pub fn text_align(&self) -> TextAlign {
732        self.text_align
733    }
734
735    pub fn placeholder(&self) -> &SharedString {
736        &self.placeholder
737    }
738
739    /// The placeholder derived from the mask pattern, e.g.: `(___) ___-____`.
740    pub fn mask_placeholder(&self) -> Option<&str> {
741        self.mask_placeholder.as_deref()
742    }
743}
744
745impl<M: InputModeKind> EventEmitter<InputEvent> for InputBaseState<M> {}
746impl<M: InputModeKind> EventEmitter<InputEditsEvent> for InputBaseState<M> {}
747
748impl<M: InputModeKind> InputBaseState<M> {
749    #[doc(hidden)]
750    pub fn cursor_layout(&self) -> Option<(Bounds<Pixels>, Pixels)> {
751        let layout = self.last_layout.as_ref()?;
752        Some((layout.cursor_bounds?, layout.line_height))
753    }
754
755    /// Caret geometry from the latest prepaint, for caret-anchored popups.
756    ///
757    /// Popups positioned in their deferred prepaint read this to anchor to
758    /// the current frame's caret instead of the previous frame's paint.
759    #[doc(hidden)]
760    pub fn prepaint_caret_geometry(&self) -> Option<PrepaintCaretGeometry> {
761        self.prepaint_caret_geometry
762    }
763
764    pub fn input_bounds(&self) -> Bounds<Pixels> {
765        self.input_bounds
766    }
767
768    pub fn text_bounds(&self) -> Option<Bounds<Pixels>> {
769        self.last_bounds
770    }
771
772    /// The cached display map for querying wrapped rows and display positions.
773    ///
774    /// Reading this does not prepare text or force layout. Before the first
775    /// layout, [`DisplayMap::wrap_row_count`] can be zero; prepared empty text
776    /// has one row. The count describes content, independent of minimum rows,
777    /// auto-grow limits and viewport height.
778    ///
779    /// Text edits use the cached wrapping parameters. Layout refreshes the
780    /// cache after width or font changes; consume that count on a subsequent
781    /// render, or use [`Self::prewrap`] to prepare it before the first layout.
782    pub fn display_map(&self) -> &DisplayMap {
783        &self.display_map
784    }
785
786    /// Wrap the text for `font`, `font_size` and `width` before the first
787    /// layout, so that [`DisplayMap::wrap_row_count`] is known on the first frame.
788    ///
789    /// Call it after setting the value and the wrapping indent. `width` is the
790    /// width of the input's text area: its width less its padding, and less
791    /// the line-number gutter for an editor with line numbers. It wraps as
792    /// layout does, a little short of that width and with the state's wrapping
793    /// indent. Once the input has been laid out this does nothing, and layout
794    /// keeps the wrap up to date. The font and font size must match the input's
795    /// actual text style.
796    pub fn prewrap(
797        &mut self,
798        font: gpui::Font,
799        font_size: Pixels,
800        width: Pixels,
801        cx: &mut Context<Self>,
802    ) {
803        if self.last_layout.is_some() {
804            return;
805        }
806        let wrap_width = (width - RIGHT_MARGIN).max(px(1.));
807        self.display_map.set_font(font, font_size, cx);
808        self.display_map
809            .set_tab_size(self.mode.tab_size().tab_size, cx);
810        self.display_map
811            .on_layout_changed(self.soft_wrap.then_some(wrap_width), cx);
812        self.display_map
813            .set_wrapping_indent(self.wrapping_indent, cx);
814        self.display_map.ensure_text_prepared(&self.text, cx);
815    }
816
817    pub fn diagnostic_popover(&self) -> Option<Rc<crate::input::DiagnosticEntry>> {
818        self.diagnostic_popover.clone()
819    }
820
821    pub fn presentation(&self) -> InputPresentation {
822        InputPresentation {
823            focus_handle: self.focus_handle.clone(),
824            disabled: self.disabled,
825            readonly: self.readonly,
826            loading: self.loading,
827            masked: self.masked,
828            multi_line: self.is_multi_line(),
829            code_editor: self.is_code_editor(),
830            text_align: self.text_align,
831            placeholder: self.placeholder.clone(),
832            mask_placeholder: self.mask_pattern.placeholder(),
833        }
834    }
835
836    /// Whether this input spans more than one line.
837    ///
838    /// Answered by the mode marker, which is fixed when the state is built.
839    /// [`LayoutMode`] holds the row counts and growth policy, not the kind.
840    #[inline]
841    /// Whether this input paints scrollbars.
842    ///
843    /// Only a multi-line input can scroll: a single-line input keeps its
844    /// caret in view by moving its own offset, and never has a viewport a
845    /// user could drag. Adding the editor scrollbar to every input put a
846    /// thumb inside every text field, which is a control the field does not
847    /// have.
848    pub(crate) fn shows_scrollbar(&self) -> bool {
849        self.is_multi_line()
850    }
851
852    pub fn is_multi_line(&self) -> bool {
853        M::MULTI_LINE
854    }
855
856    /// Whether this input is a single-line text field. See [`Self::is_multi_line`].
857    #[inline]
858    pub fn is_single_line(&self) -> bool {
859        !M::MULTI_LINE
860    }
861
862    /// Whether this input is a source-code editor.
863    #[inline]
864    pub fn is_code_editor(&self) -> bool {
865        M::CODE_EDITOR
866    }
867
868    /// Whether the user is allowed to copy the selection out.
869    ///
870    /// A masked input keeps its value out of the clipboard.
871    pub fn is_copyable(&self) -> bool {
872        self.selections.iter().any(|sel| !sel.is_empty()) && !self.masked
873    }
874
875    pub fn context_menu_capabilities(&self) -> InputContextMenuCapabilities {
876        let (go_to_definition, code_actions) = self.extras.context_menu_capabilities();
877        InputContextMenuCapabilities::new()
878            .disabled(self.disabled)
879            .readonly(self.readonly)
880            .code_editor(self.is_code_editor())
881            .selection(!self.active_selection().is_empty())
882            .masked(self.masked)
883            .go_to_definition(go_to_definition)
884            .code_actions(code_actions)
885    }
886
887    pub fn set_text_align(&mut self, text_align: TextAlign, cx: &mut Context<Self>) {
888        if !self.is_single_line() || self.text_align == text_align {
889            return;
890        }
891
892        self.text_align = text_align;
893        cx.notify();
894    }
895
896    /// Flip the password mask.
897    ///
898    /// Setting the mask is a single-line method, but flipping it stays here:
899    /// the reveal button is rendered from the generic path, and it can only be
900    /// switched on through [`crate::input::InputState`] anyway.
901    pub fn toggle_masked(&mut self, _: &mut Window, cx: &mut Context<Self>) {
902        self.masked = !self.masked;
903        cx.notify();
904    }
905
906    pub fn on_context_menu(
907        &mut self,
908        handler: Rc<
909            dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App),
910        >,
911    ) {
912        self.context_menu_handler = Some(handler);
913    }
914
915    /// Keeps the selection highlighted while this popup or one of its children has focus.
916    ///
917    /// This does not transfer keyboard focus or keep the caret visible. The
918    /// association holds only a weak focus handle. Pass `None` to clear it.
919    pub fn set_selection_focus(&mut self, focus: Option<FocusHandle>, cx: &mut Context<Self>) {
920        self.selection_focus = focus.as_ref().map(FocusHandle::downgrade);
921        cx.notify();
922    }
923
924    /// Whether the input or its associated selection popup has focus.
925    pub fn has_selection_focus(&self, window: &Window, cx: &App) -> bool {
926        self.focus_handle.is_focused(window)
927            || self
928                .selection_focus
929                .as_ref()
930                .and_then(WeakFocusHandle::upgrade)
931                .is_some_and(|focus| focus.is_focused(window) || focus.contains_focused(window, cx))
932    }
933
934    /// Build the engine. Each mode's own `new` sets its layout on top of this.
935    fn new_in_mode(window: &mut Window, cx: &mut Context<Self>) -> Self {
936        let focus_handle = cx.focus_handle().tab_stop(true);
937        let blink_cursor = cx.new(BlinkCursor::new);
938        let undo_manager = UndoManager::new();
939
940        let _subscriptions = vec![
941            // Key bindings can consume events before on_key_down. Observe input
942            // before action dispatch so every keystroke resets the blink delay.
943            cx.intercept_keystrokes({
944                let focus_handle = focus_handle.clone();
945                let blink_cursor = blink_cursor.downgrade();
946                move |_, window, cx| {
947                    if focus_handle.is_focused(window) {
948                        _ = blink_cursor.update(cx, |cursor, cx| cursor.pause(cx));
949                    }
950                }
951            }),
952            // Observe the blink cursor to repaint the view when it changes.
953            cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
954            // Blink the cursor when the window is active, pause when it's not.
955            cx.observe_window_activation(window, |input, window, cx| {
956                if window.is_window_active() {
957                    let focus_handle = input.focus_handle.clone();
958                    if focus_handle.is_focused(window) {
959                        input.blink_cursor.update(cx, |blink_cursor, cx| {
960                            blink_cursor.start(cx);
961                        });
962                    }
963                }
964            }),
965            cx.on_focus(&focus_handle, window, Self::on_focus),
966            cx.on_blur(&focus_handle, window, Self::on_blur),
967        ];
968
969        let text_style = window.text_style();
970
971        Self {
972            extras: M::Extras::default(),
973            focus_handle: focus_handle.clone(),
974            selection_focus: None,
975            text: "".into(),
976            display_map: DisplayMap::new(text_style.font(), window.rem_size(), None),
977            search_session: super::SearchSession::default(),
978            search_activation_revision: 0,
979            searchable: false,
980            replaceable: true,
981            soft_wrap: true,
982            wrapping_indent: WrappingIndent::default(),
983            scroll_beyond_last_line: None,
984            cursor_surrounding_lines: None,
985            blink_cursor,
986            undo_manager,
987            inline_tokens: None,
988            pending_token: None,
989            replaying_history: false,
990            validated_token_edit: false,
991            document_revision: 0,
992            token_presentation: Default::default(),
993            token_bounds: Default::default(),
994            hovered_token: None,
995            pending_token_hover_exits: Vec::new(),
996            token_layout_cache: None,
997            pressed_token: None,
998            selections: Selections::default(),
999            selected_word_range: None,
1000            ime_marked_range: None,
1001            ime_original_text: None,
1002            input_bounds: Bounds::default(),
1003            selecting: false,
1004            touch_selection: Default::default(),
1005            disabled: false,
1006            readonly: false,
1007            text_align: TextAlign::Left,
1008            masked: false,
1009            clean_on_escape: false,
1010            submit_on_enter: false,
1011            input_action: None,
1012            autocorrect: None,
1013            autocapitalize: None,
1014            show_whitespaces: false,
1015            loading: false,
1016            pattern: None,
1017            validate: None,
1018            number_step: Some(NumberStep::Fixed(1.)),
1019            number_min: None,
1020            number_max: None,
1021            mode: LayoutMode::default(),
1022            last_layout: None,
1023            prepaint_caret_geometry: None,
1024            last_bounds: None,
1025            revealed_viewport: None,
1026            last_selected_range: None,
1027            last_input_edits: Vec::new(),
1028            column_select_start: None,
1029            last_cursor: None,
1030            scroll_handle: ScrollHandle::new(),
1031            scroll_bounce: crate::ScrollBounce::enabled_by_default(),
1032            overscroll: Overscroll::default(),
1033            scroll_size: gpui::size(px(0.), px(0.)),
1034            editor_scrollbar_snapshot: Cell::new(None),
1035            overview_ruler_rows: Default::default(),
1036            longest_line_width: Cell::new(None),
1037            editor_paddings: Edges::default(),
1038            deferred_scroll_offset: None,
1039            placeholder: SharedString::default(),
1040            mask_pattern: MaskPattern::default(),
1041            mask_pattern_set: false,
1042            editor_style: InputEditorStyle::default(),
1043            projected_editor_style: InputEditorStyle::default(),
1044            accessibility: None,
1045            scrollbar_renderer: None,
1046            diagnostic_popover: None,
1047            context_menu_handler: None,
1048            pending_context_menu: None,
1049            enable_context_menu: true,
1050            completion_inserting: false,
1051            overlay_action_handler: None,
1052            silent_replace_text: false,
1053            emit_events: true,
1054            _subscriptions,
1055            _pending_update: false,
1056            cursor_line_end_affinity: false,
1057            auto_scroll: AutoScroll::default(),
1058        }
1059    }
1060
1061    /// Sets whether the context menu that shows on right-click is enabled.
1062    ///
1063    /// The context menu is enabled by default. Disabling it turns off the
1064    /// right-click menu entirely, including a custom menu set on the element.
1065    /// A custom menu already replaces the built-in one, so keep this enabled
1066    /// to show only your own items.
1067    pub fn context_menu(mut self, enable: bool) -> Self {
1068        self.enable_context_menu = enable;
1069        self
1070    }
1071
1072    pub fn set_context_menu_enabled(&mut self, enabled: bool) {
1073        self.enable_context_menu = enabled;
1074    }
1075
1076    /// Set whether search UI allows replacement, default is true.
1077    #[doc(hidden)]
1078    pub fn replaceable(mut self, allow: bool) -> Self {
1079        self.replaceable = allow;
1080        self
1081    }
1082
1083    /// Set placeholder
1084    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1085        self.placeholder = placeholder.into();
1086        self
1087    }
1088
1089    /// Set highlighter language for for [`LayoutMode::CodeEditor`] mode.
1090    pub fn set_highlighter(
1091        &mut self,
1092        new_language: impl Into<SharedString>,
1093        cx: &mut Context<Self>,
1094    ) {
1095        match &mut self.mode {
1096            LayoutMode::CodeEditor {
1097                language,
1098                highlighter,
1099                ..
1100            } => {
1101                language.set_name(new_language.into());
1102                *highlighter.borrow_mut() = None;
1103            }
1104            _ => {}
1105        }
1106        self.refresh(cx);
1107    }
1108
1109    fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
1110        match &mut self.mode {
1111            LayoutMode::CodeEditor { highlighter, .. } => {
1112                *highlighter.borrow_mut() = None;
1113            }
1114            _ => {}
1115        }
1116        cx.notify();
1117    }
1118
1119    /// Install the parser/highlighter adapter used by code-editor mode.
1120    pub fn set_highlighter_factory(
1121        &mut self,
1122        factory: InputHighlighterFactory,
1123        cx: &mut Context<Self>,
1124    ) {
1125        self.mode.set_highlighter_factory(factory);
1126        self._pending_update = true;
1127        self.token_layout_cache = None;
1128        cx.notify();
1129    }
1130
1131    /// Install a default adapter without replacing an application-provided one.
1132    pub fn ensure_highlighter_factory(&mut self, factory: InputHighlighterFactory) {
1133        self.mode.ensure_highlighter_factory(factory);
1134    }
1135
1136    pub fn set_editor_style(&mut self, style: InputEditorStyle) {
1137        self.editor_style = style.clone();
1138        self.projected_editor_style = style;
1139    }
1140
1141    /// Project semantics onto the editor, never onto a second focus owner.
1142    pub fn set_accessibility(&mut self, accessibility: InputAccessibility) {
1143        self.accessibility = Some(accessibility);
1144    }
1145
1146    fn handle_accessibility_focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1147        if !self.disabled {
1148            self.focus(window, cx);
1149        }
1150    }
1151
1152    fn handle_accessibility_set_value(
1153        &mut self,
1154        data: Option<&gpui::accesskit::ActionData>,
1155        window: &mut Window,
1156        cx: &mut Context<Self>,
1157    ) {
1158        let Some(gpui::accesskit::ActionData::Value(value)) = data else {
1159            return;
1160        };
1161        // The action may have been queued before editability changed.
1162        if self.is_editable() {
1163            self.replace_all(value.to_string(), window, cx);
1164        }
1165    }
1166
1167    /// Set presentation padding for multi-line text and its scrollbar layout.
1168    #[doc(hidden)]
1169    pub fn set_editor_paddings(&mut self, paddings: Edges<Pixels>) {
1170        self.editor_paddings = paddings;
1171    }
1172
1173    /// Supply the element painted in place of the editor's scrollbar, or
1174    /// `None` to keep Base's. The renderer is called where the editor paints
1175    /// its scrollbar, with that frame's [`EditorScrollbarContext`]. Like
1176    /// [`Self::set_editor_style`], this does not notify, so it can be called
1177    /// while rendering.
1178    ///
1179    /// [`EditorScrollbarContext`]: crate::input::EditorScrollbarContext
1180    pub fn set_scrollbar_renderer(
1181        &mut self,
1182        renderer: Option<crate::input::EditorScrollbarRenderer>,
1183    ) {
1184        self.scrollbar_renderer = renderer;
1185    }
1186
1187    pub fn apply_highlighter_fold_candidates(
1188        &mut self,
1189        candidates: Vec<crate::input::FoldRange>,
1190        cx: &mut Context<Self>,
1191    ) {
1192        if self.mode.is_folding() {
1193            self.display_map.set_fold_candidates(candidates);
1194        }
1195        cx.notify();
1196    }
1197
1198    #[inline]
1199    pub fn diagnostics(&self) -> Option<&DiagnosticSet> {
1200        self.mode.diagnostics()
1201    }
1202
1203    #[inline]
1204    pub fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
1205        self.mode.diagnostics_mut()
1206    }
1207
1208    /// Set placeholder
1209    pub fn set_placeholder(
1210        &mut self,
1211        placeholder: impl Into<SharedString>,
1212        _: &mut Window,
1213        cx: &mut Context<Self>,
1214    ) {
1215        self.placeholder = placeholder.into();
1216        cx.notify();
1217    }
1218
1219    /// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.
1220    ///
1221    /// Returns:
1222    ///
1223    /// - The index of the line (zero-based) containing the offset.
1224    /// - The index of the sub-line (zero-based) within the line containing the offset.
1225    /// - The position of the offset.
1226    pub(super) fn line_and_position_for_offset(
1227        &self,
1228        offset: usize,
1229    ) -> (usize, usize, Option<Point<Pixels>>) {
1230        let Some(last_layout) = &self.last_layout else {
1231            return (0, 0, None);
1232        };
1233        let line_height = last_layout.line_height;
1234
1235        let mut y_offset = last_layout.visible_top;
1236        for (vi, line) in last_layout.lines.iter().enumerate() {
1237            let prev_lines_offset = last_layout.visible_line_byte_offsets[vi];
1238            let local_offset = offset.saturating_sub(prev_lines_offset);
1239            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
1240                let sub_line_index = (pos.y / line_height) as usize;
1241                let adjusted_pos = point(pos.x + last_layout.line_number_width, pos.y + y_offset);
1242                return (vi, sub_line_index, Some(adjusted_pos));
1243            }
1244
1245            y_offset += line.size(line_height).height;
1246        }
1247        (0, 0, None)
1248    }
1249
1250    /// Set the value of the input field: plain text, or [`InputContent`] to
1251    /// restore text together with its inline tokens. Editing history is
1252    /// cleared and no [`InputEvent::Change`] is emitted.
1253    ///
1254    /// For single-line inputs the caret is placed at the end of the text while
1255    /// the view is scrolled back to the start, so a long value shows its
1256    /// beginning instead of its tail (matching HTML `<input>`). Multi-line
1257    /// inputs reset the selection to `0..0`.
1258    pub fn set_value(
1259        &mut self,
1260        value: impl Into<super::InputContent>,
1261        window: &mut Window,
1262        cx: &mut Context<Self>,
1263    ) {
1264        let content = value.into();
1265        self.inline_tokens = None;
1266        self.undo_manager.set_ignoring(true);
1267        self.emit_events = false;
1268        self.replace_text(content.text().clone(), window, cx);
1269        self.ime_original_text = None;
1270        self.install_tokens(content);
1271        self.undo_manager.set_ignoring(false);
1272        self.emit_events = true;
1273
1274        self.reset_selection();
1275        self.reset_lsp_state();
1276        self.reset_scroll_to_start();
1277
1278        self.undo_manager.clear();
1279        cx.notify();
1280    }
1281
1282    /// Replace the entire text content while preserving undo history.
1283    ///
1284    /// Unlike [`set_value`](Self::set_value), this method records the
1285    /// replacement in the undo stack, allowing the user to undo/redo
1286    /// the change. The selection is placed at the end of the new text
1287    /// for single-line inputs, or cleared (0..0) for multi-line inputs.
1288    ///
1289    /// Use this when programmatically replacing the full text but the
1290    /// user should still be able to undo the operation — e.g. formatting.
1291    pub fn replace_all(
1292        &mut self,
1293        text: impl Into<SharedString>,
1294        window: &mut Window,
1295        cx: &mut Context<Self>,
1296    ) {
1297        self.replace_text(text, window, cx);
1298        self.reset_selection();
1299        self.reset_lsp_state();
1300        self.reset_scroll_to_start();
1301
1302        cx.notify();
1303    }
1304
1305    /// Perform `f` with the user-facing edit restrictions lifted.
1306    ///
1307    /// The `disabled` and `readonly` modes only reject the changes made by the
1308    /// user, the programmatic APIs must always be able to update the text.
1309    pub(super) fn with_edits_allowed(&mut self, f: impl FnOnce(&mut Self)) {
1310        let (was_disabled, was_readonly) = (self.disabled, self.readonly);
1311        (self.disabled, self.readonly) = (false, false);
1312        f(self);
1313        (self.disabled, self.readonly) = (was_disabled, was_readonly);
1314    }
1315
1316    /// Insert text at the current cursor position.
1317    ///
1318    /// And the cursor will be moved to the end of inserted text.
1319    pub fn insert(
1320        &mut self,
1321        text: impl Into<SharedString>,
1322        window: &mut Window,
1323        cx: &mut Context<Self>,
1324    ) {
1325        let text: SharedString = text.into();
1326        self.with_edits_allowed(|this| {
1327            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1328            let range_utf16 = this.range_to_utf16(&(this.cursor()..this.cursor()));
1329            this.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
1330            let end = this.active_selection().end;
1331            this.set_cursor_to(end);
1332        });
1333    }
1334
1335    /// Replace text at the current cursor position.
1336    ///
1337    /// And the cursor will be moved to the end of replaced text.
1338    pub fn replace(
1339        &mut self,
1340        text: impl Into<SharedString>,
1341        window: &mut Window,
1342        cx: &mut Context<Self>,
1343    ) {
1344        let text: SharedString = text.into();
1345        self.with_edits_allowed(|this| {
1346            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1347            this.replace_text_in_range_silent(None, &text, window, cx);
1348            let end = this.active_selection().end;
1349            this.set_cursor_to(end);
1350        });
1351    }
1352
1353    fn replace_text(
1354        &mut self,
1355        text: impl Into<SharedString>,
1356        window: &mut Window,
1357        cx: &mut Context<Self>,
1358    ) {
1359        let text: SharedString = text.into();
1360        self.with_edits_allowed(|this| {
1361            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1362            let range = 0..this.text.chars().map(|c| c.len_utf16()).sum();
1363            this.replace_text_in_range_silent(Some(range), &text, window, cx);
1364            this.reset_highlighter(cx);
1365        });
1366    }
1367
1368    fn reset_selection(&mut self) {
1369        self.selections.remove_all_but_active();
1370
1371        // For single-line inputs the caret is placed at the end of the text
1372        // (matching HTML `<input>`); multi-line inputs reset the selection to
1373        // `0..0`.
1374        if self.is_single_line() {
1375            let end = self.text.len();
1376            self.set_cursor_to(end);
1377        } else {
1378            self.active_selection_mut().clear();
1379        }
1380    }
1381
1382    fn reset_lsp_state(&mut self) {
1383        if self.is_code_editor() {
1384            self._pending_update = true;
1385            M::reset_language_features(self);
1386        }
1387    }
1388
1389    fn reset_scroll_to_start(&mut self) {
1390        // Move scroll to the start. For single-line the caret is at the end, so
1391        // override the cursor-follow scroll for the next painted frame to keep
1392        // the start visible; the deferred offset is consumed during that paint.
1393        self.scroll_handle.set_offset(point(px(0.), px(0.)));
1394        if self.is_single_line() {
1395            self.deferred_scroll_offset = Some(point(px(0.), px(0.)));
1396        }
1397    }
1398
1399    /// Set with disabled mode.
1400    ///
1401    /// See also: [`Self::set_disabled`].
1402    #[allow(unused)]
1403    pub(crate) fn disabled(mut self, disabled: bool) -> Self {
1404        self.disabled = disabled;
1405        self
1406    }
1407
1408    pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
1409        if self.disabled == disabled {
1410            return;
1411        }
1412
1413        self.disabled = disabled;
1414        if disabled {
1415            M::hide_context_menu(self, cx);
1416            M::clear_inline_completion(self, cx);
1417        }
1418        cx.notify();
1419    }
1420
1421    /// Set with read-only mode.
1422    ///
1423    /// Unlike [`Self::disabled`], a read-only input keeps the normal appearance,
1424    /// focus, cursor, selection and copy behavior, it only rejects any change
1425    /// of the text made by the user.
1426    ///
1427    /// See also: [`Self::set_readonly`].
1428    #[allow(unused)]
1429    pub(crate) fn readonly(mut self, readonly: bool) -> Self {
1430        self.readonly = readonly;
1431        self
1432    }
1433
1434    pub fn set_readonly(&mut self, readonly: bool, cx: &mut Context<Self>) {
1435        if self.readonly == readonly {
1436            return;
1437        }
1438
1439        self.readonly = readonly;
1440        if readonly {
1441            M::hide_context_menu(self, cx);
1442            M::clear_inline_completion(self, cx);
1443            self.search_session.replace_mode = false;
1444        }
1445        cx.notify();
1446    }
1447
1448    /// Returns true if the user is allowed to change the text.
1449    ///
1450    /// This is false when the input is `disabled` or `readonly`, the programmatic
1451    /// APIs (e.g.: [`Self::set_value`], [`Self::insert`]) are not limited by this.
1452    pub fn is_editable(&self) -> bool {
1453        !self.disabled && !self.readonly
1454    }
1455
1456    /// Set true to clear the input by pressing Escape key.
1457    ///
1458    /// Not on Android, where Escape is also the back button.
1459    pub fn clean_on_escape(mut self) -> Self {
1460        self.clean_on_escape = true;
1461        self
1462    }
1463
1464    pub fn set_clean_on_escape(&mut self, clean: bool) {
1465        self.clean_on_escape = clean;
1466    }
1467
1468    /// Set true to treat `Enter` as a submit action in multi-line mode,
1469    /// while `Shift+Enter` inserts a newline.
1470    ///
1471    /// Default is `false` (both `Enter` and `Shift+Enter` insert a newline).
1472    #[doc(hidden)]
1473    pub fn submit_on_enter(mut self, submit: bool) -> Self {
1474        self.submit_on_enter = submit;
1475        self
1476    }
1477
1478    pub fn set_submit_on_enter(&mut self, submit: bool, cx: &mut Context<Self>) {
1479        self.submit_on_enter = submit;
1480        cx.notify();
1481    }
1482
1483    /// Set the action a software keyboard shows on its enter key, like Search
1484    /// or Send. Pressing it still emits [`InputEvent::PressEnter`].
1485    ///
1486    /// Default is [`TextInputAction::Done`] for a single-line input (and a
1487    /// multi-line one that submits on enter), and [`TextInputAction::Enter`]
1488    /// (a line break) for a multi-line input.
1489    pub fn input_action(mut self, action: TextInputAction) -> Self {
1490        self.input_action = Some(action);
1491        self
1492    }
1493
1494    /// Set whether the platform may autocorrect the text and offer word
1495    /// suggestions.
1496    ///
1497    /// Default is `false`. Enable this explicitly for fields that accept prose;
1498    /// masking or revealing text does not change the setting.
1499    pub fn autocorrect(mut self, autocorrect: bool) -> Self {
1500        self.autocorrect = Some(autocorrect);
1501        self
1502    }
1503
1504    /// Set how a software keyboard capitalizes the text.
1505    ///
1506    /// Default is [`Autocapitalize::None`]. Enable capitalization explicitly for
1507    /// fields where it is appropriate, independently of autocorrection.
1508    pub fn autocapitalize(mut self, autocapitalize: Autocapitalize) -> Self {
1509        self.autocapitalize = Some(autocapitalize);
1510        self
1511    }
1512
1513    /// The text assistance a platform IME (a software keyboard) should give
1514    /// this input, from [`Self::input_action`], [`Self::autocorrect`] and
1515    /// [`Self::autocapitalize`] or the defaults they describe.
1516    pub fn text_input_configuration(&self) -> TextInputConfiguration {
1517        // Masking is a presentation choice, not a reliable content type: a
1518        // revealed password must not acquire text assistance. Keep assistance
1519        // opt-in for structured fields such as passwords, email and URLs.
1520        let autocorrect = self.autocorrect.unwrap_or(false);
1521        let input_action =
1522            self.input_action
1523                .unwrap_or(if self.is_multi_line() && !self.submit_on_enter {
1524                    TextInputAction::Enter
1525                } else {
1526                    TextInputAction::Done
1527                });
1528        TextInputConfiguration {
1529            autocorrect,
1530            autocapitalize: self.autocapitalize.unwrap_or(Autocapitalize::None),
1531            suggestions: autocorrect,
1532            input_action,
1533        }
1534    }
1535
1536    /// Set whether to show whitespace characters.
1537    #[doc(hidden)]
1538    pub fn show_whitespaces(mut self, show: bool) -> Self {
1539        self.show_whitespaces = show;
1540        self
1541    }
1542
1543    /// Update whether to show whitespace characters.
1544    pub fn set_show_whitespaces(&mut self, show: bool, _: &mut Window, cx: &mut Context<Self>) {
1545        self.show_whitespaces = show;
1546        cx.notify();
1547    }
1548
1549    /// Empty rows reserved below the last line of content ("scroll
1550    /// beyond last line"), code-editor mode only. Mirrors VSCode's
1551    /// `editor.scrollBeyondLastLine` / Zed's `scroll_beyond_last_line`.
1552    ///
1553    /// - `None` (default): half the viewport, floored at
1554    ///   [`BOTTOM_MARGIN_ROWS`] line-heights.
1555    /// - `Some(0)`: no trailing space; the cursor sits flush with the
1556    ///   last row at scroll-max.
1557    /// - `Some(n)`: exactly `n` rows.
1558    pub fn scroll_beyond_last_line(mut self, rows: Option<usize>) -> Self {
1559        self.scroll_beyond_last_line = rows;
1560        self
1561    }
1562
1563    /// Update [`Self::scroll_beyond_last_line`] after construction.
1564    pub fn set_scroll_beyond_last_line(
1565        &mut self,
1566        rows: Option<usize>,
1567        _: &mut Window,
1568        cx: &mut Context<Self>,
1569    ) {
1570        if self.scroll_beyond_last_line == rows {
1571            return;
1572        }
1573        self.scroll_beyond_last_line = rows;
1574        cx.notify();
1575    }
1576
1577    /// Minimum number of lines the cursor is kept clear of the viewport's
1578    /// top/bottom edge before auto-scroll engages. Mirrors VSCode's
1579    /// `editor.cursorSurroundingLines` / Zed's `vertical_scroll_margin`.
1580    /// Orthogonal to [`Self::scroll_beyond_last_line`], which sizes the
1581    /// empty region; this controls the cursor's resting distance from the
1582    /// edge.
1583    ///
1584    /// - `None` (default): [`BOTTOM_MARGIN_ROWS`] lines, falling back to
1585    ///   one line on small viewports.
1586    /// - `Some(n)`: requests `n` lines, clamped to half the viewport.
1587    ///
1588    /// In a constrained viewport, edge clearance yields to the cursor's
1589    /// complete visual row so surrounding space cannot clip the text being edited.
1590    pub fn cursor_surrounding_lines(mut self, lines: Option<usize>) -> Self {
1591        self.cursor_surrounding_lines = lines;
1592        self
1593    }
1594
1595    /// Update [`Self::cursor_surrounding_lines`] after construction.
1596    pub fn set_cursor_surrounding_lines(
1597        &mut self,
1598        lines: Option<usize>,
1599        _: &mut Window,
1600        cx: &mut Context<Self>,
1601    ) {
1602        if self.cursor_surrounding_lines == lines {
1603            return;
1604        }
1605        self.cursor_surrounding_lines = lines;
1606        cx.notify();
1607    }
1608
1609    /// Set the default value of the input field.
1610    pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {
1611        let text: SharedString = value.into();
1612        self.text = Rope::from(self.normalize_input(&text).as_ref());
1613        if let Some(diagnostics) = self.mode.diagnostics_mut() {
1614            diagnostics.reset(&self.text)
1615        }
1616        // Note: We can't call display_map.set_text here because it needs cx.
1617        // The text will be set during prepare_if_need in element.rs
1618        self._pending_update = true;
1619        self
1620    }
1621
1622    /// Return the value of the input field as an owned string.
1623    ///
1624    /// The string is materialized on each call. See [`Self::text`] for the
1625    /// [`Rope`] the state owns, which is borrowed and costs nothing to read.
1626    pub fn value(&self) -> SharedString {
1627        SharedString::new(self.text.to_string())
1628    }
1629
1630    /// Return the portion of the value within the input field that
1631    /// is selected by the user, as an owned string.
1632    ///
1633    /// The string is materialized on each call. See [`Self::selected_text`]
1634    /// for the same selection borrowed out of the [`Rope`] the state owns.
1635    pub fn selected_value(&self) -> SharedString {
1636        SharedString::new(self.selected_text().to_string())
1637    }
1638
1639    /// Return the value without mask.
1640    pub fn unmask_value(&self) -> SharedString {
1641        self.mask_pattern.unmask(&self.text.to_string()).into()
1642    }
1643
1644    /// Kept so existing render paths keep compiling.
1645    ///
1646    /// Configuration used to be collected by a facade and applied here; the
1647    /// state now configures itself, so this does nothing and can be deleted at
1648    /// the call site.
1649    #[doc(hidden)]
1650    pub fn prepare(&mut self, _: &mut Window, _: &mut Context<Self>) {}
1651
1652    /// Return the text [`Rope`] of the input field.
1653    ///
1654    /// Borrowed from the state, so reading even a large document copies
1655    /// nothing. See [`Self::value`] when an owned string is wanted.
1656    pub fn text(&self) -> &Rope {
1657        &self.text
1658    }
1659
1660    /// Report the change with its own snapshot before the legacy notification.
1661    fn emit_change(&mut self, cx: &mut Context<Self>) {
1662        if let Some(original) = self.ime_original_text.take() {
1663            self.last_input_edits = vec![(
1664                Self::full_input_edit(&original, &self.text),
1665                self.text.clone(),
1666            )];
1667        }
1668        cx.emit(InputEditsEvent {
1669            edits: self.last_input_edits.clone(),
1670        });
1671        cx.emit(InputEvent::Change);
1672    }
1673
1674    fn full_input_edit(old_text: &Rope, new_text: &Rope) -> InputEdit {
1675        InputEdit {
1676            start_byte: 0,
1677            old_end_byte: old_text.len(),
1678            new_end_byte: new_text.len(),
1679            start_position: old_text.offset_to_point(0),
1680            old_end_position: old_text.offset_to_point(old_text.len()),
1681            new_end_position: new_text.offset_to_point(new_text.len()),
1682        }
1683    }
1684
1685    /// Return the (0-based) [`Position`] of the cursor.
1686    pub fn cursor_position(&self) -> Position {
1687        let offset = self.cursor();
1688        self.text.offset_to_position(offset)
1689    }
1690
1691    /// Set (0-based) [`Position`] of the cursor.
1692    ///
1693    /// This will move the cursor to the specified line and column, and update the selection range.
1694    pub fn set_cursor_position(
1695        &mut self,
1696        position: impl Into<Position>,
1697        window: &mut Window,
1698        cx: &mut Context<Self>,
1699    ) {
1700        let position: Position = position.into();
1701        let offset = self.text.position_to_offset(&position);
1702
1703        self.move_to(offset, None, cx);
1704        self.update_preferred_column();
1705        self.focus(window, cx);
1706    }
1707
1708    /// Focus the input field.
1709    pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1710        self.focus_handle.focus(window, cx);
1711        self.blink_cursor.update(cx, |cursor, cx| {
1712            cursor.start(cx);
1713        });
1714    }
1715
1716    /// Refresh the input, so the next render re-runs syntax highlighting and
1717    /// the LSP providers, not just a redraw.
1718    ///
1719    /// Assigning the `lsp` providers (or other render-affecting state) at
1720    /// runtime does not take effect until the text next changes. Call this
1721    /// afterwards to force the refresh on the next render.
1722    ///
1723    /// ```ignore
1724    /// input.update(cx, |state, cx| {
1725    ///     state.extras.lsp.hover_provider = Some(provider);
1726    ///     state.refresh(cx);
1727    /// });
1728    /// ```
1729    pub fn refresh(&mut self, cx: &mut Context<Self>) {
1730        self._pending_update = true;
1731        cx.notify();
1732    }
1733
1734    pub(super) fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
1735        self.undo_manager.break_transaction_coalescing();
1736        self.select_all_cursors_to(|s, sel| s.previous_boundary(sel.cursor_offset()), cx);
1737    }
1738
1739    pub(super) fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
1740        self.select_all_cursors_to(|s, sel| s.next_boundary(sel.cursor_offset()), cx);
1741    }
1742
1743    pub(super) fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
1744        if self.is_single_line() {
1745            return;
1746        }
1747        self.select_all_cursors_to_with_affinity(
1748            |s, sel| {
1749                s.vertical_selection_target(
1750                    sel.cursor_offset(),
1751                    sel.column_anchor,
1752                    s.line_end_affinity_for(sel),
1753                    -1,
1754                )
1755            },
1756            true,
1757            cx,
1758        );
1759    }
1760
1761    pub(super) fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
1762        if self.is_single_line() {
1763            return;
1764        }
1765        self.select_all_cursors_to_with_affinity(
1766            |s, sel| {
1767                s.vertical_selection_target(
1768                    sel.cursor_offset(),
1769                    sel.column_anchor,
1770                    s.line_end_affinity_for(sel),
1771                    1,
1772                )
1773            },
1774            true,
1775            cx,
1776        );
1777    }
1778
1779    pub(super) fn on_action_select_all(
1780        &mut self,
1781        _: &SelectAll,
1782        window: &mut Window,
1783        cx: &mut Context<Self>,
1784    ) {
1785        self.select_all(window, cx);
1786    }
1787
1788    pub(super) fn select_to_start(
1789        &mut self,
1790        _: &SelectToStart,
1791        _: &mut Window,
1792        cx: &mut Context<Self>,
1793    ) {
1794        self.undo_manager.break_transaction_coalescing();
1795        self.select_all_cursors_to(|_, _| 0, cx);
1796    }
1797
1798    pub(super) fn select_to_end(
1799        &mut self,
1800        _: &SelectToEnd,
1801        _: &mut Window,
1802        cx: &mut Context<Self>,
1803    ) {
1804        self.undo_manager.break_transaction_coalescing();
1805        let end = self.text.len();
1806        self.select_all_cursors_to(move |_, _| end, cx);
1807    }
1808
1809    pub(super) fn select_to_start_of_line(
1810        &mut self,
1811        _: &SelectToStartOfLine,
1812        _: &mut Window,
1813        cx: &mut Context<Self>,
1814    ) {
1815        self.undo_manager.break_transaction_coalescing();
1816        self.select_all_cursors_to(
1817            |s, sel| s.start_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1818            cx,
1819        );
1820    }
1821
1822    pub(super) fn select_to_end_of_line(
1823        &mut self,
1824        _: &SelectToEndOfLine,
1825        _: &mut Window,
1826        cx: &mut Context<Self>,
1827    ) {
1828        self.undo_manager.break_transaction_coalescing();
1829        self.select_all_cursors_to_with_affinity(
1830            |s, sel| {
1831                (
1832                    s.end_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1833                    true,
1834                )
1835            },
1836            false,
1837            cx,
1838        );
1839    }
1840
1841    pub(super) fn select_to_previous_word(
1842        &mut self,
1843        _: &SelectToPreviousWordStart,
1844        _: &mut Window,
1845        cx: &mut Context<Self>,
1846    ) {
1847        self.undo_manager.break_transaction_coalescing();
1848        self.select_all_cursors_to(
1849            |s, sel| s.previous_start_of_word_at(sel.cursor_offset()),
1850            cx,
1851        );
1852    }
1853
1854    pub(super) fn select_to_next_word(
1855        &mut self,
1856        _: &SelectToNextWordEnd,
1857        _: &mut Window,
1858        cx: &mut Context<Self>,
1859    ) {
1860        self.undo_manager.break_transaction_coalescing();
1861        self.select_all_cursors_to(|s, sel| s.next_end_of_word_at(sel.cursor_offset()), cx);
1862    }
1863
1864    /// Return the start offset of the previous word.
1865    /// Return the previous start offset of the word before `offset`.
1866    pub(super) fn previous_start_of_word_at(&self, offset: usize) -> usize {
1867        if self.masked {
1868            // The mask replaces every character, so the displayed text has no
1869            // word boundaries to move or delete by. Collapse the word to the
1870            // whole text.
1871            return 0;
1872        }
1873
1874        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1875        // FIXME: Avoid to_string
1876        let left_part = self.text.slice(0..offset).to_string();
1877
1878        let target = UnicodeSegmentation::split_word_bound_indices(left_part.as_str())
1879            .rfind(|(_, s)| !s.trim_start().is_empty())
1880            .map(|(i, _)| i)
1881            .unwrap_or(0);
1882        self.token_boundary(target, Bias::Left)
1883    }
1884
1885    /// Return the next end offset of the word after `offset`.
1886    pub(super) fn next_end_of_word_at(&self, offset: usize) -> usize {
1887        if self.masked {
1888            // See `previous_start_of_word_at`.
1889            return self.text.len();
1890        }
1891
1892        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1893        let right_part = self.text.slice(offset..self.text.len()).to_string();
1894
1895        let target = UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
1896            .find(|(_, s)| !s.trim_start().is_empty())
1897            .map(|(i, s)| offset + i + s.len())
1898            .unwrap_or(self.text.len());
1899        self.token_boundary(target, Bias::Right)
1900    }
1901
1902    /// Get start of line byte offset for the given `offset`.
1903    ///
1904    /// When soft wrap is active, first press goes to visual line start,
1905    /// second press (already at visual start) goes to logical line start.
1906    pub(super) fn start_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1907        if self.is_single_line() {
1908            return 0;
1909        }
1910
1911        let row = self.text.offset_to_point(offset).row;
1912        let logical_start = self.text.line_start_offset(row);
1913
1914        if self.soft_wrap && self.is_code_editor() {
1915            let wrap_point = self
1916                .display_map
1917                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1918            if let Some(line) = self.display_map.line(row)
1919                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1920            {
1921                let visual_start = logical_start + range.start;
1922                if offset != visual_start {
1923                    return visual_start;
1924                }
1925            }
1926        }
1927
1928        logical_start
1929    }
1930
1931    /// Get end of line byte offset for the given `offset`.
1932    ///
1933    /// When soft wrap is active, first press goes to visual line end,
1934    /// second press (already at visual end) goes to logical line end.
1935    pub(super) fn end_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1936        if self.is_single_line() {
1937            return self.text.len();
1938        }
1939
1940        let row = self.text.offset_to_point(offset).row;
1941        let logical_start = self.text.line_start_offset(row);
1942        let logical_end = self.text.line_end_offset(row);
1943
1944        if self.soft_wrap && self.is_code_editor() {
1945            // Use the row the caret is drawn on: at a wrap boundary the raw offset would name
1946            // the next row, and a second End press would keep walking down instead of falling
1947            // through to the logical line end.
1948            let wrap_point = self
1949                .display_map
1950                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1951            if let Some(line) = self.display_map.line(row)
1952                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1953            {
1954                let visual_end = logical_start + range.end;
1955                if offset != visual_end {
1956                    return visual_end;
1957                }
1958            }
1959        }
1960
1961        logical_end
1962    }
1963
1964    /// Get indent string of next line.
1965    ///
1966    /// To get current and next line indent, to return more depth one.
1967    pub(super) fn indent_of_next_line(&mut self) -> String {
1968        self.indent_of_next_line_at(self.cursor())
1969    }
1970
1971    /// Get indent string of the next line, relative to the given `offset`.
1972    pub(super) fn indent_of_next_line_at(&mut self, offset: usize) -> String {
1973        if self.is_single_line() {
1974            return "".into();
1975        }
1976
1977        let mut current_indent = String::new();
1978        let mut next_indent = String::new();
1979        let line_end_affinity = self.line_end_affinity_at(offset);
1980        let current_line_start_pos = self.start_of_line_at(offset, line_end_affinity);
1981        let next_line_start_pos = self.end_of_line_at(offset, line_end_affinity);
1982        for c in self.text.slice(current_line_start_pos..).chars() {
1983            if !c.is_whitespace() {
1984                break;
1985            }
1986            if c == '\n' || c == '\r' {
1987                break;
1988            }
1989            current_indent.push(c);
1990        }
1991
1992        for c in self.text.slice(next_line_start_pos..).chars() {
1993            if !c.is_whitespace() {
1994                break;
1995            }
1996            if c == '\n' || c == '\r' {
1997                break;
1998            }
1999            next_indent.push(c);
2000        }
2001
2002        if next_indent.len() > current_indent.len() {
2003            return next_indent;
2004        }
2005
2006        // Smart indent: one extra level after lines ending with a trigger.
2007        // Skipped inside strings and comments: the trigger is literal text.
2008        if let Some(rules) = self.mode.language_config() {
2009            if self.mode.is_smart_indent()
2010                && M::editing_syntax_context(self, offset) == crate::input::SyntaxContext::Code
2011            {
2012                let line_before: String = self
2013                    .text
2014                    .slice(current_line_start_pos..offset)
2015                    .chars()
2016                    .collect();
2017                if rules.opens_indent(line_before.trim_end()) {
2018                    let tab = self.mode.tab_size();
2019                    if tab.hard_tabs {
2020                        current_indent.push('\t');
2021                    } else {
2022                        for _ in 0..tab.tab_size {
2023                            current_indent.push(' ');
2024                        }
2025                    }
2026                } else {
2027                    let line_after = self.text.slice(offset..next_line_start_pos).to_string();
2028                    if rules.closes_indent(&line_after) {
2029                        let tab = self.mode.tab_size();
2030                        if current_indent.ends_with('\t') {
2031                            current_indent.pop();
2032                        } else {
2033                            for _ in 0..tab.tab_size {
2034                                if !current_indent.ends_with(' ') {
2035                                    break;
2036                                }
2037                                current_indent.pop();
2038                            }
2039                        }
2040                    }
2041                }
2042            }
2043        }
2044
2045        current_indent
2046    }
2047
2048    /// Delete every selection as one batch. Collapsed cursors are first
2049    /// expanded to a deletion range by `collapsed_target` and non-empty
2050    /// selections delete their own range.
2051    ///
2052    /// `collapsed_intent` is the intent to record when every cursor is
2053    /// collapsed, which is what makes a run of single-character deletes undo
2054    /// as one gesture. Deleting a real selection is always atomic.
2055    fn delete_selections(
2056        &mut self,
2057        silent: bool,
2058        collapsed_intent: EditIntent,
2059        mut collapsed_target: impl FnMut(&mut Self, usize) -> Range<usize>,
2060        window: &mut Window,
2061        cx: &mut Context<Self>,
2062    ) {
2063        if !self.is_editable() {
2064            return;
2065        }
2066        let cursors: Vec<CursorSelection> = self.selections.iter().copied().collect();
2067        let intent = if cursors.iter().all(|sel| sel.is_empty()) {
2068            collapsed_intent
2069        } else {
2070            EditIntent::Atomic
2071        };
2072        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(cursors.len());
2073        for sel in &cursors {
2074            let range = if sel.is_empty() {
2075                collapsed_target(self, sel.cursor_offset())
2076            } else {
2077                sel.start..sel.end
2078            };
2079            let (start, end) = (range.start.min(range.end), range.start.max(range.end));
2080            let mut selection = *sel;
2081            selection.start = start;
2082            selection.end = end;
2083            new_selections.push(selection);
2084        }
2085        // Capture the user's selections before expanding or merging deletion
2086        // ranges. The edit ranges cannot reconstruct their original carets.
2087        self.undo_manager.begin_transaction_with(intent);
2088        self.undo_manager
2089            .record_selections(cursors.clone(), cursors.clone());
2090        self.selections.replace_all(new_selections);
2091        self.undo_manager.set_pending_intent(intent);
2092
2093        let was_silent = self.silent_replace_text;
2094        self.silent_replace_text = silent;
2095        self.replace_text_in_range(None, "", window, cx);
2096        self.silent_replace_text = was_silent;
2097        self.undo_manager
2098            .record_selections(cursors, self.selections.iter().copied().collect());
2099        self.undo_manager.commit_transaction();
2100        self.pause_blink_cursor(cx);
2101    }
2102
2103    /// Decide pairing against the pre-edit text, before an opening quote can
2104    /// change its own syntax context. Ordinary characters never query syntax.
2105    fn auto_close_target(&self, range: &Range<usize>, text: &str) -> Option<(usize, SharedString)> {
2106        if self.silent_replace_text
2107            || self.ime_marked_range.is_some()
2108            || !self.selections.is_single()
2109            || !self.active_selection().is_empty()
2110            || !range.is_empty()
2111            || range.start != self.cursor()
2112            || text.chars().count() != 1
2113            || !self.mode.is_auto_close()
2114        {
2115            return None;
2116        }
2117        let rules = self.mode.language_config()?;
2118        if self
2119            .text
2120            .chars_at(range.start)
2121            .next()
2122            .is_some_and(|c| !c.is_whitespace() && !rules.auto_close_before.contains(c))
2123        {
2124            return None;
2125        }
2126        for (open, close, not_in) in rules.closing_pairs() {
2127            let Some(prefix) = open.strip_suffix(text) else {
2128                continue;
2129            };
2130            if !self.text_before_matches(range.start, prefix) {
2131                continue;
2132            }
2133            let start = range.start - prefix.len();
2134            if open == close
2135                && (self.is_escaped_at(start)
2136                    || self
2137                        .text
2138                        .chars_at(start)
2139                        .reversed()
2140                        .next()
2141                        .is_some_and(|c| c.is_alphanumeric() || c == '_'))
2142            {
2143                continue;
2144            }
2145            if not_in.contains(&M::editing_syntax_context(self, start)) {
2146                continue;
2147            }
2148            return Some((open.len(), close.into()));
2149        }
2150        None
2151    }
2152
2153    fn text_before_matches(&self, offset: usize, text: &str) -> bool {
2154        offset >= text.len()
2155            && self.text.is_char_boundary(offset - text.len())
2156            && self.text.slice(offset - text.len()..offset) == text
2157    }
2158
2159    fn text_after_matches(&self, offset: usize, text: &str) -> bool {
2160        offset + text.len() <= self.text.len()
2161            && self.text.is_char_boundary(offset + text.len())
2162            && self.text.slice(offset..offset + text.len()) == text
2163    }
2164
2165    fn is_escaped_at(&self, offset: usize) -> bool {
2166        self.text
2167            .chars_at(offset)
2168            .reversed()
2169            .take_while(|c| *c == '\\')
2170            .count()
2171            % 2
2172            == 1
2173    }
2174
2175    /// Cursor target when a typed closer should skip over an existing one.
2176    ///
2177    /// Returns `Some(offset)` when `new_text` is a single closer from the
2178    /// active [`LanguageConfig`](crate::input::language_config::LanguageConfig) that already follows the collapsed cursor and is
2179    /// not escaped. The caller then moves the cursor without editing text or
2180    /// history. `None` means insert normally.
2181    fn skip_over_target(&self, new_text: &str) -> Option<usize> {
2182        if !self.mode.is_auto_close() || new_text.chars().count() != 1 {
2183            return None;
2184        }
2185        let rules = self.mode.language_config()?;
2186        let cursor = self.cursor();
2187        for (open, close, not_in) in rules.closing_pairs() {
2188            for (index, _) in close.char_indices() {
2189                if !close[index..].starts_with(new_text)
2190                    || !self.text_before_matches(cursor, &close[..index])
2191                    || !self.text_after_matches(cursor, &close[index..])
2192                {
2193                    continue;
2194                }
2195                let context = M::editing_syntax_context(self, cursor);
2196                let generated =
2197                    self.mode
2198                        .auto_closed_pairs()
2199                        .contains_closer(cursor, index, close.len());
2200                if !generated
2201                    && not_in.contains(&context)
2202                    && !(context == crate::input::SyntaxContext::String && open == close)
2203                {
2204                    continue;
2205                }
2206                if self.is_escaped_at(cursor - index) {
2207                    continue;
2208                }
2209                return Some(cursor + new_text.len());
2210            }
2211        }
2212        None
2213    }
2214
2215    pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
2216        // Nothing to delete at the start of the text. Propagate so an ancestor
2217        // (e.g. a command palette navigating back a level) can act on it.
2218        // This is harmless when nothing upstream is bound to backspace. With multiple
2219        // cursors the others can still delete, so only the lone-cursor case
2220        // propagates.
2221        if self.selections.is_single() && self.active_selection().is_empty() && self.cursor() == 0 {
2222            cx.propagate();
2223            return;
2224        }
2225
2226        // Pair deletion follows automatic closing rules, independently of
2227        // structural brackets used by Enter.
2228        if self.mode.is_auto_close()
2229            && self.selections.is_single()
2230            && self.active_selection().is_empty()
2231        {
2232            let off = self.cursor();
2233            let deletion = self.mode.language_config().and_then(|rules| {
2234                rules.closing_pairs().find_map(|(open, close, not_in)| {
2235                    if !self.text_before_matches(off, open) || !self.text_after_matches(off, close)
2236                    {
2237                        return None;
2238                    }
2239                    let start = off - open.len();
2240                    let generated = self
2241                        .mode
2242                        .auto_closed_pairs()
2243                        .contains(start..off, off..off + close.len());
2244                    if self.is_escaped_at(start)
2245                        || (!generated && not_in.contains(&M::editing_syntax_context(self, start)))
2246                    {
2247                        return None;
2248                    }
2249                    Some(start..off + close.len())
2250                })
2251            });
2252            if let Some(range) = deletion {
2253                let utf16 = self.range_to_utf16(&range);
2254                self.replace_text_in_range_silent(Some(utf16), "", window, cx);
2255                return;
2256            }
2257        }
2258
2259        self.delete_selections(
2260            false,
2261            EditIntent::Backspace,
2262            |s, offset| s.previous_boundary(offset)..offset,
2263            window,
2264            cx,
2265        );
2266    }
2267
2268    pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
2269        self.delete_selections(
2270            false,
2271            EditIntent::DeleteForward,
2272            |s, offset| offset..s.next_boundary(offset),
2273            window,
2274            cx,
2275        );
2276    }
2277
2278    pub(super) fn delete_to_beginning_of_line(
2279        &mut self,
2280        _: &DeleteToBeginningOfLine,
2281        window: &mut Window,
2282        cx: &mut Context<Self>,
2283    ) {
2284        self.delete_selections(
2285            true,
2286            EditIntent::Atomic,
2287            |s, offset| {
2288                let mut start = s.start_of_line_at(offset, s.line_end_affinity_at(offset));
2289                if start == offset {
2290                    start = start.saturating_sub(1);
2291                }
2292                start..offset
2293            },
2294            window,
2295            cx,
2296        );
2297    }
2298
2299    pub(super) fn delete_to_end_of_line(
2300        &mut self,
2301        _: &DeleteToEndOfLine,
2302        window: &mut Window,
2303        cx: &mut Context<Self>,
2304    ) {
2305        self.delete_selections(
2306            true,
2307            EditIntent::Atomic,
2308            |s, offset| {
2309                let mut end = s.end_of_line_at(offset, s.line_end_affinity_at(offset));
2310                if end == offset {
2311                    end = (end + 1).clamp(0, s.text.len());
2312                }
2313                offset..end
2314            },
2315            window,
2316            cx,
2317        );
2318    }
2319
2320    pub(super) fn delete_previous_word(
2321        &mut self,
2322        _: &DeleteToPreviousWordStart,
2323        window: &mut Window,
2324        cx: &mut Context<Self>,
2325    ) {
2326        self.delete_selections(
2327            true,
2328            EditIntent::Atomic,
2329            |s, offset| s.previous_start_of_word_at(offset)..offset,
2330            window,
2331            cx,
2332        );
2333    }
2334
2335    pub(super) fn delete_next_word(
2336        &mut self,
2337        _: &DeleteToNextWordEnd,
2338        window: &mut Window,
2339        cx: &mut Context<Self>,
2340    ) {
2341        self.delete_selections(
2342            true,
2343            EditIntent::Atomic,
2344            |s, offset| offset..s.next_end_of_word_at(offset),
2345            window,
2346            cx,
2347        );
2348    }
2349
2350    pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {
2351        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2352            return;
2353        }
2354
2355        // Clear inline completion on enter (user chose not to accept it)
2356        if M::has_inline_completion(self) {
2357            M::clear_inline_completion(self, cx);
2358        }
2359
2360        // In multi-line mode with `submit_on_enter` enabled, a plain `Enter`
2361        // (without Shift) is treated as submit: propagate the action and emit
2362        // PressEnter without inserting a newline. `Shift+Enter` still inserts
2363        // a newline.
2364        let insert_newline = self.is_multi_line() && (!self.submit_on_enter || action.shift);
2365
2366        if insert_newline {
2367            if !self.selections.is_single() {
2368                // Insert a newline (with per-line indent) at every cursor.
2369                self.selections.merge_overlapping();
2370                let selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
2371                let mut edits: Vec<(Range<usize>, String)> = Vec::with_capacity(selections.len());
2372                for sel in &selections {
2373                    let indent = if self.is_code_editor() {
2374                        self.indent_of_next_line_at(sel.cursor_offset())
2375                    } else {
2376                        String::new()
2377                    };
2378                    edits.push((sel.start..sel.end, format!("\n{}", indent)));
2379                }
2380                edits.sort_by_key(|(range, _)| range.start);
2381                self.replace_text_in_ranges(&edits, window, cx);
2382                self.pause_blink_cursor(cx);
2383            } else {
2384                // Bracket split: Enter between `{|}` produces `{\n  \n}`
2385                // with the cursor on the middle line. Controlled by smart_indent,
2386                // independently of automatic closing.
2387                // Skipped inside strings and comments: splitting there would
2388                // restructure literal text.
2389                let mut split = false;
2390                if self.is_code_editor() && self.active_selection().is_empty() {
2391                    if let Some(rules) = self.mode.language_config() {
2392                        if self.mode.is_smart_indent()
2393                            && M::editing_syntax_context(self, self.cursor())
2394                                == crate::input::SyntaxContext::Code
2395                        {
2396                            let off = self.cursor();
2397                            let is_pair = rules.brackets.iter().any(|p| {
2398                                !p.open.is_empty()
2399                                    && !p.close.is_empty()
2400                                    && p.open != p.close
2401                                    && self.text_before_matches(off, p.open.as_ref())
2402                                    && self.text_after_matches(off, p.close.as_ref())
2403                            });
2404                            if is_pair {
2405                                // Base indent: leading whitespace only.
2406                                let line_end_affinity = self.line_end_affinity_at(off);
2407                                let line_start = self.start_of_line_at(off, line_end_affinity);
2408                                let mut indent = String::new();
2409                                for c in self.text.slice(line_start..).chars() {
2410                                    if !c.is_whitespace() || c == '\n' || c == '\r' {
2411                                        break;
2412                                    }
2413                                    indent.push(c);
2414                                }
2415                                // Extra level only with smart indent enabled.
2416                                let mut inner = indent.clone();
2417                                if self.mode.is_smart_indent() {
2418                                    let tab = self.mode.tab_size();
2419                                    if tab.hard_tabs {
2420                                        inner.push('\t');
2421                                    } else {
2422                                        for _ in 0..tab.tab_size {
2423                                            inner.push(' ');
2424                                        }
2425                                    }
2426                                }
2427                                let new_line_text = format!("\n{inner}\n{indent}");
2428                                self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2429                                self.set_cursor_to(off + 1 + inner.len());
2430                                self.update_preferred_column();
2431                                let cursors = self.selections.iter().copied().collect::<Vec<_>>();
2432                                self.undo_manager
2433                                    .record_selections(cursors.clone(), cursors);
2434                                self.pause_blink_cursor(cx);
2435                                split = true;
2436                            }
2437                        }
2438                    }
2439                }
2440                if !split {
2441                    // Get current line indent
2442                    let indent = if self.is_code_editor() {
2443                        self.indent_of_next_line()
2444                    } else {
2445                        "".to_string()
2446                    };
2447
2448                    // Add newline and indent
2449                    let new_line_text = format!("\n{}", indent);
2450                    self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2451                    self.pause_blink_cursor(cx);
2452                }
2453            }
2454        } else {
2455            // Single line input or submit-on-enter: just emit the event
2456            // (e.g.: in a dialog to confirm, or a chat textarea to send).
2457            self.undo_manager.break_transaction_coalescing();
2458            cx.propagate();
2459        }
2460
2461        cx.emit(InputEvent::PressEnter {
2462            secondary: action.secondary,
2463            shift: action.shift,
2464        });
2465    }
2466
2467    pub fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2468        self.replace_text("", window, cx);
2469        self.set_selection(0, 0);
2470        self.scroll_to(0, None, cx);
2471    }
2472
2473    pub(super) fn escape(&mut self, action: &Escape, window: &mut Window, cx: &mut Context<Self>) {
2474        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2475            return;
2476        }
2477
2478        // Escape also dismisses a request whose popup has not arrived yet.
2479        M::hide_context_menu(self, cx);
2480
2481        // Collapse extra cursors back to the active one first.
2482        if !self.selections.is_single() {
2483            self.undo_manager.break_transaction_coalescing();
2484            self.selections.remove_all_but_active();
2485            cx.notify();
2486            return;
2487        }
2488
2489        // Clear inline completion on escape
2490        if M::has_inline_completion(self) {
2491            M::clear_inline_completion(self, cx);
2492            return; // Consume the escape, don't propagate
2493        }
2494        M::clear_inline_completion(self, cx);
2495
2496        // The handles and the edit menu are the topmost surface to dismiss.
2497        if self.touch_selection().is_some() {
2498            self.dismiss_touch_selection(cx);
2499            return;
2500        }
2501
2502        if self.ime_marked_range.is_some() {
2503            self.unmark_text(window, cx);
2504        }
2505
2506        // Android delivers its back button as escape, and back leaves a field's
2507        // text alone: it closes what is open or navigates.
2508        if self.clean_on_escape && !cfg!(target_os = "android") {
2509            return self.clean(window, cx);
2510        }
2511
2512        cx.propagate();
2513    }
2514
2515    /// Show the right-click context menu as a native OS menu.
2516    pub(crate) fn handle_right_click_menu(
2517        &mut self,
2518        position: Point<Pixels>,
2519        offset: usize,
2520        window: &mut Window,
2521        cx: &mut Context<Self>,
2522    ) {
2523        if self.disabled {
2524            return;
2525        }
2526        if crate::GlobalState::is_in_deferred_context(cx) {
2527            return;
2528        }
2529
2530        if !self.active_selection().contains(offset) {
2531            self.move_to(offset, None, cx);
2532        }
2533
2534        if self.is_code_editor() {
2535            M::on_hover_definition(self, offset, window, cx);
2536        }
2537
2538        if let Some(handler) = self.context_menu_handler.clone() {
2539            let capabilities = self.context_menu_capabilities();
2540            let input = cx.weak_entity();
2541            // The handler runs with the input released, so a custom builder can
2542            // read it (cursor, selection, text) to build its items.
2543            window.defer(cx, move |window, cx| {
2544                if input.upgrade().is_none() {
2545                    return;
2546                }
2547                let previous_focus = window.focused(cx);
2548                handler(NativeMenu::new(), capabilities, position, window, cx);
2549                // A drawn menu takes focus synchronously; native menus leave it
2550                // on the input. Capture it here rather than in the handler.
2551                input
2552                    .update(cx, |this, cx| {
2553                        if window.focused(cx) == previous_focus
2554                            && !this.focus_handle.is_focused(window)
2555                            && this.has_selection_focus(window, cx)
2556                        {
2557                            // A custom handler can reuse an already-focused popup.
2558                            return;
2559                        }
2560                        let menu_focus = window.focused(cx).filter(|focus| {
2561                            Some(focus) != previous_focus.as_ref() && *focus != this.focus_handle
2562                        });
2563                        this.set_selection_focus(menu_focus, cx);
2564                    })
2565                    .ok();
2566            });
2567        }
2568    }
2569
2570    pub(super) fn add_cursor_above(
2571        &mut self,
2572        _: &AddCursorAbove,
2573        _: &mut Window,
2574        cx: &mut Context<Self>,
2575    ) {
2576        self.add_cursor_vertical(-1, cx);
2577    }
2578
2579    pub(super) fn add_cursor_below(
2580        &mut self,
2581        _: &AddCursorBelow,
2582        _: &mut Window,
2583        cx: &mut Context<Self>,
2584    ) {
2585        self.add_cursor_vertical(1, cx);
2586    }
2587
2588    /// Add a new cursor one display line above (`move_lines < 0`) or below each
2589    /// existing cursor, preserving the column. Cursors that would not move (at
2590    /// the first/last display row) or that would duplicate an existing cursor
2591    /// are skipped.
2592    fn add_cursor_vertical(&mut self, move_lines: isize, cx: &mut Context<Self>) {
2593        if !self.is_multi_line() {
2594            return;
2595        }
2596
2597        self.pause_blink_cursor(cx);
2598        // Changing the cursor set ends the editing gesture that came before it.
2599        self.undo_manager.break_transaction_coalescing();
2600
2601        let sources: Vec<(usize, Option<(Pixels, usize)>, bool)> = self
2602            .selections
2603            .iter()
2604            .map(|sel| {
2605                (
2606                    sel.cursor_offset(),
2607                    sel.column_anchor,
2608                    self.line_end_affinity_for(sel),
2609                )
2610            })
2611            .collect();
2612        let mut offsets: std::collections::HashSet<usize> =
2613            sources.iter().map(|(offset, _, _)| *offset).collect();
2614
2615        let mut newest: Option<usize> = None;
2616        for (offset, anchor, line_end_affinity) in sources {
2617            let anchor = anchor.or_else(|| self.preferred_column_for(offset));
2618            let (target, _) = self.vertical_target(offset, anchor, line_end_affinity, move_lines);
2619            if target == offset || offsets.contains(&target) {
2620                continue;
2621            }
2622            offsets.insert(target);
2623            let id = self.selections.generate_id();
2624            let mut cursor = CursorSelection::new(id, target, target);
2625            cursor.column_anchor = anchor;
2626            self.selections.add(cursor);
2627            newest = Some(target);
2628        }
2629
2630        if let Some(newest) = newest {
2631            self.scroll_to(newest, None, cx);
2632        }
2633        cx.notify();
2634    }
2635
2636    /// Add an additional collapsed cursor at `offset`.
2637    ///
2638    /// Rejected when `offset` lands inside an existing selection or exactly on
2639    /// an existing cursor.
2640    pub(super) fn add_cursor_at(&mut self, offset: usize, cx: &mut Context<Self>) {
2641        if !self.is_multi_line() {
2642            return;
2643        }
2644
2645        for sel in self.selections.iter() {
2646            if sel.contains(offset) {
2647                return;
2648            }
2649            if sel.is_collapsed() && sel.cursor_offset() == offset {
2650                return;
2651            }
2652        }
2653
2654        self.undo_manager.break_transaction_coalescing();
2655        let id = self.selections.generate_id();
2656        let mut selection = CursorSelection::new(id, offset, offset);
2657        selection.column_anchor = self.preferred_column_for(offset);
2658        self.selections.add(selection);
2659        cx.notify();
2660    }
2661
2662    /// Build a columnar (block) selection spanning the rows between the two
2663    /// points, one selection per display row at the same column span.
2664    ///
2665    /// The span keeps the columns the pointer sat past the end of a short row, so a row
2666    /// that runs out of text does not narrow the block for the rows that do reach that
2667    /// far. Each row is then clipped to its own end, selecting as much of the span as it
2668    /// actually has.
2669    pub(super) fn build_columnar_selection(
2670        &mut self,
2671        start: ColumnarPoint,
2672        end: ColumnarPoint,
2673        cx: &mut Context<Self>,
2674    ) {
2675        if !self.is_multi_line() {
2676            return;
2677        }
2678
2679        self.undo_manager.break_transaction_coalescing();
2680
2681        let (start_row, start_col) = self.columnar_row_column(start);
2682        let (end_row, end_col) = self.columnar_row_column(end);
2683        let (start_row, end_row) = (start_row.min(end_row), start_row.max(end_row));
2684        let (start_col, end_col) = (start_col.min(end_col), start_col.max(end_col));
2685
2686        let mut new_selections = Vec::with_capacity(end_row - start_row + 1);
2687        for row in start_row..=end_row {
2688            let sel_start = self
2689                .display_map
2690                .display_row_column_to_offset(row, start_col);
2691            let sel_end = self.display_map.display_row_column_to_offset(row, end_col);
2692            let id = self.selections.generate_id();
2693            let sel_start = self.text.clip_offset(sel_start, Bias::Left);
2694            let sel_end = self.text.clip_offset(sel_end, Bias::Left);
2695            new_selections.push(CursorSelection::new(id, sel_start, sel_end));
2696        }
2697
2698        self.selections.replace_all(new_selections);
2699        cx.notify();
2700    }
2701
2702    /// Resolve a columnar point to the display row it is on and the column it reaches,
2703    /// counting the columns beyond the row's end so a short row keeps the full span.
2704    fn columnar_row_column(&self, point: ColumnarPoint) -> (usize, usize) {
2705        let display_point = self.display_map.offset_to_wrap_display_point(point.offset);
2706        let row = self
2707            .display_map
2708            .wrap_row_to_display_row(display_point.row)
2709            .unwrap_or_else(|| {
2710                self.display_map
2711                    .nearest_visible_display_row(display_point.row)
2712            });
2713
2714        (row, display_point.column + point.columns_past_line_end)
2715    }
2716
2717    pub(super) fn on_mouse_down(
2718        &mut self,
2719        event: &MouseDownEvent,
2720        window: &mut Window,
2721        cx: &mut Context<Self>,
2722    ) {
2723        // A token that consumed the press (a double click selecting it) must
2724        // not also place the caret.
2725        if window.default_prevented() {
2726            return;
2727        }
2728        // A tap asks for the virtual keyboard. Focus alone does not when it
2729        // moves from another input, or stays here, after the user hid it.
2730        if event.button == MouseButton::Left
2731            && crate::GlobalState::is_touch_press(cx)
2732            && !self.disabled
2733            && !self.readonly
2734        {
2735            window.request_virtual_keyboard();
2736        }
2737        self.undo_manager.break_transaction_coalescing();
2738        // Treat every press like a cursor move: take focus now rather than
2739        // after GPUI's focus observer runs, show the caret at once and restart
2740        // the idle delay, whatever phase the blink was in.
2741        if !self.disabled {
2742            self.focus(window, cx);
2743            self.pause_blink_cursor(cx);
2744        }
2745        // Input has its own text selection; suppress the window-level text
2746        // selection (Root) so it does not start a drag from here.
2747        crate::global_state::GlobalState::suppress_text_selection(cx);
2748
2749        // Clear inline completion on any mouse interaction
2750        M::clear_inline_completion(self, cx);
2751        // A tap on the touch selection asks for its menu back. Any other press
2752        // places the caret or starts a new drag, and the handles and the menu
2753        // no longer belong to what is selected.
2754        if event.button == MouseButton::Left
2755            && event.click_count == 1
2756            && self.reopen_edit_menu_at(event.position, cx)
2757        {
2758            return;
2759        }
2760        self.dismiss_touch_selection(cx);
2761
2762        // If there have IME marked range and is empty (Means pressed Esc to abort IME typing)
2763        // Clear the marked range.
2764        if let Some(ime_marked_range) = &self.ime_marked_range {
2765            if ime_marked_range.len() == 0 {
2766                self.ime_marked_range = None;
2767            }
2768        }
2769
2770        self.selecting = true;
2771        let (offset, line_end_affinity, columns_past_line_end) =
2772            self.resolve_mouse_position(event.position);
2773
2774        if M::on_click(self, event, offset, window, cx) {
2775            return;
2776        }
2777
2778        // Triple click to select line
2779        if event.button == MouseButton::Left && event.click_count >= 3 {
2780            self.select_line(offset, window, cx);
2781            return;
2782        }
2783
2784        // Double click to select word
2785        if event.button == MouseButton::Left && event.click_count == 2 {
2786            self.select_word(offset, window, cx);
2787            // A double tap is touch's other way to select a word, and it
2788            // gets the handles and the menu like a long press does.
2789            if crate::GlobalState::is_touch_press(cx) {
2790                self.keep_touch_selection(cx);
2791            }
2792            return;
2793        }
2794
2795        // Show Mouse context menu
2796        if event.button == MouseButton::Right {
2797            if self.enable_context_menu {
2798                if !self.active_selection().contains(offset) {
2799                    self.move_to(offset, None, cx);
2800                }
2801                self.pending_context_menu = Some((event.position, offset));
2802            }
2803            return;
2804        }
2805
2806        // Multi-cursor placement, multi-line only.
2807        if self.is_multi_line() && event.button == MouseButton::Left {
2808            if event.modifiers.alt
2809                && (event.modifiers.shift || (cfg!(target_os = "linux") && event.modifiers.control))
2810            {
2811                // Alt+Shift starts a block; Linux also accepts Ghostty's Ctrl+Alt.
2812                // Mark selecting so the drag handler extends the block.
2813                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2814                self.selecting = true;
2815                self.move_to_with_affinity(offset, None, line_end_affinity, cx);
2816                return;
2817            } else if event.modifiers.alt {
2818                self.add_cursor_at(offset, cx);
2819                // Keep click-to-add behavior, but use this press as the block
2820                // anchor if the user continues dragging with the left button.
2821                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2822                return;
2823            }
2824        }
2825
2826        if event.modifiers.shift {
2827            self.select_to_with_affinity(offset, line_end_affinity, cx);
2828        } else {
2829            self.move_to_with_affinity(offset, None, line_end_affinity, cx)
2830        }
2831    }
2832
2833    pub(super) fn on_mouse_up(
2834        &mut self,
2835        event: &MouseUpEvent,
2836        window: &mut Window,
2837        cx: &mut Context<Self>,
2838    ) {
2839        if event.button == MouseButton::Right {
2840            if let Some((position, offset)) = self.pending_context_menu.take() {
2841                self.handle_right_click_menu(position, offset, window, cx);
2842            }
2843        }
2844        if self.active_selection().is_empty() {
2845            self.active_selection_mut().reversed = false;
2846        }
2847        self.selecting = false;
2848        self.selected_word_range = None;
2849        self.column_select_start = None;
2850        self.auto_scroll.stop();
2851    }
2852
2853    pub(super) fn on_mouse_move(
2854        &mut self,
2855        event: &MouseMoveEvent,
2856        window: &mut Window,
2857        cx: &mut Context<Self>,
2858    ) {
2859        // `last_bounds` is moved by the scroll offset; `input_bounds` is the visible area.
2860        let within_bounds = self.input_bounds.contains(&event.position);
2861
2862        if !within_bounds {
2863            // Clear hover when mouse leaves the input
2864            M::clear_hover_state(self, cx);
2865            return;
2866        }
2867
2868        // Show diagnostic popover on mouse move
2869        let (offset, _) = self.index_for_mouse_position(event.position);
2870        M::on_mouse_move(self, offset, event, window, cx);
2871
2872        if self.is_code_editor() {
2873            // Keep the current popover while the pointer stays on the same
2874            // diagnostic, so moving over a squiggle neither repaints nor
2875            // rebuilds the popover.
2876            match self
2877                .mode
2878                .diagnostics()
2879                .and_then(|set| set.for_offset(offset))
2880            {
2881                Some(diagnostic) => {
2882                    if self.diagnostic_popover.as_deref() != Some(diagnostic) {
2883                        self.diagnostic_popover = Some(Rc::new(diagnostic.clone()));
2884                        cx.notify();
2885                    }
2886                }
2887                None => {
2888                    if self.diagnostic_popover.take().is_some() {
2889                        cx.notify();
2890                    }
2891                }
2892            }
2893        }
2894    }
2895
2896    pub(super) fn on_scroll_wheel(
2897        &mut self,
2898        event: &ScrollEvent,
2899        window: &mut Window,
2900        cx: &mut Context<Self>,
2901    ) {
2902        let line_height = self
2903            .last_layout
2904            .as_ref()
2905            .map(|layout| layout.line_height)
2906            .unwrap_or(window.line_height());
2907        let delta = event.delta.pixel_delta(line_height);
2908
2909        let old_offset = self.scroll_handle.offset();
2910        let precise = matches!(event.delta, gpui::ScrollDelta::Pixels(_));
2911        if !precise || !self.scroll_bounce || cx.reduce_motion() || !self.overflows_vertically() {
2912            let displaced = self.overscroll.offset() != 0.;
2913            self.overscroll.reset();
2914            if displaced {
2915                cx.notify();
2916            }
2917        }
2918        let bounces = self.scroll_bounce
2919            && precise
2920            && !cx.reduce_motion()
2921            && delta.y.abs() >= delta.x.abs()
2922            && self.overflows_vertically();
2923        if bounces {
2924            let mut overscroll = std::mem::take(&mut self.overscroll);
2925            let stretched = overscroll.packet(
2926                delta.y.as_f32(),
2927                event.touch_phase,
2928                self.input_bounds.size.height.as_f32(),
2929                cx.background_executor().now(),
2930                |dy| {
2931                    let before = self.scroll_handle.offset();
2932                    self.update_scroll_offset(
2933                        Some(point(before.x + delta.x, before.y + px(dy))),
2934                        cx,
2935                    );
2936                    (self.scroll_handle.offset().y - before.y).as_f32()
2937                },
2938            );
2939            self.overscroll = overscroll;
2940            if stretched {
2941                cx.notify();
2942            }
2943        } else {
2944            // Axis drift must not drop the end of a vertical drag.
2945            if matches!(
2946                event.touch_phase,
2947                gpui::TouchPhase::Ended | gpui::TouchPhase::Cancelled
2948            ) && self.overscroll.offset() != 0.
2949            {
2950                self.overscroll.packet(
2951                    0.,
2952                    event.touch_phase,
2953                    self.input_bounds.size.height.as_f32(),
2954                    cx.background_executor().now(),
2955                    |_| 0.,
2956                );
2957                cx.notify();
2958            }
2959            self.update_scroll_offset(Some(old_offset + delta), cx);
2960        }
2961
2962        // Text that has room to scroll keeps a scroll over it even where the
2963        // text ends, so reaching the end does not scroll the region around the
2964        // input; text that fits leaves the scroll to that region.
2965        if self.scroll_handle.offset() != old_offset || self.overflows_along(delta) {
2966            cx.stop_propagation();
2967        }
2968
2969        if self.diagnostic_popover.take().is_some() {
2970            cx.notify();
2971        }
2972        // The handles follow the text; the menu would sit over whatever
2973        // scrolls underneath it, so it steps aside until the finger lifts.
2974        self.edit_menu_on_scroll(event.touch_phase, cx);
2975    }
2976
2977    /// Whether the text takes a wheel event moving by `delta` from the
2978    /// scroll areas around it: it has room to scroll along the axis the
2979    /// event mostly moves on. Unlike [`Self::overflows_along`], an event that
2980    /// moves on neither axis, such as a gesture's start or end, goes to text
2981    /// that can scroll vertically.
2982    pub(super) fn takes_scroll(&self, delta: Point<Pixels>) -> bool {
2983        if delta.y.abs() >= delta.x.abs() {
2984            self.overflows_vertically()
2985        } else {
2986            self.scroll_size.width > self.input_bounds.size.width
2987        }
2988    }
2989
2990    /// Whether the text has room to scroll along the axis `delta` mostly
2991    /// moves on. A trackpad swipe drifts a little off its axis, so the drift
2992    /// alone does not claim the scroll.
2993    fn overflows_along(&self, delta: Point<Pixels>) -> bool {
2994        if delta.y.abs() >= delta.x.abs() {
2995            delta.y != px(0.) && self.overflows_vertically()
2996        } else {
2997            self.scroll_size.width > self.input_bounds.size.width
2998        }
2999    }
3000
3001    /// Whether multi-line text is taller than the input.
3002    fn overflows_vertically(&self) -> bool {
3003        self.is_multi_line() && self.scroll_size.height > self.input_bounds.size.height
3004    }
3005
3006    pub(super) fn update_scroll_offset(
3007        &mut self,
3008        offset: Option<Point<Pixels>>,
3009        cx: &mut Context<Self>,
3010    ) {
3011        let mut offset = offset.unwrap_or(self.scroll_handle.offset());
3012        let safe_y_range =
3013            (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
3014
3015        offset.y = if self.is_single_line() {
3016            px(0.)
3017        } else {
3018            offset.y.clamp(safe_y_range.start, safe_y_range.end)
3019        };
3020        offset.x = clamp_horizontal_scroll_offset(
3021            offset.x,
3022            self.scroll_size.width,
3023            self.input_bounds.size.width,
3024            self.text_align,
3025        );
3026        if self.scroll_handle.offset() != offset {
3027            self.scroll_handle.set_offset(offset);
3028            cx.notify();
3029        }
3030    }
3031
3032    /// Scroll to make the given offset visible.
3033    ///
3034    /// If `direction` is Some, will keep edges at the same side.
3035    pub(crate) fn scroll_to(
3036        &mut self,
3037        offset: usize,
3038        direction: Option<MoveDirection>,
3039        cx: &mut Context<Self>,
3040    ) {
3041        let padding = if direction.is_some() {
3042            ScrollPadding::SurroundingLines
3043        } else {
3044            ScrollPadding::Minimal
3045        };
3046        self.scroll_to_with_padding(offset, direction, padding, cx);
3047    }
3048
3049    /// Reveal an offset with independently chosen direction restriction and padding.
3050    /// Search uses surrounding lines without restricting movement to match order.
3051    pub(crate) fn scroll_to_with_padding(
3052        &mut self,
3053        offset: usize,
3054        direction: Option<MoveDirection>,
3055        padding: ScrollPadding,
3056        cx: &mut Context<Self>,
3057    ) {
3058        let Some(last_layout) = self.last_layout.as_ref() else {
3059            return;
3060        };
3061        let Some(bounds) = self.last_bounds.as_ref() else {
3062            return;
3063        };
3064
3065        let mut scroll_offset = self.scroll_handle.offset();
3066        let was_offset = scroll_offset;
3067        let line_height = last_layout.line_height;
3068
3069        let point = self.text.offset_to_point(offset);
3070
3071        let row = point.row;
3072
3073        // Resolve the wrapped row even when the target is outside the last layout.
3074        let display_row = self
3075            .display_map
3076            .offset_to_display_row(offset, self.line_end_affinity_at(offset));
3077        let row_offset_y = line_height * display_row;
3078
3079        // For Right alignment use 0 margin: the cursor indicator is clamped inside bounds
3080        // in layout_cursors, so shifting the text here would cause a first-click visual jump.
3081        let safety_margin = match last_layout.text_align {
3082            TextAlign::Left => RIGHT_MARGIN,
3083            TextAlign::Right => px(0.),
3084            TextAlign::Center => CURSOR_WIDTH,
3085        };
3086        if let Some(vi) = last_layout
3087            .visible_buffer_lines
3088            .iter()
3089            .position(|&line| line == row)
3090        {
3091            let line = &last_layout.lines[vi];
3092            let local_offset = offset.saturating_sub(last_layout.visible_line_byte_offsets[vi]);
3093            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
3094                let bounds_width = bounds.size.width - last_layout.line_number_width;
3095                let col_offset_x = pos.x;
3096                if col_offset_x - safety_margin < -scroll_offset.x {
3097                    // If the position is out of the visible area, scroll to make it visible
3098                    scroll_offset.x = -col_offset_x + safety_margin;
3099                } else if col_offset_x + safety_margin > -scroll_offset.x + bounds_width {
3100                    scroll_offset.x = -(col_offset_x - bounds_width + safety_margin);
3101                }
3102            }
3103        }
3104
3105        // Scroll the row into view. Use the same edge clearance helper as
3106        // `TextElement::layout_cursors` so both scroll-into-view paths agree
3107        // (a mismatch flickered on `Down` at end-of-buffer with a small
3108        // `cursor_surrounding_lines` override).
3109        let edge_height =
3110            if matches!(padding, ScrollPadding::SurroundingLines) && self.is_code_editor() {
3111                super::element::cursor_surrounding_padding(
3112                    self.mode.is_auto_grow(),
3113                    self.cursor_surrounding_lines,
3114                    super::element::viewport_visible_lines(bounds.size.height, line_height),
3115                    line_height,
3116                )
3117            } else {
3118                px(0.)
3119            };
3120        scroll_offset.y = super::element::reveal_row(
3121            scroll_offset.y,
3122            row_offset_y,
3123            line_height,
3124            bounds.size.height,
3125            edge_height,
3126        );
3127
3128        // Avoid necessary scroll, when it was already in the correct position.
3129        if direction == Some(MoveDirection::Up) {
3130            scroll_offset.y = scroll_offset.y.max(was_offset.y);
3131        } else if direction == Some(MoveDirection::Down) {
3132            scroll_offset.y = scroll_offset.y.min(was_offset.y);
3133        }
3134
3135        // Clamp the deferred target into the same safe range that
3136        // `update_scroll_offset` enforces on persist, so paint never shows an
3137        // over-scrolled frame before the post-paint clamp pulls it back.
3138        let safe_y_min = (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.));
3139        scroll_offset.x = scroll_offset.x.min(px(0.));
3140        scroll_offset.y = scroll_offset.y.clamp(safe_y_min, px(0.));
3141        self.deferred_scroll_offset = Some(scroll_offset);
3142        cx.notify();
3143    }
3144
3145    pub(super) fn show_character_palette(
3146        &mut self,
3147        _: &ShowCharacterPalette,
3148        window: &mut Window,
3149        _: &mut Context<Self>,
3150    ) {
3151        window.show_character_palette();
3152    }
3153
3154    /// The text of every non-empty selection, in document order.
3155    fn selected_texts(&self) -> Vec<String> {
3156        let mut selections: Vec<CursorSelection> = self
3157            .selections
3158            .iter()
3159            .copied()
3160            .filter(|sel| !sel.is_empty())
3161            .collect();
3162        selections.sort_by_key(|sel| sel.start);
3163        selections
3164            .iter()
3165            .map(|sel| self.text.slice(*sel).to_string())
3166            .collect()
3167    }
3168
3169    pub(super) fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
3170        if !self.is_copyable() {
3171            return;
3172        }
3173
3174        let texts = self.selected_texts();
3175
3176        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
3177    }
3178
3179    pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
3180        if !self.is_copyable() {
3181            return;
3182        }
3183
3184        let texts = self.selected_texts();
3185
3186        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
3187
3188        self.undo_manager.set_pending_intent(EditIntent::Atomic);
3189        self.replace_text_in_range_silent(None, "", window, cx);
3190    }
3191
3192    pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
3193        if !self.is_editable() {
3194            return;
3195        }
3196        if let Some(clipboard) = cx.read_from_clipboard() {
3197            self.insert_clipboard(clipboard, window, cx);
3198            return;
3199        }
3200        // The synchronous read is empty on platforms whose clipboard is
3201        // asynchronous and permission-gated (the web), so fall back to the
3202        // real read. It has to start here, still inside the user activation
3203        // that dispatched `Paste`, or the browser refuses the read.
3204        let read = cx.read_from_clipboard_async();
3205        let target = self.paste_target();
3206        cx.spawn_in(window, async move |this, cx| match read.await {
3207            Ok(Some(clipboard)) => {
3208                this.update_in(cx, |this, window, cx| {
3209                    // The read can sit behind a permission prompt for as
3210                    // long as the user likes. If they edited, moved the
3211                    // caret or left the input meanwhile, the paste would
3212                    // land where they no longer mean it to; drop it, as the
3213                    // browser's own paste event only reaches the focused
3214                    // input too.
3215                    if this.is_editable()
3216                        && this.focus_handle.is_focused(window)
3217                        && this.paste_target() == target
3218                    {
3219                        this.insert_clipboard(clipboard, window, cx);
3220                    }
3221                })
3222                .ok();
3223            }
3224            Ok(None) => {}
3225            Err(error) => tracing::warn!("failed to read the clipboard for paste: {error}"),
3226        })
3227        .detach();
3228    }
3229
3230    /// Where a paste would go right now: the document as edited so far and
3231    /// the selections it would replace. Two equal targets mean an edit made
3232    /// for one still applies to the other.
3233    fn paste_target(&self) -> PasteTarget {
3234        PasteTarget {
3235            document_revision: self.document_revision,
3236            selections: self.selections.iter().copied().collect(),
3237        }
3238    }
3239
3240    /// Inserts the clipboard text the way `Paste` does: one atomic edit,
3241    /// newlines dropped on a single-line input, one line per selection when
3242    /// the counts match on a multi-line one. A clipboard without text (an
3243    /// image, say) is left alone rather than replacing the selection with
3244    /// nothing.
3245    fn insert_clipboard(
3246        &mut self,
3247        clipboard: ClipboardItem,
3248        window: &mut Window,
3249        cx: &mut Context<Self>,
3250    ) {
3251        let Some(mut new_text) = clipboard.text().filter(|text| !text.is_empty()) else {
3252            return;
3253        };
3254        // A paste is one atomic edit, never part of a typing run.
3255        self.undo_manager.set_pending_intent(EditIntent::Atomic);
3256
3257        if !self.is_multi_line() {
3258            new_text = new_text.replace('\n', "");
3259            self.replace_text_in_range_silent(None, &new_text, window, cx);
3260            self.scroll_to(self.cursor(), None, cx);
3261            return;
3262        }
3263
3264        // Distribute one clipboard line per selection when the counts match.
3265        // Otherwise insert the whole clipboard text at each cursor.
3266        if !self.selections.is_single() {
3267            self.selections.merge_overlapping();
3268        }
3269        let lines: Vec<String> = new_text.split('\n').map(|s| s.to_string()).collect();
3270        let count = self.selections.len();
3271        if count > 1 && lines.len() == count {
3272            let mut selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
3273            selections.sort_by_key(|sel| sel.start);
3274            let edits: Vec<(Range<usize>, String)> = selections
3275                .iter()
3276                .zip(lines)
3277                .map(|(sel, line)| (sel.start..sel.end, line))
3278                .collect();
3279            self.replace_text_in_ranges(&edits, window, cx);
3280        } else {
3281            self.replace_text_in_range_silent(None, &new_text, window, cx);
3282        }
3283        self.scroll_to(self.cursor(), None, cx);
3284    }
3285
3286    /// The intent of a batch the caller did not label: inserting text at
3287    /// collapsed cursors is typing, anything else stands on its own.
3288    fn typing_intent(&self, edits: &[(Range<usize>, String)], new_text: &str) -> EditIntent {
3289        if !new_text.is_empty()
3290            && !new_text.contains(['\n', '\r'])
3291            && edits.iter().all(|(range, _)| range.is_empty())
3292        {
3293            EditIntent::Typing
3294        } else {
3295            EditIntent::Atomic
3296        }
3297    }
3298
3299    /// Where a cursor stood before an edit made with this intent.
3300    ///
3301    /// Backspace and forward delete expand a collapsed cursor over the text
3302    /// they are about to remove, so the recorded cursor has to collapse back to
3303    /// the side it came from for undo to restore it where the user left it.
3304    fn collapse_for_intent(
3305        intent: EditIntent,
3306        mut selection: CursorSelection,
3307        range: &Range<usize>,
3308    ) -> CursorSelection {
3309        match intent {
3310            EditIntent::Backspace => selection.place_at(range.end, None),
3311            EditIntent::DeleteForward => selection.place_at(range.start, None),
3312            EditIntent::Typing | EditIntent::Atomic => {}
3313        }
3314        selection
3315    }
3316
3317    /// Whether an edit rewrites the character before a collapsed cursor, as the
3318    /// macOS Korean IME does on each keystroke instead of marking text.
3319    fn rewrites_typed_char(
3320        &self,
3321        selection: CursorSelection,
3322        range: &Range<usize>,
3323        old_text: &str,
3324        new_text: &str,
3325    ) -> bool {
3326        !self.silent_replace_text
3327            && selection.is_collapsed()
3328            && selection.cursor_offset() == range.end
3329            && old_text.chars().count() == 1
3330            && !old_text.contains(['\n', '\r'])
3331            && !new_text.contains(['\n', '\r'])
3332    }
3333
3334    fn push_history(
3335        &mut self,
3336        text: &Rope,
3337        range: &Range<usize>,
3338        new_text: &str,
3339        requested_intent: Option<EditIntent>,
3340        selection_before: CursorSelection,
3341        selection_after: Option<CursorSelection>,
3342    ) -> bool {
3343        self.document_revision = self.document_revision.wrapping_add(1);
3344        let token_delta = self.edit_tokens(range, new_text.len());
3345        if self.undo_manager.is_ignoring() {
3346            return false;
3347        }
3348
3349        let range =
3350            text.clip_offset(range.start, Bias::Left)..text.clip_offset(range.end, Bias::Right);
3351        let old_text = text.slice(range.clone()).to_string();
3352        let new_range = range.start..range.start + new_text.len();
3353
3354        let intent = requested_intent.unwrap_or_else(|| {
3355            if range.is_empty()
3356                && old_text.is_empty()
3357                && !new_text.is_empty()
3358                && !new_text.contains(['\n', '\r'])
3359                || self.rewrites_typed_char(selection_before, &range, &old_text, new_text)
3360            {
3361                EditIntent::Typing
3362            } else {
3363                EditIntent::Atomic
3364            }
3365        });
3366
3367        let selection_before = Self::collapse_for_intent(intent, selection_before, &range);
3368        let selection_after =
3369            selection_after.unwrap_or_else(|| (new_range.end..new_range.end).into());
3370
3371        let open_transaction = self.undo_manager.has_open_transaction();
3372        let mut change = Change::new(range, &old_text, new_range, new_text);
3373        change.token_delta = token_delta;
3374        let recorded = self.undo_manager.record_transaction(change, intent);
3375        // A batch records its own cursor sets. This covers a change that is a
3376        // transaction on its own.
3377        if recorded && !open_transaction {
3378            self.undo_manager
3379                .record_selections(vec![selection_before], vec![selection_after]);
3380        }
3381        recorded
3382    }
3383
3384    pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
3385        self.replay_history(true, window, cx);
3386    }
3387
3388    pub(super) fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
3389        self.replay_history(false, window, cx);
3390    }
3391
3392    fn replay_history(&mut self, undo: bool, window: &mut Window, cx: &mut Context<Self>) {
3393        if !self.is_editable() {
3394            return;
3395        }
3396        let replay = if undo {
3397            self.undo_manager.undo()
3398        } else {
3399            self.undo_manager.redo()
3400        };
3401        let Some(replay) = replay else {
3402            return;
3403        };
3404        let token_aware = self.inline_tokens.is_some()
3405            || replay
3406                .changes
3407                .iter()
3408                .any(|change| change.token_delta.is_some());
3409        let emit_events = self.emit_events;
3410        if token_aware {
3411            self.emit_events = false;
3412            self.last_input_edits.clear();
3413        }
3414        self.replaying_history = token_aware;
3415        self.undo_manager.set_ignoring(true);
3416        for change in &replay.changes {
3417            let (range, text) = if undo {
3418                (change.new_range.into(), &change.old_text)
3419            } else {
3420                (change.old_range.into(), &change.new_text)
3421            };
3422            let range_utf16 = self.range_to_utf16(&range);
3423            self.replace_text_in_range_silent(Some(range_utf16), text, window, cx);
3424            self.replay_tokens(&range, text.len(), change.token_delta.as_deref(), undo);
3425        }
3426        self.restore_selections(replay.selections);
3427        self.mode
3428            .restore_auto_closed_pairs(replay.auto_closed_pairs.unwrap_or_default());
3429        if token_aware {
3430            // Token replay bypasses IME normalization; a stale marked range
3431            // would otherwise describe text that no longer exists.
3432            self.ime_marked_range = None;
3433        }
3434        self.undo_manager.set_ignoring(false);
3435        self.replaying_history = false;
3436        self.emit_events = emit_events;
3437        if token_aware {
3438            if emit_events {
3439                self.emit_change(cx);
3440            } else {
3441                self.last_input_edits.clear();
3442            }
3443        }
3444        cx.notify();
3445    }
3446
3447    /// Restore a set of selections captured in a transaction, clamping offsets
3448    /// to the current text length. `None` leaves the current selections as the
3449    /// replay left them.
3450    fn restore_selections(&mut self, selections: Option<Vec<CursorSelection>>) {
3451        let Some(selections) = selections else {
3452            return;
3453        };
3454
3455        let len = self.text.len();
3456        let restored: Vec<CursorSelection> = selections
3457            .into_iter()
3458            .map(|mut sel| {
3459                sel.start = sel.start.min(len);
3460                sel.end = sel.end.min(len);
3461                sel
3462            })
3463            .collect();
3464        self.selections.replace_all(restored);
3465        self.selections.merge_overlapping();
3466    }
3467
3468    /// Get byte offset of the cursor.
3469    ///
3470    /// The offset is the UTF-8 offset.
3471    pub fn cursor(&self) -> usize {
3472        if let Some(ime_marked_range) = &self.ime_marked_range {
3473            return ime_marked_range.end;
3474        }
3475
3476        self.selections.active().cursor_offset()
3477    }
3478
3479    /// Returns the active selection.
3480    pub(super) fn active_selection(&self) -> &CursorSelection {
3481        self.selections.active()
3482    }
3483
3484    /// Returns a mutable reference to the active selection.
3485    pub(super) fn active_selection_mut(&mut self) -> &mut CursorSelection {
3486        self.selections.active_mut()
3487    }
3488
3489    /// Sets the active selection to the given range, keeping its `reversed`
3490    /// and `column_anchor` state untouched.
3491    pub(super) fn set_selection(&mut self, start: usize, end: usize) {
3492        let range = self.normalize_token_range(start..end);
3493        let (start, end) = (range.start, range.end);
3494        let active = self.active_selection_mut();
3495        active.start = start;
3496        active.end = end;
3497    }
3498
3499    /// Collapses the active selection to a cursor at the given offset,
3500    /// clearing `reversed`.
3501    pub(super) fn set_cursor_to(&mut self, offset: usize) {
3502        let offset = self.token_boundary(offset, Bias::Right);
3503        let active = self.active_selection_mut();
3504        active.start = offset;
3505        active.end = offset;
3506        active.reversed = false;
3507    }
3508
3509    /// Visible row range in the last laid-out viewport, `None` before first layout.
3510    pub fn visible_row_range(&self) -> Option<std::ops::Range<usize>> {
3511        self.last_layout.as_ref().map(|l| l.visible_range.clone())
3512    }
3513
3514    /// Current scroll offset of the editor viewport.
3515    pub fn scroll_offset(&self) -> gpui::Point<gpui::Pixels> {
3516        self.scroll_handle.offset()
3517    }
3518
3519    /// Set scroll offset of the editor viewport.
3520    ///
3521    /// The offset will be clamped to the valid range, and applied after the next layout.
3522    pub fn set_scroll_offset(&mut self, offset: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
3523        self.deferred_scroll_offset = Some(offset);
3524        cx.notify();
3525    }
3526
3527    /// Laid-out line height; `None` before first layout.
3528    pub fn line_height(&self) -> Option<gpui::Pixels> {
3529        self.last_layout.as_ref().map(|l| l.line_height)
3530    }
3531
3532    /// Returns the active selection as a byte range into the text.
3533    ///
3534    /// With multiple cursors, this reads only the active selection.
3535    ///
3536    /// The range is empty (`start == end`) when no text is selected; in
3537    /// that case the offset equals `cursor()`. Byte offsets are measured
3538    /// in the underlying rope's byte units.
3539    pub fn selected_range(&self) -> std::ops::Range<usize> {
3540        (*self.selections.active()).into()
3541    }
3542
3543    /// Whether the active selection's caret is at the start of its range.
3544    ///
3545    /// Collapsed selections are not reversed. Use this with [`Self::selected_range`]
3546    /// and [`Self::set_selected_range_with_direction`] to restore a selection.
3547    pub fn is_selection_reversed(&self) -> bool {
3548        let selection = self.selections.active();
3549        !selection.is_empty() && selection.reversed
3550    }
3551
3552    pub fn select_all(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3553        self.undo_manager.break_transaction_coalescing();
3554        self.selections.remove_all_but_active();
3555        self.set_selection(0, self.text.len());
3556        cx.notify();
3557    }
3558
3559    /// Set the selected range using UTF-8 byte offsets, removing additional cursors.
3560    ///
3561    /// Non-empty ranges expand to character boundaries. Empty ranges remain empty and are
3562    /// clipped to the preceding character boundary.
3563    pub fn set_selected_range(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3564        self.set_selected_range_with_direction(range, false, cx);
3565    }
3566
3567    /// Set the selected UTF-8 byte range and the active caret direction.
3568    ///
3569    /// With `reversed`, the caret is at the start and the anchor is at the end.
3570    /// Empty ranges collapse to a caret regardless of direction. Range clipping,
3571    /// token boundaries and removal of additional cursors follow
3572    /// [`Self::set_selected_range`].
3573    pub fn set_selected_range_with_direction(
3574        &mut self,
3575        range: Range<usize>,
3576        reversed: bool,
3577        cx: &mut Context<Self>,
3578    ) {
3579        let range = self.normalize_token_range(range);
3580        let end_bias = if range.start == range.end {
3581            Bias::Left
3582        } else {
3583            Bias::Right
3584        };
3585        let start = self.text.clip_offset(range.start, Bias::Left);
3586        let end = self.text.clip_offset(range.end, end_bias);
3587
3588        let (anchor, head) = if reversed { (end, start) } else { (start, end) };
3589        self.move_to(anchor, None, cx);
3590        self.active_selection_mut().reversed = false;
3591        self.selected_word_range = None;
3592        self.select_to(head, cx);
3593    }
3594
3595    /// Add `range` as an additional selection, keeping the existing ones —
3596    /// the programmatic equivalent of Alt-clicking another cursor into the
3597    /// text.
3598    ///
3599    /// The range is clipped to the text and expanded to character
3600    /// boundaries, like [`Self::set_selected_range`]. A range already
3601    /// covered by a selection adds nothing. Overlapping selections are merged.
3602    /// On a single-line input there is no second cursor to add, so the call
3603    /// replaces the selection instead.
3604    /// The view scrolls to the new selection.
3605    pub fn add_selection(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3606        if !self.is_multi_line() {
3607            self.set_selected_range(range, cx);
3608            return;
3609        }
3610
3611        let range = self.normalize_token_range(range);
3612        let end_bias = if range.start == range.end {
3613            Bias::Left
3614        } else {
3615            Bias::Right
3616        };
3617        let start = self.cursor_boundary(range.start, Bias::Left);
3618        // Match set_selected_range: expand UTF-8 boundaries first, then place
3619        // the selection endpoint without splitting a CRLF newline.
3620        let end = self.cursor_boundary(self.text.clip_offset(range.end, end_bias), Bias::Left);
3621
3622        for sel in self.selections.iter() {
3623            if sel.start <= start && end <= sel.end {
3624                return;
3625            }
3626        }
3627
3628        self.undo_manager.break_transaction_coalescing();
3629        self.selected_word_range = None;
3630        self.pause_blink_cursor(cx);
3631        M::hide_context_menu(self, cx);
3632        M::clear_inline_completion(self, cx);
3633        let id = self.selections.generate_id();
3634        let mut selection = CursorSelection::new(id, start, end);
3635        selection.column_anchor = self.preferred_column_for(end);
3636        self.selections.add(selection);
3637        self.selections.merge_overlapping();
3638        self.scroll_to(end, None, cx);
3639        cx.notify();
3640    }
3641
3642    /// Resolve a mouse position to a byte offset in the text.
3643    ///
3644    /// Also reports the caret's line-end affinity for that offset: `true` when the position
3645    /// landed on the wrap boundary of a non-final visual row, meaning the caret belongs at the
3646    /// end of that row rather than at the start of the next one. Callers that place or extend a
3647    /// selection must pass it on, or clicking past the last glyph of a wrapped row leaves a
3648    /// caret one row below the pointer.
3649    pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> (usize, bool) {
3650        let (offset, line_end_affinity, _) = self.resolve_mouse_position(position);
3651        (offset, line_end_affinity)
3652    }
3653
3654    /// Resolve a mouse position to a text offset, the caret affinity to use for it, and
3655    /// how many columns the pointer sits past the end of that row.
3656    ///
3657    /// Only a columnar selection needs the third value; everywhere else a position past
3658    /// the end of a row means the end of that row, and
3659    /// [`Self::index_for_mouse_position`] is the call to make.
3660    pub(super) fn resolve_mouse_position(&self, position: Point<Pixels>) -> (usize, bool, usize) {
3661        // If the text is empty, always return 0
3662        if self.text.len() == 0 {
3663            return (0, false, 0);
3664        }
3665
3666        let (Some(bounds), Some(last_layout)) =
3667            (self.last_bounds.as_ref(), self.last_layout.as_ref())
3668        else {
3669            return (0, false, 0);
3670        };
3671
3672        let line_height = last_layout.line_height;
3673        let line_number_width = last_layout.line_number_width;
3674
3675        // TIP: About the IBeam cursor
3676        //
3677        // If cursor style is IBeam, the mouse mouse position is in the middle of the cursor (This is special in OS)
3678
3679        // The position is relative to the bounds of the text input
3680        //
3681        // bounds.origin:
3682        //
3683        // - included the input padding.
3684        // - included the scroll offset.
3685        let inner_position = position - bounds.origin - point(line_number_width, px(0.));
3686
3687        let mut y_offset = last_layout.visible_top;
3688        // Position relative to the last line walked, kept for a pointer that ends up
3689        // below every line.
3690        let mut last_line_pos = None;
3691
3692        // Traverse visible buffer lines (compact, no hidden entries)
3693        for (vi, (line_layout, _buffer_line)) in last_layout
3694            .lines
3695            .iter()
3696            .zip(last_layout.visible_buffer_lines.iter())
3697            .enumerate()
3698        {
3699            let line_start_offset = last_layout.visible_line_byte_offsets[vi];
3700
3701            // Calculate line origin for this display row
3702            let line_origin = point(px(0.), y_offset);
3703            let pos = inner_position - line_origin;
3704
3705            // Return offset by use closest_index_for_x if is single line mode.
3706            if self.is_single_line() {
3707                let local_index = line_layout.closest_index_for_x(pos.x, last_layout);
3708                // A single line never wraps, so there is no boundary to disambiguate.
3709                return (
3710                    self.resolve_index(line_start_offset + local_index),
3711                    false,
3712                    0,
3713                );
3714            }
3715
3716            // Check if mouse is in this line's bounds
3717            if let Some((local_index, line_end_affinity)) =
3718                line_layout.closest_index_for_position(pos, last_layout)
3719            {
3720                return (
3721                    self.resolve_index(line_start_offset + local_index),
3722                    line_end_affinity,
3723                    line_layout.columns_past_line_end(pos, last_layout),
3724                );
3725            } else if pos.y < px(0.) {
3726                // Mouse is above this line, return start of this line
3727                return (self.resolve_index(line_start_offset), false, 0);
3728            }
3729
3730            y_offset += line_layout.size(line_height).height;
3731            last_line_pos = Some(pos);
3732        }
3733
3734        // Clamp to the last laid-out row. Returning the end of the whole document
3735        // would select unseen text before drag autoscroll has reached it.
3736        let last_position = last_layout
3737            .lines
3738            .last()
3739            .zip(last_line_pos)
3740            .zip(last_layout.visible_line_byte_offsets.last())
3741            .map(|((line_layout, pos), line_start)| {
3742                let last_row_top = (line_layout.size(line_height).height - line_height).max(px(0.));
3743                let pos = point(pos.x, last_row_top);
3744                (
3745                    self.resolve_index(line_start + line_layout.len()),
3746                    false,
3747                    line_layout.columns_past_line_end(pos, last_layout),
3748                )
3749            });
3750
3751        last_position.unwrap_or((self.text.len(), false, 0))
3752    }
3753
3754    /// Map a display byte index back to a text offset, undoing the mask expansion when the input
3755    /// is masked.
3756    fn resolve_index(&self, index: usize) -> usize {
3757        if self.masked {
3758            self.text.char_index_to_offset(index / MASK_CHAR.len_utf8())
3759        } else {
3760            index.min(self.text.len())
3761        }
3762    }
3763
3764    /// Returns a y offsetted point for the line origin.
3765    /// Select the text from the current cursor position to the given offset.
3766    ///
3767    /// The offset is the UTF-8 offset.
3768    ///
3769    /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
3770    /// Extend a single selection so its moving end lands at `offset`, flipping
3771    /// `reversed` when the ends cross. When a sticky `word_range` is given the
3772    /// selection is kept covering it.
3773    fn extend_selection(
3774        sel: &mut CursorSelection,
3775        offset: usize,
3776        word_range: Option<CursorSelection>,
3777    ) {
3778        if sel.reversed {
3779            sel.start = offset;
3780        } else {
3781            sel.end = offset;
3782        }
3783
3784        if sel.end < sel.start {
3785            sel.reversed = !sel.reversed;
3786            std::mem::swap(&mut sel.start, &mut sel.end);
3787        }
3788
3789        if let Some(word_range) = word_range {
3790            if sel.start > word_range.start {
3791                sel.start = word_range.start;
3792            }
3793            if sel.end < word_range.end {
3794                sel.end = word_range.end;
3795            }
3796        }
3797    }
3798
3799    /// Extend only the active selection to `offset`. Used by mouse drag.
3800    pub(crate) fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
3801        self.select_to_with_affinity(offset, false, cx);
3802    }
3803
3804    /// Like [`Self::select_to`], but also carries the caret's line-end affinity.
3805    ///
3806    /// See [`Self::move_to_with_affinity`] for why the affinity travels with the offset. Note
3807    /// that plain [`Self::select_to`] clears the affinity: every offset it is given came from
3808    /// the text rather than from a visual position, so the caret has no reason to keep sticking
3809    /// to the end of a wrapped row.
3810    pub(crate) fn select_to_with_affinity(
3811        &mut self,
3812        offset: usize,
3813        line_end_affinity: bool,
3814        cx: &mut Context<Self>,
3815    ) {
3816        M::clear_inline_completion(self, cx);
3817
3818        self.cursor_line_end_affinity = line_end_affinity;
3819        let offset = self.cursor_boundary(offset, Bias::Left);
3820        let word_range = self.selected_word_range;
3821        Self::extend_selection(self.active_selection_mut(), offset, word_range);
3822        let range = self.normalize_token_range(self.selected_range());
3823        self.set_selection(range.start, range.end);
3824
3825        if self.active_selection().is_empty() {
3826            self.update_preferred_column();
3827        }
3828        cx.notify()
3829    }
3830
3831    /// Extend every selection to the offset produced by `f`, then merge any
3832    /// selections that now overlap. Used by keyboard selection commands.
3833    fn select_all_cursors_to(
3834        &mut self,
3835        f: impl Fn(&Self, &CursorSelection) -> usize,
3836        cx: &mut Context<Self>,
3837    ) {
3838        self.select_all_cursors_to_with_affinity(|s, sel| (f(s, sel), false), false, cx);
3839    }
3840
3841    /// Extend selections with caret affinity, preserving their column anchors even
3842    /// when vertical movement collapses a selection to a cursor.
3843    fn select_all_cursors_to_with_affinity(
3844        &mut self,
3845        f: impl Fn(&Self, &CursorSelection) -> (usize, bool),
3846        preserve_column: bool,
3847        cx: &mut Context<Self>,
3848    ) {
3849        self.pause_blink_cursor(cx);
3850        self.undo_manager.break_transaction_coalescing();
3851        M::clear_inline_completion(self, cx);
3852
3853        let mut active_affinity = false;
3854        let new_selections: Vec<CursorSelection> = self
3855            .selections
3856            .iter()
3857            .map(|sel| {
3858                let (offset, affinity) = f(self, sel);
3859                if sel.id == self.active_selection().id {
3860                    active_affinity = affinity;
3861                }
3862                let offset = self.cursor_boundary(offset, Bias::Left);
3863                let mut new_sel = *sel;
3864                Self::extend_selection(&mut new_sel, offset, None);
3865                let range = self.normalize_token_range(new_sel.start..new_sel.end);
3866                new_sel.start = range.start;
3867                new_sel.end = range.end;
3868                if !preserve_column {
3869                    new_sel.column_anchor =
3870                        self.preferred_column_for_with_affinity(new_sel.cursor_offset(), affinity);
3871                } else if new_sel.column_anchor.is_none() {
3872                    new_sel.column_anchor = self.preferred_column_for_with_affinity(
3873                        sel.cursor_offset(),
3874                        self.line_end_affinity_for(sel),
3875                    );
3876                }
3877                new_sel
3878            })
3879            .collect();
3880        self.cursor_line_end_affinity = active_affinity;
3881        self.selections.replace_all(new_selections);
3882        self.selections.merge_overlapping();
3883
3884        self.scroll_to(self.cursor(), None, cx);
3885        cx.notify()
3886    }
3887
3888    /// Unselects the currently selected text.
3889    pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3890        self.undo_manager.break_transaction_coalescing();
3891        let offset = self.cursor();
3892        self.set_cursor_to(offset);
3893        cx.notify()
3894    }
3895
3896    #[inline]
3897    pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {
3898        self.text.offset_utf16_to_offset(offset)
3899    }
3900
3901    #[inline]
3902    pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {
3903        self.text.offset_to_offset_utf16(offset)
3904    }
3905
3906    #[inline]
3907    pub(crate) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
3908        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
3909    }
3910
3911    #[inline]
3912    pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
3913        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
3914    }
3915
3916    /// If offset falls on a hidden (folded) line, clamp backward to the end of
3917    /// the fold header line (last visible position before the fold).
3918    fn clamp_offset_to_visible_backward(&self, offset: usize) -> usize {
3919        let line = self.text.offset_to_point(offset).row;
3920        if self.display_map.is_buffer_line_hidden(line) {
3921            for fold in self.display_map.folded_ranges() {
3922                if line > fold.start_line && line <= fold.end_line {
3923                    return self.text.line_end_offset(fold.start_line);
3924                }
3925            }
3926        }
3927        offset
3928    }
3929
3930    /// If offset falls on a hidden (folded) line, clamp forward to the start of
3931    /// the fold end line (first visible position after the fold).
3932    fn clamp_offset_to_visible_forward(&self, offset: usize) -> usize {
3933        let line = self.text.offset_to_point(offset).row;
3934        if self.display_map.is_buffer_line_hidden(line) {
3935            for fold in self.display_map.folded_ranges() {
3936                if line > fold.start_line && line <= fold.end_line {
3937                    return self.text.line_start_offset(fold.end_line);
3938                }
3939            }
3940        }
3941        offset
3942    }
3943
3944    /// Clip a cursor/selection offset without splitting a CRLF newline. This does
3945    /// not alter the rope or its byte/UTF-16 conversion used for exact source APIs.
3946    pub(super) fn cursor_boundary(&self, offset: usize, bias: Bias) -> usize {
3947        let offset = self.token_boundary(self.text.clip_offset(offset, bias), bias);
3948        if offset > 0
3949            && self.text.char_at(offset - 1) == Some('\r')
3950            && self.text.char_at(offset) == Some('\n')
3951        {
3952            if bias == Bias::Left {
3953                offset - 1
3954            } else {
3955                offset + 1
3956            }
3957        } else {
3958            offset
3959        }
3960    }
3961
3962    pub(super) fn previous_boundary(&self, offset: usize) -> usize {
3963        let offset = self.cursor_boundary(offset.saturating_sub(1), Bias::Left);
3964        self.clamp_offset_to_visible_backward(offset)
3965    }
3966
3967    pub(super) fn next_boundary(&self, offset: usize) -> usize {
3968        let offset = self.cursor_boundary(offset.saturating_add(1), Bias::Right);
3969        self.clamp_offset_to_visible_forward(offset)
3970    }
3971
3972    /// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
3973    pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
3974        (self.focus_handle.is_focused(window) || M::is_context_menu_open(self, cx))
3975            && !self.disabled
3976            && self.blink_cursor.read(cx).visible()
3977            && window.is_window_active()
3978    }
3979
3980    fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3981        self.blink_cursor.update(cx, |cursor, cx| {
3982            cursor.start(cx);
3983        });
3984        cx.emit(InputEvent::Focus);
3985    }
3986
3987    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3988        self.revealed_viewport = None;
3989        if M::is_context_menu_open(self, cx) {
3990            return;
3991        }
3992
3993        M::hide_context_menu(self, cx);
3994        self.undo_manager.break_transaction_coalescing();
3995
3996        // NOTE: Do not cancel select, when blur.
3997        // Because maybe user want to copy the selected text by AppMenuBar (will take focus handle).
3998
3999        M::clear_hover_state(self, cx);
4000        self.diagnostic_popover = None;
4001        M::clear_inline_completion(self, cx);
4002        self.dismiss_touch_selection(cx);
4003        self.blink_cursor.update(cx, |cursor, cx| {
4004            cursor.stop(cx);
4005        });
4006        self.clamp_number_value(window, cx);
4007        cx.emit(InputEvent::Blur);
4008        cx.notify();
4009    }
4010
4011    /// Clamp the number value to the `min`/`max` range, used on blur.
4012    ///
4013    /// Out-of-range values are allowed while typing (e.g. `1` is an
4014    /// intermediate state of `15` when min is 10), and clamped on blur.
4015    fn clamp_number_value(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4016        if !self.is_single_line() {
4017            return;
4018        }
4019        if !matches!(self.mask_pattern, MaskPattern::Number { .. }) {
4020            return;
4021        }
4022        if self.number_min.is_none() && self.number_max.is_none() {
4023            return;
4024        }
4025
4026        let Ok(value) = self.unmask_value().parse::<f64>() else {
4027            return;
4028        };
4029
4030        let clamped = match (self.number_min, self.number_max) {
4031            (Some(min), _) if value < min => min,
4032            (_, Some(max)) if value > max => max,
4033            _ => return,
4034        };
4035
4036        // The clamped value must pass the `pattern`/`validate` check,
4037        // otherwise keep the value as is.
4038        let new_text = clamped.to_string();
4039        if !self.is_valid_input(&new_text, cx) {
4040            return;
4041        }
4042
4043        let range = self.range_to_utf16(&(0..self.text.len()));
4044        self.replace_text_in_range_silent(Some(range), &new_text, window, cx);
4045    }
4046
4047    pub(super) fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
4048        self.blink_cursor.update(cx, |cursor, cx| {
4049            cursor.pause(cx);
4050        });
4051    }
4052
4053    pub(super) fn on_drag_move(
4054        &mut self,
4055        event: &MouseMoveEvent,
4056        window: &mut Window,
4057        cx: &mut Context<Self>,
4058    ) {
4059        if self.text.len() == 0 {
4060            return;
4061        }
4062
4063        if self.last_layout.is_none() {
4064            return;
4065        }
4066
4067        if !self.focus_handle.is_focused(window) {
4068            return;
4069        }
4070
4071        if !self.selecting {
4072            return;
4073        }
4074
4075        self.auto_scroll.last_drag_position = Some(event.position);
4076        let (offset, line_end_affinity, columns_past_line_end) =
4077            self.resolve_mouse_position(event.position);
4078        if let Some(start) = self.column_select_start {
4079            let end = ColumnarPoint::new(offset, columns_past_line_end);
4080            self.build_columnar_selection(start, end, cx);
4081        } else {
4082            self.select_to_with_affinity(offset, line_end_affinity, cx);
4083        }
4084
4085        if !self.is_single_line() {
4086            let delta = AutoScroll::compute_delta(event.position.y, self.input_bounds);
4087            // Input's ScrollHandle uses negative-y-is-down; negate the positive-towards-bottom delta.
4088            let scroll_delta = delta.map(|d| -d);
4089            self.auto_scroll.set(scroll_delta, cx, |delta, state, cx| {
4090                let current = state.scroll_handle.offset();
4091                state.update_scroll_offset(Some(point(current.x, current.y + delta)), cx);
4092                if let Some(pos) = state.auto_scroll.last_drag_position {
4093                    let (offset, line_end_affinity, columns_past_line_end) =
4094                        state.resolve_mouse_position(pos);
4095                    if let Some(start) = state.column_select_start {
4096                        let end = ColumnarPoint::new(offset, columns_past_line_end);
4097                        state.build_columnar_selection(start, end, cx);
4098                    } else {
4099                        state.select_to_with_affinity(offset, line_end_affinity, cx);
4100                    }
4101                }
4102            });
4103        }
4104    }
4105
4106    /// Normalize the inserted text before applying it to the input.
4107    ///
4108    /// For number inputs (with [`MaskPattern::Number`]), this converts
4109    /// full-width number characters into their ASCII equivalents,
4110    /// e.g. `12。5` -> `12.5`.
4111    pub(super) fn normalize_input<'a>(&self, new_text: &'a str) -> Cow<'a, str> {
4112        let normalized = if matches!(self.mask_pattern, MaskPattern::Number { .. }) {
4113            normalize_number_input(new_text)
4114        } else {
4115            Cow::Borrowed(new_text)
4116        };
4117
4118        if self.is_single_line() && normalized.contains(['\n', '\r']) {
4119            Cow::Owned(normalized.replace(['\n', '\r'], ""))
4120        } else {
4121            normalized
4122        }
4123    }
4124
4125    pub(crate) fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {
4126        if new_text.is_empty() {
4127            return true;
4128        }
4129
4130        if let Some(validate) = &self.validate {
4131            if !validate(new_text, cx) {
4132                return false;
4133            }
4134        }
4135
4136        if !self.mask_pattern.is_valid(new_text) {
4137            return false;
4138        }
4139
4140        let Some(pattern) = &self.pattern else {
4141            return true;
4142        };
4143
4144        pattern.is_match(new_text)
4145    }
4146
4147    /// Set the mask pattern for formatting the input text.
4148    ///
4149    /// The pattern can contain:
4150    /// - 9: Any digit or dot
4151    /// - A: Any letter
4152    /// - *: Any character
4153    /// - Other characters will be treated as literal mask characters
4154    ///
4155    /// Example: "(999)999-999" for phone numbers
4156    pub fn mask_pattern(mut self, pattern: impl Into<MaskPattern>) -> Self {
4157        self.mask_pattern = pattern.into();
4158        self.mask_pattern_set = true;
4159        if let Some(placeholder) = self.mask_pattern.placeholder() {
4160            self.placeholder = placeholder.into();
4161        }
4162        self
4163    }
4164
4165    pub fn set_mask_pattern(
4166        &mut self,
4167        pattern: impl Into<MaskPattern>,
4168        _: &mut Window,
4169        cx: &mut Context<Self>,
4170    ) {
4171        self.mask_pattern = pattern.into();
4172        self.mask_pattern_set = true;
4173        if let Some(placeholder) = self.mask_pattern.placeholder() {
4174            self.placeholder = placeholder.into();
4175        }
4176        cx.notify();
4177    }
4178
4179    /// Apply the default numeric mask unless the caller explicitly selected a mask.
4180    pub fn ensure_number_mask(&mut self) {
4181        if self.mask_pattern_set {
4182            return;
4183        }
4184        self.mask_pattern = MaskPattern::Number {
4185            separator: None,
4186            fraction: None,
4187        };
4188    }
4189
4190    pub(super) fn set_input_bounds(&mut self, new_bounds: Bounds<Pixels>, cx: &mut Context<Self>) {
4191        let wrap_width_changed = self.input_bounds.size.width != new_bounds.size.width;
4192        self.input_bounds = new_bounds;
4193
4194        // Update display_map wrap_width if changed.
4195        if let Some(last_layout) = self.last_layout.as_ref() {
4196            if wrap_width_changed {
4197                let wrap_width = if !self.soft_wrap {
4198                    // None to disable wrapping (will use Pixels::MAX)
4199                    None
4200                } else {
4201                    last_layout.wrap_width
4202                };
4203
4204                self.display_map.on_layout_changed(wrap_width, cx);
4205                if self.is_multi_line() {
4206                    self.mode.update_auto_grow(&self.display_map);
4207                }
4208                cx.notify();
4209            }
4210        }
4211    }
4212
4213    /// Return the active selection's text, borrowed out of the [`Rope`]
4214    /// the state owns.
4215    ///
4216    /// See [`Self::selected_value`] when an owned string is wanted.
4217    pub fn selected_text(&self) -> RopeSlice<'_> {
4218        let range_utf16 = self.range_to_utf16(&self.selected_range());
4219        let range = self.range_from_utf16(&range_utf16);
4220        self.text.slice(range)
4221    }
4222
4223    /// Return the rendered bounds for a UTF-8 byte range in the current input contents.
4224    ///
4225    /// Returns `None` when the requested range is not currently laid out or visible.
4226    pub fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
4227        let Some(last_layout) = self.last_layout.as_ref() else {
4228            return None;
4229        };
4230
4231        let Some(last_bounds) = self.last_bounds else {
4232            return None;
4233        };
4234
4235        let (_, _, start_pos) = self.line_and_position_for_offset(range.start);
4236        let (_, _, end_pos) = self.line_and_position_for_offset(range.end);
4237
4238        let Some(start_pos) = start_pos else {
4239            return None;
4240        };
4241        let Some(end_pos) = end_pos else {
4242            return None;
4243        };
4244
4245        Some(Bounds::from_corners(
4246            last_bounds.origin + start_pos,
4247            last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),
4248        ))
4249    }
4250
4251    /// Replace text in range in silent.
4252    ///
4253    /// This will not trigger any UI interaction, such as auto-completion.
4254    pub(crate) fn replace_text_in_range_silent(
4255        &mut self,
4256        range_utf16: Option<Range<usize>>,
4257        new_text: &str,
4258        window: &mut Window,
4259        cx: &mut Context<Self>,
4260    ) {
4261        self.silent_replace_text = true;
4262        self.replace_text_in_range(range_utf16, new_text, window, cx);
4263        self.silent_replace_text = false;
4264    }
4265
4266    /// Apply a batch of edits as one atomic history transaction.
4267    ///
4268    /// `edits` are `(byte range in the current pre-edit document, replacement)`
4269    /// pairs.
4270    pub(crate) fn replace_text_in_ranges(
4271        &mut self,
4272        edits: &[(Range<usize>, String)],
4273        window: &mut Window,
4274        cx: &mut Context<Self>,
4275    ) {
4276        if !self.is_editable() || edits.is_empty() {
4277            return;
4278        }
4279
4280        // Every edit invalidates provider responses for the previous document,
4281        // including deletion and indentation which do not trigger completion.
4282        M::hide_context_menu(self, cx);
4283        M::clear_inline_completion(self, cx);
4284
4285        // Sort descending by start so applying front-of-vec first edits the
4286        // highest offsets first, leaving lower offsets unchanged.
4287        let mut sorted: Vec<(Range<usize>, &str)> = edits
4288            .iter()
4289            .map(|(range, text)| (self.normalize_token_range(range.clone()), text.as_str()))
4290            .collect();
4291        sorted.sort_by_key(|edit| std::cmp::Reverse(edit.0.start));
4292        if !self.token_spans().is_empty() {
4293            let mut merged: Vec<(Range<usize>, &str)> = Vec::with_capacity(sorted.len());
4294            for (range, text) in sorted {
4295                if let Some(last) = merged
4296                    .last_mut()
4297                    .filter(|last| range.end > last.0.start || range == last.0)
4298                {
4299                    last.0.start = range.start.min(last.0.start);
4300                    last.0.end = range.end.max(last.0.end);
4301                } else {
4302                    merged.push((range, text));
4303                }
4304            }
4305            sorted = merged;
4306        }
4307
4308        #[cfg(debug_assertions)]
4309        for pair in sorted.windows(2) {
4310            debug_assert!(
4311                pair[1].0.end <= pair[0].0.start,
4312                "replace_text_in_ranges requires disjoint ranges"
4313            );
4314        }
4315
4316        // Wrap multiple edits in one explicit transaction so they undo as a
4317        // unit. A single edit records directly, which keeps it eligible for
4318        // the undo manager's typing coalescing.
4319        let requested_intent = self.undo_manager.take_pending_intent();
4320        let selection_before = *self.active_selection();
4321        let original_selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
4322        // Snapshot the cursors before applying, so undo can restore them. A
4323        // delete has already expanded them over the text it removes, so they
4324        // collapse back to where the user left them.
4325        let selections_before: Vec<CursorSelection> = self
4326            .selections
4327            .iter()
4328            .map(|selection| {
4329                Self::collapse_for_intent(
4330                    requested_intent.unwrap_or(EditIntent::Atomic),
4331                    *selection,
4332                    &(selection.start..selection.end),
4333                )
4334            })
4335            .collect();
4336        let group = sorted.len() > 1;
4337        if group {
4338            self.undo_manager.begin_transaction();
4339        }
4340
4341        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
4342        let mut recorded = false;
4343        // Several edits reach the highlighter as one batch, so it reparses
4344        // once for the change instead of once per edit.
4345        let mut highlighter_edits = Vec::new();
4346        // Token-aware history replay applies several replacements but emits one
4347        // `Change` at the end, so its edits accumulate instead of being
4348        // discarded with each silent replacement.
4349        let accumulate_replay_edits = self.replaying_history && !self.emit_events;
4350        if !accumulate_replay_edits {
4351            self.last_input_edits.clear();
4352        }
4353        for (range, new_text) in &sorted {
4354            let old_text = self.text.clone();
4355            self.mode.adjust_auto_closed_pair(range, new_text.len());
4356            self.text.replace(range.clone(), new_text);
4357
4358            M::adjust_annotations(self, range, new_text.len());
4359            recorded |= self.push_history(
4360                &old_text,
4361                range,
4362                new_text,
4363                requested_intent,
4364                selection_before,
4365                None,
4366            );
4367
4368            // Incremental, single-range updates must run per edit.
4369            self.display_map
4370                .adjust_folds_for_edit(&old_text, range, new_text);
4371            self.display_map
4372                .on_text_changed(&self.text, range, &Rope::from(*new_text), cx);
4373
4374            let edit = super::mode::replacement_input_edit(&old_text, &self.text, range, new_text);
4375            if self.emit_events || self.replaying_history {
4376                self.last_input_edits.push((edit, self.text.clone()));
4377            }
4378
4379            if group {
4380                if self.is_code_editor() {
4381                    highlighter_edits.push((edit, self.text.clone()));
4382                }
4383            } else {
4384                self.mode.update_highlighter(
4385                    super::mode::HighlighterUpdate {
4386                        selected_range: range,
4387                        old_text: &old_text,
4388                        new_text: &self.text,
4389                        change_text: new_text,
4390                        force: true,
4391                        input_edit: Some(edit),
4392                    },
4393                    window,
4394                    cx,
4395                );
4396
4397                self.update_fold_candidates_incremental(range, new_text);
4398            }
4399        }
4400
4401        if group {
4402            self.mode
4403                .update_highlighter_batch::<M>(&highlighter_edits, window, cx);
4404
4405            // Fold candidates are read from the reparsed document, so each
4406            // edit is located where it ended up once every edit was applied.
4407            // `sorted` runs from the highest offset down.
4408            let mut shift = 0isize;
4409            for (range, new_text) in sorted.iter().rev() {
4410                let start = (range.start as isize + shift) as usize;
4411                self.update_fold_candidates_incremental(&(start..start), new_text);
4412                shift += new_text.len() as isize - range.len() as isize;
4413            }
4414
4415            self.undo_manager.commit_transaction();
4416        }
4417
4418        // One observable update per batch instead of one per edit.
4419        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4420            diagnostics.reset(&self.text)
4421        }
4422        M::refresh_language_features(self, window, cx);
4423        self.update_search(cx);
4424
4425        // Compute the resulting cursors.
4426        // One collapsed cursor per edit, at the end of its inserted text.
4427        let mut ascending: Vec<(Range<usize>, &str)> = sorted.clone();
4428        ascending.sort_by_key(|edit| edit.0.start);
4429        let text_len = self.text.len();
4430        let mut delta: isize = 0;
4431        let mut edit_results = Vec::with_capacity(ascending.len());
4432        for (range, new_text) in &ascending {
4433            let offset = ((range.start as isize + delta) as usize + new_text.len()).min(text_len);
4434            edit_results.push((range.clone(), offset));
4435            delta += new_text.len() as isize - (range.end as isize - range.start as isize);
4436        }
4437
4438        let mut used = vec![false; edit_results.len()];
4439        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(ascending.len());
4440        for selection in original_selections {
4441            if let Ok(index) = edit_results
4442                .binary_search_by_key(&(selection.start, selection.end), |(range, _)| {
4443                    (range.start, range.end)
4444                })
4445                && !used[index]
4446            {
4447                let offset = edit_results[index].1;
4448                used[index] = true;
4449                let mut selection = selection;
4450                selection.place_at(offset, None);
4451                new_selections.push(selection);
4452            }
4453        }
4454        for (index, (_, offset)) in edit_results.into_iter().enumerate() {
4455            if !used[index] {
4456                new_selections.push(CursorSelection::new(
4457                    self.selections.generate_id(),
4458                    offset,
4459                    offset,
4460                ));
4461            }
4462        }
4463        self.selections.replace_all(new_selections);
4464        self.selections.merge_overlapping();
4465
4466        // Record the cursor snapshots for undo/redo restore.
4467        let selections_after: Vec<CursorSelection> = self.selections.iter().copied().collect();
4468        if recorded {
4469            self.undo_manager.record_auto_closed_pairs(
4470                auto_closed_pairs_before,
4471                self.mode.auto_closed_pairs().clone(),
4472            );
4473            self.undo_manager
4474                .record_selections(selections_before, selections_after);
4475        }
4476
4477        self.ime_marked_range.take();
4478        self.update_preferred_column();
4479        self.dismiss_touch_selection(cx);
4480        if self.is_multi_line() {
4481            self.mode.update_auto_grow(&self.display_map);
4482        }
4483        if self.emit_events {
4484            self.emit_change(cx);
4485        }
4486        cx.notify();
4487    }
4488
4489    /// Update fold candidates from tree-sitter syntax tree (full extraction).
4490    /// Used only on initial load or language changes.
4491    fn update_fold_candidates(&mut self) {
4492        if !self.mode.is_folding() {
4493            return;
4494        }
4495
4496        let Some(highlighter_rc) = self.mode.highlighter() else {
4497            return;
4498        };
4499
4500        let highlighter = highlighter_rc.borrow();
4501        let Some(highlighter) = highlighter.as_ref() else {
4502            return;
4503        };
4504
4505        let fold_ranges = highlighter.fold_ranges(&self.text);
4506        self.display_map.set_fold_candidates(fold_ranges);
4507    }
4508
4509    /// Incrementally update fold candidates after a text edit.
4510    /// Only traverses the edited region of the syntax tree instead of the full tree.
4511    fn update_fold_candidates_incremental(&mut self, edit_range: &Range<usize>, new_text: &str) {
4512        if !self.mode.is_folding() {
4513            return;
4514        }
4515
4516        let Some(highlighter_rc) = self.mode.highlighter() else {
4517            return;
4518        };
4519
4520        let highlighter = highlighter_rc.borrow();
4521        let Some(highlighter) = highlighter.as_ref() else {
4522            return;
4523        };
4524
4525        // The new byte range in the updated text after the edit
4526        let new_end = edit_range.start + new_text.len();
4527        self.display_map.update_fold_candidates_for_edit(
4528            |range, text| highlighter.fold_ranges_for_edit(range, text),
4529            edit_range.start..new_end,
4530            &self.text,
4531        );
4532    }
4533}
4534
4535impl<M: InputModeKind> EntityInputHandler for InputBaseState<M> {
4536    fn text_for_range(
4537        &mut self,
4538        range_utf16: Range<usize>,
4539        adjusted_range: &mut Option<Range<usize>>,
4540        _window: &mut Window,
4541        _cx: &mut Context<Self>,
4542    ) -> Option<String> {
4543        let range = self.range_from_utf16(&range_utf16);
4544        adjusted_range.replace(self.range_to_utf16(&range));
4545        Some(self.text.slice(range).to_string())
4546    }
4547
4548    fn selected_text_range(
4549        &mut self,
4550        _ignore_disabled_input: bool,
4551        _window: &mut Window,
4552        _cx: &mut Context<Self>,
4553    ) -> Option<UTF16Selection> {
4554        Some(UTF16Selection {
4555            range: self.range_to_utf16(&self.selected_range()),
4556            reversed: self.active_selection().reversed,
4557        })
4558    }
4559
4560    /// Select the given range, as the platform's own text UI asks when it
4561    /// drives the selection: a native selection handle, or the macOS IME.
4562    ///
4563    /// The end that moved becomes the cursor, so dragging the start handle
4564    /// leaves a reversed selection and keeps the moving end in view. Text and
4565    /// undo history are left alone.
4566    fn set_selected_text_range(
4567        &mut self,
4568        range_utf16: Range<usize>,
4569        _window: &mut Window,
4570        cx: &mut Context<Self>,
4571    ) {
4572        let previous = self.selected_range();
4573        let was_reversed = self.active_selection().reversed;
4574        let range = self.range_from_utf16(&range_utf16);
4575        let range = range.start.min(range.end)..range.start.max(range.end);
4576        self.set_selected_range(range, cx);
4577
4578        let range = self.selected_range();
4579        let reversed = if range == previous {
4580            was_reversed
4581        } else if !range.is_empty() && range.end == previous.start {
4582            true
4583        } else if !range.is_empty() && range.start == previous.end {
4584            false
4585        } else {
4586            !range.is_empty() && range.start != previous.start && range.end == previous.end
4587        };
4588        self.active_selection_mut().reversed = reversed;
4589        self.scroll_to(self.cursor(), None, cx);
4590        cx.notify();
4591    }
4592
4593    fn marked_text_range(
4594        &self,
4595        _window: &mut Window,
4596        _cx: &mut Context<Self>,
4597    ) -> Option<Range<usize>> {
4598        self.ime_marked_range
4599            .map(|range| self.range_to_utf16(&range.into()))
4600    }
4601
4602    fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
4603        self.ime_marked_range = None;
4604        self.undo_manager.commit_transaction();
4605    }
4606
4607    /// Replace text in range.
4608    ///
4609    /// - If the new text is invalid, it will not be replaced.
4610    /// - If `range_utf16` is not provided, the current selected range will be used.
4611    fn replace_text_in_range(
4612        &mut self,
4613        range_utf16: Option<Range<usize>>,
4614        new_text: &str,
4615        window: &mut Window,
4616        cx: &mut Context<Self>,
4617    ) {
4618        let requested_intent = self.undo_manager.take_pending_intent();
4619        if !self.is_editable() {
4620            return;
4621        }
4622        let selection_before = *self.active_selection();
4623        // Committing a composition ends the transaction it opened, whether or
4624        // not the platform follows up with `unmark_text`.
4625        let ends_composition = self.ime_marked_range.is_some();
4626
4627        self.pause_blink_cursor(cx);
4628
4629        // NOTE: The normalization keeps the UTF-16 length, but may change the
4630        // UTF-8 byte length, so all the byte-offset calculations below must
4631        // use the normalized text.
4632        let new_text = if self.replaying_history {
4633            Cow::Borrowed(new_text)
4634        } else {
4635            self.normalize_input(new_text)
4636        };
4637        let new_text: &str = &new_text;
4638
4639        let range = range_utf16
4640            .as_ref()
4641            .map(|range_utf16| self.range_from_utf16(range_utf16))
4642            .or(self.ime_marked_range.map(|range| {
4643                let range = self.range_to_utf16(&(range.start..range.end));
4644                self.range_from_utf16(&range)
4645            }))
4646            .unwrap_or(self.selected_range());
4647        let range = self.normalize_token_range(range);
4648
4649        // Skip-over as a pure cursor move: a typed closer that already follows
4650        // the cursor moves past it without touching text or history, so Undo
4651        // never exposes a transient duplicate. Only for interactive single
4652        // keystrokes (current selection, no IME mark, not silent replays).
4653        // `take_pending_intent` above already consumed any request.
4654        if range.is_empty()
4655            && range.start == self.cursor()
4656            && self.ime_marked_range.is_none()
4657            && !self.silent_replace_text
4658            && self.is_code_editor()
4659            && self.selections.is_single()
4660            && self.active_selection().is_empty()
4661        {
4662            if let Some(target) = self.skip_over_target(new_text) {
4663                self.set_cursor_to(target);
4664                self.update_preferred_column();
4665                cx.notify();
4666                return;
4667            }
4668        }
4669
4670        if self.is_multi_line() {
4671            let multi_cursor = range_utf16.is_none()
4672                && self.ime_marked_range.is_none()
4673                && !self.selections.is_single();
4674            if multi_cursor {
4675                self.selections.merge_overlapping();
4676                let mut edits: Vec<(Range<usize>, String)> = self
4677                    .selections
4678                    .iter()
4679                    .map(|sel| (sel.start..sel.end, new_text.to_string()))
4680                    .collect();
4681                edits.sort_by_key(|(range, _)| range.start);
4682                // One keystroke across several cursors is one batch, committed
4683                // with the intent of the keystroke so a run of them coalesces
4684                // into one undo just like single-cursor typing.
4685                let intent =
4686                    requested_intent.unwrap_or_else(|| self.typing_intent(&edits, new_text));
4687                self.undo_manager.begin_transaction_with(intent);
4688                self.undo_manager.set_pending_intent(intent);
4689                self.replace_text_in_ranges(&edits, window, cx);
4690                self.undo_manager.commit_transaction();
4691            } else {
4692                if range_utf16.is_some() {
4693                    self.selections.remove_all_but_active();
4694                }
4695                if let Some(intent) = requested_intent {
4696                    self.undo_manager.set_pending_intent(intent);
4697                }
4698                if let Some((open_len, closer)) = self.auto_close_target(&range, new_text) {
4699                    // One edit keeps the pair atomic even at undo coalescing limits.
4700                    let replacement = format!("{new_text}{closer}");
4701                    self.replace_text_in_ranges(&[(range.clone(), replacement)], window, cx);
4702                    let cursor = range.start + new_text.len();
4703                    self.mode.track_auto_closed_pair(
4704                        cursor - open_len..cursor,
4705                        cursor..cursor + closer.len(),
4706                    );
4707                    self.undo_manager
4708                        .record_auto_closed_pairs_after(self.mode.auto_closed_pairs().clone());
4709                    self.set_cursor_to(cursor);
4710                    self.update_preferred_column();
4711                    self.undo_manager.record_selections(
4712                        vec![selection_before],
4713                        self.selections.iter().copied().collect(),
4714                    );
4715                } else {
4716                    self.replace_text_in_ranges(
4717                        &[(range.clone(), new_text.to_string())],
4718                        window,
4719                        cx,
4720                    );
4721                }
4722            }
4723            if ends_composition {
4724                self.undo_manager.commit_transaction();
4725            }
4726
4727            if !self.silent_replace_text {
4728                M::on_text_typed(self, &range, new_text, window, cx);
4729            }
4730            return;
4731        }
4732
4733        // Single-line path
4734        let old_text = self.text.clone();
4735        // Token-aware history replay applies several replacements but emits one
4736        // `Change` at the end, so its edits accumulate instead of being
4737        // discarded with each silent replacement.
4738        let accumulate_replay_edits = self.replaying_history && !self.emit_events;
4739        if !accumulate_replay_edits {
4740            self.last_input_edits.clear();
4741        }
4742        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4743        self.text.replace(range.clone(), new_text);
4744
4745        let mut new_offset = (range.start + new_text.len()).min(self.text.len());
4746
4747        // True if the mask has changed the text, e.g. regrouping the
4748        // separators or completing a leading dot.
4749        let mut mask_changed = false;
4750
4751        if self.is_single_line() {
4752            let pending_text = self.text.to_string();
4753            // Check if the new text is valid.
4754            //
4755            // Only reject the edit if the old text was valid, to avoid
4756            // trapping a pre-existing invalid text (e.g. a `default_value`
4757            // that does not conform), the user can still edit to fix it.
4758            if !self.replaying_history
4759                && !self.validated_token_edit
4760                && !self.is_valid_input(&pending_text, cx)
4761                && self.is_valid_input(&old_text.to_string(), cx)
4762            {
4763                self.text = old_text;
4764                return;
4765            }
4766
4767            if !self.replaying_history && !self.mask_pattern.is_none() {
4768                let mask_text = self.mask_pattern.mask(&pending_text);
4769                mask_changed = mask_text.as_str() != pending_text;
4770                self.text = Rope::from(mask_text.as_str());
4771                let new_text_len =
4772                    (new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
4773                new_offset = (range.start + new_text_len).min(mask_text.len());
4774            }
4775        }
4776
4777        if mask_changed {
4778            // Masking rewrites the whole document, so ranges recorded against
4779            // the old text no longer point at anything.
4780            M::reset_annotations(self);
4781        } else {
4782            M::adjust_annotations(self, &range, new_text.len());
4783        }
4784        if mask_changed {
4785            // A segment-based history entry no longer matches the masked
4786            // document, record a whole-document change instead, so that
4787            // undo/redo can restore the text exactly.
4788            self.push_history(
4789                &old_text,
4790                &(0..old_text.len()),
4791                &self.text.to_string(),
4792                Some(EditIntent::Atomic),
4793                selection_before,
4794                Some((new_offset..new_offset).into()),
4795            );
4796        } else {
4797            self.push_history(
4798                &old_text,
4799                &range,
4800                &new_text,
4801                requested_intent,
4802                selection_before,
4803                None,
4804            );
4805        }
4806        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4807            diagnostics.reset(&self.text)
4808        }
4809        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4810        self.display_map
4811            .adjust_folds_for_edit(&old_text, &range, new_text);
4812        self.display_map
4813            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4814
4815        let edit = if mask_changed {
4816            Self::full_input_edit(&old_text, &self.text)
4817        } else {
4818            super::mode::replacement_input_edit(&old_text, &self.text, &range, new_text)
4819        };
4820        if self.emit_events || self.replaying_history {
4821            self.last_input_edits.push((edit, self.text.clone()));
4822        }
4823
4824        self.mode.update_highlighter::<M>(
4825            super::mode::HighlighterUpdate {
4826                selected_range: &range,
4827                old_text: &old_text,
4828                new_text: &self.text,
4829                change_text: &new_text,
4830                force: true,
4831                input_edit: Some(edit),
4832            },
4833            window,
4834            cx,
4835        );
4836
4837        self.update_fold_candidates_incremental(&range, new_text);
4838        M::refresh_language_features(self, window, cx);
4839        self.set_cursor_to(new_offset);
4840        self.ime_marked_range.take();
4841        // A commit ends the IME composition: macOS delivers `insertText:` for
4842        // the confirmed candidate without a following `unmarkText`, so close
4843        // the transaction here. Leaving it open would keep merging every later
4844        // edit into the same change, which then carries the text and selection
4845        // of the first composition.
4846        if ends_composition {
4847            self.undo_manager
4848                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4849            self.undo_manager.commit_transaction();
4850        }
4851        self.update_preferred_column();
4852        self.update_search(cx);
4853        self.dismiss_touch_selection(cx);
4854        if self.is_multi_line() {
4855            self.mode.update_auto_grow(&self.display_map);
4856        }
4857        if !self.silent_replace_text {
4858            M::on_text_typed(self, &range, &new_text, window, cx);
4859        }
4860        if self.emit_events {
4861            self.emit_change(cx);
4862        }
4863        cx.notify();
4864    }
4865
4866    /// Mark text is the IME temporary insert on typing.
4867    fn replace_and_mark_text_in_range(
4868        &mut self,
4869        range_utf16: Option<Range<usize>>,
4870        new_text: &str,
4871        new_selected_range_utf16: Option<Range<usize>>,
4872        window: &mut Window,
4873        cx: &mut Context<Self>,
4874    ) {
4875        let requested_intent = self.undo_manager.take_pending_intent();
4876        if !self.is_editable() {
4877            return;
4878        }
4879        let selection_before = *self.active_selection();
4880
4881        let starts_composition = self.ime_marked_range.is_none();
4882        if starts_composition {
4883            // Even a canceled preedit separates the typing gestures on either
4884            // side; its no-op transaction must not reconnect those gestures.
4885            self.undo_manager.break_transaction_coalescing();
4886            self.undo_manager.begin_transaction();
4887        }
4888
4889        // Collapse any extra cursors so we never leave stale secondary cursors behind.
4890        self.selections.remove_all_but_active();
4891
4892        M::reset_language_features(self);
4893
4894        // See the same NOTE in `replace_text_in_range`.
4895        let new_text = if self.replaying_history {
4896            Cow::Borrowed(new_text)
4897        } else {
4898            self.normalize_input(new_text)
4899        };
4900        let new_text: &str = &new_text;
4901
4902        let range = range_utf16
4903            .as_ref()
4904            .map(|range_utf16| self.range_from_utf16(range_utf16))
4905            .or(self.ime_marked_range.map(|range| {
4906                let range = self.range_to_utf16(&(range.start..range.end));
4907                self.range_from_utf16(&range)
4908            }))
4909            .unwrap_or(self.selected_range());
4910        let range = self.normalize_token_range(range);
4911
4912        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
4913        let old_text = self.text.clone();
4914        // Marked text does not emit `InputEvent::Change`, so no edit belongs to
4915        // the most recent change after it.
4916        self.last_input_edits.clear();
4917        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4918        self.text.replace(range.clone(), new_text);
4919
4920        if self.is_single_line() {
4921            let pending_text = self.text.to_string();
4922            // See the same NOTE in `replace_text_in_range`.
4923            if !self.replaying_history
4924                && !self.validated_token_edit
4925                && !self.is_valid_input(&pending_text, cx)
4926                && self.is_valid_input(&old_text.to_string(), cx)
4927            {
4928                self.text = old_text;
4929                if starts_composition {
4930                    self.undo_manager.commit_transaction();
4931                }
4932                return;
4933            }
4934        }
4935
4936        if self.ime_original_text.is_none() {
4937            self.ime_original_text = Some(old_text.clone());
4938        }
4939        M::adjust_annotations(self, &range, new_text.len());
4940        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4941            diagnostics.reset(&self.text)
4942        }
4943        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4944        self.display_map
4945            .adjust_folds_for_edit(&old_text, &range, new_text);
4946        self.display_map
4947            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4948
4949        self.mode.update_highlighter::<M>(
4950            super::mode::HighlighterUpdate {
4951                selected_range: &range,
4952                old_text: &old_text,
4953                new_text: &self.text,
4954                change_text: &new_text,
4955                force: true,
4956                input_edit: None,
4957            },
4958            window,
4959            cx,
4960        );
4961
4962        self.update_fold_candidates_incremental(&range, new_text);
4963        M::refresh_language_features(self, window, cx);
4964        if new_text.is_empty() {
4965            // Cancel selection, when cancel IME input.
4966            self.set_cursor_to(range.start);
4967            self.ime_marked_range = None;
4968        } else {
4969            self.ime_marked_range = Some((range.start..range.start + new_text.len()).into());
4970            let new_range = new_selected_range_utf16
4971                .as_ref()
4972                .map(|range_utf16| {
4973                    let new_text = Rope::from(new_text);
4974                    range.start + new_text.offset_utf16_to_offset(range_utf16.start)
4975                        ..range.start + new_text.offset_utf16_to_offset(range_utf16.end)
4976                })
4977                .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
4978            self.set_selection(new_range.start, new_range.end);
4979        }
4980        if self.is_multi_line() {
4981            self.mode.update_auto_grow(&self.display_map);
4982        }
4983        if self.push_history(
4984            &old_text,
4985            &range,
4986            new_text,
4987            requested_intent,
4988            selection_before,
4989            Some(*self.active_selection()),
4990        ) {
4991            self.undo_manager
4992                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4993            self.undo_manager.record_auto_closed_pairs(
4994                auto_closed_pairs_before,
4995                self.mode.auto_closed_pairs().clone(),
4996            );
4997        }
4998        if new_text.is_empty() {
4999            self.undo_manager.commit_transaction();
5000        }
5001        cx.notify();
5002    }
5003
5004    /// Used to position IME candidates.
5005    fn bounds_for_range(
5006        &mut self,
5007        range_utf16: Range<usize>,
5008        bounds: Bounds<Pixels>,
5009        _window: &mut Window,
5010        _cx: &mut Context<Self>,
5011    ) -> Option<Bounds<Pixels>> {
5012        let last_layout = self.last_layout.as_ref()?;
5013        let line_height = last_layout.line_height;
5014        let line_number_width = last_layout.line_number_width;
5015        let range = self.range_from_utf16(&range_utf16);
5016
5017        let mut start_origin = None;
5018        let mut end_origin = None;
5019        let line_number_origin = point(line_number_width, px(0.));
5020        let mut y_offset = last_layout.visible_top;
5021
5022        for (vi, line) in last_layout.lines.iter().enumerate() {
5023            if start_origin.is_some() && end_origin.is_some() {
5024                break;
5025            }
5026
5027            let index_offset = last_layout.visible_line_byte_offsets[vi];
5028
5029            if start_origin.is_none() {
5030                if let Some(p) = line.position_for_index(
5031                    range.start.saturating_sub(index_offset),
5032                    last_layout,
5033                    false,
5034                ) {
5035                    start_origin = Some(p + point(px(0.), y_offset));
5036                }
5037            }
5038
5039            if end_origin.is_none() {
5040                if let Some(p) = line.position_for_index(
5041                    range.end.saturating_sub(index_offset),
5042                    last_layout,
5043                    false,
5044                ) {
5045                    end_origin = Some(p + point(px(0.), y_offset));
5046                }
5047            }
5048
5049            y_offset += line.size(line_height).height;
5050        }
5051
5052        let start_origin = start_origin.or_else(|| {
5053            let offset = self.last_cursor.or(Some(self.cursor()))?;
5054            let (_, _, origin) = self.line_and_position_for_offset(offset);
5055            origin.map(|origin| origin - line_number_origin)
5056        })?;
5057        let mut end_origin = end_origin.unwrap_or(start_origin);
5058        // Ensure at same line.
5059        end_origin.y = start_origin.y;
5060
5061        Some(Bounds::from_corners(
5062            bounds.origin + line_number_origin + start_origin,
5063            // + line_height for show IME panel under the cursor line.
5064            bounds.origin + line_number_origin + point(end_origin.x, end_origin.y + line_height),
5065        ))
5066    }
5067
5068    fn character_index_for_point(
5069        &mut self,
5070        point: gpui::Point<Pixels>,
5071        _window: &mut Window,
5072        _cx: &mut Context<Self>,
5073    ) -> Option<usize> {
5074        let last_layout = self.last_layout.as_ref()?;
5075        let line_point = self.last_bounds?.localize(&point)?;
5076
5077        for (vi, line) in last_layout.lines.iter().enumerate() {
5078            let offset = last_layout.visible_line_byte_offsets[vi];
5079            if let Some(utf8_index) = line.index_for_position(line_point, last_layout) {
5080                return Some(self.offset_to_utf16(offset + utf8_index));
5081            }
5082        }
5083
5084        None
5085    }
5086
5087    fn text_input_configuration(
5088        &mut self,
5089        _window: &mut Window,
5090        _cx: &mut Context<Self>,
5091    ) -> TextInputConfiguration {
5092        InputBaseState::text_input_configuration(self)
5093    }
5094}
5095
5096impl<M: InputModeKind> Focusable for InputBaseState<M> {
5097    fn focus_handle(&self, _cx: &App) -> FocusHandle {
5098        self.focus_handle.clone()
5099    }
5100}
5101
5102impl<M: InputModeKind> Render for InputBaseState<M> {
5103    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
5104        // Before anything reads it: the element resolves this style during
5105        // layout and paint, and both happen after this call in the same frame.
5106        self.editor_style = self
5107            .projected_editor_style
5108            .resolved(&crate::Theme::global(cx).tokens);
5109        let entity = cx.entity();
5110        if self._pending_update {
5111            self.mode.update_highlighter::<M>(
5112                super::mode::HighlighterUpdate {
5113                    selected_range: &(0..0),
5114                    old_text: &self.text,
5115                    new_text: &self.text,
5116                    change_text: "",
5117                    force: false,
5118                    input_edit: None,
5119                },
5120                window,
5121                cx,
5122            );
5123
5124            self.update_fold_candidates();
5125            M::refresh_language_features(self, window, cx);
5126            self._pending_update = false;
5127        }
5128
5129        let element = div()
5130            .id(EDITOR_ELEMENT_ID)
5131            .key_context(CONTEXT)
5132            .when_some(self.accessibility.as_ref(), |this, accessibility| {
5133                this.when_some(accessibility.role, |this, role| this.role(role))
5134                    .when_some(accessibility.label.clone(), |this, label| {
5135                        this.aria_label(label)
5136                    })
5137                    .when_some(accessibility.placeholder.clone(), |this, placeholder| {
5138                        this.aria_placeholder(placeholder)
5139                    })
5140                    .when_some(accessibility.accessibility_id.clone(), |this, id| {
5141                        this.accessibility_id(id)
5142                    })
5143                    .when(
5144                        accessibility.value_exposed
5145                            && !self.masked
5146                            && (window.is_a11y_active() || cfg!(feature = "test-support")),
5147                        |this| this.aria_value(self.text.to_string()),
5148                    )
5149                    .on_a11y_action(gpui::AccessibleAction::Focus, {
5150                        let entity = entity.clone();
5151                        move |_, window, cx| {
5152                            entity.update(cx, |state, cx| {
5153                                state.handle_accessibility_focus(window, cx)
5154                            });
5155                        }
5156                    })
5157                    .when(self.is_editable(), |this| {
5158                        this.on_a11y_action(gpui::AccessibleAction::SetValue, {
5159                            let entity = entity.clone();
5160                            move |data, window, cx| {
5161                                entity.update(cx, |state, cx| {
5162                                    state.handle_accessibility_set_value(data, window, cx)
5163                                });
5164                            }
5165                        })
5166                    })
5167            })
5168            .when(self.is_editable(), |this| {
5169                this.on_action(window.listener_for(&entity, InputBaseState::backspace))
5170                    .on_action(window.listener_for(&entity, InputBaseState::delete))
5171                    .on_action(
5172                        window.listener_for(&entity, InputBaseState::delete_to_beginning_of_line),
5173                    )
5174                    .on_action(window.listener_for(&entity, InputBaseState::delete_to_end_of_line))
5175                    .on_action(window.listener_for(&entity, InputBaseState::delete_previous_word))
5176                    .on_action(window.listener_for(&entity, InputBaseState::delete_next_word))
5177                    .on_action(window.listener_for(&entity, InputBaseState::enter))
5178                    .on_action(window.listener_for(&entity, InputBaseState::escape))
5179                    .on_action(window.listener_for(&entity, InputBaseState::paste))
5180                    .on_action(window.listener_for(&entity, InputBaseState::cut))
5181                    .on_action(window.listener_for(&entity, InputBaseState::undo))
5182                    .on_action(window.listener_for(&entity, InputBaseState::redo))
5183                    .when(self.is_multi_line(), |this| {
5184                        this.on_action(window.listener_for(&entity, InputBaseState::indent_inline))
5185                            .on_action(window.listener_for(&entity, InputBaseState::outdent_inline))
5186                            .on_action(window.listener_for(&entity, InputBaseState::indent_block))
5187                            .on_action(window.listener_for(&entity, InputBaseState::outdent_block))
5188                    })
5189            })
5190            .on_action(window.listener_for(&entity, InputBaseState::left))
5191            .on_action(window.listener_for(&entity, InputBaseState::right))
5192            .on_action(window.listener_for(&entity, InputBaseState::select_left))
5193            .on_action(window.listener_for(&entity, InputBaseState::select_right))
5194            .when(self.is_multi_line(), |this| {
5195                this.on_action(window.listener_for(&entity, InputBaseState::up))
5196                    .on_action(window.listener_for(&entity, InputBaseState::down))
5197                    .on_action(window.listener_for(&entity, InputBaseState::select_up))
5198                    .on_action(window.listener_for(&entity, InputBaseState::select_down))
5199                    .on_action(window.listener_for(&entity, InputBaseState::page_up))
5200                    .on_action(window.listener_for(&entity, InputBaseState::page_down))
5201                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_above))
5202                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_below))
5203            })
5204            .on_action(window.listener_for(&entity, InputBaseState::on_action_select_all))
5205            .on_action(window.listener_for(&entity, InputBaseState::select_to_start_of_line))
5206            .on_action(window.listener_for(&entity, InputBaseState::select_to_end_of_line))
5207            .on_action(window.listener_for(&entity, InputBaseState::select_to_previous_word))
5208            .on_action(window.listener_for(&entity, InputBaseState::select_to_next_word))
5209            .on_action(window.listener_for(&entity, InputBaseState::home))
5210            .on_action(window.listener_for(&entity, InputBaseState::end))
5211            .on_action(window.listener_for(&entity, InputBaseState::move_to_start))
5212            .on_action(window.listener_for(&entity, InputBaseState::move_to_end))
5213            .on_action(window.listener_for(&entity, InputBaseState::move_to_previous_word))
5214            .on_action(window.listener_for(&entity, InputBaseState::move_to_next_word))
5215            .on_action(window.listener_for(&entity, InputBaseState::select_to_start))
5216            .on_action(window.listener_for(&entity, InputBaseState::select_to_end))
5217            .on_action(window.listener_for(&entity, InputBaseState::show_character_palette))
5218            .on_action({
5219                let entity = entity.clone();
5220                move |_: &ActivateToken, window, cx| {
5221                    let state = entity.read(cx);
5222                    let activation = state
5223                        .token_spans()
5224                        .iter()
5225                        .find(|span| span.range() == state.selected_range())
5226                        .and_then(|span| {
5227                            state.range_to_bounds(&span.range()).and_then(|bounds| {
5228                                state.token_activation(
5229                                    span.range().start,
5230                                    bounds,
5231                                    gpui::ClickEvent::Keyboard(gpui::KeyboardClickEvent {
5232                                        bounds,
5233                                        ..Default::default()
5234                                    }),
5235                                )
5236                            })
5237                        });
5238                    if let Some((listener, event)) = activation {
5239                        listener(&event, window, cx);
5240                    }
5241                }
5242            })
5243            .on_action(window.listener_for(&entity, InputBaseState::copy))
5244            .on_action(window.listener_for(&entity, InputBaseState::on_action_search))
5245            .on_action(window.listener_for(&entity, InputBaseState::on_action_replace))
5246            .on_mouse_down(
5247                MouseButton::Left,
5248                window.listener_for(&entity, InputBaseState::on_mouse_down),
5249            )
5250            .on_mouse_down(
5251                MouseButton::Right,
5252                window.listener_for(&entity, InputBaseState::on_mouse_down),
5253            )
5254            .on_mouse_up(
5255                MouseButton::Left,
5256                window.listener_for(&entity, InputBaseState::on_mouse_up),
5257            )
5258            .on_mouse_up(
5259                MouseButton::Right,
5260                window.listener_for(&entity, InputBaseState::on_mouse_up),
5261            )
5262            .on_mouse_move(window.listener_for(&entity, InputBaseState::on_mouse_move))
5263            .on_scroll(window.listener_for(&entity, InputBaseState::on_scroll_wheel))
5264            .when(self.is_multi_line() && !self.disabled, |this| {
5265                this.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
5266            })
5267            .when(!self.disabled, |this| {
5268                if self.is_multi_line() && window.modifiers().alt {
5269                    this.cursor_crosshair()
5270                } else {
5271                    this.cursor_text()
5272                }
5273            })
5274            .flex_1()
5275            // A single line fills the frame and sits at its vertical center,
5276            // so the frame needs no layout of its own to hold it. It is never
5277            // shorter than its line, though: this root clips (overflow-x
5278            // hidden clips both axes), and a frame whose padding leaves less
5279            // than a line, like Component's Input, would cut off descenders.
5280            .map(|this| {
5281                if self.is_multi_line() {
5282                    this.h_full()
5283                } else {
5284                    this.min_h_full().flex().items_center()
5285                }
5286            })
5287            .flex_grow_1()
5288            .overflow_x_hidden()
5289            .when(self.is_multi_line(), |this| {
5290                this.pt(self.editor_paddings.top)
5291                    .pr(self.editor_paddings.right)
5292                    .pb(self.editor_paddings.bottom)
5293                    .pl(self.editor_paddings.left)
5294            })
5295            .child(TextElement::new(entity.clone()).placeholder(self.placeholder.clone()))
5296            .when(self.shows_scrollbar(), |this| {
5297                this.child(EditorScrollbar::new(entity.clone()))
5298            });
5299
5300        // Actions only one mode handles are registered by that mode, where
5301        // `Self` is concrete enough to name its own entity type.
5302        M::register_actions(element, &entity, window)
5303            .test_support()
5304            .track_focus(&self.focus_handle)
5305    }
5306}
5307
5308#[cfg(test)]
5309mod tests {
5310    use super::*;
5311
5312    #[gpui::test]
5313    fn accessibility_actions_recheck_live_editability(cx: &mut TestAppContext) {
5314        cx.update(crate::init);
5315        let (state, cx) = cx.add_window_view(|window, cx| {
5316            crate::input::InputState::new(window, cx).default_value("initial")
5317        });
5318        let changes = Rc::new(Cell::new(0));
5319        let observed = changes.clone();
5320        let _subscription = cx.update(|_, cx| {
5321            cx.subscribe(&state, move |_, event: &InputEvent, _| {
5322                if matches!(event, InputEvent::Change) {
5323                    observed.set(observed.get() + 1);
5324                }
5325            })
5326        });
5327        cx.update(|window, cx| {
5328            state.update(cx, |state, cx| {
5329                state.handle_accessibility_set_value(None, window, cx);
5330                state.handle_accessibility_focus(window, cx);
5331                assert!(state.focus_handle.is_focused(window));
5332            });
5333            window.draw(cx).clear(cx);
5334        });
5335        assert_eq!(state.read_with(cx, |state, _| state.value()), "initial");
5336        assert_eq!(changes.get(), 0);
5337
5338        let action = gpui::accesskit::ActionData::Value("updated🦀".into());
5339        cx.update(|window, cx| {
5340            state.update(cx, |state, cx| {
5341                state.handle_accessibility_set_value(Some(&action), window, cx)
5342            });
5343        });
5344        assert_eq!(state.read_with(cx, |state, _| state.value()), "updated🦀");
5345        assert_eq!(changes.get(), 1);
5346        for disabled in [false, true] {
5347            cx.update(|window, cx| {
5348                window.blur(cx);
5349                state.update(cx, |state, cx| {
5350                    state.set_disabled(disabled, cx);
5351                    state.set_readonly(!disabled, cx);
5352                    state.handle_accessibility_focus(window, cx);
5353                    assert_eq!(state.focus_handle.is_focused(window), !disabled);
5354                    let rejected = gpui::accesskit::ActionData::Value("rejected".into());
5355                    state.handle_accessibility_set_value(Some(&rejected), window, cx);
5356                });
5357            });
5358            assert_eq!(state.read_with(cx, |state, _| state.value()), "updated🦀");
5359            assert_eq!(changes.get(), 1);
5360        }
5361        cx.update(|window, cx| {
5362            state.update(cx, |state, cx| {
5363                state.set_disabled(false, cx);
5364                state.set_readonly(false, cx);
5365                state.handle_accessibility_focus(window, cx);
5366            });
5367            window.draw(cx).clear(cx);
5368            window.dispatch_action(Box::new(Undo), cx);
5369        });
5370        assert_eq!(state.read_with(cx, |state, _| state.value()), "initial");
5371        cx.update(|window, cx| {
5372            state.update(cx, |state, cx| {
5373                state.set_masked(true, window, cx);
5374                state.handle_accessibility_set_value(Some(&action), window, cx);
5375            });
5376        });
5377        assert_eq!(state.read_with(cx, |state, _| state.value()), "updated🦀");
5378    }
5379
5380    #[gpui::test]
5381    fn accessibility_set_value_preserves_exact_editor_text(cx: &mut TestAppContext) {
5382        cx.update(crate::init);
5383        let (editor, cx) = cx.add_window_view(|window, cx| {
5384            crate::input::EditorState::new(window, cx).language("rust")
5385        });
5386        let action = gpui::accesskit::ActionData::Value("(".into());
5387        cx.update(|window, cx| {
5388            editor.update(cx, |editor, cx| {
5389                editor.handle_accessibility_set_value(Some(&action), window, cx)
5390            });
5391        });
5392        assert_eq!(editor.read_with(cx, |editor, _| editor.value()), "(");
5393    }
5394
5395    #[gpui::test]
5396    fn revealing_a_password_keeps_text_assistance_disabled(cx: &mut TestAppContext) {
5397        cx.update(crate::init);
5398        cx.open_window(size(px(400.), px(100.)), |window, cx| {
5399            let state = cx.new(|cx| crate::input::InputState::new(window, cx).masked(true));
5400            let hidden = state.read(cx).text_input_configuration();
5401            assert!(!hidden.autocorrect && !hidden.suggestions);
5402            assert_eq!(hidden.autocapitalize, Autocapitalize::None);
5403
5404            // The component's reveal button uses this same state transition.
5405            state.update(cx, |state, cx| state.toggle_masked(window, cx));
5406            assert_eq!(state.read(cx).text_input_configuration(), hidden);
5407            state.update(cx, |state, cx| state.toggle_masked(window, cx));
5408            assert_eq!(state.read(cx).text_input_configuration(), hidden);
5409            gpui::EmptyView
5410        });
5411    }
5412
5413    #[gpui::test]
5414    fn text_input_configuration_follows_the_input(cx: &mut TestAppContext) {
5415        use crate::input::{InputState, TextareaState};
5416
5417        cx.update(crate::init);
5418        let mut states = None;
5419        cx.open_window(size(px(400.), px(100.)), |window, cx| {
5420            states = Some((
5421                cx.new(|cx| InputState::new(window, cx)),
5422                cx.new(|cx| InputState::new(window, cx).masked(true)),
5423                cx.new(|cx| TextareaState::new(window, cx)),
5424                cx.new(|cx| {
5425                    InputState::new(window, cx)
5426                        .input_action(TextInputAction::Search)
5427                        .autocorrect(true)
5428                        .autocapitalize(Autocapitalize::Words)
5429                }),
5430            ));
5431            gpui::EmptyView
5432        });
5433        let (plain, masked, textarea, search) = states.unwrap();
5434
5435        cx.read(|cx| {
5436            assert_eq!(
5437                plain.read(cx).text_input_configuration(),
5438                TextInputConfiguration {
5439                    input_action: TextInputAction::Done,
5440                    ..Default::default()
5441                }
5442            );
5443            assert_eq!(
5444                masked.read(cx).text_input_configuration(),
5445                TextInputConfiguration {
5446                    input_action: TextInputAction::Done,
5447                    ..Default::default()
5448                }
5449            );
5450            assert_eq!(
5451                textarea.read(cx).text_input_configuration(),
5452                TextInputConfiguration {
5453                    input_action: TextInputAction::Enter,
5454                    ..Default::default()
5455                }
5456            );
5457            let search = search.read(cx).text_input_configuration();
5458            assert_eq!(search.input_action, TextInputAction::Search);
5459            assert_eq!(search.autocapitalize, Autocapitalize::Words);
5460            assert!(search.autocorrect);
5461            assert!(search.suggestions);
5462        });
5463    }
5464
5465    #[gpui::test]
5466    fn test_multicursor_unicode_completion_uses_post_edit_start(cx: &mut TestAppContext) {
5467        use crate::input::{CompletionProvider, EditorState};
5468        use gpui::Task;
5469        use lsp_types::{CompletionContext, CompletionResponse};
5470        use std::cell::RefCell;
5471
5472        #[derive(Default)]
5473        struct Provider {
5474            starts: RefCell<Vec<usize>>,
5475            queries: RefCell<Vec<(usize, String)>>,
5476        }
5477
5478        impl CompletionProvider for Provider {
5479            fn is_completion_trigger(&self, offset: usize, _: &str, _: &mut App) -> bool {
5480                self.starts.borrow_mut().push(offset);
5481                true
5482            }
5483
5484            fn completions(
5485                &self,
5486                _: &Rope,
5487                offset: usize,
5488                trigger: CompletionContext,
5489                _: &mut Window,
5490                _: &mut App,
5491            ) -> Task<anyhow::Result<CompletionResponse>> {
5492                self.queries
5493                    .borrow_mut()
5494                    .push((offset, trigger.trigger_character.unwrap()));
5495                Task::ready(Ok(CompletionResponse::Array(vec![])))
5496            }
5497        }
5498
5499        cx.update(crate::init);
5500        let mut editor = None;
5501        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5502            editor = Some(cx.new(|cx| EditorState::new(window, cx).default_value("aa")));
5503            gpui::EmptyView
5504        });
5505        let editor = editor.unwrap();
5506        let provider = Rc::new(Provider::default());
5507        window
5508            .update(cx, |_, window, cx| {
5509                editor.update(cx, |state, cx| {
5510                    state.extras.lsp.completion_provider = Some(provider.clone());
5511                    state
5512                        .extras
5513                        .context_menu_content
5514                        .completion
5515                        .trigger_start_offset = Some(0);
5516                    state.extras.context_menu_content.completion.query = "aa".into();
5517                    state.set_cursor_to(1);
5518                    state.add_cursor_at(0, cx);
5519
5520                    state.replace_text_in_range(None, "中", window, cx);
5521
5522                    assert_eq!(state.value(), "中a中a");
5523                    assert_eq!(provider.starts.borrow().as_slice(), &[4]);
5524                    assert_eq!(provider.queries.borrow().as_slice(), &[(7, "中".into())]);
5525                });
5526            })
5527            .unwrap();
5528    }
5529
5530    #[gpui::test]
5531    fn selected_range_restores_unicode_direction_without_splitting_characters(
5532        cx: &mut TestAppContext,
5533    ) {
5534        use crate::input::EditorState;
5535        cx.update(crate::init);
5536        cx.open_window(size(px(400.), px(100.)), |window, cx| {
5537            let _state = cx.new(|cx| {
5538                let mut state = EditorState::new(window, cx).default_value("中😀a");
5539                state.set_selected_range_with_direction(3..8, true, cx);
5540                let selected = state.selected_text_range(false, window, cx).unwrap();
5541                assert_eq!(selected.range, 1..4);
5542                assert!(selected.reversed);
5543                assert_eq!(state.selected_range(), 3..8);
5544                assert!(state.is_selection_reversed());
5545                let range = state.selected_range();
5546                let reversed = state.is_selection_reversed();
5547                state.set_selected_range_with_direction(range, reversed, cx);
5548                assert!(state.is_selection_reversed());
5549                state.set_selected_range(3..8, cx);
5550                let selected = state.selected_text_range(false, window, cx).unwrap();
5551                assert_eq!(selected.range, 1..4);
5552                assert!(!selected.reversed);
5553                assert!(!state.is_selection_reversed());
5554                // A reversed Rust Range remains empty for existing callers.
5555                state.set_selected_range(Range { start: 8, end: 3 }, cx);
5556                assert_eq!(state.selected_range(), 8..8);
5557                assert!(!state.is_selection_reversed());
5558                state.set_selected_range_with_direction(4..4, true, cx);
5559                assert!(!state.is_selection_reversed());
5560                assert_eq!(
5561                    state.selected_text_range(false, window, cx).unwrap().range,
5562                    1..1
5563                );
5564                state
5565            });
5566            gpui::EmptyView
5567        });
5568    }
5569
5570    #[gpui::test]
5571    fn test_inline_token_wrap_and_size_refresh(cx: &mut TestAppContext) {
5572        use crate::input::{InlineToken, InlineTokenPresentation};
5573        let width = Rc::new(Cell::new(4000.));
5574        let render_width = width.clone();
5575        let view = InputView::build_textarea(cx, |state| state.rows(4).default_value("@a@b"));
5576        view.window_handle
5577            .update(cx, |_, window, cx| {
5578                view.input.update(cx, |state, cx| {
5579                    state
5580                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5581                        .unwrap();
5582                    state
5583                        .replace_range_with_token(2..4, InlineToken::new("b", "@b"), window, cx)
5584                        .unwrap();
5585                    state.set_token_presentation(
5586                        InlineTokenPresentation::default()
5587                            .token(move |_, _, _| div().w(px(render_width.get()))),
5588                    );
5589                });
5590            })
5591            .unwrap();
5592        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5593        visual.update(|window, cx| window.draw(cx).clear(cx));
5594        view.input.read_with(&visual, |state, _| {
5595            assert_eq!(state.display_map.wrap_row_count(), 2);
5596            let first = state.range_to_bounds(&(0..2)).unwrap();
5597            let second = state.range_to_bounds(&(2..4)).unwrap();
5598            assert!(second.origin.y > first.origin.y);
5599            assert_eq!(
5600                state.last_layout.as_ref().unwrap().lines[0].wrapped_lines[0].len,
5601                2
5602            );
5603        });
5604        width.set(100.);
5605        view.input.update(&mut visual, |_, cx| cx.notify());
5606        visual.update(|window, cx| window.draw(cx).clear(cx));
5607        view.input.read_with(&visual, |state, _| {
5608            assert_eq!(state.display_map.wrap_row_count(), 1)
5609        });
5610    }
5611
5612    #[gpui::test]
5613    fn test_inline_token_geometry_and_reentrant_activation(cx: &mut TestAppContext) {
5614        use crate::input::{InlineToken, InlineTokenPresentation};
5615        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5616        let target = view.input.clone();
5617        view.window_handle
5618            .update(cx, |_, window, cx| {
5619                view.input.update(cx, |state, cx| {
5620                    state
5621                        .replace_range_with_token(
5622                            7..13,
5623                            InlineToken::new("a", "@alice"),
5624                            window,
5625                            cx,
5626                        )
5627                        .unwrap();
5628                    state.set_token_presentation(
5629                        InlineTokenPresentation::default()
5630                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5631                            .on_token_click(move |_, window, cx| {
5632                                target.update(cx, |state, cx| {
5633                                    state.set_value("activated", window, cx)
5634                                })
5635                            }),
5636                    );
5637                    state.focus(window, cx);
5638                    state.set_selected_range(7..13, cx);
5639                });
5640            })
5641            .unwrap();
5642        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5643        visual.update(|window, cx| window.draw(cx).clear(cx));
5644        visual.update(|window, cx| {
5645            let state = view.input.read(cx);
5646            let bounds = state.range_to_bounds(&(7..13)).unwrap();
5647            assert!((bounds.size.width - px(100.)).abs() < px(1.), "{bounds:?}");
5648            let (left, _) = state.index_for_mouse_position(bounds.origin + point(px(10.), px(5.)));
5649            let (right, _) = state.index_for_mouse_position(bounds.origin + point(px(90.), px(5.)));
5650            assert_eq!((left, right), (7, 13));
5651            window.dispatch_action(Box::new(ActivateToken), cx);
5652        });
5653        visual.run_until_parked();
5654        view.input.read_with(&visual, |state, _| {
5655            assert_eq!(state.value().as_ref(), "activated")
5656        });
5657    }
5658
5659    #[gpui::test]
5660    fn test_inline_token_click_selects_it(cx: &mut TestAppContext) {
5661        use crate::input::InlineToken;
5662        cx.update(crate::init);
5663        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5664        view.window_handle
5665            .update(cx, |_, window, cx| {
5666                view.input.update(cx, |state, cx| {
5667                    state
5668                        .replace_range_with_token(
5669                            7..13,
5670                            InlineToken::new("a", "@alice"),
5671                            window,
5672                            cx,
5673                        )
5674                        .unwrap();
5675                    state.set_selected_range(0..0, cx);
5676                });
5677            })
5678            .unwrap();
5679        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5680        visual.update(|window, cx| window.draw(cx).clear(cx));
5681        let bounds = view
5682            .input
5683            .read_with(&visual, |state, _| state.range_to_bounds(&(7..13)).unwrap());
5684        visual.simulate_click(bounds.center(), gpui::Modifiers::default());
5685        visual.run_until_parked();
5686        view.input.read_with(&visual, |state, _| {
5687            assert_eq!(state.selected_range(), 7..13, "a click selects the token");
5688        });
5689    }
5690
5691    #[gpui::test]
5692    fn test_inline_token_hover_reports_presence_without_selecting(cx: &mut TestAppContext) {
5693        use crate::input::{InlineToken, InlineTokenPresentation};
5694        use gpui::{Bounds, point, px, size};
5695        cx.update(crate::init);
5696        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5697        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
5698        view.window_handle
5699            .update(cx, |_, window, cx| {
5700                view.input.update(cx, |state, cx| {
5701                    state
5702                        .replace_range_with_token(
5703                            7..13,
5704                            InlineToken::new("a", "@alice"),
5705                            window,
5706                            cx,
5707                        )
5708                        .unwrap();
5709                    state.set_token_presentation(
5710                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5711                    );
5712                    state.set_selected_range(0..0, cx);
5713                });
5714            })
5715            .unwrap();
5716        view.window_handle
5717            .update(cx, |_, _, cx| {
5718                view.input.update(cx, |state, _| {
5719                    let (_, entered) = state
5720                        .token_hover(7, bounds, true, None)
5721                        .expect("hover enters an enabled token");
5722                    assert!(entered.is_hovered());
5723                    assert_eq!(entered.token().id().as_ref(), "a");
5724                    assert_eq!(entered.range(), 7..13);
5725                    assert_eq!(entered.bounds(), bounds);
5726                    let (_, left) = state
5727                        .token_hover(7, bounds, false, None)
5728                        .expect("hover leaves the same token");
5729                    assert!(!left.is_hovered());
5730                    assert_eq!(state.selected_range(), 0..0, "hover never selects");
5731                    assert!(state.token_hover(99, bounds, true, None).is_none());
5732                });
5733            })
5734            .unwrap();
5735
5736        // Disabled tokens suppress hover, matching click; readonly allows it.
5737        view.window_handle
5738            .update(cx, |_, _, cx| {
5739                view.input.update(cx, |state, cx| {
5740                    state.set_disabled(true, cx);
5741                });
5742            })
5743            .unwrap();
5744        view.window_handle
5745            .update(cx, |_, _, cx| {
5746                view.input.update(cx, |state, _| {
5747                    assert!(state.token_hover(7, bounds, true, None).is_none())
5748                })
5749            })
5750            .unwrap();
5751        view.window_handle
5752            .update(cx, |_, _, cx| {
5753                view.input.update(cx, |state, cx| {
5754                    state.set_disabled(false, cx);
5755                    state.set_readonly(true, cx);
5756                });
5757            })
5758            .unwrap();
5759        view.window_handle
5760            .update(cx, |_, _, cx| {
5761                view.input.update(cx, |state, _| {
5762                    assert!(state.token_hover(7, bounds, true, None).is_some())
5763                })
5764            })
5765            .unwrap();
5766
5767        // Without a hover listener there is no hover activation.
5768        view.window_handle
5769            .update(cx, |_, _, cx| {
5770                view.input.update(cx, |state, _| {
5771                    state.set_token_presentation(InlineTokenPresentation::default());
5772                });
5773            })
5774            .unwrap();
5775        view.window_handle
5776            .update(cx, |_, _, cx| {
5777                view.input.update(cx, |state, _| {
5778                    assert!(state.token_hover(7, bounds, true, None).is_none())
5779                })
5780            })
5781            .unwrap();
5782    }
5783
5784    #[gpui::test]
5785    fn test_inline_token_hover_exit_delivered_when_token_removed(cx: &mut TestAppContext) {
5786        use crate::input::{InlineToken, InlineTokenPresentation};
5787        use std::cell::RefCell;
5788        use std::rc::Rc;
5789        cx.update(crate::init);
5790        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5791        let events: Rc<RefCell<Vec<(SharedString, bool)>>> = Rc::new(RefCell::new(Vec::new()));
5792        let sink = events.clone();
5793        view.window_handle
5794            .update(cx, |_, window, cx| {
5795                view.input.update(cx, |state, cx| {
5796                    state
5797                        .replace_range_with_token(
5798                            7..13,
5799                            InlineToken::new("a", "@alice"),
5800                            window,
5801                            cx,
5802                        )
5803                        .unwrap();
5804                    state.set_token_presentation(
5805                        InlineTokenPresentation::default()
5806                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5807                            .on_token_hover(move |event, _, _| {
5808                                sink.borrow_mut()
5809                                    .push((event.token().id().clone(), event.is_hovered()));
5810                            }),
5811                    );
5812                });
5813            })
5814            .unwrap();
5815        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5816        visual.update(|window, cx| window.draw(cx).clear(cx));
5817        let known_bounds = view.input.read_with(&visual, |state, _| {
5818            state
5819                .token_bounds
5820                .get(&7)
5821                .copied()
5822                .expect("placed token bounds")
5823        });
5824        visual.simulate_mouse_move(known_bounds.center(), None, gpui::Modifiers::default());
5825        visual.run_until_parked();
5826        visual.update(|window, cx| window.draw(cx).clear(cx));
5827        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5828
5829        // Remove the token while the pointer is still over its stale row: the
5830        // exit must fire even though the token element that owns the hover
5831        // mask is gone.
5832        view.window_handle
5833            .update(cx, |_, window, cx| {
5834                view.input
5835                    .update(cx, |state, cx| state.set_value("no tokens", window, cx));
5836            })
5837            .unwrap();
5838        visual.run_until_parked();
5839        visual.update(|window, cx| window.draw(cx).clear(cx));
5840        assert_eq!(
5841            events.borrow().as_slice(),
5842            &[("a".into(), true), ("a".into(), false)]
5843        );
5844    }
5845
5846    #[gpui::test]
5847    fn test_inline_token_hover_bounds_stay_single_row_at_wrap_boundary(cx: &mut TestAppContext) {
5848        use crate::input::{InlineToken, InlineTokenPresentation};
5849        cx.update(crate::init);
5850        let view = InputView::build_textarea(cx, |state| {
5851            state
5852                .rows(4)
5853                .default_value("aaaa bbbb cccc dddd @alice eeee ffff")
5854        });
5855        view.window_handle
5856            .update(cx, |_, window, cx| {
5857                view.input.update(cx, |state, cx| {
5858                    state
5859                        .replace_range_with_token(
5860                            20..26,
5861                            InlineToken::new("a", "@alice"),
5862                            window,
5863                            cx,
5864                        )
5865                        .unwrap();
5866                    state.set_token_presentation(
5867                        InlineTokenPresentation::default()
5868                            .token(|_, _, _| div().w(px(60.)).h(px(20.))),
5869                    );
5870                });
5871            })
5872            .unwrap();
5873        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5874        visual.update(|window, cx| window.draw(cx).clear(cx));
5875        let (placed, ranged, line_height) = view.input.read_with(&visual, |state, _| {
5876            (
5877                state
5878                    .token_bounds
5879                    .get(&20)
5880                    .copied()
5881                    .expect("placed token bounds"),
5882                state.range_to_bounds(&(20..26)),
5883                state.last_layout.as_ref().unwrap().line_height,
5884            )
5885        });
5886        // The laid-out element always occupies exactly one row of height, even
5887        // when the range-extent path resolves the same end boundary onto the
5888        // next visual row at a soft-wrap edge.
5889        assert!(placed.size.height <= line_height + px(1.), "{placed:?}");
5890        assert!(
5891            placed.size.width <= px(61.) && placed.size.width > px(0.),
5892            "{placed:?}"
5893        );
5894        if let Some(ranged) = ranged {
5895            assert!(
5896                ranged.size.height >= placed.size.height - px(1.),
5897                "{ranged:?} vs {placed:?}"
5898            );
5899        }
5900    }
5901
5902    #[gpui::test]
5903    fn test_inline_token_hover_same_offset_replacement_exits_predecessor(cx: &mut TestAppContext) {
5904        use crate::input::{InlineToken, InlineTokenPresentation};
5905        use std::{cell::RefCell, rc::Rc};
5906        cx.update(crate::init);
5907        let view = InputView::build(cx, |state| state.default_value("@alice!"));
5908        let events = Rc::new(RefCell::new(Vec::new()));
5909        let sink = events.clone();
5910        view.window_handle
5911            .update(cx, |_, window, cx| {
5912                view.input.update(cx, |state, cx| {
5913                    state
5914                        .replace_range_with_token(0..6, InlineToken::new("a", "@alice"), window, cx)
5915                        .unwrap();
5916                    state.set_token_presentation(
5917                        InlineTokenPresentation::default()
5918                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5919                            .on_token_hover(move |event, _, _| {
5920                                sink.borrow_mut()
5921                                    .push((event.token().id().clone(), event.is_hovered()));
5922                            }),
5923                    );
5924                });
5925            })
5926            .unwrap();
5927        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5928        visual.update(|window, cx| window.draw(cx).clear(cx));
5929        let bounds = view
5930            .input
5931            .read_with(&visual, |state, _| state.token_bounds[&0]);
5932        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5933        visual.run_until_parked();
5934        visual.update(|window, cx| window.draw(cx).clear(cx));
5935        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5936
5937        view.window_handle
5938            .update(cx, |_, window, cx| {
5939                view.input.update(cx, |state, cx| {
5940                    state
5941                        .replace_range_with_token(0..6, InlineToken::new("b", "@alice"), window, cx)
5942                        .unwrap();
5943                });
5944            })
5945            .unwrap();
5946        visual.run_until_parked();
5947        visual.update(|window, cx| window.draw(cx).clear(cx));
5948        visual.simulate_mouse_move(
5949            bounds.center() + gpui::point(px(1.), px(0.)),
5950            None,
5951            gpui::Modifiers::default(),
5952        );
5953        visual.run_until_parked();
5954        visual.update(|window, cx| window.draw(cx).clear(cx));
5955        assert_eq!(
5956            events.borrow().as_slice(),
5957            &[("a".into(), true), ("a".into(), false), ("b".into(), true)]
5958        );
5959        // Disabling keeps the token mounted; re-enabling must reset GPUI's
5960        // retained hover state even with a stationary pointer.
5961        view.input
5962            .update(&mut visual, |state, cx| state.set_disabled(true, cx));
5963        visual.update(|window, cx| window.draw(cx).clear(cx));
5964        visual.run_until_parked();
5965        assert_eq!(events.borrow().last(), Some(&("b".into(), false)));
5966        view.input
5967            .update(&mut visual, |state, cx| state.set_disabled(false, cx));
5968        visual.update(|window, cx| window.draw(cx).clear(cx));
5969        visual.run_until_parked();
5970        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5971        visual.run_until_parked();
5972        assert_eq!(
5973            events.borrow().as_slice(),
5974            &[
5975                ("a".into(), true),
5976                ("a".into(), false),
5977                ("b".into(), true),
5978                ("b".into(), false),
5979                ("b".into(), true)
5980            ]
5981        );
5982        visual.simulate_mouse_move(
5983            bounds.bottom_right() + gpui::point(px(10.), px(10.)),
5984            None,
5985            gpui::Modifiers::default(),
5986        );
5987        visual.run_until_parked();
5988        assert_eq!(
5989            events.borrow().as_slice(),
5990            &[
5991                ("a".into(), true),
5992                ("a".into(), false),
5993                ("b".into(), true),
5994                ("b".into(), false),
5995                ("b".into(), true),
5996                ("b".into(), false)
5997            ]
5998        );
5999    }
6000
6001    #[gpui::test]
6002    fn test_inline_token_hover_older_exit_keeps_newer_snapshot(cx: &mut TestAppContext) {
6003        use crate::input::{InlineToken, InlineTokenPresentation};
6004        use gpui::{Bounds, point, px, size};
6005        cx.update(crate::init);
6006        let view = InputView::build(cx, |state| state.default_value("@a @b"));
6007        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
6008        view.window_handle
6009            .update(cx, |_, window, cx| {
6010                view.input.update(cx, |state, cx| {
6011                    state
6012                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
6013                        .unwrap();
6014                    state
6015                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
6016                        .unwrap();
6017                    state.set_token_presentation(
6018                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
6019                    );
6020                    // The pointer moves A -> B; GPUI dispatches B's enter
6021                    // before A's exit.
6022                    state
6023                        .token_hover(0, bounds, true, None)
6024                        .expect("hover enters A");
6025                    state
6026                        .token_hover(3, bounds, true, None)
6027                        .expect("hover enters B");
6028                    let (_, exit_a) = state.token_hover(0, bounds, false, None).expect("A exits");
6029                    assert!(!exit_a.is_hovered());
6030                    // B's snapshot must survive A's late exit.
6031                    assert_eq!(
6032                        state
6033                            .hovered_token
6034                            .as_ref()
6035                            .expect("B still hovered")
6036                            .span
6037                            .token()
6038                            .id()
6039                            .as_ref(),
6040                        "b"
6041                    );
6042                    // Removing B afterwards still produces B's reconciled exit.
6043                    state.set_value("no tokens", window, cx);
6044                    state.token_bounds.clear();
6045                    let (listener, exit_b) = state.reconcile_token_hover().expect("B must exit");
6046                    assert!(!exit_b.is_hovered());
6047                    assert_eq!(exit_b.token().id().as_ref(), "b");
6048                    drop(listener);
6049                });
6050            })
6051            .unwrap();
6052    }
6053
6054    #[gpui::test]
6055    fn test_inline_token_hover_late_exit_matches_replaced_token(cx: &mut TestAppContext) {
6056        use crate::input::{InlineToken, InlineTokenPresentation};
6057        cx.update(crate::init);
6058        let view = InputView::build(cx, |state| state.default_value("@a"));
6059        view.window_handle
6060            .update(cx, |_, window, cx| {
6061                view.input.update(cx, |state, cx| {
6062                    let a = InlineToken::new("a", "@a");
6063                    let b = InlineToken::new("b", "@a");
6064                    state
6065                        .replace_range_with_token(0..2, a.clone(), window, cx)
6066                        .unwrap();
6067                    state.set_token_presentation(
6068                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
6069                    );
6070                    let bounds = gpui::Bounds::default();
6071                    state.token_hover(0, bounds, true, Some(&a)).unwrap();
6072                    state
6073                        .replace_range_with_token(0..2, b.clone(), window, cx)
6074                        .unwrap();
6075                    state.token_hover(0, bounds, true, Some(&b)).unwrap();
6076                    let (_, exit_a) = state.token_hover(0, bounds, false, Some(&a)).unwrap();
6077                    assert_eq!(exit_a.token().id().as_ref(), "a");
6078                    assert_eq!(state.hovered_token.as_ref().unwrap().span.token(), &b);
6079                    let (_, exit_b) = state.token_hover(0, bounds, false, Some(&b)).unwrap();
6080                    assert_eq!(exit_b.token().id().as_ref(), "b");
6081                    assert!(state.pending_token_hover_exits.is_empty());
6082                });
6083            })
6084            .unwrap();
6085    }
6086
6087    #[gpui::test]
6088    fn test_inline_token_hover_exit_keeps_entry_utf16_after_edit(cx: &mut TestAppContext) {
6089        use crate::input::{InlineToken, InlineTokenPresentation};
6090        use gpui::EntityInputHandler as _;
6091        cx.update(crate::init);
6092        let view = InputView::build(cx, |state| state.default_value("🙂 @a"));
6093        view.window_handle
6094            .update(cx, |_, window, cx| {
6095                view.input.update(cx, |state, cx| {
6096                    state
6097                        .replace_range_with_token(5..7, InlineToken::new("a", "@a"), window, cx)
6098                        .unwrap();
6099                    state.set_token_presentation(
6100                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
6101                    );
6102                    let bounds = gpui::Bounds::default();
6103                    let (_, enter) = state.token_hover(5, bounds, true, None).unwrap();
6104                    assert_eq!(enter.range_utf16(), (3, 5));
6105                    state.replace_text_in_range(Some(0..2), "abcd", window, cx);
6106                    assert_eq!(state.token_spans()[0].range(), 5..7);
6107                    let (_, exit) = state.token_hover(5, bounds, false, None).unwrap();
6108                    assert_eq!(exit.range_utf16(), (3, 5));
6109                });
6110            })
6111            .unwrap();
6112    }
6113
6114    #[gpui::test]
6115    fn test_set_selected_text_range(cx: &mut TestAppContext) {
6116        use gpui::EntityInputHandler as _;
6117        cx.update(crate::init);
6118        // "🙂" is 2 UTF-16 units and 4 bytes.
6119        let view = InputView::build(cx, |state| state.default_value("🙂 hello world"));
6120        view.window_handle
6121            .update(cx, |_, window, cx| {
6122                view.input.update(cx, |state, cx| {
6123                    fn selection(
6124                        state: &mut InputBaseState<InputMode>,
6125                        window: &mut Window,
6126                        cx: &mut Context<InputBaseState<InputMode>>,
6127                    ) -> (Range<usize>, bool) {
6128                        let selection = state.selected_text_range(false, window, cx).unwrap();
6129                        (selection.range, selection.reversed)
6130                    }
6131
6132                    state.set_selected_text_range(3..8, window, cx);
6133                    assert_eq!(state.selected_range(), 5..10);
6134                    assert_eq!(selection(state, window, cx), (3..8, false));
6135
6136                    // Dragging the end handle keeps the selection forward.
6137                    state.set_selected_text_range(3..14, window, cx);
6138                    assert_eq!(selection(state, window, cx), (3..14, false));
6139                    assert_eq!(state.cursor(), 16);
6140
6141                    // Dragging the start handle reverses it, so the start is the cursor.
6142                    state.set_selected_text_range(9..14, window, cx);
6143                    assert_eq!(selection(state, window, cx), (9..14, true));
6144                    assert_eq!(state.cursor(), 11);
6145
6146                    // A repeated range keeps its direction.
6147                    state.set_selected_text_range(9..14, window, cx);
6148                    assert_eq!(selection(state, window, cx), (9..14, true));
6149
6150                    // Out of range clamps; a backwards range is normalized.
6151                    state.set_selected_text_range(100..9, window, cx);
6152                    assert_eq!(selection(state, window, cx), (9..14, true));
6153
6154                    // Either handle can cross the stationary end.
6155                    state.set_selected_text_range(3..8, window, cx);
6156                    state.set_selected_text_range(2..3, window, cx);
6157                    assert_eq!(selection(state, window, cx), (2..3, true));
6158                    assert_eq!(state.cursor(), 4);
6159                    state.set_selected_text_range(3..8, window, cx);
6160                    state.set_selected_text_range(8..9, window, cx);
6161                    assert_eq!(selection(state, window, cx), (8..9, false));
6162                    assert_eq!(state.cursor(), 11);
6163
6164                    // Collapsing places a forward cursor and leaves the text alone.
6165                    state.set_selected_text_range(2..2, window, cx);
6166                    assert_eq!(selection(state, window, cx), (2..2, false));
6167                    assert_eq!(state.value(), "🙂 hello world");
6168                });
6169            })
6170            .unwrap();
6171    }
6172
6173    #[gpui::test]
6174    fn test_inline_token_hover_callback_edit_preserves_both_exits(cx: &mut TestAppContext) {
6175        use crate::input::{InlineToken, InlineTokenPresentation};
6176        cx.update(crate::init);
6177        let view = InputView::build(cx, |state| state.default_value("@a @b"));
6178        view.window_handle
6179            .update(cx, |_, window, cx| {
6180                view.input.update(cx, |state, cx| {
6181                    state
6182                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
6183                        .unwrap();
6184                    state
6185                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
6186                        .unwrap();
6187                    state.set_token_presentation(
6188                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
6189                    );
6190                    let bounds = gpui::Bounds::default();
6191                    state.token_hover(0, bounds, true, None).unwrap();
6192                    state.token_hover(3, bounds, true, None).unwrap();
6193                    // Model an edit in B's enter callback before A's queued exit.
6194                    state.set_value("gone", window, cx);
6195                    let (_, exit_a) = state.token_hover(0, bounds, false, None).unwrap();
6196                    assert_eq!(exit_a.token().id().as_ref(), "a");
6197                    let (_, exit_b) = state.reconcile_token_hover().unwrap();
6198                    assert_eq!(exit_b.token().id().as_ref(), "b");
6199                    assert!(state.token_hover(0, bounds, false, None).is_none());
6200                    assert!(state.reconcile_token_hover().is_none());
6201                });
6202            })
6203            .unwrap();
6204    }
6205
6206    #[gpui::test]
6207    fn test_inline_token_edit_history_and_validation(cx: &mut TestAppContext) {
6208        let view = InputView::build(cx, |state| state.default_value("问 @alice!"));
6209        view.window_handle
6210            .update(cx, |_, window, cx| {
6211                view.input.update(cx, |state, cx| {
6212                    let token =
6213                        crate::input::InlineToken::new("alice-1", "@alice").with_label("Alice");
6214                    state
6215                        .replace_range_with_token(4..10, token.clone(), window, cx)
6216                        .unwrap();
6217                    assert_eq!(state.value().as_ref(), "问 @alice!");
6218                    assert_eq!(state.tokens()[0].range(), 4..10);
6219                    assert_eq!(state.next_end_of_word_at(4), 10);
6220                    assert_eq!(state.previous_start_of_word_at(10), 4);
6221                    state.undo(&Undo, window, cx);
6222                    assert!(
6223                        state.tokens().is_empty(),
6224                        "identity-only association is undoable"
6225                    );
6226                    assert_eq!(state.value().as_ref(), "问 @alice!");
6227                    state.redo(&Redo, window, cx);
6228                    assert_eq!(state.tokens()[0].token(), &token);
6229                    state.set_selected_range(6..7, cx);
6230                    assert_eq!(state.selected_range(), 4..10);
6231                    state.replace("", window, cx);
6232                    assert_eq!(state.value().as_ref(), "问 !");
6233                    assert!(state.tokens().is_empty());
6234                    state.undo(&Undo, window, cx);
6235                    assert_eq!(state.tokens()[0].token(), &token);
6236                    state.set_selected_range(0..0, cx);
6237                    state.replace("🙂", window, cx);
6238                    assert_eq!(state.tokens()[0].range(), 8..14);
6239                    state.undo(&Undo, window, cx);
6240                    assert_eq!(state.tokens()[0].range(), 4..10);
6241                    let before = state.content();
6242                    state
6243                        .replace_range_with_token(0..0, token.clone(), window, cx)
6244                        .expect("the same reference may occur twice");
6245                    assert_eq!(state.value().as_ref(), "@alice问 @alice!");
6246                    assert_eq!(state.tokens().len(), 2);
6247                    assert_eq!(state.tokens()[1].token(), &token);
6248                    state.undo(&Undo, window, cx);
6249                    assert_eq!(state.content(), before);
6250                    state.set_value(before.text().clone(), window, cx);
6251                    assert!(state.tokens().is_empty());
6252                    assert!(!state.undo_manager.has_undos());
6253                });
6254            })
6255            .unwrap();
6256    }
6257
6258    #[gpui::test]
6259    fn test_inline_token_textarea_ime_and_modes(cx: &mut TestAppContext) {
6260        use crate::input::{InlineToken, InlineTokenError, InputContent};
6261        let view = InputView::build_textarea(cx, |state| state);
6262        view.window_handle
6263            .update(cx, |_, window, cx| {
6264                view.input.update(cx, |state, cx| {
6265                    let content = InputContent::new("@a@b\n后面")
6266                        .with_token(0..2, InlineToken::new("a", "@a"))
6267                        .unwrap()
6268                        .with_token(2..4, InlineToken::new("b", "@b"))
6269                        .unwrap();
6270                    state.set_value(content.clone(), window, cx);
6271                    state.set_selected_range(2..2, cx);
6272                    assert_eq!(state.previous_boundary(2), 0);
6273                    assert_eq!(state.next_boundary(2), 4);
6274                    state.replace_and_mark_text_in_range(Some(1..2), "中", Some(1..1), window, cx);
6275                    assert_eq!(state.value().as_ref(), "中@b\n后面");
6276                    assert_eq!(state.tokens().len(), 1);
6277                    assert_eq!(
6278                        state.replace_with_token(InlineToken::new("x", "x"), window, cx),
6279                        Err(InlineTokenError::CompositionActive)
6280                    );
6281                    state.replace_text_in_range(None, "中文", window, cx);
6282                    state.undo(&Undo, window, cx);
6283                    assert_eq!(state.content(), content);
6284                    state.redo(&Redo, window, cx);
6285                    assert_eq!(state.value().as_ref(), "中文@b\n后面");
6286                    state.undo(&Undo, window, cx);
6287                    state.replace_all("plain", window, cx);
6288                    assert!(state.tokens().is_empty());
6289                    state.undo(&Undo, window, cx);
6290                    assert_eq!(state.content(), content);
6291                });
6292            })
6293            .unwrap();
6294        let view = InputView::build(cx, |state| state);
6295        view.window_handle
6296            .update(cx, |_, window, cx| {
6297                view.input.update(cx, |state, cx| {
6298                    assert_eq!(
6299                        InputContent::new("a\u{301}")
6300                            .with_token(0..1, InlineToken::new("bad", "a"))
6301                            .err(),
6302                        Some(InlineTokenError::InvalidBoundary)
6303                    );
6304                    assert_eq!(
6305                        InputContent::new("@a@a")
6306                            .with_token(0..2, InlineToken::new("a", "@a"))
6307                            .unwrap()
6308                            .with_token(1..3, InlineToken::new("x", "a@"))
6309                            .err(),
6310                        Some(InlineTokenError::OverlappingTokens)
6311                    );
6312                    state
6313                        .replace_with_token(InlineToken::new("a", "@a"), window, cx)
6314                        .unwrap();
6315                    let before = state.content();
6316                    state.set_masked(true, window, cx);
6317                    assert!(!state.tokens_visible());
6318                    assert_eq!(
6319                        state.replace_with_token(InlineToken::new("b", "b"), window, cx),
6320                        Err(InlineTokenError::UnsupportedMode)
6321                    );
6322                    assert_eq!(state.content(), before);
6323                    state.undo(&Undo, window, cx);
6324                    assert!(state.tokens().is_empty());
6325                    state.redo(&Redo, window, cx);
6326                    assert_eq!(state.content(), before);
6327                    assert!(!state.tokens_visible());
6328                });
6329            })
6330            .unwrap();
6331    }
6332
6333    use crate::theme::Theme;
6334    use gpui::{TestAppContext, VisualTestContext, size};
6335
6336    use crate::input::{EditorMode, EditorState, InputMode, LanguageConfig, TabSize, TextareaMode};
6337
6338    fn set_test_syntax_provider(
6339        provider: Rc<dyn crate::input::SyntaxContextProvider>,
6340        cx: &mut App,
6341    ) {
6342        struct TestLanguages(Rc<dyn crate::input::SyntaxContextProvider>);
6343        impl crate::input::LanguageProvider for TestLanguages {
6344            fn syntax_context_provider(
6345                &self,
6346                _: &str,
6347            ) -> Option<Rc<dyn crate::input::SyntaxContextProvider>> {
6348                Some(self.0.clone())
6349            }
6350        }
6351        crate::input::set_language_provider(Rc::new(TestLanguages(provider)), cx);
6352    }
6353
6354    struct TestRoot<M: InputModeKind>(Entity<InputBaseState<M>>);
6355
6356    impl<M: InputModeKind> Render for TestRoot<M> {
6357        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6358            div().size_full().child(self.0.clone())
6359        }
6360    }
6361
6362    struct InputView<M: InputModeKind> {
6363        input: Entity<InputBaseState<M>>,
6364        window_handle: gpui::WindowHandle<TestRoot<M>>,
6365    }
6366
6367    /// Helper to open a state of one mode in a window for testing.
6368    impl<M: InputModeKind> InputView<M> {
6369        fn build_with(
6370            cx: &mut TestAppContext,
6371            make: impl FnOnce(&mut Window, &mut Context<InputBaseState<M>>) -> InputBaseState<M>
6372            + 'static,
6373        ) -> Self {
6374            let mut input: Option<Entity<InputBaseState<M>>> = None;
6375
6376            let window = cx.update(|cx| {
6377                cx.open_window(Default::default(), |window, cx| {
6378                    // Set up the theme first
6379                    cx.set_global(Theme::default());
6380                    // Initialize input keybindings
6381                    super::super::init(cx);
6382
6383                    input = Some(cx.new(|cx| make(window, cx)));
6384
6385                    cx.new(|_| TestRoot(input.clone().unwrap()))
6386                })
6387                .unwrap()
6388            });
6389
6390            Self {
6391                input: input.clone().unwrap(),
6392                window_handle: window,
6393            }
6394        }
6395    }
6396
6397    impl InputView<EditorMode> {
6398        /// An editor state, for the tests that exercise code-editor behavior.
6399        fn new(cx: &mut TestAppContext) -> Self {
6400            Self::build_editor(cx, |state| state)
6401        }
6402
6403        fn build_editor(
6404            cx: &mut TestAppContext,
6405            f: impl FnOnce(InputBaseState<EditorMode>) -> InputBaseState<EditorMode> + 'static,
6406        ) -> Self {
6407            Self::build_with(cx, move |window, cx| {
6408                f(crate::input::EditorState::new(window, cx).language("sql"))
6409            })
6410        }
6411    }
6412
6413    impl InputView<TextareaMode> {
6414        fn build_textarea(
6415            cx: &mut TestAppContext,
6416            f: impl FnOnce(InputBaseState<TextareaMode>) -> InputBaseState<TextareaMode> + 'static,
6417        ) -> Self {
6418            Self::build_with(cx, move |window, cx| {
6419                f(crate::input::TextareaState::new(window, cx))
6420            })
6421        }
6422    }
6423
6424    impl InputView<InputMode> {
6425        /// A single-line state, the default these tests were written against.
6426        fn build(
6427            cx: &mut TestAppContext,
6428            f: impl FnOnce(InputBaseState<InputMode>) -> InputBaseState<InputMode> + 'static,
6429        ) -> Self {
6430            Self::build_with(cx, move |window, cx| {
6431                f(crate::input::InputState::new(window, cx))
6432            })
6433        }
6434    }
6435
6436    /// Sends one trackpad packet straight to the input.
6437    fn swipe<M: InputModeKind>(
6438        view: &InputView<M>,
6439        cx: &mut VisualTestContext,
6440        dy: f32,
6441        phase: gpui::TouchPhase,
6442    ) {
6443        cx.update(|window, cx| {
6444            view.input.update(cx, |state, cx| {
6445                state.on_scroll_wheel(
6446                    &ScrollEvent {
6447                        delta: gpui::ScrollDelta::Pixels(point(px(0.), px(dy))),
6448                        touch_phase: phase,
6449                        ..Default::default()
6450                    },
6451                    window,
6452                    cx,
6453                )
6454            })
6455        });
6456    }
6457
6458    fn long_text() -> String {
6459        (0..60)
6460            .map(|n| format!("line {n}"))
6461            .collect::<Vec<_>>()
6462            .join("\n")
6463    }
6464
6465    #[gpui::test]
6466    fn test_scroll_bounce_stretches_past_the_end_and_springs_back(cx: &mut TestAppContext) {
6467        use gpui::TouchPhase::{Ended, Moved, Started};
6468
6469        let view = InputView::build_textarea(cx, |state| {
6470            state.scroll_bounce(true).default_value(long_text())
6471        });
6472        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6473        cx.update(|window, cx| window.draw(cx).clear(cx));
6474        let drawn_at = |cx: &mut VisualTestContext| {
6475            view.input
6476                .read_with(cx, |state, _| state.last_bounds.unwrap().origin.y)
6477        };
6478
6479        swipe(&view, &mut cx, -20., Started);
6480        swipe(&view, &mut cx, -100_000., Moved);
6481        let (end, stretch) = view.input.read_with(&cx, |state, _| {
6482            (state.scroll_handle.offset().y, state.overscroll.offset())
6483        });
6484        assert!(end < px(0.), "the text scrolled to its end");
6485        assert!(stretch < 0., "the rest stretched the edge: {stretch}");
6486        cx.update(|window, cx| window.draw(cx).clear(cx));
6487        let stretched_at = drawn_at(&mut cx);
6488
6489        swipe(&view, &mut cx, 0., Ended);
6490        for _ in 0..120 {
6491            cx.executor()
6492                .advance_clock(std::time::Duration::from_millis(16));
6493            cx.update(|window, cx| window.draw(cx).clear(cx));
6494        }
6495        view.input.read_with(&cx, |state, _| {
6496            assert_eq!(state.overscroll.offset(), 0., "the edge sprang back");
6497            assert_eq!(
6498                state.scroll_handle.offset().y,
6499                end,
6500                "scrolling stayed exact"
6501            );
6502        });
6503        let moved = (stretched_at - drawn_at(&mut cx)).as_f32();
6504        assert!(
6505            (moved - stretch).abs() < 0.001,
6506            "the stretch only moved where the text was drawn: {moved} vs {stretch}"
6507        );
6508    }
6509
6510    /// Swipes far past the end of the text and returns where the text
6511    /// scrolled to and how far its edge stretched.
6512    fn swipe_past_the_end(
6513        view: &InputView<TextareaMode>,
6514        cx: &mut VisualTestContext,
6515    ) -> (Pixels, f32) {
6516        use gpui::TouchPhase::{Moved, Started};
6517
6518        cx.update(|window, cx| window.draw(cx).clear(cx));
6519        swipe(view, cx, -20., Started);
6520        swipe(view, cx, -100_000., Moved);
6521        view.input.read_with(cx, |state, _| {
6522            (state.scroll_handle.offset().y, state.overscroll.offset())
6523        })
6524    }
6525
6526    #[gpui::test]
6527    fn test_scroll_bounce_stays_put_under_reduced_motion(cx: &mut TestAppContext) {
6528        cx.update(|cx| cx.set_reduce_motion(true));
6529        let view = InputView::build_textarea(cx, |state| {
6530            state.scroll_bounce(true).default_value(long_text())
6531        });
6532        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6533        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6534        assert!(end < px(0.), "the text still scrolls");
6535        assert_eq!(stretch, 0., "the edge does not stretch");
6536    }
6537
6538    #[gpui::test]
6539    fn test_scroll_bounce_follows_the_platform_by_default(cx: &mut TestAppContext) {
6540        let view = InputView::build_textarea(cx, |state| state.default_value(long_text()));
6541        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6542        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6543        assert!(end < px(0.), "the text scrolls");
6544        assert_eq!(
6545            stretch != 0.,
6546            crate::ScrollBounce::enabled_by_default(),
6547            "the edge stretches where the platform's scroll views do"
6548        );
6549    }
6550
6551    #[gpui::test]
6552    fn test_scroll_bounce_leaves_text_that_fits(cx: &mut TestAppContext) {
6553        let view = InputView::build_textarea(cx, |state| {
6554            state.scroll_bounce(true).default_value("one line")
6555        });
6556        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6557        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6558        assert_eq!((end, stretch), (px(0.), 0.));
6559    }
6560
6561    #[gpui::test]
6562    fn test_scroll_bounce_resets_when_replacement_text_fits(cx: &mut TestAppContext) {
6563        let view = InputView::build_textarea(cx, |state| {
6564            state.scroll_bounce(true).default_value(long_text())
6565        });
6566        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6567        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6568        assert!(stretch < 0.);
6569        cx.update(|window, cx| {
6570            view.input
6571                .update(cx, |state, cx| state.set_value("short", window, cx));
6572            window.draw(cx).clear(cx);
6573        });
6574        swipe(&view, &mut cx, 0., gpui::TouchPhase::Ended);
6575        view.input.read_with(&cx, |state, _| {
6576            assert_eq!(state.overscroll.offset(), 0.);
6577            assert_eq!(state.scroll_handle.offset().y, px(0.));
6578        });
6579    }
6580
6581    #[gpui::test]
6582    fn test_scroll_bounce_releases_when_the_end_packet_is_horizontal(cx: &mut TestAppContext) {
6583        let view = InputView::build_textarea(cx, |state| {
6584            state.scroll_bounce(true).default_value(long_text())
6585        });
6586        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6587        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6588        assert!(stretch < 0.);
6589        cx.update(|window, cx| {
6590            view.input.update(cx, |state, cx| {
6591                state.on_scroll_wheel(
6592                    &ScrollEvent {
6593                        delta: gpui::ScrollDelta::Pixels(point(px(1.), px(0.))),
6594                        touch_phase: gpui::TouchPhase::Ended,
6595                        ..Default::default()
6596                    },
6597                    window,
6598                    cx,
6599                );
6600            });
6601        });
6602        for _ in 0..120 {
6603            cx.executor()
6604                .advance_clock(std::time::Duration::from_millis(16));
6605            cx.update(|window, cx| window.draw(cx).clear(cx));
6606        }
6607        assert_eq!(
6608            view.input
6609                .read_with(&cx, |state, _| state.overscroll.offset()),
6610            0.
6611        );
6612    }
6613
6614    #[gpui::test]
6615    fn test_scroll_bounce_ordinary_mouse_wheel_does_not_stretch(cx: &mut TestAppContext) {
6616        let view = InputView::build_textarea(cx, |state| {
6617            state.scroll_bounce(true).default_value(long_text())
6618        });
6619        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6620        cx.update(|window, cx| {
6621            window.draw(cx).clear(cx);
6622            view.input.update(cx, |state, cx| {
6623                state.on_scroll_wheel(
6624                    &ScrollEvent {
6625                        delta: gpui::ScrollDelta::Lines(point(0., -100_000.)),
6626                        touch_phase: gpui::TouchPhase::Moved,
6627                        ..Default::default()
6628                    },
6629                    window,
6630                    cx,
6631                );
6632                assert!(state.scroll_handle.offset().y < px(0.));
6633                assert_eq!(state.overscroll.offset(), 0.);
6634            });
6635        });
6636    }
6637
6638    #[gpui::test]
6639    fn test_scroll_bounce_new_wheel_input_interrupts_the_return(cx: &mut TestAppContext) {
6640        let view = InputView::build_textarea(cx, |state| {
6641            state.scroll_bounce(true).default_value(long_text())
6642        });
6643        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6644        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6645        assert!(stretch < 0.);
6646        swipe(&view, &mut cx, 0., gpui::TouchPhase::Ended);
6647        cx.executor()
6648            .advance_clock(std::time::Duration::from_millis(16));
6649        cx.update(|window, cx| {
6650            window.draw(cx).clear(cx);
6651            view.input.update(cx, |state, cx| {
6652                state.on_scroll_wheel(
6653                    &ScrollEvent {
6654                        delta: gpui::ScrollDelta::Lines(point(0., 2.)),
6655                        touch_phase: gpui::TouchPhase::Moved,
6656                        ..Default::default()
6657                    },
6658                    window,
6659                    cx,
6660                );
6661                assert!(state.scroll_handle.offset().y > end);
6662                assert_eq!(state.overscroll.offset(), 0.);
6663            });
6664        });
6665    }
6666
6667    #[gpui::test]
6668    fn test_turning_scroll_bounce_off_drops_the_stretch(cx: &mut TestAppContext) {
6669        let view = InputView::build_textarea(cx, |state| {
6670            state.scroll_bounce(true).default_value(long_text())
6671        });
6672        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6673        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6674        assert!(stretch < 0., "the edge stretched: {stretch}");
6675        view.input
6676            .update(&mut cx, |state, cx| state.set_scroll_bounce(false, cx));
6677        assert_eq!(
6678            view.input
6679                .read_with(&cx, |state, _| state.overscroll.offset()),
6680            0.
6681        );
6682    }
6683
6684    /// The frame is laid out by the application, which should not have to
6685    /// center a single line inside it.
6686    #[gpui::test]
6687    fn single_line_is_centered_in_a_taller_frame(cx: &mut TestAppContext) {
6688        struct Frame(Entity<InputBaseState<InputMode>>);
6689        impl Render for Frame {
6690            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6691                crate::input::InputBase::new("frame")
6692                    .h(px(60.))
6693                    .child(crate::input::Input::new(&self.0))
6694            }
6695        }
6696
6697        cx.update(crate::init);
6698        let mut input = None;
6699        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6700            let state = cx.new(|cx| crate::input::InputState::new(window, cx).default_value("a"));
6701            input = Some(state.clone());
6702            Frame(state)
6703        });
6704        let input = input.unwrap();
6705        let mut cx = VisualTestContext::from_window(window.into(), cx);
6706        cx.update(|window, cx| window.draw(cx).clear(cx));
6707
6708        input.read_with(&cx, |state, _| {
6709            let line = state.input_bounds;
6710            assert_eq!(line.center().y, px(30.), "line {line:?}");
6711        });
6712    }
6713
6714    #[gpui::test]
6715    fn test_noop_scroll_notifies_diagnostic_dismissal(cx: &mut TestAppContext) {
6716        use std::{cell::Cell, rc::Rc};
6717
6718        cx.update(crate::init);
6719        let mut input = None;
6720        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6721            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
6722            gpui::EmptyView
6723        });
6724        let input = input.unwrap();
6725        input.update(cx, |state, cx| {
6726            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
6727            state.scroll_size = size(px(100.), px(20.));
6728            state.present_diagnostic(crate::input::DiagnosticEntry::default(), cx);
6729        });
6730        let notifications = Rc::new(Cell::new(0));
6731        let count = notifications.clone();
6732        let _subscription =
6733            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6734
6735        window
6736            .update(cx, |_, window, cx| {
6737                input.update(cx, |state, cx| {
6738                    state.on_scroll_wheel(
6739                        &ScrollEvent {
6740                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
6741                            ..Default::default()
6742                        },
6743                        window,
6744                        cx,
6745                    );
6746                    assert_eq!(state.scroll_handle.offset(), Point::default());
6747                    assert!(state.diagnostic_popover().is_none());
6748                });
6749            })
6750            .unwrap();
6751        assert_eq!(
6752            notifications.get(),
6753            1,
6754            "clearing a diagnostic must notify even when scrolling is clamped"
6755        );
6756        window
6757            .update(cx, |_, window, cx| {
6758                input.update(cx, |state, cx| {
6759                    state.on_scroll_wheel(
6760                        &ScrollEvent {
6761                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
6762                            ..Default::default()
6763                        },
6764                        window,
6765                        cx,
6766                    );
6767                });
6768            })
6769            .unwrap();
6770        assert_eq!(notifications.get(), 1, "an unchanged input must stay quiet");
6771    }
6772
6773    /// Hover must reach the whole visible editor after scrolling, not only
6774    /// the part that overlaps the unscrolled text bounds.
6775    #[gpui::test]
6776    fn test_hover_reaches_bottom_of_scrolled_editor(cx: &mut TestAppContext) {
6777        use crate::input::{Diagnostic, Position};
6778        struct Frame(Entity<crate::input::EditorState>);
6779        impl Render for Frame {
6780            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6781                crate::input::InputBase::new("frame")
6782                    .h(px(100.))
6783                    .child(crate::input::Editor::new(&self.0))
6784            }
6785        }
6786
6787        cx.update(crate::init);
6788        let text = (0..100).map(|i| format!("line {i}\n")).collect::<String>();
6789        let mut input = None;
6790        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6791            let state = cx.new(|cx| crate::input::EditorState::new(window, cx).default_value(text));
6792            input = Some(state.clone());
6793            Frame(state)
6794        });
6795        let input = input.unwrap();
6796        let mut cx = VisualTestContext::from_window(window.into(), cx);
6797        input.update(&mut cx, |state, _| {
6798            state.diagnostics_mut().unwrap().push(Diagnostic::new(
6799                Position::new(0, 0)..Position::new(100, 0),
6800                "everywhere",
6801            ));
6802        });
6803        cx.update(|window, cx| window.draw(cx).clear(cx));
6804        cx.update(|window, cx| {
6805            input.update(cx, |state, cx| {
6806                state.on_scroll_wheel(
6807                    &ScrollEvent {
6808                        delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-400.))),
6809                        ..Default::default()
6810                    },
6811                    window,
6812                    cx,
6813                );
6814            })
6815        });
6816        cx.update(|window, cx| window.draw(cx).clear(cx));
6817
6818        let bounds = input.read_with(&cx, |state, _| state.input_bounds);
6819        cx.simulate_mouse_move(
6820            point(bounds.center().x, bounds.bottom() - px(5.)),
6821            None,
6822            gpui::Modifiers::default(),
6823        );
6824        assert!(
6825            input.read_with(&cx, |state, _| state.diagnostic_popover().is_some()),
6826            "no hover at the bottom of a scrolled editor, bounds {bounds:?}"
6827        );
6828    }
6829
6830    #[gpui::test]
6831    fn test_cursor_layout_consumer_updates_after_selection(cx: &mut TestAppContext) {
6832        use std::{cell::Cell, rc::Rc};
6833        struct Panel {
6834            input: Entity<crate::input::EditorState>,
6835            observed: Rc<Cell<Option<(Bounds<Pixels>, Pixels)>>>,
6836        }
6837        impl Render for Panel {
6838            fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
6839                self.observed.set(self.input.read(cx).cursor_layout());
6840                div().size_full().child(self.input.clone())
6841            }
6842        }
6843        struct Root(Entity<Panel>);
6844        impl Render for Root {
6845            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6846                div().size_full().child(
6847                    self.0
6848                        .clone()
6849                        .cached(gpui::StyleRefinement::default().size_full()),
6850                )
6851            }
6852        }
6853        cx.update(crate::init);
6854        let observed = Rc::new(Cell::new(None));
6855        let mut input = None;
6856        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6857            let state = cx.new(|cx| {
6858                let mut state = crate::input::EditorState::new(window, cx);
6859                state.set_value("abcdefgh", window, cx);
6860                state
6861            });
6862            input = Some(state.clone());
6863            Root(cx.new(|_| Panel {
6864                input: state,
6865                observed: observed.clone(),
6866            }))
6867        });
6868        cx.run_until_parked();
6869        let input = input.unwrap();
6870        let before = input.read_with(cx, |state, _| state.cursor_layout());
6871        // The caret geometry only exists once the input has painted, and a
6872        // notify sent from inside a draw marks the view dirty without asking
6873        // for another frame, so the consumer reads the geometry on the next
6874        // invalidation rather than during the paint that produced it.
6875        input.update(cx, |_, cx| cx.notify());
6876        cx.run_until_parked();
6877        assert_eq!(observed.get(), before);
6878        window
6879            .update(cx, |_, _, cx| {
6880                input.update(cx, |state, cx| state.select_to_with_affinity(3, false, cx));
6881            })
6882            .unwrap();
6883        cx.run_until_parked();
6884        window.update(cx, |_, _, cx| cx.notify()).unwrap();
6885        let after = input.read_with(cx, |state, _| state.cursor_layout());
6886        assert_ne!(after, before, "the caret must actually move");
6887        assert_eq!(
6888            observed.get(),
6889            after,
6890            "render consumers must receive the newly painted caret geometry"
6891        );
6892    }
6893
6894    #[gpui::test]
6895    fn test_input_scroll_offset_notifies_only_on_change(cx: &mut TestAppContext) {
6896        use std::{cell::Cell, rc::Rc};
6897
6898        cx.update(crate::init);
6899        let mut input = None;
6900        let _window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6901            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
6902            gpui::EmptyView
6903        });
6904        let input = input.unwrap();
6905        let notifications = Rc::new(Cell::new(0));
6906        let count = notifications.clone();
6907        let _subscription =
6908            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6909
6910        input.update(cx, |state, _| {
6911            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
6912            state.scroll_size = size(px(300.), px(20.));
6913        });
6914        for target in [
6915            None,
6916            Some(point(px(0.), px(0.))),
6917            Some(point(px(20.), px(50.))),
6918        ] {
6919            input.update(cx, |state, cx| state.update_scroll_offset(target, cx));
6920            assert_eq!(
6921                notifications.get(),
6922                0,
6923                "an unchanged clamped offset must stay quiet"
6924            );
6925        }
6926        input.update(cx, |state, cx| {
6927            state.update_scroll_offset(Some(point(px(-40.), px(0.))), cx);
6928            assert_eq!(state.scroll_handle.offset(), point(px(-40.), px(0.)));
6929        });
6930        assert_eq!(notifications.get(), 1, "a scroll must notify");
6931        input.update(cx, |state, cx| {
6932            state.update_scroll_offset(Some(point(px(-400.), px(50.))), cx);
6933            assert_eq!(state.scroll_handle.offset(), point(px(-200.), px(0.)));
6934        });
6935        assert_eq!(notifications.get(), 2);
6936        input.update(cx, |state, cx| {
6937            state.update_scroll_offset(Some(point(px(-500.), px(0.))), cx);
6938        });
6939        assert_eq!(
6940            notifications.get(),
6941            2,
6942            "clamping to the current offset must stay quiet"
6943        );
6944        input.update(cx, |state, cx| {
6945            state.scroll_size.width = px(110.);
6946            state.update_scroll_offset(None, cx);
6947            assert_eq!(state.scroll_handle.offset(), point(px(-10.), px(0.)));
6948        });
6949        assert_eq!(
6950            notifications.get(),
6951            3,
6952            "a smaller scroll range must clamp and notify"
6953        );
6954    }
6955
6956    #[gpui::test]
6957    fn test_set_line_number_notifies_only_on_change(cx: &mut TestAppContext) {
6958        use std::{cell::Cell, rc::Rc};
6959
6960        cx.update(crate::init);
6961        let mut input = None;
6962        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6963            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
6964            gpui::EmptyView
6965        });
6966        let input = input.unwrap();
6967        let shown = input.read_with(cx, |state, _| state.mode.line_number());
6968        let notifications = Rc::new(Cell::new(0));
6969        let count = notifications.clone();
6970        let _subscription =
6971            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6972
6973        window
6974            .update(cx, |_, window, cx| {
6975                input.update(cx, |state, cx| state.set_line_number(shown, window, cx))
6976            })
6977            .unwrap();
6978        assert_eq!(notifications.get(), 0);
6979
6980        window
6981            .update(cx, |_, window, cx| {
6982                input.update(cx, |state, cx| state.set_line_number(!shown, window, cx))
6983            })
6984            .unwrap();
6985        assert_eq!(notifications.get(), 1);
6986        assert_eq!(
6987            input.read_with(cx, |state, _| state.mode.line_number()),
6988            !shown
6989        );
6990    }
6991
6992    #[gpui::test]
6993    fn test_set_value_on_unfocused_input_stays_quiet(cx: &mut TestAppContext) {
6994        use std::{cell::Cell, rc::Rc};
6995
6996        cx.update(crate::init);
6997        let mut input = None;
6998        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6999            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
7000            gpui::EmptyView
7001        });
7002        let input = input.unwrap();
7003        cx.run_until_parked();
7004
7005        let notifications = Rc::new(Cell::new(0));
7006        let count = notifications.clone();
7007        let _subscription =
7008            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
7009        cx.run_until_parked();
7010
7011        // Seeding a form is a write, so it notifies once. The input is not
7012        // focused and draws no caret, so nothing may notify after that.
7013        window
7014            .update(cx, |_, window, cx| {
7015                input.update(cx, |state, cx| state.set_value("seeded", window, cx));
7016            })
7017            .unwrap();
7018        cx.run_until_parked();
7019        let settled = notifications.get();
7020
7021        cx.executor()
7022            .advance_clock(std::time::Duration::from_secs(3));
7023        cx.run_until_parked();
7024
7025        assert_eq!(
7026            notifications.get(),
7027            settled,
7028            "an unfocused input is blinking, and every blink repaints the view it is in"
7029        );
7030    }
7031
7032    #[gpui::test]
7033    fn test_input_does_not_invalidate_cached_parent_during_paint(cx: &mut TestAppContext) {
7034        use std::{cell::Cell, rc::Rc};
7035
7036        struct Panel {
7037            input: Entity<crate::input::InputState>,
7038            renders: Rc<Cell<usize>>,
7039        }
7040        impl Render for Panel {
7041            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
7042                self.renders.set(self.renders.get() + 1);
7043                div().size_full().child(self.input.clone())
7044            }
7045        }
7046        struct Root(Entity<Panel>);
7047        impl Render for Root {
7048            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
7049                div().size_full().child(
7050                    self.0
7051                        .clone()
7052                        .cached(gpui::StyleRefinement::default().size_full()),
7053                )
7054            }
7055        }
7056
7057        cx.update(crate::init);
7058        let renders = Rc::new(Cell::new(0));
7059        let mut input = None;
7060        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
7061            let state = cx.new(|cx| crate::input::InputState::new(window, cx));
7062            input = Some(state.clone());
7063            Root(cx.new(|_| Panel {
7064                input: state,
7065                renders: renders.clone(),
7066            }))
7067        });
7068        cx.run_until_parked();
7069        let before = renders.get();
7070        assert!(before > 0);
7071        for _ in 0..60 {
7072            window.update(cx, |_, _, cx| cx.notify()).unwrap();
7073        }
7074        assert_eq!(
7075            renders.get(),
7076            before,
7077            "an idle input must not invalidate its cached parent"
7078        );
7079
7080        let input = input.unwrap();
7081        window
7082            .update(cx, |_, window, cx| {
7083                input.update(cx, |state, cx| state.set_value("changed", window, cx));
7084            })
7085            .unwrap();
7086        assert!(
7087            renders.get() > before,
7088            "text changes must invalidate the parent"
7089        );
7090        // The first paint reports the changed text extent to layout consumers.
7091        window.update(cx, |_, _, cx| cx.notify()).unwrap();
7092        let before = renders.get();
7093        for _ in 0..60 {
7094            window.update(cx, |_, _, cx| cx.notify()).unwrap();
7095        }
7096        assert_eq!(renders.get(), before, "the edited input must settle again");
7097
7098        cx.simulate_window_resize(window.into(), size(px(240.), px(100.)));
7099        cx.run_until_parked();
7100        assert!(renders.get() > before, "resizing must invalidate layout");
7101    }
7102
7103    #[gpui::test]
7104    fn textarea_cursor_treats_crlf_as_one_newline(cx: &mut TestAppContext) {
7105        cx.update(crate::init);
7106        let view = InputView::build_textarea(cx, |state| state);
7107        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7108        cx.update(|window, cx| {
7109            view.input.update(cx, |state, cx| {
7110                for original in ["\r\nlast", "\u{feff}first\r\nlast\n", "first\nlast\r\n"] {
7111                    state.set_value(original, window, cx);
7112                    let newline = original.find('\n').unwrap();
7113                    let end = if original.as_bytes()[newline.saturating_sub(1)] == b'\r' {
7114                        newline - 1
7115                    } else {
7116                        newline
7117                    };
7118                    state.end(&MoveEnd, window, cx);
7119                    assert_eq!(state.cursor(), end);
7120                    state.right(&MoveRight, window, cx);
7121                    assert_eq!(state.cursor(), newline + 1);
7122                    state.left(&MoveLeft, window, cx);
7123                    assert_eq!(state.cursor(), end);
7124                    state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
7125                    assert_eq!(state.selected_range(), end..end);
7126                    state.move_to_end(&MoveToEnd, window, cx);
7127                    assert_eq!(state.cursor(), original.len());
7128                    state.move_to_start(&MoveToStart, window, cx);
7129                    assert_eq!(state.cursor(), 0);
7130                    assert_eq!(state.value().as_bytes(), original.as_bytes());
7131                }
7132                // A lone CR is an ordinary character; it must not skip its neighbour.
7133                state.set_value("a\rb", window, cx);
7134                state.right(&MoveRight, window, cx);
7135                assert_eq!(state.cursor(), 1);
7136                state.right(&MoveRight, window, cx);
7137                assert_eq!(state.cursor(), 2);
7138            })
7139        });
7140    }
7141
7142    #[gpui::test]
7143    fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) {
7144        cx.update(crate::init);
7145
7146        // A single-line input keeps its caret in view by moving its own offset;
7147        // it has no viewport to drag, so a scrollbar in a text field is a
7148        // control that does not exist.
7149        let single = InputView::build(cx, |state| state);
7150        single
7151            .input
7152            .update(cx, |state, _| assert!(!state.shows_scrollbar()));
7153
7154        let multi = InputView::build_textarea(cx, |state| state);
7155        multi
7156            .input
7157            .update(cx, |state, _| assert!(state.shows_scrollbar()));
7158    }
7159
7160    #[gpui::test]
7161    fn context_menu_handler_is_deferred_and_respects_disabled(cx: &mut TestAppContext) {
7162        use std::{cell::Cell, rc::Rc};
7163        cx.update(crate::init);
7164        let input_view = InputView::new(cx);
7165        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7166        let input = input_view.input;
7167        let calls = Rc::new(Cell::new(0usize));
7168        let items = Rc::new(Cell::new(0usize));
7169        let menu_focus = cx.update(|_, cx| cx.focus_handle());
7170
7171        cx.update(|window, cx| {
7172            input.update(cx, |state, cx| {
7173                let calls2 = calls.clone();
7174                let items2 = items.clone();
7175                let menu_focus = menu_focus.clone();
7176                state.on_context_menu(Rc::new(move |menu, _, _, window, cx| {
7177                    calls2.set(calls2.get() + 1);
7178                    items2.set(menu.items.len());
7179                    menu_focus.focus(window, cx);
7180                }));
7181                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
7182            })
7183        });
7184        assert_eq!(calls.get(), 1);
7185        assert_eq!(items.get(), 0);
7186        input.read_with(&cx, |state, _| {
7187            assert_eq!(
7188                state
7189                    .selection_focus
7190                    .as_ref()
7191                    .and_then(WeakFocusHandle::upgrade)
7192                    .as_ref(),
7193                Some(&menu_focus),
7194                "the menu callback must retain selection without reborrowing its input"
7195            );
7196        });
7197
7198        // A custom handler may keep using the same, already-focused menu.
7199        cx.update(|window, cx| {
7200            input.update(cx, |state, cx| {
7201                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
7202            });
7203        });
7204        assert_eq!(calls.get(), 2);
7205        cx.update(|window, cx| {
7206            assert!(input.read(cx).has_selection_focus(window, cx));
7207        });
7208
7209        cx.update(|window, cx| {
7210            input.update(cx, |state, cx| {
7211                state.disabled = true;
7212                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
7213            })
7214        });
7215        assert_eq!(calls.get(), 2);
7216    }
7217
7218    #[gpui::test]
7219    fn context_menu_handler_can_read_its_input(cx: &mut TestAppContext) {
7220        use std::{cell::Cell, rc::Rc};
7221        cx.update(crate::init);
7222        let input_view = InputView::new(cx);
7223        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7224        let input = input_view.input;
7225        let cursor = Rc::new(Cell::new(None));
7226
7227        cx.update(|window, cx| {
7228            input.update(cx, |state, cx| {
7229                state.set_value("hello", window, cx);
7230                let weak = cx.weak_entity();
7231                let cursor = cursor.clone();
7232                state.on_context_menu(Rc::new(move |_, _, _, _, cx| {
7233                    cursor.set(weak.upgrade().map(|input| input.read(cx).cursor()));
7234                }));
7235                state.handle_right_click_menu(point(px(0.), px(0.)), 2, window, cx);
7236            })
7237        });
7238        assert_eq!(cursor.get(), Some(2));
7239    }
7240
7241    #[gpui::test]
7242    fn test_readonly_rejects_user_edits_only(cx: &mut TestAppContext) {
7243        let input_view = InputView::new(cx);
7244        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7245        let input = input_view.input;
7246
7247        cx.update(|window, cx| {
7248            input.update(cx, |state, cx| {
7249                state.set_value("hello", window, cx);
7250                state.set_readonly(true, cx);
7251            });
7252        });
7253
7254        cx.update(|_, cx| {
7255            input.read_with(cx, |state, _| {
7256                assert!(!state.is_editable());
7257                assert!(!state.is_replaceable());
7258            });
7259        });
7260
7261        // Typing (and IME) goes through the input handler, it must be rejected.
7262        cx.update(|window, cx| {
7263            input.update(cx, |state, cx| {
7264                state.replace_text_in_range(None, " world", window, cx);
7265                state.replace_and_mark_text_in_range(None, "あ", None, window, cx);
7266            });
7267        });
7268        cx.update(|_, cx| {
7269            input.read_with(cx, |state, _| assert_eq!(state.value(), "hello"));
7270        });
7271
7272        // The programmatic APIs are not limited by the readonly mode.
7273        cx.update(|window, cx| {
7274            input.update(cx, |state, cx| {
7275                state.insert(" world", window, cx);
7276                state.set_value("changed", window, cx);
7277            });
7278        });
7279        cx.update(|_, cx| {
7280            input.read_with(cx, |state, _| assert_eq!(state.value(), "changed"));
7281        });
7282
7283        // And the user can edit again after leaving the readonly mode.
7284        // The caret is at the start, because `set_value` has reset the selection.
7285        cx.update(|window, cx| {
7286            input.update(cx, |state, cx| {
7287                state.set_readonly(false, cx);
7288                state.replace_text_in_range(None, "!", window, cx);
7289            });
7290        });
7291        cx.update(|_, cx| {
7292            input.read_with(cx, |state, _| {
7293                assert!(state.is_editable());
7294                assert_eq!(state.value(), "!changed");
7295            });
7296        });
7297    }
7298
7299    /// Regression test: editing at a caret that sits far outside the viewport
7300    /// must reveal it in one frame. The cursor-follow in `layout_cursors` runs
7301    /// only on the frame the selection changes, so its old one-line step left
7302    /// the caret offscreen after typing at the end of a long paste.
7303    #[gpui::test]
7304    fn test_edit_reveals_far_offscreen_caret(cx: &mut TestAppContext) {
7305        let mut input = None;
7306        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
7307            cx.set_global(Theme::default());
7308            super::super::init(cx);
7309            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 6));
7310            input = Some(state.clone());
7311            TestRoot(state)
7312        });
7313        let mut cx = VisualTestContext::from_window(window.into(), cx);
7314        let input = input.unwrap();
7315
7316        let text: String = (1..=100)
7317            .map(|i| format!("line {i}"))
7318            .collect::<Vec<_>>()
7319            .join("\n");
7320        cx.update(|window, cx| {
7321            input.update(cx, |state, cx| {
7322                state.set_value(text, window, cx);
7323                let end = state.text.len();
7324                state.set_selection(end, end);
7325            });
7326        });
7327        cx.run_until_parked();
7328
7329        // The user scrolled back to the top to read the pasted text.
7330        cx.update(|_, cx| {
7331            input.update(cx, |state, cx| {
7332                state.scroll_handle.set_offset(point(px(0.), px(0.)));
7333                cx.notify();
7334            });
7335        });
7336        cx.run_until_parked();
7337
7338        cx.update(|window, cx| {
7339            input.update(cx, |state, cx| {
7340                state.replace_text_in_range(None, "X", window, cx);
7341            });
7342        });
7343        cx.run_until_parked();
7344
7345        cx.update(|_, cx| {
7346            input.read_with(cx, |state, _| {
7347                assert!(state.value().ends_with("line 100X"));
7348                let (caret, _) = state.cursor_layout().expect("caret painted");
7349                let viewport = state.input_bounds();
7350                let scroll_y = state.scroll_handle.offset().y;
7351                let top = caret.origin.y - viewport.origin.y + scroll_y;
7352                assert!(
7353                    top >= px(0.) && top + caret.size.height <= viewport.size.height,
7354                    "caret top {top:?} outside viewport height {:?} (scroll {scroll_y:?})",
7355                    viewport.size.height,
7356                );
7357            });
7358        });
7359    }
7360
7361    /// Regression test: changing the auto-grow limits keeps the rows fitted to
7362    /// the text. `set_auto_grow` used to start over at `min_rows`, and the rows
7363    /// were counted again only on the next edit or wrap width change.
7364    #[gpui::test]
7365    fn test_set_auto_grow_keeps_rows_fitted(cx: &mut TestAppContext) {
7366        let mut input = None;
7367        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
7368            cx.set_global(Theme::default());
7369            super::super::init(cx);
7370            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 10));
7371            input = Some(state.clone());
7372            TestRoot(state)
7373        });
7374        let mut cx = VisualTestContext::from_window(window.into(), cx);
7375        let input = input.unwrap();
7376
7377        let text: String = (1..=8)
7378            .map(|i| format!("line {i}"))
7379            .collect::<Vec<_>>()
7380            .join("\n");
7381        cx.update(|window, cx| {
7382            input.update(cx, |state, cx| state.set_value(text, window, cx));
7383        });
7384        cx.run_until_parked();
7385        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 8)));
7386
7387        // A larger maximum keeps all eight rows.
7388        cx.update(|_, cx| input.update(cx, |state, cx| state.set_auto_grow(1, 12, cx)));
7389        cx.run_until_parked();
7390        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 8)));
7391
7392        // A smaller maximum caps them.
7393        cx.update(|_, cx| input.update(cx, |state, cx| state.set_auto_grow(1, 4, cx)));
7394        cx.run_until_parked();
7395        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 4)));
7396    }
7397
7398    /// Regression test: `scroll_to` at end-of-buffer must produce a deferred
7399    /// scroll target within the safe scroll range, so the painted frame
7400    /// matches what `update_scroll_offset` persists (no jitter). A small
7401    /// `cursor_surrounding_lines` override used to mismatch the hardcoded
7402    /// 3-line edge clearance in `scroll_to`, overshooting `safe_y_min`.
7403    #[gpui::test]
7404    fn test_scroll_to_eob_does_not_overshoot_safe_range(cx: &mut TestAppContext) {
7405        let input_view = InputView::new(cx);
7406        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7407        let input = input_view.input;
7408
7409        // JetBrains-style: 1 trailing empty row + 1-line cursor surrounding.
7410        cx.update(|window, cx| {
7411            input.update(cx, |state, cx| {
7412                state.set_scroll_beyond_last_line(Some(1), window, cx);
7413                state.set_cursor_surrounding_lines(Some(1), window, cx);
7414                let text: String = (1..=50)
7415                    .map(|i| format!("line {i}"))
7416                    .collect::<Vec<_>>()
7417                    .join("\n");
7418                state.set_value(text, window, cx);
7419            });
7420        });
7421        cx.run_until_parked();
7422
7423        // Sanity: paint populated `scroll_size` and `input_bounds` — without
7424        // these, `safe_y_min` below collapses to 0 and the assertion is vacuous.
7425        cx.update(|_, cx| {
7426            input.read_with(cx, |state, _| {
7427                assert!(
7428                    state.scroll_size.height > px(0.),
7429                    "scroll_size not populated by initial paint"
7430                );
7431                assert!(
7432                    state.input_bounds.size.height > px(0.),
7433                    "input_bounds not populated by initial paint"
7434                );
7435            });
7436        });
7437
7438        // Move cursor to end with downward direction — same code path as a
7439        // `Down` keystroke at EOB. `scroll_to` runs synchronously inside
7440        // `move_to`; inspect `deferred_scroll_offset` in the same closure
7441        // before the next paint consumes and clears it.
7442        cx.update(|_, cx| {
7443            input.update(cx, |state, cx| {
7444                let end = state.text.len();
7445                state.move_to(end, Some(MoveDirection::Down), cx);
7446
7447                let deferred = state
7448                    .deferred_scroll_offset
7449                    .expect("scroll_to should populate deferred_scroll_offset");
7450                let safe_y_min =
7451                    (-state.scroll_size.height + state.input_bounds.size.height).min(px(0.));
7452
7453                assert!(
7454                    deferred.y >= safe_y_min,
7455                    "deferred_scroll_offset.y = {:?} below safe_y_min = {:?} \
7456                     — paint would jitter (Bug C regression)",
7457                    deferred.y,
7458                    safe_y_min,
7459                );
7460            });
7461        });
7462    }
7463
7464    #[gpui::test]
7465    fn test_next_search_match_reveals_with_padding_after_manual_scroll(cx: &mut TestAppContext) {
7466        assert_search_reveals_with_padding_after_manual_scroll(false, cx);
7467    }
7468
7469    #[gpui::test]
7470    fn test_previous_search_match_reveals_with_padding_after_manual_scroll(
7471        cx: &mut TestAppContext,
7472    ) {
7473        assert_search_reveals_with_padding_after_manual_scroll(true, cx);
7474    }
7475
7476    fn assert_search_reveals_with_padding_after_manual_scroll(
7477        previous: bool,
7478        cx: &mut TestAppContext,
7479    ) {
7480        let input_view = InputView::new(cx);
7481        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7482        let input = input_view.input;
7483        let text = (0..160)
7484            .map(|row| {
7485                if matches!(row, 20 | 60 | 100) {
7486                    format!("match on row {row}")
7487                } else {
7488                    format!("line {row}")
7489                }
7490            })
7491            .collect::<Vec<_>>()
7492            .join("\n");
7493        let start = text.match_indices("match").nth(1).unwrap().0;
7494        let expected_match = start..start + "match".len();
7495        cx.update(|window, cx| {
7496            input.update(cx, |state, cx| {
7497                state.set_cursor_surrounding_lines(Some(3), window, cx);
7498                state.set_value(text, window, cx);
7499                state.set_search_query("match", true, cx);
7500                if previous {
7501                    state.search_session.matcher.next();
7502                    state.search_session.matcher.next();
7503                }
7504            });
7505        });
7506        cx.run_until_parked();
7507        cx.update(|_, cx| {
7508            input.update(cx, |state, cx| {
7509                let line_height = state.last_layout.as_ref().unwrap().line_height;
7510                let y = if previous { px(0.) } else { -line_height * 80. };
7511                state.set_scroll_offset(point(px(0.), y), cx);
7512            });
7513        });
7514        cx.run_until_parked();
7515        input.read_with(&cx, |state, _| {
7516            let visible = state.visible_row_range().unwrap();
7517            if previous {
7518                assert!(visible.end <= 60, "target must be below the viewport");
7519            } else {
7520                assert!(visible.start > 60, "target must be above the viewport");
7521            }
7522        });
7523        cx.update(|_, cx| {
7524            input.update(cx, |state, cx| {
7525                let range = if previous {
7526                    state.previous_search_match(cx)
7527                } else {
7528                    state.next_search_match(cx)
7529                };
7530                assert_eq!(range, Some(expected_match));
7531                assert_eq!(state.search_session.matcher.label(), "2/3");
7532            });
7533        });
7534        cx.run_until_parked();
7535        input.read_with(&cx, |state, _| {
7536            assert!(state.visible_row_range().unwrap().contains(&60));
7537            let line_height = state.last_layout.as_ref().unwrap().line_height;
7538            let target_y = line_height * 60. + state.scroll_handle.offset().y;
7539            // Three lines of edge clearance include the matched line itself.
7540            assert!(target_y >= line_height * 2. - px(0.1));
7541            assert!(
7542                target_y + line_height * 3.
7543                    <= state.last_bounds.as_ref().unwrap().size.height + px(0.1),
7544                "search must preserve the configured surrounding-line padding"
7545            );
7546        });
7547    }
7548
7549    /// A host that wants the search shortcut for its own search UI.
7550    struct SearchHost {
7551        editor: Entity<InputBaseState<EditorMode>>,
7552        search_requests: Rc<Cell<usize>>,
7553    }
7554
7555    impl Render for SearchHost {
7556        fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
7557            let search_requests = self.search_requests.clone();
7558            div()
7559                .size_full()
7560                .on_action(cx.listener(move |_, _: &Search, _, _| {
7561                    search_requests.set(search_requests.get() + 1);
7562                }))
7563                .child(self.editor.clone())
7564        }
7565    }
7566
7567    /// Opens an editor inside [`SearchHost`], focused, and presses the search
7568    /// shortcut once. Returns the editor and the host's request count.
7569    fn press_search_shortcut(
7570        cx: &mut TestAppContext,
7571        searchable: bool,
7572    ) -> (Entity<InputBaseState<EditorMode>>, Rc<Cell<usize>>) {
7573        let search_requests = Rc::new(Cell::new(0));
7574        let mut editor = None;
7575        let window = cx.update(|cx| {
7576            cx.open_window(Default::default(), |window, cx| {
7577                cx.set_global(Theme::default());
7578                super::super::init(cx);
7579                let state =
7580                    cx.new(|cx| crate::input::EditorState::new(window, cx).searchable(searchable));
7581                editor = Some(state.clone());
7582                cx.new(|_| SearchHost {
7583                    editor: state,
7584                    search_requests: search_requests.clone(),
7585                })
7586            })
7587            .unwrap()
7588        });
7589        let editor = editor.unwrap();
7590        let mut cx = VisualTestContext::from_window(window.into(), cx);
7591        cx.update(|window, cx| {
7592            editor.update(cx, |state, cx| state.focus(window, cx));
7593        });
7594        cx.run_until_parked();
7595        #[cfg(target_os = "macos")]
7596        cx.simulate_keystrokes("cmd-f");
7597        #[cfg(not(target_os = "macos"))]
7598        cx.simulate_keystrokes("ctrl-f");
7599        cx.run_until_parked();
7600        (editor, search_requests)
7601    }
7602
7603    #[gpui::test]
7604    fn test_search_shortcut_reaches_the_host_when_not_searchable(cx: &mut TestAppContext) {
7605        let (editor, search_requests) = press_search_shortcut(cx, false);
7606        assert_eq!(search_requests.get(), 1);
7607        editor.read_with(cx, |state, _| {
7608            assert!(!state.search_session().open);
7609            assert!(!state.search_session().is_active());
7610        });
7611    }
7612
7613    #[gpui::test]
7614    fn test_search_shortcut_opens_the_panel_when_searchable(cx: &mut TestAppContext) {
7615        let (editor, search_requests) = press_search_shortcut(cx, true);
7616        assert_eq!(search_requests.get(), 0);
7617        editor.read_with(cx, |state, _| {
7618            assert!(state.search_session().open);
7619            assert!(state.search_session().is_active());
7620        });
7621    }
7622
7623    #[gpui::test]
7624    fn test_set_search_query_highlights_without_the_panel(cx: &mut TestAppContext) {
7625        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
7626        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7627        let input = input_view.input;
7628        cx.update(|window, cx| {
7629            input.update(cx, |state, cx| {
7630                state.set_value("foo bar foo", window, cx);
7631                state.set_search_query("foo", true, cx);
7632            });
7633        });
7634        cx.run_until_parked();
7635        input.read_with(&cx, |state, _| {
7636            let session = state.search_session();
7637            assert!(session.is_active());
7638            assert!(!session.open);
7639            assert_eq!(session.matcher.len(), 2);
7640        });
7641        cx.update(|_, cx| {
7642            input.update(cx, |state, cx| state.close_search(cx));
7643        });
7644        input.read_with(&cx, |state, _| {
7645            assert!(!state.search_session().is_active());
7646        });
7647    }
7648
7649    /// A closed search skips rescanning on edits, so every path that reads
7650    /// the matches again must see the edited text, not the text at close.
7651    #[gpui::test]
7652    fn test_closed_search_resyncs_matches_after_edits(cx: &mut TestAppContext) {
7653        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
7654        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7655        let input = input_view.input;
7656        cx.update(|window, cx| {
7657            input.update(cx, |state, cx| {
7658                state.set_value("foo bar foo", window, cx);
7659                state.set_search_query("foo", true, cx);
7660                state.close_search(cx);
7661
7662                state.set_value("bar foo", window, cx);
7663                assert_eq!(state.next_search_match(cx), Some(4..7));
7664
7665                state.set_value("foo foo foo", window, cx);
7666                state.set_search_query("foo", true, cx);
7667                assert_eq!(state.search_session().matcher.len(), 3);
7668            });
7669        });
7670    }
7671
7672    #[gpui::test]
7673    fn test_search_reveals_offscreen_wrapped_match(cx: &mut TestAppContext) {
7674        let input_view = InputView::new(cx);
7675        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7676        let input = input_view.input;
7677        let text = format!(
7678            "match\n{}\n{}match\n{}",
7679            "line\n".repeat(80),
7680            "wrapped text ".repeat(500),
7681            "line\n".repeat(80)
7682        );
7683        cx.update(|window, cx| {
7684            input.update(cx, |state, cx| {
7685                state.set_cursor_surrounding_lines(Some(3), window, cx);
7686                state.set_value(text, window, cx);
7687                state.set_search_query("match", true, cx);
7688            });
7689        });
7690        cx.run_until_parked();
7691        cx.update(|_, cx| {
7692            input.update(cx, |state, cx| {
7693                state.next_search_match(cx).unwrap();
7694            });
7695        });
7696        cx.run_until_parked();
7697        input.read_with(&cx, |state, _| {
7698            let range = state.search_session.matcher.matched_ranges()[1].clone();
7699            let layout = state.last_layout.as_ref().unwrap();
7700            let (_, _, position) = state.line_and_position_for_offset(range.end);
7701            let y = position.expect("wrapped match must be laid out").y
7702                + state.scroll_handle.offset().y;
7703            assert!(y >= layout.line_height * 2. - px(0.1));
7704            assert!(
7705                y + layout.line_height * 3. <= state.last_bounds.unwrap().size.height + px(0.1),
7706                "wrapped match must retain surrounding display rows"
7707            );
7708        });
7709    }
7710
7711    #[gpui::test]
7712    fn test_number_step(cx: &mut TestAppContext) {
7713        let input = InputView::build(cx, |state| state).input;
7714
7715        cx.update(|cx| {
7716            input.update(cx, |_state, cx| {
7717                assert_eq!(
7718                    NumberStep::from(5.).value(123., StepAction::Increment, cx),
7719                    5.
7720                );
7721
7722                // The step can differ by direction at a boundary: at 1.0 it
7723                // is 0.1 going down and 0.5 going up.
7724                let step = NumberStep::by_value(|value, action, _cx| {
7725                    let below = match action {
7726                        StepAction::Increment => value < 1.0,
7727                        StepAction::Decrement => value <= 1.0,
7728                    };
7729                    if below { 0.1 } else { 0.5 }
7730                });
7731                assert_eq!(step.value(0.5, StepAction::Increment, cx), 0.1);
7732                assert_eq!(step.value(1.0, StepAction::Increment, cx), 0.5);
7733                assert_eq!(step.value(1.0, StepAction::Decrement, cx), 0.1);
7734                assert_eq!(step.value(2.0, StepAction::Decrement, cx), 0.5);
7735            });
7736        });
7737    }
7738
7739    #[gpui::test]
7740    fn test_number_input_normalization(cx: &mut TestAppContext) {
7741        let input_view = InputView::build(cx, |state| {
7742            state.mask_pattern(MaskPattern::Number {
7743                separator: None,
7744                fraction: None,
7745            })
7746        });
7747        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7748        let input = input_view.input;
7749
7750        // Full-width digits and the ideographic full stop are normalized,
7751        // and the cursor is at the end (in normalized bytes, not the
7752        // original 12 bytes).
7753        cx.update(|window, cx| {
7754            input.update(cx, |state, cx| {
7755                state.replace_text_in_range(None, "12。5", window, cx);
7756            });
7757        });
7758        cx.run_until_parked();
7759        cx.update(|_, cx| {
7760            input.read_with(cx, |state, _| {
7761                assert_eq!(state.value(), "12.5");
7762                let cursor: Range<usize> = state.selected_range();
7763                assert_eq!(cursor, 4..4);
7764            });
7765        });
7766
7767        // Non-numeric input is rejected.
7768        cx.update(|window, cx| {
7769            input.update(cx, |state, cx| {
7770                state.replace_text_in_range(None, "abc", window, cx);
7771            });
7772        });
7773        cx.run_until_parked();
7774        cx.update(|_, cx| {
7775            input.read_with(cx, |state, _| {
7776                assert_eq!(state.value(), "12.5");
7777            });
7778        });
7779
7780        // A bare leading dot is kept as-is (normalized from the ideographic
7781        // full stop), not completed to "0.", so it stays editable.
7782        cx.update(|window, cx| {
7783            input.update(cx, |state, cx| {
7784                let range = state.range_to_utf16(&(0..state.text.len()));
7785                state.replace_text_in_range(Some(range), "。", window, cx);
7786            });
7787        });
7788        cx.run_until_parked();
7789        cx.update(|_, cx| {
7790            input.read_with(cx, |state, _| {
7791                assert_eq!(state.value(), ".");
7792                let cursor: Range<usize> = state.selected_range();
7793                assert_eq!(cursor, 1..1);
7794            });
7795        });
7796    }
7797
7798    #[gpui::test]
7799    fn test_mask_pattern_accepts_non_ascii_text(cx: &mut TestAppContext) {
7800        let input_view = InputView::build(cx, |state| state.mask_pattern("9999年99月"));
7801        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7802        let input = input_view.input;
7803
7804        cx.update(|window, cx| {
7805            input.update(cx, |state, cx| {
7806                state.replace_text_in_range(None, "2024年12月", window, cx);
7807            });
7808        });
7809        cx.run_until_parked();
7810        cx.update(|_, cx| {
7811            input.read_with(cx, |state, _| {
7812                assert_eq!(state.value(), "2024年12月");
7813            });
7814        });
7815    }
7816
7817    #[gpui::test]
7818    fn test_number_input_normalization_with_separator(cx: &mut TestAppContext) {
7819        let input_view = InputView::build(cx, |state| {
7820            state.mask_pattern(MaskPattern::Number {
7821                separator: Some(','),
7822                fraction: Some(2),
7823            })
7824        });
7825        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7826        let input = input_view.input;
7827
7828        cx.update(|window, cx| {
7829            input.update(cx, |state, cx| {
7830                state.replace_text_in_range(None, "1234", window, cx);
7831            });
7832        });
7833        cx.run_until_parked();
7834        cx.update(|_, cx| {
7835            input.read_with(cx, |state, _| {
7836                assert_eq!(state.value(), "1,234");
7837                assert_eq!(state.unmask_value(), "1234");
7838            });
7839        });
7840    }
7841
7842    #[gpui::test]
7843    fn test_number_input_clamp_on_blur(cx: &mut TestAppContext) {
7844        let input_view = InputView::build(cx, |state| {
7845            state
7846                .mask_pattern(MaskPattern::Number {
7847                    separator: None,
7848                    fraction: None,
7849                })
7850                .min(10.)
7851                .max(100.)
7852        });
7853        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7854        let input = input_view.input;
7855
7856        // Out-of-range values are allowed while typing, and clamped on blur.
7857        cx.update(|window, cx| {
7858            input.update(cx, |state, cx| {
7859                state.replace_text_in_range(None, "1000", window, cx);
7860                assert_eq!(state.value(), "1000");
7861                state.clamp_number_value(window, cx);
7862                assert_eq!(state.value(), "100");
7863
7864                let range = state.range_to_utf16(&(0..state.text.len()));
7865                state.replace_text_in_range(Some(range), "1", window, cx);
7866                assert_eq!(state.value(), "1");
7867                state.clamp_number_value(window, cx);
7868                assert_eq!(state.value(), "10");
7869            });
7870        });
7871    }
7872
7873    #[gpui::test]
7874    fn test_number_input_undo_with_mask(cx: &mut TestAppContext) {
7875        let input_view = InputView::build(cx, |state| {
7876            state.mask_pattern(MaskPattern::Number {
7877                separator: Some(','),
7878                fraction: None,
7879            })
7880        });
7881        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7882        let input = input_view.input;
7883
7884        // When the mask changes the text (regrouping separators), a
7885        // whole-document change is recorded, so undo/redo can restore it.
7886        cx.update(|window, cx| {
7887            input.update(cx, |state, cx| {
7888                state.replace_text_in_range(None, "1234", window, cx);
7889                assert_eq!(state.value(), "1,234");
7890                state.replace_text_in_range(None, "5", window, cx);
7891                assert_eq!(state.value(), "12,345");
7892
7893                // Each whole-document mask rewrite is an atomic undo step.
7894                // Before the whole-document history fix, undo produced a
7895                // corrupted value like "1,2344".
7896                state.undo(&Undo, window, cx);
7897                assert_eq!(state.value(), "1,234");
7898                state.undo(&Undo, window, cx);
7899                assert_eq!(state.value(), "");
7900                state.redo(&Redo, window, cx);
7901                assert_eq!(state.value(), "1,234");
7902                state.redo(&Redo, window, cx);
7903                assert_eq!(state.value(), "12,345");
7904            });
7905        });
7906    }
7907
7908    #[gpui::test]
7909    fn test_undo_manager_coalesces_adjacent_typing_transactions(cx: &mut TestAppContext) {
7910        let input_view = InputView::build(cx, |state| state);
7911        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7912        let input = input_view.input;
7913
7914        cx.update(|window, cx| {
7915            input.update(cx, |state, cx| {
7916                state.replace_text_in_range(None, "a", window, cx);
7917                state.replace_text_in_range(None, "b", window, cx);
7918                assert_eq!(state.value(), "ab");
7919
7920                state.undo(&Undo, window, cx);
7921                assert_eq!(state.value(), "");
7922            });
7923        });
7924    }
7925
7926    #[gpui::test]
7927    fn test_undo_manager_cursor_movement_splits_typing(cx: &mut TestAppContext) {
7928        let input_view = InputView::build(cx, |state| state);
7929        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7930        let input = input_view.input;
7931
7932        cx.update(|window, cx| {
7933            input.update(cx, |state, cx| {
7934                state.replace_text_in_range(None, "a", window, cx);
7935                state.replace_text_in_range(None, "b", window, cx);
7936                state.left(&MoveLeft, window, cx);
7937                state.replace_text_in_range(None, "x", window, cx);
7938                assert_eq!(state.value(), "axb");
7939
7940                state.undo(&Undo, window, cx);
7941                assert_eq!(state.value(), "ab");
7942                state.undo(&Undo, window, cx);
7943                assert_eq!(state.value(), "");
7944            });
7945        });
7946    }
7947
7948    #[gpui::test]
7949    fn test_undo_manager_splits_backward_and_forward_delete(cx: &mut TestAppContext) {
7950        let input_view = InputView::build(cx, |state| state);
7951        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7952        let input = input_view.input;
7953
7954        cx.update(|window, cx| {
7955            input.update(cx, |state, cx| {
7956                state.set_value("abcd", window, cx);
7957                state.set_selected_range(2..2, cx);
7958                state.backspace(&Backspace, window, cx);
7959                state.delete(&Delete, window, cx);
7960                assert_eq!(state.value(), "ad");
7961
7962                state.undo(&Undo, window, cx);
7963                assert_eq!(state.value(), "acd");
7964                state.undo(&Undo, window, cx);
7965                assert_eq!(state.value(), "abcd");
7966            });
7967        });
7968    }
7969
7970    #[gpui::test]
7971    fn test_undo_manager_coalesces_directional_character_deletes(cx: &mut TestAppContext) {
7972        let input_view = InputView::build(cx, |state| state);
7973        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7974        let input = input_view.input;
7975
7976        cx.update(|window, cx| {
7977            input.update(cx, |state, cx| {
7978                state.set_value("abcd", window, cx);
7979                state.backspace(&Backspace, window, cx);
7980                state.backspace(&Backspace, window, cx);
7981                assert_eq!(state.value(), "ab");
7982                state.undo(&Undo, window, cx);
7983                assert_eq!(state.value(), "abcd");
7984                assert_eq!(state.selected_range(), 4..4);
7985
7986                state.set_value("abcd", window, cx);
7987                state.set_selected_range(1..1, cx);
7988                state.delete(&Delete, window, cx);
7989                state.delete(&Delete, window, cx);
7990                assert_eq!(state.value(), "ad");
7991                state.undo(&Undo, window, cx);
7992                assert_eq!(state.value(), "abcd");
7993                assert_eq!(state.selected_range(), 1..1);
7994            });
7995        });
7996    }
7997
7998    #[gpui::test]
7999    fn test_undo_manager_atomic_paste_isolated_from_typing(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            cx.write_to_clipboard(ClipboardItem::new_string("P".to_string()));
8006            input.update(cx, |state, cx| {
8007                state.replace_text_in_range(None, "a", window, cx);
8008                state.paste(&Paste, window, cx);
8009                state.replace_text_in_range(None, "b", window, cx);
8010                assert_eq!(state.value(), "aPb");
8011
8012                state.undo(&Undo, window, cx);
8013                assert_eq!(state.value(), "aP");
8014                state.undo(&Undo, window, cx);
8015                assert_eq!(state.value(), "a");
8016                state.undo(&Undo, window, cx);
8017                assert_eq!(state.value(), "");
8018            });
8019        });
8020    }
8021
8022    #[gpui::test]
8023    fn test_undo_manager_programmatic_insert_is_atomic(cx: &mut TestAppContext) {
8024        let input_view = InputView::build(cx, |state| state);
8025        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8026        let input = input_view.input;
8027
8028        cx.update(|window, cx| {
8029            input.update(cx, |state, cx| {
8030                state.replace_text_in_range(None, "a", window, cx);
8031                state.insert("P", window, cx);
8032                state.replace_text_in_range(None, "b", window, cx);
8033                assert_eq!(state.value(), "aPb");
8034
8035                state.undo(&Undo, window, cx);
8036                assert_eq!(state.value(), "aP");
8037                state.undo(&Undo, window, cx);
8038                assert_eq!(state.value(), "a");
8039            });
8040        });
8041    }
8042
8043    #[gpui::test]
8044    fn test_undo_manager_selection_round_trip_splits_typing(cx: &mut TestAppContext) {
8045        let input_view = InputView::build(cx, |state| state);
8046        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8047        let input = input_view.input;
8048
8049        cx.update(|window, cx| {
8050            input.update(cx, |state, cx| {
8051                state.replace_text_in_range(None, "a", window, cx);
8052                state.replace_text_in_range(None, "b", window, cx);
8053                state.select_all(window, cx);
8054                state.unselect(window, cx);
8055                state.replace_text_in_range(None, "c", window, cx);
8056
8057                state.undo(&Undo, window, cx);
8058                assert_eq!(state.value(), "ab");
8059                state.undo(&Undo, window, cx);
8060                assert_eq!(state.value(), "");
8061            });
8062        });
8063    }
8064
8065    #[gpui::test]
8066    fn test_undo_manager_enter_is_atomic(cx: &mut TestAppContext) {
8067        let input_view = InputView::build_textarea(cx, |state| state);
8068        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8069        let input = input_view.input;
8070
8071        cx.update(|window, cx| {
8072            input.update(cx, |state, cx| {
8073                state.replace_text_in_range(None, "a", window, cx);
8074                state.enter(
8075                    &Enter {
8076                        secondary: false,
8077                        shift: false,
8078                    },
8079                    window,
8080                    cx,
8081                );
8082                state.replace_text_in_range(None, "b", window, cx);
8083
8084                state.undo(&Undo, window, cx);
8085                assert_eq!(state.value(), "a\n");
8086                state.undo(&Undo, window, cx);
8087                assert_eq!(state.value(), "a");
8088                state.undo(&Undo, window, cx);
8089                assert_eq!(state.value(), "");
8090            });
8091        });
8092    }
8093
8094    #[gpui::test]
8095    fn test_undo_manager_single_line_return_commits_the_typing_session(cx: &mut TestAppContext) {
8096        let input_view = InputView::build(cx, |state| state);
8097        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8098        let input = input_view.input;
8099
8100        cx.update(|window, cx| {
8101            input.update(cx, |state, cx| {
8102                for part in ["a", "b", "c"] {
8103                    state.replace_text_in_range(None, part, window, cx);
8104                }
8105                state.enter(
8106                    &Enter {
8107                        secondary: false,
8108                        shift: false,
8109                    },
8110                    window,
8111                    cx,
8112                );
8113                for part in ["d", "e", "f"] {
8114                    state.replace_text_in_range(None, part, window, cx);
8115                }
8116                assert_eq!(state.value(), "abcdef");
8117
8118                state.undo(&Undo, window, cx);
8119                assert_eq!(state.value(), "abc");
8120                state.undo(&Undo, window, cx);
8121                assert_eq!(state.value(), "");
8122            });
8123        });
8124    }
8125
8126    #[gpui::test]
8127    fn test_undo_manager_submit_on_enter_commits_the_textarea_session(cx: &mut TestAppContext) {
8128        let input_view = InputView::build_textarea(cx, |state| state.submit_on_enter(true));
8129        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8130        let input = input_view.input;
8131
8132        cx.update(|window, cx| {
8133            input.update(cx, |state, cx| {
8134                state.replace_text_in_range(None, "before submit", window, cx);
8135                state.enter(
8136                    &Enter {
8137                        secondary: false,
8138                        shift: false,
8139                    },
8140                    window,
8141                    cx,
8142                );
8143                state.replace_text_in_range(None, " after submit", window, cx);
8144                assert_eq!(state.value(), "before submit after submit");
8145
8146                state.undo(&Undo, window, cx);
8147                assert_eq!(state.value(), "before submit");
8148                state.undo(&Undo, window, cx);
8149                assert_eq!(state.value(), "");
8150            });
8151        });
8152    }
8153
8154    #[gpui::test]
8155    fn test_undo_manager_blur_commits_the_typing_session(cx: &mut TestAppContext) {
8156        let input_view = InputView::build(cx, |state| state);
8157        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8158        let input = input_view.input;
8159
8160        cx.update(|window, cx| {
8161            input.update(cx, |state, cx| {
8162                state.replace_text_in_range(None, "before blur", window, cx);
8163                state.on_blur(window, cx);
8164                state.on_focus(window, cx);
8165                state.replace_text_in_range(None, " after focus", window, cx);
8166                assert_eq!(state.value(), "before blur after focus");
8167
8168                state.undo(&Undo, window, cx);
8169                assert_eq!(state.value(), "before blur");
8170                state.undo(&Undo, window, cx);
8171                assert_eq!(state.value(), "");
8172            });
8173        });
8174    }
8175
8176    #[gpui::test]
8177    fn test_undo_manager_keeps_rapid_lines_in_distinct_transactions(cx: &mut TestAppContext) {
8178        let input_view = InputView::build_textarea(cx, |state| state);
8179        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8180        let input = input_view.input;
8181
8182        cx.update(|window, cx| {
8183            input.update(cx, |state, cx| {
8184                let enter = Enter {
8185                    secondary: false,
8186                    shift: false,
8187                };
8188                state.replace_text_in_range(None, "a", window, cx);
8189                state.enter(&enter, window, cx);
8190                state.replace_text_in_range(None, "b", window, cx);
8191                state.enter(&enter, window, cx);
8192                state.replace_text_in_range(None, "c", window, cx);
8193                assert_eq!(state.value(), "a\nb\nc");
8194
8195                for expected in ["a\nb\n", "a\nb", "a\n", "a", ""] {
8196                    state.undo(&Undo, window, cx);
8197                    assert_eq!(state.value(), expected);
8198                }
8199            });
8200        });
8201    }
8202
8203    #[gpui::test]
8204    fn test_undo_manager_coalesces_long_unicode_typing_without_a_timer(cx: &mut TestAppContext) {
8205        let input_view = InputView::build_textarea(cx, |state| state);
8206        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8207        let input = input_view.input;
8208        let parts = [
8209            "The ",
8210            "quick ",
8211            "brown fox, ",
8212            "你好,世界 ",
8213            "🦀 jumps over 13 lazy dogs.",
8214        ];
8215        let expected = parts.concat();
8216
8217        cx.update(|window, cx| {
8218            input.update(cx, |state, cx| {
8219                for part in parts {
8220                    state.replace_text_in_range(None, part, window, cx);
8221                }
8222                assert_eq!(state.value(), expected);
8223
8224                state.undo(&Undo, window, cx);
8225                assert_eq!(state.value(), "");
8226                state.redo(&Redo, window, cx);
8227                assert_eq!(state.value(), expected);
8228            });
8229        });
8230    }
8231
8232    #[gpui::test]
8233    fn test_undo_manager_long_multiline_sequence_has_structural_boundaries(
8234        cx: &mut TestAppContext,
8235    ) {
8236        let input_view = InputView::build_textarea(cx, |state| state);
8237        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8238        let input = input_view.input;
8239        let enter = Enter {
8240            secondary: false,
8241            shift: false,
8242        };
8243
8244        cx.update(|window, cx| {
8245            input.update(cx, |state, cx| {
8246                for (index, line) in [
8247                    "first line with punctuation!",
8248                    "第二行包含 Unicode 🦀",
8249                    "third line has several words",
8250                ]
8251                .into_iter()
8252                .enumerate()
8253                {
8254                    for chunk in line.split_inclusive(' ') {
8255                        state.replace_text_in_range(None, chunk, window, cx);
8256                    }
8257                    if index < 2 {
8258                        state.enter(&enter, window, cx);
8259                    }
8260                }
8261
8262                assert_eq!(
8263                    state.value(),
8264                    "first line with punctuation!\n第二行包含 Unicode 🦀\nthird line has several words"
8265                );
8266                state.undo(&Undo, window, cx);
8267                assert_eq!(
8268                    state.value(),
8269                    "first line with punctuation!\n第二行包含 Unicode 🦀\n"
8270                );
8271                state.undo(&Undo, window, cx);
8272                assert_eq!(
8273                    state.value(),
8274                    "first line with punctuation!\n第二行包含 Unicode 🦀"
8275                );
8276                state.undo(&Undo, window, cx);
8277                assert_eq!(state.value(), "first line with punctuation!\n");
8278            });
8279        });
8280    }
8281
8282    #[gpui::test]
8283    fn test_masked_input_keeps_its_value_out_of_the_clipboard(cx: &mut TestAppContext) {
8284        let input_view = InputView::build(cx, |state| state);
8285        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8286        let input = input_view.input;
8287
8288        cx.update(|window, cx| {
8289            input.update(cx, |state, cx| {
8290                state.set_value("hunter2", window, cx);
8291                state.set_masked(true, window, cx);
8292                state.select_all(window, cx);
8293                cx.write_to_clipboard(ClipboardItem::new_string("sentinel".into()));
8294
8295                state.copy(&Copy, window, cx);
8296                assert_eq!(
8297                    cx.read_from_clipboard().and_then(|item| item.text()),
8298                    Some("sentinel".to_string())
8299                );
8300
8301                // Cut neither copies nor deletes.
8302                state.cut(&Cut, window, cx);
8303                assert_eq!(state.value(), "hunter2");
8304                assert_eq!(
8305                    cx.read_from_clipboard().and_then(|item| item.text()),
8306                    Some("sentinel".to_string())
8307                );
8308
8309                // Revealing the value restores both.
8310                state.set_masked(false, window, cx);
8311                state.copy(&Copy, window, cx);
8312                assert_eq!(
8313                    cx.read_from_clipboard().and_then(|item| item.text()),
8314                    Some("hunter2".to_string())
8315                );
8316            });
8317        });
8318    }
8319
8320    #[gpui::test]
8321    fn test_masked_input_collapses_word_boundaries(cx: &mut TestAppContext) {
8322        let input_view = InputView::build(cx, |state| state);
8323        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8324        let input = input_view.input;
8325
8326        cx.update(|window, cx| {
8327            input.update(cx, |state, cx| {
8328                state.set_value("aaa bbb ccc", window, cx);
8329                state.set_masked(true, window, cx);
8330                state.set_selected_range(7..7, cx);
8331
8332                // The mask hides word boundaries, so a word delete takes
8333                // everything before the caret and leaves the rest.
8334                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
8335                assert_eq!(state.value(), " ccc");
8336                assert_eq!(state.selected_range(), 0..0);
8337
8338                state.delete_next_word(&DeleteToNextWordEnd, window, cx);
8339                assert_eq!(state.value(), "");
8340
8341                // A double click takes the whole value, not one word.
8342                state.set_value("aaa bbb ccc", window, cx);
8343                state.select_word(9, window, cx);
8344                assert_eq!(state.selected_range(), 0..11);
8345
8346                // Unmasked, the same delete only takes one word.
8347                state.set_masked(false, window, cx);
8348                state.set_value("aaa bbb ccc", window, cx);
8349                state.set_selected_range(11..11, cx);
8350                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
8351                assert_eq!(state.value(), "aaa bbb ");
8352
8353                state.set_value("aaa bbb ccc", window, cx);
8354                state.select_word(9, window, cx);
8355                assert_eq!(state.selected_range(), 8..11);
8356            });
8357        });
8358    }
8359
8360    #[gpui::test]
8361    fn test_masked_input_disables_the_copy_context_menu_items(cx: &mut TestAppContext) {
8362        let input_view = InputView::build(cx, |state| state);
8363        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8364        let input = input_view.input;
8365
8366        cx.update(|window, cx| {
8367            input.update(cx, |state, cx| {
8368                state.set_value("hunter2", window, cx);
8369                state.select_all(window, cx);
8370                assert!(state.context_menu_capabilities().is_copyable());
8371
8372                state.set_masked(true, window, cx);
8373                let capabilities = state.context_menu_capabilities();
8374                assert!(capabilities.is_masked());
8375                assert!(capabilities.has_selection());
8376                assert!(!capabilities.is_copyable());
8377            });
8378        });
8379    }
8380
8381    #[gpui::test]
8382    fn test_undo_manager_cut_and_repeated_pastes_are_distinct_transactions(
8383        cx: &mut TestAppContext,
8384    ) {
8385        let input_view = InputView::build(cx, |state| state);
8386        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8387        let input = input_view.input;
8388
8389        cx.update(|window, cx| {
8390            input.update(cx, |state, cx| {
8391                state.replace_text_in_range(None, "alpha beta gamma", window, cx);
8392                state.set_selected_range(6..10, cx);
8393                state.cut(&Cut, window, cx);
8394                assert_eq!(state.value(), "alpha  gamma");
8395
8396                state.paste(&Paste, window, cx);
8397                state.paste(&Paste, window, cx);
8398                assert_eq!(state.value(), "alpha betabeta gamma");
8399
8400                state.undo(&Undo, window, cx);
8401                assert_eq!(state.value(), "alpha beta gamma");
8402                state.undo(&Undo, window, cx);
8403                assert_eq!(state.value(), "alpha  gamma");
8404                state.undo(&Undo, window, cx);
8405                assert_eq!(state.value(), "alpha beta gamma");
8406            });
8407        });
8408    }
8409
8410    #[gpui::test]
8411    fn test_undo_manager_word_and_line_deletes_do_not_coalesce(cx: &mut TestAppContext) {
8412        let input_view = InputView::build_textarea(cx, |state| state);
8413        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8414        let input = input_view.input;
8415
8416        cx.update(|window, cx| {
8417            input.update(cx, |state, cx| {
8418                state.set_value("one two three\nfour five", window, cx);
8419                state.set_selected_range(13..13, cx);
8420                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
8421                assert_eq!(state.value(), "one two \nfour five");
8422                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
8423                assert_eq!(state.value(), "one two four five");
8424
8425                state.undo(&Undo, window, cx);
8426                assert_eq!(state.value(), "one two \nfour five");
8427                state.undo(&Undo, window, cx);
8428                assert_eq!(state.value(), "one two three\nfour five");
8429            });
8430        });
8431    }
8432
8433    #[gpui::test]
8434    fn test_undo_manager_multiline_replacement_is_one_atomic_transaction(cx: &mut TestAppContext) {
8435        let input_view = InputView::build_textarea(cx, |state| state);
8436        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8437        let input = input_view.input;
8438
8439        cx.update(|window, cx| {
8440            input.update(cx, |state, cx| {
8441                state.replace_text_in_range(None, "before", window, cx);
8442                state.set_selected_range(0..6, cx);
8443                state.replace_text_in_range(None, "line one\nline two\n第三行", window, cx);
8444                assert_eq!(state.value(), "line one\nline two\n第三行");
8445
8446                state.undo(&Undo, window, cx);
8447                assert_eq!(state.value(), "before");
8448                assert_eq!(state.selected_range(), 0..6);
8449                state.redo(&Redo, window, cx);
8450                assert_eq!(state.value(), "line one\nline two\n第三行");
8451            });
8452        });
8453    }
8454
8455    #[gpui::test]
8456    fn test_undo_manager_composition_isolated_from_long_typing(cx: &mut TestAppContext) {
8457        let input_view = InputView::build(cx, |state| state);
8458        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8459        let input = input_view.input;
8460
8461        cx.update(|window, cx| {
8462            input.update(cx, |state, cx| {
8463                state.replace_text_in_range(None, "prefix ", window, cx);
8464                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
8465                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
8466                state.replace_and_mark_text_in_range(None, "你", None, window, cx);
8467                state.unmark_text(window, cx);
8468                state.replace_text_in_range(None, " suffix", window, cx);
8469                assert_eq!(state.value(), "prefix 你 suffix");
8470
8471                state.undo(&Undo, window, cx);
8472                assert_eq!(state.value(), "prefix 你");
8473                state.undo(&Undo, window, cx);
8474                assert_eq!(state.value(), "prefix ");
8475                state.undo(&Undo, window, cx);
8476                assert_eq!(state.value(), "");
8477            });
8478        });
8479    }
8480
8481    #[gpui::test]
8482    fn test_undo_manager_selected_replacement_is_atomic(cx: &mut TestAppContext) {
8483        let input_view = InputView::build(cx, |state| state);
8484        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8485        let input = input_view.input;
8486
8487        cx.update(|window, cx| {
8488            input.update(cx, |state, cx| {
8489                state.replace_text_in_range(None, "abc", window, cx);
8490                state.set_selected_range(1..2, cx);
8491                state.replace_text_in_range(None, "X", window, cx);
8492                state.replace_text_in_range(None, "z", window, cx);
8493                assert_eq!(state.value(), "aXzc");
8494
8495                state.undo(&Undo, window, cx);
8496                assert_eq!(state.value(), "aXc");
8497                state.undo(&Undo, window, cx);
8498                assert_eq!(state.value(), "abc");
8499                state.undo(&Undo, window, cx);
8500                assert_eq!(state.value(), "");
8501            });
8502        });
8503    }
8504
8505    #[gpui::test]
8506    fn test_number_input_leading_dot_editable(cx: &mut TestAppContext) {
8507        let input_view = InputView::build(cx, |state| {
8508            state.mask_pattern(MaskPattern::Number {
8509                separator: None,
8510                fraction: None,
8511            })
8512        });
8513        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8514        let input = input_view.input;
8515
8516        cx.update(|window, cx| {
8517            input.update(cx, |state, cx| {
8518                state.replace_text_in_range(None, "1.2", window, cx);
8519
8520                // Delete the integer part "1": the value keeps the leading dot
8521                // (".2"), not completed to "0.2", so the digits before the dot
8522                // stay editable.
8523                let range = state.range_to_utf16(&(0..1));
8524                state.replace_text_in_range(Some(range), "", window, cx);
8525                assert_eq!(state.value(), ".2");
8526                let cursor: Range<usize> = state.selected_range();
8527                assert_eq!(cursor, 0..0);
8528
8529                // The user can type a new integer part.
8530                state.replace_text_in_range(Some(0..0), "3", window, cx);
8531                assert_eq!(state.value(), "3.2");
8532            });
8533        });
8534    }
8535
8536    #[gpui::test]
8537    fn test_number_input_escape_invalid_text(cx: &mut TestAppContext) {
8538        // A pre-existing invalid text (e.g. a `default_value` that does not
8539        // conform) must not trap the user, the edit is allowed to fix it.
8540        let input_view = InputView::build(cx, |state| {
8541            state
8542                .mask_pattern(MaskPattern::Number {
8543                    separator: None,
8544                    fraction: None,
8545                })
8546                .default_value("1,234")
8547        });
8548        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8549        let input = input_view.input;
8550
8551        cx.update(|window, cx| {
8552            input.update(cx, |state, cx| {
8553                // Delete the last char, the pending text "1,23" is still
8554                // invalid, but the edit is allowed since the old text was
8555                // already invalid.
8556                let range = state.range_to_utf16(&(4..5));
8557                state.replace_text_in_range(Some(range), "", window, cx);
8558                assert_eq!(state.value(), "1,23");
8559
8560                // Once the text becomes valid, the validation works as usual.
8561                let range = state.range_to_utf16(&(1..2));
8562                state.replace_text_in_range(Some(range), "", window, cx);
8563                assert_eq!(state.value(), "123");
8564                state.replace_text_in_range(None, "a", window, cx);
8565                assert_eq!(state.value(), "123");
8566            });
8567        });
8568    }
8569
8570    /// After `set_value` on a single-line input the caret sits at the end (like
8571    /// HTML `<input>`), yet the view is scrolled back to the start so a long
8572    /// value shows its beginning instead of its tail.
8573    #[gpui::test]
8574    fn test_set_value_single_line_caret_at_end_view_at_start(cx: &mut TestAppContext) {
8575        let input_view = InputView::build(cx, |state| state);
8576        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8577        let input = input_view.input;
8578
8579        // Long enough to overflow any reasonable single-line input width.
8580        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
8581        let len = value.len();
8582
8583        // Right after `set_value`, before the next paint consumes the deferred
8584        // offset: caret is at the end, and the view is forced back to the start.
8585        cx.update(|window, cx| {
8586            input.update(cx, |state, cx| {
8587                state.set_value(value.clone(), window, cx);
8588
8589                assert_eq!(
8590                    state.selected_range(),
8591                    len..len,
8592                    "single-line caret should be at the end after set_value"
8593                );
8594                assert_eq!(
8595                    state.deferred_scroll_offset,
8596                    Some(point(px(0.), px(0.))),
8597                    "the view should be forced back to the start"
8598                );
8599            });
8600        });
8601
8602        // After a paint, the steady-state view stays at the start (x == 0) even
8603        // though the caret is at the far end.
8604        cx.run_until_parked();
8605        cx.update(|_, cx| {
8606            input.read_with(cx, |state, _| {
8607                assert!(
8608                    state.scroll_size.width > state.input_bounds.size.width,
8609                    "value must overflow the input width or this test is vacuous"
8610                );
8611                assert_eq!(
8612                    state.scroll_handle.offset().x,
8613                    px(0.),
8614                    "long value should display from its start, not its tail"
8615                );
8616            });
8617        });
8618    }
8619
8620    /// `replace_all` on a single-line input replaces the text, puts the
8621    /// caret at the end, and — like `set_value` — snaps the view back to the
8622    /// start so a long value shows its beginning instead of its tail.
8623    #[gpui::test]
8624    fn test_replace_all_single_line(cx: &mut TestAppContext) {
8625        let input_view = InputView::build(cx, |state| state);
8626        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8627        let input = input_view.input;
8628
8629        // Long enough to overflow any reasonable single-line input width.
8630        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
8631        let len = value.len();
8632
8633        // Right after `replace_all`, before the next paint consumes the
8634        // deferred offset: caret is at the end, and the view is forced back
8635        // to the start.
8636        cx.update(|window, cx| {
8637            input.update(cx, |state, cx| {
8638                state.set_value("hello", window, cx);
8639                state.replace_all(value.clone(), window, cx);
8640                assert_eq!(state.value(), value);
8641                assert_eq!(
8642                    state.selected_range(),
8643                    len..len,
8644                    "single-line caret should be at the end after replace_all"
8645                );
8646                assert_eq!(
8647                    state.scroll_handle.offset(),
8648                    point(px(0.), px(0.)),
8649                    "the scroll offset should be reset to the start"
8650                );
8651                assert_eq!(
8652                    state.deferred_scroll_offset,
8653                    Some(point(px(0.), px(0.))),
8654                    "single-line should set a deferred scroll offset to keep the start visible"
8655                );
8656            });
8657        });
8658
8659        // After a paint, the steady-state view stays at the start (x == 0)
8660        // even though the caret is at the far end.
8661        cx.run_until_parked();
8662        cx.update(|_, cx| {
8663            input.read_with(cx, |state, _| {
8664                assert!(
8665                    state.scroll_size.width > state.input_bounds.size.width,
8666                    "value must overflow the input width or this test is vacuous"
8667                );
8668                assert_eq!(
8669                    state.scroll_handle.offset().x,
8670                    px(0.),
8671                    "long value should display from its start, not its tail"
8672                );
8673            });
8674        });
8675    }
8676
8677    #[gpui::test]
8678    fn test_single_line_removes_newlines(cx: &mut TestAppContext) {
8679        let input_view = InputView::build(cx, |state| state.default_value("default\nvalue"));
8680        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8681        let input = input_view.input;
8682
8683        cx.update(|window, cx| {
8684            input.update(cx, |state, cx| {
8685                assert_eq!(state.value(), "defaultvalue");
8686
8687                state.set_value("first\nsecond\r\nthird\rfourth", window, cx);
8688                assert_eq!(state.value(), "firstsecondthirdfourth");
8689
8690                state.set_value("", window, cx);
8691                state.insert("a\nb", window, cx);
8692                assert_eq!(state.value(), "ab");
8693            });
8694
8695            cx.write_to_clipboard(ClipboardItem::new_string("a\r\nb\nc\rd".to_string()));
8696            input.update(cx, |state, cx| {
8697                state.set_value("", window, cx);
8698                state.paste(&Paste, window, cx);
8699                assert_eq!(state.value(), "abcd");
8700            });
8701        });
8702
8703        cx.run_until_parked();
8704    }
8705
8706    /// `replace_all` on a multi-line (non-code-editor) input clears the
8707    /// selection to `0..0` and resets the scroll offset, but does not set a
8708    /// deferred scroll offset (single-line only).
8709    #[gpui::test]
8710    fn test_replace_all_multi_line(cx: &mut TestAppContext) {
8711        let input_view = InputView::build_textarea(cx, |state| state);
8712        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8713        let input = input_view.input;
8714
8715        cx.update(|window, cx| {
8716            input.update(cx, |state, cx| {
8717                state.set_value("foo\nbar", window, cx);
8718                state.replace_all("baz\nqux", window, cx);
8719                assert_eq!(state.value(), "baz\nqux");
8720                assert_eq!(
8721                    state.selected_range(),
8722                    0..0,
8723                    "multi-line selection should be cleared after replace_all"
8724                );
8725                assert_eq!(
8726                    state.scroll_handle.offset(),
8727                    point(px(0.), px(0.)),
8728                    "the scroll offset should be reset to the start"
8729                );
8730                assert!(
8731                    state.deferred_scroll_offset.is_none(),
8732                    "multi-line should not set a deferred scroll offset"
8733                );
8734            });
8735        });
8736    }
8737
8738    /// Unlike `set_value`, `replace_all` records the change so the user can
8739    /// undo it back to the previous text and redo to the new text.
8740    #[gpui::test]
8741    fn test_replace_all_preserves_undo_history(cx: &mut TestAppContext) {
8742        let input_view = InputView::build(cx, |state| state);
8743        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8744        let input = input_view.input;
8745
8746        cx.update(|window, cx| {
8747            input.update(cx, |state, cx| {
8748                // Seed with a value and clear history so the baseline is clean.
8749                state.set_value("first", window, cx);
8750                assert!(
8751                    !state.undo_manager.has_undos(),
8752                    "history should be empty after set_value"
8753                );
8754
8755                // replace_all records a single undoable change.
8756                state.replace_all("second", window, cx);
8757                assert_eq!(state.value(), "second");
8758                assert!(
8759                    state.undo_manager.has_undos(),
8760                    "replace_all should record an undo step"
8761                );
8762
8763                // Undo restores the previous text.
8764                state.undo(&Undo, window, cx);
8765                assert_eq!(state.value(), "first");
8766
8767                // Redo reapplies the replacement.
8768                state.redo(&Redo, window, cx);
8769                assert_eq!(state.value(), "second");
8770            });
8771        });
8772    }
8773
8774    /// `replace_all` on a code editor marks a pending update and resets LSP
8775    /// state, so diagnostics/completions refresh against the new text.
8776    #[gpui::test]
8777    fn test_replace_all_code_editor(cx: &mut TestAppContext) {
8778        let input_view = InputView::new(cx);
8779        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8780        let input = input_view.input;
8781
8782        cx.update(|window, cx| {
8783            input.update(cx, |state, cx| {
8784                // Plant a pending-update flag and some LSP state to verify reset.
8785                state.set_value("select 1", window, cx);
8786                state._pending_update = false;
8787
8788                state.replace_all("select 2", window, cx);
8789                assert_eq!(state.value(), "select 2");
8790                assert!(
8791                    state._pending_update,
8792                    "replace_all on a code editor should request a pending update"
8793                );
8794            });
8795        });
8796    }
8797
8798    #[gpui::test]
8799    fn test_set_selected_range(cx: &mut TestAppContext) {
8800        let input_view = InputView::build(cx, |state| state.default_value("hello world"));
8801        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8802        let input = input_view.input;
8803
8804        cx.update(|_, cx| {
8805            input.update(cx, |s, cx| {
8806                s.set_selected_range(0..5, cx);
8807                assert_eq!(s.selected_range(), 0..5);
8808                assert_eq!(s.selected_text().to_string(), "hello");
8809
8810                s.set_selected_range(6..11, cx);
8811                assert_eq!(s.selected_text().to_string(), "world");
8812
8813                // clamped + collapsed
8814                s.set_selected_range(100..100, cx);
8815                assert_eq!(s.selected_range(), 11..11);
8816            });
8817        });
8818    }
8819
8820    /// A single-edit batch round-trips through undo/redo.
8821    #[gpui::test]
8822    fn test_replace_text_in_ranges_single_edit(cx: &mut TestAppContext) {
8823        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
8824        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8825        let input = input_view.input;
8826
8827        cx.update(|window, cx| {
8828            input.update(cx, |s, cx| {
8829                s.replace_text_in_ranges(&[(0..5, "HELLO".to_string())], window, cx);
8830                assert_eq!(s.value(), "HELLO world");
8831
8832                s.undo(&Undo, window, cx);
8833                assert_eq!(s.value(), "hello world");
8834
8835                s.redo(&Redo, window, cx);
8836                assert_eq!(s.value(), "HELLO world");
8837            });
8838        });
8839    }
8840
8841    /// A single undo restores the exact original text and a single redo
8842    /// re-applies all edits, verifying the back-to-front application ordering.
8843    #[gpui::test]
8844    fn test_replace_text_in_ranges_multi_edit_transaction(cx: &mut TestAppContext) {
8845        let input_view = InputView::build_textarea(cx, |state| state.default_value("aaa bbb ccc"));
8846        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8847        let input = input_view.input;
8848
8849        cx.update(|window, cx| {
8850            input.update(cx, |s, cx| {
8851                // Two edits at different positions, given in pre-edit
8852                // coordinates and in arbitrary (non-sorted) order.
8853                s.replace_text_in_ranges(
8854                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
8855                    window,
8856                    cx,
8857                );
8858                assert_eq!(s.value(), "X bbb Y");
8859
8860                // One collapsed cursor per edit, at the end of each inserted text.
8861                let cursors: Vec<usize> =
8862                    s.selections.iter().map(|sel| sel.cursor_offset()).collect();
8863                assert_eq!(cursors, vec![1, 7]);
8864
8865                // The whole batch is a single undo transaction.
8866                assert_eq!(s.undo_manager.undo_count(), 1);
8867
8868                // One undo restores the exact original text.
8869                s.undo(&Undo, window, cx);
8870                assert_eq!(s.value(), "aaa bbb ccc");
8871
8872                // One redo re-applies all edits.
8873                s.redo(&Redo, window, cx);
8874                assert_eq!(s.value(), "X bbb Y");
8875            });
8876        });
8877    }
8878
8879    /// A multi-edit change reaches the highlighter once, with every edit in
8880    /// application order and the text right after each, which is what the
8881    /// default `update_batch` replays through `update` one edit at a time.
8882    #[gpui::test]
8883    fn test_replace_text_in_ranges_drives_the_highlighter_once(cx: &mut TestAppContext) {
8884        use crate::input::{FoldRange, HighlightStyleResolver, InputEdit, InputHighlighter};
8885        use gpui::HighlightStyle;
8886        use std::cell::RefCell;
8887
8888        struct RecordingHighlighter(Rc<RefCell<Vec<Vec<String>>>>);
8889
8890        impl InputHighlighter for RecordingHighlighter {
8891            fn language(&self) -> SharedString {
8892                "recording".into()
8893            }
8894
8895            fn update(
8896                &mut self,
8897                _: Option<InputEdit>,
8898                text: &Rope,
8899                _: bool,
8900                _: &mut Window,
8901                _: &mut Context<EditorState>,
8902            ) {
8903                self.0.borrow_mut().push(vec![text.to_string()]);
8904            }
8905
8906            fn update_batch(
8907                &mut self,
8908                edits: &[(InputEdit, Rope)],
8909                _: bool,
8910                _: &mut Window,
8911                _: &mut Context<EditorState>,
8912            ) {
8913                let texts = edits.iter().map(|(_, text)| text.to_string()).collect();
8914                self.0.borrow_mut().push(texts);
8915            }
8916
8917            fn styles(
8918                &self,
8919                range: &Range<usize>,
8920                _: &dyn HighlightStyleResolver,
8921            ) -> Vec<(Range<usize>, HighlightStyle)> {
8922                vec![(range.clone(), HighlightStyle::default())]
8923            }
8924
8925            fn fold_ranges(&self, _: &Rope) -> Vec<FoldRange> {
8926                Vec::new()
8927            }
8928        }
8929
8930        let updates = Rc::new(RefCell::new(Vec::new()));
8931        let input_view = InputView::new(cx);
8932        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8933        let input = input_view.input;
8934
8935        cx.update(|window, cx| {
8936            input.update(cx, |s, cx| {
8937                s.set_value("aaa bbb ccc", window, cx);
8938                let recorded = updates.clone();
8939                s.set_highlighter_factory(
8940                    Rc::new(move |_: &str| {
8941                        let highlighter: Box<dyn InputHighlighter> =
8942                            Box::new(RecordingHighlighter(recorded.clone()));
8943                        Some(highlighter)
8944                    }),
8945                    cx,
8946                );
8947                s.replace_text_in_ranges(
8948                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
8949                    window,
8950                    cx,
8951                );
8952            });
8953        });
8954
8955        assert_eq!(
8956            *updates.borrow(),
8957            vec![vec!["aaa bbb Y".to_string(), "X bbb Y".to_string()]]
8958        );
8959    }
8960
8961    /// One keypress records a single edit with the text right after it.
8962    #[gpui::test]
8963    fn test_last_input_edits_single_insert(cx: &mut TestAppContext) {
8964        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
8965        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8966        let input = input_view.input;
8967
8968        cx.update(|window, cx| {
8969            input.update(cx, |state, cx| {
8970                state.replace_text_in_ranges(&[(5..5, " big".to_string())], window, cx);
8971
8972                let edits = state.last_input_edits.as_slice();
8973                assert_eq!(edits.len(), 1);
8974                let (edit, text_after) = &edits[0];
8975                assert_eq!(edit.start_byte, 5);
8976                assert_eq!(edit.old_end_byte, 5);
8977                assert_eq!(edit.new_end_byte, 9);
8978                assert_eq!(edit.start_position, crate::input::Point::new(0, 5));
8979                assert_eq!(edit.old_end_position, crate::input::Point::new(0, 5));
8980                assert_eq!(edit.new_end_position, crate::input::Point::new(0, 9));
8981                assert_eq!(text_after.to_string(), "hello big world");
8982            });
8983        });
8984    }
8985
8986    /// Deletion and a multi-line paste each describe their whole replacement.
8987    #[gpui::test]
8988    fn test_last_input_edits_delete_and_paste(cx: &mut TestAppContext) {
8989        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
8990        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8991        let input = input_view.input;
8992
8993        cx.update(|window, cx| {
8994            input.update(cx, |state, cx| {
8995                state.replace_text_in_ranges(&[(5..11, String::new())], window, cx);
8996                assert_eq!(state.value(), "hello");
8997
8998                let edits = state.last_input_edits.as_slice();
8999                assert_eq!(edits.len(), 1);
9000                let (edit, text_after) = &edits[0];
9001                assert_eq!(edit.start_byte, 5);
9002                assert_eq!(edit.old_end_byte, 11);
9003                assert_eq!(edit.new_end_byte, 5);
9004                assert_eq!(text_after.to_string(), "hello");
9005
9006                state.replace_text_in_ranges(&[(5..5, "\nnext".to_string())], window, cx);
9007                assert_eq!(state.value(), "hello\nnext");
9008
9009                let edits = state.last_input_edits.as_slice();
9010                assert_eq!(edits.len(), 1);
9011                let (edit, text_after) = &edits[0];
9012                assert_eq!(edit.start_byte, 5);
9013                assert_eq!(edit.old_end_byte, 5);
9014                assert_eq!(edit.new_end_byte, 10);
9015                assert_eq!(edit.start_position, crate::input::Point::new(0, 5));
9016                assert_eq!(edit.old_end_position, crate::input::Point::new(0, 5));
9017                assert_eq!(edit.new_end_position, crate::input::Point::new(1, 4));
9018                assert_eq!(text_after.to_string(), "hello\nnext");
9019            });
9020        });
9021    }
9022
9023    /// A grouped change records every applied edit in application order.
9024    #[gpui::test]
9025    fn test_last_input_edits_grouped_change(cx: &mut TestAppContext) {
9026        let input_view = InputView::build_textarea(cx, |state| state.default_value("aaa bbb ccc"));
9027        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9028        let input = input_view.input;
9029
9030        cx.update(|window, cx| {
9031            input.update(cx, |state, cx| {
9032                state.replace_text_in_ranges(
9033                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
9034                    window,
9035                    cx,
9036                );
9037                assert_eq!(state.value(), "X bbb Y");
9038
9039                let edits = state.last_input_edits.as_slice();
9040                assert_eq!(edits.len(), 2);
9041                // The higher offset is applied first, before the lower one.
9042                assert_eq!(edits[0].0.start_byte, 8);
9043                assert_eq!(edits[0].0.old_end_byte, 11);
9044                assert_eq!(edits[0].0.new_end_byte, 9);
9045                assert_eq!(edits[0].0.start_position, crate::input::Point::new(0, 8));
9046                assert_eq!(edits[0].0.old_end_position, crate::input::Point::new(0, 11));
9047                assert_eq!(edits[0].0.new_end_position, crate::input::Point::new(0, 9));
9048                assert_eq!(edits[0].1.to_string(), "aaa bbb Y");
9049
9050                assert_eq!(edits[1].0.start_byte, 0);
9051                assert_eq!(edits[1].0.old_end_byte, 3);
9052                assert_eq!(edits[1].0.new_end_byte, 1);
9053                assert_eq!(edits[1].0.start_position, crate::input::Point::new(0, 0));
9054                assert_eq!(edits[1].0.old_end_position, crate::input::Point::new(0, 3));
9055                assert_eq!(edits[1].0.new_end_position, crate::input::Point::new(0, 1));
9056                assert_eq!(edits[1].1.to_string(), "X bbb Y");
9057            });
9058        });
9059    }
9060
9061    /// Edits are recorded even when no highlighter can be installed.
9062    #[gpui::test]
9063    fn test_last_input_edits_without_a_highlighter(cx: &mut TestAppContext) {
9064        let input_view = InputView::new(cx);
9065        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9066        let input = input_view.input;
9067
9068        cx.update(|window, cx| {
9069            input.update(cx, |state, cx| {
9070                state.set_value("fn main() {}", window, cx);
9071                let no_highlighter: InputHighlighterFactory = Rc::new(|_: &str| None);
9072                state.set_highlighter_factory(no_highlighter, cx);
9073                assert!(state.last_input_edits.as_slice().is_empty());
9074
9075                state.replace_text_in_ranges(&[(12..12, "\n".to_string())], window, cx);
9076                assert_eq!(state.value(), "fn main() {}\n");
9077
9078                let edits = state.last_input_edits.as_slice();
9079                assert_eq!(edits.len(), 1);
9080                let (edit, text_after) = &edits[0];
9081                assert_eq!(edit.start_byte, 12);
9082                assert_eq!(edit.old_end_byte, 12);
9083                assert_eq!(edit.new_end_byte, 13);
9084                assert_eq!(edit.start_position, crate::input::Point::new(0, 12));
9085                assert_eq!(edit.old_end_position, crate::input::Point::new(0, 12));
9086                assert_eq!(edit.new_end_position, crate::input::Point::new(1, 0));
9087                assert_eq!(text_after.to_string(), "fn main() {}\n");
9088            });
9089        });
9090    }
9091
9092    /// `set_value` is silent and leaves no edits behind for the next change.
9093    #[gpui::test]
9094    fn test_last_input_edits_cleared_by_set_value(cx: &mut TestAppContext) {
9095        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello"));
9096        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9097        let input = input_view.input;
9098
9099        cx.update(|window, cx| {
9100            input.update(cx, |state, cx| {
9101                state.replace_text_in_ranges(&[(5..5, "!".to_string())], window, cx);
9102                assert_eq!(state.last_input_edits.as_slice().len(), 1);
9103
9104                state.set_value("replaced", window, cx);
9105                assert!(state.last_input_edits.as_slice().is_empty());
9106
9107                state.replace_text_in_ranges(&[(8..8, "?".to_string())], window, cx);
9108                let edits = state.last_input_edits.as_slice();
9109                assert_eq!(edits.len(), 1);
9110                assert_eq!(edits[0].1.to_string(), "replaced?");
9111            });
9112        });
9113    }
9114
9115    /// Apply an emitted batch as an incremental consumer would and verify every snapshot.
9116    fn replay_input_edits(text: &mut String, event: &InputEditsEvent) {
9117        for (edit, after) in event.edits() {
9118            let replacement = after.slice(edit.start_byte..edit.new_end_byte).to_string();
9119            text.replace_range(edit.start_byte..edit.old_end_byte, &replacement);
9120            assert_eq!(*text, after.to_string());
9121        }
9122    }
9123
9124    #[gpui::test]
9125    fn input_edits_events_preserve_queued_changes_and_undo(cx: &mut TestAppContext) {
9126        let view = InputView::build_textarea(cx, |state| state.default_value("hello"));
9127        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9128        let input = view.input;
9129        let observed = Rc::new(std::cell::RefCell::new(Vec::<InputEditsEvent>::new()));
9130        let seen = observed.clone();
9131        let _subscription = cx.update(|_, cx| {
9132            cx.subscribe(&input, move |_, event: &InputEditsEvent, _| {
9133                seen.borrow_mut().push(event.clone());
9134            })
9135        });
9136        cx.update(|window, cx| {
9137            input.update(cx, |state, cx| {
9138                state.replace_text_in_ranges(&[(5..5, "!".into())], window, cx);
9139                state.replace_text_in_ranges(&[(6..6, "?".into())], window, cx);
9140                state.undo(&Undo, window, cx);
9141            });
9142        });
9143        let events = observed.borrow();
9144        assert!(events.len() >= 3);
9145        let mut text = "hello".to_string();
9146        for event in events.iter() {
9147            replay_input_edits(&mut text, event);
9148        }
9149        assert_eq!(
9150            text,
9151            input.read_with(&cx, |state, _| state.value().to_string())
9152        );
9153        assert_eq!(events[0].edits()[0].1.to_string(), "hello!");
9154        assert_eq!(events[1].edits()[0].1.to_string(), "hello!?");
9155    }
9156
9157    #[gpui::test]
9158    fn input_edits_event_replays_ime_commit(cx: &mut TestAppContext) {
9159        let view = InputView::build_textarea(cx, |state| state.default_value(""));
9160        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9161        let input = view.input;
9162        let observed = Rc::new(std::cell::RefCell::new(Vec::<InputEditsEvent>::new()));
9163        let seen = observed.clone();
9164        let _subscription = cx.update(|_, cx| {
9165            cx.subscribe(&input, move |_, event: &InputEditsEvent, _| {
9166                seen.borrow_mut().push(event.clone());
9167            })
9168        });
9169        cx.update(|window, cx| {
9170            input.update(cx, |state, cx| {
9171                state.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
9172                state.replace_and_mark_text_in_range(None, "ni", Some(2..2), window, cx);
9173                state.replace_text_in_range(None, "你", window, cx);
9174            });
9175        });
9176        let events = observed.borrow();
9177        assert_eq!(events.len(), 1);
9178        let mut text = String::new();
9179        replay_input_edits(&mut text, &events[0]);
9180        assert_eq!(text, "你");
9181    }
9182
9183    #[gpui::test]
9184    fn input_edits_event_replays_after_cancelled_composition(cx: &mut TestAppContext) {
9185        let view = InputView::build_textarea(cx, |state| state.default_value(""));
9186        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9187        let input = view.input;
9188        let observed = Rc::new(std::cell::RefCell::new(Vec::<InputEditsEvent>::new()));
9189        let seen = observed.clone();
9190        let _subscription = cx.update(|_, cx| {
9191            cx.subscribe(&input, move |_, event: &InputEditsEvent, _| {
9192                seen.borrow_mut().push(event.clone());
9193            })
9194        });
9195        cx.update(|window, cx| {
9196            input.update(cx, |state, cx| {
9197                state.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
9198                state.replace_and_mark_text_in_range(None, "", None, window, cx);
9199                state.replace_text_in_range(None, "x", window, cx);
9200            });
9201        });
9202        let events = observed.borrow();
9203        assert_eq!(events.len(), 1);
9204        let mut text = String::new();
9205        replay_input_edits(&mut text, &events[0]);
9206        assert_eq!(text, "x");
9207    }
9208
9209    #[gpui::test]
9210    fn input_edits_event_replays_mask_rewrite(cx: &mut TestAppContext) {
9211        let view = InputView::build(cx, |state| {
9212            state
9213                .mask_pattern(MaskPattern::Number {
9214                    separator: Some(','),
9215                    fraction: None,
9216                })
9217                .default_value("999")
9218        });
9219        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9220        let input = view.input;
9221        let observed = Rc::new(std::cell::RefCell::new(Vec::<InputEditsEvent>::new()));
9222        let seen = observed.clone();
9223        let _subscription = cx.update(|_, cx| {
9224            cx.subscribe(&input, move |_, event: &InputEditsEvent, _| {
9225                seen.borrow_mut().push(event.clone());
9226            })
9227        });
9228        cx.update(|window, cx| {
9229            input.update(cx, |state, cx| {
9230                state.set_cursor_to(3);
9231                state.replace_text_in_range(None, "9", window, cx)
9232            });
9233        });
9234        let events = observed.borrow();
9235        assert_eq!(events.len(), 1);
9236        let mut text = "999".to_string();
9237        replay_input_edits(&mut text, &events[0]);
9238        assert_eq!(text, "9,999");
9239    }
9240
9241    /// An IME composition (marking then commit) undoes as a single unit.
9242    #[gpui::test]
9243    fn test_ime_composition_undoes_as_one_unit(cx: &mut TestAppContext) {
9244        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
9245        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9246        let input = input_view.input;
9247
9248        cx.update(|window, cx| {
9249            input.update(cx, |s, cx| {
9250                // Simulate an IME composition: mark, refine, then commit.
9251                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
9252                s.replace_and_mark_text_in_range(None, "ni", Some(2..2), window, cx);
9253                s.replace_text_in_range(None, "你", window, cx);
9254                assert_eq!(s.value(), "你");
9255
9256                // The entire composition is one undo transaction.
9257                assert_eq!(s.undo_manager.undo_count(), 1);
9258
9259                s.undo(&Undo, window, cx);
9260                assert_eq!(s.value(), "");
9261
9262                s.redo(&Redo, window, cx);
9263                assert_eq!(s.value(), "你");
9264            });
9265        });
9266    }
9267
9268    /// A keystroke right after a committed composition must be its own undo
9269    /// entry, not merged into the (finalized) composition transaction.
9270    #[gpui::test]
9271    fn test_edit_after_composition_is_separate_undo(cx: &mut TestAppContext) {
9272        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
9273        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9274        let input = input_view.input;
9275
9276        cx.update(|window, cx| {
9277            input.update(cx, |s, cx| {
9278                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
9279                s.replace_text_in_range(None, "你", window, cx);
9280                assert_eq!(s.value(), "你");
9281                assert_eq!(s.undo_manager.undo_count(), 1);
9282
9283                // Typing after the commit is a distinct transaction.
9284                s.replace_text_in_range(None, "x", window, cx);
9285                assert_eq!(s.value(), "你x");
9286                assert_eq!(s.undo_manager.undo_count(), 2);
9287
9288                s.undo(&Undo, window, cx);
9289                assert_eq!(s.value(), "你");
9290                s.undo(&Undo, window, cx);
9291                assert_eq!(s.value(), "");
9292            });
9293        });
9294    }
9295
9296    /// Canceling a composition via `unmark_text` closes its transaction so it
9297    /// does not leak and swallow a later edit.
9298    #[gpui::test]
9299    fn test_composition_cancel_via_unmark_does_not_leak(cx: &mut TestAppContext) {
9300        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
9301        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9302        let input = input_view.input;
9303
9304        cx.update(|window, cx| {
9305            input.update(cx, |s, cx| {
9306                // Start a composition, then cancel it via unmark.
9307                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
9308                s.unmark_text(window, cx);
9309                let after_cancel = s.undo_manager.undo_count();
9310
9311                // A later edit is recorded independently.
9312                s.replace_text_in_range(None, "x", window, cx);
9313                assert_eq!(s.undo_manager.undo_count(), after_cancel + 1);
9314            });
9315        });
9316    }
9317
9318    #[gpui::test]
9319    fn test_set_selected_range_clips_to_utf8_boundaries(cx: &mut TestAppContext) {
9320        let input_view = InputView::build(cx, |state| state.default_value("éx"));
9321        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9322        let input = input_view.input;
9323
9324        cx.update(|window, cx| {
9325            input.update(cx, |state, cx| {
9326                state.set_selected_range(0..1, cx);
9327                assert_eq!(state.selected_range(), 0..2);
9328                state.copy(&Copy, window, cx);
9329
9330                state.set_selected_range(1..1, cx);
9331                assert_eq!(state.selected_range(), 0..0);
9332            });
9333        });
9334    }
9335
9336    #[gpui::test]
9337    fn test_ime_selection_is_relative_to_replacement_start(cx: &mut TestAppContext) {
9338        let input_view = InputView::build(cx, |state| state.default_value("你好 "));
9339        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9340        let input = input_view.input;
9341
9342        cx.update(|window, cx| {
9343            input.update(cx, |state, cx| {
9344                state.set_selected_range(7..7, cx);
9345                state.replace_and_mark_text_in_range(None, "s", Some(1..1), window, cx);
9346                state.replace_and_mark_text_in_range(None, "sh", Some(2..2), window, cx);
9347
9348                assert_eq!(state.value(), "你好 sh");
9349                assert_eq!(state.selected_range(), 9..9);
9350                assert_eq!(state.ime_marked_range, Some((7..9).into()));
9351            });
9352        });
9353    }
9354
9355    #[gpui::test]
9356    fn test_undo_manager_composition_is_one_undo_group(cx: &mut TestAppContext) {
9357        let input_view = InputView::build(cx, |state| state);
9358        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9359        let input = input_view.input;
9360
9361        cx.update(|window, cx| {
9362            input.update(cx, |state, cx| {
9363                state.set_value("a", window, cx);
9364                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
9365                state.replace_and_mark_text_in_range(None, "sh", None, window, cx);
9366                state.replace_text_in_range(None, "是", window, cx);
9367                assert_eq!(state.value(), "a是");
9368
9369                state.undo(&Undo, window, cx);
9370                assert_eq!(state.value(), "a");
9371                state.redo(&Redo, window, cx);
9372                assert_eq!(state.value(), "a是");
9373            });
9374        });
9375    }
9376
9377    #[gpui::test]
9378    fn test_undo_manager_consecutive_compositions_are_separate_groups(cx: &mut TestAppContext) {
9379        let input_view = InputView::build(cx, |state| state);
9380        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9381        let input = input_view.input;
9382
9383        cx.update(|window, cx| {
9384            input.update(cx, |state, cx| {
9385                // First composition: "jin" -> "今天"
9386                state.replace_and_mark_text_in_range(None, "j", None, window, cx);
9387                state.replace_and_mark_text_in_range(None, "jin", None, window, cx);
9388                state.replace_text_in_range(None, "今天", window, cx);
9389                // Second composition: "wo" -> "我们"
9390                state.replace_and_mark_text_in_range(None, "w", None, window, cx);
9391                state.replace_and_mark_text_in_range(None, "wo", None, window, cx);
9392                state.replace_text_in_range(None, "我们", window, cx);
9393                assert_eq!(state.value(), "今天我们");
9394                assert_eq!(state.selected_range(), 12..12);
9395
9396                state.undo(&Undo, window, cx);
9397                assert_eq!(state.value(), "今天");
9398                assert_eq!(state.selected_range(), 6..6);
9399
9400                state.undo(&Undo, window, cx);
9401                assert_eq!(state.value(), "");
9402                assert_eq!(state.selected_range(), 0..0);
9403
9404                state.redo(&Redo, window, cx);
9405                assert_eq!(state.value(), "今天");
9406                assert_eq!(state.selected_range(), 6..6);
9407
9408                state.redo(&Redo, window, cx);
9409                assert_eq!(state.value(), "今天我们");
9410                assert_eq!(state.selected_range(), 12..12);
9411            });
9412        });
9413    }
9414
9415    #[gpui::test]
9416    fn test_undo_manager_typing_after_composition_is_a_separate_group(cx: &mut TestAppContext) {
9417        let input_view = InputView::build(cx, |state| state);
9418        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9419        let input = input_view.input;
9420
9421        cx.update(|window, cx| {
9422            input.update(cx, |state, cx| {
9423                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
9424                state.replace_text_in_range(None, "你", window, cx);
9425                state.undo_manager.set_pending_intent(EditIntent::Typing);
9426                state.replace_text_in_range(None, "a", window, cx);
9427                state.undo_manager.set_pending_intent(EditIntent::Typing);
9428                state.replace_text_in_range(None, "b", window, cx);
9429                assert_eq!(state.value(), "你ab");
9430
9431                state.undo(&Undo, window, cx);
9432                assert_eq!(state.value(), "你");
9433
9434                state.undo(&Undo, window, cx);
9435                assert_eq!(state.value(), "");
9436            });
9437        });
9438    }
9439
9440    #[gpui::test]
9441    fn test_undo_manager_composition_cancel_leaves_no_entry(cx: &mut TestAppContext) {
9442        let input_view = InputView::build(cx, |state| state);
9443        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9444        let input = input_view.input;
9445
9446        cx.update(|window, cx| {
9447            input.update(cx, |state, cx| {
9448                state.set_value("a", window, cx);
9449                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
9450                state.replace_and_mark_text_in_range(None, "", None, window, cx);
9451
9452                assert_eq!(state.value(), "a");
9453                assert!(!state.undo_manager.has_undos());
9454            });
9455        });
9456    }
9457
9458    /// The macOS Korean IME rewrites the character it just inserted.
9459    #[gpui::test]
9460    fn test_undo_manager_ime_rewrites_of_typed_char_are_one_group(cx: &mut TestAppContext) {
9461        let input_view = InputView::build(cx, |state| state);
9462        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9463        let input = input_view.input;
9464
9465        cx.update(|window, cx| {
9466            input.update(cx, |state, cx| {
9467                state.set_value("é ", window, cx);
9468                state.set_selected_range(3..3, cx);
9469                // g k s r m f: "ㅎ" -> "하" -> "한", then "ㄱ" -> "그" -> "글"
9470                state.replace_text_in_range(None, "ㅎ", window, cx);
9471                state.replace_text_in_range(Some(2..3), "하", window, cx);
9472                state.replace_text_in_range(Some(2..3), "한", window, cx);
9473                state.replace_text_in_range(None, "ㄱ", window, cx);
9474                state.replace_text_in_range(Some(3..4), "그", window, cx);
9475                state.replace_text_in_range(Some(3..4), "글", window, cx);
9476                assert_eq!(state.value(), "é 한글");
9477                assert_eq!(state.undo_manager.undo_count(), 1);
9478
9479                state.undo(&Undo, window, cx);
9480                assert_eq!(state.value(), "é ");
9481                assert_eq!(state.selected_range(), 3..3);
9482
9483                state.redo(&Redo, window, cx);
9484                assert_eq!(state.value(), "é 한글");
9485                assert_eq!(state.selected_range(), 9..9);
9486            });
9487        });
9488    }
9489
9490    #[gpui::test]
9491    fn test_undo_manager_rewrite_after_cursor_movement_is_separate(cx: &mut TestAppContext) {
9492        let input_view = InputView::build(cx, |state| state);
9493        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9494        let input = input_view.input;
9495
9496        cx.update(|window, cx| {
9497            input.update(cx, |state, cx| {
9498                state.replace_text_in_range(None, "c", window, cx);
9499                state.replace_text_in_range(None, "e", window, cx);
9500                state.left(&MoveLeft, window, cx);
9501                state.right(&MoveRight, window, cx);
9502                state.replace_text_in_range(Some(1..2), "é", window, cx);
9503                assert_eq!(state.value(), "cé");
9504
9505                state.undo(&Undo, window, cx);
9506                assert_eq!(state.value(), "ce");
9507                state.undo(&Undo, window, cx);
9508                assert_eq!(state.value(), "");
9509            });
9510        });
9511    }
9512
9513    #[gpui::test]
9514    fn test_undo_manager_selection_restored_by_undo_and_redo(cx: &mut TestAppContext) {
9515        let input_view = InputView::build(cx, |state| state);
9516        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9517        let input = input_view.input;
9518
9519        cx.update(|window, cx| {
9520            input.update(cx, |state, cx| {
9521                state.set_value("abc", window, cx);
9522                state.set_selected_range(1..2, cx);
9523                state.replace_text_in_range(None, "X", window, cx);
9524
9525                state.undo(&Undo, window, cx);
9526                assert_eq!(state.value(), "abc");
9527                assert_eq!(state.selected_range(), 1..2);
9528
9529                state.redo(&Redo, window, cx);
9530                assert_eq!(state.value(), "aXc");
9531                assert_eq!(state.selected_range(), 2..2);
9532            });
9533        });
9534    }
9535
9536    #[gpui::test]
9537    fn test_undo_manager_forward_delete_restores_cursor(cx: &mut TestAppContext) {
9538        let input_view = InputView::build(cx, |state| state);
9539        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9540        let input = input_view.input;
9541
9542        cx.update(|window, cx| {
9543            input.update(cx, |state, cx| {
9544                state.set_value("abc", window, cx);
9545                state.set_selected_range(1..1, cx);
9546                state.delete(&Delete, window, cx);
9547
9548                state.undo(&Undo, window, cx);
9549                assert_eq!(state.value(), "abc");
9550                assert_eq!(state.selected_range(), 1..1);
9551            });
9552        });
9553    }
9554
9555    #[gpui::test]
9556    fn test_undo_manager_selection_movement_preserves_redo(cx: &mut TestAppContext) {
9557        let input_view = InputView::build(cx, |state| state);
9558        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9559        let input = input_view.input;
9560
9561        cx.update(|window, cx| {
9562            input.update(cx, |state, cx| {
9563                state.replace_text_in_range(None, "ab", window, cx);
9564                state.undo(&Undo, window, cx);
9565                state.set_selected_range(0..0, cx);
9566                state.redo(&Redo, window, cx);
9567                assert_eq!(state.value(), "ab");
9568
9569                state.undo(&Undo, window, cx);
9570                state.replace_text_in_range(None, "x", window, cx);
9571                state.redo(&Redo, window, cx);
9572                assert_eq!(state.value(), "x");
9573            });
9574        });
9575    }
9576
9577    #[gpui::test]
9578    fn test_undo_manager_noop_edit_preserves_redo(cx: &mut TestAppContext) {
9579        let input_view = InputView::build(cx, |state| state);
9580        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9581        let input = input_view.input;
9582
9583        cx.update(|window, cx| {
9584            input.update(cx, |state, cx| {
9585                state.replace_text_in_range(None, "a", window, cx);
9586                state.move_to(0, None, cx);
9587                state.replace_text_in_range(None, "", window, cx);
9588                state.undo(&Undo, window, cx);
9589                assert_eq!(state.value(), "");
9590                state.redo(&Redo, window, cx);
9591                assert_eq!(state.value(), "a");
9592                assert_eq!(state.cursor(), 1);
9593            });
9594        });
9595    }
9596
9597    #[gpui::test]
9598    fn test_cursor_round_trip_stops_typing_coalescing(cx: &mut TestAppContext) {
9599        let input_view = InputView::build(cx, |state| state);
9600        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9601        let input = input_view.input;
9602
9603        cx.update(|window, cx| {
9604            input.update(cx, |state, cx| {
9605                state.replace_text_in_range(None, "a", window, cx);
9606                state.left(&MoveLeft, window, cx);
9607                state.right(&MoveRight, window, cx);
9608                state.replace_text_in_range(None, "b", window, cx);
9609
9610                state.undo(&Undo, window, cx);
9611                assert_eq!(state.value(), "a");
9612                state.undo(&Undo, window, cx);
9613                assert_eq!(state.value(), "");
9614            });
9615        });
9616    }
9617
9618    #[gpui::test]
9619    fn test_undo_manager_noop_edit_breaks_coalescing_without_clearing_history(
9620        cx: &mut TestAppContext,
9621    ) {
9622        let input_view = InputView::build(cx, |state| state);
9623        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9624        let input = input_view.input;
9625
9626        cx.update(|window, cx| {
9627            input.update(cx, |state, cx| {
9628                state.replace_text_in_range(None, "alpha", window, cx);
9629                state.replace_text_in_range(None, "", window, cx);
9630                state.replace_text_in_range(None, "beta", window, cx);
9631                assert_eq!(state.value(), "alphabeta");
9632
9633                state.undo(&Undo, window, cx);
9634                assert_eq!(state.value(), "alpha");
9635                state.undo(&Undo, window, cx);
9636                assert_eq!(state.value(), "");
9637            });
9638        });
9639    }
9640
9641    #[gpui::test]
9642    fn test_undo_manager_masked_redo_restores_actual_cursor(cx: &mut TestAppContext) {
9643        let input_view = InputView::build(cx, |state| {
9644            state.mask_pattern(MaskPattern::Number {
9645                separator: Some(','),
9646                fraction: None,
9647            })
9648        });
9649        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9650        let input = input_view.input;
9651
9652        cx.update(|window, cx| {
9653            input.update(cx, |state, cx| {
9654                state.set_value("12345", window, cx);
9655                state.set_selected_range(2..2, cx);
9656                state.replace_text_in_range(None, "9", window, cx);
9657                let selection_after_edit = state.selected_range();
9658                assert_ne!(selection_after_edit.end, state.value().len());
9659
9660                state.undo(&Undo, window, cx);
9661                state.redo(&Redo, window, cx);
9662                assert_eq!(state.selected_range(), selection_after_edit);
9663            });
9664        });
9665    }
9666
9667    /// Unfolding at a position opens exactly the folds hiding it.
9668    ///
9669    /// A fold keeps its own first and last line visible, so a position on
9670    /// either of them opens nothing. Nested folds all open at once, sibling
9671    /// folds stay closed, and the opened ranges stay fold candidates.
9672    #[gpui::test]
9673    fn test_unfold_at(cx: &mut TestAppContext) {
9674        use crate::input::{FoldRange, Position};
9675
9676        let view = InputView::<EditorMode>::new(cx);
9677        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9678        let input = view.input;
9679
9680        // An outer fold over lines 0..=5, a fold nested inside it, and a
9681        // sibling fold that must never be touched.
9682        cx.update(|window, cx| {
9683            input.update(cx, |state, cx| {
9684                state.set_value("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl", window, cx);
9685                state.apply_highlighter_fold_candidates(
9686                    vec![
9687                        FoldRange::new(0, 5),
9688                        FoldRange::new(2, 4),
9689                        FoldRange::new(7, 10),
9690                    ],
9691                    cx,
9692                );
9693                state.display_map.set_folded(0, true);
9694                state.display_map.set_folded(2, true);
9695                state.display_map.set_folded(7, true);
9696            });
9697        });
9698
9699        // The outer fold's own first and last line stay visible, so neither
9700        // position opens anything.
9701        for line in [0, 5] {
9702            cx.update(|_, cx| {
9703                input.update(cx, |state, cx| {
9704                    assert!(!state.display_map.is_buffer_line_hidden(line));
9705                    assert!(!state.unfold_at(Position::new(line as u32, 0), cx));
9706                });
9707                input.read_with(cx, |state, _| {
9708                    assert!(state.display_map.is_folded_at(0));
9709                    assert!(state.display_map.is_folded_at(2));
9710                    assert!(state.display_map.is_folded_at(7));
9711                });
9712            });
9713        }
9714
9715        // Line 3 is hidden by both the outer and the nested fold, so both
9716        // open; the sibling fold does not.
9717        cx.update(|_, cx| {
9718            input.update(cx, |state, cx| {
9719                assert!(state.unfold_at(Position::new(3, 0), cx));
9720            });
9721            input.read_with(cx, |state, _| {
9722                assert!(!state.display_map.is_buffer_line_hidden(3));
9723                assert!(!state.display_map.is_folded_at(0));
9724                assert!(!state.display_map.is_folded_at(2));
9725                assert!(state.display_map.is_folded_at(7));
9726                // The opened ranges are still candidates for refolding.
9727                assert!(state.display_map.is_fold_candidate(0));
9728                assert!(state.display_map.is_fold_candidate(2));
9729            });
9730        });
9731
9732        // Nothing is hidden there any more, so a second call is a no-op.
9733        cx.update(|_, cx| {
9734            input.update(cx, |state, cx| {
9735                assert!(!state.unfold_at(Position::new(3, 0), cx));
9736            });
9737        });
9738    }
9739
9740    /// Losing focus hides the hover popover but keeps the decorations.
9741    ///
9742    /// Both used to be dropped by one call, so clicking away threw away
9743    /// decorations the application had installed and never asked to remove.
9744    #[gpui::test]
9745    fn test_blur_keeps_decorations(cx: &mut TestAppContext) {
9746        let view = InputView::<EditorMode>::new(cx);
9747        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9748        let input = view.input;
9749
9750        cx.update(|window, cx| {
9751            input.update(cx, |state, cx| {
9752                state.set_value("select 1", window, cx);
9753                let _collection = state.create_decorations_collection(
9754                    vec![crate::input::TextDecoration::new(
9755                        0..6,
9756                        gpui::HighlightStyle {
9757                            font_weight: Some(gpui::FontWeight::BOLD),
9758                            ..Default::default()
9759                        },
9760                    )],
9761                    cx,
9762                );
9763                state.present_hover(
9764                    0..6,
9765                    lsp_types::Hover {
9766                        contents: lsp_types::HoverContents::Scalar(
9767                            lsp_types::MarkedString::String("docs".into()),
9768                        ),
9769                        range: None,
9770                    },
9771                    cx,
9772                );
9773                assert!(state.hover_popover().is_some());
9774
9775                state.on_blur(window, cx);
9776
9777                assert!(
9778                    state.hover_popover().is_none(),
9779                    "blur should hide the hover popover"
9780                );
9781                let decorations = state.extras.decoration_layers();
9782                assert!(
9783                    decorations.iter().any(|layer| !layer.is_empty()),
9784                    "blur must not discard decorations"
9785                );
9786            });
9787        });
9788    }
9789
9790    /// The mode marker is the only source of truth for the kind of input.
9791    ///
9792    /// An auto-growing textarea capped at one row used to report itself as
9793    /// single-line, because the answer was derived from the row counts.
9794    #[gpui::test]
9795    fn test_kind_does_not_follow_the_row_count(cx: &mut TestAppContext) {
9796        let view = InputView::build_textarea(cx, |state| state.auto_grow(1, 1));
9797        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9798        view.input.read_with(&mut cx, |state, _| {
9799            assert!(state.is_multi_line());
9800            assert!(!state.is_single_line());
9801            assert!(!state.is_code_editor());
9802        });
9803    }
9804
9805    #[gpui::test]
9806    fn test_textarea_prewrap_prepares_content_rows_before_layout(cx: &mut TestAppContext) {
9807        cx.update(crate::init);
9808        for (value, expected_rows) in [("", 1), ("one\ntwo", 2)] {
9809            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9810                let font = window.text_style().font();
9811                cx.new(|cx| {
9812                    let mut state = crate::input::TextareaState::new(window, cx)
9813                        .rows(12)
9814                        .default_value(value);
9815                    assert_eq!(state.display_map().wrap_row_count(), 0);
9816                    state.prewrap(font, px(14.), px(120.), cx);
9817                    assert_eq!(state.display_map().wrap_row_count(), expected_rows);
9818                    assert_eq!(state.value(), value);
9819                    state
9820                });
9821                gpui::EmptyView
9822            });
9823        }
9824    }
9825
9826    #[gpui::test]
9827    fn test_textarea_prewrap_respects_soft_wrap_before_layout(cx: &mut TestAppContext) {
9828        cx.update(crate::init);
9829        let value = "select id, name, email, phone from users where active\nlimit 10";
9830        for soft_wrap in [true, false] {
9831            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9832                let font = window.text_style().font();
9833                cx.new(|cx| {
9834                    let mut state = crate::input::TextareaState::new(window, cx)
9835                        .rows(1)
9836                        .soft_wrap(soft_wrap)
9837                        .default_value(value);
9838                    state.prewrap(font, px(14.), px(120.), cx);
9839                    let rows = state.display_map().wrap_row_count();
9840                    if soft_wrap {
9841                        assert!(rows > 2, "the first logical line must wrap");
9842                    } else {
9843                        assert_eq!(rows, 2);
9844                    }
9845                    assert_eq!(state.value(), value);
9846                    state
9847                });
9848                gpui::EmptyView
9849            });
9850        }
9851    }
9852
9853    #[gpui::test]
9854    fn test_prewrap_counts_wrapped_rows_before_the_first_paint(cx: &mut TestAppContext) {
9855        cx.update(crate::init);
9856        let text = "select id, name, email, phone from users where active\nlimit 10";
9857        for (soft_wrap, wraps) in [(true, true), (false, false)] {
9858            let mut input = None;
9859            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9860                let font = window.text_style().font();
9861                let editor = cx.new(|cx| {
9862                    let mut state = crate::input::EditorState::new(window, cx)
9863                        .soft_wrap(soft_wrap)
9864                        .default_value(text);
9865                    state.prewrap(font, px(14.), px(120.), cx);
9866                    state
9867                });
9868                input = Some(editor);
9869                gpui::EmptyView
9870            });
9871            let rows = input
9872                .unwrap()
9873                .read_with(cx, |state, _| state.display_map().wrap_row_count());
9874            // Two lines; at 120px the first wraps unless soft wrap is off.
9875            assert_eq!(rows > 2, wraps, "soft_wrap({soft_wrap}): {rows} rows");
9876            if !wraps {
9877                assert_eq!(rows, 2);
9878            }
9879        }
9880    }
9881
9882    #[gpui::test]
9883    fn test_prewrap_uses_the_configured_tab_stops(cx: &mut TestAppContext) {
9884        cx.update(crate::init);
9885        cx.open_window(size(px(400.), px(100.)), |window, cx| {
9886            let font = window.text_style().font();
9887            let font_size = px(14.);
9888            let font_id = window.text_system().resolve_font(&font);
9889            let space_width = window.text_system().layout_width(font_id, font_size, ' ');
9890            cx.new(|cx| {
9891                let mut state = crate::input::EditorState::new(window, cx)
9892                    .tab_size(TabSize {
9893                        tab_size: 4,
9894                        hard_tabs: true,
9895                    })
9896                    .default_value("\t\t");
9897                for (tab_size, expected_rows) in [(4, 2), (2, 1), (8, 2)] {
9898                    state.set_tab_size(
9899                        TabSize {
9900                            tab_size,
9901                            hard_tabs: true,
9902                        },
9903                        cx,
9904                    );
9905                    state.prewrap(font.clone(), font_size, space_width * 5. + RIGHT_MARGIN, cx);
9906                    assert_eq!(state.display_map().wrap_row_count(), expected_rows);
9907                }
9908                state
9909            });
9910            gpui::EmptyView
9911        });
9912    }
9913
9914    /// Prewrap counts with the state's wrapping indent, as the first layout
9915    /// does: continuation rows indented as deep as their line take more rows.
9916    #[gpui::test]
9917    fn test_prewrap_wraps_with_the_wrapping_indent(cx: &mut TestAppContext) {
9918        cx.update(crate::init);
9919        let text = "            select id, name, email, phone from users where active";
9920        let mut rows = Vec::new();
9921        for indent in [WrappingIndent::Same, WrappingIndent::None] {
9922            let mut input = None;
9923            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9924                let font = window.text_style().font();
9925                input = Some(cx.new(|cx| {
9926                    let mut state = crate::input::EditorState::new(window, cx)
9927                        .wrapping_indent(indent)
9928                        .default_value(text);
9929                    state.prewrap(font, px(14.), px(200.), cx);
9930                    state
9931                }));
9932                gpui::EmptyView
9933            });
9934            rows.push(
9935                input
9936                    .unwrap()
9937                    .read_with(cx, |state, _| state.display_map().wrap_row_count()),
9938            );
9939        }
9940        assert!(rows[0] > rows[1], "rows with Same, None: {rows:?}");
9941    }
9942
9943    /// Once the editor is laid out, layout owns the wrap and prewrap leaves it.
9944    #[gpui::test]
9945    fn test_prewrap_does_nothing_once_laid_out(cx: &mut TestAppContext) {
9946        struct Frame(Entity<crate::input::EditorState>);
9947        impl Render for Frame {
9948            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
9949                crate::input::InputBase::new("frame")
9950                    .h(px(100.))
9951                    .child(crate::input::Editor::new(&self.0))
9952            }
9953        }
9954
9955        cx.update(crate::init);
9956        let text = "select id, name, email, phone from users where active\nlimit 10";
9957        let mut input = None;
9958        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
9959            let state = cx.new(|cx| crate::input::EditorState::new(window, cx).default_value(text));
9960            input = Some(state.clone());
9961            Frame(state)
9962        });
9963        let input = input.unwrap();
9964        let mut cx = VisualTestContext::from_window(window.into(), cx);
9965        cx.update(|window, cx| window.draw(cx).clear(cx));
9966
9967        let laid_out = input.read_with(&cx, |state, _| state.display_map().wrap_row_count());
9968        assert!(laid_out >= 2, "laid out to {laid_out} rows");
9969        let font = cx.update(|window, _| window.text_style().font());
9970        input.update(&mut cx, |state, cx| {
9971            state.prewrap(font, px(14.), px(60.), cx)
9972        });
9973        assert_eq!(
9974            input.read_with(&cx, |state, _| state.display_map().wrap_row_count()),
9975            laid_out
9976        );
9977    }
9978
9979    /// Soft wrap is on by default, for every mode that can wrap.
9980    ///
9981    /// The default lives in the shared constructor, where a mode-specific
9982    /// `new` can silently fail to restore it; this pins it down.
9983    #[gpui::test]
9984    fn test_soft_wrap_is_enabled_by_default(cx: &mut TestAppContext) {
9985        let textarea = InputView::build_textarea(cx, |state| state);
9986        let mut textarea_cx = VisualTestContext::from_window(textarea.window_handle.into(), cx);
9987        textarea
9988            .input
9989            .read_with(&mut textarea_cx, |state, _| assert!(state.soft_wrap));
9990
9991        let editor = InputView::<EditorMode>::new(cx);
9992        let mut editor_cx = VisualTestContext::from_window(editor.window_handle.into(), cx);
9993        editor
9994            .input
9995            .read_with(&mut editor_cx, |state, _| assert!(state.soft_wrap));
9996    }
9997
9998    /// Parse a cursor spec into `(text, cursor_offsets)`. Non-empty lines are
9999    /// joined with `\n` plus a trailing `\n`. `|` marks a cursor. Leading
10000    /// whitespace is kept, so a spec can express indentation.
10001    fn parse_cursor_spec(input: &str) -> (String, Vec<usize>) {
10002        let mut full_text = String::new();
10003        let mut cursor_offsets = Vec::new();
10004        let non_empty_lines: Vec<&str> = input.lines().filter(|l| !l.is_empty()).collect();
10005
10006        for (line_idx, line) in non_empty_lines.iter().enumerate() {
10007            let mut positions = Vec::new();
10008            let mut text = String::new();
10009            for ch in line.chars() {
10010                if ch == '|' {
10011                    positions.push(text.len());
10012                } else {
10013                    text.push(ch);
10014                }
10015            }
10016
10017            if line_idx > 0 {
10018                full_text.push('\n');
10019            }
10020            let line_start = full_text.len();
10021            for pos in positions {
10022                cursor_offsets.push(line_start + pos);
10023            }
10024            full_text.push_str(&text);
10025        }
10026        full_text.push('\n');
10027
10028        (full_text, cursor_offsets)
10029    }
10030
10031    /// Build a multi-line input for multi-cursor tests.
10032    fn multi_line(cx: &mut TestAppContext) -> InputView<TextareaMode> {
10033        InputView::build_textarea(cx, |state| state)
10034    }
10035
10036    /// Set the text and cursor positions from a spec (see [`parse_cursor_spec`]).
10037    fn setup_cursors<M: InputModeKind>(
10038        cx: &mut VisualTestContext,
10039        input: &Entity<InputBaseState<M>>,
10040        spec: &str,
10041    ) {
10042        let (full_text, offsets) = parse_cursor_spec(spec);
10043        cx.update(|window, cx| {
10044            input.update(cx, |state, cx| {
10045                state.set_value(&full_text, window, cx);
10046                let selections = offsets
10047                    .into_iter()
10048                    .map(|offset| {
10049                        CursorSelection::new(state.selections.generate_id(), offset, offset)
10050                    })
10051                    .collect();
10052                state.selections.replace_all(selections);
10053                cx.notify();
10054            });
10055        });
10056    }
10057
10058    /// Assert the text and cursor positions match a spec.
10059    #[track_caller]
10060    fn assert_cursors<M: InputModeKind>(
10061        cx: &mut VisualTestContext,
10062        input: &Entity<InputBaseState<M>>,
10063        spec: &str,
10064    ) {
10065        let (expected_text, mut expected_cursors) = parse_cursor_spec(spec);
10066        expected_cursors.sort();
10067
10068        let (actual_text, mut actual_cursors) = input.read_with(cx, |state, _| {
10069            (
10070                state.text.to_string(),
10071                state
10072                    .selections
10073                    .iter()
10074                    .map(|s| s.cursor_offset())
10075                    .collect::<Vec<_>>(),
10076            )
10077        });
10078        actual_cursors.sort();
10079
10080        assert_eq!(
10081            actual_text, expected_text,
10082            "Text mismatch:\nExpected: {expected_text:?}\nActual:   {actual_text:?}"
10083        );
10084        assert_eq!(
10085            actual_cursors, expected_cursors,
10086            "Cursor mismatch:\nExpected: {expected_cursors:?}\nActual:   {actual_cursors:?}"
10087        );
10088    }
10089
10090    #[gpui::test]
10091    fn test_alt_drag_selects_a_block_and_replaces_each_row(cx: &mut TestAppContext) {
10092        cx.update(crate::init);
10093        let view = InputView::<EditorMode>::new(cx);
10094        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10095        for modifiers in [
10096            gpui::Modifiers {
10097                alt: true,
10098                ..Default::default()
10099            },
10100            gpui::Modifiers {
10101                alt: true,
10102                shift: true,
10103                ..Default::default()
10104            },
10105            #[cfg(target_os = "linux")]
10106            gpui::Modifiers {
10107                alt: true,
10108                control: true,
10109                ..Default::default()
10110            },
10111        ] {
10112            setup_cursors(&mut cx, &view.input, "|abcd\nabcd\nabcd");
10113            cx.update(|window, cx| {
10114                view.input.update(cx, |state, cx| state.focus(window, cx));
10115            });
10116            let (start, end) = view.input.read_with(&cx, |state, _| {
10117                let layout = state.last_layout.as_ref().unwrap();
10118                let origin = state.last_bounds.unwrap().origin;
10119                let position = |row: usize, col| {
10120                    let local = layout.lines[row]
10121                        .position_for_index(col, layout, false)
10122                        .unwrap();
10123                    origin
10124                        + point(
10125                            layout.line_number_width + local.x,
10126                            layout.line_height * (row as f32 + 0.5),
10127                        )
10128                };
10129                (position(0, 1), position(2, 3))
10130            });
10131            // Land inside the third column instead of exactly on its trailing
10132            // glyph boundary, where pixel rounding can select the next column.
10133            let end = point(end.x - px(0.5), end.y);
10134            // A cached Ctrl-hover definition must not steal a column gesture.
10135            cx.update(|_, cx| {
10136                view.input.update(cx, |state, _| {
10137                    state.extras.hover_definition.update(
10138                        0..4,
10139                        vec![lsp_types::LocationLink {
10140                            origin_selection_range: None,
10141                            target_uri: "file:///tmp/column-selection.rs".parse().unwrap(),
10142                            target_range: Default::default(),
10143                            target_selection_range: Default::default(),
10144                        }],
10145                    );
10146                });
10147            });
10148            cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
10149            cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
10150            cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
10151            view.input.read_with(&cx, |state, _| {
10152                let ranges: Vec<_> = state
10153                    .selections
10154                    .iter()
10155                    .map(|sel| sel.start..sel.end)
10156                    .collect();
10157                assert_eq!(ranges, vec![1..3, 6..8, 11..13]);
10158            });
10159            // Moving after release must leave the block intact.
10160            cx.simulate_mouse_move(start, None, modifiers);
10161            cx.simulate_keystrokes("x");
10162            assert_cursors(&mut cx, &view.input, "ax|d\nax|d\nax|d");
10163        }
10164    }
10165
10166    #[gpui::test]
10167    fn test_alt_drag_extends_upward_from_an_existing_cursor(cx: &mut TestAppContext) {
10168        cx.update(crate::init);
10169        let view = InputView::<EditorMode>::new(cx);
10170        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10171        setup_cursors(&mut cx, &view.input, "abcd\nabcd\na|bcd");
10172        cx.update(|window, cx| {
10173            view.input.update(cx, |state, cx| state.focus(window, cx));
10174        });
10175        let (start, end) = view.input.read_with(&cx, |state, _| {
10176            let layout = state.last_layout.as_ref().unwrap();
10177            let origin = state.last_bounds.unwrap().origin;
10178            let local = layout.lines[0]
10179                .position_for_index(1, layout, false)
10180                .unwrap();
10181            let x = layout.line_number_width + local.x;
10182            (
10183                origin + point(x, layout.line_height * 2.5),
10184                origin + point(x, layout.line_height * 0.5),
10185            )
10186        });
10187        let modifiers = gpui::Modifiers {
10188            alt: true,
10189            ..Default::default()
10190        };
10191        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
10192        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
10193        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
10194        assert_cursors(&mut cx, &view.input, "a|bcd\na|bcd\na|bcd");
10195    }
10196
10197    #[gpui::test]
10198    fn test_alt_drag_over_a_short_row_keeps_the_block_width(cx: &mut TestAppContext) {
10199        cx.update(crate::init);
10200        let view = InputView::<EditorMode>::new(cx);
10201        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10202        setup_cursors(&mut cx, &view.input, "abcdef\nab\nabcdef");
10203        cx.update(|window, cx| {
10204            view.input.update(cx, |state, cx| state.focus(window, cx));
10205        });
10206        // Drag from row 0 column 1 down to row 1, at the x of row 0's column 5. Row 1
10207        // only has two characters, so the pointer ends past its end.
10208        let (start, end) = view.input.read_with(&cx, |state, _| {
10209            let layout = state.last_layout.as_ref().unwrap();
10210            let origin = state.last_bounds.unwrap().origin;
10211            let x_of = |index| {
10212                layout.line_number_width
10213                    + layout.lines[0]
10214                        .position_for_index(index, layout, false)
10215                        .unwrap()
10216                        .x
10217            };
10218            (
10219                origin + point(x_of(1), layout.line_height * 0.5),
10220                origin + point(x_of(5), layout.line_height * 1.5),
10221            )
10222        });
10223        let modifiers = gpui::Modifiers {
10224            alt: true,
10225            shift: true,
10226            ..Default::default()
10227        };
10228        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
10229        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
10230        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
10231
10232        // The short row is clipped to its own end; the long row keeps the full span.
10233        view.input.read_with(&cx, |state, _| {
10234            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
10235            assert_eq!(ranges, vec![(1, 5), (8, 9)]);
10236        });
10237    }
10238
10239    #[gpui::test]
10240    fn test_alt_mouse_release_outside_editor_ends_column_selection(cx: &mut TestAppContext) {
10241        cx.update(crate::init);
10242        let view = InputView::<EditorMode>::new(cx);
10243        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10244        setup_cursors(&mut cx, &view.input, "|abcd\nabcd");
10245        let position = view.input.read_with(&cx, |state, _| {
10246            let layout = state.last_layout.as_ref().unwrap();
10247            state.last_bounds.unwrap().origin
10248                + point(layout.line_number_width + px(2.), layout.line_height * 0.5)
10249        });
10250        let modifiers = gpui::Modifiers {
10251            alt: true,
10252            ..Default::default()
10253        };
10254        cx.simulate_mouse_down(position, MouseButton::Left, modifiers);
10255        cx.simulate_mouse_up(point(px(-100.), px(-100.)), MouseButton::Left, modifiers);
10256        view.input.read_with(&cx, |state, _| {
10257            assert!(!state.selecting);
10258            assert!(state.column_select_start.is_none());
10259        });
10260    }
10261
10262    #[gpui::test]
10263    fn test_consumed_keystrokes_keep_cursor_visible(cx: &mut TestAppContext) {
10264        let view = InputView::<EditorMode>::new(cx);
10265        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10266        setup_cursors(&mut cx, &view.input, "a|b");
10267        cx.update(|window, cx| {
10268            view.input.update(cx, |state, cx| {
10269                state.focus(window, cx);
10270                state.pause_blink_cursor(cx);
10271            });
10272        });
10273        cx.run_until_parked();
10274        cx.executor()
10275            .advance_clock(std::time::Duration::from_millis(300));
10276        cx.run_until_parked();
10277        view.input.read_with(&cx, |state, cx| {
10278            assert!(!state.blink_cursor.read(cx).visible());
10279        });
10280        // Copy consumes its shortcut without editing text or moving selections.
10281        for _ in 0..5 {
10282            #[cfg(target_os = "macos")]
10283            cx.simulate_keystrokes("cmd-c");
10284            #[cfg(not(target_os = "macos"))]
10285            cx.simulate_keystrokes("ctrl-c");
10286            cx.run_until_parked();
10287            cx.executor()
10288                .advance_clock(std::time::Duration::from_millis(200));
10289            cx.run_until_parked();
10290            view.input.read_with(&cx, |state, cx| {
10291                assert!(state.blink_cursor.read(cx).visible());
10292            });
10293        }
10294    }
10295
10296    #[gpui::test]
10297    fn test_mouse_down_shows_cursor_like_a_cursor_move(cx: &mut TestAppContext) {
10298        cx.update(crate::init);
10299        let view = InputView::<EditorMode>::new(cx);
10300        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10301        setup_cursors(&mut cx, &view.input, "abcd|efgh");
10302        let position = |offset: Pixels, cx: &mut VisualTestContext| {
10303            view.input.read_with(cx, |state, _| {
10304                let layout = state.last_layout.as_ref().unwrap();
10305                state.last_bounds.unwrap().origin
10306                    + point(layout.line_number_width + offset, layout.line_height * 0.5)
10307            })
10308        };
10309        let visible = |cx: &mut VisualTestContext| {
10310            view.input
10311                .read_with(cx, |state, cx| state.blink_cursor.read(cx).visible())
10312        };
10313
10314        // The press itself shows the caret, without waiting for the focus
10315        // observer, and the idle delay holds it there like a cursor move.
10316        let first = position(px(2.), &mut cx);
10317        cx.simulate_mouse_down(first, MouseButton::Left, Default::default());
10318        assert!(visible(&mut cx));
10319        cx.simulate_mouse_up(first, MouseButton::Left, Default::default());
10320        cx.run_until_parked();
10321
10322        // Pressing elsewhere in the focused input, in the hidden phase of the
10323        // blink, shows the caret again at the press.
10324        cx.executor()
10325            .advance_clock(std::time::Duration::from_millis(400));
10326        cx.run_until_parked();
10327        assert!(!visible(&mut cx));
10328        let second = position(px(30.), &mut cx);
10329        cx.simulate_mouse_down(second, MouseButton::Left, Default::default());
10330        assert!(visible(&mut cx));
10331        cx.executor()
10332            .advance_clock(std::time::Duration::from_millis(250));
10333        cx.run_until_parked();
10334        assert!(visible(&mut cx));
10335        cx.simulate_mouse_up(second, MouseButton::Left, Default::default());
10336    }
10337
10338    #[gpui::test]
10339    fn test_multi_cursor_actions_reveal_hidden_carets(cx: &mut TestAppContext) {
10340        let view = InputView::<EditorMode>::new(cx);
10341        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10342        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
10343        // Start each action in the hidden phase without depending on a
10344        // key-down listener: actions and text input also arrive directly.
10345        for action in 0..6 {
10346            cx.update(|_, cx| {
10347                view.input.update(cx, |state, cx| {
10348                    state.blink_cursor = cx.new(BlinkCursor::new);
10349                    state.blink_cursor.update(cx, |cursor, cx| cursor.start(cx));
10350                });
10351            });
10352            cx.run_until_parked();
10353            cx.executor()
10354                .advance_clock(std::time::Duration::from_millis(500));
10355            cx.run_until_parked();
10356            view.input.read_with(&cx, |state, cx| {
10357                assert!(!state.blink_cursor.read(cx).visible(), "action {action}");
10358            });
10359
10360            cx.update(|window, cx| {
10361                view.input.update(cx, |state, cx| {
10362                    match action {
10363                        0 => state.add_cursor_above(&AddCursorAbove, window, cx),
10364                        1 => state.add_cursor_below(&AddCursorBelow, window, cx),
10365                        2 => state.select_up(&SelectUp, window, cx),
10366                        3 => state.select_down(&SelectDown, window, cx),
10367                        4 => state.replace_text_in_range(None, "x", window, cx),
10368                        _ => state.backspace(&Backspace, window, cx),
10369                    }
10370                    assert!(state.blink_cursor.read(cx).visible(), "action {action}");
10371                });
10372            });
10373        }
10374    }
10375
10376    #[gpui::test]
10377    fn test_multi_cursor_keyboard_dispatch(cx: &mut TestAppContext) {
10378        let view = InputView::<EditorMode>::new(cx);
10379        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10380        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
10381        cx.update(|window, cx| {
10382            view.input.update(cx, |state, cx| state.focus(window, cx));
10383        });
10384        #[cfg(target_os = "macos")]
10385        cx.simulate_keystrokes("cmd-alt-up");
10386        #[cfg(target_os = "windows")]
10387        cx.simulate_keystrokes("ctrl-alt-up");
10388        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
10389        cx.simulate_keystrokes("alt-shift-up");
10390        view.input
10391            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
10392        #[cfg(target_os = "macos")]
10393        cx.simulate_keystrokes("cmd-alt-down");
10394        #[cfg(target_os = "windows")]
10395        cx.simulate_keystrokes("ctrl-alt-down");
10396        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
10397        cx.simulate_keystrokes("alt-shift-down");
10398        view.input
10399            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 3));
10400        cx.simulate_keystrokes("x");
10401        assert_cursors(&mut cx, &view.input, "ax|b\nax|b\nax|b");
10402    }
10403
10404    #[gpui::test]
10405    fn test_multi_cursor_platform_word_selection_dispatch(cx: &mut TestAppContext) {
10406        let view = InputView::<EditorMode>::new(cx);
10407        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10408        setup_cursors(&mut cx, &view.input, "one |two\none |two");
10409        cx.update(|window, cx| {
10410            view.input.update(cx, |state, cx| state.focus(window, cx));
10411        });
10412        #[cfg(any(target_os = "macos", target_os = "linux"))]
10413        cx.simulate_keystrokes("alt-shift-right");
10414        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
10415        cx.simulate_keystrokes("ctrl-shift-right");
10416        cx.simulate_keystrokes("x");
10417        assert_cursors(&mut cx, &view.input, "one x|\none x|");
10418        setup_cursors(&mut cx, &view.input, "one two|\none two|");
10419        #[cfg(any(target_os = "macos", target_os = "linux"))]
10420        cx.simulate_keystrokes("alt-shift-left");
10421        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
10422        cx.simulate_keystrokes("ctrl-shift-left");
10423        cx.simulate_keystrokes("x");
10424        assert_cursors(&mut cx, &view.input, "one x|\none x|");
10425    }
10426
10427    #[cfg(not(target_os = "macos"))]
10428    #[gpui::test]
10429    fn test_multi_cursor_horizontal_selection_dispatch(cx: &mut TestAppContext) {
10430        let view = InputView::<EditorMode>::new(cx);
10431        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10432        setup_cursors(&mut cx, &view.input, "ab\na|b");
10433        cx.update(|window, cx| {
10434            view.input.update(cx, |state, cx| state.focus(window, cx));
10435        });
10436        #[cfg(target_os = "windows")]
10437        cx.simulate_keystrokes("ctrl-alt-up");
10438        #[cfg(not(target_os = "windows"))]
10439        cx.simulate_keystrokes("alt-shift-up");
10440        #[cfg(target_os = "linux")]
10441        cx.simulate_keystrokes("shift-right");
10442        #[cfg(not(target_os = "linux"))]
10443        cx.simulate_keystrokes("alt-shift-right");
10444        view.input.read_with(&cx, |state, _| {
10445            assert_eq!(
10446                state
10447                    .selections
10448                    .iter()
10449                    .map(|s| s.start..s.end)
10450                    .collect::<Vec<_>>(),
10451                vec![4..5, 1..2]
10452            );
10453        });
10454        #[cfg(target_os = "linux")]
10455        cx.simulate_keystrokes("shift-left shift-left");
10456        #[cfg(not(target_os = "linux"))]
10457        cx.simulate_keystrokes("alt-shift-left alt-shift-left");
10458        view.input.read_with(&cx, |state, _| {
10459            assert_eq!(
10460                state
10461                    .selections
10462                    .iter()
10463                    .map(|s| s.start..s.end)
10464                    .collect::<Vec<_>>(),
10465                vec![3..4, 0..1]
10466            );
10467        });
10468        cx.simulate_keystrokes("x");
10469        assert_cursors(&mut cx, &view.input, "x|b\nx|b");
10470    }
10471
10472    #[gpui::test]
10473    fn test_multi_cursor_alt_click_dispatch(cx: &mut TestAppContext) {
10474        cx.update(crate::init);
10475        let view = InputView::<EditorMode>::new(cx);
10476        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10477        setup_cursors(&mut cx, &view.input, "a|b\nab\nab");
10478        cx.update(|window, cx| {
10479            view.input.update(cx, |state, cx| state.focus(window, cx));
10480        });
10481        let position = view.input.read_with(&cx, |state, _| {
10482            let bounds = state.last_bounds.unwrap();
10483            let layout = state.last_layout.as_ref().unwrap();
10484            bounds.origin + point(layout.line_number_width + px(2.), layout.line_height * 1.5)
10485        });
10486        cx.simulate_click(
10487            position,
10488            gpui::Modifiers {
10489                alt: true,
10490                ..Default::default()
10491            },
10492        );
10493        view.input
10494            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
10495    }
10496
10497    #[gpui::test]
10498    fn test_add_selection(cx: &mut TestAppContext) {
10499        let view = InputView::<EditorMode>::new(cx);
10500        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10501        cx.update(|window, cx| {
10502            view.input.update(cx, |state, cx| {
10503                state.focus(window, cx);
10504                state.set_value("foo bar foo", window, cx);
10505                state.set_selected_range(0..3, cx);
10506                // The programmatic second cursor: the next occurrence.
10507                state.add_selection(8..11, cx);
10508                // Adding a range that is already selected changes nothing.
10509                state.add_selection(0..3, cx);
10510            });
10511        });
10512        view.input.read_with(&cx, |state, _| {
10513            let mut ranges: Vec<Range<usize>> =
10514                state.selections.iter().map(|s| s.start..s.end).collect();
10515            ranges.sort_by_key(|r| r.start);
10516            assert_eq!(ranges, vec![0..3, 8..11]);
10517        });
10518        // Typing replaces every selection at once.
10519        cx.simulate_keystrokes("x");
10520        view.input.read_with(&cx, |state, _| {
10521            assert_eq!(state.text.to_string(), "x bar x")
10522        });
10523    }
10524
10525    #[gpui::test]
10526    fn test_add_selection_preserves_crlf_boundaries(cx: &mut TestAppContext) {
10527        let view = InputView::build_textarea(cx, |state| state.default_value("a\r\nb"));
10528        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10529        cx.update(|window, cx| {
10530            view.input.update(cx, |state, cx| {
10531                state.focus(window, cx);
10532                state.set_selected_range(4..4, cx);
10533                state.add_selection(2..2, cx);
10534                let ranges: Vec<_> = state.selections.iter().map(|s| s.start..s.end).collect();
10535                assert_eq!(ranges, vec![4..4, 1..1]);
10536            });
10537        });
10538        cx.simulate_keystrokes("x");
10539        view.input.read_with(&cx, |state, _| {
10540            assert_eq!(state.value(), "ax\r\nbx");
10541        });
10542        cx.update(|window, cx| {
10543            view.input.update(cx, |state, cx| {
10544                state.set_value("a\r\nb", window, cx);
10545                state.set_selected_range(4..4, cx);
10546                state.add_selection(2..3, cx);
10547                state.cut(&Cut, window, cx);
10548                assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "\r\n");
10549                assert_eq!(state.value(), "ab");
10550            });
10551        });
10552    }
10553
10554    #[gpui::test]
10555    fn test_add_selection_merges_overlaps_before_copy_and_cut(cx: &mut TestAppContext) {
10556        let view = InputView::<EditorMode>::new(cx);
10557        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10558        cx.update(|window, cx| {
10559            view.input.update(cx, |state, cx| {
10560                // Cover partial overlaps, a containing range, and a bridge
10561                // between two previously disjoint selections.
10562                for additions in [vec![2..5], vec![0..5], vec![4..5, 2..4]] {
10563                    state.set_value("abcdef", window, cx);
10564                    state.set_selected_range(0..3, cx);
10565                    let active_id = state.active_selection().id;
10566                    for range in additions {
10567                        state.add_selection(range, cx);
10568                    }
10569                    assert_eq!(state.active_selection().id, active_id);
10570                    assert_eq!(state.selected_range(), 0..5);
10571                    assert_eq!(state.selections.len(), 1);
10572                    state.copy(&Copy, window, cx);
10573                    assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "abcde");
10574                    state.cut(&Cut, window, cx);
10575                    assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "abcde");
10576                    assert_eq!(state.value(), "f");
10577                    state.undo(&Undo, window, cx);
10578                    assert_eq!(state.value(), "abcdef");
10579                    assert_eq!(state.selected_range(), 0..5);
10580                }
10581            });
10582        });
10583    }
10584
10585    #[gpui::test]
10586    fn test_add_selection_clears_inline_completion(cx: &mut TestAppContext) {
10587        use crate::input::CompletionProvider;
10588        use gpui::Task;
10589        use lsp_types::{
10590            CompletionContext, CompletionResponse, InlineCompletionContext, InlineCompletionItem,
10591            InlineCompletionResponse,
10592        };
10593        use std::time::Duration;
10594
10595        struct Provider(Rc<Cell<usize>>);
10596
10597        impl CompletionProvider for Provider {
10598            fn is_completion_trigger(&self, _: usize, _: &str, _: &mut App) -> bool {
10599                false
10600            }
10601
10602            fn completions(
10603                &self,
10604                _: &Rope,
10605                _: usize,
10606                _: CompletionContext,
10607                _: &mut Window,
10608                _: &mut App,
10609            ) -> Task<anyhow::Result<CompletionResponse>> {
10610                Task::ready(Ok(CompletionResponse::Array(vec![])))
10611            }
10612
10613            fn inline_completion(
10614                &self,
10615                _: &Rope,
10616                _: usize,
10617                _: InlineCompletionContext,
10618                _: &mut Window,
10619                _: &mut App,
10620            ) -> Task<anyhow::Result<InlineCompletionResponse>> {
10621                self.0.set(self.0.get() + 1);
10622                Task::ready(Ok(InlineCompletionResponse::Array(vec![])))
10623            }
10624        }
10625
10626        let view = InputView::<EditorMode>::new(cx);
10627        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10628        let calls = Rc::new(Cell::new(0));
10629        cx.update(|window, cx| {
10630            view.input.update(cx, |state, cx| {
10631                state.focus(window, cx);
10632                state.set_value("aa\naa", window, cx);
10633                state.set_selected_range(2..2, cx);
10634                state.extras.inline_completion.item = Some(InlineCompletionItem {
10635                    insert_text: "suggestion".into(),
10636                    filter_text: None,
10637                    range: None,
10638                    command: None,
10639                    insert_text_format: None,
10640                });
10641                state.add_selection(5..5, cx);
10642                assert!(!state.has_inline_completion());
10643                state.indent_inline(&IndentInline, window, cx);
10644                assert_eq!(state.value(), "aa  \naa  ");
10645                assert_eq!(state.selections.len(), 2);
10646
10647                // The active cursor stays put, so only canceling the task
10648                // prevents this pending request from fetching a suggestion.
10649                state.set_value("aa\naa", window, cx);
10650                state.set_selected_range(2..2, cx);
10651                state.extras.lsp.completion_provider = Some(Rc::new(Provider(calls.clone())));
10652                state.schedule_inline_completion(window, cx);
10653            });
10654        });
10655        cx.run_until_parked();
10656        cx.update(|_, cx| {
10657            view.input
10658                .update(cx, |state, cx| state.add_selection(5..5, cx));
10659        });
10660        cx.executor().advance_clock(Duration::from_secs(1));
10661        cx.run_until_parked();
10662        assert_eq!(calls.get(), 0);
10663        view.input.read_with(&cx, |state, _| {
10664            assert!(!state.has_inline_completion());
10665            assert_eq!(state.selections.len(), 2);
10666            assert_eq!(state.value(), "aa\naa");
10667        });
10668    }
10669
10670    #[gpui::test]
10671    fn test_word_delete_undo_restores_caret(cx: &mut TestAppContext) {
10672        let view = multi_line(cx);
10673        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10674        setup_cursors(&mut cx, &view.input, "hello|");
10675        cx.update(|window, cx| {
10676            view.input.update(cx, |state, cx| {
10677                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
10678                state.undo(&Undo, window, cx);
10679                assert_eq!(state.selected_range(), 5..5);
10680            });
10681        });
10682    }
10683
10684    #[gpui::test]
10685    fn test_merged_delete_undo_restores_all_carets(cx: &mut TestAppContext) {
10686        let view = multi_line(cx);
10687        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10688        setup_cursors(&mut cx, &view.input, "a|b|c");
10689        cx.update(|window, cx| {
10690            view.input.update(cx, |state, cx| {
10691                state.backspace(&Backspace, window, cx);
10692                state.undo(&Undo, window, cx);
10693            });
10694        });
10695        assert_cursors(&mut cx, &view.input, "a|b|c");
10696    }
10697
10698    #[gpui::test]
10699    fn test_shift_end_respects_soft_wrap_end(cx: &mut TestAppContext) {
10700        let view = InputView::<EditorMode>::new(cx);
10701        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10702        cx.update(|window, cx| {
10703            view.input.update(cx, |state, cx| {
10704                state.set_value("abcdef ".repeat(30), window, cx);
10705                state.display_map.on_layout_changed(Some(px(60.)), cx);
10706                let line = state.display_map.line(0).unwrap();
10707                assert!(line.wrapped_lines.len() > 1);
10708                let boundary = line.wrapped_lines[0].end;
10709                state.move_to_with_affinity(boundary, None, true, cx);
10710                state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
10711                assert_eq!(state.selected_range(), boundary..state.text.len());
10712            });
10713        });
10714    }
10715
10716    #[gpui::test]
10717    fn test_outdent_unindented_unicode_is_unchanged(cx: &mut TestAppContext) {
10718        let view = multi_line(cx);
10719        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10720        setup_cursors(&mut cx, &view.input, "|你好\n|世界");
10721        cx.update(|window, cx| {
10722            view.input.update(cx, |state, cx| {
10723                state.outdent(false, window, cx);
10724                state.outdent(true, window, cx);
10725            });
10726        });
10727        assert_cursors(&mut cx, &view.input, "|你好\n|世界");
10728    }
10729
10730    #[gpui::test]
10731    fn test_column_selection_stays_on_unicode_boundaries(cx: &mut TestAppContext) {
10732        let view = multi_line(cx);
10733        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10734        setup_cursors(&mut cx, &view.input, "ab\n你好\ncd");
10735        cx.update(|window, cx| {
10736            view.input.update(cx, |state, cx| {
10737                state.build_columnar_selection(
10738                    ColumnarPoint::new(1, 0),
10739                    ColumnarPoint::new(11, 0),
10740                    cx,
10741                );
10742                let text = state.value();
10743                for sel in state.selections.iter() {
10744                    assert!(text.is_char_boundary(sel.start));
10745                    assert!(text.is_char_boundary(sel.end));
10746                }
10747                state.replace_text_in_range(None, "X", window, cx);
10748            });
10749        });
10750    }
10751
10752    #[gpui::test]
10753    fn test_editor_decorations_follow_typing(cx: &mut TestAppContext) {
10754        let view = InputView::<EditorMode>::new(cx);
10755        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10756        cx.update(|window, cx| {
10757            view.input.update(cx, |state, cx| {
10758                state.set_value("abc def", window, cx);
10759                state.create_decorations_collection(
10760                    vec![crate::input::TextDecoration::new(
10761                        4..7,
10762                        gpui::HighlightStyle::default(),
10763                    )],
10764                    cx,
10765                );
10766                state.set_selected_range(0..0, cx);
10767                state.replace_text_in_range(None, "X", window, cx);
10768                let layers = state.extras.decoration_layers();
10769                assert_eq!(layers.into_iter().flatten().next().unwrap().range, 5..8);
10770            });
10771        });
10772    }
10773
10774    #[gpui::test]
10775    fn test_backspace_preserves_escaped_quote_terminator(cx: &mut TestAppContext) {
10776        let view = InputView::<EditorMode>::new(cx);
10777        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10778        setup_cursors(&mut cx, &view.input, r#"{"s":"\"|"}"#);
10779        cx.update(|window, cx| {
10780            view.input.update(cx, |state, cx| {
10781                set_test_syntax_provider(std::rc::Rc::new(StringAllProvider), cx);
10782                state.backspace(&Backspace, window, cx);
10783            });
10784        });
10785        assert_cursors(&mut cx, &view.input, r#"{"s":"\|"}"#);
10786    }
10787
10788    #[gpui::test]
10789    fn test_ordinary_typing_does_not_query_syntax(cx: &mut TestAppContext) {
10790        struct UnexpectedQuery;
10791        impl crate::input::SyntaxContextProvider for UnexpectedQuery {
10792            fn context_at(&self, _: &ropey::Rope, _: usize) -> crate::input::SyntaxContext {
10793                panic!("ordinary typing must not query syntax");
10794            }
10795        }
10796        let view = InputView::<EditorMode>::new(cx);
10797        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10798        setup_cursors(&mut cx, &view.input, "|");
10799        cx.update(|window, cx| {
10800            view.input.update(cx, |state, cx| {
10801                set_test_syntax_provider(std::rc::Rc::new(UnexpectedQuery), cx);
10802                for c in ["a", "b", "c"] {
10803                    state.replace_text_in_range(None, c, window, cx);
10804                }
10805            });
10806        });
10807        assert_cursors(&mut cx, &view.input, "abc|");
10808    }
10809
10810    #[gpui::test]
10811    fn test_auto_close_is_atomic_at_history_limit(cx: &mut TestAppContext) {
10812        let view = InputView::<EditorMode>::new(cx);
10813        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10814        setup_cursors(&mut cx, &view.input, "|");
10815        cx.update(|window, cx| {
10816            view.input.update(cx, |state, cx| {
10817                for _ in 0..999 {
10818                    state.replace_text_in_range(None, "a", window, cx);
10819                }
10820                state.replace_text_in_range(None, "(", window, cx);
10821                state.undo(&Undo, window, cx);
10822                assert!(!state.text.to_string().contains('('));
10823                assert!(!state.text.to_string().contains(')'));
10824                state.redo(&Redo, window, cx);
10825            });
10826        });
10827        assert_cursors(&mut cx, &view.input, &format!("{}(|)", "a".repeat(999)));
10828    }
10829
10830    #[gpui::test]
10831    fn test_comment_closer_is_inserted_literally(cx: &mut TestAppContext) {
10832        let view = InputView::<EditorMode>::new(cx);
10833        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10834        setup_cursors(&mut cx, &view.input, "// (|)");
10835        cx.update(|window, cx| {
10836            view.input.update(cx, |state, cx| {
10837                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10838                state.replace_text_in_range(None, ")", window, cx);
10839            });
10840        });
10841        assert_cursors(&mut cx, &view.input, "// ()|)");
10842    }
10843
10844    #[gpui::test]
10845    fn test_explicit_range_skip_does_not_delete_existing_closer_on_undo(cx: &mut TestAppContext) {
10846        let view = InputView::<EditorMode>::new(cx);
10847        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10848        setup_cursors(&mut cx, &view.input, "(a|)");
10849        let before = view.input.read_with(&cx, |state, _| state.text.to_string());
10850        cx.update(|window, cx| {
10851            view.input.update(cx, |state, cx| {
10852                state.replace_text_in_range(Some(2..2), ")", window, cx);
10853            });
10854        });
10855        assert_cursors(&mut cx, &view.input, "(a)|");
10856        cx.update(|window, cx| {
10857            view.input
10858                .update(cx, |state, cx| state.undo(&Undo, window, cx));
10859        });
10860        view.input.read_with(&cx, |state, _| {
10861            assert_eq!(
10862                state.text.to_string(),
10863                before,
10864                "skip must not remove an existing closer on undo"
10865            );
10866        });
10867    }
10868
10869    #[gpui::test]
10870    fn test_pair_redo_restores_interior_cursor(cx: &mut TestAppContext) {
10871        let view = InputView::<EditorMode>::new(cx);
10872        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10873        setup_cursors(&mut cx, &view.input, "|");
10874        cx.update(|window, cx| {
10875            view.input.update(cx, |state, cx| {
10876                state.replace_text_in_range(None, "(", window, cx);
10877            });
10878        });
10879        assert_cursors(&mut cx, &view.input, "(|)");
10880        cx.update(|window, cx| {
10881            view.input.update(cx, |state, cx| {
10882                state.undo(&Undo, window, cx);
10883                state.redo(&Redo, window, cx);
10884            });
10885        });
10886        assert_cursors(&mut cx, &view.input, "(|)");
10887    }
10888
10889    #[gpui::test]
10890    fn test_pair_enter_redo_restores_interior_cursor(cx: &mut TestAppContext) {
10891        let view = InputView::<EditorMode>::new(cx);
10892        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10893        setup_cursors(&mut cx, &view.input, "{|}");
10894        cx.update(|window, cx| {
10895            view.input.update(cx, |state, cx| {
10896                state.enter(
10897                    &Enter {
10898                        secondary: false,
10899                        shift: false,
10900                    },
10901                    window,
10902                    cx,
10903                );
10904            });
10905        });
10906        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10907        cx.update(|window, cx| {
10908            view.input.update(cx, |state, cx| {
10909                state.undo(&Undo, window, cx);
10910            });
10911        });
10912        assert_cursors(&mut cx, &view.input, "{|}");
10913        cx.update(|window, cx| {
10914            view.input
10915                .update(cx, |state, cx| state.redo(&Redo, window, cx));
10916        });
10917        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10918    }
10919
10920    #[gpui::test]
10921    fn test_auto_close_parens(cx: &mut TestAppContext) {
10922        let view = InputView::<EditorMode>::new(cx);
10923        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10924        setup_cursors(&mut cx, &view.input, "|");
10925        cx.update(|window, cx| {
10926            view.input.update(cx, |state, cx| {
10927                state.replace_text_in_range(None, "(", window, cx);
10928            });
10929        });
10930        assert_cursors(&mut cx, &view.input, "(|)");
10931        // Selection must be collapsed: typing next must insert, not replace.
10932        cx.update(|window, cx| {
10933            view.input.update(cx, |state, cx| {
10934                state.replace_text_in_range(None, "x", window, cx);
10935            });
10936        });
10937        assert_cursors(&mut cx, &view.input, "(x|)");
10938    }
10939
10940    #[gpui::test]
10941    fn test_auto_close_skip_over_closer(cx: &mut TestAppContext) {
10942        let view = InputView::<EditorMode>::new(cx);
10943        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10944        setup_cursors(&mut cx, &view.input, "(|)");
10945        cx.update(|window, cx| {
10946            view.input.update(cx, |state, cx| {
10947                state.replace_text_in_range(None, ")", window, cx);
10948            });
10949        });
10950        // No duplicate closer, cursor moves past the existing one.
10951        assert_cursors(&mut cx, &view.input, "()|");
10952        // Selection must be collapsed, not covering blank space.
10953        view.input.read_with(&cx, |state, _| {
10954            assert!(state.active_selection().is_empty());
10955        });
10956    }
10957
10958    #[gpui::test]
10959    fn test_auto_close_no_pair_inside_word(cx: &mut TestAppContext) {
10960        let view = InputView::<EditorMode>::new(cx);
10961        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10962        setup_cursors(&mut cx, &view.input, "don|");
10963        cx.update(|window, cx| {
10964            view.input.update(cx, |state, cx| {
10965                state.replace_text_in_range(None, "'", window, cx);
10966            });
10967        });
10968        // Contraction: no auto-close.
10969        assert_cursors(&mut cx, &view.input, "don'|");
10970    }
10971
10972    #[gpui::test]
10973    fn test_auto_close_quote_after_cjk(cx: &mut TestAppContext) {
10974        let view = InputView::<EditorMode>::new(cx);
10975        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10976        // Unicode-safe previous-char lookup: must not panic on multi-byte prefix.
10977        // CJK counts as word-like, so no pairing — single quote inserted.
10978        setup_cursors(&mut cx, &view.input, "中|");
10979        cx.update(|window, cx| {
10980            view.input.update(cx, |state, cx| {
10981                state.replace_text_in_range(None, "'", window, cx);
10982            });
10983        });
10984        assert_cursors(&mut cx, &view.input, "中'|");
10985    }
10986
10987    #[gpui::test]
10988    fn test_auto_close_closer_skips_at_string_end(cx: &mut TestAppContext) {
10989        let view = InputView::<EditorMode>::new(cx);
10990        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10991        // Cursor before existing closing quote: typing `"` skips past it.
10992        setup_cursors(&mut cx, &view.input, "\"hello|\"");
10993        cx.update(|window, cx| {
10994            view.input.update(cx, |state, cx| {
10995                state.replace_text_in_range(None, "\"", window, cx);
10996            });
10997        });
10998        assert_cursors(&mut cx, &view.input, "\"hello\"|");
10999        view.input.read_with(&cx, |state, _| {
11000            assert!(state.active_selection().is_empty());
11001        });
11002    }
11003
11004    #[gpui::test]
11005    fn test_backspace_pair_with_cjk_prefix(cx: &mut TestAppContext) {
11006        let view = InputView::<EditorMode>::new(cx);
11007        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11008        // UTF-16 conversion: deleting `()` after CJK must not touch neighbors.
11009        setup_cursors(&mut cx, &view.input, "中(|)abc");
11010        cx.update(|window, cx| {
11011            view.input
11012                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
11013        });
11014        assert_cursors(&mut cx, &view.input, "中|abc");
11015    }
11016
11017    #[gpui::test]
11018    fn test_enter_split_respects_smart_indent_off(cx: &mut TestAppContext) {
11019        let view = InputView::<EditorMode>::new(cx);
11020        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11021        setup_cursors(&mut cx, &view.input, "{|}");
11022        cx.update(|window, cx| {
11023            view.input.update(cx, |state, cx| {
11024                state.set_smart_indent(false, window, cx);
11025                state.enter(
11026                    &Enter {
11027                        secondary: false,
11028                        shift: false,
11029                    },
11030                    window,
11031                    cx,
11032                );
11033            });
11034        });
11035        // Smart indent controls both structural splitting and extra indentation.
11036        assert_cursors(&mut cx, &view.input, "{\n|}");
11037    }
11038
11039    #[gpui::test]
11040    fn test_enter_split_is_independent_of_auto_close(cx: &mut TestAppContext) {
11041        let view = InputView::<EditorMode>::new(cx);
11042        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11043        setup_cursors(&mut cx, &view.input, "{|}");
11044        cx.update(|window, cx| {
11045            view.input.update(cx, |state, cx| {
11046                state.set_auto_close(false, window, cx);
11047                state.enter(
11048                    &Enter {
11049                        secondary: false,
11050                        shift: false,
11051                    },
11052                    window,
11053                    cx,
11054                );
11055            });
11056        });
11057        // Disabling automatic insertion does not disable structural indentation.
11058        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
11059    }
11060
11061    #[gpui::test]
11062    fn test_language_config_applies_before_render_and_on_language_change(cx: &mut TestAppContext) {
11063        use crate::input::{AutoClosingPair, set_language_config};
11064        let view = InputView::<EditorMode>::new(cx);
11065        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11066        cx.update(|window, cx| {
11067            set_language_config(
11068                "alpha",
11069                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
11070                cx,
11071            );
11072            set_language_config(
11073                "beta",
11074                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("‹", "›")]),
11075                cx,
11076            );
11077            let first = cx.new(|cx| EditorState::new(window, cx).language("ALPHA"));
11078            let second = cx.new(|cx| EditorState::new(window, cx).language("beta"));
11079            first.update(cx, |state, cx| {
11080                state.replace_text_in_range(None, "«", window, cx);
11081                assert_eq!(state.text().to_string(), "«»");
11082                state.set_value("", window, cx);
11083                state.set_smart_indent(false, window, cx);
11084                state.set_highlighter("beta", cx);
11085                assert!(!state.mode.is_smart_indent());
11086                state.replace_text_in_range(None, "‹", window, cx);
11087                assert_eq!(state.text().to_string(), "‹›");
11088                state.set_value("", window, cx);
11089                state.set_highlighter("unknown", cx);
11090                state.replace_text_in_range(None, "(", window, cx);
11091                assert_eq!(state.text().to_string(), "()");
11092            });
11093            second.update(cx, |state, cx| {
11094                state.replace_text_in_range(None, "‹", window, cx);
11095                assert_eq!(state.text().to_string(), "‹›");
11096            });
11097        });
11098    }
11099
11100    #[gpui::test]
11101    fn test_language_service_replacement_updates_syntax_without_render(cx: &mut TestAppContext) {
11102        use crate::input::{LanguageProvider, SyntaxContextProvider, set_language_provider};
11103        struct StringLanguages(Rc<Cell<usize>>);
11104        impl LanguageProvider for StringLanguages {
11105            fn syntax_context_provider(&self, _: &str) -> Option<Rc<dyn SyntaxContextProvider>> {
11106                self.0.set(self.0.get() + 1);
11107                Some(Rc::new(StringAllProvider))
11108            }
11109        }
11110        struct CodeLanguages;
11111        impl LanguageProvider for CodeLanguages {}
11112        let view = InputView::<EditorMode>::new(cx);
11113        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11114        cx.update(|window, cx| {
11115            let creations = Rc::new(Cell::new(0));
11116            set_language_provider(Rc::new(StringLanguages(creations.clone())), cx);
11117            view.input.update(cx, |state, cx| {
11118                state.replace_text_in_range(None, "(", window, cx);
11119                state.replace_text_in_range(None, "[", window, cx);
11120                assert_eq!(state.text().to_string(), "([");
11121                assert_eq!(
11122                    creations.get(),
11123                    1,
11124                    "retain the document provider between edits"
11125                );
11126            });
11127            set_language_provider(Rc::new(CodeLanguages), cx);
11128            view.input.update(cx, |state, cx| {
11129                state.replace_text_in_range(None, "{", window, cx);
11130                assert_eq!(state.text().to_string(), "([{}");
11131            });
11132        });
11133    }
11134
11135    #[gpui::test]
11136    fn test_language_config_updates_existing_editors(cx: &mut TestAppContext) {
11137        use crate::input::{AutoClosingPair, set_language_config};
11138        let view = InputView::<EditorMode>::new(cx);
11139        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11140        setup_cursors(&mut cx, &view.input, "|");
11141        cx.update(|window, cx| {
11142            view.input.update(cx, |state, cx| {
11143                state.set_highlighter("CUSTOM", cx);
11144                state.set_auto_close(false, window, cx);
11145                state.set_smart_indent(false, window, cx);
11146            });
11147            set_language_config(
11148                "custom",
11149                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
11150                cx,
11151            );
11152            // Batched registrations must update every affected language.
11153            set_language_config("other", LanguageConfig::default(), cx);
11154            view.input.update(cx, |state, cx| {
11155                assert!(!state.mode.is_auto_close());
11156                assert!(!state.mode.is_smart_indent());
11157                state.set_auto_close(true, window, cx);
11158                state.replace_text_in_range(None, "«", window, cx);
11159            });
11160        });
11161        assert_cursors(&mut cx, &view.input, "«|»");
11162    }
11163
11164    #[gpui::test]
11165    fn test_auto_close_before_is_language_configurable(cx: &mut TestAppContext) {
11166        let view = InputView::<EditorMode>::new(cx);
11167        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11168        setup_cursors(&mut cx, &view.input, "|word");
11169        cx.update(|window, cx| {
11170            view.input.update(cx, |state, cx| {
11171                state.replace_text_in_range(None, "(", window, cx)
11172            });
11173        });
11174        assert_cursors(&mut cx, &view.input, "(|word");
11175        setup_cursors(&mut cx, &view.input, "|word");
11176        cx.update(|window, cx| {
11177            view.input.update(cx, |state, cx| {
11178                crate::input::set_language_config(
11179                    state.language_name(),
11180                    LanguageConfig::default().auto_close_before("w"),
11181                    cx,
11182                );
11183                state.replace_text_in_range(None, "(", window, cx);
11184            });
11185        });
11186        assert_cursors(&mut cx, &view.input, "(|)word");
11187    }
11188
11189    #[gpui::test]
11190    fn test_pair_context_restrictions_are_per_pair(cx: &mut TestAppContext) {
11191        use crate::input::{AutoClosingPair, SyntaxContext};
11192        let view = InputView::<EditorMode>::new(cx);
11193        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11194        setup_cursors(&mut cx, &view.input, "|");
11195        cx.update(|window, cx| {
11196            view.input.update(cx, |state, cx| {
11197                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
11198                crate::input::set_language_config(
11199                    state.language_name(),
11200                    LanguageConfig::default().auto_closing_pairs([
11201                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::String]),
11202                        AutoClosingPair::new("[", "]").not_in([SyntaxContext::Comment]),
11203                    ]),
11204                    cx,
11205                );
11206                state.replace_text_in_range(None, "(", window, cx);
11207                state.replace_text_in_range(None, "[", window, cx);
11208            });
11209        });
11210        assert_cursors(&mut cx, &view.input, "([|)");
11211    }
11212
11213    #[gpui::test]
11214    fn test_multichar_delimiters_insert_delete_and_skip(cx: &mut TestAppContext) {
11215        use crate::input::{AutoClosingPair, SyntaxContext};
11216        struct BlockComment;
11217        impl crate::input::SyntaxContextProvider for BlockComment {
11218            fn context_at(&self, text: &ropey::Rope, _: usize) -> SyntaxContext {
11219                if text.to_string().contains("/*") {
11220                    SyntaxContext::Comment
11221                } else {
11222                    SyntaxContext::Code
11223                }
11224            }
11225        }
11226        let view = InputView::<EditorMode>::new(cx);
11227        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11228        setup_cursors(&mut cx, &view.input, "|");
11229        cx.update(|window, cx| {
11230            view.input.update(cx, |state, cx| {
11231                set_test_syntax_provider(std::rc::Rc::new(BlockComment), cx);
11232                crate::input::set_language_config(
11233                    state.language_name(),
11234                    LanguageConfig::default()
11235                        .auto_closing_pairs([AutoClosingPair::new("/*", "*/")
11236                            .not_in([SyntaxContext::String, SyntaxContext::Comment])]),
11237                    cx,
11238                );
11239                state.replace_text_in_range(None, "/", window, cx);
11240                state.replace_text_in_range(None, "*", window, cx);
11241            });
11242        });
11243        assert_cursors(&mut cx, &view.input, "/*|*/");
11244        cx.update(|window, cx| {
11245            view.input
11246                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
11247        });
11248        assert_cursors(&mut cx, &view.input, "|");
11249        cx.update(|window, cx| {
11250            view.input.update(cx, |state, cx| {
11251                state.undo(&Undo, window, cx);
11252                state.replace_text_in_range(None, "*", window, cx);
11253                state.replace_text_in_range(None, "/", window, cx);
11254            });
11255        });
11256        assert_cursors(&mut cx, &view.input, "/**/|");
11257    }
11258
11259    #[gpui::test]
11260    fn test_auto_closed_pairs_fallback_and_explicit_disable(cx: &mut TestAppContext) {
11261        use crate::input::BracketPair;
11262        let view = InputView::<EditorMode>::new(cx);
11263        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11264        setup_cursors(&mut cx, &view.input, "|");
11265        cx.update(|window, cx| {
11266            view.input.update(cx, |state, cx| {
11267                let mut rules = LanguageConfig::default().brackets([BracketPair::new("«", "»")]);
11268                rules.auto_closing_pairs = None;
11269                crate::input::set_language_config(state.language_name(), rules.clone(), cx);
11270                state.replace_text_in_range(None, "«", window, cx);
11271                crate::input::set_language_config(
11272                    state.language_name(),
11273                    rules.auto_closing_pairs([]),
11274                    cx,
11275                );
11276                state.replace_text_in_range(None, "«", window, cx);
11277            });
11278        });
11279        assert_cursors(&mut cx, &view.input, "««|»");
11280    }
11281
11282    #[gpui::test]
11283    fn test_undo_open_composition_preserves_generated_pairs(cx: &mut TestAppContext) {
11284        use crate::input::{AutoClosingPair, SyntaxContext};
11285        let view = InputView::<EditorMode>::new(cx);
11286        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11287        setup_cursors(&mut cx, &view.input, "|");
11288        cx.update(|window, cx| {
11289            view.input.update(cx, |state, cx| {
11290                crate::input::set_language_config(
11291                    state.language_name(),
11292                    LanguageConfig::default()
11293                        .auto_closing_pairs([
11294                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
11295                        ]),
11296                    cx,
11297                );
11298                state.replace_text_in_range(None, "/", window, cx);
11299                state.replace_text_in_range(None, "*", window, cx);
11300                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
11301                state.replace_and_mark_text_in_range(None, "x", Some(1..1), window, cx);
11302                state.undo(&Undo, window, cx);
11303                state.replace_text_in_range(None, "*", window, cx);
11304                state.replace_text_in_range(None, "/", window, cx);
11305            });
11306        });
11307        assert_cursors(&mut cx, &view.input, "/**/|");
11308    }
11309
11310    #[gpui::test]
11311    fn test_undo_does_not_promote_literal_comment_delimiters(cx: &mut TestAppContext) {
11312        use crate::input::{AutoClosingPair, SyntaxContext};
11313        let view = InputView::<EditorMode>::new(cx);
11314        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11315        setup_cursors(&mut cx, &view.input, "/*|*/");
11316        cx.update(|window, cx| {
11317            view.input.update(cx, |state, cx| {
11318                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
11319                crate::input::set_language_config(
11320                    state.language_name(),
11321                    LanguageConfig::default()
11322                        .auto_closing_pairs([
11323                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
11324                        ]),
11325                    cx,
11326                );
11327                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
11328                state.undo(&Undo, window, cx);
11329                state.backspace(&Backspace, window, cx);
11330            });
11331        });
11332        assert_cursors(&mut cx, &view.input, "/|*/");
11333    }
11334
11335    #[gpui::test]
11336    fn test_multiple_generated_pairs_retain_their_identity(cx: &mut TestAppContext) {
11337        use crate::input::{AutoClosingPair, SyntaxContext};
11338        struct CommentScope;
11339        impl crate::input::SyntaxContextProvider for CommentScope {
11340            fn context_at(&self, text: &ropey::Rope, offset: usize) -> SyntaxContext {
11341                if text
11342                    .to_string()
11343                    .find("*/")
11344                    .is_some_and(|end| offset < end + 2)
11345                {
11346                    SyntaxContext::Comment
11347                } else {
11348                    SyntaxContext::Code
11349                }
11350            }
11351        }
11352        let view = InputView::<EditorMode>::new(cx);
11353        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11354        setup_cursors(&mut cx, &view.input, "|");
11355        cx.update(|window, cx| {
11356            view.input.update(cx, |state, cx| {
11357                set_test_syntax_provider(std::rc::Rc::new(CommentScope), cx);
11358                crate::input::set_language_config(
11359                    state.language_name(),
11360                    LanguageConfig::default().auto_closing_pairs([
11361                        AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment]),
11362                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::Comment]),
11363                    ]),
11364                    cx,
11365                );
11366                state.replace_text_in_range(None, "/", window, cx);
11367                state.replace_text_in_range(None, "*", window, cx);
11368                state.set_selected_range(4..4, cx);
11369                state.replace_text_in_range(None, "(", window, cx);
11370                state.set_selected_range(2..2, cx);
11371                state.replace_text_in_range(None, "*", window, cx);
11372                state.replace_text_in_range(None, "/", window, cx);
11373            });
11374        });
11375        assert_cursors(&mut cx, &view.input, "/**/|()");
11376        cx.update(|window, cx| {
11377            view.input.update(cx, |state, cx| {
11378                state.set_selected_range(2..2, cx);
11379                state.backspace(&Backspace, window, cx);
11380                state.undo(&Undo, window, cx);
11381                state.redo(&Redo, window, cx);
11382            });
11383        });
11384        assert_cursors(&mut cx, &view.input, "|()");
11385    }
11386
11387    #[gpui::test]
11388    fn test_replacing_generated_opener_invalidates_the_pair(cx: &mut TestAppContext) {
11389        use crate::input::{AutoClosingPair, SyntaxContext};
11390        let view = InputView::<EditorMode>::new(cx);
11391        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11392        setup_cursors(&mut cx, &view.input, "|");
11393        cx.update(|window, cx| {
11394            view.input.update(cx, |state, cx| {
11395                crate::input::set_language_config(
11396                    state.language_name(),
11397                    LanguageConfig::default()
11398                        .auto_closing_pairs([
11399                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
11400                        ]),
11401                    cx,
11402                );
11403                state.replace_text_in_range(None, "/", window, cx);
11404                state.replace_text_in_range(None, "*", window, cx);
11405                state.replace_text_in_range(Some(0..2), "//", window, cx);
11406                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
11407                state.replace_text_in_range(None, "*", window, cx);
11408            });
11409        });
11410        assert_cursors(&mut cx, &view.input, "//*|*/");
11411    }
11412
11413    #[gpui::test]
11414    fn test_configuration_preserves_compiled_regex_options(cx: &mut TestAppContext) {
11415        use crate::input::IndentationRules;
11416        let view = InputView::<EditorMode>::new(cx);
11417        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11418        setup_cursors(&mut cx, &view.input, "BEGIN|");
11419        cx.update(|window, cx| {
11420            view.input.update(cx, |state, cx| {
11421                let rules = |insensitive| {
11422                    LanguageConfig::default().indentation_rules(IndentationRules::new(
11423                        regex::RegexBuilder::new("begin$")
11424                            .case_insensitive(insensitive)
11425                            .build()
11426                            .unwrap(),
11427                        regex::Regex::new("^end").unwrap(),
11428                    ))
11429                };
11430                crate::input::set_language_config(state.language_name(), rules(false), cx);
11431                crate::input::set_language_config(state.language_name(), rules(true), cx);
11432                state.enter(
11433                    &Enter {
11434                        secondary: false,
11435                        shift: false,
11436                    },
11437                    window,
11438                    cx,
11439                );
11440            });
11441        });
11442        assert_cursors(&mut cx, &view.input, "BEGIN\n  |");
11443    }
11444
11445    #[gpui::test]
11446    fn test_indent_patterns_are_applied_on_enter(cx: &mut TestAppContext) {
11447        use crate::input::IndentationRules;
11448        let view = InputView::<EditorMode>::new(cx);
11449        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11450        let rules = LanguageConfig::default().indentation_rules(IndentationRules::new(
11451            regex::Regex::new(r"\bbegin\s*$").unwrap(),
11452            regex::Regex::new(r"^\s*end\b").unwrap(),
11453        ));
11454        for (before, expected) in [("begin|", "begin\n  |"), ("  |end", "  \n|end")] {
11455            setup_cursors(&mut cx, &view.input, before);
11456            cx.update(|window, cx| {
11457                view.input.update(cx, |state, cx| {
11458                    crate::input::set_language_config(state.language_name(), rules.clone(), cx);
11459                    state.enter(
11460                        &Enter {
11461                            secondary: false,
11462                            shift: false,
11463                        },
11464                        window,
11465                        cx,
11466                    );
11467                });
11468            });
11469            assert_cursors(&mut cx, &view.input, expected);
11470        }
11471    }
11472
11473    #[gpui::test]
11474    fn test_custom_language_config(cx: &mut TestAppContext) {
11475        let view = InputView::<EditorMode>::new(cx);
11476        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11477        cx.update(|_window, cx| {
11478            view.input.update(cx, |state, cx| {
11479                crate::input::set_language_config(
11480                    state.language_name(),
11481                    crate::input::language_config::LanguageConfig::default()
11482                        .brackets([crate::input::BracketPair::new("«", "»")])
11483                        .auto_closing_pairs([crate::input::AutoClosingPair::new("«", "»")]),
11484                    cx,
11485                );
11486            });
11487        });
11488        // Custom pair closes.
11489        setup_cursors(&mut cx, &view.input, "|");
11490        cx.update(|window, cx| {
11491            view.input.update(cx, |state, cx| {
11492                state.replace_text_in_range(None, "«", window, cx);
11493            });
11494        });
11495        assert_cursors(&mut cx, &view.input, "«|»");
11496        // Default `(` no longer pairs under custom rules.
11497        cx.update(|window, cx| {
11498            view.input.update(cx, |state, cx| {
11499                state.set_value("\n", window, cx);
11500                state.set_selected_range(0..0, cx);
11501                state.replace_text_in_range(None, "(", window, cx);
11502            });
11503        });
11504        assert_cursors(&mut cx, &view.input, "(|");
11505    }
11506
11507    #[gpui::test]
11508    fn test_skip_records_no_history(cx: &mut TestAppContext) {
11509        let view = InputView::<EditorMode>::new(cx);
11510        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11511        setup_cursors(&mut cx, &view.input, "(a|)");
11512        cx.update(|window, cx| {
11513            view.input.update(cx, |state, cx| {
11514                // Type a normal char (recorded), then skip over `)`.
11515                state.replace_text_in_range(None, "b", window, cx);
11516            });
11517        });
11518        assert_cursors(&mut cx, &view.input, "(ab|)");
11519        cx.update(|window, cx| {
11520            view.input.update(cx, |state, cx| {
11521                state.replace_text_in_range(None, ")", window, cx);
11522            });
11523        });
11524        // Skip is a pure cursor move: no history entry of its own.
11525        assert_cursors(&mut cx, &view.input, "(ab)|");
11526        // Undo removes `b`, never exposing a transient `())`.
11527        cx.update(|window, cx| {
11528            view.input
11529                .update(cx, |state, cx| state.undo(&Undo, window, cx));
11530        });
11531        assert_cursors(&mut cx, &view.input, "(a|)");
11532    }
11533
11534    #[gpui::test]
11535    fn test_pair_insert_undo_removes_both(cx: &mut TestAppContext) {
11536        let view = InputView::<EditorMode>::new(cx);
11537        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11538        setup_cursors(&mut cx, &view.input, "|");
11539        cx.update(|window, cx| {
11540            view.input.update(cx, |state, cx| {
11541                state.replace_text_in_range(None, "(", window, cx);
11542            });
11543        });
11544        assert_cursors(&mut cx, &view.input, "(|)");
11545        // One undo removes the whole pair (typing coalescing).
11546        cx.update(|window, cx| {
11547            view.input
11548                .update(cx, |state, cx| state.undo(&Undo, window, cx));
11549        });
11550        assert_cursors(&mut cx, &view.input, "|");
11551    }
11552
11553    #[gpui::test]
11554    fn test_escaped_quote_does_not_skip(cx: &mut TestAppContext) {
11555        let view = InputView::<EditorMode>::new(cx);
11556        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11557        // Odd backslashes: the typed quote is escaped, insert literally.
11558        setup_cursors(&mut cx, &view.input, "\"hello\\|\"");
11559        cx.update(|window, cx| {
11560            view.input.update(cx, |state, cx| {
11561                state.replace_text_in_range(None, "\"", window, cx);
11562            });
11563        });
11564        assert_cursors(&mut cx, &view.input, "\"hello\\\"|\"");
11565    }
11566
11567    #[gpui::test]
11568    fn test_even_backslashes_still_skip(cx: &mut TestAppContext) {
11569        let view = InputView::<EditorMode>::new(cx);
11570        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11571        // Even backslashes: quote is not escaped, skip over the terminator.
11572        setup_cursors(&mut cx, &view.input, "\"hello\\\\|\"");
11573        cx.update(|window, cx| {
11574            view.input.update(cx, |state, cx| {
11575                state.replace_text_in_range(None, "\"", window, cx);
11576            });
11577        });
11578        assert_cursors(&mut cx, &view.input, "\"hello\\\\\"|");
11579    }
11580
11581    #[gpui::test]
11582    fn test_editor_options_survive_configuration_replacement(cx: &mut TestAppContext) {
11583        use crate::input::{BracketPair, LanguageConfig};
11584
11585        let view = InputView::<EditorMode>::new(cx);
11586        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11587        cx.update(|window, cx| {
11588            view.input.update(cx, |state, cx| {
11589                // Language changes update rules without changing editor preferences.
11590                state.set_smart_indent(false, window, cx);
11591                crate::input::set_language_config(
11592                    state.language_name(),
11593                    LanguageConfig::default().brackets([BracketPair::new("«", "»")]),
11594                    cx,
11595                );
11596            });
11597        });
11598        view.input.read_with(&cx, |state, _| {
11599            assert!(!state.mode.is_smart_indent(), "editor option preserved");
11600            assert!(state.mode.is_auto_close());
11601            let pairs = &state.mode.language_config().unwrap().brackets;
11602            assert_eq!(pairs.len(), 1, "pairs follow configuration");
11603            assert_eq!(pairs[0].open.as_ref(), "«");
11604        });
11605    }
11606
11607    struct CommentAllProvider;
11608    impl crate::input::SyntaxContextProvider for CommentAllProvider {
11609        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
11610            crate::input::SyntaxContext::Comment
11611        }
11612    }
11613
11614    struct StringAllProvider;
11615    impl crate::input::SyntaxContextProvider for StringAllProvider {
11616        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
11617            crate::input::SyntaxContext::String
11618        }
11619    }
11620
11621    #[gpui::test]
11622    fn test_smart_indent_suppressed_in_strings(cx: &mut TestAppContext) {
11623        use std::rc::Rc;
11624
11625        let view = InputView::<EditorMode>::new(cx);
11626        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11627        cx.update(|_window, cx| {
11628            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
11629        });
11630        // Trigger `{` inside a string: no extra level, base indent only.
11631        setup_cursors(&mut cx, &view.input, "  x = {|");
11632        cx.update(|window, cx| {
11633            view.input.update(cx, |state, cx| {
11634                state.enter(
11635                    &Enter {
11636                        secondary: false,
11637                        shift: false,
11638                    },
11639                    window,
11640                    cx,
11641                );
11642            });
11643        });
11644        assert_cursors(&mut cx, &view.input, "  x = {\n  |");
11645    }
11646
11647    #[gpui::test]
11648    fn test_split_suppressed_in_strings(cx: &mut TestAppContext) {
11649        use std::rc::Rc;
11650
11651        let view = InputView::<EditorMode>::new(cx);
11652        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11653        cx.update(|_window, cx| {
11654            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
11655        });
11656        // No three-way split inside strings: plain newline instead.
11657        setup_cursors(&mut cx, &view.input, "{|}");
11658        cx.update(|window, cx| {
11659            view.input.update(cx, |state, cx| {
11660                state.enter(
11661                    &Enter {
11662                        secondary: false,
11663                        shift: false,
11664                    },
11665                    window,
11666                    cx,
11667                );
11668            });
11669        });
11670        assert_cursors(&mut cx, &view.input, "{\n|}");
11671    }
11672
11673    #[gpui::test]
11674    fn test_comment_context_disables_pairing(cx: &mut TestAppContext) {
11675        use std::rc::Rc;
11676
11677        let view = InputView::<EditorMode>::new(cx);
11678        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11679        cx.update(|_window, cx| {
11680            set_test_syntax_provider(Rc::new(CommentAllProvider), cx);
11681        });
11682        setup_cursors(&mut cx, &view.input, "|");
11683        cx.update(|window, cx| {
11684            view.input.update(cx, |state, cx| {
11685                state.replace_text_in_range(None, "(", window, cx);
11686            });
11687        });
11688        // In comments every character is literal.
11689        assert_cursors(&mut cx, &view.input, "(|");
11690    }
11691
11692    #[gpui::test]
11693    fn test_backspace_deletes_empty_pair(cx: &mut TestAppContext) {
11694        let view = InputView::<EditorMode>::new(cx);
11695        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11696        setup_cursors(&mut cx, &view.input, "{|}");
11697        cx.update(|window, cx| {
11698            view.input
11699                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
11700        });
11701        assert_cursors(&mut cx, &view.input, "|");
11702    }
11703
11704    #[gpui::test]
11705    fn test_backspace_keeps_closer_with_content(cx: &mut TestAppContext) {
11706        let view = InputView::<EditorMode>::new(cx);
11707        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11708        setup_cursors(&mut cx, &view.input, "{d|}");
11709        cx.update(|window, cx| {
11710            view.input
11711                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
11712        });
11713        // Only `d` is deleted, the closing brace stays.
11714        assert_cursors(&mut cx, &view.input, "{|}");
11715    }
11716
11717    #[gpui::test]
11718    fn test_enter_splits_brackets(cx: &mut TestAppContext) {
11719        let view = InputView::<EditorMode>::new(cx);
11720        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11721        setup_cursors(&mut cx, &view.input, "{|}");
11722        cx.update(|window, cx| {
11723            view.input.update(cx, |state, cx| {
11724                state.enter(
11725                    &Enter {
11726                        secondary: false,
11727                        shift: false,
11728                    },
11729                    window,
11730                    cx,
11731                );
11732            });
11733        });
11734        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
11735        // Selection must be collapsed: typing next must insert, not replace.
11736        view.input.read_with(&cx, |state, _| {
11737            assert!(state.active_selection().is_empty());
11738        });
11739        cx.update(|window, cx| {
11740            view.input.update(cx, |state, cx| {
11741                state.replace_text_in_range(None, "x", window, cx);
11742            });
11743        });
11744        assert_cursors(&mut cx, &view.input, "{\n  x|\n}");
11745    }
11746
11747    #[gpui::test]
11748    fn test_enter_smart_indent_after_brace(cx: &mut TestAppContext) {
11749        let view = InputView::<EditorMode>::new(cx);
11750        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11751        setup_cursors(&mut cx, &view.input, "fn main() {|");
11752        cx.update(|window, cx| {
11753            view.input.update(cx, |state, cx| {
11754                state.enter(
11755                    &Enter {
11756                        secondary: false,
11757                        shift: false,
11758                    },
11759                    window,
11760                    cx,
11761                );
11762            });
11763        });
11764        assert_cursors(&mut cx, &view.input, "fn main() {\n  |");
11765    }
11766
11767    #[gpui::test]
11768    fn test_block_indent_tracks_all_preceding_edits(cx: &mut TestAppContext) {
11769        let view = multi_line(cx);
11770        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11771        setup_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
11772        cx.update(|window, cx| {
11773            view.input
11774                .update(cx, |state, cx| state.indent(true, window, cx));
11775        });
11776        assert_cursors(&mut cx, &view.input, "  |ab\n  |cd\n  |ef");
11777        cx.update(|window, cx| {
11778            view.input
11779                .update(cx, |state, cx| state.outdent(true, window, cx));
11780        });
11781        assert_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
11782    }
11783
11784    #[gpui::test]
11785    fn test_block_outdent_clamps_cursor_inside_indent(cx: &mut TestAppContext) {
11786        let view = multi_line(cx);
11787        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11788        setup_cursors(&mut cx, &view.input, "ab\n | cd");
11789        cx.update(|window, cx| {
11790            view.input
11791                .update(cx, |state, cx| state.outdent(true, window, cx));
11792        });
11793        assert_cursors(&mut cx, &view.input, "ab\n|cd");
11794    }
11795
11796    #[gpui::test]
11797    fn test_ime_restores_original_selection(cx: &mut TestAppContext) {
11798        let view = InputView::build(cx, |state| state);
11799        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11800        cx.update(|window, cx| {
11801            view.input.update(cx, |state, cx| {
11802                state.set_value("abc", window, cx);
11803                state.set_selected_range(1..2, cx);
11804                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
11805                state.replace_text_in_range(None, "你", window, cx);
11806                state.undo(&Undo, window, cx);
11807                assert_eq!(state.value(), "abc");
11808                assert_eq!(state.selected_range(), 1..2);
11809                state.redo(&Redo, window, cx);
11810                assert_eq!(state.selected_range(), 4..4);
11811            });
11812        });
11813    }
11814
11815    #[gpui::test]
11816    fn test_noop_does_not_change_redo_selection(cx: &mut TestAppContext) {
11817        let view = multi_line(cx);
11818        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11819        cx.update(|window, cx| {
11820            view.input.update(cx, |state, cx| {
11821                state.set_value("abc", window, cx);
11822                state.set_selected_range(1..1, cx);
11823                state.replace_text_in_range(None, "X", window, cx);
11824                state.set_selected_range(0..0, cx);
11825                state.replace_text_in_range(None, "", window, cx);
11826                state.undo(&Undo, window, cx);
11827                state.redo(&Redo, window, cx);
11828                assert_eq!(state.value(), "aXbc");
11829                assert_eq!(state.selected_range(), 2..2);
11830            });
11831        });
11832    }
11833
11834    #[gpui::test]
11835    fn test_multi_cursor_insert_text(cx: &mut TestAppContext) {
11836        let view = multi_line(cx);
11837        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11838        let input = view.input;
11839
11840        setup_cursors(&mut cx, &input, "|hello |world|");
11841        cx.update(|window, cx| {
11842            input.update(cx, |state, cx| {
11843                state.replace_text_in_range(None, ">>>", window, cx);
11844            });
11845        });
11846        assert_cursors(&mut cx, &input, ">>>|hello >>>|world>>>|");
11847    }
11848
11849    #[gpui::test]
11850    fn test_multi_cursor_delete_backward(cx: &mut TestAppContext) {
11851        let view = multi_line(cx);
11852        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11853        let input = view.input;
11854
11855        setup_cursors(&mut cx, &input, "|islands| cars|");
11856        cx.update(|window, cx| {
11857            input.update(cx, |state, cx| {
11858                state.backspace(&Backspace, window, cx);
11859            });
11860        });
11861        // The first cursor has nothing to delete. The others delete an `s`.
11862        assert_cursors(&mut cx, &input, "|island| car|");
11863    }
11864
11865    #[gpui::test]
11866    fn test_multi_cursor_delete_forward_merges(cx: &mut TestAppContext) {
11867        let view = multi_line(cx);
11868        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11869        let input = view.input;
11870
11871        setup_cursors(&mut cx, &input, "hello| |world");
11872        cx.update(|window, cx| {
11873            input.update(cx, |state, cx| {
11874                state.delete(&Delete, window, cx);
11875            });
11876        });
11877        // Adjacent deletions merge into a single cursor.
11878        assert_cursors(&mut cx, &input, "hello|orld");
11879    }
11880
11881    #[gpui::test]
11882    fn test_multi_cursor_multiline_insert_and_delete(cx: &mut TestAppContext) {
11883        let view = multi_line(cx);
11884        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11885        let input = view.input;
11886
11887        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11888        cx.update(|window, cx| {
11889            input.update(cx, |state, cx| {
11890                state.replace_text_in_range(None, "a", window, cx);
11891            });
11892        });
11893        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11894
11895        // The whole multi-edit insert is a single undo transaction.
11896        input.read_with(&cx, |state, _| {
11897            assert_eq!(state.undo_manager.undo_count(), 1);
11898        });
11899
11900        cx.update(|window, cx| {
11901            input.update(cx, |state, cx| {
11902                state.backspace(&Backspace, window, cx);
11903            });
11904        });
11905        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11906    }
11907
11908    #[gpui::test]
11909    fn test_add_cursor_below_preserves_column(cx: &mut TestAppContext) {
11910        let view = multi_line(cx);
11911        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11912        let input = view.input;
11913
11914        setup_cursors(&mut cx, &input, "ab|cd\nabcd");
11915        cx.update(|window, cx| {
11916            input.update(cx, |state, cx| {
11917                state.add_cursor_below(&AddCursorBelow, window, cx);
11918            });
11919        });
11920        assert_cursors(&mut cx, &input, "ab|cd\nab|cd");
11921    }
11922
11923    #[gpui::test]
11924    fn test_add_cursor_at_rejects_duplicates(cx: &mut TestAppContext) {
11925        let view = multi_line(cx);
11926        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11927        let input = view.input;
11928
11929        setup_cursors(&mut cx, &input, "he|llo");
11930        cx.update(|_, cx| {
11931            input.update(cx, |state, cx| {
11932                // Duplicate of the existing cursor is rejected.
11933                state.add_cursor_at(2, cx);
11934                assert_eq!(state.selections.len(), 1);
11935                // A distinct offset adds a cursor.
11936                state.add_cursor_at(4, cx);
11937                assert_eq!(state.selections.len(), 2);
11938            });
11939        });
11940    }
11941
11942    #[gpui::test]
11943    fn test_multi_cursor_undo_redo_restores_selections(cx: &mut TestAppContext) {
11944        let view = multi_line(cx);
11945        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11946        let input = view.input;
11947
11948        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11949        cx.update(|window, cx| {
11950            input.update(cx, |state, cx| {
11951                state.replace_text_in_range(None, "a", window, cx);
11952            });
11953        });
11954        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11955
11956        cx.update(|window, cx| {
11957            input.update(cx, |state, cx| {
11958                state.undo(&Undo, window, cx);
11959            });
11960        });
11961        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11962
11963        cx.update(|window, cx| {
11964            input.update(cx, |state, cx| {
11965                state.redo(&Redo, window, cx);
11966            });
11967        });
11968        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11969    }
11970
11971    #[gpui::test]
11972    fn test_multi_cursor_undo_redo_different_line_lengths(cx: &mut TestAppContext) {
11973        let view = multi_line(cx);
11974        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11975        let input = view.input;
11976
11977        setup_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
11978        cx.update(|window, cx| {
11979            input.update(cx, |state, cx| {
11980                state.replace_text_in_range(None, "a", window, cx);
11981            });
11982        });
11983        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
11984
11985        cx.update(|window, cx| {
11986            input.update(cx, |state, cx| {
11987                state.undo(&Undo, window, cx);
11988            });
11989        });
11990        assert_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
11991
11992        cx.update(|window, cx| {
11993            input.update(cx, |state, cx| {
11994                state.redo(&Redo, window, cx);
11995            });
11996        });
11997        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
11998    }
11999
12000    #[gpui::test]
12001    fn test_multi_cursor_undo_multiple_inserts(cx: &mut TestAppContext) {
12002        let view = multi_line(cx);
12003        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12004        let input = view.input;
12005
12006        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
12007        for ch in ['a', 'b', 'c'] {
12008            cx.update(|window, cx| {
12009                input.update(cx, |state, cx| {
12010                    state.replace_text_in_range(None, &ch.to_string(), window, cx);
12011                });
12012            });
12013        }
12014        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
12015
12016        // The repeated keystrokes form one typing gesture.
12017        cx.update(|window, cx| {
12018            input.update(cx, |state, cx| {
12019                assert_eq!(state.undo_manager.undo_count(), 1);
12020                state.undo(&Undo, window, cx);
12021            });
12022        });
12023        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
12024
12025        cx.update(|window, cx| {
12026            input.update(cx, |state, cx| {
12027                state.redo(&Redo, window, cx);
12028            });
12029        });
12030        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
12031    }
12032
12033    #[gpui::test]
12034    fn test_multi_cursor_backspace_run_is_one_undo(cx: &mut TestAppContext) {
12035        let view = multi_line(cx);
12036        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12037        let input = view.input;
12038
12039        setup_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
12040        for _ in 0..3 {
12041            cx.update(|window, cx| {
12042                input.update(cx, |state, cx| {
12043                    state.backspace(&Backspace, window, cx);
12044                });
12045            });
12046        }
12047        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
12048
12049        cx.update(|window, cx| {
12050            input.update(cx, |state, cx| {
12051                assert_eq!(state.undo_manager.undo_count(), 1);
12052                state.undo(&Undo, window, cx);
12053            });
12054        });
12055        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
12056    }
12057
12058    #[gpui::test]
12059    fn test_adding_a_cursor_splits_the_typing_gesture(cx: &mut TestAppContext) {
12060        let view = multi_line(cx);
12061        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12062        let input = view.input;
12063
12064        setup_cursors(&mut cx, &input, "|1\n2\n3");
12065        cx.update(|window, cx| {
12066            input.update(cx, |state, cx| {
12067                state.replace_text_in_range(None, "a", window, cx);
12068                state.add_cursor_below(&AddCursorBelow, window, cx);
12069                state.replace_text_in_range(None, "b", window, cx);
12070            });
12071        });
12072        assert_cursors(&mut cx, &input, "ab|1\n2b|\n3");
12073
12074        // The keystroke after the cursor was added is its own undo entry.
12075        cx.update(|window, cx| {
12076            input.update(cx, |state, cx| {
12077                state.undo(&Undo, window, cx);
12078            });
12079        });
12080        assert_cursors(&mut cx, &input, "a|1\n2|\n3");
12081    }
12082
12083    #[gpui::test]
12084    fn test_multi_cursor_indent_is_one_undo(cx: &mut TestAppContext) {
12085        let view = multi_line(cx);
12086        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12087        let input = view.input;
12088
12089        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
12090        cx.update(|window, cx| {
12091            input.update(cx, |state, cx| {
12092                state.indent_inline(&IndentInline, window, cx);
12093                assert_eq!(state.undo_manager.undo_count(), 1);
12094                state.undo(&Undo, window, cx);
12095            });
12096        });
12097        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
12098    }
12099
12100    #[gpui::test]
12101    fn test_multi_cursor_indent_then_outdent_roundtrips(cx: &mut TestAppContext) {
12102        let view = multi_line(cx);
12103        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12104        let input = view.input;
12105
12106        // Cursors at line starts.
12107        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
12108        cx.update(|window, cx| {
12109            input.update(cx, |state, cx| {
12110                state.indent(false, window, cx);
12111            });
12112        });
12113        assert_cursors(&mut cx, &input, "  |1\n  |2\n  |3");
12114
12115        cx.update(|window, cx| {
12116            input.update(cx, |state, cx| {
12117                state.outdent(false, window, cx);
12118            });
12119        });
12120        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
12121    }
12122
12123    #[gpui::test]
12124    fn test_inline_outdent_only_removes_line_indentation(cx: &mut TestAppContext) {
12125        let view = multi_line(cx);
12126        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12127        let input = view.input;
12128
12129        // A mid-line indent lands at the cursor, and the outdent does not
12130        // take it back: it only ever removes leading line indentation.
12131        setup_cursors(&mut cx, &input, "1|2\n1|2");
12132        cx.update(|window, cx| {
12133            input.update(cx, |state, cx| {
12134                state.indent(false, window, cx);
12135            });
12136        });
12137        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
12138
12139        cx.update(|window, cx| {
12140            input.update(cx, |state, cx| {
12141                state.outdent(false, window, cx);
12142            });
12143        });
12144        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
12145
12146        // A line with leading indentation loses that, wherever the cursor is.
12147        setup_cursors(&mut cx, &input, "  1|2\n  1|2");
12148        cx.update(|window, cx| {
12149            input.update(cx, |state, cx| {
12150                state.outdent(false, window, cx);
12151            });
12152        });
12153        assert_cursors(&mut cx, &input, "1|2\n1|2");
12154    }
12155
12156    #[gpui::test]
12157    fn test_readonly_multi_cursor_commands_leave_state_unchanged(cx: &mut TestAppContext) {
12158        let view = multi_line(cx);
12159        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12160        let input = view.input;
12161
12162        setup_cursors(&mut cx, &input, "a|b\nc|d");
12163        cx.update(|window, cx| {
12164            input.update(cx, |state, cx| {
12165                let before: Vec<_> = state.selections.iter().copied().collect();
12166                state.set_readonly(true, cx);
12167                state.backspace(&Backspace, window, cx);
12168                state.indent_inline(&IndentInline, window, cx);
12169
12170                assert_eq!(state.value(), "ab\ncd\n");
12171                assert_eq!(state.selections.iter().copied().collect::<Vec<_>>(), before);
12172            });
12173        });
12174    }
12175
12176    #[gpui::test]
12177    fn test_multi_cursor_edit_preserves_the_active_cursor(cx: &mut TestAppContext) {
12178        let view = multi_line(cx);
12179        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12180        let input = view.input;
12181
12182        setup_cursors(&mut cx, &input, "x|\n|y");
12183        cx.update(|window, cx| {
12184            input.update(cx, |state, cx| {
12185                let mut selections: Vec<_> = state.selections.iter().copied().collect();
12186                selections.swap(0, 1);
12187                state.selections.replace_all(selections);
12188                let active_id = state.active_selection().id;
12189                state.replace_text_in_range(None, "!", window, cx);
12190                assert_eq!(state.active_selection().id, active_id);
12191            });
12192        });
12193    }
12194
12195    #[gpui::test]
12196    fn test_block_indent_outdent_with_selection(cx: &mut TestAppContext) {
12197        let view = multi_line(cx);
12198        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12199        let input = view.input;
12200
12201        cx.update(|window, cx| {
12202            input.update(cx, |state, cx| {
12203                state.set_value("line1\nline2\nline3", window, cx);
12204                let id = state.selections.generate_id();
12205                state
12206                    .selections
12207                    .replace_all(vec![CursorSelection::new(id, 0, 17)]);
12208                cx.notify();
12209            });
12210        });
12211
12212        cx.update(|window, cx| {
12213            input.update(cx, |state, cx| {
12214                state.indent(true, window, cx);
12215            });
12216        });
12217        input.read_with(&cx, |state, _| {
12218            assert_eq!(state.text.to_string(), "  line1\n  line2\n  line3");
12219        });
12220
12221        cx.update(|window, cx| {
12222            input.update(cx, |state, cx| {
12223                state.outdent(true, window, cx);
12224            });
12225        });
12226        input.read_with(&cx, |state, _| {
12227            assert_eq!(state.text.to_string(), "line1\nline2\nline3");
12228        });
12229    }
12230
12231    #[gpui::test]
12232    fn test_multi_cursor_word_movement(cx: &mut TestAppContext) {
12233        let view = multi_line(cx);
12234        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12235        let input = view.input;
12236
12237        setup_cursors(
12238            &mut cx,
12239            &input,
12240            "on|e two three\none t|wo three\non|e two three",
12241        );
12242
12243        cx.update(|window, cx| {
12244            input.update(cx, |state, cx| {
12245                state.move_to_next_word(&MoveToNextWord, window, cx);
12246            });
12247        });
12248        assert_cursors(
12249            &mut cx,
12250            &input,
12251            "one| two three\none two| three\none| two three",
12252        );
12253
12254        cx.update(|window, cx| {
12255            input.update(cx, |state, cx| {
12256                state.move_to_previous_word(&MoveToPreviousWord, window, cx);
12257            });
12258        });
12259        assert_cursors(
12260            &mut cx,
12261            &input,
12262            "|one two three\none |two three\n|one two three",
12263        );
12264
12265        // Move to end/start of document collapses to a single cursor.
12266        cx.update(|window, cx| {
12267            input.update(cx, |state, cx| {
12268                state.move_to_end(&MoveToEnd, window, cx);
12269            });
12270        });
12271        input.read_with(&cx, |state, _| {
12272            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
12273            assert_eq!(cursors, vec![state.text.len()]);
12274        });
12275
12276        cx.update(|window, cx| {
12277            input.update(cx, |state, cx| {
12278                state.move_to_start(&MoveToStart, window, cx);
12279            });
12280        });
12281        input.read_with(&cx, |state, _| {
12282            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
12283            assert_eq!(cursors, vec![0]);
12284        });
12285    }
12286
12287    #[gpui::test]
12288    fn test_multi_cursor_selection_commands(cx: &mut TestAppContext) {
12289        let view = multi_line(cx);
12290        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12291        let input = view.input;
12292
12293        setup_cursors(
12294            &mut cx,
12295            &input,
12296            "on|e two three\none t|wo three\non|e two three",
12297        );
12298        cx.update(|window, cx| {
12299            input.update(cx, |state, cx| {
12300                state.select_to_start_of_line(&SelectToStartOfLine, window, cx);
12301            });
12302        });
12303        assert_cursors(
12304            &mut cx,
12305            &input,
12306            "|one two three\n|one two three\n|one two three",
12307        );
12308
12309        // Select to document start collapses to the active cursor only.
12310        setup_cursors(
12311            &mut cx,
12312            &input,
12313            "on|e two three\none t|wo three\non|e two three",
12314        );
12315        cx.update(|window, cx| {
12316            input.update(cx, |state, cx| {
12317                state.select_to_start(&SelectToStart, window, cx);
12318            });
12319        });
12320        input.read_with(&cx, |state, _| {
12321            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
12322            assert_eq!(cursors, vec![0]);
12323        });
12324    }
12325
12326    #[gpui::test]
12327    fn test_multi_cursor_replace_selection(cx: &mut TestAppContext) {
12328        let view = multi_line(cx);
12329        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12330        let input = view.input;
12331
12332        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
12333        cx.update(|window, cx| {
12334            input.update(cx, |state, cx| {
12335                state.select_right(&SelectRight, window, cx);
12336            });
12337        });
12338        input.read_with(&cx, |state, _| {
12339            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
12340            assert_eq!(ranges, vec![(0, 1), (2, 3), (4, 5)]);
12341        });
12342
12343        cx.update(|window, cx| {
12344            input.update(cx, |state, cx| {
12345                state.replace_text_in_range(None, "x", window, cx);
12346            });
12347        });
12348        assert_cursors(&mut cx, &input, "x|\nx|\nx|");
12349    }
12350
12351    #[gpui::test]
12352    fn test_multi_cursor_escape_collapses(cx: &mut TestAppContext) {
12353        let view = multi_line(cx);
12354        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12355        let input = view.input;
12356
12357        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
12358        cx.update(|window, cx| {
12359            input.update(cx, |state, cx| {
12360                state.escape(&Escape, window, cx);
12361            });
12362        });
12363        input.read_with(&cx, |state, _| {
12364            assert_eq!(state.selections.len(), 1);
12365        });
12366    }
12367
12368    #[gpui::test]
12369    fn test_build_columnar_selection(cx: &mut TestAppContext) {
12370        let view = multi_line(cx);
12371        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12372        let input = view.input;
12373
12374        setup_cursors(&mut cx, &input, "abcd\nabcd\nabcd");
12375        cx.update(|_, cx| {
12376            input.update(cx, |state, cx| {
12377                // From row 0 col 1 to row 2 col 3.
12378                state.build_columnar_selection(
12379                    ColumnarPoint::new(1, 0),
12380                    ColumnarPoint::new(13, 0),
12381                    cx,
12382                );
12383                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
12384                assert_eq!(ranges, vec![(1, 3), (6, 8), (11, 13)]);
12385            });
12386        });
12387    }
12388
12389    #[gpui::test]
12390    fn test_columnar_selection_keeps_width_over_short_rows(cx: &mut TestAppContext) {
12391        let view = multi_line(cx);
12392        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12393        let input = view.input;
12394
12395        setup_cursors(&mut cx, &input, "abcdef\nab\nabcdef");
12396        cx.update(|_, cx| {
12397            input.update(cx, |state, cx| {
12398                // Ends on the short row, 3 columns past its end: the block still spans
12399                // columns 1..5, and only that row is clipped to what it has.
12400                state.build_columnar_selection(
12401                    ColumnarPoint::new(1, 0),
12402                    ColumnarPoint::new(9, 3),
12403                    cx,
12404                );
12405                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
12406                assert_eq!(ranges, vec![(1, 5), (8, 9)]);
12407            });
12408        });
12409    }
12410
12411    #[gpui::test]
12412    fn test_multi_cursor_paste_distributes_lines(cx: &mut TestAppContext) {
12413        let view = multi_line(cx);
12414        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12415        let input = view.input;
12416
12417        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
12418        cx.update(|_, cx| {
12419            cx.write_to_clipboard(ClipboardItem::new_string("x\ny\nz".to_string()));
12420        });
12421        cx.update(|window, cx| {
12422            input.update(cx, |state, cx| {
12423                state.paste(&Paste, window, cx);
12424            });
12425        });
12426        // One clipboard line per cursor.
12427        assert_cursors(&mut cx, &input, "x|1\ny|2\nz|3");
12428    }
12429
12430    #[gpui::test]
12431    fn test_paste_without_text_leaves_the_selection_alone(cx: &mut TestAppContext) {
12432        use gpui::{ClipboardEntry, Image, ImageFormat};
12433
12434        let input_view = InputView::build(cx, |state| state);
12435        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
12436        let input = input_view.input;
12437
12438        cx.update(|window, cx| {
12439            input.update(cx, |state, cx| {
12440                state.set_value("hello world", window, cx);
12441                state.select_all(window, cx);
12442                cx.write_to_clipboard(ClipboardItem {
12443                    entries: vec![ClipboardEntry::Image(Image::from_bytes(
12444                        ImageFormat::Png,
12445                        vec![0x89, b'P', b'N', b'G'],
12446                    ))],
12447                });
12448
12449                // An image-only clipboard must not replace the selection
12450                // with nothing.
12451                state.paste(&Paste, window, cx);
12452                assert_eq!(state.value(), "hello world");
12453                assert_eq!(state.selected_range(), 0..11);
12454            });
12455        });
12456    }
12457
12458    #[gpui::test]
12459    fn test_paste_target_tracks_edits_and_selections(cx: &mut TestAppContext) {
12460        let view = multi_line(cx);
12461        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12462        let input = view.input;
12463
12464        setup_cursors(&mut cx, &input, "ab|c");
12465        let target = input.read_with(&cx, |state, _| state.paste_target());
12466        // Nothing happened: a paste asked for then still applies.
12467        assert_eq!(
12468            input.read_with(&cx, |state, _| state.paste_target()),
12469            target
12470        );
12471
12472        // Moving the caret changes where the paste would go.
12473        cx.update(|_, cx| {
12474            input.update(cx, |state, cx| {
12475                let id = state.selections.generate_id();
12476                state
12477                    .selections
12478                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
12479                cx.notify();
12480            });
12481        });
12482        let moved = input.read_with(&cx, |state, _| state.paste_target());
12483        assert_ne!(moved, target);
12484
12485        // Editing the text changes it too, even with the caret put back.
12486        cx.update(|window, cx| {
12487            input.update(cx, |state, cx| {
12488                state.replace_text_in_range_silent(None, "x", window, cx);
12489                let id = state.selections.generate_id();
12490                state
12491                    .selections
12492                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
12493            });
12494        });
12495        assert_ne!(input.read_with(&cx, |state, _| state.paste_target()), moved);
12496    }
12497
12498    #[gpui::test]
12499    fn test_ime_candidate_bounds_fallback_before_repaint(cx: &mut TestAppContext) {
12500        let input_view = InputView::build_textarea(cx, |state| {
12501            state.default_value("这是一段已经输入的中文文字")
12502        });
12503        let mut visual = VisualTestContext::from_window(input_view.window_handle.into(), cx);
12504        visual.update(|window, cx| window.draw(cx).clear(cx));
12505
12506        visual.update(|window, cx| {
12507            input_view.input.update(cx, |state, cx| {
12508                state.focus(window, cx);
12509                let len = state.text.len();
12510                state.set_cursor_to(len);
12511            });
12512        });
12513        visual.update(|window, cx| window.draw(cx).clear(cx));
12514
12515        visual.update(|window, cx| {
12516            input_view.input.update(cx, |state, cx| {
12517                let bounds = state.text_bounds().unwrap_or_default();
12518                let before = state.selected_text_range(false, window, cx).unwrap().range;
12519                let previous = state
12520                    .bounds_for_range(before.end..before.end, bounds, window, cx)
12521                    .unwrap();
12522
12523                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
12524
12525                let after = state.selected_text_range(false, window, cx).unwrap().range;
12526                let pending = state
12527                    .bounds_for_range(after.end..after.end, bounds, window, cx)
12528                    .unwrap();
12529
12530                assert!(
12531                    (pending.origin.x - previous.origin.x).abs() < px(40.0),
12532                    "pending origin.x {:?} must remain close to previous origin.x {:?}",
12533                    pending.origin.x,
12534                    previous.origin.x
12535                );
12536
12537                let range_bounds = state
12538                    .bounds_for_range(before.end..after.end, bounds, window, cx)
12539                    .unwrap();
12540                assert!(
12541                    (range_bounds.origin.x - previous.origin.x).abs() < px(40.0),
12542                    "range_bounds origin.x {:?} must remain close to previous origin.x {:?}",
12543                    range_bounds.origin.x,
12544                    previous.origin.x
12545                );
12546                assert!(range_bounds.size.width >= px(0.0));
12547            });
12548        });
12549    }
12550}
12551
12552/// Methods that only a single-line input offers.
12553impl InputBaseState<crate::input::InputMode> {
12554    /// Create a single-line text input state.
12555    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
12556        Self::new_in_mode(window, cx)
12557    }
12558
12559    /// Set a custom step function of the [`super::NumberInput`].
12560    ///
12561    /// The `f` receives the current value and the [`StepAction`], and returns
12562    /// the step to apply, so the step can vary with the value.
12563    ///
12564    /// # Example
12565    ///
12566    /// ```ignore
12567    /// // At the boundary 1.0 the step is 0.1 going down and 0.5 going up.
12568    /// InputState::new(window, cx).step_by(|value, action, _cx| match action {
12569    ///     StepAction::Increment => if value < 1.0 { 0.1 } else { 0.5 },
12570    ///     StepAction::Decrement => if value <= 1.0 { 0.1 } else { 0.5 },
12571    /// })
12572    /// ```
12573    pub fn step_by(mut self, f: impl Fn(f64, StepAction, &mut App) -> f64 + 'static) -> Self {
12574        self.number_step = Some(NumberStep::by_value(f));
12575        self
12576    }
12577
12578    /// Set with password masked state.
12579    pub fn masked(mut self, masked: bool) -> Self {
12580        self.masked = masked;
12581        self
12582    }
12583
12584    /// Set the password masked state of the input field.
12585    pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
12586        self.masked = masked;
12587        cx.notify();
12588    }
12589
12590    /// Set the regular expression pattern of the input field.
12591    pub fn pattern(mut self, pattern: regex::Regex) -> Self {
12592        self.pattern = Some(pattern);
12593        self
12594    }
12595
12596    /// Set the regular expression pattern of the input field with reference.
12597    pub fn set_pattern(
12598        &mut self,
12599        pattern: regex::Regex,
12600        _window: &mut Window,
12601        _cx: &mut Context<Self>,
12602    ) {
12603        self.pattern = Some(pattern);
12604    }
12605
12606    /// Set the validation function of the input field.
12607    pub fn validate(mut self, f: impl Fn(&str, &mut App) -> bool + 'static) -> Self {
12608        self.validate = Some(Box::new(f));
12609        self
12610    }
12611
12612    pub fn set_validator(
12613        &mut self,
12614        validate: impl Fn(&str, &mut App) -> bool + 'static,
12615        _cx: &mut Context<Self>,
12616    ) {
12617        self.validate = Some(Box::new(validate));
12618    }
12619
12620    /// Set the step value of the [`super::NumberInput`] for increment/decrement.
12621    ///
12622    /// If any of `step`, `min`, `max` is set, the [`super::NumberInput`] will
12623    /// update the value internally (step by `step`, default 1, clamp to the
12624    /// `min`/`max` range and emit [`InputEvent::Change`]) instead of emitting
12625    /// [`super::NumberInputEvent::Step`].
12626    ///
12627    /// See also [`Self::step_by`] to calculate the step value
12628    /// based on the current value.
12629    pub fn step(mut self, step: impl Into<NumberStep>) -> Self {
12630        self.number_step = Some(step.into());
12631        self
12632    }
12633
12634    /// Set the minimum value of the [`super::NumberInput`].
12635    ///
12636    /// The value will be clamped to the minimum value on stepping and on
12637    /// blur (only if the clamped value passes the `pattern`/`validate` check).
12638    /// See also [`Self::step`].
12639    pub fn min(mut self, min: f64) -> Self {
12640        self.number_min = Some(min);
12641        self
12642    }
12643
12644    /// Set the maximum value of the [`super::NumberInput`].
12645    ///
12646    /// The value will be clamped to the maximum value on stepping and on
12647    /// blur (only if the clamped value passes the `pattern`/`validate` check).
12648    /// See also [`Self::step`].
12649    pub fn max(mut self, max: f64) -> Self {
12650        self.number_max = Some(max);
12651        self
12652    }
12653
12654    /// Update the step value after construction, `None` to fall back to
12655    /// emitting [`super::NumberInputEvent::Step`] (if `min`, `max` are unset).
12656    ///
12657    /// See [`Self::step`] and [`Self::step_by`].
12658    pub fn set_step(
12659        &mut self,
12660        step: impl Into<Option<NumberStep>>,
12661        _: &mut Window,
12662        _: &mut Context<Self>,
12663    ) {
12664        self.number_step = step.into();
12665    }
12666
12667    /// Update the minimum value after construction. See [`Self::min`].
12668    pub fn set_min(&mut self, min: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
12669        self.number_min = min;
12670    }
12671
12672    /// Update the maximum value after construction. See [`Self::max`].
12673    pub fn set_max(&mut self, max: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
12674        self.number_max = max;
12675    }
12676
12677    /// Set true to show spinner at the input right.
12678    pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
12679        self.loading = loading;
12680        cx.notify();
12681    }
12682}
12683
12684/// Methods shared by the two multi-line modes, and reachable on neither a
12685/// single-line input nor anything else.
12686impl<M: crate::input::MultiLineMode> InputBaseState<M> {
12687    /// Set whether text taller than the input stretches past its top and
12688    /// bottom and springs back when precise trackpad or Magic Mouse scrolling
12689    /// or its momentum runs into an end. Ordinary line-wheel scrolling does
12690    /// not bounce. The default follows
12691    /// [`crate::ScrollBounce::enabled_by_default`]: on on iOS, Android and
12692    /// macOS, off elsewhere. Under reduced motion the text stops at its ends
12693    /// without stretching.
12694    pub fn scroll_bounce(mut self, scroll_bounce: bool) -> Self {
12695        self.scroll_bounce = scroll_bounce;
12696        self
12697    }
12698
12699    /// Update whether the text stretches past its ends, see
12700    /// [`InputBaseState::scroll_bounce`]. Turning it off drops a stretch in
12701    /// progress.
12702    pub fn set_scroll_bounce(&mut self, scroll_bounce: bool, cx: &mut Context<Self>) {
12703        if self.scroll_bounce != scroll_bounce {
12704            self.scroll_bounce = scroll_bounce;
12705            self.overscroll.reset();
12706            cx.notify();
12707        }
12708    }
12709
12710    /// Whether the built-in search panel and its shortcut are enabled. Off by
12711    /// default, on for the code editor.
12712    ///
12713    /// This only concerns the panel. An input that is not searchable still
12714    /// answers [`InputBaseState::set_search_query`] and the other search
12715    /// methods, and lets `Ctrl-F` / `Cmd-F` bubble up to its ancestors, so an
12716    /// application can put its own search UI on top of the same engine.
12717    pub fn searchable(mut self, searchable: bool) -> Self {
12718        self.searchable = searchable;
12719        self
12720    }
12721
12722    /// See [`InputBaseState::searchable`].
12723    pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
12724        self.searchable = searchable;
12725        cx.notify();
12726    }
12727
12728    /// Set the soft wrap mode, default is true.
12729    #[doc(hidden)]
12730    pub fn soft_wrap(mut self, wrap: bool) -> Self {
12731        self.soft_wrap = wrap;
12732        self
12733    }
12734
12735    /// Update the soft wrap mode, default is true.
12736    pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
12737        self.soft_wrap = wrap;
12738        if wrap {
12739            let wrap_width = self
12740                .last_layout
12741                .as_ref()
12742                .and_then(|b| b.wrap_width)
12743                .unwrap_or(self.input_bounds.size.width);
12744
12745            self.display_map.on_layout_changed(Some(wrap_width), cx);
12746
12747            // Reset scroll to left 0
12748            let mut offset = self.scroll_handle.offset();
12749            offset.x = px(0.);
12750            self.scroll_handle.set_offset(offset);
12751        } else {
12752            self.display_map.on_layout_changed(None, cx);
12753        }
12754        cx.notify();
12755    }
12756
12757    /// Set how soft-wrapped continuation lines are indented, default is [`WrappingIndent::Same`]
12758    #[doc(hidden)]
12759    pub fn wrapping_indent(mut self, wrapping_indent: WrappingIndent) -> Self {
12760        self.wrapping_indent = wrapping_indent;
12761        self
12762    }
12763
12764    /// Update how soft-wrapped continuation lines are indented.
12765    pub fn set_wrapping_indent(
12766        &mut self,
12767        wrapping_indent: WrappingIndent,
12768        _: &mut Window,
12769        cx: &mut Context<Self>,
12770    ) {
12771        self.wrapping_indent = wrapping_indent;
12772        self.display_map.set_wrapping_indent(wrapping_indent, cx);
12773        cx.notify();
12774    }
12775}
12776
12777/// Methods that only ordinary multi-line text offers.
12778impl InputBaseState<crate::input::TextareaMode> {
12779    /// Create a multi-line text state.
12780    ///
12781    /// Being multi-line is carried by the mode, not by the layout, so the
12782    /// default plain-text layout needs no adjustment here.
12783    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
12784        Self::new_in_mode(window, cx)
12785    }
12786
12787    pub fn set_auto_grow(&mut self, min_rows: usize, max_rows: usize, cx: &mut Context<Self>) {
12788        self.mode = LayoutMode::auto_grow(min_rows, max_rows.max(min_rows));
12789        self.mode.update_auto_grow(&self.display_map);
12790        cx.notify();
12791    }
12792
12793    /// Set the number of rows for the multi-line Textarea.
12794    ///
12795    /// This is only used when `multi_line` is set to true. The input is at least
12796    /// this many lines tall.
12797    ///
12798    /// default: 1
12799    #[doc(hidden)]
12800    pub fn rows(mut self, rows: usize) -> Self {
12801        match &mut self.mode {
12802            LayoutMode::PlainText { rows: r, .. } | LayoutMode::CodeEditor { rows: r, .. } => {
12803                *r = rows
12804            }
12805            LayoutMode::AutoGrow {
12806                max_rows: max_r,
12807                rows: r,
12808                ..
12809            } => {
12810                *r = rows;
12811                *max_r = rows;
12812            }
12813        }
12814        self
12815    }
12816
12817    pub fn set_rows(&mut self, rows: usize, cx: &mut Context<Self>) {
12818        match &mut self.mode {
12819            LayoutMode::PlainText { rows: value, .. }
12820            | LayoutMode::CodeEditor { rows: value, .. } => *value = rows,
12821            LayoutMode::AutoGrow {
12822                rows: value,
12823                max_rows,
12824                ..
12825            } => {
12826                *value = rows;
12827                *max_rows = rows;
12828            }
12829        }
12830        cx.notify();
12831    }
12832
12833    /// Grow with the content from `min_rows` through `max_rows`.
12834    pub fn auto_grow(mut self, min_rows: usize, max_rows: usize) -> Self {
12835        self.mode = LayoutMode::auto_grow(min_rows, max_rows);
12836        self
12837    }
12838}
12839
12840/// Methods that only a source-code editor offers.
12841impl InputBaseState<crate::input::EditorMode> {
12842    /// Create a source-code editor state.
12843    ///
12844    /// Default options: line numbers on, tab size 2 with soft tabs, indent
12845    /// guides on, multi-line, and search enabled. Set the language for syntax
12846    /// highlighting with [`Self::language`]; without one the text is shown
12847    /// unhighlighted.
12848    ///
12849    /// The editor aims at simple code editing or display, not at being a
12850    /// full-featured code editor. It offers syntax highlighting, auto indent,
12851    /// line numbers, and handles large text up to about 50K lines.
12852    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
12853        let mut state = Self::new_in_mode(window, cx);
12854        state.mode = LayoutMode::code_editor(super::EditorLanguage::new(cx));
12855        state.searchable = true;
12856        state
12857    }
12858
12859    /// Set the language to highlight, e.g. `"rust"`.
12860    ///
12861    /// See [`Self::set_highlighter`] to change it after construction.
12862    pub fn language(mut self, language: impl Into<SharedString>) -> Self {
12863        if let LayoutMode::CodeEditor {
12864            language: l,
12865            highlighter,
12866            ..
12867        } = &mut self.mode
12868        {
12869            l.set_name(language.into());
12870            *highlighter.borrow_mut() = None;
12871        }
12872        self
12873    }
12874
12875    /// The current language name, e.g. `"rust"`.
12876    pub fn language_name(&self) -> SharedString {
12877        match &self.mode {
12878            LayoutMode::CodeEditor { language, .. } => language.name(),
12879            _ => SharedString::default(),
12880        }
12881    }
12882
12883    /// Set enable/disable code folding.
12884    ///
12885    /// Default: true
12886    #[doc(hidden)]
12887    pub fn folding(mut self, folding: bool) -> Self {
12888        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
12889            *f = folding;
12890        }
12891        self
12892    }
12893
12894    /// Set code folding at runtime.
12895    ///
12896    /// When disabling, all existing folds are cleared.
12897    pub fn set_folding(&mut self, folding: bool, _: &mut Window, cx: &mut Context<Self>) {
12898        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
12899            *f = folding;
12900        }
12901        if !folding {
12902            self.display_map.clear_folds();
12903        }
12904        cx.notify();
12905    }
12906
12907    /// Unfold any folded ranges that hide the given position.
12908    ///
12909    /// Use this to reveal a position before acting on it (e.g. before
12910    /// [`Self::set_cursor_position`], which stops at a fold boundary),
12911    /// without touching folds elsewhere in the buffer. Fold candidates are
12912    /// kept, so the opened ranges can be folded again from the gutter.
12913    ///
12914    /// A fold keeps its own first and last line visible, so a position on
12915    /// either of them opens nothing. Nested folds all open, since opening
12916    /// only the outermost would leave the position hidden.
12917    ///
12918    /// Returns whether any fold was opened.
12919    pub fn unfold_at(&mut self, position: impl Into<Position>, cx: &mut Context<Self>) -> bool {
12920        let offset = self.text.position_to_offset(&position.into());
12921        let line = self.text.offset_to_point(offset).row;
12922        // A fold hides start_line + 1 ..= end_line - 1, so a line is hidden
12923        // exactly when some folded range strictly contains it.
12924        let covering: Vec<usize> = self
12925            .display_map
12926            .folded_ranges()
12927            .iter()
12928            .filter(|fold| line > fold.start_line && line < fold.end_line)
12929            .map(|fold| fold.start_line)
12930            .collect();
12931        if covering.is_empty() {
12932            return false;
12933        }
12934
12935        for start_line in covering {
12936            self.display_map.set_folded(start_line, false);
12937        }
12938        cx.notify();
12939        true
12940    }
12941
12942    /// Set enable/disable line number.
12943    #[doc(hidden)]
12944    pub fn line_number(mut self, line_number: bool) -> Self {
12945        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
12946            *l = line_number;
12947        }
12948        self
12949    }
12950
12951    /// Set line number.
12952    pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
12953        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
12954            if *l == line_number {
12955                return;
12956            }
12957            *l = line_number;
12958        }
12959        cx.notify();
12960    }
12961
12962    /// Set enable/disable automatic closing brackets and quotes.
12963    ///
12964    /// When enabled, typing an opener from [`LanguageConfig::auto_closing_pairs`](crate::input::language_config::LanguageConfig::auto_closing_pairs) inserts the
12965    /// matching closer and places the cursor inside. Typing a closer that is
12966    /// already present just moves past it. Default: true
12967    #[doc(hidden)]
12968    pub fn auto_close(mut self, auto_close: bool) -> Self {
12969        self.mode.set_auto_close(auto_close);
12970        self
12971    }
12972
12973    /// Set automatic closing brackets and quotes at runtime.
12974    pub fn set_auto_close(&mut self, auto_close: bool, _: &mut Window, cx: &mut Context<Self>) {
12975        self.mode.set_auto_close(auto_close);
12976        cx.notify();
12977    }
12978
12979    /// Set enable/disable smart indent on Enter.
12980    ///
12981    /// When enabled, Enter uses structural brackets and
12982    /// [`LanguageConfig::indentation_rules`](crate::input::language_config::LanguageConfig::indentation_rules) to choose indentation.
12983    /// Default: true
12984    #[doc(hidden)]
12985    pub fn smart_indent(mut self, smart_indent: bool) -> Self {
12986        self.mode.set_smart_indent(smart_indent);
12987        self
12988    }
12989
12990    /// Set smart indent on Enter at runtime.
12991    pub fn set_smart_indent(&mut self, smart_indent: bool, _: &mut Window, cx: &mut Context<Self>) {
12992        self.mode.set_smart_indent(smart_indent);
12993        cx.notify();
12994    }
12995}