Skip to main content

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