use crate::doc::{Doc, Elem, GroupKind, Spacing, TextKind};
impl Elem {
const fn is_hard_spacing(&self) -> bool {
matches!(
self,
Self::Spacing(
Spacing::Hardspace | Spacing::Hardline | Spacing::Emptyline | Spacing::Newlines(_)
)
)
}
fn is_comment(&self) -> bool {
match self {
Self::Text(_, _, TextKind::Comment | TextKind::TrailingComment, _) => true,
Self::Group(_, inner) => inner.iter().all(|x| x.is_comment() || x.is_hard_spacing()),
_ => false,
}
}
}
fn simplify_group(ann: GroupKind, mut body: Doc) -> Doc {
if body.len() == 1
&& matches!(&body[0], Elem::Group(a2, _) if ann == *a2)
&& let Some(Elem::Group(_, inner)) = body.0.pop()
{
return inner;
}
body
}
fn lift_bounds(body: &[Elem]) -> (usize, usize) {
let pre_end = body
.iter()
.position(|e| !e.is_hard_spacing() && !e.is_comment())
.unwrap_or(body.len());
let post_start = body
.iter()
.rposition(|e| !e.is_hard_spacing())
.map_or(0, |p| p + 1)
.max(pre_end);
(pre_end, post_start)
}
const HOLE: Elem = Elem::Spacing(Spacing::Softbreak);
enum GroupFixup {
Keep(Doc),
Dissolve { pre: Doc, post: Doc },
Lift { pre: Doc, core: Doc, post: Doc },
}
fn split_liftable(ann: GroupKind, mut body: Doc) -> GroupFixup {
let (pre_end, post_start) = lift_bounds(&body);
if pre_end == 0 && post_start == body.len() && !body.is_empty() {
return GroupFixup::Keep(simplify_group(ann, body));
}
let post = Doc(body.0.split_off(post_start));
let core = Doc(body.0.split_off(pre_end));
let pre = body;
if core.is_empty() {
GroupFixup::Dissolve { pre, post }
} else {
GroupFixup::Lift {
pre,
core: simplify_group(ann, core),
post,
}
}
}
#[allow(clippy::too_many_lines)]
pub(super) fn fixup_mut(
doc: &mut Vec<Elem>,
mut nest_acc: isize,
mut offset_acc: isize,
pull_hardspace: bool,
) -> bool {
let mut virtual_hs = pull_hardspace;
let mut read_idx = 0usize;
let mut write_idx = 0usize;
while read_idx < doc.len() {
let elem = std::mem::replace(&mut doc[read_idx], HOLE);
read_idx += 1;
match elem {
Elem::Nest(dn, doff) => {
nest_acc += dn;
offset_acc += doff;
}
Elem::Spacing(mut a) => {
if write_idx == 0 && virtual_hs {
a = Spacing::Hardspace.merge(a);
virtual_hs = false;
}
if let Some(Elem::Spacing(b)) = doc.get(read_idx) {
doc[read_idx] = Elem::Spacing(a.merge(*b));
} else if matches!(
write_idx.checked_sub(1).map(|i| &doc[i]),
Some(Elem::Spacing(_))
) {
if let Elem::Spacing(b) = &mut doc[write_idx - 1] {
*b = b.merge(a);
}
} else {
doc[write_idx] = Elem::Spacing(a);
write_idx += 1;
}
}
Elem::Text(nest, offset, ann, txt) => {
if write_idx > 0
&& let Elem::Text(_, _, prev_ann, prev_txt) = &mut doc[write_idx - 1]
&& ann == *prev_ann
{
prev_txt.push_str(&txt);
continue;
}
let nest = nest.cast_signed() + nest_acc;
let offset = offset.cast_signed() + offset_acc;
debug_assert!(nest >= 0 && offset >= 0, "unbalanced Nest deltas");
doc[write_idx] = Elem::Text(nest.cast_unsigned(), offset.cast_unsigned(), ann, txt);
write_idx += 1;
}
Elem::Group(ann, mut body) => {
let pull = if write_idx > 0 {
matches!(doc[write_idx - 1], Elem::Spacing(Spacing::Hardspace))
} else {
virtual_hs
};
if fixup_mut(&mut body.0, nest_acc, offset_acc, pull) {
if write_idx > 0 {
write_idx -= 1;
doc[write_idx] = HOLE;
} else {
virtual_hs = false;
}
}
match split_liftable(ann, body) {
GroupFixup::Keep(body) => {
doc[write_idx] = Elem::Group(ann, body);
write_idx += 1;
}
GroupFixup::Dissolve { pre, post } => {
let mut lifted = Vec::with_capacity(pre.len() + post.len() + 2);
lifted.push(Elem::Nest(-nest_acc, -offset_acc));
lifted.extend(pre);
lifted.extend(post);
lifted.push(Elem::Nest(nest_acc, offset_acc));
doc.splice(write_idx..read_idx, lifted);
read_idx = write_idx;
}
GroupFixup::Lift {
mut pre,
core,
post,
} => {
if write_idx > 0
&& let (Elem::Spacing(prev), Some(Elem::Spacing(first))) =
(&doc[write_idx - 1], pre.first())
{
let merged = prev.merge(*first);
doc[write_idx - 1] = Elem::Spacing(merged);
pre.0.remove(0);
}
let pre_len = pre.len();
doc.splice(
write_idx..read_idx,
pre.into_iter()
.chain(std::iter::once(Elem::Group(ann, core)))
.chain(post),
);
write_idx += pre_len + 1;
read_idx = write_idx;
}
}
}
}
}
doc.truncate(write_idx);
pull_hardspace && !virtual_hs
}