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_meta_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::ReplaceChar(..)
212            | RawTag::SpawnedWidget(..)
213                if b < self.init_point.byte() => {}
214            _ => return false,
215        }
216
217        true
218    }
219}
220
221impl Iterator for FwdIter<'_> {
222    type Item = Item;
223
224    #[inline]
225    fn next(&mut self) -> Option<Self::Item> {
226        let tag = self.tags.peek();
227
228        if let Some(&(b, tag)) = tag
229            && (b <= self.point.byte() || self.conceals > 0)
230        {
231            self.tags.next();
232
233            if self.handle_meta_tag(&tag, b) {
234                self.next()
235            } else {
236                Some(Item::new(self.points(), Part::from_raw(tag)))
237            }
238        } else if let Some(char) = self.chars.next() {
239            let points = self.points();
240            self.point = self.point.fwd(char);
241
242            self.ghost = match self.main_iter {
243                Some(..) => self.ghost.map(|(g, d)| (g.fwd(char), d + 1)),
244                None => None,
245            };
246
247            Some(Item::new(points, Part::Char(char)))
248        } else if let Some(backup) = self.main_iter.take() {
249            (self.point, self.chars, self.tags) = backup;
250            self.next()
251        } else {
252            None
253        }
254    }
255}
256
257/// An [`Iterator`] over the [`Part`]s of the [`Text`].
258///
259/// This is useful for both printing and measurement of [`Text`], and
260/// can incorporate string replacements as part of its design.
261#[derive(Clone)]
262pub struct RevIter<'a> {
263    text: &'a Text,
264    point: Point,
265    init_point: Point,
266    chars: RevChars<'a>,
267    tags: tags::RevTags<'a>,
268    conceals: usize,
269
270    main_iter: Option<(Point, RevChars<'a>, tags::RevTags<'a>)>,
271    ghost: Option<(Point, usize)>,
272
273    // Iteration options:
274    print_ghosts: bool,
275    _conceals: Conceal<'a>,
276}
277
278impl<'a> RevIter<'a> {
279    /// Returns a new reverse [`Iterator`] over the [`Item`]s in the
280    /// [`Text`]
281    pub(super) fn new_at(text: &'a Text, points: TwoPoints) -> Self {
282        let TwoPoints { real, ghost } = points;
283        let point = real.min(text.len());
284
285        let ghost = ghost.and_then(|offset| {
286            let points = text.ghost_max_points_at(real.byte());
287            points.ghost.map(|max| (max.min(offset), max.byte()))
288        });
289
290        Self {
291            text,
292            point,
293            init_point: point,
294            chars: buf_chars_rev(text, point.byte()),
295            tags: text.tags_rev(point.byte(), None),
296            conceals: 0,
297
298            main_iter: None,
299            ghost,
300
301            print_ghosts: true,
302            _conceals: Conceal::All,
303        }
304    }
305
306    ////////// Iteration modifiers
307
308    /// Disable all [`Conceal`]s
309    ///
310    /// [`Conceal`]: super::Conceal
311    pub fn no_conceals(self) -> Self {
312        Self { _conceals: Conceal::None, ..self }
313    }
314
315    /// Disable all [`Ghost`]s
316    ///
317    /// [`Ghost`]: super::Ghost
318    pub fn no_ghosts(self) -> Self {
319        Self { print_ghosts: false, ..self }
320    }
321
322    /// Returns an [`Iterator`] over only the `char`s
323    ///
324    /// The difference betwen this and a regular [`Bytes::chars_rev`]
325    /// is that this [`Iterator`] will return [`Ghost`] `char`s and
326    /// won't return `char`s that have been concealed
327    ///
328    /// [`Tag`]: super::Tag
329    /// [`Ghost`]: super::Ghost
330    /// [`Bytes::chars_rev`]: super::Bytes::chars_rev
331    pub fn no_tags(self) -> impl Iterator<Item = Item> + 'a {
332        self.filter(|item| item.part.is_char())
333    }
334
335    ////////// Querying functions
336
337    /// Returns the current real and ghost [`Point`]s
338    pub fn points(&self) -> TwoPoints {
339        if let Some((real, ..)) = self.main_iter.as_ref() {
340            TwoPoints::new(*real, self.point)
341        } else if let Some((ghost, _)) = self.ghost {
342            TwoPoints::new(self.point, ghost)
343        } else {
344            TwoPoints::new_after_ghost(self.point)
345        }
346    }
347
348    /// The [`Text`] that's being iterated over
349    pub fn text(&self) -> &'a Text {
350        self.text
351    }
352
353    /// Wether the [`Iterator`] is on a [`Ghost`]
354    ///
355    /// [`Ghost`]: super::Ghost
356    pub fn is_on_ghost(&self) -> bool {
357        self.main_iter.is_some()
358    }
359
360    /// Handles special [`Tag`]s and [`Tag`] exceptions
361    ///
362    /// [`Tag`]: super::Tag
363    #[inline]
364    fn handled_meta_tag(&mut self, tag: &RawTag, b: usize) -> bool {
365        match tag {
366            RawTag::Ghost(_, id) => {
367                if !self.print_ghosts || b > self.point.byte() || self.conceals > 0 {
368                    return true;
369                }
370                let text = self.text.get_ghost(*id).unwrap();
371
372                let (ghost_b, offset) = if let Some((offset, dist)) = &mut self.ghost {
373                    if *dist - text.len().byte() >= offset.byte() {
374                        *dist -= text.len().byte();
375                        return true;
376                    }
377                    (
378                        text.point_at_byte(offset.byte() + text.len().byte() - *dist),
379                        *offset,
380                    )
381                } else {
382                    let this = text.len();
383                    let points = self.text.ghost_max_points_at(b);
384                    (this, points.ghost.unwrap())
385                };
386
387                let iter = text.iter_rev(ghost_b.to_two_points_before());
388                let point = std::mem::replace(&mut self.point, offset);
389                let chars = std::mem::replace(&mut self.chars, iter.chars);
390                let tags = std::mem::replace(&mut self.tags, iter.tags);
391
392                self.ghost = Some((offset, offset.byte()));
393                self.main_iter = Some((point, chars, tags));
394            }
395
396            RawTag::StartConceal(_) => {
397                self.conceals = self.conceals.saturating_sub(1);
398                if self.conceals == 0 {
399                    self.ghost.take_if(|_| b < self.point.byte());
400                    self.point = self.point.min(self.text.point_at_byte(b));
401                    self.chars = buf_chars_rev(self.text, self.point.byte());
402                }
403            }
404            RawTag::EndConceal(_) => self.conceals += 1,
405            RawTag::ConcealUntil(b) => {
406                let point = self.text.point_at_byte(*b as usize);
407                *self = RevIter::new_at(self.text, point.to_two_points_before());
408                return false;
409            }
410            RawTag::MainCaret(_)
411            | RawTag::ExtraCaret(_)
412            | RawTag::Spacer(_)
413            | RawTag::ReplaceChar(..)
414            | RawTag::SpawnedWidget(..)
415                if b > self.init_point.byte() => {}
416            _ => return false,
417        }
418
419        true
420    }
421}
422
423impl Iterator for RevIter<'_> {
424    type Item = Item;
425
426    #[inline]
427    fn next(&mut self) -> Option<Self::Item> {
428        let tag = self.tags.peek();
429
430        if let Some(&(b, tag)) = tag
431            && (b >= self.point.byte() || self.conceals > 0)
432        {
433            self.tags.next();
434
435            if self.handled_meta_tag(&tag, b) {
436                self.next()
437            } else {
438                Some(Item::new(self.points(), Part::from_raw(tag)))
439            }
440        } else if let Some(char) = self.chars.next() {
441            self.point = self.point.rev(char);
442
443            self.ghost = match self.main_iter {
444                Some(..) => self.ghost.map(|(g, d)| (g.rev(char), d - 1)),
445                None => None,
446            };
447
448            Some(Item::new(self.points(), Part::Char(char)))
449        } else if let Some(last_iter) = self.main_iter.take() {
450            (self.point, self.chars, self.tags) = last_iter;
451            self.next()
452        } else {
453            None
454        }
455    }
456}
457
458fn buf_chars_fwd(text: &Text, b: usize) -> FwdChars<'_> {
459    let [s0, s1] = text
460        .slices(b..)
461        .to_array()
462        .map(|s| unsafe { std::str::from_utf8_unchecked(s) });
463    s0.chars().chain(s1.chars())
464}
465
466fn buf_chars_rev(text: &Text, b: usize) -> RevChars<'_> {
467    let [s0, s1] = text
468        .slices(..b)
469        .to_array()
470        .map(|s| unsafe { std::str::from_utf8_unchecked(s) });
471    s1.chars().rev().chain(s0.chars().rev())
472}
473
474/// An element of a [`Text`]
475///
476/// This struct is comprised of three parts:
477///
478/// - A real [`Point`], representing a position on the real [`Text`];
479/// - A ghost [`Point`], which is a position in a [`Ghost`], [`None`]
480///   if not in a [`Ghost`];
481/// - A [`Part`], which will either be a `char` or a [`Tag`];
482///
483/// [`Ghost`]: super::Ghost
484/// [`Tag`]: super::Tag
485#[derive(Debug, Clone, Copy)]
486pub struct Item {
487    /// The real [`Point`]
488    pub real: Point,
489    /// The [`Point`] in a [`Ghost`]
490    ///
491    /// If there are multiple [`Ghost`]s in the same character, this
492    /// [`Point`] will point to a sum of the previous [`Text`]'s
493    /// [lengths] plus the position on this specific [`Ghost`], so
494    /// every [`Point`] should point to a specific position in a char.
495    ///
496    /// [`Ghost`]: super::Ghost
497    /// [lengths]: super::Bytes::len
498    pub ghost: Option<Point>,
499    /// A [`Part`], which will either be a `char` or a [`Tag`];
500    ///
501    /// [`Tag`]: super::Tag
502    pub part: Part,
503}
504
505impl Item {
506    /// Returns a new [`Item`]
507    #[inline]
508    const fn new(points: TwoPoints, part: Part) -> Self {
509        let TwoPoints { real, ghost } = points;
510        Self { real, ghost, part }
511    }
512
513    /// Whether this [`Item`] is in a [`Ghost`]
514    ///
515    /// [`Ghost`]: super::Ghost
516    pub const fn is_real(&self) -> bool {
517        self.ghost.is_none()
518    }
519
520    /// Returns the real position, if not on a [`Ghost`]
521    ///
522    /// [`Ghost`]: super::Ghost
523    pub const fn as_real_char(self) -> Option<(Point, char)> {
524        let Some(char) = self.part.as_char() else {
525            return None;
526        };
527        if self.ghost.is_none() {
528            Some((self.real, char))
529        } else {
530            None
531        }
532    }
533
534    /// The real [byte](Point::byte)
535    pub const fn byte(&self) -> usize {
536        self.real.byte()
537    }
538
539    /// The real [char](Point::char)
540    pub const fn char(&self) -> usize {
541        self.real.char()
542    }
543
544    /// The real [line](Point::line)
545    pub const fn line(&self) -> usize {
546        self.real.line()
547    }
548
549    /// The real and ghost [`Point`]s, can be used as [`TwoPoints`]
550    pub const fn points(&self) -> TwoPoints {
551        if let Some(ghost) = self.ghost {
552            TwoPoints::new(self.real, ghost)
553        } else {
554            TwoPoints::new_after_ghost(self.real)
555        }
556    }
557}
558
559// To be rethought
560#[allow(dead_code)]
561#[derive(Debug, Default, Clone)]
562enum Conceal<'a> {
563    #[default]
564    All,
565    None,
566    Excluding(&'a [Selection]),
567    NotOnLineOf(&'a [Selection]),
568}
569
570type FwdChars<'a> = Chain<Chars<'a>, Chars<'a>>;
571type RevChars<'a> = Chain<Rev<Chars<'a>>, Rev<Chars<'a>>>;
572
573use crate::form::FormId;
574
575/// A part of the [`Text`], can be a [`char`] or a [`Tag`].
576///
577/// This type is used in iteration by [Ui]s in order to
578/// correctly print Duat's content. Additionally, you may be
579/// able to tell that there is no ghost text or concealment
580/// tags, and there is a [`ResetState`].
581///
582/// That is because the [`Text`]'s iteration process automatically
583/// gets rid of these tags, since, from the point of view of the
584/// ui, ghost text is just regular text, while conceals are
585/// simply the lack of text. And if the ui can handle printing
586/// regular text, printing ghost text should be a breeze.
587///
588/// [`Text`]: super::Text
589/// [`Tag`]: super::Tag
590/// [Ui]: crate::ui::traits::RawUi
591/// [`ResetState`]: Part::ResetState
592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
593pub enum Part {
594    /// A printed `char`, can be real or a [`Ghost`]
595    ///
596    /// [`Ghost`]: super::Ghost
597    Char(char),
598    /// Push a [`Form`] to the [`Painter`]
599    ///
600    /// [`Form`]: crate::form::Form
601    /// [`Painter`]: crate::form::Painter
602    PushForm(FormId, u8),
603    /// Pop a [`Form`] from the [`Painter`]
604    ///
605    /// [`Form`]: crate::form::Form
606    /// [`Painter`]: crate::form::Painter
607    PopForm(FormId),
608    /// Place the main `caret` or the `"MainCaret"` [`Form`] to
609    /// the [`Painter`]
610    ///
611    /// [`Form`]: crate::form::Form
612    /// [`Painter`]: crate::form::Painter
613    MainCaret,
614    /// Place the extra `caret` or the `"ExtraCaret"` [`Form`] to
615    /// the [`Painter`]
616    ///
617    /// [`Form`]: crate::form::Form
618    /// [`Painter`]: crate::form::Painter
619    ExtraCaret,
620    /// Add a [`Spacer`]
621    ///
622    /// [`Spacer`]: super::Spacer
623    Spacer,
624    /// Replaces the next character, or the next space of a tab
625    ReplaceChar(char),
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::Spacer(_) => Part::Spacer,
664            RawTag::ReplaceChar(_, char) => Part::ReplaceChar(char),
665            RawTag::StartToggle(_, id) => Part::ToggleStart(id),
666            RawTag::EndToggle(_, id) => Part::ToggleEnd(id),
667            RawTag::ConcealUntil(_) => Part::ResetState,
668            RawTag::SpawnedWidget(_, id) => Part::SpawnedWidget(id),
669            RawTag::StartConceal(_) | RawTag::EndConceal(_) | RawTag::Ghost(..) => {
670                unreachable!("These tags are automatically processed elsewhere.")
671            }
672        }
673    }
674
675    /// Returns `true` if the part is [`Char`]
676    ///
677    /// [`Char`]: Part::Char
678    #[must_use]
679    #[inline]
680    pub const fn is_char(&self) -> bool {
681        matches!(self, Part::Char(_))
682    }
683
684    /// Returns `true` if the part is not [`Char`]
685    ///
686    /// [`Char`]: Part::Char
687    #[inline]
688    pub const fn is_tag(&self) -> bool {
689        !self.is_char()
690    }
691
692    /// Returns [`Some`] if the part is [`Char`]
693    ///
694    /// [`Char`]: Part::Char
695    #[inline]
696    pub const fn as_char(&self) -> Option<char> {
697        if let Self::Char(v) = self {
698            Some(*v)
699        } else {
700            None
701        }
702    }
703}