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