duat_core/text/
iter.rs

1//! The functions for iteration of [`Text`]s
2//!
3//! These functions will iterate over the text, reverse or forwards,
4//! keeping track of characters and [`Tag`]s, sending them in order,
5//! while also hiding the existance of certain "meta" tags, namely the
6//! [ghost] and [concealment] tags. This allows for a seemless
7//! iteration which is especially useful for printing, as the printer
8//! only needs to care about [`char`]s and [`Tag`]s, most of which are
9//! just [`Form`] changing [`Tag`]s
10//!
11//! [`Tag`]: super::Tag
12//! [ghost]: super::Ghost
13//! [concealment]: super::StartConceal
14//! [`Form`]: crate::form::Form
15use std::{
16    iter::{Chain, Rev},
17    str::Chars,
18};
19
20use super::{
21    Point, Text, ToggleId, TwoPoints,
22    tags::{self, RawTag},
23};
24use crate::mode::Selection;
25
26/// An [`Iterator`] over the [`Part`]s of the [`Text`].
27///
28/// This is useful for both printing and measurement of [`Text`], and
29/// can incorporate string replacements as part of its design.
30#[derive(Clone)]
31pub struct FwdIter<'a> {
32    text: &'a Text,
33    point: Point,
34    init_point: Point,
35    chars: FwdChars<'a>,
36    tags: tags::FwdTags<'a>,
37    conceals: u32,
38
39    // Things to deal with ghost text.
40    main_iter: Option<(Point, FwdChars<'a>, tags::FwdTags<'a>)>,
41    ghost: Option<(Point, usize)>,
42
43    // Configuration on how to iterate.
44    print_ghosts: bool,
45    _conceals: Conceal<'a>,
46}
47
48impl<'a> FwdIter<'a> {
49    /// Returns a new forward [`Iterator`] over the [`Item`]s in the
50    /// [`Text`]
51    pub(super) fn new_at(text: &'a Text, tp: impl TwoPoints) -> Self {
52        let (r, g) = tp.to_points();
53        let point = r.min(text.len());
54
55        let ghost = g.and_then(|offset| {
56            let (_, max) = text.ghost_max_points_at(r.byte());
57            max.map(|max| (max.min(offset), 0))
58        });
59
60        Self {
61            text,
62            point,
63            init_point: point,
64            chars: buf_chars_fwd(text, point.byte()),
65            tags: text.tags_fwd(point.byte()),
66            conceals: 0,
67
68            main_iter: None,
69            ghost,
70
71            print_ghosts: true,
72            _conceals: Conceal::All,
73        }
74    }
75
76    ////////// Iteration modifiers
77
78    /// Disable all [`Conceal`]s
79    ///
80    /// [`Conceal`]: super::Conceal
81    pub fn no_conceals(self) -> Self {
82        Self { _conceals: Conceal::None, ..self }
83    }
84
85    /// Disable all [`Conceal`]s containing any of the
86    /// `caret`s
87    ///
88    /// Not yet implemented
89    ///
90    /// [`Conceal`]: super::Conceal
91    pub fn dont_conceal_containing(self, list: &'a [Selection]) -> Self {
92        Self {
93            _conceals: Conceal::Excluding(list),
94            ..self
95        }
96    }
97
98    /// Disable all [`Ghost`]s
99    ///
100    /// [`Ghost`]: super::Ghost
101    pub fn no_ghosts(self) -> Self {
102        Self { print_ghosts: false, ..self }
103    }
104
105    /// Returns an [`Iterator`] over only the `char`s
106    ///
107    /// The difference betwen this and a regular [`Text::chars_fwd`]
108    /// is that this [`Iterator`] will return [`Ghost`] `char`s and
109    /// won't return `char`s that have been concealed
110    ///
111    /// [`Tag`]: super::Tag
112    /// [`Ghost`]: super::Ghost
113    pub fn no_tags(self) -> impl Iterator<Item = Item> + 'a {
114        self.filter(|item| item.part.is_char())
115    }
116
117    /// Skips to a certain [`TwoPoints`]
118    ///
119    /// Does nothing if the [`TwoPoints`] are behind.
120    pub fn skip_to(&mut self, tp: impl TwoPoints) {
121        *self = self.text.iter_fwd(tp.to_points().max(self.points()))
122    }
123
124    ////////// Querying functions
125
126    /// Wether the [`Iterator`] is on a [`Ghost`]
127    ///
128    /// [`Ghost`]: super::Ghost
129    #[inline(always)]
130    pub fn is_on_ghost(&self) -> bool {
131        self.main_iter.is_some()
132    }
133
134    /// Returns the current real and ghost [`Point`]s of the
135    /// [`Iterator`]
136    #[inline(always)]
137    pub fn points(&self) -> (Point, Option<Point>) {
138        if let Some((real, ..)) = self.main_iter.as_ref() {
139            (*real, self.ghost.map(|(tg, _)| tg))
140        } else {
141            (self.point, self.ghost.map(|(p, _)| p))
142        }
143    }
144
145    /// Handles special [`Tag`]s and [`Tag`] exceptions
146    ///
147    /// [`Tag`]: super::Tag
148    #[inline(always)]
149    fn handle_special_tag(&mut self, tag: &RawTag, b: usize) -> bool {
150        match tag {
151            RawTag::Ghost(_, id) => {
152                if !self.print_ghosts || b < self.point.byte() || self.conceals > 0 {
153                    return true;
154                }
155                let text = self.text.get_ghost(*id).unwrap();
156
157                let (this_ghost, total_ghost) = if let Some((ghost, dist)) = &mut self.ghost {
158                    if ghost.byte() >= *dist + text.len().byte() {
159                        *dist += text.len().byte();
160                        return true;
161                    }
162                    (text.point_at(ghost.byte() - *dist), *ghost)
163                } else {
164                    (Point::default(), Point::default())
165                };
166
167                let iter = text.iter_fwd(this_ghost);
168                let point = std::mem::replace(&mut self.point, this_ghost);
169                let chars = std::mem::replace(&mut self.chars, iter.chars);
170                let tags = std::mem::replace(&mut self.tags, iter.tags);
171
172                self.ghost = Some((total_ghost, total_ghost.byte()));
173                self.main_iter = Some((point, chars, tags));
174            }
175            RawTag::StartConceal(_) => {
176                self.conceals += 1;
177            }
178            RawTag::EndConceal(_) => {
179                self.conceals = self.conceals.saturating_sub(1);
180                if self.conceals == 0 {
181                    // If we have moved forward and were in a ghost, that ghost is no
182                    // longer valid.
183                    self.ghost.take_if(|_| self.point.byte() < b);
184                    self.point = self.point.max(self.text.point_at(b));
185                    self.chars = buf_chars_fwd(self.text, self.point.byte());
186                }
187            }
188            RawTag::ConcealUntil(b) => {
189                let point = self.text.point_at(*b as usize);
190                *self = FwdIter::new_at(self.text, point);
191                return false;
192            }
193            RawTag::MainCaret(_) | RawTag::ExtraCaret(_) | RawTag::Spacer(_)
194                if b < self.init_point.byte() => {}
195            _ => return false,
196        }
197
198        true
199    }
200}
201
202impl Iterator for FwdIter<'_> {
203    type Item = Item;
204
205    #[inline]
206    fn next(&mut self) -> Option<Self::Item> {
207        let tag = self.tags.peek();
208
209        if let Some(&(b, tag)) = tag
210            && (b <= self.point.byte() || self.conceals > 0)
211        {
212            self.tags.next();
213
214            if self.handle_special_tag(&tag, b) {
215                self.next()
216            } else {
217                Some(Item::new(self.points(), Part::from_raw(tag)))
218            }
219        } else if let Some(char) = self.chars.next() {
220            let points = self.points();
221            self.point = self.point.fwd(char);
222
223            self.ghost = match self.main_iter {
224                Some(..) => self.ghost.map(|(g, d)| (g.fwd(char), d + char.len_utf8())),
225                None => None,
226            };
227
228            Some(Item::new(points, Part::Char(char)))
229        } else if let Some(backup) = self.main_iter.take() {
230            (self.point, self.chars, self.tags) = backup;
231            self.next()
232        } else {
233            None
234        }
235    }
236}
237
238/// An [`Iterator`] over the [`Part`]s of the [`Text`].
239///
240/// This is useful for both printing and measurement of [`Text`], and
241/// can incorporate string replacements as part of its design.
242#[derive(Clone)]
243pub struct RevIter<'a> {
244    text: &'a Text,
245    point: Point,
246    init_point: Point,
247    chars: RevChars<'a>,
248    tags: tags::RevTags<'a>,
249    conceals: usize,
250
251    main_iter: Option<(Point, RevChars<'a>, tags::RevTags<'a>)>,
252    ghost: Option<(Point, usize)>,
253
254    // Iteration options:
255    print_ghosts: bool,
256    _conceals: Conceal<'a>,
257}
258
259impl<'a> RevIter<'a> {
260    /// Returns a new reverse [`Iterator`] over the [`Item`]s in the
261    /// [`Text`]
262    pub(super) fn new_at(text: &'a Text, tp: impl TwoPoints) -> Self {
263        let (r, g) = tp.to_points();
264        let point = r.min(text.len());
265
266        let ghost = g.and_then(|offset| {
267            let (_, max) = text.ghost_max_points_at(r.byte());
268            max.map(|max| (max.min(offset), max.byte()))
269        });
270
271        Self {
272            text,
273            point,
274            init_point: point,
275            chars: buf_chars_rev(text, point.byte()),
276            tags: text.tags_rev(point.byte()),
277            conceals: 0,
278
279            main_iter: None,
280            ghost,
281
282            print_ghosts: true,
283            _conceals: Conceal::All,
284        }
285    }
286
287    ////////// Iteration modifiers
288
289    /// Disable all [`Conceal`]s
290    ///
291    /// [`Conceal`]: super::Conceal
292    pub fn no_conceals(self) -> Self {
293        Self { _conceals: Conceal::None, ..self }
294    }
295
296    /// Disable all [`Ghost`]s
297    ///
298    /// [`Ghost`]: super::Ghost
299    pub fn no_ghosts(self) -> Self {
300        Self { print_ghosts: false, ..self }
301    }
302
303    /// Returns an [`Iterator`] over only the `char`s
304    ///
305    /// The difference betwen this and a regular [`Text::chars_rev`]
306    /// is that this [`Iterator`] will return [`Ghost`] `char`s and
307    /// won't return `char`s that have been concealed
308    ///
309    /// [`Tag`]: super::Tag
310    /// [`Ghost`]: super::Ghost
311    pub fn no_tags(self) -> impl Iterator<Item = Item> + 'a {
312        self.filter(|item| item.part.is_char())
313    }
314
315    ////////// Querying functions
316
317    /// Returns the current real and ghost [`Point`]s
318    pub fn points(&self) -> (Point, Option<Point>) {
319        if let Some((real, ..)) = self.main_iter.as_ref() {
320            (*real, Some(self.point))
321        } else {
322            (self.point, self.ghost.map(|(p, _)| p))
323        }
324    }
325
326    /// Wether the [`Iterator`] is on a [`Ghost`]
327    ///
328    /// [`Ghost`]: super::Ghost
329    pub fn is_on_ghost(&self) -> bool {
330        self.main_iter.is_some()
331    }
332
333    /// Handles special [`Tag`]s and [`Tag`] exceptions
334    ///
335    /// [`Tag`]: super::Tag
336    #[inline]
337    fn handled_meta_tag(&mut self, tag: &RawTag, b: usize) -> bool {
338        match tag {
339            RawTag::Ghost(_, id) => {
340                if !self.print_ghosts || b > self.point.byte() || self.conceals > 0 {
341                    return true;
342                }
343                let text = self.text.get_ghost(*id).unwrap();
344
345                let (ghost_b, offset) = if let Some((offset, dist)) = &mut self.ghost {
346                    if *dist - text.len().byte() >= offset.byte() {
347                        *dist -= text.len().byte();
348                        return true;
349                    }
350                    (
351                        text.point_at(offset.byte() + text.len().byte() - *dist),
352                        *offset,
353                    )
354                } else {
355                    let this = text.len();
356                    let (_, max) = self.text.ghost_max_points_at(b);
357                    (this, max.unwrap())
358                };
359
360                let iter = text.iter_rev(ghost_b);
361                let point = std::mem::replace(&mut self.point, offset);
362                let chars = std::mem::replace(&mut self.chars, iter.chars);
363                let tags = std::mem::replace(&mut self.tags, iter.tags);
364
365                self.ghost = Some((offset, offset.byte()));
366                self.main_iter = Some((point, chars, tags));
367            }
368
369            RawTag::StartConceal(_) => {
370                self.conceals = self.conceals.saturating_sub(1);
371                if self.conceals == 0 {
372                    self.ghost.take_if(|_| b < self.point.byte());
373                    self.point = self.point.min(self.text.point_at(b));
374                    self.chars = buf_chars_rev(self.text, self.point.byte());
375                }
376            }
377            RawTag::EndConceal(_) => self.conceals += 1,
378            RawTag::ConcealUntil(b) => {
379                let point = self.text.point_at(*b as usize);
380                *self = RevIter::new_at(self.text, point);
381                return false;
382            }
383            RawTag::MainCaret(_) | RawTag::ExtraCaret(_) | RawTag::Spacer(_)
384                if b > self.init_point.byte() => {}
385            _ => return false,
386        }
387
388        true
389    }
390}
391
392impl Iterator for RevIter<'_> {
393    type Item = Item;
394
395    #[inline]
396    fn next(&mut self) -> Option<Self::Item> {
397        let tag = self.tags.peek();
398
399        if let Some(&(b, tag)) = tag
400            && (b >= self.point.byte() || self.conceals > 0)
401        {
402            self.tags.next();
403
404            if self.handled_meta_tag(&tag, b) {
405                self.next()
406            } else {
407                Some(Item::new(self.points(), Part::from_raw(tag)))
408            }
409        } else if let Some(char) = self.chars.next() {
410            self.point = self.point.rev(char);
411
412            self.ghost = match self.main_iter {
413                Some(..) => self.ghost.map(|(g, d)| (g.rev(char), d - char.len_utf8())),
414                None => None,
415            };
416
417            Some(Item::new(self.points(), Part::Char(char)))
418        } else if let Some(last_iter) = self.main_iter.take() {
419            (self.point, self.chars, self.tags) = last_iter;
420            self.next()
421        } else {
422            None
423        }
424    }
425}
426
427fn buf_chars_fwd(text: &Text, b: usize) -> FwdChars<'_> {
428    let [s0, s1] = text.strs(b..).to_array();
429    s0.chars().chain(s1.chars())
430}
431
432fn buf_chars_rev(text: &Text, b: usize) -> RevChars<'_> {
433    let [s0, s1] = text.strs(..b).to_array();
434    s1.chars().rev().chain(s0.chars().rev())
435}
436
437/// An element of a [`Text`]
438///
439/// This struct is comprised of three parts:
440///
441/// - A real [`Point`], representing a position on the real [`Text`];
442/// - A ghost [`Point`], which is a position in a [`Ghost`], [`None`]
443///   if not in a [`Ghost`];
444/// - A [`Part`], which will either be a `char` or a [`Tag`];
445///
446/// [`Ghost`]: super::Ghost
447/// [`Tag`]: super::Tag
448#[derive(Debug, Clone, Copy)]
449pub struct Item {
450    /// The real [`Point`]
451    pub real: Point,
452    /// The [`Point`] in a [`Ghost`]
453    ///
454    /// If there are multiple [`Ghost`]s in the same byte, this
455    /// [`Point`] will point to a sum of the previous [`Text`]'s
456    /// [lengths] plus the position on this specific [`Ghost`], so
457    /// every [`Point`] should point to a specific position in a byte.
458    ///
459    /// [`Ghost`]: super::Ghost
460    /// [lengths]: Text::len
461    pub ghost: Option<Point>,
462    /// A [`Part`], which will either be a `char` or a [`Tag`];
463    ///
464    /// [`Tag`]: super::Tag
465    pub part: Part,
466}
467
468impl Item {
469    /// Returns a new [`Item`]
470    #[inline]
471    fn new(tp: impl TwoPoints, part: Part) -> Self {
472        let (real, ghost) = tp.to_points();
473        Self { real, ghost, part }
474    }
475
476    /// Whether this [`Item`] is in a [`Ghost`]
477    ///
478    /// [`Ghost`]: super::Ghost
479    pub fn is_real(&self) -> bool {
480        self.ghost.is_none()
481    }
482
483    /// Returns the real position, if not on a [`Ghost`]
484    ///
485    /// [`Ghost`]: super::Ghost
486    pub fn as_real_char(self) -> Option<(Point, char)> {
487        if self.ghost.is_none() {
488            Some(self.real).zip(self.part.as_char())
489        } else {
490            None
491        }
492    }
493
494    /// The real [byte](Point::byte)
495    pub fn byte(&self) -> usize {
496        self.real.byte()
497    }
498
499    /// The real [char](Point::char)
500    pub fn char(&self) -> usize {
501        self.real.char()
502    }
503
504    /// The real [line](Point::line)
505    pub fn line(&self) -> usize {
506        self.real.line()
507    }
508
509    /// The real and ghost [`Point`]s, can be used as [`TwoPoints`]
510    pub fn points(&self) -> (Point, Option<Point>) {
511        (self.real, self.ghost)
512    }
513}
514
515// To be rethought
516#[allow(dead_code)]
517#[derive(Debug, Default, Clone)]
518enum Conceal<'a> {
519    #[default]
520    All,
521    None,
522    Excluding(&'a [Selection]),
523    NotOnLineOf(&'a [Selection]),
524}
525
526type FwdChars<'a> = Chain<Chars<'a>, Chars<'a>>;
527type RevChars<'a> = Chain<Rev<Chars<'a>>, Rev<Chars<'a>>>;
528
529use crate::form::FormId;
530
531/// A part of the [`Text`], can be a [`char`] or a [`Tag`].
532///
533/// This type is used in iteration by [`Ui`]s in order to
534/// correctly print Duat's content. Additionally, you may be
535/// able to tell that there is no ghost text or concealment
536/// tags, and there is a [`ResetState`].
537///
538/// That is because the [`Text`]'s iteration process automatically
539/// gets rid of these tags, since, from the point of view of the
540/// ui, ghost text is just regular text, while conceals are
541/// simply the lack of text. And if the ui can handle printing
542/// regular text, printing ghost text should be a breeze.
543///
544/// [`Text`]: super::Text
545/// [`Tag`]: super::Tag
546/// [`Ui`]: crate::ui::Ui
547/// [`ResetState`]: Part::ResetState
548#[derive(Debug, Clone, Copy, PartialEq, Eq)]
549pub enum Part {
550    /// A printed `char`, can be real or a [`Ghost`]
551    ///
552    /// [`Ghost`]: super::Ghost
553    Char(char),
554    /// Push a [`Form`] to the [`Painter`]
555    ///
556    /// [`Form`]: crate::form::Form
557    /// [`Painter`]: crate::form::Painter
558    PushForm(FormId),
559    /// Pop a [`Form`] from the [`Painter`]
560    ///
561    /// [`Form`]: crate::form::Form
562    /// [`Painter`]: crate::form::Painter
563    PopForm(FormId),
564    /// Place the main `caret` or the `"MainCaret"` [`Form`] to
565    /// the [`Painter`]
566    ///
567    /// [`Form`]: crate::form::Form
568    /// [`Painter`]: crate::form::Painter
569    MainCaret,
570    /// Place the extra `caret` or the `"ExtraCaret"` [`Form`] to
571    /// the [`Painter`]
572    ///
573    /// [`Form`]: crate::form::Form
574    /// [`Painter`]: crate::form::Painter
575    ExtraCaret,
576    /// End other forms of alignment
577    AlignLeft,
578    /// Begin centered alignment
579    AlignCenter,
580    /// Begin right alignment
581    AlignRight,
582    /// Add a [`Spacer`]
583    ///
584    /// [`Spacer`]: super::Spacer
585    Spacer,
586    /// Starts a toggleable region for the given [`ToggleId`]
587    ///
588    /// Not yet implemented
589    ToggleStart(ToggleId),
590    /// Ends a toggleable region for the given [`ToggleId`]
591    ///
592    /// Not yet implemented
593    ToggleEnd(ToggleId),
594    /// Resets all [`FormId`]s, [`ToggleId`]s and alignments
595    ///
596    /// Used when a [`Conceal`] covers a large region, which Duat
597    /// optimizes by just not iterating over the [`Part`]s within.
598    /// This could skip some [`Tag`]s, so this variant serves the
599    /// purpose of terminating or initiating in place of skipped
600    /// [`Tag`]s
601    ///
602    /// [`Conceal`]: super::Conceal
603    /// [`Tag`]: super::Tag
604    ResetState,
605}
606
607impl Part {
608    /// Returns a new [`Part`] from a [`RawTag`]
609    #[inline]
610    pub(super) fn from_raw(value: RawTag) -> Self {
611        match value {
612            RawTag::PushForm(_, id, _) => Part::PushForm(id),
613            RawTag::PopForm(_, id) => Part::PopForm(id),
614            RawTag::MainCaret(_) => Part::MainCaret,
615            RawTag::ExtraCaret(_) => Part::ExtraCaret,
616            RawTag::StartAlignCenter(_) => Part::AlignCenter,
617            RawTag::EndAlignCenter(_) => Part::AlignLeft,
618            RawTag::StartAlignRight(_) => Part::AlignRight,
619            RawTag::EndAlignRight(_) => Part::AlignLeft,
620            RawTag::Spacer(_) => Part::Spacer,
621            RawTag::StartToggle(_, id) => Part::ToggleStart(id),
622            RawTag::EndToggle(_, id) => Part::ToggleEnd(id),
623            RawTag::ConcealUntil(_) => Part::ResetState,
624            RawTag::StartConceal(_) | RawTag::EndConceal(_) | RawTag::Ghost(..) => {
625                unreachable!("These tags are automatically processed elsewhere.")
626            }
627        }
628    }
629
630    /// Returns `true` if the part is [`Char`]
631    ///
632    /// [`Char`]: Part::Char
633    #[must_use]
634    #[inline]
635    pub fn is_char(&self) -> bool {
636        matches!(self, Part::Char(_))
637    }
638
639    /// Returns [`Some`] if the part is [`Char`]
640    ///
641    /// [`Char`]: Part::Char
642    #[inline]
643    pub fn as_char(&self) -> Option<char> {
644        if let Self::Char(v) = self {
645            Some(*v)
646        } else {
647            None
648        }
649    }
650
651    /// Returns `true` if the part is not [`Char`]
652    ///
653    /// [`Char`]: Part::Char
654    #[inline]
655    pub fn is_tag(&self) -> bool {
656        !self.is_char()
657    }
658}