Fix nested lists

main
kirbylife 2024-08-07 01:15:46 -06:00
parent 122f9d120b
commit a739b0da61
1 changed files with 87 additions and 10 deletions

View File

@ -19,7 +19,7 @@ pub fn convert(markdown_text: &str) -> String {
md::Event::Start(md::Tag::Heading(level)) => state.start_heading(level), md::Event::Start(md::Tag::Heading(level)) => state.start_heading(level),
md::Event::Start(md::Tag::BlockQuote) => state.start_block_quote(), md::Event::Start(md::Tag::BlockQuote) => state.start_block_quote(),
md::Event::Start(md::Tag::CodeBlock(_)) => state.start_code_block(), md::Event::Start(md::Tag::CodeBlock(_)) => state.start_code_block(),
md::Event::Start(md::Tag::List(_)) => (), md::Event::Start(md::Tag::List(_)) => state.start_list(),
md::Event::Start(md::Tag::Item) => state.start_list_item(), md::Event::Start(md::Tag::Item) => state.start_list_item(),
md::Event::Start(md::Tag::FootnoteDefinition(_)) => { md::Event::Start(md::Tag::FootnoteDefinition(_)) => {
unimplemented!("footnotes disabled") unimplemented!("footnotes disabled")
@ -39,7 +39,7 @@ pub fn convert(markdown_text: &str) -> String {
md::Event::End(md::Tag::BlockQuote) => (), md::Event::End(md::Tag::BlockQuote) => (),
md::Event::End(md::Tag::CodeBlock(_)) => state.finish_node(), md::Event::End(md::Tag::CodeBlock(_)) => state.finish_node(),
md::Event::End(md::Tag::List(_)) => state.finish_list(), md::Event::End(md::Tag::List(_)) => state.finish_list(),
md::Event::End(md::Tag::Item) => state.finish_node(), md::Event::End(md::Tag::Item) => state.finish_item(),
md::Event::End(md::Tag::FootnoteDefinition(_)) => unimplemented!("footnotes disabled"), md::Event::End(md::Tag::FootnoteDefinition(_)) => unimplemented!("footnotes disabled"),
md::Event::End(md::Tag::Table(_)) => state.finish_table_building(), md::Event::End(md::Tag::Table(_)) => state.finish_table_building(),
md::Event::End(md::Tag::TableHead) => (), md::Event::End(md::Tag::TableHead) => (),
@ -118,6 +118,8 @@ struct State {
link_text_stack: Vec<String>, link_text_stack: Vec<String>,
table: Vec<Vec<String>>, table: Vec<Vec<String>>,
building_table: bool, building_table: bool,
nested_list_level: Option<u8>,
list_items: Vec<String>,
} }
impl State { impl State {
@ -130,6 +132,8 @@ impl State {
link_text_stack: vec![], link_text_stack: vec![],
table: vec![], table: vec![],
building_table: false, building_table: false,
nested_list_level: None,
list_items: vec![],
} }
} }
@ -166,8 +170,16 @@ impl State {
} }
} }
fn start_list(&mut self) {
let level = match self.nested_list_level {
Some(n) => n + 1,
None => 0,
};
self.nested_list_level = Some(level);
}
fn start_list_item(&mut self) { fn start_list_item(&mut self) {
self.pending_node_type = NodeType::ListItem; self.list_items.push(String::new());
} }
fn toggle_emphasis(&mut self) { fn toggle_emphasis(&mut self) {
@ -191,6 +203,12 @@ impl State {
self.pending_node_content += "[image: "; self.pending_node_content += "[image: ";
} }
fn finish_item(&mut self) {
if self.nested_list_level.is_none() {
self.finish_node();
}
}
fn finish_table_building(&mut self) { fn finish_table_building(&mut self) {
let mut table = Table::new(); let mut table = Table::new();
@ -209,14 +227,31 @@ impl State {
} }
fn finish_list(&mut self) { fn finish_list(&mut self) {
self.nodes.push(vec![]); let level = match self.nested_list_level {
Some(0) => {
for item in self.list_items.clone() {
self.pending_node_type = NodeType::ListItem;
self.pending_node_content = item;
self.finish_node();
}
self.list_items.clear();
self.force_links();
None
}
Some(n) => Some(n - 1),
None => unreachable!("How can you finish a list without level?"),
};
self.nested_list_level = level;
} }
fn end_link(&mut self, href: &str) { fn end_link(&mut self, href: &str) {
let text = self let text = if self.nested_list_level.is_some() {
.link_text_stack href.to_string()
.pop() } else {
.unwrap_or_else(|| href.to_string()); self.link_text_stack
.pop()
.unwrap_or_else(|| href.to_string())
};
self.pending_other.push(gemtext::Node::Link { self.pending_other.push(gemtext::Node::Link {
to: href.to_string(), to: href.to_string(),
name: Some(text), name: Some(text),
@ -236,6 +271,11 @@ impl State {
self.pending_node_content += "]"; self.pending_node_content += "]";
} }
fn force_links(&mut self) {
let last_cluster = self.nodes.last_mut().expect("empty cluster list??");
last_cluster.extend(self.pending_other.drain(..));
}
// will create an empty paragraph if pending_text is empty // will create an empty paragraph if pending_text is empty
fn finish_node(&mut self) { fn finish_node(&mut self) {
match ( match (
@ -249,13 +289,19 @@ impl State {
let new_node = self.pending_node_type.take().construct(node_text); let new_node = self.pending_node_type.take().construct(node_text);
let last_cluster = self.nodes.last_mut().expect("empty cluster list??"); let last_cluster = self.nodes.last_mut().expect("empty cluster list??");
last_cluster.push(new_node); last_cluster.push(new_node);
last_cluster.extend(self.pending_other.drain(..)); if self.nested_list_level.is_none() {
last_cluster.extend(self.pending_other.drain(..));
}
self.pending_node_content = String::new(); self.pending_node_content = String::new();
} }
fn add_text(&mut self, text: &str) { fn add_text(&mut self, text: &str) {
if self.building_table { if self.nested_list_level.is_some() {
if let Some(last) = self.list_items.last_mut() {
last.push_str(text);
}
} else if self.building_table {
if let Some(last_row) = self.table.last_mut() { if let Some(last_row) = self.table.last_mut() {
if let Some(last_cell) = last_row.last_mut() { if let Some(last_cell) = last_row.last_mut() {
last_cell.push_str(&text.split("<br>").collect::<Vec<&str>>().join("\n")); last_cell.push_str(&text.split("<br>").collect::<Vec<&str>>().join("\n"));
@ -282,6 +328,12 @@ impl State {
); );
} }
} }
} else if self.nested_list_level.is_some() {
if let Some(last) = self.list_items.last_mut() {
last.push_str("`");
last.push_str(code);
last.push_str("`");
}
} else { } else {
self.pending_node_content += "`"; self.pending_node_content += "`";
self.pending_node_content += code; self.pending_node_content += code;
@ -431,3 +483,28 @@ fn test_multi_tables() {
"#; "#;
assert_eq!(convert(markdown).trim(), gemtext.trim()); assert_eq!(convert(markdown).trim(), gemtext.trim());
} }
#[cfg(test)]
#[test]
fn test_nested_list() {
let markdown = r#"
- item 1
- item 2
- subitem 2.1
- subitem [2.2](https://example.com)
- subitem [2.3](https://example.com)
- item 3
"#;
let gemtext = r#"
* item 1
* item 2
* subitem 2.1
* subitem 2.2
* subitem 2.3
* item 3
=> https://example.com https://example.com
=> https://example.com https://example.com
"#;
assert_eq!(convert(markdown).trim(), gemtext.trim());
}