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