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