diff --git a/src/parser/plain_list.rs b/src/parser/plain_list.rs index 7311048..4d6e5cf 100644 --- a/src/parser/plain_list.rs +++ b/src/parser/plain_list.rs @@ -42,18 +42,27 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s class: ExitClass::Beta, exit_matcher: &plain_list_end, })); + // children stores tuple of (input string, parsed object) so we can re-parse the final item let mut children = Vec::new(); let mut first_item_indentation: Option = None; let mut remaining = input; + // The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here: + // + // 1. Parse all items while consuming trailing whitespace, then edit the final item to remove trailing whitespace. + // 2. Parse all items without consuming trailing whitespace, then edit all but the final one to add in the trailing whitespace. + // 3. Re-parse the final item with consume trailing whitespace disabled. + // + // While #3 is the most slow, it also seems to cleanest and involves the least manual mutation of already-parsed objects so I am going with #3 for now, but we should revisit #1 or #2 when the parser is more developed. + loop { let list_item = parser_with_context!(plain_list_item)(&parser_context)(remaining); match list_item { Ok((remain, item)) if item.indentation == *first_item_indentation.get_or_insert(item.indentation) => { + children.push((remaining, item)); remaining = remain; - children.push(item); } Ok(_) | Err(_) => { break; @@ -66,16 +75,31 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s } } - if children.is_empty() { - return Err(nom::Err::Error(CustomError::MyError(MyError( - "Plain lists require at least one element.", - )))); - } + let (final_child_start, _final_item_first_parse) = match children.pop() { + Some(final_child) => final_child, + None => { + return Err(nom::Err::Error(CustomError::MyError(MyError( + "Plain lists require at least one element.", + )))); + } + }; + let final_item_context = + parser_context.with_additional_node(ContextElement::ConsumeTrailingWhitespace(false)); + let (remaining, reparsed_final_item) = + parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?; + children.push((final_child_start, reparsed_final_item)); - // TODO: trailing whitespace + let (remaining, _trailing_ws) = + maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; let source = get_consumed(input, remaining); - Ok((remaining, PlainList { source, children })) + Ok(( + remaining, + PlainList { + source, + children: children.into_iter().map(|(_start, item)| item).collect(), + }, + )) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -109,6 +133,7 @@ pub fn plain_list_item<'r, 's>( let (remaining, _ws) = space1(remaining)?; let parser_context = context + .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) .with_additional_node(ContextElement::ListItem(indent_level)) .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Beta,