Skip to main content

ui/
input.rs

1//! [`TextField`] — a single-line text field with IME, selection and clipboard.
2//!
3//! Unlike the rest of this crate, a text field cannot be a plain function
4//! returning a `Div`: editing needs state (content, selection, IME marked
5//! range), a focus handle, and gpui's [`EntityInputHandler`]. So it is an
6//! entity the caller holds — SwiftUI's `TextField` bound to `@State`, not a
7//! stateless view.
8//!
9//! Ported from gpui's `examples/input.rs` (Apache-2.0), restyled onto
10//! [`Theme`] tokens, with the key bindings **scoped to the field's key
11//! context** rather than installed globally: a component library must not make
12//! `cmd-a` mean "select all text" for the whole application.
13//!
14//! ```ignore
15//! ui::input::init(cx);                      // once, at startup
16//! let field = cx.new(|cx| TextField::new(cx).with_placeholder("Search…"));
17//! // …then render it: .child(field.clone())
18//! ```
19
20use std::{borrow::Cow, ops::Range, time::Duration};
21
22use gpui::{
23    App, Bounds, ClipboardItem, Context, CursorStyle, DispatchPhase, ElementId,
24    ElementInputHandler, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable, Global,
25    GlobalElementId, KeyBinding, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent,
26    MouseUpEvent, PaintQuad, Pixels, Point, SharedString, Style, Task, TextRun, UTF16Selection,
27    UnderlineStyle, Window, WrappedLine, actions, div, fill, prelude::*, px, relative,
28};
29use unicode_segmentation::UnicodeSegmentation as _;
30
31use theme::{HighlightKind, Metrics, SyntaxPalette, TextStyle, Theme};
32
33actions!(
34    bezel_text_field,
35    [
36        Backspace,
37        Delete,
38        Left,
39        Right,
40        SelectLeft,
41        SelectRight,
42        SelectAll,
43        Home,
44        End,
45        SelectHome,
46        SelectEnd,
47        WordLeft,
48        WordRight,
49        SelectWordLeft,
50        SelectWordRight,
51        DeleteWordLeft,
52        DeleteWordRight,
53        DeleteToLineStart,
54        DeleteToLineEnd,
55        ShowCharacterPalette,
56        Paste,
57        Cut,
58        Copy,
59        Up,
60        Down,
61        SelectUp,
62        SelectDown,
63        InsertNewline,
64        Undo,
65        Redo,
66    ]
67);
68
69/// How many undo steps a field keeps by default.
70///
71/// Steps, not keystrokes: a run of typing coalesces into one, so this is deeper
72/// than it looks. A text field is not a document — nobody walks a search box
73/// back through a long history — and the ceiling is what stops a long-lived
74/// field accumulating snapshots forever. Override per field with
75/// [`TextField::with_undo_limit`].
76pub const DEFAULT_UNDO_LIMIT: usize = 10;
77
78/// What a field reports, one per user action.
79///
80/// [`gpui::Context::observe`] fires on every `notify`, which includes repaints
81/// nothing asked for — the caret's own blink among them. A picker refiltering
82/// on those throws its cursor back to the first row twice a second. Subscribe
83/// to this instead, and take only the half you need: a picker filters on
84/// [`Self::Changed`], a composer reading the word behind the caret wants both.
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum FieldEvent {
87    /// The text is different.
88    Changed,
89    /// The text is the same and the caret is somewhere else.
90    Moved,
91}
92
93/// Half the caret's blink period — the 500ms on, 500ms off macOS itself uses.
94const BLINK: Duration = Duration::from_millis(500);
95
96/// Whether carets blink. Absent means they do — a global nobody installed is
97/// the default, not the opposite of it.
98struct CaretBlink(bool);
99
100impl Global for CaretBlink {}
101
102/// Whether a caret blinks or is held solid. Read where a blink would start:
103/// [`TextField`] here, and the editor's own caret.
104pub fn caret_blink(cx: &App) -> bool {
105    cx.try_global::<CaretBlink>().is_none_or(|blink| blink.0)
106}
107
108/// A caret held solid is still a caret — turning the blink off stops the task
109/// and leaves the caret lit, never caught on the half of the beat that hides it.
110pub fn set_caret_blink(blink: bool, cx: &mut App) {
111    cx.set_global(CaretBlink(blink));
112    cx.refresh_windows();
113}
114
115/// Width of the caret. Named because horizontal scrolling has to keep the caret
116/// itself on screen, not merely the character before it.
117const CARET_WIDTH: Pixels = px(2.);
118
119/// The key context the field claims; bindings from [`init`] are scoped to it.
120pub const KEY_CONTEXT: &str = "TextField";
121
122/// Claimed *in addition* to [`KEY_CONTEXT`] by a multi-line field.
123///
124/// Vertical motion and `enter` hang off this rather than off every field,
125/// because a single-line field is routinely nested inside something that has
126/// already claimed those keys: [`crate::palette`] and [`crate::combobox`] both
127/// bind `up`, `down`, `ctrl-n`, `ctrl-p` and `enter` to drive their lists, and
128/// their query field sits *deeper* in the focus path — so binding those on
129/// every `TextField` would win the dispatch and break list navigation in both.
130pub const MULTILINE_KEY_CONTEXT: &str = "TextArea";
131
132/// Install the bindings — [`bindings`], bound. Call once at startup.
133pub fn init(cx: &mut App) {
134    cx.bind_keys(bindings());
135}
136
137/// The field's keymap, as data, so an app can have it without having to
138/// take it — see [`crate::keys`] for layering over it or taking a chord
139/// away.
140///
141/// Every binding is scoped to [`KEY_CONTEXT`], so they are inert outside a
142/// focused field and an app is free to bind the same chords elsewhere.
143pub fn bindings() -> Vec<KeyBinding> {
144    let mut bindings = Vec::new();
145    let ctx = Some(KEY_CONTEXT);
146    bindings.extend([
147        // Character movement and editing, everywhere.
148        KeyBinding::new("backspace", Backspace, ctx),
149        KeyBinding::new("delete", Delete, ctx),
150        KeyBinding::new("left", Left, ctx),
151        KeyBinding::new("right", Right, ctx),
152        KeyBinding::new("shift-left", SelectLeft, ctx),
153        KeyBinding::new("shift-right", SelectRight, ctx),
154        KeyBinding::new("home", Home, ctx),
155        KeyBinding::new("end", End, ctx),
156        KeyBinding::new("shift-home", SelectHome, ctx),
157        KeyBinding::new("shift-end", SelectEnd, ctx),
158    ]);
159
160    // Multi-line only — see [`MULTILINE_KEY_CONTEXT`] for why these cannot be
161    // bound on every field.
162    let area = Some(MULTILINE_KEY_CONTEXT);
163    bindings.extend([
164        KeyBinding::new("enter", InsertNewline, area),
165        KeyBinding::new("up", Up, area),
166        KeyBinding::new("down", Down, area),
167        KeyBinding::new("shift-up", SelectUp, area),
168        KeyBinding::new("shift-down", SelectDown, area),
169    ]);
170
171    #[cfg(target_os = "macos")]
172    bindings.extend([
173        KeyBinding::new("cmd-a", SelectAll, ctx),
174        KeyBinding::new("cmd-c", Copy, ctx),
175        KeyBinding::new("cmd-x", Cut, ctx),
176        KeyBinding::new("cmd-v", Paste, ctx),
177        KeyBinding::new("cmd-z", Undo, ctx),
178        KeyBinding::new("cmd-shift-z", Redo, ctx),
179        KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, ctx),
180        // cmd = line, option = word: the macOS convention.
181        KeyBinding::new("cmd-left", Home, ctx),
182        KeyBinding::new("cmd-right", End, ctx),
183        KeyBinding::new("cmd-shift-left", SelectHome, ctx),
184        KeyBinding::new("cmd-shift-right", SelectEnd, ctx),
185        KeyBinding::new("alt-left", WordLeft, ctx),
186        KeyBinding::new("alt-right", WordRight, ctx),
187        KeyBinding::new("alt-shift-left", SelectWordLeft, ctx),
188        KeyBinding::new("alt-shift-right", SelectWordRight, ctx),
189        KeyBinding::new("cmd-backspace", DeleteToLineStart, ctx),
190        KeyBinding::new("alt-backspace", DeleteWordLeft, ctx),
191        KeyBinding::new("alt-delete", DeleteWordRight, ctx),
192        // The emacs bindings macOS honours in every native text field.
193        KeyBinding::new("ctrl-a", Home, ctx),
194        KeyBinding::new("ctrl-e", End, ctx),
195        KeyBinding::new("ctrl-b", Left, ctx),
196        KeyBinding::new("ctrl-f", Right, ctx),
197        KeyBinding::new("ctrl-h", Backspace, ctx),
198        KeyBinding::new("ctrl-d", Delete, ctx),
199        KeyBinding::new("ctrl-k", DeleteToLineEnd, ctx),
200    ]);
201
202    // `C-n`/`C-p` are emacs' vertical motion and macOS `NSTextView` natives
203    // both — the two tests a chord has to pass to earn a binding here.
204    #[cfg(target_os = "macos")]
205    bindings.extend([
206        KeyBinding::new("ctrl-n", Down, area),
207        KeyBinding::new("ctrl-p", Up, area),
208    ]);
209
210    #[cfg(not(target_os = "macos"))]
211    bindings.extend([
212        KeyBinding::new("ctrl-a", SelectAll, ctx),
213        KeyBinding::new("ctrl-c", Copy, ctx),
214        KeyBinding::new("ctrl-x", Cut, ctx),
215        KeyBinding::new("ctrl-v", Paste, ctx),
216        // ctrl = word on Windows/Linux, where there is no line modifier.
217        KeyBinding::new("ctrl-left", WordLeft, ctx),
218        KeyBinding::new("ctrl-right", WordRight, ctx),
219        KeyBinding::new("ctrl-shift-left", SelectWordLeft, ctx),
220        KeyBinding::new("ctrl-shift-right", SelectWordRight, ctx),
221        KeyBinding::new("ctrl-backspace", DeleteWordLeft, ctx),
222        KeyBinding::new("ctrl-delete", DeleteWordRight, ctx),
223        KeyBinding::new("ctrl-z", Undo, ctx),
224        KeyBinding::new("ctrl-shift-z", Redo, ctx),
225    ]);
226
227    bindings
228}
229
230/// What case a field holds its text in.
231///
232/// Applied to every edit rather than to the string on its way out, so what is
233/// painted, what `content()` returns and what a caller stores are one string.
234#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
235pub enum Case {
236    /// Whatever was typed.
237    #[default]
238    Mixed,
239    /// Upper, for a field naming something a heading is drawn from.
240    Upper,
241}
242
243impl Case {
244    /// `text` in this case. Borrowed where the case leaves it alone, which
245    /// is the default and every keystroke through it.
246    fn apply(self, text: &str) -> Cow<'_, str> {
247        match self {
248            Self::Mixed => Cow::Borrowed(text),
249            Self::Upper => Cow::Owned(text.to_uppercase()),
250        }
251    }
252}
253
254/// What shape the field takes.
255///
256/// Editing is identical across all three — every action works on the content
257/// and a byte range, and none of them cares where the lines break. What this
258/// decides is the box: how tall it is, whether text wraps, and with that what
259/// `enter` and a pasted newline are allowed to mean.
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
261pub enum Shape {
262    /// One line, no wrapping. `enter` does not insert; a pasted newline becomes
263    /// a space rather than silently truncating what was pasted.
264    #[default]
265    Line,
266    /// Exactly `rows` lines tall, wrapping, scrolling past that.
267    Rows(usize),
268    /// Wraps and grows with the content between `min` and `max` rows, then
269    /// scrolls — the composer shape.
270    Grow { min: usize, max: usize },
271}
272
273impl Shape {
274    /// Whether newlines are content. The single branch every editing policy
275    /// hangs off, so it is asked once rather than matched in each caller.
276    fn is_multiline(self) -> bool {
277        !matches!(self, Self::Line)
278    }
279}
280
281/// A point the field can be returned to.
282///
283/// A whole snapshot rather than a diff: a field holds a sentence, not a file,
284/// and `SharedString` clones are a refcount bump. A rope and a transaction log
285/// is what an editor needs and would be the wrong machinery here.
286#[derive(Clone)]
287struct Snapshot {
288    content: SharedString,
289    selection: Range<usize>,
290    reversed: bool,
291}
292
293/// Which way an edit went, so a run of the same kind can coalesce into one
294/// undo step instead of giving the text back a character at a time.
295#[derive(Clone, Copy, PartialEq, Eq)]
296pub enum EditKind {
297    Insert,
298    Delete,
299}
300
301/// A text field. [`Shape`] decides whether it is one line or many; everything
302/// else about it is the same either way.
303pub struct TextField {
304    focus_handle: FocusHandle,
305    content: SharedString,
306    placeholder: SharedString,
307    shape: Shape,
308    /// The case every edit is put through — see [`Case`].
309    case: Case,
310    selected_range: Range<usize>,
311    selection_reversed: bool,
312    /// The IME composition range (underlined while composing).
313    marked_range: Option<Range<usize>>,
314    /// One entry per hard newline; each wraps into rows of its own. Empty until
315    /// the first paint.
316    last_layout: Vec<WrappedLine>,
317    last_bounds: Option<Bounds<Pixels>>,
318    is_selecting: bool,
319    /// The column vertical motion is trying to keep, in pixels from the left of
320    /// the row. Held across a run of up/down so that walking through a short
321    /// line and out the other side returns to the column you started in, and
322    /// dropped by anything horizontal — which is every other way the caret
323    /// moves, so [`TextField::move_to`] and [`TextField::select_to`] clear it
324    /// and the vertical handlers put it back.
325    goal_x: Option<Pixels>,
326    /// How far the text is scrolled inside the box. Clamped every frame,
327    /// because the content it is measured against changes under it.
328    ///
329    /// Both axes, though only ever one at a time: a wrapped field's lines are
330    /// shaped to the box width so they cannot overflow sideways, and a
331    /// single-line field is exactly one row tall so it cannot overflow
332    /// downwards. The clamp falls out of that and needs no test for shape.
333    scroll: Point<Pixels>,
334    history: crate::history::SnapshotHistory<Snapshot>,
335    /// The kind of the last edit and the offset it left the caret at, which is
336    /// what decides whether the next edit joins that group or starts a new one.
337    /// Adjacency rather than a pause, so there is no timing threshold to invent.
338    last_edit: Option<(EditKind, usize)>,
339    /// A context this field claims *in addition* to [`KEY_CONTEXT`] and
340    /// [`MULTILINE_KEY_CONTEXT`] — see [`Self::with_key_context`].
341    key_context: Option<SharedString>,
342    /// Whether the field paints a box around itself. Off for a field that
343    /// stands in for text already in place — a rename in a list row, where the
344    /// box would be a second frame inside the row's own.
345    frame: bool,
346    /// What the text is set in. Its leading is a multiple of the painted size,
347    /// so the whole line box follows [`theme::set_base_text_size`].
348    metrics: Metrics,
349    /// Which half of the blink the caret is in. Flipped by [`Self::start_blink`].
350    caret_on: bool,
351    /// The blink, alive only while the field holds focus.
352    blink: Option<Task<()>>,
353    /// Set by anything that moves the caret, cleared once a frame has scrolled
354    /// it back into view.
355    ///
356    /// Without the flag the wheel could never win: following the caret
357    /// unconditionally would snap the view back to it on the very next frame,
358    /// so scrolling away to read would be impossible.
359    follow_caret: bool,
360    /// Byte ranges to paint in a syntax colour, in document order — see
361    /// [`Self::set_spans`]. Empty for every field that is prose.
362    spans: Vec<(Range<usize>, HighlightKind)>,
363}
364
365impl EventEmitter<FieldEvent> for TextField {}
366
367impl TextField {
368    pub fn new(cx: &mut Context<Self>) -> Self {
369        Self {
370            // A field is a tab stop from birth; a stateless control needs
371            // `focus::focusable` because its handle lives in the caller.
372            focus_handle: cx.focus_handle().tab_stop(true),
373            frame: true,
374            content: "".into(),
375            placeholder: "".into(),
376            shape: Shape::Line,
377            case: Case::default(),
378            selected_range: 0..0,
379            selection_reversed: false,
380            marked_range: None,
381            last_layout: Vec::new(),
382            last_bounds: None,
383            is_selecting: false,
384            goal_x: None,
385            scroll: Point::default(),
386            history: crate::history::SnapshotHistory::new(DEFAULT_UNDO_LIMIT),
387            last_edit: None,
388            key_context: None,
389            metrics: TextStyle::Body.into(),
390            caret_on: true,
391            blink: None,
392            follow_caret: false,
393            spans: Vec::new(),
394        }
395    }
396
397    /// How many undo steps to keep. App-wide configuration would be a gpui
398    /// global alongside [`crate::input::init`], not a [`Theme`] field — the
399    /// theme is rebuilt on every light/dark switch, which would quietly reset
400    /// anything behavioural parked in it.
401    pub fn with_undo_limit(mut self, limit: usize) -> Self {
402        self.history.set_limit(limit);
403        self
404    }
405
406    pub fn with_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
407        self.placeholder = placeholder.into();
408        self
409    }
410
411    /// Claim an extra key context on this field, so an app can bind a key
412    /// *here* that it does not want bound in every other field.
413    ///
414    /// The composer case, and the reason this exists: `enter` sends a message
415    /// and `shift-enter` breaks a line, while the notes field two panels over
416    /// still takes `enter` as a newline. gpui resolves a keystroke to the
417    /// binding whose context matches **deepest** in the focus path, and nothing
418    /// is deeper than the focused field — so a container around it cannot win
419    /// `enter`, however it is bound. Rebinding [`MULTILINE_KEY_CONTEXT`]
420    /// globally would win, and would take the newline away from every other
421    /// multi-line field in the app. A context of the field's own is the only
422    /// thing that is both deep enough and narrow enough.
423    ///
424    /// ```ignore
425    /// const COMPOSER: &str = "Composer";
426    /// cx.bind_keys([
427    ///     KeyBinding::new("enter", Send, Some(COMPOSER)),
428    ///     KeyBinding::new("shift-enter", input::InsertNewline, Some(COMPOSER)),
429    /// ]);
430    /// let field = cx.new(|cx| {
431    ///     TextField::new(cx)
432    ///         .with_shape(Shape::Grow { min: 3, max: 12 })
433    ///         .with_key_context(COMPOSER)
434    /// });
435    /// ```
436    pub fn with_key_context(mut self, context: impl Into<SharedString>) -> Self {
437        self.key_context = Some(context.into());
438        self
439    }
440
441    /// Draw without the box, padding and focus ring. The caller owns the
442    /// spacing then, and owns showing that the field is focused.
443    pub fn with_frame(mut self, frame: bool) -> Self {
444        self.frame = frame;
445        self
446    }
447
448    /// Set the text in something other than body copy —
449    /// `Typography::of(cx).h1` sets a field the way a document sets its own
450    /// heading.
451    pub fn with_metrics(mut self, metrics: Metrics) -> Self {
452        self.metrics = metrics;
453        self
454    }
455
456    /// Update typography without replacing the editing state.
457    pub fn set_metrics(&mut self, metrics: Metrics, cx: &mut Context<Self>) {
458        if self.metrics != metrics {
459            self.metrics = metrics;
460            cx.notify();
461        }
462    }
463
464    pub fn with_shape(mut self, shape: Shape) -> Self {
465        self.shape = shape;
466        self
467    }
468
469    pub fn with_case(mut self, case: Case) -> Self {
470        self.case = case;
471        self
472    }
473
474    /// The case from here on. What the field already holds is left as it is:
475    /// the text came from somewhere else, and a caller switching the case of a
476    /// shared field would otherwise rewrite the name it is showing.
477    pub fn set_case(&mut self, case: Case) {
478        self.case = case;
479    }
480
481    pub fn case(&self) -> Case {
482        self.case
483    }
484
485    pub fn shape(&self) -> Shape {
486        self.shape
487    }
488
489    pub fn content(&self) -> &SharedString {
490        &self.content
491    }
492
493    /// The syntax spans this field is painting — see [`Self::set_spans`].
494    pub fn spans(&self) -> &[(Range<usize>, HighlightKind)] {
495        &self.spans
496    }
497
498    /// Replace the content, putting the cursor at the end.
499    pub fn set_content(&mut self, content: impl Into<SharedString>, cx: &mut Context<Self>) {
500        let normalized = normalize(&content.into(), self.shape);
501        self.content = self.case.apply(&normalized).into_owned().into();
502        // Colours describe text this field no longer holds.
503        self.spans.clear();
504        // A programmatic reset is not something the user did, so there is
505        // nothing here for them to undo back past.
506        self.history.clear();
507        self.last_edit = None;
508        let end = self.content.len();
509        self.selected_range = end..end;
510        self.marked_range = None;
511        cx.emit(FieldEvent::Changed);
512        cx.notify();
513    }
514
515    pub fn clear(&mut self, cx: &mut Context<Self>) {
516        self.set_content("", cx);
517    }
518
519    /// Paint these byte ranges in syntax colours, in document order. Whatever
520    /// they leave uncovered stays the field's own text colour, so a language
521    /// nothing can colour is simply no spans at all.
522    ///
523    /// This field holds text, not a document: it does not parse, and it does
524    /// not keep the spans in step with edits. The caller recomputes them —
525    /// [`FieldEvent::Changed`] is the signal — and until it does, the frames in
526    /// between paint the ranges it last handed over. Stale ones are dropped
527    /// rather than shifted, so the worst a late recolour looks like is a word
528    /// in the wrong colour for a frame.
529    ///
530    /// [`set_content`](Self::set_content) clears them: replacing the text
531    /// replaces what the colours were about.
532    pub fn set_spans(&mut self, spans: Vec<(Range<usize>, HighlightKind)>, cx: &mut Context<Self>) {
533        self.spans = spans;
534        cx.notify();
535    }
536
537    /// [`Self::with_placeholder`] after construction, for a hint that follows
538    /// something else — the field naming whichever agent, file or channel is
539    /// selected rather than being rebuilt each time one is.
540    pub fn set_placeholder(
541        &mut self,
542        placeholder: impl Into<SharedString>,
543        cx: &mut Context<Self>,
544    ) {
545        self.placeholder = placeholder.into();
546        cx.notify();
547    }
548
549    /// The caret moved: bring it back into view, and drop the blink so the next
550    /// render starts a fresh one. Without the reset the caret would blink
551    /// through your own typing, which reads as a dropped keystroke.
552    fn caret_moved(&mut self) {
553        self.follow_caret = true;
554        self.blink = None;
555    }
556
557    /// Blink the caret for as long as the field holds focus.
558    fn start_blink(&mut self, cx: &mut Context<Self>) {
559        self.caret_on = true;
560        self.blink = Some(cx.spawn(async move |field, cx| {
561            loop {
562                cx.background_executor().timer(BLINK).await;
563                let flipped = field.update(cx, |field, cx| {
564                    field.caret_on = !field.caret_on;
565                    cx.notify();
566                });
567                if flipped.is_err() {
568                    break;
569                }
570            }
571        }));
572    }
573
574    /// Where the caret is, as a byte offset into [`Self::content`].
575    ///
576    /// What a caller needs to read the text *behind* the caret: the `#` an
577    /// autocomplete triggers on, the word a lookup would act on. With a
578    /// selection this is the moving end, which is where typing would land.
579    pub fn cursor(&self) -> usize {
580        self.cursor_offset()
581    }
582
583    /// Where a byte offset sits on screen — the bounds of the row it is on, in
584    /// window coordinates.
585    ///
586    /// The anchor for anything that hangs off a position *in the text* rather
587    /// than off the field: a mention picker under the `#` that opened it, in a
588    /// box that is also growing a row at a time. Pass it to
589    /// [`crate::popover::menu_at`], which takes a window point.
590    ///
591    /// `None` until the field has painted once — this is measured off the
592    /// shaped layout, and there is none before then.
593    pub fn offset_bounds(&self, offset: usize) -> Option<Bounds<Pixels>> {
594        self.row_bounds(self.text_origin()?, offset..offset, self.line_height())
595    }
596
597    /// The rectangle `range` spans, starting from the row it opens on, relative
598    /// to `origin`.
599    ///
600    /// The IME's candidate panel and [`Self::offset_bounds`] are the same
601    /// question asked by two callers, so they ask it here — computed apart they
602    /// would answer differently the first time one of them forgot the scroll.
603    fn row_bounds(
604        &self,
605        origin: Point<Pixels>,
606        range: Range<usize>,
607        line_height: Pixels,
608    ) -> Option<Bounds<Pixels>> {
609        let start = position_for_offset(&self.last_layout, range.start, line_height)?;
610        let end = position_for_offset(&self.last_layout, range.end, line_height)?;
611        Some(Bounds::from_corners(
612            origin + start,
613            origin + gpui::point(end.x, end.y + line_height),
614        ))
615    }
616
617    fn left(&mut self, _: &Left, _: &mut Window, cx: &mut Context<Self>) {
618        if self.selected_range.is_empty() {
619            self.move_to(self.previous_boundary(self.cursor_offset()), cx);
620        } else {
621            self.move_to(self.selected_range.start, cx)
622        }
623    }
624
625    fn right(&mut self, _: &Right, _: &mut Window, cx: &mut Context<Self>) {
626        if self.selected_range.is_empty() {
627            self.move_to(self.next_boundary(self.selected_range.end), cx);
628        } else {
629            self.move_to(self.selected_range.end, cx)
630        }
631    }
632
633    fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
634        self.select_to(self.previous_boundary(self.cursor_offset()), cx);
635    }
636
637    fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
638        self.select_to(self.next_boundary(self.cursor_offset()), cx);
639    }
640
641    fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
642        self.move_to(0, cx);
643        self.select_to(self.content.len(), cx)
644    }
645
646    fn home(&mut self, _: &Home, _: &mut Window, cx: &mut Context<Self>) {
647        self.move_to(line_start(&self.content, self.cursor_offset()), cx);
648    }
649
650    fn end(&mut self, _: &End, _: &mut Window, cx: &mut Context<Self>) {
651        self.move_to(line_end(&self.content, self.cursor_offset()), cx);
652    }
653
654    fn select_home(&mut self, _: &SelectHome, _: &mut Window, cx: &mut Context<Self>) {
655        self.select_to(line_start(&self.content, self.cursor_offset()), cx);
656    }
657
658    fn select_end(&mut self, _: &SelectEnd, _: &mut Window, cx: &mut Context<Self>) {
659        self.select_to(line_end(&self.content, self.cursor_offset()), cx);
660    }
661
662    fn up(&mut self, _: &Up, _: &mut Window, cx: &mut Context<Self>) {
663        self.vertical(-1, false, cx);
664    }
665
666    fn down(&mut self, _: &Down, _: &mut Window, cx: &mut Context<Self>) {
667        self.vertical(1, false, cx);
668    }
669
670    fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
671        self.vertical(-1, true, cx);
672    }
673
674    fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
675        self.vertical(1, true, cx);
676    }
677
678    /// Move the caret `rows` rows, keeping the goal column.
679    ///
680    /// Rows are *visual*, so this walks soft wraps one at a time rather than
681    /// jumping a whole paragraph — the opposite call from `ctrl-a`/`ctrl-e`,
682    /// and the right one: down should land where it looks like it will.
683    ///
684    /// Geometry rather than arithmetic on line numbers, so wrapped rows and hard
685    /// newlines are the same case and neither needs counting.
686    fn vertical(&mut self, rows: i32, extend: bool, cx: &mut Context<Self>) {
687        if self.last_layout.is_empty() {
688            return;
689        }
690        let line_height = self.line_height();
691        let Some(at) = position_for_offset(&self.last_layout, self.cursor_offset(), line_height)
692        else {
693            return;
694        };
695        let goal = self.goal_x.unwrap_or(at.x);
696        let target = at.y + line_height * rows as f32;
697        // Off the top is the start of the text and off the bottom is its end —
698        // what every native field does with up/down on the first/last row.
699        let offset = if target < px(0.) {
700            0
701        } else {
702            offset_for_position(&self.last_layout, gpui::point(goal, target), line_height)
703        };
704
705        if extend {
706            self.select_to(offset, cx);
707        } else {
708            self.move_to(offset, cx);
709        }
710        // Both of the above clear the goal; this is the one motion that keeps it.
711        self.goal_x = Some(goal);
712    }
713
714    /// `enter`. Guarded as well as bound to [`MULTILINE_KEY_CONTEXT`], because
715    /// an action can also be dispatched directly.
716    fn insert_newline(&mut self, _: &InsertNewline, window: &mut Window, cx: &mut Context<Self>) {
717        if self.shape.is_multiline() {
718            self.replace_text_in_range(None, "\n", window, cx);
719        }
720    }
721
722    fn word_left(&mut self, _: &WordLeft, _: &mut Window, cx: &mut Context<Self>) {
723        self.move_to(
724            previous_word_boundary(&self.content, self.cursor_offset()),
725            cx,
726        );
727    }
728
729    fn word_right(&mut self, _: &WordRight, _: &mut Window, cx: &mut Context<Self>) {
730        self.move_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
731    }
732
733    fn select_word_left(&mut self, _: &SelectWordLeft, _: &mut Window, cx: &mut Context<Self>) {
734        self.select_to(
735            previous_word_boundary(&self.content, self.cursor_offset()),
736            cx,
737        );
738    }
739
740    fn select_word_right(&mut self, _: &SelectWordRight, _: &mut Window, cx: &mut Context<Self>) {
741        self.select_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
742    }
743
744    /// Every delete-by-unit action is "extend the selection over the unit, then
745    /// replace it" — so a non-empty selection always wins, matching how every
746    /// native field behaves.
747    fn delete_word_left(
748        &mut self,
749        _: &DeleteWordLeft,
750        window: &mut Window,
751        cx: &mut Context<Self>,
752    ) {
753        if self.selected_range.is_empty() {
754            self.select_to(
755                previous_word_boundary(&self.content, self.cursor_offset()),
756                cx,
757            );
758        }
759        self.replace_text_in_range(None, "", window, cx)
760    }
761
762    fn delete_word_right(
763        &mut self,
764        _: &DeleteWordRight,
765        window: &mut Window,
766        cx: &mut Context<Self>,
767    ) {
768        if self.selected_range.is_empty() {
769            self.select_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
770        }
771        self.replace_text_in_range(None, "", window, cx)
772    }
773
774    fn delete_to_line_start(
775        &mut self,
776        _: &DeleteToLineStart,
777        window: &mut Window,
778        cx: &mut Context<Self>,
779    ) {
780        if self.selected_range.is_empty() {
781            self.select_to(line_start(&self.content, self.cursor_offset()), cx);
782        }
783        self.replace_text_in_range(None, "", window, cx)
784    }
785
786    fn delete_to_line_end(
787        &mut self,
788        _: &DeleteToLineEnd,
789        window: &mut Window,
790        cx: &mut Context<Self>,
791    ) {
792        if self.selected_range.is_empty() {
793            self.select_to(line_end(&self.content, self.cursor_offset()), cx);
794        }
795        self.replace_text_in_range(None, "", window, cx)
796    }
797
798    fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
799        if self.selected_range.is_empty() {
800            let prev = self.previous_boundary(self.cursor_offset());
801            if self.cursor_offset() == prev {
802                return;
803            }
804            self.select_to(prev, cx)
805        }
806        self.replace_text_in_range(None, "", window, cx)
807    }
808
809    fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
810        if self.selected_range.is_empty() {
811            let next = self.next_boundary(self.cursor_offset());
812            if self.cursor_offset() == next {
813                return;
814            }
815            self.select_to(next, cx)
816        }
817        self.replace_text_in_range(None, "", window, cx)
818    }
819
820    fn on_mouse_down(
821        &mut self,
822        event: &MouseDownEvent,
823        _window: &mut Window,
824        cx: &mut Context<Self>,
825    ) {
826        self.is_selecting = true;
827        let offset = self.index_for_mouse_position(event.position, self.line_height());
828        if event.modifiers.shift {
829            self.select_to(offset, cx);
830        } else {
831            self.move_to(offset, cx)
832        }
833    }
834
835    fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut Window, _: &mut Context<Self>) {
836        self.is_selecting = false;
837    }
838
839    /// Scrolling is the one thing that moves the view without moving the caret,
840    /// so it deliberately does not set `follow_caret` — the next frame clamps
841    /// this, and the caret is left wherever it was.
842    fn on_scroll_wheel(
843        &mut self,
844        event: &gpui::ScrollWheelEvent,
845        _window: &mut Window,
846        cx: &mut Context<Self>,
847    ) {
848        let delta = event.delta.pixel_delta(self.line_height());
849        self.scroll.x = (self.scroll.x - delta.x).max(px(0.));
850        self.scroll.y = (self.scroll.y - delta.y).max(px(0.));
851        cx.notify();
852    }
853
854    /// Carry the run to wherever the pointer went. Driven by the window rather
855    /// than by the box — see [`TextFieldElement::paint`] — so `line_height` is
856    /// passed in: the field's own is only current while the field is painting.
857    fn drag_to(&mut self, position: Point<Pixels>, line_height: Pixels, cx: &mut Context<Self>) {
858        if !self.is_selecting {
859            return;
860        }
861        let offset = self.index_for_mouse_position(position, line_height);
862        // A pointer crossing a character is the event worth having; the twenty
863        // samples it takes to cross one are not.
864        if offset != self.cursor_offset() {
865            self.select_to(offset, cx);
866        }
867    }
868
869    fn show_character_palette(
870        &mut self,
871        _: &ShowCharacterPalette,
872        window: &mut Window,
873        _: &mut Context<Self>,
874    ) {
875        window.show_character_palette();
876    }
877
878    fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
879        if let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) {
880            self.replace_text_in_range(None, &normalize(&text, self.shape), window, cx);
881        }
882    }
883
884    fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
885        if !self.selected_range.is_empty() {
886            cx.write_to_clipboard(ClipboardItem::new_string(
887                self.content[self.selected_range.clone()].to_string(),
888            ));
889        }
890    }
891
892    fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
893        if !self.selected_range.is_empty() {
894            cx.write_to_clipboard(ClipboardItem::new_string(
895                self.content[self.selected_range.clone()].to_string(),
896            ));
897            self.replace_text_in_range(None, "", window, cx)
898        }
899    }
900
901    fn snapshot(&self) -> Snapshot {
902        Snapshot {
903            content: self.content.clone(),
904            selection: self.selected_range.clone(),
905            reversed: self.selection_reversed,
906        }
907    }
908
909    fn restore(&mut self, point: Snapshot, cx: &mut Context<Self>) {
910        self.content = point.content;
911        self.selected_range = point.selection;
912        self.selection_reversed = point.reversed;
913        self.marked_range = None;
914        // The next edit must not join whatever group was open before.
915        self.last_edit = None;
916        self.caret_moved();
917        cx.emit(FieldEvent::Changed);
918        cx.notify();
919    }
920
921    /// Record the state before an edit, unless that edit continues the group the
922    /// last one opened. Contiguity is the whole rule: the same kind of edit,
923    /// starting where the caret was left. Type a run and it is one step; move
924    /// the caret, or switch from typing to deleting, and the next one starts a
925    /// group of its own.
926    fn push_undo(&mut self, kind: EditKind, at: usize) {
927        let before = (!joins_group(self.last_edit, kind, at)).then(|| self.snapshot());
928        self.history.record(before);
929    }
930
931    fn undo(&mut self, _: &Undo, _: &mut Window, cx: &mut Context<Self>) {
932        let current = self.snapshot();
933        if let Some(point) = self.history.undo(|| current) {
934            self.restore(point, cx);
935        }
936    }
937
938    fn redo(&mut self, _: &Redo, _: &mut Window, cx: &mut Context<Self>) {
939        let current = self.snapshot();
940        if let Some(point) = self.history.redo(|| current) {
941            self.restore(point, cx);
942        }
943    }
944
945    fn move_to(&mut self, offset: usize, cx: &mut Context<Self>) {
946        self.selected_range = offset..offset;
947        self.goal_x = None;
948        self.caret_moved();
949        cx.emit(FieldEvent::Moved);
950        cx.notify()
951    }
952
953    fn cursor_offset(&self) -> usize {
954        if self.selection_reversed {
955            self.selected_range.start
956        } else {
957            self.selected_range.end
958        }
959    }
960
961    /// The row height every mapping between a screen point and a byte offset
962    /// walks by: this field's own, which is what its shaped lines were laid
963    /// out at — see [`TextField::render`], which sets it on the box.
964    ///
965    /// Never `window.line_height()`. That answers for whatever text style is
966    /// current, and outside this field's own paint there is none of it on the
967    /// stack — a click handler is told the window's default instead. Walk the
968    /// rows at that stride and the caret lands a line or two above the one
969    /// under the pointer, further out the lower you click, until the last
970    /// lines of a full box cannot be reached at all.
971    fn line_height(&self) -> Pixels {
972        px(self.metrics.line_height())
973    }
974
975    /// Where the shaped text starts on screen: the box, moved up by the scroll.
976    /// Every mapping between a screen point and a byte offset goes through it.
977    fn text_origin(&self) -> Option<Point<Pixels>> {
978        Some(self.last_bounds?.origin - self.scroll)
979    }
980
981    fn index_for_mouse_position(&self, position: Point<Pixels>, line_height: Pixels) -> usize {
982        if self.content.is_empty() || self.last_layout.is_empty() {
983            return 0;
984        }
985        let Some(origin) = self.text_origin() else {
986            return 0;
987        };
988        offset_for_position(&self.last_layout, position - origin, line_height)
989    }
990
991    fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
992        self.goal_x = None;
993        self.caret_moved();
994        if self.selection_reversed {
995            self.selected_range.start = offset
996        } else {
997            self.selected_range.end = offset
998        };
999        if self.selected_range.end < self.selected_range.start {
1000            self.selection_reversed = !self.selection_reversed;
1001            self.selected_range = self.selected_range.end..self.selected_range.start;
1002        }
1003        cx.emit(FieldEvent::Moved);
1004        cx.notify()
1005    }
1006
1007    fn offset_to_utf16(&self, offset: usize) -> usize {
1008        offset_to_utf16(&self.content, offset)
1009    }
1010
1011    fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
1012        range_to_utf16(&self.content, range.clone())
1013    }
1014
1015    fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
1016        range_from_utf16(&self.content, range_utf16.clone())
1017    }
1018
1019    fn previous_boundary(&self, offset: usize) -> usize {
1020        previous_boundary(&self.content, offset)
1021    }
1022
1023    fn next_boundary(&self, offset: usize) -> usize {
1024        next_boundary(&self.content, offset)
1025    }
1026}
1027
1028// ---------------------------------------------------------------------------
1029// Pure offset math — the part with the sharp edges, kept free of gpui so it
1030// can be unit-tested.
1031// ---------------------------------------------------------------------------
1032
1033/// UTF-16 offset (what the platform IME speaks) → byte offset.
1034pub fn offset_from_utf16(text: &str, offset: usize) -> usize {
1035    let mut utf8_offset = 0;
1036    let mut utf16_count = 0;
1037    for ch in text.chars() {
1038        if utf16_count >= offset {
1039            break;
1040        }
1041        utf16_count += ch.len_utf16();
1042        utf8_offset += ch.len_utf8();
1043    }
1044    utf8_offset
1045}
1046
1047/// Byte offset → UTF-16 offset.
1048pub fn offset_to_utf16(text: &str, offset: usize) -> usize {
1049    let mut utf16_offset = 0;
1050    let mut utf8_count = 0;
1051    for ch in text.chars() {
1052        if utf8_count >= offset {
1053            break;
1054        }
1055        utf8_count += ch.len_utf8();
1056        utf16_offset += ch.len_utf16();
1057    }
1058    utf16_offset
1059}
1060
1061/// Platform range → byte range, clamped to character boundaries in `text`.
1062pub fn range_from_utf16(text: &str, range: Range<usize>) -> Range<usize> {
1063    offset_from_utf16(text, range.start)..offset_from_utf16(text, range.end)
1064}
1065
1066/// Byte range → platform range.
1067pub fn range_to_utf16(text: &str, range: Range<usize>) -> Range<usize> {
1068    offset_to_utf16(text, range.start)..offset_to_utf16(text, range.end)
1069}
1070
1071/// An IME selection is relative to the replacement text, not the document.
1072pub fn composition_selection(
1073    text: &str,
1074    start: usize,
1075    selection: Option<Range<usize>>,
1076) -> Range<usize> {
1077    let range = selection
1078        .map(|range| range_from_utf16(text, range))
1079        .unwrap_or(text.len()..text.len());
1080    start + range.start..start + range.end
1081}
1082
1083/// Previous *grapheme* boundary, so arrow keys and backspace step over a flag
1084/// emoji or a combining mark as one unit instead of splitting it into pieces
1085/// that render as garbage.
1086pub fn previous_boundary(text: &str, offset: usize) -> usize {
1087    text.grapheme_indices(true)
1088        .rev()
1089        .find_map(|(idx, _)| (idx < offset).then_some(idx))
1090        .unwrap_or(0)
1091}
1092
1093/// Next grapheme boundary; clamps to the end of the text.
1094pub fn next_boundary(text: &str, offset: usize) -> usize {
1095    text.grapheme_indices(true)
1096        .find_map(|(idx, _)| (idx > offset).then_some(idx))
1097        .unwrap_or(text.len())
1098}
1099
1100/// Whether an edit continues the group the last one opened, rather than
1101/// starting an undo step of its own.
1102///
1103/// Two conditions, both structural: the same kind of edit, landing where the
1104/// last one left the caret. Deliberately not "within N milliseconds" — a time
1105/// threshold is a number nobody has measured, and adjacency is what actually
1106/// distinguishes a run of typing from a fresh thought somewhere else.
1107pub fn joins_group(last: Option<(EditKind, usize)>, kind: EditKind, at: usize) -> bool {
1108    last.is_some_and(|(last_kind, offset)| last_kind == kind && at == offset)
1109}
1110
1111/// The line breaks a field of this shape is allowed to hold.
1112///
1113/// CRLF is folded to LF whatever the shape: `shape_text` splits on `\n` alone,
1114/// so a surviving `\r` shapes as a glyph and puts every offset after it out by
1115/// one. A single-line field then keeps the text but not the breaks — a pasted
1116/// newline becomes a space rather than silently truncating what was pasted.
1117///
1118/// The invariant this buys: a [`Shape::Line`] field's content never contains a
1119/// newline, so nothing downstream has to ask whether it might.
1120pub fn normalize(text: &str, shape: Shape) -> String {
1121    let text = text.replace("\r\n", "\n").replace('\r', "\n");
1122    if shape.is_multiline() {
1123        text
1124    } else {
1125        text.replace('\n', " ")
1126    }
1127}
1128
1129/// Start of the logical line holding `offset` — the byte after the previous
1130/// newline.
1131///
1132/// Logical, not visual: with soft wrapping these two readings diverge, and
1133/// `ctrl-a` here goes to the start of the whole paragraph rather than stopping
1134/// at the wrap. That is emacs' `C-a`, and a deliberate divergence from macOS
1135/// `NSTextView`, which stops at the visual row. On text with no newline — every
1136/// [`Shape::Line`] field — the two are identical.
1137pub fn line_start(text: &str, offset: usize) -> usize {
1138    text[..offset].rfind('\n').map_or(0, |at| at + 1)
1139}
1140
1141/// End of the logical line holding `offset` — the byte before the next newline.
1142pub fn line_end(text: &str, offset: usize) -> usize {
1143    text[offset..]
1144        .find('\n')
1145        .map_or(text.len(), |at| offset + at)
1146}
1147
1148/// A word-bound segment counts as a word if it has any alphanumeric content;
1149/// whitespace and punctuation runs are the things word motion skips over.
1150fn is_word(segment: &str) -> bool {
1151    segment.chars().any(char::is_alphanumeric)
1152}
1153
1154/// Start of the word at or before `offset` — option-left.
1155///
1156/// Word units are Unicode word bounds (UAX#29), not space-delimited runs. In
1157/// practice that means `foo.bar` and `foo_bar` are ONE word — a dot or
1158/// underscore between letters does not break — while `a-b`, `path/to/file` and
1159/// `foo, bar` do break. Good defaults for identifiers and paths, and verified
1160/// against the segmenter rather than assumed (see tests).
1161pub fn previous_word_boundary(text: &str, offset: usize) -> usize {
1162    text.split_word_bound_indices()
1163        .filter(|(start, _)| *start < offset)
1164        .rfind(|(_, segment)| is_word(segment))
1165        .map(|(start, _)| start)
1166        .unwrap_or(0)
1167}
1168
1169/// End of the word at or after `offset` — option-right.
1170pub fn next_word_boundary(text: &str, offset: usize) -> usize {
1171    text.split_word_bound_indices()
1172        .filter(|(start, segment)| start + segment.len() > offset)
1173        .find(|(_, segment)| is_word(segment))
1174        .map(|(start, segment)| start + segment.len())
1175        .unwrap_or(text.len())
1176}
1177
1178/// What gets painted, and whether it is the placeholder — which is the only
1179/// reason the colour differs.
1180fn display_text(field: &TextField) -> (SharedString, bool) {
1181    if field.content.is_empty() {
1182        (field.placeholder.clone(), true)
1183    } else {
1184        (field.content.clone(), false)
1185    }
1186}
1187
1188/// One run per span, the text between them in the field's own colour.
1189///
1190/// The runs a field paints [`TextField::set_spans`] as, public for the same
1191/// reason [`next_boundary`] is: anything shaping the same text with the same
1192/// palette wants the same answer, and this is where it is decided.
1193///
1194/// The spans are whatever the caller last handed over, which may describe text
1195/// this frame no longer holds — see [`TextField::set_spans`]. A range that runs
1196/// past the end, overlaps the one before it, or cuts a character in half is
1197/// dropped rather than shifting the runs after it: shaping needs the lengths to
1198/// add up to the text, and a colour that is wrong for one frame is cheaper than
1199/// a panic.
1200///
1201/// `base.len` is ignored: every run is measured off `text`.
1202pub fn coloured(
1203    text: &str,
1204    spans: &[(Range<usize>, HighlightKind)],
1205    base: &TextRun,
1206    palette: &SyntaxPalette,
1207) -> Vec<TextRun> {
1208    if spans.is_empty() {
1209        return vec![TextRun {
1210            len: text.len(),
1211            ..base.clone()
1212        }];
1213    }
1214    let mut runs = Vec::with_capacity(spans.len() * 2 + 1);
1215    let mut at = 0;
1216    for (range, kind) in spans {
1217        if range.start < at
1218            || range.end <= range.start
1219            || range.end > text.len()
1220            || !text.is_char_boundary(range.start)
1221            || !text.is_char_boundary(range.end)
1222        {
1223            continue;
1224        }
1225        if range.start > at {
1226            runs.push(TextRun {
1227                len: range.start - at,
1228                ..base.clone()
1229            });
1230        }
1231        runs.push(TextRun {
1232            len: range.end - range.start,
1233            color: palette.color(*kind),
1234            ..base.clone()
1235        });
1236        at = range.end;
1237    }
1238    if at < text.len() {
1239        runs.push(TextRun {
1240            len: text.len() - at,
1241            ..base.clone()
1242        });
1243    }
1244    runs
1245}
1246
1247/// The IME composition range underlined, so the user can see what is still
1248/// provisional. Each run is cut at the range's edges and the pieces inside it
1249/// take the line, in whatever colour they already had.
1250pub fn underlined(runs: Vec<TextRun>, marked: &Range<usize>) -> Vec<TextRun> {
1251    let mut out = Vec::with_capacity(runs.len() + 2);
1252    let mut at = 0;
1253    for run in runs {
1254        let end = at + run.len;
1255        for (start, stop, mark) in [
1256            (at, end.min(marked.start), false),
1257            (at.max(marked.start), end.min(marked.end), true),
1258            (at.max(marked.end), end, false),
1259        ] {
1260            if stop <= start {
1261                continue;
1262            }
1263            out.push(TextRun {
1264                len: stop - start,
1265                underline: mark.then(|| UnderlineStyle {
1266                    color: Some(run.color),
1267                    thickness: px(1.0),
1268                    wavy: false,
1269                }),
1270                ..run.clone()
1271            });
1272        }
1273        at = end;
1274    }
1275    out
1276}
1277
1278// ---------------------------------------------------------------------------
1279// Line geometry. `shape_text` returns one `WrappedLine` per hard newline, each
1280// wrapping into rows of its own; gpui resolves positions *within* a line, so
1281// everything here is walking that list and nothing re-implements shaping.
1282// ---------------------------------------------------------------------------
1283
1284/// Each shaped line with the byte offset it starts at. `shape_text` splits on
1285/// `\n` and drops the separator, so each line starts one byte past the last.
1286fn lines_from(lines: &[WrappedLine]) -> impl Iterator<Item = (usize, &WrappedLine)> {
1287    lines.iter().scan(0usize, |start, line| {
1288        let at = *start;
1289        *start = at + line.len() + 1;
1290        Some((at, line))
1291    })
1292}
1293
1294/// Every visual row: the byte range it covers and its top edge, relative to the
1295/// text origin. A wrap boundary resolves to a byte index exactly the way
1296/// `WrappedLineLayout::position_for_index` does it internally — that mapping is
1297/// not exposed, and selection needs one quad per row.
1298fn rows(lines: &[WrappedLine], line_height: Pixels) -> Vec<(Range<usize>, Pixels)> {
1299    let mut out = Vec::new();
1300    let mut top = px(0.);
1301    for (start, line) in lines_from(lines) {
1302        let mut row_start = start;
1303        for boundary in line.wrap_boundaries() {
1304            let at = start + line.runs()[boundary.run_ix].glyphs[boundary.glyph_ix].index;
1305            out.push((row_start..at, top));
1306            row_start = at;
1307            top += line_height;
1308        }
1309        out.push((row_start..start + line.len(), top));
1310        top += line_height;
1311    }
1312    out
1313}
1314
1315/// Byte offset → position relative to the text origin.
1316fn position_for_offset(
1317    lines: &[WrappedLine],
1318    offset: usize,
1319    line_height: Pixels,
1320) -> Option<Point<Pixels>> {
1321    let mut top = px(0.);
1322    for (start, line) in lines_from(lines) {
1323        if offset <= start + line.len() {
1324            let local = line.position_for_index(offset.saturating_sub(start), line_height)?;
1325            return Some(gpui::point(local.x, local.y + top));
1326        }
1327        top += line.size(line_height).height;
1328    }
1329    None
1330}
1331
1332/// Position relative to the text origin → the closest byte offset.
1333fn offset_for_position(
1334    lines: &[WrappedLine],
1335    position: Point<Pixels>,
1336    line_height: Pixels,
1337) -> usize {
1338    let mut top = px(0.);
1339    let mut last = 0;
1340    for (start, line) in lines_from(lines) {
1341        let height = line.size(line_height).height;
1342        last = start + line.len();
1343        if position.y < top + height {
1344            let local = gpui::point(position.x, position.y - top);
1345            let (Ok(index) | Err(index)) = line.closest_index_for_position(local, line_height);
1346            return start + index;
1347        }
1348        top += height;
1349    }
1350    last
1351}
1352
1353/// The selection as one rect per visual row, relative to the text origin.
1354///
1355/// A row's left edge is always x=0, so a continuation row is taken from there
1356/// rather than by looking the offset up — at a soft wrap the two rows share a
1357/// byte offset, and the lookup resolves it to the end of the earlier row.
1358fn selection_rows(
1359    lines: &[WrappedLine],
1360    range: &Range<usize>,
1361    line_height: Pixels,
1362) -> Vec<Bounds<Pixels>> {
1363    rows(lines, line_height)
1364        .into_iter()
1365        .filter(|(row, _)| range.start <= row.end && range.end >= row.start)
1366        .filter_map(|(row, top)| {
1367            let left = if range.start <= row.start {
1368                px(0.)
1369            } else {
1370                position_for_offset(lines, range.start, line_height)?.x
1371            };
1372            let right = position_for_offset(lines, range.end.min(row.end), line_height)?.x;
1373            (right > left).then(|| {
1374                Bounds::from_corners(
1375                    gpui::point(left, top),
1376                    gpui::point(right, top + line_height),
1377                )
1378            })
1379        })
1380        .collect()
1381}
1382
1383impl EntityInputHandler for TextField {
1384    fn text_for_range(
1385        &mut self,
1386        range_utf16: Range<usize>,
1387        actual_range: &mut Option<Range<usize>>,
1388        _window: &mut Window,
1389        _cx: &mut Context<Self>,
1390    ) -> Option<String> {
1391        let range = self.range_from_utf16(&range_utf16);
1392        actual_range.replace(self.range_to_utf16(&range));
1393        Some(self.content[range].to_string())
1394    }
1395
1396    fn selected_text_range(
1397        &mut self,
1398        _ignore_disabled_input: bool,
1399        _window: &mut Window,
1400        _cx: &mut Context<Self>,
1401    ) -> Option<UTF16Selection> {
1402        Some(UTF16Selection {
1403            range: self.range_to_utf16(&self.selected_range),
1404            reversed: self.selection_reversed,
1405        })
1406    }
1407
1408    fn marked_text_range(
1409        &self,
1410        _window: &mut Window,
1411        _cx: &mut Context<Self>,
1412    ) -> Option<Range<usize>> {
1413        self.marked_range
1414            .as_ref()
1415            .map(|range| self.range_to_utf16(range))
1416    }
1417
1418    fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
1419        self.marked_range = None;
1420    }
1421
1422    fn replace_text_in_range(
1423        &mut self,
1424        range_utf16: Option<Range<usize>>,
1425        new_text: &str,
1426        _: &mut Window,
1427        cx: &mut Context<Self>,
1428    ) {
1429        let range = range_utf16
1430            .as_ref()
1431            .map(|range_utf16| self.range_from_utf16(range_utf16))
1432            .or(self.marked_range.clone())
1433            .unwrap_or(self.selected_range.clone());
1434
1435        // Every edit lands here — typing, deleting, cut, paste, and the IME
1436        // *committing*. Not `replace_and_mark_text_in_range`, which is the
1437        // composing path: provisional text must not become undo steps, or every
1438        // keystroke of Chinese input would be one.
1439        let kind = if new_text.is_empty() {
1440            EditKind::Delete
1441        } else {
1442            EditKind::Insert
1443        };
1444        // A delete grows leftwards, so its group continues at the range's end;
1445        // an insert continues at its start.
1446        self.push_undo(
1447            kind,
1448            if new_text.is_empty() {
1449                range.end
1450            } else {
1451                range.start
1452            },
1453        );
1454
1455        // Cased here rather than on the way out: the caret is placed from this
1456        // string's length, and a case that changes it (`ß` uppercases to two
1457        // bytes) would leave the caret off by the difference.
1458        let new_text = self.case.apply(new_text);
1459        self.content =
1460            (self.content[0..range.start].to_owned() + &new_text + &self.content[range.end..])
1461                .into();
1462        self.selected_range = range.start + new_text.len()..range.start + new_text.len();
1463        self.marked_range.take();
1464        self.last_edit = Some((kind, self.selected_range.end));
1465        self.caret_moved();
1466        cx.emit(FieldEvent::Changed);
1467        cx.notify();
1468    }
1469
1470    fn replace_and_mark_text_in_range(
1471        &mut self,
1472        range_utf16: Option<Range<usize>>,
1473        new_text: &str,
1474        new_selected_range_utf16: Option<Range<usize>>,
1475        _window: &mut Window,
1476        cx: &mut Context<Self>,
1477    ) {
1478        let range = range_utf16
1479            .as_ref()
1480            .map(|range_utf16| self.range_from_utf16(range_utf16))
1481            .or(self.marked_range.clone())
1482            .unwrap_or(self.selected_range.clone());
1483
1484        // The composing text is cased too, so what an IME is showing is what
1485        // committing it will leave behind.
1486        let new_text = self.case.apply(new_text);
1487        self.content =
1488            (self.content[0..range.start].to_owned() + &new_text + &self.content[range.end..])
1489                .into();
1490        self.marked_range =
1491            (!new_text.is_empty()).then(|| range.start..range.start + new_text.len());
1492        self.selected_range =
1493            composition_selection(&new_text, range.start, new_selected_range_utf16);
1494        self.selection_reversed = false;
1495
1496        self.caret_moved();
1497        cx.emit(FieldEvent::Changed);
1498        cx.notify();
1499    }
1500
1501    fn bounds_for_range(
1502        &mut self,
1503        range_utf16: Range<usize>,
1504        bounds: Bounds<Pixels>,
1505        _window: &mut Window,
1506        _cx: &mut Context<Self>,
1507    ) -> Option<Bounds<Pixels>> {
1508        let range = self.range_from_utf16(&range_utf16);
1509        // The IME panel anchors under the composing text, so this has to be the
1510        // row that text is on, not the whole field. `bounds` is what
1511        // `last_bounds` is set from, so this is the same origin
1512        // [`TextField::offset_bounds`] measures from.
1513        self.row_bounds(bounds.origin - self.scroll, range, self.line_height())
1514    }
1515
1516    fn character_index_for_point(
1517        &mut self,
1518        point: Point<Pixels>,
1519        _window: &mut Window,
1520        _cx: &mut Context<Self>,
1521    ) -> Option<usize> {
1522        self.last_bounds?.localize(&point)?;
1523        let origin = self.text_origin()?;
1524        let offset = offset_for_position(&self.last_layout, point - origin, self.line_height());
1525        Some(self.offset_to_utf16(offset))
1526    }
1527}
1528
1529impl Focusable for TextField {
1530    fn focus_handle(&self, _: &App) -> FocusHandle {
1531        self.focus_handle.clone()
1532    }
1533}
1534
1535impl Render for TextField {
1536    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1537        // The only place the blink starts: `caret_moved` drops the task, so the
1538        // next render brings it back in phase, solid beat first.
1539        if self.focus_handle.is_focused(_window) && caret_blink(cx) {
1540            if self.blink.is_none() {
1541                self.start_blink(cx);
1542            }
1543        } else {
1544            self.blink = None;
1545            self.caret_on = true;
1546        }
1547        let theme = Theme::of(cx);
1548        let mut key_context = gpui::KeyContext::default();
1549        key_context.add(KEY_CONTEXT);
1550        if self.shape.is_multiline() {
1551            key_context.add(MULTILINE_KEY_CONTEXT);
1552        }
1553        if let Some(extra) = self.key_context.clone() {
1554            key_context.add(extra);
1555        }
1556        div()
1557            .key_context(key_context)
1558            .track_focus(&self.focus_handle(cx))
1559            .cursor(CursorStyle::IBeam)
1560            .on_action(cx.listener(Self::backspace))
1561            .on_action(cx.listener(Self::delete))
1562            .on_action(cx.listener(Self::left))
1563            .on_action(cx.listener(Self::right))
1564            .on_action(cx.listener(Self::select_left))
1565            .on_action(cx.listener(Self::select_right))
1566            .on_action(cx.listener(Self::select_all))
1567            .on_action(cx.listener(Self::home))
1568            .on_action(cx.listener(Self::end))
1569            .on_action(cx.listener(Self::select_home))
1570            .on_action(cx.listener(Self::select_end))
1571            .on_action(cx.listener(Self::word_left))
1572            .on_action(cx.listener(Self::word_right))
1573            .on_action(cx.listener(Self::select_word_left))
1574            .on_action(cx.listener(Self::select_word_right))
1575            .on_action(cx.listener(Self::up))
1576            .on_action(cx.listener(Self::down))
1577            .on_action(cx.listener(Self::select_up))
1578            .on_action(cx.listener(Self::select_down))
1579            .on_action(cx.listener(Self::insert_newline))
1580            .on_action(cx.listener(Self::undo))
1581            .on_action(cx.listener(Self::redo))
1582            .on_action(cx.listener(Self::delete_word_left))
1583            .on_action(cx.listener(Self::delete_word_right))
1584            .on_action(cx.listener(Self::delete_to_line_start))
1585            .on_action(cx.listener(Self::delete_to_line_end))
1586            .on_action(cx.listener(Self::show_character_palette))
1587            .on_action(cx.listener(Self::paste))
1588            .on_action(cx.listener(Self::cut))
1589            .on_action(cx.listener(Self::copy))
1590            .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
1591            .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
1592            .on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
1593            .on_scroll_wheel(cx.listener(Self::on_scroll_wheel))
1594            .w_full()
1595            .when(self.frame, |field| {
1596                field
1597                    .px(px(10.0))
1598                    .py(px(7.0))
1599                    .rounded(px(Theme::button_radius()))
1600                    .bg(theme.input_bg)
1601                    .border_1()
1602                    .border_color(if self.focus_handle.is_focused(_window) {
1603                        theme.ring
1604                    } else {
1605                        theme.border
1606                    })
1607            })
1608            .text_size(px(self.metrics.size()))
1609            .font_weight(self.metrics.weight)
1610            .line_height(px(self.metrics.line_height()))
1611            .text_color(theme.text)
1612            .child(TextFieldElement { field: cx.entity() })
1613    }
1614}
1615
1616/// Paints the shaped lines plus selection and caret. A custom element because
1617/// all three are geometry derived from the shaped text, which only exists after
1618/// layout.
1619struct TextFieldElement {
1620    field: Entity<TextField>,
1621}
1622
1623struct FieldPrepaint {
1624    lines: Vec<WrappedLine>,
1625    /// Top-left of the text, which is the box moved up by the scroll offset.
1626    origin: Point<Pixels>,
1627    cursor: Option<PaintQuad>,
1628    /// One quad per visual row the selection covers.
1629    selection: Vec<PaintQuad>,
1630}
1631
1632impl IntoElement for TextFieldElement {
1633    type Element = Self;
1634
1635    fn into_element(self) -> Self::Element {
1636        self
1637    }
1638}
1639
1640impl Element for TextFieldElement {
1641    type RequestLayoutState = ();
1642    type PrepaintState = FieldPrepaint;
1643
1644    fn id(&self) -> Option<ElementId> {
1645        None
1646    }
1647
1648    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1649        None
1650    }
1651
1652    fn request_layout(
1653        &mut self,
1654        _id: Option<&GlobalElementId>,
1655        _inspector_id: Option<&gpui::InspectorElementId>,
1656        window: &mut Window,
1657        cx: &mut App,
1658    ) -> (LayoutId, ()) {
1659        let mut style = Style::default();
1660        style.size.width = relative(1.).into();
1661        let field = self.field.read(cx);
1662        // The field's own, here as everywhere: sizing the box off one height
1663        // and hit-testing it at another is how a click lands on the wrong row.
1664        let line_height = field.line_height();
1665        let shape = field.shape;
1666
1667        let (min, max) = match shape {
1668            Shape::Line => {
1669                style.size.height = line_height.into();
1670                return (window.request_layout(style, [], cx), ());
1671            }
1672            Shape::Rows(rows) => {
1673                style.size.height = (line_height * rows.max(1) as f32).into();
1674                return (window.request_layout(style, [], cx), ());
1675            }
1676            // Growing needs the row count, and the row count needs shaping at
1677            // the width layout is still deciding — which is exactly what a
1678            // measured layout is for.
1679            Shape::Grow { min, max } => (min.max(1), max.max(min.max(1))),
1680        };
1681
1682        let text = display_text(field).0;
1683        let id = window.request_measured_layout(style, move |known, available, window, _cx| {
1684            let text_style = window.text_style();
1685            let font_size = text_style.font_size.to_pixels(window.rem_size());
1686            // Prefer the width layout has already settled on. Taffy also probes
1687            // with min/max-content, where there is no width to wrap against —
1688            // and counting rows off unwrapped text under-reports them, which
1689            // would size the box for fewer lines than it goes on to paint.
1690            let wrap_width = known.width.or(match available.width {
1691                gpui::AvailableSpace::Definite(width) => Some(width),
1692                _ => None,
1693            });
1694            let run = TextRun {
1695                len: text.len(),
1696                font: text_style.font(),
1697                color: text_style.color,
1698                background_color: None,
1699                underline: None,
1700                strikethrough: None,
1701            };
1702            let count = window
1703                .text_system()
1704                .shape_text(text.clone(), font_size, &[run], wrap_width, None)
1705                .map(|lines| {
1706                    lines
1707                        .iter()
1708                        .map(|line| line.wrap_boundaries().len() + 1)
1709                        .sum::<usize>()
1710                })
1711                .unwrap_or(1);
1712            gpui::size(
1713                wrap_width.unwrap_or(px(0.)),
1714                line_height * count.clamp(min, max) as f32,
1715            )
1716        });
1717        (id, ())
1718    }
1719
1720    fn prepaint(
1721        &mut self,
1722        _id: Option<&GlobalElementId>,
1723        _inspector_id: Option<&gpui::InspectorElementId>,
1724        bounds: Bounds<Pixels>,
1725        _request_layout: &mut Self::RequestLayoutState,
1726        window: &mut Window,
1727        cx: &mut App,
1728    ) -> FieldPrepaint {
1729        let theme = Theme::of(cx).clone();
1730        let field = self.field.read(cx);
1731        let selected_range = field.selected_range.clone();
1732        let cursor = field.cursor_offset();
1733        let shape = field.shape;
1734        let marked_range = field.marked_range.clone();
1735        let scrolled = field.scroll;
1736        let follow_caret = field.follow_caret;
1737        let style = window.text_style();
1738
1739        let (text, is_placeholder) = display_text(field);
1740        let text_color = if is_placeholder {
1741            theme.text_faint
1742        } else {
1743            style.color
1744        };
1745
1746        let run = TextRun {
1747            len: text.len(),
1748            font: style.font(),
1749            color: text_color,
1750            background_color: None,
1751            underline: None,
1752            strikethrough: None,
1753        };
1754        // Syntax first, then the IME composition range underlined over the top
1755        // of it: both cut the text into runs, and the underline has to land on
1756        // whatever colour is already there.
1757        let runs = if is_placeholder {
1758            vec![run]
1759        } else {
1760            coloured(&text, &field.spans, &run, &theme.syntax)
1761        };
1762        let runs = match marked_range.as_ref() {
1763            Some(marked) => underlined(runs, marked),
1764            None => runs,
1765        };
1766
1767        let font_size = style.font_size.to_pixels(window.rem_size());
1768        let line_height = field.line_height();
1769        // A single line never wraps: it scrolls sideways instead, so shaping it
1770        // against the field's width would fold it into rows nothing can reach.
1771        let wrap_width = shape.is_multiline().then_some(bounds.size.width);
1772        let lines = window
1773            .text_system()
1774            .shape_text(text, font_size, &runs, wrap_width, None)
1775            .map(|lines| lines.into_vec())
1776            .unwrap_or_default();
1777
1778        // Clamp every frame, not just when scrolling: the content this is
1779        // measured against shrinks under it — delete the last line while parked
1780        // at the bottom and an unclamped offset leaves the box showing nothing.
1781        //
1782        // Only one axis is ever live. Wrapped lines are shaped to the box width,
1783        // so `max.x` is zero for a multi-line field; a single line is one row
1784        // tall, so `max.y` is zero for a single-line one. Neither needs asking
1785        // which shape it is.
1786        let content_height: Pixels = lines.iter().map(|l| l.size(line_height).height).sum();
1787        let content_width = lines.iter().map(|l| l.width()).fold(px(0.), Pixels::max);
1788        let max = gpui::point(
1789            (content_width - bounds.size.width).max(px(0.)),
1790            (content_height - bounds.size.height).max(px(0.)),
1791        );
1792        let mut scroll = gpui::point(
1793            scrolled.x.clamp(px(0.), max.x),
1794            scrolled.y.clamp(px(0.), max.y),
1795        );
1796        if follow_caret && let Some(at) = position_for_offset(&lines, cursor, line_height) {
1797            if at.y < scroll.y {
1798                scroll.y = at.y;
1799            } else if at.y + line_height > scroll.y + bounds.size.height {
1800                scroll.y = at.y + line_height - bounds.size.height;
1801            }
1802            // The caret is the thing being kept in view, so it is its own width
1803            // that has to clear the right edge — not the character before it.
1804            if at.x < scroll.x {
1805                scroll.x = at.x;
1806            } else if at.x + CARET_WIDTH > scroll.x + bounds.size.width {
1807                scroll.x = at.x + CARET_WIDTH - bounds.size.width;
1808            }
1809            scroll.x = scroll.x.clamp(px(0.), max.x);
1810            scroll.y = scroll.y.clamp(px(0.), max.y);
1811        }
1812        self.field.update(cx, |field, _| {
1813            field.scroll = scroll;
1814            field.follow_caret = false;
1815        });
1816        let origin = bounds.origin - scroll;
1817
1818        let (selection, cursor) = if selected_range.is_empty() {
1819            let at = position_for_offset(&lines, cursor, line_height).unwrap_or_default();
1820            (
1821                Vec::new(),
1822                Some(fill(
1823                    // The font's size rather than the line's: leading is not
1824                    // the caret's to fill.
1825                    Bounds::new(
1826                        origin + at + gpui::point(px(0.), (line_height - font_size) / 2.),
1827                        gpui::size(CARET_WIDTH, font_size),
1828                    ),
1829                    theme.caret,
1830                )),
1831            )
1832        } else {
1833            (
1834                selection_rows(&lines, &selected_range, line_height)
1835                    .into_iter()
1836                    .map(|rect| {
1837                        fill(
1838                            Bounds::new(origin + rect.origin, rect.size),
1839                            theme.selection,
1840                        )
1841                    })
1842                    .collect(),
1843                None,
1844            )
1845        };
1846
1847        FieldPrepaint {
1848            lines,
1849            origin,
1850            cursor,
1851            selection,
1852        }
1853    }
1854
1855    fn paint(
1856        &mut self,
1857        _id: Option<&GlobalElementId>,
1858        _inspector_id: Option<&gpui::InspectorElementId>,
1859        bounds: Bounds<Pixels>,
1860        _request_layout: &mut Self::RequestLayoutState,
1861        prepaint: &mut Self::PrepaintState,
1862        window: &mut Window,
1863        cx: &mut App,
1864    ) {
1865        let focus_handle = self.field.read(cx).focus_handle.clone();
1866        let caret_on = self.field.read(cx).caret_on;
1867        window.handle_input(
1868            &focus_handle,
1869            ElementInputHandler::new(bounds, self.field.clone()),
1870            cx,
1871        );
1872        let line_height = self.field.read(cx).line_height();
1873        // A drag that leaves the box is still a drag. `on_mouse_move` on the
1874        // field's own div fires only while the box is the thing under the
1875        // pointer, so a run dragged past the edge froze at the last character
1876        // inside it — and the last line of a full box was unreachable, since
1877        // reaching it means passing the edge. This is the window's own move,
1878        // which arrives wherever the pointer went.
1879        //
1880        // Registered every frame because that is the contract: the listener is
1881        // cleared with the frame that installed it.
1882        let dragged = self.field.clone();
1883        window.on_mouse_event(move |event: &MouseMoveEvent, phase, _window, cx| {
1884            // The button being down is what makes this a drag. `is_selecting`
1885            // is only cleared on the release, so a press whose release went
1886            // somewhere we never heard about would otherwise leave a plain
1887            // hover dragging the run around.
1888            if phase != DispatchPhase::Bubble || !event.dragging() {
1889                return;
1890            }
1891            dragged.update(cx, |field, cx| {
1892                field.drag_to(event.position, line_height, cx);
1893            });
1894        });
1895        let lines = std::mem::take(&mut prepaint.lines);
1896        let selection = std::mem::take(&mut prepaint.selection);
1897        let cursor = prepaint.cursor.take();
1898        let origin = prepaint.origin;
1899
1900        // Scrolled text runs past the box in both directions, so everything the
1901        // field draws is masked to it — text, selection and caret alike.
1902        window.with_content_mask(Some(gpui::ContentMask::new(bounds)), |window| {
1903            for selection in selection {
1904                window.paint_quad(selection);
1905            }
1906
1907            let mut top = origin;
1908            for line in &lines {
1909                line.paint(top, line_height, gpui::TextAlign::Left, None, window, cx)
1910                    .ok();
1911                top.y += line.size(line_height).height;
1912            }
1913
1914            // The caret only exists while focused — an unfocused field showing
1915            // one reads as two cursors on screen.
1916            if focus_handle.is_focused(window)
1917                && caret_on
1918                && let Some(cursor) = cursor
1919            {
1920                window.paint_quad(cursor);
1921            }
1922        });
1923
1924        self.field.update(cx, |field, _| {
1925            field.last_layout = lines;
1926            field.last_bounds = Some(bounds);
1927        });
1928    }
1929}