diff --git a/org_mode_samples/lesser_element/lesser_block/example/line_number_negative.org b/org_mode_samples/lesser_element/lesser_block/example/line_number_negative.org new file mode 100644 index 0000000..d3489ee --- /dev/null +++ b/org_mode_samples/lesser_element/lesser_block/example/line_number_negative.org @@ -0,0 +1,7 @@ +#+begin_example -n -10 +foo +#+end_example + +#+begin_example +n -15 +bar +#+end_example diff --git a/org_mode_samples/lesser_element/lesser_block/example/line_number_zero.org b/org_mode_samples/lesser_element/lesser_block/example/line_number_zero.org new file mode 100644 index 0000000..023fe68 --- /dev/null +++ b/org_mode_samples/lesser_element/lesser_block/example/line_number_zero.org @@ -0,0 +1,7 @@ +#+begin_example -n 0 +foo +#+end_example + +#+begin_example +n 0 +bar +#+end_example diff --git a/src/parser/lesser_block.rs b/src/parser/lesser_block.rs index 5e97261..340a1de 100644 --- a/src/parser/lesser_block.rs +++ b/src/parser/lesser_block.rs @@ -6,10 +6,12 @@ use nom::character::complete::space0; use nom::character::complete::space1; use nom::combinator::consumed; use nom::combinator::eof; +use nom::combinator::map; use nom::combinator::opt; use nom::combinator::recognize; use nom::combinator::verify; use nom::multi::many_till; +use nom::multi::separated_list1; use nom::sequence::tuple; use super::org_source::OrgSource; @@ -29,9 +31,11 @@ use crate::parser::util::text_until_exit; use crate::types::CommentBlock; use crate::types::ExampleBlock; use crate::types::ExportBlock; +use crate::types::LineNumber; use crate::types::Object; use crate::types::PlainText; use crate::types::SrcBlock; +use crate::types::SwitchNumberLines; use crate::types::VerseBlock; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -130,7 +134,7 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>( input: OrgSource<'s>, ) -> Res, ExampleBlock<'s>> { let (remaining, _name) = lesser_block_begin("example")(context, input)?; - let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; + let (remaining, parameters) = opt(tuple((space1, example_switches)))(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?; let lesser_block_end_specialized = lesser_block_end("example"); let contexts = [ @@ -150,13 +154,20 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>( let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?; let source = get_consumed(input, remaining); + let (switches, number_lines) = { + if let Some(parameters) = parameters { + (Some(parameters.source), parameters.number_lines) + } else { + (None, None) + } + }; Ok(( remaining, ExampleBlock { source: source.into(), name: source.into(), - switches: parameters.map(|parameters| Into::<&str>::into(parameters)), - number_lines: None, // TODO + switches, + number_lines, contents: contents.into(), }, )) @@ -302,3 +313,64 @@ fn _lesser_block_begin<'b, 'g, 'r, 's, 'c>( ))(remaining)?; Ok((remaining, name)) } + +struct ExampleSwitches<'s> { + source: &'s str, + number_lines: Option, +} + +enum SwitchState { + Normal, + NewLineNumber, + ContinuedLineNumber, +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn example_switches<'s>(input: OrgSource<'s>) -> Res, ExampleSwitches<'s>> { + let mut number_lines = None; + let (remaining, (source, words)) = consumed(separated_list1( + space1, + map(is_not(" \t\r\n"), |val| Into::<&str>::into(val)), + ))(input)?; + + let mut state = SwitchState::Normal; + for word in words { + state = match (state, word) { + (SwitchState::Normal, "-n") => SwitchState::NewLineNumber, + (SwitchState::Normal, "+n") => SwitchState::ContinuedLineNumber, + (SwitchState::NewLineNumber, _) => { + let val = word + .parse::() + .expect("TODO: I should be able to return CustomError from nom parsers."); + if val < 0 { + number_lines = Some(SwitchNumberLines::New(0)); + } else { + // Note that this can result in a negative 1 if the val is originally 0. + number_lines = Some(SwitchNumberLines::New(val - 1)); + } + SwitchState::Normal + } + (SwitchState::ContinuedLineNumber, _) => { + let val = word + .parse::() + .expect("TODO: I should be able to return CustomError from nom parsers."); + if val < 0 { + number_lines = Some(SwitchNumberLines::Continued(0)); + } else { + // Note that this can result in a negative 1 if the val is originally 0. + number_lines = Some(SwitchNumberLines::Continued(val - 1)); + } + SwitchState::Normal + } + (state @ SwitchState::Normal, _) => state, + }; + } + + Ok(( + remaining, + ExampleSwitches { + source: Into::<&str>::into(source), + number_lines, + }, + )) +} diff --git a/src/types/lesser_element.rs b/src/types/lesser_element.rs index 7e5e6d4..20e932b 100644 --- a/src/types/lesser_element.rs +++ b/src/types/lesser_element.rs @@ -108,7 +108,10 @@ pub struct LatexEnvironment<'s> { pub source: &'s str, } -pub type LineNumber = usize; +/// A line number used in switches to lesser blocks. +/// +/// This must be signed because emacs subtracts 1 from the actual value in the org-mode text, which makes a 0 turn into a -1. +pub type LineNumber = isize; #[derive(Debug)] pub enum SwitchNumberLines {