1use std::ops::Range;
21
22use gpui::{
23 App, Bounds, ClipboardItem, Context, CursorStyle, ElementId, ElementInputHandler, Entity,
24 EntityInputHandler, FocusHandle, Focusable, GlobalElementId, KeyBinding, LayoutId, MouseButton,
25 MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, SharedString, Style,
26 TextRun, UTF16Selection, UnderlineStyle, Window, WrappedLine, actions, div, fill, prelude::*,
27 px, relative,
28};
29use unicode_segmentation::UnicodeSegmentation as _;
30
31use theme::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
78const CARET_WIDTH: Pixels = px(2.);
81
82pub const KEY_CONTEXT: &str = "TextField";
84
85pub const MULTILINE_KEY_CONTEXT: &str = "TextArea";
94
95pub fn init(cx: &mut App) {
113 let ctx = Some(KEY_CONTEXT);
114 cx.bind_keys([
115 KeyBinding::new("backspace", Backspace, ctx),
117 KeyBinding::new("delete", Delete, ctx),
118 KeyBinding::new("left", Left, ctx),
119 KeyBinding::new("right", Right, ctx),
120 KeyBinding::new("shift-left", SelectLeft, ctx),
121 KeyBinding::new("shift-right", SelectRight, ctx),
122 KeyBinding::new("home", Home, ctx),
123 KeyBinding::new("end", End, ctx),
124 KeyBinding::new("shift-home", SelectHome, ctx),
125 KeyBinding::new("shift-end", SelectEnd, ctx),
126 ]);
127
128 let area = Some(MULTILINE_KEY_CONTEXT);
131 cx.bind_keys([
132 KeyBinding::new("enter", InsertNewline, area),
133 KeyBinding::new("up", Up, area),
134 KeyBinding::new("down", Down, area),
135 KeyBinding::new("shift-up", SelectUp, area),
136 KeyBinding::new("shift-down", SelectDown, area),
137 ]);
138
139 #[cfg(target_os = "macos")]
140 cx.bind_keys([
141 KeyBinding::new("cmd-a", SelectAll, ctx),
142 KeyBinding::new("cmd-c", Copy, ctx),
143 KeyBinding::new("cmd-x", Cut, ctx),
144 KeyBinding::new("cmd-v", Paste, ctx),
145 KeyBinding::new("cmd-z", Undo, ctx),
146 KeyBinding::new("cmd-shift-z", Redo, ctx),
147 KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, ctx),
148 KeyBinding::new("cmd-left", Home, ctx),
150 KeyBinding::new("cmd-right", End, ctx),
151 KeyBinding::new("cmd-shift-left", SelectHome, ctx),
152 KeyBinding::new("cmd-shift-right", SelectEnd, ctx),
153 KeyBinding::new("alt-left", WordLeft, ctx),
154 KeyBinding::new("alt-right", WordRight, ctx),
155 KeyBinding::new("alt-shift-left", SelectWordLeft, ctx),
156 KeyBinding::new("alt-shift-right", SelectWordRight, ctx),
157 KeyBinding::new("cmd-backspace", DeleteToLineStart, ctx),
158 KeyBinding::new("alt-backspace", DeleteWordLeft, ctx),
159 KeyBinding::new("alt-delete", DeleteWordRight, ctx),
160 KeyBinding::new("ctrl-a", Home, ctx),
162 KeyBinding::new("ctrl-e", End, ctx),
163 KeyBinding::new("ctrl-b", Left, ctx),
164 KeyBinding::new("ctrl-f", Right, ctx),
165 KeyBinding::new("ctrl-h", Backspace, ctx),
166 KeyBinding::new("ctrl-d", Delete, ctx),
167 KeyBinding::new("ctrl-k", DeleteToLineEnd, ctx),
168 ]);
169
170 #[cfg(target_os = "macos")]
173 cx.bind_keys([
174 KeyBinding::new("ctrl-n", Down, area),
175 KeyBinding::new("ctrl-p", Up, area),
176 ]);
177
178 #[cfg(not(target_os = "macos"))]
179 cx.bind_keys([
180 KeyBinding::new("ctrl-a", SelectAll, ctx),
181 KeyBinding::new("ctrl-c", Copy, ctx),
182 KeyBinding::new("ctrl-x", Cut, ctx),
183 KeyBinding::new("ctrl-v", Paste, ctx),
184 KeyBinding::new("ctrl-left", WordLeft, ctx),
186 KeyBinding::new("ctrl-right", WordRight, ctx),
187 KeyBinding::new("ctrl-shift-left", SelectWordLeft, ctx),
188 KeyBinding::new("ctrl-shift-right", SelectWordRight, ctx),
189 KeyBinding::new("ctrl-backspace", DeleteWordLeft, ctx),
190 KeyBinding::new("ctrl-delete", DeleteWordRight, ctx),
191 KeyBinding::new("ctrl-z", Undo, ctx),
192 KeyBinding::new("ctrl-shift-z", Redo, ctx),
193 ]);
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
203pub enum Shape {
204 #[default]
207 Line,
208 Rows(usize),
210 Grow { min: usize, max: usize },
213}
214
215impl Shape {
216 fn is_multiline(self) -> bool {
219 !matches!(self, Self::Line)
220 }
221}
222
223#[derive(Clone)]
229struct Snapshot {
230 content: SharedString,
231 selection: Range<usize>,
232 reversed: bool,
233}
234
235#[derive(Clone, Copy, PartialEq, Eq)]
238pub enum EditKind {
239 Insert,
240 Delete,
241}
242
243pub struct TextField {
246 focus_handle: FocusHandle,
247 content: SharedString,
248 placeholder: SharedString,
249 shape: Shape,
250 selected_range: Range<usize>,
251 selection_reversed: bool,
252 marked_range: Option<Range<usize>>,
254 last_layout: Vec<WrappedLine>,
257 last_bounds: Option<Bounds<Pixels>>,
258 is_selecting: bool,
259 goal_x: Option<Pixels>,
266 scroll: Point<Pixels>,
274 undo: std::collections::VecDeque<Snapshot>,
278 redo: Vec<Snapshot>,
282 undo_limit: usize,
283 last_edit: Option<(EditKind, usize)>,
287 key_context: Option<SharedString>,
290 follow_caret: bool,
297}
298
299impl TextField {
300 pub fn new(cx: &mut Context<Self>) -> Self {
301 Self {
302 focus_handle: cx.focus_handle().tab_stop(true),
305 content: "".into(),
306 placeholder: "".into(),
307 shape: Shape::Line,
308 selected_range: 0..0,
309 selection_reversed: false,
310 marked_range: None,
311 last_layout: Vec::new(),
312 last_bounds: None,
313 is_selecting: false,
314 goal_x: None,
315 scroll: Point::default(),
316 undo: std::collections::VecDeque::new(),
317 redo: Vec::new(),
318 undo_limit: DEFAULT_UNDO_LIMIT,
319 last_edit: None,
320 key_context: None,
321 follow_caret: false,
322 }
323 }
324
325 pub fn with_undo_limit(mut self, limit: usize) -> Self {
330 self.undo_limit = limit;
331 self
332 }
333
334 pub fn with_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
335 self.placeholder = placeholder.into();
336 self
337 }
338
339 pub fn with_key_context(mut self, context: impl Into<SharedString>) -> Self {
365 self.key_context = Some(context.into());
366 self
367 }
368
369 pub fn with_shape(mut self, shape: Shape) -> Self {
370 self.shape = shape;
371 self
372 }
373
374 pub fn shape(&self) -> Shape {
375 self.shape
376 }
377
378 pub fn content(&self) -> &SharedString {
379 &self.content
380 }
381
382 pub fn set_content(&mut self, content: impl Into<SharedString>, cx: &mut Context<Self>) {
384 self.content = normalize(&content.into(), self.shape).into();
385 self.undo.clear();
388 self.redo.clear();
389 self.last_edit = None;
390 let end = self.content.len();
391 self.selected_range = end..end;
392 self.marked_range = None;
393 cx.notify();
394 }
395
396 pub fn clear(&mut self, cx: &mut Context<Self>) {
397 self.set_content("", cx);
398 }
399
400 pub fn set_placeholder(
404 &mut self,
405 placeholder: impl Into<SharedString>,
406 cx: &mut Context<Self>,
407 ) {
408 self.placeholder = placeholder.into();
409 cx.notify();
410 }
411
412 pub fn cursor(&self) -> usize {
418 self.cursor_offset()
419 }
420
421 pub fn offset_bounds(&self, offset: usize, window: &Window) -> Option<Bounds<Pixels>> {
432 self.row_bounds(self.text_origin()?, offset..offset, window.line_height())
433 }
434
435 fn row_bounds(
442 &self,
443 origin: Point<Pixels>,
444 range: Range<usize>,
445 line_height: Pixels,
446 ) -> Option<Bounds<Pixels>> {
447 let start = position_for_offset(&self.last_layout, range.start, line_height)?;
448 let end = position_for_offset(&self.last_layout, range.end, line_height)?;
449 Some(Bounds::from_corners(
450 origin + start,
451 origin + gpui::point(end.x, end.y + line_height),
452 ))
453 }
454
455 fn left(&mut self, _: &Left, _: &mut Window, cx: &mut Context<Self>) {
456 if self.selected_range.is_empty() {
457 self.move_to(self.previous_boundary(self.cursor_offset()), cx);
458 } else {
459 self.move_to(self.selected_range.start, cx)
460 }
461 }
462
463 fn right(&mut self, _: &Right, _: &mut Window, cx: &mut Context<Self>) {
464 if self.selected_range.is_empty() {
465 self.move_to(self.next_boundary(self.selected_range.end), cx);
466 } else {
467 self.move_to(self.selected_range.end, cx)
468 }
469 }
470
471 fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
472 self.select_to(self.previous_boundary(self.cursor_offset()), cx);
473 }
474
475 fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
476 self.select_to(self.next_boundary(self.cursor_offset()), cx);
477 }
478
479 fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
480 self.move_to(0, cx);
481 self.select_to(self.content.len(), cx)
482 }
483
484 fn home(&mut self, _: &Home, _: &mut Window, cx: &mut Context<Self>) {
485 self.move_to(line_start(&self.content, self.cursor_offset()), cx);
486 }
487
488 fn end(&mut self, _: &End, _: &mut Window, cx: &mut Context<Self>) {
489 self.move_to(line_end(&self.content, self.cursor_offset()), cx);
490 }
491
492 fn select_home(&mut self, _: &SelectHome, _: &mut Window, cx: &mut Context<Self>) {
493 self.select_to(line_start(&self.content, self.cursor_offset()), cx);
494 }
495
496 fn select_end(&mut self, _: &SelectEnd, _: &mut Window, cx: &mut Context<Self>) {
497 self.select_to(line_end(&self.content, self.cursor_offset()), cx);
498 }
499
500 fn up(&mut self, _: &Up, window: &mut Window, cx: &mut Context<Self>) {
501 self.vertical(-1, false, window, cx);
502 }
503
504 fn down(&mut self, _: &Down, window: &mut Window, cx: &mut Context<Self>) {
505 self.vertical(1, false, window, cx);
506 }
507
508 fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
509 self.vertical(-1, true, window, cx);
510 }
511
512 fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
513 self.vertical(1, true, window, cx);
514 }
515
516 fn vertical(&mut self, rows: i32, extend: bool, window: &mut Window, cx: &mut Context<Self>) {
525 if self.last_layout.is_empty() {
526 return;
527 }
528 let line_height = window.line_height();
529 let Some(at) = position_for_offset(&self.last_layout, self.cursor_offset(), line_height)
530 else {
531 return;
532 };
533 let goal = self.goal_x.unwrap_or(at.x);
534 let target = at.y + line_height * rows as f32;
535 let offset = if target < px(0.) {
538 0
539 } else {
540 offset_for_position(&self.last_layout, gpui::point(goal, target), line_height)
541 };
542
543 if extend {
544 self.select_to(offset, cx);
545 } else {
546 self.move_to(offset, cx);
547 }
548 self.goal_x = Some(goal);
550 }
551
552 fn insert_newline(&mut self, _: &InsertNewline, window: &mut Window, cx: &mut Context<Self>) {
555 if self.shape.is_multiline() {
556 self.replace_text_in_range(None, "\n", window, cx);
557 }
558 }
559
560 fn word_left(&mut self, _: &WordLeft, _: &mut Window, cx: &mut Context<Self>) {
561 self.move_to(
562 previous_word_boundary(&self.content, self.cursor_offset()),
563 cx,
564 );
565 }
566
567 fn word_right(&mut self, _: &WordRight, _: &mut Window, cx: &mut Context<Self>) {
568 self.move_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
569 }
570
571 fn select_word_left(&mut self, _: &SelectWordLeft, _: &mut Window, cx: &mut Context<Self>) {
572 self.select_to(
573 previous_word_boundary(&self.content, self.cursor_offset()),
574 cx,
575 );
576 }
577
578 fn select_word_right(&mut self, _: &SelectWordRight, _: &mut Window, cx: &mut Context<Self>) {
579 self.select_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
580 }
581
582 fn delete_word_left(
586 &mut self,
587 _: &DeleteWordLeft,
588 window: &mut Window,
589 cx: &mut Context<Self>,
590 ) {
591 if self.selected_range.is_empty() {
592 self.select_to(
593 previous_word_boundary(&self.content, self.cursor_offset()),
594 cx,
595 );
596 }
597 self.replace_text_in_range(None, "", window, cx)
598 }
599
600 fn delete_word_right(
601 &mut self,
602 _: &DeleteWordRight,
603 window: &mut Window,
604 cx: &mut Context<Self>,
605 ) {
606 if self.selected_range.is_empty() {
607 self.select_to(next_word_boundary(&self.content, self.cursor_offset()), cx);
608 }
609 self.replace_text_in_range(None, "", window, cx)
610 }
611
612 fn delete_to_line_start(
613 &mut self,
614 _: &DeleteToLineStart,
615 window: &mut Window,
616 cx: &mut Context<Self>,
617 ) {
618 if self.selected_range.is_empty() {
619 self.select_to(line_start(&self.content, self.cursor_offset()), cx);
620 }
621 self.replace_text_in_range(None, "", window, cx)
622 }
623
624 fn delete_to_line_end(
625 &mut self,
626 _: &DeleteToLineEnd,
627 window: &mut Window,
628 cx: &mut Context<Self>,
629 ) {
630 if self.selected_range.is_empty() {
631 self.select_to(line_end(&self.content, self.cursor_offset()), cx);
632 }
633 self.replace_text_in_range(None, "", window, cx)
634 }
635
636 fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
637 if self.selected_range.is_empty() {
638 let prev = self.previous_boundary(self.cursor_offset());
639 if self.cursor_offset() == prev {
640 return;
641 }
642 self.select_to(prev, cx)
643 }
644 self.replace_text_in_range(None, "", window, cx)
645 }
646
647 fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
648 if self.selected_range.is_empty() {
649 let next = self.next_boundary(self.cursor_offset());
650 if self.cursor_offset() == next {
651 return;
652 }
653 self.select_to(next, cx)
654 }
655 self.replace_text_in_range(None, "", window, cx)
656 }
657
658 fn on_mouse_down(
659 &mut self,
660 event: &MouseDownEvent,
661 window: &mut Window,
662 cx: &mut Context<Self>,
663 ) {
664 self.is_selecting = true;
665 let offset = self.index_for_mouse_position(event.position, window.line_height());
666 if event.modifiers.shift {
667 self.select_to(offset, cx);
668 } else {
669 self.move_to(offset, cx)
670 }
671 }
672
673 fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut Window, _: &mut Context<Self>) {
674 self.is_selecting = false;
675 }
676
677 fn on_scroll_wheel(
681 &mut self,
682 event: &gpui::ScrollWheelEvent,
683 window: &mut Window,
684 cx: &mut Context<Self>,
685 ) {
686 let delta = event.delta.pixel_delta(window.line_height());
687 self.scroll.x = (self.scroll.x - delta.x).max(px(0.));
688 self.scroll.y = (self.scroll.y - delta.y).max(px(0.));
689 cx.notify();
690 }
691
692 fn on_mouse_move(
693 &mut self,
694 event: &MouseMoveEvent,
695 window: &mut Window,
696 cx: &mut Context<Self>,
697 ) {
698 if self.is_selecting {
699 let offset = self.index_for_mouse_position(event.position, window.line_height());
700 self.select_to(offset, cx);
701 }
702 }
703
704 fn show_character_palette(
705 &mut self,
706 _: &ShowCharacterPalette,
707 window: &mut Window,
708 _: &mut Context<Self>,
709 ) {
710 window.show_character_palette();
711 }
712
713 fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
714 if let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) {
715 self.replace_text_in_range(None, &normalize(&text, self.shape), window, cx);
716 }
717 }
718
719 fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
720 if !self.selected_range.is_empty() {
721 cx.write_to_clipboard(ClipboardItem::new_string(
722 self.content[self.selected_range.clone()].to_string(),
723 ));
724 }
725 }
726
727 fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
728 if !self.selected_range.is_empty() {
729 cx.write_to_clipboard(ClipboardItem::new_string(
730 self.content[self.selected_range.clone()].to_string(),
731 ));
732 self.replace_text_in_range(None, "", window, cx)
733 }
734 }
735
736 fn snapshot(&self) -> Snapshot {
737 Snapshot {
738 content: self.content.clone(),
739 selection: self.selected_range.clone(),
740 reversed: self.selection_reversed,
741 }
742 }
743
744 fn restore(&mut self, point: Snapshot, cx: &mut Context<Self>) {
745 self.content = point.content;
746 self.selected_range = point.selection;
747 self.selection_reversed = point.reversed;
748 self.marked_range = None;
749 self.last_edit = None;
751 self.follow_caret = true;
752 cx.notify();
753 }
754
755 fn push_undo(&mut self, kind: EditKind, at: usize) {
761 if !joins_group(self.last_edit, kind, at) {
762 self.undo.push_back(self.snapshot());
763 while self.undo.len() > self.undo_limit {
764 self.undo.pop_front();
765 }
766 }
767 self.redo.clear();
768 }
769
770 fn undo(&mut self, _: &Undo, _: &mut Window, cx: &mut Context<Self>) {
771 let Some(point) = self.undo.pop_back() else {
772 return;
773 };
774 self.redo.push(self.snapshot());
775 self.restore(point, cx);
776 }
777
778 fn redo(&mut self, _: &Redo, _: &mut Window, cx: &mut Context<Self>) {
779 let Some(point) = self.redo.pop() else {
780 return;
781 };
782 self.undo.push_back(self.snapshot());
783 self.restore(point, cx);
784 }
785
786 fn move_to(&mut self, offset: usize, cx: &mut Context<Self>) {
787 self.selected_range = offset..offset;
788 self.goal_x = None;
789 self.follow_caret = true;
790 cx.notify()
791 }
792
793 fn cursor_offset(&self) -> usize {
794 if self.selection_reversed {
795 self.selected_range.start
796 } else {
797 self.selected_range.end
798 }
799 }
800
801 fn text_origin(&self) -> Option<Point<Pixels>> {
804 Some(self.last_bounds?.origin - self.scroll)
805 }
806
807 fn index_for_mouse_position(&self, position: Point<Pixels>, line_height: Pixels) -> usize {
808 if self.content.is_empty() || self.last_layout.is_empty() {
809 return 0;
810 }
811 let Some(origin) = self.text_origin() else {
812 return 0;
813 };
814 offset_for_position(&self.last_layout, position - origin, line_height)
815 }
816
817 fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
818 self.goal_x = None;
819 self.follow_caret = true;
820 if self.selection_reversed {
821 self.selected_range.start = offset
822 } else {
823 self.selected_range.end = offset
824 };
825 if self.selected_range.end < self.selected_range.start {
826 self.selection_reversed = !self.selection_reversed;
827 self.selected_range = self.selected_range.end..self.selected_range.start;
828 }
829 cx.notify()
830 }
831
832 fn offset_from_utf16(&self, offset: usize) -> usize {
833 offset_from_utf16(&self.content, offset)
834 }
835
836 fn offset_to_utf16(&self, offset: usize) -> usize {
837 offset_to_utf16(&self.content, offset)
838 }
839
840 fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
841 self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
842 }
843
844 fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
845 self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
846 }
847
848 fn previous_boundary(&self, offset: usize) -> usize {
849 previous_boundary(&self.content, offset)
850 }
851
852 fn next_boundary(&self, offset: usize) -> usize {
853 next_boundary(&self.content, offset)
854 }
855}
856
857pub fn offset_from_utf16(text: &str, offset: usize) -> usize {
864 let mut utf8_offset = 0;
865 let mut utf16_count = 0;
866 for ch in text.chars() {
867 if utf16_count >= offset {
868 break;
869 }
870 utf16_count += ch.len_utf16();
871 utf8_offset += ch.len_utf8();
872 }
873 utf8_offset
874}
875
876pub fn offset_to_utf16(text: &str, offset: usize) -> usize {
878 let mut utf16_offset = 0;
879 let mut utf8_count = 0;
880 for ch in text.chars() {
881 if utf8_count >= offset {
882 break;
883 }
884 utf8_count += ch.len_utf8();
885 utf16_offset += ch.len_utf16();
886 }
887 utf16_offset
888}
889
890pub fn previous_boundary(text: &str, offset: usize) -> usize {
894 text.grapheme_indices(true)
895 .rev()
896 .find_map(|(idx, _)| (idx < offset).then_some(idx))
897 .unwrap_or(0)
898}
899
900pub fn next_boundary(text: &str, offset: usize) -> usize {
902 text.grapheme_indices(true)
903 .find_map(|(idx, _)| (idx > offset).then_some(idx))
904 .unwrap_or(text.len())
905}
906
907pub fn joins_group(last: Option<(EditKind, usize)>, kind: EditKind, at: usize) -> bool {
915 last.is_some_and(|(last_kind, offset)| last_kind == kind && at == offset)
916}
917
918pub fn normalize(text: &str, shape: Shape) -> String {
928 let text = text.replace("\r\n", "\n").replace('\r', "\n");
929 if shape.is_multiline() {
930 text
931 } else {
932 text.replace('\n', " ")
933 }
934}
935
936pub fn line_start(text: &str, offset: usize) -> usize {
945 text[..offset].rfind('\n').map_or(0, |at| at + 1)
946}
947
948pub fn line_end(text: &str, offset: usize) -> usize {
950 text[offset..]
951 .find('\n')
952 .map_or(text.len(), |at| offset + at)
953}
954
955fn is_word(segment: &str) -> bool {
958 segment.chars().any(char::is_alphanumeric)
959}
960
961pub fn previous_word_boundary(text: &str, offset: usize) -> usize {
969 text.split_word_bound_indices()
970 .filter(|(start, _)| *start < offset)
971 .rfind(|(_, segment)| is_word(segment))
972 .map(|(start, _)| start)
973 .unwrap_or(0)
974}
975
976pub fn next_word_boundary(text: &str, offset: usize) -> usize {
978 text.split_word_bound_indices()
979 .filter(|(start, segment)| start + segment.len() > offset)
980 .find(|(_, segment)| is_word(segment))
981 .map(|(start, segment)| start + segment.len())
982 .unwrap_or(text.len())
983}
984
985fn display_text(field: &TextField) -> (SharedString, bool) {
988 if field.content.is_empty() {
989 (field.placeholder.clone(), true)
990 } else {
991 (field.content.clone(), false)
992 }
993}
994
995fn lines_from(lines: &[WrappedLine]) -> impl Iterator<Item = (usize, &WrappedLine)> {
1004 lines.iter().scan(0usize, |start, line| {
1005 let at = *start;
1006 *start = at + line.len() + 1;
1007 Some((at, line))
1008 })
1009}
1010
1011fn rows(lines: &[WrappedLine], line_height: Pixels) -> Vec<(Range<usize>, Pixels)> {
1016 let mut out = Vec::new();
1017 let mut top = px(0.);
1018 for (start, line) in lines_from(lines) {
1019 let mut row_start = start;
1020 for boundary in line.wrap_boundaries() {
1021 let at = start + line.runs()[boundary.run_ix].glyphs[boundary.glyph_ix].index;
1022 out.push((row_start..at, top));
1023 row_start = at;
1024 top += line_height;
1025 }
1026 out.push((row_start..start + line.len(), top));
1027 top += line_height;
1028 }
1029 out
1030}
1031
1032fn position_for_offset(
1034 lines: &[WrappedLine],
1035 offset: usize,
1036 line_height: Pixels,
1037) -> Option<Point<Pixels>> {
1038 let mut top = px(0.);
1039 for (start, line) in lines_from(lines) {
1040 if offset <= start + line.len() {
1041 let local = line.position_for_index(offset.saturating_sub(start), line_height)?;
1042 return Some(gpui::point(local.x, local.y + top));
1043 }
1044 top += line.size(line_height).height;
1045 }
1046 None
1047}
1048
1049fn offset_for_position(
1051 lines: &[WrappedLine],
1052 position: Point<Pixels>,
1053 line_height: Pixels,
1054) -> usize {
1055 let mut top = px(0.);
1056 let mut last = 0;
1057 for (start, line) in lines_from(lines) {
1058 let height = line.size(line_height).height;
1059 last = start + line.len();
1060 if position.y < top + height {
1061 let local = gpui::point(position.x, position.y - top);
1062 let (Ok(index) | Err(index)) = line.closest_index_for_position(local, line_height);
1063 return start + index;
1064 }
1065 top += height;
1066 }
1067 last
1068}
1069
1070fn selection_rows(
1076 lines: &[WrappedLine],
1077 range: &Range<usize>,
1078 line_height: Pixels,
1079) -> Vec<Bounds<Pixels>> {
1080 rows(lines, line_height)
1081 .into_iter()
1082 .filter(|(row, _)| range.start <= row.end && range.end >= row.start)
1083 .filter_map(|(row, top)| {
1084 let left = if range.start <= row.start {
1085 px(0.)
1086 } else {
1087 position_for_offset(lines, range.start, line_height)?.x
1088 };
1089 let right = position_for_offset(lines, range.end.min(row.end), line_height)?.x;
1090 (right > left).then(|| {
1091 Bounds::from_corners(
1092 gpui::point(left, top),
1093 gpui::point(right, top + line_height),
1094 )
1095 })
1096 })
1097 .collect()
1098}
1099
1100impl EntityInputHandler for TextField {
1101 fn text_for_range(
1102 &mut self,
1103 range_utf16: Range<usize>,
1104 actual_range: &mut Option<Range<usize>>,
1105 _window: &mut Window,
1106 _cx: &mut Context<Self>,
1107 ) -> Option<String> {
1108 let range = self.range_from_utf16(&range_utf16);
1109 actual_range.replace(self.range_to_utf16(&range));
1110 Some(self.content[range].to_string())
1111 }
1112
1113 fn selected_text_range(
1114 &mut self,
1115 _ignore_disabled_input: bool,
1116 _window: &mut Window,
1117 _cx: &mut Context<Self>,
1118 ) -> Option<UTF16Selection> {
1119 Some(UTF16Selection {
1120 range: self.range_to_utf16(&self.selected_range),
1121 reversed: self.selection_reversed,
1122 })
1123 }
1124
1125 fn marked_text_range(
1126 &self,
1127 _window: &mut Window,
1128 _cx: &mut Context<Self>,
1129 ) -> Option<Range<usize>> {
1130 self.marked_range
1131 .as_ref()
1132 .map(|range| self.range_to_utf16(range))
1133 }
1134
1135 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
1136 self.marked_range = None;
1137 }
1138
1139 fn replace_text_in_range(
1140 &mut self,
1141 range_utf16: Option<Range<usize>>,
1142 new_text: &str,
1143 _: &mut Window,
1144 cx: &mut Context<Self>,
1145 ) {
1146 let range = range_utf16
1147 .as_ref()
1148 .map(|range_utf16| self.range_from_utf16(range_utf16))
1149 .or(self.marked_range.clone())
1150 .unwrap_or(self.selected_range.clone());
1151
1152 let kind = if new_text.is_empty() {
1157 EditKind::Delete
1158 } else {
1159 EditKind::Insert
1160 };
1161 self.push_undo(
1164 kind,
1165 if new_text.is_empty() {
1166 range.end
1167 } else {
1168 range.start
1169 },
1170 );
1171
1172 self.content =
1173 (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
1174 .into();
1175 self.selected_range = range.start + new_text.len()..range.start + new_text.len();
1176 self.marked_range.take();
1177 self.last_edit = Some((kind, self.selected_range.end));
1178 self.follow_caret = true;
1179 cx.notify();
1180 }
1181
1182 fn replace_and_mark_text_in_range(
1183 &mut self,
1184 range_utf16: Option<Range<usize>>,
1185 new_text: &str,
1186 new_selected_range_utf16: Option<Range<usize>>,
1187 _window: &mut Window,
1188 cx: &mut Context<Self>,
1189 ) {
1190 let range = range_utf16
1191 .as_ref()
1192 .map(|range_utf16| self.range_from_utf16(range_utf16))
1193 .or(self.marked_range.clone())
1194 .unwrap_or(self.selected_range.clone());
1195
1196 self.content =
1197 (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
1198 .into();
1199 self.marked_range =
1200 (!new_text.is_empty()).then(|| range.start..range.start + new_text.len());
1201 self.selected_range = new_selected_range_utf16
1202 .as_ref()
1203 .map(|range_utf16| self.range_from_utf16(range_utf16))
1204 .map(|new_range| new_range.start + range.start..new_range.end + range.end)
1205 .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
1206
1207 cx.notify();
1208 }
1209
1210 fn bounds_for_range(
1211 &mut self,
1212 range_utf16: Range<usize>,
1213 bounds: Bounds<Pixels>,
1214 window: &mut Window,
1215 _cx: &mut Context<Self>,
1216 ) -> Option<Bounds<Pixels>> {
1217 let range = self.range_from_utf16(&range_utf16);
1218 self.row_bounds(bounds.origin - self.scroll, range, window.line_height())
1223 }
1224
1225 fn character_index_for_point(
1226 &mut self,
1227 point: Point<Pixels>,
1228 window: &mut Window,
1229 _cx: &mut Context<Self>,
1230 ) -> Option<usize> {
1231 self.last_bounds?.localize(&point)?;
1232 let origin = self.text_origin()?;
1233 let offset = offset_for_position(&self.last_layout, point - origin, window.line_height());
1234 Some(self.offset_to_utf16(offset))
1235 }
1236}
1237
1238impl Focusable for TextField {
1239 fn focus_handle(&self, _: &App) -> FocusHandle {
1240 self.focus_handle.clone()
1241 }
1242}
1243
1244impl Render for TextField {
1245 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1246 let theme = Theme::of(cx);
1247 let mut key_context = gpui::KeyContext::default();
1248 key_context.add(KEY_CONTEXT);
1249 if self.shape.is_multiline() {
1250 key_context.add(MULTILINE_KEY_CONTEXT);
1251 }
1252 if let Some(extra) = self.key_context.clone() {
1253 key_context.add(extra);
1254 }
1255 div()
1256 .key_context(key_context)
1257 .track_focus(&self.focus_handle(cx))
1258 .cursor(CursorStyle::IBeam)
1259 .on_action(cx.listener(Self::backspace))
1260 .on_action(cx.listener(Self::delete))
1261 .on_action(cx.listener(Self::left))
1262 .on_action(cx.listener(Self::right))
1263 .on_action(cx.listener(Self::select_left))
1264 .on_action(cx.listener(Self::select_right))
1265 .on_action(cx.listener(Self::select_all))
1266 .on_action(cx.listener(Self::home))
1267 .on_action(cx.listener(Self::end))
1268 .on_action(cx.listener(Self::select_home))
1269 .on_action(cx.listener(Self::select_end))
1270 .on_action(cx.listener(Self::word_left))
1271 .on_action(cx.listener(Self::word_right))
1272 .on_action(cx.listener(Self::select_word_left))
1273 .on_action(cx.listener(Self::select_word_right))
1274 .on_action(cx.listener(Self::up))
1275 .on_action(cx.listener(Self::down))
1276 .on_action(cx.listener(Self::select_up))
1277 .on_action(cx.listener(Self::select_down))
1278 .on_action(cx.listener(Self::insert_newline))
1279 .on_action(cx.listener(Self::undo))
1280 .on_action(cx.listener(Self::redo))
1281 .on_action(cx.listener(Self::delete_word_left))
1282 .on_action(cx.listener(Self::delete_word_right))
1283 .on_action(cx.listener(Self::delete_to_line_start))
1284 .on_action(cx.listener(Self::delete_to_line_end))
1285 .on_action(cx.listener(Self::show_character_palette))
1286 .on_action(cx.listener(Self::paste))
1287 .on_action(cx.listener(Self::cut))
1288 .on_action(cx.listener(Self::copy))
1289 .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
1290 .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
1291 .on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
1292 .on_mouse_move(cx.listener(Self::on_mouse_move))
1293 .on_scroll_wheel(cx.listener(Self::on_scroll_wheel))
1294 .w_full()
1295 .px(px(10.0))
1296 .py(px(7.0))
1297 .rounded(px(Theme::button_radius()))
1298 .bg(theme.input_bg)
1299 .border_1()
1300 .border_color(if self.focus_handle.is_focused(_window) {
1301 theme.caret
1302 } else {
1303 theme.border
1304 })
1305 .text_size(px(13.0))
1306 .line_height(px(18.0))
1307 .text_color(theme.text)
1308 .child(TextFieldElement { field: cx.entity() })
1309 }
1310}
1311
1312struct TextFieldElement {
1316 field: Entity<TextField>,
1317}
1318
1319struct FieldPrepaint {
1320 lines: Vec<WrappedLine>,
1321 origin: Point<Pixels>,
1323 cursor: Option<PaintQuad>,
1324 selection: Vec<PaintQuad>,
1326}
1327
1328impl IntoElement for TextFieldElement {
1329 type Element = Self;
1330
1331 fn into_element(self) -> Self::Element {
1332 self
1333 }
1334}
1335
1336impl Element for TextFieldElement {
1337 type RequestLayoutState = ();
1338 type PrepaintState = FieldPrepaint;
1339
1340 fn id(&self) -> Option<ElementId> {
1341 None
1342 }
1343
1344 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1345 None
1346 }
1347
1348 fn request_layout(
1349 &mut self,
1350 _id: Option<&GlobalElementId>,
1351 _inspector_id: Option<&gpui::InspectorElementId>,
1352 window: &mut Window,
1353 cx: &mut App,
1354 ) -> (LayoutId, ()) {
1355 let mut style = Style::default();
1356 style.size.width = relative(1.).into();
1357 let line_height = window.line_height();
1358 let field = self.field.read(cx);
1359 let shape = field.shape;
1360
1361 let (min, max) = match shape {
1362 Shape::Line => {
1363 style.size.height = line_height.into();
1364 return (window.request_layout(style, [], cx), ());
1365 }
1366 Shape::Rows(rows) => {
1367 style.size.height = (line_height * rows.max(1) as f32).into();
1368 return (window.request_layout(style, [], cx), ());
1369 }
1370 Shape::Grow { min, max } => (min.max(1), max.max(min.max(1))),
1374 };
1375
1376 let text = display_text(field).0;
1377 let id = window.request_measured_layout(style, move |known, available, window, _cx| {
1378 let text_style = window.text_style();
1379 let font_size = text_style.font_size.to_pixels(window.rem_size());
1380 let wrap_width = known.width.or(match available.width {
1385 gpui::AvailableSpace::Definite(width) => Some(width),
1386 _ => None,
1387 });
1388 let run = TextRun {
1389 len: text.len(),
1390 font: text_style.font(),
1391 color: text_style.color,
1392 background_color: None,
1393 underline: None,
1394 strikethrough: None,
1395 };
1396 let count = window
1397 .text_system()
1398 .shape_text(text.clone(), font_size, &[run], wrap_width, None)
1399 .map(|lines| {
1400 lines
1401 .iter()
1402 .map(|line| line.wrap_boundaries().len() + 1)
1403 .sum::<usize>()
1404 })
1405 .unwrap_or(1);
1406 gpui::size(
1407 wrap_width.unwrap_or(px(0.)),
1408 line_height * count.clamp(min, max) as f32,
1409 )
1410 });
1411 (id, ())
1412 }
1413
1414 fn prepaint(
1415 &mut self,
1416 _id: Option<&GlobalElementId>,
1417 _inspector_id: Option<&gpui::InspectorElementId>,
1418 bounds: Bounds<Pixels>,
1419 _request_layout: &mut Self::RequestLayoutState,
1420 window: &mut Window,
1421 cx: &mut App,
1422 ) -> FieldPrepaint {
1423 let theme = Theme::of(cx).clone();
1424 let field = self.field.read(cx);
1425 let selected_range = field.selected_range.clone();
1426 let cursor = field.cursor_offset();
1427 let shape = field.shape;
1428 let marked_range = field.marked_range.clone();
1429 let scrolled = field.scroll;
1430 let follow_caret = field.follow_caret;
1431 let style = window.text_style();
1432
1433 let (text, is_placeholder) = display_text(field);
1434 let text_color = if is_placeholder {
1435 theme.text_faint
1436 } else {
1437 style.color
1438 };
1439
1440 let run = TextRun {
1441 len: text.len(),
1442 font: style.font(),
1443 color: text_color,
1444 background_color: None,
1445 underline: None,
1446 strikethrough: None,
1447 };
1448 let runs = if let Some(marked) = marked_range.as_ref() {
1451 vec![
1452 TextRun {
1453 len: marked.start,
1454 ..run.clone()
1455 },
1456 TextRun {
1457 len: marked.end - marked.start,
1458 underline: Some(UnderlineStyle {
1459 color: Some(run.color),
1460 thickness: px(1.0),
1461 wavy: false,
1462 }),
1463 ..run.clone()
1464 },
1465 TextRun {
1466 len: text.len() - marked.end,
1467 ..run
1468 },
1469 ]
1470 .into_iter()
1471 .filter(|run| run.len > 0)
1472 .collect()
1473 } else {
1474 vec![run]
1475 };
1476
1477 let font_size = style.font_size.to_pixels(window.rem_size());
1478 let line_height = window.line_height();
1479 let wrap_width = shape.is_multiline().then_some(bounds.size.width);
1482 let lines = window
1483 .text_system()
1484 .shape_text(text, font_size, &runs, wrap_width, None)
1485 .map(|lines| lines.into_vec())
1486 .unwrap_or_default();
1487
1488 let content_height: Pixels = lines.iter().map(|l| l.size(line_height).height).sum();
1497 let content_width = lines.iter().map(|l| l.width()).fold(px(0.), Pixels::max);
1498 let max = gpui::point(
1499 (content_width - bounds.size.width).max(px(0.)),
1500 (content_height - bounds.size.height).max(px(0.)),
1501 );
1502 let mut scroll = gpui::point(
1503 scrolled.x.clamp(px(0.), max.x),
1504 scrolled.y.clamp(px(0.), max.y),
1505 );
1506 if follow_caret && let Some(at) = position_for_offset(&lines, cursor, line_height) {
1507 if at.y < scroll.y {
1508 scroll.y = at.y;
1509 } else if at.y + line_height > scroll.y + bounds.size.height {
1510 scroll.y = at.y + line_height - bounds.size.height;
1511 }
1512 if at.x < scroll.x {
1515 scroll.x = at.x;
1516 } else if at.x + CARET_WIDTH > scroll.x + bounds.size.width {
1517 scroll.x = at.x + CARET_WIDTH - bounds.size.width;
1518 }
1519 scroll.x = scroll.x.clamp(px(0.), max.x);
1520 scroll.y = scroll.y.clamp(px(0.), max.y);
1521 }
1522 self.field.update(cx, |field, _| {
1523 field.scroll = scroll;
1524 field.follow_caret = false;
1525 });
1526 let origin = bounds.origin - scroll;
1527
1528 let (selection, cursor) = if selected_range.is_empty() {
1529 let at = position_for_offset(&lines, cursor, line_height).unwrap_or_default();
1530 (
1531 Vec::new(),
1532 Some(fill(
1533 Bounds::new(origin + at, gpui::size(CARET_WIDTH, line_height)),
1534 theme.caret,
1535 )),
1536 )
1537 } else {
1538 (
1539 selection_rows(&lines, &selected_range, line_height)
1540 .into_iter()
1541 .map(|rect| {
1542 fill(
1543 Bounds::new(origin + rect.origin, rect.size),
1544 theme.selection,
1545 )
1546 })
1547 .collect(),
1548 None,
1549 )
1550 };
1551
1552 FieldPrepaint {
1553 lines,
1554 origin,
1555 cursor,
1556 selection,
1557 }
1558 }
1559
1560 fn paint(
1561 &mut self,
1562 _id: Option<&GlobalElementId>,
1563 _inspector_id: Option<&gpui::InspectorElementId>,
1564 bounds: Bounds<Pixels>,
1565 _request_layout: &mut Self::RequestLayoutState,
1566 prepaint: &mut Self::PrepaintState,
1567 window: &mut Window,
1568 cx: &mut App,
1569 ) {
1570 let focus_handle = self.field.read(cx).focus_handle.clone();
1571 window.handle_input(
1572 &focus_handle,
1573 ElementInputHandler::new(bounds, self.field.clone()),
1574 cx,
1575 );
1576 let line_height = window.line_height();
1577 let lines = std::mem::take(&mut prepaint.lines);
1578 let selection = std::mem::take(&mut prepaint.selection);
1579 let cursor = prepaint.cursor.take();
1580 let origin = prepaint.origin;
1581
1582 window.with_content_mask(Some(gpui::ContentMask { bounds }), |window| {
1585 for selection in selection {
1586 window.paint_quad(selection);
1587 }
1588
1589 let mut top = origin;
1590 for line in &lines {
1591 line.paint(top, line_height, gpui::TextAlign::Left, None, window, cx)
1592 .ok();
1593 top.y += line.size(line_height).height;
1594 }
1595
1596 if focus_handle.is_focused(window)
1599 && let Some(cursor) = cursor
1600 {
1601 window.paint_quad(cursor);
1602 }
1603 });
1604
1605 self.field.update(cx, |field, _| {
1606 field.last_layout = lines;
1607 field.last_bounds = Some(bounds);
1608 });
1609 }
1610}