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