use super::Handle;
use crate::markdown::{
construct_name::ConstructName,
state::{Info, State},
util::{
check_bullet::check_bullet, check_bullet_ordered::check_bullet_ordered,
check_bullet_other::check_bullet_other, check_rule::check_rule,
},
};
use crate::{
mdast::{List, Node},
message::Message,
};
impl Handle for List {
fn handle(
&self,
state: &mut State,
_info: &Info,
_parent: Option<&Node>,
node: &Node,
) -> Result<alloc::string::String, Message> {
state.enter(ConstructName::List);
let bullet_current = state.bullet_current;
let mut bullet = if self.ordered {
check_bullet_ordered(state)?
} else {
check_bullet(state)?
};
let bullet_other = if self.ordered {
if bullet == '.' {
')'
} else {
'.'
}
} else {
check_bullet_other(state)?
};
let mut use_different_marker = false;
if let Some(bullet_last_used) = state.bullet_last_used {
use_different_marker = bullet == bullet_last_used;
}
if !self.ordered {
let is_valid_bullet = bullet == '*' || bullet == '-';
let is_within_bounds = state.stack.len() >= 4 && state.index_stack.len() >= 3;
let first_list_item_has_no_children = !self.children.is_empty()
&& self.children[0]
.children()
.map(|inner| inner.is_empty())
.expect("There's at least one list item.");
if is_valid_bullet
&& is_within_bounds
&& first_list_item_has_no_children
&& state.stack[state.stack.len() - 1] == ConstructName::List
&& state.stack[state.stack.len() - 2] == ConstructName::ListItem
&& state.stack[state.stack.len() - 3] == ConstructName::List
&& state.stack[state.stack.len() - 4] == ConstructName::ListItem
&& state.index_stack[state.index_stack.len() - 1] == 0
&& state.index_stack[state.index_stack.len() - 2] == 0
&& state.index_stack[state.index_stack.len() - 3] == 0
{
use_different_marker = true;
}
if check_rule(state)? == bullet {
for child in &self.children {
let child_children = child
.children()
.map(|children| children.as_slice())
.unwrap_or(&[]);
if !child_children.is_empty()
&& matches!(child, Node::ListItem(_))
&& matches!(child_children[0], Node::ThematicBreak(_))
{
use_different_marker = true;
break;
}
}
}
}
if use_different_marker {
bullet = bullet_other;
}
state.bullet_current = Some(bullet);
let value = state.container_flow(node)?;
state.bullet_last_used = Some(bullet);
state.bullet_current = bullet_current;
state.exit();
Ok(value)
}
}
#[cfg(test)]
mod tests {
use super::Handle;
use crate::{
markdown::{
state::{Info, State},
Options,
},
mdast::{List, ListItem, Node, Paragraph, Text},
};
use alloc::vec;
#[test]
fn keeps_the_rule_marker_when_no_item_starts_with_a_thematic_break() {
let options = Options {
rule: '-',
..Options::default()
};
let mut state = State::new(&options);
let list = List {
children: vec![Node::ListItem(ListItem {
children: vec![Node::Paragraph(Paragraph {
children: vec![Node::Text(Text {
value: "ordinary item".into(),
position: None,
})],
position: None,
})],
position: None,
spread: false,
checked: None,
})],
position: None,
ordered: false,
start: None,
spread: false,
};
let node = Node::List(list.clone());
assert_eq!(
list.handle(&mut state, &Info::new("", ""), None, &node)
.unwrap(),
"- ordinary item"
);
}
#[test]
fn ignores_non_list_item_children_when_avoiding_rule_markers() {
let options = Options {
rule: '-',
..Options::default()
};
let mut state = State::new(&options);
let list = List {
children: vec![Node::Heading(crate::mdast::Heading {
children: vec![Node::Text(Text {
value: "heading".into(),
position: None,
})],
depth: 1,
position: None,
})],
position: None,
ordered: false,
start: None,
spread: false,
};
let node = Node::List(list.clone());
assert_eq!(
list.handle(&mut state, &Info::new("", ""), None, &node)
.unwrap(),
"# heading"
);
}
#[test]
fn changes_the_list_marker_when_an_item_starts_with_a_thematic_break() {
use crate::mdast::ThematicBreak;
let options = Options {
rule: '-',
..Options::default()
};
let mut state = State::new(&options);
let list = List {
children: vec![Node::ListItem(ListItem {
children: vec![
Node::ThematicBreak(ThematicBreak { position: None }),
Node::Paragraph(Paragraph {
children: vec![Node::Text(Text {
value: "following content".into(),
position: None,
})],
position: None,
}),
],
position: None,
spread: false,
checked: None,
})],
position: None,
ordered: false,
start: None,
spread: false,
};
let node = Node::List(list.clone());
let markdown = list
.handle(&mut state, &Info::new("", ""), None, &node)
.unwrap();
assert!(markdown.starts_with("* "), "{:?}", markdown);
}
#[test]
fn handles_empty_list_items_when_avoiding_rule_markers() {
let options = Options {
rule: '-',
..Options::default()
};
let mut state = State::new(&options);
let list = List {
children: vec![Node::ListItem(ListItem {
children: vec![],
position: None,
spread: false,
checked: None,
})],
position: None,
ordered: false,
start: None,
spread: false,
};
let node = Node::List(list.clone());
assert!(list
.handle(&mut state, &Info::new("", ""), None, &node)
.is_ok());
}
}