Compare commits

..

6 Commits

Author SHA1 Message Date
Tom Alexander
7b61329889
Add test showing we are not parsing links wrapped in brackets correctly.
Some checks failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-09-20 03:48:22 -04:00
Tom Alexander
9bcfb2f1da
Decide headline nesting by star count, not headline level.
It is possible to have two headlines that have the same level but different star counts when set to Odd because of rounding. Deciding nesting by star count instead of headline level avoids this issue.
2023-09-20 03:22:25 -04:00
Tom Alexander
4c8d9a3063
Do not require a colon to close dynamic blocks. 2023-09-20 02:37:26 -04:00
Tom Alexander
48cb3c4a02
Move the post-colon check into the item_tag_divider parser. 2023-09-19 23:57:40 -04:00
Tom Alexander
9e60ff6683
Support rematching on italic, underline, and strike-through. 2023-09-19 23:25:49 -04:00
Tom Alexander
c1de001786
Require a space after colon instead of tab for fixed width area. 2023-09-19 20:22:29 -04:00
20 changed files with 180 additions and 56 deletions

View File

@ -1,5 +1,5 @@
FROM alpine:3.17 AS build FROM alpine:3.17 AS build
RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk libgccjit-dev
FROM build AS build-emacs FROM build AS build-emacs
@ -8,7 +8,7 @@ RUN git clone --depth 1 --branch $EMACS_VERSION https://git.savannah.gnu.org/git
WORKDIR /root/emacs WORKDIR /root/emacs
RUN mkdir /root/dist RUN mkdir /root/dist
RUN ./autogen.sh RUN ./autogen.sh
RUN ./configure --prefix /usr --without-x --without-sound RUN ./configure --prefix /usr --without-x --without-sound --with-native-compilation=aot
RUN make RUN make
RUN make DESTDIR="/root/dist" install RUN make DESTDIR="/root/dist" install
@ -27,7 +27,7 @@ RUN make DESTDIR="/root/dist" install
FROM rustlang/rust:nightly-alpine3.17 AS tester FROM rustlang/rust:nightly-alpine3.17 AS tester
ENV LANG=en_US.UTF-8 ENV LANG=en_US.UTF-8
RUN apk add --no-cache musl-dev ncurses gnutls RUN apk add --no-cache musl-dev ncurses gnutls libgccjit
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
COPY --from=build-emacs /root/dist/ / COPY --from=build-emacs /root/dist/ /
COPY --from=build-org-mode /root/dist/ / COPY --from=build-org-mode /root/dist/ /
@ -88,7 +88,7 @@ ARG DOOMEMACS_PATH=/foreign_documents/doomemacs
ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git
RUN mkdir -p $DOOMEMACS_PATH && git -C $DOOMEMACS_PATH init --initial-branch=main && git -C $DOOMEMACS_PATH remote add origin $DOOMEMACS_REPO && git -C $DOOMEMACS_PATH fetch origin $DOOMEMACS_VERSION && git -C $DOOMEMACS_PATH checkout FETCH_HEAD RUN mkdir -p $DOOMEMACS_PATH && git -C $DOOMEMACS_PATH init --initial-branch=main && git -C $DOOMEMACS_PATH remote add origin $DOOMEMACS_REPO && git -C $DOOMEMACS_PATH fetch origin $DOOMEMACS_VERSION && git -C $DOOMEMACS_PATH checkout FETCH_HEAD
ARG WORG_VERSION=74e80b0f7600801b1d1594542602394c085cc2f9 ARG WORG_VERSION=0c8d5679b536af450b61812246a3e02b8103f4b8
ARG WORG_PATH=/foreign_documents/worg ARG WORG_PATH=/foreign_documents/worg
ARG WORG_REPO=https://git.sr.ht/~bzg/worg ARG WORG_REPO=https://git.sr.ht/~bzg/worg
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD

View File

