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::Tag::GhostText
13//! [concealment]: super::Tag::StartConceal
14//! [`Form`]: crate::form::Form
15use std::{
16    iter::{Chain, Rev},
17    str::Chars,
18};
19
20use super::{
21    Point, Text, ToggleId, TwoPoints,
22    tags::{self, RawTag},
23};
24use crate::mode::Cursor;
25
26#[derive(Debug, Clone, Copy)]
27pub struct Item {
28    pub real: Point,
29    pub ghost: Option<Point>,
30    pub part: Part,
31}
32
33impl Item {
34    pub fn is_real(&self) -> bool {
35        self.ghost.is_none()
36    }
37
38    pub fn as_real_char(self) -> Option<(Point, char)> {
39        if self.ghost.is_none() {
40            Some(self.real).zip(self.part.as_char())
41        } else {
42            None
43        }
44    }
45
46    pub fn byte(&self) -> usize {
47        self.real.byte()
48    }
49
50    pub fn char(&self) -> usize {
51        self.real.char()
52    }
53
54    pub fn line(&self) -> usize {
55        self.real.line()
56    }
57
58    pub fn points(&self) -> (Point, Option<Point>) {
59        (self.real, self.ghost)
60    }
61
62    pub fn lines(&self) -> (usize, Option<usize>) {
63        (self.real.line(), self.ghost.map(|g| g.line()))
64    }
65
66    #[inline]
67    fn new(tp: impl TwoPoints, part: Part) -> Self {
68        let (real, ghost) = tp.to_points();
69        Self { real, ghost, part }
70    }
71}
72
73/// An [`Iterator`] over the [`Part`]s of the [`Text`].
74///
75/// This is useful for both printing and measurement of [`Text`], and
76/// can incorporate string replacements as part of its design.
77#[derive(Clone)]
78pub struct FwdIter<'a> {
79    text: &'a Text,
80    point: Point,
81    init_point: Point,
82    chars: FwdChars<'a>,
83    tags: tags::FwdTags<'a>,
84    conceals: u32,
85
86    // Things to deal with ghost text.
87    main_iter: Option<(Point, FwdChars<'a>, tags::FwdTags<'a>)>,
88    ghost: Option<(Point, usize)>,
89
90    // Configuration on how to iterate.
91    print_ghosts: bool,
92    _conceals: Conceal<'a>,
93}
94
95impl<'a> FwdIter<'a> {
96    pub(super) fn new_at(text: &'a Text, tp: impl TwoPoints) -> Self {
97        let (r, g) = tp.to_points();
98        let point = r.min(text.len());
99
100        let ghost = g.and_then(|offset| {
101            let (_, max) = text.ghost_max_points_at(r.byte());
102            max.map(|max| (max.min(offset), 0))
103        });
104
105        Self {
106            text,
107            point,
108            init_point: point,
109            chars: buf_chars_fwd(text, point.byte()),
110            tags: text.tags_fwd(point.byte()),
111            conceals: 0,
112
113            main_iter: None,
114            ghost,
115
116            print_ghosts: true,
117            _conceals: Conceal::All,
118        }
119    }
120
121    pub fn no_conceals(self) -> Self {
122        Self { _conceals: Conceal::None, ..self }
123    }
124
125    pub fn dont_conceal_containing(self, list: &'a [Cursor]) -> Self {
126        Self {
127            _conceals: Conceal::Excluding(list),
128            ..self
129        }
130    }
131
132    pub fn no_ghosts(self) -> Self {
133        Self { print_ghosts: false, ..self }
134    }
135
136    pub fn no_tags(self) -> impl Iterator<Item = Item> + 'a {
137        self.filter(|item| item.part.is_char())
138    }
139
140    pub fn skip_to(&mut self, tp: impl TwoPoints) {
141        *self = self.text.iter_fwd(tp.to_points().max(self.points()))
142    }
143
144    #[inline]
145    fn handle_special_tag(&mut self, tag: &RawTag, b: usize) -> bool {
146        match tag {
147            RawTag::GhostText(_, id) => {
148                if !self.print_ghosts || b < self.point.byte() || self.conceals > 0 {
149                    return true;
150                }
151                let text = self.text.get_ghost(*id).unwrap();
152
153                let (this_ghost, total_ghost) = if let Some((ghost, dist)) = &mut self.ghost {
154                    if ghost.byte() >= *dist + text.len().byte() {
155                        *dist += text.len().byte();
156                        return true;
157                    }
158                    (text.point_at(ghost.byte() - *dist), *ghost)
159                } else {
160                    (Point::default(), Point::default())
161                };
162
163                let iter = text.iter_fwd(this_ghost);
164                let point = std::mem::replace(&mut self.point, this_ghost);
165                let chars = std::mem::replace(&mut self.chars, iter.chars);
166                let tags = std::mem::replace(&mut self.tags, iter.tags);
167
168                self.ghost = Some((total_ghost, total_ghost.byte()));
169                self.main_iter = Some((point, chars, tags));
170            }
171            RawTag::StartConceal(_) => {
172                self.conceals += 1;
173            }
174            RawTag::EndConceal(_) => {
175                self.conceals = self.conceals.saturating_sub(1);
176                if self.conceals == 0 {
177                    // If we have moved forward and were in a ghost, that ghost is no
178                    // longer valid.
179                    self.ghost.take_if(|_| self.point.byte() < b);
180                    self.point = self.point.max(self.text.point_at(b));
181                    self.chars = buf_chars_fwd(self.text, self.point.byte());
182                }
183            }
184            RawTag::ConcealUntil(b) => {
185                let point = self.text.point_at(*b as usize);
186                *self = FwdIter::new_at(self.text, point);
187                return false;
188            }
189            RawTag::MainCursor(_) | RawTag::ExtraCursor(_) | RawTag::Spacer(_)
190                if b < self.init_point.byte() => {}
191            _ => return false,
192        }
193
194        true
195    }
196
197    pub fn on_ghost(&self) -> bool {
198        self.main_iter.is_some()
199    }
200
201    pub fn points(&self) -> (Point, Option<Point>) {
202        if let Some((real, ..)) = self.main_iter.as_ref() {
203            (*real, self.ghost.map(|(tg, _)| tg))
204        } else {
205            (self.point, self.ghost.map(|(p, _)| p))
206        }
207    }
208}
209
210impl Iterator for FwdIter<'_> {
211    type Item = Item;
212
213    #[inline]
214    fn next(&mut self) -> Option<Self::Item> {
215        let tag = self.tags.peek();
216
217        if let Some(&(b, tag)) = tag
218            && (b <= self.point.byte() || self.conceals > 0)
219        {
220            self.tags.next();
221
222            if self.handle_special_tag(&tag, b) {
223                self.next()
224            } else {
225                Some(Item::new(self.points(), Part::from_raw(tag)))
226            }
227        } else if let Some(char) = self.chars.next() {
228            let points = self.points();
229            self.point = self.point.fwd(char);
230
231            self.ghost = match self.main_iter {
232                Some(..) => self.ghost.map(|(g, d)| (g.fwd(char), d + char.len_utf8())),
233                None => None,
234            };
235
236            Some(Item::new(points, Part::Char(char)))
237        } else if let Some(backup) = self.main_iter.take() {
238            (self.point, self.chars, self.tags) = backup;
239            self.next()
240        } else {
241            None
242        }
243    }
244}
245
246/// An [`Iterator`] over the [`Part`]s of the [`Text`].
247///
248/// This is useful for both printing and measurement of [`Text`], and
249/// can incorporate string replacements as part of its design.
250#[derive(Clone)]
251pub struct RevIter<'a> {
252    text: &'a Text,
253    point: Point,
254    init_point: Point,
255    chars: RevChars<'a>,
256    tags: tags::RevTags<'a>,
257    conceals: usize,
258
259    main_iter: Option<(Point, RevChars<'a>, tags::RevTags<'a>)>,
260    ghost: Option<(Point, usize)>,
261
262    // Iteration options:
263    print_ghosts: bool,
264    _conceals: Conceal<'a>,
265}
266
267impl<'a> RevIter<'a> {
268    pub(super) fn new_at(text: &'a Text, tp: impl TwoPoints) -> Self {
269        let (r, g) = tp.to_points();
270        let point = r.min(text.len());
271
272        let ghost = g.and_then(|offset| {
273            let (_, max) = text.ghost_max_points_at(r.byte());
274            max.map(|max| (max.min(offset), max.byte()))
275        });
276
277        Self {
278            text,
279            point,
280            init_point: point,
281            chars: buf_chars_rev(text, point.byte()),
282            tags: text.tags_rev(point.byte()),
283            conceals: 0,
284
285            main_iter: None,
286            ghost,
287
288            print_ghosts: true,
289            _conceals: Conceal::All,
290        }
291    }
292
293    pub fn no_conceals(self) -> Self {
294        Self { _conceals: Conceal::None, ..self }
295    }
296
297    pub fn no_ghosts(self) -> Self {
298        Self { print_ghosts: false, ..self }
299    }
300
301    pub fn no_tags(self) -> impl Iterator<Item = Item> + 'a {
302        self.filter(|item| item.part.is_char())
303    }
304
305    #[inline]
306    fn handled_meta_tag(&mut self, tag: &RawTag, b: usize) -> bool {
307        match tag {
308            RawTag::GhostText(_, id) => {
309                if !self.print_ghosts || b > self.point.byte() || self.conceals > 0 {
310                    return true;
311                }
312                let text = self.text.get_ghost(*id).unwrap();
313
314                let (ghost_b, offset) = if let Some((offset, dist)) = &mut self.ghost {
315                    if *dist - text.len().byte() >= offset.byte() {
316                        *dist -= text.len().byte();
317                        return true;
318                    }
319                    (
320                        text.point_at(offset.byte() + text.len().byte() - *dist),
321                        *offset,
322                    )
323                } else {
324                    let this = text.len();
325                    let (_, max) = self.text.ghost_max_points_at(b);
326                    (this, max.unwrap())
327                };
328
329                let iter = text.iter_rev(ghost_b);
330                let point = std::mem::replace(&mut self.point, offset);
331                let chars = std::mem::replace(&mut self.chars, iter.chars);
332                let tags = std::mem::replace(&mut self.tags, iter.tags);
333
334                self.ghost = Some((offset, offset.byte()));
335                self.main_iter = Some((point, chars, tags));
336            }
337
338            RawTag::StartConceal(_) => {
339                self.conceals = self.conceals.saturating_sub(1);
340                if self.conceals == 0 {
341                    self.ghost.take_if(|_| b < self.point.byte());
342                    self.point = self.point.min(self.text.point_at(b));
343                    self.chars = buf_chars_rev(self.text, self.point.byte());
344                }
345            }
346            RawTag::EndConceal(_) => self.conceals += 1,
347            RawTag::ConcealUntil(b) => {
348                let point = self.text.point_at(*b as usize);
349                *self = RevIter::new_at(self.text, point);
350                return false;
351            }
352            RawTag::MainCursor(_) | RawTag::ExtraCursor(_) | RawTag::Spacer(_)
353                if b > self.init_point.byte() => {}
354            _ => return false,
355        }
356
357        true
358    }
359
360    pub fn points(&self) -> (Point, Option<Point>) {
361        if let Some((real, ..)) = self.main_iter.as_ref() {
362            (*real, Some(self.point))
363        } else {
364            (self.point, self.ghost.map(|(p, _)| p))
365        }
366    }
367
368    pub fn is_on_ghost(&self) -> bool {
369        self.main_iter.is_some()
370    }
371}
372
373impl Iterator for RevIter<'_> {
374    type Item = Item;
375
376    #[inline]
377    fn next(&mut self) -> Option<Self::Item> {
378        let tag = self.tags.peek();
379
380        if let Some(&(b, tag)) = tag
381            && (b >= self.point.byte() || self.conceals > 0)
382        {
383            self.tags.next();
384
385            if self.handled_meta_tag(&tag, b) {
386                self.next()
387            } else {
388                Some(Item::new(self.points(), Part::from_raw(tag)))
389            }
390        } else if let Some(char) = self.chars.next() {
391            self.point = self.point.rev(char);
392
393            self.ghost = match self.main_iter {
394                Some(..) => self.ghost.map(|(g, d)| (g.rev(char), d - char.len_utf8())),
395                None => None,
396            };
397
398            Some(Item::new(self.points(), Part::Char(char)))
399        } else if let Some(last_iter) = self.main_iter.take() {
400            (self.point, self.chars, self.tags) = last_iter;
401            self.next()
402        } else {
403            None
404        }
405    }
406}
407
408fn buf_chars_fwd(text: &Text, b: usize) -> FwdChars {
409    let [s0, s1] = text.strs(b..).to_array();
410    s0.chars().chain(s1.chars())
411}
412
413fn buf_chars_rev(text: &Text, b: usize) -> RevChars {
414    let [s0, s1] = text.strs(..b).to_array();
415    s1.chars().rev().chain(s0.chars().rev())
416}
417
418// To be rethought
419#[allow(dead_code)]
420#[derive(Debug, Default, Clone)]
421enum Conceal<'a> {
422    #[default]
423    All,
424    None,
425    Excluding(&'a [Cursor]),
426    NotOnLineOf(&'a [Cursor]),
427}
428
429type FwdChars<'a> = Chain<Chars<'a>, Chars<'a>>;
430type RevChars<'a> = Chain<Rev<Chars<'a>>, Rev<Chars<'a>>>;
431
432use crate::form::FormId;
433
434/// A part of the [`Text`], can be a [`char`] or a [`Tag`].
435///
436/// This type is used in iteration by [`Ui`]s in order to
437/// correctly print Duat's content. Additionally, you may be
438/// able to tell that there is no ghost text or concealment
439/// tags, and there is a [`ResetState`].
440///
441/// That is because the [`Text`]'s iteration process automatically
442/// gets rid of these tags, since, from the point of view of the
443/// ui, ghost text is just regular text, while conceals are
444/// simply the lack of text. And if the ui can handle printing
445/// regular text, printing ghost text should be a breeze.
446///
447/// [`Text`]: super::Text
448/// [`Tag`]: super::Tag
449/// [`Ui`]: crate::ui::Ui
450/// [`ResetState`]: Part::ResetState
451#[derive(Debug, Clone, Copy, PartialEq, Eq)]
452pub enum Part {
453    Char(char),
454    PushForm(FormId),
455    PopForm(FormId),
456    MainCursor,
457    ExtraCursor,
458    AlignLeft,
459    AlignCenter,
460    AlignRight,
461    Spacer,
462    ToggleStart(ToggleId),
463    ToggleEnd(ToggleId),
464    ResetState,
465}
466
467impl Part {
468    /// Returns a new [`Part`] from a [`RawTag`]
469    #[inline]
470    pub(super) fn from_raw(value: RawTag) -> Self {
471        match value {
472            RawTag::PushForm(_, id, _) => Part::PushForm(id),
473            RawTag::PopForm(_, id) => Part::PopForm(id),
474            RawTag::MainCursor(_) => Part::MainCursor,
475            RawTag::ExtraCursor(_) => Part::ExtraCursor,
476            RawTag::StartAlignCenter(_) => Part::AlignCenter,
477            RawTag::EndAlignCenter(_) => Part::AlignLeft,
478            RawTag::StartAlignRight(_) => Part::AlignRight,
479            RawTag::EndAlignRight(_) => Part::AlignLeft,
480            RawTag::Spacer(_) => Part::Spacer,
481            RawTag::ToggleStart(_, id) => Part::ToggleStart(id),
482            RawTag::ToggleEnd(_, id) => Part::ToggleEnd(id),
483            RawTag::ConcealUntil(_) => Part::ResetState,
484            RawTag::StartConceal(_) | RawTag::EndConceal(_) | RawTag::GhostText(..) => {
485                unreachable!("These tags are automatically processed elsewhere.")
486            }
487        }
488    }
489
490    /// Returns `true` if the part is [`Char`]
491    ///
492    /// [`Char`]: Part::Char
493    #[must_use]
494    #[inline]
495    pub fn is_char(&self) -> bool {
496        matches!(self, Part::Char(_))
497    }
498
499    /// Returns [`Some`] if the part is [`Char`]
500    ///
501    /// [`Char`]: Part::Char
502    #[inline]
503    pub fn as_char(&self) -> Option<char> {
504        if let Self::Char(v) = self {
505            Some(*v)
506        } else {
507            None
508        }
509    }
510
511    /// Returns `true` if the part is not [`Char`]
512    ///
513    /// [`Char`]: Part::Char
514    #[inline]
515    pub fn is_tag(&self) -> bool {
516        !self.is_char()
517    }
518}