use brink_syntax::ast::{self, AstNode};
use crate::provenance::NodeClass;
use crate::{Content, ContentPart, Stmt};
use super::super::conditional::{lower_inline_logic_into_parts, lower_multiline_block_from_inline};
use super::super::context::{LowerScope, LowerSink, Lowered};
use super::super::divert::LowerDivert;
use super::super::helpers::content_ends_with_glue;
use super::LowerBody;
use super::helpers::{lower_content_node_children, lower_tags};
pub enum ContentLineOutput {
Content {
content: Content,
divert: Option<Stmt>,
ends_with_glue: bool,
},
BareDivert(Stmt),
PromotedBlock {
stmt: Stmt,
trailing_content: Option<Content>,
divert: Option<Stmt>,
needs_eol: bool,
},
Empty,
}
impl LowerBody for ast::ContentLine {
type Output = ContentLineOutput;
fn lower_body(
&self,
scope: &LowerScope,
sink: &mut impl LowerSink,
) -> Lowered<ContentLineOutput> {
if let Some(mc) = self.mixed_content()
&& let Some(il) = mc.inline_logics().next()
&& let Some(stmt) = lower_multiline_block_from_inline(&il, scope, sink)
{
let trailing_parts = lower_trailing_parts(&mc, &il, scope, sink);
let trailing_content = if trailing_parts.is_empty() {
None
} else {
Some(Content {
ptr: None,
parts: trailing_parts,
tags: Vec::new(),
})
};
let divert = self
.divert()
.and_then(|dn| dn.lower_divert(scope, sink).ok());
let ends_glue = trailing_content
.as_ref()
.is_some_and(|c| content_ends_with_glue(&c.parts));
let needs_eol = (trailing_content.is_some() && !ends_glue && divert.is_none())
|| (trailing_content.is_none() && divert.is_none());
return Ok(ContentLineOutput::PromotedBlock {
stmt,
trailing_content,
divert,
needs_eol,
});
}
let parts = self
.mixed_content()
.map(|mc| lower_content_node_children(mc.syntax(), scope, sink))
.unwrap_or_default();
let tags = lower_tags(self.tags(), scope, sink);
if parts.is_empty() && tags.is_empty() {
if let Some(dn) = self.divert()
&& let Ok(stmt) = dn.lower_divert(scope, sink)
{
return Ok(ContentLineOutput::BareDivert(stmt));
}
return Ok(ContentLineOutput::Empty);
}
let ends_with_glue = content_ends_with_glue(&parts);
let divert = self
.divert()
.and_then(|dn| dn.lower_divert(scope, sink).ok());
Ok(ContentLineOutput::Content {
content: Content {
ptr: Some(scope.prov(NodeClass::Content, self.syntax())),
parts,
tags,
},
divert,
ends_with_glue,
})
}
}
fn lower_trailing_parts(
mc: &ast::MixedContent,
il: &ast::InlineLogic,
scope: &LowerScope,
sink: &mut impl LowerSink,
) -> Vec<ContentPart> {
let il_syntax = il.syntax().clone();
let mut past_promoted = false;
let mut trailing_parts = Vec::new();
let mut ws_before = false;
for child in mc.syntax().children_with_tokens() {
if let rowan::NodeOrToken::Node(ref child_node) = child
&& *child_node == il_syntax
{
past_promoted = true;
continue;
}
if !past_promoted {
continue;
}
let rowan::NodeOrToken::Node(child_node) = child else {
ws_before = child.kind() == brink_syntax::SyntaxKind::WHITESPACE;
continue;
};
let ws_before_this = std::mem::take(&mut ws_before);
{
match child_node.kind() {
brink_syntax::SyntaxKind::TEXT => {
let text = child_node.text().to_string();
if !text.is_empty() {
trailing_parts.push(ContentPart::Text(text));
}
}
brink_syntax::SyntaxKind::GLUE_NODE => {
super::helpers::push_glue(&mut trailing_parts, ws_before_this);
}
brink_syntax::SyntaxKind::ESCAPE => {
let text = child_node.text().to_string();
if text.len() > 1 {
trailing_parts.push(ContentPart::Text(text[1..].to_string()));
}
}
brink_syntax::SyntaxKind::INLINE_LOGIC => {
if let Some(inline) = ast::InlineLogic::cast(child_node) {
lower_inline_logic_into_parts(&inline, &mut trailing_parts, scope, sink);
}
}
_ => {}
}
}
}
trailing_parts
}