@ -41,9 +41,9 @@ function main {
set -e set -e
if [ "$all_status" -ne 0 ]; then if [ "$all_status" -ne 0 ]; then
echo "$(red_text "Some tests failed.")" red_text "Some tests failed."
else else
echo "$(green_text "All tests passed.")" green_text "All tests passed."
fi fi
return "$all_status" return "$all_status"
} }
@ -64,8 +64,9 @@ function indent {
local depth="$1" local depth="$1"
local scaled_depth=$((depth * 2)) local scaled_depth=$((depth * 2))
shift 1 shift 1
local prefix=$(printf -- "%${scaled_depth}s") local prefix
while read l; do prefix=$(printf -- "%${scaled_depth}s")
while read -r l; do
(IFS=' '; printf -- '%s%s\n' "$prefix" "$l") (IFS=' '; printf -- '%s%s\n' "$prefix" "$l")
done done
} }
@ -93,12 +94,13 @@ function compare_all_org_document {
local target_document local target_document
local all_status=0 local all_status=0
while read target_document; do while read target_document; do
local relative_path=$($REALPATH --relative-to "$root_dir" "$target_document") local relative_path
relative_path=$($REALPATH --relative-to "$root_dir" "$target_document")
set +e set +e
(run_compare "$relative_path" "$target_document") (run_compare "$relative_path" "$target_document")
if [ "$?" -ne 0 ]; then all_status=1; fi if [ "$?" -ne 0 ]; then all_status=1; fi
set -e set -e
done<<<$(find "$root_dir" -type f -iname '*.org') done<<<"$(find "$root_dir" -type f -iname '*.org' | sort)"
return "$all_status" return "$all_status"
} }

View File

@ -0,0 +1,3 @@
#+BEGIN: timestamp :format "%Y-%m-%d %H:%M"
#+END

View File

@ -0,0 +1,2 @@
# Fixed width areas must begin with colon followed by a space, not a tab, so this is not a fixed width area.
: foo

View File

@ -0,0 +1 @@
[[[http://foo.bar/baz][lorem]]]

View File

@ -0,0 +1,4 @@
# Since "foos" has an extra "s", this does not match the target.
the foos bar
The <<<foo>>> and stuff.

View File

@ -5,3 +5,4 @@
*** Lorem *** Lorem
* Ipsum * Ipsum
**** Dolar **** Dolar
***** Cat

View File

@ -8,7 +8,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${TRACE:="NO"} # or YES to send traces to jaeger : ${TRACE:="NO"} # or YES to send traces to jaeger
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking : ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
: ${NO_COLOR:=""} # Set to anything to disable color output : ${NO_COLOR:=""} # Set to anything to disable color output
: ${PROFILE:="release-lto"} : ${PROFILE:="debug"}
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make) MAKE=$(command -v gmake || command -v make)

View File

@ -64,7 +64,7 @@ function run_parse {
local lines="$1" local lines="$1"
cd "$SOURCE_FOLDER" cd "$SOURCE_FOLDER"
head -n "$lines" "$SOURCE_FOLDER/$TARGET_DOCUMENT" | "${DIR}/run_docker_compare.bash" head -n "$lines" "$SOURCE_FOLDER/$TARGET_DOCUMENT" | PROFILE=release-lto "${DIR}/run_docker_compare.bash"
local status=$? local status=$?
return "$status" return "$status"
} }

View File

@ -894,6 +894,8 @@ fn compare_dynamic_block<'s>(
Ok(_) => {} Ok(_) => {}
}; };
// TODO: Compare :block-name :arguments
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_element(source, emacs_child, rust_child)?); child_status.push(compare_element(source, emacs_child, rust_child)?);
} }

View File

