floem/views/editor/
mod.rs

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