use brink_syntax::ast::{self, AstNode};
use rowan::TextRange;
use crate::Block;
use super::super::backbone::{BranchChild, classify_branch_child};
use super::super::choice::LowerChoice;
use super::super::content::{BodyBackend, ContentAccumulator, HandleResult};
use super::super::context::{LowerScope, LowerSink, Lowered};
use super::LowerBlock;
use super::weave::WeaveBackend;
impl LowerBlock for ast::MultilineBranchBody {
fn lower_block(&self, scope: &LowerScope, sink: &mut impl LowerSink) -> Lowered<Block> {
Ok(lower_branch_body_from_syntax(self.syntax(), scope, sink))
}
}
pub fn lower_branch_body(
body: &brink_syntax::SyntaxNode,
scope: &LowerScope,
sink: &mut impl LowerSink,
) -> Block {
lower_branch_body_from_syntax(body, scope, sink)
}
#[expect(
clippy::too_many_lines,
reason = "one arm per BranchChild variant — grows with the classifier \
(NS-A2 added the annotation-line arm)"
)]
fn lower_branch_body_from_syntax(
body: &brink_syntax::SyntaxNode,
scope: &LowerScope,
sink: &mut impl LowerSink,
) -> Block {
let mut acc = ContentAccumulator::new(WeaveBackend::new(), scope.file_id);
let mut pending_ws: Option<(String, TextRange)> = None;
let mut seen_content = false;
let mut after_content_block = false;
for child in body.children_with_tokens() {
match classify_branch_child(&child) {
BranchChild::ContentLine(cl) => {
pending_ws = None;
acc.handle(&cl, scope, sink);
}
BranchChild::LogicLine(ll) => {
pending_ws = None;
acc.handle(&ll, scope, sink);
}
BranchChild::TagLine(tl) => {
pending_ws = None;
acc.handle(&tl, scope, sink);
}
BranchChild::AnnotationLine(al) => {
super::super::directive::handle_annotation_line(&al, sink);
}
BranchChild::DivertNode(dn) => {
pending_ws = None;
acc.handle(&dn, scope, sink);
}
BranchChild::InlineLogic(il) => {
let range = il.syntax().text_range();
match acc.handle(&il, scope, sink) {
HandleResult::Block => {
pending_ws = None;
after_content_block = true;
}
HandleResult::Inline => {
flush_pending_ws(&mut acc, &mut pending_ws);
acc.note_range(range);
seen_content = true;
}
}
}
BranchChild::Text(t) => {
flush_pending_ws(&mut acc, &mut pending_ws);
seen_content = true;
let range = child.text_range();
acc.push_text(t, range);
}
BranchChild::Glue => {
if pending_ws.is_some() && acc.last_part_is_inline_construct() {
pending_ws = None;
acc.push_glue_after(child.text_range(), true);
} else {
flush_pending_ws(&mut acc, &mut pending_ws);
acc.push_glue(child.text_range());
}
seen_content = true;
}
BranchChild::Escape(t) => {
flush_pending_ws(&mut acc, &mut pending_ws);
seen_content = true;
let range = child.text_range();
acc.push_escape(&t, range);
}
BranchChild::Choice(c) => {
pending_ws = None;
acc.flush();
let depth = c.bullets().map_or(1, |b| b.depth());
if let Ok(choice) = c.lower_choice(scope, sink) {
acc.backend_mut().push_choice(choice, depth);
}
}
BranchChild::Trivia => {}
BranchChild::Stop => break,
BranchChild::Newline => {
if acc.has_buffered_parts() {
let ends_glue = acc.ends_with_glue();
acc.flush();
if !ends_glue {
acc.push_eol();
}
} else if after_content_block {
acc.push_eol();
}
seen_content = false;
pending_ws = None;
after_content_block = false;
}
BranchChild::Whitespace(ws) => {
if seen_content {
let range = child.text_range();
if let Some((ref mut existing, ref mut existing_range)) = pending_ws {
existing.push_str(&ws);
*existing_range = existing_range.cover(range);
} else {
pending_ws = Some((ws, range));
}
}
}
}
}
if acc.has_buffered_parts() {
let ends_glue = acc.ends_with_glue();
acc.flush();
if !ends_glue {
acc.push_eol();
}
}
acc.finish()
}
fn flush_pending_ws<B: BodyBackend>(
acc: &mut ContentAccumulator<B>,
pending_ws: &mut Option<(String, TextRange)>,
) {
if let Some((ws, range)) = pending_ws.take() {
acc.push_text(ws, range);
}
}