@ -1,14 +1,18 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::is_not; use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
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::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
@ -67,24 +71,23 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
}; };
let element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, children) = match tuple(( not(exit_matcher)(remaining)?;
not(exit_matcher), let (remaining, leading_blank_lines) = opt(consumed(tuple((
blank_line, blank_line,
many_till(blank_line, exit_matcher), many0(preceded(not(exit_matcher), blank_line)),
))(remaining) ))))(remaining)?;
{ let leading_blank_lines =
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => { leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into())); let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain);
element.set_source(source.into()); element.set_source(source.into());
(remain, vec![element]) element
} });
Err(_) => { let (remaining, (mut children, _exit_contents)) =
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(element_matcher, exit_matcher)(remaining)?;
(remaining, children) if let Some(lines) = leading_blank_lines {
children.insert(0, lines);
} }
};
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?; let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@ -117,7 +120,8 @@ fn dynamic_block_end<'b, 'g, 'r, 's>(
start_of_line(input)?; start_of_line(input)?;
let (remaining, source) = recognize(tuple(( let (remaining, source) = recognize(tuple((
space0, space0,
tag_no_case("#+end:"), tag_no_case("#+end"),
opt(tag(":")),
alt((eof, line_ending)), alt((eof, line_ending)),
)))(input)?; )))(input)?;
Ok((remaining, source)) Ok((remaining, source))

View File

@ -3,7 +3,6 @@ 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::recognize; use nom::combinator::recognize;
@ -12,6 +11,7 @@ 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::only_space1;
use super::util::org_line_ending; use super::util::org_line_ending;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
@ -50,7 +50,7 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
let (remaining, _indent) = space0(input)?; let (remaining, _indent) = space0(input)?;
let (remaining, _) = tuple(( let (remaining, _) = tuple((
tag(":"), tag(":"),
alt((recognize(tuple((space1, is_not("\r\n")))), space0)), alt((recognize(tuple((only_space1, is_not("\r\n")))), space0)),
org_line_ending, org_line_ending,
))(remaining)?; ))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);

View File

