diff --git a/src/nodes/list.rs b/src/nodes/list.rs index e56e814..3e2f95f 100644 --- a/src/nodes/list.rs +++ b/src/nodes/list.rs @@ -14,6 +14,15 @@ pub enum ListTypes { Ordered, } +impl Display for ListTypes { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ListTypes::Unordered => f.write_str("-"), + ListTypes::Ordered => f.write_str("+"), + } + } +} + /// # List /// /// ## Types @@ -140,8 +149,9 @@ impl List { impl Display for List { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let level = String::from(" ").repeat(self.level); for n in self.body.iter() { - f.write_str(n.to_string().as_str())?; + f.write_str(format!("{}{} {}", level, self.list_type, n.to_string()).as_str())?; } Ok(()) } diff --git a/src/nodes/list_item.rs b/src/nodes/list_item.rs index c2fed97..4e1a736 100644 --- a/src/nodes/list_item.rs +++ b/src/nodes/list_item.rs @@ -2,43 +2,25 @@ use std::fmt::Display; use serde::Serialize; -use super::{List, ListTypes, ParagraphNodes}; +use super::{List, ParagraphNodes}; #[derive(Debug, PartialEq, Serialize, Clone, Eq)] pub struct ListItem { - pub list_type: ListTypes, - pub level: usize, pub text: Vec, pub nested_list: Option, } impl ListItem { - pub fn new( - list_type: ListTypes, - level: usize, - text: Vec, - nested_list: Option, - ) -> Self { - Self { - list_type, - level, - text, - nested_list, - } + pub fn new(text: Vec, nested_list: Option) -> Self { + Self { text, nested_list } } } impl Display for ListItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let list_type = match self.list_type { - ListTypes::Unordered => '-', - ListTypes::Ordered => '+', - }; write!( f, - "{}{} {}{}", - String::from(' ').repeat(self.level), - list_type, + "{}{}", self.text.iter().map(|n| n.to_string()).collect::(), self.nested_list .as_ref() diff --git a/src/parser/list.rs b/src/parser/list.rs index 7e9cba3..bfb23af 100644 --- a/src/parser/list.rs +++ b/src/parser/list.rs @@ -5,42 +5,11 @@ use crate::{ use super::{paragraph, Parser}; -pub(crate) fn list(p: &mut Parser) -> Option { - let mut state: State = State::Idle; - let start = p.pos(); - while let Some((t, _)) = p.peek() { - match t.kind { - TokenKind::Minus => { - state = State::NextLevelUnordered; - p.next_token(); - } - TokenKind::Plus => { - state = State::NextLevelOrdered; - p.next_token(); - } - TokenKind::Space if state == State::NextLevelUnordered => { - let Some(l) = parse_list(p, &ListTypes::Unordered, 0) else { - break; - }; - return Some(l); - } - TokenKind::Space if state == State::NextLevelOrdered => { - let Some(l) = parse_list(p, &ListTypes::Ordered, 0) else { - break; - }; - return Some(l); - } - _ => { - break; - } - } - } - p.move_to(start); - p.flip_to_literal_at(start); - None +pub(crate) fn list(p: &mut Parser, list_type: &ListTypes) -> Option { + parse_list(p, list_type, 0) } -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] enum State { NextLevel, NextLevelOrdered, @@ -55,23 +24,23 @@ enum State { fn parse_list(p: &mut Parser<'_>, list_type: &ListTypes, level: usize) -> Option { let start_pos = p.pos(); let mut list = List::new(list_type.clone(), level, vec![]); - let mut list_item = ListItem::new(list_type.clone(), level, vec![], None); - let mut state: State = State::Idle; + let mut list_item = ListItem::new(vec![], None); + let mut state: State = State::SameLevelCommit; p.next_token(); while let Some((t, pos)) = p.peek() { match t.kind { TokenKind::Terminator => break, - TokenKind::Space if state == State::Idle && t.slice.len() < level => { + TokenKind::Space if t.position.column == 0 && t.slice.len() < level => { state = State::PreviousLevel; p.next_token(); } - TokenKind::Space if state == State::Idle && t.slice.len() == level => { + TokenKind::Space if t.position.column == 0 && t.slice.len() == level => { state = State::SameLevel; p.next_token(); } - TokenKind::Space if state == State::Idle && t.slice.len() == level + 1 => { + TokenKind::Space if t.position.column == 0 && t.slice.len() == level + 1 => { state = State::NextLevel; p.next_token(); } @@ -83,37 +52,45 @@ fn parse_list(p: &mut Parser<'_>, list_type: &ListTypes, level: usize) -> Option state = State::NextLevelOrdered; p.next_token(); } - TokenKind::Minus - if t.slice.len() == 1 - && state == State::SameLevel - && list_type == &ListTypes::Unordered => - { - state = State::SameLevelCommit; + TokenKind::Minus if t.slice.len() == 1 && state == State::PreviousLevel => { + state = State::PreviousLevelCommit; p.next_token(); } - TokenKind::Plus - if t.slice.len() == 1 - && state == State::SameLevel - && list_type == &ListTypes::Ordered => - { - state = State::SameLevelCommit; + TokenKind::Plus if t.slice.len() == 1 && state == State::PreviousLevel => { + state = State::PreviousLevelCommit; p.next_token(); } - TokenKind::Minus if t.slice.len() == 1 && list_type == &ListTypes::Unordered => { + TokenKind::Minus if t.slice.len() == 1 && state == State::SameLevel => { state = State::SameLevelCommit; p.next_token(); } - - TokenKind::Plus if t.slice.len() == 1 && list_type == &ListTypes::Ordered => { + TokenKind::Plus if t.slice.len() == 1 && state == State::SameLevel => { state = State::SameLevelCommit; p.next_token(); } - TokenKind::Minus if t.slice.len() == 1 && state == State::PreviousLevel => { - state = State::PreviousLevelCommit; + + TokenKind::Minus + if t.slice.len() == 1 + && t.position.column == 0 + && list_type == &ListTypes::Unordered => + { + if level == 0 { + state = State::SameLevelCommit; + } else { + state = State::PreviousLevelCommit; + } p.next_token(); } - TokenKind::Plus if t.slice.len() == 1 && state == State::PreviousLevel => { - state = State::PreviousLevelCommit; + TokenKind::Plus + if t.slice.len() == 1 + && t.position.column == 0 + && list_type == &ListTypes::Ordered => + { + if level == 0 { + state = State::SameLevelCommit; + } else { + state = State::PreviousLevelCommit; + } p.next_token(); } TokenKind::Space if state == State::NextLevelUnordered => { @@ -121,7 +98,7 @@ fn parse_list(p: &mut Parser<'_>, list_type: &ListTypes, level: usize) -> Option if let Some(nested_list) = parse_list(p, &ListTypes::Unordered, level + 1) { list_item.nested_list.replace(nested_list); list.body.push(list_item); - list_item = ListItem::new(list_type.clone(), level, vec![], None); + list_item = ListItem::new(vec![], None); } else { p.next_token(); } @@ -131,20 +108,22 @@ fn parse_list(p: &mut Parser<'_>, list_type: &ListTypes, level: usize) -> Option if let Some(nested_list) = parse_list(p, &ListTypes::Ordered, level + 1) { list_item.nested_list.replace(nested_list); list.body.push(list_item); - list_item = ListItem::new(list_type.clone(), level, vec![], None); + list_item = ListItem::new(vec![], None); } else { p.next_token(); } } TokenKind::Space if state == State::SameLevelCommit => { state = State::Idle; - list.body.push(list_item); - list_item = ListItem::new(list_type.clone(), level, vec![], None); + if !list_item.text.is_empty() { + list.body.push(list_item); + list_item = ListItem::new(vec![], None); + } p.next_token(); } TokenKind::Space if state == State::PreviousLevelCommit => { - // back to EOL so Previous level can decide what to do. - p.move_to(pos - 3); + // back to new line so Previous level can decide what to do. + p.move_to(if level == 1 { pos - 1 } else { pos - 2 }); break; } _ => { @@ -186,23 +165,13 @@ mod tests { let mut p = Parser::new("- level 0\n- level 0"); assert_eq!( - list(&mut p), + list(&mut p, &ListTypes::Unordered), Some(List::new( ListTypes::Unordered, 0, vec![ - ListItem::new( - ListTypes::Unordered, - 0, - vec![String::from("level 0").into()], - None - ), - ListItem::new( - ListTypes::Unordered, - 0, - vec![String::from("level 0").into()], - None - ) + ListItem::new(vec![String::from("level 0").into()], None), + ListItem::new(vec![String::from("level 0").into()], None) ], )) ); @@ -213,23 +182,13 @@ mod tests { let mut p = Parser::new("+ level 0\n+ same level"); assert_eq!( - list(&mut p), + list(&mut p, &ListTypes::Ordered), Some(List::new( ListTypes::Ordered, 0, vec![ - ListItem::new( - ListTypes::Ordered, - 0, - vec![String::from("level 0").into()], - None - ), - ListItem::new( - ListTypes::Ordered, - 0, - vec![String::from("same level").into()], - None - ), + ListItem::new(vec![String::from("level 0").into()], None), + ListItem::new(vec![String::from("same level").into()], None), ], )) ); @@ -243,24 +202,17 @@ mod tests { ListTypes::Ordered, 0, vec![ListItem::new( - ListTypes::Ordered, - 0, vec![String::from("level 0").into()], Some(List::new( ListTypes::Unordered, 1, - vec![ListItem::new( - ListTypes::Unordered, - 1, - vec![String::from("level 0").into()], - None, - )], + vec![ListItem::new(vec![String::from("level 0").into()], None)], )), ) .into()], ); - assert_eq!(list(&mut p), Some(list_node)); + assert_eq!(list(&mut p, &ListTypes::Ordered), Some(list_node)); } #[test] @@ -273,23 +225,16 @@ mod tests { ListTypes::Unordered, 0, vec![ListItem::new( - ListTypes::Unordered, - 0, vec![String::from("one").into()], Some(List::new( ListTypes::Unordered, 1, - vec![ListItem::new( - ListTypes::Unordered, - 1, - vec![String::from("two").into()], - None, - )], + vec![ListItem::new(vec![String::from("two").into()], None)], )), )], ); - assert_eq!(list(&mut p), Some(list_node)); + assert_eq!(list(&mut p, &ListTypes::Unordered), Some(list_node)); } #[test] @@ -303,15 +248,11 @@ something"#; ListTypes::Unordered, 0, vec![ListItem::new( - ListTypes::Unordered, - 0, vec![String::from("one").into()], Some(List::new( ListTypes::Unordered, 1, vec![ListItem::new( - ListTypes::Unordered, - 1, vec![String::from("two\nsomething").into()], None, )], @@ -319,7 +260,7 @@ something"#; )], ); - assert_eq!(list(&mut p), Some(list_node)); + assert_eq!(list(&mut p, &ListTypes::Unordered), Some(list_node)); } #[test] @@ -327,13 +268,11 @@ something"#; let mut p = Parser::new("+ level 0\n- same level"); assert_eq!( - list(&mut p), + list(&mut p, &ListTypes::Ordered), Some(List::new( ListTypes::Ordered, 0, vec![ListItem::new( - ListTypes::Ordered, - 0, vec![ String::from("level 0").into(), String::from("- same level").into() @@ -349,13 +288,11 @@ something"#; let mut p = Parser::new("- level 0\n+ same level"); assert_eq!( - list(&mut p), + list(&mut p, &ListTypes::Unordered), Some(List::new( ListTypes::Unordered, 0, vec![ListItem::new( - ListTypes::Unordered, - 0, vec![ String::from("level 0").into(), String::from("+ same level").into() @@ -365,4 +302,58 @@ something"#; )) ); } + + #[test] + fn multiple_levels() { + let mut p = Parser::new( + "- Level 0\n - Level 1\n - Level 2\n - Level 1\n- Level 0\n - Level 1\n - Level 2\n- Level 0" + ); + + assert_eq!( + list(&mut p, &ListTypes::Unordered), + Some(List::new( + ListTypes::Unordered, + 0, + vec![ + ListItem::new( + vec![String::from("Level 0").into(),], + Some(List::new( + ListTypes::Unordered, + 1, + vec![ + ListItem::new( + vec![String::from("Level 1").into()], + Some(List::new( + ListTypes::Unordered, + 2, + vec![ListItem::new( + vec![String::from("Level 2").into()], + None + )] + )) + ), + ListItem::new(vec![String::from("Level 1").into()], None) + ] + )) + ), + ListItem::new( + vec![String::from("Level 0").into()], + Some(List::new( + ListTypes::Unordered, + 1, + vec![ListItem::new( + vec![String::from("Level 1").into()], + Some(List::new( + ListTypes::Unordered, + 2, + vec![ListItem::new(vec![String::from("Level 2").into()], None)] + )) + )] + )) + ), + ListItem::new(vec![String::from("Level 0").into()], None), + ], + )) + ); + } } diff --git a/src/parser/yamd.rs b/src/parser/yamd.rs index 6b626a5..f2ccdc0 100644 --- a/src/parser/yamd.rs +++ b/src/parser/yamd.rs @@ -1,6 +1,6 @@ use crate::{ lexer::{Token, TokenKind}, - nodes::{ThematicBreak, Yamd}, + nodes::{ListTypes, ThematicBreak, Yamd}, }; use super::{ @@ -24,12 +24,12 @@ where } } TokenKind::Minus if t.slice.len() == 1 => { - if let Some(l) = list(p) { + if let Some(l) = list(p, &ListTypes::Unordered) { yamd.body.push(l.into()) } } TokenKind::Plus if t.slice.len() == 1 => { - if let Some(l) = list(p) { + if let Some(l) = list(p, &ListTypes::Ordered) { yamd.body.push(l.into()) } } @@ -179,15 +179,11 @@ end"#; ListTypes::Unordered, 0, vec![ListItem::new( - ListTypes::Unordered, - 0, vec![String::from("one").into()], Some(List::new( ListTypes::Unordered, 1, vec![ListItem::new( - ListTypes::Unordered, - 1, vec![String::from("two").into()], None )]