1use std::ops::Range;
29use std::time::Duration;
30
31use gpui::prelude::*;
32use gpui::{
33 fill, point, px, size, App, Bounds, ClipboardItem, Context, ElementInputHandler, Entity,
34 FocusHandle, GlobalElementId, Hsla, KeyDownEvent, LayoutId, MouseDownEvent, MouseMoveEvent,
35 MouseUpEvent, PaintQuad, Pixels, Point, ShapedLine, SharedString, Style, TextRun,
36 UnderlineStyle, Window,
37};
38
39use super::edit::TextEdit;
40use super::{apply_nav, KeyOutcome};
41use crate::theme::theme;
42
43const MASK: char = '\u{2022}';
45const MASK_STR: &str = "\u{2022}";
47
48const BLINK: Duration = Duration::from_millis(530);
52
53const SCROLL_PAD: f32 = 2.0;
56
57#[derive(Debug, Default)]
62pub struct LineState {
63 pub(crate) shaped: Option<ShapedLine>,
66 pub(crate) bounds: Option<Bounds<Pixels>>,
67 pub(crate) scroll: Pixels,
69 pub(crate) marked: Option<Range<usize>>,
71 pub(crate) selecting: bool,
73 pub(crate) masked: bool,
75 pub(crate) empty: bool,
77 pub(crate) focused: bool,
78 pub(crate) caret_on: bool,
79 pub(crate) blinking: bool,
81}
82
83impl LineState {
84 pub fn new() -> Self {
85 LineState {
86 caret_on: true,
87 ..Default::default()
88 }
89 }
90
91 fn shaped_byte(&self, edit: &TextEdit, index: usize) -> usize {
95 if self.masked {
96 index.min(edit.len()) * MASK.len_utf8()
97 } else {
98 edit.byte_of(index)
99 }
100 }
101
102 fn char_index(&self, edit: &TextEdit, byte: usize) -> usize {
104 if self.masked {
105 (byte / MASK.len_utf8()).min(edit.len())
106 } else {
107 edit.char_of(byte)
108 }
109 }
110
111 pub(crate) fn index_at(&self, edit: &TextEdit, position: Point<Pixels>) -> Option<usize> {
115 let (bounds, shaped) = (self.bounds?, self.shaped.as_ref()?);
116 if self.empty {
117 return Some(0);
118 }
119 let x = position.x - bounds.left() + self.scroll;
120 Some(self.char_index(edit, shaped.closest_index_for_x(x)))
121 }
122
123 fn wake(&mut self) {
125 self.caret_on = true;
126 }
127}
128
129pub trait LineEditor: 'static + Sized + gpui::EntityInputHandler {
137 fn edit(&self) -> &TextEdit;
138 fn edit_mut(&mut self) -> &mut TextEdit;
139 fn line(&self) -> &LineState;
140 fn line_mut(&mut self) -> &mut LineState;
141 fn line_focus(&self) -> &FocusHandle;
142
143 fn line_masked(&self) -> bool {
145 false
146 }
147
148 fn line_read_only(&self) -> bool {
150 false
151 }
152
153 fn line_max_length(&self) -> Option<usize> {
156 None
157 }
158
159 fn line_filter(&self, text: String) -> String {
164 text
165 }
166
167 fn line_changed(&mut self, cx: &mut Context<Self>);
170}
171
172macro_rules! line_input_handler {
179 ($ty:ty) => {
180 impl ::gpui::EntityInputHandler for $ty {
181 fn text_for_range(
182 &mut self,
183 range_utf16: ::std::ops::Range<usize>,
184 actual: &mut Option<::std::ops::Range<usize>>,
185 _window: &mut ::gpui::Window,
186 _cx: &mut ::gpui::Context<Self>,
187 ) -> Option<String> {
188 let range = $crate::input::line::from_utf16(self.edit(), &range_utf16);
189 actual.replace($crate::input::line::to_utf16(self.edit(), &range));
190 Some($crate::input::line::slice(self.edit(), &range))
191 }
192
193 fn selected_text_range(
194 &mut self,
195 _ignore_disabled: bool,
196 _window: &mut ::gpui::Window,
197 _cx: &mut ::gpui::Context<Self>,
198 ) -> Option<::gpui::UTF16Selection> {
199 Some($crate::input::line::utf16_selection(self.edit()))
200 }
201
202 fn marked_text_range(
203 &self,
204 _window: &mut ::gpui::Window,
205 _cx: &mut ::gpui::Context<Self>,
206 ) -> Option<::std::ops::Range<usize>> {
207 let marked = self.line().marked.clone()?;
208 Some($crate::input::line::to_utf16(self.edit(), &marked))
209 }
210
211 fn unmark_text(
212 &mut self,
213 _window: &mut ::gpui::Window,
214 _cx: &mut ::gpui::Context<Self>,
215 ) {
216 self.line_mut().marked = None;
217 }
218
219 fn replace_text_in_range(
220 &mut self,
221 range_utf16: Option<::std::ops::Range<usize>>,
222 text: &str,
223 _window: &mut ::gpui::Window,
224 cx: &mut ::gpui::Context<Self>,
225 ) {
226 $crate::input::line::replace(self, range_utf16, text, None, cx);
227 }
228
229 fn replace_and_mark_text_in_range(
230 &mut self,
231 range_utf16: Option<::std::ops::Range<usize>>,
232 text: &str,
233 selected_utf16: Option<::std::ops::Range<usize>>,
234 _window: &mut ::gpui::Window,
235 cx: &mut ::gpui::Context<Self>,
236 ) {
237 $crate::input::line::replace(self, range_utf16, text, Some(selected_utf16), cx);
238 }
239
240 fn bounds_for_range(
241 &mut self,
242 range_utf16: ::std::ops::Range<usize>,
243 bounds: ::gpui::Bounds<::gpui::Pixels>,
244 _window: &mut ::gpui::Window,
245 _cx: &mut ::gpui::Context<Self>,
246 ) -> Option<::gpui::Bounds<::gpui::Pixels>> {
247 $crate::input::line::range_bounds(self, range_utf16, bounds)
248 }
249
250 fn character_index_for_point(
251 &mut self,
252 point: ::gpui::Point<::gpui::Pixels>,
253 _window: &mut ::gpui::Window,
254 _cx: &mut ::gpui::Context<Self>,
255 ) -> Option<usize> {
256 let index = self.line().index_at(self.edit(), point)?;
257 Some($crate::input::line::to_utf16(self.edit(), &(0..index)).end)
258 }
259 }
260 };
261}
262
263pub(crate) use line_input_handler;
264
265pub(crate) fn to_utf16(edit: &TextEdit, range: &Range<usize>) -> Range<usize> {
271 let buffer = edit.chars();
274 let units = |chars: usize| {
275 buffer[..chars.min(buffer.len())]
276 .iter()
277 .map(|c| c.len_utf16())
278 .sum::<usize>()
279 };
280 units(range.start)..units(range.end)
281}
282
283pub(crate) fn from_utf16(edit: &TextEdit, range: &Range<usize>) -> Range<usize> {
284 let buffer = edit.chars();
285 let chars = |units: usize| {
286 let mut seen = 0;
287 for (index, c) in buffer.iter().enumerate() {
288 if seen >= units {
289 return index;
290 }
291 seen += c.len_utf16();
292 }
293 buffer.len()
294 };
295 chars(range.start)..chars(range.end)
296}
297
298pub(crate) fn slice(edit: &TextEdit, range: &Range<usize>) -> String {
299 let buffer = edit.chars();
300 let start = range.start.min(buffer.len());
301 let end = range.end.clamp(start, buffer.len());
302 buffer[start..end].iter().collect()
303}
304
305pub(crate) fn utf16_selection(edit: &TextEdit) -> gpui::UTF16Selection {
306 let (start, end) = edit.selection().unwrap_or((edit.cursor(), edit.cursor()));
307 let reversed = edit.cursor() == start && start != end;
308 gpui::UTF16Selection {
309 range: to_utf16(edit, &(start..end)),
310 reversed,
311 }
312}
313
314fn flatten(text: &str) -> String {
319 text.chars()
320 .map(|c| if c == '\n' || c == '\r' { ' ' } else { c })
321 .filter(|c| !c.is_control())
322 .collect()
323}
324
325pub(crate) fn replace<V: LineEditor>(
328 this: &mut V,
329 range_utf16: Option<Range<usize>>,
330 text: &str,
331 marking: Option<Option<Range<usize>>>,
332 cx: &mut Context<V>,
333) {
334 if this.line_read_only() {
335 return;
336 }
337 let text = this.line_filter(flatten(text));
338
339 let range = range_utf16
340 .map(|r| from_utf16(this.edit(), &r))
341 .or_else(|| this.line().marked.clone())
342 .unwrap_or_else(|| {
343 this.edit()
344 .selection()
345 .map(|(s, e)| s..e)
346 .unwrap_or_else(|| this.edit().cursor()..this.edit().cursor())
347 });
348
349 let text = match this.line_max_length() {
352 Some(max) => {
353 let kept = this.edit().len() - range.len().min(this.edit().len());
354 text.chars().take(max.saturating_sub(kept)).collect()
355 }
356 None => text,
357 };
358
359 let start = range.start;
360 this.edit_mut().replace_range(range, &text);
361 match marking {
362 Some(selection) => {
363 let end = start + text.chars().count();
364 this.line_mut().marked = (!text.is_empty()).then_some(start..end);
365 if let Some(selection) = selection {
366 let selection = from_utf16(this.edit(), &selection);
367 this.edit_mut()
368 .set_selection(start + selection.start, start + selection.end);
369 }
370 }
371 None => this.line_mut().marked = None,
372 }
373 this.line_mut().wake();
374 this.line_changed(cx);
375}
376
377pub(crate) fn range_bounds<V: LineEditor>(
379 this: &V,
380 range_utf16: Range<usize>,
381 bounds: Bounds<Pixels>,
382) -> Option<Bounds<Pixels>> {
383 let shaped = this.line().shaped.as_ref()?;
384 let range = from_utf16(this.edit(), &range_utf16);
385 let x = |index: usize| {
386 bounds.left() + shaped.x_for_index(this.line().shaped_byte(this.edit(), index))
387 - this.line().scroll
388 };
389 Some(Bounds::from_corners(
390 point(x(range.start), bounds.top()),
391 point(x(range.end), bounds.bottom()),
392 ))
393}
394
395pub(crate) fn mouse_down<V: LineEditor>(
401 this: &mut V,
402 event: &MouseDownEvent,
403 window: &mut Window,
404 cx: &mut Context<V>,
405) {
406 window.focus(this.line_focus());
407 let Some(index) = this.line().index_at(this.edit(), event.position) else {
408 cx.notify();
409 return;
410 };
411 match event.click_count {
412 1 if event.modifiers.shift => this.edit_mut().extend_to(index),
413 1 => this.edit_mut().set_cursor(index),
414 2 => {
415 let (start, end) = this.edit().word_at(index);
416 this.edit_mut().set_selection(start, end);
417 }
418 _ => this.edit_mut().select_all(),
419 }
420 this.line_mut().selecting = true;
421 this.line_mut().wake();
422 cx.notify();
423}
424
425pub(crate) fn mouse_move<V: LineEditor>(
427 this: &mut V,
428 event: &MouseMoveEvent,
429 _window: &mut Window,
430 cx: &mut Context<V>,
431) {
432 if !this.line().selecting {
433 return;
434 }
435 if let Some(index) = this.line().index_at(this.edit(), event.position) {
436 this.edit_mut().extend_to(index);
437 cx.notify();
438 }
439}
440
441pub(crate) fn mouse_up<V: LineEditor>(
442 this: &mut V,
443 _event: &MouseUpEvent,
444 _window: &mut Window,
445 cx: &mut Context<V>,
446) {
447 if this.line().selecting {
448 this.line_mut().selecting = false;
449 cx.notify();
450 }
451}
452
453pub(crate) fn wire<V: LineEditor>(
461 element: gpui::Stateful<gpui::Div>,
462 focus: &FocusHandle,
463 cx: &mut Context<V>,
464) -> gpui::Stateful<gpui::Div> {
465 element
466 .track_focus(focus)
467 .cursor(gpui::CursorStyle::IBeam)
468 .on_mouse_down(gpui::MouseButton::Left, cx.listener(mouse_down))
469 .on_mouse_move(cx.listener(mouse_move))
470 .on_mouse_up(gpui::MouseButton::Left, cx.listener(mouse_up))
471 .on_mouse_up_out(gpui::MouseButton::Left, cx.listener(mouse_up))
472}
473
474macro_rules! line_focus_builders {
480 ($ty:ty) => {
481 impl $ty {
482 pub fn tab_index(mut self, index: isize) -> Self {
486 self.focus = self.focus.clone().tab_index(index);
487 self
488 }
489
490 pub fn tab_stop(mut self, tab_stop: bool) -> Self {
492 self.focus = self.focus.clone().tab_stop(tab_stop);
493 self
494 }
495
496 pub fn focus_handle(&self) -> ::gpui::FocusHandle {
498 self.focus.clone()
499 }
500 }
501 };
502}
503
504pub(crate) use line_focus_builders;
505
506pub(crate) fn keys<V: LineEditor>(
516 this: &mut V,
517 event: &KeyDownEvent,
518 window: &mut Window,
519 cx: &mut Context<V>,
520) -> KeyOutcome {
521 let ks = &event.keystroke;
522 let m = &ks.modifiers;
523
524 if ks.key == "tab" && !m.platform && !m.control {
528 if m.shift {
529 window.focus_prev();
530 } else {
531 window.focus_next();
532 }
533 cx.stop_propagation();
534 return KeyOutcome::Edited;
535 }
536
537 if m.platform && !m.alt && !m.control {
538 match ks.key.as_str() {
539 "c" => return copy(this, cx),
540 "x" => return cut(this, cx),
541 "v" => return paste(this, cx),
542 "z" if m.shift => return history(this, false, cx),
543 "z" => return history(this, true, cx),
544 "y" => return history(this, false, cx),
545 _ => {}
546 }
547 }
548
549 if this.line_read_only() && mutates(ks.key.as_str()) {
550 return KeyOutcome::Pass;
551 }
552
553 let outcome = apply_nav(this.edit_mut(), ks);
554 if outcome == KeyOutcome::Edited {
555 this.line_mut().wake();
556 }
557 outcome
558}
559
560fn mutates(key: &str) -> bool {
562 matches!(key, "backspace" | "delete" | "k")
563}
564
565fn copy<V: LineEditor>(this: &mut V, cx: &mut Context<V>) -> KeyOutcome {
566 if !this.line_masked() {
567 if let Some(text) = this.edit().selected_text() {
568 cx.write_to_clipboard(ClipboardItem::new_string(text));
569 }
570 }
571 cx.stop_propagation();
572 KeyOutcome::Pass
573}
574
575fn cut<V: LineEditor>(this: &mut V, cx: &mut Context<V>) -> KeyOutcome {
576 if this.line_masked() || this.line_read_only() {
577 cx.stop_propagation();
578 return KeyOutcome::Pass;
579 }
580 let Some(text) = this.edit().selected_text() else {
581 cx.stop_propagation();
582 return KeyOutcome::Pass;
583 };
584 cx.write_to_clipboard(ClipboardItem::new_string(text));
585 this.edit_mut().delete_selection();
586 this.line_mut().wake();
587 cx.stop_propagation();
588 KeyOutcome::Edited
589}
590
591fn paste<V: LineEditor>(this: &mut V, cx: &mut Context<V>) -> KeyOutcome {
592 if this.line_read_only() {
593 cx.stop_propagation();
594 return KeyOutcome::Pass;
595 }
596 let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) else {
597 cx.stop_propagation();
598 return KeyOutcome::Pass;
599 };
600 let text = this.line_filter(flatten(&text));
601 let text = match this.line_max_length() {
602 Some(max) => {
603 let selected = this.edit().selection().map_or(0, |(s, e)| e - s);
604 let room = max.saturating_sub(this.edit().len() - selected);
605 text.chars().take(room).collect()
606 }
607 None => text,
608 };
609 this.edit_mut().break_undo();
610 this.edit_mut().insert(&text);
611 this.edit_mut().break_undo();
612 this.line_mut().wake();
613 cx.stop_propagation();
614 KeyOutcome::Edited
615}
616
617fn history<V: LineEditor>(this: &mut V, undo: bool, cx: &mut Context<V>) -> KeyOutcome {
618 if this.line_read_only() {
619 cx.stop_propagation();
620 return KeyOutcome::Pass;
621 }
622 let changed = if undo {
623 this.edit_mut().undo()
624 } else {
625 this.edit_mut().redo()
626 };
627 this.line_mut().wake();
628 cx.stop_propagation();
629 if changed {
630 KeyOutcome::Edited
631 } else {
632 KeyOutcome::Pass
633 }
634}
635
636pub struct Line<V: LineEditor> {
644 field: Entity<V>,
645 placeholder: SharedString,
646 placeholder_color: Option<Hsla>,
650}
651
652impl<V: LineEditor> Line<V> {
653 pub fn new(field: Entity<V>) -> Self {
654 Line {
655 field,
656 placeholder: SharedString::default(),
657 placeholder_color: None,
658 }
659 }
660
661 pub fn placeholder(mut self, placeholder: impl Into<SharedString>, color: Hsla) -> Self {
663 self.placeholder = placeholder.into();
664 self.placeholder_color = Some(color);
665 self
666 }
667}
668
669pub struct LinePrepaint {
671 shaped: Option<ShapedLine>,
672 caret: Option<PaintQuad>,
673 selection: Option<PaintQuad>,
674 scroll: Pixels,
675}
676
677impl<V: LineEditor> IntoElement for Line<V> {
678 type Element = Self;
679
680 fn into_element(self) -> Self::Element {
681 self
682 }
683}
684
685impl<V: LineEditor> Element for Line<V> {
686 type RequestLayoutState = ();
687 type PrepaintState = LinePrepaint;
688
689 fn id(&self) -> Option<gpui::ElementId> {
690 None
691 }
692
693 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
694 None
695 }
696
697 fn request_layout(
698 &mut self,
699 _id: Option<&GlobalElementId>,
700 _inspector: Option<&gpui::InspectorElementId>,
701 window: &mut Window,
702 cx: &mut App,
703 ) -> (LayoutId, ()) {
704 let mut style = Style::default();
705 style.size.width = gpui::relative(1.0).into();
706 style.size.height = window.line_height().into();
707 (window.request_layout(style, [], cx), ())
708 }
709
710 fn prepaint(
711 &mut self,
712 _id: Option<&GlobalElementId>,
713 _inspector: Option<&gpui::InspectorElementId>,
714 bounds: Bounds<Pixels>,
715 _layout: &mut (),
716 window: &mut Window,
717 cx: &mut App,
718 ) -> LinePrepaint {
719 let t = theme(cx);
723 let text_color = t.text().hsla();
724 let caret_color = t.primary().hsla();
725 let selection_color = t.selection();
726 let dimmed = t.dimmed().hsla();
727
728 let focused = self.field.read(cx).line_focus().is_focused(window);
729 let field = self.field.read(cx);
730 let masked = field.line_masked();
731 let empty = field.edit().is_empty();
732 let cursor = field.edit().cursor();
733 let selection = field.edit().selection();
734 let marked = field.line().marked.clone();
735 let chars = field.edit().len();
736 let caret_on = field.line().caret_on;
737
738 let display: SharedString = if empty {
742 self.placeholder.clone()
743 } else if masked {
744 SharedString::from(MASK_STR.repeat(chars))
745 } else {
746 SharedString::from(field.edit().text())
747 };
748
749 let style = window.text_style();
750 let font_size = style.font_size.to_pixels(window.rem_size());
751 let color = if empty {
752 self.placeholder_color.unwrap_or(dimmed)
753 } else {
754 text_color
755 };
756 let run = TextRun {
757 len: display.len(),
758 font: style.font(),
759 color,
760 background_color: None,
761 underline: None,
762 strikethrough: None,
763 };
764
765 let runs = match marked.filter(|_| !empty) {
768 Some(marked) => {
769 let limit = display.len();
775 let byte = |index: usize| {
776 if masked {
777 index.saturating_mul(MASK.len_utf8())
778 } else {
779 byte_of(&display, index)
780 }
781 .min(limit)
782 };
783 let start = byte(marked.start);
784 let end = byte(marked.end).max(start);
785 vec![
786 TextRun {
787 len: start,
788 ..run.clone()
789 },
790 TextRun {
791 len: end.saturating_sub(start),
792 underline: Some(UnderlineStyle {
793 color: Some(color),
794 thickness: px(1.0),
795 wavy: false,
796 }),
797 ..run.clone()
798 },
799 TextRun {
800 len: display.len().saturating_sub(end),
801 ..run
802 },
803 ]
804 .into_iter()
805 .filter(|run| run.len > 0)
806 .collect()
807 }
808 None => vec![run],
809 };
810
811 let shaped = window
814 .text_system()
815 .shape_line(display.clone(), font_size, &runs, None);
816
817 let byte = |index: usize| {
823 if masked {
824 index.saturating_mul(MASK.len_utf8())
825 } else {
826 byte_of(&display, index)
827 }
828 };
829 let caret_x = if empty {
830 px(0.0)
831 } else {
832 shaped.x_for_index(byte(cursor))
833 };
834 let width = bounds.size.width;
835 let pad = px(SCROLL_PAD);
836 let mut scroll = self.field.read(cx).line().scroll;
837 scroll = scroll.min((shaped.width - width + pad).max(px(0.0)));
840 if caret_x - scroll > width - pad {
841 scroll = caret_x - width + pad;
842 }
843 if caret_x - scroll < px(0.0) {
844 scroll = caret_x;
845 }
846 scroll = scroll.max(px(0.0));
847
848 let quads = if focused && !empty {
849 match selection {
850 Some((start, end)) => (
851 None,
852 Some(fill(
853 Bounds::from_corners(
854 point(
855 bounds.left() + shaped.x_for_index(byte(start)) - scroll,
856 bounds.top(),
857 ),
858 point(
859 bounds.left() + shaped.x_for_index(byte(end)) - scroll,
860 bounds.bottom(),
861 ),
862 ),
863 selection_color,
864 )),
865 ),
866 None => (caret_quad(bounds, caret_x - scroll, caret_color), None),
867 }
868 } else if focused {
869 (caret_quad(bounds, caret_x - scroll, caret_color), None)
870 } else {
871 (None, None)
872 };
873
874 self.field.update(cx, |field, cx| {
875 let state = field.line_mut();
876 state.scroll = scroll;
877 state.masked = masked;
878 state.empty = empty;
879 if state.focused != focused {
880 state.focused = focused;
881 state.caret_on = true;
882 }
883 if focused && !state.blinking {
886 state.blinking = true;
887 blink(cx);
888 }
889 });
890
891 LinePrepaint {
892 shaped: Some(shaped),
893 caret: quads.0.filter(|_| caret_on),
894 selection: quads.1,
895 scroll,
896 }
897 }
898
899 fn paint(
900 &mut self,
901 _id: Option<&GlobalElementId>,
902 _inspector: Option<&gpui::InspectorElementId>,
903 bounds: Bounds<Pixels>,
904 _layout: &mut (),
905 prepaint: &mut LinePrepaint,
906 window: &mut Window,
907 cx: &mut App,
908 ) {
909 let focus = self.field.read(cx).line_focus().clone();
910 window.handle_input(
911 &focus,
912 ElementInputHandler::new(bounds, self.field.clone()),
913 cx,
914 );
915
916 let shaped = prepaint.shaped.take().unwrap_or_default();
917 let origin = point(bounds.origin.x - prepaint.scroll, bounds.origin.y);
918 window.with_content_mask(Some(gpui::ContentMask { bounds }), |window| {
921 if let Some(selection) = prepaint.selection.take() {
922 window.paint_quad(selection);
923 }
924 shaped.paint(origin, window.line_height(), window, cx).ok();
925 if let Some(caret) = prepaint.caret.take() {
926 window.paint_quad(caret);
927 }
928 });
929
930 self.field.update(cx, |field, _| {
931 let state = field.line_mut();
932 state.shaped = Some(shaped);
933 state.bounds = Some(bounds);
934 });
935 }
936}
937
938fn caret_quad(bounds: Bounds<Pixels>, x: Pixels, color: Hsla) -> Option<PaintQuad> {
939 Some(fill(
940 Bounds::new(
941 point(bounds.left() + x, bounds.top()),
942 size(px(1.0), bounds.size.height),
943 ),
944 color,
945 ))
946}
947
948fn byte_of(text: &str, index: usize) -> usize {
950 text.char_indices()
951 .nth(index)
952 .map(|(byte, _)| byte)
953 .unwrap_or(text.len())
954}
955
956fn blink<V: LineEditor>(cx: &mut Context<V>) {
959 cx.spawn(async move |field, cx| loop {
960 cx.background_executor().timer(BLINK).await;
961 let running = field
962 .update(cx, |field, cx| {
963 let state = field.line_mut();
964 if !state.focused {
965 state.blinking = false;
966 state.caret_on = true;
967 cx.notify();
968 return false;
969 }
970 state.caret_on = !state.caret_on;
971 cx.notify();
972 true
973 })
974 .unwrap_or(false);
975 if !running {
976 break;
977 }
978 })
979 .detach();
980}