Skip to main content

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