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, OverflowWrap, TextLayout, TextWrapMode},
20 view::{IntoView, View},
21};
22use floem_editor_core::{
23 buffer::rope_text::{RopeText, RopeTextVal},
24 command::MoveCommand,
25 cursor::{ColPosition, Cursor, CursorAffinity, CursorMode},
26 mode::Mode,
27 movement::Movement,
28 register::Register,
29 selection::Selection,
30 soft_tab::{SnapDirection, snap_to_soft_tab_line_col},
31 word::WordCursor,
32};
33use floem_reactive::{SignalGet, SignalTrack, SignalUpdate, SignalWith, Trigger};
34use lapce_xi_rope::Rope;
35use parley::Affinity;
36
37pub mod actions;
38pub mod color;
39pub mod command;
40pub mod gutter;
41pub mod id;
42pub mod keypress;
43pub mod layout;
44pub mod listener;
45pub mod movement;
46pub mod phantom_text;
47pub mod text;
48pub mod text_document;
49pub mod view;
50pub mod visual_line;
51
52pub use floem_editor_core as core;
53use ui_events::{keyboard::Modifiers, pointer::PointerState};
54
55use self::{
56 command::Command,
57 id::EditorId,
58 layout::TextLayoutLine,
59 phantom_text::PhantomTextLine,
60 text::{Document, Preedit, PreeditData, RenderWhitespace, Styling, WrapMethod},
61 view::{LineInfo, ScreenLines, ScreenLinesBase},
62 visual_line::{
63 ConfigId, FontSizeCacheId, LayoutEvent, LineFontSizeProvider, Lines, RVLine, ResolvedWrap,
64 TextLayoutProvider, VLine, VLineInfo,
65 },
66};
67
68use super::Label;
69
70prop!(pub WrapProp: WrapMethod {} = WrapMethod::EditorWidth);
71impl StylePropValue for WrapMethod {
72 fn debug_view(&self) -> Option<Box<dyn View>> {
73 Some(crate::views::Label::new(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::Label::new(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(Label::new(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: Brush {} = Brush::Solid(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)]
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(crate) editor_view_focused_value: RwSignal<bool>,
172 pub editor_view_focused: Trigger,
173 pub editor_view_focus_lost: Trigger,
174 pub editor_view_id: RwSignal<Option<crate::view::ViewId>>,
175
176 pub scroll_delta: RwSignal<Vec2>,
178 pub scroll_to: RwSignal<Option<Vec2>>,
179
180 lines: Rc<Lines>,
182 pub screen_lines: RwSignal<ScreenLines>,
183
184 pub register: RwSignal<Register>,
186 pub cursor_info: CursorInfo,
188
189 pub last_movement: RwSignal<Movement>,
190
191 pub ime_allowed: RwSignal<bool>,
195 pub(crate) ime_cursor_area: RwSignal<Option<(Point, Size)>>,
196 owns_preedit: RwSignal<bool>,
197
198 pub es: RwSignal<EditorStyle>,
200
201 pub floem_style_id: RwSignal<u64>,
202}
203impl Editor {
204 pub fn new(cx: Scope, doc: Rc<dyn Document>, style: Rc<dyn Styling>, modal: bool) -> Editor {
209 let id = EditorId::next();
210 Editor::new_id(cx, id, doc, style, modal)
211 }
212
213 pub fn new_id(
220 cx: Scope,
221 id: EditorId,
222 doc: Rc<dyn Document>,
223 style: Rc<dyn Styling>,
224 modal: bool,
225 ) -> Editor {
226 let editor = Editor::new_direct(cx, id, doc, style, modal);
227 editor.recreate_view_effects();
228
229 editor
230 }
231
232 pub fn new_direct(
249 cx: Scope,
250 id: EditorId,
251 doc: Rc<dyn Document>,
252 style: Rc<dyn Styling>,
253 modal: bool,
254 ) -> Editor {
255 let cx = cx.create_child();
256
257 let viewport = cx.create_rw_signal(Rect::ZERO);
258 let cursor_mode = if modal {
259 CursorMode::Normal {
260 offset: 0,
261 affinity: CursorAffinity::Backward,
262 }
263 } else {
264 CursorMode::Insert(Selection::caret(0, CursorAffinity::Backward))
265 };
266 let cursor = Cursor::new(cursor_mode, None, None);
267 let cursor = cx.create_rw_signal(cursor);
268
269 let doc = cx.create_rw_signal(doc);
270 let style = cx.create_rw_signal(style);
271
272 let font_sizes = RefCell::new(Rc::new(EditorFontSizes {
273 id,
274 style: style.read_only(),
275 doc: doc.read_only(),
276 }));
277 let lines = Rc::new(Lines::new(cx, font_sizes));
278 let screen_lines = cx.create_rw_signal(ScreenLines::new(cx, viewport.get_untracked()));
279
280 let editor_style = cx.create_rw_signal(EditorStyle::default());
281
282 let ed = Editor {
283 cx: Cell::new(cx),
284 effects_cx: Cell::new(cx.create_child()),
285 id,
286 active: cx.create_rw_signal(false),
287 read_only: cx.create_rw_signal(false),
288 doc,
289 style,
290 cursor,
291 window_origin: cx.create_rw_signal(Point::ZERO),
292 viewport,
293 parent_size: cx.create_rw_signal(Rect::ZERO),
294 scroll_delta: cx.create_rw_signal(Vec2::ZERO),
295 scroll_to: cx.create_rw_signal(None),
296 editor_view_focused_value: cx.create_rw_signal(false),
297 editor_view_focused: cx.create_trigger(),
298 editor_view_focus_lost: cx.create_trigger(),
299 editor_view_id: cx.create_rw_signal(None),
300 lines,
301 screen_lines,
302 register: cx.create_rw_signal(Register::default()),
303 cursor_info: CursorInfo::new(cx),
304 last_movement: cx.create_rw_signal(Movement::Left),
305 ime_allowed: cx.create_rw_signal(false),
306 ime_cursor_area: cx.create_rw_signal(None),
307 owns_preedit: cx.create_rw_signal(false),
308 es: editor_style,
309 floem_style_id: cx.create_rw_signal(0),
310 };
311
312 create_view_effects(ed.effects_cx.get(), &ed);
313
314 ed
315 }
316
317 pub fn id(&self) -> EditorId {
318 self.id
319 }
320
321 pub fn doc(&self) -> Rc<dyn Document> {
323 self.doc.get_untracked()
324 }
325
326 pub fn try_doc(&self) -> Option<Rc<dyn Document>> {
329 self.doc.try_get()
330 }
331
332 pub fn try_doc_untracked(&self) -> Option<Rc<dyn Document>> {
335 self.doc.try_get_untracked()
336 }
337
338 pub fn doc_track(&self) -> Rc<dyn Document> {
339 self.doc.get()
340 }
341
342 pub fn doc_signal(&self) -> RwSignal<Rc<dyn Document>> {
344 self.doc
345 }
346
347 pub fn config_id(&self) -> ConfigId {
348 let style_id = self.style.with(|s| s.id());
349 let floem_style_id = self.floem_style_id;
350 ConfigId::new(style_id, floem_style_id.get_untracked())
351 }
352
353 pub fn recreate_view_effects(&self) {
354 Effect::batch(|| {
355 self.effects_cx.get().dispose();
356 self.effects_cx.set(self.cx.get().create_child());
357 create_view_effects(self.effects_cx.get(), self);
358 });
359 }
360
361 pub fn update_doc(&self, doc: Rc<dyn Document>, styling: Option<Rc<dyn Styling>>) {
363 Effect::batch(|| {
364 self.effects_cx.get().dispose();
366
367 *self.lines.font_sizes.borrow_mut() = Rc::new(EditorFontSizes {
368 id: self.id(),
369 style: self.style.read_only(),
370 doc: self.doc.read_only(),
371 });
372 self.lines.clear(0, None);
373 self.doc.set(doc);
374 if let Some(styling) = styling {
375 self.style.set(styling);
376 }
377 self.screen_lines.update(|screen_lines| {
378 screen_lines.clear(self.viewport.get_untracked());
379 });
380
381 self.effects_cx.set(self.cx.get().create_child());
383 create_view_effects(self.effects_cx.get(), self);
384 });
385 }
386
387 pub fn update_styling(&self, styling: Rc<dyn Styling>) {
388 Effect::batch(|| {
389 self.effects_cx.get().dispose();
391
392 *self.lines.font_sizes.borrow_mut() = Rc::new(EditorFontSizes {
393 id: self.id(),
394 style: self.style.read_only(),
395 doc: self.doc.read_only(),
396 });
397 self.lines.clear(0, None);
398
399 self.style.set(styling);
400
401 self.screen_lines.update(|screen_lines| {
402 screen_lines.clear(self.viewport.get_untracked());
403 });
404
405 self.effects_cx.set(self.cx.get().create_child());
407 create_view_effects(self.effects_cx.get(), self);
408 });
409 }
410
411 pub fn duplicate(&self, editor_id: Option<EditorId>) -> Editor {
412 let doc = self.doc();
413 let style = self.style();
414 let mut editor = Editor::new_direct(
415 self.cx.get(),
416 editor_id.unwrap_or_else(EditorId::next),
417 doc,
418 style,
419 false,
420 );
421
422 Effect::batch(|| {
423 editor.read_only.set(self.read_only.get_untracked());
424 editor.es.set(self.es.get_untracked());
425 editor
426 .floem_style_id
427 .set(self.floem_style_id.get_untracked());
428 editor.cursor.set(self.cursor.get_untracked());
429 editor.scroll_delta.set(self.scroll_delta.get_untracked());
430 editor.scroll_to.set(self.scroll_to.get_untracked());
431 editor.window_origin.set(self.window_origin.get_untracked());
432 editor.viewport.set(self.viewport.get_untracked());
433 editor.parent_size.set(self.parent_size.get_untracked());
434 editor.register.set(self.register.get_untracked());
435 editor.cursor_info = self.cursor_info.clone();
436 editor.last_movement.set(self.last_movement.get_untracked());
437 });
440
441 editor.recreate_view_effects();
442
443 editor
444 }
445
446 pub fn style(&self) -> Rc<dyn Styling> {
448 self.style.get_untracked()
449 }
450
451 pub fn text(&self) -> Rope {
455 self.doc().text()
456 }
457
458 pub fn rope_text(&self) -> RopeTextVal {
460 self.doc().rope_text()
461 }
462
463 pub fn lines(&self) -> &Lines {
464 &self.lines
465 }
466
467 pub fn text_prov(&self) -> &Self {
468 self
469 }
470
471 fn preedit(&self) -> PreeditData {
472 self.doc.with_untracked(|doc| doc.preedit())
473 }
474
475 pub fn set_preedit(&self, text: String, cursor: Option<(usize, usize)>, offset: usize) {
476 Effect::batch(|| {
477 self.doc().cache_rev().update(|cache_rev| {
478 *cache_rev += 1;
479 });
480
481 if self.preedit().preedit.with_untracked(|p| p.is_none()) {
482 self.owns_preedit.set(true);
483 }
484
485 self.preedit().preedit.set(Some(Preedit {
488 text,
489 cursor,
490 offset,
491 }));
492 });
493 }
494
495 pub fn commit_preedit(&self) {
496 if !self.owns_preedit.get_untracked() {
497 return;
498 }
499
500 Effect::batch(|| {
501 self.owns_preedit.set(false);
502
503 let commited = self.preedit().preedit.with_untracked(|preedit| {
504 let Some(preedit) = preedit else {
505 return false;
506 };
507
508 self.receive_char(&preedit.text);
509
510 true
511 });
512
513 if !commited {
514 return;
515 }
516
517 self.preedit().preedit.set(None);
518 self.ime_cursor_area.set(None);
519
520 if self.editor_view_focused_value.get_untracked() {
521 set_ime_allowed(false);
522 set_ime_allowed(true);
523 }
524 });
525 }
526
527 pub fn clear_preedit(&self) {
528 self.owns_preedit.set(false);
529
530 let preedit = self.preedit();
531 if preedit.preedit.with_untracked(|preedit| preedit.is_none()) {
532 return;
533 }
534
535 Effect::batch(|| {
536 preedit.preedit.set(None);
537 self.doc().cache_rev().update(|cache_rev| {
538 *cache_rev += 1;
539 });
540 });
541 }
542
543 pub fn receive_char(&self, c: &str) {
544 self.doc().receive_char(self, c)
545 }
546
547 fn compute_screen_lines(&self, base: RwSignal<ScreenLinesBase>) -> ScreenLines {
548 self.doc().compute_screen_lines(self, base)
552 }
553
554 pub fn pointer_down_primary(&self, state: &PointerState) {
556 self.active.set(true);
557 self.left_click(state);
558 }
559
560 pub fn left_click(&self, state: &PointerState) {
561 match state.count {
562 1 => {
563 self.single_click(state);
564 }
565 2 => {
566 self.double_click(state);
567 }
568 3 => {
569 self.triple_click(state);
570 }
571 _ => {}
572 }
573 }
574
575 pub fn single_click(&self, pointer_event: &PointerState) {
576 self.commit_preedit();
577
578 let mode = self.cursor.with_untracked(|c| c.get_mode());
579 let (new_offset, _, mut affinity) =
580 self.offset_of_point(mode, pointer_event.logical_point());
581
582 if self.preedit().preedit.with_untracked(|p| p.is_some()) {
583 affinity = CursorAffinity::Forward;
585 }
586
587 self.cursor.update(|cursor| {
588 cursor.set_offset(
589 new_offset,
590 affinity,
591 pointer_event.modifiers.shift(),
592 pointer_event.modifiers.alt(),
593 );
594 });
595 }
596
597 pub fn double_click(&self, pointer_event: &PointerState) {
598 self.commit_preedit();
599
600 let mode = self.cursor.with_untracked(|c| c.get_mode());
601 let (mouse_offset, ..) = self.offset_of_point(mode, pointer_event.logical_point());
602 let (start, end) = self.select_word(mouse_offset);
603
604 self.cursor.update(|cursor| {
605 cursor.add_region(
606 start,
607 end,
608 CursorAffinity::Backward,
609 pointer_event.modifiers.shift(),
610 pointer_event.modifiers.alt(),
611 );
612 });
613 }
614
615 pub fn triple_click(&self, pointer_event: &PointerState) {
616 self.commit_preedit();
617
618 let mode = self.cursor.with_untracked(|c| c.get_mode());
619 let (mouse_offset, ..) = self.offset_of_point(mode, pointer_event.logical_point());
620 let line = self.line_of_offset(mouse_offset);
621 let start = self.offset_of_line(line);
622 let end = self.offset_of_line(line + 1);
623
624 self.cursor.update(|cursor| {
625 cursor.add_region(
626 start,
627 end,
628 CursorAffinity::Backward,
629 pointer_event.modifiers.shift(),
630 pointer_event.modifiers.alt(),
631 )
632 });
633 }
634
635 pub fn pointer_move(&self, pointer_event: &PointerState) {
636 let mode = self.cursor.with_untracked(|c| c.get_mode());
637 let (offset, _, affinity) = self.offset_of_point(mode, pointer_event.logical_point());
638 if self.active.get_untracked() && self.cursor.with_untracked(|c| c.offset()) != offset {
639 self.commit_preedit();
640
641 self.cursor.update(|cursor| {
642 cursor.set_offset(offset, affinity, true, pointer_event.modifiers.alt())
643 });
644 }
645 }
646
647 pub fn pointer_up(&self, _pointer_event: &PointerState) {
648 self.active.set(false);
649 }
650
651 fn right_click(&self, pointer_event: &PointerState) {
652 let mode = self.cursor.with_untracked(|c| c.get_mode());
653 let (offset, ..) = self.offset_of_point(mode, pointer_event.logical_point());
654 let doc = self.doc();
655 let pointer_inside_selection = self
656 .cursor
657 .with_untracked(|c| c.edit_selection(&doc.rope_text()).contains(offset));
658 if !pointer_inside_selection {
659 self.single_click(pointer_event);
661 }
662 }
663
664 pub fn page_move(&self, down: bool, mods: Modifiers) {
666 let viewport = self.viewport.get_untracked();
667 let line_height = f64::from(self.line_height(0));
669 let lines = (viewport.height() / line_height / 2.0).round() as usize;
670 let distance = (lines as f64) * line_height;
671 self.scroll_delta
672 .set(Vec2::new(0.0, if down { distance } else { -distance }));
673 let cmd = if down {
674 MoveCommand::Down
675 } else {
676 MoveCommand::Up
677 };
678 let cmd = Command::Move(cmd);
679 self.doc().run_command(self, &cmd, Some(lines), mods);
680 }
681
682 pub fn center_window(&self) {
683 let viewport = self.viewport.get_untracked();
684 let line_height = f64::from(self.line_height(0));
686 let offset = self.cursor.with_untracked(|cursor| cursor.offset());
687 let (line, _col) = self.offset_to_line_col(offset);
688
689 let viewport_center = viewport.height() / 2.0;
690
691 let current_line_position = line as f64 * line_height;
692
693 let desired_top = current_line_position - viewport_center + (line_height / 2.0);
694
695 let scroll_delta = desired_top - viewport.y0;
696
697 self.scroll_delta.set(Vec2::new(0.0, scroll_delta));
698 }
699
700 pub fn top_of_window(&self, scroll_off: usize) {
701 let viewport = self.viewport.get_untracked();
702 let line_height = f64::from(self.line_height(0));
704 let offset = self.cursor.with_untracked(|cursor| cursor.offset());
705 let (line, _col) = self.offset_to_line_col(offset);
706
707 let desired_top = (line.saturating_sub(scroll_off)) as f64 * line_height;
708
709 let scroll_delta = desired_top - viewport.y0;
710
711 self.scroll_delta.set(Vec2::new(0.0, scroll_delta));
712 }
713
714 pub fn bottom_of_window(&self, scroll_off: usize) {
715 let viewport = self.viewport.get_untracked();
716 let line_height = f64::from(self.line_height(0));
718 let offset = self.cursor.with_untracked(|cursor| cursor.offset());
719 let (line, _col) = self.offset_to_line_col(offset);
720
721 let desired_bottom = (line + scroll_off + 1) as f64 * line_height - viewport.height();
722
723 let scroll_delta = desired_bottom - viewport.y0;
724
725 self.scroll_delta.set(Vec2::new(0.0, scroll_delta));
726 }
727
728 pub fn scroll(&self, top_shift: f64, down: bool, count: usize, mods: Modifiers) {
729 let viewport = self.viewport.get_untracked();
730 let line_height = f64::from(self.line_height(0));
732 let diff = line_height * count as f64;
733 let diff = if down { diff } else { -diff };
734
735 let offset = self.cursor.with_untracked(|cursor| cursor.offset());
736 let (line, _col) = self.offset_to_line_col(offset);
737 let top = viewport.y0 + diff + top_shift;
738 let bottom = viewport.y0 + diff + viewport.height();
739
740 let new_line = if (line + 1) as f64 * line_height + line_height > bottom {
741 let line = (bottom / line_height).floor() as usize;
742 line.saturating_sub(2)
743 } else if line as f64 * line_height - line_height < top {
744 let line = (top / line_height).ceil() as usize;
745 line + 1
746 } else {
747 line
748 };
749
750 self.scroll_delta.set(Vec2::new(0.0, diff));
751
752 let res = match new_line.cmp(&line) {
753 Ordering::Greater => Some((MoveCommand::Down, new_line - line)),
754 Ordering::Less => Some((MoveCommand::Up, line - new_line)),
755 _ => None,
756 };
757
758 if let Some((cmd, count)) = res {
759 let cmd = Command::Move(cmd);
760 self.doc().run_command(self, &cmd, Some(count), mods);
761 }
762 }
763
764 pub fn phantom_text(&self, line: usize) -> PhantomTextLine {
767 self.doc()
768 .phantom_text(self.id(), &self.es.get_untracked(), line)
769 }
770
771 pub fn line_height(&self, line: usize) -> f32 {
772 self.style().line_height(self.id(), line)
773 }
774
775 pub fn iter_vlines(
779 &self,
780 backwards: bool,
781 start: VLine,
782 ) -> impl Iterator<Item = VLineInfo> + '_ {
783 self.lines.iter_vlines(self.text_prov(), backwards, start)
784 }
785
786 pub fn iter_vlines_over(
789 &self,
790 backwards: bool,
791 start: VLine,
792 end: VLine,
793 ) -> impl Iterator<Item = VLineInfo> + '_ {
794 self.lines
795 .iter_vlines_over(self.text_prov(), backwards, start, end)
796 }
797
798 pub fn iter_rvlines(
802 &self,
803 backwards: bool,
804 start: RVLine,
805 ) -> impl Iterator<Item = VLineInfo<()>> + '_ {
806 self.lines.iter_rvlines(self.text_prov(), backwards, start)
807 }
808
809 pub fn iter_rvlines_over(
816 &self,
817 backwards: bool,
818 start: RVLine,
819 end_line: usize,
820 ) -> impl Iterator<Item = VLineInfo<()>> + '_ {
821 self.lines
822 .iter_rvlines_over(self.text_prov(), backwards, start, end_line)
823 }
824
825 pub fn first_rvline_info(&self) -> VLineInfo<()> {
828 self.rvline_info(RVLine::default())
829 }
830
831 pub fn num_lines(&self) -> usize {
833 self.rope_text().num_lines()
834 }
835
836 pub fn last_line(&self) -> usize {
838 self.rope_text().last_line()
839 }
840
841 pub fn last_vline(&self) -> VLine {
842 self.lines.last_vline(self.text_prov())
843 }
844
845 pub fn last_rvline(&self) -> RVLine {
846 self.lines.last_rvline(self.text_prov())
847 }
848
849 pub fn last_rvline_info(&self) -> VLineInfo<()> {
850 self.rvline_info(self.last_rvline())
851 }
852
853 pub fn offset_to_line_col(&self, offset: usize) -> (usize, usize) {
857 self.rope_text().offset_to_line_col(offset)
858 }
859
860 pub fn offset_of_line(&self, offset: usize) -> usize {
861 self.rope_text().offset_of_line(offset)
862 }
863
864 pub fn offset_of_line_col(&self, line: usize, col: usize) -> usize {
865 self.rope_text().offset_of_line_col(line, col)
866 }
867
868 pub fn line_of_offset(&self, offset: usize) -> usize {
870 self.rope_text().line_of_offset(offset)
871 }
872
873 pub fn first_non_blank_character_on_line(&self, line: usize) -> usize {
875 self.rope_text().first_non_blank_character_on_line(line)
876 }
877
878 pub fn line_end_col(&self, line: usize, caret: bool) -> usize {
879 self.rope_text().line_end_col(line, caret)
880 }
881
882 pub fn select_word(&self, offset: usize) -> (usize, usize) {
883 self.rope_text().select_word(offset)
884 }
885
886 pub fn vline_of_offset(&self, offset: usize, affinity: CursorAffinity) -> VLine {
892 self.lines
893 .vline_of_offset(&self.text_prov(), offset, affinity)
894 }
895
896 pub fn vline_of_line(&self, line: usize) -> VLine {
897 self.lines.vline_of_line(&self.text_prov(), line)
898 }
899
900 pub fn rvline_of_line(&self, line: usize) -> RVLine {
901 self.lines.rvline_of_line(&self.text_prov(), line)
902 }
903
904 pub fn vline_of_rvline(&self, rvline: RVLine) -> VLine {
905 self.lines.vline_of_rvline(&self.text_prov(), rvline)
906 }
907
908 pub fn offset_of_vline(&self, vline: VLine) -> usize {
910 self.lines.offset_of_vline(&self.text_prov(), vline)
911 }
912
913 pub fn vline_col_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (VLine, usize) {
917 self.lines
918 .vline_col_of_offset(&self.text_prov(), offset, affinity)
919 }
920
921 pub fn rvline_of_offset(&self, offset: usize, affinity: CursorAffinity) -> RVLine {
922 self.lines
923 .rvline_of_offset(&self.text_prov(), offset, affinity)
924 }
925
926 pub fn rvline_col_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (RVLine, usize) {
927 self.lines
928 .rvline_col_of_offset(&self.text_prov(), offset, affinity)
929 }
930
931 pub fn offset_of_rvline(&self, rvline: RVLine) -> usize {
932 self.lines.offset_of_rvline(&self.text_prov(), rvline)
933 }
934
935 pub fn vline_info(&self, vline: VLine) -> VLineInfo {
936 let vline = vline.min(self.last_vline());
937 self.iter_vlines(false, vline).next().unwrap()
938 }
939
940 pub fn screen_rvline_info_of_offset(
941 &self,
942 offset: usize,
943 affinity: CursorAffinity,
944 ) -> Option<VLineInfo<()>> {
945 let rvline = self.rvline_of_offset(offset, affinity);
946 self.screen_lines.with_untracked(|screen_lines| {
947 screen_lines
948 .iter_vline_info()
949 .find(|vline_info| vline_info.rvline == rvline)
950 })
951 }
952
953 pub fn rvline_info(&self, rvline: RVLine) -> VLineInfo<()> {
954 let rvline = rvline.min(self.last_rvline());
955 self.iter_rvlines(false, rvline).next().unwrap()
956 }
957
958 pub fn rvline_info_of_offset(&self, offset: usize, affinity: CursorAffinity) -> VLineInfo<()> {
959 let rvline = self.rvline_of_offset(offset, affinity);
960 self.rvline_info(rvline)
961 }
962
963 pub fn first_col<T: std::fmt::Debug>(&self, info: VLineInfo<T>) -> usize {
965 info.first_col(&self.text_prov())
966 }
967
968 pub fn last_col<T: std::fmt::Debug>(&self, info: VLineInfo<T>, caret: bool) -> usize {
970 info.last_col(&self.text_prov(), caret)
971 }
972
973 pub fn max_line_width(&self) -> f64 {
976 self.lines.max_width()
977 }
978
979 pub fn line_point_of_offset(&self, offset: usize, affinity: CursorAffinity) -> Point {
982 let (line, col) = self.offset_to_line_col(offset);
983 self.line_point_of_line_col(line, col, affinity, false)
984 }
985
986 pub fn line_point_of_line_col(
989 &self,
990 line: usize,
991 col: usize,
992 affinity: CursorAffinity,
993 force_affinity: bool,
994 ) -> Point {
995 let text_layout = self.text_layout(line);
996 let index = if force_affinity {
997 text_layout
998 .phantom_text
999 .col_after_force(col, affinity == CursorAffinity::Forward)
1000 } else {
1001 text_layout
1002 .phantom_text
1003 .col_after(col, affinity == CursorAffinity::Forward)
1004 };
1005
1006 let aff = match affinity {
1007 CursorAffinity::Backward => Affinity::Upstream,
1008 CursorAffinity::Forward => Affinity::Downstream,
1009 };
1010
1011 text_layout.text.cursor_point(index, aff)
1012 }
1013
1014 pub fn points_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (Point, Point) {
1016 let line = self.line_of_offset(offset);
1017 let line_height = f64::from(self.style().line_height(self.id(), line));
1018
1019 let vline = self.rvline_info_of_offset(offset, affinity);
1020 let Some(info) = self.screen_lines.with_untracked(|sl| sl.info(vline.rvline)) else {
1021 return (Point::new(0.0, 0.0), Point::new(0.0, 0.0));
1025 };
1026
1027 let y = info.vline_y;
1028
1029 let x = self.line_point_of_offset(offset, affinity).x;
1030
1031 (Point::new(x, y), Point::new(x, y + line_height))
1032 }
1033
1034 pub fn offset_of_point(&self, mode: Mode, point: Point) -> (usize, bool, CursorAffinity) {
1039 let ((line, col), is_inside, affinity) = self.line_col_of_point(mode, point);
1040 (self.offset_of_line_col(line, col), is_inside, affinity)
1041 }
1042
1043 pub fn line_col_of_point_with_phantom(&self, point: Point) -> (usize, usize) {
1045 let line_height = f64::from(self.style().line_height(self.id(), 0));
1046 let info = if point.y <= 0.0 {
1047 Some(self.first_rvline_info())
1048 } else {
1049 self.screen_lines
1050 .with_untracked(|sl| {
1051 sl.iter_line_info().find(|info| {
1052 info.vline_y <= point.y && info.vline_y + line_height >= point.y
1053 })
1054 })
1055 .map(|info| info.vline_info)
1056 };
1057 let info = info.unwrap_or_else(|| {
1058 for (y_idx, info) in self.iter_rvlines(false, RVLine::default()).enumerate() {
1059 let vline_y = y_idx as f64 * line_height;
1060 if vline_y <= point.y && vline_y + line_height >= point.y {
1061 return info;
1062 }
1063 }
1064
1065 self.last_rvline_info()
1066 });
1067
1068 let rvline = info.rvline;
1069 let line = rvline.line;
1070 let text_layout = self.text_layout(line);
1071
1072 let y = text_layout.get_layout_y(rvline.line_index).unwrap_or(0.0);
1073
1074 let index = text_layout
1075 .text
1076 .hit_test(Point::new(point.x, y as f64))
1077 .map(|cursor| text_layout.text.cursor_to_byte_index(&cursor))
1078 .unwrap_or(0);
1079 (line, index)
1080 }
1081
1082 pub fn line_col_of_point(
1087 &self,
1088 mode: Mode,
1089 point: Point,
1090 ) -> ((usize, usize), bool, CursorAffinity) {
1091 let line_height = f64::from(self.style().line_height(self.id(), 0));
1093 let info = if point.y <= 0.0 {
1094 Some(self.first_rvline_info())
1095 } else {
1096 self.screen_lines
1097 .with_untracked(|sl| {
1098 sl.iter_line_info().find(|info| {
1099 info.vline_y <= point.y && info.vline_y + line_height >= point.y
1100 })
1101 })
1102 .map(|info| info.vline_info)
1103 };
1104 let info = info.unwrap_or_else(|| {
1105 for (y_idx, info) in self.iter_rvlines(false, RVLine::default()).enumerate() {
1106 let vline_y = y_idx as f64 * line_height;
1107 if vline_y <= point.y && vline_y + line_height >= point.y {
1108 return info;
1109 }
1110 }
1111
1112 self.last_rvline_info()
1113 });
1114
1115 let rvline = info.rvline;
1116 let line = rvline.line;
1117 let text_layout = self.text_layout(line);
1118
1119 let y = text_layout.get_layout_y(rvline.line_index).unwrap_or(0.0);
1120
1121 let hit_point = Point::new(point.x, y as f64);
1122 let size = text_layout.text.size();
1123 let is_inside = hit_point.x <= size.width && hit_point.y <= size.height;
1124 let (index, affinity) = text_layout
1125 .text
1126 .hit_test(hit_point)
1127 .map(|cursor| {
1128 (
1129 text_layout.text.cursor_to_byte_index(&cursor),
1130 cursor.affinity(),
1131 )
1132 })
1133 .unwrap_or((0, Affinity::default()));
1134 let mut affinity = match affinity {
1135 Affinity::Upstream => CursorAffinity::Backward,
1136 Affinity::Downstream => CursorAffinity::Forward,
1137 };
1138 let col = text_layout.phantom_text.before_col(index);
1141 let max_col = self.line_end_col(line, mode != Mode::Normal);
1144 let mut col = col.min(max_col);
1145
1146 if !is_inside {
1149 col = info.last_col(&self.text_prov(), true);
1151 }
1152
1153 let tab_width = self.style().tab_width(self.id(), line);
1154 if self.style().atomic_soft_tabs(self.id(), line) && tab_width > 1 {
1155 col = snap_to_soft_tab_line_col(
1156 &self.text(),
1157 line,
1158 col,
1159 SnapDirection::Nearest,
1160 tab_width,
1161 );
1162 affinity = CursorAffinity::Forward;
1163 }
1164
1165 ((line, col), is_inside, affinity)
1166 }
1167
1168 pub fn line_horiz_col(&self, line: usize, horiz: &ColPosition, caret: bool) -> usize {
1170 match *horiz {
1171 ColPosition::Col(x) => {
1172 let text_layout = self.text_layout(line);
1175 let n = text_layout
1176 .text
1177 .hit_test(Point::new(x, 0.0))
1178 .map(|cursor| text_layout.text.cursor_to_byte_index(&cursor))
1179 .unwrap_or(0);
1180 let col = text_layout.phantom_text.before_col(n);
1181
1182 col.min(self.line_end_col(line, caret))
1183 }
1184 ColPosition::End => self.line_end_col(line, caret),
1185 ColPosition::Start => 0,
1186 ColPosition::FirstNonBlank => self.first_non_blank_character_on_line(line),
1187 }
1188 }
1189
1190 pub fn rvline_horiz_col(&self, rvline: RVLine, horiz: &ColPosition, caret: bool) -> usize {
1193 let RVLine { line, line_index } = rvline;
1194
1195 match *horiz {
1196 ColPosition::Col(x) => {
1197 let text_layout = self.text_layout(line);
1198 let line_count = text_layout.text.visual_line_count();
1199 let y_pos = text_layout
1200 .text
1201 .visual_line_y(line_index)
1202 .or_else(|| {
1203 if line_count > 0 {
1204 text_layout.text.visual_line_y(line_count - 1)
1205 } else {
1206 None
1207 }
1208 })
1209 .unwrap_or(0.0);
1210 let n = text_layout
1211 .text
1212 .hit_test(Point::new(x, y_pos as f64))
1213 .map(|cursor| text_layout.text.cursor_to_byte_index(&cursor))
1214 .unwrap_or(0);
1215 let col = text_layout.phantom_text.before_col(n);
1216
1217 col.min(self.line_end_col(line, caret))
1218 }
1219 ColPosition::End => {
1220 let info = self.rvline_info(rvline);
1221 self.last_col(info, caret)
1222 }
1223 ColPosition::Start => {
1224 let info = self.rvline_info(rvline);
1225 self.first_col(info)
1226 }
1227 ColPosition::FirstNonBlank => {
1228 let info = self.rvline_info(rvline);
1229 let rope_text = self.text_prov().rope_text();
1230 let next_offset =
1231 WordCursor::new(rope_text.text(), info.interval.start).next_non_blank_char();
1232
1233 next_offset - info.interval.start + self.first_col(info)
1234 }
1235 }
1236 }
1237
1238 pub fn move_right(&self, offset: usize, mode: Mode, count: usize) -> usize {
1242 self.rope_text().move_right(offset, mode, count)
1243 }
1244
1245 pub fn move_left(&self, offset: usize, mode: Mode, count: usize) -> usize {
1248 self.rope_text().move_left(offset, mode, count)
1249 }
1250}
1251
1252impl std::fmt::Debug for Editor {
1253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1254 f.debug_tuple("Editor").field(&self.id).finish()
1255 }
1256}
1257
1258impl Editor {
1260 pub fn text_layout(&self, line: usize) -> Arc<TextLayoutLine> {
1262 self.text_layout_trigger(line, true)
1263 }
1264
1265 pub fn text_layout_trigger(&self, line: usize, trigger: bool) -> Arc<TextLayoutLine> {
1266 let cache_rev = self.doc().cache_rev().get_untracked();
1267 self.lines
1268 .get_init_text_layout(cache_rev, self.config_id(), self, line, trigger)
1269 }
1270
1271 fn try_get_text_layout(&self, line: usize) -> Option<Arc<TextLayoutLine>> {
1272 let cache_rev = self.doc().cache_rev().get_untracked();
1273 self.lines
1274 .try_get_text_layout(cache_rev, self.config_id(), line)
1275 }
1276
1277 fn new_whitespace_layout(
1281 line_content: &str,
1282 text_layout: &TextLayout,
1283 phantom: &PhantomTextLine,
1284 render_whitespace: RenderWhitespace,
1285 ) -> Option<Vec<(char, (f64, f64))>> {
1286 let mut render_leading = false;
1287 let mut render_boundary = false;
1288 let mut render_between = false;
1289
1290 match render_whitespace {
1292 RenderWhitespace::All => {
1293 render_leading = true;
1294 render_boundary = true;
1295 render_between = true;
1296 }
1297 RenderWhitespace::Boundary => {
1298 render_leading = true;
1299 render_boundary = true;
1300 }
1301 RenderWhitespace::Trailing => {} RenderWhitespace::None => return None,
1303 }
1304
1305 let mut whitespace_buffer = Vec::new();
1306 let mut rendered_whitespaces: Vec<(char, (f64, f64))> = Vec::new();
1307 let mut char_found = false;
1308 let mut col = 0;
1309 for c in line_content.chars() {
1310 match c {
1311 '\t' => {
1312 let col_left = phantom.col_after(col, true);
1313 let col_right = phantom.col_after(col + 1, false);
1314 let x0 = text_layout.cursor_point(col_left, Affinity::Upstream).x;
1315 let x1 = text_layout.cursor_point(col_right, Affinity::Upstream).x;
1316 whitespace_buffer.push(('\t', (x0, x1)));
1317 }
1318 ' ' => {
1319 let col_left = phantom.col_after(col, true);
1320 let col_right = phantom.col_after(col + 1, false);
1321 let x0 = text_layout.cursor_point(col_left, Affinity::Upstream).x;
1322 let x1 = text_layout.cursor_point(col_right, Affinity::Upstream).x;
1323 whitespace_buffer.push((' ', (x0, x1)));
1324 }
1325 _ => {
1326 if (char_found && render_between)
1327 || (char_found && render_boundary && whitespace_buffer.len() > 1)
1328 || (!char_found && render_leading)
1329 {
1330 rendered_whitespaces.extend(whitespace_buffer.iter());
1331 }
1332
1333 char_found = true;
1334 whitespace_buffer.clear();
1335 }
1336 }
1337 col += c.len_utf8();
1338 }
1339 rendered_whitespaces.extend(whitespace_buffer.iter());
1340
1341 Some(rendered_whitespaces)
1342 }
1343}
1344impl TextLayoutProvider for Editor {
1345 fn text(&self) -> Rope {
1347 Editor::text(self)
1348 }
1349
1350 fn new_text_layout(
1351 &self,
1352 line: usize,
1353 _font_size: usize,
1354 _wrap: ResolvedWrap,
1355 ) -> Arc<TextLayoutLine> {
1356 let edid = self.id();
1359 let text = self.rope_text();
1360 let style = self.style();
1361 let doc = self.doc();
1362
1363 let line_content_original = text.line_content(line);
1364
1365 let font_size = style.font_size(edid, line);
1366
1367 let line_content = if let Some(s) = line_content_original.strip_suffix("\r\n") {
1372 format!("{s} ")
1373 } else if let Some(s) = line_content_original.strip_suffix('\n') {
1374 format!("{s} ",)
1375 } else {
1376 line_content_original.to_string()
1377 };
1378 let phantom_text = doc.phantom_text(edid, &self.es.get_untracked(), line);
1380 let line_content = phantom_text.combine_with_text(&line_content);
1381
1382 let family = style.font_family(edid, line);
1383 let attrs = Attrs::new()
1384 .color(self.es.with(|s| s.ed_text_color()))
1385 .family(&family)
1386 .font_size(font_size as f32)
1387 .line_height(LineHeightValue::Pt(style.line_height(edid, line)));
1388 let mut attrs_list = AttrsList::new(attrs.clone());
1389
1390 self.es.with_untracked(|es| {
1391 style.apply_attr_styles(edid, es, line, attrs.clone(), &mut attrs_list);
1392 });
1393
1394 for (offset, size, col, phantom) in phantom_text.offset_size_iter() {
1396 let start = col + offset;
1397 let end = start + size;
1398
1399 let mut attrs = attrs.clone();
1400 if let Some(fg) = phantom.fg {
1401 attrs = attrs.color(fg);
1402 } else {
1403 attrs = attrs.color(self.es.with(|es| es.phantom_color()))
1404 }
1405 if let Some(phantom_font_size) = phantom.font_size {
1406 attrs = attrs.font_size(phantom_font_size.min(font_size) as f32);
1407 }
1408 attrs_list.add_span(start..end, attrs);
1409 }
1416
1417 let mut text_layout = TextLayout::new();
1418 text_layout.set_tab_width(style.tab_width(edid, line));
1420 text_layout.set_text(&line_content, attrs_list, None);
1421
1422 match self.es.with(|s| s.wrap_method()) {
1423 WrapMethod::None => {}
1424 WrapMethod::EditorWidth => {
1425 let width = self.viewport.get_untracked().width();
1426 text_layout.set_text_wrap_mode(TextWrapMode::Wrap);
1427 text_layout.set_overflow_wrap(OverflowWrap::BreakWord);
1428 text_layout.set_size(width as f32, f32::MAX);
1429 }
1430 WrapMethod::WrapWidth { width } => {
1431 text_layout.set_text_wrap_mode(TextWrapMode::Wrap);
1432 text_layout.set_overflow_wrap(OverflowWrap::BreakWord);
1433 text_layout.set_size(width, f32::MAX);
1434 }
1435 WrapMethod::WrapColumn { .. } => {}
1437 }
1438
1439 let whitespaces = Self::new_whitespace_layout(
1440 &line_content_original,
1441 &text_layout,
1442 &phantom_text,
1443 self.es.with(|s| s.render_whitespace()),
1444 );
1445
1446 let indent_line = style.indent_line(edid, line, &line_content_original);
1447
1448 let indent = if indent_line != line {
1449 let layout = self.try_get_text_layout(indent_line).unwrap_or_else(|| {
1452 self.new_text_layout(
1453 indent_line,
1454 style.font_size(edid, indent_line),
1455 self.lines.wrap(),
1456 )
1457 });
1458 layout.indent + 1.0
1459 } else {
1460 let offset = text.first_non_blank_character_on_line(indent_line);
1461 let (_, col) = text.offset_to_line_col(offset);
1462 text_layout.cursor_point(col, Affinity::Upstream).x
1463 };
1464
1465 let mut layout_line = TextLayoutLine {
1466 text: text_layout,
1467 extra_style: Vec::new(),
1468 whitespaces,
1469 indent,
1470 phantom_text,
1471 };
1472 self.es.with_untracked(|es| {
1473 style.apply_layout_styles(edid, es, line, &mut layout_line);
1474 });
1475
1476 Arc::new(layout_line)
1477 }
1478
1479 fn before_phantom_col(&self, line: usize, col: usize) -> usize {
1480 self.doc()
1481 .before_phantom_col(self.id(), &self.es.get_untracked(), line, col)
1482 }
1483
1484 fn has_multiline_phantom(&self) -> bool {
1485 self.doc()
1486 .has_multiline_phantom(self.id(), &self.es.get_untracked())
1487 }
1488}
1489
1490struct EditorFontSizes {
1491 id: EditorId,
1492 style: ReadSignal<Rc<dyn Styling>>,
1493 doc: ReadSignal<Rc<dyn Document>>,
1494}
1495impl LineFontSizeProvider for EditorFontSizes {
1496 fn font_size(&self, line: usize) -> usize {
1497 self.style
1498 .with_untracked(|style| style.font_size(self.id, line))
1499 }
1500
1501 fn cache_id(&self) -> FontSizeCacheId {
1502 let mut hasher = DefaultHasher::new();
1503
1504 self.style
1507 .with_untracked(|style| style.id().hash(&mut hasher));
1508 self.doc
1509 .with_untracked(|doc| doc.cache_rev().get_untracked().hash(&mut hasher));
1510
1511 hasher.finish()
1512 }
1513}
1514
1515const MIN_WRAPPED_WIDTH: f32 = 100.0;
1517
1518fn create_view_effects(cx: Scope, ed: &Editor) {
1522 let ed2 = ed.clone();
1524 let ed3 = ed.clone();
1525 let ed4 = ed.clone();
1526
1527 {
1529 let cursor_info = ed.cursor_info.clone();
1530 let cursor = ed.cursor;
1531 cx.create_effect(move |_| {
1532 cursor.track();
1533 cursor_info.reset();
1534 });
1535 }
1536
1537 let update_screen_lines = |ed: &Editor| {
1538 ed.screen_lines.update(|screen_lines| {
1543 let new_screen_lines = ed.compute_screen_lines(screen_lines.base);
1544
1545 *screen_lines = new_screen_lines;
1546 });
1547 };
1548
1549 ed3.lines.layout_event.listen_with(cx, move |val| {
1552 let ed = &ed2;
1553 match val {
1557 LayoutEvent::CreatedLayout { line, .. } => {
1558 let sl = ed.screen_lines.get_untracked();
1559
1560 let should_update = sl.on_created_layout(ed, line);
1562
1563 if should_update {
1564 Effect::untrack(|| {
1565 update_screen_lines(ed);
1566 });
1567
1568 ed2.text_layout_trigger(line, true);
1573 }
1574 }
1575 }
1576 });
1577
1578 let viewport_changed_trigger = cx.create_trigger();
1582
1583 cx.create_effect(move |_| {
1586 let ed = &ed3;
1587
1588 let viewport = ed.viewport.get();
1589
1590 let wrap = match ed.es.with(|s| s.wrap_method()) {
1591 WrapMethod::None => ResolvedWrap::None,
1592 WrapMethod::EditorWidth => {
1593 ResolvedWrap::Width((viewport.width() as f32).max(MIN_WRAPPED_WIDTH))
1594 }
1595 WrapMethod::WrapColumn { .. } => todo!(),
1596 WrapMethod::WrapWidth { width } => ResolvedWrap::Width(width),
1597 };
1598
1599 ed.lines.set_wrap(wrap);
1600
1601 let base = ed.screen_lines.with_untracked(|sl| sl.base);
1603
1604 if viewport != base.with_untracked(|base| base.active_viewport) {
1606 Effect::batch(|| {
1607 base.update(|base| {
1608 base.active_viewport = viewport;
1609 });
1610 viewport_changed_trigger.notify();
1613 });
1614 }
1615 });
1616 cx.create_effect(move |_| {
1619 viewport_changed_trigger.track();
1620
1621 update_screen_lines(&ed4);
1622 });
1623}
1624
1625pub fn normal_compute_screen_lines(
1626 editor: &Editor,
1627 base: RwSignal<ScreenLinesBase>,
1628) -> ScreenLines {
1629 let lines = &editor.lines;
1630 let style = editor.style.get();
1631 let line_height = style.line_height(editor.id(), 0);
1633
1634 let (y0, y1) = base.with_untracked(|base| (base.active_viewport.y0, base.active_viewport.y1));
1635 let min_vline = VLine((y0 / line_height as f64).floor() as usize);
1637 let max_vline = VLine((y1 / line_height as f64).ceil() as usize);
1638
1639 let cache_rev = editor.doc.get().cache_rev().get();
1640 editor.lines.check_cache_rev(cache_rev);
1641
1642 let min_info = editor.iter_vlines(false, min_vline).next();
1643
1644 let mut rvlines = Vec::new();
1645 let mut info = HashMap::new();
1646
1647 let Some(min_info) = min_info else {
1648 return ScreenLines {
1649 lines: Rc::new(rvlines),
1650 info: Rc::new(info),
1651 diff_sections: None,
1652 base,
1653 };
1654 };
1655
1656 let count = max_vline.get() - min_vline.get();
1659 let iter = lines
1660 .iter_rvlines_init(
1661 editor.text_prov(),
1662 cache_rev,
1663 editor.config_id(),
1664 min_info.rvline,
1665 false,
1666 )
1667 .take(count);
1668
1669 for (i, vline_info) in iter.enumerate() {
1670 rvlines.push(vline_info.rvline);
1671
1672 let line_height = f64::from(style.line_height(editor.id(), vline_info.rvline.line));
1673
1674 let y_idx = min_vline.get() + i;
1675 let vline_y = y_idx as f64 * line_height;
1676 let line_y = vline_y - vline_info.rvline.line_index as f64 * line_height;
1677
1678 info.insert(
1681 vline_info.rvline,
1682 LineInfo {
1683 y: line_y - y0,
1684 vline_y: vline_y - y0,
1685 vline_info,
1686 },
1687 );
1688 }
1689
1690 ScreenLines {
1691 lines: Rc::new(rvlines),
1692 info: Rc::new(info),
1693 diff_sections: None,
1694 base,
1695 }
1696}
1697
1698#[derive(Clone)]
1701pub struct CursorInfo {
1702 pub hidden: RwSignal<bool>,
1703
1704 pub blink_timer: RwSignal<TimerToken>,
1705 pub should_blink: Rc<dyn Fn() -> bool + 'static>,
1707 pub blink_interval: Rc<dyn Fn() -> u64 + 'static>,
1708}
1709
1710impl CursorInfo {
1711 pub fn new(cx: Scope) -> CursorInfo {
1712 CursorInfo {
1713 hidden: cx.create_rw_signal(false),
1714
1715 blink_timer: cx.create_rw_signal(TimerToken::INVALID),
1716 should_blink: Rc::new(|| true),
1717 blink_interval: Rc::new(|| 500),
1718 }
1719 }
1720
1721 pub fn blink(&self) {
1722 let info = self.clone();
1723 let blink_interval = (info.blink_interval)();
1724 if blink_interval > 0 && (info.should_blink)() {
1725 let blink_timer = info.blink_timer;
1726 let timer_token =
1727 exec_after(Duration::from_millis(blink_interval), move |timer_token| {
1728 if info.blink_timer.try_get_untracked() == Some(timer_token) {
1729 info.hidden.update(|hide| {
1730 *hide = !*hide;
1731 });
1732 info.blink();
1733 }
1734 });
1735 blink_timer.set(timer_token);
1736 }
1737 }
1738
1739 pub fn reset(&self) {
1740 if self.hidden.get_untracked() {
1741 self.hidden.set(false);
1742 }
1743
1744 self.blink_timer.set(TimerToken::INVALID);
1745
1746 self.blink();
1747 }
1748}