diff --git a/src/parser/plain_list.rs b/src/parser/plain_list.rs index ea6c20d0..54a790c8 100644 --- a/src/parser/plain_list.rs +++ b/src/parser/plain_list.rs @@ -3,6 +3,7 @@ use nom::bytes::complete::tag; use nom::character::complete::anychar; use nom::character::complete::digit1; use nom::character::complete::line_ending; +use nom::character::complete::multispace1; use nom::character::complete::one_of; use nom::character::complete::space0; use nom::character::complete::space1; @@ -17,6 +18,7 @@ use nom::multi::many0; use nom::multi::many1; use nom::multi::many_till; use nom::sequence::tuple; +use nom::InputTake; use super::affiliated_keyword::parse_affiliated_keywords; use super::element_parser::element; @@ -81,6 +83,28 @@ where return Err(nom::Err::Error(CustomError::Static("No element detected."))); } +#[cfg_attr( + feature = "tracing", + tracing::instrument(ret, level = "debug", skip(context)) +)] +pub(crate) fn detect_not_plain_list_item_indent<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, +) -> Res, (u16, OrgSource<'s>)> { + if let Ok((_remaining, (_, indent, _))) = tuple(( + start_of_line, + parser_with_context!(indentation_level)(context), + not(tuple(( + parser_with_context!(bullet)(context), + alt((space1, line_ending, eof)), + ))), + ))(input) + { + return Ok((input, indent)); + } + return Err(nom::Err::Error(CustomError::Static("No element detected."))); +} + #[cfg_attr( feature = "tracing", tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords)) @@ -204,15 +228,21 @@ fn plain_list_item<'b, 'g, 'r, 's>( }; let exit_matcher = plain_list_item_end(indent_level); + let final_item_whitespace_cutoff = final_item_whitespace_cutoff(indent_level); let contexts = [ ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Beta, exit_matcher: &exit_matcher, }), + ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Beta, + exit_matcher: &final_item_whitespace_cutoff, + }), ]; let parser_context = context.with_additional_node(&contexts[0]); let parser_context = parser_context.with_additional_node(&contexts[1]); + let parser_context = parser_context.with_additional_node(&contexts[2]); let maybe_contentless_item: Res, ()> = detect_contentless_item_contents(&parser_context, remaining); @@ -356,6 +386,52 @@ fn counter_set_value<'s>(input: OrgSource<'s>) -> Res, PlainListIt ))(input) } +const fn final_item_whitespace_cutoff(indent_level: IndentationLevel) -> impl ContextMatcher { + move |context, input: OrgSource<'_>| { + impl_final_item_whitespace_cutoff(context, input, indent_level) + } +} + +#[cfg_attr( + feature = "tracing", + tracing::instrument(ret, level = "debug", skip(context)) +)] +fn impl_final_item_whitespace_cutoff<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, + indent_level: IndentationLevel, +) -> Res, OrgSource<'s>> { + start_of_line(input)?; + // element!(plain_list_end, context, input); + + if let Ok((_remaining, _)) = verify( + tuple(( + opt(blank_line), + bind_context!(indentation_level, context), + not(multispace1), + )), + |(_, (depth, _stars), _not_whitespace)| *depth < indent_level, + )(input) + { + return Ok((input, input.take(0))); + } + + if let Ok((_remaining, _)) = tuple(( + opt(blank_line), + verify( + bind_context!(detect_not_plain_list_item_indent, context), + |(depth, _)| *depth == indent_level, + ), + ))(input) + { + return Ok((input, input.take(0))); + } + + Err(nom::Err::Error(CustomError::Static( + "No whitespace cut-off.", + ))) +} + #[cfg_attr( feature = "tracing", tracing::instrument(ret, level = "debug", skip(_context))