1use std::{ops::Range, time::Duration};
21
22use gpui::{
23 App, Bounds, ClipboardItem, Context, CursorStyle, ElementId, ElementInputHandler, Entity,
24 EntityInputHandler, EventEmitter, FocusHandle, Focusable, Global, GlobalElementId, KeyBinding,
25 LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point,
26 SharedString, Style, Task, TextRun, UTF16Selection, UnderlineStyle, Window, WrappedLine,
27 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) {
150 let ctx = Some(KEY_CONTEXT);
151 cx.bind_keys([
152 KeyBinding::new("backspace", Backspace, ctx),
154 KeyBinding::new("delete", Delete, ctx),
155 KeyBinding::new("left", Left, ctx),
156 KeyBinding::new("right", Right, ctx),
157 KeyBinding::new("shift-left", SelectLeft, ctx),
158 KeyBinding::new("shift-right", SelectRight, ctx),
159 KeyBinding::new("home", Home, ctx),
160 KeyBinding::new("end", End, ctx),
161 KeyBinding::new("shift-home", SelectHome, ctx),
162 KeyBinding::new("shift-end", SelectEnd, ctx),
163 ]);
164
165 let area = Some(MULTILINE_KEY_CONTEXT);
168 cx.bind_keys([
169 KeyBinding::new("enter", InsertNewline, area),
170 KeyBinding::new("up", Up, area),
171 KeyBinding::new("down", Down, area),
172 KeyBinding::new("shift-up", SelectUp, area),
173 KeyBinding::new("shift-down", SelectDown, area),
174 ]);
175
176 #[cfg(target_os = "macos")]
177 cx.bind_keys([
178 KeyBinding::new("cmd-a", SelectAll, ctx),
179 KeyBinding::new("cmd-c", Copy, ctx),
180 KeyBinding::new("cmd-x", Cut, ctx),
181 KeyBinding::new("cmd-v", Paste, ctx),
182 KeyBinding::new("cmd-z", Undo, ctx),
183 KeyBinding::new("cmd-shift-z", Redo, ctx),
184 KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, ctx),
185 KeyBinding::new("cmd-left", Home, ctx),
187 KeyBinding::new("cmd-right", End, ctx),
188 KeyBinding::new("cmd-shift-left", SelectHome, ctx),
189 KeyBinding::new("cmd-shift-right", SelectEnd, ctx),
190 KeyBinding::new("alt-left", WordLeft, ctx),
191 KeyBinding::new("alt-right", WordRight, ctx),
192 KeyBinding::new("alt-shift-left", SelectWordLeft, ctx),
193 KeyBinding::new("alt-shift-right", SelectWordRight, ctx),
194 KeyBinding::new("cmd-backspace", DeleteToLineStart, ctx),
195 KeyBinding::new("alt-backspace", DeleteWordLeft, ctx),
196 KeyBinding::new("alt-delete", DeleteWordRight, ctx),
197 KeyBinding::new("ctrl-a", Home, ctx),
199 KeyBinding::new("ctrl-e", End, ctx),
200 KeyBinding::new("ctrl-b", Left, ctx),
201 KeyBinding::new("ctrl-f", Right, ctx),
202 KeyBinding::new("ctrl-h", Backspace, ctx),
203 KeyBinding::new("ctrl-d", Delete, ctx),
204 KeyBinding::new("ctrl-k", DeleteToLineEnd, ctx),
205 ]);
206
207 #[cfg(target_os = "macos")]
210 cx.bind_keys([
211 KeyBinding::new("ctrl-n", Down, area),
212 KeyBinding::new("ctrl-p", Up, area),
213 ]);
214
215 #[cfg(not(target_os = "macos"))]
216 cx.bind_keys([
217 KeyBinding::new("ctrl-a", SelectAll, ctx),
218 KeyBinding::new("ctrl-c", Copy, ctx),
219 KeyBinding::new("ctrl-x", Cut, ctx),
220 KeyBinding::new("ctrl-v", Paste, ctx),
221 KeyBinding::new("ctrl-left", WordLeft, ctx),
223 KeyBinding::new("ctrl-right", WordRight, ctx),
224 KeyBinding::new("ctrl-shift-left", SelectWordLeft, ctx),
225 KeyBinding::new("ctrl-shift-right", SelectWordRight, ctx),
226 KeyBinding::new("ctrl-backspace", DeleteWordLeft, ctx),
227 KeyBinding::new("ctrl-delete", DeleteWordRight, ctx),
228 KeyBinding::new("ctrl-z", Undo, ctx),
229 KeyBinding::new("ctrl-shift-z", Redo, ctx),
230 ]);
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
240pub enum Shape {
241 #[default]
244 Line,
245 Rows(usize),
247 Grow { min: usize, max: usize },
250}
251
252impl Shape {
253 fn is_multiline(self) -> bool {
256 !matches!(self, Self::Line)
257 }
258}
259
260#[derive(Clone)]
266struct Snapshot {
267 content: SharedString,
268 selection: Range<usize>,
269 reversed: bool,
270}
271
272#[derive(Clone, Copy, PartialEq, Eq)]
275pub enum EditKind {
276 Insert,
277 Delete,
278}
279
280pub struct TextField {
283 focus_handle: FocusHandle,
284 content: SharedString,
285 placeholder: SharedString,
286 shape: Shape,
287 selected_range: Range<usize>,
288 selection_reversed: bool,
289 marked_range: Option<Range<usize>>,
291 last_layout: Vec<WrappedLine>,
294 last_bounds: Option<Bounds<Pixels>>,
295 is_selecting: bool,
296 goal_x: Option<Pixels>,
303 scroll: Point<Pixels>,
311 undo: std::collections::VecDeque<Snapshot>,
315 redo: Vec<Snapshot>,
319 undo_limit: usize,
320 last_edit: Option<(EditKind, usize)>,
324 key_context: Option<SharedString>,
327 frame: bool,
331 metrics: Metrics,
334 caret_on: bool,
336 blink: Option<Task<()>>,
338 follow_caret: bool,
345}
346
347impl EventEmitter<FieldEvent> for TextField {}
348
349impl TextField {
350 pub fn new(cx: &mut Context<Self>) -> Self {
351 Self {
352 focus_handle: cx.focus_handle().tab_stop(true),
355 frame: true,
356 content: "".into(),
357 placeholder: "".into(),
358 shape: Shape::Line,
359 selected_range: 0..0,
360 selection_reversed: false,
361 marked_range: None,
362 last_layout: Vec::new(),
363 last_bounds: None,
364 is_selecting: false,
365 goal_x: None,
366 scroll: Point::default(),
367 undo: std::collections::VecDeque::new(),
368 redo: Vec::new(),
369 undo_limit: DEFAULT_UNDO_LIMIT,
370 last_edit: None,
371 key_context: None,
372 metrics: TextStyle::Body.into(),
373 caret_on: true,
374 blink: None,
375 follow_caret: false,
376 }
377 }
378
379 pub fn with_undo_limit(mut self, limit: usize) -> Self {
384 self.undo_limit = limit;
385 self
386 }
387
388 pub fn with_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
389 self.placeholder = placeholder.into();
390 self
391 }
392
393 pub fn with_key_context(mut self, context: impl Into<SharedString>) -> Self {
419 self.key_context = Some(context.into());
420 self
421 }
422
423 pub fn with_frame(mut self, frame: bool) -> Self {
426 self.frame = frame;
427 self
428 }
429
430 pub fn with_metrics(mut self, metrics: Metrics) -> Self {
434 self.metrics = metrics;
435 self
436 }
437
438 pub fn with_shape(mut self, shape: Shape) -> Self {
439 self.shape = shape;
440 self
441 }
442
443 pub fn shape(&self) -> Shape {
444 self.shape
445 }
446
447 pub fn content(&self) -> &SharedString {
448 &self.content
449 }
450
451 pub fn set_content(&mut self, content: impl Into<SharedString>, cx: &mut Context<Self>) {
453 self.content = normalize(&content.into(), self.shape).into();
454 self.undo.clear();
457 self.redo.clear();
458 self.last_edit = None;
459 let end = self.content.len();
460 self.selected_range = end..end;
461 self.marked_range = None;
462 cx.emit(FieldEvent::Changed);
463 cx.notify();
464 }
465
466 pub fn clear(&mut self, cx: &mut Context<Self>) {
467 self.set_content("", cx);
468 }
469
470 pub fn set_placeholder(
474 &mut self,
475 placeholder: impl Into<SharedString>,
476 cx: &mut Context<Self>,
477 ) {
478 self.placeholder = placeholder.into();
479 cx.notify();
480 }
481
482 fn caret_moved(&mut self) {
486 self.follow_caret = true;
487 self.blink = None;
488 }
489
490 fn start_blink(&mut self, cx: &mut Context<Self>) {
492 self.caret_on = true;
493 self.blink = Some(cx.spawn(async move |field, cx| {
494 loop {
495 cx.background_executor().timer(BLINK).await;
496 let flipped = field.update(cx, |field, cx| {
497 field.caret_on = !field.caret_on;
498 cx.notify();
499 });
500 if flipped.is_err() {
501 break;
502 }
503 }
504 }));
505 }
506
507 pub fn cursor(&self) -> usize {
513 self.cursor_offset()
514 }
515
516 pub fn offset_bounds(&self, offset: usize, window: &Window) -> Option<Bounds<Pixels>> {
527 self.row_bounds(self.text_origin()?, offset..offset, window.line_height())
528 }
529
530 fn row_bounds(
537 &self,
538 origin: Point<Pixels>,
539 range: Range<usize>,
540 line_height: Pixels,
541 ) -> Option<Bounds<Pixels>> {
542 let start = position_for_offset(&self.last_layout, range.start, line_height)?;
543 let end = position_for_offset(&self.last_layout, range.end, line_height)?;
544 Some(Bounds::from_corners(
545 origin + start,
546 origin + gpui::point(end.x, end.y + line_height),
547 ))
548 }
549
550 fn left(&mut self, _: &Left, _: &mut Window, cx: &mut Context<Self>) {
551 if self.selected_range.is_empty() {
552 self.move_to(self.previous_boundary(self.cursor_offset()), cx);
553 } else {
554 self.move_to(self.selected_range.start, cx)
555 }
556 }
557
558 fn right(&mut self, _: &Right, _: &mut Window, cx: &mut Context<Self>) {
559 if self.selected_range.is_empty() {
560 self.move_to(self.next_boundary(self.selected_range.end), cx);
561 } else {
562 self.move_to(self.selected_range.end, cx)
563 }
564 }
565
566 fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
567 self.select_to(self.previous_boundary(self.cursor_offset()), cx);
568 }
569
570 fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
571 self.select_to(self.next_boundary(self.cursor_offset()), cx);
572 }
573
574 fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
575 self.move_to(0, cx);
576 self.select_to(self.content.len(), cx)
577 }
578
579 fn home(&mut self, _: &Home, _: &mut Window, cx: &mut Context<Self>) {
580 self.move_to(line_start(&self.content, self.cursor_offset()), cx);
581 }
582
583 fn end(&mut self, _: &End, _: &mut Window, cx: &mut Context<Self>) {
584 self.move_to(line_end(&self.content, self.cursor_offset()), cx);
585 }
586
587 fn select_home(&mut self, _: &SelectHome, _: &mut Window, cx: &mut Context<Self>) {
588 self.select_to(line_start(&self.content, self.cursor_offset()), cx);
589 }
590
591 fn select_end(&mut self, _: &SelectEnd, _: &mut Window, cx: &mut Context<Self>) {
592 self.select_to(line_end(&self.content, self.cursor_offset()), cx);
593 }
594
595 fn up(&mut self, _: &Up, window: &mut Window, cx: &mut Context<Self>) {
596 self.vertical(-1, false, window, cx);
597 }
598
599 fn down(&mut self, _: &Down, window: &mut Window, cx: &mut Context<Self>) {
600 self.vertical(1, false, window, cx);
601 }
602
603 fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
604 self.vertical(-1, true, window, cx);
605 }
606
607 fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
608 self.vertical(1, true, window, cx);
609 }
610
611 fn vertical(&mut self, rows: i32, extend: bool, window: &mut Window, cx: &mut Context<Self>) {
620 if self.last_layout.is_empty() {
621 return;
622 }
623 let line_height = window.line_height();
624 let Some(at) = position_for_offset(&self.last_layout, self.cursor_offset(), line_height)
625 else {
626 return;
627 };
628 let goal = self.goal_x.unwrap_or(at.x);
629 let target = at.y + line_height * rows as f32;
630 let offset = if target < px(0.) {
633 0
634 } else {
635 offset_for_position(&self.last_layout, gpui::point(goal, target), line_height)
636 };
637
638 if extend {
639 self.select_to(offset, cx);
640 } else {
641 self.move_to(offset, cx);
642 }
643 self.goal_x = Some(goal);
645 }
646
647 fn insert_newline(&mut self, _: &InsertNewline, window: &mut Window, cx: &mut Context<Self>) {
650 if self.shape.is_multiline() {
651 self.replace_text_in_range(None, "\n", window, cx);
652 }
653 }
654
655 fn word_left(&mut self, _: &WordLeft, _: &mut Window, cx: &mut Context<Self>) {
656 self.move_to(
657 previous_word_boundary(&self.content, self.cursor_offset()),
658 cx,
659 );
660 }
661
662 fn word_right(&mut self, _: &WordRight, _: &mut Window, cx: &mut Context<Self>) {
663 self.move_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
664 }
665
666 fn select_word_left(&mut self, _: &SelectWordLeft, _: &mut Window, cx: &mut Context<Self>) {
667 self.select_to(
668 previous_word_boundary(&self.content, self.cursor_offset()),
669 cx,
670 );
671 }
672
673 fn select_word_right(&mut self, _: &SelectWordRight, _: &mut Window, cx: &mut Context<Self>) {
674 self.select_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
675 }
676
677 fn delete_word_left(
681 &mut self,
682 _: &DeleteWordLeft,
683 window: &mut Window,
684 cx: &mut Context<Self>,
685 ) {
686 if self.selected_range.is_empty() {
687 self.select_to(
688 previous_word_boundary(&self.content, self.cursor_offset()),
689 cx,
690 );
691 }
692 self.replace_text_in_range(None, "", window, cx)
693 }
694
695 fn delete_word_right(
696 &mut self,
697 _: &DeleteWordRight,
698 window: &mut Window,
699 cx: &mut Context<Self>,
700 ) {
701 if self.selected_range.is_empty() {
702 self.select_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
703 }
704 self.replace_text_in_range(None, "", window, cx)
705 }
706
707 fn delete_to_line_start(
708 &mut self,
709 _: &DeleteToLineStart,
710 window: &mut Window,
711 cx: &mut Context<Self>,
712 ) {
713 if self.selected_range.is_empty() {
714 self.select_to(line_start(&self.content, self.cursor_offset()), cx);
715 }
716 self.replace_text_in_range(None, "", window, cx)
717 }
718
719 fn delete_to_line_end(
720 &mut self,
721 _: &DeleteToLineEnd,
722 window: &mut Window,
723 cx: &mut Context<Self>,
724 ) {
725 if self.selected_range.is_empty() {
726 self.select_to(line_end(&self.content, self.cursor_offset()), cx);
727 }
728 self.replace_text_in_range(None, "", window, cx)
729 }
730
731 fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
732 if self.selected_range.is_empty() {
733 let prev = self.previous_boundary(self.cursor_offset());
734 if self.cursor_offset() == prev {
735 return;
736 }
737 self.select_to(prev, cx)
738 }
739 self.replace_text_in_range(None, "", window, cx)
740 }
741
742 fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
743 if self.selected_range.is_empty() {
744 let next = self.next_boundary(self.cursor_offset());
745 if self.cursor_offset() == next {
746 return;
747 }
748 self.select_to(next, cx)
749 }
750 self.replace_text_in_range(None, "", window, cx)
751 }
752
753 fn on_mouse_down(
754 &mut self,
755 event: &MouseDownEvent,
756 window: &mut Window,
757 cx: &mut Context<Self>,
758 ) {
759 self.is_selecting = true;
760 let offset = self.index_for_mouse_position(event.position, window.line_height());
761 if event.modifiers.shift {
762 self.select_to(offset, cx);
763 } else {
764 self.move_to(offset, cx)
765 }
766 }
767
768 fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut Window, _: &mut Context<Self>) {
769 self.is_selecting = false;
770 }
771
772 fn on_scroll_wheel(
776 &mut self,
777 event: &gpui::ScrollWheelEvent,
778 window: &mut Window,
779 cx: &mut Context<Self>,
780 ) {
781 let delta = event.delta.pixel_delta(window.line_height());
782 self.scroll.x = (self.scroll.x - delta.x).max(px(0.));
783 self.scroll.y = (self.scroll.y - delta.y).max(px(0.));
784 cx.notify();
785 }
786
787 fn on_mouse_move(
788 &mut self,
789 event: &MouseMoveEvent,
790 window: &mut Window,
791 cx: &mut Context<Self>,
792 ) {
793 if self.is_selecting {
794 let offset = self.index_for_mouse_position(event.position, window.line_height());
795 self.select_to(offset, cx);
796 }
797 }
798
799 fn show_character_palette(
800 &mut self,
801 _: &ShowCharacterPalette,
802 window: &mut Window,
803 _: &mut Context<Self>,
804 ) {
805 window.show_character_palette();
806 }
807
808 fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
809 if let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) {
810 self.replace_text_in_range(None, &normalize(&text, self.shape), window, cx);
811 }
812 }
813
814 fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
815 if !self.selected_range.is_empty() {
816 cx.write_to_clipboard(ClipboardItem::new_string(
817 self.content[self.selected_range.clone()].to_string(),
818 ));
819 }
820 }
821
822 fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
823 if !self.selected_range.is_empty() {
824 cx.write_to_clipboard(ClipboardItem::new_string(
825 self.content[self.selected_range.clone()].to_string(),
826 ));
827 self.replace_text_in_range(None, "", window, cx)
828 }
829 }
830
831 fn snapshot(&self) -> Snapshot {
832 Snapshot {
833 content: self.content.clone(),
834 selection: self.selected_range.clone(),
835 reversed: self.selection_reversed,
836 }
837 }
838
839 fn restore(&mut self, point: Snapshot, cx: &mut Context<Self>) {
840 self.content = point.content;
841 self.selected_range = point.selection;
842 self.selection_reversed = point.reversed;
843 self.marked_range = None;
844 self.last_edit = None;
846 self.caret_moved();
847 cx.emit(FieldEvent::Changed);
848 cx.notify();
849 }
850
851 fn push_undo(&mut self, kind: EditKind, at: usize) {
857 if !joins_group(self.last_edit, kind, at) {
858 self.undo.push_back(self.snapshot());
859 while self.undo.len() > self.undo_limit {
860 self.undo.pop_front();
861 }
862 }
863 self.redo.clear();
864 }
865
866 fn undo(&mut self, _: &Undo, _: &mut Window, cx: &mut Context<Self>) {
867 let Some(point) = self.undo.pop_back() else {
868 return;
869 };
870 self.redo.push(self.snapshot());
871 self.restore(point, cx);
872 }
873
874 fn redo(&mut self, _: &Redo, _: &mut Window, cx: &mut Context<Self>) {
875 let Some(point) = self.redo.pop() else {
876 return;
877 };
878 self.undo.push_back(self.snapshot());
879 self.restore(point, cx);
880 }
881
882 fn move_to(&mut self, offset: usize, cx: &mut Context<Self>) {
883 self.selected_range = offset..offset;
884 self.goal_x = None;
885 self.caret_moved();
886 cx.emit(FieldEvent::Moved);
887 cx.notify()
888 }
889
890 fn cursor_offset(&self) -> usize {
891 if self.selection_reversed {
892 self.selected_range.start
893 } else {
894 self.selected_range.end
895 }
896 }
897
898 fn text_origin(&self) -> Option<Point<Pixels>> {
901 Some(self.last_bounds?.origin - self.scroll)
902 }
903
904 fn index_for_mouse_position(&self, position: Point<Pixels>, line_height: Pixels) -> usize {
905 if self.content.is_empty() || self.last_layout.is_empty() {
906 return 0;
907 }
908 let Some(origin) = self.text_origin() else {
909 return 0;
910 };
911 offset_for_position(&self.last_layout, position - origin, line_height)
912 }
913
914 fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
915 self.goal_x = None;
916 self.caret_moved();
917 if self.selection_reversed {
918 self.selected_range.start = offset
919 } else {
920 self.selected_range.end = offset
921 };
922 if self.selected_range.end < self.selected_range.start {
923 self.selection_reversed = !self.selection_reversed;
924 self.selected_range = self.selected_range.end..self.selected_range.start;
925 }
926 cx.emit(FieldEvent::Moved);
927 cx.notify()
928 }
929
930 fn offset_from_utf16(&self, offset: usize) -> usize {
931 offset_from_utf16(&self.content, offset)
932 }
933
934 fn offset_to_utf16(&self, offset: usize) -> usize {
935 offset_to_utf16(&self.content, offset)
936 }
937
938 fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
939 self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
940 }
941
942 fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
943 self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
944 }
945
946 fn previous_boundary(&self, offset: usize) -> usize {
947 previous_boundary(&self.content, offset)
948 }
949
950 fn next_boundary(&self, offset: usize) -> usize {
951 next_boundary(&self.content, offset)
952 }
953}
954
955pub fn offset_from_utf16(text: &str, offset: usize) -> usize {
962 let mut utf8_offset = 0;
963 let mut utf16_count = 0;
964 for ch in text.chars() {
965 if utf16_count >= offset {
966 break;
967 }
968 utf16_count += ch.len_utf16();
969 utf8_offset += ch.len_utf8();
970 }
971 utf8_offset
972}
973
974pub fn offset_to_utf16(text: &str, offset: usize) -> usize {
976 let mut utf16_offset = 0;
977 let mut utf8_count = 0;
978 for ch in text.chars() {
979 if utf8_count >= offset {
980 break;
981 }
982 utf8_count += ch.len_utf8();
983 utf16_offset += ch.len_utf16();
984 }
985 utf16_offset
986}
987
988pub fn previous_boundary(text: &str, offset: usize) -> usize {
992 text.grapheme_indices(true)
993 .rev()
994 .find_map(|(idx, _)| (idx < offset).then_some(idx))
995 .unwrap_or(0)
996}
997
998pub fn next_boundary(text: &str, offset: usize) -> usize {
1000 text.grapheme_indices(true)
1001 .find_map(|(idx, _)| (idx > offset).then_some(idx))
1002 .unwrap_or(text.len())
1003}
1004
1005pub fn joins_group(last: Option<(EditKind, usize)>, kind: EditKind, at: usize) -> bool {
1013 last.is_some_and(|(last_kind, offset)| last_kind == kind && at == offset)
1014}
1015
1016pub fn normalize(text: &str, shape: Shape) -> String {
1026 let text = text.replace("\r\n", "\n").replace('\r', "\n");
1027 if shape.is_multiline() {
1028 text
1029 } else {
1030 text.replace('\n', " ")
1031 }
1032}
1033
1034pub fn line_start(text: &str, offset: usize) -> usize {
1043 text[..offset].rfind('\n').map_or(0, |at| at + 1)
1044}
1045
1046pub fn line_end(text: &str, offset: usize) -> usize {
1048 text[offset..]
1049 .find('\n')
1050 .map_or(text.len(), |at| offset + at)
1051}
1052
1053fn is_word(segment: &str) -> bool {
1056 segment.chars().any(char::is_alphanumeric)
1057}
1058
1059pub fn previous_word_boundary(text: &str, offset: usize) -> usize {
1067 text.split_word_bound_indices()
1068 .filter(|(start, _)| *start < offset)
1069 .rfind(|(_, segment)| is_word(segment))
1070 .map(|(start, _)| start)
1071 .unwrap_or(0)
1072}
1073
1074pub fn next_word_boundary(text: &str, offset: usize) -> usize {
1076 text.split_word_bound_indices()
1077 .filter(|(start, segment)| start + segment.len() > offset)
1078 .find(|(_, segment)| is_word(segment))
1079 .map(|(start, segment)| start + segment.len())
1080 .unwrap_or(text.len())
1081}
1082
1083fn display_text(field: &TextField) -> (SharedString, bool) {
1086 if field.content.is_empty() {
1087 (field.placeholder.clone(), true)
1088 } else {
1089 (field.content.clone(), false)
1090 }
1091}
1092
1093fn lines_from(lines: &[WrappedLine]) -> impl Iterator<Item = (usize, &WrappedLine)> {
1102 lines.iter().scan(0usize, |start, line| {
1103 let at = *start;
1104 *start = at + line.len() + 1;
1105 Some((at, line))
1106 })
1107}
1108
1109fn rows(lines: &[WrappedLine], line_height: Pixels) -> Vec<(Range<usize>, Pixels)> {
1114 let mut out = Vec::new();
1115 let mut top = px(0.);
1116 for (start, line) in lines_from(lines) {
1117 let mut row_start = start;
1118 for boundary in line.wrap_boundaries() {
1119 let at = start + line.runs()[boundary.run_ix].glyphs[boundary.glyph_ix].index;
1120 out.push((row_start..at, top));
1121 row_start = at;
1122 top += line_height;
1123 }
1124 out.push((row_start..start + line.len(), top));
1125 top += line_height;
1126 }
1127 out
1128}
1129
1130fn position_for_offset(
1132 lines: &[WrappedLine],
1133 offset: usize,
1134 line_height: Pixels,
1135) -> Option<Point<Pixels>> {
1136 let mut top = px(0.);
1137 for (start, line) in lines_from(lines) {
1138 if offset <= start + line.len() {
1139 let local = line.position_for_index(offset.saturating_sub(start), line_height)?;
1140 return Some(gpui::point(local.x, local.y + top));
1141 }
1142 top += line.size(line_height).height;
1143 }
1144 None
1145}
1146
1147fn offset_for_position(
1149 lines: &[WrappedLine],
1150 position: Point<Pixels>,
1151 line_height: Pixels,
1152) -> usize {
1153 let mut top = px(0.);
1154 let mut last = 0;
1155 for (start, line) in lines_from(lines) {
1156 let height = line.size(line_height).height;
1157 last = start + line.len();
1158 if position.y < top + height {
1159 let local = gpui::point(position.x, position.y - top);
1160 let (Ok(index) | Err(index)) = line.closest_index_for_position(local, line_height);
1161 return start + index;
1162 }
1163 top += height;
1164 }
1165 last
1166}
1167
1168fn selection_rows(
1174 lines: &[WrappedLine],
1175 range: &Range<usize>,
1176 line_height: Pixels,
1177) -> Vec<Bounds<Pixels>> {
1178 rows(lines, line_height)
1179 .into_iter()
1180 .filter(|(row, _)| range.start <= row.end && range.end >= row.start)
1181 .filter_map(|(row, top)| {
1182 let left = if range.start <= row.start {
1183 px(0.)
1184 } else {
1185 position_for_offset(lines, range.start, line_height)?.x
1186 };
1187 let right = position_for_offset(lines, range.end.min(row.end), line_height)?.x;
1188 (right > left).then(|| {
1189 Bounds::from_corners(
1190 gpui::point(left, top),
1191 gpui::point(right, top + line_height),
1192 )
1193 })
1194 })
1195 .collect()
1196}
1197
1198impl EntityInputHandler for TextField {
1199 fn text_for_range(
1200 &mut self,
1201 range_utf16: Range<usize>,
1202 actual_range: &mut Option<Range<usize>>,
1203 _window: &mut Window,
1204 _cx: &mut Context<Self>,
1205 ) -> Option<String> {
1206 let range = self.range_from_utf16(&range_utf16);
1207 actual_range.replace(self.range_to_utf16(&range));
1208 Some(self.content[range].to_string())
1209 }
1210
1211 fn selected_text_range(
1212 &mut self,
1213 _ignore_disabled_input: bool,
1214 _window: &mut Window,
1215 _cx: &mut Context<Self>,
1216 ) -> Option<UTF16Selection> {
1217 Some(UTF16Selection {
1218 range: self.range_to_utf16(&self.selected_range),
1219 reversed: self.selection_reversed,
1220 })
1221 }
1222
1223 fn marked_text_range(
1224 &self,
1225 _window: &mut Window,
1226 _cx: &mut Context<Self>,
1227 ) -> Option<Range<usize>> {
1228 self.marked_range
1229 .as_ref()
1230 .map(|range| self.range_to_utf16(range))
1231 }
1232
1233 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
1234 self.marked_range = None;
1235 }
1236
1237 fn replace_text_in_range(
1238 &mut self,
1239 range_utf16: Option<Range<usize>>,
1240 new_text: &str,
1241 _: &mut Window,
1242 cx: &mut Context<Self>,
1243 ) {
1244 let range = range_utf16
1245 .as_ref()
1246 .map(|range_utf16| self.range_from_utf16(range_utf16))
1247 .or(self.marked_range.clone())
1248 .unwrap_or(self.selected_range.clone());
1249
1250 let kind = if new_text.is_empty() {
1255 EditKind::Delete
1256 } else {
1257 EditKind::Insert
1258 };
1259 self.push_undo(
1262 kind,
1263 if new_text.is_empty() {
1264 range.end
1265 } else {
1266 range.start
1267 },
1268 );
1269
1270 self.content =
1271 (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
1272 .into();
1273 self.selected_range = range.start + new_text.len()..range.start + new_text.len();
1274 self.marked_range.take();
1275 self.last_edit = Some((kind, self.selected_range.end));
1276 self.caret_moved();
1277 cx.emit(FieldEvent::Changed);
1278 cx.notify();
1279 }
1280
1281 fn replace_and_mark_text_in_range(
1282 &mut self,
1283 range_utf16: Option<Range<usize>>,
1284 new_text: &str,
1285 new_selected_range_utf16: Option<Range<usize>>,
1286 _window: &mut Window,
1287 cx: &mut Context<Self>,
1288 ) {
1289 let range = range_utf16
1290 .as_ref()
1291 .map(|range_utf16| self.range_from_utf16(range_utf16))
1292 .or(self.marked_range.clone())
1293 .unwrap_or(self.selected_range.clone());
1294
1295 self.content =
1296 (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
1297 .into();
1298 self.marked_range =
1299 (!new_text.is_empty()).then(|| range.start..range.start + new_text.len());
1300 self.selected_range = new_selected_range_utf16
1301 .as_ref()
1302 .map(|range_utf16| self.range_from_utf16(range_utf16))
1303 .map(|new_range| new_range.start + range.start..new_range.end + range.end)
1304 .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
1305
1306 self.caret_moved();
1307 cx.emit(FieldEvent::Changed);
1308 cx.notify();
1309 }
1310
1311 fn bounds_for_range(
1312 &mut self,
1313 range_utf16: Range<usize>,
1314 bounds: Bounds<Pixels>,
1315 window: &mut Window,
1316 _cx: &mut Context<Self>,
1317 ) -> Option<Bounds<Pixels>> {
1318 let range = self.range_from_utf16(&range_utf16);
1319 self.row_bounds(bounds.origin - self.scroll, range, window.line_height())
1324 }
1325
1326 fn character_index_for_point(
1327 &mut self,
1328 point: Point<Pixels>,
1329 window: &mut Window,
1330 _cx: &mut Context<Self>,
1331 ) -> Option<usize> {
1332 self.last_bounds?.localize(&point)?;
1333 let origin = self.text_origin()?;
1334 let offset = offset_for_position(&self.last_layout, point - origin, window.line_height());
1335 Some(self.offset_to_utf16(offset))
1336 }
1337}
1338
1339impl Focusable for TextField {
1340 fn focus_handle(&self, _: &App) -> FocusHandle {
1341 self.focus_handle.clone()
1342 }
1343}
1344
1345impl Render for TextField {
1346 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1347 if self.focus_handle.is_focused(_window) && caret_blink(cx) {
1350 if self.blink.is_none() {
1351 self.start_blink(cx);
1352 }
1353 } else {
1354 self.blink = None;
1355 self.caret_on = true;
1356 }
1357 let theme = Theme::of(cx);
1358 let mut key_context = gpui::KeyContext::default();
1359 key_context.add(KEY_CONTEXT);
1360 if self.shape.is_multiline() {
1361 key_context.add(MULTILINE_KEY_CONTEXT);
1362 }
1363 if let Some(extra) = self.key_context.clone() {
1364 key_context.add(extra);
1365 }
1366 div()
1367 .key_context(key_context)
1368 .track_focus(&self.focus_handle(cx))
1369 .cursor(CursorStyle::IBeam)
1370 .on_action(cx.listener(Self::backspace))
1371 .on_action(cx.listener(Self::delete))
1372 .on_action(cx.listener(Self::left))
1373 .on_action(cx.listener(Self::right))
1374 .on_action(cx.listener(Self::select_left))
1375 .on_action(cx.listener(Self::select_right))
1376 .on_action(cx.listener(Self::select_all))
1377 .on_action(cx.listener(Self::home))
1378 .on_action(cx.listener(Self::end))
1379 .on_action(cx.listener(Self::select_home))
1380 .on_action(cx.listener(Self::select_end))
1381 .on_action(cx.listener(Self::word_left))
1382 .on_action(cx.listener(Self::word_right))
1383 .on_action(cx.listener(Self::select_word_left))
1384 .on_action(cx.listener(Self::select_word_right))
1385 .on_action(cx.listener(Self::up))
1386 .on_action(cx.listener(Self::down))
1387 .on_action(cx.listener(Self::select_up))
1388 .on_action(cx.listener(Self::select_down))
1389 .on_action(cx.listener(Self::insert_newline))
1390 .on_action(cx.listener(Self::undo))
1391 .on_action(cx.listener(Self::redo))
1392 .on_action(cx.listener(Self::delete_word_left))
1393 .on_action(cx.listener(Self::delete_word_right))
1394 .on_action(cx.listener(Self::delete_to_line_start))
1395 .on_action(cx.listener(Self::delete_to_line_end))
1396 .on_action(cx.listener(Self::show_character_palette))
1397 .on_action(cx.listener(Self::paste))
1398 .on_action(cx.listener(Self::cut))
1399 .on_action(cx.listener(Self::copy))
1400 .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
1401 .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
1402 .on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
1403 .on_mouse_move(cx.listener(Self::on_mouse_move))
1404 .on_scroll_wheel(cx.listener(Self::on_scroll_wheel))
1405 .w_full()
1406 .when(self.frame, |field| {
1407 field
1408 .px(px(10.0))
1409 .py(px(7.0))
1410 .rounded(px(Theme::button_radius()))
1411 .bg(theme.input_bg)
1412 .border_1()
1413 .border_color(if self.focus_handle.is_focused(_window) {
1414 theme.ring
1415 } else {
1416 theme.border
1417 })
1418 })
1419 .text_size(px(self.metrics.size()))
1420 .font_weight(self.metrics.weight)
1421 .line_height(px(self.metrics.line_height()))
1422 .text_color(theme.text)
1423 .child(TextFieldElement { field: cx.entity() })
1424 }
1425}
1426
1427struct TextFieldElement {
1431 field: Entity<TextField>,
1432}
1433
1434struct FieldPrepaint {
1435 lines: Vec<WrappedLine>,
1436 origin: Point<Pixels>,
1438 cursor: Option<PaintQuad>,
1439 selection: Vec<PaintQuad>,
1441}
1442
1443impl IntoElement for TextFieldElement {
1444 type Element = Self;
1445
1446 fn into_element(self) -> Self::Element {
1447 self
1448 }
1449}
1450
1451impl Element for TextFieldElement {
1452 type RequestLayoutState = ();
1453 type PrepaintState = FieldPrepaint;
1454
1455 fn id(&self) -> Option<ElementId> {
1456 None
1457 }
1458
1459 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1460 None
1461 }
1462
1463 fn request_layout(
1464 &mut self,
1465 _id: Option<&GlobalElementId>,
1466 _inspector_id: Option<&gpui::InspectorElementId>,
1467 window: &mut Window,
1468 cx: &mut App,
1469 ) -> (LayoutId, ()) {
1470 let mut style = Style::default();
1471 style.size.width = relative(1.).into();
1472 let line_height = window.line_height();
1473 let field = self.field.read(cx);
1474 let shape = field.shape;
1475
1476 let (min, max) = match shape {
1477 Shape::Line => {
1478 style.size.height = line_height.into();
1479 return (window.request_layout(style, [], cx), ());
1480 }
1481 Shape::Rows(rows) => {
1482 style.size.height = (line_height * rows.max(1) as f32).into();
1483 return (window.request_layout(style, [], cx), ());
1484 }
1485 Shape::Grow { min, max } => (min.max(1), max.max(min.max(1))),
1489 };
1490
1491 let text = display_text(field).0;
1492 let id = window.request_measured_layout(style, move |known, available, window, _cx| {
1493 let text_style = window.text_style();
1494 let font_size = text_style.font_size.to_pixels(window.rem_size());
1495 let wrap_width = known.width.or(match available.width {
1500 gpui::AvailableSpace::Definite(width) => Some(width),
1501 _ => None,
1502 });
1503 let run = TextRun {
1504 len: text.len(),
1505 font: text_style.font(),
1506 color: text_style.color,
1507 background_color: None,
1508 underline: None,
1509 strikethrough: None,
1510 };
1511 let count = window
1512 .text_system()
1513 .shape_text(text.clone(), font_size, &[run], wrap_width, None)
1514 .map(|lines| {
1515 lines
1516 .iter()
1517 .map(|line| line.wrap_boundaries().len() + 1)
1518 .sum::<usize>()
1519 })
1520 .unwrap_or(1);
1521 gpui::size(
1522 wrap_width.unwrap_or(px(0.)),
1523 line_height * count.clamp(min, max) as f32,
1524 )
1525 });
1526 (id, ())
1527 }
1528
1529 fn prepaint(
1530 &mut self,
1531 _id: Option<&GlobalElementId>,
1532 _inspector_id: Option<&gpui::InspectorElementId>,
1533 bounds: Bounds<Pixels>,
1534 _request_layout: &mut Self::RequestLayoutState,
1535 window: &mut Window,
1536 cx: &mut App,
1537 ) -> FieldPrepaint {
1538 let theme = Theme::of(cx).clone();
1539 let field = self.field.read(cx);
1540 let selected_range = field.selected_range.clone();
1541 let cursor = field.cursor_offset();
1542 let shape = field.shape;
1543 let marked_range = field.marked_range.clone();
1544 let scrolled = field.scroll;
1545 let follow_caret = field.follow_caret;
1546 let style = window.text_style();
1547
1548 let (text, is_placeholder) = display_text(field);
1549 let text_color = if is_placeholder {
1550 theme.text_faint
1551 } else {
1552 style.color
1553 };
1554
1555 let run = TextRun {
1556 len: text.len(),
1557 font: style.font(),
1558 color: text_color,
1559 background_color: None,
1560 underline: None,
1561 strikethrough: None,
1562 };
1563 let runs = if let Some(marked) = marked_range.as_ref() {
1566 vec![
1567 TextRun {
1568 len: marked.start,
1569 ..run.clone()
1570 },
1571 TextRun {
1572 len: marked.end - marked.start,
1573 underline: Some(UnderlineStyle {
1574 color: Some(run.color),
1575 thickness: px(1.0),
1576 wavy: false,
1577 }),
1578 ..run.clone()
1579 },
1580 TextRun {
1581 len: text.len() - marked.end,
1582 ..run
1583 },
1584 ]
1585 .into_iter()
1586 .filter(|run| run.len > 0)
1587 .collect()
1588 } else {
1589 vec![run]
1590 };
1591
1592 let font_size = style.font_size.to_pixels(window.rem_size());
1593 let line_height = window.line_height();
1594 let wrap_width = shape.is_multiline().then_some(bounds.size.width);
1597 let lines = window
1598 .text_system()
1599 .shape_text(text, font_size, &runs, wrap_width, None)
1600 .map(|lines| lines.into_vec())
1601 .unwrap_or_default();
1602
1603 let content_height: Pixels = lines.iter().map(|l| l.size(line_height).height).sum();
1612 let content_width = lines.iter().map(|l| l.width()).fold(px(0.), Pixels::max);
1613 let max = gpui::point(
1614 (content_width - bounds.size.width).max(px(0.)),
1615 (content_height - bounds.size.height).max(px(0.)),
1616 );
1617 let mut scroll = gpui::point(
1618 scrolled.x.clamp(px(0.), max.x),
1619 scrolled.y.clamp(px(0.), max.y),
1620 );
1621 if follow_caret && let Some(at) = position_for_offset(&lines, cursor, line_height) {
1622 if at.y < scroll.y {
1623 scroll.y = at.y;
1624 } else if at.y + line_height > scroll.y + bounds.size.height {
1625 scroll.y = at.y + line_height - bounds.size.height;
1626 }
1627 if at.x < scroll.x {
1630 scroll.x = at.x;
1631 } else if at.x + CARET_WIDTH > scroll.x + bounds.size.width {
1632 scroll.x = at.x + CARET_WIDTH - bounds.size.width;
1633 }
1634 scroll.x = scroll.x.clamp(px(0.), max.x);
1635 scroll.y = scroll.y.clamp(px(0.), max.y);
1636 }
1637 self.field.update(cx, |field, _| {
1638 field.scroll = scroll;
1639 field.follow_caret = false;
1640 });
1641 let origin = bounds.origin - scroll;
1642
1643 let (selection, cursor) = if selected_range.is_empty() {
1644 let at = position_for_offset(&lines, cursor, line_height).unwrap_or_default();
1645 (
1646 Vec::new(),
1647 Some(fill(
1648 Bounds::new(
1651 origin + at + gpui::point(px(0.), (line_height - font_size) / 2.),
1652 gpui::size(CARET_WIDTH, font_size),
1653 ),
1654 theme.caret,
1655 )),
1656 )
1657 } else {
1658 (
1659 selection_rows(&lines, &selected_range, line_height)
1660 .into_iter()
1661 .map(|rect| {
1662 fill(
1663 Bounds::new(origin + rect.origin, rect.size),
1664 theme.selection,
1665 )
1666 })
1667 .collect(),
1668 None,
1669 )
1670 };
1671
1672 FieldPrepaint {
1673 lines,
1674 origin,
1675 cursor,
1676 selection,
1677 }
1678 }
1679
1680 fn paint(
1681 &mut self,
1682 _id: Option<&GlobalElementId>,
1683 _inspector_id: Option<&gpui::InspectorElementId>,
1684 bounds: Bounds<Pixels>,
1685 _request_layout: &mut Self::RequestLayoutState,
1686 prepaint: &mut Self::PrepaintState,
1687 window: &mut Window,
1688 cx: &mut App,
1689 ) {
1690 let focus_handle = self.field.read(cx).focus_handle.clone();
1691 let caret_on = self.field.read(cx).caret_on;
1692 window.handle_input(
1693 &focus_handle,
1694 ElementInputHandler::new(bounds, self.field.clone()),
1695 cx,
1696 );
1697 let line_height = window.line_height();
1698 let lines = std::mem::take(&mut prepaint.lines);
1699 let selection = std::mem::take(&mut prepaint.selection);
1700 let cursor = prepaint.cursor.take();
1701 let origin = prepaint.origin;
1702
1703 window.with_content_mask(Some(gpui::ContentMask { bounds }), |window| {
1706 for selection in selection {
1707 window.paint_quad(selection);
1708 }
1709
1710 let mut top = origin;
1711 for line in &lines {
1712 line.paint(top, line_height, gpui::TextAlign::Left, None, window, cx)
1713 .ok();
1714 top.y += line.size(line_height).height;
1715 }
1716
1717 if focus_handle.is_focused(window)
1720 && caret_on
1721 && let Some(cursor) = cursor
1722 {
1723 window.paint_quad(cursor);
1724 }
1725 });
1726
1727 self.field.update(cx, |field, _| {
1728 field.last_layout = lines;
1729 field.last_bounds = Some(bounds);
1730 });
1731 }
1732}