duat_core/text/
mod.rs

1//! The primary data structure in Duat
2//!
3//! This struct is responsible for all of the text that will be
4//! printed to the screen, as well as any modifications of it.
5//!
6//! The [`Text`] is a very versatile holder for characters, below is a
7//! list of some of its capabilities:
8//!
9//! - Be cheaply* edited at any point, due to its two [gap buffers];
10//! - Be [colored] in any way, at any point;
11//! - Have any arbitrary range concealed, that is, hidden from view,
12//!   but still in there;
13//! - Arbitrary [ghost text], that is, [`Text`] that shows up, but is
14//!   not actually part of the [`Text`], i.e., it can be easily
15//!   ignored by external modifiers (like an LSP or tree-sitter) of
16//!   the file, without any special checks;
17//! - Left/right/center alignment of output (although that is
18//!   implemented by the [`Ui`]);
19//! - The ability to undo/redo changes in the history;
20//! - In the future, button ranges that can interact with the mouse;
21//!
22//! The [`Text`] struct is created in two different ways:
23//!
24//! - By calling [`Text::new`] or one of its [`From`] implementations;
25//! - By building it with the [`text!`] macro;
26//!
27//! The first method is recommended if you want a [`Text`] that will
28//! be modified by input. The only real example of this is the
29//! [`File`] widget.
30//!
31//! The second method is what should be used most of the time, as it
32//! lets you quickly create formatted [`Widget`]s/[`StatusLine`] parts
33//! in a very modular way:
34//!
35//! ```rust
36//! # use duat_core::text::{text, Text};
37//! fn number_of_horses(count: usize) -> Text {
38//!     if count == 1 {
39//!         text!([HorseCount] 1 " " [Horses] "horse")
40//!     } else {
41//!         text!([HorseCount] count " " [Horses] "horses")
42//!     }
43//! }
44//! ```
45//!
46//! You can use this whenever you need to update a widget, for
47//! example, just create a new [`Text`] to printed to the screen.
48//!
49//! However, when recreating the entire [`Text`] with a [`text!`]
50//! macro would be too expensive, you can use [`Text`] modifying
51//! functions:
52//!
53//! ```rust
54//! # use duat_core::text::{text, Text};
55//! let mut prompted = text!([Prompt] "type a key:");
56//! let end = prompted.len();
57//! prompted.replace_range((end, end), "a")
58//! ```
59//!
60//! These would be used mostly on the [`File`] widget and other whose
61//! [`Mode`]s make use of [`EditHelper`]s.
62//!
63//! [gap buffers]: GapBuffer
64//! [colored]: crate::form::Form
65//! [ghost text]: Tag::GhostText
66//! [`Ui`]: crate::ui::Ui
67//! [`File`]: crate::widgets::File
68//! [`Widget`]: crate::widgets::Widget
69//! [`StatusLine`]: crate::widgets::StatusLine
70//! [`Mode`]: crate::mode::Mode
71//! [`EditHelper`]: crate::mode::EditHelper
72mod builder;
73mod history;
74mod iter;
75mod ops;
76mod reader;
77mod records;
78mod search;
79mod tags;
80mod treesitter;
81
82use std::{
83    array::IntoIter,
84    ops::{Range, RangeBounds},
85    path::Path,
86    rc::Rc,
87    str::from_utf8_unchecked,
88    sync::{
89        Arc,
90        atomic::{AtomicBool, Ordering},
91    },
92};
93
94pub use gapbuf::GapBuffer;
95use records::Records;
96use tags::{FwdTags, RevTags};
97
98pub(crate) use self::history::History;
99use self::tags::Tags;
100pub use self::{
101    builder::{AlignCenter, AlignLeft, AlignRight, Builder, Ghost, err, hint, ok, text},
102    history::Change,
103    iter::{Item, Iter, Part, RevIter},
104    ops::{Point, TextRange, TwoPoints, utf8_char_width},
105    reader::Reader,
106    search::{Matcheable, RegexPattern, Searcher},
107    tags::{Key, Keys, Tag, ToggleId},
108    treesitter::TsParser,
109};
110use crate::{
111    DuatError, cache,
112    cfg::PrintCfg,
113    mode::{Cursor, Cursors},
114    ui::Area,
115};
116
117/// The text in a given [`Area`]
118#[derive(Default)]
119pub struct Text {
120    buf: GapBuffer<u8>,
121    tags: Box<Tags>,
122    records: Box<Records<[usize; 3]>>,
123    cursors: Option<Box<Cursors>>,
124    // Specific to Files
125    history: Option<Box<History>>,
126    readers: Vec<(Box<dyn Reader>, Vec<Range<usize>>)>,
127    ts_parser: Option<(Box<TsParser>, Vec<Range<usize>>)>,
128    has_changed: bool,
129    has_unsaved_changes: AtomicBool,
130    // Used in Text building
131    forced_new_line: bool,
132}
133
134impl Text {
135    ////////// Creation of Text
136
137    /// Returns a new empty [`Text`]
138    pub fn new() -> Self {
139        Self::from_buf(GapBuffer::default(), None, false)
140    }
141
142    pub fn new_with_cursors() -> Self {
143        Self::from_buf(GapBuffer::default(), Some(Cursors::default()), false)
144    }
145
146    /// Returns a new empty [`Text`] with history enabled
147    pub(crate) fn new_with_history() -> Self {
148        Self::from_buf(GapBuffer::default(), Some(Cursors::default()), true)
149    }
150
151    /// Creates a [`Text`] from a file's [path]
152    ///
153    /// [path]: Path
154    pub(crate) fn from_file(
155        buf: GapBuffer<u8>,
156        cursors: Cursors,
157        path: impl AsRef<Path>,
158        has_unsaved_changes: bool,
159    ) -> Self {
160        let mut text = Self::from_buf(buf, Some(cursors), true);
161        text.has_unsaved_changes
162            .store(has_unsaved_changes, Ordering::Relaxed);
163
164        if let Some(history) = cache::load_cache(path.as_ref()) {
165            text.history = Some(Box::new(history));
166        }
167
168        let tree_sitter = TsParser::new(&mut text, path);
169        text.ts_parser = tree_sitter.map(|ts| (Box::new(ts), Vec::new()));
170        text
171    }
172
173    /// Creates a [`Text`] from a [`GapBuffer`]
174    pub(crate) fn from_buf(
175        mut buf: GapBuffer<u8>,
176        cursors: Option<Cursors>,
177        with_history: bool,
178    ) -> Self {
179        let forced_new_line = if buf.is_empty() || buf[buf.len() - 1] != b'\n' {
180            buf.push_back(b'\n');
181            true
182        } else {
183            false
184        };
185        let len = buf.len();
186        let chars = unsafe {
187            let (s0, s1) = buf.as_slices();
188            std::str::from_utf8_unchecked(s0).chars().count()
189                + std::str::from_utf8_unchecked(s1).chars().count()
190        };
191        let lines = buf.iter().filter(|b| **b == b'\n').count();
192        let tags = Box::new(Tags::with_len(buf.len()));
193
194        Self {
195            buf,
196            tags,
197            cursors: cursors.map(Box::new),
198            records: Box::new(Records::with_max([len, chars, lines])),
199            history: with_history.then(Box::default),
200            readers: Vec::new(),
201            ts_parser: None,
202            forced_new_line,
203            has_changed: false,
204            has_unsaved_changes: AtomicBool::new(false),
205        }
206    }
207
208    /// Returns a [`Builder`] for [`Text`]
209    ///
210    /// This builder can be used to iteratively create text, by
211    /// assuming that the user wants no* [`Tag`] overlap, and that
212    /// they want to construct the [`Text`] in [`Tag`]/content pairs.
213    ///
214    /// ```rust
215    /// use duat_core::text::{Tag, Text, text};
216    /// let mut builder = Text::builder();
217    /// ```
218    pub fn builder() -> Builder {
219        Builder::new()
220    }
221
222    ////////// Querying functions
223
224    /// The [`Point`] at the end of the text
225    pub fn len(&self) -> Point {
226        let [b, c, l] = self.records.max();
227        Point::from_raw(b, c, l)
228    }
229
230    /// Whether or not there are any characters in the [`Text`]
231    ///
232    /// # Note
233    ///
234    /// This does not check for tags, so with a [`Tag::GhostText`],
235    /// there could actually be a "string" of characters on the
236    /// [`Text`], it just wouldn't be considered real "text".
237    pub fn is_empty(&self) -> bool {
238        self.buf.is_empty()
239    }
240
241    /// The `char` at the [`Point`]'s position
242    pub fn char_at(&self, point: Point) -> Option<char> {
243        let [s0, s1] = self.strs();
244        if point.byte() < s0.len() {
245            s0[point.byte()..].chars().next()
246        } else {
247            s1[point.byte() - s0.len()..].chars().next()
248        }
249    }
250
251    /// The two [`&str`]s that compose the [buffer]
252    ///
253    /// In order to iterate over them, I recommend using the
254    /// [`flat_map`] method:
255    ///
256    /// ```rust
257    /// # use duat_core::text::Text;
258    /// let text = Text::new();
259    /// text.strs().into_iter().flat_map(str::chars);
260    /// ```
261    ///
262    /// Do note that you should avoid iterators like [`str::lines`],
263    /// as they will separate the line that is partially owned by each
264    /// [`&str`]:
265    ///
266    /// ```rust
267    /// let broken_up_line = [
268    ///     "This is line 1, business as usual This is line 2, but it",
269    ///     "is broken into two separate strings So 4 lines would be counted, \
270    ///      instead of 3",
271    /// ];
272    /// ```
273    ///
274    /// If you want the two [`&str`]s in a range, see
275    /// [`strs_in`]
276    ///
277    /// [`&str`]: str
278    /// [buffer]: GapBuffer
279    /// [`flat_map`]: Iterator::flat_map
280    /// [`strs_in`]: Self::strs_in
281    pub fn strs(&self) -> [&'_ str; 2] {
282        let (s0, s1) = self.buf.as_slices();
283        unsafe { [from_utf8_unchecked(s0), from_utf8_unchecked(s1)] }
284    }
285
286    /// Returns 2 [`&str`]s in the given [range]
287    ///
288    /// # Note
289    ///
290    /// The reason why this function returns two strings is that the
291    /// contents of the text are stored in a [`GapBuffer`], which
292    /// works with two strings.
293    ///
294    /// If you want to iterate over them, you can do the following:
295    ///
296    /// ```rust
297    /// # use duat_core::text::{Point, Text};
298    /// # let (p1, p2) = (Point::default(), Point::default());
299    /// let text = Text::new();
300    /// text.strs_in((p1, p2)).flat_map(str::bytes);
301    /// ```
302    ///
303    /// Do note that you should avoid iterators like [`str::lines`],
304    /// as they will separate the line that is partially owned by each
305    /// [`&str`]:
306    ///
307    /// ```rust
308    /// let broken_up_line = [
309    ///     "This is line 1, business as usual.\nThis is line 2, but it",
310    ///     "is broken into two separate strings.\nSo 4 lines would be counted, \
311    ///      instead of 3",
312    /// ];
313    /// ```
314    ///
315    /// # [`TextRange`] behavior:
316    ///
317    /// If you give a single [`usize`]/[`Point`], it will be
318    /// interpreted as a range from.
319    ///
320    /// If you want the two full [`&str`]s, see [`strs`]
321    ///
322    /// [`&str`]: str
323    /// [range]: TextRange
324    /// [`strs`]: Self::strs
325    pub fn strs_in(&self, range: impl TextRange) -> IntoIter<&str, 2> {
326        let range = range.to_range_fwd(self.len().byte());
327        self.strs_in_range_inner(range).into_iter()
328    }
329
330    /// Returns an iterator over the lines in a given range
331    ///
332    /// The lines are inclusive, that is, it will iterate over the
333    /// whole line, not just the parts within the range.
334    pub fn lines_in(
335        &mut self,
336        range: impl TextRange,
337    ) -> impl DoubleEndedIterator<Item = (usize, &str)> {
338        let range = range.to_range_fwd(self.len().byte());
339        let start = self.point_at_line(self.point_at(range.start).line());
340        let end = {
341            let end = self.point_at(range.end);
342            let line_start = self.point_at_line(end.line());
343            match line_start == end {
344                true => end,
345                false => self.point_at_line((end.line() + 1).min(self.len().line())),
346            }
347        };
348        self.make_contiguous_in((start, end));
349        unsafe {
350            let lines = self.continuous_in_unchecked((start, end)).lines();
351            let (fwd_i, rev_i) = (start.line(), end.line());
352            TextLines { lines, fwd_i, rev_i }
353        }
354    }
355
356    /// Returns the two `&str`s in the byte range.
357    fn strs_in_range_inner(&self, range: impl RangeBounds<usize>) -> [&str; 2] {
358        let (s0, s1) = self.buf.as_slices();
359        let (start, end) = crate::get_ends(range, self.len().byte());
360        let (start, end) = (start, end);
361        // Make sure the start and end are in character bounds.
362        assert!(
363            [start, end]
364                .into_iter()
365                .filter_map(|b| self.buf.get(b))
366                .all(|b| utf8_char_width(*b) > 0),
367        );
368
369        unsafe {
370            let r0 = start.min(s0.len())..end.min(s0.len());
371            let r1 = start.saturating_sub(s0.len()).min(s1.len())
372                ..end.saturating_sub(s0.len()).min(s1.len());
373
374            [from_utf8_unchecked(&s0[r0]), from_utf8_unchecked(&s1[r1])]
375        }
376    }
377
378    /// Returns the [`TsParser`], if there is one
379    ///
380    /// This parser uses tree-sitter internally for things like syntax
381    /// highlighing and indentation, but it have all sorts of
382    /// utilities in user code as well.
383    pub fn ts_parser(&self) -> Option<&TsParser> {
384        self.ts_parser.as_ref().map(|(ts, _)| ts.as_ref())
385    }
386
387    /// Gets the indentation on a given [`Point`]
388    ///
389    /// Will either return the required indentation (in spaces) or
390    /// [`None`], in which case the caller will have to decide how to
391    /// proceed. This usually means "keep previous level of
392    /// indentation".
393    pub fn ts_indent_on(&mut self, p: Point, cfg: PrintCfg) -> Option<usize> {
394        let ts = self.ts_parser.take();
395        let indent = ts.as_ref().and_then(|(ts, _)| ts.indent_on(self, p, cfg));
396        self.ts_parser = ts;
397        indent
398    }
399
400    ////////// Point querying functions
401
402    /// The [`Point`] corresponding to the byte position, 0 indexed
403    ///
404    /// If the byte position would fall in between two characters
405    /// (because the first one comprises more than one byte), the
406    /// first character is chosen as the [`Point`] where the byte is
407    /// located.
408    ///
409    /// # Panics
410    ///
411    /// Will panic if `at` is greater than the length of the text
412    #[inline(always)]
413    pub fn point_at(&self, at: usize) -> Point {
414        assert!(
415            at <= self.len().byte(),
416            "byte out of bounds: the len is {}, but the byte is {at}",
417            self.len().byte()
418        );
419        let [b, c, mut l] = self.records.closest_to(at);
420
421        let found = if at >= b {
422            let [s0, s1] = self.strs_in_range_inner(b..);
423
424            s0.char_indices()
425                .chain(s1.char_indices().map(|(b, char)| (b + s0.len(), char)))
426                .enumerate()
427                .map(|(i, (this_b, char))| {
428                    l += (char == '\n') as usize;
429                    (b + this_b, c + i, l - (char == '\n') as usize)
430                })
431                .take_while(|&(b, ..)| at >= b)
432                .last()
433        } else {
434            let mut c_len = 0;
435            self.strs_in_range_inner(..b)
436                .into_iter()
437                .flat_map(str::chars)
438                .rev()
439                .enumerate()
440                .map(|(i, char)| {
441                    l -= (char == '\n') as usize;
442                    c_len += char.len_utf8();
443                    (b - c_len, c - (i + 1), l)
444                })
445                .take_while(|&(b, ..)| b >= at)
446                .last()
447        };
448
449        found
450            .map(|(b, c, l)| Point::from_raw(b, c, l))
451            .unwrap_or(self.len())
452    }
453
454    /// The [`Point`] associated with a char position, 0 indexed
455    ///
456    /// # Panics
457    ///
458    /// Will panic if `at` is greater than the number of chars in the
459    /// text.
460    #[inline(always)]
461    pub fn point_at_char(&self, at: usize) -> Point {
462        assert!(
463            at <= self.len().char(),
464            "byte out of bounds: the len is {}, but the char is {at}",
465            self.len().char()
466        );
467        let [b, c, mut l] = self.records.closest_to_by_key(at, |[_, c, _]| *c);
468
469        let found = if at >= c {
470            let [s0, s1] = self.strs_in_range_inner(b..);
471
472            s0.char_indices()
473                .chain(s1.char_indices().map(|(b, char)| (b + s0.len(), char)))
474                .enumerate()
475                .map(|(i, (this_b, char))| {
476                    l += (char == '\n') as usize;
477                    (b + this_b, c + i, l - (char == '\n') as usize)
478                })
479                .take_while(|&(_, c, _)| at >= c)
480                .last()
481        } else {
482            let mut c_len = 0;
483            self.strs_in_range_inner(..)
484                .into_iter()
485                .flat_map(str::chars)
486                .rev()
487                .enumerate()
488                .map(|(i, char)| {
489                    l -= (char == '\n') as usize;
490                    c_len += char.len_utf8();
491                    (b - c_len, c - (i + 1), l)
492                })
493                .take_while(|&(_, c, _)| c >= at)
494                .last()
495        };
496
497        found
498            .map(|(b, c, l)| Point::from_raw(b, c, l))
499            .unwrap_or(self.len())
500    }
501
502    /// The [`Point`] where the `at`th line starts, 0 indexed
503    ///
504    /// If `at == number_of_lines`, returns the last point of the
505    /// text.
506    ///
507    /// # Panics
508    ///
509    /// Will panic if the number `at` is greater than the number of
510    /// lines on the text
511    #[inline(always)]
512    pub fn point_at_line(&self, at: usize) -> Point {
513        assert!(
514            at <= self.len().line(),
515            "byte out of bounds: the len is {}, but the line is {at}",
516            self.len().line()
517        );
518        let (b, c, mut l) = {
519            let [mut b, mut c, l] = self.records.closest_to_by_key(at, |[.., l]| *l);
520            self.strs_in_range_inner(..b)
521                .into_iter()
522                .flat_map(str::chars)
523                .rev()
524                .take_while(|c| *c != '\n')
525                .for_each(|char| {
526                    b -= char.len_utf8();
527                    c -= 1;
528                });
529            (b, c, l)
530        };
531
532        let found = if at >= l {
533            let [s0, s1] = self.strs_in_range_inner(b..);
534
535            s0.char_indices()
536                .chain(s1.char_indices().map(|(b, char)| (b + s0.len(), char)))
537                .enumerate()
538                .map(|(i, (this_b, char))| {
539                    l += (char == '\n') as usize;
540                    (b + this_b, c + i, l - (char == '\n') as usize)
541                })
542                .find(|&(.., l)| at == l)
543        } else {
544            let mut c_len = 0;
545            self.strs_in_range_inner(..b)
546                .into_iter()
547                .flat_map(str::chars)
548                .rev()
549                .enumerate()
550                .map(|(i, char)| {
551                    l -= (char == '\n') as usize;
552                    c_len += char.len_utf8();
553                    (b - c_len, c - (i + 1), l)
554                })
555                .take_while(|&(.., l)| l >= at)
556                .last()
557        };
558
559        found
560            .map(|(b, c, l)| Point::from_raw(b, c, l))
561            .unwrap_or(self.len())
562    }
563
564    /// The start and end [`Point`]s for a given `at` line
565    ///
566    /// If `at == number_of_lines`, these points will be the same.
567    ///
568    /// # Panics
569    ///
570    /// Will panic if the number `at` is greater than the number of
571    /// lines on the text
572    #[inline(always)]
573    pub fn points_of_line(&self, at: usize) -> (Point, Point) {
574        assert!(
575            at <= self.len().line(),
576            "byte out of bounds: the len is {}, but the line is {at}",
577            self.len().line()
578        );
579
580        let start = self.point_at_line(at);
581        let end = self
582            .chars_fwd(start)
583            .find_map(|(p, _)| (p.line() > start.line()).then_some(p))
584            .unwrap_or(start);
585        (start, end)
586    }
587
588    /// The [points] at the end of the text
589    ///
590    /// This will essentially return the [last point] of the text,
591    /// alongside the last possible [`Point`] of any
592    /// [`Tag::GhostText`] at the end of the text.
593    ///
594    /// [points]: TwoPoints
595    /// [last point]: Self::len
596    pub fn len_points(&self) -> (Point, Option<Point>) {
597        self.ghost_max_points_at(self.len().byte())
598    }
599
600    /// The last [`Point`] associated with a `char`
601    ///
602    /// This will give the [`Point`] of the last `char` of the text.
603    /// The difference between this method and [`len`] is that
604    /// it will return a [`Point`] one position earlier than it. If
605    /// the text is completely empty, it will return [`None`].
606    ///
607    /// [`len`]: Self::len
608    pub fn last_point(&self) -> Option<Point> {
609        self.strs_in_range_inner(..)
610            .into_iter()
611            .flat_map(str::chars)
612            .next_back()
613            .map(|char| self.len().rev(char))
614    }
615
616    ////////// Points queying functions.
617
618    /// The maximum [points] in the `at`th byte
619    ///
620    /// This point is essentially the [point] at that byte, plus the
621    /// last possible [`Point`] of any [`Tag::GhostText`]s in that
622    /// position.
623    ///
624    /// [points]: TwoPoints
625    /// [point]: Self::point_at
626    #[inline(always)]
627    pub fn ghost_max_points_at(&self, at: usize) -> (Point, Option<Point>) {
628        let point = self.point_at(at);
629        (point, self.tags.ghosts_total_at(point.byte()))
630    }
631
632    /// Points visually after the [`TwoPoints`]
633    ///
634    /// If the [`TwoPoints`] in question is concealed, treats the
635    /// next visible character as the first character, and returns
636    /// the points of the next visible character.
637    ///
638    /// This method is useful if you want to iterator reversibly
639    /// right after a certain point, thus including the character
640    /// of said point.
641    pub fn points_after(&self, tp: impl TwoPoints) -> Option<(Point, Option<Point>)> {
642        self.iter_fwd(tp)
643            .filter_map(|item| item.part.as_char().map(|_| item.points()))
644            .chain([self.len_points()])
645            .nth(1)
646    }
647
648    /// The visual start of the line
649    ///
650    /// This point is defined not by where the line actually begins,
651    /// but by where the last '\n' was located. For example, if
652    /// [`Tag`]s create ghost text or omit text from multiple
653    /// different lines, this point may differ from where in the
654    /// [`Text`] the physical line actually begins.
655    pub fn visual_line_start(&self, p: impl TwoPoints) -> (Point, Option<Point>) {
656        let (real, ghost) = p.to_points();
657
658        let mut iter = self.iter_rev((real, ghost)).peekable();
659        let mut points = (real, ghost);
660        while let Some(peek) = iter.peek() {
661            match peek.part {
662                Part::Char('\n') => {
663                    return points;
664                }
665                Part::Char(_) => points = iter.next().unwrap().to_points(),
666                _ => drop(iter.next()),
667            }
668        }
669
670        points
671    }
672
673    ////////// String modification functions
674
675    /// Replaces a [range] in the [`Text`]
676    ///
677    /// # [`TextRange`] behavior:
678    ///
679    /// If you give a single [`usize`]/[`Point`], it will be
680    /// interpreted as a range from.
681    ///
682    /// [range]: TextRange
683    pub fn replace_range(&mut self, range: impl TextRange, edit: impl ToString) {
684        let range = range.to_range_at(self.len().byte());
685        let (start, end) = (self.point_at(range.start), self.point_at(range.end));
686        let change = Change::new(edit, (start, end), self);
687
688        self.has_changed = true;
689        self.replace_range_inner(change.as_ref());
690        self.history.as_mut().map(|h| h.add_change(None, change));
691    }
692
693    pub fn apply_change(
694        &mut self,
695        guess_i: Option<usize>,
696        change: Change<String>,
697    ) -> Option<usize> {
698        self.has_changed = true;
699        self.replace_range_inner(change.as_ref());
700        self.history.as_mut().map(|h| h.add_change(guess_i, change))
701    }
702
703    /// Merges `String`s with the body of text, given a range to
704    /// replace
705    fn replace_range_inner(&mut self, change: Change<&str>) {
706        let edit = change.added_text();
707        let start = change.start();
708        let taken_end = change.taken_end();
709
710        let new_len = {
711            let lines = edit.bytes().filter(|b| *b == b'\n').count();
712            [edit.len(), edit.chars().count(), lines]
713        };
714
715        let old_len = unsafe {
716            let range = start.byte()..change.taken_end().byte();
717            let str = String::from_utf8_unchecked(
718                self.buf
719                    .splice(range, edit.as_bytes().iter().cloned())
720                    .collect(),
721            );
722
723            let lines = str.bytes().filter(|b| *b == b'\n').count();
724            [str.len(), str.chars().count(), lines]
725        };
726
727        let start_rec = [start.byte(), start.char(), start.line()];
728        self.records.transform(start_rec, old_len, new_len);
729        self.records.insert(start_rec);
730
731        self.tags
732            .transform(start.byte()..taken_end.byte(), change.added_end().byte());
733
734        *self.has_unsaved_changes.get_mut() = true;
735    }
736
737    /// This is used by [`Area`]s in order to update visible text
738    ///
739    /// In order to not update too much, an [`Area`] will request that
740    /// a region of the [`Text`] (usually roughly what is shown on
741    /// screen) to be updated, rather than the whole [`Text`].
742    ///
743    /// This should be done within the [`Area::print`] and
744    /// [`Area::print_with`] functions.
745    pub fn update_range(&mut self, range: (Point, Point)) {
746        let within = range.0.byte()..(range.1.byte() + 1);
747        let mut readers = std::mem::take(&mut self.readers);
748        let mut ts = self.ts_parser.take();
749
750        if let Some(changes) = self.history.as_mut().and_then(|h| h.unprocessed_changes()) {
751            let changes: Vec<Change<&str>> = changes.iter().map(|c| c.as_ref()).collect();
752            self.process_changes(&mut ts, &mut readers, &changes);
753        }
754
755        if let Some((ts, ranges)) = ts.as_mut() {
756            let mut new_ranges = Vec::new();
757
758            for range in ranges.iter() {
759                let (to_check, split_off) = split_range_within(range.clone(), within.clone());
760                if let Some(range) = to_check {
761                    ts.update_range(self, range);
762                }
763                new_ranges.extend(split_off.into_iter().flatten());
764            }
765            *ranges = new_ranges
766        }
767        for (reader, ranges) in readers.iter_mut() {
768            let mut new_ranges = Vec::new();
769
770            for range in ranges.iter() {
771                let (to_check, split_off) = split_range_within(range.clone(), within.clone());
772                if let Some(range) = to_check {
773                    reader.update_range(self, range);
774                }
775                new_ranges.extend(split_off.into_iter().flatten());
776            }
777            *ranges = new_ranges;
778        }
779        self.tags.update_range(within);
780
781        self.has_changed = false;
782        self.readers = readers;
783        self.ts_parser = ts;
784    }
785
786    fn process_changes(
787        &mut self,
788        ts: &mut Option<(Box<TsParser>, Vec<Range<usize>>)>,
789        readers: &mut Vec<(Box<dyn Reader>, Vec<Range<usize>>)>,
790        changes: &[Change<&str>],
791    ) {
792        const MAX_RANGES_TO_CONSIDER: usize = 15;
793        if let Some((ts, _)) = ts.as_mut() {
794            ts.apply_changes(self, changes);
795        }
796        for (reader, _) in readers.iter_mut() {
797            reader.apply_changes(self, changes);
798        }
799
800        if changes.len() <= MAX_RANGES_TO_CONSIDER {
801            if let Some((ts, ranges)) = ts.as_mut() {
802                for range in ts.ranges_to_update(self, changes) {
803                    if !range.is_empty() {
804                        merge_range_in(ranges, range);
805                    }
806                }
807            }
808            for (reader, ranges) in readers.iter_mut() {
809                for range in reader.ranges_to_update(self, changes) {
810                    if !range.is_empty() {
811                        merge_range_in(ranges, range);
812                    }
813                }
814            }
815        // If there are too many changes, cut on processing and
816        // just assume that everything needs to be updated.
817        } else {
818            let ts_range_iter = ts.as_mut().map(|(_, r)| r).into_iter();
819            for range in ts_range_iter.chain(readers.iter_mut().map(|(_, r)| r)) {
820                *range = vec![0..self.len().byte()];
821            }
822        }
823    }
824
825    pub fn needs_update(&self) -> bool {
826        self.has_changed
827            || self.ts_parser.as_ref().is_some_and(|(_, r)| !r.is_empty())
828            || self.readers.iter().any(|(_, r)| !r.is_empty())
829    }
830
831    ////////// History manipulation functions
832
833    /// Undoes the last moment, if there was one
834    pub fn undo(&mut self, area: &impl Area, cfg: PrintCfg) {
835        let Some(mut history) = self.history.take() else {
836            return;
837        };
838        let mut cursors = self.cursors.take().unwrap();
839        let Some(changes) = history.move_backwards() else {
840            self.history = Some(history);
841            self.cursors = Some(cursors);
842            return;
843        };
844
845        self.apply_and_process_changes(&changes, Some((area, &mut cursors, cfg)));
846
847        self.has_changed = true;
848        self.history = Some(history);
849        self.cursors = Some(cursors);
850    }
851
852    /// Redoes the last moment in the history, if there is one
853    pub fn redo(&mut self, area: &impl Area, cfg: PrintCfg) {
854        let Some(mut history) = self.history.take() else {
855            return;
856        };
857        let mut cursors = self.cursors.take().unwrap();
858        let Some(changes) = history.move_forward() else {
859            self.history = Some(history);
860            self.cursors = Some(cursors);
861            return;
862        };
863
864        self.apply_and_process_changes(&changes, Some((area, &mut cursors, cfg)));
865
866        self.has_changed = true;
867        self.history = Some(history);
868        self.cursors = Some(cursors);
869    }
870
871    pub fn apply_and_process_changes(
872        &mut self,
873        changes: &[Change<&str>],
874        mut cursors_to_remake: Option<(&impl Area, &mut Cursors, PrintCfg)>,
875    ) {
876        if let Some((_, cursors, _)) = cursors_to_remake.as_mut() {
877            cursors.clear();
878        }
879
880        for (i, change) in changes.iter().enumerate() {
881            let start = change.start();
882            self.replace_range_inner(*change);
883
884            let Some((area, cursors, cfg)) = cursors_to_remake.as_mut() else {
885                continue;
886            };
887            cursors.insert_from_parts(i, start, change.added_text().len(), self, *area, *cfg);
888        }
889        let mut ts = self.ts_parser.take();
890        let mut readers = std::mem::take(&mut self.readers);
891
892        self.process_changes(&mut ts, &mut readers, changes);
893
894        self.ts_parser = ts;
895        self.readers = readers;
896    }
897
898    /// Finishes the current moment and adds a new one to the history
899    pub fn new_moment(&mut self) {
900        if let Some(h) = self.history.as_mut() {
901            h.new_moment()
902        }
903    }
904
905    ////////// Writing functions
906
907    /// Clones the inner [`GapBuffer`] as a [`String`]
908    ///
909    /// This function will also cut out a final '\n' from the string.
910    // NOTE: Inherent because I don't want this to implement Display
911    #[allow(clippy::inherent_to_string)]
912    pub fn to_string(&self) -> String {
913        let [s0, s1] = self.strs_in_range_inner(..);
914        if !s1.is_empty() {
915            s0.to_string() + s1.strip_suffix('\n').unwrap_or(s1)
916        } else {
917            s0.strip_suffix('\n').unwrap_or(s0).to_string()
918        }
919    }
920
921    /// Writes the contents of this [`Text`] to a [writer]
922    ///
923    /// [writer]: std::io::Write
924    pub fn write_to(&self, mut writer: impl std::io::Write) -> std::io::Result<usize> {
925        self.has_unsaved_changes.store(false, Ordering::Relaxed);
926        let (s0, s1) = self.buf.as_slices();
927        Ok(writer.write(s0)? + writer.write(s1)?)
928    }
929
930    /// Wether or not the content has changed since the last [write]
931    ///
932    /// Returns `true` only if the actual bytes of the [`Text`] have
933    /// been changed, ignoring [`Tag`]s and all the other things,
934    /// since those are not written to the filesystem.
935    ///
936    /// [write]: Text::write_to
937    pub fn has_unsaved_changes(&self) -> bool {
938        self.has_unsaved_changes.load(Ordering::Relaxed)
939    }
940
941    ////////// Reload related functions
942
943    pub(crate) fn take_buf(self) -> GapBuffer<u8> {
944        self.buf
945    }
946
947    ////////// Single str acquisition functions
948
949    /// Moves the [`GapBuffer`]'s gap, so that the `range` is whole
950    ///
951    /// The return value is the value of the gap, if the second `&str`
952    /// is the contiguous one.
953    pub(crate) fn make_contiguous_in(&mut self, range: impl TextRange) {
954        let range = range.to_range_fwd(self.len().byte());
955        let gap = self.buf.gap();
956
957        if range.end <= gap || range.start >= gap {
958            return;
959        }
960
961        if gap.abs_diff(range.start) < gap.abs_diff(range.end) {
962            self.buf.set_gap(range.start);
963        } else {
964            self.buf.set_gap(range.end);
965        }
966    }
967
968    /// Assumes that the `range` given is continuous in `self`
969    ///
970    /// You *MUST* CALL [`make_contiguous_in`] before using this
971    /// function. The sole purpose of this function is to not keep the
972    /// [`Text`] mutably borrowed.
973    ///
974    /// [`make_contiguous_in`]: Self::make_contiguous_in
975    pub(crate) unsafe fn continuous_in_unchecked(&self, range: impl TextRange) -> &str {
976        let range = range.to_range_fwd(self.len().byte());
977        let [s0, s1] = self.strs();
978        if range.end <= self.buf.gap() {
979            unsafe { s0.get_unchecked(range) }
980        } else {
981            let gap = self.buf.gap();
982            unsafe { s1.get_unchecked(range.start - gap..range.end - gap) }
983        }
984    }
985
986    ////////// Tag addition/deletion functions
987
988    /// Inserts a [`Tag`] at the given position
989    pub fn insert_tag(&mut self, at: usize, tag: Tag, key: Key) {
990        self.tags.insert(at, tag, key);
991    }
992
993    /// Removes the [`Tag`]s of a [key] from a region
994    ///
995    /// # Caution
996    ///
997    /// While it is fine to do this on your own widgets, you should
998    /// refrain from using this function in a [`File`]s [`Text`], as
999    /// it must iterate over all tags in the file, so if there are a
1000    /// lot of other tags, this operation may be slow.
1001    ///
1002    /// # [`TextRange`] behavior
1003    ///
1004    /// If you give it a [`Point`] or [`usize`], it will be treated as
1005    /// a one byte range.
1006    ///
1007    /// [key]: Keys
1008    /// [`File`]: crate::widgets::File
1009    pub fn remove_tags(&mut self, range: impl TextRange, keys: impl Keys) {
1010        let range = range.to_range_at(self.len().byte());
1011        if range.end == range.start + 1 {
1012            self.tags.remove_at(range.start, keys)
1013        } else {
1014            self.tags.remove_from(range, keys)
1015        }
1016    }
1017
1018    /// Removes all [`Tag`]s
1019    ///
1020    /// Refrain from using this function on [`File`]s, as there may be
1021    /// other [`Tag`] providers, and you should avoid messing with
1022    /// their tags.
1023    ///
1024    /// [`File`]: crate::widgets::File
1025    pub fn clear_tags(&mut self) {
1026        self.tags = Box::new(Tags::with_len(self.buf.len()));
1027    }
1028
1029    /////////// Cursor functions
1030
1031    /// Enables the usage of [`Cursors`] in this [`Text`]
1032    ///
1033    /// This is automatically done whenever you use the [`EditHelper`]
1034    /// struct.
1035    ///
1036    /// [`EditHelper`]: crate::mode::EditHelper
1037    pub fn enable_cursors(&mut self) {
1038        if self.cursors.is_none() {
1039            self.cursors = Some(Box::default())
1040        }
1041    }
1042
1043    /// Removes the tags for all the cursors, used before they are
1044    /// expected to move
1045    pub(crate) fn add_cursors(&mut self, area: &impl Area, cfg: PrintCfg) {
1046        let Some(cursors) = self.cursors.take() else {
1047            return;
1048        };
1049
1050        if cursors.len() < 500 {
1051            for (cursor, is_main) in cursors.iter() {
1052                self.add_cursor(cursor, is_main, &cursors);
1053            }
1054        } else {
1055            let (start, _) = area.first_points(self, cfg);
1056            let (end, _) = area.last_points(self, cfg);
1057            for (cursor, is_main) in cursors.iter() {
1058                let range = cursor.range(cursors.is_incl(), self);
1059                if range.end > start.byte() && range.start < end.byte() {
1060                    self.add_cursor(cursor, is_main, &cursors);
1061                }
1062            }
1063        }
1064
1065        self.cursors = Some(cursors);
1066    }
1067
1068    /// Adds the tags for all the cursors, used after they are
1069    /// expected to have moved
1070    pub(crate) fn remove_cursors(&mut self, area: &impl Area, cfg: PrintCfg) {
1071        let Some(cursors) = self.cursors.take() else {
1072            return;
1073        };
1074
1075        if cursors.len() < 500 {
1076            for (cursor, _) in cursors.iter() {
1077                self.remove_cursor(cursor, &cursors);
1078            }
1079        } else {
1080            let (start, _) = area.first_points(self, cfg);
1081            let (end, _) = area.last_points(self, cfg);
1082            for (cursor, _) in cursors.iter() {
1083                let range = cursor.range(cursors.is_incl(), self);
1084                if range.end > start.byte() && range.start < end.byte() {
1085                    self.remove_cursor(cursor, &cursors);
1086                }
1087            }
1088        }
1089
1090        self.cursors = Some(cursors)
1091    }
1092
1093    pub(crate) fn shift_cursors(
1094        &mut self,
1095        from: usize,
1096        by: (i32, i32, i32),
1097        area: &impl Area,
1098        cfg: PrintCfg,
1099    ) {
1100        let Some(mut cursors) = self.cursors.take() else {
1101            return;
1102        };
1103
1104        if by != (0, 0, 0) {
1105            cursors.shift_by(from, by, self, area, cfg);
1106        }
1107
1108        self.cursors = Some(cursors);
1109    }
1110
1111    /// Adds a [`Cursor`] to the [`Text`]
1112    fn add_cursor(&mut self, cursor: &Cursor, is_main: bool, cursors: &Cursors) {
1113        let (start, end) = if let Some(anchor) = cursor.anchor() {
1114            if anchor <= cursor.caret() {
1115                // If the caret is at the end, the selection should end before it,
1116                // so a non inclusive selection it used.
1117                cursor.point_range(false, self)
1118            } else {
1119                cursor.point_range(cursors.is_incl(), self)
1120            }
1121        } else {
1122            cursor.point_range(false, self)
1123        };
1124        let no_selection = if start == end { 1 } else { 3 };
1125
1126        let tags = cursor_tags(is_main)
1127            .into_iter()
1128            .zip([cursor.caret(), end, start]);
1129        for (tag, p) in tags.take(no_selection) {
1130            let record = [p.byte(), p.char(), p.line()];
1131            self.records.insert(record);
1132            self.tags.insert(p.byte(), tag, Key::for_cursors());
1133        }
1134    }
1135
1136    /// Removes a [`Cursor`] from the [`Text`]
1137    fn remove_cursor(&mut self, cursor: &Cursor, cursors: &Cursors) {
1138        let (start, end) = if let Some(anchor) = cursor.anchor()
1139            && anchor < cursor.caret()
1140        {
1141            // If the caret is at the end, the selection should end before it,
1142            // so a non inclusive selection it used.
1143            cursor.point_range(false, self)
1144        } else {
1145            cursor.point_range(cursors.is_incl(), self)
1146        };
1147        let skip = if start == end { 1 } else { 0 };
1148
1149        for p in [start, end].into_iter().skip(skip) {
1150            self.tags.remove_at(p.byte(), Key::for_cursors());
1151        }
1152    }
1153
1154    /////////// Iterator methods
1155
1156    /// A forward iterator of the [chars and tags] of the [`Text`]
1157    ///
1158    /// [chars and tags]: Part
1159    pub fn iter_fwd(&self, at: impl TwoPoints) -> Iter<'_> {
1160        Iter::new_at(self, at)
1161    }
1162
1163    /// A reverse iterator of the [chars and tags] of the [`Text`]
1164    ///
1165    /// [chars and tags]: Part
1166    pub fn iter_rev(&self, at: impl TwoPoints) -> RevIter<'_> {
1167        RevIter::new_at(self, at)
1168    }
1169
1170    /// A forward iterator of the [`char`]s of the [`Text`]
1171    ///
1172    /// Each [`char`] will be accompanied by a [`Point`], which is the
1173    /// position where said character starts, e.g.
1174    /// [`Point::default()`] for the first character
1175    pub fn chars_fwd(&self, p: Point) -> impl Iterator<Item = (Point, char)> + '_ {
1176        self.strs_in_range_inner(p.byte()..)
1177            .into_iter()
1178            .flat_map(str::chars)
1179            .scan(p, |p, char| {
1180                let old_p = *p;
1181                *p = p.fwd(char);
1182                Some((old_p, char))
1183            })
1184    }
1185
1186    /// A reverse iterator of the [`char`]s of the [`Text`]
1187    ///
1188    /// Each [`char`] will be accompanied by a [`Point`], which is the
1189    /// position where said character starts, e.g.
1190    /// [`Point::default()`] for the first character
1191    pub fn chars_rev(&self, p: Point) -> impl Iterator<Item = (Point, char)> + '_ {
1192        self.strs_in_range_inner(..p.byte())
1193            .into_iter()
1194            .flat_map(str::chars)
1195            .rev()
1196            .scan(p, |p, char| {
1197                *p = p.rev(char);
1198                Some((*p, char))
1199            })
1200    }
1201
1202    /// A forward iterator over the [`Tag`]s of the [`Text`]
1203    ///
1204    /// # Note
1205    ///
1206    /// Duat works fine with [`Tag`]s in the middle of a codepoint,
1207    /// but external utilizers may not, so keep that in mind.
1208    pub fn tags_fwd(&self, at: usize) -> FwdTags {
1209        self.tags.fwd_at(at)
1210    }
1211
1212    /// An reverse iterator over the [`Tag`]s of the [`Text`]
1213    ///
1214    /// # Note
1215    ///
1216    /// Duat works fine with [`Tag`]s in the middle of a codepoint,
1217    /// but external utilizers may not, so keep that in mind.
1218    pub fn tags_rev(&self, at: usize) -> RevTags {
1219        self.tags.rev_at(at)
1220    }
1221
1222    /// The [`Cursors`] printed to this [`Text`], if they exist
1223    pub fn cursors(&self) -> Option<&Cursors> {
1224        self.cursors.as_ref().map(|b| b.as_ref())
1225    }
1226
1227    /// A mut reference to this [`Text`]'s [`Cursors`] if they exist
1228    pub fn cursors_mut(&mut self) -> Option<&mut Cursors> {
1229        self.cursors.as_mut().map(|b| b.as_mut())
1230    }
1231
1232    pub(crate) fn history(&self) -> Option<&History> {
1233        self.history.as_deref()
1234    }
1235}
1236
1237impl std::fmt::Debug for Text {
1238    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1239        f.debug_struct("Text")
1240            .field_with("buf", |f| {
1241                write!(f, "'{}', '{}'", self.strs()[0], self.strs()[1])
1242            })
1243            .field("tags", &self.tags)
1244            .field("records", &self.records)
1245            .finish()
1246    }
1247}
1248
1249impl Clone for Text {
1250    fn clone(&self) -> Self {
1251        Self {
1252            buf: self.buf.clone(),
1253            tags: self.tags.clone(),
1254            cursors: self.cursors.clone(),
1255            records: self.records.clone(),
1256            history: self.history.clone(),
1257            readers: Vec::new(),
1258            ts_parser: None,
1259            forced_new_line: self.forced_new_line,
1260            has_changed: self.has_changed,
1261            has_unsaved_changes: AtomicBool::new(false),
1262        }
1263    }
1264}
1265
1266impl<E> From<E> for Text
1267where
1268    E: DuatError,
1269{
1270    fn from(value: E) -> Self {
1271        *value.into_text()
1272    }
1273}
1274
1275impl From<std::io::Error> for Text {
1276    fn from(value: std::io::Error) -> Self {
1277        err!({ value.kind().to_string() })
1278    }
1279}
1280
1281impl PartialEq for Text {
1282    fn eq(&self, other: &Self) -> bool {
1283        self.buf == other.buf && self.tags == other.tags
1284    }
1285}
1286impl Eq for Text {}
1287
1288mod point {}
1289
1290mod part {}
1291
1292/// A list of [`Tag`]s to be added with a [`Cursor`]
1293fn cursor_tags(is_main: bool) -> [Tag; 3] {
1294    use tags::Tag::{ExtraCursor, MainCursor, PopForm, PushForm};
1295
1296    use crate::form::{E_SEL_ID, M_SEL_ID};
1297
1298    if is_main {
1299        [MainCursor, PopForm(M_SEL_ID), PushForm(M_SEL_ID)]
1300    } else {
1301        [ExtraCursor, PopForm(E_SEL_ID), PushForm(E_SEL_ID)]
1302    }
1303}
1304
1305/// Splits a range within a region
1306///
1307/// The first return is the part of `within` that must be updated.
1308/// The second return is what is left of `range`.
1309///
1310/// If `range` is fully inside `within`, remove `range`;
1311/// If `within` is fully inside `range`, split `range` in 2;
1312/// If `within` intersects `range` in one side, chop it off;
1313fn split_range_within(
1314    range: Range<usize>,
1315    within: Range<usize>,
1316) -> (Option<Range<usize>>, [Option<Range<usize>>; 2]) {
1317    if range.start >= within.end || within.start >= range.end {
1318        (None, [Some(range), None])
1319    } else {
1320        let start_range = (within.start > range.start).then_some(range.start..within.start);
1321        let end_range = (range.end > within.end).then_some(within.end..range.end);
1322        let split_ranges = [start_range, end_range];
1323        let range_to_check = range.start.max(within.start)..(range.end.min(within.end));
1324        (Some(range_to_check), split_ranges)
1325    }
1326}
1327
1328/// Merges a new range in a sorted list of ranges
1329///
1330/// Since ranges are not allowed to intersect, they will be sorted
1331/// both in their starting bound and in their ending bound.
1332fn merge_range_in(ranges: &mut Vec<Range<usize>>, range: Range<usize>) {
1333    let (r_range, start) = match ranges.binary_search_by_key(&range.start, |r| r.start) {
1334        // Same thing here
1335        Ok(i) => (i..i + 1, range.start),
1336        Err(i) => {
1337            // This is if we intersect the added part
1338            if let Some(older_i) = i.checked_sub(1)
1339                && range.start <= ranges[older_i].end
1340            {
1341                (older_i..i, ranges[older_i].start)
1342            // And here is if we intersect nothing on the
1343            // start, no changes drained.
1344            } else {
1345                (i..i, range.start)
1346            }
1347        }
1348    };
1349    let start_i = r_range.end;
1350    // Otherwise search ahead for another change to be merged
1351    let (r_range, end) = match ranges[start_i..].binary_search_by_key(&range.end, |r| r.start) {
1352        Ok(i) => (r_range.start..start_i + i + 1, ranges[start_i + i].end),
1353        Err(i) => {
1354            if let Some(older) = ranges.get(start_i + i)
1355                && older.start <= range.end
1356            {
1357                (r_range.start..start_i + i + 1, range.end.max(older.end))
1358            } else {
1359                (r_range.start..start_i + i, range.end)
1360            }
1361        }
1362    };
1363
1364    ranges.splice(r_range, [start..end]);
1365}
1366
1367impl From<&std::path::PathBuf> for Text {
1368    fn from(value: &std::path::PathBuf) -> Self {
1369        let value = value.to_str().unwrap_or("");
1370        Self::from(value)
1371    }
1372}
1373
1374impl_from_to_string!(u8);
1375impl_from_to_string!(u16);
1376impl_from_to_string!(u32);
1377impl_from_to_string!(u64);
1378impl_from_to_string!(u128);
1379impl_from_to_string!(usize);
1380impl_from_to_string!(i8);
1381impl_from_to_string!(i16);
1382impl_from_to_string!(i32);
1383impl_from_to_string!(i64);
1384impl_from_to_string!(i128);
1385impl_from_to_string!(isize);
1386impl_from_to_string!(f32);
1387impl_from_to_string!(f64);
1388impl_from_to_string!(char);
1389impl_from_to_string!(&str);
1390impl_from_to_string!(String);
1391impl_from_to_string!(Box<str>);
1392impl_from_to_string!(Rc<str>);
1393impl_from_to_string!(Arc<str>);
1394
1395/// Implements [`From<$t>`] for [`Text`]
1396macro impl_from_to_string($t:ty) {
1397    impl From<$t> for Text {
1398        fn from(value: $t) -> Self {
1399            let value = <$t as ToString>::to_string(&value);
1400            let buf = GapBuffer::from_iter(value.bytes());
1401            Self::from_buf(buf, None, false)
1402        }
1403    }
1404}
1405
1406pub struct TextLines<'a> {
1407    lines: std::str::Lines<'a>,
1408    fwd_i: usize,
1409    rev_i: usize,
1410}
1411
1412impl<'a> Iterator for TextLines<'a> {
1413    type Item = (usize, &'a str);
1414
1415    fn next(&mut self) -> Option<Self::Item> {
1416        self.lines.next().map(|line| {
1417            self.fwd_i += 1;
1418            (self.fwd_i - 1, line)
1419        })
1420    }
1421}
1422
1423impl DoubleEndedIterator for TextLines<'_> {
1424    fn next_back(&mut self) -> Option<Self::Item> {
1425        self.lines.next_back().map(|line| {
1426            self.rev_i -= 1;
1427            (self.rev_i, line)
1428        })
1429    }
1430}