From 8fd9ff384895127e12991ff9e91e136408d2fe17 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Fri, 8 Dec 2023 15:51:38 -0500 Subject: [PATCH] Implement the new fields for bold, italic, underline, and strike-through. --- src/parser/radio_link.rs | 2 ++ src/parser/text_markup.rs | 61 +++++++++++++++++++++++++++------------ src/parser/util.rs | 13 +++++++-- src/types/object.rs | 40 ++++++++++++++++++++----- 4 files changed, 86 insertions(+), 30 deletions(-) diff --git a/src/parser/radio_link.rs b/src/parser/radio_link.rs index 903d64e..06710de 100644 --- a/src/parser/radio_link.rs +++ b/src/parser/radio_link.rs @@ -225,6 +225,8 @@ mod tests { let input = OrgSource::new("foo *bar* baz"); let radio_target_match = vec![Object::Bold(Bold { source: "*bar*", + contents: "bar", + post_blank: Some(" "), children: vec![Object::PlainText(PlainText { source: "bar" })], })]; let global_settings = GlobalSettings { diff --git a/src/parser/text_markup.rs b/src/parser/text_markup.rs index cdb1763..58662cf 100644 --- a/src/parser/text_markup.rs +++ b/src/parser/text_markup.rs @@ -3,11 +3,13 @@ use nom::bytes::complete::tag; use nom::character::complete::anychar; use nom::character::complete::multispace1; use nom::character::complete::one_of; -use nom::character::complete::space0; +use nom::character::complete::space1; use nom::combinator::all_consuming; +use nom::combinator::consumed; use nom::combinator::map; use nom::combinator::map_parser; use nom::combinator::not; +use nom::combinator::opt; use nom::combinator::peek; use nom::combinator::recognize; use nom::combinator::verify; @@ -76,12 +78,14 @@ fn bold<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Bold<'s>> { - let (remaining, children) = text_markup_object("*")(context, input)?; + let (remaining, (contents, children, post_blank)) = text_markup_object("*")(context, input)?; let source = get_consumed(input, remaining); Ok(( remaining, Bold { source: source.into(), + contents: contents.into(), + post_blank: post_blank.map(Into::<&str>::into), children, }, )) @@ -95,12 +99,14 @@ fn italic<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Italic<'s>> { - let (remaining, children) = text_markup_object("/")(context, input)?; + let (remaining, (contents, children, post_blank)) = text_markup_object("/")(context, input)?; let source = get_consumed(input, remaining); Ok(( remaining, Italic { source: source.into(), + contents: contents.into(), + post_blank: post_blank.map(Into::<&str>::into), children, }, )) @@ -114,12 +120,14 @@ fn underline<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Underline<'s>> { - let (remaining, children) = text_markup_object("_")(context, input)?; + let (remaining, (contents, children, post_blank)) = text_markup_object("_")(context, input)?; let source = get_consumed(input, remaining); Ok(( remaining, Underline { source: source.into(), + contents: contents.into(), + post_blank: post_blank.map(Into::<&str>::into), children, }, )) @@ -133,12 +141,14 @@ fn strike_through<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, StrikeThrough<'s>> { - let (remaining, children) = text_markup_object("+")(context, input)?; + let (remaining, (contents, children, post_blank)) = text_markup_object("+")(context, input)?; let source = get_consumed(input, remaining); Ok(( remaining, StrikeThrough { source: source.into(), + contents: contents.into(), + post_blank: post_blank.map(Into::<&str>::into), children, }, )) @@ -187,8 +197,10 @@ fn text_markup_object( ) -> impl for<'b, 'g, 'r, 's> Fn( RefContext<'b, 'g, 'r, 's>, OrgSource<'s>, -) -> Res, Vec>> - + '_ { +) -> Res< + OrgSource<'s>, + (OrgSource<'s>, Vec>, Option>), +> + '_ { move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol) } @@ -200,7 +212,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, marker_symbol: &'c str, -) -> Res, Vec>> { +) -> Res, (OrgSource<'s>, Vec>, Option>)> { let (remaining, _) = pre(context, input)?; let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, _peek_not_whitespace) = @@ -215,7 +227,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>( let initial_context = ContextElement::document_context(); let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context)); - let (remaining, children) = map_parser( + let (remaining, (contents, children)) = consumed(map_parser( verify( parser_with_context!(text_until_exit)(&parser_context), |text| text.len() > 0, @@ -225,7 +237,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>( &initial_context, )))(i) }), - )(remaining)?; + ))(remaining)?; { #[cfg(feature = "tracing")] @@ -240,9 +252,9 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>( } let (remaining, _close) = text_markup_end_specialized(context, remaining)?; - let (remaining, _trailing_whitespace) = + let (remaining, post_blank) = maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; - Ok((remaining, children)) + Ok((remaining, (contents, children, post_blank))) } fn text_markup_string( @@ -382,13 +394,15 @@ impl<'x> RematchObject<'x> for Bold<'x> { _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Object<'s>> { - let (remaining, children) = + let (remaining, (contents, children, post_blank)) = _rematch_text_markup_object(_context, input, "*", &self.children)?; let source = get_consumed(input, remaining); Ok(( remaining, Object::Bold(Bold { source: source.into(), + contents: contents.into(), + post_blank: post_blank.map(Into::<&str>::into), children, }), )) @@ -405,13 +419,15 @@ impl<'x> RematchObject<'x> for Italic<'x> { _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Object<'s>> { - let (remaining, children) = + let (remaining, (contents, children, post_blank)) = _rematch_text_markup_object(_context, input, "/", &self.children)?; let source = get_consumed(input, remaining); Ok(( remaining, Object::Italic(Italic { source: source.into(), + contents: contents.into(), + post_blank: post_blank.map(Into::<&str>::into), children, }), )) @@ -428,13 +444,15 @@ impl<'x> RematchObject<'x> for Underline<'x> { _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Object<'s>> { - let (remaining, children) = + let (remaining, (contents, children, post_blank)) = _rematch_text_markup_object(_context, input, "_", &self.children)?; let source = get_consumed(input, remaining); Ok(( remaining, Object::Underline(Underline { source: source.into(), + contents: contents.into(), + post_blank: post_blank.map(Into::<&str>::into), children, }), )) @@ -451,13 +469,15 @@ impl<'x> RematchObject<'x> for StrikeThrough<'x> { _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Object<'s>> { - let (remaining, children) = + let (remaining, (contents, children, post_blank)) = _rematch_text_markup_object(_context, input, "+", &self.children)?; let source = get_consumed(input, remaining); Ok(( remaining, Object::StrikeThrough(StrikeThrough { source: source.into(), + contents: contents.into(), + post_blank: post_blank.map(Into::<&str>::into), children, }), )) @@ -473,7 +493,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>( input: OrgSource<'s>, marker_symbol: &'static str, original_match_children: &'x Vec>, -) -> Res, Vec>> { +) -> Res, (OrgSource<'s>, Vec>, Option>)> { let (remaining, _) = pre(context, input)?; let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?; @@ -484,6 +504,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>( }); let parser_context = context.with_additional_node(&parser_context); + let contents_begin = remaining; let (remaining, children) = // TODO: This doesn't really check the exit matcher between each object. I think it may be possible to construct an org document that parses incorrectly with the current code. rematch_target(&parser_context, original_match_children, remaining)?; @@ -499,8 +520,10 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>( ))); } } + let contents_end = remaining; + let contents = contents_begin.get_until(contents_end); let (remaining, _close) = text_markup_end_specialized(context, remaining)?; - let (remaining, _trailing_whitespace) = space0(remaining)?; - Ok((remaining, children)) + let (remaining, post_blank) = opt(space1)(remaining)?; + Ok((remaining, (contents, children, post_blank))) } diff --git a/src/parser/util.rs b/src/parser/util.rs index 82f1250..29523f2 100644 --- a/src/parser/util.rs +++ b/src/parser/util.rs @@ -81,14 +81,21 @@ pub(crate) fn maybe_consume_object_trailing_whitespace_if_not_exiting<'b, 'g, 'r input: OrgSource<'s>, ) -> Res, Option>> { // We have to check exit matcher after each character because description list tags need to end with a space unconsumed (" ::"). - let (remaining, _) = many_till( + let (remaining, post_blank) = recognize(many_till( one_of(" \t"), alt(( peek(recognize(none_of(" \t"))), parser_with_context!(exit_matcher_parser)(context), )), - )(input)?; - Ok((remaining, None)) + ))(input)?; + Ok(( + remaining, + if post_blank.len() == 0 { + None + } else { + Some(post_blank) + }, + )) } #[cfg_attr( diff --git a/src/types/object.rs b/src/types/object.rs index de10ee5..40db622 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -43,24 +43,32 @@ pub enum Object<'s> { #[derive(Debug)] pub struct Bold<'s> { pub source: &'s str, + pub contents: &'s str, + pub post_blank: Option<&'s str>, pub children: Vec>, } #[derive(Debug)] pub struct Italic<'s> { pub source: &'s str, + pub contents: &'s str, + pub post_blank: Option<&'s str>, pub children: Vec>, } #[derive(Debug)] pub struct Underline<'s> { pub source: &'s str, + pub contents: &'s str, + pub post_blank: Option<&'s str>, pub children: Vec>, } #[derive(Debug)] pub struct StrikeThrough<'s> { pub source: &'s str, + pub contents: &'s str, + pub post_blank: Option<&'s str>, pub children: Vec>, } @@ -523,11 +531,15 @@ impl<'s> StandardProperties<'s> for Bold<'s> { } fn get_contents<'b>(&'b self) -> Option<&'s str> { - todo!() + Some(self.contents) } fn get_post_blank(&self) -> PostBlank { - todo!() + self.post_blank + .map(|post_blank| post_blank.chars().count()) + .unwrap_or(0) + .try_into() + .expect("Too much post-blank to fit into a PostBlank.") } } @@ -537,11 +549,15 @@ impl<'s> StandardProperties<'s> for Italic<'s> { } fn get_contents<'b>(&'b self) -> Option<&'s str> { - todo!() + Some(self.contents) } fn get_post_blank(&self) -> PostBlank { - todo!() + self.post_blank + .map(|post_blank| post_blank.chars().count()) + .unwrap_or(0) + .try_into() + .expect("Too much post-blank to fit into a PostBlank.") } } @@ -551,11 +567,15 @@ impl<'s> StandardProperties<'s> for Underline<'s> { } fn get_contents<'b>(&'b self) -> Option<&'s str> { - todo!() + Some(self.contents) } fn get_post_blank(&self) -> PostBlank { - todo!() + self.post_blank + .map(|post_blank| post_blank.chars().count()) + .unwrap_or(0) + .try_into() + .expect("Too much post-blank to fit into a PostBlank.") } } @@ -565,11 +585,15 @@ impl<'s> StandardProperties<'s> for StrikeThrough<'s> { } fn get_contents<'b>(&'b self) -> Option<&'s str> { - todo!() + Some(self.contents) } fn get_post_blank(&self) -> PostBlank { - todo!() + self.post_blank + .map(|post_blank| post_blank.chars().count()) + .unwrap_or(0) + .try_into() + .expect("Too much post-blank to fit into a PostBlank.") } }