From a739b0da61b28a1dff5002d1d1c0245fbfa4762b Mon Sep 17 00:00:00 2001 From: kirbylife Date: Wed, 7 Aug 2024 01:15:46 -0600 Subject: [PATCH] Fix nested lists --- src/lib.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e18b10f..ae22728 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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::BlockQuote) => state.start_block_quote(), 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::FootnoteDefinition(_)) => { 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::CodeBlock(_)) => state.finish_node(), 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::Table(_)) => state.finish_table_building(), md::Event::End(md::Tag::TableHead) => (), @@ -118,6 +118,8 @@ struct State { link_text_stack: Vec, table: Vec>, building_table: bool, + nested_list_level: Option, + list_items: Vec, } impl State { @@ -130,6 +132,8 @@ impl State { link_text_stack: vec![], table: vec![], 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) { - self.pending_node_type = NodeType::ListItem; + self.list_items.push(String::new()); } fn toggle_emphasis(&mut self) { @@ -191,6 +203,12 @@ impl State { 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) { let mut table = Table::new(); @@ -209,14 +227,31 @@ impl State { } 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) { - let text = self - .link_text_stack - .pop() - .unwrap_or_else(|| href.to_string()); + let text = if self.nested_list_level.is_some() { + href.to_string() + } else { + self.link_text_stack + .pop() + .unwrap_or_else(|| href.to_string()) + }; self.pending_other.push(gemtext::Node::Link { to: href.to_string(), name: Some(text), @@ -236,6 +271,11 @@ impl State { 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 fn finish_node(&mut self) { match ( @@ -249,13 +289,19 @@ impl State { let new_node = self.pending_node_type.take().construct(node_text); let last_cluster = self.nodes.last_mut().expect("empty cluster list??"); 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(); } 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_cell) = last_row.last_mut() { last_cell.push_str(&text.split("
").collect::>().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 { self.pending_node_content += "`"; self.pending_node_content += code; @@ -431,3 +483,28 @@ fn test_multi_tables() { "#; 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()); +}