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