Compare commits
12 Commits
ac0db64081
...
3d86e75059
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3d86e75059 | ||
![]() |
ca6fdf1924 | ||
![]() |
66d16d89ed | ||
![]() |
ee5e0698b1 | ||
![]() |
22681b6a58 | ||
![]() |
876d33239e | ||
![]() |
87941271a4 | ||
![]() |
32b19d68d0 | ||
![]() |
830097b0a9 | ||
![]() |
44e9f708c9 | ||
![]() |
fc4ff97c14 | ||
![]() |
33372429dd |
@ -25,3 +25,4 @@ This could significantly reduce our calls to exit matchers.
|
|||||||
I think targets would break this.
|
I think targets would break this.
|
||||||
|
|
||||||
The exit matchers are already implicitly building this behavior since they should all exit very early when the starting character is wrong. Putting this logic in a centralized place, far away from where those characters are actually going to be used, is unfortunate for readability.
|
The exit matchers are already implicitly building this behavior since they should all exit very early when the starting character is wrong. Putting this logic in a centralized place, far away from where those characters are actually going to be used, is unfortunate for readability.
|
||||||
|
** Use exit matcher to cut off trailing whitespace instead of re-matching in plain lists.
|
||||||
|
3
org_mode_samples/object/radio_link/different_case.org
Normal file
3
org_mode_samples/object/radio_link/different_case.org
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<<<Foo Bar Baz>>>
|
||||||
|
|
||||||
|
foo bar baz
|
@ -0,0 +1,6 @@
|
|||||||
|
<<<foo bar baz>>>
|
||||||
|
|
||||||
|
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
baz
|
1
org_mode_samples/object/regular_link/elisp.org
Normal file
1
org_mode_samples/object/regular_link/elisp.org
Normal file
@ -0,0 +1 @@
|
|||||||
|
[[elisp:(local-set-key "\M-\x" 'foo-bar-baz)]]
|
2
org_mode_samples/sections_and_headings/empty_heading.org
Normal file
2
org_mode_samples/sections_and_headings/empty_heading.org
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
* DONE
|
||||||
|
*
|
@ -0,0 +1,6 @@
|
|||||||
|
#+TODO: TODO(t) INPROGRESS(i/!) | DONE(d!) CANCELED(c@/!)
|
||||||
|
# ! : Log changes leading to this state.
|
||||||
|
# @ : Log changes leading to this state and prompt for a comment to include.
|
||||||
|
# /! : Log changes leaving this state if and only if to a state that does not log. This can be combined with the above like WAIT(w!/!) or DELAYED(d@/!)
|
||||||
|
* INPROGRESS
|
||||||
|
- State "TODO" from "INPROGRESS" [2023-09-14 Thu 02:13]
|
@ -546,14 +546,26 @@ fn compare_heading<'s>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Compare title
|
// Compare title
|
||||||
let title = get_property(emacs, ":title")?.ok_or("Missing :title attribute.")?;
|
let title = get_property(emacs, ":title")?;
|
||||||
let title_status = title
|
match (title, rust.title.len()) {
|
||||||
.as_list()?
|
(None, 0) => {}
|
||||||
.iter()
|
(None, _) => {
|
||||||
.zip(rust.title.iter())
|
this_status = DiffStatus::Bad;
|
||||||
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child))
|
message = Some(format!(
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
"Titles do not match (emacs != rust): {:?} != {:?}",
|
||||||
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?);
|
title, rust.title
|
||||||
|
))
|
||||||
|
}
|
||||||
|
(Some(title), _) => {
|
||||||
|
let title_status = title
|
||||||
|
.as_list()?
|
||||||
|
.iter()
|
||||||
|
.zip(rust.title.iter())
|
||||||
|
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Compare priority
|
// Compare priority
|
||||||
let priority = get_property(emacs, ":priority")?;
|
let priority = get_property(emacs, ":priority")?;
|
||||||
@ -1914,6 +1926,8 @@ fn compare_regular_link<'s>(
|
|||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Compare :type :path :format :raw-link :application :search-option
|
||||||
|
|
||||||
Ok(DiffResult {
|
Ok(DiffResult {
|
||||||
status: this_status,
|
status: this_status,
|
||||||
name: emacs_name.to_owned(),
|
name: emacs_name.to_owned(),
|
||||||
|
@ -12,6 +12,10 @@ pub struct GlobalSettings<'g, 's> {
|
|||||||
pub file_access: &'g dyn FileAccessInterface,
|
pub file_access: &'g dyn FileAccessInterface,
|
||||||
pub in_progress_todo_keywords: BTreeSet<String>,
|
pub in_progress_todo_keywords: BTreeSet<String>,
|
||||||
pub complete_todo_keywords: BTreeSet<String>,
|
pub complete_todo_keywords: BTreeSet<String>,
|
||||||
|
/// Set to true to allow for plain lists using single letters as the bullet in the same way that numbers are used.
|
||||||
|
///
|
||||||
|
/// Corresponds to the org-list-allow-alphabetical elisp variable.
|
||||||
|
pub org_list_allow_alphabetical: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'g, 's> GlobalSettings<'g, 's> {
|
impl<'g, 's> GlobalSettings<'g, 's> {
|
||||||
@ -23,6 +27,7 @@ impl<'g, 's> GlobalSettings<'g, 's> {
|
|||||||
},
|
},
|
||||||
in_progress_todo_keywords: BTreeSet::new(),
|
in_progress_todo_keywords: BTreeSet::new(),
|
||||||
complete_todo_keywords: BTreeSet::new(),
|
complete_todo_keywords: BTreeSet::new(),
|
||||||
|
org_list_allow_alphabetical: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ fn _detect_element<'b, 'g, 'r, 's>(
|
|||||||
can_be_paragraph: bool,
|
can_be_paragraph: bool,
|
||||||
) -> Res<OrgSource<'s>, ()> {
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
if alt((
|
if alt((
|
||||||
detect_plain_list,
|
parser_with_context!(detect_plain_list)(context),
|
||||||
detect_footnote_definition,
|
detect_footnote_definition,
|
||||||
detect_diary_sexp,
|
detect_diary_sexp,
|
||||||
detect_comment,
|
detect_comment,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::bytes::complete::tag_no_case;
|
|
||||||
use nom::character::complete::satisfy;
|
use nom::character::complete::satisfy;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
||||||
@ -439,7 +439,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, Entity<'s>> {
|
) -> Res<OrgSource<'s>, Entity<'s>> {
|
||||||
let (remaining, _) = tag("\\")(input)?;
|
let (remaining, _) = tag("\\")(input)?;
|
||||||
let (remaining, entity_name) = name(context, remaining)?;
|
let (remaining, entity_name) = name(context, remaining)?;
|
||||||
let (remaining, _) = alt((tag("{}"), peek(recognize(entity_end))))(remaining)?;
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, _trailing_whitespace) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
|
|
||||||
@ -460,9 +460,12 @@ fn name<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
// TODO: This should be defined by org-entities and optionally org-entities-user
|
// TODO: This should be defined by org-entities and optionally org-entities-user
|
||||||
for entity in ORG_ENTITIES {
|
for entity in ORG_ENTITIES {
|
||||||
let result = tag_no_case::<_, _, CustomError<_>>(entity)(input);
|
let result = tuple((
|
||||||
|
tag::<_, _, CustomError<_>>(entity),
|
||||||
|
alt((tag("{}"), peek(recognize(entity_end)))),
|
||||||
|
))(input);
|
||||||
match result {
|
match result {
|
||||||
Ok((remaining, ent)) => {
|
Ok((remaining, (ent, _))) => {
|
||||||
return Ok((remaining, ent));
|
return Ok((remaining, ent));
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
|
@ -3,15 +3,17 @@ use nom::bytes::complete::is_not;
|
|||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::character::complete::space1;
|
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::recognize;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
use nom::sequence::preceded;
|
use nom::sequence::preceded;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
|
use super::util::org_line_ending;
|
||||||
|
use super::util::org_spaces0;
|
||||||
|
use super::util::org_spaces1;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
@ -47,10 +49,10 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _indent) = space0(input)?;
|
let (remaining, _indent) = space0(input)?;
|
||||||
let (remaining, (_colon, _leading_whitespace_and_content, _line_ending)) = tuple((
|
let (remaining, _) = tuple((
|
||||||
tag(":"),
|
tag(":"),
|
||||||
opt(tuple((space1, is_not("\r\n")))),
|
alt((recognize(tuple((org_spaces1, is_not("\r\n")))), org_spaces0)),
|
||||||
alt((line_ending, eof)),
|
org_line_ending,
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, source))
|
Ok((remaining, source))
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::line_ending;
|
|
||||||
use nom::character::complete::space0;
|
|
||||||
use nom::character::complete::space1;
|
use nom::character::complete::space1;
|
||||||
use nom::combinator::eof;
|
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
@ -19,6 +17,11 @@ use nom::sequence::tuple;
|
|||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::section::section;
|
use super::section::section;
|
||||||
use super::util::get_consumed;
|
use super::util::get_consumed;
|
||||||
|
use super::util::org_line_ending;
|
||||||
|
use super::util::org_space;
|
||||||
|
use super::util::org_space_or_line_ending;
|
||||||
|
use super::util::org_spaces0;
|
||||||
|
use super::util::org_spaces1;
|
||||||
use super::util::start_of_line;
|
use super::util::start_of_line;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
@ -81,10 +84,10 @@ fn _heading<'b, 'g, 'r, 's>(
|
|||||||
Heading {
|
Heading {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
stars: star_count,
|
stars: star_count,
|
||||||
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| {
|
todo_keyword: maybe_todo_keyword.map(|(todo_keyword_type, todo_keyword)| {
|
||||||
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
||||||
}),
|
}),
|
||||||
priority_cookie: maybe_priority.map(|(priority, _)| priority),
|
priority_cookie: maybe_priority.map(|(_, priority)| priority),
|
||||||
title,
|
title,
|
||||||
tags: heading_tags,
|
tags: heading_tags,
|
||||||
children,
|
children,
|
||||||
@ -109,9 +112,9 @@ fn headline<'b, 'g, 'r, 's>(
|
|||||||
OrgSource<'s>,
|
OrgSource<'s>,
|
||||||
(
|
(
|
||||||
usize,
|
usize,
|
||||||
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>,
|
Option<(TodoKeywordType, OrgSource<'s>)>,
|
||||||
Option<(PriorityCookie, OrgSource<'s>)>,
|
Option<(OrgSource<'s>, PriorityCookie)>,
|
||||||
Option<(OrgSource<'s>, OrgSource<'s>)>,
|
Option<OrgSource<'s>>,
|
||||||
Vec<Object<'s>>,
|
Vec<Object<'s>>,
|
||||||
Vec<&'s str>,
|
Vec<&'s str>,
|
||||||
),
|
),
|
||||||
@ -122,45 +125,45 @@ fn headline<'b, 'g, 'r, 's>(
|
|||||||
});
|
});
|
||||||
let parser_context = context.with_additional_node(&parser_context);
|
let parser_context = context.with_additional_node(&parser_context);
|
||||||
|
|
||||||
let (
|
let (remaining, (_, star_count, _)) = tuple((
|
||||||
remaining,
|
|
||||||
(
|
|
||||||
_,
|
|
||||||
star_count,
|
|
||||||
_,
|
|
||||||
maybe_todo_keyword,
|
|
||||||
maybe_priority,
|
|
||||||
maybe_comment,
|
|
||||||
title,
|
|
||||||
maybe_tags,
|
|
||||||
_,
|
|
||||||
_,
|
|
||||||
),
|
|
||||||
) = tuple((
|
|
||||||
start_of_line,
|
start_of_line,
|
||||||
verify(many1_count(tag("*")), |star_count| {
|
verify(many1_count(tag("*")), |star_count| {
|
||||||
*star_count > parent_stars
|
*star_count > parent_stars
|
||||||
}),
|
}),
|
||||||
space1,
|
peek(org_space),
|
||||||
opt(tuple((
|
|
||||||
parser_with_context!(heading_keyword)(&parser_context),
|
|
||||||
space1,
|
|
||||||
))),
|
|
||||||
opt(tuple((priority_cookie, space1))),
|
|
||||||
opt(tuple((tag("COMMENT"), space1))),
|
|
||||||
many1(parser_with_context!(standard_set_object)(&parser_context)),
|
|
||||||
opt(tuple((space0, tags))),
|
|
||||||
space0,
|
|
||||||
alt((line_ending, eof)),
|
|
||||||
))(input)?;
|
))(input)?;
|
||||||
|
|
||||||
|
let (remaining, maybe_todo_keyword) = opt(tuple((
|
||||||
|
org_spaces1,
|
||||||
|
parser_with_context!(heading_keyword)(&parser_context),
|
||||||
|
peek(org_space_or_line_ending),
|
||||||
|
)))(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, maybe_priority) = opt(tuple((org_spaces1, priority_cookie)))(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, maybe_comment) = opt(tuple((
|
||||||
|
org_spaces1,
|
||||||
|
tag("COMMENT"),
|
||||||
|
peek(org_space_or_line_ending),
|
||||||
|
)))(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, maybe_title) = opt(tuple((
|
||||||
|
org_spaces1,
|
||||||
|
many1(parser_with_context!(standard_set_object)(&parser_context)),
|
||||||
|
)))(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, maybe_tags) = opt(tuple((org_spaces0, tags)))(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = tuple((org_spaces0, org_line_ending))(remaining)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
(
|
(
|
||||||
star_count,
|
star_count,
|
||||||
maybe_todo_keyword,
|
maybe_todo_keyword.map(|(_, todo, _)| todo),
|
||||||
maybe_priority,
|
maybe_priority,
|
||||||
maybe_comment,
|
maybe_comment.map(|(_, comment, _)| comment),
|
||||||
title,
|
maybe_title.map(|(_, title)| title).unwrap_or(Vec::new()),
|
||||||
maybe_tags
|
maybe_tags
|
||||||
.map(|(_ws, tags)| {
|
.map(|(_ws, tags)| {
|
||||||
tags.into_iter()
|
tags.into_iter()
|
||||||
@ -178,8 +181,9 @@ fn headline_title_end<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
opt(tuple((space0, tags, space0))),
|
org_spaces0,
|
||||||
alt((line_ending, eof)),
|
opt(tuple((tags, org_spaces0))),
|
||||||
|
org_line_ending,
|
||||||
)))(input)
|
)))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,9 +44,17 @@ pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, V
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn todo_keyword_word<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn todo_keyword_word<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
verify(take_till(|c| " \t\r\n|".contains(c)), |result: &str| {
|
let (remaining, keyword) = verify(take_till(|c| "( \t\r\n|".contains(c)), |result: &str| {
|
||||||
!result.is_empty()
|
!result.is_empty()
|
||||||
})(input)
|
})(input)?;
|
||||||
|
|
||||||
|
let (remaining, _) = opt(tuple((
|
||||||
|
tag("("),
|
||||||
|
take_till(|c| "() \t\r\n|".contains(c)),
|
||||||
|
tag(")"),
|
||||||
|
)))(remaining)?;
|
||||||
|
|
||||||
|
Ok((remaining, keyword))
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -41,12 +41,15 @@ use crate::types::PlainList;
|
|||||||
use crate::types::PlainListItem;
|
use crate::types::PlainListItem;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub(crate) fn detect_plain_list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
if verify(
|
if verify(
|
||||||
tuple((
|
tuple((
|
||||||
start_of_line,
|
start_of_line,
|
||||||
space0,
|
space0,
|
||||||
bullet,
|
parser_with_context!(bullet)(context),
|
||||||
alt((space1, line_ending, eof)),
|
alt((space1, line_ending, eof)),
|
||||||
)),
|
)),
|
||||||
|(_start, indent, bull, _after_whitespace)| {
|
|(_start, indent, bull, _after_whitespace)| {
|
||||||
@ -145,12 +148,17 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, leading_whitespace) = space0(input)?;
|
let (remaining, leading_whitespace) = space0(input)?;
|
||||||
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
||||||
let indent_level = leading_whitespace.len();
|
let indent_level = leading_whitespace.len();
|
||||||
let (remaining, bull) = verify(bullet, |bull: &OrgSource<'_>| {
|
let (remaining, bull) = verify(
|
||||||
Into::<&str>::into(bull) != "*" || indent_level > 0
|
parser_with_context!(bullet)(context),
|
||||||
})(remaining)?;
|
|bull: &OrgSource<'_>| Into::<&str>::into(bull) != "*" || indent_level > 0,
|
||||||
|
)(remaining)?;
|
||||||
|
|
||||||
let (remaining, _maybe_counter_set) =
|
let (remaining, _maybe_counter_set) = opt(tuple((
|
||||||
opt(tuple((space1, tag("[@"), counter, tag("]"))))(remaining)?;
|
space1,
|
||||||
|
tag("[@"),
|
||||||
|
parser_with_context!(counter)(context),
|
||||||
|
tag("]"),
|
||||||
|
)))(remaining)?;
|
||||||
|
|
||||||
// TODO: parse checkbox
|
// TODO: parse checkbox
|
||||||
|
|
||||||
@ -228,18 +236,36 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn bullet<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((
|
alt((
|
||||||
tag("*"),
|
tag("*"),
|
||||||
tag("-"),
|
tag("-"),
|
||||||
tag("+"),
|
tag("+"),
|
||||||
recognize(tuple((counter, alt((tag("."), tag(")")))))),
|
recognize(tuple((
|
||||||
))(i)
|
parser_with_context!(counter)(context),
|
||||||
|
alt((tag("."), tag(")"))),
|
||||||
|
))),
|
||||||
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn counter<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn counter<'b, 'g, 'r, 's>(
|
||||||
alt((recognize(one_of("abcdefghijklmnopqrstuvwxyz")), digit1))(i)
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
if context.get_global_settings().org_list_allow_alphabetical {
|
||||||
|
alt((
|
||||||
|
recognize(one_of(
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||||
|
)),
|
||||||
|
digit1,
|
||||||
|
))(input)
|
||||||
|
} else {
|
||||||
|
digit1(input)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@ -558,21 +584,30 @@ dolar"#,
|
|||||||
r#"+
|
r#"+
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
let result = detect_plain_list(input);
|
let global_settings = GlobalSettings::default();
|
||||||
|
let initial_context = ContextElement::document_context();
|
||||||
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn detect_eof() {
|
fn detect_eof() {
|
||||||
let input = OrgSource::new(r#"+"#);
|
let input = OrgSource::new(r#"+"#);
|
||||||
let result = detect_plain_list(input);
|
let global_settings = GlobalSettings::default();
|
||||||
|
let initial_context = ContextElement::document_context();
|
||||||
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn detect_no_gap() {
|
fn detect_no_gap() {
|
||||||
let input = OrgSource::new(r#"+foo"#);
|
let input = OrgSource::new(r#"+foo"#);
|
||||||
let result = detect_plain_list(input);
|
let global_settings = GlobalSettings::default();
|
||||||
|
let initial_context = ContextElement::document_context();
|
||||||
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
|
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
@ -580,7 +615,10 @@ dolar"#,
|
|||||||
#[test]
|
#[test]
|
||||||
fn detect_with_gap() {
|
fn detect_with_gap() {
|
||||||
let input = OrgSource::new(r#"+ foo"#);
|
let input = OrgSource::new(r#"+ foo"#);
|
||||||
let result = detect_plain_list(input);
|
let global_settings = GlobalSettings::default();
|
||||||
|
let initial_context = ContextElement::document_context();
|
||||||
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::is_not;
|
||||||
|
use nom::bytes::complete::tag_no_case;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::combinator::map;
|
use nom::character::complete::line_ending;
|
||||||
|
use nom::character::complete::one_of;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many1;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::radio_link::RematchObject;
|
use super::radio_link::RematchObject;
|
||||||
use super::util::exit_matcher_parser;
|
use super::util::exit_matcher_parser;
|
||||||
|
use super::util::get_consumed;
|
||||||
|
use super::util::org_space_or_line_ending;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
use crate::types::PlainText;
|
use crate::types::PlainText;
|
||||||
@ -72,11 +79,52 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
map(tag(self.source), |s| {
|
let mut remaining = input;
|
||||||
|
let mut goal = self.source;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if goal.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// let is_whitespace = recognize(many1(org_space_or_line_ending))(input);
|
||||||
|
let is_not_whitespace = is_not::<&str, &str, CustomError<_>>(" \t\r\n")(goal);
|
||||||
|
match is_not_whitespace {
|
||||||
|
Ok((new_goal, payload)) => {
|
||||||
|
let (new_remaining, _) = tag_no_case(payload)(remaining)?;
|
||||||
|
remaining = new_remaining;
|
||||||
|
goal = new_goal;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_whitespace = recognize(many1(alt((
|
||||||
|
recognize(one_of::<&str, &str, CustomError<_>>(" \t")),
|
||||||
|
line_ending,
|
||||||
|
))))(goal);
|
||||||
|
match is_whitespace {
|
||||||
|
Ok((new_goal, _)) => {
|
||||||
|
let (new_remaining, _) = many1(org_space_or_line_ending)(remaining)?;
|
||||||
|
remaining = new_remaining;
|
||||||
|
goal = new_goal;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Target does not match.".into(),
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
Object::PlainText(PlainText {
|
Object::PlainText(PlainText {
|
||||||
source: Into::<&str>::into(s),
|
source: Into::<&str>::into(source),
|
||||||
})
|
}),
|
||||||
})(input)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use nom::branch::alt;
|
|||||||
use nom::bytes::complete::escaped;
|
use nom::bytes::complete::escaped;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::bytes::complete::take_till1;
|
use nom::bytes::complete::take_till1;
|
||||||
use nom::character::complete::one_of;
|
use nom::character::complete::anychar;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ fn pathreg<'b, 'g, 'r, 's>(
|
|||||||
_ => false,
|
_ => false,
|
||||||
}),
|
}),
|
||||||
'\\',
|
'\\',
|
||||||
one_of(r#"]"#),
|
anychar,
|
||||||
)(input)?;
|
)(input)?;
|
||||||
Ok((remaining, path))
|
Ok((remaining, path))
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::is_a;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
use nom::character::complete::none_of;
|
use nom::character::complete::none_of;
|
||||||
@ -9,9 +10,11 @@ use nom::combinator::not;
|
|||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
use nom::Slice;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
@ -212,6 +215,9 @@ fn text_until_eol<'r, 's>(
|
|||||||
Ok(line.trim())
|
Ok(line.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a tuple of (input, output) from a nom parser.
|
||||||
|
///
|
||||||
|
/// This is similar to recognize except it returns the input instead of the portion of the input that was consumed.
|
||||||
pub(crate) fn include_input<'s, F, O>(
|
pub(crate) fn include_input<'s, F, O>(
|
||||||
mut inner: F,
|
mut inner: F,
|
||||||
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
|
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
|
||||||
@ -223,3 +229,44 @@ where
|
|||||||
Ok((remaining, (input, output)))
|
Ok((remaining, (input, output)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Match single space or tab.
|
||||||
|
///
|
||||||
|
/// In org-mode syntax, spaces and tabs are interchangeable.
|
||||||
|
pub(crate) fn org_space<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, char> {
|
||||||
|
one_of(" \t")(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches a single space, tab, line ending, or end of file.
|
||||||
|
///
|
||||||
|
/// In org-mode syntax there are often delimiters that could be any whitespace at all or the end of file.
|
||||||
|
pub(crate) fn org_space_or_line_ending<'s>(
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
alt((recognize(one_of(" \t")), org_line_ending))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match as many spaces and tabs as possible. No minimum match.
|
||||||
|
///
|
||||||
|
/// In org-mode syntax, spaces and tabs are interchangeable.
|
||||||
|
pub(crate) fn org_spaces0<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let found = is_a(" \t")(input);
|
||||||
|
if found.is_ok() {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
Ok((input, input.slice(..0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match as many spaces and tabs as possible. Minimum 1 character.
|
||||||
|
///
|
||||||
|
/// In org-mode syntax, spaces and tabs are interchangeable.
|
||||||
|
pub(crate) fn org_spaces1<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
verify(is_a(" \t"), |res: &OrgSource<'_>| res.len() > 0)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match a line break or the end of the file.
|
||||||
|
///
|
||||||
|
/// In org-mode syntax, the end of the file can serve the same purpose as a line break syntactically.
|
||||||
|
pub(crate) fn org_line_ending<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
alt((line_ending, eof))(input)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user