use crate::block::cursor::Cursor;
use crate::block::ir::Ir;
use crate::block::scan;
use crate::pos::{Segment, Span};
use crate::syntax::liquid;
use super::state::StackMatch;
use super::{Engine, line_content_end, next_line_start};
pub(super) struct LiquidTag {
pub pieces: Vec<Segment>,
pub span: Span,
pub last_line_end: u32,
}
impl<'s> Engine<'s> {
#[expect(clippy::cast_possible_truncation)] pub(super) fn liquid_scan(
&mut self,
mut cur: Cursor<'s>,
content_end: u32,
) -> Option<LiquidTag> {
let closer = liquid::open(cur.tail()).expect("probed as a liquid opener");
let kind = usize::from(closer == b'}');
let start = cur.offset();
if start.saturating_add(2) <= self.liquid_dead[kind] {
return None;
}
let mut pieces = Vec::new();
let mut line_end = content_end;
let mut search_from = 2usize;
loop {
let tail = cur.tail();
if let Some(after) = liquid::close(&tail[search_from..], closer) {
let close = search_from + after;
if !scan::is_blank(&tail[close..]) {
self.liquid_dead[kind] = cur.offset() + close as u32 - 2;
return None;
}
let end = cur.offset() + close as u32;
pieces.push(cur.segment(end));
return Some(LiquidTag {
pieces,
span: Span::new(start, end),
last_line_end: line_end,
});
}
pieces.push(cur.segment(line_end));
let Some(next_start) = next_line_start(self.source, line_end) else {
self.liquid_dead[kind] = u32::MAX;
return None;
};
let next_end = line_content_end(self.source, next_start);
let mut next = self.line_cursor(next_start, next_end);
if !matches!(self.match_stack(&mut next), StackMatch::Prefixes(m) if m == self.stack.len())
{
return None;
}
if next
.flow_start()
.is_some_and(|c| self.probe(c.tail(), false).is_some_and(|s| s.is_container()))
{
return None;
}
cur = next;
line_end = next_end;
search_from = 0;
}
}
pub(super) fn open_liquid(&mut self, tag: LiquidTag) {
self.close_list_top();
self.attach(Ir::Liquid { pieces: tag.pieces, span: tag.span });
self.skip_to = tag.last_line_end;
}
}