1use std::{ops::Range, time::Duration};
21
22use gpui::{
23 App, Bounds, ClipboardItem, Context, CursorStyle, DispatchPhase, ElementId,
24 ElementInputHandler, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable, Global,
25 GlobalElementId, KeyBinding, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent,
26 MouseUpEvent, PaintQuad, Pixels, Point, SharedString, Style, Task, TextRun, UTF16Selection,
27 UnderlineStyle, Window, WrappedLine, actions, div, fill, prelude::*, px, relative,
28};
29use unicode_segmentation::UnicodeSegmentation as _;
30
31use theme::{Metrics, TextStyle, Theme};
32
33actions!(
34 bezel_text_field,
35 [
36 Backspace,
37 Delete,
38 Left,
39 Right,
40 SelectLeft,
41 SelectRight,
42 SelectAll,
43 Home,
44 End,
45 SelectHome,
46 SelectEnd,
47 WordLeft,
48 WordRight,
49 SelectWordLeft,
50 SelectWordRight,
51 DeleteWordLeft,
52 DeleteWordRight,
53 DeleteToLineStart,
54 DeleteToLineEnd,
55 ShowCharacterPalette,
56 Paste,
57 Cut,
58 Copy,
59 Up,
60 Down,
61 SelectUp,
62 SelectDown,
63 InsertNewline,
64 Undo,
65 Redo,
66 ]
67);
68
69pub const DEFAULT_UNDO_LIMIT: usize = 10;
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum FieldEvent {
87 Changed,
89 Moved,
91}
92
93const BLINK: Duration = Duration::from_millis(500);
95
96struct CaretBlink(bool);
99
100impl Global for CaretBlink {}
101
102pub fn caret_blink(cx: &App) -> bool {
105 cx.try_global::<CaretBlink>().is_none_or(|blink| blink.0)
106}
107
108pub fn set_caret_blink(blink: bool, cx: &mut App) {
111 cx.set_global(CaretBlink(blink));
112 cx.refresh_windows();
113}
114
115const CARET_WIDTH: Pixels = px(2.);
118
119pub const KEY_CONTEXT: &str = "TextField";
121
122pub const MULTILINE_KEY_CONTEXT: &str = "TextArea";
131
132pub fn init(cx: &mut App) {
134 cx.bind_keys(bindings());
135}
136
137pub fn bindings() -> Vec<KeyBinding> {
144 let mut bindings = Vec::new();
145 let ctx = Some(KEY_CONTEXT);
146 bindings.extend([
147 KeyBinding::new("backspace", Backspace, ctx),
149 KeyBinding::new("delete", Delete, ctx),
150 KeyBinding::new("left", Left, ctx),
151 KeyBinding::new("right", Right, ctx),
152 KeyBinding::new("shift-left", SelectLeft, ctx),
153 KeyBinding::new("shift-right", SelectRight, ctx),
154 KeyBinding::new("home", Home, ctx),
155 KeyBinding::new("end", End, ctx),
156 KeyBinding::new("shift-home", SelectHome, ctx),
157 KeyBinding::new("shift-end", SelectEnd, ctx),
158 ]);
159
160 let area = Some(MULTILINE_KEY_CONTEXT);
163 bindings.extend([
164 KeyBinding::new("enter", InsertNewline, area),
165 KeyBinding::new("up", Up, area),
166 KeyBinding::new("down", Down, area),
167 KeyBinding::new("shift-up", SelectUp, area),
168 KeyBinding::new("shift-down", SelectDown, area),
169 ]);
170
171 #[cfg(target_os = "macos")]
172 bindings.extend([
173 KeyBinding::new("cmd-a", SelectAll, ctx),
174 KeyBinding::new("cmd-c", Copy, ctx),
175 KeyBinding::new("cmd-x", Cut, ctx),
176 KeyBinding::new("cmd-v", Paste, ctx),
177 KeyBinding::new("cmd-z", Undo, ctx),
178 KeyBinding::new("cmd-shift-z", Redo, ctx),
179 KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, ctx),
180 KeyBinding::new("cmd-left", Home, ctx),
182 KeyBinding::new("cmd-right", End, ctx),
183 KeyBinding::new("cmd-shift-left", SelectHome, ctx),
184 KeyBinding::new("cmd-shift-right", SelectEnd, ctx),
185 KeyBinding::new("alt-left", WordLeft, ctx),
186 KeyBinding::new("alt-right", WordRight, ctx),
187 KeyBinding::new("alt-shift-left", SelectWordLeft, ctx),
188 KeyBinding::new("alt-shift-right", SelectWordRight, ctx),
189 KeyBinding::new("cmd-backspace", DeleteToLineStart, ctx),
190 KeyBinding::new("alt-backspace", DeleteWordLeft, ctx),
191 KeyBinding::new("alt-delete", DeleteWordRight, ctx),
192 KeyBinding::new("ctrl-a", Home, ctx),
194 KeyBinding::new("ctrl-e", End, ctx),
195 KeyBinding::new("ctrl-b", Left, ctx),
196 KeyBinding::new("ctrl-f", Right, ctx),
197 KeyBinding::new("ctrl-h", Backspace, ctx),
198 KeyBinding::new("ctrl-d", Delete, ctx),
199 KeyBinding::new("ctrl-k", DeleteToLineEnd, ctx),
200 ]);
201
202 #[cfg(target_os = "macos")]
205 bindings.extend([
206 KeyBinding::new("ctrl-n", Down, area),
207 KeyBinding::new("ctrl-p", Up, area),
208 ]);
209
210 #[cfg(not(target_os = "macos"))]
211 bindings.extend([
212 KeyBinding::new("ctrl-a", SelectAll, ctx),
213 KeyBinding::new("ctrl-c", Copy, ctx),
214 KeyBinding::new("ctrl-x", Cut, ctx),
215 KeyBinding::new("ctrl-v", Paste, ctx),
216 KeyBinding::new("ctrl-left", WordLeft, ctx),
218 KeyBinding::new("ctrl-right", WordRight, ctx),
219 KeyBinding::new("ctrl-shift-left", SelectWordLeft, ctx),
220 KeyBinding::new("ctrl-shift-right", SelectWordRight, ctx),
221 KeyBinding::new("ctrl-backspace", DeleteWordLeft, ctx),
222 KeyBinding::new("ctrl-delete", DeleteWordRight, ctx),
223 KeyBinding::new("ctrl-z", Undo, ctx),
224 KeyBinding::new("ctrl-shift-z", Redo, ctx),
225 ]);
226
227 bindings
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
237pub enum Shape {
238 #[default]
241 Line,
242 Rows(usize),
244 Grow { min: usize, max: usize },
247}
248
249impl Shape {
250 fn is_multiline(self) -> bool {
253 !matches!(self, Self::Line)
254 }
255}
256
257#[derive(Clone)]
263struct Snapshot {
264 content: SharedString,
265 selection: Range<usize>,
266 reversed: bool,
267}
268
269#[derive(Clone, Copy, PartialEq, Eq)]
272pub enum EditKind {
273 Insert,
274 Delete,
275}
276
277pub struct TextField {
280 focus_handle: FocusHandle,
281 content: SharedString,
282 placeholder: SharedString,
283 shape: Shape,
284 selected_range: Range<usize>,
285 selection_reversed: bool,
286 marked_range: Option<Range<usize>>,
288 last_layout: Vec<WrappedLine>,
291 last_bounds: Option<Bounds<Pixels>>,
292 is_selecting: bool,
293 goal_x: Option<Pixels>,
300 scroll: Point<Pixels>,
308 history: crate::history::SnapshotHistory<Snapshot>,
309 last_edit: Option<(EditKind, usize)>,
313 key_context: Option<SharedString>,
316 frame: bool,
320 metrics: Metrics,
323 caret_on: bool,
325 blink: Option<Task<()>>,
327 follow_caret: bool,
334}
335
336impl EventEmitter<FieldEvent> for TextField {}
337
338impl TextField {
339 pub fn new(cx: &mut Context<Self>) -> Self {
340 Self {
341 focus_handle: cx.focus_handle().tab_stop(true),
344 frame: true,
345 content: "".into(),
346 placeholder: "".into(),
347 shape: Shape::Line,
348 selected_range: 0..0,
349 selection_reversed: false,
350 marked_range: None,
351 last_layout: Vec::new(),
352 last_bounds: None,
353 is_selecting: false,
354 goal_x: None,
355 scroll: Point::default(),
356 history: crate::history::SnapshotHistory::new(DEFAULT_UNDO_LIMIT),
357 last_edit: None,
358 key_context: None,
359 metrics: TextStyle::Body.into(),
360 caret_on: true,
361 blink: None,
362 follow_caret: false,
363 }
364 }
365
366 pub fn with_undo_limit(mut self, limit: usize) -> Self {
371 self.history.set_limit(limit);
372 self
373 }
374
375 pub fn with_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
376 self.placeholder = placeholder.into();
377 self
378 }
379
380 pub fn with_key_context(mut self, context: impl Into<SharedString>) -> Self {
406 self.key_context = Some(context.into());
407 self
408 }
409
410 pub fn with_frame(mut self, frame: bool) -> Self {
413 self.frame = frame;
414 self
415 }
416
417 pub fn with_metrics(mut self, metrics: Metrics) -> Self {
421 self.metrics = metrics;
422 self
423 }
424
425 pub fn with_shape(mut self, shape: Shape) -> Self {
426 self.shape = shape;
427 self
428 }
429
430 pub fn shape(&self) -> Shape {
431 self.shape
432 }
433
434 pub fn content(&self) -> &SharedString {
435 &self.content
436 }
437
438 pub fn set_content(&mut self, content: impl Into<SharedString>, cx: &mut Context<Self>) {
440 self.content = normalize(&content.into(), self.shape).into();
441 self.history.clear();
444 self.last_edit = None;
445 let end = self.content.len();
446 self.selected_range = end..end;
447 self.marked_range = None;
448 cx.emit(FieldEvent::Changed);
449 cx.notify();
450 }
451
452 pub fn clear(&mut self, cx: &mut Context<Self>) {
453 self.set_content("", cx);
454 }
455
456 pub fn set_placeholder(
460 &mut self,
461 placeholder: impl Into<SharedString>,
462 cx: &mut Context<Self>,
463 ) {
464 self.placeholder = placeholder.into();
465 cx.notify();
466 }
467
468 fn caret_moved(&mut self) {
472 self.follow_caret = true;
473 self.blink = None;
474 }
475
476 fn start_blink(&mut self, cx: &mut Context<Self>) {
478 self.caret_on = true;
479 self.blink = Some(cx.spawn(async move |field, cx| {
480 loop {
481 cx.background_executor().timer(BLINK).await;
482 let flipped = field.update(cx, |field, cx| {
483 field.caret_on = !field.caret_on;
484 cx.notify();
485 });
486 if flipped.is_err() {
487 break;
488 }
489 }
490 }));
491 }
492
493 pub fn cursor(&self) -> usize {
499 self.cursor_offset()
500 }
501
502 pub fn offset_bounds(&self, offset: usize) -> Option<Bounds<Pixels>> {
513 self.row_bounds(self.text_origin()?, offset..offset, self.line_height())
514 }
515
516 fn row_bounds(
523 &self,
524 origin: Point<Pixels>,
525 range: Range<usize>,
526 line_height: Pixels,
527 ) -> Option<Bounds<Pixels>> {
528 let start = position_for_offset(&self.last_layout, range.start, line_height)?;
529 let end = position_for_offset(&self.last_layout, range.end, line_height)?;
530 Some(Bounds::from_corners(
531 origin + start,
532 origin + gpui::point(end.x, end.y + line_height),
533 ))
534 }
535
536 fn left(&mut self, _: &Left, _: &mut Window, cx: &mut Context<Self>) {
537 if self.selected_range.is_empty() {
538 self.move_to(self.previous_boundary(self.cursor_offset()), cx);
539 } else {
540 self.move_to(self.selected_range.start, cx)
541 }
542 }
543
544 fn right(&mut self, _: &Right, _: &mut Window, cx: &mut Context<Self>) {
545 if self.selected_range.is_empty() {
546 self.move_to(self.next_boundary(self.selected_range.end), cx);
547 } else {
548 self.move_to(self.selected_range.end, cx)
549 }
550 }
551
552 fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
553 self.select_to(self.previous_boundary(self.cursor_offset()), cx);
554 }
555
556 fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
557 self.select_to(self.next_boundary(self.cursor_offset()), cx);
558 }
559
560 fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
561 self.move_to(0, cx);
562 self.select_to(self.content.len(), cx)
563 }
564
565 fn home(&mut self, _: &Home, _: &mut Window, cx: &mut Context<Self>) {
566 self.move_to(line_start(&self.content, self.cursor_offset()), cx);
567 }
568
569 fn end(&mut self, _: &End, _: &mut Window, cx: &mut Context<Self>) {
570 self.move_to(line_end(&self.content, self.cursor_offset()), cx);
571 }
572
573 fn select_home(&mut self, _: &SelectHome, _: &mut Window, cx: &mut Context<Self>) {
574 self.select_to(line_start(&self.content, self.cursor_offset()), cx);
575 }
576
577 fn select_end(&mut self, _: &SelectEnd, _: &mut Window, cx: &mut Context<Self>) {
578 self.select_to(line_end(&self.content, self.cursor_offset()), cx);
579 }
580
581 fn up(&mut self, _: &Up, _: &mut Window, cx: &mut Context<Self>) {
582 self.vertical(-1, false, cx);
583 }
584
585 fn down(&mut self, _: &Down, _: &mut Window, cx: &mut Context<Self>) {
586 self.vertical(1, false, cx);
587 }
588
589 fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
590 self.vertical(-1, true, cx);
591 }
592
593 fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
594 self.vertical(1, true, cx);
595 }
596
597 fn vertical(&mut self, rows: i32, extend: bool, cx: &mut Context<Self>) {
606 if self.last_layout.is_empty() {
607 return;
608 }
609 let line_height = self.line_height();
610 let Some(at) = position_for_offset(&self.last_layout, self.cursor_offset(), line_height)
611 else {
612 return;
613 };
614 let goal = self.goal_x.unwrap_or(at.x);
615 let target = at.y + line_height * rows as f32;
616 let offset = if target < px(0.) {
619 0
620 } else {
621 offset_for_position(&self.last_layout, gpui::point(goal, target), line_height)
622 };
623
624 if extend {
625 self.select_to(offset, cx);
626 } else {
627 self.move_to(offset, cx);
628 }
629 self.goal_x = Some(goal);
631 }
632
633 fn insert_newline(&mut self, _: &InsertNewline, window: &mut Window, cx: &mut Context<Self>) {
636 if self.shape.is_multiline() {
637 self.replace_text_in_range(None, "\n", window, cx);
638 }
639 }
640
641 fn word_left(&mut self, _: &WordLeft, _: &mut Window, cx: &mut Context<Self>) {
642 self.move_to(
643 previous_word_boundary(&self.content, self.cursor_offset()),
644 cx,
645 );
646 }
647
648 fn word_right(&mut self, _: &WordRight, _: &mut Window, cx: &mut Context<Self>) {
649 self.move_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
650 }
651
652 fn select_word_left(&mut self, _: &SelectWordLeft, _: &mut Window, cx: &mut Context<Self>) {
653 self.select_to(
654 previous_word_boundary(&self.content, self.cursor_offset()),
655 cx,
656 );
657 }
658
659 fn select_word_right(&mut self, _: &SelectWordRight, _: &mut Window, cx: &mut Context<Self>) {
660 self.select_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
661 }
662
663 fn delete_word_left(
667 &mut self,
668 _: &DeleteWordLeft,
669 window: &mut Window,
670 cx: &mut Context<Self>,
671 ) {
672 if self.selected_range.is_empty() {
673 self.select_to(
674 previous_word_boundary(&self.content, self.cursor_offset()),
675 cx,
676 );
677 }
678 self.replace_text_in_range(None, "", window, cx)
679 }
680
681 fn delete_word_right(
682 &mut self,
683 _: &DeleteWordRight,
684 window: &mut Window,
685 cx: &mut Context<Self>,
686 ) {
687 if self.selected_range.is_empty() {
688 self.select_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
689 }
690 self.replace_text_in_range(None, "", window, cx)
691 }
692
693 fn delete_to_line_start(
694 &mut self,
695 _: &DeleteToLineStart,
696 window: &mut Window,
697 cx: &mut Context<Self>,
698 ) {
699 if self.selected_range.is_empty() {
700 self.select_to(line_start(&self.content, self.cursor_offset()), cx);
701 }
702 self.replace_text_in_range(None, "", window, cx)
703 }
704
705 fn delete_to_line_end(
706 &mut self,
707 _: &DeleteToLineEnd,
708 window: &mut Window,
709 cx: &mut Context<Self>,
710 ) {
711 if self.selected_range.is_empty() {
712 self.select_to(line_end(&self.content, self.cursor_offset()), cx);
713 }
714 self.replace_text_in_range(None, "", window, cx)
715 }
716
717 fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
718 if self.selected_range.is_empty() {
719 let prev = self.previous_boundary(self.cursor_offset());
720 if self.cursor_offset() == prev {
721 return;
722 }
723 self.select_to(prev, cx)
724 }
725 self.replace_text_in_range(None, "", window, cx)
726 }
727
728 fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
729 if self.selected_range.is_empty() {
730 let next = self.next_boundary(self.cursor_offset());
731 if self.cursor_offset() == next {
732 return;
733 }
734 self.select_to(next, cx)
735 }
736 self.replace_text_in_range(None, "", window, cx)
737 }
738
739 fn on_mouse_down(
740 &mut self,
741 event: &MouseDownEvent,
742 _window: &mut Window,
743 cx: &mut Context<Self>,
744 ) {
745 self.is_selecting = true;
746 let offset = self.index_for_mouse_position(event.position, self.line_height());
747 if event.modifiers.shift {
748 self.select_to(offset, cx);
749 } else {
750 self.move_to(offset, cx)
751 }
752 }
753
754 fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut Window, _: &mut Context<Self>) {
755 self.is_selecting = false;
756 }
757
758 fn on_scroll_wheel(
762 &mut self,
763 event: &gpui::ScrollWheelEvent,
764 _window: &mut Window,
765 cx: &mut Context<Self>,
766 ) {
767 let delta = event.delta.pixel_delta(self.line_height());
768 self.scroll.x = (self.scroll.x - delta.x).max(px(0.));
769 self.scroll.y = (self.scroll.y - delta.y).max(px(0.));
770 cx.notify();
771 }
772
773 fn drag_to(&mut self, position: Point<Pixels>, line_height: Pixels, cx: &mut Context<Self>) {
777 if !self.is_selecting {
778 return;
779 }
780 let offset = self.index_for_mouse_position(position, line_height);
781 if offset != self.cursor_offset() {
784 self.select_to(offset, cx);
785 }
786 }
787
788 fn show_character_palette(
789 &mut self,
790 _: &ShowCharacterPalette,
791 window: &mut Window,
792 _: &mut Context<Self>,
793 ) {
794 window.show_character_palette();
795 }
796
797 fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
798 if let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) {
799 self.replace_text_in_range(None, &normalize(&text, self.shape), window, cx);
800 }
801 }
802
803 fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
804 if !self.selected_range.is_empty() {
805 cx.write_to_clipboard(ClipboardItem::new_string(
806 self.content[self.selected_range.clone()].to_string(),
807 ));
808 }
809 }
810
811 fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
812 if !self.selected_range.is_empty() {
813 cx.write_to_clipboard(ClipboardItem::new_string(
814 self.content[self.selected_range.clone()].to_string(),
815 ));
816 self.replace_text_in_range(None, "", window, cx)
817 }
818 }
819
820 fn snapshot(&self) -> Snapshot {
821 Snapshot {
822 content: self.content.clone(),
823 selection: self.selected_range.clone(),
824 reversed: self.selection_reversed,
825 }
826 }
827
828 fn restore(&mut self, point: Snapshot, cx: &mut Context<Self>) {
829 self.content = point.content;
830 self.selected_range = point.selection;
831 self.selection_reversed = point.reversed;
832 self.marked_range = None;
833 self.last_edit = None;
835 self.caret_moved();
836 cx.emit(FieldEvent::Changed);
837 cx.notify();
838 }
839
840 fn push_undo(&mut self, kind: EditKind, at: usize) {
846 let before = (!joins_group(self.last_edit, kind, at)).then(|| self.snapshot());
847 self.history.record(before);
848 }
849
850 fn undo(&mut self, _: &Undo, _: &mut Window, cx: &mut Context<Self>) {
851 let current = self.snapshot();
852 if let Some(point) = self.history.undo(|| current) {
853 self.restore(point, cx);
854 }
855 }
856
857 fn redo(&mut self, _: &Redo, _: &mut Window, cx: &mut Context<Self>) {
858 let current = self.snapshot();
859 if let Some(point) = self.history.redo(|| current) {
860 self.restore(point, cx);
861 }
862 }
863
864 fn move_to(&mut self, offset: usize, cx: &mut Context<Self>) {
865 self.selected_range = offset..offset;
866 self.goal_x = None;
867 self.caret_moved();
868 cx.emit(FieldEvent::Moved);
869 cx.notify()
870 }
871
872 fn cursor_offset(&self) -> usize {
873 if self.selection_reversed {
874 self.selected_range.start
875 } else {
876 self.selected_range.end
877 }
878 }
879
880 fn line_height(&self) -> Pixels {
891 px(self.metrics.line_height())
892 }
893
894 fn text_origin(&self) -> Option<Point<Pixels>> {
897 Some(self.last_bounds?.origin - self.scroll)
898 }
899
900 fn index_for_mouse_position(&self, position: Point<Pixels>, line_height: Pixels) -> usize {
901 if self.content.is_empty() || self.last_layout.is_empty() {
902 return 0;
903 }
904 let Some(origin) = self.text_origin() else {
905 return 0;
906 };
907 offset_for_position(&self.last_layout, position - origin, line_height)
908 }
909
910 fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
911 self.goal_x = None;
912 self.caret_moved();
913 if self.selection_reversed {
914 self.selected_range.start = offset
915 } else {
916 self.selected_range.end = offset
917 };
918 if self.selected_range.end < self.selected_range.start {
919 self.selection_reversed = !self.selection_reversed;
920 self.selected_range = self.selected_range.end..self.selected_range.start;
921 }
922 cx.emit(FieldEvent::Moved);
923 cx.notify()
924 }
925
926 fn offset_to_utf16(&self, offset: usize) -> usize {
927 offset_to_utf16(&self.content, offset)
928 }
929
930 fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
931 range_to_utf16(&self.content, range.clone())
932 }
933
934 fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
935 range_from_utf16(&self.content, range_utf16.clone())
936 }
937
938 fn previous_boundary(&self, offset: usize) -> usize {
939 previous_boundary(&self.content, offset)
940 }
941
942 fn next_boundary(&self, offset: usize) -> usize {
943 next_boundary(&self.content, offset)
944 }
945}
946
947pub fn offset_from_utf16(text: &str, offset: usize) -> usize {
954 let mut utf8_offset = 0;
955 let mut utf16_count = 0;
956 for ch in text.chars() {
957 if utf16_count >= offset {
958 break;
959 }
960 utf16_count += ch.len_utf16();
961 utf8_offset += ch.len_utf8();
962 }
963 utf8_offset
964}
965
966pub fn offset_to_utf16(text: &str, offset: usize) -> usize {
968 let mut utf16_offset = 0;
969 let mut utf8_count = 0;
970 for ch in text.chars() {
971 if utf8_count >= offset {
972 break;
973 }
974 utf8_count += ch.len_utf8();
975 utf16_offset += ch.len_utf16();
976 }
977 utf16_offset
978}
979
980pub fn range_from_utf16(text: &str, range: Range<usize>) -> Range<usize> {
982 offset_from_utf16(text, range.start)..offset_from_utf16(text, range.end)
983}
984
985pub fn range_to_utf16(text: &str, range: Range<usize>) -> Range<usize> {
987 offset_to_utf16(text, range.start)..offset_to_utf16(text, range.end)
988}
989
990pub fn composition_selection(
992 text: &str,
993 start: usize,
994 selection: Option<Range<usize>>,
995) -> Range<usize> {
996 let range = selection
997 .map(|range| range_from_utf16(text, range))
998 .unwrap_or(text.len()..text.len());
999 start + range.start..start + range.end
1000}
1001
1002pub fn previous_boundary(text: &str, offset: usize) -> usize {
1006 text.grapheme_indices(true)
1007 .rev()
1008 .find_map(|(idx, _)| (idx < offset).then_some(idx))
1009 .unwrap_or(0)
1010}
1011
1012pub fn next_boundary(text: &str, offset: usize) -> usize {
1014 text.grapheme_indices(true)
1015 .find_map(|(idx, _)| (idx > offset).then_some(idx))
1016 .unwrap_or(text.len())
1017}
1018
1019pub fn joins_group(last: Option<(EditKind, usize)>, kind: EditKind, at: usize) -> bool {
1027 last.is_some_and(|(last_kind, offset)| last_kind == kind && at == offset)
1028}
1029
1030pub fn normalize(text: &str, shape: Shape) -> String {
1040 let text = text.replace("\r\n", "\n").replace('\r', "\n");
1041 if shape.is_multiline() {
1042 text
1043 } else {
1044 text.replace('\n', " ")
1045 }
1046}
1047
1048pub fn line_start(text: &str, offset: usize) -> usize {
1057 text[..offset].rfind('\n').map_or(0, |at| at + 1)
1058}
1059
1060pub fn line_end(text: &str, offset: usize) -> usize {
1062 text[offset..]
1063 .find('\n')
1064 .map_or(text.len(), |at| offset + at)
1065}
1066
1067fn is_word(segment: &str) -> bool {
1070 segment.chars().any(char::is_alphanumeric)
1071}
1072
1073pub fn previous_word_boundary(text: &str, offset: usize) -> usize {
1081 text.split_word_bound_indices()
1082 .filter(|(start, _)| *start < offset)
1083 .rfind(|(_, segment)| is_word(segment))
1084 .map(|(start, _)| start)
1085 .unwrap_or(0)
1086}
1087
1088pub fn next_word_boundary(text: &str, offset: usize) -> usize {
1090 text.split_word_bound_indices()
1091 .filter(|(start, segment)| start + segment.len() > offset)
1092 .find(|(_, segment)| is_word(segment))
1093 .map(|(start, segment)| start + segment.len())
1094 .unwrap_or(text.len())
1095}
1096
1097fn display_text(field: &TextField) -> (SharedString, bool) {
1100 if field.content.is_empty() {
1101 (field.placeholder.clone(), true)
1102 } else {
1103 (field.content.clone(), false)
1104 }
1105}
1106
1107fn lines_from(lines: &[WrappedLine]) -> impl Iterator<Item = (usize, &WrappedLine)> {
1116 lines.iter().scan(0usize, |start, line| {
1117 let at = *start;
1118 *start = at + line.len() + 1;
1119 Some((at, line))
1120 })
1121}
1122
1123fn rows(lines: &[WrappedLine], line_height: Pixels) -> Vec<(Range<usize>, Pixels)> {
1128 let mut out = Vec::new();
1129 let mut top = px(0.);
1130 for (start, line) in lines_from(lines) {
1131 let mut row_start = start;
1132 for boundary in line.wrap_boundaries() {
1133 let at = start + line.runs()[boundary.run_ix].glyphs[boundary.glyph_ix].index;
1134 out.push((row_start..at, top));
1135 row_start = at;
1136 top += line_height;
1137 }
1138 out.push((row_start..start + line.len(), top));
1139 top += line_height;
1140 }
1141 out
1142}
1143
1144fn position_for_offset(
1146 lines: &[WrappedLine],
1147 offset: usize,
1148 line_height: Pixels,
1149) -> Option<Point<Pixels>> {
1150 let mut top = px(0.);
1151 for (start, line) in lines_from(lines) {
1152 if offset <= start + line.len() {
1153 let local = line.position_for_index(offset.saturating_sub(start), line_height)?;
1154 return Some(gpui::point(local.x, local.y + top));
1155 }
1156 top += line.size(line_height).height;
1157 }
1158 None
1159}
1160
1161fn offset_for_position(
1163 lines: &[WrappedLine],
1164 position: Point<Pixels>,
1165 line_height: Pixels,
1166) -> usize {
1167 let mut top = px(0.);
1168 let mut last = 0;
1169 for (start, line) in lines_from(lines) {
1170 let height = line.size(line_height).height;
1171 last = start + line.len();
1172 if position.y < top + height {
1173 let local = gpui::point(position.x, position.y - top);
1174 let (Ok(index) | Err(index)) = line.closest_index_for_position(local, line_height);
1175 return start + index;
1176 }
1177 top += height;
1178 }
1179 last
1180}
1181
1182fn selection_rows(
1188 lines: &[WrappedLine],
1189 range: &Range<usize>,
1190 line_height: Pixels,
1191) -> Vec<Bounds<Pixels>> {
1192 rows(lines, line_height)
1193 .into_iter()
1194 .filter(|(row, _)| range.start <= row.end && range.end >= row.start)
1195 .filter_map(|(row, top)| {
1196 let left = if range.start <= row.start {
1197 px(0.)
1198 } else {
1199 position_for_offset(lines, range.start, line_height)?.x
1200 };
1201 let right = position_for_offset(lines, range.end.min(row.end), line_height)?.x;
1202 (right > left).then(|| {
1203 Bounds::from_corners(
1204 gpui::point(left, top),
1205 gpui::point(right, top + line_height),
1206 )
1207 })
1208 })
1209 .collect()
1210}
1211
1212impl EntityInputHandler for TextField {
1213 fn text_for_range(
1214 &mut self,
1215 range_utf16: Range<usize>,
1216 actual_range: &mut Option<Range<usize>>,
1217 _window: &mut Window,
1218 _cx: &mut Context<Self>,
1219 ) -> Option<String> {
1220 let range = self.range_from_utf16(&range_utf16);
1221 actual_range.replace(self.range_to_utf16(&range));
1222 Some(self.content[range].to_string())
1223 }
1224
1225 fn selected_text_range(
1226 &mut self,
1227 _ignore_disabled_input: bool,
1228 _window: &mut Window,
1229 _cx: &mut Context<Self>,
1230 ) -> Option<UTF16Selection> {
1231 Some(UTF16Selection {
1232 range: self.range_to_utf16(&self.selected_range),
1233 reversed: self.selection_reversed,
1234 })
1235 }
1236
1237 fn marked_text_range(
1238 &self,
1239 _window: &mut Window,
1240 _cx: &mut Context<Self>,
1241 ) -> Option<Range<usize>> {
1242 self.marked_range
1243 .as_ref()
1244 .map(|range| self.range_to_utf16(range))
1245 }
1246
1247 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
1248 self.marked_range = None;
1249 }
1250
1251 fn replace_text_in_range(
1252 &mut self,
1253 range_utf16: Option<Range<usize>>,
1254 new_text: &str,
1255 _: &mut Window,
1256 cx: &mut Context<Self>,
1257 ) {
1258 let range = range_utf16
1259 .as_ref()
1260 .map(|range_utf16| self.range_from_utf16(range_utf16))
1261 .or(self.marked_range.clone())
1262 .unwrap_or(self.selected_range.clone());
1263
1264 let kind = if new_text.is_empty() {
1269 EditKind::Delete
1270 } else {
1271 EditKind::Insert
1272 };
1273 self.push_undo(
1276 kind,
1277 if new_text.is_empty() {
1278 range.end
1279 } else {
1280 range.start
1281 },
1282 );
1283
1284 self.content =
1285 (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
1286 .into();
1287 self.selected_range = range.start + new_text.len()..range.start + new_text.len();
1288 self.marked_range.take();
1289 self.last_edit = Some((kind, self.selected_range.end));
1290 self.caret_moved();
1291 cx.emit(FieldEvent::Changed);
1292 cx.notify();
1293 }
1294
1295 fn replace_and_mark_text_in_range(
1296 &mut self,
1297 range_utf16: Option<Range<usize>>,
1298 new_text: &str,
1299 new_selected_range_utf16: Option<Range<usize>>,
1300 _window: &mut Window,
1301 cx: &mut Context<Self>,
1302 ) {
1303 let range = range_utf16
1304 .as_ref()
1305 .map(|range_utf16| self.range_from_utf16(range_utf16))
1306 .or(self.marked_range.clone())
1307 .unwrap_or(self.selected_range.clone());
1308
1309 self.content =
1310 (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
1311 .into();
1312 self.marked_range =
1313 (!new_text.is_empty()).then(|| range.start..range.start + new_text.len());
1314 self.selected_range =
1315 composition_selection(new_text, range.start, new_selected_range_utf16);
1316 self.selection_reversed = false;
1317
1318 self.caret_moved();
1319 cx.emit(FieldEvent::Changed);
1320 cx.notify();
1321 }
1322
1323 fn bounds_for_range(
1324 &mut self,
1325 range_utf16: Range<usize>,
1326 bounds: Bounds<Pixels>,
1327 _window: &mut Window,
1328 _cx: &mut Context<Self>,
1329 ) -> Option<Bounds<Pixels>> {
1330 let range = self.range_from_utf16(&range_utf16);
1331 self.row_bounds(bounds.origin - self.scroll, range, self.line_height())
1336 }
1337
1338 fn character_index_for_point(
1339 &mut self,
1340 point: Point<Pixels>,
1341 _window: &mut Window,
1342 _cx: &mut Context<Self>,
1343 ) -> Option<usize> {
1344 self.last_bounds?.localize(&point)?;
1345 let origin = self.text_origin()?;
1346 let offset = offset_for_position(&self.last_layout, point - origin, self.line_height());
1347 Some(self.offset_to_utf16(offset))
1348 }
1349}
1350
1351impl Focusable for TextField {
1352 fn focus_handle(&self, _: &App) -> FocusHandle {
1353 self.focus_handle.clone()
1354 }
1355}
1356
1357impl Render for TextField {
1358 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1359 if self.focus_handle.is_focused(_window) && caret_blink(cx) {
1362 if self.blink.is_none() {
1363 self.start_blink(cx);
1364 }
1365 } else {
1366 self.blink = None;
1367 self.caret_on = true;
1368 }
1369 let theme = Theme::of(cx);
1370 let mut key_context = gpui::KeyContext::default();
1371 key_context.add(KEY_CONTEXT);
1372 if self.shape.is_multiline() {
1373 key_context.add(MULTILINE_KEY_CONTEXT);
1374 }
1375 if let Some(extra) = self.key_context.clone() {
1376 key_context.add(extra);
1377 }
1378 div()
1379 .key_context(key_context)
1380 .track_focus(&self.focus_handle(cx))
1381 .cursor(CursorStyle::IBeam)
1382 .on_action(cx.listener(Self::backspace))
1383 .on_action(cx.listener(Self::delete))
1384 .on_action(cx.listener(Self::left))
1385 .on_action(cx.listener(Self::right))
1386 .on_action(cx.listener(Self::select_left))
1387 .on_action(cx.listener(Self::select_right))
1388 .on_action(cx.listener(Self::select_all))
1389 .on_action(cx.listener(Self::home))
1390 .on_action(cx.listener(Self::end))
1391 .on_action(cx.listener(Self::select_home))
1392 .on_action(cx.listener(Self::select_end))
1393 .on_action(cx.listener(Self::word_left))
1394 .on_action(cx.listener(Self::word_right))
1395 .on_action(cx.listener(Self::select_word_left))
1396 .on_action(cx.listener(Self::select_word_right))
1397 .on_action(cx.listener(Self::up))
1398 .on_action(cx.listener(Self::down))
1399 .on_action(cx.listener(Self::select_up))
1400 .on_action(cx.listener(Self::select_down))
1401 .on_action(cx.listener(Self::insert_newline))
1402 .on_action(cx.listener(Self::undo))
1403 .on_action(cx.listener(Self::redo))
1404 .on_action(cx.listener(Self::delete_word_left))
1405 .on_action(cx.listener(Self::delete_word_right))
1406 .on_action(cx.listener(Self::delete_to_line_start))
1407 .on_action(cx.listener(Self::delete_to_line_end))
1408 .on_action(cx.listener(Self::show_character_palette))
1409 .on_action(cx.listener(Self::paste))
1410 .on_action(cx.listener(Self::cut))
1411 .on_action(cx.listener(Self::copy))
1412 .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
1413 .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
1414 .on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
1415 .on_scroll_wheel(cx.listener(Self::on_scroll_wheel))
1416 .w_full()
1417 .when(self.frame, |field| {
1418 field
1419 .px(px(10.0))
1420 .py(px(7.0))
1421 .rounded(px(Theme::button_radius()))
1422 .bg(theme.input_bg)
1423 .border_1()
1424 .border_color(if self.focus_handle.is_focused(_window) {
1425 theme.ring
1426 } else {
1427 theme.border
1428 })
1429 })
1430 .text_size(px(self.metrics.size()))
1431 .font_weight(self.metrics.weight)
1432 .line_height(px(self.metrics.line_height()))
1433 .text_color(theme.text)
1434 .child(TextFieldElement { field: cx.entity() })
1435 }
1436}
1437
1438struct TextFieldElement {
1442 field: Entity<TextField>,
1443}
1444
1445struct FieldPrepaint {
1446 lines: Vec<WrappedLine>,
1447 origin: Point<Pixels>,
1449 cursor: Option<PaintQuad>,
1450 selection: Vec<PaintQuad>,
1452}
1453
1454impl IntoElement for TextFieldElement {
1455 type Element = Self;
1456
1457 fn into_element(self) -> Self::Element {
1458 self
1459 }
1460}
1461
1462impl Element for TextFieldElement {
1463 type RequestLayoutState = ();
1464 type PrepaintState = FieldPrepaint;
1465
1466 fn id(&self) -> Option<ElementId> {
1467 None
1468 }
1469
1470 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1471 None
1472 }
1473
1474 fn request_layout(
1475 &mut self,
1476 _id: Option<&GlobalElementId>,
1477 _inspector_id: Option<&gpui::InspectorElementId>,
1478 window: &mut Window,
1479 cx: &mut App,
1480 ) -> (LayoutId, ()) {
1481 let mut style = Style::default();
1482 style.size.width = relative(1.).into();
1483 let field = self.field.read(cx);
1484 let line_height = field.line_height();
1487 let shape = field.shape;
1488
1489 let (min, max) = match shape {
1490 Shape::Line => {
1491 style.size.height = line_height.into();
1492 return (window.request_layout(style, [], cx), ());
1493 }
1494 Shape::Rows(rows) => {
1495 style.size.height = (line_height * rows.max(1) as f32).into();
1496 return (window.request_layout(style, [], cx), ());
1497 }
1498 Shape::Grow { min, max } => (min.max(1), max.max(min.max(1))),
1502 };
1503
1504 let text = display_text(field).0;
1505 let id = window.request_measured_layout(style, move |known, available, window, _cx| {
1506 let text_style = window.text_style();
1507 let font_size = text_style.font_size.to_pixels(window.rem_size());
1508 let wrap_width = known.width.or(match available.width {
1513 gpui::AvailableSpace::Definite(width) => Some(width),
1514 _ => None,
1515 });
1516 let run = TextRun {
1517 len: text.len(),
1518 font: text_style.font(),
1519 color: text_style.color,
1520 background_color: None,
1521 underline: None,
1522 strikethrough: None,
1523 };
1524 let count = window
1525 .text_system()
1526 .shape_text(text.clone(), font_size, &[run], wrap_width, None)
1527 .map(|lines| {
1528 lines
1529 .iter()
1530 .map(|line| line.wrap_boundaries().len() + 1)
1531 .sum::<usize>()
1532 })
1533 .unwrap_or(1);
1534 gpui::size(
1535 wrap_width.unwrap_or(px(0.)),
1536 line_height * count.clamp(min, max) as f32,
1537 )
1538 });
1539 (id, ())
1540 }
1541
1542 fn prepaint(
1543 &mut self,
1544 _id: Option<&GlobalElementId>,
1545 _inspector_id: Option<&gpui::InspectorElementId>,
1546 bounds: Bounds<Pixels>,
1547 _request_layout: &mut Self::RequestLayoutState,
1548 window: &mut Window,
1549 cx: &mut App,
1550 ) -> FieldPrepaint {
1551 let theme = Theme::of(cx).clone();
1552 let field = self.field.read(cx);
1553 let selected_range = field.selected_range.clone();
1554 let cursor = field.cursor_offset();
1555 let shape = field.shape;
1556 let marked_range = field.marked_range.clone();
1557 let scrolled = field.scroll;
1558 let follow_caret = field.follow_caret;
1559 let style = window.text_style();
1560
1561 let (text, is_placeholder) = display_text(field);
1562 let text_color = if is_placeholder {
1563 theme.text_faint
1564 } else {
1565 style.color
1566 };
1567
1568 let run = TextRun {
1569 len: text.len(),
1570 font: style.font(),
1571 color: text_color,
1572 background_color: None,
1573 underline: None,
1574 strikethrough: None,
1575 };
1576 let runs = if let Some(marked) = marked_range.as_ref() {
1579 vec![
1580 TextRun {
1581 len: marked.start,
1582 ..run.clone()
1583 },
1584 TextRun {
1585 len: marked.end - marked.start,
1586 underline: Some(UnderlineStyle {
1587 color: Some(run.color),
1588 thickness: px(1.0),
1589 wavy: false,
1590 }),
1591 ..run.clone()
1592 },
1593 TextRun {
1594 len: text.len() - marked.end,
1595 ..run
1596 },
1597 ]
1598 .into_iter()
1599 .filter(|run| run.len > 0)
1600 .collect()
1601 } else {
1602 vec![run]
1603 };
1604
1605 let font_size = style.font_size.to_pixels(window.rem_size());
1606 let line_height = field.line_height();
1607 let wrap_width = shape.is_multiline().then_some(bounds.size.width);
1610 let lines = window
1611 .text_system()
1612 .shape_text(text, font_size, &runs, wrap_width, None)
1613 .map(|lines| lines.into_vec())
1614 .unwrap_or_default();
1615
1616 let content_height: Pixels = lines.iter().map(|l| l.size(line_height).height).sum();
1625 let content_width = lines.iter().map(|l| l.width()).fold(px(0.), Pixels::max);
1626 let max = gpui::point(
1627 (content_width - bounds.size.width).max(px(0.)),
1628 (content_height - bounds.size.height).max(px(0.)),
1629 );
1630 let mut scroll = gpui::point(
1631 scrolled.x.clamp(px(0.), max.x),
1632 scrolled.y.clamp(px(0.), max.y),
1633 );
1634 if follow_caret && let Some(at) = position_for_offset(&lines, cursor, line_height) {
1635 if at.y < scroll.y {
1636 scroll.y = at.y;
1637 } else if at.y + line_height > scroll.y + bounds.size.height {
1638 scroll.y = at.y + line_height - bounds.size.height;
1639 }
1640 if at.x < scroll.x {
1643 scroll.x = at.x;
1644 } else if at.x + CARET_WIDTH > scroll.x + bounds.size.width {
1645 scroll.x = at.x + CARET_WIDTH - bounds.size.width;
1646 }
1647 scroll.x = scroll.x.clamp(px(0.), max.x);
1648 scroll.y = scroll.y.clamp(px(0.), max.y);
1649 }
1650 self.field.update(cx, |field, _| {
1651 field.scroll = scroll;
1652 field.follow_caret = false;
1653 });
1654 let origin = bounds.origin - scroll;
1655
1656 let (selection, cursor) = if selected_range.is_empty() {
1657 let at = position_for_offset(&lines, cursor, line_height).unwrap_or_default();
1658 (
1659 Vec::new(),
1660 Some(fill(
1661 Bounds::new(
1664 origin + at + gpui::point(px(0.), (line_height - font_size) / 2.),
1665 gpui::size(CARET_WIDTH, font_size),
1666 ),
1667 theme.caret,
1668 )),
1669 )
1670 } else {
1671 (
1672 selection_rows(&lines, &selected_range, line_height)
1673 .into_iter()
1674 .map(|rect| {
1675 fill(
1676 Bounds::new(origin + rect.origin, rect.size),
1677 theme.selection,
1678 )
1679 })
1680 .collect(),
1681 None,
1682 )
1683 };
1684
1685 FieldPrepaint {
1686 lines,
1687 origin,
1688 cursor,
1689 selection,
1690 }
1691 }
1692
1693 fn paint(
1694 &mut self,
1695 _id: Option<&GlobalElementId>,
1696 _inspector_id: Option<&gpui::InspectorElementId>,
1697 bounds: Bounds<Pixels>,
1698 _request_layout: &mut Self::RequestLayoutState,
1699 prepaint: &mut Self::PrepaintState,
1700 window: &mut Window,
1701 cx: &mut App,
1702 ) {
1703 let focus_handle = self.field.read(cx).focus_handle.clone();
1704 let caret_on = self.field.read(cx).caret_on;
1705 window.handle_input(
1706 &focus_handle,
1707 ElementInputHandler::new(bounds, self.field.clone()),
1708 cx,
1709 );
1710 let line_height = self.field.read(cx).line_height();
1711 let dragged = self.field.clone();
1721 window.on_mouse_event(move |event: &MouseMoveEvent, phase, _window, cx| {
1722 if phase != DispatchPhase::Bubble || !event.dragging() {
1727 return;
1728 }
1729 dragged.update(cx, |field, cx| {
1730 field.drag_to(event.position, line_height, cx);
1731 });
1732 });
1733 let lines = std::mem::take(&mut prepaint.lines);
1734 let selection = std::mem::take(&mut prepaint.selection);
1735 let cursor = prepaint.cursor.take();
1736 let origin = prepaint.origin;
1737
1738 window.with_content_mask(Some(gpui::ContentMask::new(bounds)), |window| {
1741 for selection in selection {
1742 window.paint_quad(selection);
1743 }
1744
1745 let mut top = origin;
1746 for line in &lines {
1747 line.paint(top, line_height, gpui::TextAlign::Left, None, window, cx)
1748 .ok();
1749 top.y += line.size(line_height).height;
1750 }
1751
1752 if focus_handle.is_focused(window)
1755 && caret_on
1756 && let Some(cursor) = cursor
1757 {
1758 window.paint_quad(cursor);
1759 }
1760 });
1761
1762 self.field.update(cx, |field, _| {
1763 field.last_layout = lines;
1764 field.last_bounds = Some(bounds);
1765 });
1766 }
1767}