diff --git a/elisp_snippets/dump_org_element_affiliated_keywords.el b/elisp_snippets/dump_org_element_affiliated_keywords.el new file mode 100644 index 00000000..ec09961a --- /dev/null +++ b/elisp_snippets/dump_org_element_affiliated_keywords.el @@ -0,0 +1,3 @@ +(dolist (var org-element-affiliated-keywords) + (message "\"%s\"," (downcase var)) + ) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index fda1ef5e..1998e19c 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -120,7 +120,7 @@ impl<'s> DiffResult<'s> { indentation = " ".repeat(indentation), status_text = status_text, name = self.name, - char_offset = rust_offset, + char_offset = rust_offset + 1, message = self.message.as_ref().map(|m| m.as_str()).unwrap_or("") ); for child in self.children.iter() { diff --git a/src/parser/element_parser.rs b/src/parser/element_parser.rs index 2c79e46c..eb3e7071 100644 --- a/src/parser/element_parser.rs +++ b/src/parser/element_parser.rs @@ -12,6 +12,7 @@ use super::fixed_width_area::fixed_width_area; use super::footnote_definition::footnote_definition; use super::greater_block::greater_block; use super::horizontal_rule::horizontal_rule; +use super::keyword::affiliated_keyword; use super::keyword::keyword; use super::latex_environment::latex_environment; use super::lesser_block::comment_block; @@ -62,10 +63,12 @@ fn _element<'r, 's>( let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context); let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context); let keyword_matcher = parser_with_context!(keyword)(context); + let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context); let paragraph_matcher = parser_with_context!(paragraph)(context); let latex_environment_matcher = parser_with_context!(latex_environment)(context); - let (remaining, mut affiliated_keywords) = many0(keyword_matcher)(input)?; + // TODO: Affiliated keywords cannot be on comments, clocks, headings, inlinetasks, items, node properties, planning, property drawers, sections, and table rows + let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?; let (remaining, mut element) = match alt(( map(plain_list_matcher, Element::PlainList), map(greater_block_matcher, Element::GreaterBlock), diff --git a/src/parser/keyword.rs b/src/parser/keyword.rs index 4c1bb410..cdb1e1f4 100644 --- a/src/parser/keyword.rs +++ b/src/parser/keyword.rs @@ -2,6 +2,7 @@ use nom::branch::alt; use nom::bytes::complete::is_not; use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; +use nom::character::complete::anychar; use nom::character::complete::line_ending; use nom::character::complete::space0; use nom::character::complete::space1; @@ -9,14 +10,24 @@ use nom::combinator::eof; use nom::combinator::not; use nom::combinator::peek; use nom::combinator::recognize; +use nom::multi::many_till; use nom::sequence::tuple; +use super::org_source::BracketDepth; use super::org_source::OrgSource; use super::Context; +use crate::error::CustomError; +use crate::error::MyError; use crate::error::Res; use crate::parser::util::start_of_line; use crate::parser::Keyword; +const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [ + "caption", "data", "header", "headers", "label", "name", "plot", "resname", "result", + "results", "source", "srcname", "tblname", +]; +const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"]; + #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub fn keyword<'r, 's>( _context: Context<'r, 's>, @@ -41,3 +52,104 @@ pub fn keyword<'r, 's>( }, )) } + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +pub fn affiliated_keyword<'r, 's>( + _context: Context<'r, 's>, + input: OrgSource<'s>, +) -> Res, Keyword<'s>> { + start_of_line(input)?; + // org-element-dual-keywords + + // TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references. + let (remaining, rule) = recognize(tuple(( + space0, + tag("#+"), + affiliated_key, + tag(":"), + alt((recognize(tuple((space1, is_not("\r\n")))), space0)), + alt((line_ending, eof)), + )))(input)?; + Ok(( + remaining, + Keyword { + source: rule.into(), + }, + )) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn affiliated_key<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + alt(( + recognize(tuple((dual_affiliated_key, tag("["), optval, tag("]")))), + plain_affiliated_key, + ))(input) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn plain_affiliated_key<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + for keyword in ORG_ELEMENT_AFFILIATED_KEYWORDS { + let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input); + match result { + Ok((remaining, ent)) => { + return Ok((remaining, ent)); + } + Err(_) => {} + } + } + + Err(nom::Err::Error(CustomError::MyError(MyError( + "NoKeywordKey".into(), + )))) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn dual_affiliated_key<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + for keyword in ORG_ELEMENT_DUAL_KEYWORDS { + let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input); + match result { + Ok((remaining, ent)) => { + return Ok((remaining, ent)); + } + Err(_) => {} + } + } + + Err(nom::Err::Error(CustomError::MyError(MyError( + "NoKeywordKey".into(), + )))) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn optval<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + recognize(many_till( + anychar, + peek(optval_end(input.get_bracket_depth())), + ))(input) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +const fn optval_end( + starting_bracket_depth: BracketDepth, +) -> impl for<'s> Fn(OrgSource<'s>) -> Res, OrgSource<'s>> { + move |input: OrgSource<'_>| _optval_end(input, starting_bracket_depth) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn _optval_end<'s>( + input: OrgSource<'s>, + starting_bracket_depth: BracketDepth, +) -> Res, OrgSource<'s>> { + let current_depth = input.get_bracket_depth() - starting_bracket_depth; + if current_depth < 0 { + // This shouldn't be possible because if depth is 0 then a closing bracket should end the opval. + unreachable!("Exceeded optval bracket depth.") + } + if current_depth == 0 { + let close_bracket = tag::<&str, OrgSource<'_>, CustomError>>("]")(input); + if close_bracket.is_ok() { + return close_bracket; + } + } + tag("\n")(input) +}