@ -34,12 +34,13 @@ use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::types::DocumentElement; use crate::types::DocumentElement;
use crate::types::Heading; use crate::types::Heading;
use crate::types::HeadlineLevel;
use crate::types::Object; use crate::types::Object;
use crate::types::PriorityCookie; use crate::types::PriorityCookie;
use crate::types::TodoKeywordType; use crate::types::TodoKeywordType;
pub(crate) const fn heading( pub(crate) const fn heading(
parent_level: usize, parent_level: HeadlineLevel,
) -> impl for<'b, 'g, 'r, 's> Fn( ) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>, OrgSource<'s>,
@ -51,15 +52,23 @@ pub(crate) const fn heading(
fn _heading<'b, 'g, 'r, 's>( fn _heading<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
parent_level: usize, parent_star_count: HeadlineLevel,
) -> Res<OrgSource<'s>, Heading<'s>> { ) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?; not(|i| context.check_exit_matcher(i))(input)?;
let ( let (
remaining, remaining,
(headline_level, maybe_todo_keyword, maybe_priority, maybe_comment, title, heading_tags), (
) = headline(context, input, parent_level)?; headline_level,
star_count,
maybe_todo_keyword,
maybe_priority,
maybe_comment,
title,
heading_tags,
),
) = headline(context, input, parent_star_count)?;
let section_matcher = parser_with_context!(section)(context); let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(headline_level))(context); let heading_matcher = parser_with_context!(heading(star_count))(context);
let (remaining, maybe_section) = let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?; opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?; let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
@ -106,11 +115,12 @@ pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()
fn headline<'b, 'g, 'r, 's>( fn headline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
parent_level: usize, parent_star_count: HeadlineLevel,
) -> Res< ) -> Res<
OrgSource<'s>, OrgSource<'s>,
( (
usize, HeadlineLevel,
HeadlineLevel,
Option<(TodoKeywordType, OrgSource<'s>)>, Option<(TodoKeywordType, OrgSource<'s>)>,
Option<(OrgSource<'s>, PriorityCookie)>, Option<(OrgSource<'s>, PriorityCookie)>,
Option<OrgSource<'s>>, Option<OrgSource<'s>>,
@ -124,11 +134,11 @@ 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 (remaining, (_, (star_count, _), _)) = tuple(( let (remaining, (_, (headline_level, star_count, _), _)) = tuple((
start_of_line, start_of_line,
verify( verify(
parser_with_context!(headline_level)(&parser_context), parser_with_context!(headline_level)(&parser_context),
|(level, _)| *level > parent_level, |(_, count, _)| *count > parent_star_count,
), ),
peek(org_space), peek(org_space),
))(input)?; ))(input)?;
@ -159,6 +169,7 @@ fn headline<'b, 'g, 'r, 's>(
Ok(( Ok((
remaining, remaining,
( (
headline_level,
star_count, star_count,
maybe_todo_keyword.map(|(_, todo, _)| todo), maybe_todo_keyword.map(|(_, todo, _)| todo),
maybe_priority, maybe_priority,
@ -261,9 +272,9 @@ fn priority_cookie<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PriorityCooki
fn headline_level<'b, 'g, 'r, 's>( fn headline_level<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (usize, OrgSource<'s>)> { ) -> Res<OrgSource<'s>, (HeadlineLevel, HeadlineLevel, OrgSource<'s>)> {
let (remaining, stars) = is_a("*")(input)?; let (remaining, stars) = is_a("*")(input)?;
let count = stars.len(); let count = stars.len().try_into().unwrap();
let level = match context.get_global_settings().odd_levels_only { let level = match context.get_global_settings().odd_levels_only {
crate::context::HeadlineLevelFilter::Odd => { crate::context::HeadlineLevelFilter::Odd => {
if count % 2 == 0 { if count % 2 == 0 {
@ -274,5 +285,5 @@ fn headline_level<'b, 'g, 'r, 's>(
} }
crate::context::HeadlineLevelFilter::OddEven => count, crate::context::HeadlineLevelFilter::OddEven => count,
}; };
Ok((remaining, (level, stars))) Ok((remaining, (level, count, stars)))
} }

View File

@ -359,22 +359,22 @@ fn item_tag_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((item_tag_divider, line_ending))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_divider<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( recognize(tuple((
item_tag_divider, one_of(" \t"),
tag("::"),
peek(tuple((
opt(tuple(( opt(tuple((
peek(one_of(" \t")), peek(one_of(" \t")),
many_till(anychar, peek(alt((item_tag_divider, line_ending, eof)))), many_till(anychar, peek(alt((item_tag_divider, line_ending, eof)))),
))), ))),
alt((line_ending, eof)), alt((line_ending, eof)),
))), ))),
line_ending, )))(input)
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_divider<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((one_of(" \t"), tag("::"))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@ -4,11 +4,13 @@ use nom::bytes::complete::tag_no_case;
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::one_of; use nom::character::complete::one_of;
use nom::combinator::eof;
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::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::radio_link::RematchObject; use super::radio_link::RematchObject;
@ -87,11 +89,17 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
break; 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); let is_not_whitespace = is_not::<&str, &str, CustomError<_>>(" \t\r\n")(goal);
match is_not_whitespace { match is_not_whitespace {
Ok((new_goal, payload)) => { Ok((new_goal, payload)) => {
let (new_remaining, _) = tag_no_case(payload)(remaining)?; let (new_remaining, _) = tuple((
tag_no_case(payload),
// TODO: Test to see what the REAL condition is. Checking for not-alphabetic works fine for now, but the real criteria might be something like the plain text exit matcher.
peek(alt((
recognize(verify(anychar, |c| !c.is_alphanumeric())),
eof,
))),
))(remaining)?;
remaining = new_remaining; remaining = new_remaining;
goal = new_goal; goal = new_goal;
continue; continue;

View File

@ -62,6 +62,22 @@ pub(crate) fn rematch_target<'x, 'b, 'g, 'r, 's>(
remaining = new_remaining; remaining = new_remaining;
new_matches.push(new_match); new_matches.push(new_match);
} }
Object::Italic(italic) => {
let (new_remaining, new_match) = italic.rematch_object(context, remaining)?;
remaining = new_remaining;
new_matches.push(new_match);
}
Object::Underline(underline) => {
let (new_remaining, new_match) = underline.rematch_object(context, remaining)?;
remaining = new_remaining;
new_matches.push(new_match);
}
Object::StrikeThrough(strikethrough) => {
let (new_remaining, new_match) =
strikethrough.rematch_object(context, remaining)?;
remaining = new_remaining;
new_matches.push(new_match);
}
Object::PlainText(plaintext) => { Object::PlainText(plaintext) => {
let (new_remaining, new_match) = plaintext.rematch_object(context, remaining)?; let (new_remaining, new_match) = plaintext.rematch_object(context, remaining)?;
remaining = new_remaining; remaining = new_remaining;

View File

@ -355,6 +355,66 @@ impl<'x> RematchObject<'x> for Bold<'x> {
} }
} }
impl<'x> RematchObject<'x> for Italic<'x> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn rematch_object<'b, 'g, 'r, 's>(
&'x self,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
_rematch_text_markup_object(_context, input, "/", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::Italic(Italic {
source: source.into(),
children,
}),
))
}
}
impl<'x> RematchObject<'x> for Underline<'x> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn rematch_object<'b, 'g, 'r, 's>(
&'x self,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
_rematch_text_markup_object(_context, input, "_", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::Underline(Underline {
source: source.into(),
children,
}),
))
}
}
impl<'x> RematchObject<'x> for StrikeThrough<'x> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn rematch_object<'b, 'g, 'r, 's>(
&'x self,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
_rematch_text_markup_object(_context, input, "+", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::StrikeThrough(StrikeThrough {
source: source.into(),
children,
}),
))
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>( fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,

View File

@ -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;
@ -228,9 +229,16 @@ where
} }
} }
/// Match at least one space character.
///
/// This is similar to nom's space1 parser except space1 matches both spaces and tabs whereas this only matches spaces.
pub(crate) fn only_space1<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_a(" ")(input)
}
/// Match single space or tab. /// Match single space or tab.
/// ///
/// In org-mode syntax, spaces and tabs are interchangeable. /// In org-mode syntax, spaces and tabs are often (but not always!) interchangeable.
pub(crate) fn org_space<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, char> { pub(crate) fn org_space<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, char> {
one_of(" \t")(input) one_of(" \t")(input)
} }

