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