Skip to main content

floem/views/editor/
visual_line.rs

1//! Visual Line implementation
2//!
3//! Files are easily broken up into buffer lines by just splitting on `\n` or `\r\n`.
4//! However, editors require features like wrapping and multiline phantom text. These break the
5//! nice one-to-one correspondence between buffer lines and visual lines.
6//!
7//! When rendering with those, we have to display based on visual lines rather than the
8//! underlying buffer lines. As well, it is expected for interaction - like movement and clicking -
9//! to work in a similar intuitive manner as it would be if there was no wrapping or phantom text.
10//! Ex: Moving down a line should move to the next visual line, not the next buffer line by
11//! default.
12//! (Sometimes! Some vim defaults are to move to the next buffer line, or there might be other
13//! differences)
14//!
15//! There's two types of ways of talking about Visual Lines:
16//! - [`VLine`]: Variables are often written with `vline` in the name
17//! - [`RVLine`]: Variables are often written with `rvline` in the name
18//!
19//! [`VLine`] is an absolute visual line within the file. This is useful for some positioning tasks
20//! but is more expensive to calculate due to the nontriviality of the `buffer line <-> visual line`
21//! conversion when the file has any wrapping or multiline phantom text.
22//!
23//! Typically, code should prefer to use [`RVLine`]. This simply stores the underlying
24//! buffer line, and a line index. This is not enough for absolute positioning within the display,
25//! but it is enough for most other things (like movement). This is easier to calculate since it
26//! only needs to find the right (potentially wrapped or multiline) layout for the easy-to-work
27//! with buffer line.
28//!
29//! [`VLine`] is a single `usize` internally which can be multiplied by the line-height to get the
30//! absolute position. This means that it is not stable across text layouts being changed.
31//! An [`RVLine`] holds the buffer line and the 'line index' within the layout. The line index
32//! would be `0` for the first line, `1` if it is on the second wrapped line, etc. This is more
33//! stable across text layouts being changed, as it is only relative to a specific line.
34//!
35//! -----
36//!
37//! [`Lines`] is the main structure. It is responsible for holding the text layouts, as well as
38//! providing the functions to convert between (r)vlines and buffer lines.
39//!
40//! ----
41//!
42//! Many of [`Lines`] functions are passed a [`TextLayoutProvider`].
43//! This serves the dual-purpose of giving us the text of the underlying file, as well as
44//! for constructing the text layouts that we use for rendering.
45//! Having a trait that is passed in simplifies the logic, since the caller is the one who tracks
46//! the text in whatever manner they chose.
47
48// TODO: This file is getting long. Possibly it should be broken out into multiple files.
49// Especially as it will only grow with more utility functions.
50
51// TODO(minor): We use a lot of `impl TextLayoutProvider`.
52// This has the desired benefit of inlining the functions, so that the compiler can optimize the
53// logic better than a naive for-loop or whatnot.
54// However it does have the issue that it overuses generics, and we sometimes end up instantiating
55// multiple versions of the same function. `T: TextLayoutProvider`, `&T`...
56// - It would be better to standardize on one way of doing that, probably `&impl TextLayoutProvider`
57
58use std::{
59    cell::{Cell, RefCell},
60    cmp::Ordering,
61    collections::HashMap,
62    rc::Rc,
63    sync::Arc,
64};
65
66use floem_editor_core::{
67    buffer::rope_text::{RopeText, RopeTextVal},
68    cursor::CursorAffinity,
69    word::WordCursor,
70};
71use floem_reactive::Scope;
72use lapce_xi_rope::{Interval, Rope};
73
74use super::{layout::TextLayoutLine, listener::Listener};
75
76#[derive(Debug, Clone, Copy, PartialEq)]
77pub enum ResolvedWrap {
78    None,
79    Column(usize),
80    Width(f32),
81}
82impl ResolvedWrap {
83    pub fn is_different_kind(self, other: ResolvedWrap) -> bool {
84        !matches!(
85            (self, other),
86            (ResolvedWrap::None, ResolvedWrap::None)
87                | (ResolvedWrap::Column(_), ResolvedWrap::Column(_))
88                | (ResolvedWrap::Width(_), ResolvedWrap::Width(_))
89        )
90    }
91}
92
93/// A line within the editor view.
94///
95/// This gives the absolute position of the visual line.
96#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
97pub struct VLine(pub usize);
98impl VLine {
99    pub fn get(&self) -> usize {
100        self.0
101    }
102}
103
104/// A visual line relative to some other line within the editor view.
105#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
106pub struct RVLine {
107    /// The buffer line this is for
108    pub line: usize,
109    /// The index of the actual visual line's layout
110    pub line_index: usize,
111}
112impl RVLine {
113    pub fn new(line: usize, line_index: usize) -> RVLine {
114        RVLine { line, line_index }
115    }
116
117    /// Is this the first visual line for the buffer line?
118    pub fn is_first(&self) -> bool {
119        self.line_index == 0
120    }
121}
122
123/// (Font Size -> (Buffer Line Number -> Text Layout))
124pub type Layouts = HashMap<usize, HashMap<usize, Arc<TextLayoutLine>>>;
125
126#[derive(Debug, Default, PartialEq, Clone, Copy)]
127pub struct ConfigId {
128    editor_style_id: u64,
129    floem_style_id: u64,
130}
131impl ConfigId {
132    pub fn new(editor_style_id: u64, floem_style_id: u64) -> Self {
133        Self {
134            editor_style_id,
135            floem_style_id,
136        }
137    }
138}
139
140#[derive(Default)]
141pub struct TextLayoutCache {
142    /// The id of the last config so that we can clear when the config changes
143    /// the first is the styling id and the second is an id for changes from Floem style
144    config_id: ConfigId,
145    /// The most recent cache revision of the document.
146    cache_rev: u64,
147    /// (Font Size -> (Buffer Line Number -> Text Layout))
148    ///
149    /// Different font-sizes are cached separately, which is useful for features like code lens
150    /// where the font-size can rapidly change.
151    ///
152    /// It would also be useful for a prospective minimap feature.
153    pub layouts: Layouts,
154    /// The maximum width seen so far, used to determine if we need to show horizontal scrollbar
155    pub max_width: f64,
156}
157impl TextLayoutCache {
158    pub fn clear(&mut self, cache_rev: u64, config_id: Option<ConfigId>) {
159        self.layouts.clear();
160        if let Some(config_id) = config_id {
161            self.config_id = config_id;
162        }
163        self.cache_rev = cache_rev;
164        self.max_width = 0.0;
165    }
166
167    /// Clear the layouts without changing the document cache revision.
168    ///
169    /// Ex: Wrapping width changed, which does not change what the document holds.
170    pub fn clear_unchanged(&mut self) {
171        self.layouts.clear();
172        self.max_width = 0.0;
173    }
174
175    pub fn get(&self, font_size: usize, line: usize) -> Option<&Arc<TextLayoutLine>> {
176        self.layouts.get(&font_size).and_then(|c| c.get(&line))
177    }
178
179    pub fn get_mut(&mut self, font_size: usize, line: usize) -> Option<&mut Arc<TextLayoutLine>> {
180        self.layouts
181            .get_mut(&font_size)
182            .and_then(|c| c.get_mut(&line))
183    }
184
185    /// Get the `(start, end)` columns of the `line` and `line_index`
186    pub fn get_layout_col(
187        &self,
188        text_prov: &impl TextLayoutProvider,
189        font_size: usize,
190        line: usize,
191        line_index: usize,
192    ) -> Option<(usize, usize)> {
193        self.get(font_size, line)
194            .and_then(|l| l.layout_cols(text_prov, line).nth(line_index))
195    }
196}
197
198// TODO(minor): Should we rename this? It does more than just providing the text layout. It provides the text, text layouts, phantom text, and whether it has multiline phantom text. It is more of an outside state.
199/// The [`TextLayoutProvider`] serves two primary roles:
200/// - Providing the [`Rope`] text of the underlying file
201/// - Constructing the text layout for a given line
202///
203/// Note: `text` does not necessarily include every piece of text. The obvious example is phantom
204/// text, which is not in the underlying buffer.
205///
206/// Using this trait rather than passing around something like [Document](super::text::Document) allows the backend to
207/// be swapped out if needed. This would be useful if we ever wanted to reuse it across different
208/// views that did not naturally fit into our 'document' model. As well as when we want to extract
209/// the editor view code int a separate crate for Floem.
210pub trait TextLayoutProvider {
211    fn text(&self) -> Rope;
212
213    /// Shorthand for getting a rope text version of `text`.
214    ///
215    /// This MUST hold the same rope that `text` would return.
216    fn rope_text(&self) -> RopeTextVal {
217        RopeTextVal::new(self.text())
218    }
219
220    // TODO(minor): Do we really need to pass font size to this? The outer-api is providing line
221    // font size provider already, so it should be able to just use that.
222    fn new_text_layout(
223        &self,
224        line: usize,
225        font_size: usize,
226        wrap: ResolvedWrap,
227    ) -> Arc<TextLayoutLine>;
228
229    /// Translate a column position into the position it would be before combining with the phantom
230    /// text
231    fn before_phantom_col(&self, line: usize, col: usize) -> usize;
232
233    /// Whether the text has *any* multiline phantom text.
234    ///
235    /// This is used to determine whether we can use the fast route where the lines are linear,
236    /// which also requires no wrapping.
237    ///
238    /// This should be a conservative estimate, so if you aren't bothering to check all of your
239    /// phantom text then just return true.
240    fn has_multiline_phantom(&self) -> bool;
241}
242impl<T: TextLayoutProvider> TextLayoutProvider for &T {
243    fn text(&self) -> Rope {
244        (**self).text()
245    }
246
247    fn new_text_layout(
248        &self,
249        line: usize,
250        font_size: usize,
251        wrap: ResolvedWrap,
252    ) -> Arc<TextLayoutLine> {
253        (**self).new_text_layout(line, font_size, wrap)
254    }
255
256    fn before_phantom_col(&self, line: usize, col: usize) -> usize {
257        (**self).before_phantom_col(line, col)
258    }
259
260    fn has_multiline_phantom(&self) -> bool {
261        (**self).has_multiline_phantom()
262    }
263}
264
265pub type FontSizeCacheId = u64;
266pub trait LineFontSizeProvider {
267    /// Get the 'general' font size for a specific buffer line.
268    ///
269    /// This is typically the editor font size.
270    ///
271    /// There might be alternate font-sizes within the line, like for phantom text, but those are
272    /// not considered here.
273    fn font_size(&self, line: usize) -> usize;
274
275    /// An identifier used to mark when the font size info has changed.
276    ///
277    /// This lets us update information.
278    fn cache_id(&self) -> FontSizeCacheId;
279}
280
281/// Layout events
282///
283/// This is primarily needed for logic which tracks visual lines intelligently, like
284/// `ScreenLines` in Lapce.
285///
286/// This is currently limited to only a `CreatedLayout` event, as changed to the cache rev would
287/// capture the idea of all the layouts being cleared. In the future it could be expanded to more
288/// events, especially if cache rev gets more specific than clearing everything.
289#[derive(Debug, Clone, PartialEq)]
290pub enum LayoutEvent {
291    CreatedLayout { font_size: usize, line: usize },
292}
293
294/// The main structure for tracking visual line information.
295pub struct Lines {
296    /// This is inside out from the usual way of writing Arc-RefCells due to sometimes wanting to
297    /// swap out font sizes, while also grabbing an `Arc` to hold.
298    ///
299    /// An `Arc<RefCell<_>>` has the issue that with a `dyn` it can't know they're the same size
300    /// if you were to assign. So this allows us to swap out the `Arc`, though it does mean that
301    /// the other holders of the `Arc` don't get the new version. That is fine currently.
302    pub font_sizes: RefCell<Rc<dyn LineFontSizeProvider>>,
303    text_layouts: Rc<RefCell<TextLayoutCache>>,
304    wrap: Cell<ResolvedWrap>,
305    font_size_cache_id: Cell<FontSizeCacheId>,
306    last_vline: Rc<Cell<Option<VLine>>>,
307    pub layout_event: Listener<LayoutEvent>,
308}
309impl Lines {
310    pub fn new(cx: Scope, font_sizes: RefCell<Rc<dyn LineFontSizeProvider>>) -> Lines {
311        let id = font_sizes.borrow().cache_id();
312        Lines {
313            font_sizes,
314            text_layouts: Rc::new(RefCell::new(TextLayoutCache::default())),
315            wrap: Cell::new(ResolvedWrap::None),
316            font_size_cache_id: Cell::new(id),
317            last_vline: Rc::new(Cell::new(None)),
318            layout_event: Listener::new_empty(cx),
319        }
320    }
321
322    /// The current wrapping style
323    pub fn wrap(&self) -> ResolvedWrap {
324        self.wrap.get()
325    }
326
327    /// Set the wrapping style
328    ///
329    /// Does nothing if the wrapping style is the same as the current one.
330    /// Will trigger a clear of the text layouts if the wrapping style is different.
331    pub fn set_wrap(&self, wrap: ResolvedWrap) {
332        if wrap == self.wrap.get() {
333            return;
334        }
335
336        // TODO(perf): We could improve this by only clearing the lines that would actually change
337        // Ex: Single vline lines don't need to be cleared if the wrapping changes from
338        // some width to None, or from some width to some larger width.
339        self.clear_unchanged();
340
341        self.wrap.set(wrap);
342    }
343
344    /// The max width of the text layouts displayed
345    pub fn max_width(&self) -> f64 {
346        self.text_layouts.borrow().max_width
347    }
348
349    /// Check if the lines can be modelled as a purely linear file.
350    ///
351    /// If `true` this makes various operations simpler because there is a one-to-one
352    /// correspondence between visual lines and buffer lines.
353    ///
354    /// However, if there is wrapping or any multiline phantom text, then we can't rely on that.
355    ///
356    /// TODO:?
357    /// We could be smarter about various pieces.
358    ///
359    /// - If there was no lines that exceeded the wrap width then we could do the fast path
360    ///    - Would require tracking that but might not be too hard to do it whenever we create a
361    ///      text layout
362    /// - `is_linear` could be up to some line, which allows us to make at least the earliest parts
363    ///   before any wrapping were faster. However, early lines are faster to calculate anyways.
364    pub fn is_linear(&self, text_prov: impl TextLayoutProvider) -> bool {
365        self.wrap.get() == ResolvedWrap::None && !text_prov.has_multiline_phantom()
366    }
367
368    /// Get the font size that [`Self::font_sizes`] provides
369    pub fn font_size(&self, line: usize) -> usize {
370        self.font_sizes.borrow().font_size(line)
371    }
372
373    /// Get the last visual line of the file.
374    ///
375    /// Cached.
376    pub fn last_vline(&self, text_prov: impl TextLayoutProvider) -> VLine {
377        let current_id = self.font_sizes.borrow().cache_id();
378        if current_id != self.font_size_cache_id.get() {
379            self.last_vline.set(None);
380            self.font_size_cache_id.set(current_id);
381        }
382
383        if let Some(last_vline) = self.last_vline.get() {
384            last_vline
385        } else {
386            // For most files this should easily be fast enough.
387            // Though it could still be improved.
388            let rope_text = text_prov.rope_text();
389            let hard_line_count = rope_text.num_lines();
390
391            let line_count = if self.is_linear(text_prov) {
392                hard_line_count
393            } else {
394                let mut soft_line_count = 0;
395
396                let layouts = self.text_layouts.borrow();
397                for i in 0..hard_line_count {
398                    let font_size = self.font_size(i);
399                    if let Some(text_layout) = layouts.get(font_size, i) {
400                        let line_count = text_layout.line_count();
401                        soft_line_count += line_count;
402                    } else {
403                        soft_line_count += 1;
404                    }
405                }
406
407                soft_line_count
408            };
409
410            let last_vline = line_count.saturating_sub(1);
411            self.last_vline.set(Some(VLine(last_vline)));
412            VLine(last_vline)
413        }
414    }
415
416    /// Clear the cache for the last vline
417    pub fn clear_last_vline(&self) {
418        self.last_vline.set(None);
419    }
420
421    /// The last relative visual line.
422    ///
423    /// Cheap, so not cached
424    pub fn last_rvline(&self, text_prov: impl TextLayoutProvider) -> RVLine {
425        let rope_text = text_prov.rope_text();
426        let last_line = rope_text.last_line();
427        let layouts = self.text_layouts.borrow();
428        let font_size = self.font_size(last_line);
429
430        if let Some(layout) = layouts.get(font_size, last_line) {
431            let line_count = layout.line_count();
432
433            RVLine::new(last_line, line_count - 1)
434        } else {
435            RVLine::new(last_line, 0)
436        }
437    }
438
439    /// 'len' version of [`Lines::last_vline`]
440    ///
441    /// Cached.
442    pub fn num_vlines(&self, text_prov: impl TextLayoutProvider) -> usize {
443        self.last_vline(text_prov).get() + 1
444    }
445
446    /// Get the text layout for the given buffer line number.
447    /// This will create the text layout if it doesn't exist.
448    ///
449    /// `trigger` (default to true) decides whether the creation of the text layout should trigger
450    /// the [`LayoutEvent::CreatedLayout`] event.
451    ///
452    /// This will check the `config_id`, which decides whether it should clear out the text layout
453    /// cache.
454    pub fn get_init_text_layout(
455        &self,
456        cache_rev: u64,
457        config_id: ConfigId,
458        text_prov: impl TextLayoutProvider,
459        line: usize,
460        trigger: bool,
461    ) -> Arc<TextLayoutLine> {
462        self.check_cache(cache_rev, config_id);
463
464        let font_size = self.font_size(line);
465        get_init_text_layout(
466            &self.text_layouts,
467            trigger.then_some(self.layout_event),
468            text_prov,
469            line,
470            font_size,
471            self.wrap.get(),
472            &self.last_vline,
473        )
474    }
475
476    /// Try to get the text layout for the given line number.
477    ///
478    /// This will check the `config_id`, which decides whether it should clear out the text layout
479    /// cache.
480    pub fn try_get_text_layout(
481        &self,
482        cache_rev: u64,
483        config_id: ConfigId,
484        line: usize,
485    ) -> Option<Arc<TextLayoutLine>> {
486        self.check_cache(cache_rev, config_id);
487
488        let font_size = self.font_size(line);
489
490        self.text_layouts
491            .borrow()
492            .layouts
493            .get(&font_size)
494            .and_then(|f| f.get(&line))
495            .cloned()
496    }
497
498    /// Initialize the text layout of every line in the real line interval.
499    ///
500    /// `trigger` (default to true) decides whether the creation of the text layout should trigger
501    /// the [`LayoutEvent::CreatedLayout`] event.
502    pub fn init_line_interval(
503        &self,
504        cache_rev: u64,
505        config_id: ConfigId,
506        text_prov: &impl TextLayoutProvider,
507        lines: impl Iterator<Item = usize>,
508        trigger: bool,
509    ) {
510        for line in lines {
511            self.get_init_text_layout(cache_rev, config_id, text_prov, line, trigger);
512        }
513    }
514
515    /// Initialize the text layout of every line in the file.
516    /// This should typically not be used.
517    ///
518    /// `trigger` (default to true) decides whether the creation of the text layout should trigger
519    /// the [`LayoutEvent::CreatedLayout`] event.
520    pub fn init_all(
521        &self,
522        cache_rev: u64,
523        config_id: ConfigId,
524        text_prov: &impl TextLayoutProvider,
525        trigger: bool,
526    ) {
527        let text = text_prov.text();
528        let last_line = text.line_of_offset(text.len());
529        self.init_line_interval(cache_rev, config_id, text_prov, 0..=last_line, trigger);
530    }
531
532    /// Iterator over [`VLineInfo`]s, starting at `start_line`.
533    pub fn iter_vlines(
534        &self,
535        text_prov: impl TextLayoutProvider,
536        backwards: bool,
537        start: VLine,
538    ) -> impl Iterator<Item = VLineInfo> {
539        VisualLines::new(self, text_prov, backwards, start)
540    }
541
542    /// Iterator over [`VLineInfo`]s, starting at `start_line` and ending at `end_line`.
543    ///
544    /// `start_line..end_line`
545    pub fn iter_vlines_over(
546        &self,
547        text_prov: impl TextLayoutProvider,
548        backwards: bool,
549        start: VLine,
550        end: VLine,
551    ) -> impl Iterator<Item = VLineInfo> {
552        self.iter_vlines(text_prov, backwards, start)
553            .take_while(move |info| info.vline < end)
554    }
555
556    /// Iterator over *relative* [`VLineInfo`]s, starting at the rvline, `start_line`.
557    ///
558    /// This is preferable over `iter_vlines` if you do not need to absolute visual line value and
559    /// can provide the buffer line.
560    pub fn iter_rvlines(
561        &self,
562        text_prov: impl TextLayoutProvider,
563        backwards: bool,
564        start: RVLine,
565    ) -> impl Iterator<Item = VLineInfo<()>> {
566        VisualLinesRelative::new(self, text_prov, backwards, start)
567    }
568
569    /// Iterator over *relative* [`VLineInfo`]s, starting at the rvline `start_line` and
570    /// ending at the buffer line `end_line`.
571    ///
572    /// `start_line..end_line`
573    ///
574    /// This is preferable over `iter_vlines` if you do not need the absolute visual line value and
575    /// you can provide the buffer line.
576    pub fn iter_rvlines_over(
577        &self,
578        text_prov: impl TextLayoutProvider,
579        backwards: bool,
580        start: RVLine,
581        end_line: usize,
582    ) -> impl Iterator<Item = VLineInfo<()>> {
583        self.iter_rvlines(text_prov, backwards, start)
584            .take_while(move |info| info.rvline.line < end_line)
585    }
586
587    // TODO(minor): Get rid of the clone bound.
588    /// Initialize the text layouts as you iterate over them.
589    pub fn iter_vlines_init(
590        &self,
591        text_prov: impl TextLayoutProvider + Clone,
592        cache_rev: u64,
593        config_id: ConfigId,
594        start: VLine,
595        trigger: bool,
596    ) -> impl Iterator<Item = VLineInfo> {
597        self.check_cache(cache_rev, config_id);
598
599        if start <= self.last_vline(&text_prov) {
600            // We initialize the text layout for the line that start line is for
601            let (_, rvline) = find_vline_init_info(self, &text_prov, start).unwrap();
602            self.get_init_text_layout(cache_rev, config_id, &text_prov, rvline.line, trigger);
603            // If the start line was past the last vline then we don't need to initialize anything
604            // since it won't get anything.
605        }
606
607        let text_layouts = self.text_layouts.clone();
608        let font_sizes = self.font_sizes.clone();
609        let wrap = self.wrap.get();
610        let last_vline = self.last_vline.clone();
611        let layout_event = trigger.then_some(self.layout_event);
612        self.iter_vlines(text_prov.clone(), false, start)
613            .inspect(move |v| {
614                if v.is_first() {
615                    // For every (first) vline we initialize the next buffer line's text layout
616                    // This ensures it is ready for when re reach it.
617                    let next_line = v.rvline.line + 1;
618                    let font_size = font_sizes.borrow().font_size(next_line);
619                    // `init_iter_vlines` is the reason `get_init_text_layout` is split out.
620                    // Being split out lets us avoid attaching lifetimes to the iterator, since it
621                    // only uses Rc/Arcs it is given.
622                    // This is useful since `Lines` would be in a
623                    // `Rc<RefCell<_>>` which would make iterators with lifetimes referring to
624                    // `Lines` a pain.
625                    get_init_text_layout(
626                        &text_layouts,
627                        layout_event,
628                        &text_prov,
629                        next_line,
630                        font_size,
631                        wrap,
632                        &last_vline,
633                    );
634                }
635            })
636    }
637
638    /// Iterator over [`VLineInfo`]s, starting at `start_line` and ending at `end_line`.
639    /// `start_line..end_line`
640    ///
641    /// Initializes the text layouts as you iterate over them.
642    ///
643    /// `trigger` (default to true) decides whether the creation of the text layout should trigger
644    /// the [`LayoutEvent::CreatedLayout`] event.
645    pub fn iter_vlines_init_over(
646        &self,
647        text_prov: impl TextLayoutProvider + Clone,
648        cache_rev: u64,
649        config_id: ConfigId,
650        start: VLine,
651        end: VLine,
652        trigger: bool,
653    ) -> impl Iterator<Item = VLineInfo> {
654        self.iter_vlines_init(text_prov, cache_rev, config_id, start, trigger)
655            .take_while(move |info| info.vline < end)
656    }
657
658    /// Iterator over *relative* [`VLineInfo`]s, starting at the rvline, `start_line` and
659    /// ending at the buffer line `end_line`.
660    ///
661    /// `start_line..end_line`
662    ///
663    /// `trigger` (default to true) decides whether the creation of the text layout should trigger
664    /// the [`LayoutEvent::CreatedLayout`] event.
665    pub fn iter_rvlines_init(
666        &self,
667        text_prov: impl TextLayoutProvider + Clone,
668        cache_rev: u64,
669        config_id: ConfigId,
670        start: RVLine,
671        trigger: bool,
672    ) -> impl Iterator<Item = VLineInfo<()>> {
673        self.check_cache(cache_rev, config_id);
674
675        if start.line <= text_prov.rope_text().last_line() {
676            // Initialize the text layout for the line that start line is for
677            self.get_init_text_layout(cache_rev, config_id, &text_prov, start.line, trigger);
678        }
679
680        let text_layouts = self.text_layouts.clone();
681        let font_sizes = self.font_sizes.clone();
682        let wrap = self.wrap.get();
683        let last_vline = self.last_vline.clone();
684        let layout_event = trigger.then_some(self.layout_event);
685        self.iter_rvlines(text_prov.clone(), false, start)
686            .inspect(move |v| {
687                if v.is_first() {
688                    // For every (first) vline we initialize the next buffer line's text layout
689                    // This ensures it is ready for when re reach it.
690                    let next_line = v.rvline.line + 1;
691                    let font_size = font_sizes.borrow().font_size(next_line);
692                    // `init_iter_lines` is the reason `get_init_text_layout` is split out.
693                    // Being split out lets us avoid attaching lifetimes to the iterator, since it
694                    // only uses Rc/Arcs that it. This is useful since `Lines` would be in a
695                    // `Rc<RefCell<_>>` which would make iterators with lifetimes referring to
696                    // `Lines` a pain.
697                    get_init_text_layout(
698                        &text_layouts,
699                        layout_event,
700                        &text_prov,
701                        next_line,
702                        font_size,
703                        wrap,
704                        &last_vline,
705                    );
706                }
707            })
708    }
709
710    /// Get the visual line of the offset.
711    ///
712    /// `affinity` decides whether an offset at a soft line break is considered to be on the
713    /// previous line or the next line.
714    ///
715    /// If `affinity` is `CursorAffinity::Forward` and is at the very end of the wrapped line, then
716    /// the offset is considered to be on the next vline.
717    pub fn vline_of_offset(
718        &self,
719        text_prov: &impl TextLayoutProvider,
720        offset: usize,
721        affinity: CursorAffinity,
722    ) -> VLine {
723        let text = text_prov.text();
724
725        let offset = offset.min(text.len());
726
727        if self.is_linear(text_prov) {
728            let buffer_line = text.line_of_offset(offset);
729            return VLine(buffer_line);
730        }
731
732        let Some((vline, _line_index)) = find_vline_of_offset(self, text_prov, offset, affinity)
733        else {
734            // We assume it is out of bounds
735            return self.last_vline(text_prov);
736        };
737
738        vline
739    }
740
741    /// Get the visual line and column of the given offset.
742    ///
743    /// The column is before phantom text is applied and is into the overall line, not the
744    /// individual visual line.
745    pub fn vline_col_of_offset(
746        &self,
747        text_prov: &impl TextLayoutProvider,
748        offset: usize,
749        affinity: CursorAffinity,
750    ) -> (VLine, usize) {
751        let vline = self.vline_of_offset(text_prov, offset, affinity);
752        let last_col = self
753            .iter_vlines(text_prov, false, vline)
754            .next()
755            .map(|info| info.last_col(text_prov, true))
756            .unwrap_or(0);
757
758        let line = text_prov.text().line_of_offset(offset);
759        let line_offset = text_prov.text().offset_of_line(line);
760
761        let col = offset - line_offset;
762        let col = col.min(last_col);
763
764        (vline, col)
765    }
766
767    /// Get the nearest offset to the start of the visual line
768    pub fn offset_of_vline(&self, text_prov: &impl TextLayoutProvider, vline: VLine) -> usize {
769        find_vline_init_info(self, text_prov, vline)
770            .map(|x| x.0)
771            .unwrap_or_else(|| text_prov.text().len())
772    }
773
774    /// Get the first visual line of the buffer line.
775    pub fn vline_of_line(&self, text_prov: &impl TextLayoutProvider, line: usize) -> VLine {
776        if self.is_linear(text_prov) {
777            return VLine(line);
778        }
779
780        find_vline_of_line(self, text_prov, line).unwrap_or_else(|| self.last_vline(text_prov))
781    }
782
783    /// Find the matching visual line for the given relative visual line.
784    pub fn vline_of_rvline(&self, text_prov: &impl TextLayoutProvider, rvline: RVLine) -> VLine {
785        if self.is_linear(text_prov) {
786            debug_assert_eq!(
787                rvline.line_index, 0,
788                "Got a nonzero line index despite being linear, old RVLine was used."
789            );
790            return VLine(rvline.line);
791        }
792
793        let vline = self.vline_of_line(text_prov, rvline.line);
794
795        // TODO(minor): There may be edge cases with this, like when you have a bunch of multiline
796        // phantom text at the same offset
797        VLine(vline.get() + rvline.line_index)
798    }
799
800    /// Get the relative visual line of the offset.
801    ///
802    /// `affinity` decides whether an offset at a soft line break is considered to be on the
803    /// previous line or the next line.
804    /// If `affinity` is `CursorAffinity::Forward` and is at the very end of the wrapped line, then
805    /// the offset is considered to be on the next rvline.
806    pub fn rvline_of_offset(
807        &self,
808        text_prov: &impl TextLayoutProvider,
809        offset: usize,
810        affinity: CursorAffinity,
811    ) -> RVLine {
812        let text = text_prov.text();
813
814        let offset = offset.min(text.len());
815
816        if self.is_linear(text_prov) {
817            let buffer_line = text.line_of_offset(offset);
818            return RVLine::new(buffer_line, 0);
819        }
820
821        find_rvline_of_offset(self, text_prov, offset, affinity)
822            .unwrap_or_else(|| self.last_rvline(text_prov))
823    }
824
825    /// Get the relative visual line and column of the given offset
826    ///
827    /// The column is before phantom text is applied and is into the overall line, not the
828    /// individual visual line.
829    pub fn rvline_col_of_offset(
830        &self,
831        text_prov: &impl TextLayoutProvider,
832        offset: usize,
833        affinity: CursorAffinity,
834    ) -> (RVLine, usize) {
835        let rvline = self.rvline_of_offset(text_prov, offset, affinity);
836        let info = self.iter_rvlines(text_prov, false, rvline).next().unwrap();
837        let line_offset = text_prov.text().offset_of_line(rvline.line);
838
839        let col = offset - line_offset;
840        let col = col.min(info.last_col(text_prov, true));
841
842        (rvline, col)
843    }
844
845    /// Get the offset of a relative visual line
846    pub fn offset_of_rvline(
847        &self,
848        text_prov: &impl TextLayoutProvider,
849        RVLine { line, line_index }: RVLine,
850    ) -> usize {
851        let rope_text = text_prov.rope_text();
852        let font_size = self.font_size(line);
853        let layouts = self.text_layouts.borrow();
854
855        // We could remove the debug asserts and allow invalid line indices. However I think it is
856        // desirable to avoid those since they are probably indicative of bugs.
857        if let Some(text_layout) = layouts.get(font_size, line) {
858            debug_assert!(
859                line_index < text_layout.line_count(),
860                "Line index was out of bounds. This likely indicates keeping an rvline past when it was valid."
861            );
862
863            let line_index = line_index.min(text_layout.line_count() - 1);
864
865            let col = text_layout.start_layout_cols().nth(line_index).unwrap_or(0);
866            let col = text_prov.before_phantom_col(line, col);
867
868            rope_text.offset_of_line_col(line, col)
869        } else {
870            // There was no text layout for this line, so we treat it like if line index is zero
871            // even if it is not.
872
873            debug_assert_eq!(
874                line_index, 0,
875                "Line index was zero. This likely indicates keeping an rvline past when it was valid."
876            );
877
878            rope_text.offset_of_line(line)
879        }
880    }
881
882    /// Get the relative visual line of the buffer line
883    pub fn rvline_of_line(&self, text_prov: &impl TextLayoutProvider, line: usize) -> RVLine {
884        if self.is_linear(text_prov) {
885            return RVLine::new(line, 0);
886        }
887
888        let offset = text_prov.rope_text().offset_of_line(line);
889
890        find_rvline_of_offset(self, text_prov, offset, CursorAffinity::Backward)
891            .unwrap_or_else(|| self.last_rvline(text_prov))
892    }
893
894    /// Check whether the cache rev or config id has changed, clearing the cache if it has.
895    pub fn check_cache(&self, cache_rev: u64, config_id: ConfigId) {
896        let (prev_cache_rev, prev_config_id) = {
897            let l = self.text_layouts.borrow();
898            (l.cache_rev, l.config_id)
899        };
900
901        if cache_rev != prev_cache_rev || config_id != prev_config_id {
902            self.clear(cache_rev, Some(config_id));
903        }
904    }
905
906    /// Check whether the text layout cache revision is different.
907    ///
908    /// Clears the layouts and updates the cache rev if it was different.
909    pub fn check_cache_rev(&self, cache_rev: u64) {
910        if cache_rev != self.text_layouts.borrow().cache_rev {
911            self.clear(cache_rev, None);
912        }
913    }
914
915    /// Clear the text layouts with a given cache revision
916    pub fn clear(&self, cache_rev: u64, config_id: Option<ConfigId>) {
917        self.text_layouts.borrow_mut().clear(cache_rev, config_id);
918        self.last_vline.set(None);
919    }
920
921    /// Clear the layouts and vline without changing the cache rev or config id.
922    pub fn clear_unchanged(&self) {
923        self.text_layouts.borrow_mut().clear_unchanged();
924        self.last_vline.set(None);
925    }
926}
927
928/// This is a separate function as a hacky solution to lifetimes.
929///
930/// While it being on `Lines` makes the most sense, it being separate lets us only have
931/// `text_layouts` and `wrap` from the original to then initialize a text layout. This simplifies
932/// lifetime issues in some functions, since they can just have an `Arc`/`Rc`.
933///
934/// Note: This does not clear the cache or check via config id. That should be done outside this
935/// as `Lines` does require knowing when the cache is invalidated.
936fn get_init_text_layout(
937    text_layouts: &RefCell<TextLayoutCache>,
938    layout_event: Option<Listener<LayoutEvent>>,
939    text_prov: impl TextLayoutProvider,
940    line: usize,
941    font_size: usize,
942    wrap: ResolvedWrap,
943    last_vline: &Cell<Option<VLine>>,
944) -> Arc<TextLayoutLine> {
945    // If we don't have a second layer of the hashmap initialized for this specific font size,
946    // do it now
947    if !text_layouts.borrow().layouts.contains_key(&font_size) {
948        let mut cache = text_layouts.borrow_mut();
949        cache.layouts.insert(font_size, HashMap::new());
950    }
951
952    // Get whether there's an entry for this specific font size and line
953    let cache_exists = text_layouts
954        .borrow()
955        .layouts
956        .get(&font_size)
957        .unwrap()
958        .get(&line)
959        .is_some();
960    // If there isn't an entry then we actually have to create it
961    if !cache_exists {
962        let text_layout = text_prov.new_text_layout(line, font_size, wrap);
963
964        // Update last vline
965        if let Some(vline) = last_vline.get() {
966            let last_line = text_prov.rope_text().last_line();
967            if line <= last_line {
968                // We can get rid of the old line count and add our new count.
969                // This lets us typically avoid having to calculate the last visual line.
970                let vline = vline.get();
971                let new_vline = vline + (text_layout.line_count() - 1);
972
973                last_vline.set(Some(VLine(new_vline)));
974            }
975            // If the line is past the end of the file, then we don't need to update the last
976            // visual line. It is garbage.
977        }
978        // Otherwise last vline was already None.
979
980        {
981            // Add the text layout to the cache.
982            let mut cache = text_layouts.borrow_mut();
983            let width = text_layout.text.size().width;
984            if width > cache.max_width {
985                cache.max_width = width;
986            }
987            cache
988                .layouts
989                .get_mut(&font_size)
990                .unwrap()
991                .insert(line, text_layout);
992        }
993
994        if let Some(layout_event) = layout_event {
995            layout_event.send(LayoutEvent::CreatedLayout { font_size, line });
996        }
997    }
998
999    // Just get the entry, assuming it has been created because we initialize it above.
1000    text_layouts
1001        .borrow()
1002        .layouts
1003        .get(&font_size)
1004        .unwrap()
1005        .get(&line)
1006        .cloned()
1007        .unwrap()
1008}
1009
1010/// Returns `(visual line, line_index)`
1011fn find_vline_of_offset(
1012    lines: &Lines,
1013    text_prov: &impl TextLayoutProvider,
1014    offset: usize,
1015    affinity: CursorAffinity,
1016) -> Option<(VLine, usize)> {
1017    let layouts = lines.text_layouts.borrow();
1018
1019    let rope_text = text_prov.rope_text();
1020
1021    let buffer_line = rope_text.line_of_offset(offset);
1022    let line_start_offset = rope_text.offset_of_line(buffer_line);
1023    let vline = find_vline_of_line(lines, text_prov, buffer_line)?;
1024
1025    let font_size = lines.font_size(buffer_line);
1026    let Some(text_layout) = layouts.get(font_size, buffer_line) else {
1027        // No text layout for this line, so the vline we found is definitely correct.
1028        // As well, there is no previous soft line to consider
1029        return Some((vline, 0));
1030    };
1031
1032    let col = offset - line_start_offset;
1033
1034    let (vline, line_index) =
1035        find_start_line_index(text_prov, text_layout, buffer_line, col, affinity)
1036            .map(|line_index| (VLine(vline.get() + line_index), line_index))?;
1037
1038    // If the most recent line break was due to a soft line break,
1039    if line_index > 0
1040        && let CursorAffinity::Backward = affinity
1041    {
1042        // TODO: This can definitely be smarter. We're doing a vline search, and then this is
1043        // practically doing another!
1044        let line_end = lines.offset_of_vline(text_prov, vline);
1045        // then if we're right at that soft line break, a backwards affinity
1046        // means that we are on the previous visual line.
1047        if line_end == offset && vline.get() != 0 {
1048            return Some((VLine(vline.get() - 1), line_index - 1));
1049        }
1050    }
1051
1052    Some((vline, line_index))
1053}
1054
1055fn find_rvline_of_offset(
1056    lines: &Lines,
1057    text_prov: &impl TextLayoutProvider,
1058    offset: usize,
1059    affinity: CursorAffinity,
1060) -> Option<RVLine> {
1061    let layouts = lines.text_layouts.borrow();
1062
1063    let rope_text = text_prov.rope_text();
1064
1065    let buffer_line = rope_text.line_of_offset(offset);
1066    let line_start_offset = rope_text.offset_of_line(buffer_line);
1067
1068    let font_size = lines.font_size(buffer_line);
1069    let Some(text_layout) = layouts.get(font_size, buffer_line) else {
1070        // There is no text layout for this line so the line index is always zero.
1071        return Some(RVLine::new(buffer_line, 0));
1072    };
1073
1074    let col = offset - line_start_offset;
1075
1076    let rv = find_start_line_index(text_prov, text_layout, buffer_line, col, affinity)
1077        .map(|line_index| RVLine::new(buffer_line, line_index))?;
1078
1079    // If the most recent line break was due to a soft line break,
1080    if rv.line_index > 0
1081        && let CursorAffinity::Backward = affinity
1082    {
1083        let line_end = lines.offset_of_rvline(text_prov, rv);
1084        // then if we're right at that soft line break, a backwards affinity
1085        // means that we are on the previous visual line.
1086        if line_end == offset {
1087            if rv.line_index > 0 {
1088                return Some(RVLine::new(rv.line, rv.line_index - 1));
1089            } else if rv.line == 0 {
1090                // There is no previous line, we do nothing.
1091            } else {
1092                // We have to get rvline info for that rvline, so we can get the last line index
1093                // This should always have at least one rvline in it.
1094                let font_sizes = lines.font_sizes.borrow();
1095                let (prev, _) = prev_rvline(&layouts, text_prov, &**font_sizes, rv)?;
1096                return Some(prev);
1097            }
1098        }
1099    }
1100
1101    Some(rv)
1102}
1103
1104// TODO: a lot of these just take lines, so should possibly just be put on it.
1105
1106/// Find the line index which contains the column.
1107fn find_start_line_index(
1108    text_prov: &impl TextLayoutProvider,
1109    text_layout: &TextLayoutLine,
1110    line: usize,
1111    col: usize,
1112    affinity: CursorAffinity,
1113) -> Option<usize> {
1114    let mut starts = text_layout.start_layout_cols().enumerate().peekable();
1115
1116    while let Some((i, layout_start)) = starts.next() {
1117        if affinity == CursorAffinity::Backward {
1118            // TODO: we should just apply after_col to col to do this transformation once
1119            let layout_start = text_prov.before_phantom_col(line, layout_start);
1120            if layout_start >= col {
1121                return Some(i);
1122            }
1123        }
1124
1125        let next_start = starts
1126            .peek()
1127            .map(|(_, next_start)| text_prov.before_phantom_col(line, *next_start));
1128
1129        if let Some(next_start) = next_start {
1130            if next_start > col {
1131                // The next layout starts *past* our column, so we're on the previous line.
1132                return Some(i);
1133            }
1134        } else {
1135            // There was no next glyph, which implies that we are either on this line or not at all
1136            return Some(i);
1137        }
1138    }
1139
1140    None
1141}
1142
1143/// Get the first visual line of a buffer line.
1144fn find_vline_of_line(
1145    lines: &Lines,
1146    text_prov: &impl TextLayoutProvider,
1147    line: usize,
1148) -> Option<VLine> {
1149    let rope = text_prov.rope_text();
1150
1151    let last_line = rope.last_line();
1152
1153    if line > last_line / 2 {
1154        // Often the last vline will already be cached, which lets us half the search time.
1155        // The compiler may or may not be smart enough to combine the last vline calculation with
1156        // our calculation of the vline of the line we're looking for, but it might not.
1157        // If it doesn't, we could write a custom version easily.
1158        let last_vline = lines.last_vline(text_prov);
1159        let last_rvline = lines.last_rvline(text_prov);
1160        let last_start_vline = VLine(last_vline.get() - last_rvline.line_index);
1161        find_vline_of_line_backwards(lines, (last_start_vline, last_line), line)
1162    } else {
1163        find_vline_of_line_forwards(lines, (VLine(0), 0), line)
1164    }
1165}
1166
1167/// Get the first visual line of a buffer line.
1168///
1169/// This searches backwards from `pivot`, so it should be *after* the given line.
1170/// This requires that the `pivot` is the first line index of the line it is for.
1171fn find_vline_of_line_backwards(
1172    lines: &Lines,
1173    (start, s_line): (VLine, usize),
1174    line: usize,
1175) -> Option<VLine> {
1176    if line > s_line {
1177        return None;
1178    } else if line == s_line {
1179        return Some(start);
1180    } else if line == 0 {
1181        return Some(VLine(0));
1182    }
1183
1184    let layouts = lines.text_layouts.borrow();
1185
1186    let mut cur_vline = start.get();
1187
1188    for cur_line in line..s_line {
1189        let font_size = lines.font_size(cur_line);
1190
1191        let Some(text_layout) = layouts.get(font_size, cur_line) else {
1192            // no text layout, so its just a normal line
1193            cur_vline -= 1;
1194            continue;
1195        };
1196
1197        let line_count = text_layout.line_count();
1198
1199        cur_vline -= line_count;
1200    }
1201
1202    Some(VLine(cur_vline))
1203}
1204
1205fn find_vline_of_line_forwards(
1206    lines: &Lines,
1207    (start, s_line): (VLine, usize),
1208    line: usize,
1209) -> Option<VLine> {
1210    match line.cmp(&s_line) {
1211        Ordering::Equal => return Some(start),
1212        Ordering::Less => return None,
1213        Ordering::Greater => (),
1214    }
1215
1216    let layouts = lines.text_layouts.borrow();
1217
1218    let mut cur_vline = start.get();
1219
1220    for cur_line in s_line..line {
1221        let font_size = lines.font_size(cur_line);
1222
1223        let Some(text_layout) = layouts.get(font_size, cur_line) else {
1224            // no text layout, so its just a normal line
1225            cur_vline += 1;
1226            continue;
1227        };
1228
1229        let line_count = text_layout.line_count();
1230        cur_vline += line_count;
1231    }
1232
1233    Some(VLine(cur_vline))
1234}
1235
1236/// Find the (start offset, buffer line, layout line index) of a given visual line.
1237///
1238/// start offset is into the file, rather than the text layouts string, so it does not include
1239/// phantom text.
1240///
1241/// Returns `None` if the visual line is out of bounds.
1242fn find_vline_init_info(
1243    lines: &Lines,
1244    text_prov: &impl TextLayoutProvider,
1245    vline: VLine,
1246) -> Option<(usize, RVLine)> {
1247    let rope_text = text_prov.rope_text();
1248
1249    if vline.get() == 0 {
1250        return Some((0, RVLine::new(0, 0)));
1251    }
1252
1253    if lines.is_linear(text_prov) {
1254        // If lines is linear then we can trivially convert the visual line to a buffer line
1255        let line = vline.get();
1256        if line > rope_text.last_line() {
1257            return None;
1258        }
1259
1260        return Some((rope_text.offset_of_line(line), RVLine::new(line, 0)));
1261    }
1262
1263    let last_vline = lines.last_vline(text_prov);
1264
1265    if vline > last_vline {
1266        return None;
1267    }
1268
1269    if vline.get() > last_vline.get() / 2 {
1270        let last_rvline = lines.last_rvline(text_prov);
1271        find_vline_init_info_rv_backward(lines, text_prov, (last_vline, last_rvline), vline)
1272    } else {
1273        find_vline_init_info_forward(lines, text_prov, (VLine(0), 0), vline)
1274    }
1275}
1276
1277// TODO(minor): should we package (VLine, buffer line) into a struct since we use it for these
1278// pseudo relative calculations often?
1279/// Find the `(start offset, rvline)` of a given [`VLine`]
1280///
1281/// start offset is into the file, rather than text layout's string, so it does not include
1282/// phantom text.
1283///
1284/// Returns `None` if the visual line is out of bounds, or if the start is past our target.
1285fn find_vline_init_info_forward(
1286    lines: &Lines,
1287    text_prov: &impl TextLayoutProvider,
1288    (start, start_line): (VLine, usize),
1289    vline: VLine,
1290) -> Option<(usize, RVLine)> {
1291    if start > vline {
1292        return None;
1293    }
1294
1295    let rope_text = text_prov.rope_text();
1296
1297    let mut cur_line = start_line;
1298    let mut cur_vline = start.get();
1299
1300    let layouts = lines.text_layouts.borrow();
1301    while cur_vline < vline.get() {
1302        let font_size = lines.font_size(cur_line);
1303        let line_count = if let Some(text_layout) = layouts.get(font_size, cur_line) {
1304            let line_count = text_layout.line_count();
1305
1306            // We can then check if the visual line is in this intervening range.
1307            if cur_vline + line_count > vline.get() {
1308                // We found the line that contains the visual line.
1309                // We can now find the offset of the visual line within the line.
1310                let line_index = vline.get() - cur_vline;
1311                // TODO: is it fine to unwrap here?
1312                let col = text_layout.start_layout_cols().nth(line_index).unwrap_or(0);
1313                let col = text_prov.before_phantom_col(cur_line, col);
1314
1315                let offset = rope_text.offset_of_line_col(cur_line, col);
1316                return Some((offset, RVLine::new(cur_line, line_index)));
1317            }
1318
1319            // The visual line is not in this line, so we have to keep looking.
1320            line_count
1321        } else {
1322            // There was no text layout so we only have to consider the line breaks in this line.
1323            // Which, since we don't handle phantom text, is just one.
1324
1325            1
1326        };
1327
1328        cur_line += 1;
1329        cur_vline += line_count;
1330    }
1331
1332    // We've reached the visual line we're looking for, we can return the offset.
1333    // This also handles the case where the vline is past the end of the text.
1334    if cur_vline == vline.get() {
1335        if cur_line > rope_text.last_line() {
1336            return None;
1337        }
1338
1339        // We use cur_line because if our target vline is out of bounds
1340        // then the result should be len
1341        Some((rope_text.offset_of_line(cur_line), RVLine::new(cur_line, 0)))
1342    } else {
1343        // We've gone past the visual line we're looking for, so it is out of bounds.
1344        None
1345    }
1346}
1347
1348/// Find the `(start offset, rvline)` of a given [`VLine`]
1349///
1350/// `start offset` is into the file, rather than the text layout's content, so it does not
1351/// include phantom text.
1352///
1353/// Returns `None` if the visual line is out of bounds or if the start is before our target.
1354/// This iterates backwards.
1355fn find_vline_init_info_rv_backward(
1356    lines: &Lines,
1357    text_prov: &impl TextLayoutProvider,
1358    (start, start_rvline): (VLine, RVLine),
1359    vline: VLine,
1360) -> Option<(usize, RVLine)> {
1361    if start < vline {
1362        // The start was before the target.
1363        return None;
1364    }
1365
1366    // This would the vline at the very start of the buffer line
1367    let shifted_start = VLine(start.get() - start_rvline.line_index);
1368    match shifted_start.cmp(&vline) {
1369        // The shifted start was equivalent to the vline, which makes it easy to compute
1370        Ordering::Equal => {
1371            let offset = text_prov.rope_text().offset_of_line(start_rvline.line);
1372            Some((offset, RVLine::new(start_rvline.line, 0)))
1373        }
1374        // The new start is before the vline, that means the vline is on the same line.
1375        Ordering::Less => {
1376            let line_index = vline.get() - shifted_start.get();
1377            let layouts = lines.text_layouts.borrow();
1378            let font_size = lines.font_size(start_rvline.line);
1379            if let Some(text_layout) = layouts.get(font_size, start_rvline.line) {
1380                vline_init_info_b(
1381                    text_prov,
1382                    text_layout,
1383                    RVLine::new(start_rvline.line, line_index),
1384                )
1385            } else {
1386                // There was no text layout so we only have to consider the line breaks in this line.
1387
1388                let base_offset = text_prov.rope_text().offset_of_line(start_rvline.line);
1389                Some((base_offset, RVLine::new(start_rvline.line, 0)))
1390            }
1391        }
1392        Ordering::Greater => find_vline_init_info_backward(
1393            lines,
1394            text_prov,
1395            (shifted_start, start_rvline.line),
1396            vline,
1397        ),
1398    }
1399}
1400
1401fn find_vline_init_info_backward(
1402    lines: &Lines,
1403    text_prov: &impl TextLayoutProvider,
1404    (mut start, mut start_line): (VLine, usize),
1405    vline: VLine,
1406) -> Option<(usize, RVLine)> {
1407    loop {
1408        let (prev_vline, prev_line) = prev_line_start(lines, start, start_line)?;
1409
1410        match prev_vline.cmp(&vline) {
1411            // We found the target, and it was at the start
1412            Ordering::Equal => {
1413                let offset = text_prov.rope_text().offset_of_line(prev_line);
1414                return Some((offset, RVLine::new(prev_line, 0)));
1415            }
1416            // The target is on this line, so we can just search for it
1417            Ordering::Less => {
1418                let font_size = lines.font_size(prev_line);
1419                let layouts = lines.text_layouts.borrow();
1420                if let Some(text_layout) = layouts.get(font_size, prev_line) {
1421                    return vline_init_info_b(
1422                        text_prov,
1423                        text_layout,
1424                        RVLine::new(prev_line, vline.get() - prev_vline.get()),
1425                    );
1426                } else {
1427                    // There was no text layout so we only have to consider the line breaks in this line.
1428                    // Which, since we don't handle phantom text, is just one.
1429
1430                    let base_offset = text_prov.rope_text().offset_of_line(prev_line);
1431                    return Some((base_offset, RVLine::new(prev_line, 0)));
1432                }
1433            }
1434            // The target is before this line, so we have to keep searching
1435            Ordering::Greater => {
1436                start = prev_vline;
1437                start_line = prev_line;
1438            }
1439        }
1440    }
1441}
1442
1443/// Get the previous (line, start visual line) from a (line, start visual line).
1444fn prev_line_start(lines: &Lines, vline: VLine, line: usize) -> Option<(VLine, usize)> {
1445    if line == 0 {
1446        return None;
1447    }
1448
1449    let layouts = lines.text_layouts.borrow();
1450
1451    let prev_line = line - 1;
1452    let font_size = lines.font_size(line);
1453    if let Some(layout) = layouts.get(font_size, prev_line) {
1454        let line_count = layout.line_count();
1455        let prev_vline = vline.get() - line_count;
1456        Some((VLine(prev_vline), prev_line))
1457    } else {
1458        // There's no layout for the previous line which makes this easy
1459        Some((VLine(vline.get() - 1), prev_line))
1460    }
1461}
1462
1463fn vline_init_info_b(
1464    text_prov: &impl TextLayoutProvider,
1465    text_layout: &TextLayoutLine,
1466    rv: RVLine,
1467) -> Option<(usize, RVLine)> {
1468    let rope_text = text_prov.rope_text();
1469    let col = text_layout
1470        .start_layout_cols()
1471        .nth(rv.line_index)
1472        .unwrap_or(0);
1473    let col = text_prov.before_phantom_col(rv.line, col);
1474
1475    let offset = rope_text.offset_of_line_col(rv.line, col);
1476
1477    Some((offset, rv))
1478}
1479
1480/// Information about the visual line and how it relates to the underlying buffer line.
1481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1482#[non_exhaustive]
1483pub struct VLineInfo<L = VLine> {
1484    /// Start offset to end offset in the buffer that this visual line covers.
1485    ///
1486    /// Note that this is obviously not including phantom text.
1487    pub interval: Interval,
1488    /// The total number of lines in this buffer line. Always at least 1.
1489    pub line_count: usize,
1490    pub rvline: RVLine,
1491    /// The actual visual line this is for.
1492    ///
1493    /// For relative visual line iteration, this is empty.
1494    pub vline: L,
1495}
1496impl<L: std::fmt::Debug> VLineInfo<L> {
1497    /// Create a new instance of `VLineInfo`
1498    ///
1499    /// This should rarely be used directly.
1500    pub fn new<I: Into<Interval>>(iv: I, rvline: RVLine, line_count: usize, vline: L) -> Self {
1501        Self {
1502            interval: iv.into(),
1503            line_count,
1504            rvline,
1505            vline,
1506        }
1507    }
1508
1509    pub fn to_blank(&self) -> VLineInfo<()> {
1510        VLineInfo::new(self.interval, self.rvline, self.line_count, ())
1511    }
1512
1513    /// Check whether the interval is empty.
1514    ///
1515    /// Note that there could still be phantom text on this line.
1516    pub fn is_empty(&self) -> bool {
1517        self.interval.is_empty()
1518    }
1519
1520    /// Check whether the interval is empty and we're not on the first line,
1521    /// thus likely being phantom text (or possibly poor wrapping)
1522    pub fn is_empty_phantom(&self) -> bool {
1523        self.is_empty() && self.rvline.line_index != 0
1524    }
1525
1526    pub fn is_first(&self) -> bool {
1527        self.rvline.is_first()
1528    }
1529
1530    // TODO: is this correct for phantom lines?
1531    // TODO: can't we just use the line count field now?
1532    /// Is this the last visual line for the relevant buffer line?
1533    pub fn is_last(&self, text_prov: &impl TextLayoutProvider) -> bool {
1534        let rope_text = text_prov.rope_text();
1535        let line_end = rope_text.line_end_offset(self.rvline.line, false);
1536        let vline_end = self.line_end_offset(text_prov, false);
1537
1538        line_end == vline_end
1539    }
1540
1541    /// Get the first column of the overall line of the visual line
1542    pub fn first_col(&self, text_prov: &impl TextLayoutProvider) -> usize {
1543        let line_start = self.interval.start;
1544        let start_offset = text_prov.text().offset_of_line(self.rvline.line);
1545        line_start - start_offset
1546    }
1547
1548    /// Get the last column in the overall line of this visual line
1549    ///
1550    /// The caret decides whether it is after the last character, or before it.
1551    /// ```rust,ignore
1552    /// // line content = "conf = Config::default();\n"
1553    /// // wrapped breakup = ["conf = ", "Config::default();\n"]
1554    ///
1555    /// // when vline_info is for "conf = "
1556    /// assert_eq!(vline_info.last_col(text_prov, false), 6) // "conf =| "
1557    /// assert_eq!(vline_info.last_col(text_prov, true), 7) // "conf = |"
1558    /// // when vline_info is for "Config::default();\n"
1559    /// // Notice that the column is in the overall line, not the wrapped line.
1560    /// assert_eq!(vline_info.last_col(text_prov, false), 24) // "Config::default()|;"
1561    /// assert_eq!(vline_info.last_col(text_prov, true), 25) // "Config::default();|"
1562    /// ```
1563    pub fn last_col(&self, text_prov: &impl TextLayoutProvider, caret: bool) -> usize {
1564        let vline_end = self.interval.end;
1565        let start_offset = text_prov.text().offset_of_line(self.rvline.line);
1566        // If these subtractions crash, then it is likely due to a bad vline being kept around
1567        // somewhere
1568        if !caret && !self.is_empty() {
1569            let vline_pre_end = text_prov.rope_text().prev_grapheme_offset(vline_end, 1, 0);
1570            vline_pre_end - start_offset
1571        } else {
1572            vline_end - start_offset
1573        }
1574    }
1575
1576    // TODO: we could generalize `RopeText::line_end_offset` to any interval, and then just use it here instead of basically reimplementing it.
1577    pub fn line_end_offset(&self, text_prov: &impl TextLayoutProvider, caret: bool) -> usize {
1578        let text = text_prov.text();
1579        let rope_text = text_prov.rope_text();
1580
1581        let mut offset = self.interval.end;
1582        let mut line_content: &str = &text.slice_to_cow(self.interval);
1583        if line_content.ends_with("\r\n") {
1584            offset -= 2;
1585            line_content = &line_content[..line_content.len() - 2];
1586        } else if line_content.ends_with('\n') {
1587            offset -= 1;
1588            line_content = &line_content[..line_content.len() - 1];
1589        }
1590        if !caret && !line_content.is_empty() {
1591            offset = rope_text.prev_grapheme_offset(offset, 1, 0);
1592        }
1593        offset
1594    }
1595
1596    /// Returns the offset of the first non-blank character in the line.
1597    pub fn first_non_blank_character(&self, text_prov: &impl TextLayoutProvider) -> usize {
1598        WordCursor::new(&text_prov.text(), self.interval.start).next_non_blank_char()
1599    }
1600}
1601
1602/// Iterator of the visual lines in a [`Lines`].
1603///
1604/// This only considers wrapped and phantom text lines that have been rendered into a text layout.
1605///
1606/// In principle, we could consider the newlines in phantom text for lines that have not been
1607/// rendered. However, that is more expensive to compute and is probably not actually *useful*.
1608struct VisualLines<T: TextLayoutProvider> {
1609    v: VisualLinesRelative<T>,
1610    vline: VLine,
1611}
1612impl<T: TextLayoutProvider> VisualLines<T> {
1613    pub fn new(lines: &Lines, text_prov: T, backwards: bool, start: VLine) -> VisualLines<T> {
1614        // TODO(minor): If we aren't using offset here then don't calculate it.
1615        let Some((_offset, rvline)) = find_vline_init_info(lines, &text_prov, start) else {
1616            return VisualLines::empty(lines, text_prov, backwards);
1617        };
1618
1619        VisualLines {
1620            v: VisualLinesRelative::new(lines, text_prov, backwards, rvline),
1621            vline: start,
1622        }
1623    }
1624
1625    pub fn empty(lines: &Lines, text_prov: T, backwards: bool) -> VisualLines<T> {
1626        VisualLines {
1627            v: VisualLinesRelative::empty(lines, text_prov, backwards),
1628            vline: VLine(0),
1629        }
1630    }
1631}
1632impl<T: TextLayoutProvider> Iterator for VisualLines<T> {
1633    type Item = VLineInfo;
1634
1635    fn next(&mut self) -> Option<VLineInfo> {
1636        let was_first_iter = self.v.is_first_iter;
1637        let info = self.v.next()?;
1638
1639        if !was_first_iter {
1640            if self.v.backwards {
1641                // This saturation isn't really needed, but just in case.
1642                debug_assert!(
1643                    self.vline.get() != 0,
1644                    "Expected VLine to always be nonzero if we were going backwards"
1645                );
1646                self.vline = VLine(self.vline.get().saturating_sub(1));
1647            } else {
1648                self.vline = VLine(self.vline.get() + 1);
1649            }
1650        }
1651
1652        Some(VLineInfo {
1653            interval: info.interval,
1654            line_count: info.line_count,
1655            rvline: info.rvline,
1656            vline: self.vline,
1657        })
1658    }
1659}
1660
1661/// Iterator of the visual lines in a [`Lines`] relative to some starting buffer line.
1662///
1663/// This only considers wrapped and phantom text lines that have been rendered into a text layout.
1664struct VisualLinesRelative<T: TextLayoutProvider> {
1665    font_sizes: Rc<dyn LineFontSizeProvider>,
1666    text_layouts: Rc<RefCell<TextLayoutCache>>,
1667    text_prov: T,
1668
1669    is_done: bool,
1670
1671    rvline: RVLine,
1672    /// Our current offset into the rope.
1673    offset: usize,
1674
1675    /// Which direction we should move in.
1676    backwards: bool,
1677    /// Whether there is a one-to-one mapping between buffer lines and visual lines.
1678    linear: bool,
1679
1680    is_first_iter: bool,
1681}
1682impl<T: TextLayoutProvider> VisualLinesRelative<T> {
1683    pub fn new(
1684        lines: &Lines,
1685        text_prov: T,
1686        backwards: bool,
1687        start: RVLine,
1688    ) -> VisualLinesRelative<T> {
1689        // Empty iterator if we're past the end of the possible lines
1690        if start > lines.last_rvline(&text_prov) {
1691            return VisualLinesRelative::empty(lines, text_prov, backwards);
1692        }
1693
1694        let layouts = lines.text_layouts.borrow();
1695        let font_size = lines.font_size(start.line);
1696        let offset = rvline_offset(&layouts, &text_prov, font_size, start);
1697
1698        let linear = lines.is_linear(&text_prov);
1699
1700        VisualLinesRelative {
1701            font_sizes: lines.font_sizes.borrow().clone(),
1702            text_layouts: lines.text_layouts.clone(),
1703            text_prov,
1704            is_done: false,
1705            rvline: start,
1706            offset,
1707            backwards,
1708            linear,
1709            is_first_iter: true,
1710        }
1711    }
1712
1713    pub fn empty(lines: &Lines, text_prov: T, backwards: bool) -> VisualLinesRelative<T> {
1714        VisualLinesRelative {
1715            font_sizes: lines.font_sizes.borrow().clone(),
1716            text_layouts: lines.text_layouts.clone(),
1717            text_prov,
1718            is_done: true,
1719            rvline: RVLine::new(0, 0),
1720            offset: 0,
1721            backwards,
1722            linear: true,
1723            is_first_iter: true,
1724        }
1725    }
1726}
1727impl<T: TextLayoutProvider> Iterator for VisualLinesRelative<T> {
1728    type Item = VLineInfo<()>;
1729
1730    fn next(&mut self) -> Option<Self::Item> {
1731        if self.is_done {
1732            return None;
1733        }
1734
1735        let layouts = self.text_layouts.borrow();
1736        if self.is_first_iter {
1737            // This skips the next line call on the first line.
1738            self.is_first_iter = false;
1739        } else {
1740            let v = shift_rvline(
1741                &layouts,
1742                &self.text_prov,
1743                &*self.font_sizes,
1744                self.rvline,
1745                self.backwards,
1746                self.linear,
1747            );
1748            let Some((new_rel_vline, offset)) = v else {
1749                self.is_done = true;
1750                return None;
1751            };
1752
1753            self.rvline = new_rel_vline;
1754            self.offset = offset;
1755
1756            if self.rvline.line > self.text_prov.rope_text().last_line() {
1757                self.is_done = true;
1758                return None;
1759            }
1760        }
1761
1762        let line = self.rvline.line;
1763        let line_index = self.rvline.line_index;
1764        let vline = self.rvline;
1765
1766        let start = self.offset;
1767
1768        let font_size = self.font_sizes.font_size(line);
1769        let end = end_of_rvline(&layouts, &self.text_prov, font_size, self.rvline);
1770
1771        let line_count = if let Some(text_layout) = layouts.get(font_size, line) {
1772            text_layout.line_count()
1773        } else {
1774            1
1775        };
1776        debug_assert!(
1777            start <= end,
1778            "line: {line}, line_index: {line_index}, line_count: {line_count}, vline: {vline:?}, start: {start}, end: {end}, backwards: {} text_len: {}",
1779            self.backwards,
1780            self.text_prov.text().len()
1781        );
1782        let info = VLineInfo::new(start..end, self.rvline, line_count, ());
1783
1784        Some(info)
1785    }
1786}
1787
1788// TODO: This might skip spaces at the end of lines, which we probably don't want?
1789/// Get the end offset of the visual line from the file's line and the line index.
1790fn end_of_rvline(
1791    layouts: &TextLayoutCache,
1792    text_prov: &impl TextLayoutProvider,
1793    font_size: usize,
1794    RVLine { line, line_index }: RVLine,
1795) -> usize {
1796    if line > text_prov.rope_text().last_line() {
1797        return text_prov.text().len();
1798    }
1799
1800    if let Some((_, end_col)) = layouts.get_layout_col(text_prov, font_size, line, line_index) {
1801        let end_col = text_prov.before_phantom_col(line, end_col);
1802        text_prov.rope_text().offset_of_line_col(line, end_col)
1803    } else {
1804        let rope_text = text_prov.rope_text();
1805
1806        rope_text.line_end_offset(line, true)
1807    }
1808}
1809
1810/// Shift a relative visual line forward or backwards based on the `backwards` parameter.
1811fn shift_rvline(
1812    layouts: &TextLayoutCache,
1813    text_prov: &impl TextLayoutProvider,
1814    font_sizes: &dyn LineFontSizeProvider,
1815    vline: RVLine,
1816    backwards: bool,
1817    linear: bool,
1818) -> Option<(RVLine, usize)> {
1819    if linear {
1820        let rope_text = text_prov.rope_text();
1821        debug_assert_eq!(
1822            vline.line_index, 0,
1823            "Line index should be zero if we're linearly working with lines"
1824        );
1825        if backwards {
1826            if vline.line == 0 {
1827                return None;
1828            }
1829
1830            let prev_line = vline.line - 1;
1831            let offset = rope_text.offset_of_line(prev_line);
1832            Some((RVLine::new(prev_line, 0), offset))
1833        } else {
1834            let next_line = vline.line + 1;
1835
1836            if next_line > rope_text.last_line() {
1837                return None;
1838            }
1839
1840            let offset = rope_text.offset_of_line(next_line);
1841            Some((RVLine::new(next_line, 0), offset))
1842        }
1843    } else if backwards {
1844        prev_rvline(layouts, text_prov, font_sizes, vline)
1845    } else {
1846        let font_size = font_sizes.font_size(vline.line);
1847        Some(next_rvline(layouts, text_prov, font_size, vline))
1848    }
1849}
1850
1851fn rvline_offset(
1852    layouts: &TextLayoutCache,
1853    text_prov: &impl TextLayoutProvider,
1854    font_size: usize,
1855    RVLine { line, line_index }: RVLine,
1856) -> usize {
1857    let rope_text = text_prov.rope_text();
1858    if let Some((line_col, _)) = layouts.get_layout_col(text_prov, font_size, line, line_index) {
1859        let line_col = text_prov.before_phantom_col(line, line_col);
1860
1861        rope_text.offset_of_line_col(line, line_col)
1862    } else {
1863        // There was no text layout line so this is a normal line.
1864        debug_assert_eq!(line_index, 0);
1865
1866        rope_text.offset_of_line(line)
1867    }
1868}
1869
1870/// Move to the next visual line, giving the new information.
1871///
1872/// Returns `(new rel vline, offset)`
1873fn next_rvline(
1874    layouts: &TextLayoutCache,
1875    text_prov: &impl TextLayoutProvider,
1876    font_size: usize,
1877    RVLine { line, line_index }: RVLine,
1878) -> (RVLine, usize) {
1879    let rope_text = text_prov.rope_text();
1880    if let Some(layout_line) = layouts.get(font_size, line) {
1881        if let Some(line_col) = layout_line.start_layout_cols().nth(line_index + 1) {
1882            let line_col = text_prov.before_phantom_col(line, line_col);
1883            let offset = rope_text.offset_of_line_col(line, line_col);
1884
1885            (RVLine::new(line, line_index + 1), offset)
1886        } else {
1887            // There was no next layout/vline on this buffer line.
1888            // So we can simply move to the start of the next buffer line.
1889
1890            (RVLine::new(line + 1, 0), rope_text.offset_of_line(line + 1))
1891        }
1892    } else {
1893        // There was no text layout line, so this is a normal line.
1894        debug_assert_eq!(line_index, 0);
1895
1896        (RVLine::new(line + 1, 0), rope_text.offset_of_line(line + 1))
1897    }
1898}
1899
1900/// Move to the previous visual line, giving the new information.
1901///
1902/// Returns `(new line, new line_index, offset)`
1903///
1904/// Returns `None` if the line and line index are zero and thus there is no previous visual line.
1905fn prev_rvline(
1906    layouts: &TextLayoutCache,
1907    text_prov: &impl TextLayoutProvider,
1908    font_sizes: &dyn LineFontSizeProvider,
1909    RVLine { line, line_index }: RVLine,
1910) -> Option<(RVLine, usize)> {
1911    let rope_text = text_prov.rope_text();
1912    if line_index == 0 {
1913        // Line index was zero so we must be moving back a buffer line
1914        if line == 0 {
1915            return None;
1916        }
1917
1918        let prev_line = line - 1;
1919        let font_size = font_sizes.font_size(prev_line);
1920        if let Some(layout_line) = layouts.get(font_size, prev_line) {
1921            let (i, line_col) = layout_line
1922                .start_layout_cols()
1923                .enumerate()
1924                .last()
1925                .unwrap_or((0, 0));
1926            let line_col = text_prov.before_phantom_col(prev_line, line_col);
1927            let offset = rope_text.offset_of_line_col(prev_line, line_col);
1928
1929            Some((RVLine::new(prev_line, i), offset))
1930        } else {
1931            // There was no text layout line, so the previous line is a normal line.
1932            let prev_line_offset = rope_text.offset_of_line(prev_line);
1933            Some((RVLine::new(prev_line, 0), prev_line_offset))
1934        }
1935    } else {
1936        // We're still on the same buffer line, so we can just move to the previous layout/vline.
1937
1938        let prev_line_index = line_index - 1;
1939        let font_size = font_sizes.font_size(line);
1940        if let Some(layout_line) = layouts.get(font_size, line) {
1941            if let Some(line_col) = layout_line.start_layout_cols().nth(prev_line_index) {
1942                let line_col = text_prov.before_phantom_col(line, line_col);
1943                let offset = rope_text.offset_of_line_col(line, line_col);
1944
1945                Some((RVLine::new(line, prev_line_index), offset))
1946            } else {
1947                // There was no previous layout/vline on this buffer line.
1948                // So we can simply move to the end of the previous buffer line.
1949
1950                let prev_line_offset = rope_text.offset_of_line(line - 1);
1951                Some((RVLine::new(line - 1, 0), prev_line_offset))
1952            }
1953        } else {
1954            debug_assert!(
1955                false,
1956                "line_index was nonzero but there was no text layout line"
1957            );
1958            // Despite that this shouldn't happen we default to just giving the start of this
1959            // normal line
1960            let line_offset = rope_text.offset_of_line(line);
1961            Some((RVLine::new(line, 0), line_offset))
1962        }
1963    }
1964}
1965
1966#[cfg(test)]
1967mod tests {
1968    use std::{borrow::Cow, cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
1969
1970    use crate::text::{Attrs, AttrsList, FamilyOwned, OverflowWrap, TextLayout, TextWrapMode};
1971    use floem_editor_core::{
1972        buffer::rope_text::{RopeText, RopeTextRef, RopeTextVal},
1973        cursor::CursorAffinity,
1974    };
1975    use floem_reactive::Scope;
1976    use lapce_xi_rope::Rope;
1977    use smallvec::smallvec;
1978
1979    use crate::views::editor::{
1980        layout::TextLayoutLine,
1981        phantom_text::{PhantomText, PhantomTextKind, PhantomTextLine},
1982        visual_line::{end_of_rvline, find_vline_of_line_backwards, find_vline_of_line_forwards},
1983    };
1984
1985    use super::{
1986        ConfigId, FontSizeCacheId, LineFontSizeProvider, Lines, RVLine, ResolvedWrap,
1987        TextLayoutProvider, VLine, find_vline_init_info_forward, find_vline_init_info_rv_backward,
1988    };
1989
1990    /// For most of the logic we standardize on a specific font size.
1991    const FONT_SIZE: usize = 12;
1992
1993    struct TestTextLayoutProvider<'a> {
1994        text: &'a Rope,
1995        phantom: HashMap<usize, PhantomTextLine>,
1996        font_family: Vec<FamilyOwned>,
1997        #[allow(dead_code)]
1998        wrap: TextWrapMode,
1999    }
2000    impl<'a> TestTextLayoutProvider<'a> {
2001        fn new(text: &'a Rope, ph: HashMap<usize, PhantomTextLine>, wrap: TextWrapMode) -> Self {
2002            Self {
2003                text,
2004                phantom: ph,
2005                // we use a specific font to make width calculations consistent between platforms.
2006                // TODO(minor): Is there a more common font that we can use?
2007                #[cfg(not(target_os = "windows"))]
2008                font_family: vec![FamilyOwned::SansSerif],
2009                #[cfg(target_os = "windows")]
2010                font_family: vec![FamilyOwned::Name("Arial".to_string())],
2011                wrap,
2012            }
2013        }
2014    }
2015    impl TextLayoutProvider for TestTextLayoutProvider<'_> {
2016        fn text(&self) -> Rope {
2017            self.text.clone()
2018        }
2019
2020        // An implementation relatively close to the actual new text layout impl but simplified.
2021        // TODO(minor): It would be nice to just use the same impl as view's
2022        fn new_text_layout(
2023            &self,
2024            line: usize,
2025            font_size: usize,
2026            wrap: ResolvedWrap,
2027        ) -> Arc<TextLayoutLine> {
2028            let rope_text = RopeTextRef::new(self.text);
2029            let line_content_original = rope_text.line_content(line);
2030
2031            // Get the line content with newline characters replaced with spaces
2032            // and the content without the newline characters
2033            let (line_content, _line_content_original) =
2034                if let Some(s) = line_content_original.strip_suffix("\r\n") {
2035                    (
2036                        format!("{s}  "),
2037                        &line_content_original[..line_content_original.len() - 2],
2038                    )
2039                } else if let Some(s) = line_content_original.strip_suffix('\n') {
2040                    (
2041                        format!("{s} ",),
2042                        &line_content_original[..line_content_original.len() - 1],
2043                    )
2044                } else {
2045                    (
2046                        line_content_original.to_string(),
2047                        &line_content_original[..],
2048                    )
2049                };
2050
2051            let phantom_text = self.phantom.get(&line).cloned().unwrap_or_default();
2052            let line_content = phantom_text.combine_with_text(&line_content);
2053
2054            // let color
2055
2056            let attrs = Attrs::new()
2057                .family(&self.font_family)
2058                .font_size(font_size as f32);
2059            let mut attrs_list = AttrsList::new(attrs.clone());
2060
2061            // We don't do line styles, since they aren't relevant
2062
2063            // Apply phantom text specific styling
2064            for (offset, size, col, phantom) in phantom_text.offset_size_iter() {
2065                let start = col + offset;
2066                let end = start + size;
2067
2068                let mut attrs = attrs.clone();
2069                if let Some(fg) = phantom.fg {
2070                    attrs = attrs.color(fg);
2071                }
2072                if let Some(phantom_font_size) = phantom.font_size {
2073                    attrs = attrs.font_size(phantom_font_size.min(font_size) as f32);
2074                }
2075                attrs_list.add_span(start..end, attrs);
2076                // if let Some(font_family) = phantom.font_family.clone() {
2077                //     layout_builder = layout_builder.range_attribute(
2078                //         start..end,
2079                //         TextAttribute::FontFamily(font_family),
2080                //     );
2081                // }
2082            }
2083
2084            let mut text_layout = TextLayout::new();
2085            text_layout.set_text(&line_content, attrs_list, None);
2086
2087            match wrap {
2088                ResolvedWrap::None => {}
2089                ResolvedWrap::Column(_col) => todo!(),
2090                ResolvedWrap::Width(px) => {
2091                    text_layout.set_text_wrap_mode(TextWrapMode::Wrap);
2092                    text_layout.set_overflow_wrap(OverflowWrap::BreakWord);
2093                    text_layout.set_size(px, f32::MAX);
2094                }
2095            }
2096
2097            // skip phantom text background styling because it doesn't shift positions
2098            // skip severity styling
2099            // skip diagnostic background styling
2100
2101            Arc::new(TextLayoutLine {
2102                extra_style: Vec::new(),
2103                text: text_layout,
2104                whitespaces: None,
2105                indent: 0.0,
2106                phantom_text: PhantomTextLine::default(),
2107            })
2108        }
2109
2110        fn before_phantom_col(&self, line: usize, col: usize) -> usize {
2111            self.phantom
2112                .get(&line)
2113                .map(|x| x.before_col(col))
2114                .unwrap_or(col)
2115        }
2116
2117        fn has_multiline_phantom(&self) -> bool {
2118            // Conservatively, yes.
2119            true
2120        }
2121    }
2122
2123    struct TestFontSize {
2124        font_size: usize,
2125    }
2126    impl LineFontSizeProvider for TestFontSize {
2127        fn font_size(&self, _line: usize) -> usize {
2128            self.font_size
2129        }
2130
2131        fn cache_id(&self) -> FontSizeCacheId {
2132            0
2133        }
2134    }
2135
2136    fn make_lines(text: &Rope, width: f32, init: bool) -> (TestTextLayoutProvider<'_>, Lines) {
2137        make_lines_ph(text, width, init, HashMap::new())
2138    }
2139
2140    fn make_lines_ph(
2141        text: &Rope,
2142        width: f32,
2143        init: bool,
2144        ph: HashMap<usize, PhantomTextLine>,
2145    ) -> (TestTextLayoutProvider<'_>, Lines) {
2146        let wrap = TextWrapMode::Wrap;
2147        let r_wrap = ResolvedWrap::Width(width);
2148        let font_sizes = TestFontSize {
2149            font_size: FONT_SIZE,
2150        };
2151        let text = TestTextLayoutProvider::new(text, ph, wrap);
2152        let cx = Scope::new();
2153        let lines = Lines::new(cx, RefCell::new(Rc::new(font_sizes)));
2154        lines.set_wrap(r_wrap);
2155
2156        if init {
2157            let config_id = 0;
2158            let floem_style_id = 0;
2159            lines.init_all(0, ConfigId::new(config_id, floem_style_id), &text, true);
2160        }
2161
2162        (text, lines)
2163    }
2164
2165    fn render_breaks<'a>(text: &'a Rope, lines: &mut Lines, font_size: usize) -> Vec<Cow<'a, str>> {
2166        // TODO: line_content on ropetextref would have the lifetime reference rope_text
2167        // rather than the held &'a Rope.
2168        // I think this would require an alternate trait for those functions to avoid incorrect lifetimes. Annoying but workable.
2169        let rope_text = RopeTextRef::new(text);
2170        let mut result = Vec::new();
2171        let layouts = lines.text_layouts.borrow();
2172
2173        for line in 0..rope_text.num_lines() {
2174            if let Some(text_layout) = layouts.get(font_size, line) {
2175                let full_text = text_layout.text.text();
2176                let count = text_layout.text.visual_line_count();
2177                for i in 0..count {
2178                    if text_layout
2179                        .text
2180                        .parley_layout()
2181                        .get(i)
2182                        .is_none_or(|line| line.is_empty())
2183                    {
2184                        continue;
2185                    }
2186                    if let Some(text_range) = text_layout.text.visual_line_text_range(i) {
2187                        let raw = &full_text[text_range];
2188                        // Skip lines that are entirely whitespace (matches old behavior
2189                        // where trailing whitespace was stripped from glyph lists)
2190                        if !raw.chars().any(|c| !c.is_whitespace()) {
2191                            continue;
2192                        }
2193                        // Strip trailing newlines to match old glyph-based behavior
2194                        let line_content = raw.trim_end_matches(['\n', '\r']);
2195                        result.push(Cow::Owned(line_content.to_string()));
2196                    }
2197                }
2198            } else {
2199                let line_content = rope_text.line_content(line);
2200
2201                let line_content = match line_content {
2202                    Cow::Borrowed(x) => {
2203                        if let Some(x) = x.strip_suffix('\n') {
2204                            // Cow::Borrowed(x)
2205                            Cow::Owned(x.to_string())
2206                        } else {
2207                            // Cow::Borrowed(x)
2208                            Cow::Owned(x.to_string())
2209                        }
2210                    }
2211                    Cow::Owned(x) => {
2212                        if let Some(x) = x.strip_suffix('\n') {
2213                            Cow::Owned(x.to_string())
2214                        } else {
2215                            Cow::Owned(x)
2216                        }
2217                    }
2218                };
2219                result.push(line_content);
2220            }
2221        }
2222        result
2223    }
2224
2225    /// Utility fn to quickly create simple phantom text
2226    fn mph(kind: PhantomTextKind, col: usize, text: &str) -> PhantomText {
2227        PhantomText {
2228            kind,
2229            col,
2230            affinity: None,
2231            text: text.to_string(),
2232            font_size: None,
2233            fg: None,
2234            bg: None,
2235            under_line: None,
2236        }
2237    }
2238
2239    fn ffvline_info(
2240        lines: &Lines,
2241        text_prov: impl TextLayoutProvider,
2242        vline: VLine,
2243    ) -> Option<(usize, RVLine)> {
2244        find_vline_init_info_forward(lines, &text_prov, (VLine(0), 0), vline)
2245    }
2246
2247    fn fbvline_info(
2248        lines: &Lines,
2249        text_prov: impl TextLayoutProvider,
2250        vline: VLine,
2251    ) -> Option<(usize, RVLine)> {
2252        let last_vline = lines.last_vline(&text_prov);
2253        let last_rvline = lines.last_rvline(&text_prov);
2254        find_vline_init_info_rv_backward(lines, &text_prov, (last_vline, last_rvline), vline)
2255    }
2256
2257    #[test]
2258    fn find_vline_init_info_empty() {
2259        // Test empty buffer
2260        let text = Rope::from("");
2261        let (text_prov, lines) = make_lines(&text, 50.0, false);
2262
2263        assert_eq!(
2264            ffvline_info(&lines, &text_prov, VLine(0)),
2265            Some((0, RVLine::new(0, 0)))
2266        );
2267        assert_eq!(
2268            fbvline_info(&lines, &text_prov, VLine(0)),
2269            Some((0, RVLine::new(0, 0)))
2270        );
2271        assert_eq!(ffvline_info(&lines, &text_prov, VLine(1)), None);
2272        assert_eq!(fbvline_info(&lines, &text_prov, VLine(1)), None);
2273
2274        // Test empty buffer with phantom text and no wrapping
2275        let text = Rope::from("");
2276        let mut ph = HashMap::new();
2277        ph.insert(
2278            0,
2279            PhantomTextLine {
2280                text: smallvec![mph(PhantomTextKind::Completion, 0, "hello world abc")],
2281            },
2282        );
2283        let (text_prov, lines) = make_lines_ph(&text, 20.0, false, ph);
2284
2285        assert_eq!(
2286            ffvline_info(&lines, &text_prov, VLine(0)),
2287            Some((0, RVLine::new(0, 0)))
2288        );
2289        assert_eq!(
2290            fbvline_info(&lines, &text_prov, VLine(0)),
2291            Some((0, RVLine::new(0, 0)))
2292        );
2293        assert_eq!(ffvline_info(&lines, &text_prov, VLine(1)), None);
2294        assert_eq!(fbvline_info(&lines, &text_prov, VLine(1)), None);
2295
2296        // Test empty buffer with phantom text and wrapping
2297        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2298
2299        assert_eq!(
2300            ffvline_info(&lines, &text_prov, VLine(0)),
2301            Some((0, RVLine::new(0, 0)))
2302        );
2303        assert_eq!(
2304            fbvline_info(&lines, &text_prov, VLine(0)),
2305            Some((0, RVLine::new(0, 0)))
2306        );
2307        assert_eq!(
2308            ffvline_info(&lines, &text_prov, VLine(1)),
2309            Some((0, RVLine::new(0, 1)))
2310        );
2311        assert_eq!(
2312            fbvline_info(&lines, &text_prov, VLine(1)),
2313            Some((0, RVLine::new(0, 1)))
2314        );
2315        assert_eq!(
2316            ffvline_info(&lines, &text_prov, VLine(2)),
2317            Some((0, RVLine::new(0, 2)))
2318        );
2319        assert_eq!(
2320            fbvline_info(&lines, &text_prov, VLine(2)),
2321            Some((0, RVLine::new(0, 2)))
2322        );
2323        // Going outside bounds only ends up with None
2324        assert_eq!(ffvline_info(&lines, &text_prov, VLine(3)), None);
2325        assert_eq!(fbvline_info(&lines, &text_prov, VLine(3)), None);
2326        // The affinity would shift from the front/end of the phantom line
2327        // TODO: test affinity of logic behind clicking past the last vline?
2328    }
2329
2330    #[test]
2331    fn find_vline_init_info_unwrapping() {
2332        // Multiple lines with too large width for there to be any wrapping.
2333        let text = Rope::from("hello\nworld toast and jam\nthe end\nhi");
2334        let rope_text = RopeTextRef::new(&text);
2335        let (text_prov, mut lines) = make_lines(&text, 500.0, false);
2336
2337        // Assert that with no text layouts (aka no wrapping and no phantom text) the function
2338        // works
2339        for line in 0..rope_text.num_lines() {
2340            let line_offset = rope_text.offset_of_line(line);
2341
2342            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2343            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2344
2345            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2346            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2347        }
2348
2349        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2350
2351        assert_eq!(
2352            render_breaks(&text, &mut lines, FONT_SIZE),
2353            ["hello", "world toast and jam", "the end", "hi"]
2354        );
2355
2356        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2357
2358        // Assert that even with text layouts, if it has no wrapping applied (because the width is large in this case) and no phantom text then it produces the same offsets as before.
2359        for line in 0..rope_text.num_lines() {
2360            let line_offset = rope_text.offset_of_line(line);
2361
2362            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2363            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2364            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2365            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2366        }
2367
2368        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2369        assert_eq!(fbvline_info(&lines, &text_prov, VLine(20)), None);
2370
2371        assert_eq!(
2372            render_breaks(&text, &mut lines, FONT_SIZE),
2373            ["hello ", "world toast and jam ", "the end ", "hi"]
2374        );
2375    }
2376
2377    #[test]
2378    fn find_vline_init_info_phantom_unwrapping() {
2379        let text = Rope::from("hello\nworld toast and jam\nthe end\nhi");
2380        let rope_text = RopeTextRef::new(&text);
2381
2382        // Multiple lines with too large width for there to be any wrapping and phantom text
2383        let mut ph = HashMap::new();
2384        ph.insert(
2385            0,
2386            PhantomTextLine {
2387                text: smallvec![mph(PhantomTextKind::Completion, 0, "greet world")],
2388            },
2389        );
2390
2391        let (text_prov, lines) = make_lines_ph(&text, 500.0, false, ph);
2392
2393        // With no text layouts, phantom text isn't initialized so it has no affect.
2394        for line in 0..rope_text.num_lines() {
2395            let line_offset = rope_text.offset_of_line(line);
2396
2397            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2398            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2399
2400            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2401            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2402        }
2403
2404        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2405
2406        // With text layouts, the phantom text is applied.
2407        // But with a single line of phantom text, it doesn't affect the offsets.
2408        for line in 0..rope_text.num_lines() {
2409            let line_offset = rope_text.offset_of_line(line);
2410
2411            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2412            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2413
2414            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2415            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2416        }
2417
2418        // Multiple lines with too large width and a phantom text that takes up multiple lines.
2419        let mut ph = HashMap::new();
2420        ph.insert(
2421            0,
2422            PhantomTextLine {
2423                text: smallvec![mph(PhantomTextKind::Completion, 0, "greet\nworld"),],
2424            },
2425        );
2426
2427        let (text_prov, mut lines) = make_lines_ph(&text, 500.0, false, ph);
2428
2429        // With no text layouts, phantom text isn't initialized so it has no affect.
2430        for line in 0..rope_text.num_lines() {
2431            let line_offset = rope_text.offset_of_line(line);
2432
2433            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2434            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2435
2436            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2437            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2438        }
2439
2440        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2441
2442        assert_eq!(
2443            render_breaks(&text, &mut lines, FONT_SIZE),
2444            [
2445                "greet",
2446                "worldhello ",
2447                "world toast and jam ",
2448                "the end ",
2449                "hi"
2450            ]
2451        );
2452
2453        // With text layouts, the phantom text is applied.
2454        // With a phantom text that takes up multiple lines, it does not affect the offsets
2455        // but it does affect the valid visual lines.
2456        let info = ffvline_info(&lines, &text_prov, VLine(0));
2457        assert_eq!(info, Some((0, RVLine::new(0, 0))));
2458        let info = fbvline_info(&lines, &text_prov, VLine(0));
2459        assert_eq!(info, Some((0, RVLine::new(0, 0))));
2460        let info = ffvline_info(&lines, &text_prov, VLine(1));
2461        assert_eq!(info, Some((0, RVLine::new(0, 1))));
2462        let info = fbvline_info(&lines, &text_prov, VLine(1));
2463        assert_eq!(info, Some((0, RVLine::new(0, 1))));
2464
2465        for line in 2..rope_text.num_lines() {
2466            let line_offset = rope_text.offset_of_line(line - 1);
2467
2468            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2469            assert_eq!(
2470                info,
2471                (line_offset, RVLine::new(line - 1, 0)),
2472                "vline {line}"
2473            );
2474            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2475            assert_eq!(
2476                info,
2477                (line_offset, RVLine::new(line - 1, 0)),
2478                "vline {line}"
2479            );
2480        }
2481
2482        // Then there's one extra vline due to the phantom text wrapping
2483        let line_offset = rope_text.offset_of_line(rope_text.last_line());
2484
2485        let info = ffvline_info(&lines, &text_prov, VLine(rope_text.last_line() + 1));
2486        assert_eq!(
2487            info,
2488            Some((line_offset, RVLine::new(rope_text.last_line(), 0))),
2489            "line {}",
2490            rope_text.last_line() + 1,
2491        );
2492        let info = fbvline_info(&lines, &text_prov, VLine(rope_text.last_line() + 1));
2493        assert_eq!(
2494            info,
2495            Some((line_offset, RVLine::new(rope_text.last_line(), 0))),
2496            "line {}",
2497            rope_text.last_line() + 1,
2498        );
2499
2500        // Multiple lines with too large width and a phantom text that takes up multiple lines.
2501        // But the phantom text is not at the start of the first line.
2502        let mut ph = HashMap::new();
2503        ph.insert(
2504            2, // "the end"
2505            PhantomTextLine {
2506                text: smallvec![mph(PhantomTextKind::Completion, 3, "greet\nworld"),],
2507            },
2508        );
2509
2510        let (text_prov, mut lines) = make_lines_ph(&text, 500.0, false, ph);
2511
2512        // With no text layouts, phantom text isn't initialized so it has no affect.
2513        for line in 0..rope_text.num_lines() {
2514            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2515
2516            let line_offset = rope_text.offset_of_line(line);
2517
2518            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2519        }
2520
2521        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2522
2523        assert_eq!(
2524            render_breaks(&text, &mut lines, FONT_SIZE),
2525            [
2526                "hello ",
2527                "world toast and jam ",
2528                "thegreet",
2529                "world end ",
2530                "hi"
2531            ]
2532        );
2533
2534        // With text layouts, the phantom text is applied.
2535        // With a phantom text that takes up multiple lines, it does not affect the offsets
2536        // but it does affect the valid visual lines.
2537        for line in 0..3 {
2538            let line_offset = rope_text.offset_of_line(line);
2539
2540            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2541            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2542
2543            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2544            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2545        }
2546
2547        // ' end'
2548        let info = ffvline_info(&lines, &text_prov, VLine(3));
2549        assert_eq!(info, Some((29, RVLine::new(2, 1))));
2550        let info = fbvline_info(&lines, &text_prov, VLine(3));
2551        assert_eq!(info, Some((29, RVLine::new(2, 1))));
2552
2553        let info = ffvline_info(&lines, &text_prov, VLine(4));
2554        assert_eq!(info, Some((34, RVLine::new(3, 0))));
2555        let info = fbvline_info(&lines, &text_prov, VLine(4));
2556        assert_eq!(info, Some((34, RVLine::new(3, 0))));
2557    }
2558
2559    #[test]
2560    fn find_vline_init_info_basic_wrapping() {
2561        // Tests with more mixes of text layout lines and uninitialized lines
2562
2563        // Multiple lines with a small enough width for there to be a bunch of wrapping
2564        let text = Rope::from("hello\nworld toast and jam\nthe end\nhi");
2565        let rope_text = RopeTextRef::new(&text);
2566        let (text_prov, mut lines) = make_lines(&text, 30.0, false);
2567
2568        // Assert that with no text layouts (aka no wrapping and no phantom text) the function
2569        // works
2570        for line in 0..rope_text.num_lines() {
2571            let line_offset = rope_text.offset_of_line(line);
2572
2573            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2574            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "line {line}");
2575
2576            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2577            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "line {line}");
2578        }
2579
2580        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2581        assert_eq!(fbvline_info(&lines, &text_prov, VLine(20)), None);
2582
2583        assert_eq!(
2584            render_breaks(&text, &mut lines, FONT_SIZE),
2585            ["hello", "world toast and jam", "the end", "hi"]
2586        );
2587
2588        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2589
2590        {
2591            let layouts = lines.text_layouts.borrow();
2592
2593            assert!(layouts.get(FONT_SIZE, 0).is_some());
2594            assert!(layouts.get(FONT_SIZE, 1).is_some());
2595            assert!(layouts.get(FONT_SIZE, 2).is_some());
2596            assert!(layouts.get(FONT_SIZE, 3).is_some());
2597            assert!(layouts.get(FONT_SIZE, 4).is_none());
2598        }
2599
2600        // start offset, start buffer line, layout line index)
2601        let line_data = [
2602            (0, 0, 0),
2603            (6, 1, 0),
2604            (12, 1, 1),
2605            (18, 1, 2),
2606            (22, 1, 3),
2607            (26, 2, 0),
2608            (30, 2, 1),
2609            (34, 3, 0),
2610        ];
2611        assert_eq!(lines.last_vline(&text_prov), VLine(7));
2612        assert_eq!(lines.last_rvline(&text_prov), RVLine::new(3, 0));
2613        #[allow(clippy::needless_range_loop)]
2614        for line in 0..8 {
2615            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2616            assert_eq!(
2617                (info.0, info.1.line, info.1.line_index),
2618                line_data[line],
2619                "vline {line}"
2620            );
2621            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2622            assert_eq!(
2623                (info.0, info.1.line, info.1.line_index),
2624                line_data[line],
2625                "vline {line}"
2626            );
2627        }
2628
2629        // Directly out of bounds
2630        assert_eq!(ffvline_info(&lines, &text_prov, VLine(9)), None,);
2631        assert_eq!(fbvline_info(&lines, &text_prov, VLine(9)), None,);
2632
2633        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2634        assert_eq!(fbvline_info(&lines, &text_prov, VLine(20)), None);
2635
2636        assert_eq!(
2637            render_breaks(&text, &mut lines, FONT_SIZE),
2638            [
2639                "hello ", "world ", "toast ", "and ", "jam ", "the ", "end ", "hi"
2640            ]
2641        );
2642
2643        let vline_line_data = [0, 1, 5, 7];
2644
2645        let rope = text_prov.rope_text();
2646        let last_start_vline =
2647            VLine(lines.last_vline(&text_prov).get() - lines.last_rvline(&text_prov).line_index);
2648        #[allow(clippy::needless_range_loop)]
2649        for line in 0..4 {
2650            let vline = VLine(vline_line_data[line]);
2651            assert_eq!(
2652                find_vline_of_line_forwards(&lines, Default::default(), line),
2653                Some(vline)
2654            );
2655            assert_eq!(
2656                find_vline_of_line_backwards(&lines, (last_start_vline, rope.last_line()), line),
2657                Some(vline),
2658                "line: {line}"
2659            );
2660        }
2661
2662        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
2663        let (text_prov, mut lines) = make_lines(&text, 2., true);
2664
2665        assert_eq!(
2666            render_breaks(&text, &mut lines, FONT_SIZE),
2667            [
2668                "aaaa ", "bb ", "bb ", "cc ", "cc ", "dddd ", "eeee ", "ff ", "ff ", "gggg"
2669            ]
2670        );
2671
2672        // (start offset, start buffer line, layout line index)
2673        let line_data = [
2674            (0, 0, 0),
2675            (5, 1, 0),
2676            (8, 1, 1),
2677            (11, 1, 2),
2678            (14, 2, 0),
2679            (17, 2, 1),
2680            (22, 2, 2),
2681            (27, 2, 3),
2682            (30, 3, 0),
2683            (33, 3, 1),
2684        ];
2685        #[allow(clippy::needless_range_loop)]
2686        for vline in 0..10 {
2687            let info = ffvline_info(&lines, &text_prov, VLine(vline)).unwrap();
2688            assert_eq!(
2689                (info.0, info.1.line, info.1.line_index),
2690                line_data[vline],
2691                "vline {vline}"
2692            );
2693            let info = fbvline_info(&lines, &text_prov, VLine(vline)).unwrap();
2694            assert_eq!(
2695                (info.0, info.1.line, info.1.line_index),
2696                line_data[vline],
2697                "vline {vline}"
2698            );
2699        }
2700
2701        let vline_line_data = [0, 1, 4, 8];
2702
2703        let rope = text_prov.rope_text();
2704        let last_start_vline =
2705            VLine(lines.last_vline(&text_prov).get() - lines.last_rvline(&text_prov).line_index);
2706        #[allow(clippy::needless_range_loop)]
2707        for line in 0..4 {
2708            let vline = VLine(vline_line_data[line]);
2709            assert_eq!(
2710                find_vline_of_line_forwards(&lines, Default::default(), line),
2711                Some(vline)
2712            );
2713            assert_eq!(
2714                find_vline_of_line_backwards(&lines, (last_start_vline, rope.last_line()), line),
2715                Some(vline),
2716                "line: {line}"
2717            );
2718        }
2719
2720        // TODO: tests that have less line wrapping
2721    }
2722
2723    #[test]
2724    fn find_vline_init_info_basic_wrapping_phantom() {
2725        // Single line Phantom text at the very start
2726        let text = Rope::from("hello\nworld toast and jam\nthe end\nhi");
2727        let rope_text = RopeTextRef::new(&text);
2728
2729        let mut ph = HashMap::new();
2730        ph.insert(
2731            0,
2732            PhantomTextLine {
2733                text: smallvec![mph(PhantomTextKind::Completion, 0, "greet world")],
2734            },
2735        );
2736
2737        let (text_prov, mut lines) = make_lines_ph(&text, 30.0, false, ph);
2738
2739        // Assert that with no text layouts there is no change in behavior from having no phantom
2740        // text
2741        for line in 0..rope_text.num_lines() {
2742            let line_offset = rope_text.offset_of_line(line);
2743
2744            let info = ffvline_info(&lines, &text_prov, VLine(line));
2745            assert_eq!(
2746                info,
2747                Some((line_offset, RVLine::new(line, 0))),
2748                "line {line}"
2749            );
2750
2751            let info = fbvline_info(&lines, &text_prov, VLine(line));
2752            assert_eq!(
2753                info,
2754                Some((line_offset, RVLine::new(line, 0))),
2755                "line {line}"
2756            );
2757        }
2758
2759        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2760        assert_eq!(fbvline_info(&lines, &text_prov, VLine(20)), None);
2761
2762        assert_eq!(
2763            render_breaks(&text, &mut lines, FONT_SIZE),
2764            ["hello", "world toast and jam", "the end", "hi"]
2765        );
2766
2767        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2768
2769        {
2770            let layouts = lines.text_layouts.borrow();
2771
2772            assert!(layouts.get(FONT_SIZE, 0).is_some());
2773            assert!(layouts.get(FONT_SIZE, 1).is_some());
2774            assert!(layouts.get(FONT_SIZE, 2).is_some());
2775            assert!(layouts.get(FONT_SIZE, 3).is_some());
2776            assert!(layouts.get(FONT_SIZE, 4).is_none());
2777        }
2778
2779        // start offset, start buffer line, layout line index)
2780        let line_data = [
2781            (0, 0, 0),
2782            (0, 0, 1),
2783            (6, 1, 0),
2784            (12, 1, 1),
2785            (18, 1, 2),
2786            (22, 1, 3),
2787            (26, 2, 0),
2788            (30, 2, 1),
2789            (34, 3, 0),
2790        ];
2791
2792        #[allow(clippy::needless_range_loop)]
2793        for line in 0..9 {
2794            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2795            assert_eq!(
2796                (info.0, info.1.line, info.1.line_index),
2797                line_data[line],
2798                "vline {line}"
2799            );
2800
2801            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2802            assert_eq!(
2803                (info.0, info.1.line, info.1.line_index),
2804                line_data[line],
2805                "vline {line}"
2806            );
2807        }
2808
2809        // Directly out of bounds
2810        assert_eq!(ffvline_info(&lines, &text_prov, VLine(9)), None);
2811        assert_eq!(fbvline_info(&lines, &text_prov, VLine(9)), None);
2812
2813        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2814        assert_eq!(fbvline_info(&lines, &text_prov, VLine(20)), None);
2815
2816        // TODO: Currently the way we join phantom text and how cosmic wraps lines,
2817        // the phantom text will be joined with whatever the word next to it is - if there is no
2818        // spaces. It might be desirable to always separate them to let it wrap independently.
2819        // An easy way to do this is to always include a space, and then manually cut the glyph
2820        // margin in the text layout.
2821        assert_eq!(
2822            render_breaks(&text, &mut lines, FONT_SIZE),
2823            [
2824                "greet ",
2825                "worldhello ",
2826                "world ",
2827                "toast ",
2828                "and ",
2829                "jam ",
2830                "the ",
2831                "end ",
2832                "hi"
2833            ]
2834        );
2835
2836        // TODO: multiline phantom text in the middle
2837        // TODO: test at the end
2838    }
2839
2840    #[test]
2841    fn num_vlines() {
2842        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
2843        let (text_prov, lines) = make_lines(&text, 2., true);
2844        assert_eq!(lines.num_vlines(&text_prov), 10);
2845
2846        // With phantom text
2847        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
2848        let mut ph = HashMap::new();
2849        ph.insert(
2850            0,
2851            PhantomTextLine {
2852                text: smallvec![mph(PhantomTextKind::Completion, 0, "greet\nworld")],
2853            },
2854        );
2855
2856        let (text_prov, lines) = make_lines_ph(&text, 2., true, ph);
2857
2858        // Only one increase because the second line of the phantom text is directly attached to
2859        // the word at the start of the next line.
2860        assert_eq!(lines.num_vlines(&text_prov), 11);
2861    }
2862
2863    #[test]
2864    fn offset_to_line() {
2865        let text = "a b c d ".into();
2866        let (text_prov, lines) = make_lines(&text, 1., true);
2867        assert_eq!(lines.num_vlines(&text_prov), 4);
2868
2869        let vlines = [0, 0, 1, 1, 2, 2, 3, 3];
2870        for (i, v) in vlines.iter().enumerate() {
2871            assert_eq!(
2872                lines.vline_of_offset(&text_prov, i, CursorAffinity::Forward),
2873                VLine(*v),
2874                "offset: {i}"
2875            );
2876        }
2877
2878        assert_eq!(lines.offset_of_vline(&text_prov, VLine(0)), 0);
2879        assert_eq!(lines.offset_of_vline(&text_prov, VLine(1)), 2);
2880        assert_eq!(lines.offset_of_vline(&text_prov, VLine(2)), 4);
2881        assert_eq!(lines.offset_of_vline(&text_prov, VLine(3)), 6);
2882        assert_eq!(lines.offset_of_vline(&text_prov, VLine(10)), 8);
2883
2884        for offset in 0..text.len() {
2885            let line = lines.vline_of_offset(&text_prov, offset, CursorAffinity::Forward);
2886            let line_offset = lines.offset_of_vline(&text_prov, line);
2887            assert!(
2888                line_offset <= offset,
2889                "{line_offset} <= {offset} L{line:?} O{offset}"
2890            );
2891        }
2892
2893        let text = "blah\n\n\nhi\na b c d e".into();
2894        let (text_prov, lines) = make_lines(&text, 12.0 * 3.0, true);
2895        let vlines = [0, 0, 0, 0, 0];
2896        for (i, v) in vlines.iter().enumerate() {
2897            assert_eq!(
2898                lines.vline_of_offset(&text_prov, i, CursorAffinity::Forward),
2899                VLine(*v),
2900                "offset: {i}"
2901            );
2902        }
2903        assert_eq!(
2904            lines
2905                .vline_of_offset(&text_prov, 4, CursorAffinity::Backward)
2906                .get(),
2907            0
2908        );
2909        // Test that cursor affinity has no effect for hard line breaks
2910        assert_eq!(
2911            lines
2912                .vline_of_offset(&text_prov, 5, CursorAffinity::Forward)
2913                .get(),
2914            1
2915        );
2916        assert_eq!(
2917            lines
2918                .vline_of_offset(&text_prov, 5, CursorAffinity::Backward)
2919                .get(),
2920            1
2921        );
2922        // starts at 'd'. Tests that cursor affinity works for soft line breaks
2923        assert_eq!(
2924            lines
2925                .vline_of_offset(&text_prov, 16, CursorAffinity::Forward)
2926                .get(),
2927            5
2928        );
2929        assert_eq!(
2930            lines
2931                .vline_of_offset(&text_prov, 16, CursorAffinity::Backward)
2932                .get(),
2933            4
2934        );
2935
2936        assert_eq!(
2937            lines.vline_of_offset(&text_prov, 20, CursorAffinity::Forward),
2938            lines.last_vline(&text_prov)
2939        );
2940
2941        let text = "a\nb\nc\n".into();
2942        let (text_prov, lines) = make_lines(&text, 1., true);
2943        assert_eq!(lines.num_vlines(&text_prov), 4);
2944
2945        // let vlines = [(0, 0), (0, 0), (1, 1), (1, 1), (2, 2), (2, 2), (3, 3)];
2946        let vlines = [0, 0, 1, 1, 2, 2, 3, 3];
2947        for (i, v) in vlines.iter().enumerate() {
2948            assert_eq!(
2949                lines.vline_of_offset(&text_prov, i, CursorAffinity::Forward),
2950                VLine(*v),
2951                "offset: {i}"
2952            );
2953            assert_eq!(
2954                lines.vline_of_offset(&text_prov, i, CursorAffinity::Backward),
2955                VLine(*v),
2956                "offset: {i}"
2957            );
2958        }
2959
2960        let text =
2961            Rope::from("asdf\nposition: Some(EditorPosition::Offset(self.offset))\nasdf\nasdf");
2962        let (text_prov, mut lines) = make_lines(&text, 1., true);
2963        println!("Breaks: {:?}", render_breaks(&text, &mut lines, FONT_SIZE));
2964
2965        let rvline = lines.rvline_of_offset(&text_prov, 3, CursorAffinity::Backward);
2966        assert_eq!(rvline, RVLine::new(0, 0));
2967        let rvline_info = lines
2968            .iter_rvlines(&text_prov, false, rvline)
2969            .next()
2970            .unwrap();
2971        assert_eq!(rvline_info.rvline, rvline);
2972        let offset = lines.offset_of_rvline(&text_prov, rvline);
2973        assert_eq!(offset, 0);
2974        assert_eq!(
2975            lines.vline_of_offset(&text_prov, offset, CursorAffinity::Backward),
2976            VLine(0)
2977        );
2978        assert_eq!(lines.vline_of_rvline(&text_prov, rvline), VLine(0));
2979
2980        let rvline = lines.rvline_of_offset(&text_prov, 7, CursorAffinity::Backward);
2981        assert_eq!(rvline, RVLine::new(1, 0));
2982        let rvline_info = lines
2983            .iter_rvlines(&text_prov, false, rvline)
2984            .next()
2985            .unwrap();
2986        assert_eq!(rvline_info.rvline, rvline);
2987        let offset = lines.offset_of_rvline(&text_prov, rvline);
2988        assert_eq!(offset, 5);
2989        assert_eq!(
2990            lines.vline_of_offset(&text_prov, offset, CursorAffinity::Backward),
2991            VLine(1)
2992        );
2993        assert_eq!(lines.vline_of_rvline(&text_prov, rvline), VLine(1));
2994
2995        let rvline = lines.rvline_of_offset(&text_prov, 17, CursorAffinity::Backward);
2996        assert_eq!(rvline, RVLine::new(1, 1));
2997        let rvline_info = lines
2998            .iter_rvlines(&text_prov, false, rvline)
2999            .next()
3000            .unwrap();
3001        assert_eq!(rvline_info.rvline, rvline);
3002        let offset = lines.offset_of_rvline(&text_prov, rvline);
3003        assert_eq!(offset, 15);
3004        assert_eq!(
3005            lines.vline_of_offset(&text_prov, offset, CursorAffinity::Backward),
3006            VLine(1)
3007        );
3008        assert_eq!(
3009            lines.vline_of_offset(&text_prov, offset, CursorAffinity::Forward),
3010            VLine(2)
3011        );
3012        assert_eq!(lines.vline_of_rvline(&text_prov, rvline), VLine(2));
3013    }
3014
3015    #[test]
3016    fn offset_to_line_phantom() {
3017        let text = "a b c d ".into();
3018        let mut ph = HashMap::new();
3019        ph.insert(
3020            0,
3021            PhantomTextLine {
3022                text: smallvec![mph(PhantomTextKind::Completion, 1, "hi")],
3023            },
3024        );
3025
3026        let (text_prov, mut lines) = make_lines_ph(&text, 1., true, ph);
3027
3028        // The 'hi' is joined with the 'a' so it's not wrapped to a separate line
3029        assert_eq!(lines.num_vlines(&text_prov), 4);
3030
3031        assert_eq!(
3032            render_breaks(&text, &mut lines, FONT_SIZE),
3033            ["ahi ", "b ", "c ", "d "]
3034        );
3035
3036        let vlines = [0, 0, 1, 1, 2, 2, 3, 3];
3037        // Unchanged. The phantom text has no effect in the position. It doesn't shift a line with
3038        // the affinity due to its position and it isn't multiline.
3039        for (i, v) in vlines.iter().enumerate() {
3040            assert_eq!(
3041                lines.vline_of_offset(&text_prov, i, CursorAffinity::Forward),
3042                VLine(*v),
3043                "offset: {i}"
3044            );
3045        }
3046
3047        assert_eq!(lines.offset_of_vline(&text_prov, VLine(0)), 0);
3048        assert_eq!(lines.offset_of_vline(&text_prov, VLine(1)), 2);
3049        assert_eq!(lines.offset_of_vline(&text_prov, VLine(2)), 4);
3050        assert_eq!(lines.offset_of_vline(&text_prov, VLine(3)), 6);
3051        assert_eq!(lines.offset_of_vline(&text_prov, VLine(10)), 8);
3052
3053        for offset in 0..text.len() {
3054            let line = lines.vline_of_offset(&text_prov, offset, CursorAffinity::Forward);
3055            let line_offset = lines.offset_of_vline(&text_prov, line);
3056            assert!(
3057                line_offset <= offset,
3058                "{line_offset} <= {offset} L{line:?} O{offset}"
3059            );
3060        }
3061
3062        // Same as above but with a slightly shifted to make the affinity change the resulting vline
3063        let mut ph = HashMap::new();
3064        ph.insert(
3065            0,
3066            PhantomTextLine {
3067                text: smallvec![mph(PhantomTextKind::Completion, 2, "hi")],
3068            },
3069        );
3070
3071        let (text_prov, mut lines) = make_lines_ph(&text, 1., true, ph);
3072
3073        // The 'hi' is joined with the 'a' so it's not wrapped to a separate line
3074        assert_eq!(lines.num_vlines(&text_prov), 4);
3075
3076        // TODO: Should this really be forward rendered?
3077        assert_eq!(
3078            render_breaks(&text, &mut lines, FONT_SIZE),
3079            ["a ", "hib ", "c ", "d "]
3080        );
3081
3082        for (i, v) in vlines.iter().enumerate() {
3083            assert_eq!(
3084                lines.vline_of_offset(&text_prov, i, CursorAffinity::Forward),
3085                VLine(*v),
3086                "offset: {i}"
3087            );
3088        }
3089        assert_eq!(
3090            lines.vline_of_offset(&text_prov, 2, CursorAffinity::Backward),
3091            VLine(0)
3092        );
3093
3094        assert_eq!(lines.offset_of_vline(&text_prov, VLine(0)), 0);
3095        assert_eq!(lines.offset_of_vline(&text_prov, VLine(1)), 2);
3096        assert_eq!(lines.offset_of_vline(&text_prov, VLine(2)), 4);
3097        assert_eq!(lines.offset_of_vline(&text_prov, VLine(3)), 6);
3098        assert_eq!(lines.offset_of_vline(&text_prov, VLine(10)), 8);
3099
3100        for offset in 0..text.len() {
3101            let line = lines.vline_of_offset(&text_prov, offset, CursorAffinity::Forward);
3102            let line_offset = lines.offset_of_vline(&text_prov, line);
3103            assert!(
3104                line_offset <= offset,
3105                "{line_offset} <= {offset} L{line:?} O{offset}"
3106            );
3107        }
3108    }
3109
3110    #[test]
3111    fn iter_lines() {
3112        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
3113        let (text_prov, lines) = make_lines(&text, 2., true);
3114        let r: Vec<_> = lines
3115            .iter_vlines(&text_prov, false, VLine(0))
3116            .take(2)
3117            .map(|l| text.slice_to_cow(l.interval))
3118            .collect();
3119        assert_eq!(r, vec!["aaaa", "bb "]);
3120
3121        let r: Vec<_> = lines
3122            .iter_vlines(&text_prov, false, VLine(1))
3123            .take(2)
3124            .map(|l| text.slice_to_cow(l.interval))
3125            .collect();
3126        assert_eq!(r, vec!["bb ", "bb "]);
3127
3128        let v = lines.get_init_text_layout(0, ConfigId::new(0, 0), &text_prov, 2, true);
3129        let v = v.layout_cols(&text_prov, 2).collect::<Vec<_>>();
3130        assert_eq!(v, [(0, 3), (3, 8), (8, 13), (13, 15)]);
3131        let r: Vec<_> = lines
3132            .iter_vlines(&text_prov, false, VLine(3))
3133            .take(3)
3134            .map(|l| text.slice_to_cow(l.interval))
3135            .collect();
3136        assert_eq!(r, vec!["cc", "cc ", "dddd "]);
3137
3138        let mut r: Vec<_> = lines.iter_vlines(&text_prov, false, VLine(0)).collect();
3139        r.reverse();
3140        let r1: Vec<_> = lines
3141            .iter_vlines(&text_prov, true, lines.last_vline(&text_prov))
3142            .collect();
3143        assert_eq!(r, r1);
3144
3145        let rel1: Vec<_> = lines
3146            .iter_rvlines(&text_prov, false, RVLine::new(0, 0))
3147            .map(|i| i.rvline)
3148            .collect();
3149        r.reverse(); // revert back
3150        assert!(r.iter().map(|i| i.rvline).eq(rel1));
3151
3152        // Empty initialized
3153        let text: Rope = "".into();
3154        let (text_prov, lines) = make_lines(&text, 2., true);
3155        let r: Vec<_> = lines
3156            .iter_vlines(&text_prov, false, VLine(0))
3157            .map(|l| text.slice_to_cow(l.interval))
3158            .collect();
3159        assert_eq!(r, vec![""]);
3160        // Empty initialized - Out of bounds
3161        let r: Vec<_> = lines
3162            .iter_vlines(&text_prov, false, VLine(1))
3163            .map(|l| text.slice_to_cow(l.interval))
3164            .collect();
3165        assert_eq!(r, Vec::<&str>::new());
3166        let r: Vec<_> = lines
3167            .iter_vlines(&text_prov, false, VLine(2))
3168            .map(|l| text.slice_to_cow(l.interval))
3169            .collect();
3170        assert_eq!(r, Vec::<&str>::new());
3171
3172        let mut r: Vec<_> = lines.iter_vlines(&text_prov, false, VLine(0)).collect();
3173        r.reverse();
3174        let r1: Vec<_> = lines
3175            .iter_vlines(&text_prov, true, lines.last_vline(&text_prov))
3176            .collect();
3177        assert_eq!(r, r1);
3178
3179        let rel1: Vec<_> = lines
3180            .iter_rvlines(&text_prov, false, RVLine::new(0, 0))
3181            .map(|i| i.rvline)
3182            .collect();
3183        r.reverse(); // revert back
3184        assert!(r.iter().map(|i| i.rvline).eq(rel1));
3185
3186        // Empty uninitialized
3187        let text: Rope = "".into();
3188        let (text_prov, lines) = make_lines(&text, 2., false);
3189        let r: Vec<_> = lines
3190            .iter_vlines(&text_prov, false, VLine(0))
3191            .map(|l| text.slice_to_cow(l.interval))
3192            .collect();
3193        assert_eq!(r, vec![""]);
3194        let r: Vec<_> = lines
3195            .iter_vlines(&text_prov, false, VLine(1))
3196            .map(|l| text.slice_to_cow(l.interval))
3197            .collect();
3198        assert_eq!(r, Vec::<&str>::new());
3199        let r: Vec<_> = lines
3200            .iter_vlines(&text_prov, false, VLine(2))
3201            .map(|l| text.slice_to_cow(l.interval))
3202            .collect();
3203        assert_eq!(r, Vec::<&str>::new());
3204
3205        let mut r: Vec<_> = lines.iter_vlines(&text_prov, false, VLine(0)).collect();
3206        r.reverse();
3207        let r1: Vec<_> = lines
3208            .iter_vlines(&text_prov, true, lines.last_vline(&text_prov))
3209            .collect();
3210        assert_eq!(r, r1);
3211
3212        let rel1: Vec<_> = lines
3213            .iter_rvlines(&text_prov, false, RVLine::new(0, 0))
3214            .map(|i| i.rvline)
3215            .collect();
3216        r.reverse(); // revert back
3217        assert!(r.iter().map(|i| i.rvline).eq(rel1));
3218
3219        // TODO: clean up the above tests with some helper function. Very noisy at the moment.
3220        // TODO: phantom text iter lines tests?
3221    }
3222
3223    // TODO(minor): Deduplicate the test code between this and iter_lines
3224    // We're just testing whether it has equivalent behavior to iter lines (when lines are
3225    // initialized)
3226    #[test]
3227    fn init_iter_vlines() {
3228        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
3229        let (text_prov, lines) = make_lines(&text, 2., false);
3230        let r: Vec<_> = lines
3231            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(0), true)
3232            .take(2)
3233            .map(|l| text.slice_to_cow(l.interval))
3234            .collect();
3235        assert_eq!(r, vec!["aaaa", "bb "]);
3236
3237        let r: Vec<_> = lines
3238            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(1), true)
3239            .take(2)
3240            .map(|l| text.slice_to_cow(l.interval))
3241            .collect();
3242        assert_eq!(r, vec!["bb ", "bb "]);
3243
3244        let r: Vec<_> = lines
3245            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(3), true)
3246            .take(3)
3247            .map(|l| text.slice_to_cow(l.interval))
3248            .collect();
3249        assert_eq!(r, vec!["cc", "cc ", "dddd "]);
3250
3251        // Empty initialized
3252        let text: Rope = "".into();
3253        let (text_prov, lines) = make_lines(&text, 2., false);
3254        let r: Vec<_> = lines
3255            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(0), true)
3256            .map(|l| text.slice_to_cow(l.interval))
3257            .collect();
3258        assert_eq!(r, vec![""]);
3259        let r: Vec<_> = lines
3260            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(1), true)
3261            .map(|l| text.slice_to_cow(l.interval))
3262            .collect();
3263        assert_eq!(r, Vec::<&str>::new());
3264        let r: Vec<_> = lines
3265            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(2), true)
3266            .map(|l| text.slice_to_cow(l.interval))
3267            .collect();
3268        assert_eq!(r, Vec::<&str>::new());
3269    }
3270
3271    #[test]
3272    fn line_numbers() {
3273        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
3274        let (text_prov, lines) = make_lines(&text, 12.0 * 2.0, true);
3275        let get_nums = |start_vline: usize| {
3276            lines
3277                .iter_vlines(&text_prov, false, VLine(start_vline))
3278                .map(|l| {
3279                    (
3280                        l.rvline.line,
3281                        l.vline.get(),
3282                        l.is_first(),
3283                        text.slice_to_cow(l.interval),
3284                    )
3285                })
3286                .collect::<Vec<_>>()
3287        };
3288        // (line, vline, is_first, text)
3289        let x = vec![
3290            (0, 0, true, "aaaa".into()),
3291            (1, 1, true, "bb ".into()),
3292            (1, 2, false, "bb ".into()),
3293            (1, 3, false, "cc".into()),
3294            (2, 4, true, "cc ".into()),
3295            (2, 5, false, "dddd ".into()),
3296            (2, 6, false, "eeee ".into()),
3297            (2, 7, false, "ff".into()),
3298            (3, 8, true, "ff ".into()),
3299            (3, 9, false, "gggg".into()),
3300        ];
3301
3302        // This ensures that there's no inconsistencies between starting at a specific index
3303        // vs starting at zero and iterating to that index.
3304        for i in 0..x.len() {
3305            let nums = get_nums(i);
3306            println!("i: {i}, #nums: {}, #&x[i..]: {}", nums.len(), x[i..].len());
3307            assert_eq!(nums, &x[i..], "failed at #{i}");
3308        }
3309
3310        // TODO: test this without any wrapping
3311    }
3312
3313    #[test]
3314    fn last_col() {
3315        let text: Rope = Rope::from("conf = Config::default();");
3316        let (text_prov, lines) = make_lines(&text, 24.0 * 2.0, true);
3317
3318        let mut iter = lines.iter_rvlines(&text_prov, false, RVLine::default());
3319
3320        // "conf = "
3321        let v = iter.next().unwrap();
3322        assert_eq!(v.last_col(&text_prov, false), 6);
3323        assert_eq!(v.last_col(&text_prov, true), 7);
3324
3325        // "Config::default();"
3326        let v = iter.next().unwrap();
3327        assert_eq!(v.last_col(&text_prov, false), 24);
3328        assert_eq!(v.last_col(&text_prov, true), 25);
3329
3330        let text = Rope::from("blah\nthing");
3331        let (text_prov, lines) = make_lines(&text, 1000., false);
3332        let mut iter = lines.iter_rvlines(&text_prov, false, RVLine::default());
3333
3334        let rtext = RopeTextVal::new(text.clone());
3335
3336        // "blah"
3337        let v = iter.next().unwrap();
3338        assert_eq!(v.last_col(&text_prov, false), 3);
3339        assert_eq!(v.last_col(&text_prov, true), 4);
3340        assert_eq!(rtext.offset_of_line_col(0, 3), 3);
3341        assert_eq!(rtext.offset_of_line_col(0, 4), 4);
3342
3343        // "text"
3344        let v = iter.next().unwrap();
3345        assert_eq!(v.last_col(&text_prov, false), 4);
3346        assert_eq!(v.last_col(&text_prov, true), 5);
3347
3348        let text = Rope::from("blah\r\nthing");
3349        let (text_prov, lines) = make_lines(&text, 1000., false);
3350        let mut iter = lines.iter_rvlines(&text_prov, false, RVLine::default());
3351
3352        let rtext = RopeTextVal::new(text.clone());
3353
3354        // "blah"
3355        let v = iter.next().unwrap();
3356        assert_eq!(v.last_col(&text_prov, false), 3);
3357        assert_eq!(v.last_col(&text_prov, true), 4);
3358        assert_eq!(rtext.offset_of_line_col(0, 3), 3);
3359        assert_eq!(rtext.offset_of_line_col(0, 4), 4);
3360
3361        // "text"
3362        let v = iter.next().unwrap();
3363        assert_eq!(v.last_col(&text_prov, false), 4);
3364        assert_eq!(v.last_col(&text_prov, true), 5);
3365        assert_eq!(rtext.offset_of_line_col(0, 4), 4);
3366        assert_eq!(rtext.offset_of_line_col(0, 5), 4);
3367    }
3368
3369    #[test]
3370    fn layout_cols() {
3371        let text = Rope::from("aaaa\nbb bb cc\ndd");
3372        let mut layout = TextLayout::new();
3373        layout.set_text("aaaa", AttrsList::new(Attrs::new()), None);
3374        let layout = TextLayoutLine {
3375            extra_style: Vec::new(),
3376            text: layout,
3377            whitespaces: None,
3378            indent: 0.,
3379            phantom_text: PhantomTextLine::default(),
3380        };
3381
3382        let (text_prov, _) = make_lines(&text, 10000., false);
3383        assert_eq!(
3384            layout.layout_cols(&text_prov, 0).collect::<Vec<_>>(),
3385            vec![(0, 4)]
3386        );
3387        let (text_prov, _) = make_lines(&text, 10000., true);
3388        assert_eq!(
3389            layout.layout_cols(&text_prov, 0).collect::<Vec<_>>(),
3390            vec![(0, 4)]
3391        );
3392
3393        let text = Rope::from("aaaa\r\nbb bb cc\r\ndd");
3394        let mut layout = TextLayout::new();
3395        layout.set_text("aaaa", AttrsList::new(Attrs::new()), None);
3396        let layout = TextLayoutLine {
3397            extra_style: Vec::new(),
3398            text: layout,
3399            whitespaces: None,
3400            indent: 0.,
3401            phantom_text: PhantomTextLine::default(),
3402        };
3403
3404        let (text_prov, _) = make_lines(&text, 10000., false);
3405        assert_eq!(
3406            layout.layout_cols(&text_prov, 0).collect::<Vec<_>>(),
3407            vec![(0, 4)]
3408        );
3409        let (text_prov, _) = make_lines(&text, 10000., true);
3410        assert_eq!(
3411            layout.layout_cols(&text_prov, 0).collect::<Vec<_>>(),
3412            vec![(0, 4)]
3413        );
3414    }
3415
3416    /// Verify that `start_layout_cols` (standalone) returns the same start
3417    /// values as `layout_cols` for every case.
3418    #[test]
3419    fn start_layout_cols_matches_layout_cols() {
3420        // Simple single-line, no wrapping.
3421        let text = Rope::from("aaaa\nbb bb cc\ndd");
3422        let mut tl = TextLayout::new();
3423        tl.set_text("aaaa", AttrsList::new(Attrs::new()), None);
3424        let layout = TextLayoutLine {
3425            extra_style: Vec::new(),
3426            text: tl,
3427            whitespaces: None,
3428            indent: 0.,
3429            phantom_text: PhantomTextLine::default(),
3430        };
3431        let (text_prov, _) = make_lines(&text, 10000., false);
3432        let from_full: Vec<usize> = layout.layout_cols(&text_prov, 0).map(|(s, _)| s).collect();
3433        let from_standalone: Vec<usize> = layout.start_layout_cols().collect();
3434        assert_eq!(from_full, from_standalone, "single-line no wrap");
3435
3436        // Wrapped multi-line text (the case from iter_lines test).
3437        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
3438        let (text_prov, lines) = make_lines(&text, 2., true);
3439        let v = lines.get_init_text_layout(0, ConfigId::new(0, 0), &text_prov, 2, true);
3440
3441        let from_full: Vec<usize> = v.layout_cols(&text_prov, 2).map(|(s, _)| s).collect();
3442        let from_standalone: Vec<usize> = v.start_layout_cols().collect();
3443        assert_eq!(from_full, from_standalone, "wrapped multi-line");
3444
3445        // CRLF line endings.
3446        let text = Rope::from("aaaa\r\nbb bb cc\r\ndd");
3447        let mut tl = TextLayout::new();
3448        tl.set_text("aaaa", AttrsList::new(Attrs::new()), None);
3449        let layout = TextLayoutLine {
3450            extra_style: Vec::new(),
3451            text: tl,
3452            whitespaces: None,
3453            indent: 0.,
3454            phantom_text: PhantomTextLine::default(),
3455        };
3456        let (text_prov, _) = make_lines(&text, 10000., true);
3457        let from_full: Vec<usize> = layout.layout_cols(&text_prov, 0).map(|(s, _)| s).collect();
3458        let from_standalone: Vec<usize> = layout.start_layout_cols().collect();
3459        assert_eq!(from_full, from_standalone, "CRLF");
3460    }
3461
3462    /// Test that `start_layout_cols` handles whitespace-only content
3463    /// (the prefix fallback path).
3464    #[test]
3465    fn start_layout_cols_whitespace_only() {
3466        let mut tl = TextLayout::new();
3467        tl.set_text("    ", AttrsList::new(Attrs::new()), None);
3468        let layout = TextLayoutLine {
3469            extra_style: Vec::new(),
3470            text: tl,
3471            whitespaces: None,
3472            indent: 0.,
3473            phantom_text: PhantomTextLine::default(),
3474        };
3475        let starts: Vec<usize> = layout.start_layout_cols().collect();
3476        // Should produce exactly one entry (the prefix fallback).
3477        assert_eq!(starts.len(), 1, "whitespace-only should have prefix");
3478        assert_eq!(starts[0], 0, "prefix start should be 0");
3479    }
3480
3481    #[test]
3482    fn test_end_of_rvline() {
3483        fn eor(lines: &Lines, text_prov: &impl TextLayoutProvider, rvline: RVLine) -> usize {
3484            let layouts = lines.text_layouts.borrow();
3485            end_of_rvline(&layouts, text_prov, 12, rvline)
3486        }
3487
3488        fn check_equiv(text: &Rope, expected: usize, from: &str) {
3489            let (text_prov, lines) = make_lines(text, 10000., false);
3490            let end1 = eor(&lines, &text_prov, RVLine::new(0, 0));
3491
3492            let (text_prov, lines) = make_lines(text, 10000., true);
3493            assert_eq!(
3494                eor(&lines, &text_prov, RVLine::new(0, 0)),
3495                end1,
3496                "non-init end_of_rvline not equivalent to init ({from})"
3497            );
3498            assert_eq!(end1, expected, "end_of_rvline not equivalent ({from})");
3499        }
3500
3501        let text = Rope::from("");
3502        check_equiv(&text, 0, "empty");
3503
3504        let text = Rope::from("aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg");
3505        check_equiv(&text, 4, "simple multiline (LF)");
3506
3507        let text = Rope::from("aaaa\r\nbb bb cc\r\ncc dddd eeee ff\r\nff gggg");
3508        check_equiv(&text, 4, "simple multiline (CRLF)");
3509
3510        let text = Rope::from("a b c d ");
3511        let mut ph = HashMap::new();
3512        ph.insert(
3513            0,
3514            PhantomTextLine {
3515                text: smallvec![mph(PhantomTextKind::Completion, 1, "hi")],
3516            },
3517        );
3518
3519        let (text_prov, lines) = make_lines_ph(&text, 1., true, ph);
3520
3521        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 0)), 2);
3522
3523        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 1)), 4);
3524
3525        let text = Rope::from("        let j = test_test\nlet blah = 5;");
3526
3527        let mut ph = HashMap::new();
3528        ph.insert(
3529            0,
3530            PhantomTextLine {
3531                text: smallvec![
3532                    mph(
3533                        PhantomTextKind::Diagnostic,
3534                        26,
3535                        "    Syntax Error: `let` expressions are not supported here"
3536                    ),
3537                    mph(
3538                        PhantomTextKind::Diagnostic,
3539                        26,
3540                        "    Syntax Error: expected SEMICOLON"
3541                    ),
3542                ],
3543            },
3544        );
3545
3546        let (text_prov, lines) = make_lines_ph(&text, 250., true, ph);
3547
3548        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 0)), 25);
3549        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 1)), 25);
3550        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 2)), 25);
3551        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 3)), 25);
3552        assert_eq!(eor(&lines, &text_prov, RVLine::new(1, 0)), 39);
3553    }
3554
3555    #[test]
3556    fn equivalence() {
3557        // Extra tests that the visual lines you get when initting are equivalent to the ones you
3558        // get if you don't init
3559        // TODO: tests for them being equivalent even with wrapping
3560
3561        fn check_equiv(text: &Rope, from: &str) {
3562            let (text_prov, lines) = make_lines(text, 10000., false);
3563            let iter = lines.iter_rvlines(&text_prov, false, RVLine::default());
3564
3565            let (text_prov, lines) = make_lines(text, 01000., true);
3566            let iter2 = lines.iter_rvlines(&text_prov, false, RVLine::default());
3567
3568            // Just assume same length
3569            for (i, v) in iter.zip(iter2) {
3570                assert_eq!(
3571                    i, v,
3572                    "Line {} is not equivalent when initting ({from})",
3573                    i.rvline.line
3574                );
3575            }
3576        }
3577
3578        check_equiv(&Rope::from(""), "empty");
3579        check_equiv(&Rope::from("a"), "a");
3580        check_equiv(
3581            &Rope::from("aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg"),
3582            "simple multiline (LF)",
3583        );
3584        check_equiv(
3585            &Rope::from("aaaa\r\nbb bb cc\r\ncc dddd eeee ff\r\nff gggg"),
3586            "simple multiline (CRLF)",
3587        );
3588    }
3589}