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
866                .start_layout_cols(text_prov, line)
867                .nth(line_index)
868                .unwrap_or(0);
869            let col = text_prov.before_phantom_col(line, col);
870
871            rope_text.offset_of_line_col(line, col)
872        } else {
873            // There was no text layout for this line, so we treat it like if line index is zero
874            // even if it is not.
875
876            debug_assert_eq!(
877                line_index, 0,
878                "Line index was zero. This likely indicates keeping an rvline past when it was valid."
879            );
880
881            rope_text.offset_of_line(line)
882        }
883    }
884
885    /// Get the relative visual line of the buffer line
886    pub fn rvline_of_line(&self, text_prov: &impl TextLayoutProvider, line: usize) -> RVLine {
887        if self.is_linear(text_prov) {
888            return RVLine::new(line, 0);
889        }
890
891        let offset = text_prov.rope_text().offset_of_line(line);
892
893        find_rvline_of_offset(self, text_prov, offset, CursorAffinity::Backward)
894            .unwrap_or_else(|| self.last_rvline(text_prov))
895    }
896
897    /// Check whether the cache rev or config id has changed, clearing the cache if it has.
898    pub fn check_cache(&self, cache_rev: u64, config_id: ConfigId) {
899        let (prev_cache_rev, prev_config_id) = {
900            let l = self.text_layouts.borrow();
901            (l.cache_rev, l.config_id)
902        };
903
904        if cache_rev != prev_cache_rev || config_id != prev_config_id {
905            self.clear(cache_rev, Some(config_id));
906        }
907    }
908
909    /// Check whether the text layout cache revision is different.
910    ///
911    /// Clears the layouts and updates the cache rev if it was different.
912    pub fn check_cache_rev(&self, cache_rev: u64) {
913        if cache_rev != self.text_layouts.borrow().cache_rev {
914            self.clear(cache_rev, None);
915        }
916    }
917
918    /// Clear the text layouts with a given cache revision
919    pub fn clear(&self, cache_rev: u64, config_id: Option<ConfigId>) {
920        self.text_layouts.borrow_mut().clear(cache_rev, config_id);
921        self.last_vline.set(None);
922    }
923
924    /// Clear the layouts and vline without changing the cache rev or config id.
925    pub fn clear_unchanged(&self) {
926        self.text_layouts.borrow_mut().clear_unchanged();
927        self.last_vline.set(None);
928    }
929}
930
931/// This is a separate function as a hacky solution to lifetimes.
932///
933/// While it being on `Lines` makes the most sense, it being separate lets us only have
934/// `text_layouts` and `wrap` from the original to then initialize a text layout. This simplifies
935/// lifetime issues in some functions, since they can just have an `Arc`/`Rc`.
936///
937/// Note: This does not clear the cache or check via config id. That should be done outside this
938/// as `Lines` does require knowing when the cache is invalidated.
939fn get_init_text_layout(
940    text_layouts: &RefCell<TextLayoutCache>,
941    layout_event: Option<Listener<LayoutEvent>>,
942    text_prov: impl TextLayoutProvider,
943    line: usize,
944    font_size: usize,
945    wrap: ResolvedWrap,
946    last_vline: &Cell<Option<VLine>>,
947) -> Arc<TextLayoutLine> {
948    // If we don't have a second layer of the hashmap initialized for this specific font size,
949    // do it now
950    if !text_layouts.borrow().layouts.contains_key(&font_size) {
951        let mut cache = text_layouts.borrow_mut();
952        cache.layouts.insert(font_size, HashMap::new());
953    }
954
955    // Get whether there's an entry for this specific font size and line
956    let cache_exists = text_layouts
957        .borrow()
958        .layouts
959        .get(&font_size)
960        .unwrap()
961        .get(&line)
962        .is_some();
963    // If there isn't an entry then we actually have to create it
964    if !cache_exists {
965        let text_layout = text_prov.new_text_layout(line, font_size, wrap);
966
967        // Update last vline
968        if let Some(vline) = last_vline.get() {
969            let last_line = text_prov.rope_text().last_line();
970            if line <= last_line {
971                // We can get rid of the old line count and add our new count.
972                // This lets us typically avoid having to calculate the last visual line.
973                let vline = vline.get();
974                let new_vline = vline + (text_layout.line_count() - 1);
975
976                last_vline.set(Some(VLine(new_vline)));
977            }
978            // If the line is past the end of the file, then we don't need to update the last
979            // visual line. It is garbage.
980        }
981        // Otherwise last vline was already None.
982
983        {
984            // Add the text layout to the cache.
985            let mut cache = text_layouts.borrow_mut();
986            let width = text_layout.text.size().width;
987            if width > cache.max_width {
988                cache.max_width = width;
989            }
990            cache
991                .layouts
992                .get_mut(&font_size)
993                .unwrap()
994                .insert(line, text_layout);
995        }
996
997        if let Some(layout_event) = layout_event {
998            layout_event.send(LayoutEvent::CreatedLayout { font_size, line });
999        }
1000    }
1001
1002    // Just get the entry, assuming it has been created because we initialize it above.
1003    text_layouts
1004        .borrow()
1005        .layouts
1006        .get(&font_size)
1007        .unwrap()
1008        .get(&line)
1009        .cloned()
1010        .unwrap()
1011}
1012
1013/// Returns `(visual line, line_index)`
1014fn find_vline_of_offset(
1015    lines: &Lines,
1016    text_prov: &impl TextLayoutProvider,
1017    offset: usize,
1018    affinity: CursorAffinity,
1019) -> Option<(VLine, usize)> {
1020    let layouts = lines.text_layouts.borrow();
1021
1022    let rope_text = text_prov.rope_text();
1023
1024    let buffer_line = rope_text.line_of_offset(offset);
1025    let line_start_offset = rope_text.offset_of_line(buffer_line);
1026    let vline = find_vline_of_line(lines, text_prov, buffer_line)?;
1027
1028    let font_size = lines.font_size(buffer_line);
1029    let Some(text_layout) = layouts.get(font_size, buffer_line) else {
1030        // No text layout for this line, so the vline we found is definitely correct.
1031        // As well, there is no previous soft line to consider
1032        return Some((vline, 0));
1033    };
1034
1035    let col = offset - line_start_offset;
1036
1037    let (vline, line_index) =
1038        find_start_line_index(text_prov, text_layout, buffer_line, col, affinity)
1039            .map(|line_index| (VLine(vline.get() + line_index), line_index))?;
1040
1041    // If the most recent line break was due to a soft line break,
1042    if line_index > 0 {
1043        if let CursorAffinity::Backward = affinity {
1044            // TODO: This can definitely be smarter. We're doing a vline search, and then this is
1045            // practically doing another!
1046            let line_end = lines.offset_of_vline(text_prov, vline);
1047            // then if we're right at that soft line break, a backwards affinity
1048            // means that we are on the previous visual line.
1049            if line_end == offset && vline.get() != 0 {
1050                return Some((VLine(vline.get() - 1), line_index - 1));
1051            }
1052        }
1053    }
1054
1055    Some((vline, line_index))
1056}
1057
1058fn find_rvline_of_offset(
1059    lines: &Lines,
1060    text_prov: &impl TextLayoutProvider,
1061    offset: usize,
1062    affinity: CursorAffinity,
1063) -> Option<RVLine> {
1064    let layouts = lines.text_layouts.borrow();
1065
1066    let rope_text = text_prov.rope_text();
1067
1068    let buffer_line = rope_text.line_of_offset(offset);
1069    let line_start_offset = rope_text.offset_of_line(buffer_line);
1070
1071    let font_size = lines.font_size(buffer_line);
1072    let Some(text_layout) = layouts.get(font_size, buffer_line) else {
1073        // There is no text layout for this line so the line index is always zero.
1074        return Some(RVLine::new(buffer_line, 0));
1075    };
1076
1077    let col = offset - line_start_offset;
1078
1079    let rv = find_start_line_index(text_prov, text_layout, buffer_line, col, affinity)
1080        .map(|line_index| RVLine::new(buffer_line, line_index))?;
1081
1082    // If the most recent line break was due to a soft line break,
1083    if rv.line_index > 0 {
1084        if let CursorAffinity::Backward = affinity {
1085            let line_end = lines.offset_of_rvline(text_prov, rv);
1086            // then if we're right at that soft line break, a backwards affinity
1087            // means that we are on the previous visual line.
1088            if line_end == offset {
1089                if rv.line_index > 0 {
1090                    return Some(RVLine::new(rv.line, rv.line_index - 1));
1091                } else if rv.line == 0 {
1092                    // There is no previous line, we do nothing.
1093                } else {
1094                    // We have to get rvline info for that rvline, so we can get the last line index
1095                    // This should always have at least one rvline in it.
1096                    let font_sizes = lines.font_sizes.borrow();
1097                    let (prev, _) = prev_rvline(&layouts, text_prov, &**font_sizes, rv)?;
1098                    return Some(prev);
1099                }
1100            }
1101        }
1102    }
1103
1104    Some(rv)
1105}
1106
1107// TODO: a lot of these just take lines, so should possibly just be put on it.
1108
1109/// Find the line index which contains the column.
1110fn find_start_line_index(
1111    text_prov: &impl TextLayoutProvider,
1112    text_layout: &TextLayoutLine,
1113    line: usize,
1114    col: usize,
1115    affinity: CursorAffinity,
1116) -> Option<usize> {
1117    let mut starts = text_layout
1118        .layout_cols(text_prov, line)
1119        .enumerate()
1120        .peekable();
1121
1122    while let Some((i, (layout_start, _))) = starts.next() {
1123        if affinity == CursorAffinity::Backward {
1124            // TODO: we should just apply after_col to col to do this transformation once
1125            let layout_start = text_prov.before_phantom_col(line, layout_start);
1126            if layout_start >= col {
1127                return Some(i);
1128            }
1129        }
1130
1131        let next_start = starts
1132            .peek()
1133            .map(|(_, (next_start, _))| text_prov.before_phantom_col(line, *next_start));
1134
1135        if let Some(next_start) = next_start {
1136            if next_start > col {
1137                // The next layout starts *past* our column, so we're on the previous line.
1138                return Some(i);
1139            }
1140        } else {
1141            // There was no next glyph, which implies that we are either on this line or not at all
1142            return Some(i);
1143        }
1144    }
1145
1146    None
1147}
1148
1149/// Get the first visual line of a buffer line.
1150fn find_vline_of_line(
1151    lines: &Lines,
1152    text_prov: &impl TextLayoutProvider,
1153    line: usize,
1154) -> Option<VLine> {
1155    let rope = text_prov.rope_text();
1156
1157    let last_line = rope.last_line();
1158
1159    if line > last_line / 2 {
1160        // Often the last vline will already be cached, which lets us half the search time.
1161        // The compiler may or may not be smart enough to combine the last vline calculation with
1162        // our calculation of the vline of the line we're looking for, but it might not.
1163        // If it doesn't, we could write a custom version easily.
1164        let last_vline = lines.last_vline(text_prov);
1165        let last_rvline = lines.last_rvline(text_prov);
1166        let last_start_vline = VLine(last_vline.get() - last_rvline.line_index);
1167        find_vline_of_line_backwards(lines, (last_start_vline, last_line), line)
1168    } else {
1169        find_vline_of_line_forwards(lines, (VLine(0), 0), line)
1170    }
1171}
1172
1173/// Get the first visual line of a buffer line.
1174///
1175/// This searches backwards from `pivot`, so it should be *after* the given line.
1176/// This requires that the `pivot` is the first line index of the line it is for.
1177fn find_vline_of_line_backwards(
1178    lines: &Lines,
1179    (start, s_line): (VLine, usize),
1180    line: usize,
1181) -> Option<VLine> {
1182    if line > s_line {
1183        return None;
1184    } else if line == s_line {
1185        return Some(start);
1186    } else if line == 0 {
1187        return Some(VLine(0));
1188    }
1189
1190    let layouts = lines.text_layouts.borrow();
1191
1192    let mut cur_vline = start.get();
1193
1194    for cur_line in line..s_line {
1195        let font_size = lines.font_size(cur_line);
1196
1197        let Some(text_layout) = layouts.get(font_size, cur_line) else {
1198            // no text layout, so its just a normal line
1199            cur_vline -= 1;
1200            continue;
1201        };
1202
1203        let line_count = text_layout.line_count();
1204
1205        cur_vline -= line_count;
1206    }
1207
1208    Some(VLine(cur_vline))
1209}
1210
1211fn find_vline_of_line_forwards(
1212    lines: &Lines,
1213    (start, s_line): (VLine, usize),
1214    line: usize,
1215) -> Option<VLine> {
1216    match line.cmp(&s_line) {
1217        Ordering::Equal => return Some(start),
1218        Ordering::Less => return None,
1219        Ordering::Greater => (),
1220    }
1221
1222    let layouts = lines.text_layouts.borrow();
1223
1224    let mut cur_vline = start.get();
1225
1226    for cur_line in s_line..line {
1227        let font_size = lines.font_size(cur_line);
1228
1229        let Some(text_layout) = layouts.get(font_size, cur_line) else {
1230            // no text layout, so its just a normal line
1231            cur_vline += 1;
1232            continue;
1233        };
1234
1235        let line_count = text_layout.line_count();
1236        cur_vline += line_count;
1237    }
1238
1239    Some(VLine(cur_vline))
1240}
1241
1242/// Find the (start offset, buffer line, layout line index) of a given visual line.
1243///
1244/// start offset is into the file, rather than the text layouts string, so it does not include
1245/// phantom text.
1246///
1247/// Returns `None` if the visual line is out of bounds.
1248fn find_vline_init_info(
1249    lines: &Lines,
1250    text_prov: &impl TextLayoutProvider,
1251    vline: VLine,
1252) -> Option<(usize, RVLine)> {
1253    let rope_text = text_prov.rope_text();
1254
1255    if vline.get() == 0 {
1256        return Some((0, RVLine::new(0, 0)));
1257    }
1258
1259    if lines.is_linear(text_prov) {
1260        // If lines is linear then we can trivially convert the visual line to a buffer line
1261        let line = vline.get();
1262        if line > rope_text.last_line() {
1263            return None;
1264        }
1265
1266        return Some((rope_text.offset_of_line(line), RVLine::new(line, 0)));
1267    }
1268
1269    let last_vline = lines.last_vline(text_prov);
1270
1271    if vline > last_vline {
1272        return None;
1273    }
1274
1275    if vline.get() > last_vline.get() / 2 {
1276        let last_rvline = lines.last_rvline(text_prov);
1277        find_vline_init_info_rv_backward(lines, text_prov, (last_vline, last_rvline), vline)
1278    } else {
1279        find_vline_init_info_forward(lines, text_prov, (VLine(0), 0), vline)
1280    }
1281}
1282
1283// TODO(minor): should we package (VLine, buffer line) into a struct since we use it for these
1284// pseudo relative calculations often?
1285/// Find the `(start offset, rvline)` of a given [`VLine`]
1286///
1287/// start offset is into the file, rather than text layout's string, so it does not include
1288/// phantom text.
1289///
1290/// Returns `None` if the visual line is out of bounds, or if the start is past our target.
1291fn find_vline_init_info_forward(
1292    lines: &Lines,
1293    text_prov: &impl TextLayoutProvider,
1294    (start, start_line): (VLine, usize),
1295    vline: VLine,
1296) -> Option<(usize, RVLine)> {
1297    if start > vline {
1298        return None;
1299    }
1300
1301    let rope_text = text_prov.rope_text();
1302
1303    let mut cur_line = start_line;
1304    let mut cur_vline = start.get();
1305
1306    let layouts = lines.text_layouts.borrow();
1307    while cur_vline < vline.get() {
1308        let font_size = lines.font_size(cur_line);
1309        let line_count = if let Some(text_layout) = layouts.get(font_size, cur_line) {
1310            let line_count = text_layout.line_count();
1311
1312            // We can then check if the visual line is in this intervening range.
1313            if cur_vline + line_count > vline.get() {
1314                // We found the line that contains the visual line.
1315                // We can now find the offset of the visual line within the line.
1316                let line_index = vline.get() - cur_vline;
1317                // TODO: is it fine to unwrap here?
1318                let col = text_layout
1319                    .start_layout_cols(text_prov, cur_line)
1320                    .nth(line_index)
1321                    .unwrap_or(0);
1322                let col = text_prov.before_phantom_col(cur_line, col);
1323
1324                let offset = rope_text.offset_of_line_col(cur_line, col);
1325                return Some((offset, RVLine::new(cur_line, line_index)));
1326            }
1327
1328            // The visual line is not in this line, so we have to keep looking.
1329            line_count
1330        } else {
1331            // There was no text layout so we only have to consider the line breaks in this line.
1332            // Which, since we don't handle phantom text, is just one.
1333
1334            1
1335        };
1336
1337        cur_line += 1;
1338        cur_vline += line_count;
1339    }
1340
1341    // We've reached the visual line we're looking for, we can return the offset.
1342    // This also handles the case where the vline is past the end of the text.
1343    if cur_vline == vline.get() {
1344        if cur_line > rope_text.last_line() {
1345            return None;
1346        }
1347
1348        // We use cur_line because if our target vline is out of bounds
1349        // then the result should be len
1350        Some((rope_text.offset_of_line(cur_line), RVLine::new(cur_line, 0)))
1351    } else {
1352        // We've gone past the visual line we're looking for, so it is out of bounds.
1353        None
1354    }
1355}
1356
1357/// Find the `(start offset, rvline)` of a given [`VLine`]
1358///
1359/// `start offset` is into the file, rather than the text layout's content, so it does not
1360/// include phantom text.
1361///
1362/// Returns `None` if the visual line is out of bounds or if the start is before our target.
1363/// This iterates backwards.
1364fn find_vline_init_info_rv_backward(
1365    lines: &Lines,
1366    text_prov: &impl TextLayoutProvider,
1367    (start, start_rvline): (VLine, RVLine),
1368    vline: VLine,
1369) -> Option<(usize, RVLine)> {
1370    if start < vline {
1371        // The start was before the target.
1372        return None;
1373    }
1374
1375    // This would the vline at the very start of the buffer line
1376    let shifted_start = VLine(start.get() - start_rvline.line_index);
1377    match shifted_start.cmp(&vline) {
1378        // The shifted start was equivalent to the vline, which makes it easy to compute
1379        Ordering::Equal => {
1380            let offset = text_prov.rope_text().offset_of_line(start_rvline.line);
1381            Some((offset, RVLine::new(start_rvline.line, 0)))
1382        }
1383        // The new start is before the vline, that means the vline is on the same line.
1384        Ordering::Less => {
1385            let line_index = vline.get() - shifted_start.get();
1386            let layouts = lines.text_layouts.borrow();
1387            let font_size = lines.font_size(start_rvline.line);
1388            if let Some(text_layout) = layouts.get(font_size, start_rvline.line) {
1389                vline_init_info_b(
1390                    text_prov,
1391                    text_layout,
1392                    RVLine::new(start_rvline.line, line_index),
1393                )
1394            } else {
1395                // There was no text layout so we only have to consider the line breaks in this line.
1396
1397                let base_offset = text_prov.rope_text().offset_of_line(start_rvline.line);
1398                Some((base_offset, RVLine::new(start_rvline.line, 0)))
1399            }
1400        }
1401        Ordering::Greater => find_vline_init_info_backward(
1402            lines,
1403            text_prov,
1404            (shifted_start, start_rvline.line),
1405            vline,
1406        ),
1407    }
1408}
1409
1410fn find_vline_init_info_backward(
1411    lines: &Lines,
1412    text_prov: &impl TextLayoutProvider,
1413    (mut start, mut start_line): (VLine, usize),
1414    vline: VLine,
1415) -> Option<(usize, RVLine)> {
1416    loop {
1417        let (prev_vline, prev_line) = prev_line_start(lines, start, start_line)?;
1418
1419        match prev_vline.cmp(&vline) {
1420            // We found the target, and it was at the start
1421            Ordering::Equal => {
1422                let offset = text_prov.rope_text().offset_of_line(prev_line);
1423                return Some((offset, RVLine::new(prev_line, 0)));
1424            }
1425            // The target is on this line, so we can just search for it
1426            Ordering::Less => {
1427                let font_size = lines.font_size(prev_line);
1428                let layouts = lines.text_layouts.borrow();
1429                if let Some(text_layout) = layouts.get(font_size, prev_line) {
1430                    return vline_init_info_b(
1431                        text_prov,
1432                        text_layout,
1433                        RVLine::new(prev_line, vline.get() - prev_vline.get()),
1434                    );
1435                } else {
1436                    // There was no text layout so we only have to consider the line breaks in this line.
1437                    // Which, since we don't handle phantom text, is just one.
1438
1439                    let base_offset = text_prov.rope_text().offset_of_line(prev_line);
1440                    return Some((base_offset, RVLine::new(prev_line, 0)));
1441                }
1442            }
1443            // The target is before this line, so we have to keep searching
1444            Ordering::Greater => {
1445                start = prev_vline;
1446                start_line = prev_line;
1447            }
1448        }
1449    }
1450}
1451
1452/// Get the previous (line, start visual line) from a (line, start visual line).
1453fn prev_line_start(lines: &Lines, vline: VLine, line: usize) -> Option<(VLine, usize)> {
1454    if line == 0 {
1455        return None;
1456    }
1457
1458    let layouts = lines.text_layouts.borrow();
1459
1460    let prev_line = line - 1;
1461    let font_size = lines.font_size(line);
1462    if let Some(layout) = layouts.get(font_size, prev_line) {
1463        let line_count = layout.line_count();
1464        let prev_vline = vline.get() - line_count;
1465        Some((VLine(prev_vline), prev_line))
1466    } else {
1467        // There's no layout for the previous line which makes this easy
1468        Some((VLine(vline.get() - 1), prev_line))
1469    }
1470}
1471
1472fn vline_init_info_b(
1473    text_prov: &impl TextLayoutProvider,
1474    text_layout: &TextLayoutLine,
1475    rv: RVLine,
1476) -> Option<(usize, RVLine)> {
1477    let rope_text = text_prov.rope_text();
1478    let col = text_layout
1479        .start_layout_cols(text_prov, rv.line)
1480        .nth(rv.line_index)
1481        .unwrap_or(0);
1482    let col = text_prov.before_phantom_col(rv.line, col);
1483
1484    let offset = rope_text.offset_of_line_col(rv.line, col);
1485
1486    Some((offset, rv))
1487}
1488
1489/// Information about the visual line and how it relates to the underlying buffer line.
1490#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1491#[non_exhaustive]
1492pub struct VLineInfo<L = VLine> {
1493    /// Start offset to end offset in the buffer that this visual line covers.
1494    ///
1495    /// Note that this is obviously not including phantom text.
1496    pub interval: Interval,
1497    /// The total number of lines in this buffer line. Always at least 1.
1498    pub line_count: usize,
1499    pub rvline: RVLine,
1500    /// The actual visual line this is for.
1501    ///
1502    /// For relative visual line iteration, this is empty.
1503    pub vline: L,
1504}
1505impl<L: std::fmt::Debug> VLineInfo<L> {
1506    /// Create a new instance of `VLineInfo`
1507    ///
1508    /// This should rarely be used directly.
1509    pub fn new<I: Into<Interval>>(iv: I, rvline: RVLine, line_count: usize, vline: L) -> Self {
1510        Self {
1511            interval: iv.into(),
1512            line_count,
1513            rvline,
1514            vline,
1515        }
1516    }
1517
1518    pub fn to_blank(&self) -> VLineInfo<()> {
1519        VLineInfo::new(self.interval, self.rvline, self.line_count, ())
1520    }
1521
1522    /// Check whether the interval is empty.
1523    ///
1524    /// Note that there could still be phantom text on this line.
1525    pub fn is_empty(&self) -> bool {
1526        self.interval.is_empty()
1527    }
1528
1529    /// Check whether the interval is empty and we're not on the first line,
1530    /// thus likely being phantom text (or possibly poor wrapping)
1531    pub fn is_empty_phantom(&self) -> bool {
1532        self.is_empty() && self.rvline.line_index != 0
1533    }
1534
1535    pub fn is_first(&self) -> bool {
1536        self.rvline.is_first()
1537    }
1538
1539    // TODO: is this correct for phantom lines?
1540    // TODO: can't we just use the line count field now?
1541    /// Is this the last visual line for the relevant buffer line?
1542    pub fn is_last(&self, text_prov: &impl TextLayoutProvider) -> bool {
1543        let rope_text = text_prov.rope_text();
1544        let line_end = rope_text.line_end_offset(self.rvline.line, false);
1545        let vline_end = self.line_end_offset(text_prov, false);
1546
1547        line_end == vline_end
1548    }
1549
1550    /// Get the first column of the overall line of the visual line
1551    pub fn first_col(&self, text_prov: &impl TextLayoutProvider) -> usize {
1552        let line_start = self.interval.start;
1553        let start_offset = text_prov.text().offset_of_line(self.rvline.line);
1554        line_start - start_offset
1555    }
1556
1557    /// Get the last column in the overall line of this visual line
1558    ///
1559    /// The caret decides whether it is after the last character, or before it.
1560    /// ```rust,ignore
1561    /// // line content = "conf = Config::default();\n"
1562    /// // wrapped breakup = ["conf = ", "Config::default();\n"]
1563    ///
1564    /// // when vline_info is for "conf = "
1565    /// assert_eq!(vline_info.last_col(text_prov, false), 6) // "conf =| "
1566    /// assert_eq!(vline_info.last_col(text_prov, true), 7) // "conf = |"
1567    /// // when vline_info is for "Config::default();\n"
1568    /// // Notice that the column is in the overall line, not the wrapped line.
1569    /// assert_eq!(vline_info.last_col(text_prov, false), 24) // "Config::default()|;"
1570    /// assert_eq!(vline_info.last_col(text_prov, true), 25) // "Config::default();|"
1571    /// ```
1572    pub fn last_col(&self, text_prov: &impl TextLayoutProvider, caret: bool) -> usize {
1573        let vline_end = self.interval.end;
1574        let start_offset = text_prov.text().offset_of_line(self.rvline.line);
1575        // If these subtractions crash, then it is likely due to a bad vline being kept around
1576        // somewhere
1577        if !caret && !self.is_empty() {
1578            let vline_pre_end = text_prov.rope_text().prev_grapheme_offset(vline_end, 1, 0);
1579            vline_pre_end - start_offset
1580        } else {
1581            vline_end - start_offset
1582        }
1583    }
1584
1585    // TODO: we could generalize `RopeText::line_end_offset` to any interval, and then just use it here instead of basically reimplementing it.
1586    pub fn line_end_offset(&self, text_prov: &impl TextLayoutProvider, caret: bool) -> usize {
1587        let text = text_prov.text();
1588        let rope_text = text_prov.rope_text();
1589
1590        let mut offset = self.interval.end;
1591        let mut line_content: &str = &text.slice_to_cow(self.interval);
1592        if line_content.ends_with("\r\n") {
1593            offset -= 2;
1594            line_content = &line_content[..line_content.len() - 2];
1595        } else if line_content.ends_with('\n') {
1596            offset -= 1;
1597            line_content = &line_content[..line_content.len() - 1];
1598        }
1599        if !caret && !line_content.is_empty() {
1600            offset = rope_text.prev_grapheme_offset(offset, 1, 0);
1601        }
1602        offset
1603    }
1604
1605    /// Returns the offset of the first non-blank character in the line.
1606    pub fn first_non_blank_character(&self, text_prov: &impl TextLayoutProvider) -> usize {
1607        WordCursor::new(&text_prov.text(), self.interval.start).next_non_blank_char()
1608    }
1609}
1610
1611/// Iterator of the visual lines in a [`Lines`].
1612///
1613/// This only considers wrapped and phantom text lines that have been rendered into a text layout.
1614///
1615/// In principle, we could consider the newlines in phantom text for lines that have not been
1616/// rendered. However, that is more expensive to compute and is probably not actually *useful*.
1617struct VisualLines<T: TextLayoutProvider> {
1618    v: VisualLinesRelative<T>,
1619    vline: VLine,
1620}
1621impl<T: TextLayoutProvider> VisualLines<T> {
1622    pub fn new(lines: &Lines, text_prov: T, backwards: bool, start: VLine) -> VisualLines<T> {
1623        // TODO(minor): If we aren't using offset here then don't calculate it.
1624        let Some((_offset, rvline)) = find_vline_init_info(lines, &text_prov, start) else {
1625            return VisualLines::empty(lines, text_prov, backwards);
1626        };
1627
1628        VisualLines {
1629            v: VisualLinesRelative::new(lines, text_prov, backwards, rvline),
1630            vline: start,
1631        }
1632    }
1633
1634    pub fn empty(lines: &Lines, text_prov: T, backwards: bool) -> VisualLines<T> {
1635        VisualLines {
1636            v: VisualLinesRelative::empty(lines, text_prov, backwards),
1637            vline: VLine(0),
1638        }
1639    }
1640}
1641impl<T: TextLayoutProvider> Iterator for VisualLines<T> {
1642    type Item = VLineInfo;
1643
1644    fn next(&mut self) -> Option<VLineInfo> {
1645        let was_first_iter = self.v.is_first_iter;
1646        let info = self.v.next()?;
1647
1648        if !was_first_iter {
1649            if self.v.backwards {
1650                // This saturation isn't really needed, but just in case.
1651                debug_assert!(
1652                    self.vline.get() != 0,
1653                    "Expected VLine to always be nonzero if we were going backwards"
1654                );
1655                self.vline = VLine(self.vline.get().saturating_sub(1));
1656            } else {
1657                self.vline = VLine(self.vline.get() + 1);
1658            }
1659        }
1660
1661        Some(VLineInfo {
1662            interval: info.interval,
1663            line_count: info.line_count,
1664            rvline: info.rvline,
1665            vline: self.vline,
1666        })
1667    }
1668}
1669
1670/// Iterator of the visual lines in a [`Lines`] relative to some starting buffer line.
1671///
1672/// This only considers wrapped and phantom text lines that have been rendered into a text layout.
1673struct VisualLinesRelative<T: TextLayoutProvider> {
1674    font_sizes: Rc<dyn LineFontSizeProvider>,
1675    text_layouts: Rc<RefCell<TextLayoutCache>>,
1676    text_prov: T,
1677
1678    is_done: bool,
1679
1680    rvline: RVLine,
1681    /// Our current offset into the rope.
1682    offset: usize,
1683
1684    /// Which direction we should move in.
1685    backwards: bool,
1686    /// Whether there is a one-to-one mapping between buffer lines and visual lines.
1687    linear: bool,
1688
1689    is_first_iter: bool,
1690}
1691impl<T: TextLayoutProvider> VisualLinesRelative<T> {
1692    pub fn new(
1693        lines: &Lines,
1694        text_prov: T,
1695        backwards: bool,
1696        start: RVLine,
1697    ) -> VisualLinesRelative<T> {
1698        // Empty iterator if we're past the end of the possible lines
1699        if start > lines.last_rvline(&text_prov) {
1700            return VisualLinesRelative::empty(lines, text_prov, backwards);
1701        }
1702
1703        let layouts = lines.text_layouts.borrow();
1704        let font_size = lines.font_size(start.line);
1705        let offset = rvline_offset(&layouts, &text_prov, font_size, start);
1706
1707        let linear = lines.is_linear(&text_prov);
1708
1709        VisualLinesRelative {
1710            font_sizes: lines.font_sizes.borrow().clone(),
1711            text_layouts: lines.text_layouts.clone(),
1712            text_prov,
1713            is_done: false,
1714            rvline: start,
1715            offset,
1716            backwards,
1717            linear,
1718            is_first_iter: true,
1719        }
1720    }
1721
1722    pub fn empty(lines: &Lines, text_prov: T, backwards: bool) -> VisualLinesRelative<T> {
1723        VisualLinesRelative {
1724            font_sizes: lines.font_sizes.borrow().clone(),
1725            text_layouts: lines.text_layouts.clone(),
1726            text_prov,
1727            is_done: true,
1728            rvline: RVLine::new(0, 0),
1729            offset: 0,
1730            backwards,
1731            linear: true,
1732            is_first_iter: true,
1733        }
1734    }
1735}
1736impl<T: TextLayoutProvider> Iterator for VisualLinesRelative<T> {
1737    type Item = VLineInfo<()>;
1738
1739    fn next(&mut self) -> Option<Self::Item> {
1740        if self.is_done {
1741            return None;
1742        }
1743
1744        let layouts = self.text_layouts.borrow();
1745        if self.is_first_iter {
1746            // This skips the next line call on the first line.
1747            self.is_first_iter = false;
1748        } else {
1749            let v = shift_rvline(
1750                &layouts,
1751                &self.text_prov,
1752                &*self.font_sizes,
1753                self.rvline,
1754                self.backwards,
1755                self.linear,
1756            );
1757            let Some((new_rel_vline, offset)) = v else {
1758                self.is_done = true;
1759                return None;
1760            };
1761
1762            self.rvline = new_rel_vline;
1763            self.offset = offset;
1764
1765            if self.rvline.line > self.text_prov.rope_text().last_line() {
1766                self.is_done = true;
1767                return None;
1768            }
1769        }
1770
1771        let line = self.rvline.line;
1772        let line_index = self.rvline.line_index;
1773        let vline = self.rvline;
1774
1775        let start = self.offset;
1776
1777        let font_size = self.font_sizes.font_size(line);
1778        let end = end_of_rvline(&layouts, &self.text_prov, font_size, self.rvline);
1779
1780        let line_count = if let Some(text_layout) = layouts.get(font_size, line) {
1781            text_layout.line_count()
1782        } else {
1783            1
1784        };
1785        debug_assert!(
1786            start <= end,
1787            "line: {line}, line_index: {line_index}, line_count: {line_count}, vline: {vline:?}, start: {start}, end: {end}, backwards: {} text_len: {}",
1788            self.backwards,
1789            self.text_prov.text().len()
1790        );
1791        let info = VLineInfo::new(start..end, self.rvline, line_count, ());
1792
1793        Some(info)
1794    }
1795}
1796
1797// TODO: This might skip spaces at the end of lines, which we probably don't want?
1798/// Get the end offset of the visual line from the file's line and the line index.
1799fn end_of_rvline(
1800    layouts: &TextLayoutCache,
1801    text_prov: &impl TextLayoutProvider,
1802    font_size: usize,
1803    RVLine { line, line_index }: RVLine,
1804) -> usize {
1805    if line > text_prov.rope_text().last_line() {
1806        return text_prov.text().len();
1807    }
1808
1809    if let Some((_, end_col)) = layouts.get_layout_col(text_prov, font_size, line, line_index) {
1810        let end_col = text_prov.before_phantom_col(line, end_col);
1811        text_prov.rope_text().offset_of_line_col(line, end_col)
1812    } else {
1813        let rope_text = text_prov.rope_text();
1814
1815        rope_text.line_end_offset(line, true)
1816    }
1817}
1818
1819/// Shift a relative visual line forward or backwards based on the `backwards` parameter.
1820fn shift_rvline(
1821    layouts: &TextLayoutCache,
1822    text_prov: &impl TextLayoutProvider,
1823    font_sizes: &dyn LineFontSizeProvider,
1824    vline: RVLine,
1825    backwards: bool,
1826    linear: bool,
1827) -> Option<(RVLine, usize)> {
1828    if linear {
1829        let rope_text = text_prov.rope_text();
1830        debug_assert_eq!(
1831            vline.line_index, 0,
1832            "Line index should be zero if we're linearly working with lines"
1833        );
1834        if backwards {
1835            if vline.line == 0 {
1836                return None;
1837            }
1838
1839            let prev_line = vline.line - 1;
1840            let offset = rope_text.offset_of_line(prev_line);
1841            Some((RVLine::new(prev_line, 0), offset))
1842        } else {
1843            let next_line = vline.line + 1;
1844
1845            if next_line > rope_text.last_line() {
1846                return None;
1847            }
1848
1849            let offset = rope_text.offset_of_line(next_line);
1850            Some((RVLine::new(next_line, 0), offset))
1851        }
1852    } else if backwards {
1853        prev_rvline(layouts, text_prov, font_sizes, vline)
1854    } else {
1855        let font_size = font_sizes.font_size(vline.line);
1856        Some(next_rvline(layouts, text_prov, font_size, vline))
1857    }
1858}
1859
1860fn rvline_offset(
1861    layouts: &TextLayoutCache,
1862    text_prov: &impl TextLayoutProvider,
1863    font_size: usize,
1864    RVLine { line, line_index }: RVLine,
1865) -> usize {
1866    let rope_text = text_prov.rope_text();
1867    if let Some((line_col, _)) = layouts.get_layout_col(text_prov, font_size, line, line_index) {
1868        let line_col = text_prov.before_phantom_col(line, line_col);
1869
1870        rope_text.offset_of_line_col(line, line_col)
1871    } else {
1872        // There was no text layout line so this is a normal line.
1873        debug_assert_eq!(line_index, 0);
1874
1875        rope_text.offset_of_line(line)
1876    }
1877}
1878
1879/// Move to the next visual line, giving the new information.
1880///
1881/// Returns `(new rel vline, offset)`
1882fn next_rvline(
1883    layouts: &TextLayoutCache,
1884    text_prov: &impl TextLayoutProvider,
1885    font_size: usize,
1886    RVLine { line, line_index }: RVLine,
1887) -> (RVLine, usize) {
1888    let rope_text = text_prov.rope_text();
1889    if let Some(layout_line) = layouts.get(font_size, line) {
1890        if let Some((line_col, _)) = layout_line.layout_cols(text_prov, line).nth(line_index + 1) {
1891            let line_col = text_prov.before_phantom_col(line, line_col);
1892            let offset = rope_text.offset_of_line_col(line, line_col);
1893
1894            (RVLine::new(line, line_index + 1), offset)
1895        } else {
1896            // There was no next layout/vline on this buffer line.
1897            // So we can simply move to the start of the next buffer line.
1898
1899            (RVLine::new(line + 1, 0), rope_text.offset_of_line(line + 1))
1900        }
1901    } else {
1902        // There was no text layout line, so this is a normal line.
1903        debug_assert_eq!(line_index, 0);
1904
1905        (RVLine::new(line + 1, 0), rope_text.offset_of_line(line + 1))
1906    }
1907}
1908
1909/// Move to the previous visual line, giving the new information.
1910///
1911/// Returns `(new line, new line_index, offset)`
1912///
1913/// Returns `None` if the line and line index are zero and thus there is no previous visual line.
1914fn prev_rvline(
1915    layouts: &TextLayoutCache,
1916    text_prov: &impl TextLayoutProvider,
1917    font_sizes: &dyn LineFontSizeProvider,
1918    RVLine { line, line_index }: RVLine,
1919) -> Option<(RVLine, usize)> {
1920    let rope_text = text_prov.rope_text();
1921    if line_index == 0 {
1922        // Line index was zero so we must be moving back a buffer line
1923        if line == 0 {
1924            return None;
1925        }
1926
1927        let prev_line = line - 1;
1928        let font_size = font_sizes.font_size(prev_line);
1929        if let Some(layout_line) = layouts.get(font_size, prev_line) {
1930            let (i, line_col) = layout_line
1931                .start_layout_cols(text_prov, prev_line)
1932                .enumerate()
1933                .last()
1934                .unwrap_or((0, 0));
1935            let line_col = text_prov.before_phantom_col(prev_line, line_col);
1936            let offset = rope_text.offset_of_line_col(prev_line, line_col);
1937
1938            Some((RVLine::new(prev_line, i), offset))
1939        } else {
1940            // There was no text layout line, so the previous line is a normal line.
1941            let prev_line_offset = rope_text.offset_of_line(prev_line);
1942            Some((RVLine::new(prev_line, 0), prev_line_offset))
1943        }
1944    } else {
1945        // We're still on the same buffer line, so we can just move to the previous layout/vline.
1946
1947        let prev_line_index = line_index - 1;
1948        let font_size = font_sizes.font_size(line);
1949        if let Some(layout_line) = layouts.get(font_size, line) {
1950            if let Some((line_col, _)) = layout_line
1951                .layout_cols(text_prov, line)
1952                .nth(prev_line_index)
1953            {
1954                let line_col = text_prov.before_phantom_col(line, line_col);
1955                let offset = rope_text.offset_of_line_col(line, line_col);
1956
1957                Some((RVLine::new(line, prev_line_index), offset))
1958            } else {
1959                // There was no previous layout/vline on this buffer line.
1960                // So we can simply move to the end of the previous buffer line.
1961
1962                let prev_line_offset = rope_text.offset_of_line(line - 1);
1963                Some((RVLine::new(line - 1, 0), prev_line_offset))
1964            }
1965        } else {
1966            debug_assert!(
1967                false,
1968                "line_index was nonzero but there was no text layout line"
1969            );
1970            // Despite that this shouldn't happen we default to just giving the start of this
1971            // normal line
1972            let line_offset = rope_text.offset_of_line(line);
1973            Some((RVLine::new(line, 0), line_offset))
1974        }
1975    }
1976}
1977
1978#[cfg(test)]
1979mod tests {
1980    use std::{borrow::Cow, cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
1981
1982    use floem_editor_core::{
1983        buffer::rope_text::{RopeText, RopeTextRef, RopeTextVal},
1984        cursor::CursorAffinity,
1985    };
1986    use floem_reactive::Scope;
1987    use floem_renderer::text::{Attrs, AttrsList, FamilyOwned, TextLayout, Wrap};
1988    use lapce_xi_rope::Rope;
1989    use smallvec::smallvec;
1990
1991    use crate::views::editor::{
1992        layout::TextLayoutLine,
1993        phantom_text::{PhantomText, PhantomTextKind, PhantomTextLine},
1994        visual_line::{end_of_rvline, find_vline_of_line_backwards, find_vline_of_line_forwards},
1995    };
1996
1997    use super::{
1998        ConfigId, FontSizeCacheId, LineFontSizeProvider, Lines, RVLine, ResolvedWrap,
1999        TextLayoutProvider, VLine, find_vline_init_info_forward, find_vline_init_info_rv_backward,
2000    };
2001
2002    /// For most of the logic we standardize on a specific font size.
2003    const FONT_SIZE: usize = 12;
2004
2005    struct TestTextLayoutProvider<'a> {
2006        text: &'a Rope,
2007        phantom: HashMap<usize, PhantomTextLine>,
2008        font_family: Vec<FamilyOwned>,
2009        #[allow(dead_code)]
2010        wrap: Wrap,
2011    }
2012    impl<'a> TestTextLayoutProvider<'a> {
2013        fn new(text: &'a Rope, ph: HashMap<usize, PhantomTextLine>, wrap: Wrap) -> Self {
2014            Self {
2015                text,
2016                phantom: ph,
2017                // we use a specific font to make width calculations consistent between platforms.
2018                // TODO(minor): Is there a more common font that we can use?
2019                #[cfg(not(target_os = "windows"))]
2020                font_family: vec![FamilyOwned::SansSerif],
2021                #[cfg(target_os = "windows")]
2022                font_family: vec![FamilyOwned::Name("Arial".to_string())],
2023                wrap,
2024            }
2025        }
2026    }
2027    impl TextLayoutProvider for TestTextLayoutProvider<'_> {
2028        fn text(&self) -> Rope {
2029            self.text.clone()
2030        }
2031
2032        // An implementation relatively close to the actual new text layout impl but simplified.
2033        // TODO(minor): It would be nice to just use the same impl as view's
2034        fn new_text_layout(
2035            &self,
2036            line: usize,
2037            font_size: usize,
2038            wrap: ResolvedWrap,
2039        ) -> Arc<TextLayoutLine> {
2040            let rope_text = RopeTextRef::new(self.text);
2041            let line_content_original = rope_text.line_content(line);
2042
2043            // Get the line content with newline characters replaced with spaces
2044            // and the content without the newline characters
2045            let (line_content, _line_content_original) =
2046                if let Some(s) = line_content_original.strip_suffix("\r\n") {
2047                    (
2048                        format!("{s}  "),
2049                        &line_content_original[..line_content_original.len() - 2],
2050                    )
2051                } else if let Some(s) = line_content_original.strip_suffix('\n') {
2052                    (
2053                        format!("{s} ",),
2054                        &line_content_original[..line_content_original.len() - 1],
2055                    )
2056                } else {
2057                    (
2058                        line_content_original.to_string(),
2059                        &line_content_original[..],
2060                    )
2061                };
2062
2063            let phantom_text = self.phantom.get(&line).cloned().unwrap_or_default();
2064            let line_content = phantom_text.combine_with_text(&line_content);
2065
2066            // let color
2067
2068            let attrs = Attrs::new()
2069                .family(&self.font_family)
2070                .font_size(font_size as f32);
2071            let mut attrs_list = AttrsList::new(attrs.clone());
2072
2073            // We don't do line styles, since they aren't relevant
2074
2075            // Apply phantom text specific styling
2076            for (offset, size, col, phantom) in phantom_text.offset_size_iter() {
2077                let start = col + offset;
2078                let end = start + size;
2079
2080                let mut attrs = attrs.clone();
2081                if let Some(fg) = phantom.fg {
2082                    attrs = attrs.color(fg);
2083                }
2084                if let Some(phantom_font_size) = phantom.font_size {
2085                    attrs = attrs.font_size(phantom_font_size.min(font_size) as f32);
2086                }
2087                attrs_list.add_span(start..end, attrs);
2088                // if let Some(font_family) = phantom.font_family.clone() {
2089                //     layout_builder = layout_builder.range_attribute(
2090                //         start..end,
2091                //         TextAttribute::FontFamily(font_family),
2092                //     );
2093                // }
2094            }
2095
2096            let mut text_layout = TextLayout::new();
2097            text_layout.set_wrap(Wrap::Word);
2098            match wrap {
2099                // We do not have to set the wrap mode if we do not set the width
2100                ResolvedWrap::None => {}
2101                ResolvedWrap::Column(_col) => todo!(),
2102                ResolvedWrap::Width(px) => {
2103                    text_layout.set_size(px, f32::MAX);
2104                }
2105            }
2106            text_layout.set_text(&line_content, attrs_list, None);
2107
2108            // skip phantom text background styling because it doesn't shift positions
2109            // skip severity styling
2110            // skip diagnostic background styling
2111
2112            Arc::new(TextLayoutLine {
2113                extra_style: Vec::new(),
2114                text: text_layout,
2115                whitespaces: None,
2116                indent: 0.0,
2117                phantom_text: PhantomTextLine::default(),
2118            })
2119        }
2120
2121        fn before_phantom_col(&self, line: usize, col: usize) -> usize {
2122            self.phantom
2123                .get(&line)
2124                .map(|x| x.before_col(col))
2125                .unwrap_or(col)
2126        }
2127
2128        fn has_multiline_phantom(&self) -> bool {
2129            // Conservatively, yes.
2130            true
2131        }
2132    }
2133
2134    struct TestFontSize {
2135        font_size: usize,
2136    }
2137    impl LineFontSizeProvider for TestFontSize {
2138        fn font_size(&self, _line: usize) -> usize {
2139            self.font_size
2140        }
2141
2142        fn cache_id(&self) -> FontSizeCacheId {
2143            0
2144        }
2145    }
2146
2147    fn make_lines(text: &Rope, width: f32, init: bool) -> (TestTextLayoutProvider<'_>, Lines) {
2148        make_lines_ph(text, width, init, HashMap::new())
2149    }
2150
2151    fn make_lines_ph(
2152        text: &Rope,
2153        width: f32,
2154        init: bool,
2155        ph: HashMap<usize, PhantomTextLine>,
2156    ) -> (TestTextLayoutProvider<'_>, Lines) {
2157        let wrap = Wrap::Word;
2158        let r_wrap = ResolvedWrap::Width(width);
2159        let font_sizes = TestFontSize {
2160            font_size: FONT_SIZE,
2161        };
2162        let text = TestTextLayoutProvider::new(text, ph, wrap);
2163        let cx = Scope::new();
2164        let lines = Lines::new(cx, RefCell::new(Rc::new(font_sizes)));
2165        lines.set_wrap(r_wrap);
2166
2167        if init {
2168            let config_id = 0;
2169            let floem_style_id = 0;
2170            lines.init_all(0, ConfigId::new(config_id, floem_style_id), &text, true);
2171        }
2172
2173        (text, lines)
2174    }
2175
2176    fn render_breaks<'a>(text: &'a Rope, lines: &mut Lines, font_size: usize) -> Vec<Cow<'a, str>> {
2177        // TODO: line_content on ropetextref would have the lifetime reference rope_text
2178        // rather than the held &'a Rope.
2179        // I think this would require an alternate trait for those functions to avoid incorrect lifetimes. Annoying but workable.
2180        let rope_text = RopeTextRef::new(text);
2181        let mut result = Vec::new();
2182        let layouts = lines.text_layouts.borrow();
2183
2184        for line in 0..rope_text.num_lines() {
2185            if let Some(text_layout) = layouts.get(font_size, line) {
2186                for line in text_layout.text.lines() {
2187                    let layouts = line.layout_opt().unwrap();
2188                    for layout in layouts {
2189                        // Spacing
2190                        if layout.glyphs.is_empty() {
2191                            continue;
2192                        }
2193                        let start_idx = layout.glyphs[0].start;
2194                        let end_idx = layout.glyphs.last().unwrap().end;
2195                        // Hacky solution to include the ending space/newline since those get trimmed off
2196                        let line_content = line
2197                            .text()
2198                            .get(start_idx..=end_idx)
2199                            .unwrap_or(&line.text()[start_idx..end_idx]);
2200                        result.push(Cow::Owned(line_content.to_string()));
2201                    }
2202                }
2203            } else {
2204                let line_content = rope_text.line_content(line);
2205
2206                let line_content = match line_content {
2207                    Cow::Borrowed(x) => {
2208                        if let Some(x) = x.strip_suffix('\n') {
2209                            // Cow::Borrowed(x)
2210                            Cow::Owned(x.to_string())
2211                        } else {
2212                            // Cow::Borrowed(x)
2213                            Cow::Owned(x.to_string())
2214                        }
2215                    }
2216                    Cow::Owned(x) => {
2217                        if let Some(x) = x.strip_suffix('\n') {
2218                            Cow::Owned(x.to_string())
2219                        } else {
2220                            Cow::Owned(x)
2221                        }
2222                    }
2223                };
2224                result.push(line_content);
2225            }
2226        }
2227        result
2228    }
2229
2230    /// Utility fn to quickly create simple phantom text
2231    fn mph(kind: PhantomTextKind, col: usize, text: &str) -> PhantomText {
2232        PhantomText {
2233            kind,
2234            col,
2235            affinity: None,
2236            text: text.to_string(),
2237            font_size: None,
2238            fg: None,
2239            bg: None,
2240            under_line: None,
2241        }
2242    }
2243
2244    fn ffvline_info(
2245        lines: &Lines,
2246        text_prov: impl TextLayoutProvider,
2247        vline: VLine,
2248    ) -> Option<(usize, RVLine)> {
2249        find_vline_init_info_forward(lines, &text_prov, (VLine(0), 0), vline)
2250    }
2251
2252    fn fbvline_info(
2253        lines: &Lines,
2254        text_prov: impl TextLayoutProvider,
2255        vline: VLine,
2256    ) -> Option<(usize, RVLine)> {
2257        let last_vline = lines.last_vline(&text_prov);
2258        let last_rvline = lines.last_rvline(&text_prov);
2259        find_vline_init_info_rv_backward(lines, &text_prov, (last_vline, last_rvline), vline)
2260    }
2261
2262    #[test]
2263    fn find_vline_init_info_empty() {
2264        // Test empty buffer
2265        let text = Rope::from("");
2266        let (text_prov, lines) = make_lines(&text, 50.0, false);
2267
2268        assert_eq!(
2269            ffvline_info(&lines, &text_prov, VLine(0)),
2270            Some((0, RVLine::new(0, 0)))
2271        );
2272        assert_eq!(
2273            fbvline_info(&lines, &text_prov, VLine(0)),
2274            Some((0, RVLine::new(0, 0)))
2275        );
2276        assert_eq!(ffvline_info(&lines, &text_prov, VLine(1)), None);
2277        assert_eq!(fbvline_info(&lines, &text_prov, VLine(1)), None);
2278
2279        // Test empty buffer with phantom text and no wrapping
2280        let text = Rope::from("");
2281        let mut ph = HashMap::new();
2282        ph.insert(
2283            0,
2284            PhantomTextLine {
2285                text: smallvec![mph(PhantomTextKind::Completion, 0, "hello world abc")],
2286            },
2287        );
2288        let (text_prov, lines) = make_lines_ph(&text, 20.0, false, ph);
2289
2290        assert_eq!(
2291            ffvline_info(&lines, &text_prov, VLine(0)),
2292            Some((0, RVLine::new(0, 0)))
2293        );
2294        assert_eq!(
2295            fbvline_info(&lines, &text_prov, VLine(0)),
2296            Some((0, RVLine::new(0, 0)))
2297        );
2298        assert_eq!(ffvline_info(&lines, &text_prov, VLine(1)), None);
2299        assert_eq!(fbvline_info(&lines, &text_prov, VLine(1)), None);
2300
2301        // Test empty buffer with phantom text and wrapping
2302        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2303
2304        assert_eq!(
2305            ffvline_info(&lines, &text_prov, VLine(0)),
2306            Some((0, RVLine::new(0, 0)))
2307        );
2308        assert_eq!(
2309            fbvline_info(&lines, &text_prov, VLine(0)),
2310            Some((0, RVLine::new(0, 0)))
2311        );
2312        assert_eq!(
2313            ffvline_info(&lines, &text_prov, VLine(1)),
2314            Some((0, RVLine::new(0, 1)))
2315        );
2316        assert_eq!(
2317            fbvline_info(&lines, &text_prov, VLine(1)),
2318            Some((0, RVLine::new(0, 1)))
2319        );
2320        assert_eq!(
2321            ffvline_info(&lines, &text_prov, VLine(2)),
2322            Some((0, RVLine::new(0, 2)))
2323        );
2324        assert_eq!(
2325            fbvline_info(&lines, &text_prov, VLine(2)),
2326            Some((0, RVLine::new(0, 2)))
2327        );
2328        // Going outside bounds only ends up with None
2329        assert_eq!(ffvline_info(&lines, &text_prov, VLine(3)), None);
2330        assert_eq!(fbvline_info(&lines, &text_prov, VLine(3)), None);
2331        // The affinity would shift from the front/end of the phantom line
2332        // TODO: test affinity of logic behind clicking past the last vline?
2333    }
2334
2335    #[test]
2336    fn find_vline_init_info_unwrapping() {
2337        // Multiple lines with too large width for there to be any wrapping.
2338        let text = Rope::from("hello\nworld toast and jam\nthe end\nhi");
2339        let rope_text = RopeTextRef::new(&text);
2340        let (text_prov, mut lines) = make_lines(&text, 500.0, false);
2341
2342        // Assert that with no text layouts (aka no wrapping and no phantom text) the function
2343        // works
2344        for line in 0..rope_text.num_lines() {
2345            let line_offset = rope_text.offset_of_line(line);
2346
2347            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2348            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2349
2350            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2351            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2352        }
2353
2354        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2355
2356        assert_eq!(
2357            render_breaks(&text, &mut lines, FONT_SIZE),
2358            ["hello", "world toast and jam", "the end", "hi"]
2359        );
2360
2361        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2362
2363        // 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.
2364        for line in 0..rope_text.num_lines() {
2365            let line_offset = rope_text.offset_of_line(line);
2366
2367            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2368            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2369            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2370            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2371        }
2372
2373        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2374        assert_eq!(fbvline_info(&lines, &text_prov, VLine(20)), None);
2375
2376        assert_eq!(
2377            render_breaks(&text, &mut lines, FONT_SIZE),
2378            ["hello ", "world toast and jam ", "the end ", "hi"]
2379        );
2380    }
2381
2382    #[test]
2383    fn find_vline_init_info_phantom_unwrapping() {
2384        let text = Rope::from("hello\nworld toast and jam\nthe end\nhi");
2385        let rope_text = RopeTextRef::new(&text);
2386
2387        // Multiple lines with too large width for there to be any wrapping and phantom text
2388        let mut ph = HashMap::new();
2389        ph.insert(
2390            0,
2391            PhantomTextLine {
2392                text: smallvec![mph(PhantomTextKind::Completion, 0, "greet world")],
2393            },
2394        );
2395
2396        let (text_prov, lines) = make_lines_ph(&text, 500.0, false, ph);
2397
2398        // With no text layouts, phantom text isn't initialized so it has no affect.
2399        for line in 0..rope_text.num_lines() {
2400            let line_offset = rope_text.offset_of_line(line);
2401
2402            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2403            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2404
2405            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2406            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2407        }
2408
2409        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2410
2411        // With text layouts, the phantom text is applied.
2412        // But with a single line of phantom text, it doesn't affect the offsets.
2413        for line in 0..rope_text.num_lines() {
2414            let line_offset = rope_text.offset_of_line(line);
2415
2416            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2417            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2418
2419            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2420            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2421        }
2422
2423        // Multiple lines with too large width and a phantom text that takes up multiple lines.
2424        let mut ph = HashMap::new();
2425        ph.insert(
2426            0,
2427            PhantomTextLine {
2428                text: smallvec![mph(PhantomTextKind::Completion, 0, "greet\nworld"),],
2429            },
2430        );
2431
2432        let (text_prov, mut lines) = make_lines_ph(&text, 500.0, false, ph);
2433
2434        // With no text layouts, phantom text isn't initialized so it has no affect.
2435        for line in 0..rope_text.num_lines() {
2436            let line_offset = rope_text.offset_of_line(line);
2437
2438            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2439            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2440
2441            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2442            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2443        }
2444
2445        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2446
2447        assert_eq!(
2448            render_breaks(&text, &mut lines, FONT_SIZE),
2449            [
2450                "greet",
2451                "worldhello ",
2452                "world toast and jam ",
2453                "the end ",
2454                "hi"
2455            ]
2456        );
2457
2458        // With text layouts, the phantom text is applied.
2459        // With a phantom text that takes up multiple lines, it does not affect the offsets
2460        // but it does affect the valid visual lines.
2461        let info = ffvline_info(&lines, &text_prov, VLine(0));
2462        assert_eq!(info, Some((0, RVLine::new(0, 0))));
2463        let info = fbvline_info(&lines, &text_prov, VLine(0));
2464        assert_eq!(info, Some((0, RVLine::new(0, 0))));
2465        let info = ffvline_info(&lines, &text_prov, VLine(1));
2466        assert_eq!(info, Some((0, RVLine::new(0, 1))));
2467        let info = fbvline_info(&lines, &text_prov, VLine(1));
2468        assert_eq!(info, Some((0, RVLine::new(0, 1))));
2469
2470        for line in 2..rope_text.num_lines() {
2471            let line_offset = rope_text.offset_of_line(line - 1);
2472
2473            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2474            assert_eq!(
2475                info,
2476                (line_offset, RVLine::new(line - 1, 0)),
2477                "vline {line}"
2478            );
2479            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2480            assert_eq!(
2481                info,
2482                (line_offset, RVLine::new(line - 1, 0)),
2483                "vline {line}"
2484            );
2485        }
2486
2487        // Then there's one extra vline due to the phantom text wrapping
2488        let line_offset = rope_text.offset_of_line(rope_text.last_line());
2489
2490        let info = ffvline_info(&lines, &text_prov, VLine(rope_text.last_line() + 1));
2491        assert_eq!(
2492            info,
2493            Some((line_offset, RVLine::new(rope_text.last_line(), 0))),
2494            "line {}",
2495            rope_text.last_line() + 1,
2496        );
2497        let info = fbvline_info(&lines, &text_prov, VLine(rope_text.last_line() + 1));
2498        assert_eq!(
2499            info,
2500            Some((line_offset, RVLine::new(rope_text.last_line(), 0))),
2501            "line {}",
2502            rope_text.last_line() + 1,
2503        );
2504
2505        // Multiple lines with too large width and a phantom text that takes up multiple lines.
2506        // But the phantom text is not at the start of the first line.
2507        let mut ph = HashMap::new();
2508        ph.insert(
2509            2, // "the end"
2510            PhantomTextLine {
2511                text: smallvec![mph(PhantomTextKind::Completion, 3, "greet\nworld"),],
2512            },
2513        );
2514
2515        let (text_prov, mut lines) = make_lines_ph(&text, 500.0, false, ph);
2516
2517        // With no text layouts, phantom text isn't initialized so it has no affect.
2518        for line in 0..rope_text.num_lines() {
2519            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2520
2521            let line_offset = rope_text.offset_of_line(line);
2522
2523            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2524        }
2525
2526        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2527
2528        assert_eq!(
2529            render_breaks(&text, &mut lines, FONT_SIZE),
2530            [
2531                "hello ",
2532                "world toast and jam ",
2533                "thegreet",
2534                "world end ",
2535                "hi"
2536            ]
2537        );
2538
2539        // With text layouts, the phantom text is applied.
2540        // With a phantom text that takes up multiple lines, it does not affect the offsets
2541        // but it does affect the valid visual lines.
2542        for line in 0..3 {
2543            let line_offset = rope_text.offset_of_line(line);
2544
2545            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2546            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2547
2548            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2549            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "vline {line}");
2550        }
2551
2552        // ' end'
2553        let info = ffvline_info(&lines, &text_prov, VLine(3));
2554        assert_eq!(info, Some((29, RVLine::new(2, 1))));
2555        let info = fbvline_info(&lines, &text_prov, VLine(3));
2556        assert_eq!(info, Some((29, RVLine::new(2, 1))));
2557
2558        let info = ffvline_info(&lines, &text_prov, VLine(4));
2559        assert_eq!(info, Some((34, RVLine::new(3, 0))));
2560        let info = fbvline_info(&lines, &text_prov, VLine(4));
2561        assert_eq!(info, Some((34, RVLine::new(3, 0))));
2562    }
2563
2564    #[test]
2565    fn find_vline_init_info_basic_wrapping() {
2566        // Tests with more mixes of text layout lines and uninitialized lines
2567
2568        // Multiple lines with a small enough width for there to be a bunch of wrapping
2569        let text = Rope::from("hello\nworld toast and jam\nthe end\nhi");
2570        let rope_text = RopeTextRef::new(&text);
2571        let (text_prov, mut lines) = make_lines(&text, 30.0, false);
2572
2573        // Assert that with no text layouts (aka no wrapping and no phantom text) the function
2574        // works
2575        for line in 0..rope_text.num_lines() {
2576            let line_offset = rope_text.offset_of_line(line);
2577
2578            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2579            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "line {line}");
2580
2581            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2582            assert_eq!(info, (line_offset, RVLine::new(line, 0)), "line {line}");
2583        }
2584
2585        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2586        assert_eq!(fbvline_info(&lines, &text_prov, VLine(20)), None);
2587
2588        assert_eq!(
2589            render_breaks(&text, &mut lines, FONT_SIZE),
2590            ["hello", "world toast and jam", "the end", "hi"]
2591        );
2592
2593        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2594
2595        {
2596            let layouts = lines.text_layouts.borrow();
2597
2598            assert!(layouts.get(FONT_SIZE, 0).is_some());
2599            assert!(layouts.get(FONT_SIZE, 1).is_some());
2600            assert!(layouts.get(FONT_SIZE, 2).is_some());
2601            assert!(layouts.get(FONT_SIZE, 3).is_some());
2602            assert!(layouts.get(FONT_SIZE, 4).is_none());
2603        }
2604
2605        // start offset, start buffer line, layout line index)
2606        let line_data = [
2607            (0, 0, 0),
2608            (6, 1, 0),
2609            (12, 1, 1),
2610            (18, 1, 2),
2611            (22, 1, 3),
2612            (26, 2, 0),
2613            (30, 2, 1),
2614            (34, 3, 0),
2615        ];
2616        assert_eq!(lines.last_vline(&text_prov), VLine(7));
2617        assert_eq!(lines.last_rvline(&text_prov), RVLine::new(3, 0));
2618        #[allow(clippy::needless_range_loop)]
2619        for line in 0..8 {
2620            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2621            assert_eq!(
2622                (info.0, info.1.line, info.1.line_index),
2623                line_data[line],
2624                "vline {line}"
2625            );
2626            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2627            assert_eq!(
2628                (info.0, info.1.line, info.1.line_index),
2629                line_data[line],
2630                "vline {line}"
2631            );
2632        }
2633
2634        // Directly out of bounds
2635        assert_eq!(ffvline_info(&lines, &text_prov, VLine(9)), None,);
2636        assert_eq!(fbvline_info(&lines, &text_prov, VLine(9)), None,);
2637
2638        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2639        assert_eq!(fbvline_info(&lines, &text_prov, VLine(20)), None);
2640
2641        assert_eq!(
2642            render_breaks(&text, &mut lines, FONT_SIZE),
2643            [
2644                "hello ", "world ", "toast ", "and ", "jam ", "the ", "end ", "hi"
2645            ]
2646        );
2647
2648        let vline_line_data = [0, 1, 5, 7];
2649
2650        let rope = text_prov.rope_text();
2651        let last_start_vline =
2652            VLine(lines.last_vline(&text_prov).get() - lines.last_rvline(&text_prov).line_index);
2653        #[allow(clippy::needless_range_loop)]
2654        for line in 0..4 {
2655            let vline = VLine(vline_line_data[line]);
2656            assert_eq!(
2657                find_vline_of_line_forwards(&lines, Default::default(), line),
2658                Some(vline)
2659            );
2660            assert_eq!(
2661                find_vline_of_line_backwards(&lines, (last_start_vline, rope.last_line()), line),
2662                Some(vline),
2663                "line: {line}"
2664            );
2665        }
2666
2667        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
2668        let (text_prov, mut lines) = make_lines(&text, 2., true);
2669
2670        assert_eq!(
2671            render_breaks(&text, &mut lines, FONT_SIZE),
2672            [
2673                "aaaa ", "bb ", "bb ", "cc ", "cc ", "dddd ", "eeee ", "ff ", "ff ", "gggg"
2674            ]
2675        );
2676
2677        // (start offset, start buffer line, layout line index)
2678        let line_data = [
2679            (0, 0, 0),
2680            (5, 1, 0),
2681            (8, 1, 1),
2682            (11, 1, 2),
2683            (14, 2, 0),
2684            (17, 2, 1),
2685            (22, 2, 2),
2686            (27, 2, 3),
2687            (30, 3, 0),
2688            (33, 3, 1),
2689        ];
2690        #[allow(clippy::needless_range_loop)]
2691        for vline in 0..10 {
2692            let info = ffvline_info(&lines, &text_prov, VLine(vline)).unwrap();
2693            assert_eq!(
2694                (info.0, info.1.line, info.1.line_index),
2695                line_data[vline],
2696                "vline {vline}"
2697            );
2698            let info = fbvline_info(&lines, &text_prov, VLine(vline)).unwrap();
2699            assert_eq!(
2700                (info.0, info.1.line, info.1.line_index),
2701                line_data[vline],
2702                "vline {vline}"
2703            );
2704        }
2705
2706        let vline_line_data = [0, 1, 4, 8];
2707
2708        let rope = text_prov.rope_text();
2709        let last_start_vline =
2710            VLine(lines.last_vline(&text_prov).get() - lines.last_rvline(&text_prov).line_index);
2711        #[allow(clippy::needless_range_loop)]
2712        for line in 0..4 {
2713            let vline = VLine(vline_line_data[line]);
2714            assert_eq!(
2715                find_vline_of_line_forwards(&lines, Default::default(), line),
2716                Some(vline)
2717            );
2718            assert_eq!(
2719                find_vline_of_line_backwards(&lines, (last_start_vline, rope.last_line()), line),
2720                Some(vline),
2721                "line: {line}"
2722            );
2723        }
2724
2725        // TODO: tests that have less line wrapping
2726    }
2727
2728    #[test]
2729    fn find_vline_init_info_basic_wrapping_phantom() {
2730        // Single line Phantom text at the very start
2731        let text = Rope::from("hello\nworld toast and jam\nthe end\nhi");
2732        let rope_text = RopeTextRef::new(&text);
2733
2734        let mut ph = HashMap::new();
2735        ph.insert(
2736            0,
2737            PhantomTextLine {
2738                text: smallvec![mph(PhantomTextKind::Completion, 0, "greet world")],
2739            },
2740        );
2741
2742        let (text_prov, mut lines) = make_lines_ph(&text, 30.0, false, ph);
2743
2744        // Assert that with no text layouts there is no change in behavior from having no phantom
2745        // text
2746        for line in 0..rope_text.num_lines() {
2747            let line_offset = rope_text.offset_of_line(line);
2748
2749            let info = ffvline_info(&lines, &text_prov, VLine(line));
2750            assert_eq!(
2751                info,
2752                Some((line_offset, RVLine::new(line, 0))),
2753                "line {line}"
2754            );
2755
2756            let info = fbvline_info(&lines, &text_prov, VLine(line));
2757            assert_eq!(
2758                info,
2759                Some((line_offset, RVLine::new(line, 0))),
2760                "line {line}"
2761            );
2762        }
2763
2764        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2765        assert_eq!(fbvline_info(&lines, &text_prov, VLine(20)), None);
2766
2767        assert_eq!(
2768            render_breaks(&text, &mut lines, FONT_SIZE),
2769            ["hello", "world toast and jam", "the end", "hi"]
2770        );
2771
2772        lines.init_all(0, ConfigId::new(0, 0), &text_prov, true);
2773
2774        {
2775            let layouts = lines.text_layouts.borrow();
2776
2777            assert!(layouts.get(FONT_SIZE, 0).is_some());
2778            assert!(layouts.get(FONT_SIZE, 1).is_some());
2779            assert!(layouts.get(FONT_SIZE, 2).is_some());
2780            assert!(layouts.get(FONT_SIZE, 3).is_some());
2781            assert!(layouts.get(FONT_SIZE, 4).is_none());
2782        }
2783
2784        // start offset, start buffer line, layout line index)
2785        let line_data = [
2786            (0, 0, 0),
2787            (0, 0, 1),
2788            (6, 1, 0),
2789            (12, 1, 1),
2790            (18, 1, 2),
2791            (22, 1, 3),
2792            (26, 2, 0),
2793            (30, 2, 1),
2794            (34, 3, 0),
2795        ];
2796
2797        #[allow(clippy::needless_range_loop)]
2798        for line in 0..9 {
2799            let info = ffvline_info(&lines, &text_prov, VLine(line)).unwrap();
2800            assert_eq!(
2801                (info.0, info.1.line, info.1.line_index),
2802                line_data[line],
2803                "vline {line}"
2804            );
2805
2806            let info = fbvline_info(&lines, &text_prov, VLine(line)).unwrap();
2807            assert_eq!(
2808                (info.0, info.1.line, info.1.line_index),
2809                line_data[line],
2810                "vline {line}"
2811            );
2812        }
2813
2814        // Directly out of bounds
2815        assert_eq!(ffvline_info(&lines, &text_prov, VLine(9)), None);
2816        assert_eq!(fbvline_info(&lines, &text_prov, VLine(9)), None);
2817
2818        assert_eq!(ffvline_info(&lines, &text_prov, VLine(20)), None);
2819        assert_eq!(fbvline_info(&lines, &text_prov, VLine(20)), None);
2820
2821        // TODO: Currently the way we join phantom text and how cosmic wraps lines,
2822        // the phantom text will be joined with whatever the word next to it is - if there is no
2823        // spaces. It might be desirable to always separate them to let it wrap independently.
2824        // An easy way to do this is to always include a space, and then manually cut the glyph
2825        // margin in the text layout.
2826        assert_eq!(
2827            render_breaks(&text, &mut lines, FONT_SIZE),
2828            [
2829                "greet ",
2830                "worldhello ",
2831                "world ",
2832                "toast ",
2833                "and ",
2834                "jam ",
2835                "the ",
2836                "end ",
2837                "hi"
2838            ]
2839        );
2840
2841        // TODO: multiline phantom text in the middle
2842        // TODO: test at the end
2843    }
2844
2845    #[test]
2846    fn num_vlines() {
2847        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
2848        let (text_prov, lines) = make_lines(&text, 2., true);
2849        assert_eq!(lines.num_vlines(&text_prov), 10);
2850
2851        // With phantom text
2852        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
2853        let mut ph = HashMap::new();
2854        ph.insert(
2855            0,
2856            PhantomTextLine {
2857                text: smallvec![mph(PhantomTextKind::Completion, 0, "greet\nworld")],
2858            },
2859        );
2860
2861        let (text_prov, lines) = make_lines_ph(&text, 2., true, ph);
2862
2863        // Only one increase because the second line of the phantom text is directly attached to
2864        // the word at the start of the next line.
2865        assert_eq!(lines.num_vlines(&text_prov), 11);
2866    }
2867
2868    #[test]
2869    fn offset_to_line() {
2870        let text = "a b c d ".into();
2871        let (text_prov, lines) = make_lines(&text, 1., true);
2872        assert_eq!(lines.num_vlines(&text_prov), 4);
2873
2874        let vlines = [0, 0, 1, 1, 2, 2, 3, 3];
2875        for (i, v) in vlines.iter().enumerate() {
2876            assert_eq!(
2877                lines.vline_of_offset(&text_prov, i, CursorAffinity::Forward),
2878                VLine(*v),
2879                "offset: {i}"
2880            );
2881        }
2882
2883        assert_eq!(lines.offset_of_vline(&text_prov, VLine(0)), 0);
2884        assert_eq!(lines.offset_of_vline(&text_prov, VLine(1)), 2);
2885        assert_eq!(lines.offset_of_vline(&text_prov, VLine(2)), 4);
2886        assert_eq!(lines.offset_of_vline(&text_prov, VLine(3)), 6);
2887        assert_eq!(lines.offset_of_vline(&text_prov, VLine(10)), 8);
2888
2889        for offset in 0..text.len() {
2890            let line = lines.vline_of_offset(&text_prov, offset, CursorAffinity::Forward);
2891            let line_offset = lines.offset_of_vline(&text_prov, line);
2892            assert!(
2893                line_offset <= offset,
2894                "{line_offset} <= {offset} L{line:?} O{offset}"
2895            );
2896        }
2897
2898        let text = "blah\n\n\nhi\na b c d e".into();
2899        let (text_prov, lines) = make_lines(&text, 12.0 * 3.0, true);
2900        let vlines = [0, 0, 0, 0, 0];
2901        for (i, v) in vlines.iter().enumerate() {
2902            assert_eq!(
2903                lines.vline_of_offset(&text_prov, i, CursorAffinity::Forward),
2904                VLine(*v),
2905                "offset: {i}"
2906            );
2907        }
2908        assert_eq!(
2909            lines
2910                .vline_of_offset(&text_prov, 4, CursorAffinity::Backward)
2911                .get(),
2912            0
2913        );
2914        // Test that cursor affinity has no effect for hard line breaks
2915        assert_eq!(
2916            lines
2917                .vline_of_offset(&text_prov, 5, CursorAffinity::Forward)
2918                .get(),
2919            1
2920        );
2921        assert_eq!(
2922            lines
2923                .vline_of_offset(&text_prov, 5, CursorAffinity::Backward)
2924                .get(),
2925            1
2926        );
2927        // starts at 'd'. Tests that cursor affinity works for soft line breaks
2928        assert_eq!(
2929            lines
2930                .vline_of_offset(&text_prov, 16, CursorAffinity::Forward)
2931                .get(),
2932            5
2933        );
2934        assert_eq!(
2935            lines
2936                .vline_of_offset(&text_prov, 16, CursorAffinity::Backward)
2937                .get(),
2938            4
2939        );
2940
2941        assert_eq!(
2942            lines.vline_of_offset(&text_prov, 20, CursorAffinity::Forward),
2943            lines.last_vline(&text_prov)
2944        );
2945
2946        let text = "a\nb\nc\n".into();
2947        let (text_prov, lines) = make_lines(&text, 1., true);
2948        assert_eq!(lines.num_vlines(&text_prov), 4);
2949
2950        // let vlines = [(0, 0), (0, 0), (1, 1), (1, 1), (2, 2), (2, 2), (3, 3)];
2951        let vlines = [0, 0, 1, 1, 2, 2, 3, 3];
2952        for (i, v) in vlines.iter().enumerate() {
2953            assert_eq!(
2954                lines.vline_of_offset(&text_prov, i, CursorAffinity::Forward),
2955                VLine(*v),
2956                "offset: {i}"
2957            );
2958            assert_eq!(
2959                lines.vline_of_offset(&text_prov, i, CursorAffinity::Backward),
2960                VLine(*v),
2961                "offset: {i}"
2962            );
2963        }
2964
2965        let text =
2966            Rope::from("asdf\nposition: Some(EditorPosition::Offset(self.offset))\nasdf\nasdf");
2967        let (text_prov, mut lines) = make_lines(&text, 1., true);
2968        println!("Breaks: {:?}", render_breaks(&text, &mut lines, FONT_SIZE));
2969
2970        let rvline = lines.rvline_of_offset(&text_prov, 3, CursorAffinity::Backward);
2971        assert_eq!(rvline, RVLine::new(0, 0));
2972        let rvline_info = lines
2973            .iter_rvlines(&text_prov, false, rvline)
2974            .next()
2975            .unwrap();
2976        assert_eq!(rvline_info.rvline, rvline);
2977        let offset = lines.offset_of_rvline(&text_prov, rvline);
2978        assert_eq!(offset, 0);
2979        assert_eq!(
2980            lines.vline_of_offset(&text_prov, offset, CursorAffinity::Backward),
2981            VLine(0)
2982        );
2983        assert_eq!(lines.vline_of_rvline(&text_prov, rvline), VLine(0));
2984
2985        let rvline = lines.rvline_of_offset(&text_prov, 7, CursorAffinity::Backward);
2986        assert_eq!(rvline, RVLine::new(1, 0));
2987        let rvline_info = lines
2988            .iter_rvlines(&text_prov, false, rvline)
2989            .next()
2990            .unwrap();
2991        assert_eq!(rvline_info.rvline, rvline);
2992        let offset = lines.offset_of_rvline(&text_prov, rvline);
2993        assert_eq!(offset, 5);
2994        assert_eq!(
2995            lines.vline_of_offset(&text_prov, offset, CursorAffinity::Backward),
2996            VLine(1)
2997        );
2998        assert_eq!(lines.vline_of_rvline(&text_prov, rvline), VLine(1));
2999
3000        let rvline = lines.rvline_of_offset(&text_prov, 17, CursorAffinity::Backward);
3001        assert_eq!(rvline, RVLine::new(1, 1));
3002        let rvline_info = lines
3003            .iter_rvlines(&text_prov, false, rvline)
3004            .next()
3005            .unwrap();
3006        assert_eq!(rvline_info.rvline, rvline);
3007        let offset = lines.offset_of_rvline(&text_prov, rvline);
3008        assert_eq!(offset, 15);
3009        assert_eq!(
3010            lines.vline_of_offset(&text_prov, offset, CursorAffinity::Backward),
3011            VLine(1)
3012        );
3013        assert_eq!(
3014            lines.vline_of_offset(&text_prov, offset, CursorAffinity::Forward),
3015            VLine(2)
3016        );
3017        assert_eq!(lines.vline_of_rvline(&text_prov, rvline), VLine(2));
3018    }
3019
3020    #[test]
3021    fn offset_to_line_phantom() {
3022        let text = "a b c d ".into();
3023        let mut ph = HashMap::new();
3024        ph.insert(
3025            0,
3026            PhantomTextLine {
3027                text: smallvec![mph(PhantomTextKind::Completion, 1, "hi")],
3028            },
3029        );
3030
3031        let (text_prov, mut lines) = make_lines_ph(&text, 1., true, ph);
3032
3033        // The 'hi' is joined with the 'a' so it's not wrapped to a separate line
3034        assert_eq!(lines.num_vlines(&text_prov), 4);
3035
3036        assert_eq!(
3037            render_breaks(&text, &mut lines, FONT_SIZE),
3038            ["ahi ", "b ", "c ", "d "]
3039        );
3040
3041        let vlines = [0, 0, 1, 1, 2, 2, 3, 3];
3042        // Unchanged. The phantom text has no effect in the position. It doesn't shift a line with
3043        // the affinity due to its position and it isn't multiline.
3044        for (i, v) in vlines.iter().enumerate() {
3045            assert_eq!(
3046                lines.vline_of_offset(&text_prov, i, CursorAffinity::Forward),
3047                VLine(*v),
3048                "offset: {i}"
3049            );
3050        }
3051
3052        assert_eq!(lines.offset_of_vline(&text_prov, VLine(0)), 0);
3053        assert_eq!(lines.offset_of_vline(&text_prov, VLine(1)), 2);
3054        assert_eq!(lines.offset_of_vline(&text_prov, VLine(2)), 4);
3055        assert_eq!(lines.offset_of_vline(&text_prov, VLine(3)), 6);
3056        assert_eq!(lines.offset_of_vline(&text_prov, VLine(10)), 8);
3057
3058        for offset in 0..text.len() {
3059            let line = lines.vline_of_offset(&text_prov, offset, CursorAffinity::Forward);
3060            let line_offset = lines.offset_of_vline(&text_prov, line);
3061            assert!(
3062                line_offset <= offset,
3063                "{line_offset} <= {offset} L{line:?} O{offset}"
3064            );
3065        }
3066
3067        // Same as above but with a slightly shifted to make the affinity change the resulting vline
3068        let mut ph = HashMap::new();
3069        ph.insert(
3070            0,
3071            PhantomTextLine {
3072                text: smallvec![mph(PhantomTextKind::Completion, 2, "hi")],
3073            },
3074        );
3075
3076        let (text_prov, mut lines) = make_lines_ph(&text, 1., true, ph);
3077
3078        // The 'hi' is joined with the 'a' so it's not wrapped to a separate line
3079        assert_eq!(lines.num_vlines(&text_prov), 4);
3080
3081        // TODO: Should this really be forward rendered?
3082        assert_eq!(
3083            render_breaks(&text, &mut lines, FONT_SIZE),
3084            ["a ", "hib ", "c ", "d "]
3085        );
3086
3087        for (i, v) in vlines.iter().enumerate() {
3088            assert_eq!(
3089                lines.vline_of_offset(&text_prov, i, CursorAffinity::Forward),
3090                VLine(*v),
3091                "offset: {i}"
3092            );
3093        }
3094        assert_eq!(
3095            lines.vline_of_offset(&text_prov, 2, CursorAffinity::Backward),
3096            VLine(0)
3097        );
3098
3099        assert_eq!(lines.offset_of_vline(&text_prov, VLine(0)), 0);
3100        assert_eq!(lines.offset_of_vline(&text_prov, VLine(1)), 2);
3101        assert_eq!(lines.offset_of_vline(&text_prov, VLine(2)), 4);
3102        assert_eq!(lines.offset_of_vline(&text_prov, VLine(3)), 6);
3103        assert_eq!(lines.offset_of_vline(&text_prov, VLine(10)), 8);
3104
3105        for offset in 0..text.len() {
3106            let line = lines.vline_of_offset(&text_prov, offset, CursorAffinity::Forward);
3107            let line_offset = lines.offset_of_vline(&text_prov, line);
3108            assert!(
3109                line_offset <= offset,
3110                "{line_offset} <= {offset} L{line:?} O{offset}"
3111            );
3112        }
3113    }
3114
3115    #[test]
3116    fn iter_lines() {
3117        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
3118        let (text_prov, lines) = make_lines(&text, 2., true);
3119        let r: Vec<_> = lines
3120            .iter_vlines(&text_prov, false, VLine(0))
3121            .take(2)
3122            .map(|l| text.slice_to_cow(l.interval))
3123            .collect();
3124        assert_eq!(r, vec!["aaaa", "bb "]);
3125
3126        let r: Vec<_> = lines
3127            .iter_vlines(&text_prov, false, VLine(1))
3128            .take(2)
3129            .map(|l| text.slice_to_cow(l.interval))
3130            .collect();
3131        assert_eq!(r, vec!["bb ", "bb "]);
3132
3133        let v = lines.get_init_text_layout(0, ConfigId::new(0, 0), &text_prov, 2, true);
3134        let v = v.layout_cols(&text_prov, 2).collect::<Vec<_>>();
3135        assert_eq!(v, [(0, 3), (3, 8), (8, 13), (13, 15)]);
3136        let r: Vec<_> = lines
3137            .iter_vlines(&text_prov, false, VLine(3))
3138            .take(3)
3139            .map(|l| text.slice_to_cow(l.interval))
3140            .collect();
3141        assert_eq!(r, vec!["cc", "cc ", "dddd "]);
3142
3143        let mut r: Vec<_> = lines.iter_vlines(&text_prov, false, VLine(0)).collect();
3144        r.reverse();
3145        let r1: Vec<_> = lines
3146            .iter_vlines(&text_prov, true, lines.last_vline(&text_prov))
3147            .collect();
3148        assert_eq!(r, r1);
3149
3150        let rel1: Vec<_> = lines
3151            .iter_rvlines(&text_prov, false, RVLine::new(0, 0))
3152            .map(|i| i.rvline)
3153            .collect();
3154        r.reverse(); // revert back
3155        assert!(r.iter().map(|i| i.rvline).eq(rel1));
3156
3157        // Empty initialized
3158        let text: Rope = "".into();
3159        let (text_prov, lines) = make_lines(&text, 2., true);
3160        let r: Vec<_> = lines
3161            .iter_vlines(&text_prov, false, VLine(0))
3162            .map(|l| text.slice_to_cow(l.interval))
3163            .collect();
3164        assert_eq!(r, vec![""]);
3165        // Empty initialized - Out of bounds
3166        let r: Vec<_> = lines
3167            .iter_vlines(&text_prov, false, VLine(1))
3168            .map(|l| text.slice_to_cow(l.interval))
3169            .collect();
3170        assert_eq!(r, Vec::<&str>::new());
3171        let r: Vec<_> = lines
3172            .iter_vlines(&text_prov, false, VLine(2))
3173            .map(|l| text.slice_to_cow(l.interval))
3174            .collect();
3175        assert_eq!(r, Vec::<&str>::new());
3176
3177        let mut r: Vec<_> = lines.iter_vlines(&text_prov, false, VLine(0)).collect();
3178        r.reverse();
3179        let r1: Vec<_> = lines
3180            .iter_vlines(&text_prov, true, lines.last_vline(&text_prov))
3181            .collect();
3182        assert_eq!(r, r1);
3183
3184        let rel1: Vec<_> = lines
3185            .iter_rvlines(&text_prov, false, RVLine::new(0, 0))
3186            .map(|i| i.rvline)
3187            .collect();
3188        r.reverse(); // revert back
3189        assert!(r.iter().map(|i| i.rvline).eq(rel1));
3190
3191        // Empty uninitialized
3192        let text: Rope = "".into();
3193        let (text_prov, lines) = make_lines(&text, 2., false);
3194        let r: Vec<_> = lines
3195            .iter_vlines(&text_prov, false, VLine(0))
3196            .map(|l| text.slice_to_cow(l.interval))
3197            .collect();
3198        assert_eq!(r, vec![""]);
3199        let r: Vec<_> = lines
3200            .iter_vlines(&text_prov, false, VLine(1))
3201            .map(|l| text.slice_to_cow(l.interval))
3202            .collect();
3203        assert_eq!(r, Vec::<&str>::new());
3204        let r: Vec<_> = lines
3205            .iter_vlines(&text_prov, false, VLine(2))
3206            .map(|l| text.slice_to_cow(l.interval))
3207            .collect();
3208        assert_eq!(r, Vec::<&str>::new());
3209
3210        let mut r: Vec<_> = lines.iter_vlines(&text_prov, false, VLine(0)).collect();
3211        r.reverse();
3212        let r1: Vec<_> = lines
3213            .iter_vlines(&text_prov, true, lines.last_vline(&text_prov))
3214            .collect();
3215        assert_eq!(r, r1);
3216
3217        let rel1: Vec<_> = lines
3218            .iter_rvlines(&text_prov, false, RVLine::new(0, 0))
3219            .map(|i| i.rvline)
3220            .collect();
3221        r.reverse(); // revert back
3222        assert!(r.iter().map(|i| i.rvline).eq(rel1));
3223
3224        // TODO: clean up the above tests with some helper function. Very noisy at the moment.
3225        // TODO: phantom text iter lines tests?
3226    }
3227
3228    // TODO(minor): Deduplicate the test code between this and iter_lines
3229    // We're just testing whether it has equivalent behavior to iter lines (when lines are
3230    // initialized)
3231    #[test]
3232    fn init_iter_vlines() {
3233        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
3234        let (text_prov, lines) = make_lines(&text, 2., false);
3235        let r: Vec<_> = lines
3236            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(0), true)
3237            .take(2)
3238            .map(|l| text.slice_to_cow(l.interval))
3239            .collect();
3240        assert_eq!(r, vec!["aaaa", "bb "]);
3241
3242        let r: Vec<_> = lines
3243            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(1), true)
3244            .take(2)
3245            .map(|l| text.slice_to_cow(l.interval))
3246            .collect();
3247        assert_eq!(r, vec!["bb ", "bb "]);
3248
3249        let r: Vec<_> = lines
3250            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(3), true)
3251            .take(3)
3252            .map(|l| text.slice_to_cow(l.interval))
3253            .collect();
3254        assert_eq!(r, vec!["cc", "cc ", "dddd "]);
3255
3256        // Empty initialized
3257        let text: Rope = "".into();
3258        let (text_prov, lines) = make_lines(&text, 2., false);
3259        let r: Vec<_> = lines
3260            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(0), true)
3261            .map(|l| text.slice_to_cow(l.interval))
3262            .collect();
3263        assert_eq!(r, vec![""]);
3264        let r: Vec<_> = lines
3265            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(1), true)
3266            .map(|l| text.slice_to_cow(l.interval))
3267            .collect();
3268        assert_eq!(r, Vec::<&str>::new());
3269        let r: Vec<_> = lines
3270            .iter_vlines_init(&text_prov, 0, ConfigId::new(0, 0), VLine(2), true)
3271            .map(|l| text.slice_to_cow(l.interval))
3272            .collect();
3273        assert_eq!(r, Vec::<&str>::new());
3274    }
3275
3276    #[test]
3277    fn line_numbers() {
3278        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
3279        let (text_prov, lines) = make_lines(&text, 12.0 * 2.0, true);
3280        let get_nums = |start_vline: usize| {
3281            lines
3282                .iter_vlines(&text_prov, false, VLine(start_vline))
3283                .map(|l| {
3284                    (
3285                        l.rvline.line,
3286                        l.vline.get(),
3287                        l.is_first(),
3288                        text.slice_to_cow(l.interval),
3289                    )
3290                })
3291                .collect::<Vec<_>>()
3292        };
3293        // (line, vline, is_first, text)
3294        let x = vec![
3295            (0, 0, true, "aaaa".into()),
3296            (1, 1, true, "bb ".into()),
3297            (1, 2, false, "bb ".into()),
3298            (1, 3, false, "cc".into()),
3299            (2, 4, true, "cc ".into()),
3300            (2, 5, false, "dddd ".into()),
3301            (2, 6, false, "eeee ".into()),
3302            (2, 7, false, "ff".into()),
3303            (3, 8, true, "ff ".into()),
3304            (3, 9, false, "gggg".into()),
3305        ];
3306
3307        // This ensures that there's no inconsistencies between starting at a specific index
3308        // vs starting at zero and iterating to that index.
3309        for i in 0..x.len() {
3310            let nums = get_nums(i);
3311            println!("i: {i}, #nums: {}, #&x[i..]: {}", nums.len(), x[i..].len());
3312            assert_eq!(nums, &x[i..], "failed at #{i}");
3313        }
3314
3315        // TODO: test this without any wrapping
3316    }
3317
3318    #[test]
3319    fn last_col() {
3320        let text: Rope = Rope::from("conf = Config::default();");
3321        let (text_prov, lines) = make_lines(&text, 24.0 * 2.0, true);
3322
3323        let mut iter = lines.iter_rvlines(&text_prov, false, RVLine::default());
3324
3325        // "conf = "
3326        let v = iter.next().unwrap();
3327        assert_eq!(v.last_col(&text_prov, false), 6);
3328        assert_eq!(v.last_col(&text_prov, true), 7);
3329
3330        // "Config::default();"
3331        let v = iter.next().unwrap();
3332        assert_eq!(v.last_col(&text_prov, false), 24);
3333        assert_eq!(v.last_col(&text_prov, true), 25);
3334
3335        let text = Rope::from("blah\nthing");
3336        let (text_prov, lines) = make_lines(&text, 1000., false);
3337        let mut iter = lines.iter_rvlines(&text_prov, false, RVLine::default());
3338
3339        let rtext = RopeTextVal::new(text.clone());
3340
3341        // "blah"
3342        let v = iter.next().unwrap();
3343        assert_eq!(v.last_col(&text_prov, false), 3);
3344        assert_eq!(v.last_col(&text_prov, true), 4);
3345        assert_eq!(rtext.offset_of_line_col(0, 3), 3);
3346        assert_eq!(rtext.offset_of_line_col(0, 4), 4);
3347
3348        // "text"
3349        let v = iter.next().unwrap();
3350        assert_eq!(v.last_col(&text_prov, false), 4);
3351        assert_eq!(v.last_col(&text_prov, true), 5);
3352
3353        let text = Rope::from("blah\r\nthing");
3354        let (text_prov, lines) = make_lines(&text, 1000., false);
3355        let mut iter = lines.iter_rvlines(&text_prov, false, RVLine::default());
3356
3357        let rtext = RopeTextVal::new(text.clone());
3358
3359        // "blah"
3360        let v = iter.next().unwrap();
3361        assert_eq!(v.last_col(&text_prov, false), 3);
3362        assert_eq!(v.last_col(&text_prov, true), 4);
3363        assert_eq!(rtext.offset_of_line_col(0, 3), 3);
3364        assert_eq!(rtext.offset_of_line_col(0, 4), 4);
3365
3366        // "text"
3367        let v = iter.next().unwrap();
3368        assert_eq!(v.last_col(&text_prov, false), 4);
3369        assert_eq!(v.last_col(&text_prov, true), 5);
3370        assert_eq!(rtext.offset_of_line_col(0, 4), 4);
3371        assert_eq!(rtext.offset_of_line_col(0, 5), 4);
3372    }
3373
3374    #[test]
3375    fn layout_cols() {
3376        let text = Rope::from("aaaa\nbb bb cc\ndd");
3377        let mut layout = TextLayout::new();
3378        layout.set_text("aaaa", AttrsList::new(Attrs::new()), None);
3379        let layout = TextLayoutLine {
3380            extra_style: Vec::new(),
3381            text: layout,
3382            whitespaces: None,
3383            indent: 0.,
3384            phantom_text: PhantomTextLine::default(),
3385        };
3386
3387        let (text_prov, _) = make_lines(&text, 10000., false);
3388        assert_eq!(
3389            layout.layout_cols(&text_prov, 0).collect::<Vec<_>>(),
3390            vec![(0, 4)]
3391        );
3392        let (text_prov, _) = make_lines(&text, 10000., true);
3393        assert_eq!(
3394            layout.layout_cols(&text_prov, 0).collect::<Vec<_>>(),
3395            vec![(0, 4)]
3396        );
3397
3398        let text = Rope::from("aaaa\r\nbb bb cc\r\ndd");
3399        let mut layout = TextLayout::new();
3400        layout.set_text("aaaa", AttrsList::new(Attrs::new()), None);
3401        let layout = TextLayoutLine {
3402            extra_style: Vec::new(),
3403            text: layout,
3404            whitespaces: None,
3405            indent: 0.,
3406            phantom_text: PhantomTextLine::default(),
3407        };
3408
3409        let (text_prov, _) = make_lines(&text, 10000., false);
3410        assert_eq!(
3411            layout.layout_cols(&text_prov, 0).collect::<Vec<_>>(),
3412            vec![(0, 4)]
3413        );
3414        let (text_prov, _) = make_lines(&text, 10000., true);
3415        assert_eq!(
3416            layout.layout_cols(&text_prov, 0).collect::<Vec<_>>(),
3417            vec![(0, 4)]
3418        );
3419    }
3420
3421    #[test]
3422    fn test_end_of_rvline() {
3423        fn eor(lines: &Lines, text_prov: &impl TextLayoutProvider, rvline: RVLine) -> usize {
3424            let layouts = lines.text_layouts.borrow();
3425            end_of_rvline(&layouts, text_prov, 12, rvline)
3426        }
3427
3428        fn check_equiv(text: &Rope, expected: usize, from: &str) {
3429            let (text_prov, lines) = make_lines(text, 10000., false);
3430            let end1 = eor(&lines, &text_prov, RVLine::new(0, 0));
3431
3432            let (text_prov, lines) = make_lines(text, 10000., true);
3433            assert_eq!(
3434                eor(&lines, &text_prov, RVLine::new(0, 0)),
3435                end1,
3436                "non-init end_of_rvline not equivalent to init ({from})"
3437            );
3438            assert_eq!(end1, expected, "end_of_rvline not equivalent ({from})");
3439        }
3440
3441        let text = Rope::from("");
3442        check_equiv(&text, 0, "empty");
3443
3444        let text = Rope::from("aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg");
3445        check_equiv(&text, 4, "simple multiline (LF)");
3446
3447        let text = Rope::from("aaaa\r\nbb bb cc\r\ncc dddd eeee ff\r\nff gggg");
3448        check_equiv(&text, 4, "simple multiline (CRLF)");
3449
3450        let text = Rope::from("a b c d ");
3451        let mut ph = HashMap::new();
3452        ph.insert(
3453            0,
3454            PhantomTextLine {
3455                text: smallvec![mph(PhantomTextKind::Completion, 1, "hi")],
3456            },
3457        );
3458
3459        let (text_prov, lines) = make_lines_ph(&text, 1., true, ph);
3460
3461        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 0)), 2);
3462
3463        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 1)), 4);
3464
3465        let text = Rope::from("        let j = test_test\nlet blah = 5;");
3466
3467        let mut ph = HashMap::new();
3468        ph.insert(
3469            0,
3470            PhantomTextLine {
3471                text: smallvec![
3472                    mph(
3473                        PhantomTextKind::Diagnostic,
3474                        26,
3475                        "    Syntax Error: `let` expressions are not supported here"
3476                    ),
3477                    mph(
3478                        PhantomTextKind::Diagnostic,
3479                        26,
3480                        "    Syntax Error: expected SEMICOLON"
3481                    ),
3482                ],
3483            },
3484        );
3485
3486        let (text_prov, lines) = make_lines_ph(&text, 250., true, ph);
3487
3488        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 0)), 25);
3489        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 1)), 25);
3490        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 2)), 25);
3491        assert_eq!(eor(&lines, &text_prov, RVLine::new(0, 3)), 25);
3492        assert_eq!(eor(&lines, &text_prov, RVLine::new(1, 0)), 39);
3493    }
3494
3495    #[test]
3496    fn equivalence() {
3497        // Extra tests that the visual lines you get when initting are equivalent to the ones you
3498        // get if you don't init
3499        // TODO: tests for them being equivalent even with wrapping
3500
3501        fn check_equiv(text: &Rope, from: &str) {
3502            let (text_prov, lines) = make_lines(text, 10000., false);
3503            let iter = lines.iter_rvlines(&text_prov, false, RVLine::default());
3504
3505            let (text_prov, lines) = make_lines(text, 01000., true);
3506            let iter2 = lines.iter_rvlines(&text_prov, false, RVLine::default());
3507
3508            // Just assume same length
3509            for (i, v) in iter.zip(iter2) {
3510                assert_eq!(
3511                    i, v,
3512                    "Line {} is not equivalent when initting ({from})",
3513                    i.rvline.line
3514                );
3515            }
3516        }
3517
3518        check_equiv(&Rope::from(""), "empty");
3519        check_equiv(&Rope::from("a"), "a");
3520        check_equiv(
3521            &Rope::from("aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg"),
3522            "simple multiline (LF)",
3523        );
3524        check_equiv(
3525            &Rope::from("aaaa\r\nbb bb cc\r\ncc dddd eeee ff\r\nff gggg"),
3526            "simple multiline (CRLF)",
3527        );
3528    }
3529}