floem/views/editor/
mod.rs

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        // Whether line numbers are relative in modal mode
118        pub modal_relative_line: ModalRelativeLine,
119        // Whether to insert the indent that is detected for the file when a tab character
120        // is inputted.
121        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/// The main structure for the editor view itself.  
148/// This can be considered to be the data part of the `View`.
149/// It holds an `Rc<dyn Document>` within as the document it is a view into.  
150#[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    /// Whether you can edit within this editor.
160    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    /// The current scroll position.
176    pub scroll_delta: RwSignal<Vec2>,
177    pub scroll_to: RwSignal<Option<Vec2>>,
178
179    /// Holds the cache of the lines and provides many utility functions for them.
180    lines: Rc<Lines>,
181    pub screen_lines: RwSignal<ScreenLines>,
182
183    /// Modal mode register
184    pub register: RwSignal<Register>,
185    /// Cursor rendering information, such as the cursor blinking state.
186    pub cursor_info: CursorInfo,
187
188    pub last_movement: RwSignal<Movement>,
189
190    /// Whether ime input is allowed.  
191    /// Should not be set manually outside of the specific handling for ime.
192    pub ime_allowed: RwSignal<bool>,
193
194    /// The Editor Style
195    pub es: RwSignal<EditorStyle>,
196
197    pub floem_style_id: RwSignal<u64>,
198}
199impl Editor {
200    /// Create a new editor into the given document, using the styling.  
201    /// `doc`: The backing [`Document`], such as [`TextDocument`](self::text_document::TextDocument)
202    /// `style`: How the editor should be styled, such as [`SimpleStyling`](self::text::SimpleStyling)
203    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    /// Create a new editor into the given document, using the styling.  
209    /// `id` should typically be constructed by [`EditorId::next`]  
210    /// `doc`: The backing [`Document`], such as [`TextDocument`](self::text_document::TextDocument)
211    /// `style`: How the editor should be styled, such as [`SimpleStyling`](self::text::SimpleStyling)
212    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    // TODO: shouldn't this accept an `RwSignal<Rc<dyn Document>>` so that it can listen for
226    // changes in other editors?
227    // TODO: should we really allow callers to arbitrarily specify the Id? That could open up
228    // confusing behavior.
229
230    /// Create a new editor into the given document, using the styling.  
231    /// `id` should typically be constructed by [`EditorId::next`]  
232    /// `doc`: The backing [`Document`], such as [`TextDocument`](self::text_document::TextDocument)
233    /// `style`: How the editor should be styled, such as [`SimpleStyling`](self::text::SimpleStyling)
234    /// This does *not* create the view effects. Use this if you're creating an editor and then
235    /// replacing signals. Invoke [`Editor::recreate_view_effects`] when you are done.
236    /// ```rust,ignore
237    /// let shared_scroll_beyond_last_line = /* ... */;
238    /// let editor = Editor::new_direct(cx, id, doc, style);
239    /// editor.scroll_beyond_last_line.set(shared_scroll_beyond_last_line);
240    /// ```
241    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    /// Get the document untracked
312    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    // TODO: should this be `ReadSignal`? but read signal doesn't have .track
321    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    /// Swap the underlying document out
340    pub fn update_doc(&self, doc: Rc<dyn Document>, styling: Option<Rc<dyn Styling>>) {
341        batch(|| {
342            // Get rid of all the effects
343            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            // Recreate the effects
360            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            // Get rid of all the effects
368            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            // Recreate the effects
384            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            // ?
416            // editor.ime_allowed.set(self.ime_allowed.get_untracked());
417        });
418
419        editor.recreate_view_effects();
420
421        editor
422    }
423
424    /// Get the styling untracked
425    pub fn style(&self) -> Rc<dyn Styling> {
426        self.style.get_untracked()
427    }
428
429    /// Get the text of the document  
430    /// You should typically prefer [`Self::rope_text`]
431    pub fn text(&self) -> Rope {
432        self.doc().text()
433    }
434
435    /// Get the [`RopeTextVal`] from `doc` untracked
436    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        // This function *cannot* access `ScreenLines` with how it is currently implemented.
504        // This is being called from within an update to screen lines.
505
506        self.doc().compute_screen_lines(self, base)
507    }
508
509    /// Default handler for `PointerDown` event
510    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            // change affinity to display caret after preedit
540            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            // move cursor to pointer position if outside current selection
612            self.single_click(pointer_event);
613        }
614    }
615
616    // TODO: should this have modifiers state in its api
617    pub fn page_move(&self, down: bool, mods: Modifiers) {
618        let viewport = self.viewport.get_untracked();
619        // TODO: don't assume line height is constant
620        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        // TODO: don't assume line height is constant
637        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        // TODO: don't assume line height is constant
655        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        // TODO: don't assume line height is constant
669        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        // TODO: don't assume line height is constant
683        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    // === Information ===
717
718    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    // === Line Information ===
728
729    /// Iterate over the visual lines in the view, starting at the given line.
730    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    /// Iterate over the visual lines in the view, starting at the given line and ending at the
739    /// given line. `start_line..end_line`
740    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    /// Iterator over *relative* [`VLineInfo`]s, starting at the buffer line, `start_line`.  
751    /// The `visual_line`s provided by this will start at 0 from your `start_line`.  
752    /// This is preferable over `iter_lines` if you do not need to absolute visual line value.
753    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    /// Iterator over *relative* [`VLineInfo`]s, starting at the buffer line, `start_line` and
762    /// ending at `end_line`.  
763    /// `start_line..end_line`  
764    /// This is preferable over `iter_lines` if you do not need to absolute visual line value.
765    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    // ==== Position Information ====
776
777    pub fn first_rvline_info(&self) -> VLineInfo<()> {
778        self.rvline_info(RVLine::default())
779    }
780
781    /// The number of lines in the document.
782    pub fn num_lines(&self) -> usize {
783        self.rope_text().num_lines()
784    }
785
786    /// The last allowed buffer line in the document.
787    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    // ==== Line/Column Positioning ====
804
805    /// Convert an offset into the buffer into a line and idx.  
806    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    /// Get the buffer line of an offset
819    pub fn line_of_offset(&self, offset: usize) -> usize {
820        self.rope_text().line_of_offset(offset)
821    }
822
823    /// Returns the offset into the buffer of the first non blank character on the given line.
824    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    /// `affinity` decides whether an offset at a soft line break is considered to be on the
837    /// previous line or the next line.  
838    /// If `affinity` is `CursorAffinity::Forward` and is at the very end of the wrapped line, then
839    /// the offset is considered to be on the next line.
840    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    /// Get the nearest offset to the start of the visual line.
858    pub fn offset_of_vline(&self, vline: VLine) -> usize {
859        self.lines.offset_of_vline(&self.text_prov(), vline)
860    }
861
862    /// Get the visual line and column of the given offset.  
863    /// The column is before phantom text is applied.
864    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    /// Get the first column of the overall line of the visual line
912    pub fn first_col<T: std::fmt::Debug>(&self, info: VLineInfo<T>) -> usize {
913        info.first_col(&self.text_prov())
914    }
915
916    /// Get the last column in the overall line of the visual line
917    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    // ==== Points of locations ====
922
923    pub fn max_line_width(&self) -> f64 {
924        self.lines.max_width()
925    }
926
927    /// Returns the point into the text layout of the line at the given offset.
928    /// `x` being the leading edge of the character, and `y` being the baseline.
929    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    /// Returns the point into the text layout of the line at the given line and col.
935    /// `x` being the leading edge of the character, and `y` being the baseline.  
936    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    /// Get the (point above, point below) of a particular offset within the editor.
962    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            // TODO: We could do a smarter method where we get the approximate y position
973            // because, for example, this spot could be folded away, and so it would be better to
974            // supply the *nearest* position on the screen.
975            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    /// Get the offset of a particular point within the editor.
986    /// The boolean indicates whether the point is inside the text or not
987    /// Points outside of vertical bounds will return the last line.
988    /// Points outside of horizontal bounds will return the last column on the line.
989    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    /// Get the actual (line, col) of a particular point within the editor.
995    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    /// Get the (line, col) of a particular point within the editor.
1030    /// The boolean indicates whether the point is within the text bounds.
1031    /// Points outside of vertical bounds will return the last line.
1032    /// Points outside of horizontal bounds will return the last column on the line.
1033    pub fn line_col_of_point(
1034        &self,
1035        mode: Mode,
1036        point: Point,
1037    ) -> ((usize, usize), bool, CursorAffinity) {
1038        // TODO: this assumes that line height is constant!
1039        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        // We have to unapply the phantom text shifting in order to get back to the column in
1074        // the actual buffer
1075        let col = text_layout.phantom_text.before_col(hit_point.index);
1076        // Ensure that the column doesn't end up out of bounds, so things like clicking on the far
1077        // right end will just go to the end of the line.
1078        let max_col = self.line_end_col(line, mode != Mode::Normal);
1079        let mut col = col.min(max_col);
1080
1081        // TODO: this is a hack to get around text layouts not including spaces at the end of
1082        // wrapped lines, but we want to be able to click on them
1083        if !hit_point.is_inside {
1084            // TODO(minor): this is probably wrong in some manners
1085            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    // TODO: colposition probably has issues with wrapping?
1104    pub fn line_horiz_col(&self, line: usize, horiz: &ColPosition, caret: bool) -> usize {
1105        match *horiz {
1106            ColPosition::Col(x) => {
1107                // TODO: won't this be incorrect with phantom text? Shouldn't this just use
1108                // line_col_of_point and get the col from that?
1109                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    /// Advance to the right in the manner of the given mode.
1123    /// Get the column from a horizontal at a specific line index (in a text layout)
1124    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    /// Advance to the right in the manner of the given mode.  
1163    /// This is not the same as the [`Movement::Right`] command.
1164    pub fn move_right(&self, offset: usize, mode: Mode, count: usize) -> usize {
1165        self.rope_text().move_right(offset, mode, count)
1166    }
1167
1168    /// Advance to the left in the manner of the given mode.
1169    /// This is not the same as the [`Movement::Left`] command.
1170    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
1181// Text layout creation
1182impl Editor {
1183    // Get the text layout for a document line, creating it if needed.
1184    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    /// Create rendable whitespace layout by creating a new text layout
1201    /// with invisible spaces and special utf8 characters that display
1202    /// the different white space characters.
1203    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        // TODO: render whitespaces only on highlighted text
1214        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 => {} // All configs include rendering trailing whitespace
1225            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    // TODO: should this just return a `Rope`?
1269    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        // TODO: we could share text layouts between different editor views given some knowledge of
1280        // their wrapping
1281        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        // Get the line content with newline characters replaced with spaces
1291        // and the content without the newline characters
1292        // TODO: cache or add some way that text layout is created to auto insert the spaces instead
1293        // though we immediately combine with phantom text so that's a thing.
1294        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        // Combine the phantom text with the line content
1302        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        // Apply phantom text specific styling
1318        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            // if let Some(font_family) = phantom.font_family.clone() {
1333            //     layout_builder = layout_builder.range_attribute(
1334            //         start..end,
1335            //         TextAttribute::FontFamily(font_family),
1336            //     );
1337            // }
1338        }
1339
1340        let mut text_layout = TextLayout::new();
1341        // TODO: we could move tab width setting to be done by the document
1342        text_layout.set_tab_width(style.tab_width(edid, line));
1343        text_layout.set_text(&line_content, attrs_list, None);
1344
1345        // dbg!(self.editor_style.with(|s| s.wrap_method()));
1346        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            // TODO:
1358            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            // TODO: This creates the layout if it isn't already cached, but it doesn't cache the
1372            // result because the current method of managing the cache is not very smart.
1373            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        // TODO: is this actually good enough for comparing cache state?
1427        // We could just have it return an arbitrary type that impl's Eq?
1428        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
1437/// Minimum width that we'll allow the view to be wrapped at.
1438const MIN_WRAPPED_WIDTH: f32 = 100.0;
1439
1440/// Create various reactive effects to update the screen lines whenever relevant parts of the view,
1441/// doc, text layouts, viewport, etc. change.
1442/// This tries to be smart to a degree.
1443fn create_view_effects(cx: Scope, ed: &Editor) {
1444    // Cloning is fun.
1445    let ed2 = ed.clone();
1446    let ed3 = ed.clone();
1447    let ed4 = ed.clone();
1448
1449    // Reset cursor blinking whenever the cursor changes
1450    {
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        // This function should not depend on the viewport signal directly.
1461
1462        // This is wrapped in an update to make any updates-while-updating very obvious
1463        // which they wouldn't be if we computed and then `set`.
1464        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    // Listen for layout events, currently only when a layout is created, and update screen
1472    // lines based on that
1473    ed3.lines.layout_event.listen_with(cx, move |val| {
1474        let ed = &ed2;
1475        // TODO: Move this logic onto screen lines somehow, perhaps just an auxiliary
1476        // function, to avoid getting confused about what is relevant where.
1477
1478        match val {
1479            LayoutEvent::CreatedLayout { line, .. } => {
1480                let sl = ed.screen_lines.get_untracked();
1481
1482                // Intelligently update screen lines, avoiding recalculation if possible
1483                let should_update = sl.on_created_layout(ed, line);
1484
1485                if should_update {
1486                    untrack(|| {
1487                        update_screen_lines(ed);
1488                    });
1489
1490                    // Ensure that it is created even after the base/viewport signals have been
1491                    // updated.
1492                    // But we have to trigger an event since it could alter the screenlines
1493                    // TODO: this has some risk for infinite looping if we're unlucky.
1494                    ed2.text_layout_trigger(line, true);
1495                }
1496            }
1497        }
1498    });
1499
1500    // TODO: should we have some debouncing for editor width? Ideally we'll be fast enough to not
1501    // even need it, though we might not want to use a bunch of cpu whilst resizing anyway.
1502
1503    let viewport_changed_trigger = cx.create_trigger();
1504
1505    // Watch for changes to the viewport so that we can alter the wrapping
1506    // As well as updating the screen lines base
1507    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        // Update the base
1524        let base = ed.screen_lines.with_untracked(|sl| sl.base);
1525
1526        // TODO: should this be a with or with_untracked?
1527        if viewport != base.with_untracked(|base| base.active_viewport) {
1528            batch(|| {
1529                base.update(|base| {
1530                    base.active_viewport = viewport;
1531                });
1532                // TODO: Can I get rid of this and just call update screen lines with an
1533                // untrack around it?
1534                viewport_changed_trigger.notify();
1535            });
1536        }
1537    });
1538    // Watch for when the viewport as changed in a relevant manner
1539    // and for anything that `update_screen_lines` tracks.
1540    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    // TODO: don't assume universal line height!
1554    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    // Get the start and end (visual) lines that are visible in the viewport
1558    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    // TODO: the original was min_line..max_line + 1, are we iterating too little now?
1579    // the iterator is from min_vline..max_vline
1580    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        // Add the information to make it cheap to get in the future.
1601        // This y positions are shifted by the baseline y0
1602        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// TODO: should we put `cursor` on this structure?
1621/// Cursor rendering information
1622#[derive(Clone)]
1623pub struct CursorInfo {
1624    pub hidden: RwSignal<bool>,
1625
1626    pub blink_timer: RwSignal<TimerToken>,
1627    // TODO: should these just be rwsignals?
1628    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}