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