use crate::render::format::OutputFormat;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum FrameKind {
Root,
Paragraph,
BlockQuote,
BulletList,
OrderedList,
ListItem,
Heading { level: u8 },
CodeBlock { lang: Option<String> },
Emph,
Strong,
Strikeout,
Link { url: String },
InlineCode,
Transparent,
}
struct Frame {
kind: FrameKind,
parts: Vec<String>,
}
impl Frame {
fn new(kind: FrameKind) -> Self {
Self {
kind,
parts: Vec::new(),
}
}
}
pub(crate) struct MarkupRenderer<'a, F: OutputFormat> {
fmt: &'a F,
stack: Vec<Frame>,
}
impl<'a, F: OutputFormat<Output = String>> MarkupRenderer<'a, F> {
pub(crate) fn new(fmt: &'a F) -> Self {
Self {
fmt,
stack: vec![Frame::new(FrameKind::Root)],
}
}
pub(crate) fn start_container(&mut self, kind: FrameKind) {
if kind == FrameKind::Root {
return;
}
self.stack.push(Frame::new(kind));
}
pub(crate) fn end_container(&mut self) {
if self.stack.len() <= 1 {
return;
}
if let Some(frame) = self.stack.pop() {
let content = self.apply_frame(frame);
if let Some(parent) = self.stack.last_mut() {
parent.parts.push(content);
}
}
}
fn apply_frame(&self, frame: Frame) -> String {
match frame.kind {
FrameKind::Root | FrameKind::Transparent => frame.parts.join(""),
FrameKind::Paragraph => self.fmt.paragraph(frame.parts.join("")),
FrameKind::BlockQuote => self.fmt.block_quote(frame.parts.join("")),
FrameKind::BulletList => self.fmt.bullet_list(frame.parts),
FrameKind::OrderedList => self.fmt.ordered_list(frame.parts),
FrameKind::ListItem => self.fmt.list_item(frame.parts.join("")),
FrameKind::Heading { level } => self.fmt.heading(level, frame.parts.join("")),
FrameKind::CodeBlock { ref lang } => {
self.fmt.code_block(lang.as_deref(), frame.parts.join(""))
}
FrameKind::Emph => self.fmt.emph(frame.parts.join("")),
FrameKind::Strong => self.fmt.strong(frame.parts.join("")),
FrameKind::Strikeout => self.fmt.strikeout(frame.parts.join("")),
FrameKind::Link { ref url } => self.fmt.link(url, frame.parts.join("")),
FrameKind::InlineCode => self.fmt.inline_code(frame.parts.join("")),
}
}
pub(crate) fn push_text(&mut self, text: String) {
let escaped = self.fmt.text(&text);
if let Some(frame) = self.stack.last_mut() {
frame.parts.push(escaped);
}
}
pub(crate) fn push_raw_text(&mut self, text: String) {
if let Some(frame) = self.stack.last_mut() {
frame.parts.push(text);
}
}
pub(crate) fn push_output(&mut self, output: String) {
if let Some(frame) = self.stack.last_mut() {
frame.parts.push(output);
}
}
pub(crate) fn push_soft_break(&mut self) {
if let Some(frame) = self.stack.last_mut() {
frame.parts.push(" ".to_string());
}
}
pub(crate) fn push_hard_break(&mut self) {
let br = self.fmt.hard_break();
if let Some(frame) = self.stack.last_mut() {
frame.parts.push(br);
}
}
pub(crate) fn finish(mut self) -> String {
while self.stack.len() > 1 {
if let Some(frame) = self.stack.pop() {
let content = self.apply_frame(frame);
if let Some(parent) = self.stack.last_mut() {
parent.parts.push(content);
}
}
}
self.stack
.pop()
.map(|f| f.parts.join(""))
.unwrap_or_default()
}
pub(crate) fn in_raw_context(&self) -> bool {
self.stack
.last()
.is_some_and(|f| matches!(f.kind, FrameKind::CodeBlock { .. } | FrameKind::InlineCode))
}
}