1use core::indent::IndentStyle;
2use std::{
3 cell::{Cell, RefCell},
4 cmp::Ordering,
5 collections::{hash_map::DefaultHasher, HashMap},
6 hash::{Hash, Hasher},
7 rc::Rc,
8 sync::Arc,
9 time::Duration,
10};
11
12use crate::{
13 action::{exec_after, TimerToken},
14 keyboard::Modifiers,
15 kurbo::{Point, Rect, Vec2},
16 peniko::color::palette,
17 peniko::Color,
18 pointer::{PointerInputEvent, PointerMoveEvent},
19 prop, prop_extractor,
20 reactive::{batch, untrack, ReadSignal, RwSignal, Scope},
21 style::{CursorColor, StylePropValue, TextColor},
22 text::{Attrs, AttrsList, LineHeightValue, TextLayout, Wrap},
23 view::{IntoView, View},
24 views::text,
25};
26use floem_editor_core::{
27 buffer::rope_text::{RopeText, RopeTextVal},
28 command::MoveCommand,
29 cursor::{ColPosition, Cursor, CursorAffinity, CursorMode},
30 mode::Mode,
31 movement::Movement,
32 register::Register,
33 selection::Selection,
34 soft_tab::{snap_to_soft_tab_line_col, SnapDirection},
35};
36use floem_reactive::{SignalGet, SignalTrack, SignalUpdate, SignalWith, Trigger};
37use lapce_xi_rope::Rope;
38
39pub mod actions;
40pub mod color;
41pub mod command;
42pub mod gutter;
43pub mod id;
44pub mod keypress;
45pub mod layout;
46pub mod listener;
47pub mod movement;
48pub mod phantom_text;
49pub mod text;
50pub mod text_document;
51pub mod view;
52pub mod visual_line;
53
54pub use floem_editor_core as core;
55use peniko::Brush;
56
57use self::{
58 command::Command,
59 id::EditorId,
60 layout::TextLayoutLine,
61 phantom_text::PhantomTextLine,
62 text::{Document, Preedit, PreeditData, RenderWhitespace, Styling, WrapMethod},
63 view::{LineInfo, ScreenLines, ScreenLinesBase},
64 visual_line::{
65 hit_position_aff, ConfigId, FontSizeCacheId, LayoutEvent, LineFontSizeProvider, Lines,
66 RVLine, ResolvedWrap, TextLayoutProvider, VLine, VLineInfo,
67 },
68};
69
70prop!(pub WrapProp: WrapMethod {} = WrapMethod::EditorWidth);
71impl StylePropValue for WrapMethod {
72 fn debug_view(&self) -> Option<Box<dyn View>> {
73 Some(crate::views::text(self).into_any())
74 }
75}
76prop!(pub CursorSurroundingLines: usize {} = 1);
77prop!(pub ScrollBeyondLastLine: bool {} = false);
78prop!(pub ShowIndentGuide: bool {} = false);
79prop!(pub Modal: bool {} = false);
80prop!(pub ModalRelativeLine: bool {} = false);
81prop!(pub SmartTab: bool {} = false);
82prop!(pub PhantomColor: Color {} = palette::css::DIM_GRAY);
83prop!(pub PlaceholderColor: Color {} = palette::css::DIM_GRAY);
84prop!(pub PreeditUnderlineColor: Color {} = palette::css::WHITE);
85prop!(pub RenderWhitespaceProp: RenderWhitespace {} = RenderWhitespace::None);
86impl StylePropValue for RenderWhitespace {
87 fn debug_view(&self) -> Option<Box<dyn View>> {
88 Some(crate::views::text(self).into_any())
89 }
90}
91prop!(pub IndentStyleProp: IndentStyle {} = IndentStyle::Spaces(4));
92impl StylePropValue for IndentStyle {
93 fn debug_view(&self) -> Option<Box<dyn View>> {
94 Some(text(self).into_any())
95 }
96}
97prop!(pub DropdownShadow: Option<Color> {} = None);
98prop!(pub Foreground: Color { inherited } = Color::from_rgb8(0x38, 0x3A, 0x42));
99prop!(pub Focus: Option<Color> {} = None);
100prop!(pub SelectionColor: Color {} = palette::css::BLACK.with_alpha(0.5));
101prop!(pub CurrentLineColor: Option<Color> { } = None);
102prop!(pub Link: Option<Color> {} = None);
103prop!(pub VisibleWhitespaceColor: Color {} = palette::css::TRANSPARENT);
104prop!(pub IndentGuideColor: Color {} = palette::css::TRANSPARENT);
105prop!(pub StickyHeaderBackground: Option<Color> {} = None);
106
107prop_extractor! {
108 pub EditorStyle {
109 pub text_color: TextColor,
110 pub phantom_color: PhantomColor,
111 pub placeholder_color: PlaceholderColor,
112 pub preedit_underline_color: PreeditUnderlineColor,
113 pub show_indent_guide: ShowIndentGuide,
114 pub modal: Modal,
115 pub modal_relative_line: ModalRelativeLine,
117 pub smart_tab: SmartTab,
120 pub wrap_method: WrapProp,
121 pub cursor_surrounding_lines: CursorSurroundingLines,
122 pub render_whitespace: RenderWhitespaceProp,
123 pub indent_style: IndentStyleProp,
124 pub caret: CursorColor,
125 pub selection: SelectionColor,
126 pub current_line: CurrentLineColor,
127 pub visible_whitespace: VisibleWhitespaceColor,
128 pub indent_guide: IndentGuideColor,
129 pub scroll_beyond_last_line: ScrollBeyondLastLine,
130 }
131}
132impl EditorStyle {
133 pub fn ed_text_color(&self) -> Color {
134 self.text_color().unwrap_or(palette::css::BLACK)
135 }
136}
137impl EditorStyle {
138 pub fn ed_caret(&self) -> Brush {
139 self.caret()
140 }
141}
142
143pub(crate) const CHAR_WIDTH: f64 = 7.5;
144
145#[derive(Clone)]
149pub struct Editor {
150 pub cx: Cell<Scope>,
151 effects_cx: Cell<Scope>,
152
153 id: EditorId,
154
155 pub active: RwSignal<bool>,
156
157 pub read_only: RwSignal<bool>,
159
160 pub(crate) doc: RwSignal<Rc<dyn Document>>,
161 pub(crate) style: RwSignal<Rc<dyn Styling>>,
162
163 pub cursor: RwSignal<Cursor>,
164
165 pub window_origin: RwSignal<Point>,
166 pub viewport: RwSignal<Rect>,
167 pub parent_size: RwSignal<Rect>,
168
169 pub editor_view_focused: Trigger,
170 pub editor_view_focus_lost: Trigger,
171 pub editor_view_id: RwSignal<Option<crate::id::ViewId>>,
172
173 pub scroll_delta: RwSignal<Vec2>,
175 pub scroll_to: RwSignal<Option<Vec2>>,
176
177 lines: Rc<Lines>,
179 pub screen_lines: RwSignal<ScreenLines>,
180
181 pub register: RwSignal<Register>,
183 pub cursor_info: CursorInfo,
185
186 pub last_movement: RwSignal<Movement>,
187
188 pub ime_allowed: RwSignal<bool>,
191
192 pub es: RwSignal<EditorStyle>,
194
195 pub floem_style_id: RwSignal<u64>,
196}
197impl Editor {
198 pub fn new(cx: Scope, doc: Rc<dyn Document>, style: Rc<dyn Styling>, modal: bool) -> Editor {
202 let id = EditorId::next();
203 Editor::new_id(cx, id, doc, style, modal)
204 }
205
206 pub fn new_id(
211 cx: Scope,
212 id: EditorId,
213 doc: Rc<dyn Document>,
214 style: Rc<dyn Styling>,
215 modal: bool,
216 ) -> Editor {
217 let editor = Editor::new_direct(cx, id, doc, style, modal);
218 editor.recreate_view_effects();
219
220 editor
221 }
222
223 pub fn new_direct(
240 cx: Scope,
241 id: EditorId,
242 doc: Rc<dyn Document>,
243 style: Rc<dyn Styling>,
244 modal: bool,
245 ) -> Editor {
246 let cx = cx.create_child();
247
248 let viewport = cx.create_rw_signal(Rect::ZERO);
249 let cursor_mode = if modal {
250 CursorMode::Normal(0)
251 } else {
252 CursorMode::Insert(Selection::caret(0))
253 };
254 let cursor = Cursor::new(cursor_mode, None, None);
255 let cursor = cx.create_rw_signal(cursor);
256
257 let doc = cx.create_rw_signal(doc);
258 let style = cx.create_rw_signal(style);
259
260 let font_sizes = RefCell::new(Rc::new(EditorFontSizes {
261 id,
262 style: style.read_only(),
263 doc: doc.read_only(),
264 }));
265 let lines = Rc::new(Lines::new(cx, font_sizes));
266 let screen_lines = cx.create_rw_signal(ScreenLines::new(cx, viewport.get_untracked()));
267
268 let editor_style = cx.create_rw_signal(EditorStyle::default());
269
270 let ed = Editor {
271 cx: Cell::new(cx),
272 effects_cx: Cell::new(cx.create_child()),
273 id,
274 active: cx.create_rw_signal(false),
275 read_only: cx.create_rw_signal(false),
276 doc,
277 style,
278 cursor,
279 window_origin: cx.create_rw_signal(Point::ZERO),
280 viewport,
281 parent_size: cx.create_rw_signal(Rect::ZERO),
282 scroll_delta: cx.create_rw_signal(Vec2::ZERO),
283 scroll_to: cx.create_rw_signal(None),
284 editor_view_focused: cx.create_trigger(),
285 editor_view_focus_lost: cx.create_trigger(),
286 editor_view_id: cx.create_rw_signal(None),
287 lines,
288 screen_lines,
289 register: cx.create_rw_signal(Register::default()),
290 cursor_info: CursorInfo::new(cx),
291 last_movement: cx.create_rw_signal(Movement::Left),
292 ime_allowed: cx.create_rw_signal(false),
293 es: editor_style,
294 floem_style_id: cx.create_rw_signal(0),
295 };
296
297 create_view_effects(ed.effects_cx.get(), &ed);
298
299 ed
300 }
301
302 pub fn id(&self) -> EditorId {
303 self.id
304 }
305
306 pub fn doc(&self) -> Rc<dyn Document> {
308 self.doc.get_untracked()
309 }
310
311 pub fn doc_track(&self) -> Rc<dyn Document> {
312 self.doc.get()
313 }
314
315 pub fn doc_signal(&self) -> RwSignal<Rc<dyn Document>> {
317 self.doc
318 }
319
320 pub fn config_id(&self) -> ConfigId {
321 let style_id = self.style.with(|s| s.id());
322 let floem_style_id = self.floem_style_id;
323 ConfigId::new(style_id, floem_style_id.get_untracked())
324 }
325
326 pub fn recreate_view_effects(&self) {
327 batch(|| {
328 self.effects_cx.get().dispose();
329 self.effects_cx.set(self.cx.get().create_child());
330 create_view_effects(self.effects_cx.get(), self);
331 });
332 }
333
334 pub fn update_doc(&self, doc: Rc<dyn Document>, styling: Option<Rc<dyn Styling>>) {
336 batch(|| {
337 self.effects_cx.get().dispose();
339
340 *self.lines.font_sizes.borrow_mut() = Rc::new(EditorFontSizes {
341 id: self.id(),
342 style: self.style.read_only(),
343 doc: self.doc.read_only(),
344 });
345 self.lines.clear(0, None);
346 self.doc.set(doc);
347 if let Some(styling) = styling {
348 self.style.set(styling);
349 }
350 self.screen_lines.update(|screen_lines| {
351 screen_lines.clear(self.viewport.get_untracked());
352 });
353
354 self.effects_cx.set(self.cx.get().create_child());
356 create_view_effects(self.effects_cx.get(), self);
357 });
358 }
359
360 pub fn update_styling(&self, styling: Rc<dyn Styling>) {
361 batch(|| {
362 self.effects_cx.get().dispose();
364
365 *self.lines.font_sizes.borrow_mut() = Rc::new(EditorFontSizes {
366 id: self.id(),
367 style: self.style.read_only(),
368 doc: self.doc.read_only(),
369 });
370 self.lines.clear(0, None);
371
372 self.style.set(styling);
373
374 self.screen_lines.update(|screen_lines| {
375 screen_lines.clear(self.viewport.get_untracked());
376 });
377
378 self.effects_cx.set(self.cx.get().create_child());
380 create_view_effects(self.effects_cx.get(), self);
381 });
382 }
383
384 pub fn duplicate(&self, editor_id: Option<EditorId>) -> Editor {
385 let doc = self.doc();
386 let style = self.style();
387 let mut editor = Editor::new_direct(
388 self.cx.get(),
389 editor_id.unwrap_or_else(EditorId::next),
390 doc,
391 style,
392 false,
393 );
394
395 batch(|| {
396 editor.read_only.set(self.read_only.get_untracked());
397 editor.es.set(self.es.get_untracked());
398 editor
399 .floem_style_id
400 .set(self.floem_style_id.get_untracked());
401 editor.cursor.set(self.cursor.get_untracked());
402 editor.scroll_delta.set(self.scroll_delta.get_untracked());
403 editor.scroll_to.set(self.scroll_to.get_untracked());
404 editor.window_origin.set(self.window_origin.get_untracked());
405 editor.viewport.set(self.viewport.get_untracked());
406 editor.parent_size.set(self.parent_size.get_untracked());
407 editor.register.set(self.register.get_untracked());
408 editor.cursor_info = self.cursor_info.clone();
409 editor.last_movement.set(self.last_movement.get_untracked());
410 });
413
414 editor.recreate_view_effects();
415
416 editor
417 }
418
419 pub fn style(&self) -> Rc<dyn Styling> {
421 self.style.get_untracked()
422 }
423
424 pub fn text(&self) -> Rope {
427 self.doc().text()
428 }
429
430 pub fn rope_text(&self) -> RopeTextVal {
432 self.doc().rope_text()
433 }
434
435 pub fn lines(&self) -> &Lines {
436 &self.lines
437 }
438
439 pub fn text_prov(&self) -> &Self {
440 self
441 }
442
443 fn preedit(&self) -> PreeditData {
444 self.doc.with_untracked(|doc| doc.preedit())
445 }
446
447 pub fn set_preedit(&self, text: String, cursor: Option<(usize, usize)>, offset: usize) {
448 batch(|| {
449 self.preedit().preedit.set(Some(Preedit {
450 text,
451 cursor,
452 offset,
453 }));
454
455 self.doc().cache_rev().update(|cache_rev| {
456 *cache_rev += 1;
457 });
458 });
459 }
460
461 pub fn clear_preedit(&self) {
462 let preedit = self.preedit();
463 if preedit.preedit.with_untracked(|preedit| preedit.is_none()) {
464 return;
465 }
466
467 batch(|| {
468 preedit.preedit.set(None);
469 self.doc().cache_rev().update(|cache_rev| {
470 *cache_rev += 1;
471 });
472 });
473 }
474
475 pub fn receive_char(&self, c: &str) {
476 self.doc().receive_char(self, c)
477 }
478
479 fn compute_screen_lines(&self, base: RwSignal<ScreenLinesBase>) -> ScreenLines {
480 self.doc().compute_screen_lines(self, base)
484 }
485
486 pub fn pointer_down(&self, pointer_event: &PointerInputEvent) {
488 if pointer_event.button.is_primary() {
489 self.active.set(true);
490 self.left_click(pointer_event);
491 } else if pointer_event.button.is_secondary() {
492 self.right_click(pointer_event);
493 }
494 }
495
496 pub fn left_click(&self, pointer_event: &PointerInputEvent) {
497 match pointer_event.count {
498 1 => {
499 self.single_click(pointer_event);
500 }
501 2 => {
502 self.double_click(pointer_event);
503 }
504 3 => {
505 self.triple_click(pointer_event);
506 }
507 _ => {}
508 }
509 }
510
511 pub fn single_click(&self, pointer_event: &PointerInputEvent) {
512 let mode = self.cursor.with_untracked(|c| c.get_mode());
513 let (new_offset, _) = self.offset_of_point(mode, pointer_event.pos);
514 self.cursor.update(|cursor| {
515 cursor.set_offset(
516 new_offset,
517 pointer_event.modifiers.shift(),
518 pointer_event.modifiers.alt(),
519 )
520 });
521 }
522
523 pub fn double_click(&self, pointer_event: &PointerInputEvent) {
524 let mode = self.cursor.with_untracked(|c| c.get_mode());
525 let (mouse_offset, _) = self.offset_of_point(mode, pointer_event.pos);
526 let (start, end) = self.select_word(mouse_offset);
527
528 self.cursor.update(|cursor| {
529 cursor.add_region(
530 start,
531 end,
532 pointer_event.modifiers.shift(),
533 pointer_event.modifiers.alt(),
534 )
535 });
536 }
537
538 pub fn triple_click(&self, pointer_event: &PointerInputEvent) {
539 let mode = self.cursor.with_untracked(|c| c.get_mode());
540 let (mouse_offset, _) = self.offset_of_point(mode, pointer_event.pos);
541 let line = self.line_of_offset(mouse_offset);
542 let start = self.offset_of_line(line);
543 let end = self.offset_of_line(line + 1);
544
545 self.cursor.update(|cursor| {
546 cursor.add_region(
547 start,
548 end,
549 pointer_event.modifiers.shift(),
550 pointer_event.modifiers.alt(),
551 )
552 });
553 }
554
555 pub fn pointer_move(&self, pointer_event: &PointerMoveEvent) {
556 let mode = self.cursor.with_untracked(|c| c.get_mode());
557 let (offset, _is_inside) = self.offset_of_point(mode, pointer_event.pos);
558 if self.active.get_untracked() && self.cursor.with_untracked(|c| c.offset()) != offset {
559 self.cursor
560 .update(|cursor| cursor.set_offset(offset, true, pointer_event.modifiers.alt()));
561 }
562 }
563
564 pub fn pointer_up(&self, _pointer_event: &PointerInputEvent) {
565 self.active.set(false);
566 }
567
568 fn right_click(&self, pointer_event: &PointerInputEvent) {
569 let mode = self.cursor.with_untracked(|c| c.get_mode());
570 let (offset, _) = self.offset_of_point(mode, pointer_event.pos);
571 let doc = self.doc();
572 let pointer_inside_selection = self
573 .cursor
574 .with_untracked(|c| c.edit_selection(&doc.rope_text()).contains(offset));
575 if !pointer_inside_selection {
576 self.single_click(pointer_event);
578 }
579 }
580
581 pub fn page_move(&self, down: bool, mods: Modifiers) {
583 let viewport = self.viewport.get_untracked();
584 let line_height = f64::from(self.line_height(0));
586 let lines = (viewport.height() / line_height / 2.0).round() as usize;
587 let distance = (lines as f64) * line_height;
588 self.scroll_delta
589 .set(Vec2::new(0.0, if down { distance } else { -distance }));
590 let cmd = if down {
591 MoveCommand::Down
592 } else {
593 MoveCommand::Up
594 };
595 let cmd = Command::Move(cmd);
596 self.doc().run_command(self, &cmd, Some(lines), mods);
597 }
598
599 pub fn center_window(&self) {
600 let viewport = self.viewport.get_untracked();
601 let line_height = f64::from(self.line_height(0));
603 let offset = self.cursor.with_untracked(|cursor| cursor.offset());
604 let (line, _col) = self.offset_to_line_col(offset);
605
606 let viewport_center = viewport.height() / 2.0;
607
608 let current_line_position = line as f64 * line_height;
609
610 let desired_top = current_line_position - viewport_center + (line_height / 2.0);
611
612 let scroll_delta = desired_top - viewport.y0;
613
614 self.scroll_delta.set(Vec2::new(0.0, scroll_delta));
615 }
616
617 pub fn top_of_window(&self, scroll_off: usize) {
618 let viewport = self.viewport.get_untracked();
619 let line_height = f64::from(self.line_height(0));
621 let offset = self.cursor.with_untracked(|cursor| cursor.offset());
622 let (line, _col) = self.offset_to_line_col(offset);
623
624 let desired_top = (line.saturating_sub(scroll_off)) as f64 * line_height;
625
626 let scroll_delta = desired_top - viewport.y0;
627
628 self.scroll_delta.set(Vec2::new(0.0, scroll_delta));
629 }
630
631 pub fn bottom_of_window(&self, scroll_off: usize) {
632 let viewport = self.viewport.get_untracked();
633 let line_height = f64::from(self.line_height(0));
635 let offset = self.cursor.with_untracked(|cursor| cursor.offset());
636 let (line, _col) = self.offset_to_line_col(offset);
637
638 let desired_bottom = (line + scroll_off + 1) as f64 * line_height - viewport.height();
639
640 let scroll_delta = desired_bottom - viewport.y0;
641
642 self.scroll_delta.set(Vec2::new(0.0, scroll_delta));
643 }
644
645 pub fn scroll(&self, top_shift: f64, down: bool, count: usize, mods: Modifiers) {
646 let viewport = self.viewport.get_untracked();
647 let line_height = f64::from(self.line_height(0));
649 let diff = line_height * count as f64;
650 let diff = if down { diff } else { -diff };
651
652 let offset = self.cursor.with_untracked(|cursor| cursor.offset());
653 let (line, _col) = self.offset_to_line_col(offset);
654 let top = viewport.y0 + diff + top_shift;
655 let bottom = viewport.y0 + diff + viewport.height();
656
657 let new_line = if (line + 1) as f64 * line_height + line_height > bottom {
658 let line = (bottom / line_height).floor() as usize;
659 if line > 2 {
660 line - 2
661 } else {
662 0
663 }
664 } else if line as f64 * line_height - line_height < top {
665 let line = (top / line_height).ceil() as usize;
666 line + 1
667 } else {
668 line
669 };
670
671 self.scroll_delta.set(Vec2::new(0.0, diff));
672
673 let res = match new_line.cmp(&line) {
674 Ordering::Greater => Some((MoveCommand::Down, new_line - line)),
675 Ordering::Less => Some((MoveCommand::Up, line - new_line)),
676 _ => None,
677 };
678
679 if let Some((cmd, count)) = res {
680 let cmd = Command::Move(cmd);
681 self.doc().run_command(self, &cmd, Some(count), mods);
682 }
683 }
684
685 pub fn phantom_text(&self, line: usize) -> PhantomTextLine {
688 self.doc()
689 .phantom_text(self.id(), &self.es.get_untracked(), line)
690 }
691
692 pub fn line_height(&self, line: usize) -> f32 {
693 self.style().line_height(self.id(), line)
694 }
695
696 pub fn iter_vlines(
700 &self,
701 backwards: bool,
702 start: VLine,
703 ) -> impl Iterator<Item = VLineInfo> + '_ {
704 self.lines.iter_vlines(self.text_prov(), backwards, start)
705 }
706
707 pub fn iter_vlines_over(
710 &self,
711 backwards: bool,
712 start: VLine,
713 end: VLine,
714 ) -> impl Iterator<Item = VLineInfo> + '_ {
715 self.lines
716 .iter_vlines_over(self.text_prov(), backwards, start, end)
717 }
718
719 pub fn iter_rvlines(
723 &self,
724 backwards: bool,
725 start: RVLine,
726 ) -> impl Iterator<Item = VLineInfo<()>> + '_ {
727 self.lines.iter_rvlines(self.text_prov(), backwards, start)
728 }
729
730 pub fn iter_rvlines_over(
735 &self,
736 backwards: bool,
737 start: RVLine,
738 end_line: usize,
739 ) -> impl Iterator<Item = VLineInfo<()>> + '_ {
740 self.lines
741 .iter_rvlines_over(self.text_prov(), backwards, start, end_line)
742 }
743
744 pub fn first_rvline_info(&self) -> VLineInfo<()> {
747 self.rvline_info(RVLine::default())
748 }
749
750 pub fn num_lines(&self) -> usize {
752 self.rope_text().num_lines()
753 }
754
755 pub fn last_line(&self) -> usize {
757 self.rope_text().last_line()
758 }
759
760 pub fn last_vline(&self) -> VLine {
761 self.lines.last_vline(self.text_prov())
762 }
763
764 pub fn last_rvline(&self) -> RVLine {
765 self.lines.last_rvline(self.text_prov())
766 }
767
768 pub fn last_rvline_info(&self) -> VLineInfo<()> {
769 self.rvline_info(self.last_rvline())
770 }
771
772 pub fn offset_to_line_col(&self, offset: usize) -> (usize, usize) {
776 self.rope_text().offset_to_line_col(offset)
777 }
778
779 pub fn offset_of_line(&self, offset: usize) -> usize {
780 self.rope_text().offset_of_line(offset)
781 }
782
783 pub fn offset_of_line_col(&self, line: usize, col: usize) -> usize {
784 self.rope_text().offset_of_line_col(line, col)
785 }
786
787 pub fn line_of_offset(&self, offset: usize) -> usize {
789 self.rope_text().line_of_offset(offset)
790 }
791
792 pub fn first_non_blank_character_on_line(&self, line: usize) -> usize {
794 self.rope_text().first_non_blank_character_on_line(line)
795 }
796
797 pub fn line_end_col(&self, line: usize, caret: bool) -> usize {
798 self.rope_text().line_end_col(line, caret)
799 }
800
801 pub fn select_word(&self, offset: usize) -> (usize, usize) {
802 self.rope_text().select_word(offset)
803 }
804
805 pub fn vline_of_offset(&self, offset: usize, affinity: CursorAffinity) -> VLine {
810 self.lines
811 .vline_of_offset(&self.text_prov(), offset, affinity)
812 }
813
814 pub fn vline_of_line(&self, line: usize) -> VLine {
815 self.lines.vline_of_line(&self.text_prov(), line)
816 }
817
818 pub fn rvline_of_line(&self, line: usize) -> RVLine {
819 self.lines.rvline_of_line(&self.text_prov(), line)
820 }
821
822 pub fn vline_of_rvline(&self, rvline: RVLine) -> VLine {
823 self.lines.vline_of_rvline(&self.text_prov(), rvline)
824 }
825
826 pub fn offset_of_vline(&self, vline: VLine) -> usize {
828 self.lines.offset_of_vline(&self.text_prov(), vline)
829 }
830
831 pub fn vline_col_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (VLine, usize) {
834 self.lines
835 .vline_col_of_offset(&self.text_prov(), offset, affinity)
836 }
837
838 pub fn rvline_of_offset(&self, offset: usize, affinity: CursorAffinity) -> RVLine {
839 self.lines
840 .rvline_of_offset(&self.text_prov(), offset, affinity)
841 }
842
843 pub fn rvline_col_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (RVLine, usize) {
844 self.lines
845 .rvline_col_of_offset(&self.text_prov(), offset, affinity)
846 }
847
848 pub fn offset_of_rvline(&self, rvline: RVLine) -> usize {
849 self.lines.offset_of_rvline(&self.text_prov(), rvline)
850 }
851
852 pub fn vline_info(&self, vline: VLine) -> VLineInfo {
853 let vline = vline.min(self.last_vline());
854 self.iter_vlines(false, vline).next().unwrap()
855 }
856
857 pub fn screen_rvline_info_of_offset(
858 &self,
859 offset: usize,
860 affinity: CursorAffinity,
861 ) -> Option<VLineInfo<()>> {
862 let rvline = self.rvline_of_offset(offset, affinity);
863 self.screen_lines.with_untracked(|screen_lines| {
864 screen_lines
865 .iter_vline_info()
866 .find(|vline_info| vline_info.rvline == rvline)
867 })
868 }
869
870 pub fn rvline_info(&self, rvline: RVLine) -> VLineInfo<()> {
871 let rvline = rvline.min(self.last_rvline());
872 self.iter_rvlines(false, rvline).next().unwrap()
873 }
874
875 pub fn rvline_info_of_offset(&self, offset: usize, affinity: CursorAffinity) -> VLineInfo<()> {
876 let rvline = self.rvline_of_offset(offset, affinity);
877 self.rvline_info(rvline)
878 }
879
880 pub fn first_col<T: std::fmt::Debug>(&self, info: VLineInfo<T>) -> usize {
882 info.first_col(&self.text_prov())
883 }
884
885 pub fn last_col<T: std::fmt::Debug>(&self, info: VLineInfo<T>, caret: bool) -> usize {
887 info.last_col(&self.text_prov(), caret)
888 }
889
890 pub fn max_line_width(&self) -> f64 {
893 self.lines.max_width()
894 }
895
896 pub fn line_point_of_offset(&self, offset: usize, affinity: CursorAffinity) -> Point {
899 let (line, col) = self.offset_to_line_col(offset);
900 self.line_point_of_line_col(line, col, affinity, false)
901 }
902
903 pub fn line_point_of_line_col(
906 &self,
907 line: usize,
908 col: usize,
909 affinity: CursorAffinity,
910 force_affinity: bool,
911 ) -> Point {
912 let text_layout = self.text_layout(line);
913 let index = if force_affinity {
914 text_layout
915 .phantom_text
916 .col_after_force(col, affinity == CursorAffinity::Forward)
917 } else {
918 text_layout
919 .phantom_text
920 .col_after(col, affinity == CursorAffinity::Forward)
921 };
922 hit_position_aff(
923 &text_layout.text,
924 index,
925 affinity == CursorAffinity::Backward,
926 )
927 .point
928 }
929
930 pub fn points_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (Point, Point) {
932 let line = self.line_of_offset(offset);
933 let line_height = f64::from(self.style().line_height(self.id(), line));
934
935 let info = self.screen_lines.with_untracked(|sl| {
936 sl.iter_line_info().find(|info| {
937 info.vline_info.interval.start <= offset && offset <= info.vline_info.interval.end
938 })
939 });
940 let Some(info) = info else {
941 return (Point::new(0.0, 0.0), Point::new(0.0, 0.0));
945 };
946
947 let y = info.vline_y;
948
949 let x = self.line_point_of_offset(offset, affinity).x;
950
951 (Point::new(x, y), Point::new(x, y + line_height))
952 }
953
954 pub fn offset_of_point(&self, mode: Mode, point: Point) -> (usize, bool) {
959 let ((line, col), is_inside) = self.line_col_of_point(mode, point);
960 (self.offset_of_line_col(line, col), is_inside)
961 }
962
963 pub fn line_col_of_point_with_phantom(&self, point: Point) -> (usize, usize) {
965 let line_height = f64::from(self.style().line_height(self.id(), 0));
966 let info = if point.y <= 0.0 {
967 Some(self.first_rvline_info())
968 } else {
969 self.screen_lines
970 .with_untracked(|sl| {
971 sl.iter_line_info().find(|info| {
972 info.vline_y <= point.y && info.vline_y + line_height >= point.y
973 })
974 })
975 .map(|info| info.vline_info)
976 };
977 let info = info.unwrap_or_else(|| {
978 for (y_idx, info) in self.iter_rvlines(false, RVLine::default()).enumerate() {
979 let vline_y = y_idx as f64 * line_height;
980 if vline_y <= point.y && vline_y + line_height >= point.y {
981 return info;
982 }
983 }
984
985 self.last_rvline_info()
986 });
987
988 let rvline = info.rvline;
989 let line = rvline.line;
990 let text_layout = self.text_layout(line);
991
992 let y = text_layout.get_layout_y(rvline.line_index).unwrap_or(0.0);
993
994 let hit_point = text_layout.text.hit_point(Point::new(point.x, y as f64));
995 (line, hit_point.index)
996 }
997
998 pub fn line_col_of_point(&self, mode: Mode, point: Point) -> ((usize, usize), bool) {
1003 let line_height = f64::from(self.style().line_height(self.id(), 0));
1005 let info = if point.y <= 0.0 {
1006 Some(self.first_rvline_info())
1007 } else {
1008 self.screen_lines
1009 .with_untracked(|sl| {
1010 sl.iter_line_info().find(|info| {
1011 info.vline_y <= point.y && info.vline_y + line_height >= point.y
1012 })
1013 })
1014 .map(|info| info.vline_info)
1015 };
1016 let info = info.unwrap_or_else(|| {
1017 for (y_idx, info) in self.iter_rvlines(false, RVLine::default()).enumerate() {
1018 let vline_y = y_idx as f64 * line_height;
1019 if vline_y <= point.y && vline_y + line_height >= point.y {
1020 return info;
1021 }
1022 }
1023
1024 self.last_rvline_info()
1025 });
1026
1027 let rvline = info.rvline;
1028 let line = rvline.line;
1029 let text_layout = self.text_layout(line);
1030
1031 let y = text_layout.get_layout_y(rvline.line_index).unwrap_or(0.0);
1032
1033 let hit_point = text_layout.text.hit_point(Point::new(point.x, y as f64));
1034 let col = text_layout.phantom_text.before_col(hit_point.index);
1037 let max_col = self.line_end_col(line, mode != Mode::Normal);
1040 let mut col = col.min(max_col);
1041
1042 if !hit_point.is_inside {
1048 col = info.last_col(&self.text_prov(), true);
1050 }
1051
1052 let tab_width = self.style().tab_width(self.id(), line);
1053 if self.style().atomic_soft_tabs(self.id(), line) && tab_width > 1 {
1054 col = snap_to_soft_tab_line_col(
1055 &self.text(),
1056 line,
1057 col,
1058 SnapDirection::Nearest,
1059 tab_width,
1060 );
1061 }
1062
1063 ((line, col), hit_point.is_inside)
1064 }
1065
1066 pub fn line_horiz_col(&self, line: usize, horiz: &ColPosition, caret: bool) -> usize {
1068 match *horiz {
1069 ColPosition::Col(x) => {
1070 let text_layout = self.text_layout(line);
1073 let hit_point = text_layout.text.hit_point(Point::new(x, 0.0));
1074 let n = hit_point.index;
1075 let col = text_layout.phantom_text.before_col(n);
1076
1077 col.min(self.line_end_col(line, caret))
1078 }
1079 ColPosition::End => self.line_end_col(line, caret),
1080 ColPosition::Start => 0,
1081 ColPosition::FirstNonBlank => self.first_non_blank_character_on_line(line),
1082 }
1083 }
1084
1085 pub fn rvline_horiz_col(
1088 &self,
1089 RVLine { line, line_index }: RVLine,
1090 horiz: &ColPosition,
1091 caret: bool,
1092 ) -> usize {
1093 match *horiz {
1094 ColPosition::Col(x) => {
1095 let text_layout = self.text_layout(line);
1096 let y_pos = text_layout
1097 .text
1098 .layout_runs()
1099 .nth(line_index)
1100 .map(|run| run.line_y)
1101 .or_else(|| text_layout.text.layout_runs().last().map(|run| run.line_y))
1102 .unwrap_or(0.0);
1103 let hit_point = text_layout.text.hit_point(Point::new(x, y_pos as f64));
1104 let n = hit_point.index;
1105 let col = text_layout.phantom_text.before_col(n);
1106
1107 col.min(self.line_end_col(line, caret))
1108 }
1109 _ => self.line_horiz_col(line, horiz, caret),
1111 }
1112 }
1113
1114 pub fn move_right(&self, offset: usize, mode: Mode, count: usize) -> usize {
1117 self.rope_text().move_right(offset, mode, count)
1118 }
1119
1120 pub fn move_left(&self, offset: usize, mode: Mode, count: usize) -> usize {
1123 self.rope_text().move_left(offset, mode, count)
1124 }
1125}
1126
1127impl std::fmt::Debug for Editor {
1128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1129 f.debug_tuple("Editor").field(&self.id).finish()
1130 }
1131}
1132
1133impl Editor {
1135 pub fn text_layout(&self, line: usize) -> Arc<TextLayoutLine> {
1137 self.text_layout_trigger(line, true)
1138 }
1139
1140 pub fn text_layout_trigger(&self, line: usize, trigger: bool) -> Arc<TextLayoutLine> {
1141 let cache_rev = self.doc().cache_rev().get_untracked();
1142 self.lines
1143 .get_init_text_layout(cache_rev, self.config_id(), self, line, trigger)
1144 }
1145
1146 fn try_get_text_layout(&self, line: usize) -> Option<Arc<TextLayoutLine>> {
1147 let cache_rev = self.doc().cache_rev().get_untracked();
1148 self.lines
1149 .try_get_text_layout(cache_rev, self.config_id(), line)
1150 }
1151
1152 fn new_whitespace_layout(
1156 line_content: &str,
1157 text_layout: &TextLayout,
1158 phantom: &PhantomTextLine,
1159 render_whitespace: RenderWhitespace,
1160 ) -> Option<Vec<(char, (f64, f64))>> {
1161 let mut render_leading = false;
1162 let mut render_boundary = false;
1163 let mut render_between = false;
1164
1165 match render_whitespace {
1167 RenderWhitespace::All => {
1168 render_leading = true;
1169 render_boundary = true;
1170 render_between = true;
1171 }
1172 RenderWhitespace::Boundary => {
1173 render_leading = true;
1174 render_boundary = true;
1175 }
1176 RenderWhitespace::Trailing => {} RenderWhitespace::None => return None,
1178 }
1179
1180 let mut whitespace_buffer = Vec::new();
1181 let mut rendered_whitespaces: Vec<(char, (f64, f64))> = Vec::new();
1182 let mut char_found = false;
1183 let mut col = 0;
1184 for c in line_content.chars() {
1185 match c {
1186 '\t' => {
1187 let col_left = phantom.col_after(col, true);
1188 let col_right = phantom.col_after(col + 1, false);
1189 let x0 = text_layout.hit_position(col_left).point.x;
1190 let x1 = text_layout.hit_position(col_right).point.x;
1191 whitespace_buffer.push(('\t', (x0, x1)));
1192 }
1193 ' ' => {
1194 let col_left = phantom.col_after(col, true);
1195 let col_right = phantom.col_after(col + 1, false);
1196 let x0 = text_layout.hit_position(col_left).point.x;
1197 let x1 = text_layout.hit_position(col_right).point.x;
1198 whitespace_buffer.push((' ', (x0, x1)));
1199 }
1200 _ => {
1201 if (char_found && render_between)
1202 || (char_found && render_boundary && whitespace_buffer.len() > 1)
1203 || (!char_found && render_leading)
1204 {
1205 rendered_whitespaces.extend(whitespace_buffer.iter());
1206 }
1207
1208 char_found = true;
1209 whitespace_buffer.clear();
1210 }
1211 }
1212 col += c.len_utf8();
1213 }
1214 rendered_whitespaces.extend(whitespace_buffer.iter());
1215
1216 Some(rendered_whitespaces)
1217 }
1218}
1219impl TextLayoutProvider for Editor {
1220 fn text(&self) -> Rope {
1222 Editor::text(self)
1223 }
1224
1225 fn new_text_layout(
1226 &self,
1227 line: usize,
1228 _font_size: usize,
1229 _wrap: ResolvedWrap,
1230 ) -> Arc<TextLayoutLine> {
1231 let edid = self.id();
1234 let text = self.rope_text();
1235 let style = self.style();
1236 let doc = self.doc();
1237
1238 let line_content_original = text.line_content(line);
1239
1240 let font_size = style.font_size(edid, line);
1241
1242 let line_content = if let Some(s) = line_content_original.strip_suffix("\r\n") {
1247 format!("{s} ")
1248 } else if let Some(s) = line_content_original.strip_suffix('\n') {
1249 format!("{s} ",)
1250 } else {
1251 line_content_original.to_string()
1252 };
1253 let phantom_text = doc.phantom_text(edid, &self.es.get_untracked(), line);
1255 let line_content = phantom_text.combine_with_text(&line_content);
1256
1257 let family = style.font_family(edid, line);
1258 let attrs = Attrs::new()
1259 .color(self.es.with(|s| s.ed_text_color()))
1260 .family(&family)
1261 .font_size(font_size as f32)
1262 .line_height(LineHeightValue::Px(style.line_height(edid, line)));
1263 let mut attrs_list = AttrsList::new(attrs);
1264
1265 self.es.with_untracked(|es| {
1266 style.apply_attr_styles(edid, es, line, attrs, &mut attrs_list);
1267 });
1268
1269 for (offset, size, col, phantom) in phantom_text.offset_size_iter() {
1271 let start = col + offset;
1272 let end = start + size;
1273
1274 let mut attrs = attrs;
1275 if let Some(fg) = phantom.fg {
1276 attrs = attrs.color(fg);
1277 } else {
1278 attrs = attrs.color(self.es.with(|es| es.phantom_color()))
1279 }
1280 if let Some(phantom_font_size) = phantom.font_size {
1281 attrs = attrs.font_size(phantom_font_size.min(font_size) as f32);
1282 }
1283 attrs_list.add_span(start..end, attrs);
1284 }
1291
1292 let mut text_layout = TextLayout::new();
1293 text_layout.set_tab_width(style.tab_width(edid, line));
1295 text_layout.set_text(&line_content, attrs_list);
1296
1297 match self.es.with(|s| s.wrap_method()) {
1299 WrapMethod::None => {}
1300 WrapMethod::EditorWidth => {
1301 let width = self.viewport.get_untracked().width();
1302 text_layout.set_wrap(Wrap::WordOrGlyph);
1303 text_layout.set_size(width as f32, f32::MAX);
1304 }
1305 WrapMethod::WrapWidth { width } => {
1306 text_layout.set_wrap(Wrap::WordOrGlyph);
1307 text_layout.set_size(width, f32::MAX);
1308 }
1309 WrapMethod::WrapColumn { .. } => {}
1311 }
1312
1313 let whitespaces = Self::new_whitespace_layout(
1314 &line_content_original,
1315 &text_layout,
1316 &phantom_text,
1317 self.es.with(|s| s.render_whitespace()),
1318 );
1319
1320 let indent_line = style.indent_line(edid, line, &line_content_original);
1321
1322 let indent = if indent_line != line {
1323 let layout = self.try_get_text_layout(indent_line).unwrap_or_else(|| {
1326 self.new_text_layout(
1327 indent_line,
1328 style.font_size(edid, indent_line),
1329 self.lines.wrap(),
1330 )
1331 });
1332 layout.indent + 1.0
1333 } else {
1334 let offset = text.first_non_blank_character_on_line(indent_line);
1335 let (_, col) = text.offset_to_line_col(offset);
1336 text_layout.hit_position(col).point.x
1337 };
1338
1339 let mut layout_line = TextLayoutLine {
1340 text: text_layout,
1341 extra_style: Vec::new(),
1342 whitespaces,
1343 indent,
1344 phantom_text,
1345 };
1346 self.es.with_untracked(|es| {
1347 style.apply_layout_styles(edid, es, line, &mut layout_line);
1348 });
1349
1350 Arc::new(layout_line)
1351 }
1352
1353 fn before_phantom_col(&self, line: usize, col: usize) -> usize {
1354 self.doc()
1355 .before_phantom_col(self.id(), &self.es.get_untracked(), line, col)
1356 }
1357
1358 fn has_multiline_phantom(&self) -> bool {
1359 self.doc()
1360 .has_multiline_phantom(self.id(), &self.es.get_untracked())
1361 }
1362}
1363
1364struct EditorFontSizes {
1365 id: EditorId,
1366 style: ReadSignal<Rc<dyn Styling>>,
1367 doc: ReadSignal<Rc<dyn Document>>,
1368}
1369impl LineFontSizeProvider for EditorFontSizes {
1370 fn font_size(&self, line: usize) -> usize {
1371 self.style
1372 .with_untracked(|style| style.font_size(self.id, line))
1373 }
1374
1375 fn cache_id(&self) -> FontSizeCacheId {
1376 let mut hasher = DefaultHasher::new();
1377
1378 self.style
1381 .with_untracked(|style| style.id().hash(&mut hasher));
1382 self.doc
1383 .with_untracked(|doc| doc.cache_rev().get_untracked().hash(&mut hasher));
1384
1385 hasher.finish()
1386 }
1387}
1388
1389const MIN_WRAPPED_WIDTH: f32 = 100.0;
1391
1392fn create_view_effects(cx: Scope, ed: &Editor) {
1396 let ed2 = ed.clone();
1398 let ed3 = ed.clone();
1399 let ed4 = ed.clone();
1400
1401 {
1403 let cursor_info = ed.cursor_info.clone();
1404 let cursor = ed.cursor;
1405 cx.create_effect(move |_| {
1406 cursor.track();
1407 cursor_info.reset();
1408 });
1409 }
1410
1411 let update_screen_lines = |ed: &Editor| {
1412 ed.screen_lines.update(|screen_lines| {
1417 let new_screen_lines = ed.compute_screen_lines(screen_lines.base);
1418
1419 *screen_lines = new_screen_lines;
1420 });
1421 };
1422
1423 ed3.lines.layout_event.listen_with(cx, move |val| {
1426 let ed = &ed2;
1427 match val {
1431 LayoutEvent::CreatedLayout { line, .. } => {
1432 let sl = ed.screen_lines.get_untracked();
1433
1434 let should_update = sl.on_created_layout(ed, line);
1436
1437 if should_update {
1438 untrack(|| {
1439 update_screen_lines(ed);
1440 });
1441
1442 ed2.text_layout_trigger(line, true);
1447 }
1448 }
1449 }
1450 });
1451
1452 let viewport_changed_trigger = cx.create_trigger();
1456
1457 cx.create_effect(move |_| {
1460 let ed = &ed3;
1461
1462 let viewport = ed.viewport.get();
1463
1464 let wrap = match ed.es.with(|s| s.wrap_method()) {
1465 WrapMethod::None => ResolvedWrap::None,
1466 WrapMethod::EditorWidth => {
1467 ResolvedWrap::Width((viewport.width() as f32).max(MIN_WRAPPED_WIDTH))
1468 }
1469 WrapMethod::WrapColumn { .. } => todo!(),
1470 WrapMethod::WrapWidth { width } => ResolvedWrap::Width(width),
1471 };
1472
1473 ed.lines.set_wrap(wrap);
1474
1475 let base = ed.screen_lines.with_untracked(|sl| sl.base);
1477
1478 if viewport != base.with_untracked(|base| base.active_viewport) {
1480 batch(|| {
1481 base.update(|base| {
1482 base.active_viewport = viewport;
1483 });
1484 viewport_changed_trigger.notify();
1487 });
1488 }
1489 });
1490 cx.create_effect(move |_| {
1493 viewport_changed_trigger.track();
1494
1495 update_screen_lines(&ed4);
1496 });
1497}
1498
1499pub fn normal_compute_screen_lines(
1500 editor: &Editor,
1501 base: RwSignal<ScreenLinesBase>,
1502) -> ScreenLines {
1503 let lines = &editor.lines;
1504 let style = editor.style.get();
1505 let line_height = style.line_height(editor.id(), 0);
1507
1508 let (y0, y1) = base.with_untracked(|base| (base.active_viewport.y0, base.active_viewport.y1));
1509 let min_vline = VLine((y0 / line_height as f64).floor() as usize);
1511 let max_vline = VLine((y1 / line_height as f64).ceil() as usize);
1512
1513 let cache_rev = editor.doc.get().cache_rev().get();
1514 editor.lines.check_cache_rev(cache_rev);
1515
1516 let min_info = editor.iter_vlines(false, min_vline).next();
1517
1518 let mut rvlines = Vec::new();
1519 let mut info = HashMap::new();
1520
1521 let Some(min_info) = min_info else {
1522 return ScreenLines {
1523 lines: Rc::new(rvlines),
1524 info: Rc::new(info),
1525 diff_sections: None,
1526 base,
1527 };
1528 };
1529
1530 let count = max_vline.get() - min_vline.get();
1533 let iter = lines
1534 .iter_rvlines_init(
1535 editor.text_prov(),
1536 cache_rev,
1537 editor.config_id(),
1538 min_info.rvline,
1539 false,
1540 )
1541 .take(count);
1542
1543 for (i, vline_info) in iter.enumerate() {
1544 rvlines.push(vline_info.rvline);
1545
1546 let line_height = f64::from(style.line_height(editor.id(), vline_info.rvline.line));
1547
1548 let y_idx = min_vline.get() + i;
1549 let vline_y = y_idx as f64 * line_height;
1550 let line_y = vline_y - vline_info.rvline.line_index as f64 * line_height;
1551
1552 info.insert(
1555 vline_info.rvline,
1556 LineInfo {
1557 y: line_y - y0,
1558 vline_y: vline_y - y0,
1559 vline_info,
1560 },
1561 );
1562 }
1563
1564 ScreenLines {
1565 lines: Rc::new(rvlines),
1566 info: Rc::new(info),
1567 diff_sections: None,
1568 base,
1569 }
1570}
1571
1572#[derive(Clone)]
1575pub struct CursorInfo {
1576 pub hidden: RwSignal<bool>,
1577
1578 pub blink_timer: RwSignal<TimerToken>,
1579 pub should_blink: Rc<dyn Fn() -> bool + 'static>,
1581 pub blink_interval: Rc<dyn Fn() -> u64 + 'static>,
1582}
1583
1584impl CursorInfo {
1585 pub fn new(cx: Scope) -> CursorInfo {
1586 CursorInfo {
1587 hidden: cx.create_rw_signal(false),
1588
1589 blink_timer: cx.create_rw_signal(TimerToken::INVALID),
1590 should_blink: Rc::new(|| true),
1591 blink_interval: Rc::new(|| 500),
1592 }
1593 }
1594
1595 pub fn blink(&self) {
1596 let info = self.clone();
1597 let blink_interval = (info.blink_interval)();
1598 if blink_interval > 0 && (info.should_blink)() {
1599 let blink_timer = info.blink_timer;
1600 let timer_token =
1601 exec_after(Duration::from_millis(blink_interval), move |timer_token| {
1602 if info.blink_timer.try_get_untracked() == Some(timer_token) {
1603 info.hidden.update(|hide| {
1604 *hide = !*hide;
1605 });
1606 info.blink();
1607 }
1608 });
1609 blink_timer.set(timer_token);
1610 }
1611 }
1612
1613 pub fn reset(&self) {
1614 if self.hidden.get_untracked() {
1615 self.hidden.set(false);
1616 }
1617
1618 self.blink_timer.set(TimerToken::INVALID);
1619
1620 self.blink();
1621 }
1622}