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