use std::{
iter::{Chain, Rev},
str::Chars,
};
use super::{
Point, SpawnId, Text,
tags::{self, RawTag},
};
use crate::{
form::MaskId,
text::{TwoPoints, tags::InnerTags},
};
#[derive(Clone)]
pub struct FwdIter<'t> {
text: &'t Text,
point: Point,
init_point: Point,
chars: FwdChars<'t>,
tags: tags::FwdTags<'t>,
conceals: u32,
main_iter: Option<MainIter<FwdChars<'t>, tags::FwdTags<'t>>>,
ghost: Option<(Point, usize)>,
}
impl<'t> FwdIter<'t> {
#[track_caller]
pub(super) fn new_at(text: &'t Text, points: TwoPoints, is_ghost: bool) -> Self {
let TwoPoints { real, ghost } = points;
let point = real.min(if is_ghost {
text.last_point()
} else {
text.end_point()
});
let ghost = if let Some(offset) = ghost {
let points = text.ghost_max_points_at(real.byte());
points.ghost.map(|max| (max.min(offset), 0))
} else {
let points = text.ghost_max_points_at(real.byte());
points.ghost.zip(Some(0))
};
Self {
text,
point,
init_point: point,
chars: buf_chars_fwd(text, point.byte(), is_ghost),
tags: text.tags_fwd(point.byte(), None),
conceals: 0,
main_iter: None,
ghost,
}
}
#[inline(always)]
pub fn is_on_ghost(&self) -> bool {
self.main_iter.is_some()
}
#[inline(always)]
pub fn points(&self) -> TwoPoints {
if let Some(MainIter { point, .. }) = self.main_iter.as_ref() {
TwoPoints::new(*point, self.ghost.map(|(tg, _)| tg).unwrap())
} else {
TwoPoints::new_after_ghost(self.point)
}
}
pub fn text(&self) -> &Text {
self.text
}
#[inline(always)]
fn handle_meta_tag(&mut self, tag: &RawTag, b: usize) -> bool {
match tag {
RawTag::Inlay(_, id) => {
if b < self.point.byte() || self.conceals > 0 {
return true;
}
let text = self.text.get_ghost(*id).unwrap();
let (this_ghost, total_ghost) = if let Some((ghost, dist)) = &mut self.ghost {
if ghost.byte() >= *dist + text.last_point().byte() {
*dist += text.last_point().byte();
return true;
}
(text.point_at_byte(ghost.byte() - *dist), *ghost)
} else {
(Point::default(), Point::default())
};
let iter = FwdIter::new_at(text, this_ghost.to_two_points_before(), true);
let point = std::mem::replace(&mut self.point, this_ghost);
let init_point = std::mem::replace(&mut self.init_point, this_ghost);
let chars = std::mem::replace(&mut self.chars, iter.chars);
let tags = std::mem::replace(&mut self.tags, iter.tags);
self.ghost = Some((total_ghost, total_ghost.byte()));
self.main_iter = Some(MainIter { point, init_point, chars, tags });
}
RawTag::StartConceal(_) => self.conceals += 1,
RawTag::EndConceal(_) => {
self.conceals = self.conceals.saturating_sub(1);
if self.conceals == 0 {
self.ghost.take_if(|_| self.point.byte() < b);
self.point = self.point.max(self.text.point_at_byte(b));
self.chars = buf_chars_fwd(self.text, self.point.byte(), false);
}
}
RawTag::ConcealUntil(b) => {
let point = self.text.point_at_byte(*b as usize);
*self = FwdIter::new_at(self.text, point.to_two_points_before(), false);
return false;
}
RawTag::Spacer(_) | RawTag::SpawnedWidget(..) | RawTag::Overlay(..)
if b < self.init_point.byte() => {}
RawTag::StartToggle(..) | RawTag::EndToggle(..) => {}
_ => return false,
}
true
}
}
impl<'t> Iterator for FwdIter<'t> {
type Item = TextPlace<'t>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if let Some((b, tag)) = self
.tags
.next_if(|(b, _)| *b <= self.point.byte() || self.conceals > 0)
{
if self.handle_meta_tag(&tag, b) {
self.next()
} else {
let tags = &self.text.0.tags;
Some(TextPlace::new(self.points(), TextPart::from_raw(tags, tag)))
}
} else if let Some(char) = self.chars.next() {
let points = self.points();
self.point = self.point.fwd(char);
self.ghost = match self.main_iter {
Some(..) => self.ghost.map(|(g, d)| (g.fwd(char), d + 1)),
None => None,
};
Some(TextPlace::new(points, TextPart::Char(char)))
} else if let Some(main_iter) = self.main_iter.take() {
self.point = main_iter.point;
self.init_point = main_iter.init_point;
self.chars = main_iter.chars;
self.tags = main_iter.tags;
self.next()
} else {
None
}
}
}
#[derive(Clone)]
pub struct RevIter<'t> {
text: &'t Text,
point: Point,
init_point: Point,
chars: RevChars<'t>,
tags: tags::RevTags<'t>,
conceals: usize,
main_iter: Option<MainIter<RevChars<'t>, tags::RevTags<'t>>>,
ghost: Option<(Point, usize)>,
}
impl<'t> RevIter<'t> {
#[track_caller]
pub(super) fn new_at(text: &'t Text, points: TwoPoints) -> Self {
let TwoPoints { real, ghost } = points;
let point = real.min(text.end_point());
let ghost = ghost.and_then(|offset| {
let points = text.ghost_max_points_at(real.byte());
points.ghost.map(|max| (max.min(offset), max.byte()))
});
Self {
text,
point,
init_point: point,
chars: buf_chars_rev(text, point.byte()),
tags: text.tags_rev(point.byte(), None),
conceals: 0,
main_iter: None,
ghost,
}
}
pub fn points(&self) -> TwoPoints {
if let Some(MainIter { point, .. }) = self.main_iter.as_ref() {
TwoPoints::new(*point, self.point)
} else if let Some((ghost, _)) = self.ghost {
TwoPoints::new(self.point, ghost)
} else {
TwoPoints::new_after_ghost(self.point)
}
}
pub fn text(&self) -> &'t Text {
self.text
}
pub fn is_on_ghost(&self) -> bool {
self.main_iter.is_some()
}
#[inline]
fn handled_meta_tag(&mut self, tag: &RawTag, b: usize) -> bool {
match tag {
RawTag::Inlay(_, id) => {
if b > self.point.byte() || self.conceals > 0 {
return true;
}
let text = self.text.get_ghost(*id).unwrap();
let (ghost_b, this_ghost) = if let Some((offset, dist)) = &mut self.ghost {
if *dist - text.last_point().byte() >= offset.byte() {
*dist -= text.last_point().byte();
return true;
}
(
text.point_at_byte(offset.byte() + text.last_point().byte() - *dist),
*offset,
)
} else {
let this = text.last_point();
let points = self.text.ghost_max_points_at(b);
(this, points.ghost.unwrap())
};
let iter = text.iter_rev(ghost_b.to_two_points_before());
let point = std::mem::replace(&mut self.point, this_ghost);
let init_point = std::mem::replace(&mut self.init_point, this_ghost);
let chars = std::mem::replace(&mut self.chars, iter.chars);
let tags = std::mem::replace(&mut self.tags, iter.tags);
self.ghost = Some((this_ghost, this_ghost.byte()));
self.main_iter = Some(MainIter { point, init_point, chars, tags });
}
RawTag::StartConceal(_) => {
self.conceals = self.conceals.saturating_sub(1);
if self.conceals == 0 {
self.ghost.take_if(|_| b < self.point.byte());
self.point = self.point.min(self.text.point_at_byte(b));
self.chars = buf_chars_rev(self.text, self.point.byte());
}
}
RawTag::EndConceal(_) => self.conceals += 1,
RawTag::ConcealUntil(b) => {
let point = self.text.point_at_byte(*b as usize);
*self = RevIter::new_at(self.text, point.to_two_points_before());
return false;
}
RawTag::Spacer(_) | RawTag::SpawnedWidget(..) | RawTag::Overlay(..)
if b > self.init_point.byte() => {}
RawTag::StartToggle(..) | RawTag::EndToggle(..) => {}
_ => return false,
}
true
}
}
impl<'t> Iterator for RevIter<'t> {
type Item = TextPlace<'t>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if let Some((b, tag)) = self
.tags
.next_if(|(b, _)| *b >= self.point.byte() || self.conceals > 0)
{
if self.handled_meta_tag(&tag, b) {
self.next()
} else {
let tags = &self.text.0.tags;
Some(TextPlace::new(self.points(), TextPart::from_raw(tags, tag)))
}
} else if let Some(char) = self.chars.next() {
self.point = self.point.rev(char);
self.ghost = match self.main_iter {
Some(..) => self.ghost.map(|(g, d)| (g.rev(char), d - 1)),
None => None,
};
Some(TextPlace::new(self.points(), TextPart::Char(char)))
} else if let Some(main_iter) = self.main_iter.take() {
self.point = main_iter.point;
self.init_point = main_iter.init_point;
self.chars = main_iter.chars;
self.tags = main_iter.tags;
self.next()
} else {
None
}
}
}
fn buf_chars_fwd(text: &Text, b: usize, minus_last_nl: bool) -> FwdChars<'_> {
let [s0, s1] = text
.slices(b..text.len() - minus_last_nl as usize)
.map(|s| unsafe { std::str::from_utf8_unchecked(s) });
s0.chars().chain(s1.chars())
}
fn buf_chars_rev(text: &Text, b: usize) -> RevChars<'_> {
let [s0, s1] = text
.slices(..b)
.map(|s| unsafe { std::str::from_utf8_unchecked(s) });
s1.chars().rev().chain(s0.chars().rev())
}
#[derive(Debug, Clone, Copy)]
pub struct TextPlace<'t> {
pub real: Point,
pub ghost: Option<Point>,
pub part: TextPart<'t>,
}
impl<'t> TextPlace<'t> {
#[inline]
const fn new(points: TwoPoints, part: TextPart<'t>) -> Self {
let TwoPoints { real, ghost } = points;
Self { real, ghost, part }
}
pub const fn is_real(&self) -> bool {
self.ghost.is_none()
}
pub const fn as_real_char(self) -> Option<(Point, char)> {
let Some(char) = self.part.as_char() else {
return None;
};
if self.ghost.is_none() {
Some((self.real, char))
} else {
None
}
}
pub const fn byte(&self) -> usize {
self.real.byte()
}
pub const fn char(&self) -> usize {
self.real.char()
}
pub const fn line(&self) -> usize {
self.real.line()
}
pub const fn points(&self) -> TwoPoints {
if let Some(ghost) = self.ghost {
TwoPoints::new(self.real, ghost)
} else {
TwoPoints::new_after_ghost(self.real)
}
}
}
type FwdChars<'t> = Chain<Chars<'t>, Chars<'t>>;
type RevChars<'t> = Chain<Rev<Chars<'t>>, Rev<Chars<'t>>>;
use crate::form::FormId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[doc(hidden)]
pub enum TextPart<'t> {
Char(char),
PushForm(FormId, u8),
PopForm(FormId),
Spacer,
Overlay(&'t Text),
SpawnedWidget(SpawnId),
PushMask(MaskId),
PopMask(MaskId),
ResetState,
}
impl<'t> TextPart<'t> {
#[inline]
pub(super) fn from_raw(tags: &'t InnerTags, value: RawTag) -> Self {
match value {
RawTag::PushForm(_, id, prio) => Self::PushForm(id, prio),
RawTag::PopForm(_, id) => Self::PopForm(id),
RawTag::Spacer(_) => Self::Spacer,
RawTag::ConcealUntil(_) => Self::ResetState,
RawTag::SpawnedWidget(_, id) => Self::SpawnedWidget(id),
RawTag::Overlay(_, id) => Self::Overlay(tags.get_ghost(id).unwrap()),
RawTag::PushMask(_, id) => Self::PushMask(id),
RawTag::PopMask(_, id) => Self::PopMask(id),
RawTag::StartConceal(_)
| RawTag::EndConceal(_)
| RawTag::Inlay(..)
| RawTag::StartToggle(..)
| RawTag::EndToggle(..) => {
unreachable!("These tags are automatically processed elsewhere.")
}
}
}
#[must_use]
#[inline]
pub const fn is_char(&self) -> bool {
matches!(self, TextPart::Char(_))
}
#[inline]
pub const fn is_tag(&self) -> bool {
!self.is_char()
}
#[inline]
pub const fn as_char(&self) -> Option<char> {
if let Self::Char(v) = self {
Some(*v)
} else {
None
}
}
}
#[derive(Debug, Clone)]
struct MainIter<Chars, Tags> {
point: Point,
init_point: Point,
chars: Chars,
tags: Tags,
}