use super::{CommentVec, Printer, ShareSite};
use crate::ast::Expression;
use smallvec::SmallVec;
use std::borrow::Borrow;
use tsv_lang::Comment;
use tsv_lang::Span;
use tsv_lang::doc::DocBuf;
use tsv_lang::doc::arena::DocId;
pub(in crate::printer) fn block_is_before_comma(
is_last: bool,
comma_pos: Option<u32>,
start: u32,
) -> bool {
is_last || comma_pos.is_none_or(|comma| start < comma)
}
pub(in crate::printer) fn run_defers_line(run: &[&Comment]) -> bool {
run.last().is_some_and(|c| !c.is_block)
}
pub(in crate::printer) fn next_real_element_start<'a, E: Borrow<Expression<'a>>>(
elements: &[Option<E>],
idx: usize,
) -> Option<u32> {
elements[idx + 1..]
.iter()
.find_map(|e| e.as_ref().map(|e| e.borrow().span().start))
}
pub(in crate::printer) struct TrailingComments<'a> {
before_comma: SmallVec<[&'a Comment; 2]>,
after_comma: SmallVec<[&'a Comment; 2]>,
line: Option<&'a Comment>,
behind_interior: Option<Span>,
pub(in crate::printer) leads_next: SmallVec<[&'a Comment; 2]>,
pub(in crate::printer) end_pos: u32,
}
impl TrailingComments<'_> {
pub(in crate::printer) fn is_behind_interior(&self) -> bool {
self.behind_interior.is_some()
}
}
impl<'a> Printer<'a> {
#[inline]
pub(in crate::printer) fn element_claim_anchor(frozen: Option<Span>, printed_end: u32) -> u32 {
frozen.map_or(printed_end, |slice| slice.end)
}
pub(in crate::printer) fn element_shell_end(&self, elem_end: u32, bound: u32) -> u32 {
let bytes = self.source.as_bytes();
let end = (bound as usize).min(bytes.len());
let mut i = elem_end as usize;
let mut shell_end = elem_end;
while i < end {
match bytes[i] {
b')' => {
i += 1;
shell_end = i as u32;
}
b' ' | b'\t' | b'\n' | b'\r' => i += 1,
_ => break,
}
}
shell_end
}
pub(in crate::printer) fn hole_slot_comma(&self, elem_end: u32, next_real: u32) -> u32 {
self.find_comma_in_range(elem_end, next_real)
.and_then(|comma| self.find_comma_in_range(comma + 1, next_real))
.unwrap_or(next_real)
}
pub(in crate::printer) fn inline_trailing_run_end(&self, item_end: u32, comma_pos: u32) -> u32 {
self.trailing_comment_run(item_end, comma_pos)
.last()
.map_or(item_end, |c| c.span.end)
}
pub(in crate::printer) fn push_item_trailing_run(
&self,
parts: &mut DocBuf,
item_end: u32,
next_start: u32,
) -> u32 {
let comma_pos = self.find_list_comma(item_end, next_start);
let run_end = self.inline_trailing_run_end(item_end, comma_pos);
self.append_trailing_inline_block_comments(parts, item_end, run_end);
run_end
}
pub(in crate::printer) fn trailing_comment_run(
&self,
start: u32,
end: u32,
) -> impl Iterator<Item = &'a Comment> + '_ {
let mut past_line_comment = false;
self.comments_to_emit_between(start, end)
.take_while(move |c| {
let claimed = !past_line_comment && self.comment_follows_content_on_its_line(c);
past_line_comment |= !c.is_block;
claimed
})
}
pub(in crate::printer) fn closer_trailing_comment_run(
&self,
item_end: u32,
end: u32,
) -> impl Iterator<Item = &'a Comment> + '_ {
let mut past_line_comment = false;
let mut anchor = item_end;
self.comments_to_emit_between(item_end, end)
.take_while(move |c| {
let claimed = !past_line_comment
&& if c.is_block {
self.comment_follows_content_on_its_line(c)
} else {
self.is_same_line(anchor, c.span.start)
};
if claimed {
anchor = c.span.end;
}
past_line_comment |= !c.is_block;
claimed
})
}
pub(in crate::printer) fn closer_trailing_run_end(&self, item_end: u32, end: u32) -> u32 {
self.closer_trailing_comment_run(item_end, end)
.last()
.map_or(item_end, |c| c.span.end)
}
pub(in crate::printer) fn collect_trailing_comments(
&self,
elem_end: u32,
upper_bound: u32,
is_last: bool,
) -> TrailingComments<'_> {
self.collect_element_trailing_comments(elem_end, upper_bound, is_last, None)
}
pub(in crate::printer) fn collect_element_trailing_comments(
&self,
elem_end: u32,
upper_bound: u32,
is_last: bool,
interior: Option<Span>,
) -> TrailingComments<'_> {
let behind_interior = interior.filter(|_| self.paren_interior_separates_run(interior));
if !self.has_comments_to_emit_between(elem_end, upper_bound) {
return TrailingComments {
before_comma: SmallVec::new(),
after_comma: SmallVec::new(),
line: None,
behind_interior,
leads_next: SmallVec::new(),
end_pos: elem_end,
};
}
let comma_pos = self.find_comma_in_range(elem_end, upper_bound);
let run: CommentVec<'_> = self.trailing_comment_run(elem_end, upper_bound).collect();
let defers_line = run_defers_line(&run);
let is_before_comma = |c: &Comment| {
block_is_before_comma(is_last, comma_pos, c.span.start)
&& (is_last || behind_interior.is_none())
};
let displaced = behind_interior.is_some() && !is_last;
let mut before_comma = SmallVec::new();
let mut after_comma = SmallVec::new();
let mut line = None;
let mut end_pos = elem_end;
for c in run {
if !c.is_block {
line = Some(c);
} else if is_before_comma(c) {
before_comma.push(c);
} else if defers_line
|| (displaced && !self.comment_hugs_next_item(c.span.end, upper_bound))
{
after_comma.push(c);
} else {
break;
}
end_pos = c.span.end;
}
let leads_next = if displaced
&& line.is_none()
&& self.run_leads_next_item(&after_comma, comma_pos, upper_bound)
{
std::mem::take(&mut after_comma)
} else {
SmallVec::new()
};
TrailingComments {
before_comma,
after_comma,
line,
behind_interior,
leads_next,
end_pos,
}
}
fn build_block_comments_doc(&self, comments: &[&Comment]) -> DocId {
let d = self.d();
let mut parts = DocBuf::new();
for comment in comments {
parts.push(d.text(" "));
parts.push(self.build_comment_doc(comment));
}
d.concat(&parts)
}
pub(in crate::printer) fn push_element_comma_trailing(
&self,
parts: &mut DocBuf,
trailing: &TrailingComments<'_>,
comma: Option<DocId>,
) {
if let Some(interior) = trailing.behind_interior {
let site = if let Some(comma) = comma {
parts.push(comma);
ShareSite::NextElement
} else {
ShareSite::Closer
};
let run = trailing
.before_comma
.iter()
.chain(&trailing.after_comma)
.chain(&trailing.line)
.copied();
self.push_element_share_and_run(parts, Some(interior), site, run);
return;
}
if !trailing.before_comma.is_empty() {
parts.push(self.build_block_comments_doc(&trailing.before_comma));
}
if let Some(comma) = comma {
parts.push(comma);
}
if !trailing.after_comma.is_empty() {
parts.push(self.build_block_comments_doc(&trailing.after_comma));
}
if let Some(comment) = trailing.line {
parts.push(self.build_trailing_line_comment_doc(comment));
}
}
}