View File

@ -3,6 +3,7 @@ use super::Object;
use super::Source; use super::Source;
pub type PriorityCookie = u8; pub type PriorityCookie = u8;
pub type HeadlineLevel = u16;
#[derive(Debug)] #[derive(Debug)]
pub struct Document<'s> { pub struct Document<'s> {
@ -14,7 +15,7 @@ pub struct Document<'s> {
#[derive(Debug)] #[derive(Debug)]
pub struct Heading<'s> { pub struct Heading<'s> {
pub source: &'s str, pub source: &'s str,
pub level: usize, pub level: HeadlineLevel,
pub todo_keyword: Option<(TodoKeywordType, &'s str)>, pub todo_keyword: Option<(TodoKeywordType, &'s str)>,
pub priority_cookie: Option<PriorityCookie>, pub priority_cookie: Option<PriorityCookie>,
pub title: Vec<Object<'s>>, pub title: Vec<Object<'s>>,

View File

@ -7,6 +7,7 @@ mod source;
pub use document::Document; pub use document::Document;
pub use document::DocumentElement; pub use document::DocumentElement;
pub use document::Heading; pub use document::Heading;
pub use document::HeadlineLevel;
pub use document::PriorityCookie; pub use document::PriorityCookie;
pub use document::Section; pub use document::Section;
pub use document::TodoKeywordType; pub use document::TodoKeywordType;