Skip to main content

ui/
input.rs

1//! [`TextField`] — a single-line text field with IME, selection and clipboard.
2//!
3//! Unlike the rest of this crate, a text field cannot be a plain function
4//! returning a `Div`: editing needs state (content, selection, IME marked
5//! range), a focus handle, and gpui's [`EntityInputHandler`]. So it is an
6//! entity the caller holds — SwiftUI's `TextField` bound to `@State`, not a
7//! stateless view.
8//!
9//! Ported from gpui's `examples/input.rs` (Apache-2.0), restyled onto
10//! [`Theme`] tokens, with the key bindings **scoped to the field's key
11//! context** rather than installed globally: a component library must not make
12//! `cmd-a` mean "select all text" for the whole application.
13//!
14//! ```ignore
15//! ui::input::init(cx);                      // once, at startup
16//! let field = cx.new(|cx| TextField::new(cx).with_placeholder("Search…"));
17//! // …then render it: .child(field.clone())
18//! ```
19
20use std::{borrow::Cow, ops::Range, time::Duration};
21
22use gpui::{
23    App, Bounds, ClipboardItem, Context, CursorStyle, DispatchPhase, ElementId,
24    ElementInputHandler, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable, Global,
25    GlobalElementId, KeyBinding, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent,
26    MouseUpEvent, PaintQuad, Pixels, Point, SharedString, Style, Task, TextRun, UTF16Selection,
27    UnderlineStyle, Window, WrappedLine, actions, div, fill, prelude::*, px, relative,
28};
29use unicode_segmentation::UnicodeSegmentation as _;
30
31use theme::{HighlightKind, Metrics, SyntaxPalette, TextStyle, Theme};
32
33mod edit;
34mod element;
35mod ime;
36mod text;
37
38pub use element::*;
39pub use text::*;
40
41actions!(
42    bezel_text_field,
43    [
44        Backspace,
45        Delete,
46        Left,
47        Right,
48        SelectLeft,
49        SelectRight,
50        SelectAll,
51        Home,
52        End,
53        SelectHome,
54        SelectEnd,
55        WordLeft,
56        WordRight,
57        SelectWordLeft,
58        SelectWordRight,
59        DeleteWordLeft,
60        DeleteWordRight,
61        DeleteToLineStart,
62        DeleteToLineEnd,
63        ShowCharacterPalette,
64        Paste,
65        Cut,
66        Copy,
67        Up,
68        Down,
69        SelectUp,
70        SelectDown,
71        InsertNewline,
72        Undo,
73        Redo,
74    ]
75);
76
77/// How many undo steps a field keeps by default.
78///
79/// Steps, not keystrokes: a run of typing coalesces into one, so this is deeper
80/// than it looks. A text field is not a document — nobody walks a search box
81/// back through a long history — and the ceiling is what stops a long-lived
82/// field accumulating snapshots forever. Override per field with
83/// [`TextField::with_undo_limit`].
84pub const DEFAULT_UNDO_LIMIT: usize = 10;
85
86/// What a field reports, one per user action.
87///
88/// [`gpui::Context::observe`] fires on every `notify`, which includes repaints
89/// nothing asked for — the caret's own blink among them. A picker refiltering
90/// on those throws its cursor back to the first row twice a second. Subscribe
91/// to this instead, and take only the half you need: a picker filters on
92/// [`Self::Changed`], a composer reading the word behind the caret wants both.
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub enum FieldEvent {
95    /// The text is different.
96    Changed,
97    /// The text is the same and the caret is somewhere else.
98    Moved,
99}
100
101/// Half the caret's blink period — the 500ms on, 500ms off macOS itself uses.
102const BLINK: Duration = Duration::from_millis(500);
103
104/// Whether carets blink. Absent means they do — a global nobody installed is
105/// the default, not the opposite of it.
106struct CaretBlink(bool);
107
108impl Global for CaretBlink {}
109
110/// Whether a caret blinks or is held solid. Read where a blink would start:
111/// [`TextField`] here, and the editor's own caret.
112pub fn caret_blink(cx: &App) -> bool {
113    cx.try_global::<CaretBlink>().is_none_or(|blink| blink.0)
114}
115
116/// A caret held solid is still a caret — turning the blink off stops the task
117/// and leaves the caret lit, never caught on the half of the beat that hides it.
118pub fn set_caret_blink(blink: bool, cx: &mut App) {
119    cx.set_global(CaretBlink(blink));
120    cx.refresh_windows();
121}
122
123/// Width of the caret. Named because horizontal scrolling has to keep the caret
124/// itself on screen, not merely the character before it.
125const CARET_WIDTH: Pixels = px(2.);
126
127/// The key context the field claims; bindings from [`init`] are scoped to it.
128pub const KEY_CONTEXT: &str = "TextField";
129
130/// Claimed *in addition* to [`KEY_CONTEXT`] by a multi-line field.
131///
132/// Vertical motion and `enter` hang off this rather than off every field,
133/// because a single-line field is routinely nested inside something that has
134/// already claimed those keys: [`crate::palette`] and [`crate::combobox`] both
135/// bind `up`, `down`, `ctrl-n`, `ctrl-p` and `enter` to drive their lists, and
136/// their query field sits *deeper* in the focus path — so binding those on
137/// every `TextField` would win the dispatch and break list navigation in both.
138pub const MULTILINE_KEY_CONTEXT: &str = "TextArea";
139
140/// Install the bindings — [`bindings`], bound. Call once at startup.
141pub fn init(cx: &mut App) {
142    cx.bind_keys(bindings());
143}
144
145/// The field's keymap, as data, so an app can have it without having to
146/// take it — see [`crate::keys`] for layering over it or taking a chord
147/// away.
148///
149/// Every binding is scoped to [`KEY_CONTEXT`], so they are inert outside a
150/// focused field and an app is free to bind the same chords elsewhere.
151pub fn bindings() -> Vec<KeyBinding> {
152    let mut bindings = Vec::new();
153    let ctx = Some(KEY_CONTEXT);
154    bindings.extend([
155        // Character movement and editing, everywhere.
156        KeyBinding::new("backspace", Backspace, ctx),
157        KeyBinding::new("delete", Delete, ctx),
158        KeyBinding::new("left", Left, ctx),
159        KeyBinding::new("right", Right, ctx),
160        KeyBinding::new("shift-left", SelectLeft, ctx),
161        KeyBinding::new("shift-right", SelectRight, ctx),
162        KeyBinding::new("home", Home, ctx),
163        KeyBinding::new("end", End, ctx),
164        KeyBinding::new("shift-home", SelectHome, ctx),
165        KeyBinding::new("shift-end", SelectEnd, ctx),
166    ]);
167
168    // Multi-line only — see [`MULTILINE_KEY_CONTEXT`] for why these cannot be
169    // bound on every field.
170    let area = Some(MULTILINE_KEY_CONTEXT);
171    bindings.extend([
172        KeyBinding::new("enter", InsertNewline, area),
173        KeyBinding::new("up", Up, area),
174        KeyBinding::new("down", Down, area),
175        KeyBinding::new("shift-up", SelectUp, area),
176        KeyBinding::new("shift-down", SelectDown, area),
177    ]);
178
179    #[cfg(target_os = "macos")]
180    bindings.extend([
181        KeyBinding::new("cmd-a", SelectAll, ctx),
182        KeyBinding::new("cmd-c", Copy, ctx),
183        KeyBinding::new("cmd-x", Cut, ctx),
184        KeyBinding::new("cmd-v", Paste, ctx),
185        KeyBinding::new("cmd-z", Undo, ctx),
186        KeyBinding::new("cmd-shift-z", Redo, ctx),
187        KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, ctx),
188        // cmd = line, option = word: the macOS convention.
189        KeyBinding::new("cmd-left", Home, ctx),
190        KeyBinding::new("cmd-right", End, ctx),
191        KeyBinding::new("cmd-shift-left", SelectHome, ctx),
192        KeyBinding::new("cmd-shift-right", SelectEnd, ctx),
193        KeyBinding::new("alt-left", WordLeft, ctx),
194        KeyBinding::new("alt-right", WordRight, ctx),
195        KeyBinding::new("alt-shift-left", SelectWordLeft, ctx),
196        KeyBinding::new("alt-shift-right", SelectWordRight, ctx),
197        KeyBinding::new("cmd-backspace", DeleteToLineStart, ctx),
198        KeyBinding::new("alt-backspace", DeleteWordLeft, ctx),
199        KeyBinding::new("alt-delete", DeleteWordRight, ctx),
200        // The emacs bindings macOS honours in every native text field.
201        KeyBinding::new("ctrl-a", Home, ctx),
202        KeyBinding::new("ctrl-e", End, ctx),
203        KeyBinding::new("ctrl-b", Left, ctx),
204        KeyBinding::new("ctrl-f", Right, ctx),
205        KeyBinding::new("ctrl-h", Backspace, ctx),
206        KeyBinding::new("ctrl-d", Delete, ctx),
207        KeyBinding::new("ctrl-k", DeleteToLineEnd, ctx),
208    ]);
209
210    // `C-n`/`C-p` are emacs' vertical motion and macOS `NSTextView` natives
211    // both — the two tests a chord has to pass to earn a binding here.
212    #[cfg(target_os = "macos")]
213    bindings.extend([
214        KeyBinding::new("ctrl-n", Down, area),
215        KeyBinding::new("ctrl-p", Up, area),
216    ]);
217
218    #[cfg(not(target_os = "macos"))]
219    bindings.extend([
220        KeyBinding::new("ctrl-a", SelectAll, ctx),
221        KeyBinding::new("ctrl-c", Copy, ctx),
222        KeyBinding::new("ctrl-x", Cut, ctx),
223        KeyBinding::new("ctrl-v", Paste, ctx),
224        // ctrl = word on Windows/Linux, where there is no line modifier.
225        KeyBinding::new("ctrl-left", WordLeft, ctx),
226        KeyBinding::new("ctrl-right", WordRight, ctx),
227        KeyBinding::new("ctrl-shift-left", SelectWordLeft, ctx),
228        KeyBinding::new("ctrl-shift-right", SelectWordRight, ctx),
229        KeyBinding::new("ctrl-backspace", DeleteWordLeft, ctx),
230        KeyBinding::new("ctrl-delete", DeleteWordRight, ctx),
231        KeyBinding::new("ctrl-z", Undo, ctx),
232        KeyBinding::new("ctrl-shift-z", Redo, ctx),
233        KeyBinding::new("ctrl-y", Redo, ctx),
234    ]);
235
236    bindings
237}
238
239/// What case a field holds its text in.
240///
241/// Applied to every edit rather than to the string on its way out, so what is
242/// painted, what `content()` returns and what a caller stores are one string.
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
244pub enum Case {
245    /// Whatever was typed.
246    #[default]
247    Mixed,
248    /// Upper, for a field naming something a heading is drawn from.
249    Upper,
250}
251
252impl Case {
253    /// `text` in this case. Borrowed where the case leaves it alone, which
254    /// is the default and every keystroke through it.
255    fn apply(self, text: &str) -> Cow<'_, str> {
256        match self {
257            Self::Mixed => Cow::Borrowed(text),
258            Self::Upper => Cow::Owned(text.to_uppercase()),
259        }
260    }
261}
262
263/// What shape the field takes.
264///
265/// Editing is identical across all three — every action works on the content
266/// and a byte range, and none of them cares where the lines break. What this
267/// decides is the box: how tall it is, whether text wraps, and with that what
268/// `enter` and a pasted newline are allowed to mean.
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
270pub enum Shape {
271    /// One line, no wrapping. `enter` does not insert; a pasted newline becomes
272    /// a space rather than silently truncating what was pasted.
273    #[default]
274    Line,
275    /// Exactly `rows` lines tall, wrapping, scrolling past that.
276    Rows(usize),
277    /// Wraps and grows with the content between `min` and `max` rows, then
278    /// scrolls — the composer shape.
279    Grow { min: usize, max: usize },
280}
281
282impl Shape {
283    /// Whether newlines are content. The single branch every editing policy
284    /// hangs off, so it is asked once rather than matched in each caller.
285    fn is_multiline(self) -> bool {
286        !matches!(self, Self::Line)
287    }
288}
289
290/// A point the field can be returned to.
291///
292/// A whole snapshot rather than a diff: a field holds a sentence, not a file,
293/// and `SharedString` clones are a refcount bump. A rope and a transaction log
294/// is what an editor needs and would be the wrong machinery here.
295#[derive(Clone)]
296struct Snapshot {
297    content: SharedString,
298    selection: Range<usize>,
299    reversed: bool,
300}
301
302/// Which way an edit went, so a run of the same kind can coalesce into one
303/// undo step instead of giving the text back a character at a time.
304#[derive(Clone, Copy, PartialEq, Eq)]
305pub enum EditKind {
306    Insert,
307    Delete,
308}
309
310/// A text field. [`Shape`] decides whether it is one line or many; everything
311/// else about it is the same either way.
312pub struct TextField {
313    focus_handle: FocusHandle,
314    content: SharedString,
315    placeholder: SharedString,
316    shape: Shape,
317    /// The case every edit is put through — see [`Case`].
318    case: Case,
319    selected_range: Range<usize>,
320    selection_reversed: bool,
321    /// The IME composition range (underlined while composing).
322    marked_range: Option<Range<usize>>,
323    /// One entry per hard newline; each wraps into rows of its own. Empty until
324    /// the first paint.
325    last_layout: Vec<WrappedLine>,
326    last_bounds: Option<Bounds<Pixels>>,
327    is_selecting: bool,
328    /// The column vertical motion is trying to keep, in pixels from the left of
329    /// the row. Held across a run of up/down so that walking through a short
330    /// line and out the other side returns to the column you started in, and
331    /// dropped by anything horizontal — which is every other way the caret
332    /// moves, so [`TextField::move_to`] and [`TextField::select_to`] clear it
333    /// and the vertical handlers put it back.
334    goal_x: Option<Pixels>,
335    /// How far the text is scrolled inside the box. Clamped every frame,
336    /// because the content it is measured against changes under it.
337    ///
338    /// Both axes, though only ever one at a time: a wrapped field's lines are
339    /// shaped to the box width so they cannot overflow sideways, and a
340    /// single-line field is exactly one row tall so it cannot overflow
341    /// downwards. The clamp falls out of that and needs no test for shape.
342    scroll: Point<Pixels>,
343    history: crate::history::SnapshotHistory<Snapshot>,
344    /// The kind of the last edit and the offset it left the caret at, which is
345    /// what decides whether the next edit joins that group or starts a new one.
346    /// Adjacency rather than a pause, so there is no timing threshold to invent.
347    last_edit: Option<(EditKind, usize)>,
348    /// A context this field claims *in addition* to [`KEY_CONTEXT`] and
349    /// [`MULTILINE_KEY_CONTEXT`] — see [`Self::with_key_context`].
350    key_context: Option<SharedString>,
351    /// Whether the field paints a box around itself. Off for a field that
352    /// stands in for text already in place — a rename in a list row, where the
353    /// box would be a second frame inside the row's own.
354    frame: bool,
355    /// What the text is set in. Its leading is a multiple of the painted size,
356    /// so the whole line box follows [`theme::set_base_text_size`].
357    metrics: Metrics,
358    /// Which half of the blink the caret is in. Flipped by [`Self::start_blink`].
359    caret_on: bool,
360    /// The blink, alive only while the field holds focus.
361    blink: Option<Task<()>>,
362    /// Set by anything that moves the caret, cleared once a frame has scrolled
363    /// it back into view.
364    ///
365    /// Without the flag the wheel could never win: following the caret
366    /// unconditionally would snap the view back to it on the very next frame,
367    /// so scrolling away to read would be impossible.
368    follow_caret: bool,
369    /// Byte ranges to paint in a syntax colour, in document order — see
370    /// [`Self::set_spans`]. Empty for every field that is prose.
371    spans: Vec<(Range<usize>, HighlightKind)>,
372    /// Byte ranges washed behind the text — see [`Self::set_matches`].
373    matches: Vec<Range<usize>>,
374}
375
376impl EventEmitter<FieldEvent> for TextField {}
377
378impl TextField {
379    pub fn new(cx: &mut Context<Self>) -> Self {
380        Self {
381            // A field is a tab stop from birth; a stateless control needs
382            // `focus::focusable` because its handle lives in the caller.
383            focus_handle: cx.focus_handle().tab_stop(true),
384            frame: true,
385            content: "".into(),
386            placeholder: "".into(),
387            shape: Shape::Line,
388            case: Case::default(),
389            selected_range: 0..0,
390            selection_reversed: false,
391            marked_range: None,
392            last_layout: Vec::new(),
393            last_bounds: None,
394            is_selecting: false,
395            goal_x: None,
396            scroll: Point::default(),
397            history: crate::history::SnapshotHistory::new(DEFAULT_UNDO_LIMIT),
398            last_edit: None,
399            key_context: None,
400            metrics: TextStyle::Body.into(),
401            caret_on: true,
402            blink: None,
403            follow_caret: false,
404            spans: Vec::new(),
405            matches: Vec::new(),
406        }
407    }
408
409    /// How many undo steps to keep. App-wide configuration would be a gpui
410    /// global alongside [`crate::input::init`], not a [`Theme`] field — the
411    /// theme is rebuilt on every light/dark switch, which would quietly reset
412    /// anything behavioural parked in it.
413    pub fn with_undo_limit(mut self, limit: usize) -> Self {
414        self.history.set_limit(limit);
415        self
416    }
417
418    pub fn with_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
419        self.placeholder = placeholder.into();
420        self
421    }
422
423    /// Claim an extra key context on this field, so an app can bind a key
424    /// *here* that it does not want bound in every other field.
425    ///
426    /// The composer case, and the reason this exists: `enter` sends a message
427    /// and `shift-enter` breaks a line, while the notes field two panels over
428    /// still takes `enter` as a newline. gpui resolves a keystroke to the
429    /// binding whose context matches **deepest** in the focus path, and nothing
430    /// is deeper than the focused field — so a container around it cannot win
431    /// `enter`, however it is bound. Rebinding [`MULTILINE_KEY_CONTEXT`]
432    /// globally would win, and would take the newline away from every other
433    /// multi-line field in the app. A context of the field's own is the only
434    /// thing that is both deep enough and narrow enough.
435    ///
436    /// ```ignore
437    /// const COMPOSER: &str = "Composer";
438    /// cx.bind_keys([
439    ///     KeyBinding::new("enter", Send, Some(COMPOSER)),
440    ///     KeyBinding::new("shift-enter", input::InsertNewline, Some(COMPOSER)),
441    /// ]);
442    /// let field = cx.new(|cx| {
443    ///     TextField::new(cx)
444    ///         .with_shape(Shape::Grow { min: 3, max: 12 })
445    ///         .with_key_context(COMPOSER)
446    /// });
447    /// ```
448    pub fn with_key_context(mut self, context: impl Into<SharedString>) -> Self {
449        self.key_context = Some(context.into());
450        self
451    }
452
453    /// Draw without the box, padding and focus ring. The caller owns the
454    /// spacing then, and owns showing that the field is focused.
455    pub fn with_frame(mut self, frame: bool) -> Self {
456        self.frame = frame;
457        self
458    }
459
460    /// Set the text in something other than body copy —
461    /// `Typography::of(cx).h1` sets a field the way a document sets its own
462    /// heading.
463    pub fn with_metrics(mut self, metrics: Metrics) -> Self {
464        self.metrics = metrics;
465        self
466    }
467
468    /// Update typography without replacing the editing state.
469    pub fn set_metrics(&mut self, metrics: Metrics, cx: &mut Context<Self>) {
470        if self.metrics != metrics {
471            self.metrics = metrics;
472            cx.notify();
473        }
474    }
475
476    pub fn with_shape(mut self, shape: Shape) -> Self {
477        self.shape = shape;
478        self
479    }
480
481    pub fn with_case(mut self, case: Case) -> Self {
482        self.case = case;
483        self
484    }
485
486    /// The case from here on. What the field already holds is left as it is:
487    /// the text came from somewhere else, and a caller switching the case of a
488    /// shared field would otherwise rewrite the name it is showing.
489    pub fn set_case(&mut self, case: Case) {
490        self.case = case;
491    }
492
493    pub fn case(&self) -> Case {
494        self.case
495    }
496
497    pub fn shape(&self) -> Shape {
498        self.shape
499    }
500
501    pub fn content(&self) -> &SharedString {
502        &self.content
503    }
504
505    /// The syntax spans this field is painting — see [`Self::set_spans`].
506    pub fn spans(&self) -> &[(Range<usize>, HighlightKind)] {
507        &self.spans
508    }
509
510    /// Replace the content, putting the cursor at the end.
511    pub fn set_content(&mut self, content: impl Into<SharedString>, cx: &mut Context<Self>) {
512        let normalized = normalize(&content.into(), self.shape);
513        self.content = self.case.apply(&normalized).into_owned().into();
514        // Colours describe text this field no longer holds.
515        self.spans.clear();
516        self.matches.clear();
517        // A programmatic reset is not something the user did, so there is
518        // nothing here for them to undo back past.
519        self.history.clear();
520        self.last_edit = None;
521        let end = self.content.len();
522        self.selected_range = end..end;
523        self.marked_range = None;
524        cx.emit(FieldEvent::Changed);
525        cx.notify();
526    }
527
528    pub fn clear(&mut self, cx: &mut Context<Self>) {
529        self.set_content("", cx);
530    }
531
532    /// Paint these byte ranges in syntax colours, in document order. Whatever
533    /// they leave uncovered stays the field's own text colour, so a language
534    /// nothing can colour is simply no spans at all.
535    ///
536    /// This field holds text, not a document: it does not parse, and it does
537    /// not keep the spans in step with edits. The caller recomputes them —
538    /// [`FieldEvent::Changed`] is the signal — and until it does, the frames in
539    /// between paint the ranges it last handed over. Stale ones are dropped
540    /// rather than shifted, so the worst a late recolour looks like is a word
541    /// in the wrong colour for a frame.
542    ///
543    /// [`set_content`](Self::set_content) clears them: replacing the text
544    /// replaces what the colours were about.
545    pub fn set_spans(&mut self, spans: Vec<(Range<usize>, HighlightKind)>, cx: &mut Context<Self>) {
546        self.spans = spans;
547        cx.notify();
548    }
549
550    /// Wash these byte ranges behind the text, as search matches. Not kept in
551    /// step with edits, and cleared by [`Self::set_content`], the same as
552    /// [`Self::set_spans`]. A range past the end or off a char boundary is
553    /// not painted.
554    pub fn set_matches(&mut self, matches: Vec<Range<usize>>, cx: &mut Context<Self>) {
555        self.matches = matches;
556        cx.notify();
557    }
558
559    /// Select `range` and scroll it into view, with the caret at its end.
560    /// Clamped to the content and to char boundaries.
561    pub fn select(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
562        let end = floor_boundary(&self.content, range.end);
563        let start = floor_boundary(&self.content, range.start.min(end));
564        self.selected_range = start..end;
565        self.selection_reversed = false;
566        self.marked_range = None;
567        self.goal_x = None;
568        self.caret_moved();
569        cx.notify();
570    }
571
572    /// [`Self::with_placeholder`] after construction, for a hint that follows
573    /// something else — the field naming whichever agent, file or channel is
574    /// selected rather than being rebuilt each time one is.
575    pub fn set_placeholder(
576        &mut self,
577        placeholder: impl Into<SharedString>,
578        cx: &mut Context<Self>,
579    ) {
580        self.placeholder = placeholder.into();
581        cx.notify();
582    }
583
584    /// The caret moved: bring it back into view, and drop the blink so the next
585    /// render starts a fresh one. Without the reset the caret would blink
586    /// through your own typing, which reads as a dropped keystroke.
587    fn caret_moved(&mut self) {
588        self.follow_caret = true;
589        self.blink = None;
590    }
591
592    /// Blink the caret for as long as the field holds focus.
593    fn start_blink(&mut self, cx: &mut Context<Self>) {
594        self.caret_on = true;
595        self.blink = Some(cx.spawn(async move |field, cx| {
596            loop {
597                cx.background_executor().timer(BLINK).await;
598                let flipped = field.update(cx, |field, cx| {
599                    field.caret_on = !field.caret_on;
600                    cx.notify();
601                });
602                if flipped.is_err() {
603                    break;
604                }
605            }
606        }));
607    }
608
609    /// Where the caret is, as a byte offset into [`Self::content`].
610    ///
611    /// What a caller needs to read the text *behind* the caret: the `#` an
612    /// autocomplete triggers on, the word a lookup would act on. With a
613    /// selection this is the moving end, which is where typing would land.
614    pub fn cursor(&self) -> usize {
615        self.cursor_offset()
616    }
617
618    /// Where a byte offset sits on screen — the bounds of the row it is on, in
619    /// window coordinates.
620    ///
621    /// The anchor for anything that hangs off a position *in the text* rather
622    /// than off the field: a mention picker under the `#` that opened it, in a
623    /// box that is also growing a row at a time. Pass it to
624    /// [`crate::popover::menu_at`], which takes a window point.
625    ///
626    /// `None` until the field has painted once — this is measured off the
627    /// shaped layout, and there is none before then.
628    pub fn offset_bounds(&self, offset: usize) -> Option<Bounds<Pixels>> {
629        self.row_bounds(self.text_origin()?, offset..offset, self.line_height())
630    }
631
632    /// The rectangle `range` spans, starting from the row it opens on, relative
633    /// to `origin`.
634    ///
635    /// The IME's candidate panel and [`Self::offset_bounds`] are the same
636    /// question asked by two callers, so they ask it here — computed apart they
637    /// would answer differently the first time one of them forgot the scroll.
638    fn row_bounds(
639        &self,
640        origin: Point<Pixels>,
641        range: Range<usize>,
642        line_height: Pixels,
643    ) -> Option<Bounds<Pixels>> {
644        let start = position_for_offset(&self.last_layout, range.start, line_height)?;
645        let end = position_for_offset(&self.last_layout, range.end, line_height)?;
646        Some(Bounds::from_corners(
647            origin + start,
648            origin + gpui::point(end.x, end.y + line_height),
649        ))
650    }
651
652    fn move_to(&mut self, offset: usize, cx: &mut Context<Self>) {
653        self.selected_range = offset..offset;
654        self.goal_x = None;
655        self.caret_moved();
656        cx.emit(FieldEvent::Moved);
657        cx.notify()
658    }
659
660    fn cursor_offset(&self) -> usize {
661        if self.selection_reversed {
662            self.selected_range.start
663        } else {
664            self.selected_range.end
665        }
666    }
667
668    /// The row height every mapping between a screen point and a byte offset
669    /// walks by: this field's own, which is what its shaped lines were laid
670    /// out at — see [`TextField::render`], which sets it on the box.
671    ///
672    /// Never `window.line_height()`. That answers for whatever text style is
673    /// current, and outside this field's own paint there is none of it on the
674    /// stack — a click handler is told the window's default instead. Walk the
675    /// rows at that stride and the caret lands a line or two above the one
676    /// under the pointer, further out the lower you click, until the last
677    /// lines of a full box cannot be reached at all.
678    fn line_height(&self) -> Pixels {
679        px(self.metrics.line_height())
680    }
681
682    /// Where the shaped text starts on screen: the box, moved up by the scroll.
683    /// Every mapping between a screen point and a byte offset goes through it.
684    fn text_origin(&self) -> Option<Point<Pixels>> {
685        Some(self.last_bounds?.origin - self.scroll)
686    }
687
688    fn index_for_mouse_position(&self, position: Point<Pixels>, line_height: Pixels) -> usize {
689        if self.content.is_empty() || self.last_layout.is_empty() {
690            return 0;
691        }
692        let Some(origin) = self.text_origin() else {
693            return 0;
694        };
695        offset_for_position(&self.last_layout, position - origin, line_height)
696    }
697
698    fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
699        self.goal_x = None;
700        self.caret_moved();
701        if self.selection_reversed {
702            self.selected_range.start = offset
703        } else {
704            self.selected_range.end = offset
705        };
706        if self.selected_range.end < self.selected_range.start {
707            self.selection_reversed = !self.selection_reversed;
708            self.selected_range = self.selected_range.end..self.selected_range.start;
709        }
710        cx.emit(FieldEvent::Moved);
711        cx.notify()
712    }
713
714    fn offset_to_utf16(&self, offset: usize) -> usize {
715        offset_to_utf16(&self.content, offset)
716    }
717
718    fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
719        range_to_utf16(&self.content, range.clone())
720    }
721
722    fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
723        range_from_utf16(&self.content, range_utf16.clone())
724    }
725
726    fn previous_boundary(&self, offset: usize) -> usize {
727        previous_boundary(&self.content, offset)
728    }
729
730    fn next_boundary(&self, offset: usize) -> usize {
731        next_boundary(&self.content, offset)
732    }
733}
734
735impl Focusable for TextField {
736    fn focus_handle(&self, _: &App) -> FocusHandle {
737        self.focus_handle.clone()
738    }
739}