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, Hsla, 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, FoldPlaceholderRenderer, InputContextMenuCapabilities,
26    InputEditorStyle, InputHighlighterFactory, MASK_CHAR, MaskPattern, NativeMenu, NumberStep,
27    WrappingIndent,
28    blink_cursor::BlinkCursor,
29    change::Change,
30    cursor::{CursorSelection, Selections},
31    element::{
32        EditorScrollbar, EditorScrollbarSnapshot, LongestLineKey, OverviewRulerRows, TextElement,
33        clamp_horizontal_scroll_offset,
34    },
35    kind::InputModeKind,
36    mask_pattern::normalize_number_input,
37    mode::LayoutMode,
38    undo_manager::{EditIntent, UndoManager},
39};
40use crate::actions::{SelectDown, SelectLeft, SelectRight, SelectUp};
41use crate::input::blink_cursor::CURSOR_WIDTH;
42use crate::input::movement::MoveDirection;
43use crate::input::{
44    InputEdit, InputExtras as _, Position, RopeExt as _, element::RIGHT_MARGIN, layout::LastLayout,
45};
46use crate::overscroll::Overscroll;
47use crate::{AutoScroll, StepAction, TestSupportExt as _};
48
49pub(super) const EDITOR_ELEMENT_ID: &str = "input-state";
50
51/// Vertical clearance to retain when revealing a text position.
52pub(crate) enum ScrollPadding {
53    Minimal,
54    SurroundingLines,
55}
56
57#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
58#[action(namespace = input, no_json)]
59pub struct Enter {
60    /// Is confirm with secondary.
61    pub secondary: bool,
62    /// Whether the Shift modifier was held when Enter was pressed.
63    pub shift: bool,
64}
65
66impl Enter {
67    /// Returns true if `action` is a primary `Enter` action (`secondary: false`),
68    /// regardless of whether Shift was held.
69    pub fn is_primary(action: &dyn Action) -> bool {
70        action.partial_eq(&Enter {
71            secondary: false,
72            shift: false,
73        }) || action.partial_eq(&Enter {
74            secondary: false,
75            shift: true,
76        })
77    }
78}
79
80actions!(
81    input,
82    [
83        ActivateToken,
84        Backspace,
85        Delete,
86        DeleteToBeginningOfLine,
87        DeleteToEndOfLine,
88        DeleteToPreviousWordStart,
89        DeleteToNextWordEnd,
90        Indent,
91        Outdent,
92        IndentInline,
93        OutdentInline,
94        MoveUp,
95        MoveDown,
96        MoveLeft,
97        MoveRight,
98        MoveHome,
99        MoveEnd,
100        MovePageUp,
101        MovePageDown,
102        AddCursorAbove,
103        AddCursorBelow,
104        SelectAll,
105        SelectToStartOfLine,
106        SelectToEndOfLine,
107        SelectToStart,
108        SelectToEnd,
109        SelectToPreviousWordStart,
110        SelectToNextWordEnd,
111        ShowCharacterPalette,
112        Copy,
113        Cut,
114        Paste,
115        Undo,
116        Redo,
117        MoveToStartOfLine,
118        MoveToEndOfLine,
119        MoveToStart,
120        MoveToEnd,
121        MoveToPreviousWord,
122        MoveToNextWord,
123        Escape,
124        ToggleCodeActions,
125        ShowCompletions,
126        Search,
127        Replace,
128        GoToDefinition,
129    ]
130);
131
132/// An event emitted by an input.
133///
134/// Subscribe to [`InputEditsEvent`] for the immutable edits associated with a
135/// change. `Change` itself remains available for consumers that only need a
136/// notification.
137#[derive(Clone)]
138pub enum InputEvent {
139    Change,
140    PressEnter { secondary: bool, shift: bool },
141    Focus,
142    Blur,
143}
144
145/// An immutable batch of text edits emitted for one input change.
146///
147/// Each entry contains the edit and the document immediately after it, in
148/// application order. Unlike reading the input state in a `Change` subscriber,
149/// this snapshot remains associated with its event when updates are queued.
150#[derive(Clone)]
151pub struct InputEditsEvent {
152    edits: Vec<(InputEdit, Rope)>,
153}
154
155impl InputEditsEvent {
156    /// The edits and the text after each edit, in application order.
157    pub fn edits(&self) -> &[(InputEdit, Rope)] {
158        &self.edits
159    }
160}
161
162pub(super) const CONTEXT: &str = "Input";
163
164pub(crate) fn init(cx: &mut App) {
165    cx.bind_keys([
166        KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
167        KeyBinding::new("shift-backspace", Backspace, Some(CONTEXT)),
168        #[cfg(target_os = "macos")]
169        KeyBinding::new("ctrl-backspace", Backspace, Some(CONTEXT)),
170        KeyBinding::new("delete", Delete, Some(CONTEXT)),
171        KeyBinding::new("shift-delete", Delete, Some(CONTEXT)),
172        #[cfg(target_os = "macos")]
173        KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
174        #[cfg(target_os = "macos")]
175        KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
176        #[cfg(target_os = "macos")]
177        KeyBinding::new("alt-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
178        #[cfg(not(target_os = "macos"))]
179        KeyBinding::new("ctrl-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
180        #[cfg(target_os = "macos")]
181        KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)),
182        #[cfg(not(target_os = "macos"))]
183        KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)),
184        KeyBinding::new(
185            "enter",
186            Enter {
187                secondary: false,
188                shift: false,
189            },
190            Some(CONTEXT),
191        ),
192        KeyBinding::new(
193            "shift-enter",
194            Enter {
195                secondary: false,
196                shift: true,
197            },
198            Some(CONTEXT),
199        ),
200        KeyBinding::new(
201            if cx.platform_info().is_mac() {
202                "cmd-enter"
203            } else {
204                "ctrl-enter"
205            },
206            Enter {
207                secondary: true,
208                shift: false,
209            },
210            Some(CONTEXT),
211        ),
212        KeyBinding::new("escape", Escape, Some(CONTEXT)),
213        KeyBinding::new("up", MoveUp, Some(CONTEXT)),
214        KeyBinding::new("down", MoveDown, Some(CONTEXT)),
215        KeyBinding::new("left", MoveLeft, Some(CONTEXT)),
216        KeyBinding::new("right", MoveRight, Some(CONTEXT)),
217        KeyBinding::new("pageup", MovePageUp, Some(CONTEXT)),
218        KeyBinding::new("pagedown", MovePageDown, Some(CONTEXT)),
219        KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
220        KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
221        #[cfg(target_os = "macos")]
222        KeyBinding::new("cmd-]", Indent, Some(CONTEXT)),
223        #[cfg(not(target_os = "macos"))]
224        KeyBinding::new("ctrl-]", Indent, Some(CONTEXT)),
225        #[cfg(target_os = "macos")]
226        KeyBinding::new("cmd-[", Outdent, Some(CONTEXT)),
227        #[cfg(not(target_os = "macos"))]
228        KeyBinding::new("ctrl-[", Outdent, Some(CONTEXT)),
229        KeyBinding::new("shift-left", SelectLeft, Some(CONTEXT)),
230        KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
231        KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
232        KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
233        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
234        KeyBinding::new("shift-alt-left", SelectLeft, Some(CONTEXT)),
235        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
236        KeyBinding::new("shift-alt-right", SelectRight, Some(CONTEXT)),
237        // Avoid Ctrl+Alt+arrows on Linux, where desktops may reserve them.
238        #[cfg(target_os = "macos")]
239        KeyBinding::new("cmd-alt-up", AddCursorAbove, Some(CONTEXT)),
240        #[cfg(target_os = "macos")]
241        KeyBinding::new("cmd-alt-down", AddCursorBelow, Some(CONTEXT)),
242        #[cfg(target_os = "windows")]
243        KeyBinding::new("ctrl-alt-up", AddCursorAbove, Some(CONTEXT)),
244        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
245        KeyBinding::new("shift-alt-up", AddCursorAbove, Some(CONTEXT)),
246        #[cfg(target_os = "windows")]
247        KeyBinding::new("ctrl-alt-down", AddCursorBelow, Some(CONTEXT)),
248        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
249        KeyBinding::new("shift-alt-down", AddCursorBelow, Some(CONTEXT)),
250        KeyBinding::new("home", MoveHome, Some(CONTEXT)),
251        KeyBinding::new("end", MoveEnd, Some(CONTEXT)),
252        #[cfg(not(target_os = "macos"))]
253        KeyBinding::new("ctrl-home", MoveToStart, Some(CONTEXT)),
254        #[cfg(not(target_os = "macos"))]
255        KeyBinding::new("ctrl-end", MoveToEnd, Some(CONTEXT)),
256        #[cfg(not(target_os = "macos"))]
257        KeyBinding::new("ctrl-shift-home", SelectToStart, Some(CONTEXT)),
258        #[cfg(not(target_os = "macos"))]
259        KeyBinding::new("ctrl-shift-end", SelectToEnd, Some(CONTEXT)),
260        KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
261        KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
262        #[cfg(target_os = "macos")]
263        KeyBinding::new("ctrl-shift-a", SelectToStartOfLine, Some(CONTEXT)),
264        #[cfg(target_os = "macos")]
265        KeyBinding::new("ctrl-shift-e", SelectToEndOfLine, Some(CONTEXT)),
266        #[cfg(target_os = "macos")]
267        KeyBinding::new("shift-cmd-left", SelectToStartOfLine, Some(CONTEXT)),
268        #[cfg(target_os = "macos")]
269        KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
270        #[cfg(any(target_os = "macos", target_os = "linux"))]
271        KeyBinding::new("alt-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
272        #[cfg(not(target_os = "macos"))]
273        KeyBinding::new("ctrl-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
274        #[cfg(any(target_os = "macos", target_os = "linux"))]
275        KeyBinding::new("alt-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
276        #[cfg(not(target_os = "macos"))]
277        KeyBinding::new("ctrl-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
278        #[cfg(target_os = "macos")]
279        KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
280        #[cfg(target_os = "macos")]
281        KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
282        #[cfg(not(target_os = "macos"))]
283        KeyBinding::new("ctrl-a", SelectAll, Some(CONTEXT)),
284        #[cfg(target_os = "macos")]
285        KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
286        #[cfg(not(target_os = "macos"))]
287        KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
288        #[cfg(target_os = "macos")]
289        KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
290        #[cfg(not(target_os = "macos"))]
291        KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
292        #[cfg(target_os = "macos")]
293        KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
294        #[cfg(not(target_os = "macos"))]
295        KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
296        #[cfg(target_os = "macos")]
297        KeyBinding::new("ctrl-a", MoveHome, Some(CONTEXT)),
298        #[cfg(target_os = "macos")]
299        KeyBinding::new("cmd-left", MoveHome, Some(CONTEXT)),
300        #[cfg(target_os = "macos")]
301        KeyBinding::new("ctrl-e", MoveEnd, Some(CONTEXT)),
302        #[cfg(target_os = "macos")]
303        KeyBinding::new("cmd-right", MoveEnd, Some(CONTEXT)),
304        #[cfg(target_os = "macos")]
305        KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
306        #[cfg(target_os = "macos")]
307        KeyBinding::new("cmd-shift-z", Redo, Some(CONTEXT)),
308        #[cfg(target_os = "macos")]
309        KeyBinding::new("cmd-up", MoveToStart, Some(CONTEXT)),
310        #[cfg(target_os = "macos")]
311        KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
312        #[cfg(target_os = "macos")]
313        KeyBinding::new("alt-left", MoveToPreviousWord, Some(CONTEXT)),
314        #[cfg(target_os = "macos")]
315        KeyBinding::new("alt-right", MoveToNextWord, Some(CONTEXT)),
316        #[cfg(not(target_os = "macos"))]
317        KeyBinding::new("ctrl-left", MoveToPreviousWord, Some(CONTEXT)),
318        #[cfg(not(target_os = "macos"))]
319        KeyBinding::new("ctrl-right", MoveToNextWord, Some(CONTEXT)),
320        #[cfg(target_os = "macos")]
321        KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
322        #[cfg(target_os = "macos")]
323        KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
324        #[cfg(not(target_os = "macos"))]
325        KeyBinding::new("ctrl-z", Undo, Some(CONTEXT)),
326        #[cfg(not(target_os = "macos"))]
327        KeyBinding::new("ctrl-y", Redo, Some(CONTEXT)),
328        #[cfg(target_os = "macos")]
329        KeyBinding::new("cmd-.", ToggleCodeActions, Some(CONTEXT)),
330        #[cfg(not(target_os = "macos"))]
331        KeyBinding::new("ctrl-.", ToggleCodeActions, Some(CONTEXT)),
332        #[cfg(target_os = "macos")]
333        KeyBinding::new("cmd-f", Search, Some(CONTEXT)),
334        #[cfg(not(target_os = "macos"))]
335        KeyBinding::new("ctrl-f", Search, Some(CONTEXT)),
336        #[cfg(target_os = "macos")]
337        KeyBinding::new("cmd-shift-f", Replace, Some(CONTEXT)),
338        #[cfg(not(target_os = "macos"))]
339        KeyBinding::new("ctrl-h", Replace, Some(CONTEXT)),
340    ]);
341}
342
343/// A mouse position resolved for a columnar (block) selection.
344///
345/// A position past the end of a short row has no glyph to land on, so `offset` clips to
346/// that row's end and `columns_past_line_end` keeps what the clip dropped. Together they
347/// say which column the pointer really reached, which is what a block spanning rows of
348/// different lengths has to be measured in.
349#[derive(Debug, Clone, Copy, PartialEq, Eq)]
350pub(super) struct ColumnarPoint {
351    offset: usize,
352    columns_past_line_end: usize,
353}
354
355impl ColumnarPoint {
356    pub(super) fn new(offset: usize, columns_past_line_end: usize) -> Self {
357        Self {
358            offset,
359            columns_past_line_end,
360        }
361    }
362}
363
364/// The document and selections a paste was asked to replace, taken when the
365/// `Paste` action ran so a clipboard read that resolves later can tell
366/// whether it still applies. See [`InputBaseState::paste_target`].
367#[derive(Debug, PartialEq)]
368struct PasteTarget {
369    document_revision: u64,
370    selections: Vec<CursorSelection>,
371}
372
373/// Caret positioning geometry from the input's latest prepaint.
374///
375/// Stashed during prepaint so caret-anchored popups can position themselves
376/// from the current frame's geometry in their deferred prepaint.
377#[derive(Clone, Copy, Debug)]
378#[doc(hidden)]
379pub struct PrepaintCaretGeometry {
380    cursor_bounds: Bounds<Pixels>,
381    line_height: Pixels,
382    input_bounds: Bounds<Pixels>,
383    scroll_offset: Point<Pixels>,
384    /// A resize owns visibility while its requested repaint settles.
385    pub(super) resize_reveal_owned: bool,
386}
387
388impl PrepaintCaretGeometry {
389    pub(super) fn new(
390        cursor_bounds: Bounds<Pixels>,
391        line_height: Pixels,
392        input_bounds: Bounds<Pixels>,
393        scroll_offset: Point<Pixels>,
394    ) -> Self {
395        Self {
396            cursor_bounds,
397            line_height,
398            input_bounds,
399            scroll_offset,
400            resize_reveal_owned: false,
401        }
402    }
403
404    /// The active caret's bounds from the fresh prepaint layout.
405    pub fn cursor_bounds(&self) -> Bounds<Pixels> {
406        self.cursor_bounds
407    }
408
409    /// Line height from the fresh prepaint layout.
410    pub fn line_height(&self) -> Pixels {
411        self.line_height
412    }
413
414    /// The input container bounds from the fresh prepaint.
415    pub fn input_bounds(&self) -> Bounds<Pixels> {
416        self.input_bounds
417    }
418
419    /// The scroll offset that will be in effect after paint, clamped like
420    /// `InputBaseState::update_scroll_offset` clamps it.
421    pub fn scroll_offset(&self) -> Point<Pixels> {
422        self.scroll_offset
423    }
424}
425
426/// The shared text-editing engine behind [`crate::input::InputState`],
427/// [`crate::input::TextareaState`] and [`crate::input::EditorState`].
428///
429/// `M` is the mode marker: it carries no data and only decides which methods
430/// exist, so an ordinary input cannot reach the editor's language features.
431///
432/// The three states are type aliases of this one, which is why this name is
433/// public: an alias is only as usable as the type behind it, so hiding this
434/// would leave `InputState` unable to do anything. Prefer naming the aliases
435/// — write `InputState`, not `InputBaseState<InputMode>`.
436pub struct InputBaseState<M: InputModeKind> {
437    /// State only this mode needs. See [`InputModeKind::Extras`].
438    pub(crate) extras: M::Extras,
439    pub(super) focus_handle: FocusHandle,
440    selection_focus: Option<WeakFocusHandle>,
441    pub(super) mode: LayoutMode,
442    pub(super) text: Rope,
443    pub(super) display_map: DisplayMap,
444    pub(super) undo_manager: UndoManager,
445    pub(super) inline_tokens: Option<Box<super::inline_tokens::InlineTokenStore>>,
446    pub(super) pending_token: Option<super::InlineToken>,
447    pub(super) replaying_history: bool,
448    pub(super) validated_token_edit: bool,
449    pub(super) document_revision: u64,
450    pub(super) token_presentation: super::InlineTokenPresentation,
451    pub(super) token_layout_cache: Option<Box<super::token_presentation::TokenLayoutCache>>,
452    /// Real per-frame bounds of the placed token elements, keyed by token
453    /// start offset; used for hover payloads and stale-hover reconciliation.
454    pub(super) token_bounds: std::collections::HashMap<usize, Bounds<Pixels>>,
455    /// The currently hovered token: its entered span, last measured bounds,
456    /// and UTF-16 range as of entry. Retained so hover exit can still be
457    /// delivered when the token is removed, replaced, scrolled out, or
458    /// disabled.
459    pub(super) hovered_token: Option<super::token_presentation::HoverSnapshot>,
460    pub(super) pending_token_hover_exits: Vec<super::token_presentation::HoverSnapshot>,
461    /// The start offset of a pressed token, with the document revision and
462    /// pointer position at the press.
463    pub(super) pressed_token: Option<(usize, u64, Point<Pixels>)>,
464    pub(super) search_session: super::SearchSession,
465    /// Advances every time search is explicitly invoked. See
466    /// [`InputBaseState::search_activation_revision`].
467    pub(super) search_activation_revision: u64,
468    pub(super) searchable: bool,
469    pub(super) replaceable: bool,
470    pub(super) soft_wrap: bool,
471    pub(super) wrapping_indent: WrappingIndent,
472    pub(super) scroll_beyond_last_line: Option<usize>,
473    pub(super) cursor_surrounding_lines: Option<usize>,
474    pub(super) blink_cursor: Entity<BlinkCursor>,
475    pub(super) loading: bool,
476    /// The cursors and selections.
477    ///
478    /// Always contains at least one selection where index 0 is the active cursor.
479    pub(super) selections: Selections,
480    /// Range for save the selected word, use to keep word range when drag move.
481    pub(super) selected_word_range: Option<CursorSelection>,
482    /// The marked range is the temporary insert text on IME typing.
483    pub(super) ime_marked_range: Option<CursorSelection>,
484    /// Text before an unannounced IME composition, until the next change.
485    ime_original_text: Option<Rope>,
486    pub(super) last_layout: Option<LastLayout>,
487    /// Fresh caret geometry stashed during prepaint for caret-anchored popups.
488    ///
489    /// Popups are positioned in their deferred prepaint, which runs after the
490    /// input's prepaint but before its paint.
491    pub(crate) prepaint_caret_geometry: Option<PrepaintCaretGeometry>,
492    pub(super) last_cursor: Option<usize>,
493    /// Where the platform was last told the caret is, for its IME panel.
494    pub(super) ime_anchor: Option<(Point<Pixels>, Option<Bounds<Pixels>>)>,
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    pub(super) fold_placeholder_renderer: Option<FoldPlaceholderRenderer>,
558    pub(super) fold_background: Option<Hsla>,
559    accessibility: Option<InputAccessibility>,
560    /// What paints in place of Base's editor scrollbar, when a consumer
561    /// supplies its own.
562    pub(super) scrollbar_renderer: Option<crate::input::EditorScrollbarRenderer>,
563
564    /// The mask pattern for formatting the input text
565    pub(crate) mask_pattern: MaskPattern,
566    /// Whether the `mask_pattern` was explicitly set (via [`Self::mask_pattern`]
567    /// or [`Self::set_mask_pattern`]), to let [`super::NumberInput`] only apply
568    /// its default mask when the user has not made an explicit choice.
569    pub(super) mask_pattern_set: bool,
570    pub(super) placeholder: SharedString,
571
572    /// Diagnostic currently requested by pointer hover; applications render it.
573    pub(super) diagnostic_popover: Option<Rc<crate::input::DiagnosticEntry>>,
574
575    context_menu_handler: Option<
576        Rc<dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App)>,
577    >,
578    pending_context_menu: Option<(Point<Pixels>, usize)>,
579
580    /// Whether the context menu that shows on right-click is enabled.
581    ///
582    pub(super) enable_context_menu: bool,
583
584    /// A flag to indicate if we are currently inserting a completion item.
585    pub(super) completion_inserting: bool,
586    pub(super) overlay_action_handler: Option<
587        Rc<
588            dyn Fn(
589                super::InputOverlayKind,
590                Box<dyn Action>,
591                &mut Window,
592                &mut Context<InputBaseState<M>>,
593            ) -> bool,
594        >,
595    >,
596
597    /// A flag to indicate if we have a pending update to the text.
598    ///
599    /// If true, will call some update (for example LSP, Syntax Highlight) before render.
600    _pending_update: bool,
601    /// A flag to indicate if we should ignore the next completion event.
602    pub(super) silent_replace_text: bool,
603    /// A flag to indicate if we should emit InputEvents.
604    pub(super) emit_events: bool,
605
606    _subscriptions: Vec<Subscription>,
607
608    pub(super) auto_scroll: AutoScroll,
609}
610
611/// Accessibility presentation for the editor's existing keyboard-focus boundary.
612///
613/// Frames and their addon controls keep their own identities. This projection
614/// does not create a focus handle or register another text Tab stop. The engine
615/// supplies the live value and guards accessibility actions against its current
616/// disabled, read-only and masked state.
617#[derive(Clone, Debug)]
618pub struct InputAccessibility {
619    role: Option<gpui::Role>,
620    label: Option<SharedString>,
621    placeholder: Option<SharedString>,
622    accessibility_id: Option<SharedString>,
623    value_exposed: bool,
624}
625
626impl InputAccessibility {
627    pub fn new(role: Option<gpui::Role>) -> Self {
628        Self {
629            role,
630            label: None,
631            placeholder: None,
632            accessibility_id: None,
633            value_exposed: true,
634        }
635    }
636
637    pub fn with_label(mut self, label: Option<SharedString>) -> Self {
638        self.label = label;
639        self
640    }
641
642    pub fn with_placeholder(mut self, placeholder: Option<SharedString>) -> Self {
643        self.placeholder = placeholder;
644        self
645    }
646
647    pub fn with_accessibility_id(mut self, id: Option<SharedString>) -> Self {
648        self.accessibility_id = id;
649        self
650    }
651
652    /// Suppress values for secret content even when the mask is revealed.
653    pub fn with_value_exposed(mut self, exposed: bool) -> Self {
654        self.value_exposed = exposed;
655        self
656    }
657
658    pub fn role(&self) -> Option<gpui::Role> {
659        self.role
660    }
661
662    pub fn label(&self) -> Option<&SharedString> {
663        self.label.as_ref()
664    }
665
666    pub fn placeholder(&self) -> Option<&SharedString> {
667        self.placeholder.as_ref()
668    }
669
670    pub fn accessibility_id(&self) -> Option<&SharedString> {
671        self.accessibility_id.as_ref()
672    }
673
674    pub fn value_exposed(&self) -> bool {
675        self.value_exposed
676    }
677}
678
679/// Read-only styling data exposed to presentation facades.
680///
681/// The fields are private and read through the methods below, so that a new
682/// one can be added without breaking the facades.
683#[derive(Clone)]
684pub struct InputPresentation {
685    focus_handle: FocusHandle,
686    disabled: bool,
687    readonly: bool,
688    loading: bool,
689    masked: bool,
690    multi_line: bool,
691    code_editor: bool,
692    text_align: TextAlign,
693    placeholder: SharedString,
694    mask_placeholder: Option<String>,
695}
696
697impl InputPresentation {
698    pub fn focus_handle(&self) -> &FocusHandle {
699        &self.focus_handle
700    }
701
702    pub fn is_disabled(&self) -> bool {
703        self.disabled
704    }
705
706    pub fn is_readonly(&self) -> bool {
707        self.readonly
708    }
709
710    /// Returns true if the user is allowed to change the text.
711    ///
712    /// See also: [`InputBaseState::is_editable`].
713    pub fn is_editable(&self) -> bool {
714        !self.disabled && !self.readonly
715    }
716
717    pub fn is_loading(&self) -> bool {
718        self.loading
719    }
720
721    pub fn is_masked(&self) -> bool {
722        self.masked
723    }
724
725    pub fn is_multi_line(&self) -> bool {
726        self.multi_line
727    }
728
729    pub fn is_code_editor(&self) -> bool {
730        self.code_editor
731    }
732
733    pub fn text_align(&self) -> TextAlign {
734        self.text_align
735    }
736
737    pub fn placeholder(&self) -> &SharedString {
738        &self.placeholder
739    }
740
741    /// The placeholder derived from the mask pattern, e.g.: `(___) ___-____`.
742    pub fn mask_placeholder(&self) -> Option<&str> {
743        self.mask_placeholder.as_deref()
744    }
745}
746
747impl<M: InputModeKind> EventEmitter<InputEvent> for InputBaseState<M> {}
748impl<M: InputModeKind> EventEmitter<InputEditsEvent> for InputBaseState<M> {}
749
750impl<M: InputModeKind> InputBaseState<M> {
751    #[doc(hidden)]
752    pub fn cursor_layout(&self) -> Option<(Bounds<Pixels>, Pixels)> {
753        let layout = self.last_layout.as_ref()?;
754        Some((layout.cursor_bounds?, layout.line_height))
755    }
756
757    /// Caret geometry from the latest prepaint, for caret-anchored popups.
758    ///
759    /// Popups positioned in their deferred prepaint read this to anchor to
760    /// the current frame's caret instead of the previous frame's paint.
761    #[doc(hidden)]
762    pub fn prepaint_caret_geometry(&self) -> Option<PrepaintCaretGeometry> {
763        self.prepaint_caret_geometry
764    }
765
766    pub fn input_bounds(&self) -> Bounds<Pixels> {
767        self.input_bounds
768    }
769
770    pub fn text_bounds(&self) -> Option<Bounds<Pixels>> {
771        self.last_bounds
772    }
773
774    /// The cached display map for querying wrapped rows and display positions.
775    ///
776    /// Reading this does not prepare text or force layout. Before the first
777    /// layout, [`DisplayMap::wrap_row_count`] can be zero; prepared empty text
778    /// has one row. The count describes content, independent of minimum rows,
779    /// auto-grow limits and viewport height.
780    ///
781    /// Text edits use the cached wrapping parameters. Layout refreshes the
782    /// cache after width or font changes; consume that count on a subsequent
783    /// render, or use [`Self::prewrap`] to prepare it before the first layout.
784    pub fn display_map(&self) -> &DisplayMap {
785        &self.display_map
786    }
787
788    /// Wrap the text for `font`, `font_size` and `width` before the first
789    /// layout, so that [`DisplayMap::wrap_row_count`] is known on the first frame.
790    ///
791    /// Call it after setting the value and the wrapping indent. `width` is the
792    /// width of the input's text area: its width less its padding, and less
793    /// the line-number gutter for an editor with line numbers. It wraps as
794    /// layout does, a little short of that width and with the state's wrapping
795    /// indent. Once the input has been laid out this does nothing, and layout
796    /// keeps the wrap up to date. The font and font size must match the input's
797    /// actual text style.
798    pub fn prewrap(
799        &mut self,
800        font: gpui::Font,
801        font_size: Pixels,
802        width: Pixels,
803        cx: &mut Context<Self>,
804    ) {
805        if self.last_layout.is_some() {
806            return;
807        }
808        let wrap_width = (width - RIGHT_MARGIN).max(px(1.));
809        self.display_map.set_font(font, font_size, cx);
810        self.display_map
811            .set_tab_size(self.mode.tab_size().tab_size, cx);
812        self.display_map
813            .on_layout_changed(self.soft_wrap.then_some(wrap_width), cx);
814        self.display_map
815            .set_wrapping_indent(self.wrapping_indent, cx);
816        self.display_map.ensure_text_prepared(&self.text, cx);
817    }
818
819    pub fn diagnostic_popover(&self) -> Option<Rc<crate::input::DiagnosticEntry>> {
820        self.diagnostic_popover.clone()
821    }
822
823    pub fn presentation(&self) -> InputPresentation {
824        InputPresentation {
825            focus_handle: self.focus_handle.clone(),
826            disabled: self.disabled,
827            readonly: self.readonly,
828            loading: self.loading,
829            masked: self.masked,
830            multi_line: self.is_multi_line(),
831            code_editor: self.is_code_editor(),
832            text_align: self.text_align,
833            placeholder: self.placeholder.clone(),
834            mask_placeholder: self.mask_pattern.placeholder(),
835        }
836    }
837
838    /// Whether this input spans more than one line.
839    ///
840    /// Answered by the mode marker, which is fixed when the state is built.
841    /// [`LayoutMode`] holds the row counts and growth policy, not the kind.
842    #[inline]
843    /// Whether this input paints scrollbars.
844    ///
845    /// Only a multi-line input can scroll: a single-line input keeps its
846    /// caret in view by moving its own offset, and never has a viewport a
847    /// user could drag. Adding the editor scrollbar to every input put a
848    /// thumb inside every text field, which is a control the field does not
849    /// have.
850    pub(crate) fn shows_scrollbar(&self) -> bool {
851        self.is_multi_line()
852    }
853
854    pub fn is_multi_line(&self) -> bool {
855        M::MULTI_LINE
856    }
857
858    /// Whether this input is a single-line text field. See [`Self::is_multi_line`].
859    #[inline]
860    pub fn is_single_line(&self) -> bool {
861        !M::MULTI_LINE
862    }
863
864    /// Whether this input is a source-code editor.
865    #[inline]
866    pub fn is_code_editor(&self) -> bool {
867        M::CODE_EDITOR
868    }
869
870    /// Whether the user is allowed to copy the selection out.
871    ///
872    /// A masked input keeps its value out of the clipboard.
873    pub fn is_copyable(&self) -> bool {
874        self.selections.iter().any(|sel| !sel.is_empty()) && !self.masked
875    }
876
877    pub fn context_menu_capabilities(&self) -> InputContextMenuCapabilities {
878        let (go_to_definition, code_actions) = self.extras.context_menu_capabilities();
879        InputContextMenuCapabilities::new()
880            .disabled(self.disabled)
881            .readonly(self.readonly)
882            .code_editor(self.is_code_editor())
883            .selection(!self.active_selection().is_empty())
884            .masked(self.masked)
885            .go_to_definition(go_to_definition)
886            .code_actions(code_actions)
887    }
888
889    pub fn set_text_align(&mut self, text_align: TextAlign, cx: &mut Context<Self>) {
890        if !self.is_single_line() || self.text_align == text_align {
891            return;
892        }
893
894        self.text_align = text_align;
895        cx.notify();
896    }
897
898    /// Flip the password mask.
899    ///
900    /// Setting the mask is a single-line method, but flipping it stays here:
901    /// the reveal button is rendered from the generic path, and it can only be
902    /// switched on through [`crate::input::InputState`] anyway.
903    pub fn toggle_masked(&mut self, _: &mut Window, cx: &mut Context<Self>) {
904        self.masked = !self.masked;
905        cx.notify();
906    }
907
908    pub fn on_context_menu(
909        &mut self,
910        handler: Rc<
911            dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App),
912        >,
913    ) {
914        self.context_menu_handler = Some(handler);
915    }
916
917    /// Keeps the selection highlighted while this popup or one of its children has focus.
918    ///
919    /// This does not transfer keyboard focus or keep the caret visible. The
920    /// association holds only a weak focus handle. Pass `None` to clear it.
921    pub fn set_selection_focus(&mut self, focus: Option<FocusHandle>, cx: &mut Context<Self>) {
922        self.selection_focus = focus.as_ref().map(FocusHandle::downgrade);
923        cx.notify();
924    }
925
926    /// Whether the input or its associated selection popup has focus.
927    pub fn has_selection_focus(&self, window: &Window, cx: &App) -> bool {
928        self.focus_handle.is_focused(window)
929            || self
930                .selection_focus
931                .as_ref()
932                .and_then(WeakFocusHandle::upgrade)
933                .is_some_and(|focus| focus.is_focused(window) || focus.contains_focused(window, cx))
934    }
935
936    /// Build the engine. Each mode's own `new` sets its layout on top of this.
937    fn new_in_mode(window: &mut Window, cx: &mut Context<Self>) -> Self {
938        let focus_handle = cx.focus_handle().tab_stop(true);
939        let blink_cursor = cx.new(BlinkCursor::new);
940        let undo_manager = UndoManager::new();
941
942        let _subscriptions = vec![
943            // Key bindings can consume events before on_key_down. Observe input
944            // before action dispatch so every keystroke resets the blink delay.
945            cx.intercept_keystrokes({
946                let focus_handle = focus_handle.clone();
947                let blink_cursor = blink_cursor.downgrade();
948                move |_, window, cx| {
949                    if focus_handle.is_focused(window) {
950                        _ = blink_cursor.update(cx, |cursor, cx| cursor.pause(cx));
951                    }
952                }
953            }),
954            // Observe the blink cursor to repaint the view when it changes.
955            cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
956            // Blink the cursor when the window is active, pause when it's not.
957            cx.observe_window_activation(window, |input, window, cx| {
958                if window.is_window_active() {
959                    let focus_handle = input.focus_handle.clone();
960                    if focus_handle.is_focused(window) {
961                        input.blink_cursor.update(cx, |blink_cursor, cx| {
962                            blink_cursor.start(cx);
963                        });
964                    }
965                }
966            }),
967            cx.on_focus(&focus_handle, window, Self::on_focus),
968            cx.on_blur(&focus_handle, window, Self::on_blur),
969        ];
970
971        let text_style = window.text_style();
972
973        Self {
974            extras: M::Extras::default(),
975            focus_handle: focus_handle.clone(),
976            selection_focus: None,
977            text: "".into(),
978            display_map: DisplayMap::new(text_style.font(), window.rem_size(), None),
979            search_session: super::SearchSession::default(),
980            search_activation_revision: 0,
981            searchable: false,
982            replaceable: true,
983            soft_wrap: true,
984            wrapping_indent: WrappingIndent::default(),
985            scroll_beyond_last_line: None,
986            cursor_surrounding_lines: None,
987            blink_cursor,
988            undo_manager,
989            inline_tokens: None,
990            pending_token: None,
991            replaying_history: false,
992            validated_token_edit: false,
993            document_revision: 0,
994            token_presentation: Default::default(),
995            token_bounds: Default::default(),
996            hovered_token: None,
997            pending_token_hover_exits: Vec::new(),
998            token_layout_cache: None,
999            pressed_token: None,
1000            selections: Selections::default(),
1001            selected_word_range: None,
1002            ime_marked_range: None,
1003            ime_original_text: None,
1004            input_bounds: Bounds::default(),
1005            selecting: false,
1006            touch_selection: Default::default(),
1007            disabled: false,
1008            readonly: false,
1009            text_align: TextAlign::Left,
1010            masked: false,
1011            clean_on_escape: false,
1012            submit_on_enter: false,
1013            input_action: None,
1014            autocorrect: None,
1015            autocapitalize: None,
1016            show_whitespaces: false,
1017            loading: false,
1018            pattern: None,
1019            validate: None,
1020            number_step: Some(NumberStep::Fixed(1.)),
1021            number_min: None,
1022            number_max: None,
1023            mode: LayoutMode::default(),
1024            last_layout: None,
1025            prepaint_caret_geometry: None,
1026            last_bounds: None,
1027            revealed_viewport: None,
1028            last_selected_range: None,
1029            last_input_edits: Vec::new(),
1030            column_select_start: None,
1031            last_cursor: None,
1032            ime_anchor: None,
1033            scroll_handle: ScrollHandle::new(),
1034            scroll_bounce: crate::ScrollBounce::enabled_by_default(),
1035            overscroll: Overscroll::default(),
1036            scroll_size: gpui::size(px(0.), px(0.)),
1037            editor_scrollbar_snapshot: Cell::new(None),
1038            overview_ruler_rows: Default::default(),
1039            longest_line_width: Cell::new(None),
1040            editor_paddings: Edges::default(),
1041            deferred_scroll_offset: None,
1042            placeholder: SharedString::default(),
1043            mask_pattern: MaskPattern::default(),
1044            mask_pattern_set: false,
1045            editor_style: InputEditorStyle::default(),
1046            projected_editor_style: InputEditorStyle::default(),
1047            fold_placeholder_renderer: None,
1048            fold_background: None,
1049            accessibility: None,
1050            scrollbar_renderer: None,
1051            diagnostic_popover: None,
1052            context_menu_handler: None,
1053            pending_context_menu: None,
1054            enable_context_menu: true,
1055            completion_inserting: false,
1056            overlay_action_handler: None,
1057            silent_replace_text: false,
1058            emit_events: true,
1059            _subscriptions,
1060            _pending_update: false,
1061            cursor_line_end_affinity: false,
1062            auto_scroll: AutoScroll::default(),
1063        }
1064    }
1065
1066    /// Sets whether the context menu that shows on right-click is enabled.
1067    ///
1068    /// The context menu is enabled by default. Disabling it turns off the
1069    /// right-click menu entirely, including a custom menu set on the element.
1070    /// A custom menu already replaces the built-in one, so keep this enabled
1071    /// to show only your own items.
1072    pub fn context_menu(mut self, enable: bool) -> Self {
1073        self.enable_context_menu = enable;
1074        self
1075    }
1076
1077    pub fn set_context_menu_enabled(&mut self, enabled: bool) {
1078        self.enable_context_menu = enabled;
1079    }
1080
1081    /// Set whether search UI allows replacement, default is true.
1082    #[doc(hidden)]
1083    pub fn replaceable(mut self, allow: bool) -> Self {
1084        self.replaceable = allow;
1085        self
1086    }
1087
1088    /// Set placeholder
1089    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1090        self.placeholder = placeholder.into();
1091        self
1092    }
1093
1094    /// Set highlighter language for for [`LayoutMode::CodeEditor`] mode.
1095    pub fn set_highlighter(
1096        &mut self,
1097        new_language: impl Into<SharedString>,
1098        cx: &mut Context<Self>,
1099    ) {
1100        match &mut self.mode {
1101            LayoutMode::CodeEditor {
1102                language,
1103                highlighter,
1104                ..
1105            } => {
1106                language.set_name(new_language.into());
1107                *highlighter.borrow_mut() = None;
1108            }
1109            _ => {}
1110        }
1111        self.refresh(cx);
1112    }
1113
1114    fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
1115        match &mut self.mode {
1116            LayoutMode::CodeEditor { highlighter, .. } => {
1117                *highlighter.borrow_mut() = None;
1118            }
1119            _ => {}
1120        }
1121        cx.notify();
1122    }
1123
1124    /// Install the parser/highlighter adapter used by code-editor mode.
1125    pub fn set_highlighter_factory(
1126        &mut self,
1127        factory: InputHighlighterFactory,
1128        cx: &mut Context<Self>,
1129    ) {
1130        self.mode.set_highlighter_factory(factory);
1131        self._pending_update = true;
1132        self.token_layout_cache = None;
1133        cx.notify();
1134    }
1135
1136    /// Install a default adapter without replacing an application-provided one.
1137    pub fn ensure_highlighter_factory(&mut self, factory: InputHighlighterFactory) {
1138        self.mode.ensure_highlighter_factory(factory);
1139    }
1140
1141    pub fn set_editor_style(&mut self, style: InputEditorStyle) {
1142        self.editor_style = style.clone();
1143        self.projected_editor_style = style;
1144    }
1145
1146    /// Draws a placeholder right after the text of each folded line, given its
1147    /// buffer row. Clicking it unfolds the line.
1148    pub fn set_fold_placeholder_renderer(
1149        &mut self,
1150        renderer: Option<FoldPlaceholderRenderer>,
1151        cx: &mut Context<Self>,
1152    ) {
1153        self.fold_placeholder_renderer = renderer;
1154        cx.notify();
1155    }
1156
1157    /// Fills each folded line across the editor, as VS Code's
1158    /// `editor.foldBackground`.
1159    pub fn set_fold_background(&mut self, color: Option<Hsla>, cx: &mut Context<Self>) {
1160        self.fold_background = color;
1161        cx.notify();
1162    }
1163
1164    /// Project semantics onto the editor, never onto a second focus owner.
1165    pub fn set_accessibility(&mut self, accessibility: InputAccessibility) {
1166        self.accessibility = Some(accessibility);
1167    }
1168
1169    fn handle_accessibility_focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1170        if !self.disabled {
1171            self.focus(window, cx);
1172        }
1173    }
1174
1175    fn handle_accessibility_set_value(
1176        &mut self,
1177        data: Option<&gpui::accesskit::ActionData>,
1178        window: &mut Window,
1179        cx: &mut Context<Self>,
1180    ) {
1181        let Some(gpui::accesskit::ActionData::Value(value)) = data else {
1182            return;
1183        };
1184        // The action may have been queued before editability changed.
1185        if self.is_editable() {
1186            self.replace_all(value.to_string(), window, cx);
1187        }
1188    }
1189
1190    /// Set presentation padding for multi-line text and its scrollbar layout.
1191    #[doc(hidden)]
1192    pub fn set_editor_paddings(&mut self, paddings: Edges<Pixels>) {
1193        self.editor_paddings = paddings;
1194    }
1195
1196    /// Supply the element painted in place of the editor's scrollbar, or
1197    /// `None` to keep Base's. The renderer is called where the editor paints
1198    /// its scrollbar, with that frame's [`EditorScrollbarContext`]. Like
1199    /// [`Self::set_editor_style`], this does not notify, so it can be called
1200    /// while rendering.
1201    ///
1202    /// [`EditorScrollbarContext`]: crate::input::EditorScrollbarContext
1203    pub fn set_scrollbar_renderer(
1204        &mut self,
1205        renderer: Option<crate::input::EditorScrollbarRenderer>,
1206    ) {
1207        self.scrollbar_renderer = renderer;
1208    }
1209
1210    pub fn apply_highlighter_fold_candidates(
1211        &mut self,
1212        candidates: Vec<crate::input::FoldRange>,
1213        cx: &mut Context<Self>,
1214    ) {
1215        if self.mode.is_folding() {
1216            self.display_map.set_fold_candidates(candidates);
1217        }
1218        cx.notify();
1219    }
1220
1221    #[inline]
1222    pub fn diagnostics(&self) -> Option<&DiagnosticSet> {
1223        self.mode.diagnostics()
1224    }
1225
1226    #[inline]
1227    pub fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
1228        self.mode.diagnostics_mut()
1229    }
1230
1231    /// Set placeholder
1232    pub fn set_placeholder(
1233        &mut self,
1234        placeholder: impl Into<SharedString>,
1235        _: &mut Window,
1236        cx: &mut Context<Self>,
1237    ) {
1238        self.placeholder = placeholder.into();
1239        cx.notify();
1240    }
1241
1242    /// Find which line and sub-line the given offset belongs to, along with the position within that sub-line.
1243    ///
1244    /// Returns:
1245    ///
1246    /// - The index of the line (zero-based) containing the offset.
1247    /// - The index of the sub-line (zero-based) within the line containing the offset.
1248    /// - The position of the offset.
1249    pub(super) fn line_and_position_for_offset(
1250        &self,
1251        offset: usize,
1252    ) -> (usize, usize, Option<Point<Pixels>>) {
1253        let Some(last_layout) = &self.last_layout else {
1254            return (0, 0, None);
1255        };
1256        let line_height = last_layout.line_height;
1257
1258        let mut y_offset = last_layout.visible_top;
1259        for (vi, line) in last_layout.lines.iter().enumerate() {
1260            let prev_lines_offset = last_layout.visible_line_byte_offsets[vi];
1261            let local_offset = offset.saturating_sub(prev_lines_offset);
1262            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
1263                let sub_line_index = (pos.y / line_height) as usize;
1264                let adjusted_pos = point(pos.x + last_layout.line_number_width, pos.y + y_offset);
1265                return (vi, sub_line_index, Some(adjusted_pos));
1266            }
1267
1268            y_offset += line.size(line_height).height;
1269        }
1270        (0, 0, None)
1271    }
1272
1273    /// Set the value of the input field: plain text, or [`InputContent`] to
1274    /// restore text together with its inline tokens. Editing history is
1275    /// cleared and no [`InputEvent::Change`] is emitted.
1276    ///
1277    /// For single-line inputs the caret is placed at the end of the text while
1278    /// the view is scrolled back to the start, so a long value shows its
1279    /// beginning instead of its tail (matching HTML `<input>`). Multi-line
1280    /// inputs reset the selection to `0..0`.
1281    pub fn set_value(
1282        &mut self,
1283        value: impl Into<super::InputContent>,
1284        window: &mut Window,
1285        cx: &mut Context<Self>,
1286    ) {
1287        let content = value.into();
1288        self.inline_tokens = None;
1289        self.undo_manager.set_ignoring(true);
1290        self.emit_events = false;
1291        self.replace_text(content.text().clone(), window, cx);
1292        self.ime_original_text = None;
1293        self.install_tokens(content);
1294        self.undo_manager.set_ignoring(false);
1295        self.emit_events = true;
1296
1297        self.reset_selection();
1298        self.reset_lsp_state();
1299        self.reset_scroll_to_start();
1300
1301        self.undo_manager.clear();
1302        cx.notify();
1303    }
1304
1305    /// Replace the entire text content while preserving undo history.
1306    ///
1307    /// Unlike [`set_value`](Self::set_value), this method records the
1308    /// replacement in the undo stack, allowing the user to undo/redo
1309    /// the change. The selection is placed at the end of the new text
1310    /// for single-line inputs, or cleared (0..0) for multi-line inputs.
1311    ///
1312    /// Use this when programmatically replacing the full text but the
1313    /// user should still be able to undo the operation — e.g. formatting.
1314    pub fn replace_all(
1315        &mut self,
1316        text: impl Into<SharedString>,
1317        window: &mut Window,
1318        cx: &mut Context<Self>,
1319    ) {
1320        self.replace_text(text, window, cx);
1321        self.reset_selection();
1322        self.reset_lsp_state();
1323        self.reset_scroll_to_start();
1324
1325        cx.notify();
1326    }
1327
1328    /// Perform `f` with the user-facing edit restrictions lifted.
1329    ///
1330    /// The `disabled` and `readonly` modes only reject the changes made by the
1331    /// user, the programmatic APIs must always be able to update the text.
1332    pub(super) fn with_edits_allowed(&mut self, f: impl FnOnce(&mut Self)) {
1333        let (was_disabled, was_readonly) = (self.disabled, self.readonly);
1334        (self.disabled, self.readonly) = (false, false);
1335        f(self);
1336        (self.disabled, self.readonly) = (was_disabled, was_readonly);
1337    }
1338
1339    /// Insert text at the current cursor position.
1340    ///
1341    /// And the cursor will be moved to the end of inserted text.
1342    pub fn insert(
1343        &mut self,
1344        text: impl Into<SharedString>,
1345        window: &mut Window,
1346        cx: &mut Context<Self>,
1347    ) {
1348        let text: SharedString = text.into();
1349        self.with_edits_allowed(|this| {
1350            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1351            let range_utf16 = this.range_to_utf16(&(this.cursor()..this.cursor()));
1352            this.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
1353            let end = this.active_selection().end;
1354            this.set_cursor_to(end);
1355        });
1356    }
1357
1358    /// Replace text at the current cursor position.
1359    ///
1360    /// And the cursor will be moved to the end of replaced text.
1361    pub fn replace(
1362        &mut self,
1363        text: impl Into<SharedString>,
1364        window: &mut Window,
1365        cx: &mut Context<Self>,
1366    ) {
1367        let text: SharedString = text.into();
1368        self.with_edits_allowed(|this| {
1369            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1370            this.replace_text_in_range_silent(None, &text, window, cx);
1371            let end = this.active_selection().end;
1372            this.set_cursor_to(end);
1373        });
1374    }
1375
1376    fn replace_text(
1377        &mut self,
1378        text: impl Into<SharedString>,
1379        window: &mut Window,
1380        cx: &mut Context<Self>,
1381    ) {
1382        let text: SharedString = text.into();
1383        self.with_edits_allowed(|this| {
1384            this.undo_manager.set_pending_intent(EditIntent::Atomic);
1385            let range = 0..this.text.chars().map(|c| c.len_utf16()).sum();
1386            this.replace_text_in_range_silent(Some(range), &text, window, cx);
1387            this.reset_highlighter(cx);
1388        });
1389    }
1390
1391    fn reset_selection(&mut self) {
1392        self.selections.remove_all_but_active();
1393
1394        // For single-line inputs the caret is placed at the end of the text
1395        // (matching HTML `<input>`); multi-line inputs reset the selection to
1396        // `0..0`.
1397        if self.is_single_line() {
1398            let end = self.text.len();
1399            self.set_cursor_to(end);
1400        } else {
1401            self.active_selection_mut().clear();
1402        }
1403    }
1404
1405    fn reset_lsp_state(&mut self) {
1406        if self.is_code_editor() {
1407            self._pending_update = true;
1408            M::reset_language_features(self);
1409        }
1410    }
1411
1412    fn reset_scroll_to_start(&mut self) {
1413        // Move scroll to the start. For single-line the caret is at the end, so
1414        // override the cursor-follow scroll for the next painted frame to keep
1415        // the start visible; the deferred offset is consumed during that paint.
1416        self.scroll_handle.set_offset(point(px(0.), px(0.)));
1417        if self.is_single_line() {
1418            self.deferred_scroll_offset = Some(point(px(0.), px(0.)));
1419        }
1420    }
1421
1422    /// Set with disabled mode.
1423    ///
1424    /// See also: [`Self::set_disabled`].
1425    #[allow(unused)]
1426    pub(crate) fn disabled(mut self, disabled: bool) -> Self {
1427        self.disabled = disabled;
1428        self
1429    }
1430
1431    pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
1432        if self.disabled == disabled {
1433            return;
1434        }
1435
1436        self.disabled = disabled;
1437        if disabled {
1438            M::hide_context_menu(self, cx);
1439            M::clear_inline_completion(self, cx);
1440        }
1441        cx.notify();
1442    }
1443
1444    /// Set with read-only mode.
1445    ///
1446    /// Unlike [`Self::disabled`], a read-only input keeps the normal appearance,
1447    /// focus, cursor, selection and copy behavior, it only rejects any change
1448    /// of the text made by the user.
1449    ///
1450    /// See also: [`Self::set_readonly`].
1451    #[allow(unused)]
1452    pub(crate) fn readonly(mut self, readonly: bool) -> Self {
1453        self.readonly = readonly;
1454        self
1455    }
1456
1457    pub fn set_readonly(&mut self, readonly: bool, cx: &mut Context<Self>) {
1458        if self.readonly == readonly {
1459            return;
1460        }
1461
1462        self.readonly = readonly;
1463        if readonly {
1464            M::hide_context_menu(self, cx);
1465            M::clear_inline_completion(self, cx);
1466            self.search_session.replace_mode = false;
1467        }
1468        cx.notify();
1469    }
1470
1471    /// Returns true if the user is allowed to change the text.
1472    ///
1473    /// This is false when the input is `disabled` or `readonly`, the programmatic
1474    /// APIs (e.g.: [`Self::set_value`], [`Self::insert`]) are not limited by this.
1475    pub fn is_editable(&self) -> bool {
1476        !self.disabled && !self.readonly
1477    }
1478
1479    /// Set true to clear the input by pressing Escape key.
1480    ///
1481    /// Not on Android, where Escape is also the back button.
1482    pub fn clean_on_escape(mut self) -> Self {
1483        self.clean_on_escape = true;
1484        self
1485    }
1486
1487    pub fn set_clean_on_escape(&mut self, clean: bool) {
1488        self.clean_on_escape = clean;
1489    }
1490
1491    /// Set true to treat `Enter` as a submit action in multi-line mode,
1492    /// while `Shift+Enter` inserts a newline.
1493    ///
1494    /// Default is `false` (both `Enter` and `Shift+Enter` insert a newline).
1495    #[doc(hidden)]
1496    pub fn submit_on_enter(mut self, submit: bool) -> Self {
1497        self.submit_on_enter = submit;
1498        self
1499    }
1500
1501    pub fn set_submit_on_enter(&mut self, submit: bool, cx: &mut Context<Self>) {
1502        self.submit_on_enter = submit;
1503        cx.notify();
1504    }
1505
1506    /// Set the action a software keyboard shows on its enter key, like Search
1507    /// or Send. Pressing it still emits [`InputEvent::PressEnter`].
1508    ///
1509    /// Default is [`TextInputAction::Done`] for a single-line input (and a
1510    /// multi-line one that submits on enter), and [`TextInputAction::Enter`]
1511    /// (a line break) for a multi-line input.
1512    pub fn input_action(mut self, action: TextInputAction) -> Self {
1513        self.input_action = Some(action);
1514        self
1515    }
1516
1517    /// Set whether the platform may autocorrect the text and offer word
1518    /// suggestions.
1519    ///
1520    /// Default is `false`. Enable this explicitly for fields that accept prose;
1521    /// masking or revealing text does not change the setting.
1522    pub fn autocorrect(mut self, autocorrect: bool) -> Self {
1523        self.autocorrect = Some(autocorrect);
1524        self
1525    }
1526
1527    /// Set how a software keyboard capitalizes the text.
1528    ///
1529    /// Default is [`Autocapitalize::None`]. Enable capitalization explicitly for
1530    /// fields where it is appropriate, independently of autocorrection.
1531    pub fn autocapitalize(mut self, autocapitalize: Autocapitalize) -> Self {
1532        self.autocapitalize = Some(autocapitalize);
1533        self
1534    }
1535
1536    /// The text assistance a platform IME (a software keyboard) should give
1537    /// this input, from [`Self::input_action`], [`Self::autocorrect`] and
1538    /// [`Self::autocapitalize`] or the defaults they describe.
1539    pub fn text_input_configuration(&self) -> TextInputConfiguration {
1540        // Masking is a presentation choice, not a reliable content type: a
1541        // revealed password must not acquire text assistance. Keep assistance
1542        // opt-in for structured fields such as passwords, email and URLs.
1543        let autocorrect = self.autocorrect.unwrap_or(false);
1544        let input_action =
1545            self.input_action
1546                .unwrap_or(if self.is_multi_line() && !self.submit_on_enter {
1547                    TextInputAction::Enter
1548                } else {
1549                    TextInputAction::Done
1550                });
1551        TextInputConfiguration {
1552            autocorrect,
1553            autocapitalize: self.autocapitalize.unwrap_or(Autocapitalize::None),
1554            suggestions: autocorrect,
1555            input_action,
1556        }
1557    }
1558
1559    /// Set whether to show whitespace characters.
1560    #[doc(hidden)]
1561    pub fn show_whitespaces(mut self, show: bool) -> Self {
1562        self.show_whitespaces = show;
1563        self
1564    }
1565
1566    /// Update whether to show whitespace characters.
1567    pub fn set_show_whitespaces(&mut self, show: bool, _: &mut Window, cx: &mut Context<Self>) {
1568        self.show_whitespaces = show;
1569        cx.notify();
1570    }
1571
1572    /// Empty rows reserved below the last line of content ("scroll
1573    /// beyond last line"), code-editor mode only. Mirrors VSCode's
1574    /// `editor.scrollBeyondLastLine` / Zed's `scroll_beyond_last_line`.
1575    ///
1576    /// - `None` (default): half the viewport, floored at
1577    ///   [`BOTTOM_MARGIN_ROWS`] line-heights.
1578    /// - `Some(0)`: no trailing space; the cursor sits flush with the
1579    ///   last row at scroll-max.
1580    /// - `Some(n)`: exactly `n` rows.
1581    pub fn scroll_beyond_last_line(mut self, rows: Option<usize>) -> Self {
1582        self.scroll_beyond_last_line = rows;
1583        self
1584    }
1585
1586    /// Update [`Self::scroll_beyond_last_line`] after construction.
1587    pub fn set_scroll_beyond_last_line(
1588        &mut self,
1589        rows: Option<usize>,
1590        _: &mut Window,
1591        cx: &mut Context<Self>,
1592    ) {
1593        if self.scroll_beyond_last_line == rows {
1594            return;
1595        }
1596        self.scroll_beyond_last_line = rows;
1597        cx.notify();
1598    }
1599
1600    /// Minimum number of lines the cursor is kept clear of the viewport's
1601    /// top/bottom edge before auto-scroll engages. Mirrors VSCode's
1602    /// `editor.cursorSurroundingLines` / Zed's `vertical_scroll_margin`.
1603    /// Orthogonal to [`Self::scroll_beyond_last_line`], which sizes the
1604    /// empty region; this controls the cursor's resting distance from the
1605    /// edge.
1606    ///
1607    /// - `None` (default): [`BOTTOM_MARGIN_ROWS`] lines, falling back to
1608    ///   one line on small viewports.
1609    /// - `Some(n)`: requests `n` lines, clamped to half the viewport.
1610    ///
1611    /// In a constrained viewport, edge clearance yields to the cursor's
1612    /// complete visual row so surrounding space cannot clip the text being edited.
1613    pub fn cursor_surrounding_lines(mut self, lines: Option<usize>) -> Self {
1614        self.cursor_surrounding_lines = lines;
1615        self
1616    }
1617
1618    /// Update [`Self::cursor_surrounding_lines`] after construction.
1619    pub fn set_cursor_surrounding_lines(
1620        &mut self,
1621        lines: Option<usize>,
1622        _: &mut Window,
1623        cx: &mut Context<Self>,
1624    ) {
1625        if self.cursor_surrounding_lines == lines {
1626            return;
1627        }
1628        self.cursor_surrounding_lines = lines;
1629        cx.notify();
1630    }
1631
1632    /// Set the default value of the input field.
1633    pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {
1634        let text: SharedString = value.into();
1635        self.text = Rope::from(self.normalize_input(&text).as_ref());
1636        if let Some(diagnostics) = self.mode.diagnostics_mut() {
1637            diagnostics.reset(&self.text)
1638        }
1639        // Note: We can't call display_map.set_text here because it needs cx.
1640        // The text will be set during prepare_if_need in element.rs
1641        self._pending_update = true;
1642        self
1643    }
1644
1645    /// Return the value of the input field as an owned string.
1646    ///
1647    /// The string is materialized on each call. See [`Self::text`] for the
1648    /// [`Rope`] the state owns, which is borrowed and costs nothing to read.
1649    pub fn value(&self) -> SharedString {
1650        SharedString::new(self.text.to_string())
1651    }
1652
1653    /// Return the portion of the value within the input field that
1654    /// is selected by the user, as an owned string.
1655    ///
1656    /// The string is materialized on each call. See [`Self::selected_text`]
1657    /// for the same selection borrowed out of the [`Rope`] the state owns.
1658    pub fn selected_value(&self) -> SharedString {
1659        SharedString::new(self.selected_text().to_string())
1660    }
1661
1662    /// Return the value without mask.
1663    pub fn unmask_value(&self) -> SharedString {
1664        self.mask_pattern.unmask(&self.text.to_string()).into()
1665    }
1666
1667    /// Kept so existing render paths keep compiling.
1668    ///
1669    /// Configuration used to be collected by a facade and applied here; the
1670    /// state now configures itself, so this does nothing and can be deleted at
1671    /// the call site.
1672    #[doc(hidden)]
1673    pub fn prepare(&mut self, _: &mut Window, _: &mut Context<Self>) {}
1674
1675    /// Return the text [`Rope`] of the input field.
1676    ///
1677    /// Borrowed from the state, so reading even a large document copies
1678    /// nothing. See [`Self::value`] when an owned string is wanted.
1679    pub fn text(&self) -> &Rope {
1680        &self.text
1681    }
1682
1683    /// Report the change with its own snapshot before the legacy notification.
1684    fn emit_change(&mut self, cx: &mut Context<Self>) {
1685        if let Some(original) = self.ime_original_text.take() {
1686            self.last_input_edits = vec![(
1687                Self::full_input_edit(&original, &self.text),
1688                self.text.clone(),
1689            )];
1690        }
1691        cx.emit(InputEditsEvent {
1692            edits: self.last_input_edits.clone(),
1693        });
1694        cx.emit(InputEvent::Change);
1695    }
1696
1697    fn full_input_edit(old_text: &Rope, new_text: &Rope) -> InputEdit {
1698        InputEdit {
1699            start_byte: 0,
1700            old_end_byte: old_text.len(),
1701            new_end_byte: new_text.len(),
1702            start_position: old_text.offset_to_point(0),
1703            old_end_position: old_text.offset_to_point(old_text.len()),
1704            new_end_position: new_text.offset_to_point(new_text.len()),
1705        }
1706    }
1707
1708    /// Return the (0-based) [`Position`] of the cursor.
1709    pub fn cursor_position(&self) -> Position {
1710        let offset = self.cursor();
1711        self.text.offset_to_position(offset)
1712    }
1713
1714    /// Set (0-based) [`Position`] of the cursor.
1715    ///
1716    /// This will move the cursor to the specified line and column, and update the selection range.
1717    pub fn set_cursor_position(
1718        &mut self,
1719        position: impl Into<Position>,
1720        window: &mut Window,
1721        cx: &mut Context<Self>,
1722    ) {
1723        let position: Position = position.into();
1724        let offset = self.text.position_to_offset(&position);
1725
1726        self.move_to(offset, None, cx);
1727        self.update_preferred_column();
1728        self.focus(window, cx);
1729    }
1730
1731    /// Focus the input field.
1732    pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1733        self.focus_handle.focus(window, cx);
1734        self.blink_cursor.update(cx, |cursor, cx| {
1735            cursor.start(cx);
1736        });
1737    }
1738
1739    /// Refresh the input, so the next render re-runs syntax highlighting and
1740    /// the LSP providers, not just a redraw.
1741    ///
1742    /// Assigning the `lsp` providers (or other render-affecting state) at
1743    /// runtime does not take effect until the text next changes. Call this
1744    /// afterwards to force the refresh on the next render.
1745    ///
1746    /// ```ignore
1747    /// input.update(cx, |state, cx| {
1748    ///     state.extras.lsp.hover_provider = Some(provider);
1749    ///     state.refresh(cx);
1750    /// });
1751    /// ```
1752    pub fn refresh(&mut self, cx: &mut Context<Self>) {
1753        self._pending_update = true;
1754        cx.notify();
1755    }
1756
1757    pub(super) fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
1758        self.undo_manager.break_transaction_coalescing();
1759        self.select_all_cursors_to(|s, sel| s.previous_boundary(sel.cursor_offset()), cx);
1760    }
1761
1762    pub(super) fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
1763        self.select_all_cursors_to(|s, sel| s.next_boundary(sel.cursor_offset()), cx);
1764    }
1765
1766    pub(super) fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
1767        if self.is_single_line() {
1768            return;
1769        }
1770        self.select_all_cursors_to_with_affinity(
1771            |s, sel| {
1772                s.vertical_selection_target(
1773                    sel.cursor_offset(),
1774                    sel.column_anchor,
1775                    s.line_end_affinity_for(sel),
1776                    -1,
1777                )
1778            },
1779            true,
1780            cx,
1781        );
1782    }
1783
1784    pub(super) fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
1785        if self.is_single_line() {
1786            return;
1787        }
1788        self.select_all_cursors_to_with_affinity(
1789            |s, sel| {
1790                s.vertical_selection_target(
1791                    sel.cursor_offset(),
1792                    sel.column_anchor,
1793                    s.line_end_affinity_for(sel),
1794                    1,
1795                )
1796            },
1797            true,
1798            cx,
1799        );
1800    }
1801
1802    pub(super) fn on_action_select_all(
1803        &mut self,
1804        _: &SelectAll,
1805        window: &mut Window,
1806        cx: &mut Context<Self>,
1807    ) {
1808        self.select_all(window, cx);
1809    }
1810
1811    pub(super) fn select_to_start(
1812        &mut self,
1813        _: &SelectToStart,
1814        _: &mut Window,
1815        cx: &mut Context<Self>,
1816    ) {
1817        self.undo_manager.break_transaction_coalescing();
1818        self.select_all_cursors_to(|_, _| 0, cx);
1819    }
1820
1821    pub(super) fn select_to_end(
1822        &mut self,
1823        _: &SelectToEnd,
1824        _: &mut Window,
1825        cx: &mut Context<Self>,
1826    ) {
1827        self.undo_manager.break_transaction_coalescing();
1828        let end = self.text.len();
1829        self.select_all_cursors_to(move |_, _| end, cx);
1830    }
1831
1832    pub(super) fn select_to_start_of_line(
1833        &mut self,
1834        _: &SelectToStartOfLine,
1835        _: &mut Window,
1836        cx: &mut Context<Self>,
1837    ) {
1838        self.undo_manager.break_transaction_coalescing();
1839        self.select_all_cursors_to(
1840            |s, sel| s.start_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1841            cx,
1842        );
1843    }
1844
1845    pub(super) fn select_to_end_of_line(
1846        &mut self,
1847        _: &SelectToEndOfLine,
1848        _: &mut Window,
1849        cx: &mut Context<Self>,
1850    ) {
1851        self.undo_manager.break_transaction_coalescing();
1852        self.select_all_cursors_to_with_affinity(
1853            |s, sel| {
1854                (
1855                    s.end_of_line_at(sel.cursor_offset(), s.line_end_affinity_for(sel)),
1856                    true,
1857                )
1858            },
1859            false,
1860            cx,
1861        );
1862    }
1863
1864    pub(super) fn select_to_previous_word(
1865        &mut self,
1866        _: &SelectToPreviousWordStart,
1867        _: &mut Window,
1868        cx: &mut Context<Self>,
1869    ) {
1870        self.undo_manager.break_transaction_coalescing();
1871        self.select_all_cursors_to(
1872            |s, sel| s.previous_start_of_word_at(sel.cursor_offset()),
1873            cx,
1874        );
1875    }
1876
1877    pub(super) fn select_to_next_word(
1878        &mut self,
1879        _: &SelectToNextWordEnd,
1880        _: &mut Window,
1881        cx: &mut Context<Self>,
1882    ) {
1883        self.undo_manager.break_transaction_coalescing();
1884        self.select_all_cursors_to(|s, sel| s.next_end_of_word_at(sel.cursor_offset()), cx);
1885    }
1886
1887    /// Return the start offset of the previous word.
1888    /// Return the previous start offset of the word before `offset`.
1889    pub(super) fn previous_start_of_word_at(&self, offset: usize) -> usize {
1890        if self.masked {
1891            // The mask replaces every character, so the displayed text has no
1892            // word boundaries to move or delete by. Collapse the word to the
1893            // whole text.
1894            return 0;
1895        }
1896
1897        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1898        // FIXME: Avoid to_string
1899        let left_part = self.text.slice(0..offset).to_string();
1900
1901        let target = UnicodeSegmentation::split_word_bound_indices(left_part.as_str())
1902            .rfind(|(_, s)| !s.trim_start().is_empty())
1903            .map(|(i, _)| i)
1904            .unwrap_or(0);
1905        self.token_boundary(target, Bias::Left)
1906    }
1907
1908    /// Return the next end offset of the word after `offset`.
1909    pub(super) fn next_end_of_word_at(&self, offset: usize) -> usize {
1910        if self.masked {
1911            // See `previous_start_of_word_at`.
1912            return self.text.len();
1913        }
1914
1915        let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1916        let right_part = self.text.slice(offset..self.text.len()).to_string();
1917
1918        let target = UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
1919            .find(|(_, s)| !s.trim_start().is_empty())
1920            .map(|(i, s)| offset + i + s.len())
1921            .unwrap_or(self.text.len());
1922        self.token_boundary(target, Bias::Right)
1923    }
1924
1925    /// Get start of line byte offset for the given `offset`.
1926    ///
1927    /// When soft wrap is active, first press goes to visual line start,
1928    /// second press (already at visual start) goes to logical line start.
1929    pub(super) fn start_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1930        if self.is_single_line() {
1931            return 0;
1932        }
1933
1934        let row = self.text.offset_to_point(offset).row;
1935        let logical_start = self.text.line_start_offset(row);
1936
1937        if self.soft_wrap && self.is_code_editor() {
1938            let wrap_point = self
1939                .display_map
1940                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1941            if let Some(line) = self.display_map.line(row)
1942                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1943            {
1944                let visual_start = logical_start + range.start;
1945                if offset != visual_start {
1946                    return visual_start;
1947                }
1948            }
1949        }
1950
1951        logical_start
1952    }
1953
1954    /// Get end of line byte offset for the given `offset`.
1955    ///
1956    /// When soft wrap is active, first press goes to visual line end,
1957    /// second press (already at visual end) goes to logical line end.
1958    pub(super) fn end_of_line_at(&self, offset: usize, line_end_affinity: bool) -> usize {
1959        if self.is_single_line() {
1960            return self.text.len();
1961        }
1962
1963        let row = self.text.offset_to_point(offset).row;
1964        let logical_start = self.text.line_start_offset(row);
1965        let logical_end = self.text.line_end_offset(row);
1966
1967        if self.soft_wrap && self.is_code_editor() {
1968            // Use the row the caret is drawn on: at a wrap boundary the raw offset would name
1969            // the next row, and a second End press would keep walking down instead of falling
1970            // through to the logical line end.
1971            let wrap_point = self
1972                .display_map
1973                .offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
1974            if let Some(line) = self.display_map.line(row)
1975                && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1976            {
1977                let visual_end = logical_start + range.end;
1978                if offset != visual_end {
1979                    return visual_end;
1980                }
1981            }
1982        }
1983
1984        logical_end
1985    }
1986
1987    /// Get indent string of next line.
1988    ///
1989    /// To get current and next line indent, to return more depth one.
1990    pub(super) fn indent_of_next_line(&mut self) -> String {
1991        self.indent_of_next_line_at(self.cursor())
1992    }
1993
1994    /// Get indent string of the next line, relative to the given `offset`.
1995    pub(super) fn indent_of_next_line_at(&mut self, offset: usize) -> String {
1996        if self.is_single_line() {
1997            return "".into();
1998        }
1999
2000        let mut current_indent = String::new();
2001        let mut next_indent = String::new();
2002        let line_end_affinity = self.line_end_affinity_at(offset);
2003        let current_line_start_pos = self.start_of_line_at(offset, line_end_affinity);
2004        let next_line_start_pos = self.end_of_line_at(offset, line_end_affinity);
2005        for c in self.text.slice(current_line_start_pos..).chars() {
2006            if !c.is_whitespace() {
2007                break;
2008            }
2009            if c == '\n' || c == '\r' {
2010                break;
2011            }
2012            current_indent.push(c);
2013        }
2014
2015        for c in self.text.slice(next_line_start_pos..).chars() {
2016            if !c.is_whitespace() {
2017                break;
2018            }
2019            if c == '\n' || c == '\r' {
2020                break;
2021            }
2022            next_indent.push(c);
2023        }
2024
2025        if next_indent.len() > current_indent.len() {
2026            return next_indent;
2027        }
2028
2029        // Smart indent: one extra level after lines ending with a trigger.
2030        // Skipped inside strings and comments: the trigger is literal text.
2031        if let Some(rules) = self.mode.language_config() {
2032            if self.mode.is_smart_indent()
2033                && M::editing_syntax_context(self, offset) == crate::input::SyntaxContext::Code
2034            {
2035                let line_before: String = self
2036                    .text
2037                    .slice(current_line_start_pos..offset)
2038                    .chars()
2039                    .collect();
2040                if rules.opens_indent(line_before.trim_end()) {
2041                    let tab = self.mode.tab_size();
2042                    if tab.hard_tabs {
2043                        current_indent.push('\t');
2044                    } else {
2045                        for _ in 0..tab.tab_size {
2046                            current_indent.push(' ');
2047                        }
2048                    }
2049                } else {
2050                    let line_after = self.text.slice(offset..next_line_start_pos).to_string();
2051                    if rules.closes_indent(&line_after) {
2052                        let tab = self.mode.tab_size();
2053                        if current_indent.ends_with('\t') {
2054                            current_indent.pop();
2055                        } else {
2056                            for _ in 0..tab.tab_size {
2057                                if !current_indent.ends_with(' ') {
2058                                    break;
2059                                }
2060                                current_indent.pop();
2061                            }
2062                        }
2063                    }
2064                }
2065            }
2066        }
2067
2068        current_indent
2069    }
2070
2071    /// Delete every selection as one batch. Collapsed cursors are first
2072    /// expanded to a deletion range by `collapsed_target` and non-empty
2073    /// selections delete their own range.
2074    ///
2075    /// `collapsed_intent` is the intent to record when every cursor is
2076    /// collapsed, which is what makes a run of single-character deletes undo
2077    /// as one gesture. Deleting a real selection is always atomic.
2078    fn delete_selections(
2079        &mut self,
2080        silent: bool,
2081        collapsed_intent: EditIntent,
2082        mut collapsed_target: impl FnMut(&mut Self, usize) -> Range<usize>,
2083        window: &mut Window,
2084        cx: &mut Context<Self>,
2085    ) {
2086        if !self.is_editable() {
2087            return;
2088        }
2089        let cursors: Vec<CursorSelection> = self.selections.iter().copied().collect();
2090        let intent = if cursors.iter().all(|sel| sel.is_empty()) {
2091            collapsed_intent
2092        } else {
2093            EditIntent::Atomic
2094        };
2095        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(cursors.len());
2096        for sel in &cursors {
2097            let range = if sel.is_empty() {
2098                collapsed_target(self, sel.cursor_offset())
2099            } else {
2100                sel.start..sel.end
2101            };
2102            let (start, end) = (range.start.min(range.end), range.start.max(range.end));
2103            let mut selection = *sel;
2104            selection.start = start;
2105            selection.end = end;
2106            new_selections.push(selection);
2107        }
2108        // Capture the user's selections before expanding or merging deletion
2109        // ranges. The edit ranges cannot reconstruct their original carets.
2110        self.undo_manager.begin_transaction_with(intent);
2111        self.undo_manager
2112            .record_selections(cursors.clone(), cursors.clone());
2113        self.selections.replace_all(new_selections);
2114        self.undo_manager.set_pending_intent(intent);
2115
2116        let was_silent = self.silent_replace_text;
2117        self.silent_replace_text = silent;
2118        self.replace_text_in_range(None, "", window, cx);
2119        self.silent_replace_text = was_silent;
2120        self.undo_manager
2121            .record_selections(cursors, self.selections.iter().copied().collect());
2122        self.undo_manager.commit_transaction();
2123        self.pause_blink_cursor(cx);
2124    }
2125
2126    /// Decide pairing against the pre-edit text, before an opening quote can
2127    /// change its own syntax context. Ordinary characters never query syntax.
2128    fn auto_close_target(&self, range: &Range<usize>, text: &str) -> Option<(usize, SharedString)> {
2129        if self.silent_replace_text
2130            || self.ime_marked_range.is_some()
2131            || !self.selections.is_single()
2132            || !self.active_selection().is_empty()
2133            || !range.is_empty()
2134            || range.start != self.cursor()
2135            || text.chars().count() != 1
2136            || !self.mode.is_auto_close()
2137        {
2138            return None;
2139        }
2140        let rules = self.mode.language_config()?;
2141        if self
2142            .text
2143            .chars_at(range.start)
2144            .next()
2145            .is_some_and(|c| !c.is_whitespace() && !rules.auto_close_before.contains(c))
2146        {
2147            return None;
2148        }
2149        for (open, close, not_in) in rules.closing_pairs() {
2150            let Some(prefix) = open.strip_suffix(text) else {
2151                continue;
2152            };
2153            if !self.text_before_matches(range.start, prefix) {
2154                continue;
2155            }
2156            let start = range.start - prefix.len();
2157            if open == close
2158                && (self.is_escaped_at(start)
2159                    || self
2160                        .text
2161                        .chars_at(start)
2162                        .reversed()
2163                        .next()
2164                        .is_some_and(|c| c.is_alphanumeric() || c == '_'))
2165            {
2166                continue;
2167            }
2168            if not_in.contains(&M::editing_syntax_context(self, start)) {
2169                continue;
2170            }
2171            return Some((open.len(), close.into()));
2172        }
2173        None
2174    }
2175
2176    fn text_before_matches(&self, offset: usize, text: &str) -> bool {
2177        offset >= text.len()
2178            && self.text.is_char_boundary(offset - text.len())
2179            && self.text.slice(offset - text.len()..offset) == text
2180    }
2181
2182    fn text_after_matches(&self, offset: usize, text: &str) -> bool {
2183        offset + text.len() <= self.text.len()
2184            && self.text.is_char_boundary(offset + text.len())
2185            && self.text.slice(offset..offset + text.len()) == text
2186    }
2187
2188    fn is_escaped_at(&self, offset: usize) -> bool {
2189        self.text
2190            .chars_at(offset)
2191            .reversed()
2192            .take_while(|c| *c == '\\')
2193            .count()
2194            % 2
2195            == 1
2196    }
2197
2198    /// Cursor target when a typed closer should skip over an existing one.
2199    ///
2200    /// Returns `Some(offset)` when `new_text` is a single closer from the
2201    /// active [`LanguageConfig`](crate::input::language_config::LanguageConfig) that already follows the collapsed cursor and is
2202    /// not escaped. The caller then moves the cursor without editing text or
2203    /// history. `None` means insert normally.
2204    fn skip_over_target(&self, new_text: &str) -> Option<usize> {
2205        if !self.mode.is_auto_close() || new_text.chars().count() != 1 {
2206            return None;
2207        }
2208        let rules = self.mode.language_config()?;
2209        let cursor = self.cursor();
2210        for (open, close, not_in) in rules.closing_pairs() {
2211            for (index, _) in close.char_indices() {
2212                if !close[index..].starts_with(new_text)
2213                    || !self.text_before_matches(cursor, &close[..index])
2214                    || !self.text_after_matches(cursor, &close[index..])
2215                {
2216                    continue;
2217                }
2218                let context = M::editing_syntax_context(self, cursor);
2219                let generated =
2220                    self.mode
2221                        .auto_closed_pairs()
2222                        .contains_closer(cursor, index, close.len());
2223                if !generated
2224                    && not_in.contains(&context)
2225                    && !(context == crate::input::SyntaxContext::String && open == close)
2226                {
2227                    continue;
2228                }
2229                if self.is_escaped_at(cursor - index) {
2230                    continue;
2231                }
2232                return Some(cursor + new_text.len());
2233            }
2234        }
2235        None
2236    }
2237
2238    pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
2239        // Nothing to delete at the start of the text. Propagate so an ancestor
2240        // (e.g. a command palette navigating back a level) can act on it.
2241        // This is harmless when nothing upstream is bound to backspace. With multiple
2242        // cursors the others can still delete, so only the lone-cursor case
2243        // propagates.
2244        if self.selections.is_single() && self.active_selection().is_empty() && self.cursor() == 0 {
2245            cx.propagate();
2246            return;
2247        }
2248
2249        // Pair deletion follows automatic closing rules, independently of
2250        // structural brackets used by Enter.
2251        if self.mode.is_auto_close()
2252            && self.selections.is_single()
2253            && self.active_selection().is_empty()
2254        {
2255            let off = self.cursor();
2256            let deletion = self.mode.language_config().and_then(|rules| {
2257                rules.closing_pairs().find_map(|(open, close, not_in)| {
2258                    if !self.text_before_matches(off, open) || !self.text_after_matches(off, close)
2259                    {
2260                        return None;
2261                    }
2262                    let start = off - open.len();
2263                    let generated = self
2264                        .mode
2265                        .auto_closed_pairs()
2266                        .contains(start..off, off..off + close.len());
2267                    if self.is_escaped_at(start)
2268                        || (!generated && not_in.contains(&M::editing_syntax_context(self, start)))
2269                    {
2270                        return None;
2271                    }
2272                    Some(start..off + close.len())
2273                })
2274            });
2275            if let Some(range) = deletion {
2276                let utf16 = self.range_to_utf16(&range);
2277                self.replace_text_in_range_silent(Some(utf16), "", window, cx);
2278                return;
2279            }
2280        }
2281
2282        self.delete_selections(
2283            false,
2284            EditIntent::Backspace,
2285            |s, offset| s.previous_boundary(offset)..offset,
2286            window,
2287            cx,
2288        );
2289    }
2290
2291    pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
2292        self.delete_selections(
2293            false,
2294            EditIntent::DeleteForward,
2295            |s, offset| offset..s.next_boundary(offset),
2296            window,
2297            cx,
2298        );
2299    }
2300
2301    pub(super) fn delete_to_beginning_of_line(
2302        &mut self,
2303        _: &DeleteToBeginningOfLine,
2304        window: &mut Window,
2305        cx: &mut Context<Self>,
2306    ) {
2307        self.delete_selections(
2308            true,
2309            EditIntent::Atomic,
2310            |s, offset| {
2311                let mut start = s.start_of_line_at(offset, s.line_end_affinity_at(offset));
2312                if start == offset {
2313                    start = start.saturating_sub(1);
2314                }
2315                start..offset
2316            },
2317            window,
2318            cx,
2319        );
2320    }
2321
2322    pub(super) fn delete_to_end_of_line(
2323        &mut self,
2324        _: &DeleteToEndOfLine,
2325        window: &mut Window,
2326        cx: &mut Context<Self>,
2327    ) {
2328        self.delete_selections(
2329            true,
2330            EditIntent::Atomic,
2331            |s, offset| {
2332                let mut end = s.end_of_line_at(offset, s.line_end_affinity_at(offset));
2333                if end == offset {
2334                    end = (end + 1).clamp(0, s.text.len());
2335                }
2336                offset..end
2337            },
2338            window,
2339            cx,
2340        );
2341    }
2342
2343    pub(super) fn delete_previous_word(
2344        &mut self,
2345        _: &DeleteToPreviousWordStart,
2346        window: &mut Window,
2347        cx: &mut Context<Self>,
2348    ) {
2349        self.delete_selections(
2350            true,
2351            EditIntent::Atomic,
2352            |s, offset| s.previous_start_of_word_at(offset)..offset,
2353            window,
2354            cx,
2355        );
2356    }
2357
2358    pub(super) fn delete_next_word(
2359        &mut self,
2360        _: &DeleteToNextWordEnd,
2361        window: &mut Window,
2362        cx: &mut Context<Self>,
2363    ) {
2364        self.delete_selections(
2365            true,
2366            EditIntent::Atomic,
2367            |s, offset| offset..s.next_end_of_word_at(offset),
2368            window,
2369            cx,
2370        );
2371    }
2372
2373    pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {
2374        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2375            return;
2376        }
2377
2378        // Clear inline completion on enter (user chose not to accept it)
2379        if M::has_inline_completion(self) {
2380            M::clear_inline_completion(self, cx);
2381        }
2382
2383        // In multi-line mode with `submit_on_enter` enabled, a plain `Enter`
2384        // (without Shift) is treated as submit: propagate the action and emit
2385        // PressEnter without inserting a newline. `Shift+Enter` still inserts
2386        // a newline.
2387        let insert_newline = self.is_multi_line() && (!self.submit_on_enter || action.shift);
2388
2389        if insert_newline {
2390            if !self.selections.is_single() {
2391                // Insert a newline (with per-line indent) at every cursor.
2392                self.selections.merge_overlapping();
2393                let selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
2394                let mut edits: Vec<(Range<usize>, String)> = Vec::with_capacity(selections.len());
2395                for sel in &selections {
2396                    let indent = if self.is_code_editor() {
2397                        self.indent_of_next_line_at(sel.cursor_offset())
2398                    } else {
2399                        String::new()
2400                    };
2401                    edits.push((sel.start..sel.end, format!("\n{}", indent)));
2402                }
2403                edits.sort_by_key(|(range, _)| range.start);
2404                self.replace_text_in_ranges(&edits, window, cx);
2405                self.pause_blink_cursor(cx);
2406            } else {
2407                // Bracket split: Enter between `{|}` produces `{\n  \n}`
2408                // with the cursor on the middle line. Controlled by smart_indent,
2409                // independently of automatic closing.
2410                // Skipped inside strings and comments: splitting there would
2411                // restructure literal text.
2412                let mut split = false;
2413                if self.is_code_editor() && self.active_selection().is_empty() {
2414                    if let Some(rules) = self.mode.language_config() {
2415                        if self.mode.is_smart_indent()
2416                            && M::editing_syntax_context(self, self.cursor())
2417                                == crate::input::SyntaxContext::Code
2418                        {
2419                            let off = self.cursor();
2420                            let is_pair = rules.brackets.iter().any(|p| {
2421                                !p.open.is_empty()
2422                                    && !p.close.is_empty()
2423                                    && p.open != p.close
2424                                    && self.text_before_matches(off, p.open.as_ref())
2425                                    && self.text_after_matches(off, p.close.as_ref())
2426                            });
2427                            if is_pair {
2428                                // Base indent: leading whitespace only.
2429                                let line_end_affinity = self.line_end_affinity_at(off);
2430                                let line_start = self.start_of_line_at(off, line_end_affinity);
2431                                let mut indent = String::new();
2432                                for c in self.text.slice(line_start..).chars() {
2433                                    if !c.is_whitespace() || c == '\n' || c == '\r' {
2434                                        break;
2435                                    }
2436                                    indent.push(c);
2437                                }
2438                                // Extra level only with smart indent enabled.
2439                                let mut inner = indent.clone();
2440                                if self.mode.is_smart_indent() {
2441                                    let tab = self.mode.tab_size();
2442                                    if tab.hard_tabs {
2443                                        inner.push('\t');
2444                                    } else {
2445                                        for _ in 0..tab.tab_size {
2446                                            inner.push(' ');
2447                                        }
2448                                    }
2449                                }
2450                                let new_line_text = format!("\n{inner}\n{indent}");
2451                                self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2452                                self.set_cursor_to(off + 1 + inner.len());
2453                                self.update_preferred_column();
2454                                let cursors = self.selections.iter().copied().collect::<Vec<_>>();
2455                                self.undo_manager
2456                                    .record_selections(cursors.clone(), cursors);
2457                                self.pause_blink_cursor(cx);
2458                                split = true;
2459                            }
2460                        }
2461                    }
2462                }
2463                if !split {
2464                    // Get current line indent
2465                    let indent = if self.is_code_editor() {
2466                        self.indent_of_next_line()
2467                    } else {
2468                        "".to_string()
2469                    };
2470
2471                    // Add newline and indent
2472                    let new_line_text = format!("\n{}", indent);
2473                    self.replace_text_in_range_silent(None, &new_line_text, window, cx);
2474                    self.pause_blink_cursor(cx);
2475                }
2476            }
2477        } else {
2478            // Single line input or submit-on-enter: just emit the event
2479            // (e.g.: in a dialog to confirm, or a chat textarea to send).
2480            self.undo_manager.break_transaction_coalescing();
2481            cx.propagate();
2482        }
2483
2484        cx.emit(InputEvent::PressEnter {
2485            secondary: action.secondary,
2486            shift: action.shift,
2487        });
2488    }
2489
2490    pub fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2491        self.replace_text("", window, cx);
2492        self.set_selection(0, 0);
2493        self.scroll_to(0, None, cx);
2494    }
2495
2496    pub(super) fn escape(&mut self, action: &Escape, window: &mut Window, cx: &mut Context<Self>) {
2497        if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
2498            return;
2499        }
2500
2501        // Escape also dismisses a request whose popup has not arrived yet.
2502        M::hide_context_menu(self, cx);
2503
2504        // Collapse extra cursors back to the active one first.
2505        if !self.selections.is_single() {
2506            self.undo_manager.break_transaction_coalescing();
2507            self.selections.remove_all_but_active();
2508            cx.notify();
2509            return;
2510        }
2511
2512        // Clear inline completion on escape
2513        if M::has_inline_completion(self) {
2514            M::clear_inline_completion(self, cx);
2515            return; // Consume the escape, don't propagate
2516        }
2517        M::clear_inline_completion(self, cx);
2518
2519        // The handles and the edit menu are the topmost surface to dismiss.
2520        if self.touch_selection().is_some() {
2521            self.dismiss_touch_selection(cx);
2522            return;
2523        }
2524
2525        if self.ime_marked_range.is_some() {
2526            self.unmark_text(window, cx);
2527        }
2528
2529        // Android delivers its back button as escape, and back leaves a field's
2530        // text alone: it closes what is open or navigates.
2531        if self.clean_on_escape && !cfg!(target_os = "android") {
2532            return self.clean(window, cx);
2533        }
2534
2535        cx.propagate();
2536    }
2537
2538    /// Show the right-click context menu as a native OS menu.
2539    pub(crate) fn handle_right_click_menu(
2540        &mut self,
2541        position: Point<Pixels>,
2542        offset: usize,
2543        window: &mut Window,
2544        cx: &mut Context<Self>,
2545    ) {
2546        if self.disabled {
2547            return;
2548        }
2549        if crate::GlobalState::is_in_deferred_context(cx) {
2550            return;
2551        }
2552
2553        if !self.active_selection().contains(offset) {
2554            self.move_to(offset, None, cx);
2555        }
2556
2557        if self.is_code_editor() {
2558            M::on_hover_definition(self, offset, window, cx);
2559        }
2560
2561        if let Some(handler) = self.context_menu_handler.clone() {
2562            let capabilities = self.context_menu_capabilities();
2563            let input = cx.weak_entity();
2564            // The handler runs with the input released, so a custom builder can
2565            // read it (cursor, selection, text) to build its items.
2566            window.defer(cx, move |window, cx| {
2567                if input.upgrade().is_none() {
2568                    return;
2569                }
2570                let previous_focus = window.focused(cx);
2571                handler(NativeMenu::new(), capabilities, position, window, cx);
2572                // A drawn menu takes focus synchronously; native menus leave it
2573                // on the input. Capture it here rather than in the handler.
2574                input
2575                    .update(cx, |this, cx| {
2576                        if window.focused(cx) == previous_focus
2577                            && !this.focus_handle.is_focused(window)
2578                            && this.has_selection_focus(window, cx)
2579                        {
2580                            // A custom handler can reuse an already-focused popup.
2581                            return;
2582                        }
2583                        let menu_focus = window.focused(cx).filter(|focus| {
2584                            Some(focus) != previous_focus.as_ref() && *focus != this.focus_handle
2585                        });
2586                        this.set_selection_focus(menu_focus, cx);
2587                    })
2588                    .ok();
2589            });
2590        }
2591    }
2592
2593    pub(super) fn add_cursor_above(
2594        &mut self,
2595        _: &AddCursorAbove,
2596        _: &mut Window,
2597        cx: &mut Context<Self>,
2598    ) {
2599        self.add_cursor_vertical(-1, cx);
2600    }
2601
2602    pub(super) fn add_cursor_below(
2603        &mut self,
2604        _: &AddCursorBelow,
2605        _: &mut Window,
2606        cx: &mut Context<Self>,
2607    ) {
2608        self.add_cursor_vertical(1, cx);
2609    }
2610
2611    /// Add a new cursor one display line above (`move_lines < 0`) or below each
2612    /// existing cursor, preserving the column. Cursors that would not move (at
2613    /// the first/last display row) or that would duplicate an existing cursor
2614    /// are skipped.
2615    fn add_cursor_vertical(&mut self, move_lines: isize, cx: &mut Context<Self>) {
2616        if !self.is_multi_line() {
2617            return;
2618        }
2619
2620        self.pause_blink_cursor(cx);
2621        // Changing the cursor set ends the editing gesture that came before it.
2622        self.undo_manager.break_transaction_coalescing();
2623
2624        let sources: Vec<(usize, Option<(Pixels, usize)>, bool)> = self
2625            .selections
2626            .iter()
2627            .map(|sel| {
2628                (
2629                    sel.cursor_offset(),
2630                    sel.column_anchor,
2631                    self.line_end_affinity_for(sel),
2632                )
2633            })
2634            .collect();
2635        let mut offsets: std::collections::HashSet<usize> =
2636            sources.iter().map(|(offset, _, _)| *offset).collect();
2637
2638        let mut newest: Option<usize> = None;
2639        for (offset, anchor, line_end_affinity) in sources {
2640            let anchor = anchor.or_else(|| self.preferred_column_for(offset));
2641            let (target, _) = self.vertical_target(offset, anchor, line_end_affinity, move_lines);
2642            if target == offset || offsets.contains(&target) {
2643                continue;
2644            }
2645            offsets.insert(target);
2646            let id = self.selections.generate_id();
2647            let mut cursor = CursorSelection::new(id, target, target);
2648            cursor.column_anchor = anchor;
2649            self.selections.add(cursor);
2650            newest = Some(target);
2651        }
2652
2653        if let Some(newest) = newest {
2654            self.scroll_to(newest, None, cx);
2655        }
2656        cx.notify();
2657    }
2658
2659    /// Add an additional collapsed cursor at `offset`.
2660    ///
2661    /// Rejected when `offset` lands inside an existing selection or exactly on
2662    /// an existing cursor.
2663    pub(super) fn add_cursor_at(&mut self, offset: usize, cx: &mut Context<Self>) {
2664        if !self.is_multi_line() {
2665            return;
2666        }
2667
2668        for sel in self.selections.iter() {
2669            if sel.contains(offset) {
2670                return;
2671            }
2672            if sel.is_collapsed() && sel.cursor_offset() == offset {
2673                return;
2674            }
2675        }
2676
2677        self.undo_manager.break_transaction_coalescing();
2678        let id = self.selections.generate_id();
2679        let mut selection = CursorSelection::new(id, offset, offset);
2680        selection.column_anchor = self.preferred_column_for(offset);
2681        self.selections.add(selection);
2682        cx.notify();
2683    }
2684
2685    /// Build a columnar (block) selection spanning the rows between the two
2686    /// points, one selection per display row at the same column span.
2687    ///
2688    /// The span keeps the columns the pointer sat past the end of a short row, so a row
2689    /// that runs out of text does not narrow the block for the rows that do reach that
2690    /// far. Each row is then clipped to its own end, selecting as much of the span as it
2691    /// actually has.
2692    pub(super) fn build_columnar_selection(
2693        &mut self,
2694        start: ColumnarPoint,
2695        end: ColumnarPoint,
2696        cx: &mut Context<Self>,
2697    ) {
2698        if !self.is_multi_line() {
2699            return;
2700        }
2701
2702        self.undo_manager.break_transaction_coalescing();
2703
2704        let (start_row, start_col) = self.columnar_row_column(start);
2705        let (end_row, end_col) = self.columnar_row_column(end);
2706        let (start_row, end_row) = (start_row.min(end_row), start_row.max(end_row));
2707        let (start_col, end_col) = (start_col.min(end_col), start_col.max(end_col));
2708
2709        let mut new_selections = Vec::with_capacity(end_row - start_row + 1);
2710        for row in start_row..=end_row {
2711            let sel_start = self
2712                .display_map
2713                .display_row_column_to_offset(row, start_col);
2714            let sel_end = self.display_map.display_row_column_to_offset(row, end_col);
2715            let id = self.selections.generate_id();
2716            let sel_start = self.text.clip_offset(sel_start, Bias::Left);
2717            let sel_end = self.text.clip_offset(sel_end, Bias::Left);
2718            new_selections.push(CursorSelection::new(id, sel_start, sel_end));
2719        }
2720
2721        self.selections.replace_all(new_selections);
2722        cx.notify();
2723    }
2724
2725    /// Resolve a columnar point to the display row it is on and the column it reaches,
2726    /// counting the columns beyond the row's end so a short row keeps the full span.
2727    fn columnar_row_column(&self, point: ColumnarPoint) -> (usize, usize) {
2728        let display_point = self.display_map.offset_to_wrap_display_point(point.offset);
2729        let row = self
2730            .display_map
2731            .wrap_row_to_display_row(display_point.row)
2732            .unwrap_or_else(|| {
2733                self.display_map
2734                    .nearest_visible_display_row(display_point.row)
2735            });
2736
2737        (row, display_point.column + point.columns_past_line_end)
2738    }
2739
2740    pub(super) fn on_mouse_down(
2741        &mut self,
2742        event: &MouseDownEvent,
2743        window: &mut Window,
2744        cx: &mut Context<Self>,
2745    ) {
2746        // A token that consumed the press (a double click selecting it) must
2747        // not also place the caret.
2748        if window.default_prevented() {
2749            return;
2750        }
2751        // A tap asks for the virtual keyboard. Focus alone does not when it
2752        // moves from another input, or stays here, after the user hid it.
2753        if event.button == MouseButton::Left
2754            && crate::GlobalState::is_touch_press(cx)
2755            && !self.disabled
2756            && !self.readonly
2757        {
2758            window.request_virtual_keyboard();
2759        }
2760        self.undo_manager.break_transaction_coalescing();
2761        // Treat every press like a cursor move: take focus now rather than
2762        // after GPUI's focus observer runs, show the caret at once and restart
2763        // the idle delay, whatever phase the blink was in.
2764        if !self.disabled {
2765            self.focus(window, cx);
2766            self.pause_blink_cursor(cx);
2767        }
2768        // Input has its own text selection; suppress the window-level text
2769        // selection (Root) so it does not start a drag from here.
2770        crate::global_state::GlobalState::suppress_text_selection(cx);
2771
2772        // Clear inline completion on any mouse interaction
2773        M::clear_inline_completion(self, cx);
2774        // A tap on the touch selection asks for its menu back. Any other press
2775        // places the caret or starts a new drag, and the handles and the menu
2776        // no longer belong to what is selected.
2777        if event.button == MouseButton::Left
2778            && event.click_count == 1
2779            && self.reopen_edit_menu_at(event.position, cx)
2780        {
2781            return;
2782        }
2783        self.dismiss_touch_selection(cx);
2784
2785        // If there have IME marked range and is empty (Means pressed Esc to abort IME typing)
2786        // Clear the marked range.
2787        if let Some(ime_marked_range) = &self.ime_marked_range {
2788            if ime_marked_range.len() == 0 {
2789                self.ime_marked_range = None;
2790            }
2791        }
2792
2793        self.selecting = true;
2794        let (offset, line_end_affinity, columns_past_line_end) =
2795            self.resolve_mouse_position(event.position);
2796
2797        if M::on_click(self, event, offset, window, cx) {
2798            return;
2799        }
2800
2801        // Triple click to select line
2802        if event.button == MouseButton::Left && event.click_count >= 3 {
2803            self.select_line(offset, window, cx);
2804            return;
2805        }
2806
2807        // Double click to select word
2808        if event.button == MouseButton::Left && event.click_count == 2 {
2809            self.select_word(offset, window, cx);
2810            // A double tap is touch's other way to select a word, and it
2811            // gets the handles and the menu like a long press does.
2812            if crate::GlobalState::is_touch_press(cx) {
2813                self.keep_touch_selection(cx);
2814            }
2815            return;
2816        }
2817
2818        // Show Mouse context menu
2819        if event.button == MouseButton::Right {
2820            if self.enable_context_menu {
2821                if !self.active_selection().contains(offset) {
2822                    self.move_to(offset, None, cx);
2823                }
2824                self.pending_context_menu = Some((event.position, offset));
2825            }
2826            return;
2827        }
2828
2829        // Multi-cursor placement, multi-line only.
2830        if self.is_multi_line() && event.button == MouseButton::Left {
2831            if event.modifiers.alt
2832                && (event.modifiers.shift || (cfg!(target_os = "linux") && event.modifiers.control))
2833            {
2834                // Alt+Shift starts a block; Linux also accepts Ghostty's Ctrl+Alt.
2835                // Mark selecting so the drag handler extends the block.
2836                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2837                self.selecting = true;
2838                self.move_to_with_affinity(offset, None, line_end_affinity, cx);
2839                return;
2840            } else if event.modifiers.alt {
2841                self.add_cursor_at(offset, cx);
2842                // Keep click-to-add behavior, but use this press as the block
2843                // anchor if the user continues dragging with the left button.
2844                self.column_select_start = Some(ColumnarPoint::new(offset, columns_past_line_end));
2845                return;
2846            }
2847        }
2848
2849        if event.modifiers.shift {
2850            self.select_to_with_affinity(offset, line_end_affinity, cx);
2851        } else {
2852            self.move_to_with_affinity(offset, None, line_end_affinity, cx)
2853        }
2854    }
2855
2856    pub(super) fn on_mouse_up(
2857        &mut self,
2858        event: &MouseUpEvent,
2859        window: &mut Window,
2860        cx: &mut Context<Self>,
2861    ) {
2862        if event.button == MouseButton::Right {
2863            if let Some((position, offset)) = self.pending_context_menu.take() {
2864                self.handle_right_click_menu(position, offset, window, cx);
2865            }
2866        }
2867        if self.active_selection().is_empty() {
2868            self.active_selection_mut().reversed = false;
2869        }
2870        self.selecting = false;
2871        self.selected_word_range = None;
2872        self.column_select_start = None;
2873        self.auto_scroll.stop();
2874    }
2875
2876    pub(super) fn on_mouse_move(
2877        &mut self,
2878        event: &MouseMoveEvent,
2879        window: &mut Window,
2880        cx: &mut Context<Self>,
2881    ) {
2882        // `last_bounds` is moved by the scroll offset; `input_bounds` is the visible area.
2883        let within_bounds = self.input_bounds.contains(&event.position);
2884
2885        if !within_bounds {
2886            // Clear hover when mouse leaves the input
2887            M::clear_hover_state(self, cx);
2888            return;
2889        }
2890
2891        // Show diagnostic popover on mouse move
2892        let (offset, _) = self.index_for_mouse_position(event.position);
2893        M::on_mouse_move(self, offset, event, window, cx);
2894
2895        if self.is_code_editor() {
2896            // Keep the current popover while the pointer stays on the same
2897            // diagnostic, so moving over a squiggle neither repaints nor
2898            // rebuilds the popover.
2899            match self
2900                .mode
2901                .diagnostics()
2902                .and_then(|set| set.for_offset(offset))
2903            {
2904                Some(diagnostic) => {
2905                    if self.diagnostic_popover.as_deref() != Some(diagnostic) {
2906                        self.diagnostic_popover = Some(Rc::new(diagnostic.clone()));
2907                        cx.notify();
2908                    }
2909                }
2910                None => {
2911                    if self.diagnostic_popover.take().is_some() {
2912                        cx.notify();
2913                    }
2914                }
2915            }
2916        }
2917    }
2918
2919    pub(super) fn on_scroll_wheel(
2920        &mut self,
2921        event: &ScrollEvent,
2922        window: &mut Window,
2923        cx: &mut Context<Self>,
2924    ) {
2925        let line_height = self
2926            .last_layout
2927            .as_ref()
2928            .map(|layout| layout.line_height)
2929            .unwrap_or(window.line_height());
2930        let delta = event.delta.pixel_delta(line_height);
2931
2932        let old_offset = self.scroll_handle.offset();
2933        let precise = matches!(event.delta, gpui::ScrollDelta::Pixels(_));
2934        if !precise || !self.scroll_bounce || cx.reduce_motion() || !self.overflows_vertically() {
2935            let displaced = self.overscroll.offset() != 0.;
2936            self.overscroll.reset();
2937            if displaced {
2938                cx.notify();
2939            }
2940        }
2941        let bounces = self.scroll_bounce
2942            && precise
2943            && !cx.reduce_motion()
2944            && delta.y.abs() >= delta.x.abs()
2945            && self.overflows_vertically();
2946        if bounces {
2947            let mut overscroll = std::mem::take(&mut self.overscroll);
2948            let stretched = overscroll.packet(
2949                delta.y.as_f32(),
2950                event.touch_phase,
2951                self.input_bounds.size.height.as_f32(),
2952                cx.background_executor().now(),
2953                |dy| {
2954                    let before = self.scroll_handle.offset();
2955                    self.update_scroll_offset(
2956                        Some(point(before.x + delta.x, before.y + px(dy))),
2957                        cx,
2958                    );
2959                    (self.scroll_handle.offset().y - before.y).as_f32()
2960                },
2961            );
2962            self.overscroll = overscroll;
2963            if stretched {
2964                cx.notify();
2965            }
2966        } else {
2967            self.overscroll
2968                .touch_phase(event.touch_phase, cx.background_executor().now());
2969            // Axis drift must not drop the end of a vertical drag.
2970            if matches!(
2971                event.touch_phase,
2972                gpui::TouchPhase::Ended | gpui::TouchPhase::Cancelled
2973            ) && self.overscroll.offset() != 0.
2974            {
2975                self.overscroll.packet(
2976                    0.,
2977                    event.touch_phase,
2978                    self.input_bounds.size.height.as_f32(),
2979                    cx.background_executor().now(),
2980                    |_| 0.,
2981                );
2982                cx.notify();
2983            }
2984            self.update_scroll_offset(Some(old_offset + delta), cx);
2985        }
2986
2987        // Text that has room to scroll keeps a scroll over it even where the
2988        // text ends, so reaching the end does not scroll the region around the
2989        // input; text that fits leaves the scroll to that region.
2990        if self.scroll_handle.offset() != old_offset || self.overflows_along(delta) {
2991            cx.stop_propagation();
2992        }
2993
2994        if self.diagnostic_popover.take().is_some() {
2995            cx.notify();
2996        }
2997        // The handles follow the text; the menu would sit over whatever
2998        // scrolls underneath it, so it steps aside until the finger lifts.
2999        self.edit_menu_on_scroll(event.touch_phase, cx);
3000    }
3001
3002    /// Whether the text takes a wheel event moving by `delta` from the
3003    /// scroll areas around it: it has room to scroll along the axis the
3004    /// event mostly moves on. Unlike [`Self::overflows_along`], an event that
3005    /// moves on neither axis, such as a gesture's start or end, goes to text
3006    /// that can scroll vertically.
3007    pub(super) fn takes_scroll(&self, delta: Point<Pixels>) -> bool {
3008        if delta.y.abs() >= delta.x.abs() {
3009            self.overflows_vertically()
3010        } else {
3011            self.scroll_size.width > self.input_bounds.size.width
3012        }
3013    }
3014
3015    /// Whether the text has room to scroll along the axis `delta` mostly
3016    /// moves on. A trackpad swipe drifts a little off its axis, so the drift
3017    /// alone does not claim the scroll.
3018    fn overflows_along(&self, delta: Point<Pixels>) -> bool {
3019        if delta.y.abs() >= delta.x.abs() {
3020            delta.y != px(0.) && self.overflows_vertically()
3021        } else {
3022            self.scroll_size.width > self.input_bounds.size.width
3023        }
3024    }
3025
3026    /// Whether multi-line text is taller than the input.
3027    fn overflows_vertically(&self) -> bool {
3028        self.is_multi_line() && self.scroll_size.height > self.input_bounds.size.height
3029    }
3030
3031    pub(super) fn update_scroll_offset(
3032        &mut self,
3033        offset: Option<Point<Pixels>>,
3034        cx: &mut Context<Self>,
3035    ) {
3036        let mut offset = offset.unwrap_or(self.scroll_handle.offset());
3037        let safe_y_range =
3038            (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
3039
3040        offset.y = if self.is_single_line() {
3041            px(0.)
3042        } else {
3043            offset.y.clamp(safe_y_range.start, safe_y_range.end)
3044        };
3045        offset.x = clamp_horizontal_scroll_offset(
3046            offset.x,
3047            self.scroll_size.width,
3048            self.input_bounds.size.width,
3049            self.text_align,
3050        );
3051        if self.scroll_handle.offset() != offset {
3052            self.scroll_handle.set_offset(offset);
3053            cx.notify();
3054        }
3055    }
3056
3057    /// Scroll to make the given offset visible.
3058    ///
3059    /// If `direction` is Some, will keep edges at the same side.
3060    pub(crate) fn scroll_to(
3061        &mut self,
3062        offset: usize,
3063        direction: Option<MoveDirection>,
3064        cx: &mut Context<Self>,
3065    ) {
3066        let padding = if direction.is_some() {
3067            ScrollPadding::SurroundingLines
3068        } else {
3069            ScrollPadding::Minimal
3070        };
3071        self.scroll_to_with_padding(offset, direction, padding, cx);
3072    }
3073
3074    /// Reveal an offset with independently chosen direction restriction and padding.
3075    /// Search uses surrounding lines without restricting movement to match order.
3076    pub(crate) fn scroll_to_with_padding(
3077        &mut self,
3078        offset: usize,
3079        direction: Option<MoveDirection>,
3080        padding: ScrollPadding,
3081        cx: &mut Context<Self>,
3082    ) {
3083        let Some(last_layout) = self.last_layout.as_ref() else {
3084            return;
3085        };
3086        let Some(bounds) = self.last_bounds.as_ref() else {
3087            return;
3088        };
3089
3090        let mut scroll_offset = self.scroll_handle.offset();
3091        let was_offset = scroll_offset;
3092        let line_height = last_layout.line_height;
3093
3094        let point = self.text.offset_to_point(offset);
3095
3096        let row = point.row;
3097
3098        // Resolve the wrapped row even when the target is outside the last layout.
3099        let display_row = self
3100            .display_map
3101            .offset_to_display_row(offset, self.line_end_affinity_at(offset));
3102        let row_offset_y = line_height * display_row;
3103
3104        // For Right alignment use 0 margin: the cursor indicator is clamped inside bounds
3105        // in layout_cursors, so shifting the text here would cause a first-click visual jump.
3106        let safety_margin = match last_layout.text_align {
3107            TextAlign::Left => RIGHT_MARGIN,
3108            TextAlign::Right => px(0.),
3109            TextAlign::Center => CURSOR_WIDTH,
3110        };
3111        if let Some(vi) = last_layout
3112            .visible_buffer_lines
3113            .iter()
3114            .position(|&line| line == row)
3115        {
3116            let line = &last_layout.lines[vi];
3117            let local_offset = offset.saturating_sub(last_layout.visible_line_byte_offsets[vi]);
3118            if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
3119                let bounds_width = bounds.size.width - last_layout.line_number_width;
3120                let col_offset_x = pos.x;
3121                if col_offset_x - safety_margin < -scroll_offset.x {
3122                    // If the position is out of the visible area, scroll to make it visible
3123                    scroll_offset.x = -col_offset_x + safety_margin;
3124                } else if col_offset_x + safety_margin > -scroll_offset.x + bounds_width {
3125                    scroll_offset.x = -(col_offset_x - bounds_width + safety_margin);
3126                }
3127            }
3128        }
3129
3130        // Scroll the row into view. Use the same edge clearance helper as
3131        // `TextElement::layout_cursors` so both scroll-into-view paths agree
3132        // (a mismatch flickered on `Down` at end-of-buffer with a small
3133        // `cursor_surrounding_lines` override).
3134        let edge_height =
3135            if matches!(padding, ScrollPadding::SurroundingLines) && self.is_code_editor() {
3136                super::element::cursor_surrounding_padding(
3137                    self.mode.is_auto_grow(),
3138                    self.cursor_surrounding_lines,
3139                    super::element::viewport_visible_lines(bounds.size.height, line_height),
3140                    line_height,
3141                )
3142            } else {
3143                px(0.)
3144            };
3145        scroll_offset.y = super::element::reveal_row(
3146            scroll_offset.y,
3147            row_offset_y,
3148            line_height,
3149            bounds.size.height,
3150            edge_height,
3151        );
3152
3153        // Avoid necessary scroll, when it was already in the correct position.
3154        if direction == Some(MoveDirection::Up) {
3155            scroll_offset.y = scroll_offset.y.max(was_offset.y);
3156        } else if direction == Some(MoveDirection::Down) {
3157            scroll_offset.y = scroll_offset.y.min(was_offset.y);
3158        }
3159
3160        // Clamp the deferred target into the same safe range that
3161        // `update_scroll_offset` enforces on persist, so paint never shows an
3162        // over-scrolled frame before the post-paint clamp pulls it back.
3163        let safe_y_min = (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.));
3164        scroll_offset.x = scroll_offset.x.min(px(0.));
3165        scroll_offset.y = scroll_offset.y.clamp(safe_y_min, px(0.));
3166        self.deferred_scroll_offset = Some(scroll_offset);
3167        cx.notify();
3168    }
3169
3170    pub(super) fn show_character_palette(
3171        &mut self,
3172        _: &ShowCharacterPalette,
3173        window: &mut Window,
3174        _: &mut Context<Self>,
3175    ) {
3176        window.show_character_palette();
3177    }
3178
3179    /// The text of every non-empty selection, in document order.
3180    fn selected_texts(&self) -> Vec<String> {
3181        let mut selections: Vec<CursorSelection> = self
3182            .selections
3183            .iter()
3184            .copied()
3185            .filter(|sel| !sel.is_empty())
3186            .collect();
3187        selections.sort_by_key(|sel| sel.start);
3188        selections
3189            .iter()
3190            .map(|sel| self.text.slice(*sel).to_string())
3191            .collect()
3192    }
3193
3194    pub(super) fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
3195        if !self.is_copyable() {
3196            return;
3197        }
3198
3199        let texts = self.selected_texts();
3200
3201        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
3202    }
3203
3204    pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
3205        if !self.is_copyable() {
3206            return;
3207        }
3208
3209        let texts = self.selected_texts();
3210
3211        cx.write_to_clipboard(ClipboardItem::new_string(texts.join("\n")));
3212
3213        self.undo_manager.set_pending_intent(EditIntent::Atomic);
3214        self.replace_text_in_range_silent(None, "", window, cx);
3215    }
3216
3217    pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
3218        if !self.is_editable() {
3219            return;
3220        }
3221        if let Some(clipboard) = cx.read_from_clipboard() {
3222            self.insert_clipboard(clipboard, window, cx);
3223            return;
3224        }
3225        // The synchronous read is empty on platforms whose clipboard is
3226        // asynchronous and permission-gated (the web), so fall back to the
3227        // real read. It has to start here, still inside the user activation
3228        // that dispatched `Paste`, or the browser refuses the read.
3229        let read = cx.read_from_clipboard_async();
3230        let target = self.paste_target();
3231        cx.spawn_in(window, async move |this, cx| match read.await {
3232            Ok(Some(clipboard)) => {
3233                this.update_in(cx, |this, window, cx| {
3234                    // The read can sit behind a permission prompt for as
3235                    // long as the user likes. If they edited, moved the
3236                    // caret or left the input meanwhile, the paste would
3237                    // land where they no longer mean it to; drop it, as the
3238                    // browser's own paste event only reaches the focused
3239                    // input too.
3240                    if this.is_editable()
3241                        && this.focus_handle.is_focused(window)
3242                        && this.paste_target() == target
3243                    {
3244                        this.insert_clipboard(clipboard, window, cx);
3245                    }
3246                })
3247                .ok();
3248            }
3249            Ok(None) => {}
3250            Err(error) => tracing::warn!("failed to read the clipboard for paste: {error}"),
3251        })
3252        .detach();
3253    }
3254
3255    /// Where a paste would go right now: the document as edited so far and
3256    /// the selections it would replace. Two equal targets mean an edit made
3257    /// for one still applies to the other.
3258    fn paste_target(&self) -> PasteTarget {
3259        PasteTarget {
3260            document_revision: self.document_revision,
3261            selections: self.selections.iter().copied().collect(),
3262        }
3263    }
3264
3265    /// Inserts the clipboard text the way `Paste` does: one atomic edit,
3266    /// newlines dropped on a single-line input, one line per selection when
3267    /// the counts match on a multi-line one. A clipboard without text (an
3268    /// image, say) is left alone rather than replacing the selection with
3269    /// nothing.
3270    fn insert_clipboard(
3271        &mut self,
3272        clipboard: ClipboardItem,
3273        window: &mut Window,
3274        cx: &mut Context<Self>,
3275    ) {
3276        let Some(mut new_text) = clipboard.text().filter(|text| !text.is_empty()) else {
3277            return;
3278        };
3279        // A paste is one atomic edit, never part of a typing run.
3280        self.undo_manager.set_pending_intent(EditIntent::Atomic);
3281
3282        if !self.is_multi_line() {
3283            new_text = new_text.replace('\n', "");
3284            self.replace_text_in_range_silent(None, &new_text, window, cx);
3285            self.scroll_to(self.cursor(), None, cx);
3286            return;
3287        }
3288
3289        // Distribute one clipboard line per selection when the counts match.
3290        // Otherwise insert the whole clipboard text at each cursor.
3291        if !self.selections.is_single() {
3292            self.selections.merge_overlapping();
3293        }
3294        let lines: Vec<String> = new_text.split('\n').map(|s| s.to_string()).collect();
3295        let count = self.selections.len();
3296        if count > 1 && lines.len() == count {
3297            let mut selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
3298            selections.sort_by_key(|sel| sel.start);
3299            let edits: Vec<(Range<usize>, String)> = selections
3300                .iter()
3301                .zip(lines)
3302                .map(|(sel, line)| (sel.start..sel.end, line))
3303                .collect();
3304            self.replace_text_in_ranges(&edits, window, cx);
3305        } else {
3306            self.replace_text_in_range_silent(None, &new_text, window, cx);
3307        }
3308        self.scroll_to(self.cursor(), None, cx);
3309    }
3310
3311    /// The intent of a batch the caller did not label: inserting text at
3312    /// collapsed cursors is typing, anything else stands on its own.
3313    fn typing_intent(&self, edits: &[(Range<usize>, String)], new_text: &str) -> EditIntent {
3314        if !new_text.is_empty()
3315            && !new_text.contains(['\n', '\r'])
3316            && edits.iter().all(|(range, _)| range.is_empty())
3317        {
3318            EditIntent::Typing
3319        } else {
3320            EditIntent::Atomic
3321        }
3322    }
3323
3324    /// Where a cursor stood before an edit made with this intent.
3325    ///
3326    /// Backspace and forward delete expand a collapsed cursor over the text
3327    /// they are about to remove, so the recorded cursor has to collapse back to
3328    /// the side it came from for undo to restore it where the user left it.
3329    fn collapse_for_intent(
3330        intent: EditIntent,
3331        mut selection: CursorSelection,
3332        range: &Range<usize>,
3333    ) -> CursorSelection {
3334        match intent {
3335            EditIntent::Backspace => selection.place_at(range.end, None),
3336            EditIntent::DeleteForward => selection.place_at(range.start, None),
3337            EditIntent::Typing | EditIntent::Atomic => {}
3338        }
3339        selection
3340    }
3341
3342    /// Whether an edit rewrites the character before a collapsed cursor, as the
3343    /// macOS Korean IME does on each keystroke instead of marking text.
3344    fn rewrites_typed_char(
3345        &self,
3346        selection: CursorSelection,
3347        range: &Range<usize>,
3348        old_text: &str,
3349        new_text: &str,
3350    ) -> bool {
3351        !self.silent_replace_text
3352            && selection.is_collapsed()
3353            && selection.cursor_offset() == range.end
3354            && old_text.chars().count() == 1
3355            && !old_text.contains(['\n', '\r'])
3356            && !new_text.contains(['\n', '\r'])
3357    }
3358
3359    fn push_history(
3360        &mut self,
3361        text: &Rope,
3362        range: &Range<usize>,
3363        new_text: &str,
3364        requested_intent: Option<EditIntent>,
3365        selection_before: CursorSelection,
3366        selection_after: Option<CursorSelection>,
3367    ) -> bool {
3368        self.document_revision = self.document_revision.wrapping_add(1);
3369        let token_delta = self.edit_tokens(range, new_text.len());
3370        if self.undo_manager.is_ignoring() {
3371            return false;
3372        }
3373
3374        let range =
3375            text.clip_offset(range.start, Bias::Left)..text.clip_offset(range.end, Bias::Right);
3376        let old_text = text.slice(range.clone()).to_string();
3377        let new_range = range.start..range.start + new_text.len();
3378
3379        let intent = requested_intent.unwrap_or_else(|| {
3380            if range.is_empty()
3381                && old_text.is_empty()
3382                && !new_text.is_empty()
3383                && !new_text.contains(['\n', '\r'])
3384                || self.rewrites_typed_char(selection_before, &range, &old_text, new_text)
3385            {
3386                EditIntent::Typing
3387            } else {
3388                EditIntent::Atomic
3389            }
3390        });
3391
3392        let selection_before = Self::collapse_for_intent(intent, selection_before, &range);
3393        let selection_after =
3394            selection_after.unwrap_or_else(|| (new_range.end..new_range.end).into());
3395
3396        let open_transaction = self.undo_manager.has_open_transaction();
3397        let mut change = Change::new(range, &old_text, new_range, new_text);
3398        change.token_delta = token_delta;
3399        let recorded = self.undo_manager.record_transaction(change, intent);
3400        // A batch records its own cursor sets. This covers a change that is a
3401        // transaction on its own.
3402        if recorded && !open_transaction {
3403            self.undo_manager
3404                .record_selections(vec![selection_before], vec![selection_after]);
3405        }
3406        recorded
3407    }
3408
3409    pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
3410        self.replay_history(true, window, cx);
3411    }
3412
3413    pub(super) fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
3414        self.replay_history(false, window, cx);
3415    }
3416
3417    fn replay_history(&mut self, undo: bool, window: &mut Window, cx: &mut Context<Self>) {
3418        if !self.is_editable() {
3419            return;
3420        }
3421        let replay = if undo {
3422            self.undo_manager.undo()
3423        } else {
3424            self.undo_manager.redo()
3425        };
3426        let Some(replay) = replay else {
3427            return;
3428        };
3429        let token_aware = self.inline_tokens.is_some()
3430            || replay
3431                .changes
3432                .iter()
3433                .any(|change| change.token_delta.is_some());
3434        let emit_events = self.emit_events;
3435        if token_aware {
3436            self.emit_events = false;
3437            self.last_input_edits.clear();
3438        }
3439        self.replaying_history = token_aware;
3440        self.undo_manager.set_ignoring(true);
3441        for change in &replay.changes {
3442            let (range, text) = if undo {
3443                (change.new_range.into(), &change.old_text)
3444            } else {
3445                (change.old_range.into(), &change.new_text)
3446            };
3447            let range_utf16 = self.range_to_utf16(&range);
3448            self.replace_text_in_range_silent(Some(range_utf16), text, window, cx);
3449            self.replay_tokens(&range, text.len(), change.token_delta.as_deref(), undo);
3450        }
3451        self.restore_selections(replay.selections);
3452        self.mode
3453            .restore_auto_closed_pairs(replay.auto_closed_pairs.unwrap_or_default());
3454        if token_aware {
3455            // Token replay bypasses IME normalization; a stale marked range
3456            // would otherwise describe text that no longer exists.
3457            self.ime_marked_range = None;
3458        }
3459        self.undo_manager.set_ignoring(false);
3460        self.replaying_history = false;
3461        self.emit_events = emit_events;
3462        if token_aware {
3463            if emit_events {
3464                self.emit_change(cx);
3465            } else {
3466                self.last_input_edits.clear();
3467            }
3468        }
3469        cx.notify();
3470    }
3471
3472    /// Restore a set of selections captured in a transaction, clamping offsets
3473    /// to the current text length. `None` leaves the current selections as the
3474    /// replay left them.
3475    fn restore_selections(&mut self, selections: Option<Vec<CursorSelection>>) {
3476        let Some(selections) = selections else {
3477            return;
3478        };
3479
3480        let len = self.text.len();
3481        let restored: Vec<CursorSelection> = selections
3482            .into_iter()
3483            .map(|mut sel| {
3484                sel.start = sel.start.min(len);
3485                sel.end = sel.end.min(len);
3486                sel
3487            })
3488            .collect();
3489        self.selections.replace_all(restored);
3490        self.selections.merge_overlapping();
3491    }
3492
3493    /// Get byte offset of the cursor.
3494    ///
3495    /// The offset is the UTF-8 offset.
3496    pub fn cursor(&self) -> usize {
3497        if let Some(ime_marked_range) = &self.ime_marked_range {
3498            return ime_marked_range.end;
3499        }
3500
3501        self.selections.active().cursor_offset()
3502    }
3503
3504    /// Returns the active selection.
3505    pub(super) fn active_selection(&self) -> &CursorSelection {
3506        self.selections.active()
3507    }
3508
3509    /// Returns a mutable reference to the active selection.
3510    pub(super) fn active_selection_mut(&mut self) -> &mut CursorSelection {
3511        self.selections.active_mut()
3512    }
3513
3514    /// Sets the active selection to the given range, keeping its `reversed`
3515    /// and `column_anchor` state untouched.
3516    pub(super) fn set_selection(&mut self, start: usize, end: usize) {
3517        let range = self.normalize_token_range(start..end);
3518        let (start, end) = (range.start, range.end);
3519        let active = self.active_selection_mut();
3520        active.start = start;
3521        active.end = end;
3522    }
3523
3524    /// Collapses the active selection to a cursor at the given offset,
3525    /// clearing `reversed`.
3526    pub(super) fn set_cursor_to(&mut self, offset: usize) {
3527        let offset = self.token_boundary(offset, Bias::Right);
3528        let active = self.active_selection_mut();
3529        active.start = offset;
3530        active.end = offset;
3531        active.reversed = false;
3532    }
3533
3534    /// Visible row range in the last laid-out viewport, `None` before first layout.
3535    pub fn visible_row_range(&self) -> Option<std::ops::Range<usize>> {
3536        self.last_layout.as_ref().map(|l| l.visible_range.clone())
3537    }
3538
3539    /// Current scroll offset of the editor viewport.
3540    pub fn scroll_offset(&self) -> gpui::Point<gpui::Pixels> {
3541        self.scroll_handle.offset()
3542    }
3543
3544    /// Set scroll offset of the editor viewport.
3545    ///
3546    /// The offset will be clamped to the valid range, and applied after the next layout.
3547    pub fn set_scroll_offset(&mut self, offset: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
3548        self.deferred_scroll_offset = Some(offset);
3549        cx.notify();
3550    }
3551
3552    /// Laid-out line height; `None` before first layout.
3553    pub fn line_height(&self) -> Option<gpui::Pixels> {
3554        self.last_layout.as_ref().map(|l| l.line_height)
3555    }
3556
3557    /// Returns the active selection as a byte range into the text.
3558    ///
3559    /// With multiple cursors, this reads only the active selection.
3560    ///
3561    /// The range is empty (`start == end`) when no text is selected; in
3562    /// that case the offset equals `cursor()`. Byte offsets are measured
3563    /// in the underlying rope's byte units.
3564    pub fn selected_range(&self) -> std::ops::Range<usize> {
3565        (*self.selections.active()).into()
3566    }
3567
3568    /// Whether the active selection's caret is at the start of its range.
3569    ///
3570    /// Collapsed selections are not reversed. Use this with [`Self::selected_range`]
3571    /// and [`Self::set_selected_range_with_direction`] to restore a selection.
3572    pub fn is_selection_reversed(&self) -> bool {
3573        let selection = self.selections.active();
3574        !selection.is_empty() && selection.reversed
3575    }
3576
3577    pub fn select_all(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3578        self.undo_manager.break_transaction_coalescing();
3579        self.selections.remove_all_but_active();
3580        self.set_selection(0, self.text.len());
3581        cx.notify();
3582    }
3583
3584    /// Set the selected range using UTF-8 byte offsets, removing additional cursors.
3585    ///
3586    /// Non-empty ranges expand to character boundaries. Empty ranges remain empty and are
3587    /// clipped to the preceding character boundary.
3588    pub fn set_selected_range(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3589        self.set_selected_range_with_direction(range, false, cx);
3590    }
3591
3592    /// Set the selected UTF-8 byte range and the active caret direction.
3593    ///
3594    /// With `reversed`, the caret is at the start and the anchor is at the end.
3595    /// Empty ranges collapse to a caret regardless of direction. Range clipping,
3596    /// token boundaries and removal of additional cursors follow
3597    /// [`Self::set_selected_range`].
3598    pub fn set_selected_range_with_direction(
3599        &mut self,
3600        range: Range<usize>,
3601        reversed: bool,
3602        cx: &mut Context<Self>,
3603    ) {
3604        let range = self.normalize_token_range(range);
3605        let end_bias = if range.start == range.end {
3606            Bias::Left
3607        } else {
3608            Bias::Right
3609        };
3610        let start = self.text.clip_offset(range.start, Bias::Left);
3611        let end = self.text.clip_offset(range.end, end_bias);
3612
3613        let (anchor, head) = if reversed { (end, start) } else { (start, end) };
3614        self.move_to(anchor, None, cx);
3615        self.active_selection_mut().reversed = false;
3616        self.selected_word_range = None;
3617        self.select_to(head, cx);
3618    }
3619
3620    /// Add `range` as an additional selection, keeping the existing ones —
3621    /// the programmatic equivalent of Alt-clicking another cursor into the
3622    /// text.
3623    ///
3624    /// The range is clipped to the text and expanded to character
3625    /// boundaries, like [`Self::set_selected_range`]. A range already
3626    /// covered by a selection adds nothing. Overlapping selections are merged.
3627    /// On a single-line input there is no second cursor to add, so the call
3628    /// replaces the selection instead.
3629    /// The view scrolls to the new selection.
3630    pub fn add_selection(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
3631        if !self.is_multi_line() {
3632            self.set_selected_range(range, cx);
3633            return;
3634        }
3635
3636        let range = self.normalize_token_range(range);
3637        let end_bias = if range.start == range.end {
3638            Bias::Left
3639        } else {
3640            Bias::Right
3641        };
3642        let start = self.cursor_boundary(range.start, Bias::Left);
3643        // Match set_selected_range: expand UTF-8 boundaries first, then place
3644        // the selection endpoint without splitting a CRLF newline.
3645        let end = self.cursor_boundary(self.text.clip_offset(range.end, end_bias), Bias::Left);
3646
3647        for sel in self.selections.iter() {
3648            if sel.start <= start && end <= sel.end {
3649                return;
3650            }
3651        }
3652
3653        self.undo_manager.break_transaction_coalescing();
3654        self.selected_word_range = None;
3655        self.pause_blink_cursor(cx);
3656        M::hide_context_menu(self, cx);
3657        M::clear_inline_completion(self, cx);
3658        let id = self.selections.generate_id();
3659        let mut selection = CursorSelection::new(id, start, end);
3660        selection.column_anchor = self.preferred_column_for(end);
3661        self.selections.add(selection);
3662        self.selections.merge_overlapping();
3663        self.scroll_to(end, None, cx);
3664        cx.notify();
3665    }
3666
3667    /// Resolve a mouse position to a byte offset in the text.
3668    ///
3669    /// Also reports the caret's line-end affinity for that offset: `true` when the position
3670    /// landed on the wrap boundary of a non-final visual row, meaning the caret belongs at the
3671    /// end of that row rather than at the start of the next one. Callers that place or extend a
3672    /// selection must pass it on, or clicking past the last glyph of a wrapped row leaves a
3673    /// caret one row below the pointer.
3674    pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> (usize, bool) {
3675        let (offset, line_end_affinity, _) = self.resolve_mouse_position(position);
3676        (offset, line_end_affinity)
3677    }
3678
3679    /// Resolve a mouse position to a text offset, the caret affinity to use for it, and
3680    /// how many columns the pointer sits past the end of that row.
3681    ///
3682    /// Only a columnar selection needs the third value; everywhere else a position past
3683    /// the end of a row means the end of that row, and
3684    /// [`Self::index_for_mouse_position`] is the call to make.
3685    pub(super) fn resolve_mouse_position(&self, position: Point<Pixels>) -> (usize, bool, usize) {
3686        // If the text is empty, always return 0
3687        if self.text.len() == 0 {
3688            return (0, false, 0);
3689        }
3690
3691        let (Some(bounds), Some(last_layout)) =
3692            (self.last_bounds.as_ref(), self.last_layout.as_ref())
3693        else {
3694            return (0, false, 0);
3695        };
3696
3697        let line_height = last_layout.line_height;
3698        let line_number_width = last_layout.line_number_width;
3699
3700        // TIP: About the IBeam cursor
3701        //
3702        // If cursor style is IBeam, the mouse mouse position is in the middle of the cursor (This is special in OS)
3703
3704        // The position is relative to the bounds of the text input
3705        //
3706        // bounds.origin:
3707        //
3708        // - included the input padding.
3709        // - included the scroll offset.
3710        let inner_position = position - bounds.origin - point(line_number_width, px(0.));
3711
3712        let mut y_offset = last_layout.visible_top;
3713        // Position relative to the last line walked, kept for a pointer that ends up
3714        // below every line.
3715        let mut last_line_pos = None;
3716
3717        // Traverse visible buffer lines (compact, no hidden entries)
3718        for (vi, (line_layout, _buffer_line)) in last_layout
3719            .lines
3720            .iter()
3721            .zip(last_layout.visible_buffer_lines.iter())
3722            .enumerate()
3723        {
3724            let line_start_offset = last_layout.visible_line_byte_offsets[vi];
3725
3726            // Calculate line origin for this display row
3727            let line_origin = point(px(0.), y_offset);
3728            let pos = inner_position - line_origin;
3729
3730            // Return offset by use closest_index_for_x if is single line mode.
3731            if self.is_single_line() {
3732                let local_index = line_layout.closest_index_for_x(pos.x, last_layout);
3733                // A single line never wraps, so there is no boundary to disambiguate.
3734                return (
3735                    self.resolve_index(line_start_offset + local_index),
3736                    false,
3737                    0,
3738                );
3739            }
3740
3741            // Check if mouse is in this line's bounds
3742            if let Some((local_index, line_end_affinity)) =
3743                line_layout.closest_index_for_position(pos, last_layout)
3744            {
3745                return (
3746                    self.resolve_index(line_start_offset + local_index),
3747                    line_end_affinity,
3748                    line_layout.columns_past_line_end(pos, last_layout),
3749                );
3750            } else if pos.y < px(0.) {
3751                // Mouse is above this line, return start of this line
3752                return (self.resolve_index(line_start_offset), false, 0);
3753            }
3754
3755            y_offset += line_layout.size(line_height).height;
3756            last_line_pos = Some(pos);
3757        }
3758
3759        // Clamp to the last laid-out row. Returning the end of the whole document
3760        // would select unseen text before drag autoscroll has reached it.
3761        let last_position = last_layout
3762            .lines
3763            .last()
3764            .zip(last_line_pos)
3765            .zip(last_layout.visible_line_byte_offsets.last())
3766            .map(|((line_layout, pos), line_start)| {
3767                let last_row_top = (line_layout.size(line_height).height - line_height).max(px(0.));
3768                let pos = point(pos.x, last_row_top);
3769                (
3770                    self.resolve_index(line_start + line_layout.len()),
3771                    false,
3772                    line_layout.columns_past_line_end(pos, last_layout),
3773                )
3774            });
3775
3776        last_position.unwrap_or((self.text.len(), false, 0))
3777    }
3778
3779    /// Map a display byte index back to a text offset, undoing the mask expansion when the input
3780    /// is masked.
3781    fn resolve_index(&self, index: usize) -> usize {
3782        if self.masked {
3783            self.text.char_index_to_offset(index / MASK_CHAR.len_utf8())
3784        } else {
3785            index.min(self.text.len())
3786        }
3787    }
3788
3789    /// Returns a y offsetted point for the line origin.
3790    /// Select the text from the current cursor position to the given offset.
3791    ///
3792    /// The offset is the UTF-8 offset.
3793    ///
3794    /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
3795    /// Extend a single selection so its moving end lands at `offset`, flipping
3796    /// `reversed` when the ends cross. When a sticky `word_range` is given the
3797    /// selection is kept covering it.
3798    fn extend_selection(
3799        sel: &mut CursorSelection,
3800        offset: usize,
3801        word_range: Option<CursorSelection>,
3802    ) {
3803        if sel.reversed {
3804            sel.start = offset;
3805        } else {
3806            sel.end = offset;
3807        }
3808
3809        if sel.end < sel.start {
3810            sel.reversed = !sel.reversed;
3811            std::mem::swap(&mut sel.start, &mut sel.end);
3812        }
3813
3814        if let Some(word_range) = word_range {
3815            if sel.start > word_range.start {
3816                sel.start = word_range.start;
3817            }
3818            if sel.end < word_range.end {
3819                sel.end = word_range.end;
3820            }
3821        }
3822    }
3823
3824    /// Extend only the active selection to `offset`. Used by mouse drag.
3825    pub(crate) fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
3826        self.select_to_with_affinity(offset, false, cx);
3827    }
3828
3829    /// Like [`Self::select_to`], but also carries the caret's line-end affinity.
3830    ///
3831    /// See [`Self::move_to_with_affinity`] for why the affinity travels with the offset. Note
3832    /// that plain [`Self::select_to`] clears the affinity: every offset it is given came from
3833    /// the text rather than from a visual position, so the caret has no reason to keep sticking
3834    /// to the end of a wrapped row.
3835    pub(crate) fn select_to_with_affinity(
3836        &mut self,
3837        offset: usize,
3838        line_end_affinity: bool,
3839        cx: &mut Context<Self>,
3840    ) {
3841        M::clear_inline_completion(self, cx);
3842
3843        self.cursor_line_end_affinity = line_end_affinity;
3844        let offset = self.cursor_boundary(offset, Bias::Left);
3845        let word_range = self.selected_word_range;
3846        Self::extend_selection(self.active_selection_mut(), offset, word_range);
3847        let range = self.normalize_token_range(self.selected_range());
3848        self.set_selection(range.start, range.end);
3849
3850        if self.active_selection().is_empty() {
3851            self.update_preferred_column();
3852        }
3853        cx.notify()
3854    }
3855
3856    /// Extend every selection to the offset produced by `f`, then merge any
3857    /// selections that now overlap. Used by keyboard selection commands.
3858    fn select_all_cursors_to(
3859        &mut self,
3860        f: impl Fn(&Self, &CursorSelection) -> usize,
3861        cx: &mut Context<Self>,
3862    ) {
3863        self.select_all_cursors_to_with_affinity(|s, sel| (f(s, sel), false), false, cx);
3864    }
3865
3866    /// Extend selections with caret affinity, preserving their column anchors even
3867    /// when vertical movement collapses a selection to a cursor.
3868    fn select_all_cursors_to_with_affinity(
3869        &mut self,
3870        f: impl Fn(&Self, &CursorSelection) -> (usize, bool),
3871        preserve_column: bool,
3872        cx: &mut Context<Self>,
3873    ) {
3874        self.pause_blink_cursor(cx);
3875        self.undo_manager.break_transaction_coalescing();
3876        M::clear_inline_completion(self, cx);
3877
3878        let mut active_affinity = false;
3879        let new_selections: Vec<CursorSelection> = self
3880            .selections
3881            .iter()
3882            .map(|sel| {
3883                let (offset, affinity) = f(self, sel);
3884                if sel.id == self.active_selection().id {
3885                    active_affinity = affinity;
3886                }
3887                let offset = self.cursor_boundary(offset, Bias::Left);
3888                let mut new_sel = *sel;
3889                Self::extend_selection(&mut new_sel, offset, None);
3890                let range = self.normalize_token_range(new_sel.start..new_sel.end);
3891                new_sel.start = range.start;
3892                new_sel.end = range.end;
3893                if !preserve_column {
3894                    new_sel.column_anchor =
3895                        self.preferred_column_for_with_affinity(new_sel.cursor_offset(), affinity);
3896                } else if new_sel.column_anchor.is_none() {
3897                    new_sel.column_anchor = self.preferred_column_for_with_affinity(
3898                        sel.cursor_offset(),
3899                        self.line_end_affinity_for(sel),
3900                    );
3901                }
3902                new_sel
3903            })
3904            .collect();
3905        self.cursor_line_end_affinity = active_affinity;
3906        self.selections.replace_all(new_selections);
3907        self.selections.merge_overlapping();
3908
3909        self.scroll_to(self.cursor(), None, cx);
3910        cx.notify()
3911    }
3912
3913    /// Unselects the currently selected text.
3914    pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
3915        self.undo_manager.break_transaction_coalescing();
3916        let offset = self.cursor();
3917        self.set_cursor_to(offset);
3918        cx.notify()
3919    }
3920
3921    #[inline]
3922    pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {
3923        self.text.offset_utf16_to_offset(offset)
3924    }
3925
3926    #[inline]
3927    pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {
3928        self.text.offset_to_offset_utf16(offset)
3929    }
3930
3931    #[inline]
3932    pub(crate) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
3933        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
3934    }
3935
3936    #[inline]
3937    pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
3938        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
3939    }
3940
3941    /// If offset falls on a hidden (folded) line, clamp backward to the end of
3942    /// the fold header line (last visible position before the fold).
3943    fn clamp_offset_to_visible_backward(&self, offset: usize) -> usize {
3944        let line = self.text.offset_to_point(offset).row;
3945        if self.display_map.is_buffer_line_hidden(line) {
3946            for fold in self.display_map.folded_ranges() {
3947                if line > fold.start_line && line <= fold.end_line {
3948                    return self.text.line_end_offset(fold.start_line);
3949                }
3950            }
3951        }
3952        offset
3953    }
3954
3955    /// If offset falls on a hidden (folded) line, clamp forward to the start of
3956    /// the fold end line (first visible position after the fold).
3957    fn clamp_offset_to_visible_forward(&self, offset: usize) -> usize {
3958        let line = self.text.offset_to_point(offset).row;
3959        if self.display_map.is_buffer_line_hidden(line) {
3960            for fold in self.display_map.folded_ranges() {
3961                if line > fold.start_line && line <= fold.end_line {
3962                    return self.text.line_start_offset(fold.end_line);
3963                }
3964            }
3965        }
3966        offset
3967    }
3968
3969    /// Clip a cursor/selection offset without splitting a CRLF newline. This does
3970    /// not alter the rope or its byte/UTF-16 conversion used for exact source APIs.
3971    pub(super) fn cursor_boundary(&self, offset: usize, bias: Bias) -> usize {
3972        let offset = self.token_boundary(self.text.clip_offset(offset, bias), bias);
3973        if offset > 0
3974            && self.text.char_at(offset - 1) == Some('\r')
3975            && self.text.char_at(offset) == Some('\n')
3976        {
3977            if bias == Bias::Left {
3978                offset - 1
3979            } else {
3980                offset + 1
3981            }
3982        } else {
3983            offset
3984        }
3985    }
3986
3987    pub(super) fn previous_boundary(&self, offset: usize) -> usize {
3988        let offset = self.cursor_boundary(offset.saturating_sub(1), Bias::Left);
3989        self.clamp_offset_to_visible_backward(offset)
3990    }
3991
3992    pub(super) fn next_boundary(&self, offset: usize) -> usize {
3993        let offset = self.cursor_boundary(offset.saturating_add(1), Bias::Right);
3994        self.clamp_offset_to_visible_forward(offset)
3995    }
3996
3997    /// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
3998    pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
3999        (self.focus_handle.is_focused(window) || M::is_context_menu_open(self, cx))
4000            && !self.disabled
4001            && self.blink_cursor.read(cx).visible()
4002            && window.is_window_active()
4003    }
4004
4005    fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {
4006        self.blink_cursor.update(cx, |cursor, cx| {
4007            cursor.start(cx);
4008        });
4009        cx.emit(InputEvent::Focus);
4010    }
4011
4012    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4013        self.revealed_viewport = None;
4014        if M::is_context_menu_open(self, cx) {
4015            return;
4016        }
4017
4018        M::hide_context_menu(self, cx);
4019        self.undo_manager.break_transaction_coalescing();
4020
4021        // NOTE: Do not cancel select, when blur.
4022        // Because maybe user want to copy the selected text by AppMenuBar (will take focus handle).
4023
4024        M::clear_hover_state(self, cx);
4025        self.diagnostic_popover = None;
4026        M::clear_inline_completion(self, cx);
4027        self.dismiss_touch_selection(cx);
4028        self.blink_cursor.update(cx, |cursor, cx| {
4029            cursor.stop(cx);
4030        });
4031        self.clamp_number_value(window, cx);
4032        cx.emit(InputEvent::Blur);
4033        cx.notify();
4034    }
4035
4036    /// Clamp the number value to the `min`/`max` range, used on blur.
4037    ///
4038    /// Out-of-range values are allowed while typing (e.g. `1` is an
4039    /// intermediate state of `15` when min is 10), and clamped on blur.
4040    fn clamp_number_value(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4041        if !self.is_single_line() {
4042            return;
4043        }
4044        if !matches!(self.mask_pattern, MaskPattern::Number { .. }) {
4045            return;
4046        }
4047        if self.number_min.is_none() && self.number_max.is_none() {
4048            return;
4049        }
4050
4051        let Ok(value) = self.unmask_value().parse::<f64>() else {
4052            return;
4053        };
4054
4055        let clamped = match (self.number_min, self.number_max) {
4056            (Some(min), _) if value < min => min,
4057            (_, Some(max)) if value > max => max,
4058            _ => return,
4059        };
4060
4061        // The clamped value must pass the `pattern`/`validate` check,
4062        // otherwise keep the value as is.
4063        let new_text = clamped.to_string();
4064        if !self.is_valid_input(&new_text, cx) {
4065            return;
4066        }
4067
4068        let range = self.range_to_utf16(&(0..self.text.len()));
4069        self.replace_text_in_range_silent(Some(range), &new_text, window, cx);
4070    }
4071
4072    pub(super) fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
4073        self.blink_cursor.update(cx, |cursor, cx| {
4074            cursor.pause(cx);
4075        });
4076    }
4077
4078    pub(super) fn on_drag_move(
4079        &mut self,
4080        event: &MouseMoveEvent,
4081        window: &mut Window,
4082        cx: &mut Context<Self>,
4083    ) {
4084        if self.text.len() == 0 {
4085            return;
4086        }
4087
4088        if self.last_layout.is_none() {
4089            return;
4090        }
4091
4092        if !self.focus_handle.is_focused(window) {
4093            return;
4094        }
4095
4096        if !self.selecting {
4097            return;
4098        }
4099
4100        self.auto_scroll.last_drag_position = Some(event.position);
4101        let (offset, line_end_affinity, columns_past_line_end) =
4102            self.resolve_mouse_position(event.position);
4103        if let Some(start) = self.column_select_start {
4104            let end = ColumnarPoint::new(offset, columns_past_line_end);
4105            self.build_columnar_selection(start, end, cx);
4106        } else {
4107            self.select_to_with_affinity(offset, line_end_affinity, cx);
4108        }
4109
4110        if !self.is_single_line() {
4111            let delta = AutoScroll::compute_delta(event.position.y, self.input_bounds);
4112            // Input's ScrollHandle uses negative-y-is-down; negate the positive-towards-bottom delta.
4113            let scroll_delta = delta.map(|d| -d);
4114            self.auto_scroll.set(scroll_delta, cx, |delta, state, cx| {
4115                let current = state.scroll_handle.offset();
4116                state.update_scroll_offset(Some(point(current.x, current.y + delta)), cx);
4117                if let Some(pos) = state.auto_scroll.last_drag_position {
4118                    let (offset, line_end_affinity, columns_past_line_end) =
4119                        state.resolve_mouse_position(pos);
4120                    if let Some(start) = state.column_select_start {
4121                        let end = ColumnarPoint::new(offset, columns_past_line_end);
4122                        state.build_columnar_selection(start, end, cx);
4123                    } else {
4124                        state.select_to_with_affinity(offset, line_end_affinity, cx);
4125                    }
4126                }
4127            });
4128        }
4129    }
4130
4131    /// Normalize the inserted text before applying it to the input.
4132    ///
4133    /// For number inputs (with [`MaskPattern::Number`]), this converts
4134    /// full-width number characters into their ASCII equivalents,
4135    /// e.g. `12。5` -> `12.5`.
4136    pub(super) fn normalize_input<'a>(&self, new_text: &'a str) -> Cow<'a, str> {
4137        let normalized = if matches!(self.mask_pattern, MaskPattern::Number { .. }) {
4138            normalize_number_input(new_text)
4139        } else {
4140            Cow::Borrowed(new_text)
4141        };
4142
4143        if self.is_single_line() && normalized.contains(['\n', '\r']) {
4144            Cow::Owned(normalized.replace(['\n', '\r'], ""))
4145        } else {
4146            normalized
4147        }
4148    }
4149
4150    pub(crate) fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {
4151        if new_text.is_empty() {
4152            return true;
4153        }
4154
4155        if let Some(validate) = &self.validate {
4156            if !validate(new_text, cx) {
4157                return false;
4158            }
4159        }
4160
4161        if !self.mask_pattern.is_valid(new_text) {
4162            return false;
4163        }
4164
4165        let Some(pattern) = &self.pattern else {
4166            return true;
4167        };
4168
4169        pattern.is_match(new_text)
4170    }
4171
4172    /// Set the mask pattern for formatting the input text.
4173    ///
4174    /// The pattern can contain:
4175    /// - 9: Any digit or dot
4176    /// - A: Any letter
4177    /// - *: Any character
4178    /// - Other characters will be treated as literal mask characters
4179    ///
4180    /// Example: "(999)999-999" for phone numbers
4181    pub fn mask_pattern(mut self, pattern: impl Into<MaskPattern>) -> Self {
4182        self.mask_pattern = pattern.into();
4183        self.mask_pattern_set = true;
4184        if let Some(placeholder) = self.mask_pattern.placeholder() {
4185            self.placeholder = placeholder.into();
4186        }
4187        self
4188    }
4189
4190    pub fn set_mask_pattern(
4191        &mut self,
4192        pattern: impl Into<MaskPattern>,
4193        _: &mut Window,
4194        cx: &mut Context<Self>,
4195    ) {
4196        self.mask_pattern = pattern.into();
4197        self.mask_pattern_set = true;
4198        if let Some(placeholder) = self.mask_pattern.placeholder() {
4199            self.placeholder = placeholder.into();
4200        }
4201        cx.notify();
4202    }
4203
4204    /// Apply the default numeric mask unless the caller explicitly selected a mask.
4205    pub fn ensure_number_mask(&mut self) {
4206        if self.mask_pattern_set {
4207            return;
4208        }
4209        self.mask_pattern = MaskPattern::Number {
4210            separator: None,
4211            fraction: None,
4212        };
4213    }
4214
4215    pub(super) fn set_input_bounds(&mut self, new_bounds: Bounds<Pixels>, cx: &mut Context<Self>) {
4216        let wrap_width_changed = self.input_bounds.size.width != new_bounds.size.width;
4217        self.input_bounds = new_bounds;
4218
4219        // Update display_map wrap_width if changed.
4220        if let Some(last_layout) = self.last_layout.as_ref() {
4221            if wrap_width_changed {
4222                let wrap_width = if !self.soft_wrap {
4223                    // None to disable wrapping (will use Pixels::MAX)
4224                    None
4225                } else {
4226                    last_layout.wrap_width
4227                };
4228
4229                self.display_map.on_layout_changed(wrap_width, cx);
4230                if self.is_multi_line() {
4231                    self.mode.update_auto_grow(&self.display_map);
4232                }
4233                cx.notify();
4234            }
4235        }
4236    }
4237
4238    /// Return the active selection's text, borrowed out of the [`Rope`]
4239    /// the state owns.
4240    ///
4241    /// See [`Self::selected_value`] when an owned string is wanted.
4242    pub fn selected_text(&self) -> RopeSlice<'_> {
4243        let range_utf16 = self.range_to_utf16(&self.selected_range());
4244        let range = self.range_from_utf16(&range_utf16);
4245        self.text.slice(range)
4246    }
4247
4248    /// Return the rendered bounds for a UTF-8 byte range in the current input contents.
4249    ///
4250    /// Returns `None` when the requested range is not currently laid out or visible.
4251    pub fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
4252        let Some(last_layout) = self.last_layout.as_ref() else {
4253            return None;
4254        };
4255
4256        let Some(last_bounds) = self.last_bounds else {
4257            return None;
4258        };
4259
4260        let (_, _, start_pos) = self.line_and_position_for_offset(range.start);
4261        let (_, _, end_pos) = self.line_and_position_for_offset(range.end);
4262
4263        let Some(start_pos) = start_pos else {
4264            return None;
4265        };
4266        let Some(end_pos) = end_pos else {
4267            return None;
4268        };
4269
4270        Some(Bounds::from_corners(
4271            last_bounds.origin + start_pos,
4272            last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),
4273        ))
4274    }
4275
4276    /// Replace text in range in silent.
4277    ///
4278    /// This will not trigger any UI interaction, such as auto-completion.
4279    pub(crate) fn replace_text_in_range_silent(
4280        &mut self,
4281        range_utf16: Option<Range<usize>>,
4282        new_text: &str,
4283        window: &mut Window,
4284        cx: &mut Context<Self>,
4285    ) {
4286        self.silent_replace_text = true;
4287        self.replace_text_in_range(range_utf16, new_text, window, cx);
4288        self.silent_replace_text = false;
4289    }
4290
4291    /// Apply a batch of edits as one atomic history transaction.
4292    ///
4293    /// `edits` are `(byte range in the current pre-edit document, replacement)`
4294    /// pairs.
4295    pub(crate) fn replace_text_in_ranges(
4296        &mut self,
4297        edits: &[(Range<usize>, String)],
4298        window: &mut Window,
4299        cx: &mut Context<Self>,
4300    ) {
4301        if !self.is_editable() || edits.is_empty() {
4302            return;
4303        }
4304
4305        // Every edit invalidates provider responses for the previous document,
4306        // including deletion and indentation which do not trigger completion.
4307        M::hide_context_menu(self, cx);
4308        M::clear_inline_completion(self, cx);
4309
4310        // Sort descending by start so applying front-of-vec first edits the
4311        // highest offsets first, leaving lower offsets unchanged.
4312        let mut sorted: Vec<(Range<usize>, &str)> = edits
4313            .iter()
4314            .map(|(range, text)| (self.normalize_token_range(range.clone()), text.as_str()))
4315            .collect();
4316        sorted.sort_by_key(|edit| std::cmp::Reverse(edit.0.start));
4317        if !self.token_spans().is_empty() {
4318            let mut merged: Vec<(Range<usize>, &str)> = Vec::with_capacity(sorted.len());
4319            for (range, text) in sorted {
4320                if let Some(last) = merged
4321                    .last_mut()
4322                    .filter(|last| range.end > last.0.start || range == last.0)
4323                {
4324                    last.0.start = range.start.min(last.0.start);
4325                    last.0.end = range.end.max(last.0.end);
4326                } else {
4327                    merged.push((range, text));
4328                }
4329            }
4330            sorted = merged;
4331        }
4332
4333        #[cfg(debug_assertions)]
4334        for pair in sorted.windows(2) {
4335            debug_assert!(
4336                pair[1].0.end <= pair[0].0.start,
4337                "replace_text_in_ranges requires disjoint ranges"
4338            );
4339        }
4340
4341        // Wrap multiple edits in one explicit transaction so they undo as a
4342        // unit. A single edit records directly, which keeps it eligible for
4343        // the undo manager's typing coalescing.
4344        let requested_intent = self.undo_manager.take_pending_intent();
4345        let selection_before = *self.active_selection();
4346        let original_selections: Vec<CursorSelection> = self.selections.iter().copied().collect();
4347        // Snapshot the cursors before applying, so undo can restore them. A
4348        // delete has already expanded them over the text it removes, so they
4349        // collapse back to where the user left them.
4350        let selections_before: Vec<CursorSelection> = self
4351            .selections
4352            .iter()
4353            .map(|selection| {
4354                Self::collapse_for_intent(
4355                    requested_intent.unwrap_or(EditIntent::Atomic),
4356                    *selection,
4357                    &(selection.start..selection.end),
4358                )
4359            })
4360            .collect();
4361        let group = sorted.len() > 1;
4362        if group {
4363            self.undo_manager.begin_transaction();
4364        }
4365
4366        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
4367        let mut recorded = false;
4368        // Several edits reach the highlighter as one batch, so it reparses
4369        // once for the change instead of once per edit.
4370        let mut highlighter_edits = Vec::new();
4371        // Token-aware history replay applies several replacements but emits one
4372        // `Change` at the end, so its edits accumulate instead of being
4373        // discarded with each silent replacement.
4374        let accumulate_replay_edits = self.replaying_history && !self.emit_events;
4375        if !accumulate_replay_edits {
4376            self.last_input_edits.clear();
4377        }
4378        for (range, new_text) in &sorted {
4379            let old_text = self.text.clone();
4380            self.mode.adjust_auto_closed_pair(range, new_text.len());
4381            self.text.replace(range.clone(), new_text);
4382
4383            M::adjust_annotations(self, range, new_text.len());
4384            recorded |= self.push_history(
4385                &old_text,
4386                range,
4387                new_text,
4388                requested_intent,
4389                selection_before,
4390                None,
4391            );
4392
4393            // Incremental, single-range updates must run per edit.
4394            self.display_map
4395                .adjust_folds_for_edit(&old_text, range, new_text);
4396            self.display_map
4397                .on_text_changed(&self.text, range, &Rope::from(*new_text), cx);
4398
4399            let edit = super::mode::replacement_input_edit(&old_text, &self.text, range, new_text);
4400            if self.emit_events || self.replaying_history {
4401                self.last_input_edits.push((edit, self.text.clone()));
4402            }
4403
4404            if group {
4405                if self.is_code_editor() {
4406                    highlighter_edits.push((edit, self.text.clone()));
4407                }
4408            } else {
4409                self.mode.update_highlighter(
4410                    super::mode::HighlighterUpdate {
4411                        selected_range: range,
4412                        old_text: &old_text,
4413                        new_text: &self.text,
4414                        change_text: new_text,
4415                        force: true,
4416                        input_edit: Some(edit),
4417                    },
4418                    window,
4419                    cx,
4420                );
4421
4422                self.update_fold_candidates_incremental(range, new_text);
4423            }
4424        }
4425
4426        if group {
4427            self.mode
4428                .update_highlighter_batch::<M>(&highlighter_edits, window, cx);
4429
4430            // Fold candidates are read from the reparsed document, so each
4431            // edit is located where it ended up once every edit was applied.
4432            // `sorted` runs from the highest offset down.
4433            let mut shift = 0isize;
4434            for (range, new_text) in sorted.iter().rev() {
4435                let start = (range.start as isize + shift) as usize;
4436                self.update_fold_candidates_incremental(&(start..start), new_text);
4437                shift += new_text.len() as isize - range.len() as isize;
4438            }
4439
4440            self.undo_manager.commit_transaction();
4441        }
4442
4443        // One observable update per batch instead of one per edit.
4444        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4445            diagnostics.reset(&self.text)
4446        }
4447        M::refresh_language_features(self, window, cx);
4448        self.update_search(cx);
4449
4450        // Compute the resulting cursors.
4451        // One collapsed cursor per edit, at the end of its inserted text.
4452        let mut ascending: Vec<(Range<usize>, &str)> = sorted.clone();
4453        ascending.sort_by_key(|edit| edit.0.start);
4454        let text_len = self.text.len();
4455        let mut delta: isize = 0;
4456        let mut edit_results = Vec::with_capacity(ascending.len());
4457        for (range, new_text) in &ascending {
4458            let offset = ((range.start as isize + delta) as usize + new_text.len()).min(text_len);
4459            edit_results.push((range.clone(), offset));
4460            delta += new_text.len() as isize - (range.end as isize - range.start as isize);
4461        }
4462
4463        let mut used = vec![false; edit_results.len()];
4464        let mut new_selections: Vec<CursorSelection> = Vec::with_capacity(ascending.len());
4465        for selection in original_selections {
4466            if let Ok(index) = edit_results
4467                .binary_search_by_key(&(selection.start, selection.end), |(range, _)| {
4468                    (range.start, range.end)
4469                })
4470                && !used[index]
4471            {
4472                let offset = edit_results[index].1;
4473                used[index] = true;
4474                let mut selection = selection;
4475                selection.place_at(offset, None);
4476                new_selections.push(selection);
4477            }
4478        }
4479        for (index, (_, offset)) in edit_results.into_iter().enumerate() {
4480            if !used[index] {
4481                new_selections.push(CursorSelection::new(
4482                    self.selections.generate_id(),
4483                    offset,
4484                    offset,
4485                ));
4486            }
4487        }
4488        self.selections.replace_all(new_selections);
4489        self.selections.merge_overlapping();
4490
4491        // Record the cursor snapshots for undo/redo restore.
4492        let selections_after: Vec<CursorSelection> = self.selections.iter().copied().collect();
4493        if recorded {
4494            self.undo_manager.record_auto_closed_pairs(
4495                auto_closed_pairs_before,
4496                self.mode.auto_closed_pairs().clone(),
4497            );
4498            self.undo_manager
4499                .record_selections(selections_before, selections_after);
4500        }
4501
4502        self.ime_marked_range.take();
4503        self.update_preferred_column();
4504        self.dismiss_touch_selection(cx);
4505        if self.is_multi_line() {
4506            self.mode.update_auto_grow(&self.display_map);
4507        }
4508        if self.emit_events {
4509            self.emit_change(cx);
4510        }
4511        cx.notify();
4512    }
4513
4514    /// Update fold candidates from tree-sitter syntax tree (full extraction).
4515    /// Used only on initial load or language changes.
4516    fn update_fold_candidates(&mut self) {
4517        if !self.mode.is_folding() {
4518            return;
4519        }
4520
4521        let Some(highlighter_rc) = self.mode.highlighter() else {
4522            return;
4523        };
4524
4525        let highlighter = highlighter_rc.borrow();
4526        let Some(highlighter) = highlighter.as_ref() else {
4527            return;
4528        };
4529
4530        let fold_ranges = highlighter.fold_ranges(&self.text);
4531        self.display_map.set_fold_candidates(fold_ranges);
4532    }
4533
4534    /// Incrementally update fold candidates after a text edit.
4535    /// Only traverses the edited region of the syntax tree instead of the full tree.
4536    fn update_fold_candidates_incremental(&mut self, edit_range: &Range<usize>, new_text: &str) {
4537        if !self.mode.is_folding() {
4538            return;
4539        }
4540
4541        let Some(highlighter_rc) = self.mode.highlighter() else {
4542            return;
4543        };
4544
4545        let highlighter = highlighter_rc.borrow();
4546        let Some(highlighter) = highlighter.as_ref() else {
4547            return;
4548        };
4549
4550        // The new byte range in the updated text after the edit
4551        let new_end = edit_range.start + new_text.len();
4552        self.display_map.update_fold_candidates_for_edit(
4553            |range, text| highlighter.fold_ranges_for_edit(range, text),
4554            edit_range.start..new_end,
4555            &self.text,
4556        );
4557    }
4558}
4559
4560impl<M: InputModeKind> EntityInputHandler for InputBaseState<M> {
4561    fn text_for_range(
4562        &mut self,
4563        range_utf16: Range<usize>,
4564        adjusted_range: &mut Option<Range<usize>>,
4565        _window: &mut Window,
4566        _cx: &mut Context<Self>,
4567    ) -> Option<String> {
4568        let range = self.range_from_utf16(&range_utf16);
4569        adjusted_range.replace(self.range_to_utf16(&range));
4570        Some(self.text.slice(range).to_string())
4571    }
4572
4573    fn selected_text_range(
4574        &mut self,
4575        _ignore_disabled_input: bool,
4576        _window: &mut Window,
4577        _cx: &mut Context<Self>,
4578    ) -> Option<UTF16Selection> {
4579        Some(UTF16Selection {
4580            range: self.range_to_utf16(&self.selected_range()),
4581            reversed: self.active_selection().reversed,
4582        })
4583    }
4584
4585    /// Select the given range, as the platform's own text UI asks when it
4586    /// drives the selection: a native selection handle, or the macOS IME.
4587    ///
4588    /// The end that moved becomes the cursor, so dragging the start handle
4589    /// leaves a reversed selection and keeps the moving end in view. Text and
4590    /// undo history are left alone.
4591    fn set_selected_text_range(
4592        &mut self,
4593        range_utf16: Range<usize>,
4594        _window: &mut Window,
4595        cx: &mut Context<Self>,
4596    ) {
4597        let previous = self.selected_range();
4598        let was_reversed = self.active_selection().reversed;
4599        let range = self.range_from_utf16(&range_utf16);
4600        let range = range.start.min(range.end)..range.start.max(range.end);
4601        self.set_selected_range(range, cx);
4602
4603        let range = self.selected_range();
4604        let reversed = if range == previous {
4605            was_reversed
4606        } else if !range.is_empty() && range.end == previous.start {
4607            true
4608        } else if !range.is_empty() && range.start == previous.end {
4609            false
4610        } else {
4611            !range.is_empty() && range.start != previous.start && range.end == previous.end
4612        };
4613        self.active_selection_mut().reversed = reversed;
4614        self.scroll_to(self.cursor(), None, cx);
4615        cx.notify();
4616    }
4617
4618    fn marked_text_range(
4619        &self,
4620        _window: &mut Window,
4621        _cx: &mut Context<Self>,
4622    ) -> Option<Range<usize>> {
4623        self.ime_marked_range
4624            .map(|range| self.range_to_utf16(&range.into()))
4625    }
4626
4627    fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
4628        self.ime_marked_range = None;
4629        self.undo_manager.commit_transaction();
4630    }
4631
4632    /// Replace text in range.
4633    ///
4634    /// - If the new text is invalid, it will not be replaced.
4635    /// - If `range_utf16` is not provided, the current selected range will be used.
4636    fn replace_text_in_range(
4637        &mut self,
4638        range_utf16: Option<Range<usize>>,
4639        new_text: &str,
4640        window: &mut Window,
4641        cx: &mut Context<Self>,
4642    ) {
4643        let requested_intent = self.undo_manager.take_pending_intent();
4644        if !self.is_editable() {
4645            return;
4646        }
4647        let selection_before = *self.active_selection();
4648        // Committing a composition ends the transaction it opened, whether or
4649        // not the platform follows up with `unmark_text`.
4650        let ends_composition = self.ime_marked_range.is_some();
4651
4652        self.pause_blink_cursor(cx);
4653
4654        // NOTE: The normalization keeps the UTF-16 length, but may change the
4655        // UTF-8 byte length, so all the byte-offset calculations below must
4656        // use the normalized text.
4657        let new_text = if self.replaying_history {
4658            Cow::Borrowed(new_text)
4659        } else {
4660            self.normalize_input(new_text)
4661        };
4662        let new_text: &str = &new_text;
4663
4664        let range = range_utf16
4665            .as_ref()
4666            .map(|range_utf16| self.range_from_utf16(range_utf16))
4667            .or(self.ime_marked_range.map(|range| {
4668                let range = self.range_to_utf16(&(range.start..range.end));
4669                self.range_from_utf16(&range)
4670            }))
4671            .unwrap_or(self.selected_range());
4672        let range = self.normalize_token_range(range);
4673
4674        // Skip-over as a pure cursor move: a typed closer that already follows
4675        // the cursor moves past it without touching text or history, so Undo
4676        // never exposes a transient duplicate. Only for interactive single
4677        // keystrokes (current selection, no IME mark, not silent replays).
4678        // `take_pending_intent` above already consumed any request.
4679        if range.is_empty()
4680            && range.start == self.cursor()
4681            && self.ime_marked_range.is_none()
4682            && !self.silent_replace_text
4683            && self.is_code_editor()
4684            && self.selections.is_single()
4685            && self.active_selection().is_empty()
4686        {
4687            if let Some(target) = self.skip_over_target(new_text) {
4688                self.set_cursor_to(target);
4689                self.update_preferred_column();
4690                cx.notify();
4691                return;
4692            }
4693        }
4694
4695        if self.is_multi_line() {
4696            let multi_cursor = range_utf16.is_none()
4697                && self.ime_marked_range.is_none()
4698                && !self.selections.is_single();
4699            if multi_cursor {
4700                self.selections.merge_overlapping();
4701                let mut edits: Vec<(Range<usize>, String)> = self
4702                    .selections
4703                    .iter()
4704                    .map(|sel| (sel.start..sel.end, new_text.to_string()))
4705                    .collect();
4706                edits.sort_by_key(|(range, _)| range.start);
4707                // One keystroke across several cursors is one batch, committed
4708                // with the intent of the keystroke so a run of them coalesces
4709                // into one undo just like single-cursor typing.
4710                let intent =
4711                    requested_intent.unwrap_or_else(|| self.typing_intent(&edits, new_text));
4712                self.undo_manager.begin_transaction_with(intent);
4713                self.undo_manager.set_pending_intent(intent);
4714                self.replace_text_in_ranges(&edits, window, cx);
4715                self.undo_manager.commit_transaction();
4716            } else {
4717                if range_utf16.is_some() {
4718                    self.selections.remove_all_but_active();
4719                }
4720                if let Some(intent) = requested_intent {
4721                    self.undo_manager.set_pending_intent(intent);
4722                }
4723                if let Some((open_len, closer)) = self.auto_close_target(&range, new_text) {
4724                    // One edit keeps the pair atomic even at undo coalescing limits.
4725                    let replacement = format!("{new_text}{closer}");
4726                    self.replace_text_in_ranges(&[(range.clone(), replacement)], window, cx);
4727                    let cursor = range.start + new_text.len();
4728                    self.mode.track_auto_closed_pair(
4729                        cursor - open_len..cursor,
4730                        cursor..cursor + closer.len(),
4731                    );
4732                    self.undo_manager
4733                        .record_auto_closed_pairs_after(self.mode.auto_closed_pairs().clone());
4734                    self.set_cursor_to(cursor);
4735                    self.update_preferred_column();
4736                    self.undo_manager.record_selections(
4737                        vec![selection_before],
4738                        self.selections.iter().copied().collect(),
4739                    );
4740                } else {
4741                    self.replace_text_in_ranges(
4742                        &[(range.clone(), new_text.to_string())],
4743                        window,
4744                        cx,
4745                    );
4746                }
4747            }
4748            if ends_composition {
4749                self.undo_manager.commit_transaction();
4750            }
4751
4752            if !self.silent_replace_text {
4753                M::on_text_typed(self, &range, new_text, window, cx);
4754            }
4755            return;
4756        }
4757
4758        // Single-line path
4759        let old_text = self.text.clone();
4760        // Token-aware history replay applies several replacements but emits one
4761        // `Change` at the end, so its edits accumulate instead of being
4762        // discarded with each silent replacement.
4763        let accumulate_replay_edits = self.replaying_history && !self.emit_events;
4764        if !accumulate_replay_edits {
4765            self.last_input_edits.clear();
4766        }
4767        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4768        self.text.replace(range.clone(), new_text);
4769
4770        let mut new_offset = (range.start + new_text.len()).min(self.text.len());
4771
4772        // True if the mask has changed the text, e.g. regrouping the
4773        // separators or completing a leading dot.
4774        let mut mask_changed = false;
4775
4776        if self.is_single_line() {
4777            let pending_text = self.text.to_string();
4778            // Check if the new text is valid.
4779            //
4780            // Only reject the edit if the old text was valid, to avoid
4781            // trapping a pre-existing invalid text (e.g. a `default_value`
4782            // that does not conform), the user can still edit to fix it.
4783            if !self.replaying_history
4784                && !self.validated_token_edit
4785                && !self.is_valid_input(&pending_text, cx)
4786                && self.is_valid_input(&old_text.to_string(), cx)
4787            {
4788                self.text = old_text;
4789                return;
4790            }
4791
4792            if !self.replaying_history && !self.mask_pattern.is_none() {
4793                let mask_text = self.mask_pattern.mask(&pending_text);
4794                mask_changed = mask_text.as_str() != pending_text;
4795                self.text = Rope::from(mask_text.as_str());
4796                let new_text_len =
4797                    (new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
4798                new_offset = (range.start + new_text_len).min(mask_text.len());
4799            }
4800        }
4801
4802        if mask_changed {
4803            // Masking rewrites the whole document, so ranges recorded against
4804            // the old text no longer point at anything.
4805            M::reset_annotations(self);
4806        } else {
4807            M::adjust_annotations(self, &range, new_text.len());
4808        }
4809        if mask_changed {
4810            // A segment-based history entry no longer matches the masked
4811            // document, record a whole-document change instead, so that
4812            // undo/redo can restore the text exactly.
4813            self.push_history(
4814                &old_text,
4815                &(0..old_text.len()),
4816                &self.text.to_string(),
4817                Some(EditIntent::Atomic),
4818                selection_before,
4819                Some((new_offset..new_offset).into()),
4820            );
4821        } else {
4822            self.push_history(
4823                &old_text,
4824                &range,
4825                &new_text,
4826                requested_intent,
4827                selection_before,
4828                None,
4829            );
4830        }
4831        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4832            diagnostics.reset(&self.text)
4833        }
4834        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4835        self.display_map
4836            .adjust_folds_for_edit(&old_text, &range, new_text);
4837        self.display_map
4838            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4839
4840        let edit = if mask_changed {
4841            Self::full_input_edit(&old_text, &self.text)
4842        } else {
4843            super::mode::replacement_input_edit(&old_text, &self.text, &range, new_text)
4844        };
4845        if self.emit_events || self.replaying_history {
4846            self.last_input_edits.push((edit, self.text.clone()));
4847        }
4848
4849        self.mode.update_highlighter::<M>(
4850            super::mode::HighlighterUpdate {
4851                selected_range: &range,
4852                old_text: &old_text,
4853                new_text: &self.text,
4854                change_text: &new_text,
4855                force: true,
4856                input_edit: Some(edit),
4857            },
4858            window,
4859            cx,
4860        );
4861
4862        self.update_fold_candidates_incremental(&range, new_text);
4863        M::refresh_language_features(self, window, cx);
4864        self.set_cursor_to(new_offset);
4865        self.ime_marked_range.take();
4866        // A commit ends the IME composition: macOS delivers `insertText:` for
4867        // the confirmed candidate without a following `unmarkText`, so close
4868        // the transaction here. Leaving it open would keep merging every later
4869        // edit into the same change, which then carries the text and selection
4870        // of the first composition.
4871        if ends_composition {
4872            self.undo_manager
4873                .record_selections(vec![selection_before], vec![*self.active_selection()]);
4874            self.undo_manager.commit_transaction();
4875        }
4876        self.update_preferred_column();
4877        self.update_search(cx);
4878        self.dismiss_touch_selection(cx);
4879        if self.is_multi_line() {
4880            self.mode.update_auto_grow(&self.display_map);
4881        }
4882        if !self.silent_replace_text {
4883            M::on_text_typed(self, &range, &new_text, window, cx);
4884        }
4885        if self.emit_events {
4886            self.emit_change(cx);
4887        }
4888        cx.notify();
4889    }
4890
4891    /// Mark text is the IME temporary insert on typing.
4892    fn replace_and_mark_text_in_range(
4893        &mut self,
4894        range_utf16: Option<Range<usize>>,
4895        new_text: &str,
4896        new_selected_range_utf16: Option<Range<usize>>,
4897        window: &mut Window,
4898        cx: &mut Context<Self>,
4899    ) {
4900        let requested_intent = self.undo_manager.take_pending_intent();
4901        if !self.is_editable() {
4902            return;
4903        }
4904        let selection_before = *self.active_selection();
4905
4906        let starts_composition = self.ime_marked_range.is_none();
4907        if starts_composition {
4908            // Even a canceled preedit separates the typing gestures on either
4909            // side; its no-op transaction must not reconnect those gestures.
4910            self.undo_manager.break_transaction_coalescing();
4911            self.undo_manager.begin_transaction();
4912        }
4913
4914        // Collapse any extra cursors so we never leave stale secondary cursors behind.
4915        self.selections.remove_all_but_active();
4916
4917        M::reset_language_features(self);
4918
4919        // See the same NOTE in `replace_text_in_range`.
4920        let new_text = if self.replaying_history {
4921            Cow::Borrowed(new_text)
4922        } else {
4923            self.normalize_input(new_text)
4924        };
4925        let new_text: &str = &new_text;
4926
4927        let range = range_utf16
4928            .as_ref()
4929            .map(|range_utf16| self.range_from_utf16(range_utf16))
4930            .or(self.ime_marked_range.map(|range| {
4931                let range = self.range_to_utf16(&(range.start..range.end));
4932                self.range_from_utf16(&range)
4933            }))
4934            .unwrap_or(self.selected_range());
4935        let range = self.normalize_token_range(range);
4936
4937        let auto_closed_pairs_before = self.mode.auto_closed_pairs().clone();
4938        let old_text = self.text.clone();
4939        // Marked text does not emit `InputEvent::Change`, so no edit belongs to
4940        // the most recent change after it.
4941        self.last_input_edits.clear();
4942        self.mode.adjust_auto_closed_pair(&range, new_text.len());
4943        self.text.replace(range.clone(), new_text);
4944
4945        if self.is_single_line() {
4946            let pending_text = self.text.to_string();
4947            // See the same NOTE in `replace_text_in_range`.
4948            if !self.replaying_history
4949                && !self.validated_token_edit
4950                && !self.is_valid_input(&pending_text, cx)
4951                && self.is_valid_input(&old_text.to_string(), cx)
4952            {
4953                self.text = old_text;
4954                if starts_composition {
4955                    self.undo_manager.commit_transaction();
4956                }
4957                return;
4958            }
4959        }
4960
4961        if self.ime_original_text.is_none() {
4962            self.ime_original_text = Some(old_text.clone());
4963        }
4964        M::adjust_annotations(self, &range, new_text.len());
4965        if let Some(diagnostics) = self.mode.diagnostics_mut() {
4966            diagnostics.reset(&self.text)
4967        }
4968        // Adjust folds before updating wrap map: remove overlapping folds and shift others
4969        self.display_map
4970            .adjust_folds_for_edit(&old_text, &range, new_text);
4971        self.display_map
4972            .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
4973
4974        self.mode.update_highlighter::<M>(
4975            super::mode::HighlighterUpdate {
4976                selected_range: &range,
4977                old_text: &old_text,
4978                new_text: &self.text,
4979                change_text: &new_text,
4980                force: true,
4981                input_edit: None,
4982            },
4983            window,
4984            cx,
4985        );
4986
4987        self.update_fold_candidates_incremental(&range, new_text);
4988        M::refresh_language_features(self, window, cx);
4989        if new_text.is_empty() {
4990            // Cancel selection, when cancel IME input.
4991            self.set_cursor_to(range.start);
4992            self.ime_marked_range = None;
4993        } else {
4994            self.ime_marked_range = Some((range.start..range.start + new_text.len()).into());
4995            let new_range = new_selected_range_utf16
4996                .as_ref()
4997                .map(|range_utf16| {
4998                    let new_text = Rope::from(new_text);
4999                    range.start + new_text.offset_utf16_to_offset(range_utf16.start)
5000                        ..range.start + new_text.offset_utf16_to_offset(range_utf16.end)
5001                })
5002                .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
5003            self.set_selection(new_range.start, new_range.end);
5004        }
5005        if self.is_multi_line() {
5006            self.mode.update_auto_grow(&self.display_map);
5007        }
5008        if self.push_history(
5009            &old_text,
5010            &range,
5011            new_text,
5012            requested_intent,
5013            selection_before,
5014            Some(*self.active_selection()),
5015        ) {
5016            self.undo_manager
5017                .record_selections(vec![selection_before], vec![*self.active_selection()]);
5018            self.undo_manager.record_auto_closed_pairs(
5019                auto_closed_pairs_before,
5020                self.mode.auto_closed_pairs().clone(),
5021            );
5022        }
5023        if new_text.is_empty() {
5024            self.undo_manager.commit_transaction();
5025        }
5026        cx.notify();
5027    }
5028
5029    /// Used to position IME candidates.
5030    fn bounds_for_range(
5031        &mut self,
5032        range_utf16: Range<usize>,
5033        bounds: Bounds<Pixels>,
5034        _window: &mut Window,
5035        _cx: &mut Context<Self>,
5036    ) -> Option<Bounds<Pixels>> {
5037        let last_layout = self.last_layout.as_ref()?;
5038        let line_height = last_layout.line_height;
5039        let line_number_width = last_layout.line_number_width;
5040        let range = self.range_from_utf16(&range_utf16);
5041
5042        let mut start_origin = None;
5043        let mut end_origin = None;
5044        let line_number_origin = point(line_number_width, px(0.));
5045        let mut y_offset = last_layout.visible_top;
5046
5047        for (vi, line) in last_layout.lines.iter().enumerate() {
5048            if start_origin.is_some() && end_origin.is_some() {
5049                break;
5050            }
5051
5052            let index_offset = last_layout.visible_line_byte_offsets[vi];
5053
5054            if start_origin.is_none() {
5055                if let Some(p) = line.position_for_index(
5056                    range.start.saturating_sub(index_offset),
5057                    last_layout,
5058                    false,
5059                ) {
5060                    start_origin = Some(p + point(px(0.), y_offset));
5061                }
5062            }
5063
5064            if end_origin.is_none() {
5065                if let Some(p) = line.position_for_index(
5066                    range.end.saturating_sub(index_offset),
5067                    last_layout,
5068                    false,
5069                ) {
5070                    end_origin = Some(p + point(px(0.), y_offset));
5071                }
5072            }
5073
5074            y_offset += line.size(line_height).height;
5075        }
5076
5077        let start_origin = start_origin.or_else(|| {
5078            let offset = self.last_cursor.or(Some(self.cursor()))?;
5079            let (_, _, origin) = self.line_and_position_for_offset(offset);
5080            origin.map(|origin| origin - line_number_origin)
5081        })?;
5082        let mut end_origin = end_origin.unwrap_or(start_origin);
5083        // Ensure at same line.
5084        end_origin.y = start_origin.y;
5085
5086        Some(Bounds::from_corners(
5087            bounds.origin + line_number_origin + start_origin,
5088            // + line_height for show IME panel under the cursor line.
5089            bounds.origin + line_number_origin + point(end_origin.x, end_origin.y + line_height),
5090        ))
5091    }
5092
5093    fn character_index_for_point(
5094        &mut self,
5095        point: gpui::Point<Pixels>,
5096        _window: &mut Window,
5097        _cx: &mut Context<Self>,
5098    ) -> Option<usize> {
5099        let last_layout = self.last_layout.as_ref()?;
5100        let line_point = self.last_bounds?.localize(&point)?;
5101
5102        for (vi, line) in last_layout.lines.iter().enumerate() {
5103            let offset = last_layout.visible_line_byte_offsets[vi];
5104            if let Some(utf8_index) = line.index_for_position(line_point, last_layout) {
5105                return Some(self.offset_to_utf16(offset + utf8_index));
5106            }
5107        }
5108
5109        None
5110    }
5111
5112    fn text_input_configuration(
5113        &mut self,
5114        _window: &mut Window,
5115        _cx: &mut Context<Self>,
5116    ) -> TextInputConfiguration {
5117        InputBaseState::text_input_configuration(self)
5118    }
5119}
5120
5121impl<M: InputModeKind> Focusable for InputBaseState<M> {
5122    fn focus_handle(&self, _cx: &App) -> FocusHandle {
5123        self.focus_handle.clone()
5124    }
5125}
5126
5127impl<M: InputModeKind> Render for InputBaseState<M> {
5128    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
5129        // Before anything reads it: the element resolves this style during
5130        // layout and paint, and both happen after this call in the same frame.
5131        self.editor_style = self
5132            .projected_editor_style
5133            .resolved(&crate::Theme::global(cx).tokens);
5134        let entity = cx.entity();
5135        if self._pending_update {
5136            self.mode.update_highlighter::<M>(
5137                super::mode::HighlighterUpdate {
5138                    selected_range: &(0..0),
5139                    old_text: &self.text,
5140                    new_text: &self.text,
5141                    change_text: "",
5142                    force: false,
5143                    input_edit: None,
5144                },
5145                window,
5146                cx,
5147            );
5148
5149            self.update_fold_candidates();
5150            M::refresh_language_features(self, window, cx);
5151            self._pending_update = false;
5152        }
5153
5154        let element = div()
5155            .id(EDITOR_ELEMENT_ID)
5156            .key_context(CONTEXT)
5157            .when_some(self.accessibility.as_ref(), |this, accessibility| {
5158                this.when_some(accessibility.role, |this, role| this.role(role))
5159                    .when_some(accessibility.label.clone(), |this, label| {
5160                        this.aria_label(label)
5161                    })
5162                    .when_some(accessibility.placeholder.clone(), |this, placeholder| {
5163                        this.aria_placeholder(placeholder)
5164                    })
5165                    .when_some(accessibility.accessibility_id.clone(), |this, id| {
5166                        this.accessibility_id(id)
5167                    })
5168                    .when(
5169                        accessibility.value_exposed
5170                            && !self.masked
5171                            && (window.is_a11y_active() || cfg!(feature = "test-support")),
5172                        |this| this.aria_value(self.text.to_string()),
5173                    )
5174                    .on_a11y_action(gpui::AccessibleAction::Focus, {
5175                        let entity = entity.clone();
5176                        move |_, window, cx| {
5177                            entity.update(cx, |state, cx| {
5178                                state.handle_accessibility_focus(window, cx)
5179                            });
5180                        }
5181                    })
5182                    .when(self.is_editable(), |this| {
5183                        this.on_a11y_action(gpui::AccessibleAction::SetValue, {
5184                            let entity = entity.clone();
5185                            move |data, window, cx| {
5186                                entity.update(cx, |state, cx| {
5187                                    state.handle_accessibility_set_value(data, window, cx)
5188                                });
5189                            }
5190                        })
5191                    })
5192            })
5193            .when(self.is_editable(), |this| {
5194                this.on_action(window.listener_for(&entity, InputBaseState::backspace))
5195                    .on_action(window.listener_for(&entity, InputBaseState::delete))
5196                    .on_action(
5197                        window.listener_for(&entity, InputBaseState::delete_to_beginning_of_line),
5198                    )
5199                    .on_action(window.listener_for(&entity, InputBaseState::delete_to_end_of_line))
5200                    .on_action(window.listener_for(&entity, InputBaseState::delete_previous_word))
5201                    .on_action(window.listener_for(&entity, InputBaseState::delete_next_word))
5202                    .on_action(window.listener_for(&entity, InputBaseState::enter))
5203                    .on_action(window.listener_for(&entity, InputBaseState::escape))
5204                    .on_action(window.listener_for(&entity, InputBaseState::paste))
5205                    .on_action(window.listener_for(&entity, InputBaseState::cut))
5206                    .on_action(window.listener_for(&entity, InputBaseState::undo))
5207                    .on_action(window.listener_for(&entity, InputBaseState::redo))
5208                    .when(self.is_multi_line(), |this| {
5209                        this.on_action(window.listener_for(&entity, InputBaseState::indent_inline))
5210                            .on_action(window.listener_for(&entity, InputBaseState::outdent_inline))
5211                            .on_action(window.listener_for(&entity, InputBaseState::indent_block))
5212                            .on_action(window.listener_for(&entity, InputBaseState::outdent_block))
5213                    })
5214            })
5215            .on_action(window.listener_for(&entity, InputBaseState::left))
5216            .on_action(window.listener_for(&entity, InputBaseState::right))
5217            .on_action(window.listener_for(&entity, InputBaseState::select_left))
5218            .on_action(window.listener_for(&entity, InputBaseState::select_right))
5219            .when(self.is_multi_line(), |this| {
5220                this.on_action(window.listener_for(&entity, InputBaseState::up))
5221                    .on_action(window.listener_for(&entity, InputBaseState::down))
5222                    .on_action(window.listener_for(&entity, InputBaseState::select_up))
5223                    .on_action(window.listener_for(&entity, InputBaseState::select_down))
5224                    .on_action(window.listener_for(&entity, InputBaseState::page_up))
5225                    .on_action(window.listener_for(&entity, InputBaseState::page_down))
5226                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_above))
5227                    .on_action(window.listener_for(&entity, InputBaseState::add_cursor_below))
5228            })
5229            .on_action(window.listener_for(&entity, InputBaseState::on_action_select_all))
5230            .on_action(window.listener_for(&entity, InputBaseState::select_to_start_of_line))
5231            .on_action(window.listener_for(&entity, InputBaseState::select_to_end_of_line))
5232            .on_action(window.listener_for(&entity, InputBaseState::select_to_previous_word))
5233            .on_action(window.listener_for(&entity, InputBaseState::select_to_next_word))
5234            .on_action(window.listener_for(&entity, InputBaseState::home))
5235            .on_action(window.listener_for(&entity, InputBaseState::end))
5236            .on_action(window.listener_for(&entity, InputBaseState::move_to_start))
5237            .on_action(window.listener_for(&entity, InputBaseState::move_to_end))
5238            .on_action(window.listener_for(&entity, InputBaseState::move_to_previous_word))
5239            .on_action(window.listener_for(&entity, InputBaseState::move_to_next_word))
5240            .on_action(window.listener_for(&entity, InputBaseState::select_to_start))
5241            .on_action(window.listener_for(&entity, InputBaseState::select_to_end))
5242            .on_action(window.listener_for(&entity, InputBaseState::show_character_palette))
5243            .on_action({
5244                let entity = entity.clone();
5245                move |_: &ActivateToken, window, cx| {
5246                    let state = entity.read(cx);
5247                    let activation = state
5248                        .token_spans()
5249                        .iter()
5250                        .find(|span| span.range() == state.selected_range())
5251                        .and_then(|span| {
5252                            state.range_to_bounds(&span.range()).and_then(|bounds| {
5253                                state.token_activation(
5254                                    span.range().start,
5255                                    bounds,
5256                                    gpui::ClickEvent::Keyboard(gpui::KeyboardClickEvent {
5257                                        bounds,
5258                                        ..Default::default()
5259                                    }),
5260                                )
5261                            })
5262                        });
5263                    if let Some((listener, event)) = activation {
5264                        listener(&event, window, cx);
5265                    }
5266                }
5267            })
5268            .on_action(window.listener_for(&entity, InputBaseState::copy))
5269            .on_action(window.listener_for(&entity, InputBaseState::on_action_search))
5270            .on_action(window.listener_for(&entity, InputBaseState::on_action_replace))
5271            .on_mouse_down(
5272                MouseButton::Left,
5273                window.listener_for(&entity, InputBaseState::on_mouse_down),
5274            )
5275            .on_mouse_down(
5276                MouseButton::Right,
5277                window.listener_for(&entity, InputBaseState::on_mouse_down),
5278            )
5279            .on_mouse_up(
5280                MouseButton::Left,
5281                window.listener_for(&entity, InputBaseState::on_mouse_up),
5282            )
5283            .on_mouse_up(
5284                MouseButton::Right,
5285                window.listener_for(&entity, InputBaseState::on_mouse_up),
5286            )
5287            .on_mouse_move(window.listener_for(&entity, InputBaseState::on_mouse_move))
5288            .on_scroll(window.listener_for(&entity, InputBaseState::on_scroll_wheel))
5289            .when(self.is_multi_line() && !self.disabled, |this| {
5290                this.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
5291            })
5292            .when(!self.disabled, |this| {
5293                if self.is_multi_line() && window.modifiers().alt {
5294                    this.cursor_crosshair()
5295                } else {
5296                    this.cursor_text()
5297                }
5298            })
5299            .flex_1()
5300            // A single line fills the frame and sits at its vertical center,
5301            // so the frame needs no layout of its own to hold it. It is never
5302            // shorter than its line, though: this root clips (overflow-x
5303            // hidden clips both axes), and a frame whose padding leaves less
5304            // than a line, like Component's Input, would cut off descenders.
5305            .map(|this| {
5306                if self.is_multi_line() {
5307                    this.h_full()
5308                } else {
5309                    this.min_h_full().flex().items_center()
5310                }
5311            })
5312            .flex_grow_1()
5313            .overflow_x_hidden()
5314            .when(self.is_multi_line(), |this| {
5315                this.pt(self.editor_paddings.top)
5316                    .pr(self.editor_paddings.right)
5317                    .pb(self.editor_paddings.bottom)
5318                    .pl(self.editor_paddings.left)
5319            })
5320            .child(TextElement::new(entity.clone()).placeholder(self.placeholder.clone()))
5321            .when(self.shows_scrollbar(), |this| {
5322                this.child(EditorScrollbar::new(entity.clone()))
5323            });
5324
5325        // Actions only one mode handles are registered by that mode, where
5326        // `Self` is concrete enough to name its own entity type.
5327        M::register_actions(element, &entity, window)
5328            .test_support()
5329            .track_focus(&self.focus_handle)
5330    }
5331}
5332
5333#[cfg(test)]
5334mod tests {
5335    use super::*;
5336
5337    #[gpui::test]
5338    fn test_set_value_waits_for_the_first_layout(cx: &mut TestAppContext) {
5339        cx.update(crate::init);
5340        let (state, cx) = cx.add_window_view(|window, cx| {
5341            let mut state = crate::input::InputState::new(window, cx);
5342            state.set_value("hello", window, cx);
5343            // Made outside drawing, the state only has a placeholder font to shape with.
5344            assert_eq!(state.display_map().wrap_row_count(), 0);
5345            state
5346        });
5347        cx.update(|window, cx| window.draw(cx).clear(cx));
5348        state.read_with(cx, |state, _| {
5349            assert_eq!(state.value(), "hello");
5350            assert_eq!(state.display_map().wrap_row_count(), 1);
5351        });
5352    }
5353
5354    #[gpui::test]
5355    fn accessibility_actions_recheck_live_editability(cx: &mut TestAppContext) {
5356        cx.update(crate::init);
5357        let (state, cx) = cx.add_window_view(|window, cx| {
5358            crate::input::InputState::new(window, cx).default_value("initial")
5359        });
5360        let changes = Rc::new(Cell::new(0));
5361        let observed = changes.clone();
5362        let _subscription = cx.update(|_, cx| {
5363            cx.subscribe(&state, move |_, event: &InputEvent, _| {
5364                if matches!(event, InputEvent::Change) {
5365                    observed.set(observed.get() + 1);
5366                }
5367            })
5368        });
5369        cx.update(|window, cx| {
5370            state.update(cx, |state, cx| {
5371                state.handle_accessibility_set_value(None, window, cx);
5372                state.handle_accessibility_focus(window, cx);
5373                assert!(state.focus_handle.is_focused(window));
5374            });
5375            window.draw(cx).clear(cx);
5376        });
5377        assert_eq!(state.read_with(cx, |state, _| state.value()), "initial");
5378        assert_eq!(changes.get(), 0);
5379
5380        let action = gpui::accesskit::ActionData::Value("updated🦀".into());
5381        cx.update(|window, cx| {
5382            state.update(cx, |state, cx| {
5383                state.handle_accessibility_set_value(Some(&action), window, cx)
5384            });
5385        });
5386        assert_eq!(state.read_with(cx, |state, _| state.value()), "updated🦀");
5387        assert_eq!(changes.get(), 1);
5388        for disabled in [false, true] {
5389            cx.update(|window, cx| {
5390                window.blur(cx);
5391                state.update(cx, |state, cx| {
5392                    state.set_disabled(disabled, cx);
5393                    state.set_readonly(!disabled, cx);
5394                    state.handle_accessibility_focus(window, cx);
5395                    assert_eq!(state.focus_handle.is_focused(window), !disabled);
5396                    let rejected = gpui::accesskit::ActionData::Value("rejected".into());
5397                    state.handle_accessibility_set_value(Some(&rejected), window, cx);
5398                });
5399            });
5400            assert_eq!(state.read_with(cx, |state, _| state.value()), "updated🦀");
5401            assert_eq!(changes.get(), 1);
5402        }
5403        cx.update(|window, cx| {
5404            state.update(cx, |state, cx| {
5405                state.set_disabled(false, cx);
5406                state.set_readonly(false, cx);
5407                state.handle_accessibility_focus(window, cx);
5408            });
5409            window.draw(cx).clear(cx);
5410            window.dispatch_action(Box::new(Undo), cx);
5411        });
5412        assert_eq!(state.read_with(cx, |state, _| state.value()), "initial");
5413        cx.update(|window, cx| {
5414            state.update(cx, |state, cx| {
5415                state.set_masked(true, window, cx);
5416                state.handle_accessibility_set_value(Some(&action), window, cx);
5417            });
5418        });
5419        assert_eq!(state.read_with(cx, |state, _| state.value()), "updated🦀");
5420    }
5421
5422    #[gpui::test]
5423    fn accessibility_set_value_preserves_exact_editor_text(cx: &mut TestAppContext) {
5424        cx.update(crate::init);
5425        let (editor, cx) = cx.add_window_view(|window, cx| {
5426            crate::input::EditorState::new(window, cx).language("rust")
5427        });
5428        let action = gpui::accesskit::ActionData::Value("(".into());
5429        cx.update(|window, cx| {
5430            editor.update(cx, |editor, cx| {
5431                editor.handle_accessibility_set_value(Some(&action), window, cx)
5432            });
5433        });
5434        assert_eq!(editor.read_with(cx, |editor, _| editor.value()), "(");
5435    }
5436
5437    #[gpui::test]
5438    fn revealing_a_password_keeps_text_assistance_disabled(cx: &mut TestAppContext) {
5439        cx.update(crate::init);
5440        cx.open_window(size(px(400.), px(100.)), |window, cx| {
5441            let state = cx.new(|cx| crate::input::InputState::new(window, cx).masked(true));
5442            let hidden = state.read(cx).text_input_configuration();
5443            assert!(!hidden.autocorrect && !hidden.suggestions);
5444            assert_eq!(hidden.autocapitalize, Autocapitalize::None);
5445
5446            // The component's reveal button uses this same state transition.
5447            state.update(cx, |state, cx| state.toggle_masked(window, cx));
5448            assert_eq!(state.read(cx).text_input_configuration(), hidden);
5449            state.update(cx, |state, cx| state.toggle_masked(window, cx));
5450            assert_eq!(state.read(cx).text_input_configuration(), hidden);
5451            gpui::EmptyView
5452        });
5453    }
5454
5455    #[gpui::test]
5456    fn text_input_configuration_follows_the_input(cx: &mut TestAppContext) {
5457        use crate::input::{InputState, TextareaState};
5458
5459        cx.update(crate::init);
5460        let mut states = None;
5461        cx.open_window(size(px(400.), px(100.)), |window, cx| {
5462            states = Some((
5463                cx.new(|cx| InputState::new(window, cx)),
5464                cx.new(|cx| InputState::new(window, cx).masked(true)),
5465                cx.new(|cx| TextareaState::new(window, cx)),
5466                cx.new(|cx| {
5467                    InputState::new(window, cx)
5468                        .input_action(TextInputAction::Search)
5469                        .autocorrect(true)
5470                        .autocapitalize(Autocapitalize::Words)
5471                }),
5472            ));
5473            gpui::EmptyView
5474        });
5475        let (plain, masked, textarea, search) = states.unwrap();
5476
5477        cx.read(|cx| {
5478            assert_eq!(
5479                plain.read(cx).text_input_configuration(),
5480                TextInputConfiguration {
5481                    input_action: TextInputAction::Done,
5482                    ..Default::default()
5483                }
5484            );
5485            assert_eq!(
5486                masked.read(cx).text_input_configuration(),
5487                TextInputConfiguration {
5488                    input_action: TextInputAction::Done,
5489                    ..Default::default()
5490                }
5491            );
5492            assert_eq!(
5493                textarea.read(cx).text_input_configuration(),
5494                TextInputConfiguration {
5495                    input_action: TextInputAction::Enter,
5496                    ..Default::default()
5497                }
5498            );
5499            let search = search.read(cx).text_input_configuration();
5500            assert_eq!(search.input_action, TextInputAction::Search);
5501            assert_eq!(search.autocapitalize, Autocapitalize::Words);
5502            assert!(search.autocorrect);
5503            assert!(search.suggestions);
5504        });
5505    }
5506
5507    #[gpui::test]
5508    fn test_multicursor_unicode_completion_uses_post_edit_start(cx: &mut TestAppContext) {
5509        use crate::input::{CompletionProvider, EditorState};
5510        use gpui::Task;
5511        use lsp_types::{CompletionContext, CompletionResponse};
5512        use std::cell::RefCell;
5513
5514        #[derive(Default)]
5515        struct Provider {
5516            starts: RefCell<Vec<usize>>,
5517            queries: RefCell<Vec<(usize, String)>>,
5518        }
5519
5520        impl CompletionProvider for Provider {
5521            fn is_completion_trigger(&self, offset: usize, _: &str, _: &mut App) -> bool {
5522                self.starts.borrow_mut().push(offset);
5523                true
5524            }
5525
5526            fn completions(
5527                &self,
5528                _: &Rope,
5529                offset: usize,
5530                trigger: CompletionContext,
5531                _: &mut Window,
5532                _: &mut App,
5533            ) -> Task<anyhow::Result<CompletionResponse>> {
5534                self.queries
5535                    .borrow_mut()
5536                    .push((offset, trigger.trigger_character.unwrap()));
5537                Task::ready(Ok(CompletionResponse::Array(vec![])))
5538            }
5539        }
5540
5541        cx.update(crate::init);
5542        let mut editor = None;
5543        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
5544            editor = Some(cx.new(|cx| EditorState::new(window, cx).default_value("aa")));
5545            gpui::EmptyView
5546        });
5547        let editor = editor.unwrap();
5548        let provider = Rc::new(Provider::default());
5549        window
5550            .update(cx, |_, window, cx| {
5551                editor.update(cx, |state, cx| {
5552                    state.extras.lsp.completion_provider = Some(provider.clone());
5553                    state
5554                        .extras
5555                        .context_menu_content
5556                        .completion
5557                        .trigger_start_offset = Some(0);
5558                    state.extras.context_menu_content.completion.query = "aa".into();
5559                    state.set_cursor_to(1);
5560                    state.add_cursor_at(0, cx);
5561
5562                    state.replace_text_in_range(None, "中", window, cx);
5563
5564                    assert_eq!(state.value(), "中a中a");
5565                    assert_eq!(provider.starts.borrow().as_slice(), &[4]);
5566                    assert_eq!(provider.queries.borrow().as_slice(), &[(7, "中".into())]);
5567                });
5568            })
5569            .unwrap();
5570    }
5571
5572    #[gpui::test]
5573    fn selected_range_restores_unicode_direction_without_splitting_characters(
5574        cx: &mut TestAppContext,
5575    ) {
5576        use crate::input::EditorState;
5577        cx.update(crate::init);
5578        cx.open_window(size(px(400.), px(100.)), |window, cx| {
5579            let _state = cx.new(|cx| {
5580                let mut state = EditorState::new(window, cx).default_value("中😀a");
5581                state.set_selected_range_with_direction(3..8, true, cx);
5582                let selected = state.selected_text_range(false, window, cx).unwrap();
5583                assert_eq!(selected.range, 1..4);
5584                assert!(selected.reversed);
5585                assert_eq!(state.selected_range(), 3..8);
5586                assert!(state.is_selection_reversed());
5587                let range = state.selected_range();
5588                let reversed = state.is_selection_reversed();
5589                state.set_selected_range_with_direction(range, reversed, cx);
5590                assert!(state.is_selection_reversed());
5591                state.set_selected_range(3..8, cx);
5592                let selected = state.selected_text_range(false, window, cx).unwrap();
5593                assert_eq!(selected.range, 1..4);
5594                assert!(!selected.reversed);
5595                assert!(!state.is_selection_reversed());
5596                // A reversed Rust Range remains empty for existing callers.
5597                state.set_selected_range(Range { start: 8, end: 3 }, cx);
5598                assert_eq!(state.selected_range(), 8..8);
5599                assert!(!state.is_selection_reversed());
5600                state.set_selected_range_with_direction(4..4, true, cx);
5601                assert!(!state.is_selection_reversed());
5602                assert_eq!(
5603                    state.selected_text_range(false, window, cx).unwrap().range,
5604                    1..1
5605                );
5606                state
5607            });
5608            gpui::EmptyView
5609        });
5610    }
5611
5612    #[gpui::test]
5613    fn test_inline_token_wrap_and_size_refresh(cx: &mut TestAppContext) {
5614        use crate::input::{InlineToken, InlineTokenPresentation};
5615        let width = Rc::new(Cell::new(4000.));
5616        let render_width = width.clone();
5617        let view = InputView::build_textarea(cx, |state| state.rows(4).default_value("@a@b"));
5618        view.window_handle
5619            .update(cx, |_, window, cx| {
5620                view.input.update(cx, |state, cx| {
5621                    state
5622                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
5623                        .unwrap();
5624                    state
5625                        .replace_range_with_token(2..4, InlineToken::new("b", "@b"), window, cx)
5626                        .unwrap();
5627                    state.set_token_presentation(
5628                        InlineTokenPresentation::default()
5629                            .token(move |_, _, _| div().w(px(render_width.get()))),
5630                    );
5631                });
5632            })
5633            .unwrap();
5634        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5635        visual.update(|window, cx| window.draw(cx).clear(cx));
5636        view.input.read_with(&visual, |state, _| {
5637            assert_eq!(state.display_map.wrap_row_count(), 2);
5638            let first = state.range_to_bounds(&(0..2)).unwrap();
5639            let second = state.range_to_bounds(&(2..4)).unwrap();
5640            assert!(second.origin.y > first.origin.y);
5641            assert_eq!(
5642                state.last_layout.as_ref().unwrap().lines[0].wrapped_lines[0].len,
5643                2
5644            );
5645        });
5646        width.set(100.);
5647        view.input.update(&mut visual, |_, cx| cx.notify());
5648        visual.update(|window, cx| window.draw(cx).clear(cx));
5649        view.input.read_with(&visual, |state, _| {
5650            assert_eq!(state.display_map.wrap_row_count(), 1)
5651        });
5652    }
5653
5654    #[gpui::test]
5655    fn test_inline_token_geometry_and_reentrant_activation(cx: &mut TestAppContext) {
5656        use crate::input::{InlineToken, InlineTokenPresentation};
5657        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5658        let target = view.input.clone();
5659        view.window_handle
5660            .update(cx, |_, window, cx| {
5661                view.input.update(cx, |state, cx| {
5662                    state
5663                        .replace_range_with_token(
5664                            7..13,
5665                            InlineToken::new("a", "@alice"),
5666                            window,
5667                            cx,
5668                        )
5669                        .unwrap();
5670                    state.set_token_presentation(
5671                        InlineTokenPresentation::default()
5672                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5673                            .on_token_click(move |_, window, cx| {
5674                                target.update(cx, |state, cx| {
5675                                    state.set_value("activated", window, cx)
5676                                })
5677                            }),
5678                    );
5679                    state.focus(window, cx);
5680                    state.set_selected_range(7..13, cx);
5681                });
5682            })
5683            .unwrap();
5684        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5685        visual.update(|window, cx| window.draw(cx).clear(cx));
5686        visual.update(|window, cx| {
5687            let state = view.input.read(cx);
5688            let bounds = state.range_to_bounds(&(7..13)).unwrap();
5689            assert!((bounds.size.width - px(100.)).abs() < px(1.), "{bounds:?}");
5690            let (left, _) = state.index_for_mouse_position(bounds.origin + point(px(10.), px(5.)));
5691            let (right, _) = state.index_for_mouse_position(bounds.origin + point(px(90.), px(5.)));
5692            assert_eq!((left, right), (7, 13));
5693            window.dispatch_action(Box::new(ActivateToken), cx);
5694        });
5695        visual.run_until_parked();
5696        view.input.read_with(&visual, |state, _| {
5697            assert_eq!(state.value().as_ref(), "activated")
5698        });
5699    }
5700
5701    #[gpui::test]
5702    fn test_inline_token_click_selects_it(cx: &mut TestAppContext) {
5703        use crate::input::InlineToken;
5704        cx.update(crate::init);
5705        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5706        view.window_handle
5707            .update(cx, |_, window, cx| {
5708                view.input.update(cx, |state, cx| {
5709                    state
5710                        .replace_range_with_token(
5711                            7..13,
5712                            InlineToken::new("a", "@alice"),
5713                            window,
5714                            cx,
5715                        )
5716                        .unwrap();
5717                    state.set_selected_range(0..0, cx);
5718                });
5719            })
5720            .unwrap();
5721        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5722        visual.update(|window, cx| window.draw(cx).clear(cx));
5723        let bounds = view
5724            .input
5725            .read_with(&visual, |state, _| state.range_to_bounds(&(7..13)).unwrap());
5726        visual.simulate_click(bounds.center(), gpui::Modifiers::default());
5727        visual.run_until_parked();
5728        view.input.read_with(&visual, |state, _| {
5729            assert_eq!(state.selected_range(), 7..13, "a click selects the token");
5730        });
5731    }
5732
5733    #[gpui::test]
5734    fn test_inline_token_hover_reports_presence_without_selecting(cx: &mut TestAppContext) {
5735        use crate::input::{InlineToken, InlineTokenPresentation};
5736        use gpui::{Bounds, point, px, size};
5737        cx.update(crate::init);
5738        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5739        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
5740        view.window_handle
5741            .update(cx, |_, window, cx| {
5742                view.input.update(cx, |state, cx| {
5743                    state
5744                        .replace_range_with_token(
5745                            7..13,
5746                            InlineToken::new("a", "@alice"),
5747                            window,
5748                            cx,
5749                        )
5750                        .unwrap();
5751                    state.set_token_presentation(
5752                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
5753                    );
5754                    state.set_selected_range(0..0, cx);
5755                });
5756            })
5757            .unwrap();
5758        view.window_handle
5759            .update(cx, |_, _, cx| {
5760                view.input.update(cx, |state, _| {
5761                    let (_, entered) = state
5762                        .token_hover(7, bounds, true, None)
5763                        .expect("hover enters an enabled token");
5764                    assert!(entered.is_hovered());
5765                    assert_eq!(entered.token().id().as_ref(), "a");
5766                    assert_eq!(entered.range(), 7..13);
5767                    assert_eq!(entered.bounds(), bounds);
5768                    let (_, left) = state
5769                        .token_hover(7, bounds, false, None)
5770                        .expect("hover leaves the same token");
5771                    assert!(!left.is_hovered());
5772                    assert_eq!(state.selected_range(), 0..0, "hover never selects");
5773                    assert!(state.token_hover(99, bounds, true, None).is_none());
5774                });
5775            })
5776            .unwrap();
5777
5778        // Disabled tokens suppress hover, matching click; readonly allows it.
5779        view.window_handle
5780            .update(cx, |_, _, cx| {
5781                view.input.update(cx, |state, cx| {
5782                    state.set_disabled(true, cx);
5783                });
5784            })
5785            .unwrap();
5786        view.window_handle
5787            .update(cx, |_, _, cx| {
5788                view.input.update(cx, |state, _| {
5789                    assert!(state.token_hover(7, bounds, true, None).is_none())
5790                })
5791            })
5792            .unwrap();
5793        view.window_handle
5794            .update(cx, |_, _, cx| {
5795                view.input.update(cx, |state, cx| {
5796                    state.set_disabled(false, cx);
5797                    state.set_readonly(true, cx);
5798                });
5799            })
5800            .unwrap();
5801        view.window_handle
5802            .update(cx, |_, _, cx| {
5803                view.input.update(cx, |state, _| {
5804                    assert!(state.token_hover(7, bounds, true, None).is_some())
5805                })
5806            })
5807            .unwrap();
5808
5809        // Without a hover listener there is no hover activation.
5810        view.window_handle
5811            .update(cx, |_, _, cx| {
5812                view.input.update(cx, |state, _| {
5813                    state.set_token_presentation(InlineTokenPresentation::default());
5814                });
5815            })
5816            .unwrap();
5817        view.window_handle
5818            .update(cx, |_, _, cx| {
5819                view.input.update(cx, |state, _| {
5820                    assert!(state.token_hover(7, bounds, true, None).is_none())
5821                })
5822            })
5823            .unwrap();
5824    }
5825
5826    #[gpui::test]
5827    fn test_inline_token_hover_exit_delivered_when_token_removed(cx: &mut TestAppContext) {
5828        use crate::input::{InlineToken, InlineTokenPresentation};
5829        use std::cell::RefCell;
5830        use std::rc::Rc;
5831        cx.update(crate::init);
5832        let view = InputView::build(cx, |state| state.default_value("before @alice after"));
5833        let events: Rc<RefCell<Vec<(SharedString, bool)>>> = Rc::new(RefCell::new(Vec::new()));
5834        let sink = events.clone();
5835        view.window_handle
5836            .update(cx, |_, window, cx| {
5837                view.input.update(cx, |state, cx| {
5838                    state
5839                        .replace_range_with_token(
5840                            7..13,
5841                            InlineToken::new("a", "@alice"),
5842                            window,
5843                            cx,
5844                        )
5845                        .unwrap();
5846                    state.set_token_presentation(
5847                        InlineTokenPresentation::default()
5848                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5849                            .on_token_hover(move |event, _, _| {
5850                                sink.borrow_mut()
5851                                    .push((event.token().id().clone(), event.is_hovered()));
5852                            }),
5853                    );
5854                });
5855            })
5856            .unwrap();
5857        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5858        visual.update(|window, cx| window.draw(cx).clear(cx));
5859        let known_bounds = view.input.read_with(&visual, |state, _| {
5860            state
5861                .token_bounds
5862                .get(&7)
5863                .copied()
5864                .expect("placed token bounds")
5865        });
5866        visual.simulate_mouse_move(known_bounds.center(), None, gpui::Modifiers::default());
5867        visual.run_until_parked();
5868        visual.update(|window, cx| window.draw(cx).clear(cx));
5869        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5870
5871        // Remove the token while the pointer is still over its stale row: the
5872        // exit must fire even though the token element that owns the hover
5873        // mask is gone.
5874        view.window_handle
5875            .update(cx, |_, window, cx| {
5876                view.input
5877                    .update(cx, |state, cx| state.set_value("no tokens", window, cx));
5878            })
5879            .unwrap();
5880        visual.run_until_parked();
5881        visual.update(|window, cx| window.draw(cx).clear(cx));
5882        assert_eq!(
5883            events.borrow().as_slice(),
5884            &[("a".into(), true), ("a".into(), false)]
5885        );
5886    }
5887
5888    #[gpui::test]
5889    fn test_inline_token_hover_bounds_stay_single_row_at_wrap_boundary(cx: &mut TestAppContext) {
5890        use crate::input::{InlineToken, InlineTokenPresentation};
5891        cx.update(crate::init);
5892        let view = InputView::build_textarea(cx, |state| {
5893            state
5894                .rows(4)
5895                .default_value("aaaa bbbb cccc dddd @alice eeee ffff")
5896        });
5897        view.window_handle
5898            .update(cx, |_, window, cx| {
5899                view.input.update(cx, |state, cx| {
5900                    state
5901                        .replace_range_with_token(
5902                            20..26,
5903                            InlineToken::new("a", "@alice"),
5904                            window,
5905                            cx,
5906                        )
5907                        .unwrap();
5908                    state.set_token_presentation(
5909                        InlineTokenPresentation::default()
5910                            .token(|_, _, _| div().w(px(60.)).h(px(20.))),
5911                    );
5912                });
5913            })
5914            .unwrap();
5915        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5916        visual.update(|window, cx| window.draw(cx).clear(cx));
5917        let (placed, ranged, line_height) = view.input.read_with(&visual, |state, _| {
5918            (
5919                state
5920                    .token_bounds
5921                    .get(&20)
5922                    .copied()
5923                    .expect("placed token bounds"),
5924                state.range_to_bounds(&(20..26)),
5925                state.last_layout.as_ref().unwrap().line_height,
5926            )
5927        });
5928        // The laid-out element always occupies exactly one row of height, even
5929        // when the range-extent path resolves the same end boundary onto the
5930        // next visual row at a soft-wrap edge.
5931        assert!(placed.size.height <= line_height + px(1.), "{placed:?}");
5932        assert!(
5933            placed.size.width <= px(61.) && placed.size.width > px(0.),
5934            "{placed:?}"
5935        );
5936        if let Some(ranged) = ranged {
5937            assert!(
5938                ranged.size.height >= placed.size.height - px(1.),
5939                "{ranged:?} vs {placed:?}"
5940            );
5941        }
5942    }
5943
5944    #[gpui::test]
5945    fn test_inline_token_hover_same_offset_replacement_exits_predecessor(cx: &mut TestAppContext) {
5946        use crate::input::{InlineToken, InlineTokenPresentation};
5947        use std::{cell::RefCell, rc::Rc};
5948        cx.update(crate::init);
5949        let view = InputView::build(cx, |state| state.default_value("@alice!"));
5950        let events = Rc::new(RefCell::new(Vec::new()));
5951        let sink = events.clone();
5952        view.window_handle
5953            .update(cx, |_, window, cx| {
5954                view.input.update(cx, |state, cx| {
5955                    state
5956                        .replace_range_with_token(0..6, InlineToken::new("a", "@alice"), window, cx)
5957                        .unwrap();
5958                    state.set_token_presentation(
5959                        InlineTokenPresentation::default()
5960                            .token(|_, _, _| div().w(px(100.)).h(px(20.)))
5961                            .on_token_hover(move |event, _, _| {
5962                                sink.borrow_mut()
5963                                    .push((event.token().id().clone(), event.is_hovered()));
5964                            }),
5965                    );
5966                });
5967            })
5968            .unwrap();
5969        let mut visual = VisualTestContext::from_window(view.window_handle.into(), cx);
5970        visual.update(|window, cx| window.draw(cx).clear(cx));
5971        let bounds = view
5972            .input
5973            .read_with(&visual, |state, _| state.token_bounds[&0]);
5974        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
5975        visual.run_until_parked();
5976        visual.update(|window, cx| window.draw(cx).clear(cx));
5977        assert_eq!(events.borrow().as_slice(), &[("a".into(), true)]);
5978
5979        view.window_handle
5980            .update(cx, |_, window, cx| {
5981                view.input.update(cx, |state, cx| {
5982                    state
5983                        .replace_range_with_token(0..6, InlineToken::new("b", "@alice"), window, cx)
5984                        .unwrap();
5985                });
5986            })
5987            .unwrap();
5988        visual.run_until_parked();
5989        visual.update(|window, cx| window.draw(cx).clear(cx));
5990        visual.simulate_mouse_move(
5991            bounds.center() + gpui::point(px(1.), px(0.)),
5992            None,
5993            gpui::Modifiers::default(),
5994        );
5995        visual.run_until_parked();
5996        visual.update(|window, cx| window.draw(cx).clear(cx));
5997        assert_eq!(
5998            events.borrow().as_slice(),
5999            &[("a".into(), true), ("a".into(), false), ("b".into(), true)]
6000        );
6001        // Disabling keeps the token mounted; re-enabling must reset GPUI's
6002        // retained hover state even with a stationary pointer.
6003        view.input
6004            .update(&mut visual, |state, cx| state.set_disabled(true, cx));
6005        visual.update(|window, cx| window.draw(cx).clear(cx));
6006        visual.run_until_parked();
6007        assert_eq!(events.borrow().last(), Some(&("b".into(), false)));
6008        view.input
6009            .update(&mut visual, |state, cx| state.set_disabled(false, cx));
6010        visual.update(|window, cx| window.draw(cx).clear(cx));
6011        visual.run_until_parked();
6012        visual.simulate_mouse_move(bounds.center(), None, gpui::Modifiers::default());
6013        visual.run_until_parked();
6014        assert_eq!(
6015            events.borrow().as_slice(),
6016            &[
6017                ("a".into(), true),
6018                ("a".into(), false),
6019                ("b".into(), true),
6020                ("b".into(), false),
6021                ("b".into(), true)
6022            ]
6023        );
6024        visual.simulate_mouse_move(
6025            bounds.bottom_right() + gpui::point(px(10.), px(10.)),
6026            None,
6027            gpui::Modifiers::default(),
6028        );
6029        visual.run_until_parked();
6030        assert_eq!(
6031            events.borrow().as_slice(),
6032            &[
6033                ("a".into(), true),
6034                ("a".into(), false),
6035                ("b".into(), true),
6036                ("b".into(), false),
6037                ("b".into(), true),
6038                ("b".into(), false)
6039            ]
6040        );
6041    }
6042
6043    #[gpui::test]
6044    fn test_inline_token_hover_older_exit_keeps_newer_snapshot(cx: &mut TestAppContext) {
6045        use crate::input::{InlineToken, InlineTokenPresentation};
6046        use gpui::{Bounds, point, px, size};
6047        cx.update(crate::init);
6048        let view = InputView::build(cx, |state| state.default_value("@a @b"));
6049        let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(20.)));
6050        view.window_handle
6051            .update(cx, |_, window, cx| {
6052                view.input.update(cx, |state, cx| {
6053                    state
6054                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
6055                        .unwrap();
6056                    state
6057                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
6058                        .unwrap();
6059                    state.set_token_presentation(
6060                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
6061                    );
6062                    // The pointer moves A -> B; GPUI dispatches B's enter
6063                    // before A's exit.
6064                    state
6065                        .token_hover(0, bounds, true, None)
6066                        .expect("hover enters A");
6067                    state
6068                        .token_hover(3, bounds, true, None)
6069                        .expect("hover enters B");
6070                    let (_, exit_a) = state.token_hover(0, bounds, false, None).expect("A exits");
6071                    assert!(!exit_a.is_hovered());
6072                    // B's snapshot must survive A's late exit.
6073                    assert_eq!(
6074                        state
6075                            .hovered_token
6076                            .as_ref()
6077                            .expect("B still hovered")
6078                            .span
6079                            .token()
6080                            .id()
6081                            .as_ref(),
6082                        "b"
6083                    );
6084                    // Removing B afterwards still produces B's reconciled exit.
6085                    state.set_value("no tokens", window, cx);
6086                    state.token_bounds.clear();
6087                    let (listener, exit_b) = state.reconcile_token_hover().expect("B must exit");
6088                    assert!(!exit_b.is_hovered());
6089                    assert_eq!(exit_b.token().id().as_ref(), "b");
6090                    drop(listener);
6091                });
6092            })
6093            .unwrap();
6094    }
6095
6096    #[gpui::test]
6097    fn test_inline_token_hover_late_exit_matches_replaced_token(cx: &mut TestAppContext) {
6098        use crate::input::{InlineToken, InlineTokenPresentation};
6099        cx.update(crate::init);
6100        let view = InputView::build(cx, |state| state.default_value("@a"));
6101        view.window_handle
6102            .update(cx, |_, window, cx| {
6103                view.input.update(cx, |state, cx| {
6104                    let a = InlineToken::new("a", "@a");
6105                    let b = InlineToken::new("b", "@a");
6106                    state
6107                        .replace_range_with_token(0..2, a.clone(), window, cx)
6108                        .unwrap();
6109                    state.set_token_presentation(
6110                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
6111                    );
6112                    let bounds = gpui::Bounds::default();
6113                    state.token_hover(0, bounds, true, Some(&a)).unwrap();
6114                    state
6115                        .replace_range_with_token(0..2, b.clone(), window, cx)
6116                        .unwrap();
6117                    state.token_hover(0, bounds, true, Some(&b)).unwrap();
6118                    let (_, exit_a) = state.token_hover(0, bounds, false, Some(&a)).unwrap();
6119                    assert_eq!(exit_a.token().id().as_ref(), "a");
6120                    assert_eq!(state.hovered_token.as_ref().unwrap().span.token(), &b);
6121                    let (_, exit_b) = state.token_hover(0, bounds, false, Some(&b)).unwrap();
6122                    assert_eq!(exit_b.token().id().as_ref(), "b");
6123                    assert!(state.pending_token_hover_exits.is_empty());
6124                });
6125            })
6126            .unwrap();
6127    }
6128
6129    #[gpui::test]
6130    fn test_inline_token_hover_exit_keeps_entry_utf16_after_edit(cx: &mut TestAppContext) {
6131        use crate::input::{InlineToken, InlineTokenPresentation};
6132        use gpui::EntityInputHandler as _;
6133        cx.update(crate::init);
6134        let view = InputView::build(cx, |state| state.default_value("🙂 @a"));
6135        view.window_handle
6136            .update(cx, |_, window, cx| {
6137                view.input.update(cx, |state, cx| {
6138                    state
6139                        .replace_range_with_token(5..7, InlineToken::new("a", "@a"), window, cx)
6140                        .unwrap();
6141                    state.set_token_presentation(
6142                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
6143                    );
6144                    let bounds = gpui::Bounds::default();
6145                    let (_, enter) = state.token_hover(5, bounds, true, None).unwrap();
6146                    assert_eq!(enter.range_utf16(), (3, 5));
6147                    state.replace_text_in_range(Some(0..2), "abcd", window, cx);
6148                    assert_eq!(state.token_spans()[0].range(), 5..7);
6149                    let (_, exit) = state.token_hover(5, bounds, false, None).unwrap();
6150                    assert_eq!(exit.range_utf16(), (3, 5));
6151                });
6152            })
6153            .unwrap();
6154    }
6155
6156    #[gpui::test]
6157    fn test_set_selected_text_range(cx: &mut TestAppContext) {
6158        use gpui::EntityInputHandler as _;
6159        cx.update(crate::init);
6160        // "🙂" is 2 UTF-16 units and 4 bytes.
6161        let view = InputView::build(cx, |state| state.default_value("🙂 hello world"));
6162        view.window_handle
6163            .update(cx, |_, window, cx| {
6164                view.input.update(cx, |state, cx| {
6165                    fn selection(
6166                        state: &mut InputBaseState<InputMode>,
6167                        window: &mut Window,
6168                        cx: &mut Context<InputBaseState<InputMode>>,
6169                    ) -> (Range<usize>, bool) {
6170                        let selection = state.selected_text_range(false, window, cx).unwrap();
6171                        (selection.range, selection.reversed)
6172                    }
6173
6174                    state.set_selected_text_range(3..8, window, cx);
6175                    assert_eq!(state.selected_range(), 5..10);
6176                    assert_eq!(selection(state, window, cx), (3..8, false));
6177
6178                    // Dragging the end handle keeps the selection forward.
6179                    state.set_selected_text_range(3..14, window, cx);
6180                    assert_eq!(selection(state, window, cx), (3..14, false));
6181                    assert_eq!(state.cursor(), 16);
6182
6183                    // Dragging the start handle reverses it, so the start is the cursor.
6184                    state.set_selected_text_range(9..14, window, cx);
6185                    assert_eq!(selection(state, window, cx), (9..14, true));
6186                    assert_eq!(state.cursor(), 11);
6187
6188                    // A repeated range keeps its direction.
6189                    state.set_selected_text_range(9..14, window, cx);
6190                    assert_eq!(selection(state, window, cx), (9..14, true));
6191
6192                    // Out of range clamps; a backwards range is normalized.
6193                    state.set_selected_text_range(100..9, window, cx);
6194                    assert_eq!(selection(state, window, cx), (9..14, true));
6195
6196                    // Either handle can cross the stationary end.
6197                    state.set_selected_text_range(3..8, window, cx);
6198                    state.set_selected_text_range(2..3, window, cx);
6199                    assert_eq!(selection(state, window, cx), (2..3, true));
6200                    assert_eq!(state.cursor(), 4);
6201                    state.set_selected_text_range(3..8, window, cx);
6202                    state.set_selected_text_range(8..9, window, cx);
6203                    assert_eq!(selection(state, window, cx), (8..9, false));
6204                    assert_eq!(state.cursor(), 11);
6205
6206                    // Collapsing places a forward cursor and leaves the text alone.
6207                    state.set_selected_text_range(2..2, window, cx);
6208                    assert_eq!(selection(state, window, cx), (2..2, false));
6209                    assert_eq!(state.value(), "🙂 hello world");
6210                });
6211            })
6212            .unwrap();
6213    }
6214
6215    #[gpui::test]
6216    fn test_inline_token_hover_callback_edit_preserves_both_exits(cx: &mut TestAppContext) {
6217        use crate::input::{InlineToken, InlineTokenPresentation};
6218        cx.update(crate::init);
6219        let view = InputView::build(cx, |state| state.default_value("@a @b"));
6220        view.window_handle
6221            .update(cx, |_, window, cx| {
6222                view.input.update(cx, |state, cx| {
6223                    state
6224                        .replace_range_with_token(0..2, InlineToken::new("a", "@a"), window, cx)
6225                        .unwrap();
6226                    state
6227                        .replace_range_with_token(3..5, InlineToken::new("b", "@b"), window, cx)
6228                        .unwrap();
6229                    state.set_token_presentation(
6230                        InlineTokenPresentation::default().on_token_hover(|_, _, _| {}),
6231                    );
6232                    let bounds = gpui::Bounds::default();
6233                    state.token_hover(0, bounds, true, None).unwrap();
6234                    state.token_hover(3, bounds, true, None).unwrap();
6235                    // Model an edit in B's enter callback before A's queued exit.
6236                    state.set_value("gone", window, cx);
6237                    let (_, exit_a) = state.token_hover(0, bounds, false, None).unwrap();
6238                    assert_eq!(exit_a.token().id().as_ref(), "a");
6239                    let (_, exit_b) = state.reconcile_token_hover().unwrap();
6240                    assert_eq!(exit_b.token().id().as_ref(), "b");
6241                    assert!(state.token_hover(0, bounds, false, None).is_none());
6242                    assert!(state.reconcile_token_hover().is_none());
6243                });
6244            })
6245            .unwrap();
6246    }
6247
6248    #[gpui::test]
6249    fn test_inline_token_edit_history_and_validation(cx: &mut TestAppContext) {
6250        let view = InputView::build(cx, |state| state.default_value("问 @alice!"));
6251        view.window_handle
6252            .update(cx, |_, window, cx| {
6253                view.input.update(cx, |state, cx| {
6254                    let token =
6255                        crate::input::InlineToken::new("alice-1", "@alice").with_label("Alice");
6256                    state
6257                        .replace_range_with_token(4..10, token.clone(), window, cx)
6258                        .unwrap();
6259                    assert_eq!(state.value().as_ref(), "问 @alice!");
6260                    assert_eq!(state.tokens()[0].range(), 4..10);
6261                    assert_eq!(state.next_end_of_word_at(4), 10);
6262                    assert_eq!(state.previous_start_of_word_at(10), 4);
6263                    state.undo(&Undo, window, cx);
6264                    assert!(
6265                        state.tokens().is_empty(),
6266                        "identity-only association is undoable"
6267                    );
6268                    assert_eq!(state.value().as_ref(), "问 @alice!");
6269                    state.redo(&Redo, window, cx);
6270                    assert_eq!(state.tokens()[0].token(), &token);
6271                    state.set_selected_range(6..7, cx);
6272                    assert_eq!(state.selected_range(), 4..10);
6273                    state.replace("", window, cx);
6274                    assert_eq!(state.value().as_ref(), "问 !");
6275                    assert!(state.tokens().is_empty());
6276                    state.undo(&Undo, window, cx);
6277                    assert_eq!(state.tokens()[0].token(), &token);
6278                    state.set_selected_range(0..0, cx);
6279                    state.replace("🙂", window, cx);
6280                    assert_eq!(state.tokens()[0].range(), 8..14);
6281                    state.undo(&Undo, window, cx);
6282                    assert_eq!(state.tokens()[0].range(), 4..10);
6283                    let before = state.content();
6284                    state
6285                        .replace_range_with_token(0..0, token.clone(), window, cx)
6286                        .expect("the same reference may occur twice");
6287                    assert_eq!(state.value().as_ref(), "@alice问 @alice!");
6288                    assert_eq!(state.tokens().len(), 2);
6289                    assert_eq!(state.tokens()[1].token(), &token);
6290                    state.undo(&Undo, window, cx);
6291                    assert_eq!(state.content(), before);
6292                    state.set_value(before.text().clone(), window, cx);
6293                    assert!(state.tokens().is_empty());
6294                    assert!(!state.undo_manager.has_undos());
6295                });
6296            })
6297            .unwrap();
6298    }
6299
6300    #[gpui::test]
6301    fn test_inline_token_textarea_ime_and_modes(cx: &mut TestAppContext) {
6302        use crate::input::{InlineToken, InlineTokenError, InputContent};
6303        let view = InputView::build_textarea(cx, |state| state);
6304        view.window_handle
6305            .update(cx, |_, window, cx| {
6306                view.input.update(cx, |state, cx| {
6307                    let content = InputContent::new("@a@b\n后面")
6308                        .with_token(0..2, InlineToken::new("a", "@a"))
6309                        .unwrap()
6310                        .with_token(2..4, InlineToken::new("b", "@b"))
6311                        .unwrap();
6312                    state.set_value(content.clone(), window, cx);
6313                    state.set_selected_range(2..2, cx);
6314                    assert_eq!(state.previous_boundary(2), 0);
6315                    assert_eq!(state.next_boundary(2), 4);
6316                    state.replace_and_mark_text_in_range(Some(1..2), "中", Some(1..1), window, cx);
6317                    assert_eq!(state.value().as_ref(), "中@b\n后面");
6318                    assert_eq!(state.tokens().len(), 1);
6319                    assert_eq!(
6320                        state.replace_with_token(InlineToken::new("x", "x"), window, cx),
6321                        Err(InlineTokenError::CompositionActive)
6322                    );
6323                    state.replace_text_in_range(None, "中文", window, cx);
6324                    state.undo(&Undo, window, cx);
6325                    assert_eq!(state.content(), content);
6326                    state.redo(&Redo, window, cx);
6327                    assert_eq!(state.value().as_ref(), "中文@b\n后面");
6328                    state.undo(&Undo, window, cx);
6329                    state.replace_all("plain", window, cx);
6330                    assert!(state.tokens().is_empty());
6331                    state.undo(&Undo, window, cx);
6332                    assert_eq!(state.content(), content);
6333                });
6334            })
6335            .unwrap();
6336        let view = InputView::build(cx, |state| state);
6337        view.window_handle
6338            .update(cx, |_, window, cx| {
6339                view.input.update(cx, |state, cx| {
6340                    assert_eq!(
6341                        InputContent::new("a\u{301}")
6342                            .with_token(0..1, InlineToken::new("bad", "a"))
6343                            .err(),
6344                        Some(InlineTokenError::InvalidBoundary)
6345                    );
6346                    assert_eq!(
6347                        InputContent::new("@a@a")
6348                            .with_token(0..2, InlineToken::new("a", "@a"))
6349                            .unwrap()
6350                            .with_token(1..3, InlineToken::new("x", "a@"))
6351                            .err(),
6352                        Some(InlineTokenError::OverlappingTokens)
6353                    );
6354                    state
6355                        .replace_with_token(InlineToken::new("a", "@a"), window, cx)
6356                        .unwrap();
6357                    let before = state.content();
6358                    state.set_masked(true, window, cx);
6359                    assert!(!state.tokens_visible());
6360                    assert_eq!(
6361                        state.replace_with_token(InlineToken::new("b", "b"), window, cx),
6362                        Err(InlineTokenError::UnsupportedMode)
6363                    );
6364                    assert_eq!(state.content(), before);
6365                    state.undo(&Undo, window, cx);
6366                    assert!(state.tokens().is_empty());
6367                    state.redo(&Redo, window, cx);
6368                    assert_eq!(state.content(), before);
6369                    assert!(!state.tokens_visible());
6370                });
6371            })
6372            .unwrap();
6373    }
6374
6375    use crate::theme::Theme;
6376    use gpui::{TestAppContext, VisualTestContext, size};
6377
6378    use crate::input::{EditorMode, EditorState, InputMode, LanguageConfig, TabSize, TextareaMode};
6379
6380    fn set_test_syntax_provider(
6381        provider: Rc<dyn crate::input::SyntaxContextProvider>,
6382        cx: &mut App,
6383    ) {
6384        struct TestLanguages(Rc<dyn crate::input::SyntaxContextProvider>);
6385        impl crate::input::LanguageProvider for TestLanguages {
6386            fn syntax_context_provider(
6387                &self,
6388                _: &str,
6389            ) -> Option<Rc<dyn crate::input::SyntaxContextProvider>> {
6390                Some(self.0.clone())
6391            }
6392        }
6393        crate::input::set_language_provider(Rc::new(TestLanguages(provider)), cx);
6394    }
6395
6396    struct TestRoot<M: InputModeKind>(Entity<InputBaseState<M>>);
6397
6398    impl<M: InputModeKind> Render for TestRoot<M> {
6399        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6400            div().size_full().child(self.0.clone())
6401        }
6402    }
6403
6404    struct InputView<M: InputModeKind> {
6405        input: Entity<InputBaseState<M>>,
6406        window_handle: gpui::WindowHandle<TestRoot<M>>,
6407    }
6408
6409    /// Helper to open a state of one mode in a window for testing.
6410    impl<M: InputModeKind> InputView<M> {
6411        fn build_with(
6412            cx: &mut TestAppContext,
6413            make: impl FnOnce(&mut Window, &mut Context<InputBaseState<M>>) -> InputBaseState<M>
6414            + 'static,
6415        ) -> Self {
6416            let mut input: Option<Entity<InputBaseState<M>>> = None;
6417
6418            let window = cx.update(|cx| {
6419                cx.open_window(Default::default(), |window, cx| {
6420                    // Set up the theme first
6421                    cx.set_global(Theme::default());
6422                    // Initialize input keybindings
6423                    super::super::init(cx);
6424
6425                    input = Some(cx.new(|cx| make(window, cx)));
6426
6427                    cx.new(|_| TestRoot(input.clone().unwrap()))
6428                })
6429                .unwrap()
6430            });
6431
6432            Self {
6433                input: input.clone().unwrap(),
6434                window_handle: window,
6435            }
6436        }
6437    }
6438
6439    impl InputView<EditorMode> {
6440        /// An editor state, for the tests that exercise code-editor behavior.
6441        fn new(cx: &mut TestAppContext) -> Self {
6442            Self::build_editor(cx, |state| state)
6443        }
6444
6445        fn build_editor(
6446            cx: &mut TestAppContext,
6447            f: impl FnOnce(InputBaseState<EditorMode>) -> InputBaseState<EditorMode> + 'static,
6448        ) -> Self {
6449            Self::build_with(cx, move |window, cx| {
6450                f(crate::input::EditorState::new(window, cx).language("sql"))
6451            })
6452        }
6453    }
6454
6455    impl InputView<TextareaMode> {
6456        fn build_textarea(
6457            cx: &mut TestAppContext,
6458            f: impl FnOnce(InputBaseState<TextareaMode>) -> InputBaseState<TextareaMode> + 'static,
6459        ) -> Self {
6460            Self::build_with(cx, move |window, cx| {
6461                f(crate::input::TextareaState::new(window, cx))
6462            })
6463        }
6464    }
6465
6466    impl InputView<InputMode> {
6467        /// A single-line state, the default these tests were written against.
6468        fn build(
6469            cx: &mut TestAppContext,
6470            f: impl FnOnce(InputBaseState<InputMode>) -> InputBaseState<InputMode> + 'static,
6471        ) -> Self {
6472            Self::build_with(cx, move |window, cx| {
6473                f(crate::input::InputState::new(window, cx))
6474            })
6475        }
6476    }
6477
6478    /// Sends one trackpad packet straight to the input.
6479    fn swipe<M: InputModeKind>(
6480        view: &InputView<M>,
6481        cx: &mut VisualTestContext,
6482        dy: f32,
6483        phase: gpui::TouchPhase,
6484    ) {
6485        cx.update(|window, cx| {
6486            view.input.update(cx, |state, cx| {
6487                state.on_scroll_wheel(
6488                    &ScrollEvent {
6489                        delta: gpui::ScrollDelta::Pixels(point(px(0.), px(dy))),
6490                        touch_phase: phase,
6491                        ..Default::default()
6492                    },
6493                    window,
6494                    cx,
6495                )
6496            })
6497        });
6498    }
6499
6500    fn long_text() -> String {
6501        (0..60)
6502            .map(|n| format!("line {n}"))
6503            .collect::<Vec<_>>()
6504            .join("\n")
6505    }
6506
6507    #[gpui::test]
6508    fn test_scroll_bounce_stretches_past_the_end_and_springs_back(cx: &mut TestAppContext) {
6509        use gpui::TouchPhase::{Ended, Moved, Started};
6510
6511        let view = InputView::build_textarea(cx, |state| {
6512            state.scroll_bounce(true).default_value(long_text())
6513        });
6514        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6515        cx.update(|window, cx| window.draw(cx).clear(cx));
6516        let drawn_at = |cx: &mut VisualTestContext| {
6517            view.input
6518                .read_with(cx, |state, _| state.last_bounds.unwrap().origin.y)
6519        };
6520
6521        swipe(&view, &mut cx, -20., Started);
6522        swipe(&view, &mut cx, -100_000., Moved);
6523        let (end, stretch) = view.input.read_with(&cx, |state, _| {
6524            (state.scroll_handle.offset().y, state.overscroll.offset())
6525        });
6526        assert!(end < px(0.), "the text scrolled to its end");
6527        assert!(stretch < 0., "the rest stretched the edge: {stretch}");
6528        cx.update(|window, cx| window.draw(cx).clear(cx));
6529        let stretched_at = drawn_at(&mut cx);
6530
6531        swipe(&view, &mut cx, 0., Ended);
6532        for _ in 0..120 {
6533            cx.executor()
6534                .advance_clock(std::time::Duration::from_millis(16));
6535            cx.update(|window, cx| window.draw(cx).clear(cx));
6536        }
6537        view.input.read_with(&cx, |state, _| {
6538            assert_eq!(state.overscroll.offset(), 0., "the edge sprang back");
6539            assert_eq!(
6540                state.scroll_handle.offset().y,
6541                end,
6542                "scrolling stayed exact"
6543            );
6544        });
6545        let moved = (stretched_at - drawn_at(&mut cx)).as_f32();
6546        assert!(
6547            (moved - stretch).abs() < 0.001,
6548            "the stretch only moved where the text was drawn: {moved} vs {stretch}"
6549        );
6550    }
6551
6552    /// Swipes far past the end of the text and returns where the text
6553    /// scrolled to and how far its edge stretched.
6554    fn swipe_past_the_end(
6555        view: &InputView<TextareaMode>,
6556        cx: &mut VisualTestContext,
6557    ) -> (Pixels, f32) {
6558        use gpui::TouchPhase::{Moved, Started};
6559
6560        cx.update(|window, cx| window.draw(cx).clear(cx));
6561        swipe(view, cx, -20., Started);
6562        swipe(view, cx, -100_000., Moved);
6563        view.input.read_with(cx, |state, _| {
6564            (state.scroll_handle.offset().y, state.overscroll.offset())
6565        })
6566    }
6567
6568    #[gpui::test]
6569    fn test_scroll_bounce_stays_put_under_reduced_motion(cx: &mut TestAppContext) {
6570        cx.update(|cx| cx.set_reduce_motion(true));
6571        let view = InputView::build_textarea(cx, |state| {
6572            state.scroll_bounce(true).default_value(long_text())
6573        });
6574        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6575        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6576        assert!(end < px(0.), "the text still scrolls");
6577        assert_eq!(stretch, 0., "the edge does not stretch");
6578    }
6579
6580    #[gpui::test]
6581    fn test_scroll_bounce_follows_the_platform_by_default(cx: &mut TestAppContext) {
6582        let view = InputView::build_textarea(cx, |state| state.default_value(long_text()));
6583        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6584        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6585        assert!(end < px(0.), "the text scrolls");
6586        assert_eq!(
6587            stretch != 0.,
6588            crate::ScrollBounce::enabled_by_default(),
6589            "the edge stretches where the platform's scroll views do"
6590        );
6591    }
6592
6593    #[gpui::test]
6594    fn test_scroll_bounce_leaves_text_that_fits(cx: &mut TestAppContext) {
6595        let view = InputView::build_textarea(cx, |state| {
6596            state.scroll_bounce(true).default_value("one line")
6597        });
6598        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6599        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6600        assert_eq!((end, stretch), (px(0.), 0.));
6601    }
6602
6603    #[gpui::test]
6604    fn test_scroll_bounce_resets_when_replacement_text_fits(cx: &mut TestAppContext) {
6605        let view = InputView::build_textarea(cx, |state| {
6606            state.scroll_bounce(true).default_value(long_text())
6607        });
6608        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6609        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6610        assert!(stretch < 0.);
6611        cx.update(|window, cx| {
6612            view.input
6613                .update(cx, |state, cx| state.set_value("short", window, cx));
6614            window.draw(cx).clear(cx);
6615        });
6616        swipe(&view, &mut cx, 0., gpui::TouchPhase::Ended);
6617        view.input.read_with(&cx, |state, _| {
6618            assert_eq!(state.overscroll.offset(), 0.);
6619            assert_eq!(state.scroll_handle.offset().y, px(0.));
6620        });
6621    }
6622
6623    #[gpui::test]
6624    fn test_scroll_bounce_releases_when_the_end_packet_is_horizontal(cx: &mut TestAppContext) {
6625        let view = InputView::build_textarea(cx, |state| {
6626            state.scroll_bounce(true).default_value(long_text())
6627        });
6628        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6629        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6630        assert!(stretch < 0.);
6631        cx.update(|window, cx| {
6632            view.input.update(cx, |state, cx| {
6633                state.on_scroll_wheel(
6634                    &ScrollEvent {
6635                        delta: gpui::ScrollDelta::Pixels(point(px(1.), px(0.))),
6636                        touch_phase: gpui::TouchPhase::Ended,
6637                        ..Default::default()
6638                    },
6639                    window,
6640                    cx,
6641                );
6642            });
6643        });
6644        for _ in 0..120 {
6645            cx.executor()
6646                .advance_clock(std::time::Duration::from_millis(16));
6647            cx.update(|window, cx| window.draw(cx).clear(cx));
6648        }
6649        assert_eq!(
6650            view.input
6651                .read_with(&cx, |state, _| state.overscroll.offset()),
6652            0.
6653        );
6654    }
6655
6656    #[gpui::test]
6657    fn test_scroll_bounce_ordinary_mouse_wheel_does_not_stretch(cx: &mut TestAppContext) {
6658        let view = InputView::build_textarea(cx, |state| {
6659            state.scroll_bounce(true).default_value(long_text())
6660        });
6661        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6662        cx.update(|window, cx| {
6663            window.draw(cx).clear(cx);
6664            view.input.update(cx, |state, cx| {
6665                state.on_scroll_wheel(
6666                    &ScrollEvent {
6667                        delta: gpui::ScrollDelta::Lines(point(0., -100_000.)),
6668                        touch_phase: gpui::TouchPhase::Moved,
6669                        ..Default::default()
6670                    },
6671                    window,
6672                    cx,
6673                );
6674                assert!(state.scroll_handle.offset().y < px(0.));
6675                assert_eq!(state.overscroll.offset(), 0.);
6676            });
6677        });
6678    }
6679
6680    #[gpui::test]
6681    fn test_scroll_bounce_new_wheel_input_interrupts_the_return(cx: &mut TestAppContext) {
6682        let view = InputView::build_textarea(cx, |state| {
6683            state.scroll_bounce(true).default_value(long_text())
6684        });
6685        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6686        let (end, stretch) = swipe_past_the_end(&view, &mut cx);
6687        assert!(stretch < 0.);
6688        swipe(&view, &mut cx, 0., gpui::TouchPhase::Ended);
6689        cx.executor()
6690            .advance_clock(std::time::Duration::from_millis(16));
6691        cx.update(|window, cx| {
6692            window.draw(cx).clear(cx);
6693            view.input.update(cx, |state, cx| {
6694                state.on_scroll_wheel(
6695                    &ScrollEvent {
6696                        delta: gpui::ScrollDelta::Lines(point(0., 2.)),
6697                        touch_phase: gpui::TouchPhase::Moved,
6698                        ..Default::default()
6699                    },
6700                    window,
6701                    cx,
6702                );
6703                assert!(state.scroll_handle.offset().y > end);
6704                assert_eq!(state.overscroll.offset(), 0.);
6705            });
6706        });
6707    }
6708
6709    #[gpui::test]
6710    fn test_turning_scroll_bounce_off_drops_the_stretch(cx: &mut TestAppContext) {
6711        let view = InputView::build_textarea(cx, |state| {
6712            state.scroll_bounce(true).default_value(long_text())
6713        });
6714        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
6715        let (_, stretch) = swipe_past_the_end(&view, &mut cx);
6716        assert!(stretch < 0., "the edge stretched: {stretch}");
6717        view.input
6718            .update(&mut cx, |state, cx| state.set_scroll_bounce(false, cx));
6719        assert_eq!(
6720            view.input
6721                .read_with(&cx, |state, _| state.overscroll.offset()),
6722            0.
6723        );
6724    }
6725
6726    /// The frame is laid out by the application, which should not have to
6727    /// center a single line inside it.
6728    #[gpui::test]
6729    fn single_line_is_centered_in_a_taller_frame(cx: &mut TestAppContext) {
6730        struct Frame(Entity<InputBaseState<InputMode>>);
6731        impl Render for Frame {
6732            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6733                crate::input::InputBase::new("frame")
6734                    .h(px(60.))
6735                    .child(crate::input::Input::new(&self.0))
6736            }
6737        }
6738
6739        cx.update(crate::init);
6740        let mut input = None;
6741        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6742            let state = cx.new(|cx| crate::input::InputState::new(window, cx).default_value("a"));
6743            input = Some(state.clone());
6744            Frame(state)
6745        });
6746        let input = input.unwrap();
6747        let mut cx = VisualTestContext::from_window(window.into(), cx);
6748        cx.update(|window, cx| window.draw(cx).clear(cx));
6749
6750        input.read_with(&cx, |state, _| {
6751            let line = state.input_bounds;
6752            assert_eq!(line.center().y, px(30.), "line {line:?}");
6753        });
6754    }
6755
6756    #[gpui::test]
6757    fn test_noop_scroll_notifies_diagnostic_dismissal(cx: &mut TestAppContext) {
6758        use std::{cell::Cell, rc::Rc};
6759
6760        cx.update(crate::init);
6761        let mut input = None;
6762        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6763            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
6764            gpui::EmptyView
6765        });
6766        let input = input.unwrap();
6767        input.update(cx, |state, cx| {
6768            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
6769            state.scroll_size = size(px(100.), px(20.));
6770            state.present_diagnostic(crate::input::DiagnosticEntry::default(), cx);
6771        });
6772        let notifications = Rc::new(Cell::new(0));
6773        let count = notifications.clone();
6774        let _subscription =
6775            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6776
6777        window
6778            .update(cx, |_, window, cx| {
6779                input.update(cx, |state, cx| {
6780                    state.on_scroll_wheel(
6781                        &ScrollEvent {
6782                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
6783                            ..Default::default()
6784                        },
6785                        window,
6786                        cx,
6787                    );
6788                    assert_eq!(state.scroll_handle.offset(), Point::default());
6789                    assert!(state.diagnostic_popover().is_none());
6790                });
6791            })
6792            .unwrap();
6793        assert_eq!(
6794            notifications.get(),
6795            1,
6796            "clearing a diagnostic must notify even when scrolling is clamped"
6797        );
6798        window
6799            .update(cx, |_, window, cx| {
6800                input.update(cx, |state, cx| {
6801                    state.on_scroll_wheel(
6802                        &ScrollEvent {
6803                            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-40.))),
6804                            ..Default::default()
6805                        },
6806                        window,
6807                        cx,
6808                    );
6809                });
6810            })
6811            .unwrap();
6812        assert_eq!(notifications.get(), 1, "an unchanged input must stay quiet");
6813    }
6814
6815    /// Hover must reach the whole visible editor after scrolling, not only
6816    /// the part that overlaps the unscrolled text bounds.
6817    #[gpui::test]
6818    fn test_hover_reaches_bottom_of_scrolled_editor(cx: &mut TestAppContext) {
6819        use crate::input::{Diagnostic, Position};
6820        struct Frame(Entity<crate::input::EditorState>);
6821        impl Render for Frame {
6822            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6823                crate::input::InputBase::new("frame")
6824                    .h(px(100.))
6825                    .child(crate::input::Editor::new(&self.0))
6826            }
6827        }
6828
6829        cx.update(crate::init);
6830        let text = (0..100).map(|i| format!("line {i}\n")).collect::<String>();
6831        let mut input = None;
6832        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6833            let state = cx.new(|cx| crate::input::EditorState::new(window, cx).default_value(text));
6834            input = Some(state.clone());
6835            Frame(state)
6836        });
6837        let input = input.unwrap();
6838        let mut cx = VisualTestContext::from_window(window.into(), cx);
6839        input.update(&mut cx, |state, _| {
6840            state.diagnostics_mut().unwrap().push(Diagnostic::new(
6841                Position::new(0, 0)..Position::new(100, 0),
6842                "everywhere",
6843            ));
6844        });
6845        cx.update(|window, cx| window.draw(cx).clear(cx));
6846        cx.update(|window, cx| {
6847            input.update(cx, |state, cx| {
6848                state.on_scroll_wheel(
6849                    &ScrollEvent {
6850                        delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-400.))),
6851                        ..Default::default()
6852                    },
6853                    window,
6854                    cx,
6855                );
6856            })
6857        });
6858        cx.update(|window, cx| window.draw(cx).clear(cx));
6859
6860        let bounds = input.read_with(&cx, |state, _| state.input_bounds);
6861        cx.simulate_mouse_move(
6862            point(bounds.center().x, bounds.bottom() - px(5.)),
6863            None,
6864            gpui::Modifiers::default(),
6865        );
6866        assert!(
6867            input.read_with(&cx, |state, _| state.diagnostic_popover().is_some()),
6868            "no hover at the bottom of a scrolled editor, bounds {bounds:?}"
6869        );
6870    }
6871
6872    #[gpui::test]
6873    fn test_cursor_layout_consumer_updates_after_selection(cx: &mut TestAppContext) {
6874        use std::{cell::Cell, rc::Rc};
6875        struct Panel {
6876            input: Entity<crate::input::EditorState>,
6877            observed: Rc<Cell<Option<(Bounds<Pixels>, Pixels)>>>,
6878        }
6879        impl Render for Panel {
6880            fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
6881                self.observed.set(self.input.read(cx).cursor_layout());
6882                div().size_full().child(self.input.clone())
6883            }
6884        }
6885        struct Root(Entity<Panel>);
6886        impl Render for Root {
6887            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6888                div().size_full().child(
6889                    self.0
6890                        .clone()
6891                        .cached(gpui::StyleRefinement::default().size_full()),
6892                )
6893            }
6894        }
6895        cx.update(crate::init);
6896        let observed = Rc::new(Cell::new(None));
6897        let mut input = None;
6898        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6899            let state = cx.new(|cx| {
6900                let mut state = crate::input::EditorState::new(window, cx);
6901                state.set_value("abcdefgh", window, cx);
6902                state
6903            });
6904            input = Some(state.clone());
6905            Root(cx.new(|_| Panel {
6906                input: state,
6907                observed: observed.clone(),
6908            }))
6909        });
6910        cx.run_until_parked();
6911        let input = input.unwrap();
6912        let before = input.read_with(cx, |state, _| state.cursor_layout());
6913        // The caret geometry only exists once the input has painted, and a
6914        // notify sent from inside a draw marks the view dirty without asking
6915        // for another frame, so the consumer reads the geometry on the next
6916        // invalidation rather than during the paint that produced it.
6917        input.update(cx, |_, cx| cx.notify());
6918        cx.run_until_parked();
6919        assert_eq!(observed.get(), before);
6920        window
6921            .update(cx, |_, _, cx| {
6922                input.update(cx, |state, cx| state.select_to_with_affinity(3, false, cx));
6923            })
6924            .unwrap();
6925        cx.run_until_parked();
6926        window.update(cx, |_, _, cx| cx.notify()).unwrap();
6927        let after = input.read_with(cx, |state, _| state.cursor_layout());
6928        assert_ne!(after, before, "the caret must actually move");
6929        assert_eq!(
6930            observed.get(),
6931            after,
6932            "render consumers must receive the newly painted caret geometry"
6933        );
6934    }
6935
6936    #[gpui::test]
6937    fn test_input_scroll_offset_notifies_only_on_change(cx: &mut TestAppContext) {
6938        use std::{cell::Cell, rc::Rc};
6939
6940        cx.update(crate::init);
6941        let mut input = None;
6942        let _window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
6943            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
6944            gpui::EmptyView
6945        });
6946        let input = input.unwrap();
6947        let notifications = Rc::new(Cell::new(0));
6948        let count = notifications.clone();
6949        let _subscription =
6950            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
6951
6952        input.update(cx, |state, _| {
6953            state.input_bounds = Bounds::new(Point::default(), size(px(100.), px(20.)));
6954            state.scroll_size = size(px(300.), px(20.));
6955        });
6956        for target in [
6957            None,
6958            Some(point(px(0.), px(0.))),
6959            Some(point(px(20.), px(50.))),
6960        ] {
6961            input.update(cx, |state, cx| state.update_scroll_offset(target, cx));
6962            assert_eq!(
6963                notifications.get(),
6964                0,
6965                "an unchanged clamped offset must stay quiet"
6966            );
6967        }
6968        input.update(cx, |state, cx| {
6969            state.update_scroll_offset(Some(point(px(-40.), px(0.))), cx);
6970            assert_eq!(state.scroll_handle.offset(), point(px(-40.), px(0.)));
6971        });
6972        assert_eq!(notifications.get(), 1, "a scroll must notify");
6973        input.update(cx, |state, cx| {
6974            state.update_scroll_offset(Some(point(px(-400.), px(50.))), cx);
6975            assert_eq!(state.scroll_handle.offset(), point(px(-200.), px(0.)));
6976        });
6977        assert_eq!(notifications.get(), 2);
6978        input.update(cx, |state, cx| {
6979            state.update_scroll_offset(Some(point(px(-500.), px(0.))), cx);
6980        });
6981        assert_eq!(
6982            notifications.get(),
6983            2,
6984            "clamping to the current offset must stay quiet"
6985        );
6986        input.update(cx, |state, cx| {
6987            state.scroll_size.width = px(110.);
6988            state.update_scroll_offset(None, cx);
6989            assert_eq!(state.scroll_handle.offset(), point(px(-10.), px(0.)));
6990        });
6991        assert_eq!(
6992            notifications.get(),
6993            3,
6994            "a smaller scroll range must clamp and notify"
6995        );
6996    }
6997
6998    #[gpui::test]
6999    fn test_set_line_number_notifies_only_on_change(cx: &mut TestAppContext) {
7000        use std::{cell::Cell, rc::Rc};
7001
7002        cx.update(crate::init);
7003        let mut input = None;
7004        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
7005            input = Some(cx.new(|cx| crate::input::EditorState::new(window, cx)));
7006            gpui::EmptyView
7007        });
7008        let input = input.unwrap();
7009        let shown = input.read_with(cx, |state, _| state.mode.line_number());
7010        let notifications = Rc::new(Cell::new(0));
7011        let count = notifications.clone();
7012        let _subscription =
7013            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
7014
7015        window
7016            .update(cx, |_, window, cx| {
7017                input.update(cx, |state, cx| state.set_line_number(shown, window, cx))
7018            })
7019            .unwrap();
7020        assert_eq!(notifications.get(), 0);
7021
7022        window
7023            .update(cx, |_, window, cx| {
7024                input.update(cx, |state, cx| state.set_line_number(!shown, window, cx))
7025            })
7026            .unwrap();
7027        assert_eq!(notifications.get(), 1);
7028        assert_eq!(
7029            input.read_with(cx, |state, _| state.mode.line_number()),
7030            !shown
7031        );
7032    }
7033
7034    #[gpui::test]
7035    fn test_set_value_on_unfocused_input_stays_quiet(cx: &mut TestAppContext) {
7036        use std::{cell::Cell, rc::Rc};
7037
7038        cx.update(crate::init);
7039        let mut input = None;
7040        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
7041            input = Some(cx.new(|cx| crate::input::InputState::new(window, cx)));
7042            gpui::EmptyView
7043        });
7044        let input = input.unwrap();
7045        cx.run_until_parked();
7046
7047        let notifications = Rc::new(Cell::new(0));
7048        let count = notifications.clone();
7049        let _subscription =
7050            cx.update(|cx| cx.observe(&input, move |_, _| count.set(count.get() + 1)));
7051        cx.run_until_parked();
7052
7053        // Seeding a form is a write, so it notifies once. The input is not
7054        // focused and draws no caret, so nothing may notify after that.
7055        window
7056            .update(cx, |_, window, cx| {
7057                input.update(cx, |state, cx| state.set_value("seeded", window, cx));
7058            })
7059            .unwrap();
7060        cx.run_until_parked();
7061        let settled = notifications.get();
7062
7063        cx.executor()
7064            .advance_clock(std::time::Duration::from_secs(3));
7065        cx.run_until_parked();
7066
7067        assert_eq!(
7068            notifications.get(),
7069            settled,
7070            "an unfocused input is blinking, and every blink repaints the view it is in"
7071        );
7072    }
7073
7074    #[gpui::test]
7075    fn test_input_does_not_invalidate_cached_parent_during_paint(cx: &mut TestAppContext) {
7076        use std::{cell::Cell, rc::Rc};
7077
7078        struct Panel {
7079            input: Entity<crate::input::InputState>,
7080            renders: Rc<Cell<usize>>,
7081        }
7082        impl Render for Panel {
7083            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
7084                self.renders.set(self.renders.get() + 1);
7085                div().size_full().child(self.input.clone())
7086            }
7087        }
7088        struct Root(Entity<Panel>);
7089        impl Render for Root {
7090            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
7091                div().size_full().child(
7092                    self.0
7093                        .clone()
7094                        .cached(gpui::StyleRefinement::default().size_full()),
7095                )
7096            }
7097        }
7098
7099        cx.update(crate::init);
7100        let renders = Rc::new(Cell::new(0));
7101        let mut input = None;
7102        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
7103            let state = cx.new(|cx| crate::input::InputState::new(window, cx));
7104            input = Some(state.clone());
7105            Root(cx.new(|_| Panel {
7106                input: state,
7107                renders: renders.clone(),
7108            }))
7109        });
7110        cx.run_until_parked();
7111        let before = renders.get();
7112        assert!(before > 0);
7113        for _ in 0..60 {
7114            window.update(cx, |_, _, cx| cx.notify()).unwrap();
7115        }
7116        assert_eq!(
7117            renders.get(),
7118            before,
7119            "an idle input must not invalidate its cached parent"
7120        );
7121
7122        let input = input.unwrap();
7123        window
7124            .update(cx, |_, window, cx| {
7125                input.update(cx, |state, cx| state.set_value("changed", window, cx));
7126            })
7127            .unwrap();
7128        assert!(
7129            renders.get() > before,
7130            "text changes must invalidate the parent"
7131        );
7132        // The first paint reports the changed text extent to layout consumers.
7133        window.update(cx, |_, _, cx| cx.notify()).unwrap();
7134        let before = renders.get();
7135        for _ in 0..60 {
7136            window.update(cx, |_, _, cx| cx.notify()).unwrap();
7137        }
7138        assert_eq!(renders.get(), before, "the edited input must settle again");
7139
7140        cx.simulate_window_resize(window.into(), size(px(240.), px(100.)));
7141        cx.run_until_parked();
7142        assert!(renders.get() > before, "resizing must invalidate layout");
7143    }
7144
7145    #[gpui::test]
7146    fn textarea_cursor_treats_crlf_as_one_newline(cx: &mut TestAppContext) {
7147        cx.update(crate::init);
7148        let view = InputView::build_textarea(cx, |state| state);
7149        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
7150        cx.update(|window, cx| {
7151            view.input.update(cx, |state, cx| {
7152                for original in ["\r\nlast", "\u{feff}first\r\nlast\n", "first\nlast\r\n"] {
7153                    state.set_value(original, window, cx);
7154                    let newline = original.find('\n').unwrap();
7155                    let end = if original.as_bytes()[newline.saturating_sub(1)] == b'\r' {
7156                        newline - 1
7157                    } else {
7158                        newline
7159                    };
7160                    state.end(&MoveEnd, window, cx);
7161                    assert_eq!(state.cursor(), end);
7162                    state.right(&MoveRight, window, cx);
7163                    assert_eq!(state.cursor(), newline + 1);
7164                    state.left(&MoveLeft, window, cx);
7165                    assert_eq!(state.cursor(), end);
7166                    state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
7167                    assert_eq!(state.selected_range(), end..end);
7168                    state.move_to_end(&MoveToEnd, window, cx);
7169                    assert_eq!(state.cursor(), original.len());
7170                    state.move_to_start(&MoveToStart, window, cx);
7171                    assert_eq!(state.cursor(), 0);
7172                    assert_eq!(state.value().as_bytes(), original.as_bytes());
7173                }
7174                // A lone CR is an ordinary character; it must not skip its neighbour.
7175                state.set_value("a\rb", window, cx);
7176                state.right(&MoveRight, window, cx);
7177                assert_eq!(state.cursor(), 1);
7178                state.right(&MoveRight, window, cx);
7179                assert_eq!(state.cursor(), 2);
7180            })
7181        });
7182    }
7183
7184    #[gpui::test]
7185    fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) {
7186        cx.update(crate::init);
7187
7188        // A single-line input keeps its caret in view by moving its own offset;
7189        // it has no viewport to drag, so a scrollbar in a text field is a
7190        // control that does not exist.
7191        let single = InputView::build(cx, |state| state);
7192        single
7193            .input
7194            .update(cx, |state, _| assert!(!state.shows_scrollbar()));
7195
7196        let multi = InputView::build_textarea(cx, |state| state);
7197        multi
7198            .input
7199            .update(cx, |state, _| assert!(state.shows_scrollbar()));
7200    }
7201
7202    #[gpui::test]
7203    fn context_menu_handler_is_deferred_and_respects_disabled(cx: &mut TestAppContext) {
7204        use std::{cell::Cell, rc::Rc};
7205        cx.update(crate::init);
7206        let input_view = InputView::new(cx);
7207        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7208        let input = input_view.input;
7209        let calls = Rc::new(Cell::new(0usize));
7210        let items = Rc::new(Cell::new(0usize));
7211        let menu_focus = cx.update(|_, cx| cx.focus_handle());
7212
7213        cx.update(|window, cx| {
7214            input.update(cx, |state, cx| {
7215                let calls2 = calls.clone();
7216                let items2 = items.clone();
7217                let menu_focus = menu_focus.clone();
7218                state.on_context_menu(Rc::new(move |menu, _, _, window, cx| {
7219                    calls2.set(calls2.get() + 1);
7220                    items2.set(menu.items.len());
7221                    menu_focus.focus(window, cx);
7222                }));
7223                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
7224            })
7225        });
7226        assert_eq!(calls.get(), 1);
7227        assert_eq!(items.get(), 0);
7228        input.read_with(&cx, |state, _| {
7229            assert_eq!(
7230                state
7231                    .selection_focus
7232                    .as_ref()
7233                    .and_then(WeakFocusHandle::upgrade)
7234                    .as_ref(),
7235                Some(&menu_focus),
7236                "the menu callback must retain selection without reborrowing its input"
7237            );
7238        });
7239
7240        // A custom handler may keep using the same, already-focused menu.
7241        cx.update(|window, cx| {
7242            input.update(cx, |state, cx| {
7243                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
7244            });
7245        });
7246        assert_eq!(calls.get(), 2);
7247        cx.update(|window, cx| {
7248            assert!(input.read(cx).has_selection_focus(window, cx));
7249        });
7250
7251        cx.update(|window, cx| {
7252            input.update(cx, |state, cx| {
7253                state.disabled = true;
7254                state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
7255            })
7256        });
7257        assert_eq!(calls.get(), 2);
7258    }
7259
7260    #[gpui::test]
7261    fn context_menu_handler_can_read_its_input(cx: &mut TestAppContext) {
7262        use std::{cell::Cell, rc::Rc};
7263        cx.update(crate::init);
7264        let input_view = InputView::new(cx);
7265        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7266        let input = input_view.input;
7267        let cursor = Rc::new(Cell::new(None));
7268
7269        cx.update(|window, cx| {
7270            input.update(cx, |state, cx| {
7271                state.set_value("hello", window, cx);
7272                let weak = cx.weak_entity();
7273                let cursor = cursor.clone();
7274                state.on_context_menu(Rc::new(move |_, _, _, _, cx| {
7275                    cursor.set(weak.upgrade().map(|input| input.read(cx).cursor()));
7276                }));
7277                state.handle_right_click_menu(point(px(0.), px(0.)), 2, window, cx);
7278            })
7279        });
7280        assert_eq!(cursor.get(), Some(2));
7281    }
7282
7283    #[gpui::test]
7284    fn test_readonly_rejects_user_edits_only(cx: &mut TestAppContext) {
7285        let input_view = InputView::new(cx);
7286        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7287        let input = input_view.input;
7288
7289        cx.update(|window, cx| {
7290            input.update(cx, |state, cx| {
7291                state.set_value("hello", window, cx);
7292                state.set_readonly(true, cx);
7293            });
7294        });
7295
7296        cx.update(|_, cx| {
7297            input.read_with(cx, |state, _| {
7298                assert!(!state.is_editable());
7299                assert!(!state.is_replaceable());
7300            });
7301        });
7302
7303        // Typing (and IME) goes through the input handler, it must be rejected.
7304        cx.update(|window, cx| {
7305            input.update(cx, |state, cx| {
7306                state.replace_text_in_range(None, " world", window, cx);
7307                state.replace_and_mark_text_in_range(None, "あ", None, window, cx);
7308            });
7309        });
7310        cx.update(|_, cx| {
7311            input.read_with(cx, |state, _| assert_eq!(state.value(), "hello"));
7312        });
7313
7314        // The programmatic APIs are not limited by the readonly mode.
7315        cx.update(|window, cx| {
7316            input.update(cx, |state, cx| {
7317                state.insert(" world", window, cx);
7318                state.set_value("changed", window, cx);
7319            });
7320        });
7321        cx.update(|_, cx| {
7322            input.read_with(cx, |state, _| assert_eq!(state.value(), "changed"));
7323        });
7324
7325        // And the user can edit again after leaving the readonly mode.
7326        // The caret is at the start, because `set_value` has reset the selection.
7327        cx.update(|window, cx| {
7328            input.update(cx, |state, cx| {
7329                state.set_readonly(false, cx);
7330                state.replace_text_in_range(None, "!", window, cx);
7331            });
7332        });
7333        cx.update(|_, cx| {
7334            input.read_with(cx, |state, _| {
7335                assert!(state.is_editable());
7336                assert_eq!(state.value(), "!changed");
7337            });
7338        });
7339    }
7340
7341    /// Regression test: editing at a caret that sits far outside the viewport
7342    /// must reveal it in one frame. The cursor-follow in `layout_cursors` runs
7343    /// only on the frame the selection changes, so its old one-line step left
7344    /// the caret offscreen after typing at the end of a long paste.
7345    #[gpui::test]
7346    fn test_edit_reveals_far_offscreen_caret(cx: &mut TestAppContext) {
7347        let mut input = None;
7348        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
7349            cx.set_global(Theme::default());
7350            super::super::init(cx);
7351            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 6));
7352            input = Some(state.clone());
7353            TestRoot(state)
7354        });
7355        let mut cx = VisualTestContext::from_window(window.into(), cx);
7356        let input = input.unwrap();
7357
7358        let text: String = (1..=100)
7359            .map(|i| format!("line {i}"))
7360            .collect::<Vec<_>>()
7361            .join("\n");
7362        cx.update(|window, cx| {
7363            input.update(cx, |state, cx| {
7364                state.set_value(text, window, cx);
7365                let end = state.text.len();
7366                state.set_selection(end, end);
7367            });
7368        });
7369        cx.run_until_parked();
7370
7371        // The user scrolled back to the top to read the pasted text.
7372        cx.update(|_, cx| {
7373            input.update(cx, |state, cx| {
7374                state.scroll_handle.set_offset(point(px(0.), px(0.)));
7375                cx.notify();
7376            });
7377        });
7378        cx.run_until_parked();
7379
7380        cx.update(|window, cx| {
7381            input.update(cx, |state, cx| {
7382                state.replace_text_in_range(None, "X", window, cx);
7383            });
7384        });
7385        cx.run_until_parked();
7386
7387        cx.update(|_, cx| {
7388            input.read_with(cx, |state, _| {
7389                assert!(state.value().ends_with("line 100X"));
7390                let (caret, _) = state.cursor_layout().expect("caret painted");
7391                let viewport = state.input_bounds();
7392                let scroll_y = state.scroll_handle.offset().y;
7393                let top = caret.origin.y - viewport.origin.y + scroll_y;
7394                assert!(
7395                    top >= px(0.) && top + caret.size.height <= viewport.size.height,
7396                    "caret top {top:?} outside viewport height {:?} (scroll {scroll_y:?})",
7397                    viewport.size.height,
7398                );
7399            });
7400        });
7401    }
7402
7403    /// Regression test: changing the auto-grow limits keeps the rows fitted to
7404    /// the text. `set_auto_grow` used to start over at `min_rows`, and the rows
7405    /// were counted again only on the next edit or wrap width change.
7406    #[gpui::test]
7407    fn test_set_auto_grow_keeps_rows_fitted(cx: &mut TestAppContext) {
7408        let mut input = None;
7409        let window = cx.open_window(size(px(720.), px(400.)), |window, cx| {
7410            cx.set_global(Theme::default());
7411            super::super::init(cx);
7412            let state = cx.new(|cx| crate::input::TextareaState::new(window, cx).auto_grow(1, 10));
7413            input = Some(state.clone());
7414            TestRoot(state)
7415        });
7416        let mut cx = VisualTestContext::from_window(window.into(), cx);
7417        let input = input.unwrap();
7418
7419        let text: String = (1..=8)
7420            .map(|i| format!("line {i}"))
7421            .collect::<Vec<_>>()
7422            .join("\n");
7423        cx.update(|window, cx| {
7424            input.update(cx, |state, cx| state.set_value(text, window, cx));
7425        });
7426        cx.run_until_parked();
7427        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 8)));
7428
7429        // A larger maximum keeps all eight rows.
7430        cx.update(|_, cx| input.update(cx, |state, cx| state.set_auto_grow(1, 12, cx)));
7431        cx.run_until_parked();
7432        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 8)));
7433
7434        // A smaller maximum caps them.
7435        cx.update(|_, cx| input.update(cx, |state, cx| state.set_auto_grow(1, 4, cx)));
7436        cx.run_until_parked();
7437        cx.update(|_, cx| input.read_with(cx, |state, _| assert_eq!(state.mode.rows(), 4)));
7438    }
7439
7440    /// Regression test: `scroll_to` at end-of-buffer must produce a deferred
7441    /// scroll target within the safe scroll range, so the painted frame
7442    /// matches what `update_scroll_offset` persists (no jitter). A small
7443    /// `cursor_surrounding_lines` override used to mismatch the hardcoded
7444    /// 3-line edge clearance in `scroll_to`, overshooting `safe_y_min`.
7445    #[gpui::test]
7446    fn test_scroll_to_eob_does_not_overshoot_safe_range(cx: &mut TestAppContext) {
7447        let input_view = InputView::new(cx);
7448        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7449        let input = input_view.input;
7450
7451        // JetBrains-style: 1 trailing empty row + 1-line cursor surrounding.
7452        cx.update(|window, cx| {
7453            input.update(cx, |state, cx| {
7454                state.set_scroll_beyond_last_line(Some(1), window, cx);
7455                state.set_cursor_surrounding_lines(Some(1), window, cx);
7456                let text: String = (1..=50)
7457                    .map(|i| format!("line {i}"))
7458                    .collect::<Vec<_>>()
7459                    .join("\n");
7460                state.set_value(text, window, cx);
7461            });
7462        });
7463        cx.run_until_parked();
7464
7465        // Sanity: paint populated `scroll_size` and `input_bounds` — without
7466        // these, `safe_y_min` below collapses to 0 and the assertion is vacuous.
7467        cx.update(|_, cx| {
7468            input.read_with(cx, |state, _| {
7469                assert!(
7470                    state.scroll_size.height > px(0.),
7471                    "scroll_size not populated by initial paint"
7472                );
7473                assert!(
7474                    state.input_bounds.size.height > px(0.),
7475                    "input_bounds not populated by initial paint"
7476                );
7477            });
7478        });
7479
7480        // Move cursor to end with downward direction — same code path as a
7481        // `Down` keystroke at EOB. `scroll_to` runs synchronously inside
7482        // `move_to`; inspect `deferred_scroll_offset` in the same closure
7483        // before the next paint consumes and clears it.
7484        cx.update(|_, cx| {
7485            input.update(cx, |state, cx| {
7486                let end = state.text.len();
7487                state.move_to(end, Some(MoveDirection::Down), cx);
7488
7489                let deferred = state
7490                    .deferred_scroll_offset
7491                    .expect("scroll_to should populate deferred_scroll_offset");
7492                let safe_y_min =
7493                    (-state.scroll_size.height + state.input_bounds.size.height).min(px(0.));
7494
7495                assert!(
7496                    deferred.y >= safe_y_min,
7497                    "deferred_scroll_offset.y = {:?} below safe_y_min = {:?} \
7498                     — paint would jitter (Bug C regression)",
7499                    deferred.y,
7500                    safe_y_min,
7501                );
7502            });
7503        });
7504    }
7505
7506    #[gpui::test]
7507    fn test_next_search_match_reveals_with_padding_after_manual_scroll(cx: &mut TestAppContext) {
7508        assert_search_reveals_with_padding_after_manual_scroll(false, cx);
7509    }
7510
7511    #[gpui::test]
7512    fn test_previous_search_match_reveals_with_padding_after_manual_scroll(
7513        cx: &mut TestAppContext,
7514    ) {
7515        assert_search_reveals_with_padding_after_manual_scroll(true, cx);
7516    }
7517
7518    fn assert_search_reveals_with_padding_after_manual_scroll(
7519        previous: bool,
7520        cx: &mut TestAppContext,
7521    ) {
7522        let input_view = InputView::new(cx);
7523        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7524        let input = input_view.input;
7525        let text = (0..160)
7526            .map(|row| {
7527                if matches!(row, 20 | 60 | 100) {
7528                    format!("match on row {row}")
7529                } else {
7530                    format!("line {row}")
7531                }
7532            })
7533            .collect::<Vec<_>>()
7534            .join("\n");
7535        let start = text.match_indices("match").nth(1).unwrap().0;
7536        let expected_match = start..start + "match".len();
7537        cx.update(|window, cx| {
7538            input.update(cx, |state, cx| {
7539                state.set_cursor_surrounding_lines(Some(3), window, cx);
7540                state.set_value(text, window, cx);
7541                state.set_search_query("match", true, cx);
7542                if previous {
7543                    state.search_session.matcher.next();
7544                    state.search_session.matcher.next();
7545                }
7546            });
7547        });
7548        cx.run_until_parked();
7549        cx.update(|_, cx| {
7550            input.update(cx, |state, cx| {
7551                let line_height = state.last_layout.as_ref().unwrap().line_height;
7552                let y = if previous { px(0.) } else { -line_height * 80. };
7553                state.set_scroll_offset(point(px(0.), y), cx);
7554            });
7555        });
7556        cx.run_until_parked();
7557        input.read_with(&cx, |state, _| {
7558            let visible = state.visible_row_range().unwrap();
7559            if previous {
7560                assert!(visible.end <= 60, "target must be below the viewport");
7561            } else {
7562                assert!(visible.start > 60, "target must be above the viewport");
7563            }
7564        });
7565        cx.update(|_, cx| {
7566            input.update(cx, |state, cx| {
7567                let range = if previous {
7568                    state.previous_search_match(cx)
7569                } else {
7570                    state.next_search_match(cx)
7571                };
7572                assert_eq!(range, Some(expected_match));
7573                assert_eq!(state.search_session.matcher.label(), "2/3");
7574            });
7575        });
7576        cx.run_until_parked();
7577        input.read_with(&cx, |state, _| {
7578            assert!(state.visible_row_range().unwrap().contains(&60));
7579            let line_height = state.last_layout.as_ref().unwrap().line_height;
7580            let target_y = line_height * 60. + state.scroll_handle.offset().y;
7581            // Three lines of edge clearance include the matched line itself.
7582            assert!(target_y >= line_height * 2. - px(0.1));
7583            assert!(
7584                target_y + line_height * 3.
7585                    <= state.last_bounds.as_ref().unwrap().size.height + px(0.1),
7586                "search must preserve the configured surrounding-line padding"
7587            );
7588        });
7589    }
7590
7591    /// A host that wants the search shortcut for its own search UI.
7592    struct SearchHost {
7593        editor: Entity<InputBaseState<EditorMode>>,
7594        search_requests: Rc<Cell<usize>>,
7595    }
7596
7597    impl Render for SearchHost {
7598        fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
7599            let search_requests = self.search_requests.clone();
7600            div()
7601                .size_full()
7602                .on_action(cx.listener(move |_, _: &Search, _, _| {
7603                    search_requests.set(search_requests.get() + 1);
7604                }))
7605                .child(self.editor.clone())
7606        }
7607    }
7608
7609    /// Opens an editor inside [`SearchHost`], focused, and presses the search
7610    /// shortcut once. Returns the editor and the host's request count.
7611    fn press_search_shortcut(
7612        cx: &mut TestAppContext,
7613        searchable: bool,
7614    ) -> (Entity<InputBaseState<EditorMode>>, Rc<Cell<usize>>) {
7615        let search_requests = Rc::new(Cell::new(0));
7616        let mut editor = None;
7617        let window = cx.update(|cx| {
7618            cx.open_window(Default::default(), |window, cx| {
7619                cx.set_global(Theme::default());
7620                super::super::init(cx);
7621                let state =
7622                    cx.new(|cx| crate::input::EditorState::new(window, cx).searchable(searchable));
7623                editor = Some(state.clone());
7624                cx.new(|_| SearchHost {
7625                    editor: state,
7626                    search_requests: search_requests.clone(),
7627                })
7628            })
7629            .unwrap()
7630        });
7631        let editor = editor.unwrap();
7632        let mut cx = VisualTestContext::from_window(window.into(), cx);
7633        cx.update(|window, cx| {
7634            editor.update(cx, |state, cx| state.focus(window, cx));
7635        });
7636        cx.run_until_parked();
7637        #[cfg(target_os = "macos")]
7638        cx.simulate_keystrokes("cmd-f");
7639        #[cfg(not(target_os = "macos"))]
7640        cx.simulate_keystrokes("ctrl-f");
7641        cx.run_until_parked();
7642        (editor, search_requests)
7643    }
7644
7645    #[gpui::test]
7646    fn test_search_shortcut_reaches_the_host_when_not_searchable(cx: &mut TestAppContext) {
7647        let (editor, search_requests) = press_search_shortcut(cx, false);
7648        assert_eq!(search_requests.get(), 1);
7649        editor.read_with(cx, |state, _| {
7650            assert!(!state.search_session().open);
7651            assert!(!state.search_session().is_active());
7652        });
7653    }
7654
7655    #[gpui::test]
7656    fn test_search_shortcut_opens_the_panel_when_searchable(cx: &mut TestAppContext) {
7657        let (editor, search_requests) = press_search_shortcut(cx, true);
7658        assert_eq!(search_requests.get(), 0);
7659        editor.read_with(cx, |state, _| {
7660            assert!(state.search_session().open);
7661            assert!(state.search_session().is_active());
7662        });
7663    }
7664
7665    #[gpui::test]
7666    fn test_set_search_query_highlights_without_the_panel(cx: &mut TestAppContext) {
7667        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
7668        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7669        let input = input_view.input;
7670        cx.update(|window, cx| {
7671            input.update(cx, |state, cx| {
7672                state.set_value("foo bar foo", window, cx);
7673                state.set_search_query("foo", true, cx);
7674            });
7675        });
7676        cx.run_until_parked();
7677        input.read_with(&cx, |state, _| {
7678            let session = state.search_session();
7679            assert!(session.is_active());
7680            assert!(!session.open);
7681            assert_eq!(session.matcher.len(), 2);
7682        });
7683        cx.update(|_, cx| {
7684            input.update(cx, |state, cx| state.close_search(cx));
7685        });
7686        input.read_with(&cx, |state, _| {
7687            assert!(!state.search_session().is_active());
7688        });
7689    }
7690
7691    /// A closed search skips rescanning on edits, so every path that reads
7692    /// the matches again must see the edited text, not the text at close.
7693    #[gpui::test]
7694    fn test_closed_search_resyncs_matches_after_edits(cx: &mut TestAppContext) {
7695        let input_view = InputView::build_editor(cx, |state| state.searchable(false));
7696        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7697        let input = input_view.input;
7698        cx.update(|window, cx| {
7699            input.update(cx, |state, cx| {
7700                state.set_value("foo bar foo", window, cx);
7701                state.set_search_query("foo", true, cx);
7702                state.close_search(cx);
7703
7704                state.set_value("bar foo", window, cx);
7705                assert_eq!(state.next_search_match(cx), Some(4..7));
7706
7707                state.set_value("foo foo foo", window, cx);
7708                state.set_search_query("foo", true, cx);
7709                assert_eq!(state.search_session().matcher.len(), 3);
7710            });
7711        });
7712    }
7713
7714    #[gpui::test]
7715    fn test_search_reveals_offscreen_wrapped_match(cx: &mut TestAppContext) {
7716        let input_view = InputView::new(cx);
7717        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7718        let input = input_view.input;
7719        let text = format!(
7720            "match\n{}\n{}match\n{}",
7721            "line\n".repeat(80),
7722            "wrapped text ".repeat(500),
7723            "line\n".repeat(80)
7724        );
7725        cx.update(|window, cx| {
7726            input.update(cx, |state, cx| {
7727                state.set_cursor_surrounding_lines(Some(3), window, cx);
7728                state.set_value(text, window, cx);
7729                state.set_search_query("match", true, cx);
7730            });
7731        });
7732        cx.run_until_parked();
7733        cx.update(|_, cx| {
7734            input.update(cx, |state, cx| {
7735                state.next_search_match(cx).unwrap();
7736            });
7737        });
7738        cx.run_until_parked();
7739        input.read_with(&cx, |state, _| {
7740            let range = state.search_session.matcher.matched_ranges()[1].clone();
7741            let layout = state.last_layout.as_ref().unwrap();
7742            let (_, _, position) = state.line_and_position_for_offset(range.end);
7743            let y = position.expect("wrapped match must be laid out").y
7744                + state.scroll_handle.offset().y;
7745            assert!(y >= layout.line_height * 2. - px(0.1));
7746            assert!(
7747                y + layout.line_height * 3. <= state.last_bounds.unwrap().size.height + px(0.1),
7748                "wrapped match must retain surrounding display rows"
7749            );
7750        });
7751    }
7752
7753    #[gpui::test]
7754    fn test_number_step(cx: &mut TestAppContext) {
7755        let input = InputView::build(cx, |state| state).input;
7756
7757        cx.update(|cx| {
7758            input.update(cx, |_state, cx| {
7759                assert_eq!(
7760                    NumberStep::from(5.).value(123., StepAction::Increment, cx),
7761                    5.
7762                );
7763
7764                // The step can differ by direction at a boundary: at 1.0 it
7765                // is 0.1 going down and 0.5 going up.
7766                let step = NumberStep::by_value(|value, action, _cx| {
7767                    let below = match action {
7768                        StepAction::Increment => value < 1.0,
7769                        StepAction::Decrement => value <= 1.0,
7770                    };
7771                    if below { 0.1 } else { 0.5 }
7772                });
7773                assert_eq!(step.value(0.5, StepAction::Increment, cx), 0.1);
7774                assert_eq!(step.value(1.0, StepAction::Increment, cx), 0.5);
7775                assert_eq!(step.value(1.0, StepAction::Decrement, cx), 0.1);
7776                assert_eq!(step.value(2.0, StepAction::Decrement, cx), 0.5);
7777            });
7778        });
7779    }
7780
7781    #[gpui::test]
7782    fn test_number_input_normalization(cx: &mut TestAppContext) {
7783        let input_view = InputView::build(cx, |state| {
7784            state.mask_pattern(MaskPattern::Number {
7785                separator: None,
7786                fraction: None,
7787            })
7788        });
7789        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7790        let input = input_view.input;
7791
7792        // Full-width digits and the ideographic full stop are normalized,
7793        // and the cursor is at the end (in normalized bytes, not the
7794        // original 12 bytes).
7795        cx.update(|window, cx| {
7796            input.update(cx, |state, cx| {
7797                state.replace_text_in_range(None, "12。5", window, cx);
7798            });
7799        });
7800        cx.run_until_parked();
7801        cx.update(|_, cx| {
7802            input.read_with(cx, |state, _| {
7803                assert_eq!(state.value(), "12.5");
7804                let cursor: Range<usize> = state.selected_range();
7805                assert_eq!(cursor, 4..4);
7806            });
7807        });
7808
7809        // Non-numeric input is rejected.
7810        cx.update(|window, cx| {
7811            input.update(cx, |state, cx| {
7812                state.replace_text_in_range(None, "abc", window, cx);
7813            });
7814        });
7815        cx.run_until_parked();
7816        cx.update(|_, cx| {
7817            input.read_with(cx, |state, _| {
7818                assert_eq!(state.value(), "12.5");
7819            });
7820        });
7821
7822        // A bare leading dot is kept as-is (normalized from the ideographic
7823        // full stop), not completed to "0.", so it stays editable.
7824        cx.update(|window, cx| {
7825            input.update(cx, |state, cx| {
7826                let range = state.range_to_utf16(&(0..state.text.len()));
7827                state.replace_text_in_range(Some(range), "。", window, cx);
7828            });
7829        });
7830        cx.run_until_parked();
7831        cx.update(|_, cx| {
7832            input.read_with(cx, |state, _| {
7833                assert_eq!(state.value(), ".");
7834                let cursor: Range<usize> = state.selected_range();
7835                assert_eq!(cursor, 1..1);
7836            });
7837        });
7838    }
7839
7840    #[gpui::test]
7841    fn test_mask_pattern_accepts_non_ascii_text(cx: &mut TestAppContext) {
7842        let input_view = InputView::build(cx, |state| state.mask_pattern("9999年99月"));
7843        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7844        let input = input_view.input;
7845
7846        cx.update(|window, cx| {
7847            input.update(cx, |state, cx| {
7848                state.replace_text_in_range(None, "2024年12月", window, cx);
7849            });
7850        });
7851        cx.run_until_parked();
7852        cx.update(|_, cx| {
7853            input.read_with(cx, |state, _| {
7854                assert_eq!(state.value(), "2024年12月");
7855            });
7856        });
7857    }
7858
7859    #[gpui::test]
7860    fn test_number_input_normalization_with_separator(cx: &mut TestAppContext) {
7861        let input_view = InputView::build(cx, |state| {
7862            state.mask_pattern(MaskPattern::Number {
7863                separator: Some(','),
7864                fraction: Some(2),
7865            })
7866        });
7867        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7868        let input = input_view.input;
7869
7870        cx.update(|window, cx| {
7871            input.update(cx, |state, cx| {
7872                state.replace_text_in_range(None, "1234", window, cx);
7873            });
7874        });
7875        cx.run_until_parked();
7876        cx.update(|_, cx| {
7877            input.read_with(cx, |state, _| {
7878                assert_eq!(state.value(), "1,234");
7879                assert_eq!(state.unmask_value(), "1234");
7880            });
7881        });
7882    }
7883
7884    #[gpui::test]
7885    fn test_number_input_clamp_on_blur(cx: &mut TestAppContext) {
7886        let input_view = InputView::build(cx, |state| {
7887            state
7888                .mask_pattern(MaskPattern::Number {
7889                    separator: None,
7890                    fraction: None,
7891                })
7892                .min(10.)
7893                .max(100.)
7894        });
7895        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7896        let input = input_view.input;
7897
7898        // Out-of-range values are allowed while typing, and clamped on blur.
7899        cx.update(|window, cx| {
7900            input.update(cx, |state, cx| {
7901                state.replace_text_in_range(None, "1000", window, cx);
7902                assert_eq!(state.value(), "1000");
7903                state.clamp_number_value(window, cx);
7904                assert_eq!(state.value(), "100");
7905
7906                let range = state.range_to_utf16(&(0..state.text.len()));
7907                state.replace_text_in_range(Some(range), "1", window, cx);
7908                assert_eq!(state.value(), "1");
7909                state.clamp_number_value(window, cx);
7910                assert_eq!(state.value(), "10");
7911            });
7912        });
7913    }
7914
7915    #[gpui::test]
7916    fn test_number_input_undo_with_mask(cx: &mut TestAppContext) {
7917        let input_view = InputView::build(cx, |state| {
7918            state.mask_pattern(MaskPattern::Number {
7919                separator: Some(','),
7920                fraction: None,
7921            })
7922        });
7923        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7924        let input = input_view.input;
7925
7926        // When the mask changes the text (regrouping separators), a
7927        // whole-document change is recorded, so undo/redo can restore it.
7928        cx.update(|window, cx| {
7929            input.update(cx, |state, cx| {
7930                state.replace_text_in_range(None, "1234", window, cx);
7931                assert_eq!(state.value(), "1,234");
7932                state.replace_text_in_range(None, "5", window, cx);
7933                assert_eq!(state.value(), "12,345");
7934
7935                // Each whole-document mask rewrite is an atomic undo step.
7936                // Before the whole-document history fix, undo produced a
7937                // corrupted value like "1,2344".
7938                state.undo(&Undo, window, cx);
7939                assert_eq!(state.value(), "1,234");
7940                state.undo(&Undo, window, cx);
7941                assert_eq!(state.value(), "");
7942                state.redo(&Redo, window, cx);
7943                assert_eq!(state.value(), "1,234");
7944                state.redo(&Redo, window, cx);
7945                assert_eq!(state.value(), "12,345");
7946            });
7947        });
7948    }
7949
7950    #[gpui::test]
7951    fn test_undo_manager_coalesces_adjacent_typing_transactions(cx: &mut TestAppContext) {
7952        let input_view = InputView::build(cx, |state| state);
7953        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7954        let input = input_view.input;
7955
7956        cx.update(|window, cx| {
7957            input.update(cx, |state, cx| {
7958                state.replace_text_in_range(None, "a", window, cx);
7959                state.replace_text_in_range(None, "b", window, cx);
7960                assert_eq!(state.value(), "ab");
7961
7962                state.undo(&Undo, window, cx);
7963                assert_eq!(state.value(), "");
7964            });
7965        });
7966    }
7967
7968    #[gpui::test]
7969    fn test_undo_manager_cursor_movement_splits_typing(cx: &mut TestAppContext) {
7970        let input_view = InputView::build(cx, |state| state);
7971        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7972        let input = input_view.input;
7973
7974        cx.update(|window, cx| {
7975            input.update(cx, |state, cx| {
7976                state.replace_text_in_range(None, "a", window, cx);
7977                state.replace_text_in_range(None, "b", window, cx);
7978                state.left(&MoveLeft, window, cx);
7979                state.replace_text_in_range(None, "x", window, cx);
7980                assert_eq!(state.value(), "axb");
7981
7982                state.undo(&Undo, window, cx);
7983                assert_eq!(state.value(), "ab");
7984                state.undo(&Undo, window, cx);
7985                assert_eq!(state.value(), "");
7986            });
7987        });
7988    }
7989
7990    #[gpui::test]
7991    fn test_undo_manager_splits_backward_and_forward_delete(cx: &mut TestAppContext) {
7992        let input_view = InputView::build(cx, |state| state);
7993        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
7994        let input = input_view.input;
7995
7996        cx.update(|window, cx| {
7997            input.update(cx, |state, cx| {
7998                state.set_value("abcd", window, cx);
7999                state.set_selected_range(2..2, cx);
8000                state.backspace(&Backspace, window, cx);
8001                state.delete(&Delete, window, cx);
8002                assert_eq!(state.value(), "ad");
8003
8004                state.undo(&Undo, window, cx);
8005                assert_eq!(state.value(), "acd");
8006                state.undo(&Undo, window, cx);
8007                assert_eq!(state.value(), "abcd");
8008            });
8009        });
8010    }
8011
8012    #[gpui::test]
8013    fn test_undo_manager_coalesces_directional_character_deletes(cx: &mut TestAppContext) {
8014        let input_view = InputView::build(cx, |state| state);
8015        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8016        let input = input_view.input;
8017
8018        cx.update(|window, cx| {
8019            input.update(cx, |state, cx| {
8020                state.set_value("abcd", window, cx);
8021                state.backspace(&Backspace, window, cx);
8022                state.backspace(&Backspace, window, cx);
8023                assert_eq!(state.value(), "ab");
8024                state.undo(&Undo, window, cx);
8025                assert_eq!(state.value(), "abcd");
8026                assert_eq!(state.selected_range(), 4..4);
8027
8028                state.set_value("abcd", window, cx);
8029                state.set_selected_range(1..1, cx);
8030                state.delete(&Delete, window, cx);
8031                state.delete(&Delete, window, cx);
8032                assert_eq!(state.value(), "ad");
8033                state.undo(&Undo, window, cx);
8034                assert_eq!(state.value(), "abcd");
8035                assert_eq!(state.selected_range(), 1..1);
8036            });
8037        });
8038    }
8039
8040    #[gpui::test]
8041    fn test_undo_manager_atomic_paste_isolated_from_typing(cx: &mut TestAppContext) {
8042        let input_view = InputView::build(cx, |state| state);
8043        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8044        let input = input_view.input;
8045
8046        cx.update(|window, cx| {
8047            cx.write_to_clipboard(ClipboardItem::new_string("P".to_string()));
8048            input.update(cx, |state, cx| {
8049                state.replace_text_in_range(None, "a", window, cx);
8050                state.paste(&Paste, window, cx);
8051                state.replace_text_in_range(None, "b", window, cx);
8052                assert_eq!(state.value(), "aPb");
8053
8054                state.undo(&Undo, window, cx);
8055                assert_eq!(state.value(), "aP");
8056                state.undo(&Undo, window, cx);
8057                assert_eq!(state.value(), "a");
8058                state.undo(&Undo, window, cx);
8059                assert_eq!(state.value(), "");
8060            });
8061        });
8062    }
8063
8064    #[gpui::test]
8065    fn test_undo_manager_programmatic_insert_is_atomic(cx: &mut TestAppContext) {
8066        let input_view = InputView::build(cx, |state| state);
8067        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8068        let input = input_view.input;
8069
8070        cx.update(|window, cx| {
8071            input.update(cx, |state, cx| {
8072                state.replace_text_in_range(None, "a", window, cx);
8073                state.insert("P", window, cx);
8074                state.replace_text_in_range(None, "b", window, cx);
8075                assert_eq!(state.value(), "aPb");
8076
8077                state.undo(&Undo, window, cx);
8078                assert_eq!(state.value(), "aP");
8079                state.undo(&Undo, window, cx);
8080                assert_eq!(state.value(), "a");
8081            });
8082        });
8083    }
8084
8085    #[gpui::test]
8086    fn test_undo_manager_selection_round_trip_splits_typing(cx: &mut TestAppContext) {
8087        let input_view = InputView::build(cx, |state| state);
8088        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8089        let input = input_view.input;
8090
8091        cx.update(|window, cx| {
8092            input.update(cx, |state, cx| {
8093                state.replace_text_in_range(None, "a", window, cx);
8094                state.replace_text_in_range(None, "b", window, cx);
8095                state.select_all(window, cx);
8096                state.unselect(window, cx);
8097                state.replace_text_in_range(None, "c", window, cx);
8098
8099                state.undo(&Undo, window, cx);
8100                assert_eq!(state.value(), "ab");
8101                state.undo(&Undo, window, cx);
8102                assert_eq!(state.value(), "");
8103            });
8104        });
8105    }
8106
8107    #[gpui::test]
8108    fn test_undo_manager_enter_is_atomic(cx: &mut TestAppContext) {
8109        let input_view = InputView::build_textarea(cx, |state| state);
8110        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8111        let input = input_view.input;
8112
8113        cx.update(|window, cx| {
8114            input.update(cx, |state, cx| {
8115                state.replace_text_in_range(None, "a", window, cx);
8116                state.enter(
8117                    &Enter {
8118                        secondary: false,
8119                        shift: false,
8120                    },
8121                    window,
8122                    cx,
8123                );
8124                state.replace_text_in_range(None, "b", window, cx);
8125
8126                state.undo(&Undo, window, cx);
8127                assert_eq!(state.value(), "a\n");
8128                state.undo(&Undo, window, cx);
8129                assert_eq!(state.value(), "a");
8130                state.undo(&Undo, window, cx);
8131                assert_eq!(state.value(), "");
8132            });
8133        });
8134    }
8135
8136    #[gpui::test]
8137    fn test_undo_manager_single_line_return_commits_the_typing_session(cx: &mut TestAppContext) {
8138        let input_view = InputView::build(cx, |state| state);
8139        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8140        let input = input_view.input;
8141
8142        cx.update(|window, cx| {
8143            input.update(cx, |state, cx| {
8144                for part in ["a", "b", "c"] {
8145                    state.replace_text_in_range(None, part, window, cx);
8146                }
8147                state.enter(
8148                    &Enter {
8149                        secondary: false,
8150                        shift: false,
8151                    },
8152                    window,
8153                    cx,
8154                );
8155                for part in ["d", "e", "f"] {
8156                    state.replace_text_in_range(None, part, window, cx);
8157                }
8158                assert_eq!(state.value(), "abcdef");
8159
8160                state.undo(&Undo, window, cx);
8161                assert_eq!(state.value(), "abc");
8162                state.undo(&Undo, window, cx);
8163                assert_eq!(state.value(), "");
8164            });
8165        });
8166    }
8167
8168    #[gpui::test]
8169    fn test_undo_manager_submit_on_enter_commits_the_textarea_session(cx: &mut TestAppContext) {
8170        let input_view = InputView::build_textarea(cx, |state| state.submit_on_enter(true));
8171        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8172        let input = input_view.input;
8173
8174        cx.update(|window, cx| {
8175            input.update(cx, |state, cx| {
8176                state.replace_text_in_range(None, "before submit", window, cx);
8177                state.enter(
8178                    &Enter {
8179                        secondary: false,
8180                        shift: false,
8181                    },
8182                    window,
8183                    cx,
8184                );
8185                state.replace_text_in_range(None, " after submit", window, cx);
8186                assert_eq!(state.value(), "before submit after submit");
8187
8188                state.undo(&Undo, window, cx);
8189                assert_eq!(state.value(), "before submit");
8190                state.undo(&Undo, window, cx);
8191                assert_eq!(state.value(), "");
8192            });
8193        });
8194    }
8195
8196    #[gpui::test]
8197    fn test_undo_manager_blur_commits_the_typing_session(cx: &mut TestAppContext) {
8198        let input_view = InputView::build(cx, |state| state);
8199        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8200        let input = input_view.input;
8201
8202        cx.update(|window, cx| {
8203            input.update(cx, |state, cx| {
8204                state.replace_text_in_range(None, "before blur", window, cx);
8205                state.on_blur(window, cx);
8206                state.on_focus(window, cx);
8207                state.replace_text_in_range(None, " after focus", window, cx);
8208                assert_eq!(state.value(), "before blur after focus");
8209
8210                state.undo(&Undo, window, cx);
8211                assert_eq!(state.value(), "before blur");
8212                state.undo(&Undo, window, cx);
8213                assert_eq!(state.value(), "");
8214            });
8215        });
8216    }
8217
8218    #[gpui::test]
8219    fn test_undo_manager_keeps_rapid_lines_in_distinct_transactions(cx: &mut TestAppContext) {
8220        let input_view = InputView::build_textarea(cx, |state| state);
8221        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8222        let input = input_view.input;
8223
8224        cx.update(|window, cx| {
8225            input.update(cx, |state, cx| {
8226                let enter = Enter {
8227                    secondary: false,
8228                    shift: false,
8229                };
8230                state.replace_text_in_range(None, "a", window, cx);
8231                state.enter(&enter, window, cx);
8232                state.replace_text_in_range(None, "b", window, cx);
8233                state.enter(&enter, window, cx);
8234                state.replace_text_in_range(None, "c", window, cx);
8235                assert_eq!(state.value(), "a\nb\nc");
8236
8237                for expected in ["a\nb\n", "a\nb", "a\n", "a", ""] {
8238                    state.undo(&Undo, window, cx);
8239                    assert_eq!(state.value(), expected);
8240                }
8241            });
8242        });
8243    }
8244
8245    #[gpui::test]
8246    fn test_undo_manager_coalesces_long_unicode_typing_without_a_timer(cx: &mut TestAppContext) {
8247        let input_view = InputView::build_textarea(cx, |state| state);
8248        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8249        let input = input_view.input;
8250        let parts = [
8251            "The ",
8252            "quick ",
8253            "brown fox, ",
8254            "你好,世界 ",
8255            "🦀 jumps over 13 lazy dogs.",
8256        ];
8257        let expected = parts.concat();
8258
8259        cx.update(|window, cx| {
8260            input.update(cx, |state, cx| {
8261                for part in parts {
8262                    state.replace_text_in_range(None, part, window, cx);
8263                }
8264                assert_eq!(state.value(), expected);
8265
8266                state.undo(&Undo, window, cx);
8267                assert_eq!(state.value(), "");
8268                state.redo(&Redo, window, cx);
8269                assert_eq!(state.value(), expected);
8270            });
8271        });
8272    }
8273
8274    #[gpui::test]
8275    fn test_undo_manager_long_multiline_sequence_has_structural_boundaries(
8276        cx: &mut TestAppContext,
8277    ) {
8278        let input_view = InputView::build_textarea(cx, |state| state);
8279        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8280        let input = input_view.input;
8281        let enter = Enter {
8282            secondary: false,
8283            shift: false,
8284        };
8285
8286        cx.update(|window, cx| {
8287            input.update(cx, |state, cx| {
8288                for (index, line) in [
8289                    "first line with punctuation!",
8290                    "第二行包含 Unicode 🦀",
8291                    "third line has several words",
8292                ]
8293                .into_iter()
8294                .enumerate()
8295                {
8296                    for chunk in line.split_inclusive(' ') {
8297                        state.replace_text_in_range(None, chunk, window, cx);
8298                    }
8299                    if index < 2 {
8300                        state.enter(&enter, window, cx);
8301                    }
8302                }
8303
8304                assert_eq!(
8305                    state.value(),
8306                    "first line with punctuation!\n第二行包含 Unicode 🦀\nthird line has several words"
8307                );
8308                state.undo(&Undo, window, cx);
8309                assert_eq!(
8310                    state.value(),
8311                    "first line with punctuation!\n第二行包含 Unicode 🦀\n"
8312                );
8313                state.undo(&Undo, window, cx);
8314                assert_eq!(
8315                    state.value(),
8316                    "first line with punctuation!\n第二行包含 Unicode 🦀"
8317                );
8318                state.undo(&Undo, window, cx);
8319                assert_eq!(state.value(), "first line with punctuation!\n");
8320            });
8321        });
8322    }
8323
8324    #[gpui::test]
8325    fn test_masked_input_keeps_its_value_out_of_the_clipboard(cx: &mut TestAppContext) {
8326        let input_view = InputView::build(cx, |state| state);
8327        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8328        let input = input_view.input;
8329
8330        cx.update(|window, cx| {
8331            input.update(cx, |state, cx| {
8332                state.set_value("hunter2", window, cx);
8333                state.set_masked(true, window, cx);
8334                state.select_all(window, cx);
8335                cx.write_to_clipboard(ClipboardItem::new_string("sentinel".into()));
8336
8337                state.copy(&Copy, window, cx);
8338                assert_eq!(
8339                    cx.read_from_clipboard().and_then(|item| item.text()),
8340                    Some("sentinel".to_string())
8341                );
8342
8343                // Cut neither copies nor deletes.
8344                state.cut(&Cut, window, cx);
8345                assert_eq!(state.value(), "hunter2");
8346                assert_eq!(
8347                    cx.read_from_clipboard().and_then(|item| item.text()),
8348                    Some("sentinel".to_string())
8349                );
8350
8351                // Revealing the value restores both.
8352                state.set_masked(false, window, cx);
8353                state.copy(&Copy, window, cx);
8354                assert_eq!(
8355                    cx.read_from_clipboard().and_then(|item| item.text()),
8356                    Some("hunter2".to_string())
8357                );
8358            });
8359        });
8360    }
8361
8362    #[gpui::test]
8363    fn test_masked_input_collapses_word_boundaries(cx: &mut TestAppContext) {
8364        let input_view = InputView::build(cx, |state| state);
8365        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8366        let input = input_view.input;
8367
8368        cx.update(|window, cx| {
8369            input.update(cx, |state, cx| {
8370                state.set_value("aaa bbb ccc", window, cx);
8371                state.set_masked(true, window, cx);
8372                state.set_selected_range(7..7, cx);
8373
8374                // The mask hides word boundaries, so a word delete takes
8375                // everything before the caret and leaves the rest.
8376                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
8377                assert_eq!(state.value(), " ccc");
8378                assert_eq!(state.selected_range(), 0..0);
8379
8380                state.delete_next_word(&DeleteToNextWordEnd, window, cx);
8381                assert_eq!(state.value(), "");
8382
8383                // A double click takes the whole value, not one word.
8384                state.set_value("aaa bbb ccc", window, cx);
8385                state.select_word(9, window, cx);
8386                assert_eq!(state.selected_range(), 0..11);
8387
8388                // Unmasked, the same delete only takes one word.
8389                state.set_masked(false, window, cx);
8390                state.set_value("aaa bbb ccc", window, cx);
8391                state.set_selected_range(11..11, cx);
8392                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
8393                assert_eq!(state.value(), "aaa bbb ");
8394
8395                state.set_value("aaa bbb ccc", window, cx);
8396                state.select_word(9, window, cx);
8397                assert_eq!(state.selected_range(), 8..11);
8398            });
8399        });
8400    }
8401
8402    #[gpui::test]
8403    fn test_masked_input_disables_the_copy_context_menu_items(cx: &mut TestAppContext) {
8404        let input_view = InputView::build(cx, |state| state);
8405        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8406        let input = input_view.input;
8407
8408        cx.update(|window, cx| {
8409            input.update(cx, |state, cx| {
8410                state.set_value("hunter2", window, cx);
8411                state.select_all(window, cx);
8412                assert!(state.context_menu_capabilities().is_copyable());
8413
8414                state.set_masked(true, window, cx);
8415                let capabilities = state.context_menu_capabilities();
8416                assert!(capabilities.is_masked());
8417                assert!(capabilities.has_selection());
8418                assert!(!capabilities.is_copyable());
8419            });
8420        });
8421    }
8422
8423    #[gpui::test]
8424    fn test_undo_manager_cut_and_repeated_pastes_are_distinct_transactions(
8425        cx: &mut TestAppContext,
8426    ) {
8427        let input_view = InputView::build(cx, |state| state);
8428        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8429        let input = input_view.input;
8430
8431        cx.update(|window, cx| {
8432            input.update(cx, |state, cx| {
8433                state.replace_text_in_range(None, "alpha beta gamma", window, cx);
8434                state.set_selected_range(6..10, cx);
8435                state.cut(&Cut, window, cx);
8436                assert_eq!(state.value(), "alpha  gamma");
8437
8438                state.paste(&Paste, window, cx);
8439                state.paste(&Paste, window, cx);
8440                assert_eq!(state.value(), "alpha betabeta gamma");
8441
8442                state.undo(&Undo, window, cx);
8443                assert_eq!(state.value(), "alpha beta gamma");
8444                state.undo(&Undo, window, cx);
8445                assert_eq!(state.value(), "alpha  gamma");
8446                state.undo(&Undo, window, cx);
8447                assert_eq!(state.value(), "alpha beta gamma");
8448            });
8449        });
8450    }
8451
8452    #[gpui::test]
8453    fn test_undo_manager_word_and_line_deletes_do_not_coalesce(cx: &mut TestAppContext) {
8454        let input_view = InputView::build_textarea(cx, |state| state);
8455        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8456        let input = input_view.input;
8457
8458        cx.update(|window, cx| {
8459            input.update(cx, |state, cx| {
8460                state.set_value("one two three\nfour five", window, cx);
8461                state.set_selected_range(13..13, cx);
8462                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
8463                assert_eq!(state.value(), "one two \nfour five");
8464                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
8465                assert_eq!(state.value(), "one two four five");
8466
8467                state.undo(&Undo, window, cx);
8468                assert_eq!(state.value(), "one two \nfour five");
8469                state.undo(&Undo, window, cx);
8470                assert_eq!(state.value(), "one two three\nfour five");
8471            });
8472        });
8473    }
8474
8475    #[gpui::test]
8476    fn test_undo_manager_multiline_replacement_is_one_atomic_transaction(cx: &mut TestAppContext) {
8477        let input_view = InputView::build_textarea(cx, |state| state);
8478        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8479        let input = input_view.input;
8480
8481        cx.update(|window, cx| {
8482            input.update(cx, |state, cx| {
8483                state.replace_text_in_range(None, "before", window, cx);
8484                state.set_selected_range(0..6, cx);
8485                state.replace_text_in_range(None, "line one\nline two\n第三行", window, cx);
8486                assert_eq!(state.value(), "line one\nline two\n第三行");
8487
8488                state.undo(&Undo, window, cx);
8489                assert_eq!(state.value(), "before");
8490                assert_eq!(state.selected_range(), 0..6);
8491                state.redo(&Redo, window, cx);
8492                assert_eq!(state.value(), "line one\nline two\n第三行");
8493            });
8494        });
8495    }
8496
8497    #[gpui::test]
8498    fn test_undo_manager_composition_isolated_from_long_typing(cx: &mut TestAppContext) {
8499        let input_view = InputView::build(cx, |state| state);
8500        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8501        let input = input_view.input;
8502
8503        cx.update(|window, cx| {
8504            input.update(cx, |state, cx| {
8505                state.replace_text_in_range(None, "prefix ", window, cx);
8506                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
8507                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
8508                state.replace_and_mark_text_in_range(None, "你", None, window, cx);
8509                state.unmark_text(window, cx);
8510                state.replace_text_in_range(None, " suffix", window, cx);
8511                assert_eq!(state.value(), "prefix 你 suffix");
8512
8513                state.undo(&Undo, window, cx);
8514                assert_eq!(state.value(), "prefix 你");
8515                state.undo(&Undo, window, cx);
8516                assert_eq!(state.value(), "prefix ");
8517                state.undo(&Undo, window, cx);
8518                assert_eq!(state.value(), "");
8519            });
8520        });
8521    }
8522
8523    #[gpui::test]
8524    fn test_undo_manager_selected_replacement_is_atomic(cx: &mut TestAppContext) {
8525        let input_view = InputView::build(cx, |state| state);
8526        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8527        let input = input_view.input;
8528
8529        cx.update(|window, cx| {
8530            input.update(cx, |state, cx| {
8531                state.replace_text_in_range(None, "abc", window, cx);
8532                state.set_selected_range(1..2, cx);
8533                state.replace_text_in_range(None, "X", window, cx);
8534                state.replace_text_in_range(None, "z", window, cx);
8535                assert_eq!(state.value(), "aXzc");
8536
8537                state.undo(&Undo, window, cx);
8538                assert_eq!(state.value(), "aXc");
8539                state.undo(&Undo, window, cx);
8540                assert_eq!(state.value(), "abc");
8541                state.undo(&Undo, window, cx);
8542                assert_eq!(state.value(), "");
8543            });
8544        });
8545    }
8546
8547    #[gpui::test]
8548    fn test_number_input_leading_dot_editable(cx: &mut TestAppContext) {
8549        let input_view = InputView::build(cx, |state| {
8550            state.mask_pattern(MaskPattern::Number {
8551                separator: None,
8552                fraction: None,
8553            })
8554        });
8555        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8556        let input = input_view.input;
8557
8558        cx.update(|window, cx| {
8559            input.update(cx, |state, cx| {
8560                state.replace_text_in_range(None, "1.2", window, cx);
8561
8562                // Delete the integer part "1": the value keeps the leading dot
8563                // (".2"), not completed to "0.2", so the digits before the dot
8564                // stay editable.
8565                let range = state.range_to_utf16(&(0..1));
8566                state.replace_text_in_range(Some(range), "", window, cx);
8567                assert_eq!(state.value(), ".2");
8568                let cursor: Range<usize> = state.selected_range();
8569                assert_eq!(cursor, 0..0);
8570
8571                // The user can type a new integer part.
8572                state.replace_text_in_range(Some(0..0), "3", window, cx);
8573                assert_eq!(state.value(), "3.2");
8574            });
8575        });
8576    }
8577
8578    #[gpui::test]
8579    fn test_number_input_escape_invalid_text(cx: &mut TestAppContext) {
8580        // A pre-existing invalid text (e.g. a `default_value` that does not
8581        // conform) must not trap the user, the edit is allowed to fix it.
8582        let input_view = InputView::build(cx, |state| {
8583            state
8584                .mask_pattern(MaskPattern::Number {
8585                    separator: None,
8586                    fraction: None,
8587                })
8588                .default_value("1,234")
8589        });
8590        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8591        let input = input_view.input;
8592
8593        cx.update(|window, cx| {
8594            input.update(cx, |state, cx| {
8595                // Delete the last char, the pending text "1,23" is still
8596                // invalid, but the edit is allowed since the old text was
8597                // already invalid.
8598                let range = state.range_to_utf16(&(4..5));
8599                state.replace_text_in_range(Some(range), "", window, cx);
8600                assert_eq!(state.value(), "1,23");
8601
8602                // Once the text becomes valid, the validation works as usual.
8603                let range = state.range_to_utf16(&(1..2));
8604                state.replace_text_in_range(Some(range), "", window, cx);
8605                assert_eq!(state.value(), "123");
8606                state.replace_text_in_range(None, "a", window, cx);
8607                assert_eq!(state.value(), "123");
8608            });
8609        });
8610    }
8611
8612    /// After `set_value` on a single-line input the caret sits at the end (like
8613    /// HTML `<input>`), yet the view is scrolled back to the start so a long
8614    /// value shows its beginning instead of its tail.
8615    #[gpui::test]
8616    fn test_set_value_single_line_caret_at_end_view_at_start(cx: &mut TestAppContext) {
8617        let input_view = InputView::build(cx, |state| state);
8618        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8619        let input = input_view.input;
8620
8621        // Long enough to overflow any reasonable single-line input width.
8622        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
8623        let len = value.len();
8624
8625        // Right after `set_value`, before the next paint consumes the deferred
8626        // offset: caret is at the end, and the view is forced back to the start.
8627        cx.update(|window, cx| {
8628            input.update(cx, |state, cx| {
8629                state.set_value(value.clone(), window, cx);
8630
8631                assert_eq!(
8632                    state.selected_range(),
8633                    len..len,
8634                    "single-line caret should be at the end after set_value"
8635                );
8636                assert_eq!(
8637                    state.deferred_scroll_offset,
8638                    Some(point(px(0.), px(0.))),
8639                    "the view should be forced back to the start"
8640                );
8641            });
8642        });
8643
8644        // After a paint, the steady-state view stays at the start (x == 0) even
8645        // though the caret is at the far end.
8646        cx.run_until_parked();
8647        cx.update(|_, cx| {
8648            input.read_with(cx, |state, _| {
8649                assert!(
8650                    state.scroll_size.width > state.input_bounds.size.width,
8651                    "value must overflow the input width or this test is vacuous"
8652                );
8653                assert_eq!(
8654                    state.scroll_handle.offset().x,
8655                    px(0.),
8656                    "long value should display from its start, not its tail"
8657                );
8658            });
8659        });
8660    }
8661
8662    /// `replace_all` on a single-line input replaces the text, puts the
8663    /// caret at the end, and — like `set_value` — snaps the view back to the
8664    /// start so a long value shows its beginning instead of its tail.
8665    #[gpui::test]
8666    fn test_replace_all_single_line(cx: &mut TestAppContext) {
8667        let input_view = InputView::build(cx, |state| state);
8668        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8669        let input = input_view.input;
8670
8671        // Long enough to overflow any reasonable single-line input width.
8672        let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
8673        let len = value.len();
8674
8675        // Right after `replace_all`, before the next paint consumes the
8676        // deferred offset: caret is at the end, and the view is forced back
8677        // to the start.
8678        cx.update(|window, cx| {
8679            input.update(cx, |state, cx| {
8680                state.set_value("hello", window, cx);
8681                state.replace_all(value.clone(), window, cx);
8682                assert_eq!(state.value(), value);
8683                assert_eq!(
8684                    state.selected_range(),
8685                    len..len,
8686                    "single-line caret should be at the end after replace_all"
8687                );
8688                assert_eq!(
8689                    state.scroll_handle.offset(),
8690                    point(px(0.), px(0.)),
8691                    "the scroll offset should be reset to the start"
8692                );
8693                assert_eq!(
8694                    state.deferred_scroll_offset,
8695                    Some(point(px(0.), px(0.))),
8696                    "single-line should set a deferred scroll offset to keep the start visible"
8697                );
8698            });
8699        });
8700
8701        // After a paint, the steady-state view stays at the start (x == 0)
8702        // even though the caret is at the far end.
8703        cx.run_until_parked();
8704        cx.update(|_, cx| {
8705            input.read_with(cx, |state, _| {
8706                assert!(
8707                    state.scroll_size.width > state.input_bounds.size.width,
8708                    "value must overflow the input width or this test is vacuous"
8709                );
8710                assert_eq!(
8711                    state.scroll_handle.offset().x,
8712                    px(0.),
8713                    "long value should display from its start, not its tail"
8714                );
8715            });
8716        });
8717    }
8718
8719    #[gpui::test]
8720    fn test_single_line_removes_newlines(cx: &mut TestAppContext) {
8721        let input_view = InputView::build(cx, |state| state.default_value("default\nvalue"));
8722        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8723        let input = input_view.input;
8724
8725        cx.update(|window, cx| {
8726            input.update(cx, |state, cx| {
8727                assert_eq!(state.value(), "defaultvalue");
8728
8729                state.set_value("first\nsecond\r\nthird\rfourth", window, cx);
8730                assert_eq!(state.value(), "firstsecondthirdfourth");
8731
8732                state.set_value("", window, cx);
8733                state.insert("a\nb", window, cx);
8734                assert_eq!(state.value(), "ab");
8735            });
8736
8737            cx.write_to_clipboard(ClipboardItem::new_string("a\r\nb\nc\rd".to_string()));
8738            input.update(cx, |state, cx| {
8739                state.set_value("", window, cx);
8740                state.paste(&Paste, window, cx);
8741                assert_eq!(state.value(), "abcd");
8742            });
8743        });
8744
8745        cx.run_until_parked();
8746    }
8747
8748    /// `replace_all` on a multi-line (non-code-editor) input clears the
8749    /// selection to `0..0` and resets the scroll offset, but does not set a
8750    /// deferred scroll offset (single-line only).
8751    #[gpui::test]
8752    fn test_replace_all_multi_line(cx: &mut TestAppContext) {
8753        let input_view = InputView::build_textarea(cx, |state| state);
8754        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8755        let input = input_view.input;
8756
8757        cx.update(|window, cx| {
8758            input.update(cx, |state, cx| {
8759                state.set_value("foo\nbar", window, cx);
8760                state.replace_all("baz\nqux", window, cx);
8761                assert_eq!(state.value(), "baz\nqux");
8762                assert_eq!(
8763                    state.selected_range(),
8764                    0..0,
8765                    "multi-line selection should be cleared after replace_all"
8766                );
8767                assert_eq!(
8768                    state.scroll_handle.offset(),
8769                    point(px(0.), px(0.)),
8770                    "the scroll offset should be reset to the start"
8771                );
8772                assert!(
8773                    state.deferred_scroll_offset.is_none(),
8774                    "multi-line should not set a deferred scroll offset"
8775                );
8776            });
8777        });
8778    }
8779
8780    /// Unlike `set_value`, `replace_all` records the change so the user can
8781    /// undo it back to the previous text and redo to the new text.
8782    #[gpui::test]
8783    fn test_replace_all_preserves_undo_history(cx: &mut TestAppContext) {
8784        let input_view = InputView::build(cx, |state| state);
8785        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8786        let input = input_view.input;
8787
8788        cx.update(|window, cx| {
8789            input.update(cx, |state, cx| {
8790                // Seed with a value and clear history so the baseline is clean.
8791                state.set_value("first", window, cx);
8792                assert!(
8793                    !state.undo_manager.has_undos(),
8794                    "history should be empty after set_value"
8795                );
8796
8797                // replace_all records a single undoable change.
8798                state.replace_all("second", window, cx);
8799                assert_eq!(state.value(), "second");
8800                assert!(
8801                    state.undo_manager.has_undos(),
8802                    "replace_all should record an undo step"
8803                );
8804
8805                // Undo restores the previous text.
8806                state.undo(&Undo, window, cx);
8807                assert_eq!(state.value(), "first");
8808
8809                // Redo reapplies the replacement.
8810                state.redo(&Redo, window, cx);
8811                assert_eq!(state.value(), "second");
8812            });
8813        });
8814    }
8815
8816    /// `replace_all` on a code editor marks a pending update and resets LSP
8817    /// state, so diagnostics/completions refresh against the new text.
8818    #[gpui::test]
8819    fn test_replace_all_code_editor(cx: &mut TestAppContext) {
8820        let input_view = InputView::new(cx);
8821        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8822        let input = input_view.input;
8823
8824        cx.update(|window, cx| {
8825            input.update(cx, |state, cx| {
8826                // Plant a pending-update flag and some LSP state to verify reset.
8827                state.set_value("select 1", window, cx);
8828                state._pending_update = false;
8829
8830                state.replace_all("select 2", window, cx);
8831                assert_eq!(state.value(), "select 2");
8832                assert!(
8833                    state._pending_update,
8834                    "replace_all on a code editor should request a pending update"
8835                );
8836            });
8837        });
8838    }
8839
8840    #[gpui::test]
8841    fn test_set_selected_range(cx: &mut TestAppContext) {
8842        let input_view = InputView::build(cx, |state| state.default_value("hello world"));
8843        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8844        let input = input_view.input;
8845
8846        cx.update(|_, cx| {
8847            input.update(cx, |s, cx| {
8848                s.set_selected_range(0..5, cx);
8849                assert_eq!(s.selected_range(), 0..5);
8850                assert_eq!(s.selected_text().to_string(), "hello");
8851
8852                s.set_selected_range(6..11, cx);
8853                assert_eq!(s.selected_text().to_string(), "world");
8854
8855                // clamped + collapsed
8856                s.set_selected_range(100..100, cx);
8857                assert_eq!(s.selected_range(), 11..11);
8858            });
8859        });
8860    }
8861
8862    /// A single-edit batch round-trips through undo/redo.
8863    #[gpui::test]
8864    fn test_replace_text_in_ranges_single_edit(cx: &mut TestAppContext) {
8865        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
8866        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8867        let input = input_view.input;
8868
8869        cx.update(|window, cx| {
8870            input.update(cx, |s, cx| {
8871                s.replace_text_in_ranges(&[(0..5, "HELLO".to_string())], window, cx);
8872                assert_eq!(s.value(), "HELLO world");
8873
8874                s.undo(&Undo, window, cx);
8875                assert_eq!(s.value(), "hello world");
8876
8877                s.redo(&Redo, window, cx);
8878                assert_eq!(s.value(), "HELLO world");
8879            });
8880        });
8881    }
8882
8883    /// A single undo restores the exact original text and a single redo
8884    /// re-applies all edits, verifying the back-to-front application ordering.
8885    #[gpui::test]
8886    fn test_replace_text_in_ranges_multi_edit_transaction(cx: &mut TestAppContext) {
8887        let input_view = InputView::build_textarea(cx, |state| state.default_value("aaa bbb ccc"));
8888        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8889        let input = input_view.input;
8890
8891        cx.update(|window, cx| {
8892            input.update(cx, |s, cx| {
8893                // Two edits at different positions, given in pre-edit
8894                // coordinates and in arbitrary (non-sorted) order.
8895                s.replace_text_in_ranges(
8896                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
8897                    window,
8898                    cx,
8899                );
8900                assert_eq!(s.value(), "X bbb Y");
8901
8902                // One collapsed cursor per edit, at the end of each inserted text.
8903                let cursors: Vec<usize> =
8904                    s.selections.iter().map(|sel| sel.cursor_offset()).collect();
8905                assert_eq!(cursors, vec![1, 7]);
8906
8907                // The whole batch is a single undo transaction.
8908                assert_eq!(s.undo_manager.undo_count(), 1);
8909
8910                // One undo restores the exact original text.
8911                s.undo(&Undo, window, cx);
8912                assert_eq!(s.value(), "aaa bbb ccc");
8913
8914                // One redo re-applies all edits.
8915                s.redo(&Redo, window, cx);
8916                assert_eq!(s.value(), "X bbb Y");
8917            });
8918        });
8919    }
8920
8921    /// A multi-edit change reaches the highlighter once, with every edit in
8922    /// application order and the text right after each, which is what the
8923    /// default `update_batch` replays through `update` one edit at a time.
8924    #[gpui::test]
8925    fn test_replace_text_in_ranges_drives_the_highlighter_once(cx: &mut TestAppContext) {
8926        use crate::input::{FoldRange, HighlightStyleResolver, InputEdit, InputHighlighter};
8927        use gpui::HighlightStyle;
8928        use std::cell::RefCell;
8929
8930        struct RecordingHighlighter(Rc<RefCell<Vec<Vec<String>>>>);
8931
8932        impl InputHighlighter for RecordingHighlighter {
8933            fn language(&self) -> SharedString {
8934                "recording".into()
8935            }
8936
8937            fn update(
8938                &mut self,
8939                _: Option<InputEdit>,
8940                text: &Rope,
8941                _: bool,
8942                _: &mut Window,
8943                _: &mut Context<EditorState>,
8944            ) {
8945                self.0.borrow_mut().push(vec![text.to_string()]);
8946            }
8947
8948            fn update_batch(
8949                &mut self,
8950                edits: &[(InputEdit, Rope)],
8951                _: bool,
8952                _: &mut Window,
8953                _: &mut Context<EditorState>,
8954            ) {
8955                let texts = edits.iter().map(|(_, text)| text.to_string()).collect();
8956                self.0.borrow_mut().push(texts);
8957            }
8958
8959            fn styles(
8960                &self,
8961                range: &Range<usize>,
8962                _: &dyn HighlightStyleResolver,
8963            ) -> Vec<(Range<usize>, HighlightStyle)> {
8964                vec![(range.clone(), HighlightStyle::default())]
8965            }
8966
8967            fn fold_ranges(&self, _: &Rope) -> Vec<FoldRange> {
8968                Vec::new()
8969            }
8970        }
8971
8972        let updates = Rc::new(RefCell::new(Vec::new()));
8973        let input_view = InputView::new(cx);
8974        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
8975        let input = input_view.input;
8976
8977        cx.update(|window, cx| {
8978            input.update(cx, |s, cx| {
8979                s.set_value("aaa bbb ccc", window, cx);
8980                let recorded = updates.clone();
8981                s.set_highlighter_factory(
8982                    Rc::new(move |_: &str| {
8983                        let highlighter: Box<dyn InputHighlighter> =
8984                            Box::new(RecordingHighlighter(recorded.clone()));
8985                        Some(highlighter)
8986                    }),
8987                    cx,
8988                );
8989                s.replace_text_in_ranges(
8990                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
8991                    window,
8992                    cx,
8993                );
8994            });
8995        });
8996
8997        assert_eq!(
8998            *updates.borrow(),
8999            vec![vec!["aaa bbb Y".to_string(), "X bbb Y".to_string()]]
9000        );
9001    }
9002
9003    /// One keypress records a single edit with the text right after it.
9004    #[gpui::test]
9005    fn test_last_input_edits_single_insert(cx: &mut TestAppContext) {
9006        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
9007        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9008        let input = input_view.input;
9009
9010        cx.update(|window, cx| {
9011            input.update(cx, |state, cx| {
9012                state.replace_text_in_ranges(&[(5..5, " big".to_string())], window, cx);
9013
9014                let edits = state.last_input_edits.as_slice();
9015                assert_eq!(edits.len(), 1);
9016                let (edit, text_after) = &edits[0];
9017                assert_eq!(edit.start_byte, 5);
9018                assert_eq!(edit.old_end_byte, 5);
9019                assert_eq!(edit.new_end_byte, 9);
9020                assert_eq!(edit.start_position, crate::input::Point::new(0, 5));
9021                assert_eq!(edit.old_end_position, crate::input::Point::new(0, 5));
9022                assert_eq!(edit.new_end_position, crate::input::Point::new(0, 9));
9023                assert_eq!(text_after.to_string(), "hello big world");
9024            });
9025        });
9026    }
9027
9028    /// Deletion and a multi-line paste each describe their whole replacement.
9029    #[gpui::test]
9030    fn test_last_input_edits_delete_and_paste(cx: &mut TestAppContext) {
9031        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello world"));
9032        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9033        let input = input_view.input;
9034
9035        cx.update(|window, cx| {
9036            input.update(cx, |state, cx| {
9037                state.replace_text_in_ranges(&[(5..11, String::new())], window, cx);
9038                assert_eq!(state.value(), "hello");
9039
9040                let edits = state.last_input_edits.as_slice();
9041                assert_eq!(edits.len(), 1);
9042                let (edit, text_after) = &edits[0];
9043                assert_eq!(edit.start_byte, 5);
9044                assert_eq!(edit.old_end_byte, 11);
9045                assert_eq!(edit.new_end_byte, 5);
9046                assert_eq!(text_after.to_string(), "hello");
9047
9048                state.replace_text_in_ranges(&[(5..5, "\nnext".to_string())], window, cx);
9049                assert_eq!(state.value(), "hello\nnext");
9050
9051                let edits = state.last_input_edits.as_slice();
9052                assert_eq!(edits.len(), 1);
9053                let (edit, text_after) = &edits[0];
9054                assert_eq!(edit.start_byte, 5);
9055                assert_eq!(edit.old_end_byte, 5);
9056                assert_eq!(edit.new_end_byte, 10);
9057                assert_eq!(edit.start_position, crate::input::Point::new(0, 5));
9058                assert_eq!(edit.old_end_position, crate::input::Point::new(0, 5));
9059                assert_eq!(edit.new_end_position, crate::input::Point::new(1, 4));
9060                assert_eq!(text_after.to_string(), "hello\nnext");
9061            });
9062        });
9063    }
9064
9065    /// A grouped change records every applied edit in application order.
9066    #[gpui::test]
9067    fn test_last_input_edits_grouped_change(cx: &mut TestAppContext) {
9068        let input_view = InputView::build_textarea(cx, |state| state.default_value("aaa bbb ccc"));
9069        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9070        let input = input_view.input;
9071
9072        cx.update(|window, cx| {
9073            input.update(cx, |state, cx| {
9074                state.replace_text_in_ranges(
9075                    &[(0..3, "X".to_string()), (8..11, "Y".to_string())],
9076                    window,
9077                    cx,
9078                );
9079                assert_eq!(state.value(), "X bbb Y");
9080
9081                let edits = state.last_input_edits.as_slice();
9082                assert_eq!(edits.len(), 2);
9083                // The higher offset is applied first, before the lower one.
9084                assert_eq!(edits[0].0.start_byte, 8);
9085                assert_eq!(edits[0].0.old_end_byte, 11);
9086                assert_eq!(edits[0].0.new_end_byte, 9);
9087                assert_eq!(edits[0].0.start_position, crate::input::Point::new(0, 8));
9088                assert_eq!(edits[0].0.old_end_position, crate::input::Point::new(0, 11));
9089                assert_eq!(edits[0].0.new_end_position, crate::input::Point::new(0, 9));
9090                assert_eq!(edits[0].1.to_string(), "aaa bbb Y");
9091
9092                assert_eq!(edits[1].0.start_byte, 0);
9093                assert_eq!(edits[1].0.old_end_byte, 3);
9094                assert_eq!(edits[1].0.new_end_byte, 1);
9095                assert_eq!(edits[1].0.start_position, crate::input::Point::new(0, 0));
9096                assert_eq!(edits[1].0.old_end_position, crate::input::Point::new(0, 3));
9097                assert_eq!(edits[1].0.new_end_position, crate::input::Point::new(0, 1));
9098                assert_eq!(edits[1].1.to_string(), "X bbb Y");
9099            });
9100        });
9101    }
9102
9103    /// Edits are recorded even when no highlighter can be installed.
9104    #[gpui::test]
9105    fn test_last_input_edits_without_a_highlighter(cx: &mut TestAppContext) {
9106        let input_view = InputView::new(cx);
9107        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9108        let input = input_view.input;
9109
9110        cx.update(|window, cx| {
9111            input.update(cx, |state, cx| {
9112                state.set_value("fn main() {}", window, cx);
9113                let no_highlighter: InputHighlighterFactory = Rc::new(|_: &str| None);
9114                state.set_highlighter_factory(no_highlighter, cx);
9115                assert!(state.last_input_edits.as_slice().is_empty());
9116
9117                state.replace_text_in_ranges(&[(12..12, "\n".to_string())], window, cx);
9118                assert_eq!(state.value(), "fn main() {}\n");
9119
9120                let edits = state.last_input_edits.as_slice();
9121                assert_eq!(edits.len(), 1);
9122                let (edit, text_after) = &edits[0];
9123                assert_eq!(edit.start_byte, 12);
9124                assert_eq!(edit.old_end_byte, 12);
9125                assert_eq!(edit.new_end_byte, 13);
9126                assert_eq!(edit.start_position, crate::input::Point::new(0, 12));
9127                assert_eq!(edit.old_end_position, crate::input::Point::new(0, 12));
9128                assert_eq!(edit.new_end_position, crate::input::Point::new(1, 0));
9129                assert_eq!(text_after.to_string(), "fn main() {}\n");
9130            });
9131        });
9132    }
9133
9134    /// `set_value` is silent and leaves no edits behind for the next change.
9135    #[gpui::test]
9136    fn test_last_input_edits_cleared_by_set_value(cx: &mut TestAppContext) {
9137        let input_view = InputView::build_textarea(cx, |state| state.default_value("hello"));
9138        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9139        let input = input_view.input;
9140
9141        cx.update(|window, cx| {
9142            input.update(cx, |state, cx| {
9143                state.replace_text_in_ranges(&[(5..5, "!".to_string())], window, cx);
9144                assert_eq!(state.last_input_edits.as_slice().len(), 1);
9145
9146                state.set_value("replaced", window, cx);
9147                assert!(state.last_input_edits.as_slice().is_empty());
9148
9149                state.replace_text_in_ranges(&[(8..8, "?".to_string())], window, cx);
9150                let edits = state.last_input_edits.as_slice();
9151                assert_eq!(edits.len(), 1);
9152                assert_eq!(edits[0].1.to_string(), "replaced?");
9153            });
9154        });
9155    }
9156
9157    /// Apply an emitted batch as an incremental consumer would and verify every snapshot.
9158    fn replay_input_edits(text: &mut String, event: &InputEditsEvent) {
9159        for (edit, after) in event.edits() {
9160            let replacement = after.slice(edit.start_byte..edit.new_end_byte).to_string();
9161            text.replace_range(edit.start_byte..edit.old_end_byte, &replacement);
9162            assert_eq!(*text, after.to_string());
9163        }
9164    }
9165
9166    #[gpui::test]
9167    fn input_edits_events_preserve_queued_changes_and_undo(cx: &mut TestAppContext) {
9168        let view = InputView::build_textarea(cx, |state| state.default_value("hello"));
9169        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9170        let input = view.input;
9171        let observed = Rc::new(std::cell::RefCell::new(Vec::<InputEditsEvent>::new()));
9172        let seen = observed.clone();
9173        let _subscription = cx.update(|_, cx| {
9174            cx.subscribe(&input, move |_, event: &InputEditsEvent, _| {
9175                seen.borrow_mut().push(event.clone());
9176            })
9177        });
9178        cx.update(|window, cx| {
9179            input.update(cx, |state, cx| {
9180                state.replace_text_in_ranges(&[(5..5, "!".into())], window, cx);
9181                state.replace_text_in_ranges(&[(6..6, "?".into())], window, cx);
9182                state.undo(&Undo, window, cx);
9183            });
9184        });
9185        let events = observed.borrow();
9186        assert!(events.len() >= 3);
9187        let mut text = "hello".to_string();
9188        for event in events.iter() {
9189            replay_input_edits(&mut text, event);
9190        }
9191        assert_eq!(
9192            text,
9193            input.read_with(&cx, |state, _| state.value().to_string())
9194        );
9195        assert_eq!(events[0].edits()[0].1.to_string(), "hello!");
9196        assert_eq!(events[1].edits()[0].1.to_string(), "hello!?");
9197    }
9198
9199    #[gpui::test]
9200    fn input_edits_event_replays_ime_commit(cx: &mut TestAppContext) {
9201        let view = InputView::build_textarea(cx, |state| state.default_value(""));
9202        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9203        let input = view.input;
9204        let observed = Rc::new(std::cell::RefCell::new(Vec::<InputEditsEvent>::new()));
9205        let seen = observed.clone();
9206        let _subscription = cx.update(|_, cx| {
9207            cx.subscribe(&input, move |_, event: &InputEditsEvent, _| {
9208                seen.borrow_mut().push(event.clone());
9209            })
9210        });
9211        cx.update(|window, cx| {
9212            input.update(cx, |state, cx| {
9213                state.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
9214                state.replace_and_mark_text_in_range(None, "ni", Some(2..2), window, cx);
9215                state.replace_text_in_range(None, "你", window, cx);
9216            });
9217        });
9218        let events = observed.borrow();
9219        assert_eq!(events.len(), 1);
9220        let mut text = String::new();
9221        replay_input_edits(&mut text, &events[0]);
9222        assert_eq!(text, "你");
9223    }
9224
9225    #[gpui::test]
9226    fn input_edits_event_replays_after_cancelled_composition(cx: &mut TestAppContext) {
9227        let view = InputView::build_textarea(cx, |state| state.default_value(""));
9228        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9229        let input = view.input;
9230        let observed = Rc::new(std::cell::RefCell::new(Vec::<InputEditsEvent>::new()));
9231        let seen = observed.clone();
9232        let _subscription = cx.update(|_, cx| {
9233            cx.subscribe(&input, move |_, event: &InputEditsEvent, _| {
9234                seen.borrow_mut().push(event.clone());
9235            })
9236        });
9237        cx.update(|window, cx| {
9238            input.update(cx, |state, cx| {
9239                state.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
9240                state.replace_and_mark_text_in_range(None, "", None, window, cx);
9241                state.replace_text_in_range(None, "x", window, cx);
9242            });
9243        });
9244        let events = observed.borrow();
9245        assert_eq!(events.len(), 1);
9246        let mut text = String::new();
9247        replay_input_edits(&mut text, &events[0]);
9248        assert_eq!(text, "x");
9249    }
9250
9251    #[gpui::test]
9252    fn input_edits_event_replays_mask_rewrite(cx: &mut TestAppContext) {
9253        let view = InputView::build(cx, |state| {
9254            state
9255                .mask_pattern(MaskPattern::Number {
9256                    separator: Some(','),
9257                    fraction: None,
9258                })
9259                .default_value("999")
9260        });
9261        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9262        let input = view.input;
9263        let observed = Rc::new(std::cell::RefCell::new(Vec::<InputEditsEvent>::new()));
9264        let seen = observed.clone();
9265        let _subscription = cx.update(|_, cx| {
9266            cx.subscribe(&input, move |_, event: &InputEditsEvent, _| {
9267                seen.borrow_mut().push(event.clone());
9268            })
9269        });
9270        cx.update(|window, cx| {
9271            input.update(cx, |state, cx| {
9272                state.set_cursor_to(3);
9273                state.replace_text_in_range(None, "9", window, cx)
9274            });
9275        });
9276        let events = observed.borrow();
9277        assert_eq!(events.len(), 1);
9278        let mut text = "999".to_string();
9279        replay_input_edits(&mut text, &events[0]);
9280        assert_eq!(text, "9,999");
9281    }
9282
9283    /// An IME composition (marking then commit) undoes as a single unit.
9284    #[gpui::test]
9285    fn test_ime_composition_undoes_as_one_unit(cx: &mut TestAppContext) {
9286        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
9287        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9288        let input = input_view.input;
9289
9290        cx.update(|window, cx| {
9291            input.update(cx, |s, cx| {
9292                // Simulate an IME composition: mark, refine, then commit.
9293                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
9294                s.replace_and_mark_text_in_range(None, "ni", Some(2..2), window, cx);
9295                s.replace_text_in_range(None, "你", window, cx);
9296                assert_eq!(s.value(), "你");
9297
9298                // The entire composition is one undo transaction.
9299                assert_eq!(s.undo_manager.undo_count(), 1);
9300
9301                s.undo(&Undo, window, cx);
9302                assert_eq!(s.value(), "");
9303
9304                s.redo(&Redo, window, cx);
9305                assert_eq!(s.value(), "你");
9306            });
9307        });
9308    }
9309
9310    /// A keystroke right after a committed composition must be its own undo
9311    /// entry, not merged into the (finalized) composition transaction.
9312    #[gpui::test]
9313    fn test_edit_after_composition_is_separate_undo(cx: &mut TestAppContext) {
9314        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
9315        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9316        let input = input_view.input;
9317
9318        cx.update(|window, cx| {
9319            input.update(cx, |s, cx| {
9320                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
9321                s.replace_text_in_range(None, "你", window, cx);
9322                assert_eq!(s.value(), "你");
9323                assert_eq!(s.undo_manager.undo_count(), 1);
9324
9325                // Typing after the commit is a distinct transaction.
9326                s.replace_text_in_range(None, "x", window, cx);
9327                assert_eq!(s.value(), "你x");
9328                assert_eq!(s.undo_manager.undo_count(), 2);
9329
9330                s.undo(&Undo, window, cx);
9331                assert_eq!(s.value(), "你");
9332                s.undo(&Undo, window, cx);
9333                assert_eq!(s.value(), "");
9334            });
9335        });
9336    }
9337
9338    /// Canceling a composition via `unmark_text` closes its transaction so it
9339    /// does not leak and swallow a later edit.
9340    #[gpui::test]
9341    fn test_composition_cancel_via_unmark_does_not_leak(cx: &mut TestAppContext) {
9342        let input_view = InputView::build_textarea(cx, |state| state.default_value(""));
9343        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9344        let input = input_view.input;
9345
9346        cx.update(|window, cx| {
9347            input.update(cx, |s, cx| {
9348                // Start a composition, then cancel it via unmark.
9349                s.replace_and_mark_text_in_range(None, "n", Some(1..1), window, cx);
9350                s.unmark_text(window, cx);
9351                let after_cancel = s.undo_manager.undo_count();
9352
9353                // A later edit is recorded independently.
9354                s.replace_text_in_range(None, "x", window, cx);
9355                assert_eq!(s.undo_manager.undo_count(), after_cancel + 1);
9356            });
9357        });
9358    }
9359
9360    #[gpui::test]
9361    fn test_set_selected_range_clips_to_utf8_boundaries(cx: &mut TestAppContext) {
9362        let input_view = InputView::build(cx, |state| state.default_value("éx"));
9363        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9364        let input = input_view.input;
9365
9366        cx.update(|window, cx| {
9367            input.update(cx, |state, cx| {
9368                state.set_selected_range(0..1, cx);
9369                assert_eq!(state.selected_range(), 0..2);
9370                state.copy(&Copy, window, cx);
9371
9372                state.set_selected_range(1..1, cx);
9373                assert_eq!(state.selected_range(), 0..0);
9374            });
9375        });
9376    }
9377
9378    #[gpui::test]
9379    fn test_ime_selection_is_relative_to_replacement_start(cx: &mut TestAppContext) {
9380        let input_view = InputView::build(cx, |state| state.default_value("你好 "));
9381        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9382        let input = input_view.input;
9383
9384        cx.update(|window, cx| {
9385            input.update(cx, |state, cx| {
9386                state.set_selected_range(7..7, cx);
9387                state.replace_and_mark_text_in_range(None, "s", Some(1..1), window, cx);
9388                state.replace_and_mark_text_in_range(None, "sh", Some(2..2), window, cx);
9389
9390                assert_eq!(state.value(), "你好 sh");
9391                assert_eq!(state.selected_range(), 9..9);
9392                assert_eq!(state.ime_marked_range, Some((7..9).into()));
9393            });
9394        });
9395    }
9396
9397    #[gpui::test]
9398    fn test_undo_manager_composition_is_one_undo_group(cx: &mut TestAppContext) {
9399        let input_view = InputView::build(cx, |state| state);
9400        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9401        let input = input_view.input;
9402
9403        cx.update(|window, cx| {
9404            input.update(cx, |state, cx| {
9405                state.set_value("a", window, cx);
9406                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
9407                state.replace_and_mark_text_in_range(None, "sh", None, window, cx);
9408                state.replace_text_in_range(None, "是", window, cx);
9409                assert_eq!(state.value(), "a是");
9410
9411                state.undo(&Undo, window, cx);
9412                assert_eq!(state.value(), "a");
9413                state.redo(&Redo, window, cx);
9414                assert_eq!(state.value(), "a是");
9415            });
9416        });
9417    }
9418
9419    #[gpui::test]
9420    fn test_undo_manager_consecutive_compositions_are_separate_groups(cx: &mut TestAppContext) {
9421        let input_view = InputView::build(cx, |state| state);
9422        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9423        let input = input_view.input;
9424
9425        cx.update(|window, cx| {
9426            input.update(cx, |state, cx| {
9427                // First composition: "jin" -> "今天"
9428                state.replace_and_mark_text_in_range(None, "j", None, window, cx);
9429                state.replace_and_mark_text_in_range(None, "jin", None, window, cx);
9430                state.replace_text_in_range(None, "今天", window, cx);
9431                // Second composition: "wo" -> "我们"
9432                state.replace_and_mark_text_in_range(None, "w", None, window, cx);
9433                state.replace_and_mark_text_in_range(None, "wo", None, window, cx);
9434                state.replace_text_in_range(None, "我们", window, cx);
9435                assert_eq!(state.value(), "今天我们");
9436                assert_eq!(state.selected_range(), 12..12);
9437
9438                state.undo(&Undo, window, cx);
9439                assert_eq!(state.value(), "今天");
9440                assert_eq!(state.selected_range(), 6..6);
9441
9442                state.undo(&Undo, window, cx);
9443                assert_eq!(state.value(), "");
9444                assert_eq!(state.selected_range(), 0..0);
9445
9446                state.redo(&Redo, window, cx);
9447                assert_eq!(state.value(), "今天");
9448                assert_eq!(state.selected_range(), 6..6);
9449
9450                state.redo(&Redo, window, cx);
9451                assert_eq!(state.value(), "今天我们");
9452                assert_eq!(state.selected_range(), 12..12);
9453            });
9454        });
9455    }
9456
9457    #[gpui::test]
9458    fn test_undo_manager_typing_after_composition_is_a_separate_group(cx: &mut TestAppContext) {
9459        let input_view = InputView::build(cx, |state| state);
9460        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9461        let input = input_view.input;
9462
9463        cx.update(|window, cx| {
9464            input.update(cx, |state, cx| {
9465                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
9466                state.replace_text_in_range(None, "你", window, cx);
9467                state.undo_manager.set_pending_intent(EditIntent::Typing);
9468                state.replace_text_in_range(None, "a", window, cx);
9469                state.undo_manager.set_pending_intent(EditIntent::Typing);
9470                state.replace_text_in_range(None, "b", window, cx);
9471                assert_eq!(state.value(), "你ab");
9472
9473                state.undo(&Undo, window, cx);
9474                assert_eq!(state.value(), "你");
9475
9476                state.undo(&Undo, window, cx);
9477                assert_eq!(state.value(), "");
9478            });
9479        });
9480    }
9481
9482    #[gpui::test]
9483    fn test_undo_manager_composition_cancel_leaves_no_entry(cx: &mut TestAppContext) {
9484        let input_view = InputView::build(cx, |state| state);
9485        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9486        let input = input_view.input;
9487
9488        cx.update(|window, cx| {
9489            input.update(cx, |state, cx| {
9490                state.set_value("a", window, cx);
9491                state.replace_and_mark_text_in_range(None, "s", None, window, cx);
9492                state.replace_and_mark_text_in_range(None, "", None, window, cx);
9493
9494                assert_eq!(state.value(), "a");
9495                assert!(!state.undo_manager.has_undos());
9496            });
9497        });
9498    }
9499
9500    /// The macOS Korean IME rewrites the character it just inserted.
9501    #[gpui::test]
9502    fn test_undo_manager_ime_rewrites_of_typed_char_are_one_group(cx: &mut TestAppContext) {
9503        let input_view = InputView::build(cx, |state| state);
9504        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9505        let input = input_view.input;
9506
9507        cx.update(|window, cx| {
9508            input.update(cx, |state, cx| {
9509                state.set_value("é ", window, cx);
9510                state.set_selected_range(3..3, cx);
9511                // g k s r m f: "ㅎ" -> "하" -> "한", then "ㄱ" -> "그" -> "글"
9512                state.replace_text_in_range(None, "ㅎ", window, cx);
9513                state.replace_text_in_range(Some(2..3), "하", window, cx);
9514                state.replace_text_in_range(Some(2..3), "한", window, cx);
9515                state.replace_text_in_range(None, "ㄱ", window, cx);
9516                state.replace_text_in_range(Some(3..4), "그", window, cx);
9517                state.replace_text_in_range(Some(3..4), "글", window, cx);
9518                assert_eq!(state.value(), "é 한글");
9519                assert_eq!(state.undo_manager.undo_count(), 1);
9520
9521                state.undo(&Undo, window, cx);
9522                assert_eq!(state.value(), "é ");
9523                assert_eq!(state.selected_range(), 3..3);
9524
9525                state.redo(&Redo, window, cx);
9526                assert_eq!(state.value(), "é 한글");
9527                assert_eq!(state.selected_range(), 9..9);
9528            });
9529        });
9530    }
9531
9532    #[gpui::test]
9533    fn test_undo_manager_rewrite_after_cursor_movement_is_separate(cx: &mut TestAppContext) {
9534        let input_view = InputView::build(cx, |state| state);
9535        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9536        let input = input_view.input;
9537
9538        cx.update(|window, cx| {
9539            input.update(cx, |state, cx| {
9540                state.replace_text_in_range(None, "c", window, cx);
9541                state.replace_text_in_range(None, "e", window, cx);
9542                state.left(&MoveLeft, window, cx);
9543                state.right(&MoveRight, window, cx);
9544                state.replace_text_in_range(Some(1..2), "é", window, cx);
9545                assert_eq!(state.value(), "cé");
9546
9547                state.undo(&Undo, window, cx);
9548                assert_eq!(state.value(), "ce");
9549                state.undo(&Undo, window, cx);
9550                assert_eq!(state.value(), "");
9551            });
9552        });
9553    }
9554
9555    #[gpui::test]
9556    fn test_undo_manager_selection_restored_by_undo_and_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.set_value("abc", window, cx);
9564                state.set_selected_range(1..2, cx);
9565                state.replace_text_in_range(None, "X", window, cx);
9566
9567                state.undo(&Undo, window, cx);
9568                assert_eq!(state.value(), "abc");
9569                assert_eq!(state.selected_range(), 1..2);
9570
9571                state.redo(&Redo, window, cx);
9572                assert_eq!(state.value(), "aXc");
9573                assert_eq!(state.selected_range(), 2..2);
9574            });
9575        });
9576    }
9577
9578    #[gpui::test]
9579    fn test_undo_manager_forward_delete_restores_cursor(cx: &mut TestAppContext) {
9580        let input_view = InputView::build(cx, |state| state);
9581        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9582        let input = input_view.input;
9583
9584        cx.update(|window, cx| {
9585            input.update(cx, |state, cx| {
9586                state.set_value("abc", window, cx);
9587                state.set_selected_range(1..1, cx);
9588                state.delete(&Delete, window, cx);
9589
9590                state.undo(&Undo, window, cx);
9591                assert_eq!(state.value(), "abc");
9592                assert_eq!(state.selected_range(), 1..1);
9593            });
9594        });
9595    }
9596
9597    #[gpui::test]
9598    fn test_undo_manager_selection_movement_preserves_redo(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, "ab", window, cx);
9606                state.undo(&Undo, window, cx);
9607                state.set_selected_range(0..0, cx);
9608                state.redo(&Redo, window, cx);
9609                assert_eq!(state.value(), "ab");
9610
9611                state.undo(&Undo, window, cx);
9612                state.replace_text_in_range(None, "x", window, cx);
9613                state.redo(&Redo, window, cx);
9614                assert_eq!(state.value(), "x");
9615            });
9616        });
9617    }
9618
9619    #[gpui::test]
9620    fn test_undo_manager_noop_edit_preserves_redo(cx: &mut TestAppContext) {
9621        let input_view = InputView::build(cx, |state| state);
9622        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9623        let input = input_view.input;
9624
9625        cx.update(|window, cx| {
9626            input.update(cx, |state, cx| {
9627                state.replace_text_in_range(None, "a", window, cx);
9628                state.move_to(0, None, cx);
9629                state.replace_text_in_range(None, "", window, cx);
9630                state.undo(&Undo, window, cx);
9631                assert_eq!(state.value(), "");
9632                state.redo(&Redo, window, cx);
9633                assert_eq!(state.value(), "a");
9634                assert_eq!(state.cursor(), 1);
9635            });
9636        });
9637    }
9638
9639    #[gpui::test]
9640    fn test_cursor_round_trip_stops_typing_coalescing(cx: &mut TestAppContext) {
9641        let input_view = InputView::build(cx, |state| state);
9642        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9643        let input = input_view.input;
9644
9645        cx.update(|window, cx| {
9646            input.update(cx, |state, cx| {
9647                state.replace_text_in_range(None, "a", window, cx);
9648                state.left(&MoveLeft, window, cx);
9649                state.right(&MoveRight, window, cx);
9650                state.replace_text_in_range(None, "b", window, cx);
9651
9652                state.undo(&Undo, window, cx);
9653                assert_eq!(state.value(), "a");
9654                state.undo(&Undo, window, cx);
9655                assert_eq!(state.value(), "");
9656            });
9657        });
9658    }
9659
9660    #[gpui::test]
9661    fn test_undo_manager_noop_edit_breaks_coalescing_without_clearing_history(
9662        cx: &mut TestAppContext,
9663    ) {
9664        let input_view = InputView::build(cx, |state| state);
9665        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9666        let input = input_view.input;
9667
9668        cx.update(|window, cx| {
9669            input.update(cx, |state, cx| {
9670                state.replace_text_in_range(None, "alpha", window, cx);
9671                state.replace_text_in_range(None, "", window, cx);
9672                state.replace_text_in_range(None, "beta", window, cx);
9673                assert_eq!(state.value(), "alphabeta");
9674
9675                state.undo(&Undo, window, cx);
9676                assert_eq!(state.value(), "alpha");
9677                state.undo(&Undo, window, cx);
9678                assert_eq!(state.value(), "");
9679            });
9680        });
9681    }
9682
9683    #[gpui::test]
9684    fn test_undo_manager_masked_redo_restores_actual_cursor(cx: &mut TestAppContext) {
9685        let input_view = InputView::build(cx, |state| {
9686            state.mask_pattern(MaskPattern::Number {
9687                separator: Some(','),
9688                fraction: None,
9689            })
9690        });
9691        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
9692        let input = input_view.input;
9693
9694        cx.update(|window, cx| {
9695            input.update(cx, |state, cx| {
9696                state.set_value("12345", window, cx);
9697                state.set_selected_range(2..2, cx);
9698                state.replace_text_in_range(None, "9", window, cx);
9699                let selection_after_edit = state.selected_range();
9700                assert_ne!(selection_after_edit.end, state.value().len());
9701
9702                state.undo(&Undo, window, cx);
9703                state.redo(&Redo, window, cx);
9704                assert_eq!(state.selected_range(), selection_after_edit);
9705            });
9706        });
9707    }
9708
9709    /// Unfolding at a position opens exactly the folds hiding it.
9710    ///
9711    /// A fold keeps its own first and last line visible, so a position on
9712    /// either of them opens nothing. Nested folds all open at once, sibling
9713    /// folds stay closed, and the opened ranges stay fold candidates.
9714    #[gpui::test]
9715    fn test_unfold_at(cx: &mut TestAppContext) {
9716        use crate::input::{FoldRange, Position};
9717
9718        let view = InputView::<EditorMode>::new(cx);
9719        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9720        let input = view.input;
9721
9722        // An outer fold over lines 0..=5, a fold nested inside it, and a
9723        // sibling fold that must never be touched.
9724        cx.update(|window, cx| {
9725            input.update(cx, |state, cx| {
9726                state.set_value("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl", window, cx);
9727                state.apply_highlighter_fold_candidates(
9728                    vec![
9729                        FoldRange::new(0, 5),
9730                        FoldRange::new(2, 4),
9731                        FoldRange::new(7, 10),
9732                    ],
9733                    cx,
9734                );
9735                state.display_map.set_folded(0, true);
9736                state.display_map.set_folded(2, true);
9737                state.display_map.set_folded(7, true);
9738            });
9739        });
9740
9741        // The outer fold's own first and last line stay visible, so neither
9742        // position opens anything.
9743        for line in [0, 5] {
9744            cx.update(|_, cx| {
9745                input.update(cx, |state, cx| {
9746                    assert!(!state.display_map.is_buffer_line_hidden(line));
9747                    assert!(!state.unfold_at(Position::new(line as u32, 0), cx));
9748                });
9749                input.read_with(cx, |state, _| {
9750                    assert!(state.display_map.is_folded_at(0));
9751                    assert!(state.display_map.is_folded_at(2));
9752                    assert!(state.display_map.is_folded_at(7));
9753                });
9754            });
9755        }
9756
9757        // Line 3 is hidden by both the outer and the nested fold, so both
9758        // open; the sibling fold does not.
9759        cx.update(|_, cx| {
9760            input.update(cx, |state, cx| {
9761                assert!(state.unfold_at(Position::new(3, 0), cx));
9762            });
9763            input.read_with(cx, |state, _| {
9764                assert!(!state.display_map.is_buffer_line_hidden(3));
9765                assert!(!state.display_map.is_folded_at(0));
9766                assert!(!state.display_map.is_folded_at(2));
9767                assert!(state.display_map.is_folded_at(7));
9768                // The opened ranges are still candidates for refolding.
9769                assert!(state.display_map.is_fold_candidate(0));
9770                assert!(state.display_map.is_fold_candidate(2));
9771            });
9772        });
9773
9774        // Nothing is hidden there any more, so a second call is a no-op.
9775        cx.update(|_, cx| {
9776            input.update(cx, |state, cx| {
9777                assert!(!state.unfold_at(Position::new(3, 0), cx));
9778            });
9779        });
9780    }
9781
9782    /// Losing focus hides the hover popover but keeps the decorations.
9783    ///
9784    /// Both used to be dropped by one call, so clicking away threw away
9785    /// decorations the application had installed and never asked to remove.
9786    #[gpui::test]
9787    fn test_blur_keeps_decorations(cx: &mut TestAppContext) {
9788        let view = InputView::<EditorMode>::new(cx);
9789        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9790        let input = view.input;
9791
9792        cx.update(|window, cx| {
9793            input.update(cx, |state, cx| {
9794                state.set_value("select 1", window, cx);
9795                let _collection = state.create_decorations_collection(
9796                    vec![crate::input::TextDecoration::new(
9797                        0..6,
9798                        gpui::HighlightStyle {
9799                            font_weight: Some(gpui::FontWeight::BOLD),
9800                            ..Default::default()
9801                        },
9802                    )],
9803                    cx,
9804                );
9805                state.present_hover(
9806                    0..6,
9807                    lsp_types::Hover {
9808                        contents: lsp_types::HoverContents::Scalar(
9809                            lsp_types::MarkedString::String("docs".into()),
9810                        ),
9811                        range: None,
9812                    },
9813                    cx,
9814                );
9815                assert!(state.hover_popover().is_some());
9816
9817                state.on_blur(window, cx);
9818
9819                assert!(
9820                    state.hover_popover().is_none(),
9821                    "blur should hide the hover popover"
9822                );
9823                let decorations = state.extras.decoration_layers();
9824                assert!(
9825                    decorations.iter().any(|layer| !layer.is_empty()),
9826                    "blur must not discard decorations"
9827                );
9828            });
9829        });
9830    }
9831
9832    /// The mode marker is the only source of truth for the kind of input.
9833    ///
9834    /// An auto-growing textarea capped at one row used to report itself as
9835    /// single-line, because the answer was derived from the row counts.
9836    #[gpui::test]
9837    fn test_kind_does_not_follow_the_row_count(cx: &mut TestAppContext) {
9838        let view = InputView::build_textarea(cx, |state| state.auto_grow(1, 1));
9839        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
9840        view.input.read_with(&mut cx, |state, _| {
9841            assert!(state.is_multi_line());
9842            assert!(!state.is_single_line());
9843            assert!(!state.is_code_editor());
9844        });
9845    }
9846
9847    #[gpui::test]
9848    fn test_textarea_prewrap_prepares_content_rows_before_layout(cx: &mut TestAppContext) {
9849        cx.update(crate::init);
9850        for (value, expected_rows) in [("", 1), ("one\ntwo", 2)] {
9851            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9852                let font = window.text_style().font();
9853                cx.new(|cx| {
9854                    let mut state = crate::input::TextareaState::new(window, cx)
9855                        .rows(12)
9856                        .default_value(value);
9857                    assert_eq!(state.display_map().wrap_row_count(), 0);
9858                    state.prewrap(font, px(14.), px(120.), cx);
9859                    assert_eq!(state.display_map().wrap_row_count(), expected_rows);
9860                    assert_eq!(state.value(), value);
9861                    state
9862                });
9863                gpui::EmptyView
9864            });
9865        }
9866    }
9867
9868    #[gpui::test]
9869    fn test_textarea_prewrap_respects_soft_wrap_before_layout(cx: &mut TestAppContext) {
9870        cx.update(crate::init);
9871        let value = "select id, name, email, phone from users where active\nlimit 10";
9872        for soft_wrap in [true, false] {
9873            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9874                let font = window.text_style().font();
9875                cx.new(|cx| {
9876                    let mut state = crate::input::TextareaState::new(window, cx)
9877                        .rows(1)
9878                        .soft_wrap(soft_wrap)
9879                        .default_value(value);
9880                    state.prewrap(font, px(14.), px(120.), cx);
9881                    let rows = state.display_map().wrap_row_count();
9882                    if soft_wrap {
9883                        assert!(rows > 2, "the first logical line must wrap");
9884                    } else {
9885                        assert_eq!(rows, 2);
9886                    }
9887                    assert_eq!(state.value(), value);
9888                    state
9889                });
9890                gpui::EmptyView
9891            });
9892        }
9893    }
9894
9895    #[gpui::test]
9896    fn test_prewrap_counts_wrapped_rows_before_the_first_paint(cx: &mut TestAppContext) {
9897        cx.update(crate::init);
9898        let text = "select id, name, email, phone from users where active\nlimit 10";
9899        for (soft_wrap, wraps) in [(true, true), (false, false)] {
9900            let mut input = None;
9901            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9902                let font = window.text_style().font();
9903                let editor = cx.new(|cx| {
9904                    let mut state = crate::input::EditorState::new(window, cx)
9905                        .soft_wrap(soft_wrap)
9906                        .default_value(text);
9907                    state.prewrap(font, px(14.), px(120.), cx);
9908                    state
9909                });
9910                input = Some(editor);
9911                gpui::EmptyView
9912            });
9913            let rows = input
9914                .unwrap()
9915                .read_with(cx, |state, _| state.display_map().wrap_row_count());
9916            // Two lines; at 120px the first wraps unless soft wrap is off.
9917            assert_eq!(rows > 2, wraps, "soft_wrap({soft_wrap}): {rows} rows");
9918            if !wraps {
9919                assert_eq!(rows, 2);
9920            }
9921        }
9922    }
9923
9924    #[gpui::test]
9925    fn test_prewrap_uses_the_configured_tab_stops(cx: &mut TestAppContext) {
9926        cx.update(crate::init);
9927        cx.open_window(size(px(400.), px(100.)), |window, cx| {
9928            let font = window.text_style().font();
9929            let font_size = px(14.);
9930            let font_id = window.text_system().resolve_font(&font);
9931            let space_width = window.text_system().layout_width(font_id, font_size, ' ');
9932            cx.new(|cx| {
9933                let mut state = crate::input::EditorState::new(window, cx)
9934                    .tab_size(TabSize {
9935                        tab_size: 4,
9936                        hard_tabs: true,
9937                    })
9938                    .default_value("\t\t");
9939                for (tab_size, expected_rows) in [(4, 2), (2, 1), (8, 2)] {
9940                    state.set_tab_size(
9941                        TabSize {
9942                            tab_size,
9943                            hard_tabs: true,
9944                        },
9945                        cx,
9946                    );
9947                    state.prewrap(font.clone(), font_size, space_width * 5. + RIGHT_MARGIN, cx);
9948                    assert_eq!(state.display_map().wrap_row_count(), expected_rows);
9949                }
9950                state
9951            });
9952            gpui::EmptyView
9953        });
9954    }
9955
9956    /// Prewrap counts with the state's wrapping indent, as the first layout
9957    /// does: continuation rows indented as deep as their line take more rows.
9958    #[gpui::test]
9959    fn test_prewrap_wraps_with_the_wrapping_indent(cx: &mut TestAppContext) {
9960        cx.update(crate::init);
9961        let text = "            select id, name, email, phone from users where active";
9962        let mut rows = Vec::new();
9963        for indent in [WrappingIndent::Same, WrappingIndent::None] {
9964            let mut input = None;
9965            cx.open_window(size(px(400.), px(100.)), |window, cx| {
9966                let font = window.text_style().font();
9967                input = Some(cx.new(|cx| {
9968                    let mut state = crate::input::EditorState::new(window, cx)
9969                        .wrapping_indent(indent)
9970                        .default_value(text);
9971                    state.prewrap(font, px(14.), px(200.), cx);
9972                    state
9973                }));
9974                gpui::EmptyView
9975            });
9976            rows.push(
9977                input
9978                    .unwrap()
9979                    .read_with(cx, |state, _| state.display_map().wrap_row_count()),
9980            );
9981        }
9982        assert!(rows[0] > rows[1], "rows with Same, None: {rows:?}");
9983    }
9984
9985    /// Once the editor is laid out, layout owns the wrap and prewrap leaves it.
9986    #[gpui::test]
9987    fn test_prewrap_does_nothing_once_laid_out(cx: &mut TestAppContext) {
9988        struct Frame(Entity<crate::input::EditorState>);
9989        impl Render for Frame {
9990            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
9991                crate::input::InputBase::new("frame")
9992                    .h(px(100.))
9993                    .child(crate::input::Editor::new(&self.0))
9994            }
9995        }
9996
9997        cx.update(crate::init);
9998        let text = "select id, name, email, phone from users where active\nlimit 10";
9999        let mut input = None;
10000        let window = cx.open_window(size(px(400.), px(100.)), |window, cx| {
10001            let state = cx.new(|cx| crate::input::EditorState::new(window, cx).default_value(text));
10002            input = Some(state.clone());
10003            Frame(state)
10004        });
10005        let input = input.unwrap();
10006        let mut cx = VisualTestContext::from_window(window.into(), cx);
10007        cx.update(|window, cx| window.draw(cx).clear(cx));
10008
10009        let laid_out = input.read_with(&cx, |state, _| state.display_map().wrap_row_count());
10010        assert!(laid_out >= 2, "laid out to {laid_out} rows");
10011        let font = cx.update(|window, _| window.text_style().font());
10012        input.update(&mut cx, |state, cx| {
10013            state.prewrap(font, px(14.), px(60.), cx)
10014        });
10015        assert_eq!(
10016            input.read_with(&cx, |state, _| state.display_map().wrap_row_count()),
10017            laid_out
10018        );
10019    }
10020
10021    /// Soft wrap is on by default, for every mode that can wrap.
10022    ///
10023    /// The default lives in the shared constructor, where a mode-specific
10024    /// `new` can silently fail to restore it; this pins it down.
10025    #[gpui::test]
10026    fn test_soft_wrap_is_enabled_by_default(cx: &mut TestAppContext) {
10027        let textarea = InputView::build_textarea(cx, |state| state);
10028        let mut textarea_cx = VisualTestContext::from_window(textarea.window_handle.into(), cx);
10029        textarea
10030            .input
10031            .read_with(&mut textarea_cx, |state, _| assert!(state.soft_wrap));
10032
10033        let editor = InputView::<EditorMode>::new(cx);
10034        let mut editor_cx = VisualTestContext::from_window(editor.window_handle.into(), cx);
10035        editor
10036            .input
10037            .read_with(&mut editor_cx, |state, _| assert!(state.soft_wrap));
10038    }
10039
10040    /// Parse a cursor spec into `(text, cursor_offsets)`. Non-empty lines are
10041    /// joined with `\n` plus a trailing `\n`. `|` marks a cursor. Leading
10042    /// whitespace is kept, so a spec can express indentation.
10043    fn parse_cursor_spec(input: &str) -> (String, Vec<usize>) {
10044        let mut full_text = String::new();
10045        let mut cursor_offsets = Vec::new();
10046        let non_empty_lines: Vec<&str> = input.lines().filter(|l| !l.is_empty()).collect();
10047
10048        for (line_idx, line) in non_empty_lines.iter().enumerate() {
10049            let mut positions = Vec::new();
10050            let mut text = String::new();
10051            for ch in line.chars() {
10052                if ch == '|' {
10053                    positions.push(text.len());
10054                } else {
10055                    text.push(ch);
10056                }
10057            }
10058
10059            if line_idx > 0 {
10060                full_text.push('\n');
10061            }
10062            let line_start = full_text.len();
10063            for pos in positions {
10064                cursor_offsets.push(line_start + pos);
10065            }
10066            full_text.push_str(&text);
10067        }
10068        full_text.push('\n');
10069
10070        (full_text, cursor_offsets)
10071    }
10072
10073    /// Build a multi-line input for multi-cursor tests.
10074    fn multi_line(cx: &mut TestAppContext) -> InputView<TextareaMode> {
10075        InputView::build_textarea(cx, |state| state)
10076    }
10077
10078    /// Set the text and cursor positions from a spec (see [`parse_cursor_spec`]).
10079    fn setup_cursors<M: InputModeKind>(
10080        cx: &mut VisualTestContext,
10081        input: &Entity<InputBaseState<M>>,
10082        spec: &str,
10083    ) {
10084        let (full_text, offsets) = parse_cursor_spec(spec);
10085        cx.update(|window, cx| {
10086            input.update(cx, |state, cx| {
10087                state.set_value(&full_text, window, cx);
10088                let selections = offsets
10089                    .into_iter()
10090                    .map(|offset| {
10091                        CursorSelection::new(state.selections.generate_id(), offset, offset)
10092                    })
10093                    .collect();
10094                state.selections.replace_all(selections);
10095                cx.notify();
10096            });
10097        });
10098    }
10099
10100    /// Assert the text and cursor positions match a spec.
10101    #[track_caller]
10102    fn assert_cursors<M: InputModeKind>(
10103        cx: &mut VisualTestContext,
10104        input: &Entity<InputBaseState<M>>,
10105        spec: &str,
10106    ) {
10107        let (expected_text, mut expected_cursors) = parse_cursor_spec(spec);
10108        expected_cursors.sort();
10109
10110        let (actual_text, mut actual_cursors) = input.read_with(cx, |state, _| {
10111            (
10112                state.text.to_string(),
10113                state
10114                    .selections
10115                    .iter()
10116                    .map(|s| s.cursor_offset())
10117                    .collect::<Vec<_>>(),
10118            )
10119        });
10120        actual_cursors.sort();
10121
10122        assert_eq!(
10123            actual_text, expected_text,
10124            "Text mismatch:\nExpected: {expected_text:?}\nActual:   {actual_text:?}"
10125        );
10126        assert_eq!(
10127            actual_cursors, expected_cursors,
10128            "Cursor mismatch:\nExpected: {expected_cursors:?}\nActual:   {actual_cursors:?}"
10129        );
10130    }
10131
10132    #[gpui::test]
10133    fn test_alt_drag_selects_a_block_and_replaces_each_row(cx: &mut TestAppContext) {
10134        cx.update(crate::init);
10135        let view = InputView::<EditorMode>::new(cx);
10136        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10137        for modifiers in [
10138            gpui::Modifiers {
10139                alt: true,
10140                ..Default::default()
10141            },
10142            gpui::Modifiers {
10143                alt: true,
10144                shift: true,
10145                ..Default::default()
10146            },
10147            #[cfg(target_os = "linux")]
10148            gpui::Modifiers {
10149                alt: true,
10150                control: true,
10151                ..Default::default()
10152            },
10153        ] {
10154            setup_cursors(&mut cx, &view.input, "|abcd\nabcd\nabcd");
10155            cx.update(|window, cx| {
10156                view.input.update(cx, |state, cx| state.focus(window, cx));
10157            });
10158            let (start, end) = view.input.read_with(&cx, |state, _| {
10159                let layout = state.last_layout.as_ref().unwrap();
10160                let origin = state.last_bounds.unwrap().origin;
10161                let position = |row: usize, col| {
10162                    let local = layout.lines[row]
10163                        .position_for_index(col, layout, false)
10164                        .unwrap();
10165                    origin
10166                        + point(
10167                            layout.line_number_width + local.x,
10168                            layout.line_height * (row as f32 + 0.5),
10169                        )
10170                };
10171                (position(0, 1), position(2, 3))
10172            });
10173            // Land inside the third column instead of exactly on its trailing
10174            // glyph boundary, where pixel rounding can select the next column.
10175            let end = point(end.x - px(0.5), end.y);
10176            // A cached Ctrl-hover definition must not steal a column gesture.
10177            cx.update(|_, cx| {
10178                view.input.update(cx, |state, _| {
10179                    state.extras.hover_definition.update(
10180                        0..4,
10181                        vec![lsp_types::LocationLink {
10182                            origin_selection_range: None,
10183                            target_uri: "file:///tmp/column-selection.rs".parse().unwrap(),
10184                            target_range: Default::default(),
10185                            target_selection_range: Default::default(),
10186                        }],
10187                    );
10188                });
10189            });
10190            cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
10191            cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
10192            cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
10193            view.input.read_with(&cx, |state, _| {
10194                let ranges: Vec<_> = state
10195                    .selections
10196                    .iter()
10197                    .map(|sel| sel.start..sel.end)
10198                    .collect();
10199                assert_eq!(ranges, vec![1..3, 6..8, 11..13]);
10200            });
10201            // Moving after release must leave the block intact.
10202            cx.simulate_mouse_move(start, None, modifiers);
10203            cx.simulate_keystrokes("x");
10204            assert_cursors(&mut cx, &view.input, "ax|d\nax|d\nax|d");
10205        }
10206    }
10207
10208    #[gpui::test]
10209    fn test_alt_drag_extends_upward_from_an_existing_cursor(cx: &mut TestAppContext) {
10210        cx.update(crate::init);
10211        let view = InputView::<EditorMode>::new(cx);
10212        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10213        setup_cursors(&mut cx, &view.input, "abcd\nabcd\na|bcd");
10214        cx.update(|window, cx| {
10215            view.input.update(cx, |state, cx| state.focus(window, cx));
10216        });
10217        let (start, end) = view.input.read_with(&cx, |state, _| {
10218            let layout = state.last_layout.as_ref().unwrap();
10219            let origin = state.last_bounds.unwrap().origin;
10220            let local = layout.lines[0]
10221                .position_for_index(1, layout, false)
10222                .unwrap();
10223            let x = layout.line_number_width + local.x;
10224            (
10225                origin + point(x, layout.line_height * 2.5),
10226                origin + point(x, layout.line_height * 0.5),
10227            )
10228        });
10229        let modifiers = gpui::Modifiers {
10230            alt: true,
10231            ..Default::default()
10232        };
10233        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
10234        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
10235        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
10236        assert_cursors(&mut cx, &view.input, "a|bcd\na|bcd\na|bcd");
10237    }
10238
10239    #[gpui::test]
10240    fn test_alt_drag_over_a_short_row_keeps_the_block_width(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, "abcdef\nab\nabcdef");
10245        cx.update(|window, cx| {
10246            view.input.update(cx, |state, cx| state.focus(window, cx));
10247        });
10248        // Drag from row 0 column 1 down to row 1, at the x of row 0's column 5. Row 1
10249        // only has two characters, so the pointer ends past its end.
10250        let (start, end) = view.input.read_with(&cx, |state, _| {
10251            let layout = state.last_layout.as_ref().unwrap();
10252            let origin = state.last_bounds.unwrap().origin;
10253            let x_of = |index| {
10254                layout.line_number_width
10255                    + layout.lines[0]
10256                        .position_for_index(index, layout, false)
10257                        .unwrap()
10258                        .x
10259            };
10260            (
10261                origin + point(x_of(1), layout.line_height * 0.5),
10262                origin + point(x_of(5), layout.line_height * 1.5),
10263            )
10264        });
10265        let modifiers = gpui::Modifiers {
10266            alt: true,
10267            shift: true,
10268            ..Default::default()
10269        };
10270        cx.simulate_mouse_down(start, MouseButton::Left, modifiers);
10271        cx.simulate_mouse_move(end, MouseButton::Left, modifiers);
10272        cx.simulate_mouse_up(end, MouseButton::Left, modifiers);
10273
10274        // The short row is clipped to its own end; the long row keeps the full span.
10275        view.input.read_with(&cx, |state, _| {
10276            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
10277            assert_eq!(ranges, vec![(1, 5), (8, 9)]);
10278        });
10279    }
10280
10281    #[gpui::test]
10282    fn test_alt_mouse_release_outside_editor_ends_column_selection(cx: &mut TestAppContext) {
10283        cx.update(crate::init);
10284        let view = InputView::<EditorMode>::new(cx);
10285        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10286        setup_cursors(&mut cx, &view.input, "|abcd\nabcd");
10287        let position = view.input.read_with(&cx, |state, _| {
10288            let layout = state.last_layout.as_ref().unwrap();
10289            state.last_bounds.unwrap().origin
10290                + point(layout.line_number_width + px(2.), layout.line_height * 0.5)
10291        });
10292        let modifiers = gpui::Modifiers {
10293            alt: true,
10294            ..Default::default()
10295        };
10296        cx.simulate_mouse_down(position, MouseButton::Left, modifiers);
10297        cx.simulate_mouse_up(point(px(-100.), px(-100.)), MouseButton::Left, modifiers);
10298        view.input.read_with(&cx, |state, _| {
10299            assert!(!state.selecting);
10300            assert!(state.column_select_start.is_none());
10301        });
10302    }
10303
10304    #[gpui::test]
10305    fn test_consumed_keystrokes_keep_cursor_visible(cx: &mut TestAppContext) {
10306        let view = InputView::<EditorMode>::new(cx);
10307        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10308        setup_cursors(&mut cx, &view.input, "a|b");
10309        cx.update(|window, cx| {
10310            view.input.update(cx, |state, cx| {
10311                state.focus(window, cx);
10312                state.pause_blink_cursor(cx);
10313            });
10314        });
10315        cx.run_until_parked();
10316        cx.executor()
10317            .advance_clock(std::time::Duration::from_millis(300));
10318        cx.run_until_parked();
10319        view.input.read_with(&cx, |state, cx| {
10320            assert!(!state.blink_cursor.read(cx).visible());
10321        });
10322        // Copy consumes its shortcut without editing text or moving selections.
10323        for _ in 0..5 {
10324            #[cfg(target_os = "macos")]
10325            cx.simulate_keystrokes("cmd-c");
10326            #[cfg(not(target_os = "macos"))]
10327            cx.simulate_keystrokes("ctrl-c");
10328            cx.run_until_parked();
10329            cx.executor()
10330                .advance_clock(std::time::Duration::from_millis(200));
10331            cx.run_until_parked();
10332            view.input.read_with(&cx, |state, cx| {
10333                assert!(state.blink_cursor.read(cx).visible());
10334            });
10335        }
10336    }
10337
10338    #[gpui::test]
10339    fn test_mouse_down_shows_cursor_like_a_cursor_move(cx: &mut TestAppContext) {
10340        cx.update(crate::init);
10341        let view = InputView::<EditorMode>::new(cx);
10342        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10343        setup_cursors(&mut cx, &view.input, "abcd|efgh");
10344        let position = |offset: Pixels, cx: &mut VisualTestContext| {
10345            view.input.read_with(cx, |state, _| {
10346                let layout = state.last_layout.as_ref().unwrap();
10347                state.last_bounds.unwrap().origin
10348                    + point(layout.line_number_width + offset, layout.line_height * 0.5)
10349            })
10350        };
10351        let visible = |cx: &mut VisualTestContext| {
10352            view.input
10353                .read_with(cx, |state, cx| state.blink_cursor.read(cx).visible())
10354        };
10355
10356        // The press itself shows the caret, without waiting for the focus
10357        // observer, and the idle delay holds it there like a cursor move.
10358        let first = position(px(2.), &mut cx);
10359        cx.simulate_mouse_down(first, MouseButton::Left, Default::default());
10360        assert!(visible(&mut cx));
10361        cx.simulate_mouse_up(first, MouseButton::Left, Default::default());
10362        cx.run_until_parked();
10363
10364        // Pressing elsewhere in the focused input, in the hidden phase of the
10365        // blink, shows the caret again at the press.
10366        cx.executor()
10367            .advance_clock(std::time::Duration::from_millis(400));
10368        cx.run_until_parked();
10369        assert!(!visible(&mut cx));
10370        let second = position(px(30.), &mut cx);
10371        cx.simulate_mouse_down(second, MouseButton::Left, Default::default());
10372        assert!(visible(&mut cx));
10373        cx.executor()
10374            .advance_clock(std::time::Duration::from_millis(250));
10375        cx.run_until_parked();
10376        assert!(visible(&mut cx));
10377        cx.simulate_mouse_up(second, MouseButton::Left, Default::default());
10378    }
10379
10380    #[gpui::test]
10381    fn test_multi_cursor_actions_reveal_hidden_carets(cx: &mut TestAppContext) {
10382        let view = InputView::<EditorMode>::new(cx);
10383        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10384        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
10385        // Start each action in the hidden phase without depending on a
10386        // key-down listener: actions and text input also arrive directly.
10387        for action in 0..6 {
10388            cx.update(|_, cx| {
10389                view.input.update(cx, |state, cx| {
10390                    state.blink_cursor = cx.new(BlinkCursor::new);
10391                    state.blink_cursor.update(cx, |cursor, cx| cursor.start(cx));
10392                });
10393            });
10394            cx.run_until_parked();
10395            cx.executor()
10396                .advance_clock(std::time::Duration::from_millis(500));
10397            cx.run_until_parked();
10398            view.input.read_with(&cx, |state, cx| {
10399                assert!(!state.blink_cursor.read(cx).visible(), "action {action}");
10400            });
10401
10402            cx.update(|window, cx| {
10403                view.input.update(cx, |state, cx| {
10404                    match action {
10405                        0 => state.add_cursor_above(&AddCursorAbove, window, cx),
10406                        1 => state.add_cursor_below(&AddCursorBelow, window, cx),
10407                        2 => state.select_up(&SelectUp, window, cx),
10408                        3 => state.select_down(&SelectDown, window, cx),
10409                        4 => state.replace_text_in_range(None, "x", window, cx),
10410                        _ => state.backspace(&Backspace, window, cx),
10411                    }
10412                    assert!(state.blink_cursor.read(cx).visible(), "action {action}");
10413                });
10414            });
10415        }
10416    }
10417
10418    #[gpui::test]
10419    fn test_multi_cursor_keyboard_dispatch(cx: &mut TestAppContext) {
10420        let view = InputView::<EditorMode>::new(cx);
10421        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10422        setup_cursors(&mut cx, &view.input, "ab\na|b\nab");
10423        cx.update(|window, cx| {
10424            view.input.update(cx, |state, cx| state.focus(window, cx));
10425        });
10426        #[cfg(target_os = "macos")]
10427        cx.simulate_keystrokes("cmd-alt-up");
10428        #[cfg(target_os = "windows")]
10429        cx.simulate_keystrokes("ctrl-alt-up");
10430        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
10431        cx.simulate_keystrokes("alt-shift-up");
10432        view.input
10433            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
10434        #[cfg(target_os = "macos")]
10435        cx.simulate_keystrokes("cmd-alt-down");
10436        #[cfg(target_os = "windows")]
10437        cx.simulate_keystrokes("ctrl-alt-down");
10438        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
10439        cx.simulate_keystrokes("alt-shift-down");
10440        view.input
10441            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 3));
10442        cx.simulate_keystrokes("x");
10443        assert_cursors(&mut cx, &view.input, "ax|b\nax|b\nax|b");
10444    }
10445
10446    #[gpui::test]
10447    fn test_multi_cursor_platform_word_selection_dispatch(cx: &mut TestAppContext) {
10448        let view = InputView::<EditorMode>::new(cx);
10449        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10450        setup_cursors(&mut cx, &view.input, "one |two\none |two");
10451        cx.update(|window, cx| {
10452            view.input.update(cx, |state, cx| state.focus(window, cx));
10453        });
10454        #[cfg(any(target_os = "macos", target_os = "linux"))]
10455        cx.simulate_keystrokes("alt-shift-right");
10456        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
10457        cx.simulate_keystrokes("ctrl-shift-right");
10458        cx.simulate_keystrokes("x");
10459        assert_cursors(&mut cx, &view.input, "one x|\none x|");
10460        setup_cursors(&mut cx, &view.input, "one two|\none two|");
10461        #[cfg(any(target_os = "macos", target_os = "linux"))]
10462        cx.simulate_keystrokes("alt-shift-left");
10463        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
10464        cx.simulate_keystrokes("ctrl-shift-left");
10465        cx.simulate_keystrokes("x");
10466        assert_cursors(&mut cx, &view.input, "one x|\none x|");
10467    }
10468
10469    #[cfg(not(target_os = "macos"))]
10470    #[gpui::test]
10471    fn test_multi_cursor_horizontal_selection_dispatch(cx: &mut TestAppContext) {
10472        let view = InputView::<EditorMode>::new(cx);
10473        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10474        setup_cursors(&mut cx, &view.input, "ab\na|b");
10475        cx.update(|window, cx| {
10476            view.input.update(cx, |state, cx| state.focus(window, cx));
10477        });
10478        #[cfg(target_os = "windows")]
10479        cx.simulate_keystrokes("ctrl-alt-up");
10480        #[cfg(not(target_os = "windows"))]
10481        cx.simulate_keystrokes("alt-shift-up");
10482        #[cfg(target_os = "linux")]
10483        cx.simulate_keystrokes("shift-right");
10484        #[cfg(not(target_os = "linux"))]
10485        cx.simulate_keystrokes("alt-shift-right");
10486        view.input.read_with(&cx, |state, _| {
10487            assert_eq!(
10488                state
10489                    .selections
10490                    .iter()
10491                    .map(|s| s.start..s.end)
10492                    .collect::<Vec<_>>(),
10493                vec![4..5, 1..2]
10494            );
10495        });
10496        #[cfg(target_os = "linux")]
10497        cx.simulate_keystrokes("shift-left shift-left");
10498        #[cfg(not(target_os = "linux"))]
10499        cx.simulate_keystrokes("alt-shift-left alt-shift-left");
10500        view.input.read_with(&cx, |state, _| {
10501            assert_eq!(
10502                state
10503                    .selections
10504                    .iter()
10505                    .map(|s| s.start..s.end)
10506                    .collect::<Vec<_>>(),
10507                vec![3..4, 0..1]
10508            );
10509        });
10510        cx.simulate_keystrokes("x");
10511        assert_cursors(&mut cx, &view.input, "x|b\nx|b");
10512    }
10513
10514    #[gpui::test]
10515    fn test_multi_cursor_alt_click_dispatch(cx: &mut TestAppContext) {
10516        cx.update(crate::init);
10517        let view = InputView::<EditorMode>::new(cx);
10518        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10519        setup_cursors(&mut cx, &view.input, "a|b\nab\nab");
10520        cx.update(|window, cx| {
10521            view.input.update(cx, |state, cx| state.focus(window, cx));
10522        });
10523        let position = view.input.read_with(&cx, |state, _| {
10524            let bounds = state.last_bounds.unwrap();
10525            let layout = state.last_layout.as_ref().unwrap();
10526            bounds.origin + point(layout.line_number_width + px(2.), layout.line_height * 1.5)
10527        });
10528        cx.simulate_click(
10529            position,
10530            gpui::Modifiers {
10531                alt: true,
10532                ..Default::default()
10533            },
10534        );
10535        view.input
10536            .read_with(&cx, |state, _| assert_eq!(state.selections.len(), 2));
10537    }
10538
10539    #[gpui::test]
10540    fn test_add_selection(cx: &mut TestAppContext) {
10541        let view = InputView::<EditorMode>::new(cx);
10542        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10543        cx.update(|window, cx| {
10544            view.input.update(cx, |state, cx| {
10545                state.focus(window, cx);
10546                state.set_value("foo bar foo", window, cx);
10547                state.set_selected_range(0..3, cx);
10548                // The programmatic second cursor: the next occurrence.
10549                state.add_selection(8..11, cx);
10550                // Adding a range that is already selected changes nothing.
10551                state.add_selection(0..3, cx);
10552            });
10553        });
10554        view.input.read_with(&cx, |state, _| {
10555            let mut ranges: Vec<Range<usize>> =
10556                state.selections.iter().map(|s| s.start..s.end).collect();
10557            ranges.sort_by_key(|r| r.start);
10558            assert_eq!(ranges, vec![0..3, 8..11]);
10559        });
10560        // Typing replaces every selection at once.
10561        cx.simulate_keystrokes("x");
10562        view.input.read_with(&cx, |state, _| {
10563            assert_eq!(state.text.to_string(), "x bar x")
10564        });
10565    }
10566
10567    #[gpui::test]
10568    fn test_add_selection_preserves_crlf_boundaries(cx: &mut TestAppContext) {
10569        let view = InputView::build_textarea(cx, |state| state.default_value("a\r\nb"));
10570        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10571        cx.update(|window, cx| {
10572            view.input.update(cx, |state, cx| {
10573                state.focus(window, cx);
10574                state.set_selected_range(4..4, cx);
10575                state.add_selection(2..2, cx);
10576                let ranges: Vec<_> = state.selections.iter().map(|s| s.start..s.end).collect();
10577                assert_eq!(ranges, vec![4..4, 1..1]);
10578            });
10579        });
10580        cx.simulate_keystrokes("x");
10581        view.input.read_with(&cx, |state, _| {
10582            assert_eq!(state.value(), "ax\r\nbx");
10583        });
10584        cx.update(|window, cx| {
10585            view.input.update(cx, |state, cx| {
10586                state.set_value("a\r\nb", window, cx);
10587                state.set_selected_range(4..4, cx);
10588                state.add_selection(2..3, cx);
10589                state.cut(&Cut, window, cx);
10590                assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "\r\n");
10591                assert_eq!(state.value(), "ab");
10592            });
10593        });
10594    }
10595
10596    #[gpui::test]
10597    fn test_add_selection_merges_overlaps_before_copy_and_cut(cx: &mut TestAppContext) {
10598        let view = InputView::<EditorMode>::new(cx);
10599        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10600        cx.update(|window, cx| {
10601            view.input.update(cx, |state, cx| {
10602                // Cover partial overlaps, a containing range, and a bridge
10603                // between two previously disjoint selections.
10604                for additions in [vec![2..5], vec![0..5], vec![4..5, 2..4]] {
10605                    state.set_value("abcdef", window, cx);
10606                    state.set_selected_range(0..3, cx);
10607                    let active_id = state.active_selection().id;
10608                    for range in additions {
10609                        state.add_selection(range, cx);
10610                    }
10611                    assert_eq!(state.active_selection().id, active_id);
10612                    assert_eq!(state.selected_range(), 0..5);
10613                    assert_eq!(state.selections.len(), 1);
10614                    state.copy(&Copy, window, cx);
10615                    assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "abcde");
10616                    state.cut(&Cut, window, cx);
10617                    assert_eq!(cx.read_from_clipboard().unwrap().text().unwrap(), "abcde");
10618                    assert_eq!(state.value(), "f");
10619                    state.undo(&Undo, window, cx);
10620                    assert_eq!(state.value(), "abcdef");
10621                    assert_eq!(state.selected_range(), 0..5);
10622                }
10623            });
10624        });
10625    }
10626
10627    #[gpui::test]
10628    fn test_add_selection_clears_inline_completion(cx: &mut TestAppContext) {
10629        use crate::input::CompletionProvider;
10630        use gpui::Task;
10631        use lsp_types::{
10632            CompletionContext, CompletionResponse, InlineCompletionContext, InlineCompletionItem,
10633            InlineCompletionResponse,
10634        };
10635        use std::time::Duration;
10636
10637        struct Provider(Rc<Cell<usize>>);
10638
10639        impl CompletionProvider for Provider {
10640            fn is_completion_trigger(&self, _: usize, _: &str, _: &mut App) -> bool {
10641                false
10642            }
10643
10644            fn completions(
10645                &self,
10646                _: &Rope,
10647                _: usize,
10648                _: CompletionContext,
10649                _: &mut Window,
10650                _: &mut App,
10651            ) -> Task<anyhow::Result<CompletionResponse>> {
10652                Task::ready(Ok(CompletionResponse::Array(vec![])))
10653            }
10654
10655            fn inline_completion(
10656                &self,
10657                _: &Rope,
10658                _: usize,
10659                _: InlineCompletionContext,
10660                _: &mut Window,
10661                _: &mut App,
10662            ) -> Task<anyhow::Result<InlineCompletionResponse>> {
10663                self.0.set(self.0.get() + 1);
10664                Task::ready(Ok(InlineCompletionResponse::Array(vec![])))
10665            }
10666        }
10667
10668        let view = InputView::<EditorMode>::new(cx);
10669        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10670        let calls = Rc::new(Cell::new(0));
10671        cx.update(|window, cx| {
10672            view.input.update(cx, |state, cx| {
10673                state.focus(window, cx);
10674                state.set_value("aa\naa", window, cx);
10675                state.set_selected_range(2..2, cx);
10676                state.extras.inline_completion.item = Some(InlineCompletionItem {
10677                    insert_text: "suggestion".into(),
10678                    filter_text: None,
10679                    range: None,
10680                    command: None,
10681                    insert_text_format: None,
10682                });
10683                state.add_selection(5..5, cx);
10684                assert!(!state.has_inline_completion());
10685                state.indent_inline(&IndentInline, window, cx);
10686                assert_eq!(state.value(), "aa  \naa  ");
10687                assert_eq!(state.selections.len(), 2);
10688
10689                // The active cursor stays put, so only canceling the task
10690                // prevents this pending request from fetching a suggestion.
10691                state.set_value("aa\naa", window, cx);
10692                state.set_selected_range(2..2, cx);
10693                state.extras.lsp.completion_provider = Some(Rc::new(Provider(calls.clone())));
10694                state.schedule_inline_completion(window, cx);
10695            });
10696        });
10697        cx.run_until_parked();
10698        cx.update(|_, cx| {
10699            view.input
10700                .update(cx, |state, cx| state.add_selection(5..5, cx));
10701        });
10702        cx.executor().advance_clock(Duration::from_secs(1));
10703        cx.run_until_parked();
10704        assert_eq!(calls.get(), 0);
10705        view.input.read_with(&cx, |state, _| {
10706            assert!(!state.has_inline_completion());
10707            assert_eq!(state.selections.len(), 2);
10708            assert_eq!(state.value(), "aa\naa");
10709        });
10710    }
10711
10712    #[gpui::test]
10713    fn test_word_delete_undo_restores_caret(cx: &mut TestAppContext) {
10714        let view = multi_line(cx);
10715        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10716        setup_cursors(&mut cx, &view.input, "hello|");
10717        cx.update(|window, cx| {
10718            view.input.update(cx, |state, cx| {
10719                state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
10720                state.undo(&Undo, window, cx);
10721                assert_eq!(state.selected_range(), 5..5);
10722            });
10723        });
10724    }
10725
10726    #[gpui::test]
10727    fn test_merged_delete_undo_restores_all_carets(cx: &mut TestAppContext) {
10728        let view = multi_line(cx);
10729        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10730        setup_cursors(&mut cx, &view.input, "a|b|c");
10731        cx.update(|window, cx| {
10732            view.input.update(cx, |state, cx| {
10733                state.backspace(&Backspace, window, cx);
10734                state.undo(&Undo, window, cx);
10735            });
10736        });
10737        assert_cursors(&mut cx, &view.input, "a|b|c");
10738    }
10739
10740    #[gpui::test]
10741    fn test_shift_end_respects_soft_wrap_end(cx: &mut TestAppContext) {
10742        let view = InputView::<EditorMode>::new(cx);
10743        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10744        cx.update(|window, cx| {
10745            view.input.update(cx, |state, cx| {
10746                state.set_value("abcdef ".repeat(30), window, cx);
10747                state.display_map.on_layout_changed(Some(px(60.)), cx);
10748                let line = state.display_map.line(0).unwrap();
10749                assert!(line.wrapped_lines.len() > 1);
10750                let boundary = line.wrapped_lines[0].end;
10751                state.move_to_with_affinity(boundary, None, true, cx);
10752                state.select_to_end_of_line(&SelectToEndOfLine, window, cx);
10753                assert_eq!(state.selected_range(), boundary..state.text.len());
10754            });
10755        });
10756    }
10757
10758    #[gpui::test]
10759    fn test_outdent_unindented_unicode_is_unchanged(cx: &mut TestAppContext) {
10760        let view = multi_line(cx);
10761        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10762        setup_cursors(&mut cx, &view.input, "|你好\n|世界");
10763        cx.update(|window, cx| {
10764            view.input.update(cx, |state, cx| {
10765                state.outdent(false, window, cx);
10766                state.outdent(true, window, cx);
10767            });
10768        });
10769        assert_cursors(&mut cx, &view.input, "|你好\n|世界");
10770    }
10771
10772    #[gpui::test]
10773    fn test_column_selection_stays_on_unicode_boundaries(cx: &mut TestAppContext) {
10774        let view = multi_line(cx);
10775        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10776        setup_cursors(&mut cx, &view.input, "ab\n你好\ncd");
10777        cx.update(|window, cx| {
10778            view.input.update(cx, |state, cx| {
10779                state.build_columnar_selection(
10780                    ColumnarPoint::new(1, 0),
10781                    ColumnarPoint::new(11, 0),
10782                    cx,
10783                );
10784                let text = state.value();
10785                for sel in state.selections.iter() {
10786                    assert!(text.is_char_boundary(sel.start));
10787                    assert!(text.is_char_boundary(sel.end));
10788                }
10789                state.replace_text_in_range(None, "X", window, cx);
10790            });
10791        });
10792    }
10793
10794    #[gpui::test]
10795    fn test_editor_decorations_follow_typing(cx: &mut TestAppContext) {
10796        let view = InputView::<EditorMode>::new(cx);
10797        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10798        cx.update(|window, cx| {
10799            view.input.update(cx, |state, cx| {
10800                state.set_value("abc def", window, cx);
10801                state.create_decorations_collection(
10802                    vec![crate::input::TextDecoration::new(
10803                        4..7,
10804                        gpui::HighlightStyle::default(),
10805                    )],
10806                    cx,
10807                );
10808                state.set_selected_range(0..0, cx);
10809                state.replace_text_in_range(None, "X", window, cx);
10810                let layers = state.extras.decoration_layers();
10811                assert_eq!(layers.into_iter().flatten().next().unwrap().range, 5..8);
10812            });
10813        });
10814    }
10815
10816    #[gpui::test]
10817    fn test_backspace_preserves_escaped_quote_terminator(cx: &mut TestAppContext) {
10818        let view = InputView::<EditorMode>::new(cx);
10819        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10820        setup_cursors(&mut cx, &view.input, r#"{"s":"\"|"}"#);
10821        cx.update(|window, cx| {
10822            view.input.update(cx, |state, cx| {
10823                set_test_syntax_provider(std::rc::Rc::new(StringAllProvider), cx);
10824                state.backspace(&Backspace, window, cx);
10825            });
10826        });
10827        assert_cursors(&mut cx, &view.input, r#"{"s":"\|"}"#);
10828    }
10829
10830    #[gpui::test]
10831    fn test_ordinary_typing_does_not_query_syntax(cx: &mut TestAppContext) {
10832        struct UnexpectedQuery;
10833        impl crate::input::SyntaxContextProvider for UnexpectedQuery {
10834            fn context_at(&self, _: &ropey::Rope, _: usize) -> crate::input::SyntaxContext {
10835                panic!("ordinary typing must not query syntax");
10836            }
10837        }
10838        let view = InputView::<EditorMode>::new(cx);
10839        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10840        setup_cursors(&mut cx, &view.input, "|");
10841        cx.update(|window, cx| {
10842            view.input.update(cx, |state, cx| {
10843                set_test_syntax_provider(std::rc::Rc::new(UnexpectedQuery), cx);
10844                for c in ["a", "b", "c"] {
10845                    state.replace_text_in_range(None, c, window, cx);
10846                }
10847            });
10848        });
10849        assert_cursors(&mut cx, &view.input, "abc|");
10850    }
10851
10852    #[gpui::test]
10853    fn test_auto_close_is_atomic_at_history_limit(cx: &mut TestAppContext) {
10854        let view = InputView::<EditorMode>::new(cx);
10855        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10856        setup_cursors(&mut cx, &view.input, "|");
10857        cx.update(|window, cx| {
10858            view.input.update(cx, |state, cx| {
10859                for _ in 0..999 {
10860                    state.replace_text_in_range(None, "a", window, cx);
10861                }
10862                state.replace_text_in_range(None, "(", window, cx);
10863                state.undo(&Undo, window, cx);
10864                assert!(!state.text.to_string().contains('('));
10865                assert!(!state.text.to_string().contains(')'));
10866                state.redo(&Redo, window, cx);
10867            });
10868        });
10869        assert_cursors(&mut cx, &view.input, &format!("{}(|)", "a".repeat(999)));
10870    }
10871
10872    #[gpui::test]
10873    fn test_comment_closer_is_inserted_literally(cx: &mut TestAppContext) {
10874        let view = InputView::<EditorMode>::new(cx);
10875        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10876        setup_cursors(&mut cx, &view.input, "// (|)");
10877        cx.update(|window, cx| {
10878            view.input.update(cx, |state, cx| {
10879                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
10880                state.replace_text_in_range(None, ")", window, cx);
10881            });
10882        });
10883        assert_cursors(&mut cx, &view.input, "// ()|)");
10884    }
10885
10886    #[gpui::test]
10887    fn test_explicit_range_skip_does_not_delete_existing_closer_on_undo(cx: &mut TestAppContext) {
10888        let view = InputView::<EditorMode>::new(cx);
10889        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10890        setup_cursors(&mut cx, &view.input, "(a|)");
10891        let before = view.input.read_with(&cx, |state, _| state.text.to_string());
10892        cx.update(|window, cx| {
10893            view.input.update(cx, |state, cx| {
10894                state.replace_text_in_range(Some(2..2), ")", window, cx);
10895            });
10896        });
10897        assert_cursors(&mut cx, &view.input, "(a)|");
10898        cx.update(|window, cx| {
10899            view.input
10900                .update(cx, |state, cx| state.undo(&Undo, window, cx));
10901        });
10902        view.input.read_with(&cx, |state, _| {
10903            assert_eq!(
10904                state.text.to_string(),
10905                before,
10906                "skip must not remove an existing closer on undo"
10907            );
10908        });
10909    }
10910
10911    #[gpui::test]
10912    fn test_pair_redo_restores_interior_cursor(cx: &mut TestAppContext) {
10913        let view = InputView::<EditorMode>::new(cx);
10914        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10915        setup_cursors(&mut cx, &view.input, "|");
10916        cx.update(|window, cx| {
10917            view.input.update(cx, |state, cx| {
10918                state.replace_text_in_range(None, "(", window, cx);
10919            });
10920        });
10921        assert_cursors(&mut cx, &view.input, "(|)");
10922        cx.update(|window, cx| {
10923            view.input.update(cx, |state, cx| {
10924                state.undo(&Undo, window, cx);
10925                state.redo(&Redo, window, cx);
10926            });
10927        });
10928        assert_cursors(&mut cx, &view.input, "(|)");
10929    }
10930
10931    #[gpui::test]
10932    fn test_pair_enter_redo_restores_interior_cursor(cx: &mut TestAppContext) {
10933        let view = InputView::<EditorMode>::new(cx);
10934        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10935        setup_cursors(&mut cx, &view.input, "{|}");
10936        cx.update(|window, cx| {
10937            view.input.update(cx, |state, cx| {
10938                state.enter(
10939                    &Enter {
10940                        secondary: false,
10941                        shift: false,
10942                    },
10943                    window,
10944                    cx,
10945                );
10946            });
10947        });
10948        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10949        cx.update(|window, cx| {
10950            view.input.update(cx, |state, cx| {
10951                state.undo(&Undo, window, cx);
10952            });
10953        });
10954        assert_cursors(&mut cx, &view.input, "{|}");
10955        cx.update(|window, cx| {
10956            view.input
10957                .update(cx, |state, cx| state.redo(&Redo, window, cx));
10958        });
10959        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
10960    }
10961
10962    #[gpui::test]
10963    fn test_auto_close_parens(cx: &mut TestAppContext) {
10964        let view = InputView::<EditorMode>::new(cx);
10965        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10966        setup_cursors(&mut cx, &view.input, "|");
10967        cx.update(|window, cx| {
10968            view.input.update(cx, |state, cx| {
10969                state.replace_text_in_range(None, "(", window, cx);
10970            });
10971        });
10972        assert_cursors(&mut cx, &view.input, "(|)");
10973        // Selection must be collapsed: typing next must insert, not replace.
10974        cx.update(|window, cx| {
10975            view.input.update(cx, |state, cx| {
10976                state.replace_text_in_range(None, "x", window, cx);
10977            });
10978        });
10979        assert_cursors(&mut cx, &view.input, "(x|)");
10980    }
10981
10982    #[gpui::test]
10983    fn test_auto_close_skip_over_closer(cx: &mut TestAppContext) {
10984        let view = InputView::<EditorMode>::new(cx);
10985        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
10986        setup_cursors(&mut cx, &view.input, "(|)");
10987        cx.update(|window, cx| {
10988            view.input.update(cx, |state, cx| {
10989                state.replace_text_in_range(None, ")", window, cx);
10990            });
10991        });
10992        // No duplicate closer, cursor moves past the existing one.
10993        assert_cursors(&mut cx, &view.input, "()|");
10994        // Selection must be collapsed, not covering blank space.
10995        view.input.read_with(&cx, |state, _| {
10996            assert!(state.active_selection().is_empty());
10997        });
10998    }
10999
11000    #[gpui::test]
11001    fn test_auto_close_no_pair_inside_word(cx: &mut TestAppContext) {
11002        let view = InputView::<EditorMode>::new(cx);
11003        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11004        setup_cursors(&mut cx, &view.input, "don|");
11005        cx.update(|window, cx| {
11006            view.input.update(cx, |state, cx| {
11007                state.replace_text_in_range(None, "'", window, cx);
11008            });
11009        });
11010        // Contraction: no auto-close.
11011        assert_cursors(&mut cx, &view.input, "don'|");
11012    }
11013
11014    #[gpui::test]
11015    fn test_auto_close_quote_after_cjk(cx: &mut TestAppContext) {
11016        let view = InputView::<EditorMode>::new(cx);
11017        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11018        // Unicode-safe previous-char lookup: must not panic on multi-byte prefix.
11019        // CJK counts as word-like, so no pairing — single quote inserted.
11020        setup_cursors(&mut cx, &view.input, "中|");
11021        cx.update(|window, cx| {
11022            view.input.update(cx, |state, cx| {
11023                state.replace_text_in_range(None, "'", window, cx);
11024            });
11025        });
11026        assert_cursors(&mut cx, &view.input, "中'|");
11027    }
11028
11029    #[gpui::test]
11030    fn test_auto_close_closer_skips_at_string_end(cx: &mut TestAppContext) {
11031        let view = InputView::<EditorMode>::new(cx);
11032        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11033        // Cursor before existing closing quote: typing `"` skips past it.
11034        setup_cursors(&mut cx, &view.input, "\"hello|\"");
11035        cx.update(|window, cx| {
11036            view.input.update(cx, |state, cx| {
11037                state.replace_text_in_range(None, "\"", window, cx);
11038            });
11039        });
11040        assert_cursors(&mut cx, &view.input, "\"hello\"|");
11041        view.input.read_with(&cx, |state, _| {
11042            assert!(state.active_selection().is_empty());
11043        });
11044    }
11045
11046    #[gpui::test]
11047    fn test_backspace_pair_with_cjk_prefix(cx: &mut TestAppContext) {
11048        let view = InputView::<EditorMode>::new(cx);
11049        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11050        // UTF-16 conversion: deleting `()` after CJK must not touch neighbors.
11051        setup_cursors(&mut cx, &view.input, "中(|)abc");
11052        cx.update(|window, cx| {
11053            view.input
11054                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
11055        });
11056        assert_cursors(&mut cx, &view.input, "中|abc");
11057    }
11058
11059    #[gpui::test]
11060    fn test_enter_split_respects_smart_indent_off(cx: &mut TestAppContext) {
11061        let view = InputView::<EditorMode>::new(cx);
11062        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11063        setup_cursors(&mut cx, &view.input, "{|}");
11064        cx.update(|window, cx| {
11065            view.input.update(cx, |state, cx| {
11066                state.set_smart_indent(false, window, cx);
11067                state.enter(
11068                    &Enter {
11069                        secondary: false,
11070                        shift: false,
11071                    },
11072                    window,
11073                    cx,
11074                );
11075            });
11076        });
11077        // Smart indent controls both structural splitting and extra indentation.
11078        assert_cursors(&mut cx, &view.input, "{\n|}");
11079    }
11080
11081    #[gpui::test]
11082    fn test_enter_split_is_independent_of_auto_close(cx: &mut TestAppContext) {
11083        let view = InputView::<EditorMode>::new(cx);
11084        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11085        setup_cursors(&mut cx, &view.input, "{|}");
11086        cx.update(|window, cx| {
11087            view.input.update(cx, |state, cx| {
11088                state.set_auto_close(false, window, cx);
11089                state.enter(
11090                    &Enter {
11091                        secondary: false,
11092                        shift: false,
11093                    },
11094                    window,
11095                    cx,
11096                );
11097            });
11098        });
11099        // Disabling automatic insertion does not disable structural indentation.
11100        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
11101    }
11102
11103    #[gpui::test]
11104    fn test_language_config_applies_before_render_and_on_language_change(cx: &mut TestAppContext) {
11105        use crate::input::{AutoClosingPair, set_language_config};
11106        let view = InputView::<EditorMode>::new(cx);
11107        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11108        cx.update(|window, cx| {
11109            set_language_config(
11110                "alpha",
11111                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
11112                cx,
11113            );
11114            set_language_config(
11115                "beta",
11116                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("‹", "›")]),
11117                cx,
11118            );
11119            let first = cx.new(|cx| EditorState::new(window, cx).language("ALPHA"));
11120            let second = cx.new(|cx| EditorState::new(window, cx).language("beta"));
11121            first.update(cx, |state, cx| {
11122                state.replace_text_in_range(None, "«", window, cx);
11123                assert_eq!(state.text().to_string(), "«»");
11124                state.set_value("", window, cx);
11125                state.set_smart_indent(false, window, cx);
11126                state.set_highlighter("beta", cx);
11127                assert!(!state.mode.is_smart_indent());
11128                state.replace_text_in_range(None, "‹", window, cx);
11129                assert_eq!(state.text().to_string(), "‹›");
11130                state.set_value("", window, cx);
11131                state.set_highlighter("unknown", cx);
11132                state.replace_text_in_range(None, "(", window, cx);
11133                assert_eq!(state.text().to_string(), "()");
11134            });
11135            second.update(cx, |state, cx| {
11136                state.replace_text_in_range(None, "‹", window, cx);
11137                assert_eq!(state.text().to_string(), "‹›");
11138            });
11139        });
11140    }
11141
11142    #[gpui::test]
11143    fn test_language_service_replacement_updates_syntax_without_render(cx: &mut TestAppContext) {
11144        use crate::input::{LanguageProvider, SyntaxContextProvider, set_language_provider};
11145        struct StringLanguages(Rc<Cell<usize>>);
11146        impl LanguageProvider for StringLanguages {
11147            fn syntax_context_provider(&self, _: &str) -> Option<Rc<dyn SyntaxContextProvider>> {
11148                self.0.set(self.0.get() + 1);
11149                Some(Rc::new(StringAllProvider))
11150            }
11151        }
11152        struct CodeLanguages;
11153        impl LanguageProvider for CodeLanguages {}
11154        let view = InputView::<EditorMode>::new(cx);
11155        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11156        cx.update(|window, cx| {
11157            let creations = Rc::new(Cell::new(0));
11158            set_language_provider(Rc::new(StringLanguages(creations.clone())), cx);
11159            view.input.update(cx, |state, cx| {
11160                state.replace_text_in_range(None, "(", window, cx);
11161                state.replace_text_in_range(None, "[", window, cx);
11162                assert_eq!(state.text().to_string(), "([");
11163                assert_eq!(
11164                    creations.get(),
11165                    1,
11166                    "retain the document provider between edits"
11167                );
11168            });
11169            set_language_provider(Rc::new(CodeLanguages), cx);
11170            view.input.update(cx, |state, cx| {
11171                state.replace_text_in_range(None, "{", window, cx);
11172                assert_eq!(state.text().to_string(), "([{}");
11173            });
11174        });
11175    }
11176
11177    #[gpui::test]
11178    fn test_language_config_updates_existing_editors(cx: &mut TestAppContext) {
11179        use crate::input::{AutoClosingPair, set_language_config};
11180        let view = InputView::<EditorMode>::new(cx);
11181        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11182        setup_cursors(&mut cx, &view.input, "|");
11183        cx.update(|window, cx| {
11184            view.input.update(cx, |state, cx| {
11185                state.set_highlighter("CUSTOM", cx);
11186                state.set_auto_close(false, window, cx);
11187                state.set_smart_indent(false, window, cx);
11188            });
11189            set_language_config(
11190                "custom",
11191                LanguageConfig::default().auto_closing_pairs([AutoClosingPair::new("«", "»")]),
11192                cx,
11193            );
11194            // Batched registrations must update every affected language.
11195            set_language_config("other", LanguageConfig::default(), cx);
11196            view.input.update(cx, |state, cx| {
11197                assert!(!state.mode.is_auto_close());
11198                assert!(!state.mode.is_smart_indent());
11199                state.set_auto_close(true, window, cx);
11200                state.replace_text_in_range(None, "«", window, cx);
11201            });
11202        });
11203        assert_cursors(&mut cx, &view.input, "«|»");
11204    }
11205
11206    #[gpui::test]
11207    fn test_auto_close_before_is_language_configurable(cx: &mut TestAppContext) {
11208        let view = InputView::<EditorMode>::new(cx);
11209        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11210        setup_cursors(&mut cx, &view.input, "|word");
11211        cx.update(|window, cx| {
11212            view.input.update(cx, |state, cx| {
11213                state.replace_text_in_range(None, "(", window, cx)
11214            });
11215        });
11216        assert_cursors(&mut cx, &view.input, "(|word");
11217        setup_cursors(&mut cx, &view.input, "|word");
11218        cx.update(|window, cx| {
11219            view.input.update(cx, |state, cx| {
11220                crate::input::set_language_config(
11221                    state.language_name(),
11222                    LanguageConfig::default().auto_close_before("w"),
11223                    cx,
11224                );
11225                state.replace_text_in_range(None, "(", window, cx);
11226            });
11227        });
11228        assert_cursors(&mut cx, &view.input, "(|)word");
11229    }
11230
11231    #[gpui::test]
11232    fn test_pair_context_restrictions_are_per_pair(cx: &mut TestAppContext) {
11233        use crate::input::{AutoClosingPair, SyntaxContext};
11234        let view = InputView::<EditorMode>::new(cx);
11235        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11236        setup_cursors(&mut cx, &view.input, "|");
11237        cx.update(|window, cx| {
11238            view.input.update(cx, |state, cx| {
11239                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
11240                crate::input::set_language_config(
11241                    state.language_name(),
11242                    LanguageConfig::default().auto_closing_pairs([
11243                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::String]),
11244                        AutoClosingPair::new("[", "]").not_in([SyntaxContext::Comment]),
11245                    ]),
11246                    cx,
11247                );
11248                state.replace_text_in_range(None, "(", window, cx);
11249                state.replace_text_in_range(None, "[", window, cx);
11250            });
11251        });
11252        assert_cursors(&mut cx, &view.input, "([|)");
11253    }
11254
11255    #[gpui::test]
11256    fn test_multichar_delimiters_insert_delete_and_skip(cx: &mut TestAppContext) {
11257        use crate::input::{AutoClosingPair, SyntaxContext};
11258        struct BlockComment;
11259        impl crate::input::SyntaxContextProvider for BlockComment {
11260            fn context_at(&self, text: &ropey::Rope, _: usize) -> SyntaxContext {
11261                if text.to_string().contains("/*") {
11262                    SyntaxContext::Comment
11263                } else {
11264                    SyntaxContext::Code
11265                }
11266            }
11267        }
11268        let view = InputView::<EditorMode>::new(cx);
11269        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11270        setup_cursors(&mut cx, &view.input, "|");
11271        cx.update(|window, cx| {
11272            view.input.update(cx, |state, cx| {
11273                set_test_syntax_provider(std::rc::Rc::new(BlockComment), cx);
11274                crate::input::set_language_config(
11275                    state.language_name(),
11276                    LanguageConfig::default()
11277                        .auto_closing_pairs([AutoClosingPair::new("/*", "*/")
11278                            .not_in([SyntaxContext::String, SyntaxContext::Comment])]),
11279                    cx,
11280                );
11281                state.replace_text_in_range(None, "/", window, cx);
11282                state.replace_text_in_range(None, "*", window, cx);
11283            });
11284        });
11285        assert_cursors(&mut cx, &view.input, "/*|*/");
11286        cx.update(|window, cx| {
11287            view.input
11288                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
11289        });
11290        assert_cursors(&mut cx, &view.input, "|");
11291        cx.update(|window, cx| {
11292            view.input.update(cx, |state, cx| {
11293                state.undo(&Undo, window, cx);
11294                state.replace_text_in_range(None, "*", window, cx);
11295                state.replace_text_in_range(None, "/", window, cx);
11296            });
11297        });
11298        assert_cursors(&mut cx, &view.input, "/**/|");
11299    }
11300
11301    #[gpui::test]
11302    fn test_auto_closed_pairs_fallback_and_explicit_disable(cx: &mut TestAppContext) {
11303        use crate::input::BracketPair;
11304        let view = InputView::<EditorMode>::new(cx);
11305        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11306        setup_cursors(&mut cx, &view.input, "|");
11307        cx.update(|window, cx| {
11308            view.input.update(cx, |state, cx| {
11309                let mut rules = LanguageConfig::default().brackets([BracketPair::new("«", "»")]);
11310                rules.auto_closing_pairs = None;
11311                crate::input::set_language_config(state.language_name(), rules.clone(), cx);
11312                state.replace_text_in_range(None, "«", window, cx);
11313                crate::input::set_language_config(
11314                    state.language_name(),
11315                    rules.auto_closing_pairs([]),
11316                    cx,
11317                );
11318                state.replace_text_in_range(None, "«", window, cx);
11319            });
11320        });
11321        assert_cursors(&mut cx, &view.input, "««|»");
11322    }
11323
11324    #[gpui::test]
11325    fn test_undo_open_composition_preserves_generated_pairs(cx: &mut TestAppContext) {
11326        use crate::input::{AutoClosingPair, SyntaxContext};
11327        let view = InputView::<EditorMode>::new(cx);
11328        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11329        setup_cursors(&mut cx, &view.input, "|");
11330        cx.update(|window, cx| {
11331            view.input.update(cx, |state, cx| {
11332                crate::input::set_language_config(
11333                    state.language_name(),
11334                    LanguageConfig::default()
11335                        .auto_closing_pairs([
11336                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
11337                        ]),
11338                    cx,
11339                );
11340                state.replace_text_in_range(None, "/", window, cx);
11341                state.replace_text_in_range(None, "*", window, cx);
11342                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
11343                state.replace_and_mark_text_in_range(None, "x", Some(1..1), window, cx);
11344                state.undo(&Undo, window, cx);
11345                state.replace_text_in_range(None, "*", window, cx);
11346                state.replace_text_in_range(None, "/", window, cx);
11347            });
11348        });
11349        assert_cursors(&mut cx, &view.input, "/**/|");
11350    }
11351
11352    #[gpui::test]
11353    fn test_undo_does_not_promote_literal_comment_delimiters(cx: &mut TestAppContext) {
11354        use crate::input::{AutoClosingPair, SyntaxContext};
11355        let view = InputView::<EditorMode>::new(cx);
11356        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11357        setup_cursors(&mut cx, &view.input, "/*|*/");
11358        cx.update(|window, cx| {
11359            view.input.update(cx, |state, cx| {
11360                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
11361                crate::input::set_language_config(
11362                    state.language_name(),
11363                    LanguageConfig::default()
11364                        .auto_closing_pairs([
11365                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
11366                        ]),
11367                    cx,
11368                );
11369                state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
11370                state.undo(&Undo, window, cx);
11371                state.backspace(&Backspace, window, cx);
11372            });
11373        });
11374        assert_cursors(&mut cx, &view.input, "/|*/");
11375    }
11376
11377    #[gpui::test]
11378    fn test_multiple_generated_pairs_retain_their_identity(cx: &mut TestAppContext) {
11379        use crate::input::{AutoClosingPair, SyntaxContext};
11380        struct CommentScope;
11381        impl crate::input::SyntaxContextProvider for CommentScope {
11382            fn context_at(&self, text: &ropey::Rope, offset: usize) -> SyntaxContext {
11383                if text
11384                    .to_string()
11385                    .find("*/")
11386                    .is_some_and(|end| offset < end + 2)
11387                {
11388                    SyntaxContext::Comment
11389                } else {
11390                    SyntaxContext::Code
11391                }
11392            }
11393        }
11394        let view = InputView::<EditorMode>::new(cx);
11395        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11396        setup_cursors(&mut cx, &view.input, "|");
11397        cx.update(|window, cx| {
11398            view.input.update(cx, |state, cx| {
11399                set_test_syntax_provider(std::rc::Rc::new(CommentScope), cx);
11400                crate::input::set_language_config(
11401                    state.language_name(),
11402                    LanguageConfig::default().auto_closing_pairs([
11403                        AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment]),
11404                        AutoClosingPair::new("(", ")").not_in([SyntaxContext::Comment]),
11405                    ]),
11406                    cx,
11407                );
11408                state.replace_text_in_range(None, "/", window, cx);
11409                state.replace_text_in_range(None, "*", window, cx);
11410                state.set_selected_range(4..4, cx);
11411                state.replace_text_in_range(None, "(", window, cx);
11412                state.set_selected_range(2..2, cx);
11413                state.replace_text_in_range(None, "*", window, cx);
11414                state.replace_text_in_range(None, "/", window, cx);
11415            });
11416        });
11417        assert_cursors(&mut cx, &view.input, "/**/|()");
11418        cx.update(|window, cx| {
11419            view.input.update(cx, |state, cx| {
11420                state.set_selected_range(2..2, cx);
11421                state.backspace(&Backspace, window, cx);
11422                state.undo(&Undo, window, cx);
11423                state.redo(&Redo, window, cx);
11424            });
11425        });
11426        assert_cursors(&mut cx, &view.input, "|()");
11427    }
11428
11429    #[gpui::test]
11430    fn test_replacing_generated_opener_invalidates_the_pair(cx: &mut TestAppContext) {
11431        use crate::input::{AutoClosingPair, SyntaxContext};
11432        let view = InputView::<EditorMode>::new(cx);
11433        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11434        setup_cursors(&mut cx, &view.input, "|");
11435        cx.update(|window, cx| {
11436            view.input.update(cx, |state, cx| {
11437                crate::input::set_language_config(
11438                    state.language_name(),
11439                    LanguageConfig::default()
11440                        .auto_closing_pairs([
11441                            AutoClosingPair::new("/*", "*/").not_in([SyntaxContext::Comment])
11442                        ]),
11443                    cx,
11444                );
11445                state.replace_text_in_range(None, "/", window, cx);
11446                state.replace_text_in_range(None, "*", window, cx);
11447                state.replace_text_in_range(Some(0..2), "//", window, cx);
11448                set_test_syntax_provider(std::rc::Rc::new(CommentAllProvider), cx);
11449                state.replace_text_in_range(None, "*", window, cx);
11450            });
11451        });
11452        assert_cursors(&mut cx, &view.input, "//*|*/");
11453    }
11454
11455    #[gpui::test]
11456    fn test_configuration_preserves_compiled_regex_options(cx: &mut TestAppContext) {
11457        use crate::input::IndentationRules;
11458        let view = InputView::<EditorMode>::new(cx);
11459        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11460        setup_cursors(&mut cx, &view.input, "BEGIN|");
11461        cx.update(|window, cx| {
11462            view.input.update(cx, |state, cx| {
11463                let rules = |insensitive| {
11464                    LanguageConfig::default().indentation_rules(IndentationRules::new(
11465                        regex::RegexBuilder::new("begin$")
11466                            .case_insensitive(insensitive)
11467                            .build()
11468                            .unwrap(),
11469                        regex::Regex::new("^end").unwrap(),
11470                    ))
11471                };
11472                crate::input::set_language_config(state.language_name(), rules(false), cx);
11473                crate::input::set_language_config(state.language_name(), rules(true), cx);
11474                state.enter(
11475                    &Enter {
11476                        secondary: false,
11477                        shift: false,
11478                    },
11479                    window,
11480                    cx,
11481                );
11482            });
11483        });
11484        assert_cursors(&mut cx, &view.input, "BEGIN\n  |");
11485    }
11486
11487    #[gpui::test]
11488    fn test_indent_patterns_are_applied_on_enter(cx: &mut TestAppContext) {
11489        use crate::input::IndentationRules;
11490        let view = InputView::<EditorMode>::new(cx);
11491        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11492        let rules = LanguageConfig::default().indentation_rules(IndentationRules::new(
11493            regex::Regex::new(r"\bbegin\s*$").unwrap(),
11494            regex::Regex::new(r"^\s*end\b").unwrap(),
11495        ));
11496        for (before, expected) in [("begin|", "begin\n  |"), ("  |end", "  \n|end")] {
11497            setup_cursors(&mut cx, &view.input, before);
11498            cx.update(|window, cx| {
11499                view.input.update(cx, |state, cx| {
11500                    crate::input::set_language_config(state.language_name(), rules.clone(), cx);
11501                    state.enter(
11502                        &Enter {
11503                            secondary: false,
11504                            shift: false,
11505                        },
11506                        window,
11507                        cx,
11508                    );
11509                });
11510            });
11511            assert_cursors(&mut cx, &view.input, expected);
11512        }
11513    }
11514
11515    #[gpui::test]
11516    fn test_custom_language_config(cx: &mut TestAppContext) {
11517        let view = InputView::<EditorMode>::new(cx);
11518        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11519        cx.update(|_window, cx| {
11520            view.input.update(cx, |state, cx| {
11521                crate::input::set_language_config(
11522                    state.language_name(),
11523                    crate::input::language_config::LanguageConfig::default()
11524                        .brackets([crate::input::BracketPair::new("«", "»")])
11525                        .auto_closing_pairs([crate::input::AutoClosingPair::new("«", "»")]),
11526                    cx,
11527                );
11528            });
11529        });
11530        // Custom pair closes.
11531        setup_cursors(&mut cx, &view.input, "|");
11532        cx.update(|window, cx| {
11533            view.input.update(cx, |state, cx| {
11534                state.replace_text_in_range(None, "«", window, cx);
11535            });
11536        });
11537        assert_cursors(&mut cx, &view.input, "«|»");
11538        // Default `(` no longer pairs under custom rules.
11539        cx.update(|window, cx| {
11540            view.input.update(cx, |state, cx| {
11541                state.set_value("\n", window, cx);
11542                state.set_selected_range(0..0, cx);
11543                state.replace_text_in_range(None, "(", window, cx);
11544            });
11545        });
11546        assert_cursors(&mut cx, &view.input, "(|");
11547    }
11548
11549    #[gpui::test]
11550    fn test_skip_records_no_history(cx: &mut TestAppContext) {
11551        let view = InputView::<EditorMode>::new(cx);
11552        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11553        setup_cursors(&mut cx, &view.input, "(a|)");
11554        cx.update(|window, cx| {
11555            view.input.update(cx, |state, cx| {
11556                // Type a normal char (recorded), then skip over `)`.
11557                state.replace_text_in_range(None, "b", window, cx);
11558            });
11559        });
11560        assert_cursors(&mut cx, &view.input, "(ab|)");
11561        cx.update(|window, cx| {
11562            view.input.update(cx, |state, cx| {
11563                state.replace_text_in_range(None, ")", window, cx);
11564            });
11565        });
11566        // Skip is a pure cursor move: no history entry of its own.
11567        assert_cursors(&mut cx, &view.input, "(ab)|");
11568        // Undo removes `b`, never exposing a transient `())`.
11569        cx.update(|window, cx| {
11570            view.input
11571                .update(cx, |state, cx| state.undo(&Undo, window, cx));
11572        });
11573        assert_cursors(&mut cx, &view.input, "(a|)");
11574    }
11575
11576    #[gpui::test]
11577    fn test_pair_insert_undo_removes_both(cx: &mut TestAppContext) {
11578        let view = InputView::<EditorMode>::new(cx);
11579        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11580        setup_cursors(&mut cx, &view.input, "|");
11581        cx.update(|window, cx| {
11582            view.input.update(cx, |state, cx| {
11583                state.replace_text_in_range(None, "(", window, cx);
11584            });
11585        });
11586        assert_cursors(&mut cx, &view.input, "(|)");
11587        // One undo removes the whole pair (typing coalescing).
11588        cx.update(|window, cx| {
11589            view.input
11590                .update(cx, |state, cx| state.undo(&Undo, window, cx));
11591        });
11592        assert_cursors(&mut cx, &view.input, "|");
11593    }
11594
11595    #[gpui::test]
11596    fn test_escaped_quote_does_not_skip(cx: &mut TestAppContext) {
11597        let view = InputView::<EditorMode>::new(cx);
11598        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11599        // Odd backslashes: the typed quote is escaped, insert literally.
11600        setup_cursors(&mut cx, &view.input, "\"hello\\|\"");
11601        cx.update(|window, cx| {
11602            view.input.update(cx, |state, cx| {
11603                state.replace_text_in_range(None, "\"", window, cx);
11604            });
11605        });
11606        assert_cursors(&mut cx, &view.input, "\"hello\\\"|\"");
11607    }
11608
11609    #[gpui::test]
11610    fn test_even_backslashes_still_skip(cx: &mut TestAppContext) {
11611        let view = InputView::<EditorMode>::new(cx);
11612        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11613        // Even backslashes: quote is not escaped, skip over the terminator.
11614        setup_cursors(&mut cx, &view.input, "\"hello\\\\|\"");
11615        cx.update(|window, cx| {
11616            view.input.update(cx, |state, cx| {
11617                state.replace_text_in_range(None, "\"", window, cx);
11618            });
11619        });
11620        assert_cursors(&mut cx, &view.input, "\"hello\\\\\"|");
11621    }
11622
11623    #[gpui::test]
11624    fn test_editor_options_survive_configuration_replacement(cx: &mut TestAppContext) {
11625        use crate::input::{BracketPair, LanguageConfig};
11626
11627        let view = InputView::<EditorMode>::new(cx);
11628        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11629        cx.update(|window, cx| {
11630            view.input.update(cx, |state, cx| {
11631                // Language changes update rules without changing editor preferences.
11632                state.set_smart_indent(false, window, cx);
11633                crate::input::set_language_config(
11634                    state.language_name(),
11635                    LanguageConfig::default().brackets([BracketPair::new("«", "»")]),
11636                    cx,
11637                );
11638            });
11639        });
11640        view.input.read_with(&cx, |state, _| {
11641            assert!(!state.mode.is_smart_indent(), "editor option preserved");
11642            assert!(state.mode.is_auto_close());
11643            let pairs = &state.mode.language_config().unwrap().brackets;
11644            assert_eq!(pairs.len(), 1, "pairs follow configuration");
11645            assert_eq!(pairs[0].open.as_ref(), "«");
11646        });
11647    }
11648
11649    struct CommentAllProvider;
11650    impl crate::input::SyntaxContextProvider for CommentAllProvider {
11651        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
11652            crate::input::SyntaxContext::Comment
11653        }
11654    }
11655
11656    struct StringAllProvider;
11657    impl crate::input::SyntaxContextProvider for StringAllProvider {
11658        fn context_at(&self, _text: &ropey::Rope, _offset: usize) -> crate::input::SyntaxContext {
11659            crate::input::SyntaxContext::String
11660        }
11661    }
11662
11663    #[gpui::test]
11664    fn test_smart_indent_suppressed_in_strings(cx: &mut TestAppContext) {
11665        use std::rc::Rc;
11666
11667        let view = InputView::<EditorMode>::new(cx);
11668        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11669        cx.update(|_window, cx| {
11670            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
11671        });
11672        // Trigger `{` inside a string: no extra level, base indent only.
11673        setup_cursors(&mut cx, &view.input, "  x = {|");
11674        cx.update(|window, cx| {
11675            view.input.update(cx, |state, cx| {
11676                state.enter(
11677                    &Enter {
11678                        secondary: false,
11679                        shift: false,
11680                    },
11681                    window,
11682                    cx,
11683                );
11684            });
11685        });
11686        assert_cursors(&mut cx, &view.input, "  x = {\n  |");
11687    }
11688
11689    #[gpui::test]
11690    fn test_split_suppressed_in_strings(cx: &mut TestAppContext) {
11691        use std::rc::Rc;
11692
11693        let view = InputView::<EditorMode>::new(cx);
11694        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11695        cx.update(|_window, cx| {
11696            set_test_syntax_provider(Rc::new(StringAllProvider), cx);
11697        });
11698        // No three-way split inside strings: plain newline instead.
11699        setup_cursors(&mut cx, &view.input, "{|}");
11700        cx.update(|window, cx| {
11701            view.input.update(cx, |state, cx| {
11702                state.enter(
11703                    &Enter {
11704                        secondary: false,
11705                        shift: false,
11706                    },
11707                    window,
11708                    cx,
11709                );
11710            });
11711        });
11712        assert_cursors(&mut cx, &view.input, "{\n|}");
11713    }
11714
11715    #[gpui::test]
11716    fn test_comment_context_disables_pairing(cx: &mut TestAppContext) {
11717        use std::rc::Rc;
11718
11719        let view = InputView::<EditorMode>::new(cx);
11720        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11721        cx.update(|_window, cx| {
11722            set_test_syntax_provider(Rc::new(CommentAllProvider), cx);
11723        });
11724        setup_cursors(&mut cx, &view.input, "|");
11725        cx.update(|window, cx| {
11726            view.input.update(cx, |state, cx| {
11727                state.replace_text_in_range(None, "(", window, cx);
11728            });
11729        });
11730        // In comments every character is literal.
11731        assert_cursors(&mut cx, &view.input, "(|");
11732    }
11733
11734    #[gpui::test]
11735    fn test_backspace_deletes_empty_pair(cx: &mut TestAppContext) {
11736        let view = InputView::<EditorMode>::new(cx);
11737        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11738        setup_cursors(&mut cx, &view.input, "{|}");
11739        cx.update(|window, cx| {
11740            view.input
11741                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
11742        });
11743        assert_cursors(&mut cx, &view.input, "|");
11744    }
11745
11746    #[gpui::test]
11747    fn test_backspace_keeps_closer_with_content(cx: &mut TestAppContext) {
11748        let view = InputView::<EditorMode>::new(cx);
11749        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11750        setup_cursors(&mut cx, &view.input, "{d|}");
11751        cx.update(|window, cx| {
11752            view.input
11753                .update(cx, |state, cx| state.backspace(&Backspace, window, cx));
11754        });
11755        // Only `d` is deleted, the closing brace stays.
11756        assert_cursors(&mut cx, &view.input, "{|}");
11757    }
11758
11759    #[gpui::test]
11760    fn test_enter_splits_brackets(cx: &mut TestAppContext) {
11761        let view = InputView::<EditorMode>::new(cx);
11762        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11763        setup_cursors(&mut cx, &view.input, "{|}");
11764        cx.update(|window, cx| {
11765            view.input.update(cx, |state, cx| {
11766                state.enter(
11767                    &Enter {
11768                        secondary: false,
11769                        shift: false,
11770                    },
11771                    window,
11772                    cx,
11773                );
11774            });
11775        });
11776        assert_cursors(&mut cx, &view.input, "{\n  |\n}");
11777        // Selection must be collapsed: typing next must insert, not replace.
11778        view.input.read_with(&cx, |state, _| {
11779            assert!(state.active_selection().is_empty());
11780        });
11781        cx.update(|window, cx| {
11782            view.input.update(cx, |state, cx| {
11783                state.replace_text_in_range(None, "x", window, cx);
11784            });
11785        });
11786        assert_cursors(&mut cx, &view.input, "{\n  x|\n}");
11787    }
11788
11789    #[gpui::test]
11790    fn test_enter_smart_indent_after_brace(cx: &mut TestAppContext) {
11791        let view = InputView::<EditorMode>::new(cx);
11792        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11793        setup_cursors(&mut cx, &view.input, "fn main() {|");
11794        cx.update(|window, cx| {
11795            view.input.update(cx, |state, cx| {
11796                state.enter(
11797                    &Enter {
11798                        secondary: false,
11799                        shift: false,
11800                    },
11801                    window,
11802                    cx,
11803                );
11804            });
11805        });
11806        assert_cursors(&mut cx, &view.input, "fn main() {\n  |");
11807    }
11808
11809    #[gpui::test]
11810    fn test_block_indent_tracks_all_preceding_edits(cx: &mut TestAppContext) {
11811        let view = multi_line(cx);
11812        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11813        setup_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
11814        cx.update(|window, cx| {
11815            view.input
11816                .update(cx, |state, cx| state.indent(true, window, cx));
11817        });
11818        assert_cursors(&mut cx, &view.input, "  |ab\n  |cd\n  |ef");
11819        cx.update(|window, cx| {
11820            view.input
11821                .update(cx, |state, cx| state.outdent(true, window, cx));
11822        });
11823        assert_cursors(&mut cx, &view.input, "|ab\n|cd\n|ef");
11824    }
11825
11826    #[gpui::test]
11827    fn test_block_outdent_clamps_cursor_inside_indent(cx: &mut TestAppContext) {
11828        let view = multi_line(cx);
11829        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11830        setup_cursors(&mut cx, &view.input, "ab\n | cd");
11831        cx.update(|window, cx| {
11832            view.input
11833                .update(cx, |state, cx| state.outdent(true, window, cx));
11834        });
11835        assert_cursors(&mut cx, &view.input, "ab\n|cd");
11836    }
11837
11838    #[gpui::test]
11839    fn test_ime_restores_original_selection(cx: &mut TestAppContext) {
11840        let view = InputView::build(cx, |state| state);
11841        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11842        cx.update(|window, cx| {
11843            view.input.update(cx, |state, cx| {
11844                state.set_value("abc", window, cx);
11845                state.set_selected_range(1..2, cx);
11846                state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
11847                state.replace_text_in_range(None, "你", window, cx);
11848                state.undo(&Undo, window, cx);
11849                assert_eq!(state.value(), "abc");
11850                assert_eq!(state.selected_range(), 1..2);
11851                state.redo(&Redo, window, cx);
11852                assert_eq!(state.selected_range(), 4..4);
11853            });
11854        });
11855    }
11856
11857    #[gpui::test]
11858    fn test_noop_does_not_change_redo_selection(cx: &mut TestAppContext) {
11859        let view = multi_line(cx);
11860        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11861        cx.update(|window, cx| {
11862            view.input.update(cx, |state, cx| {
11863                state.set_value("abc", window, cx);
11864                state.set_selected_range(1..1, cx);
11865                state.replace_text_in_range(None, "X", window, cx);
11866                state.set_selected_range(0..0, cx);
11867                state.replace_text_in_range(None, "", window, cx);
11868                state.undo(&Undo, window, cx);
11869                state.redo(&Redo, window, cx);
11870                assert_eq!(state.value(), "aXbc");
11871                assert_eq!(state.selected_range(), 2..2);
11872            });
11873        });
11874    }
11875
11876    #[gpui::test]
11877    fn test_multi_cursor_insert_text(cx: &mut TestAppContext) {
11878        let view = multi_line(cx);
11879        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11880        let input = view.input;
11881
11882        setup_cursors(&mut cx, &input, "|hello |world|");
11883        cx.update(|window, cx| {
11884            input.update(cx, |state, cx| {
11885                state.replace_text_in_range(None, ">>>", window, cx);
11886            });
11887        });
11888        assert_cursors(&mut cx, &input, ">>>|hello >>>|world>>>|");
11889    }
11890
11891    #[gpui::test]
11892    fn test_multi_cursor_delete_backward(cx: &mut TestAppContext) {
11893        let view = multi_line(cx);
11894        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11895        let input = view.input;
11896
11897        setup_cursors(&mut cx, &input, "|islands| cars|");
11898        cx.update(|window, cx| {
11899            input.update(cx, |state, cx| {
11900                state.backspace(&Backspace, window, cx);
11901            });
11902        });
11903        // The first cursor has nothing to delete. The others delete an `s`.
11904        assert_cursors(&mut cx, &input, "|island| car|");
11905    }
11906
11907    #[gpui::test]
11908    fn test_multi_cursor_delete_forward_merges(cx: &mut TestAppContext) {
11909        let view = multi_line(cx);
11910        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11911        let input = view.input;
11912
11913        setup_cursors(&mut cx, &input, "hello| |world");
11914        cx.update(|window, cx| {
11915            input.update(cx, |state, cx| {
11916                state.delete(&Delete, window, cx);
11917            });
11918        });
11919        // Adjacent deletions merge into a single cursor.
11920        assert_cursors(&mut cx, &input, "hello|orld");
11921    }
11922
11923    #[gpui::test]
11924    fn test_multi_cursor_multiline_insert_and_delete(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, "|1\n|2\n|3");
11930        cx.update(|window, cx| {
11931            input.update(cx, |state, cx| {
11932                state.replace_text_in_range(None, "a", window, cx);
11933            });
11934        });
11935        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11936
11937        // The whole multi-edit insert is a single undo transaction.
11938        input.read_with(&cx, |state, _| {
11939            assert_eq!(state.undo_manager.undo_count(), 1);
11940        });
11941
11942        cx.update(|window, cx| {
11943            input.update(cx, |state, cx| {
11944                state.backspace(&Backspace, window, cx);
11945            });
11946        });
11947        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
11948    }
11949
11950    #[gpui::test]
11951    fn test_add_cursor_below_preserves_column(cx: &mut TestAppContext) {
11952        let view = multi_line(cx);
11953        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11954        let input = view.input;
11955
11956        setup_cursors(&mut cx, &input, "ab|cd\nabcd");
11957        cx.update(|window, cx| {
11958            input.update(cx, |state, cx| {
11959                state.add_cursor_below(&AddCursorBelow, window, cx);
11960            });
11961        });
11962        assert_cursors(&mut cx, &input, "ab|cd\nab|cd");
11963    }
11964
11965    #[gpui::test]
11966    fn test_add_cursor_at_rejects_duplicates(cx: &mut TestAppContext) {
11967        let view = multi_line(cx);
11968        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11969        let input = view.input;
11970
11971        setup_cursors(&mut cx, &input, "he|llo");
11972        cx.update(|_, cx| {
11973            input.update(cx, |state, cx| {
11974                // Duplicate of the existing cursor is rejected.
11975                state.add_cursor_at(2, cx);
11976                assert_eq!(state.selections.len(), 1);
11977                // A distinct offset adds a cursor.
11978                state.add_cursor_at(4, cx);
11979                assert_eq!(state.selections.len(), 2);
11980            });
11981        });
11982    }
11983
11984    #[gpui::test]
11985    fn test_multi_cursor_undo_redo_restores_selections(cx: &mut TestAppContext) {
11986        let view = multi_line(cx);
11987        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
11988        let input = view.input;
11989
11990        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
11991        cx.update(|window, cx| {
11992            input.update(cx, |state, cx| {
11993                state.replace_text_in_range(None, "a", window, cx);
11994            });
11995        });
11996        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
11997
11998        cx.update(|window, cx| {
11999            input.update(cx, |state, cx| {
12000                state.undo(&Undo, window, cx);
12001            });
12002        });
12003        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
12004
12005        cx.update(|window, cx| {
12006            input.update(cx, |state, cx| {
12007                state.redo(&Redo, window, cx);
12008            });
12009        });
12010        assert_cursors(&mut cx, &input, "a|1\na|2\na|3");
12011    }
12012
12013    #[gpui::test]
12014    fn test_multi_cursor_undo_redo_different_line_lengths(cx: &mut TestAppContext) {
12015        let view = multi_line(cx);
12016        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12017        let input = view.input;
12018
12019        setup_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
12020        cx.update(|window, cx| {
12021            input.update(cx, |state, cx| {
12022                state.replace_text_in_range(None, "a", window, cx);
12023            });
12024        });
12025        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
12026
12027        cx.update(|window, cx| {
12028            input.update(cx, |state, cx| {
12029                state.undo(&Undo, window, cx);
12030            });
12031        });
12032        assert_cursors(&mut cx, &input, "abc123|\nabc12345|\nabc1234567|");
12033
12034        cx.update(|window, cx| {
12035            input.update(cx, |state, cx| {
12036                state.redo(&Redo, window, cx);
12037            });
12038        });
12039        assert_cursors(&mut cx, &input, "abc123a|\nabc12345a|\nabc1234567a|");
12040    }
12041
12042    #[gpui::test]
12043    fn test_multi_cursor_undo_multiple_inserts(cx: &mut TestAppContext) {
12044        let view = multi_line(cx);
12045        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12046        let input = view.input;
12047
12048        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
12049        for ch in ['a', 'b', 'c'] {
12050            cx.update(|window, cx| {
12051                input.update(cx, |state, cx| {
12052                    state.replace_text_in_range(None, &ch.to_string(), window, cx);
12053                });
12054            });
12055        }
12056        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
12057
12058        // The repeated keystrokes form one typing gesture.
12059        cx.update(|window, cx| {
12060            input.update(cx, |state, cx| {
12061                assert_eq!(state.undo_manager.undo_count(), 1);
12062                state.undo(&Undo, window, cx);
12063            });
12064        });
12065        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
12066
12067        cx.update(|window, cx| {
12068            input.update(cx, |state, cx| {
12069                state.redo(&Redo, window, cx);
12070            });
12071        });
12072        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
12073    }
12074
12075    #[gpui::test]
12076    fn test_multi_cursor_backspace_run_is_one_undo(cx: &mut TestAppContext) {
12077        let view = multi_line(cx);
12078        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12079        let input = view.input;
12080
12081        setup_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
12082        for _ in 0..3 {
12083            cx.update(|window, cx| {
12084                input.update(cx, |state, cx| {
12085                    state.backspace(&Backspace, window, cx);
12086                });
12087            });
12088        }
12089        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
12090
12091        cx.update(|window, cx| {
12092            input.update(cx, |state, cx| {
12093                assert_eq!(state.undo_manager.undo_count(), 1);
12094                state.undo(&Undo, window, cx);
12095            });
12096        });
12097        assert_cursors(&mut cx, &input, "abc|1\nabc|2\nabc|3");
12098    }
12099
12100    #[gpui::test]
12101    fn test_adding_a_cursor_splits_the_typing_gesture(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        setup_cursors(&mut cx, &input, "|1\n2\n3");
12107        cx.update(|window, cx| {
12108            input.update(cx, |state, cx| {
12109                state.replace_text_in_range(None, "a", window, cx);
12110                state.add_cursor_below(&AddCursorBelow, window, cx);
12111                state.replace_text_in_range(None, "b", window, cx);
12112            });
12113        });
12114        assert_cursors(&mut cx, &input, "ab|1\n2b|\n3");
12115
12116        // The keystroke after the cursor was added is its own undo entry.
12117        cx.update(|window, cx| {
12118            input.update(cx, |state, cx| {
12119                state.undo(&Undo, window, cx);
12120            });
12121        });
12122        assert_cursors(&mut cx, &input, "a|1\n2|\n3");
12123    }
12124
12125    #[gpui::test]
12126    fn test_multi_cursor_indent_is_one_undo(cx: &mut TestAppContext) {
12127        let view = multi_line(cx);
12128        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12129        let input = view.input;
12130
12131        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
12132        cx.update(|window, cx| {
12133            input.update(cx, |state, cx| {
12134                state.indent_inline(&IndentInline, window, cx);
12135                assert_eq!(state.undo_manager.undo_count(), 1);
12136                state.undo(&Undo, window, cx);
12137            });
12138        });
12139        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
12140    }
12141
12142    #[gpui::test]
12143    fn test_multi_cursor_indent_then_outdent_roundtrips(cx: &mut TestAppContext) {
12144        let view = multi_line(cx);
12145        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12146        let input = view.input;
12147
12148        // Cursors at line starts.
12149        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
12150        cx.update(|window, cx| {
12151            input.update(cx, |state, cx| {
12152                state.indent(false, window, cx);
12153            });
12154        });
12155        assert_cursors(&mut cx, &input, "  |1\n  |2\n  |3");
12156
12157        cx.update(|window, cx| {
12158            input.update(cx, |state, cx| {
12159                state.outdent(false, window, cx);
12160            });
12161        });
12162        assert_cursors(&mut cx, &input, "|1\n|2\n|3");
12163    }
12164
12165    #[gpui::test]
12166    fn test_inline_outdent_only_removes_line_indentation(cx: &mut TestAppContext) {
12167        let view = multi_line(cx);
12168        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12169        let input = view.input;
12170
12171        // A mid-line indent lands at the cursor, and the outdent does not
12172        // take it back: it only ever removes leading line indentation.
12173        setup_cursors(&mut cx, &input, "1|2\n1|2");
12174        cx.update(|window, cx| {
12175            input.update(cx, |state, cx| {
12176                state.indent(false, window, cx);
12177            });
12178        });
12179        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
12180
12181        cx.update(|window, cx| {
12182            input.update(cx, |state, cx| {
12183                state.outdent(false, window, cx);
12184            });
12185        });
12186        assert_cursors(&mut cx, &input, "1  |2\n1  |2");
12187
12188        // A line with leading indentation loses that, wherever the cursor is.
12189        setup_cursors(&mut cx, &input, "  1|2\n  1|2");
12190        cx.update(|window, cx| {
12191            input.update(cx, |state, cx| {
12192                state.outdent(false, window, cx);
12193            });
12194        });
12195        assert_cursors(&mut cx, &input, "1|2\n1|2");
12196    }
12197
12198    #[gpui::test]
12199    fn test_readonly_multi_cursor_commands_leave_state_unchanged(cx: &mut TestAppContext) {
12200        let view = multi_line(cx);
12201        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12202        let input = view.input;
12203
12204        setup_cursors(&mut cx, &input, "a|b\nc|d");
12205        cx.update(|window, cx| {
12206            input.update(cx, |state, cx| {
12207                let before: Vec<_> = state.selections.iter().copied().collect();
12208                state.set_readonly(true, cx);
12209                state.backspace(&Backspace, window, cx);
12210                state.indent_inline(&IndentInline, window, cx);
12211
12212                assert_eq!(state.value(), "ab\ncd\n");
12213                assert_eq!(state.selections.iter().copied().collect::<Vec<_>>(), before);
12214            });
12215        });
12216    }
12217
12218    #[gpui::test]
12219    fn test_multi_cursor_edit_preserves_the_active_cursor(cx: &mut TestAppContext) {
12220        let view = multi_line(cx);
12221        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12222        let input = view.input;
12223
12224        setup_cursors(&mut cx, &input, "x|\n|y");
12225        cx.update(|window, cx| {
12226            input.update(cx, |state, cx| {
12227                let mut selections: Vec<_> = state.selections.iter().copied().collect();
12228                selections.swap(0, 1);
12229                state.selections.replace_all(selections);
12230                let active_id = state.active_selection().id;
12231                state.replace_text_in_range(None, "!", window, cx);
12232                assert_eq!(state.active_selection().id, active_id);
12233            });
12234        });
12235    }
12236
12237    #[gpui::test]
12238    fn test_block_indent_outdent_with_selection(cx: &mut TestAppContext) {
12239        let view = multi_line(cx);
12240        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12241        let input = view.input;
12242
12243        cx.update(|window, cx| {
12244            input.update(cx, |state, cx| {
12245                state.set_value("line1\nline2\nline3", window, cx);
12246                let id = state.selections.generate_id();
12247                state
12248                    .selections
12249                    .replace_all(vec![CursorSelection::new(id, 0, 17)]);
12250                cx.notify();
12251            });
12252        });
12253
12254        cx.update(|window, cx| {
12255            input.update(cx, |state, cx| {
12256                state.indent(true, window, cx);
12257            });
12258        });
12259        input.read_with(&cx, |state, _| {
12260            assert_eq!(state.text.to_string(), "  line1\n  line2\n  line3");
12261        });
12262
12263        cx.update(|window, cx| {
12264            input.update(cx, |state, cx| {
12265                state.outdent(true, window, cx);
12266            });
12267        });
12268        input.read_with(&cx, |state, _| {
12269            assert_eq!(state.text.to_string(), "line1\nline2\nline3");
12270        });
12271    }
12272
12273    #[gpui::test]
12274    fn test_multi_cursor_word_movement(cx: &mut TestAppContext) {
12275        let view = multi_line(cx);
12276        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12277        let input = view.input;
12278
12279        setup_cursors(
12280            &mut cx,
12281            &input,
12282            "on|e two three\none t|wo three\non|e two three",
12283        );
12284
12285        cx.update(|window, cx| {
12286            input.update(cx, |state, cx| {
12287                state.move_to_next_word(&MoveToNextWord, window, cx);
12288            });
12289        });
12290        assert_cursors(
12291            &mut cx,
12292            &input,
12293            "one| two three\none two| three\none| two three",
12294        );
12295
12296        cx.update(|window, cx| {
12297            input.update(cx, |state, cx| {
12298                state.move_to_previous_word(&MoveToPreviousWord, window, cx);
12299            });
12300        });
12301        assert_cursors(
12302            &mut cx,
12303            &input,
12304            "|one two three\none |two three\n|one two three",
12305        );
12306
12307        // Move to end/start of document collapses to a single cursor.
12308        cx.update(|window, cx| {
12309            input.update(cx, |state, cx| {
12310                state.move_to_end(&MoveToEnd, window, cx);
12311            });
12312        });
12313        input.read_with(&cx, |state, _| {
12314            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
12315            assert_eq!(cursors, vec![state.text.len()]);
12316        });
12317
12318        cx.update(|window, cx| {
12319            input.update(cx, |state, cx| {
12320                state.move_to_start(&MoveToStart, window, cx);
12321            });
12322        });
12323        input.read_with(&cx, |state, _| {
12324            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
12325            assert_eq!(cursors, vec![0]);
12326        });
12327    }
12328
12329    #[gpui::test]
12330    fn test_multi_cursor_selection_commands(cx: &mut TestAppContext) {
12331        let view = multi_line(cx);
12332        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12333        let input = view.input;
12334
12335        setup_cursors(
12336            &mut cx,
12337            &input,
12338            "on|e two three\none t|wo three\non|e two three",
12339        );
12340        cx.update(|window, cx| {
12341            input.update(cx, |state, cx| {
12342                state.select_to_start_of_line(&SelectToStartOfLine, window, cx);
12343            });
12344        });
12345        assert_cursors(
12346            &mut cx,
12347            &input,
12348            "|one two three\n|one two three\n|one two three",
12349        );
12350
12351        // Select to document start collapses to the active cursor only.
12352        setup_cursors(
12353            &mut cx,
12354            &input,
12355            "on|e two three\none t|wo three\non|e two three",
12356        );
12357        cx.update(|window, cx| {
12358            input.update(cx, |state, cx| {
12359                state.select_to_start(&SelectToStart, window, cx);
12360            });
12361        });
12362        input.read_with(&cx, |state, _| {
12363            let cursors: Vec<usize> = state.selections.iter().map(|s| s.cursor_offset()).collect();
12364            assert_eq!(cursors, vec![0]);
12365        });
12366    }
12367
12368    #[gpui::test]
12369    fn test_multi_cursor_replace_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, "|a\n|b\n|c");
12375        cx.update(|window, cx| {
12376            input.update(cx, |state, cx| {
12377                state.select_right(&SelectRight, window, cx);
12378            });
12379        });
12380        input.read_with(&cx, |state, _| {
12381            let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
12382            assert_eq!(ranges, vec![(0, 1), (2, 3), (4, 5)]);
12383        });
12384
12385        cx.update(|window, cx| {
12386            input.update(cx, |state, cx| {
12387                state.replace_text_in_range(None, "x", window, cx);
12388            });
12389        });
12390        assert_cursors(&mut cx, &input, "x|\nx|\nx|");
12391    }
12392
12393    #[gpui::test]
12394    fn test_multi_cursor_escape_collapses(cx: &mut TestAppContext) {
12395        let view = multi_line(cx);
12396        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12397        let input = view.input;
12398
12399        setup_cursors(&mut cx, &input, "|a\n|b\n|c");
12400        cx.update(|window, cx| {
12401            input.update(cx, |state, cx| {
12402                state.escape(&Escape, window, cx);
12403            });
12404        });
12405        input.read_with(&cx, |state, _| {
12406            assert_eq!(state.selections.len(), 1);
12407        });
12408    }
12409
12410    #[gpui::test]
12411    fn test_build_columnar_selection(cx: &mut TestAppContext) {
12412        let view = multi_line(cx);
12413        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12414        let input = view.input;
12415
12416        setup_cursors(&mut cx, &input, "abcd\nabcd\nabcd");
12417        cx.update(|_, cx| {
12418            input.update(cx, |state, cx| {
12419                // From row 0 col 1 to row 2 col 3.
12420                state.build_columnar_selection(
12421                    ColumnarPoint::new(1, 0),
12422                    ColumnarPoint::new(13, 0),
12423                    cx,
12424                );
12425                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
12426                assert_eq!(ranges, vec![(1, 3), (6, 8), (11, 13)]);
12427            });
12428        });
12429    }
12430
12431    #[gpui::test]
12432    fn test_columnar_selection_keeps_width_over_short_rows(cx: &mut TestAppContext) {
12433        let view = multi_line(cx);
12434        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12435        let input = view.input;
12436
12437        setup_cursors(&mut cx, &input, "abcdef\nab\nabcdef");
12438        cx.update(|_, cx| {
12439            input.update(cx, |state, cx| {
12440                // Ends on the short row, 3 columns past its end: the block still spans
12441                // columns 1..5, and only that row is clipped to what it has.
12442                state.build_columnar_selection(
12443                    ColumnarPoint::new(1, 0),
12444                    ColumnarPoint::new(9, 3),
12445                    cx,
12446                );
12447                let ranges: Vec<_> = state.selections.iter().map(|s| (s.start, s.end)).collect();
12448                assert_eq!(ranges, vec![(1, 5), (8, 9)]);
12449            });
12450        });
12451    }
12452
12453    #[gpui::test]
12454    fn test_multi_cursor_paste_distributes_lines(cx: &mut TestAppContext) {
12455        let view = multi_line(cx);
12456        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12457        let input = view.input;
12458
12459        setup_cursors(&mut cx, &input, "|1\n|2\n|3");
12460        cx.update(|_, cx| {
12461            cx.write_to_clipboard(ClipboardItem::new_string("x\ny\nz".to_string()));
12462        });
12463        cx.update(|window, cx| {
12464            input.update(cx, |state, cx| {
12465                state.paste(&Paste, window, cx);
12466            });
12467        });
12468        // One clipboard line per cursor.
12469        assert_cursors(&mut cx, &input, "x|1\ny|2\nz|3");
12470    }
12471
12472    #[gpui::test]
12473    fn test_paste_without_text_leaves_the_selection_alone(cx: &mut TestAppContext) {
12474        use gpui::{ClipboardEntry, Image, ImageFormat};
12475
12476        let input_view = InputView::build(cx, |state| state);
12477        let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
12478        let input = input_view.input;
12479
12480        cx.update(|window, cx| {
12481            input.update(cx, |state, cx| {
12482                state.set_value("hello world", window, cx);
12483                state.select_all(window, cx);
12484                cx.write_to_clipboard(ClipboardItem {
12485                    entries: vec![ClipboardEntry::Image(Image::from_bytes(
12486                        ImageFormat::Png,
12487                        vec![0x89, b'P', b'N', b'G'],
12488                    ))],
12489                });
12490
12491                // An image-only clipboard must not replace the selection
12492                // with nothing.
12493                state.paste(&Paste, window, cx);
12494                assert_eq!(state.value(), "hello world");
12495                assert_eq!(state.selected_range(), 0..11);
12496            });
12497        });
12498    }
12499
12500    #[gpui::test]
12501    fn test_paste_target_tracks_edits_and_selections(cx: &mut TestAppContext) {
12502        let view = multi_line(cx);
12503        let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
12504        let input = view.input;
12505
12506        setup_cursors(&mut cx, &input, "ab|c");
12507        let target = input.read_with(&cx, |state, _| state.paste_target());
12508        // Nothing happened: a paste asked for then still applies.
12509        assert_eq!(
12510            input.read_with(&cx, |state, _| state.paste_target()),
12511            target
12512        );
12513
12514        // Moving the caret changes where the paste would go.
12515        cx.update(|_, cx| {
12516            input.update(cx, |state, cx| {
12517                let id = state.selections.generate_id();
12518                state
12519                    .selections
12520                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
12521                cx.notify();
12522            });
12523        });
12524        let moved = input.read_with(&cx, |state, _| state.paste_target());
12525        assert_ne!(moved, target);
12526
12527        // Editing the text changes it too, even with the caret put back.
12528        cx.update(|window, cx| {
12529            input.update(cx, |state, cx| {
12530                state.replace_text_in_range_silent(None, "x", window, cx);
12531                let id = state.selections.generate_id();
12532                state
12533                    .selections
12534                    .replace_all(vec![CursorSelection::new(id, 1, 1)]);
12535            });
12536        });
12537        assert_ne!(input.read_with(&cx, |state, _| state.paste_target()), moved);
12538    }
12539
12540    #[gpui::test]
12541    fn test_ime_candidate_bounds_fallback_before_repaint(cx: &mut TestAppContext) {
12542        let input_view = InputView::build_textarea(cx, |state| {
12543            state.default_value("这是一段已经输入的中文文字")
12544        });
12545        let mut visual = VisualTestContext::from_window(input_view.window_handle.into(), cx);
12546        visual.update(|window, cx| window.draw(cx).clear(cx));
12547
12548        visual.update(|window, cx| {
12549            input_view.input.update(cx, |state, cx| {
12550                state.focus(window, cx);
12551                let len = state.text.len();
12552                state.set_cursor_to(len);
12553            });
12554        });
12555        visual.update(|window, cx| window.draw(cx).clear(cx));
12556
12557        visual.update(|window, cx| {
12558            input_view.input.update(cx, |state, cx| {
12559                let bounds = state.text_bounds().unwrap_or_default();
12560                let before = state.selected_text_range(false, window, cx).unwrap().range;
12561                let previous = state
12562                    .bounds_for_range(before.end..before.end, bounds, window, cx)
12563                    .unwrap();
12564
12565                state.replace_and_mark_text_in_range(None, "n", None, window, cx);
12566
12567                let after = state.selected_text_range(false, window, cx).unwrap().range;
12568                let pending = state
12569                    .bounds_for_range(after.end..after.end, bounds, window, cx)
12570                    .unwrap();
12571
12572                assert!(
12573                    (pending.origin.x - previous.origin.x).abs() < px(40.0),
12574                    "pending origin.x {:?} must remain close to previous origin.x {:?}",
12575                    pending.origin.x,
12576                    previous.origin.x
12577                );
12578
12579                let range_bounds = state
12580                    .bounds_for_range(before.end..after.end, bounds, window, cx)
12581                    .unwrap();
12582                assert!(
12583                    (range_bounds.origin.x - previous.origin.x).abs() < px(40.0),
12584                    "range_bounds origin.x {:?} must remain close to previous origin.x {:?}",
12585                    range_bounds.origin.x,
12586                    previous.origin.x
12587                );
12588                assert!(range_bounds.size.width >= px(0.0));
12589            });
12590        });
12591    }
12592}
12593
12594/// Methods that only a single-line input offers.
12595impl InputBaseState<crate::input::InputMode> {
12596    /// Create a single-line text input state.
12597    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
12598        Self::new_in_mode(window, cx)
12599    }
12600
12601    /// Set a custom step function of the [`super::NumberInput`].
12602    ///
12603    /// The `f` receives the current value and the [`StepAction`], and returns
12604    /// the step to apply, so the step can vary with the value.
12605    ///
12606    /// # Example
12607    ///
12608    /// ```ignore
12609    /// // At the boundary 1.0 the step is 0.1 going down and 0.5 going up.
12610    /// InputState::new(window, cx).step_by(|value, action, _cx| match action {
12611    ///     StepAction::Increment => if value < 1.0 { 0.1 } else { 0.5 },
12612    ///     StepAction::Decrement => if value <= 1.0 { 0.1 } else { 0.5 },
12613    /// })
12614    /// ```
12615    pub fn step_by(mut self, f: impl Fn(f64, StepAction, &mut App) -> f64 + 'static) -> Self {
12616        self.number_step = Some(NumberStep::by_value(f));
12617        self
12618    }
12619
12620    /// Set with password masked state.
12621    pub fn masked(mut self, masked: bool) -> Self {
12622        self.masked = masked;
12623        self
12624    }
12625
12626    /// Set the password masked state of the input field.
12627    pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
12628        self.masked = masked;
12629        cx.notify();
12630    }
12631
12632    /// Set the regular expression pattern of the input field.
12633    pub fn pattern(mut self, pattern: regex::Regex) -> Self {
12634        self.pattern = Some(pattern);
12635        self
12636    }
12637
12638    /// Set the regular expression pattern of the input field with reference.
12639    pub fn set_pattern(
12640        &mut self,
12641        pattern: regex::Regex,
12642        _window: &mut Window,
12643        _cx: &mut Context<Self>,
12644    ) {
12645        self.pattern = Some(pattern);
12646    }
12647
12648    /// Set the validation function of the input field.
12649    pub fn validate(mut self, f: impl Fn(&str, &mut App) -> bool + 'static) -> Self {
12650        self.validate = Some(Box::new(f));
12651        self
12652    }
12653
12654    pub fn set_validator(
12655        &mut self,
12656        validate: impl Fn(&str, &mut App) -> bool + 'static,
12657        _cx: &mut Context<Self>,
12658    ) {
12659        self.validate = Some(Box::new(validate));
12660    }
12661
12662    /// Set the step value of the [`super::NumberInput`] for increment/decrement.
12663    ///
12664    /// If any of `step`, `min`, `max` is set, the [`super::NumberInput`] will
12665    /// update the value internally (step by `step`, default 1, clamp to the
12666    /// `min`/`max` range and emit [`InputEvent::Change`]) instead of emitting
12667    /// [`super::NumberInputEvent::Step`].
12668    ///
12669    /// See also [`Self::step_by`] to calculate the step value
12670    /// based on the current value.
12671    pub fn step(mut self, step: impl Into<NumberStep>) -> Self {
12672        self.number_step = Some(step.into());
12673        self
12674    }
12675
12676    /// Set the minimum value of the [`super::NumberInput`].
12677    ///
12678    /// The value will be clamped to the minimum value on stepping and on
12679    /// blur (only if the clamped value passes the `pattern`/`validate` check).
12680    /// See also [`Self::step`].
12681    pub fn min(mut self, min: f64) -> Self {
12682        self.number_min = Some(min);
12683        self
12684    }
12685
12686    /// Set the maximum value of the [`super::NumberInput`].
12687    ///
12688    /// The value will be clamped to the maximum value on stepping and on
12689    /// blur (only if the clamped value passes the `pattern`/`validate` check).
12690    /// See also [`Self::step`].
12691    pub fn max(mut self, max: f64) -> Self {
12692        self.number_max = Some(max);
12693        self
12694    }
12695
12696    /// Update the step value after construction, `None` to fall back to
12697    /// emitting [`super::NumberInputEvent::Step`] (if `min`, `max` are unset).
12698    ///
12699    /// See [`Self::step`] and [`Self::step_by`].
12700    pub fn set_step(
12701        &mut self,
12702        step: impl Into<Option<NumberStep>>,
12703        _: &mut Window,
12704        _: &mut Context<Self>,
12705    ) {
12706        self.number_step = step.into();
12707    }
12708
12709    /// Update the minimum value after construction. See [`Self::min`].
12710    pub fn set_min(&mut self, min: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
12711        self.number_min = min;
12712    }
12713
12714    /// Update the maximum value after construction. See [`Self::max`].
12715    pub fn set_max(&mut self, max: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
12716        self.number_max = max;
12717    }
12718
12719    /// Set true to show spinner at the input right.
12720    pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
12721        self.loading = loading;
12722        cx.notify();
12723    }
12724}
12725
12726/// Methods shared by the two multi-line modes, and reachable on neither a
12727/// single-line input nor anything else.
12728impl<M: crate::input::MultiLineMode> InputBaseState<M> {
12729    /// Set whether text taller than the input stretches past its top and
12730    /// bottom and springs back when precise trackpad or Magic Mouse scrolling
12731    /// or its momentum runs into an end. Ordinary line-wheel scrolling does
12732    /// not bounce. The default follows
12733    /// [`crate::ScrollBounce::enabled_by_default`]: on on iOS, Android and
12734    /// macOS, off elsewhere. Under reduced motion the text stops at its ends
12735    /// without stretching.
12736    pub fn scroll_bounce(mut self, scroll_bounce: bool) -> Self {
12737        self.scroll_bounce = scroll_bounce;
12738        self
12739    }
12740
12741    /// Update whether the text stretches past its ends, see
12742    /// [`InputBaseState::scroll_bounce`]. Turning it off drops a stretch in
12743    /// progress.
12744    pub fn set_scroll_bounce(&mut self, scroll_bounce: bool, cx: &mut Context<Self>) {
12745        if self.scroll_bounce != scroll_bounce {
12746            self.scroll_bounce = scroll_bounce;
12747            self.overscroll.reset();
12748            cx.notify();
12749        }
12750    }
12751
12752    /// Whether the built-in search panel and its shortcut are enabled. Off by
12753    /// default, on for the code editor.
12754    ///
12755    /// This only concerns the panel. An input that is not searchable still
12756    /// answers [`InputBaseState::set_search_query`] and the other search
12757    /// methods, and lets `Ctrl-F` / `Cmd-F` bubble up to its ancestors, so an
12758    /// application can put its own search UI on top of the same engine.
12759    pub fn searchable(mut self, searchable: bool) -> Self {
12760        self.searchable = searchable;
12761        self
12762    }
12763
12764    /// See [`InputBaseState::searchable`].
12765    pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
12766        self.searchable = searchable;
12767        cx.notify();
12768    }
12769
12770    /// Set the soft wrap mode, default is true.
12771    #[doc(hidden)]
12772    pub fn soft_wrap(mut self, wrap: bool) -> Self {
12773        self.soft_wrap = wrap;
12774        self
12775    }
12776
12777    /// Update the soft wrap mode, default is true.
12778    pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
12779        self.soft_wrap = wrap;
12780        if wrap {
12781            let wrap_width = self
12782                .last_layout
12783                .as_ref()
12784                .and_then(|b| b.wrap_width)
12785                .unwrap_or(self.input_bounds.size.width);
12786
12787            self.display_map.on_layout_changed(Some(wrap_width), cx);
12788
12789            // Reset scroll to left 0
12790            let mut offset = self.scroll_handle.offset();
12791            offset.x = px(0.);
12792            self.scroll_handle.set_offset(offset);
12793        } else {
12794            self.display_map.on_layout_changed(None, cx);
12795        }
12796        cx.notify();
12797    }
12798
12799    /// Set how soft-wrapped continuation lines are indented, default is [`WrappingIndent::Same`]
12800    #[doc(hidden)]
12801    pub fn wrapping_indent(mut self, wrapping_indent: WrappingIndent) -> Self {
12802        self.wrapping_indent = wrapping_indent;
12803        self
12804    }
12805
12806    /// Update how soft-wrapped continuation lines are indented.
12807    pub fn set_wrapping_indent(
12808        &mut self,
12809        wrapping_indent: WrappingIndent,
12810        _: &mut Window,
12811        cx: &mut Context<Self>,
12812    ) {
12813        self.wrapping_indent = wrapping_indent;
12814        self.display_map.set_wrapping_indent(wrapping_indent, cx);
12815        cx.notify();
12816    }
12817}
12818
12819/// Methods that only ordinary multi-line text offers.
12820impl InputBaseState<crate::input::TextareaMode> {
12821    /// Create a multi-line text state.
12822    ///
12823    /// Being multi-line is carried by the mode, not by the layout, so the
12824    /// default plain-text layout needs no adjustment here.
12825    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
12826        Self::new_in_mode(window, cx)
12827    }
12828
12829    pub fn set_auto_grow(&mut self, min_rows: usize, max_rows: usize, cx: &mut Context<Self>) {
12830        self.mode = LayoutMode::auto_grow(min_rows, max_rows.max(min_rows));
12831        self.mode.update_auto_grow(&self.display_map);
12832        cx.notify();
12833    }
12834
12835    /// Set the number of rows for the multi-line Textarea.
12836    ///
12837    /// This is only used when `multi_line` is set to true. The input is at least
12838    /// this many lines tall.
12839    ///
12840    /// default: 1
12841    #[doc(hidden)]
12842    pub fn rows(mut self, rows: usize) -> Self {
12843        match &mut self.mode {
12844            LayoutMode::PlainText { rows: r, .. } | LayoutMode::CodeEditor { rows: r, .. } => {
12845                *r = rows
12846            }
12847            LayoutMode::AutoGrow {
12848                max_rows: max_r,
12849                rows: r,
12850                ..
12851            } => {
12852                *r = rows;
12853                *max_r = rows;
12854            }
12855        }
12856        self
12857    }
12858
12859    pub fn set_rows(&mut self, rows: usize, cx: &mut Context<Self>) {
12860        match &mut self.mode {
12861            LayoutMode::PlainText { rows: value, .. }
12862            | LayoutMode::CodeEditor { rows: value, .. } => *value = rows,
12863            LayoutMode::AutoGrow {
12864                rows: value,
12865                max_rows,
12866                ..
12867            } => {
12868                *value = rows;
12869                *max_rows = rows;
12870            }
12871        }
12872        cx.notify();
12873    }
12874
12875    /// Grow with the content from `min_rows` through `max_rows`.
12876    pub fn auto_grow(mut self, min_rows: usize, max_rows: usize) -> Self {
12877        self.mode = LayoutMode::auto_grow(min_rows, max_rows);
12878        self
12879    }
12880}
12881
12882/// Methods that only a source-code editor offers.
12883impl InputBaseState<crate::input::EditorMode> {
12884    /// Create a source-code editor state.
12885    ///
12886    /// Default options: line numbers on, tab size 2 with soft tabs, indent
12887    /// guides on, multi-line, and search enabled. Set the language for syntax
12888    /// highlighting with [`Self::language`]; without one the text is shown
12889    /// unhighlighted.
12890    ///
12891    /// The editor aims at simple code editing or display, not at being a
12892    /// full-featured code editor. It offers syntax highlighting, auto indent,
12893    /// line numbers, and handles large text up to about 50K lines.
12894    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
12895        let mut state = Self::new_in_mode(window, cx);
12896        state.mode = LayoutMode::code_editor(super::EditorLanguage::new(cx));
12897        state.searchable = true;
12898        state
12899    }
12900
12901    /// Set the language to highlight, e.g. `"rust"`.
12902    ///
12903    /// See [`Self::set_highlighter`] to change it after construction.
12904    pub fn language(mut self, language: impl Into<SharedString>) -> Self {
12905        if let LayoutMode::CodeEditor {
12906            language: l,
12907            highlighter,
12908            ..
12909        } = &mut self.mode
12910        {
12911            l.set_name(language.into());
12912            *highlighter.borrow_mut() = None;
12913        }
12914        self
12915    }
12916
12917    /// The current language name, e.g. `"rust"`.
12918    pub fn language_name(&self) -> SharedString {
12919        match &self.mode {
12920            LayoutMode::CodeEditor { language, .. } => language.name(),
12921            _ => SharedString::default(),
12922        }
12923    }
12924
12925    /// Set enable/disable code folding.
12926    ///
12927    /// Default: true
12928    #[doc(hidden)]
12929    pub fn folding(mut self, folding: bool) -> Self {
12930        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
12931            *f = folding;
12932        }
12933        self
12934    }
12935
12936    /// Set code folding at runtime.
12937    ///
12938    /// When disabling, all existing folds are cleared.
12939    pub fn set_folding(&mut self, folding: bool, _: &mut Window, cx: &mut Context<Self>) {
12940        if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
12941            *f = folding;
12942        }
12943        if !folding {
12944            self.display_map.clear_folds();
12945        }
12946        cx.notify();
12947    }
12948
12949    /// Unfold any folded ranges that hide the given position.
12950    ///
12951    /// Use this to reveal a position before acting on it (e.g. before
12952    /// [`Self::set_cursor_position`], which stops at a fold boundary),
12953    /// without touching folds elsewhere in the buffer. Fold candidates are
12954    /// kept, so the opened ranges can be folded again from the gutter.
12955    ///
12956    /// A fold keeps its own first and last line visible, so a position on
12957    /// either of them opens nothing. Nested folds all open, since opening
12958    /// only the outermost would leave the position hidden.
12959    ///
12960    /// Returns whether any fold was opened.
12961    pub fn unfold_at(&mut self, position: impl Into<Position>, cx: &mut Context<Self>) -> bool {
12962        let offset = self.text.position_to_offset(&position.into());
12963        let line = self.text.offset_to_point(offset).row;
12964        // A fold hides start_line + 1 ..= end_line - 1, so a line is hidden
12965        // exactly when some folded range strictly contains it.
12966        let covering: Vec<usize> = self
12967            .display_map
12968            .folded_ranges()
12969            .iter()
12970            .filter(|fold| line > fold.start_line && line < fold.end_line)
12971            .map(|fold| fold.start_line)
12972            .collect();
12973        if covering.is_empty() {
12974            return false;
12975        }
12976
12977        for start_line in covering {
12978            self.display_map.set_folded(start_line, false);
12979        }
12980        cx.notify();
12981        true
12982    }
12983
12984    /// Set enable/disable line number.
12985    #[doc(hidden)]
12986    pub fn line_number(mut self, line_number: bool) -> Self {
12987        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
12988            *l = line_number;
12989        }
12990        self
12991    }
12992
12993    /// Set line number.
12994    pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
12995        if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
12996            if *l == line_number {
12997                return;
12998            }
12999            *l = line_number;
13000        }
13001        cx.notify();
13002    }
13003
13004    /// Set enable/disable automatic closing brackets and quotes.
13005    ///
13006    /// When enabled, typing an opener from [`LanguageConfig::auto_closing_pairs`](crate::input::language_config::LanguageConfig::auto_closing_pairs) inserts the
13007    /// matching closer and places the cursor inside. Typing a closer that is
13008    /// already present just moves past it. Default: true
13009    #[doc(hidden)]
13010    pub fn auto_close(mut self, auto_close: bool) -> Self {
13011        self.mode.set_auto_close(auto_close);
13012        self
13013    }
13014
13015    /// Set automatic closing brackets and quotes at runtime.
13016    pub fn set_auto_close(&mut self, auto_close: bool, _: &mut Window, cx: &mut Context<Self>) {
13017        self.mode.set_auto_close(auto_close);
13018        cx.notify();
13019    }
13020
13021    /// Set enable/disable smart indent on Enter.
13022    ///
13023    /// When enabled, Enter uses structural brackets and
13024    /// [`LanguageConfig::indentation_rules`](crate::input::language_config::LanguageConfig::indentation_rules) to choose indentation.
13025    /// Default: true
13026    #[doc(hidden)]
13027    pub fn smart_indent(mut self, smart_indent: bool) -> Self {
13028        self.mode.set_smart_indent(smart_indent);
13029        self
13030    }
13031
13032    /// Set smart indent on Enter at runtime.
13033    pub fn set_smart_indent(&mut self, smart_indent: bool, _: &mut Window, cx: &mut Context<Self>) {
13034        self.mode.set_smart_indent(smart_indent);
13035        cx.notify();
13036    }
13037}