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