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 line.saturating_sub(2)
660 } else if line as f64 * line_height - line_height < top {
661 let line = (top / line_height).ceil() as usize;
662 line + 1
663 } else {
664 line
665 };
666
667 self.scroll_delta.set(Vec2::new(0.0, diff));
668
669 let res = match new_line.cmp(&line) {
670 Ordering::Greater => Some((MoveCommand::Down, new_line - line)),
671 Ordering::Less => Some((MoveCommand::Up, line - new_line)),
672 _ => None,
673 };
674
675 if let Some((cmd, count)) = res {
676 let cmd = Command::Move(cmd);
677 self.doc().run_command(self, &cmd, Some(count), mods);
678 }
679 }
680
681 pub fn phantom_text(&self, line: usize) -> PhantomTextLine {
684 self.doc()
685 .phantom_text(self.id(), &self.es.get_untracked(), line)
686 }
687
688 pub fn line_height(&self, line: usize) -> f32 {
689 self.style().line_height(self.id(), line)
690 }
691
692 pub fn iter_vlines(
696 &self,
697 backwards: bool,
698 start: VLine,
699 ) -> impl Iterator<Item = VLineInfo> + '_ {
700 self.lines.iter_vlines(self.text_prov(), backwards, start)
701 }
702
703 pub fn iter_vlines_over(
706 &self,
707 backwards: bool,
708 start: VLine,
709 end: VLine,
710 ) -> impl Iterator<Item = VLineInfo> + '_ {
711 self.lines
712 .iter_vlines_over(self.text_prov(), backwards, start, end)
713 }
714
715 pub fn iter_rvlines(
719 &self,
720 backwards: bool,
721 start: RVLine,
722 ) -> impl Iterator<Item = VLineInfo<()>> + '_ {
723 self.lines.iter_rvlines(self.text_prov(), backwards, start)
724 }
725
726 pub fn iter_rvlines_over(
731 &self,
732 backwards: bool,
733 start: RVLine,
734 end_line: usize,
735 ) -> impl Iterator<Item = VLineInfo<()>> + '_ {
736 self.lines
737 .iter_rvlines_over(self.text_prov(), backwards, start, end_line)
738 }
739
740 pub fn first_rvline_info(&self) -> VLineInfo<()> {
743 self.rvline_info(RVLine::default())
744 }
745
746 pub fn num_lines(&self) -> usize {
748 self.rope_text().num_lines()
749 }
750
751 pub fn last_line(&self) -> usize {
753 self.rope_text().last_line()
754 }
755
756 pub fn last_vline(&self) -> VLine {
757 self.lines.last_vline(self.text_prov())
758 }
759
760 pub fn last_rvline(&self) -> RVLine {
761 self.lines.last_rvline(self.text_prov())
762 }
763
764 pub fn last_rvline_info(&self) -> VLineInfo<()> {
765 self.rvline_info(self.last_rvline())
766 }
767
768 pub fn offset_to_line_col(&self, offset: usize) -> (usize, usize) {
772 self.rope_text().offset_to_line_col(offset)
773 }
774
775 pub fn offset_of_line(&self, offset: usize) -> usize {
776 self.rope_text().offset_of_line(offset)
777 }
778
779 pub fn offset_of_line_col(&self, line: usize, col: usize) -> usize {
780 self.rope_text().offset_of_line_col(line, col)
781 }
782
783 pub fn line_of_offset(&self, offset: usize) -> usize {
785 self.rope_text().line_of_offset(offset)
786 }
787
788 pub fn first_non_blank_character_on_line(&self, line: usize) -> usize {
790 self.rope_text().first_non_blank_character_on_line(line)
791 }
792
793 pub fn line_end_col(&self, line: usize, caret: bool) -> usize {
794 self.rope_text().line_end_col(line, caret)
795 }
796
797 pub fn select_word(&self, offset: usize) -> (usize, usize) {
798 self.rope_text().select_word(offset)
799 }
800
801 pub fn vline_of_offset(&self, offset: usize, affinity: CursorAffinity) -> VLine {
806 self.lines
807 .vline_of_offset(&self.text_prov(), offset, affinity)
808 }
809
810 pub fn vline_of_line(&self, line: usize) -> VLine {
811 self.lines.vline_of_line(&self.text_prov(), line)
812 }
813
814 pub fn rvline_of_line(&self, line: usize) -> RVLine {
815 self.lines.rvline_of_line(&self.text_prov(), line)
816 }
817
818 pub fn vline_of_rvline(&self, rvline: RVLine) -> VLine {
819 self.lines.vline_of_rvline(&self.text_prov(), rvline)
820 }
821
822 pub fn offset_of_vline(&self, vline: VLine) -> usize {
824 self.lines.offset_of_vline(&self.text_prov(), vline)
825 }
826
827 pub fn vline_col_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (VLine, usize) {
830 self.lines
831 .vline_col_of_offset(&self.text_prov(), offset, affinity)
832 }
833
834 pub fn rvline_of_offset(&self, offset: usize, affinity: CursorAffinity) -> RVLine {
835 self.lines
836 .rvline_of_offset(&self.text_prov(), offset, affinity)
837 }
838
839 pub fn rvline_col_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (RVLine, usize) {
840 self.lines
841 .rvline_col_of_offset(&self.text_prov(), offset, affinity)
842 }
843
844 pub fn offset_of_rvline(&self, rvline: RVLine) -> usize {
845 self.lines.offset_of_rvline(&self.text_prov(), rvline)
846 }
847
848 pub fn vline_info(&self, vline: VLine) -> VLineInfo {
849 let vline = vline.min(self.last_vline());
850 self.iter_vlines(false, vline).next().unwrap()
851 }
852
853 pub fn screen_rvline_info_of_offset(
854 &self,
855 offset: usize,
856 affinity: CursorAffinity,
857 ) -> Option<VLineInfo<()>> {
858 let rvline = self.rvline_of_offset(offset, affinity);
859 self.screen_lines.with_untracked(|screen_lines| {
860 screen_lines
861 .iter_vline_info()
862 .find(|vline_info| vline_info.rvline == rvline)
863 })
864 }
865
866 pub fn rvline_info(&self, rvline: RVLine) -> VLineInfo<()> {
867 let rvline = rvline.min(self.last_rvline());
868 self.iter_rvlines(false, rvline).next().unwrap()
869 }
870
871 pub fn rvline_info_of_offset(&self, offset: usize, affinity: CursorAffinity) -> VLineInfo<()> {
872 let rvline = self.rvline_of_offset(offset, affinity);
873 self.rvline_info(rvline)
874 }
875
876 pub fn first_col<T: std::fmt::Debug>(&self, info: VLineInfo<T>) -> usize {
878 info.first_col(&self.text_prov())
879 }
880
881 pub fn last_col<T: std::fmt::Debug>(&self, info: VLineInfo<T>, caret: bool) -> usize {
883 info.last_col(&self.text_prov(), caret)
884 }
885
886 pub fn max_line_width(&self) -> f64 {
889 self.lines.max_width()
890 }
891
892 pub fn line_point_of_offset(&self, offset: usize, affinity: CursorAffinity) -> Point {
895 let (line, col) = self.offset_to_line_col(offset);
896 self.line_point_of_line_col(line, col, affinity, false)
897 }
898
899 pub fn line_point_of_line_col(
902 &self,
903 line: usize,
904 col: usize,
905 affinity: CursorAffinity,
906 force_affinity: bool,
907 ) -> Point {
908 let text_layout = self.text_layout(line);
909 let index = if force_affinity {
910 text_layout
911 .phantom_text
912 .col_after_force(col, affinity == CursorAffinity::Forward)
913 } else {
914 text_layout
915 .phantom_text
916 .col_after(col, affinity == CursorAffinity::Forward)
917 };
918 hit_position_aff(
919 &text_layout.text,
920 index,
921 affinity == CursorAffinity::Backward,
922 )
923 .point
924 }
925
926 pub fn points_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (Point, Point) {
928 let line = self.line_of_offset(offset);
929 let line_height = f64::from(self.style().line_height(self.id(), line));
930
931 let info = self.screen_lines.with_untracked(|sl| {
932 sl.iter_line_info().find(|info| {
933 info.vline_info.interval.start <= offset && offset <= info.vline_info.interval.end
934 })
935 });
936 let Some(info) = info else {
937 return (Point::new(0.0, 0.0), Point::new(0.0, 0.0));
941 };
942
943 let y = info.vline_y;
944
945 let x = self.line_point_of_offset(offset, affinity).x;
946
947 (Point::new(x, y), Point::new(x, y + line_height))
948 }
949
950 pub fn offset_of_point(&self, mode: Mode, point: Point) -> (usize, bool) {
955 let ((line, col), is_inside) = self.line_col_of_point(mode, point);
956 (self.offset_of_line_col(line, col), is_inside)
957 }
958
959 pub fn line_col_of_point_with_phantom(&self, point: Point) -> (usize, usize) {
961 let line_height = f64::from(self.style().line_height(self.id(), 0));
962 let info = if point.y <= 0.0 {
963 Some(self.first_rvline_info())
964 } else {
965 self.screen_lines
966 .with_untracked(|sl| {
967 sl.iter_line_info().find(|info| {
968 info.vline_y <= point.y && info.vline_y + line_height >= point.y
969 })
970 })
971 .map(|info| info.vline_info)
972 };
973 let info = info.unwrap_or_else(|| {
974 for (y_idx, info) in self.iter_rvlines(false, RVLine::default()).enumerate() {
975 let vline_y = y_idx as f64 * line_height;
976 if vline_y <= point.y && vline_y + line_height >= point.y {
977 return info;
978 }
979 }
980
981 self.last_rvline_info()
982 });
983
984 let rvline = info.rvline;
985 let line = rvline.line;
986 let text_layout = self.text_layout(line);
987
988 let y = text_layout.get_layout_y(rvline.line_index).unwrap_or(0.0);
989
990 let hit_point = text_layout.text.hit_point(Point::new(point.x, y as f64));
991 (line, hit_point.index)
992 }
993
994 pub fn line_col_of_point(&self, mode: Mode, point: Point) -> ((usize, usize), bool) {
999 let line_height = f64::from(self.style().line_height(self.id(), 0));
1001 let info = if point.y <= 0.0 {
1002 Some(self.first_rvline_info())
1003 } else {
1004 self.screen_lines
1005 .with_untracked(|sl| {
1006 sl.iter_line_info().find(|info| {
1007 info.vline_y <= point.y && info.vline_y + line_height >= point.y
1008 })
1009 })
1010 .map(|info| info.vline_info)
1011 };
1012 let info = info.unwrap_or_else(|| {
1013 for (y_idx, info) in self.iter_rvlines(false, RVLine::default()).enumerate() {
1014 let vline_y = y_idx as f64 * line_height;
1015 if vline_y <= point.y && vline_y + line_height >= point.y {
1016 return info;
1017 }
1018 }
1019
1020 self.last_rvline_info()
1021 });
1022
1023 let rvline = info.rvline;
1024 let line = rvline.line;
1025 let text_layout = self.text_layout(line);
1026
1027 let y = text_layout.get_layout_y(rvline.line_index).unwrap_or(0.0);
1028
1029 let hit_point = text_layout.text.hit_point(Point::new(point.x, y as f64));
1030 let col = text_layout.phantom_text.before_col(hit_point.index);
1033 let max_col = self.line_end_col(line, mode != Mode::Normal);
1036 let mut col = col.min(max_col);
1037
1038 if !hit_point.is_inside {
1044 col = info.last_col(&self.text_prov(), true);
1046 }
1047
1048 let tab_width = self.style().tab_width(self.id(), line);
1049 if self.style().atomic_soft_tabs(self.id(), line) && tab_width > 1 {
1050 col = snap_to_soft_tab_line_col(
1051 &self.text(),
1052 line,
1053 col,
1054 SnapDirection::Nearest,
1055 tab_width,
1056 );
1057 }
1058
1059 ((line, col), hit_point.is_inside)
1060 }
1061
1062 pub fn line_horiz_col(&self, line: usize, horiz: &ColPosition, caret: bool) -> usize {
1064 match *horiz {
1065 ColPosition::Col(x) => {
1066 let text_layout = self.text_layout(line);
1069 let hit_point = text_layout.text.hit_point(Point::new(x, 0.0));
1070 let n = hit_point.index;
1071 let col = text_layout.phantom_text.before_col(n);
1072
1073 col.min(self.line_end_col(line, caret))
1074 }
1075 ColPosition::End => self.line_end_col(line, caret),
1076 ColPosition::Start => 0,
1077 ColPosition::FirstNonBlank => self.first_non_blank_character_on_line(line),
1078 }
1079 }
1080
1081 pub fn rvline_horiz_col(
1084 &self,
1085 RVLine { line, line_index }: RVLine,
1086 horiz: &ColPosition,
1087 caret: bool,
1088 ) -> usize {
1089 match *horiz {
1090 ColPosition::Col(x) => {
1091 let text_layout = self.text_layout(line);
1092 let y_pos = text_layout
1093 .text
1094 .layout_runs()
1095 .nth(line_index)
1096 .map(|run| run.line_y)
1097 .or_else(|| text_layout.text.layout_runs().last().map(|run| run.line_y))
1098 .unwrap_or(0.0);
1099 let hit_point = text_layout.text.hit_point(Point::new(x, y_pos as f64));
1100 let n = hit_point.index;
1101 let col = text_layout.phantom_text.before_col(n);
1102
1103 col.min(self.line_end_col(line, caret))
1104 }
1105 _ => self.line_horiz_col(line, horiz, caret),
1107 }
1108 }
1109
1110 pub fn move_right(&self, offset: usize, mode: Mode, count: usize) -> usize {
1113 self.rope_text().move_right(offset, mode, count)
1114 }
1115
1116 pub fn move_left(&self, offset: usize, mode: Mode, count: usize) -> usize {
1119 self.rope_text().move_left(offset, mode, count)
1120 }
1121}
1122
1123impl std::fmt::Debug for Editor {
1124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1125 f.debug_tuple("Editor").field(&self.id).finish()
1126 }
1127}
1128
1129impl Editor {
1131 pub fn text_layout(&self, line: usize) -> Arc<TextLayoutLine> {
1133 self.text_layout_trigger(line, true)
1134 }
1135
1136 pub fn text_layout_trigger(&self, line: usize, trigger: bool) -> Arc<TextLayoutLine> {
1137 let cache_rev = self.doc().cache_rev().get_untracked();
1138 self.lines
1139 .get_init_text_layout(cache_rev, self.config_id(), self, line, trigger)
1140 }
1141
1142 fn try_get_text_layout(&self, line: usize) -> Option<Arc<TextLayoutLine>> {
1143 let cache_rev = self.doc().cache_rev().get_untracked();
1144 self.lines
1145 .try_get_text_layout(cache_rev, self.config_id(), line)
1146 }
1147
1148 fn new_whitespace_layout(
1152 line_content: &str,
1153 text_layout: &TextLayout,
1154 phantom: &PhantomTextLine,
1155 render_whitespace: RenderWhitespace,
1156 ) -> Option<Vec<(char, (f64, f64))>> {
1157 let mut render_leading = false;
1158 let mut render_boundary = false;
1159 let mut render_between = false;
1160
1161 match render_whitespace {
1163 RenderWhitespace::All => {
1164 render_leading = true;
1165 render_boundary = true;
1166 render_between = true;
1167 }
1168 RenderWhitespace::Boundary => {
1169 render_leading = true;
1170 render_boundary = true;
1171 }
1172 RenderWhitespace::Trailing => {} RenderWhitespace::None => return None,
1174 }
1175
1176 let mut whitespace_buffer = Vec::new();
1177 let mut rendered_whitespaces: Vec<(char, (f64, f64))> = Vec::new();
1178 let mut char_found = false;
1179 let mut col = 0;
1180 for c in line_content.chars() {
1181 match c {
1182 '\t' => {
1183 let col_left = phantom.col_after(col, true);
1184 let col_right = phantom.col_after(col + 1, false);
1185 let x0 = text_layout.hit_position(col_left).point.x;
1186 let x1 = text_layout.hit_position(col_right).point.x;
1187 whitespace_buffer.push(('\t', (x0, x1)));
1188 }
1189 ' ' => {
1190 let col_left = phantom.col_after(col, true);
1191 let col_right = phantom.col_after(col + 1, false);
1192 let x0 = text_layout.hit_position(col_left).point.x;
1193 let x1 = text_layout.hit_position(col_right).point.x;
1194 whitespace_buffer.push((' ', (x0, x1)));
1195 }
1196 _ => {
1197 if (char_found && render_between)
1198 || (char_found && render_boundary && whitespace_buffer.len() > 1)
1199 || (!char_found && render_leading)
1200 {
1201 rendered_whitespaces.extend(whitespace_buffer.iter());
1202 }
1203
1204 char_found = true;
1205 whitespace_buffer.clear();
1206 }
1207 }
1208 col += c.len_utf8();
1209 }
1210 rendered_whitespaces.extend(whitespace_buffer.iter());
1211
1212 Some(rendered_whitespaces)
1213 }
1214}
1215impl TextLayoutProvider for Editor {
1216 fn text(&self) -> Rope {
1218 Editor::text(self)
1219 }
1220
1221 fn new_text_layout(
1222 &self,
1223 line: usize,
1224 _font_size: usize,
1225 _wrap: ResolvedWrap,
1226 ) -> Arc<TextLayoutLine> {
1227 let edid = self.id();
1230 let text = self.rope_text();
1231 let style = self.style();
1232 let doc = self.doc();
1233
1234 let line_content_original = text.line_content(line);
1235
1236 let font_size = style.font_size(edid, line);
1237
1238 let line_content = if let Some(s) = line_content_original.strip_suffix("\r\n") {
1243 format!("{s} ")
1244 } else if let Some(s) = line_content_original.strip_suffix('\n') {
1245 format!("{s} ",)
1246 } else {
1247 line_content_original.to_string()
1248 };
1249 let phantom_text = doc.phantom_text(edid, &self.es.get_untracked(), line);
1251 let line_content = phantom_text.combine_with_text(&line_content);
1252
1253 let family = style.font_family(edid, line);
1254 let attrs = Attrs::new()
1255 .color(self.es.with(|s| s.ed_text_color()))
1256 .family(&family)
1257 .font_size(font_size as f32)
1258 .line_height(LineHeightValue::Px(style.line_height(edid, line)));
1259 let mut attrs_list = AttrsList::new(attrs.clone());
1260
1261 self.es.with_untracked(|es| {
1262 style.apply_attr_styles(edid, es, line, attrs.clone(), &mut attrs_list);
1263 });
1264
1265 for (offset, size, col, phantom) in phantom_text.offset_size_iter() {
1267 let start = col + offset;
1268 let end = start + size;
1269
1270 let mut attrs = attrs.clone();
1271 if let Some(fg) = phantom.fg {
1272 attrs = attrs.color(fg);
1273 } else {
1274 attrs = attrs.color(self.es.with(|es| es.phantom_color()))
1275 }
1276 if let Some(phantom_font_size) = phantom.font_size {
1277 attrs = attrs.font_size(phantom_font_size.min(font_size) as f32);
1278 }
1279 attrs_list.add_span(start..end, attrs);
1280 }
1287
1288 let mut text_layout = TextLayout::new();
1289 text_layout.set_tab_width(style.tab_width(edid, line));
1291 text_layout.set_text(&line_content, attrs_list);
1292
1293 match self.es.with(|s| s.wrap_method()) {
1295 WrapMethod::None => {}
1296 WrapMethod::EditorWidth => {
1297 let width = self.viewport.get_untracked().width();
1298 text_layout.set_wrap(Wrap::WordOrGlyph);
1299 text_layout.set_size(width as f32, f32::MAX);
1300 }
1301 WrapMethod::WrapWidth { width } => {
1302 text_layout.set_wrap(Wrap::WordOrGlyph);
1303 text_layout.set_size(width, f32::MAX);
1304 }
1305 WrapMethod::WrapColumn { .. } => {}
1307 }
1308
1309 let whitespaces = Self::new_whitespace_layout(
1310 &line_content_original,
1311 &text_layout,
1312 &phantom_text,
1313 self.es.with(|s| s.render_whitespace()),
1314 );
1315
1316 let indent_line = style.indent_line(edid, line, &line_content_original);
1317
1318 let indent = if indent_line != line {
1319 let layout = self.try_get_text_layout(indent_line).unwrap_or_else(|| {
1322 self.new_text_layout(
1323 indent_line,
1324 style.font_size(edid, indent_line),
1325 self.lines.wrap(),
1326 )
1327 });
1328 layout.indent + 1.0
1329 } else {
1330 let offset = text.first_non_blank_character_on_line(indent_line);
1331 let (_, col) = text.offset_to_line_col(offset);
1332 text_layout.hit_position(col).point.x
1333 };
1334
1335 let mut layout_line = TextLayoutLine {
1336 text: text_layout,
1337 extra_style: Vec::new(),
1338 whitespaces,
1339 indent,
1340 phantom_text,
1341 };
1342 self.es.with_untracked(|es| {
1343 style.apply_layout_styles(edid, es, line, &mut layout_line);
1344 });
1345
1346 Arc::new(layout_line)
1347 }
1348
1349 fn before_phantom_col(&self, line: usize, col: usize) -> usize {
1350 self.doc()
1351 .before_phantom_col(self.id(), &self.es.get_untracked(), line, col)
1352 }
1353
1354 fn has_multiline_phantom(&self) -> bool {
1355 self.doc()
1356 .has_multiline_phantom(self.id(), &self.es.get_untracked())
1357 }
1358}
1359
1360struct EditorFontSizes {
1361 id: EditorId,
1362 style: ReadSignal<Rc<dyn Styling>>,
1363 doc: ReadSignal<Rc<dyn Document>>,
1364}
1365impl LineFontSizeProvider for EditorFontSizes {
1366 fn font_size(&self, line: usize) -> usize {
1367 self.style
1368 .with_untracked(|style| style.font_size(self.id, line))
1369 }
1370
1371 fn cache_id(&self) -> FontSizeCacheId {
1372 let mut hasher = DefaultHasher::new();
1373
1374 self.style
1377 .with_untracked(|style| style.id().hash(&mut hasher));
1378 self.doc
1379 .with_untracked(|doc| doc.cache_rev().get_untracked().hash(&mut hasher));
1380
1381 hasher.finish()
1382 }
1383}
1384
1385const MIN_WRAPPED_WIDTH: f32 = 100.0;
1387
1388fn create_view_effects(cx: Scope, ed: &Editor) {
1392 let ed2 = ed.clone();
1394 let ed3 = ed.clone();
1395 let ed4 = ed.clone();
1396
1397 {
1399 let cursor_info = ed.cursor_info.clone();
1400 let cursor = ed.cursor;
1401 cx.create_effect(move |_| {
1402 cursor.track();
1403 cursor_info.reset();
1404 });
1405 }
1406
1407 let update_screen_lines = |ed: &Editor| {
1408 ed.screen_lines.update(|screen_lines| {
1413 let new_screen_lines = ed.compute_screen_lines(screen_lines.base);
1414
1415 *screen_lines = new_screen_lines;
1416 });
1417 };
1418
1419 ed3.lines.layout_event.listen_with(cx, move |val| {
1422 let ed = &ed2;
1423 match val {
1427 LayoutEvent::CreatedLayout { line, .. } => {
1428 let sl = ed.screen_lines.get_untracked();
1429
1430 let should_update = sl.on_created_layout(ed, line);
1432
1433 if should_update {
1434 untrack(|| {
1435 update_screen_lines(ed);
1436 });
1437
1438 ed2.text_layout_trigger(line, true);
1443 }
1444 }
1445 }
1446 });
1447
1448 let viewport_changed_trigger = cx.create_trigger();
1452
1453 cx.create_effect(move |_| {
1456 let ed = &ed3;
1457
1458 let viewport = ed.viewport.get();
1459
1460 let wrap = match ed.es.with(|s| s.wrap_method()) {
1461 WrapMethod::None => ResolvedWrap::None,
1462 WrapMethod::EditorWidth => {
1463 ResolvedWrap::Width((viewport.width() as f32).max(MIN_WRAPPED_WIDTH))
1464 }
1465 WrapMethod::WrapColumn { .. } => todo!(),
1466 WrapMethod::WrapWidth { width } => ResolvedWrap::Width(width),
1467 };
1468
1469 ed.lines.set_wrap(wrap);
1470
1471 let base = ed.screen_lines.with_untracked(|sl| sl.base);
1473
1474 if viewport != base.with_untracked(|base| base.active_viewport) {
1476 batch(|| {
1477 base.update(|base| {
1478 base.active_viewport = viewport;
1479 });
1480 viewport_changed_trigger.notify();
1483 });
1484 }
1485 });
1486 cx.create_effect(move |_| {
1489 viewport_changed_trigger.track();
1490
1491 update_screen_lines(&ed4);
1492 });
1493}
1494
1495pub fn normal_compute_screen_lines(
1496 editor: &Editor,
1497 base: RwSignal<ScreenLinesBase>,
1498) -> ScreenLines {
1499 let lines = &editor.lines;
1500 let style = editor.style.get();
1501 let line_height = style.line_height(editor.id(), 0);
1503
1504 let (y0, y1) = base.with_untracked(|base| (base.active_viewport.y0, base.active_viewport.y1));
1505 let min_vline = VLine((y0 / line_height as f64).floor() as usize);
1507 let max_vline = VLine((y1 / line_height as f64).ceil() as usize);
1508
1509 let cache_rev = editor.doc.get().cache_rev().get();
1510 editor.lines.check_cache_rev(cache_rev);
1511
1512 let min_info = editor.iter_vlines(false, min_vline).next();
1513
1514 let mut rvlines = Vec::new();
1515 let mut info = HashMap::new();
1516
1517 let Some(min_info) = min_info else {
1518 return ScreenLines {
1519 lines: Rc::new(rvlines),
1520 info: Rc::new(info),
1521 diff_sections: None,
1522 base,
1523 };
1524 };
1525
1526 let count = max_vline.get() - min_vline.get();
1529 let iter = lines
1530 .iter_rvlines_init(
1531 editor.text_prov(),
1532 cache_rev,
1533 editor.config_id(),
1534 min_info.rvline,
1535 false,
1536 )
1537 .take(count);
1538
1539 for (i, vline_info) in iter.enumerate() {
1540 rvlines.push(vline_info.rvline);
1541
1542 let line_height = f64::from(style.line_height(editor.id(), vline_info.rvline.line));
1543
1544 let y_idx = min_vline.get() + i;
1545 let vline_y = y_idx as f64 * line_height;
1546 let line_y = vline_y - vline_info.rvline.line_index as f64 * line_height;
1547
1548 info.insert(
1551 vline_info.rvline,
1552 LineInfo {
1553 y: line_y - y0,
1554 vline_y: vline_y - y0,
1555 vline_info,
1556 },
1557 );
1558 }
1559
1560 ScreenLines {
1561 lines: Rc::new(rvlines),
1562 info: Rc::new(info),
1563 diff_sections: None,
1564 base,
1565 }
1566}
1567
1568#[derive(Clone)]
1571pub struct CursorInfo {
1572 pub hidden: RwSignal<bool>,
1573
1574 pub blink_timer: RwSignal<TimerToken>,
1575 pub should_blink: Rc<dyn Fn() -> bool + 'static>,
1577 pub blink_interval: Rc<dyn Fn() -> u64 + 'static>,
1578}
1579
1580impl CursorInfo {
1581 pub fn new(cx: Scope) -> CursorInfo {
1582 CursorInfo {
1583 hidden: cx.create_rw_signal(false),
1584
1585 blink_timer: cx.create_rw_signal(TimerToken::INVALID),
1586 should_blink: Rc::new(|| true),
1587 blink_interval: Rc::new(|| 500),
1588 }
1589 }
1590
1591 pub fn blink(&self) {
1592 let info = self.clone();
1593 let blink_interval = (info.blink_interval)();
1594 if blink_interval > 0 && (info.should_blink)() {
1595 let blink_timer = info.blink_timer;
1596 let timer_token =
1597 exec_after(Duration::from_millis(blink_interval), move |timer_token| {
1598 if info.blink_timer.try_get_untracked() == Some(timer_token) {
1599 info.hidden.update(|hide| {
1600 *hide = !*hide;
1601 });
1602 info.blink();
1603 }
1604 });
1605 blink_timer.set(timer_token);
1606 }
1607 }
1608
1609 pub fn reset(&self) {
1610 if self.hidden.get_untracked() {
1611 self.hidden.set(false);
1612 }
1613
1614 self.blink_timer.set(TimerToken::INVALID);
1615
1616 self.blink();
1617 }
1618}