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};
36use floem_reactive::{SignalGet, SignalTrack, SignalUpdate, SignalWith, Trigger};
37use lapce_xi_rope::Rope;
38
39pub mod actions;
40pub mod color;
41pub mod command;
42pub mod gutter;
43pub mod id;
44pub mod keypress;
45pub mod layout;
46pub mod listener;
47pub mod movement;
48pub mod phantom_text;
49pub mod text;
50pub mod text_document;
51pub mod view;
52pub mod visual_line;
53
54pub use floem_editor_core as core;
55use peniko::Brush;
56
57use self::{
58    command::Command,
59    id::EditorId,
60    layout::TextLayoutLine,
61    phantom_text::PhantomTextLine,
62    text::{Document, Preedit, PreeditData, RenderWhitespace, Styling, WrapMethod},
63    view::{LineInfo, ScreenLines, ScreenLinesBase},
64    visual_line::{
65        hit_position_aff, ConfigId, FontSizeCacheId, LayoutEvent, LineFontSizeProvider, Lines,
66        RVLine, ResolvedWrap, TextLayoutProvider, VLine, VLineInfo,
67    },
68};
69
70prop!(pub WrapProp: WrapMethod {} = WrapMethod::EditorWidth);
71impl StylePropValue for WrapMethod {
72    fn debug_view(&self) -> Option<Box<dyn View>> {
73        Some(crate::views::text(self).into_any())
74    }
75}
76prop!(pub CursorSurroundingLines: usize {} = 1);
77prop!(pub ScrollBeyondLastLine: bool {} = false);
78prop!(pub ShowIndentGuide: bool {} = false);
79prop!(pub Modal: bool {} = false);
80prop!(pub ModalRelativeLine: bool {} = false);
81prop!(pub SmartTab: bool {} = false);
82prop!(pub PhantomColor: Color {} = palette::css::DIM_GRAY);
83prop!(pub PlaceholderColor: Color {} = palette::css::DIM_GRAY);
84prop!(pub PreeditUnderlineColor: Color {} = palette::css::WHITE);
85prop!(pub RenderWhitespaceProp: RenderWhitespace {} = RenderWhitespace::None);
86impl StylePropValue for RenderWhitespace {
87    fn debug_view(&self) -> Option<Box<dyn View>> {
88        Some(crate::views::text(self).into_any())
89    }
90}
91prop!(pub IndentStyleProp: IndentStyle {} = IndentStyle::Spaces(4));
92impl StylePropValue for IndentStyle {
93    fn debug_view(&self) -> Option<Box<dyn View>> {
94        Some(text(self).into_any())
95    }
96}
97prop!(pub DropdownShadow: Option<Color> {} = None);
98prop!(pub Foreground: Color { inherited } = Color::from_rgb8(0x38, 0x3A, 0x42));
99prop!(pub Focus: Option<Color> {} = None);
100prop!(pub SelectionColor: Color {} = palette::css::BLACK.with_alpha(0.5));
101prop!(pub CurrentLineColor: Option<Color> {  } = None);
102prop!(pub Link: Option<Color> {} = None);
103prop!(pub VisibleWhitespaceColor: Color {} = palette::css::TRANSPARENT);
104prop!(pub IndentGuideColor: Color {} = palette::css::TRANSPARENT);
105prop!(pub StickyHeaderBackground: Option<Color> {} = None);
106
107prop_extractor! {
108    pub EditorStyle {
109        pub text_color: TextColor,
110        pub phantom_color: PhantomColor,
111        pub placeholder_color: PlaceholderColor,
112        pub preedit_underline_color: PreeditUnderlineColor,
113        pub show_indent_guide: ShowIndentGuide,
114        pub modal: Modal,
115        // Whether line numbers are relative in modal mode
116        pub modal_relative_line: ModalRelativeLine,
117        // Whether to insert the indent that is detected for the file when a tab character
118        // is inputted.
119        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/// The main structure for the editor view itself.  
146/// This can be considered to be the data part of the `View`.
147/// It holds an `Rc<dyn Document>` within as the document it is a view into.  
148#[derive(Clone)]
149pub struct Editor {
150    pub cx: Cell<Scope>,
151    effects_cx: Cell<Scope>,
152
153    id: EditorId,
154
155    pub active: RwSignal<bool>,
156
157    /// Whether you can edit within this editor.
158    pub read_only: RwSignal<bool>,
159
160    pub(crate) doc: RwSignal<Rc<dyn Document>>,
161    pub(crate) style: RwSignal<Rc<dyn Styling>>,
162
163    pub cursor: RwSignal<Cursor>,
164
165    pub window_origin: RwSignal<Point>,
166    pub viewport: RwSignal<Rect>,
167    pub parent_size: RwSignal<Rect>,
168
169    pub editor_view_focused: Trigger,
170    pub editor_view_focus_lost: Trigger,
171    pub editor_view_id: RwSignal<Option<crate::id::ViewId>>,
172
173    /// The current scroll position.
174    pub scroll_delta: RwSignal<Vec2>,
175    pub scroll_to: RwSignal<Option<Vec2>>,
176
177    /// Holds the cache of the lines and provides many utility functions for them.
178    lines: Rc<Lines>,
179    pub screen_lines: RwSignal<ScreenLines>,
180
181    /// Modal mode register
182    pub register: RwSignal<Register>,
183    /// Cursor rendering information, such as the cursor blinking state.
184    pub cursor_info: CursorInfo,
185
186    pub last_movement: RwSignal<Movement>,
187
188    /// Whether ime input is allowed.  
189    /// Should not be set manually outside of the specific handling for ime.
190    pub ime_allowed: RwSignal<bool>,
191
192    /// The Editor Style
193    pub es: RwSignal<EditorStyle>,
194
195    pub floem_style_id: RwSignal<u64>,
196}
197impl Editor {
198    /// Create a new editor into the given document, using the styling.  
199    /// `doc`: The backing [`Document`], such as [`TextDocument`](self::text_document::TextDocument)
200    /// `style`: How the editor should be styled, such as [`SimpleStyling`](self::text::SimpleStyling)
201    pub fn new(cx: Scope, doc: Rc<dyn Document>, style: Rc<dyn Styling>, modal: bool) -> Editor {
202        let id = EditorId::next();
203        Editor::new_id(cx, id, doc, style, modal)
204    }
205
206    /// Create a new editor into the given document, using the styling.  
207    /// `id` should typically be constructed by [`EditorId::next`]  
208    /// `doc`: The backing [`Document`], such as [`TextDocument`](self::text_document::TextDocument)
209    /// `style`: How the editor should be styled, such as [`SimpleStyling`](self::text::SimpleStyling)
210    pub fn new_id(
211        cx: Scope,
212        id: EditorId,
213        doc: Rc<dyn Document>,
214        style: Rc<dyn Styling>,
215        modal: bool,
216    ) -> Editor {
217        let editor = Editor::new_direct(cx, id, doc, style, modal);
218        editor.recreate_view_effects();
219
220        editor
221    }
222
223    // TODO: shouldn't this accept an `RwSignal<Rc<dyn Document>>` so that it can listen for
224    // changes in other editors?
225    // TODO: should we really allow callers to arbitrarily specify the Id? That could open up
226    // confusing behavior.
227
228    /// Create a new editor into the given document, using the styling.  
229    /// `id` should typically be constructed by [`EditorId::next`]  
230    /// `doc`: The backing [`Document`], such as [`TextDocument`](self::text_document::TextDocument)
231    /// `style`: How the editor should be styled, such as [`SimpleStyling`](self::text::SimpleStyling)
232    /// This does *not* create the view effects. Use this if you're creating an editor and then
233    /// replacing signals. Invoke [`Editor::recreate_view_effects`] when you are done.
234    /// ```rust,ignore
235    /// let shared_scroll_beyond_last_line = /* ... */;
236    /// let editor = Editor::new_direct(cx, id, doc, style);
237    /// editor.scroll_beyond_last_line.set(shared_scroll_beyond_last_line);
238    /// ```
239    pub fn new_direct(
240        cx: Scope,
241        id: EditorId,
242        doc: Rc<dyn Document>,
243        style: Rc<dyn Styling>,
244        modal: bool,
245    ) -> Editor {
246        let cx = cx.create_child();
247
248        let viewport = cx.create_rw_signal(Rect::ZERO);
249        let cursor_mode = if modal {
250            CursorMode::Normal(0)
251        } else {
252            CursorMode::Insert(Selection::caret(0))
253        };
254        let cursor = Cursor::new(cursor_mode, None, None);
255        let cursor = cx.create_rw_signal(cursor);
256
257        let doc = cx.create_rw_signal(doc);
258        let style = cx.create_rw_signal(style);
259
260        let font_sizes = RefCell::new(Rc::new(EditorFontSizes {
261            id,
262            style: style.read_only(),
263            doc: doc.read_only(),
264        }));
265        let lines = Rc::new(Lines::new(cx, font_sizes));
266        let screen_lines = cx.create_rw_signal(ScreenLines::new(cx, viewport.get_untracked()));
267
268        let editor_style = cx.create_rw_signal(EditorStyle::default());
269
270        let ed = Editor {
271            cx: Cell::new(cx),
272            effects_cx: Cell::new(cx.create_child()),
273            id,
274            active: cx.create_rw_signal(false),
275            read_only: cx.create_rw_signal(false),
276            doc,
277            style,
278            cursor,
279            window_origin: cx.create_rw_signal(Point::ZERO),
280            viewport,
281            parent_size: cx.create_rw_signal(Rect::ZERO),
282            scroll_delta: cx.create_rw_signal(Vec2::ZERO),
283            scroll_to: cx.create_rw_signal(None),
284            editor_view_focused: cx.create_trigger(),
285            editor_view_focus_lost: cx.create_trigger(),
286            editor_view_id: cx.create_rw_signal(None),
287            lines,
288            screen_lines,
289            register: cx.create_rw_signal(Register::default()),
290            cursor_info: CursorInfo::new(cx),
291            last_movement: cx.create_rw_signal(Movement::Left),
292            ime_allowed: cx.create_rw_signal(false),
293            es: editor_style,
294            floem_style_id: cx.create_rw_signal(0),
295        };
296
297        create_view_effects(ed.effects_cx.get(), &ed);
298
299        ed
300    }
301
302    pub fn id(&self) -> EditorId {
303        self.id
304    }
305
306    /// Get the document untracked
307    pub fn doc(&self) -> Rc<dyn Document> {
308        self.doc.get_untracked()
309    }
310
311    pub fn doc_track(&self) -> Rc<dyn Document> {
312        self.doc.get()
313    }
314
315    // TODO: should this be `ReadSignal`? but read signal doesn't have .track
316    pub fn doc_signal(&self) -> RwSignal<Rc<dyn Document>> {
317        self.doc
318    }
319
320    pub fn config_id(&self) -> ConfigId {
321        let style_id = self.style.with(|s| s.id());
322        let floem_style_id = self.floem_style_id;
323        ConfigId::new(style_id, floem_style_id.get_untracked())
324    }
325
326    pub fn recreate_view_effects(&self) {
327        batch(|| {
328            self.effects_cx.get().dispose();
329            self.effects_cx.set(self.cx.get().create_child());
330            create_view_effects(self.effects_cx.get(), self);
331        });
332    }
333
334    /// Swap the underlying document out
335    pub fn update_doc(&self, doc: Rc<dyn Document>, styling: Option<Rc<dyn Styling>>) {
336        batch(|| {
337            // Get rid of all the effects
338            self.effects_cx.get().dispose();
339
340            *self.lines.font_sizes.borrow_mut() = Rc::new(EditorFontSizes {
341                id: self.id(),
342                style: self.style.read_only(),
343                doc: self.doc.read_only(),
344            });
345            self.lines.clear(0, None);
346            self.doc.set(doc);
347            if let Some(styling) = styling {
348                self.style.set(styling);
349            }
350            self.screen_lines.update(|screen_lines| {
351                screen_lines.clear(self.viewport.get_untracked());
352            });
353
354            // Recreate the effects
355            self.effects_cx.set(self.cx.get().create_child());
356            create_view_effects(self.effects_cx.get(), self);
357        });
358    }
359
360    pub fn update_styling(&self, styling: Rc<dyn Styling>) {
361        batch(|| {
362            // Get rid of all the effects
363            self.effects_cx.get().dispose();
364
365            *self.lines.font_sizes.borrow_mut() = Rc::new(EditorFontSizes {
366                id: self.id(),
367                style: self.style.read_only(),
368                doc: self.doc.read_only(),
369            });
370            self.lines.clear(0, None);
371
372            self.style.set(styling);
373
374            self.screen_lines.update(|screen_lines| {
375                screen_lines.clear(self.viewport.get_untracked());
376            });
377
378            // Recreate the effects
379            self.effects_cx.set(self.cx.get().create_child());
380            create_view_effects(self.effects_cx.get(), self);
381        });
382    }
383
384    pub fn duplicate(&self, editor_id: Option<EditorId>) -> Editor {
385        let doc = self.doc();
386        let style = self.style();
387        let mut editor = Editor::new_direct(
388            self.cx.get(),
389            editor_id.unwrap_or_else(EditorId::next),
390            doc,
391            style,
392            false,
393        );
394
395        batch(|| {
396            editor.read_only.set(self.read_only.get_untracked());
397            editor.es.set(self.es.get_untracked());
398            editor
399                .floem_style_id
400                .set(self.floem_style_id.get_untracked());
401            editor.cursor.set(self.cursor.get_untracked());
402            editor.scroll_delta.set(self.scroll_delta.get_untracked());
403            editor.scroll_to.set(self.scroll_to.get_untracked());
404            editor.window_origin.set(self.window_origin.get_untracked());
405            editor.viewport.set(self.viewport.get_untracked());
406            editor.parent_size.set(self.parent_size.get_untracked());
407            editor.register.set(self.register.get_untracked());
408            editor.cursor_info = self.cursor_info.clone();
409            editor.last_movement.set(self.last_movement.get_untracked());
410            // ?
411            // editor.ime_allowed.set(self.ime_allowed.get_untracked());
412        });
413
414        editor.recreate_view_effects();
415
416        editor
417    }
418
419    /// Get the styling untracked
420    pub fn style(&self) -> Rc<dyn Styling> {
421        self.style.get_untracked()
422    }
423
424    /// Get the text of the document  
425    /// You should typically prefer [`Self::rope_text`]
426    pub fn text(&self) -> Rope {
427        self.doc().text()
428    }
429
430    /// Get the [`RopeTextVal`] from `doc` untracked
431    pub fn rope_text(&self) -> RopeTextVal {
432        self.doc().rope_text()
433    }
434
435    pub fn lines(&self) -> &Lines {
436        &self.lines
437    }
438
439    pub fn text_prov(&self) -> &Self {
440        self
441    }
442
443    fn preedit(&self) -> PreeditData {
444        self.doc.with_untracked(|doc| doc.preedit())
445    }
446
447    pub fn set_preedit(&self, text: String, cursor: Option<(usize, usize)>, offset: usize) {
448        batch(|| {
449            self.preedit().preedit.set(Some(Preedit {
450                text,
451                cursor,
452                offset,
453            }));
454
455            self.doc().cache_rev().update(|cache_rev| {
456                *cache_rev += 1;
457            });
458        });
459    }
460
461    pub fn clear_preedit(&self) {
462        let preedit = self.preedit();
463        if preedit.preedit.with_untracked(|preedit| preedit.is_none()) {
464            return;
465        }
466
467        batch(|| {
468            preedit.preedit.set(None);
469            self.doc().cache_rev().update(|cache_rev| {
470                *cache_rev += 1;
471            });
472        });
473    }
474
475    pub fn receive_char(&self, c: &str) {
476        self.doc().receive_char(self, c)
477    }
478
479    fn compute_screen_lines(&self, base: RwSignal<ScreenLinesBase>) -> ScreenLines {
480        // This function *cannot* access `ScreenLines` with how it is currently implemented.
481        // This is being called from within an update to screen lines.
482
483        self.doc().compute_screen_lines(self, base)
484    }
485
486    /// Default handler for `PointerDown` event
487    pub fn pointer_down(&self, pointer_event: &PointerInputEvent) {
488        if pointer_event.button.is_primary() {
489            self.active.set(true);
490            self.left_click(pointer_event);
491        } else if pointer_event.button.is_secondary() {
492            self.right_click(pointer_event);
493        }
494    }
495
496    pub fn left_click(&self, pointer_event: &PointerInputEvent) {
497        match pointer_event.count {
498            1 => {
499                self.single_click(pointer_event);
500            }
501            2 => {
502                self.double_click(pointer_event);
503            }
504            3 => {
505                self.triple_click(pointer_event);
506            }
507            _ => {}
508        }
509    }
510
511    pub fn single_click(&self, pointer_event: &PointerInputEvent) {
512        let mode = self.cursor.with_untracked(|c| c.get_mode());
513        let (new_offset, _) = self.offset_of_point(mode, pointer_event.pos);
514        self.cursor.update(|cursor| {
515            cursor.set_offset(
516                new_offset,
517                pointer_event.modifiers.shift(),
518                pointer_event.modifiers.alt(),
519            )
520        });
521    }
522
523    pub fn double_click(&self, pointer_event: &PointerInputEvent) {
524        let mode = self.cursor.with_untracked(|c| c.get_mode());
525        let (mouse_offset, _) = self.offset_of_point(mode, pointer_event.pos);
526        let (start, end) = self.select_word(mouse_offset);
527
528        self.cursor.update(|cursor| {
529            cursor.add_region(
530                start,
531                end,
532                pointer_event.modifiers.shift(),
533                pointer_event.modifiers.alt(),
534            )
535        });
536    }
537
538    pub fn triple_click(&self, pointer_event: &PointerInputEvent) {
539        let mode = self.cursor.with_untracked(|c| c.get_mode());
540        let (mouse_offset, _) = self.offset_of_point(mode, pointer_event.pos);
541        let line = self.line_of_offset(mouse_offset);
542        let start = self.offset_of_line(line);
543        let end = self.offset_of_line(line + 1);
544
545        self.cursor.update(|cursor| {
546            cursor.add_region(
547                start,
548                end,
549                pointer_event.modifiers.shift(),
550                pointer_event.modifiers.alt(),
551            )
552        });
553    }
554
555    pub fn pointer_move(&self, pointer_event: &PointerMoveEvent) {
556        let mode = self.cursor.with_untracked(|c| c.get_mode());
557        let (offset, _is_inside) = self.offset_of_point(mode, pointer_event.pos);
558        if self.active.get_untracked() && self.cursor.with_untracked(|c| c.offset()) != offset {
559            self.cursor
560                .update(|cursor| cursor.set_offset(offset, true, pointer_event.modifiers.alt()));
561        }
562    }
563
564    pub fn pointer_up(&self, _pointer_event: &PointerInputEvent) {
565        self.active.set(false);
566    }
567
568    fn right_click(&self, pointer_event: &PointerInputEvent) {
569        let mode = self.cursor.with_untracked(|c| c.get_mode());
570        let (offset, _) = self.offset_of_point(mode, pointer_event.pos);
571        let doc = self.doc();
572        let pointer_inside_selection = self
573            .cursor
574            .with_untracked(|c| c.edit_selection(&doc.rope_text()).contains(offset));
575        if !pointer_inside_selection {
576            // move cursor to pointer position if outside current selection
577            self.single_click(pointer_event);
578        }
579    }
580
581    // TODO: should this have modifiers state in its api
582    pub fn page_move(&self, down: bool, mods: Modifiers) {
583        let viewport = self.viewport.get_untracked();
584        // TODO: don't assume line height is constant
585        let line_height = f64::from(self.line_height(0));
586        let lines = (viewport.height() / line_height / 2.0).round() as usize;
587        let distance = (lines as f64) * line_height;
588        self.scroll_delta
589            .set(Vec2::new(0.0, if down { distance } else { -distance }));
590        let cmd = if down {
591            MoveCommand::Down
592        } else {
593            MoveCommand::Up
594        };
595        let cmd = Command::Move(cmd);
596        self.doc().run_command(self, &cmd, Some(lines), mods);
597    }
598
599    pub fn center_window(&self) {
600        let viewport = self.viewport.get_untracked();
601        // TODO: don't assume line height is constant
602        let line_height = f64::from(self.line_height(0));
603        let offset = self.cursor.with_untracked(|cursor| cursor.offset());
604        let (line, _col) = self.offset_to_line_col(offset);
605
606        let viewport_center = viewport.height() / 2.0;
607
608        let current_line_position = line as f64 * line_height;
609
610        let desired_top = current_line_position - viewport_center + (line_height / 2.0);
611
612        let scroll_delta = desired_top - viewport.y0;
613
614        self.scroll_delta.set(Vec2::new(0.0, scroll_delta));
615    }
616
617    pub fn top_of_window(&self, scroll_off: usize) {
618        let viewport = self.viewport.get_untracked();
619        // TODO: don't assume line height is constant
620        let line_height = f64::from(self.line_height(0));
621        let offset = self.cursor.with_untracked(|cursor| cursor.offset());
622        let (line, _col) = self.offset_to_line_col(offset);
623
624        let desired_top = (line.saturating_sub(scroll_off)) as f64 * line_height;
625
626        let scroll_delta = desired_top - viewport.y0;
627
628        self.scroll_delta.set(Vec2::new(0.0, scroll_delta));
629    }
630
631    pub fn bottom_of_window(&self, scroll_off: usize) {
632        let viewport = self.viewport.get_untracked();
633        // TODO: don't assume line height is constant
634        let line_height = f64::from(self.line_height(0));
635        let offset = self.cursor.with_untracked(|cursor| cursor.offset());
636        let (line, _col) = self.offset_to_line_col(offset);
637
638        let desired_bottom = (line + scroll_off + 1) as f64 * line_height - viewport.height();
639
640        let scroll_delta = desired_bottom - viewport.y0;
641
642        self.scroll_delta.set(Vec2::new(0.0, scroll_delta));
643    }
644
645    pub fn scroll(&self, top_shift: f64, down: bool, count: usize, mods: Modifiers) {
646        let viewport = self.viewport.get_untracked();
647        // TODO: don't assume line height is constant
648        let line_height = f64::from(self.line_height(0));
649        let diff = line_height * count as f64;
650        let diff = if down { diff } else { -diff };
651
652        let offset = self.cursor.with_untracked(|cursor| cursor.offset());
653        let (line, _col) = self.offset_to_line_col(offset);
654        let top = viewport.y0 + diff + top_shift;
655        let bottom = viewport.y0 + diff + viewport.height();
656
657        let new_line = if (line + 1) as f64 * line_height + line_height > bottom {
658            let line = (bottom / line_height).floor() as usize;
659            if line > 2 {
660                line - 2
661            } else {
662                0
663            }
664        } else if line as f64 * line_height - line_height < top {
665            let line = (top / line_height).ceil() as usize;
666            line + 1
667        } else {
668            line
669        };
670
671        self.scroll_delta.set(Vec2::new(0.0, diff));
672
673        let res = match new_line.cmp(&line) {
674            Ordering::Greater => Some((MoveCommand::Down, new_line - line)),
675            Ordering::Less => Some((MoveCommand::Up, line - new_line)),
676            _ => None,
677        };
678
679        if let Some((cmd, count)) = res {
680            let cmd = Command::Move(cmd);
681            self.doc().run_command(self, &cmd, Some(count), mods);
682        }
683    }
684
685    // === Information ===
686
687    pub fn phantom_text(&self, line: usize) -> PhantomTextLine {
688        self.doc()
689            .phantom_text(self.id(), &self.es.get_untracked(), line)
690    }
691
692    pub fn line_height(&self, line: usize) -> f32 {
693        self.style().line_height(self.id(), line)
694    }
695
696    // === Line Information ===
697
698    /// Iterate over the visual lines in the view, starting at the given line.
699    pub fn iter_vlines(
700        &self,
701        backwards: bool,
702        start: VLine,
703    ) -> impl Iterator<Item = VLineInfo> + '_ {
704        self.lines.iter_vlines(self.text_prov(), backwards, start)
705    }
706
707    /// Iterate over the visual lines in the view, starting at the given line and ending at the
708    /// given line. `start_line..end_line`
709    pub fn iter_vlines_over(
710        &self,
711        backwards: bool,
712        start: VLine,
713        end: VLine,
714    ) -> impl Iterator<Item = VLineInfo> + '_ {
715        self.lines
716            .iter_vlines_over(self.text_prov(), backwards, start, end)
717    }
718
719    /// Iterator over *relative* [`VLineInfo`]s, starting at the buffer line, `start_line`.  
720    /// The `visual_line`s provided by this will start at 0 from your `start_line`.  
721    /// This is preferable over `iter_lines` if you do not need to absolute visual line value.
722    pub fn iter_rvlines(
723        &self,
724        backwards: bool,
725        start: RVLine,
726    ) -> impl Iterator<Item = VLineInfo<()>> + '_ {
727        self.lines.iter_rvlines(self.text_prov(), backwards, start)
728    }
729
730    /// Iterator over *relative* [`VLineInfo`]s, starting at the buffer line, `start_line` and
731    /// ending at `end_line`.  
732    /// `start_line..end_line`  
733    /// This is preferable over `iter_lines` if you do not need to absolute visual line value.
734    pub fn iter_rvlines_over(
735        &self,
736        backwards: bool,
737        start: RVLine,
738        end_line: usize,
739    ) -> impl Iterator<Item = VLineInfo<()>> + '_ {
740        self.lines
741            .iter_rvlines_over(self.text_prov(), backwards, start, end_line)
742    }
743
744    // ==== Position Information ====
745
746    pub fn first_rvline_info(&self) -> VLineInfo<()> {
747        self.rvline_info(RVLine::default())
748    }
749
750    /// The number of lines in the document.
751    pub fn num_lines(&self) -> usize {
752        self.rope_text().num_lines()
753    }
754
755    /// The last allowed buffer line in the document.
756    pub fn last_line(&self) -> usize {
757        self.rope_text().last_line()
758    }
759
760    pub fn last_vline(&self) -> VLine {
761        self.lines.last_vline(self.text_prov())
762    }
763
764    pub fn last_rvline(&self) -> RVLine {
765        self.lines.last_rvline(self.text_prov())
766    }
767
768    pub fn last_rvline_info(&self) -> VLineInfo<()> {
769        self.rvline_info(self.last_rvline())
770    }
771
772    // ==== Line/Column Positioning ====
773
774    /// Convert an offset into the buffer into a line and idx.  
775    pub fn offset_to_line_col(&self, offset: usize) -> (usize, usize) {
776        self.rope_text().offset_to_line_col(offset)
777    }
778
779    pub fn offset_of_line(&self, offset: usize) -> usize {
780        self.rope_text().offset_of_line(offset)
781    }
782
783    pub fn offset_of_line_col(&self, line: usize, col: usize) -> usize {
784        self.rope_text().offset_of_line_col(line, col)
785    }
786
787    /// Get the buffer line of an offset
788    pub fn line_of_offset(&self, offset: usize) -> usize {
789        self.rope_text().line_of_offset(offset)
790    }
791
792    /// Returns the offset into the buffer of the first non blank character on the given line.
793    pub fn first_non_blank_character_on_line(&self, line: usize) -> usize {
794        self.rope_text().first_non_blank_character_on_line(line)
795    }
796
797    pub fn line_end_col(&self, line: usize, caret: bool) -> usize {
798        self.rope_text().line_end_col(line, caret)
799    }
800
801    pub fn select_word(&self, offset: usize) -> (usize, usize) {
802        self.rope_text().select_word(offset)
803    }
804
805    /// `affinity` decides whether an offset at a soft line break is considered to be on the
806    /// previous line or the next line.  
807    /// If `affinity` is `CursorAffinity::Forward` and is at the very end of the wrapped line, then
808    /// the offset is considered to be on the next line.
809    pub fn vline_of_offset(&self, offset: usize, affinity: CursorAffinity) -> VLine {
810        self.lines
811            .vline_of_offset(&self.text_prov(), offset, affinity)
812    }
813
814    pub fn vline_of_line(&self, line: usize) -> VLine {
815        self.lines.vline_of_line(&self.text_prov(), line)
816    }
817
818    pub fn rvline_of_line(&self, line: usize) -> RVLine {
819        self.lines.rvline_of_line(&self.text_prov(), line)
820    }
821
822    pub fn vline_of_rvline(&self, rvline: RVLine) -> VLine {
823        self.lines.vline_of_rvline(&self.text_prov(), rvline)
824    }
825
826    /// Get the nearest offset to the start of the visual line.
827    pub fn offset_of_vline(&self, vline: VLine) -> usize {
828        self.lines.offset_of_vline(&self.text_prov(), vline)
829    }
830
831    /// Get the visual line and column of the given offset.  
832    /// The column is before phantom text is applied.
833    pub fn vline_col_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (VLine, usize) {
834        self.lines
835            .vline_col_of_offset(&self.text_prov(), offset, affinity)
836    }
837
838    pub fn rvline_of_offset(&self, offset: usize, affinity: CursorAffinity) -> RVLine {
839        self.lines
840            .rvline_of_offset(&self.text_prov(), offset, affinity)
841    }
842
843    pub fn rvline_col_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (RVLine, usize) {
844        self.lines
845            .rvline_col_of_offset(&self.text_prov(), offset, affinity)
846    }
847
848    pub fn offset_of_rvline(&self, rvline: RVLine) -> usize {
849        self.lines.offset_of_rvline(&self.text_prov(), rvline)
850    }
851
852    pub fn vline_info(&self, vline: VLine) -> VLineInfo {
853        let vline = vline.min(self.last_vline());
854        self.iter_vlines(false, vline).next().unwrap()
855    }
856
857    pub fn screen_rvline_info_of_offset(
858        &self,
859        offset: usize,
860        affinity: CursorAffinity,
861    ) -> Option<VLineInfo<()>> {
862        let rvline = self.rvline_of_offset(offset, affinity);
863        self.screen_lines.with_untracked(|screen_lines| {
864            screen_lines
865                .iter_vline_info()
866                .find(|vline_info| vline_info.rvline == rvline)
867        })
868    }
869
870    pub fn rvline_info(&self, rvline: RVLine) -> VLineInfo<()> {
871        let rvline = rvline.min(self.last_rvline());
872        self.iter_rvlines(false, rvline).next().unwrap()
873    }
874
875    pub fn rvline_info_of_offset(&self, offset: usize, affinity: CursorAffinity) -> VLineInfo<()> {
876        let rvline = self.rvline_of_offset(offset, affinity);
877        self.rvline_info(rvline)
878    }
879
880    /// Get the first column of the overall line of the visual line
881    pub fn first_col<T: std::fmt::Debug>(&self, info: VLineInfo<T>) -> usize {
882        info.first_col(&self.text_prov())
883    }
884
885    /// Get the last column in the overall line of the visual line
886    pub fn last_col<T: std::fmt::Debug>(&self, info: VLineInfo<T>, caret: bool) -> usize {
887        info.last_col(&self.text_prov(), caret)
888    }
889
890    // ==== Points of locations ====
891
892    pub fn max_line_width(&self) -> f64 {
893        self.lines.max_width()
894    }
895
896    /// Returns the point into the text layout of the line at the given offset.
897    /// `x` being the leading edge of the character, and `y` being the baseline.
898    pub fn line_point_of_offset(&self, offset: usize, affinity: CursorAffinity) -> Point {
899        let (line, col) = self.offset_to_line_col(offset);
900        self.line_point_of_line_col(line, col, affinity, false)
901    }
902
903    /// Returns the point into the text layout of the line at the given line and col.
904    /// `x` being the leading edge of the character, and `y` being the baseline.  
905    pub fn line_point_of_line_col(
906        &self,
907        line: usize,
908        col: usize,
909        affinity: CursorAffinity,
910        force_affinity: bool,
911    ) -> Point {
912        let text_layout = self.text_layout(line);
913        let index = if force_affinity {
914            text_layout
915                .phantom_text
916                .col_after_force(col, affinity == CursorAffinity::Forward)
917        } else {
918            text_layout
919                .phantom_text
920                .col_after(col, affinity == CursorAffinity::Forward)
921        };
922        hit_position_aff(
923            &text_layout.text,
924            index,
925            affinity == CursorAffinity::Backward,
926        )
927        .point
928    }
929
930    /// Get the (point above, point below) of a particular offset within the editor.
931    pub fn points_of_offset(&self, offset: usize, affinity: CursorAffinity) -> (Point, Point) {
932        let line = self.line_of_offset(offset);
933        let line_height = f64::from(self.style().line_height(self.id(), line));
934
935        let info = self.screen_lines.with_untracked(|sl| {
936            sl.iter_line_info().find(|info| {
937                info.vline_info.interval.start <= offset && offset <= info.vline_info.interval.end
938            })
939        });
940        let Some(info) = info else {
941            // TODO: We could do a smarter method where we get the approximate y position
942            // because, for example, this spot could be folded away, and so it would be better to
943            // supply the *nearest* position on the screen.
944            return (Point::new(0.0, 0.0), Point::new(0.0, 0.0));
945        };
946
947        let y = info.vline_y;
948
949        let x = self.line_point_of_offset(offset, affinity).x;
950
951        (Point::new(x, y), Point::new(x, y + line_height))
952    }
953
954    /// Get the offset of a particular point within the editor.
955    /// The boolean indicates whether the point is inside the text or not
956    /// Points outside of vertical bounds will return the last line.
957    /// Points outside of horizontal bounds will return the last column on the line.
958    pub fn offset_of_point(&self, mode: Mode, point: Point) -> (usize, bool) {
959        let ((line, col), is_inside) = self.line_col_of_point(mode, point);
960        (self.offset_of_line_col(line, col), is_inside)
961    }
962
963    /// Get the actual (line, col) of a particular point within the editor.
964    pub fn line_col_of_point_with_phantom(&self, point: Point) -> (usize, usize) {
965        let line_height = f64::from(self.style().line_height(self.id(), 0));
966        let info = if point.y <= 0.0 {
967            Some(self.first_rvline_info())
968        } else {
969            self.screen_lines
970                .with_untracked(|sl| {
971                    sl.iter_line_info().find(|info| {
972                        info.vline_y <= point.y && info.vline_y + line_height >= point.y
973                    })
974                })
975                .map(|info| info.vline_info)
976        };
977        let info = info.unwrap_or_else(|| {
978            for (y_idx, info) in self.iter_rvlines(false, RVLine::default()).enumerate() {
979                let vline_y = y_idx as f64 * line_height;
980                if vline_y <= point.y && vline_y + line_height >= point.y {
981                    return info;
982                }
983            }
984
985            self.last_rvline_info()
986        });
987
988        let rvline = info.rvline;
989        let line = rvline.line;
990        let text_layout = self.text_layout(line);
991
992        let y = text_layout.get_layout_y(rvline.line_index).unwrap_or(0.0);
993
994        let hit_point = text_layout.text.hit_point(Point::new(point.x, y as f64));
995        (line, hit_point.index)
996    }
997
998    /// Get the (line, col) of a particular point within the editor.
999    /// The boolean indicates whether the point is within the text bounds.
1000    /// Points outside of vertical bounds will return the last line.
1001    /// Points outside of horizontal bounds will return the last column on the line.
1002    pub fn line_col_of_point(&self, mode: Mode, point: Point) -> ((usize, usize), bool) {
1003        // TODO: this assumes that line height is constant!
1004        let line_height = f64::from(self.style().line_height(self.id(), 0));
1005        let info = if point.y <= 0.0 {
1006            Some(self.first_rvline_info())
1007        } else {
1008            self.screen_lines
1009                .with_untracked(|sl| {
1010                    sl.iter_line_info().find(|info| {
1011                        info.vline_y <= point.y && info.vline_y + line_height >= point.y
1012                    })
1013                })
1014                .map(|info| info.vline_info)
1015        };
1016        let info = info.unwrap_or_else(|| {
1017            for (y_idx, info) in self.iter_rvlines(false, RVLine::default()).enumerate() {
1018                let vline_y = y_idx as f64 * line_height;
1019                if vline_y <= point.y && vline_y + line_height >= point.y {
1020                    return info;
1021                }
1022            }
1023
1024            self.last_rvline_info()
1025        });
1026
1027        let rvline = info.rvline;
1028        let line = rvline.line;
1029        let text_layout = self.text_layout(line);
1030
1031        let y = text_layout.get_layout_y(rvline.line_index).unwrap_or(0.0);
1032
1033        let hit_point = text_layout.text.hit_point(Point::new(point.x, y as f64));
1034        // We have to unapply the phantom text shifting in order to get back to the column in
1035        // the actual buffer
1036        let col = text_layout.phantom_text.before_col(hit_point.index);
1037        // Ensure that the column doesn't end up out of bounds, so things like clicking on the far
1038        // right end will just go to the end of the line.
1039        let max_col = self.line_end_col(line, mode != Mode::Normal);
1040        let mut col = col.min(max_col);
1041
1042        // TODO: we need to handle affinity. Clicking at end of a wrapped line should give it a
1043        // backwards affinity, while being at the start of the next line should be a forwards aff
1044
1045        // TODO: this is a hack to get around text layouts not including spaces at the end of
1046        // wrapped lines, but we want to be able to click on them
1047        if !hit_point.is_inside {
1048            // TODO(minor): this is probably wrong in some manners
1049            col = info.last_col(&self.text_prov(), true);
1050        }
1051
1052        let tab_width = self.style().tab_width(self.id(), line);
1053        if self.style().atomic_soft_tabs(self.id(), line) && tab_width > 1 {
1054            col = snap_to_soft_tab_line_col(
1055                &self.text(),
1056                line,
1057                col,
1058                SnapDirection::Nearest,
1059                tab_width,
1060            );
1061        }
1062
1063        ((line, col), hit_point.is_inside)
1064    }
1065
1066    // TODO: colposition probably has issues with wrapping?
1067    pub fn line_horiz_col(&self, line: usize, horiz: &ColPosition, caret: bool) -> usize {
1068        match *horiz {
1069            ColPosition::Col(x) => {
1070                // TODO: won't this be incorrect with phantom text? Shouldn't this just use
1071                // line_col_of_point and get the col from that?
1072                let text_layout = self.text_layout(line);
1073                let hit_point = text_layout.text.hit_point(Point::new(x, 0.0));
1074                let n = hit_point.index;
1075                let col = text_layout.phantom_text.before_col(n);
1076
1077                col.min(self.line_end_col(line, caret))
1078            }
1079            ColPosition::End => self.line_end_col(line, caret),
1080            ColPosition::Start => 0,
1081            ColPosition::FirstNonBlank => self.first_non_blank_character_on_line(line),
1082        }
1083    }
1084
1085    /// Advance to the right in the manner of the given mode.
1086    /// Get the column from a horizontal at a specific line index (in a text layout)
1087    pub fn rvline_horiz_col(
1088        &self,
1089        RVLine { line, line_index }: RVLine,
1090        horiz: &ColPosition,
1091        caret: bool,
1092    ) -> usize {
1093        match *horiz {
1094            ColPosition::Col(x) => {
1095                let text_layout = self.text_layout(line);
1096                let y_pos = text_layout
1097                    .text
1098                    .layout_runs()
1099                    .nth(line_index)
1100                    .map(|run| run.line_y)
1101                    .or_else(|| text_layout.text.layout_runs().last().map(|run| run.line_y))
1102                    .unwrap_or(0.0);
1103                let hit_point = text_layout.text.hit_point(Point::new(x, y_pos as f64));
1104                let n = hit_point.index;
1105                let col = text_layout.phantom_text.before_col(n);
1106
1107                col.min(self.line_end_col(line, caret))
1108            }
1109            // Otherwise it is the same as the other function
1110            _ => self.line_horiz_col(line, horiz, caret),
1111        }
1112    }
1113
1114    /// Advance to the right in the manner of the given mode.  
1115    /// This is not the same as the [`Movement::Right`] command.
1116    pub fn move_right(&self, offset: usize, mode: Mode, count: usize) -> usize {
1117        self.rope_text().move_right(offset, mode, count)
1118    }
1119
1120    /// Advance to the left in the manner of the given mode.
1121    /// This is not the same as the [`Movement::Left`] command.
1122    pub fn move_left(&self, offset: usize, mode: Mode, count: usize) -> usize {
1123        self.rope_text().move_left(offset, mode, count)
1124    }
1125}
1126
1127impl std::fmt::Debug for Editor {
1128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1129        f.debug_tuple("Editor").field(&self.id).finish()
1130    }
1131}
1132
1133// Text layout creation
1134impl Editor {
1135    // Get the text layout for a document line, creating it if needed.
1136    pub fn text_layout(&self, line: usize) -> Arc<TextLayoutLine> {
1137        self.text_layout_trigger(line, true)
1138    }
1139
1140    pub fn text_layout_trigger(&self, line: usize, trigger: bool) -> Arc<TextLayoutLine> {
1141        let cache_rev = self.doc().cache_rev().get_untracked();
1142        self.lines
1143            .get_init_text_layout(cache_rev, self.config_id(), self, line, trigger)
1144    }
1145
1146    fn try_get_text_layout(&self, line: usize) -> Option<Arc<TextLayoutLine>> {
1147        let cache_rev = self.doc().cache_rev().get_untracked();
1148        self.lines
1149            .try_get_text_layout(cache_rev, self.config_id(), line)
1150    }
1151
1152    /// Create rendable whitespace layout by creating a new text layout
1153    /// with invisible spaces and special utf8 characters that display
1154    /// the different white space characters.
1155    fn new_whitespace_layout(
1156        line_content: &str,
1157        text_layout: &TextLayout,
1158        phantom: &PhantomTextLine,
1159        render_whitespace: RenderWhitespace,
1160    ) -> Option<Vec<(char, (f64, f64))>> {
1161        let mut render_leading = false;
1162        let mut render_boundary = false;
1163        let mut render_between = false;
1164
1165        // TODO: render whitespaces only on highlighted text
1166        match render_whitespace {
1167            RenderWhitespace::All => {
1168                render_leading = true;
1169                render_boundary = true;
1170                render_between = true;
1171            }
1172            RenderWhitespace::Boundary => {
1173                render_leading = true;
1174                render_boundary = true;
1175            }
1176            RenderWhitespace::Trailing => {} // All configs include rendering trailing whitespace
1177            RenderWhitespace::None => return None,
1178        }
1179
1180        let mut whitespace_buffer = Vec::new();
1181        let mut rendered_whitespaces: Vec<(char, (f64, f64))> = Vec::new();
1182        let mut char_found = false;
1183        let mut col = 0;
1184        for c in line_content.chars() {
1185            match c {
1186                '\t' => {
1187                    let col_left = phantom.col_after(col, true);
1188                    let col_right = phantom.col_after(col + 1, false);
1189                    let x0 = text_layout.hit_position(col_left).point.x;
1190                    let x1 = text_layout.hit_position(col_right).point.x;
1191                    whitespace_buffer.push(('\t', (x0, x1)));
1192                }
1193                ' ' => {
1194                    let col_left = phantom.col_after(col, true);
1195                    let col_right = phantom.col_after(col + 1, false);
1196                    let x0 = text_layout.hit_position(col_left).point.x;
1197                    let x1 = text_layout.hit_position(col_right).point.x;
1198                    whitespace_buffer.push((' ', (x0, x1)));
1199                }
1200                _ => {
1201                    if (char_found && render_between)
1202                        || (char_found && render_boundary && whitespace_buffer.len() > 1)
1203                        || (!char_found && render_leading)
1204                    {
1205                        rendered_whitespaces.extend(whitespace_buffer.iter());
1206                    }
1207
1208                    char_found = true;
1209                    whitespace_buffer.clear();
1210                }
1211            }
1212            col += c.len_utf8();
1213        }
1214        rendered_whitespaces.extend(whitespace_buffer.iter());
1215
1216        Some(rendered_whitespaces)
1217    }
1218}
1219impl TextLayoutProvider for Editor {
1220    // TODO: should this just return a `Rope`?
1221    fn text(&self) -> Rope {
1222        Editor::text(self)
1223    }
1224
1225    fn new_text_layout(
1226        &self,
1227        line: usize,
1228        _font_size: usize,
1229        _wrap: ResolvedWrap,
1230    ) -> Arc<TextLayoutLine> {
1231        // TODO: we could share text layouts between different editor views given some knowledge of
1232        // their wrapping
1233        let edid = self.id();
1234        let text = self.rope_text();
1235        let style = self.style();
1236        let doc = self.doc();
1237
1238        let line_content_original = text.line_content(line);
1239
1240        let font_size = style.font_size(edid, line);
1241
1242        // Get the line content with newline characters replaced with spaces
1243        // and the content without the newline characters
1244        // TODO: cache or add some way that text layout is created to auto insert the spaces instead
1245        // though we immediately combine with phantom text so that's a thing.
1246        let line_content = if let Some(s) = line_content_original.strip_suffix("\r\n") {
1247            format!("{s}  ")
1248        } else if let Some(s) = line_content_original.strip_suffix('\n') {
1249            format!("{s} ",)
1250        } else {
1251            line_content_original.to_string()
1252        };
1253        // Combine the phantom text with the line content
1254        let phantom_text = doc.phantom_text(edid, &self.es.get_untracked(), line);
1255        let line_content = phantom_text.combine_with_text(&line_content);
1256
1257        let family = style.font_family(edid, line);
1258        let attrs = Attrs::new()
1259            .color(self.es.with(|s| s.ed_text_color()))
1260            .family(&family)
1261            .font_size(font_size as f32)
1262            .line_height(LineHeightValue::Px(style.line_height(edid, line)));
1263        let mut attrs_list = AttrsList::new(attrs);
1264
1265        self.es.with_untracked(|es| {
1266            style.apply_attr_styles(edid, es, line, attrs, &mut attrs_list);
1267        });
1268
1269        // Apply phantom text specific styling
1270        for (offset, size, col, phantom) in phantom_text.offset_size_iter() {
1271            let start = col + offset;
1272            let end = start + size;
1273
1274            let mut attrs = attrs;
1275            if let Some(fg) = phantom.fg {
1276                attrs = attrs.color(fg);
1277            } else {
1278                attrs = attrs.color(self.es.with(|es| es.phantom_color()))
1279            }
1280            if let Some(phantom_font_size) = phantom.font_size {
1281                attrs = attrs.font_size(phantom_font_size.min(font_size) as f32);
1282            }
1283            attrs_list.add_span(start..end, attrs);
1284            // if let Some(font_family) = phantom.font_family.clone() {
1285            //     layout_builder = layout_builder.range_attribute(
1286            //         start..end,
1287            //         TextAttribute::FontFamily(font_family),
1288            //     );
1289            // }
1290        }
1291
1292        let mut text_layout = TextLayout::new();
1293        // TODO: we could move tab width setting to be done by the document
1294        text_layout.set_tab_width(style.tab_width(edid, line));
1295        text_layout.set_text(&line_content, attrs_list);
1296
1297        // dbg!(self.editor_style.with(|s| s.wrap_method()));
1298        match self.es.with(|s| s.wrap_method()) {
1299            WrapMethod::None => {}
1300            WrapMethod::EditorWidth => {
1301                let width = self.viewport.get_untracked().width();
1302                text_layout.set_wrap(Wrap::WordOrGlyph);
1303                text_layout.set_size(width as f32, f32::MAX);
1304            }
1305            WrapMethod::WrapWidth { width } => {
1306                text_layout.set_wrap(Wrap::WordOrGlyph);
1307                text_layout.set_size(width, f32::MAX);
1308            }
1309            // TODO:
1310            WrapMethod::WrapColumn { .. } => {}
1311        }
1312
1313        let whitespaces = Self::new_whitespace_layout(
1314            &line_content_original,
1315            &text_layout,
1316            &phantom_text,
1317            self.es.with(|s| s.render_whitespace()),
1318        );
1319
1320        let indent_line = style.indent_line(edid, line, &line_content_original);
1321
1322        let indent = if indent_line != line {
1323            // TODO: This creates the layout if it isn't already cached, but it doesn't cache the
1324            // result because the current method of managing the cache is not very smart.
1325            let layout = self.try_get_text_layout(indent_line).unwrap_or_else(|| {
1326                self.new_text_layout(
1327                    indent_line,
1328                    style.font_size(edid, indent_line),
1329                    self.lines.wrap(),
1330                )
1331            });
1332            layout.indent + 1.0
1333        } else {
1334            let offset = text.first_non_blank_character_on_line(indent_line);
1335            let (_, col) = text.offset_to_line_col(offset);
1336            text_layout.hit_position(col).point.x
1337        };
1338
1339        let mut layout_line = TextLayoutLine {
1340            text: text_layout,
1341            extra_style: Vec::new(),
1342            whitespaces,
1343            indent,
1344            phantom_text,
1345        };
1346        self.es.with_untracked(|es| {
1347            style.apply_layout_styles(edid, es, line, &mut layout_line);
1348        });
1349
1350        Arc::new(layout_line)
1351    }
1352
1353    fn before_phantom_col(&self, line: usize, col: usize) -> usize {
1354        self.doc()
1355            .before_phantom_col(self.id(), &self.es.get_untracked(), line, col)
1356    }
1357
1358    fn has_multiline_phantom(&self) -> bool {
1359        self.doc()
1360            .has_multiline_phantom(self.id(), &self.es.get_untracked())
1361    }
1362}
1363
1364struct EditorFontSizes {
1365    id: EditorId,
1366    style: ReadSignal<Rc<dyn Styling>>,
1367    doc: ReadSignal<Rc<dyn Document>>,
1368}
1369impl LineFontSizeProvider for EditorFontSizes {
1370    fn font_size(&self, line: usize) -> usize {
1371        self.style
1372            .with_untracked(|style| style.font_size(self.id, line))
1373    }
1374
1375    fn cache_id(&self) -> FontSizeCacheId {
1376        let mut hasher = DefaultHasher::new();
1377
1378        // TODO: is this actually good enough for comparing cache state?
1379        // We could just have it return an arbitrary type that impl's Eq?
1380        self.style
1381            .with_untracked(|style| style.id().hash(&mut hasher));
1382        self.doc
1383            .with_untracked(|doc| doc.cache_rev().get_untracked().hash(&mut hasher));
1384
1385        hasher.finish()
1386    }
1387}
1388
1389/// Minimum width that we'll allow the view to be wrapped at.
1390const MIN_WRAPPED_WIDTH: f32 = 100.0;
1391
1392/// Create various reactive effects to update the screen lines whenever relevant parts of the view,
1393/// doc, text layouts, viewport, etc. change.
1394/// This tries to be smart to a degree.
1395fn create_view_effects(cx: Scope, ed: &Editor) {
1396    // Cloning is fun.
1397    let ed2 = ed.clone();
1398    let ed3 = ed.clone();
1399    let ed4 = ed.clone();
1400
1401    // Reset cursor blinking whenever the cursor changes
1402    {
1403        let cursor_info = ed.cursor_info.clone();
1404        let cursor = ed.cursor;
1405        cx.create_effect(move |_| {
1406            cursor.track();
1407            cursor_info.reset();
1408        });
1409    }
1410
1411    let update_screen_lines = |ed: &Editor| {
1412        // This function should not depend on the viewport signal directly.
1413
1414        // This is wrapped in an update to make any updates-while-updating very obvious
1415        // which they wouldn't be if we computed and then `set`.
1416        ed.screen_lines.update(|screen_lines| {
1417            let new_screen_lines = ed.compute_screen_lines(screen_lines.base);
1418
1419            *screen_lines = new_screen_lines;
1420        });
1421    };
1422
1423    // Listen for layout events, currently only when a layout is created, and update screen
1424    // lines based on that
1425    ed3.lines.layout_event.listen_with(cx, move |val| {
1426        let ed = &ed2;
1427        // TODO: Move this logic onto screen lines somehow, perhaps just an auxiliary
1428        // function, to avoid getting confused about what is relevant where.
1429
1430        match val {
1431            LayoutEvent::CreatedLayout { line, .. } => {
1432                let sl = ed.screen_lines.get_untracked();
1433
1434                // Intelligently update screen lines, avoiding recalculation if possible
1435                let should_update = sl.on_created_layout(ed, line);
1436
1437                if should_update {
1438                    untrack(|| {
1439                        update_screen_lines(ed);
1440                    });
1441
1442                    // Ensure that it is created even after the base/viewport signals have been
1443                    // updated.
1444                    // But we have to trigger an event since it could alter the screenlines
1445                    // TODO: this has some risk for infinite looping if we're unlucky.
1446                    ed2.text_layout_trigger(line, true);
1447                }
1448            }
1449        }
1450    });
1451
1452    // TODO: should we have some debouncing for editor width? Ideally we'll be fast enough to not
1453    // even need it, though we might not want to use a bunch of cpu whilst resizing anyway.
1454
1455    let viewport_changed_trigger = cx.create_trigger();
1456
1457    // Watch for changes to the viewport so that we can alter the wrapping
1458    // As well as updating the screen lines base
1459    cx.create_effect(move |_| {
1460        let ed = &ed3;
1461
1462        let viewport = ed.viewport.get();
1463
1464        let wrap = match ed.es.with(|s| s.wrap_method()) {
1465            WrapMethod::None => ResolvedWrap::None,
1466            WrapMethod::EditorWidth => {
1467                ResolvedWrap::Width((viewport.width() as f32).max(MIN_WRAPPED_WIDTH))
1468            }
1469            WrapMethod::WrapColumn { .. } => todo!(),
1470            WrapMethod::WrapWidth { width } => ResolvedWrap::Width(width),
1471        };
1472
1473        ed.lines.set_wrap(wrap);
1474
1475        // Update the base
1476        let base = ed.screen_lines.with_untracked(|sl| sl.base);
1477
1478        // TODO: should this be a with or with_untracked?
1479        if viewport != base.with_untracked(|base| base.active_viewport) {
1480            batch(|| {
1481                base.update(|base| {
1482                    base.active_viewport = viewport;
1483                });
1484                // TODO: Can I get rid of this and just call update screen lines with an
1485                // untrack around it?
1486                viewport_changed_trigger.notify();
1487            });
1488        }
1489    });
1490    // Watch for when the viewport as changed in a relevant manner
1491    // and for anything that `update_screen_lines` tracks.
1492    cx.create_effect(move |_| {
1493        viewport_changed_trigger.track();
1494
1495        update_screen_lines(&ed4);
1496    });
1497}
1498
1499pub fn normal_compute_screen_lines(
1500    editor: &Editor,
1501    base: RwSignal<ScreenLinesBase>,
1502) -> ScreenLines {
1503    let lines = &editor.lines;
1504    let style = editor.style.get();
1505    // TODO: don't assume universal line height!
1506    let line_height = style.line_height(editor.id(), 0);
1507
1508    let (y0, y1) = base.with_untracked(|base| (base.active_viewport.y0, base.active_viewport.y1));
1509    // Get the start and end (visual) lines that are visible in the viewport
1510    let min_vline = VLine((y0 / line_height as f64).floor() as usize);
1511    let max_vline = VLine((y1 / line_height as f64).ceil() as usize);
1512
1513    let cache_rev = editor.doc.get().cache_rev().get();
1514    editor.lines.check_cache_rev(cache_rev);
1515
1516    let min_info = editor.iter_vlines(false, min_vline).next();
1517
1518    let mut rvlines = Vec::new();
1519    let mut info = HashMap::new();
1520
1521    let Some(min_info) = min_info else {
1522        return ScreenLines {
1523            lines: Rc::new(rvlines),
1524            info: Rc::new(info),
1525            diff_sections: None,
1526            base,
1527        };
1528    };
1529
1530    // TODO: the original was min_line..max_line + 1, are we iterating too little now?
1531    // the iterator is from min_vline..max_vline
1532    let count = max_vline.get() - min_vline.get();
1533    let iter = lines
1534        .iter_rvlines_init(
1535            editor.text_prov(),
1536            cache_rev,
1537            editor.config_id(),
1538            min_info.rvline,
1539            false,
1540        )
1541        .take(count);
1542
1543    for (i, vline_info) in iter.enumerate() {
1544        rvlines.push(vline_info.rvline);
1545
1546        let line_height = f64::from(style.line_height(editor.id(), vline_info.rvline.line));
1547
1548        let y_idx = min_vline.get() + i;
1549        let vline_y = y_idx as f64 * line_height;
1550        let line_y = vline_y - vline_info.rvline.line_index as f64 * line_height;
1551
1552        // Add the information to make it cheap to get in the future.
1553        // This y positions are shifted by the baseline y0
1554        info.insert(
1555            vline_info.rvline,
1556            LineInfo {
1557                y: line_y - y0,
1558                vline_y: vline_y - y0,
1559                vline_info,
1560            },
1561        );
1562    }
1563
1564    ScreenLines {
1565        lines: Rc::new(rvlines),
1566        info: Rc::new(info),
1567        diff_sections: None,
1568        base,
1569    }
1570}
1571
1572// TODO: should we put `cursor` on this structure?
1573/// Cursor rendering information
1574#[derive(Clone)]
1575pub struct CursorInfo {
1576    pub hidden: RwSignal<bool>,
1577
1578    pub blink_timer: RwSignal<TimerToken>,
1579    // TODO: should these just be rwsignals?
1580    pub should_blink: Rc<dyn Fn() -> bool + 'static>,
1581    pub blink_interval: Rc<dyn Fn() -> u64 + 'static>,
1582}
1583
1584impl CursorInfo {
1585    pub fn new(cx: Scope) -> CursorInfo {
1586        CursorInfo {
1587            hidden: cx.create_rw_signal(false),
1588
1589            blink_timer: cx.create_rw_signal(TimerToken::INVALID),
1590            should_blink: Rc::new(|| true),
1591            blink_interval: Rc::new(|| 500),
1592        }
1593    }
1594
1595    pub fn blink(&self) {
1596        let info = self.clone();
1597        let blink_interval = (info.blink_interval)();
1598        if blink_interval > 0 && (info.should_blink)() {
1599            let blink_timer = info.blink_timer;
1600            let timer_token =
1601                exec_after(Duration::from_millis(blink_interval), move |timer_token| {
1602                    if info.blink_timer.try_get_untracked() == Some(timer_token) {
1603                        info.hidden.update(|hide| {
1604                            *hide = !*hide;
1605                        });
1606                        info.blink();
1607                    }
1608                });
1609            blink_timer.set(timer_token);
1610        }
1611    }
1612
1613    pub fn reset(&self) {
1614        if self.hidden.get_untracked() {
1615            self.hidden.set(false);
1616        }
1617
1618        self.blink_timer.set(TimerToken::INVALID);
1619
1620        self.blink();
1621    }
1622}