use super::super::printing::{
group_comment_gap, print_group, print_group_expanded_skip_first_comments,
print_group_skip_first_comments, push_gap_comments_and_break,
};
use super::super::types::ChainGroup;
use crate::printer::Printer;
use tsv_lang::ClassifiedComments;
use tsv_lang::doc::{DocBuf, arena::DocId};
pub(super) fn gap_has_break_forcing_comments(classified: &ClassifiedComments<'_>) -> bool {
!classified.trailing_line.is_empty()
|| !classified.leading_block.is_empty()
|| !classified.leading_line.is_empty()
}
pub(crate) struct ChainPartsBuilder<'a, 'p, 'pr> {
parts: &'p mut DocBuf,
printer: &'a Printer<'pr>,
use_expanded: bool,
}
impl<'a, 'p, 'pr> ChainPartsBuilder<'a, 'p, 'pr> {
pub(crate) fn new(
parts: &'p mut DocBuf,
printer: &'a Printer<'pr>,
use_expanded: bool,
group_count: usize,
) -> Self {
parts.reserve(group_count * 5);
Self {
parts,
printer,
use_expanded,
}
}
pub(crate) fn add_group(&mut self, group: &ChainGroup<'_>) {
self.add_comments_and_break(group);
self.add_group_doc(group);
}
pub(crate) fn add_group_no_break(&mut self, group: &ChainGroup<'_>) {
self.add_trailing_comments_only(group);
self.add_group_doc(group);
}
fn add_trailing_comments_only(&mut self, group: &ChainGroup<'_>) {
if let Some((object_end, property_start)) = group_comment_gap(group, self.printer) {
let classified = self.printer.classify_comments(object_end, property_start);
debug_assert!(
!gap_has_break_forcing_comments(&classified),
"a break-forcing comment reached the no-break chain path — \
`last_has_break_forcing_comments` should have routed this group to `add_group`"
);
self.parts.push(
self.printer
.build_trailing_block_doc(&classified.trailing_block),
);
}
}
fn add_comments_and_break(&mut self, group: &ChainGroup<'_>) {
if let Some((object_end, property_start)) = group_comment_gap(group, self.printer) {
push_gap_comments_and_break(
self.parts,
self.printer,
object_end,
property_start,
group
.nodes
.first()
.and_then(super::super::ChainNode::paren_gap_skip),
);
} else {
let d = self.printer.arena();
self.parts.push(d.hardline());
}
}
fn add_group_doc(&mut self, group: &ChainGroup<'_>) {
self.parts.push(if self.use_expanded {
print_group_expanded_skip_first_comments(group, self.printer)
} else {
print_group_skip_first_comments(group, self.printer)
});
}
}
pub(crate) fn build_rest_parts_with_comments<'a>(
parts: &mut DocBuf,
rest_groups: &[ChainGroup<'a>],
printer: &Printer<'_>,
use_expanded: bool,
) {
let last_is_simple_member = rest_groups.last().is_some_and(|g| {
g.nodes.len() == 1 && g.nodes.iter().all(|n| n.is_member() && !n.is_call())
});
let last_has_break_forcing_comments = last_is_simple_member
&& rest_groups.last().is_some_and(|g| {
group_comment_gap(g, printer).is_some_and(|(object_end, property_start)| {
let classified = printer.classify_comments(object_end, property_start);
gap_has_break_forcing_comments(&classified)
})
});
let mut builder = ChainPartsBuilder::new(parts, printer, use_expanded, rest_groups.len());
for (i, group) in rest_groups.iter().enumerate() {
let is_last = i == rest_groups.len() - 1;
if is_last && last_is_simple_member && !last_has_break_forcing_comments {
builder.add_group_no_break(group);
} else {
builder.add_group(group);
}
}
}
pub(super) fn build_expanded_chain_doc<'a>(
groups: &[ChainGroup<'a>],
split_at: usize,
printer: &Printer<'_>,
) -> DocId {
let d = printer.arena();
if groups.is_empty() {
return d.empty();
}
let (first_groups, rest) = groups.split_at(split_at.min(groups.len()));
let first_doc = d.concat_iter(first_groups.iter().map(|g| print_group(g, printer)));
if rest.is_empty() {
return first_doc;
}
let mut rest_parts = d.pooled_docbuf();
build_rest_parts_with_comments(&mut rest_parts, rest, printer, false);
d.concat(&[first_doc, d.indent(d.concat(&rest_parts))])
}
pub(super) fn build_expanded_doc<'a>(
groups: &[ChainGroup<'a>],
should_merge: bool,
printer: &Printer<'_>,
) -> DocId {
let split_at = if should_merge { 2 } else { 1 };
build_expanded_chain_doc(groups, split_at, printer)
}
pub(super) fn build_first_groups_doc<'a>(
first_groups: &[ChainGroup<'a>],
printer: &Printer<'_>,
) -> DocId {
let d = printer.arena();
d.concat_iter(first_groups.iter().map(|g| print_group(g, printer)))
}