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