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