Add a config option for org-list-allow-alphabetical.

This fixes an issue where lines in a paragraph were incorrectly getting identified as lists because I had defaulted to assuming alphabetical bullets were allowed.
This commit is contained in:
Tom Alexander 2023-09-14 00:27:54 -04:00
parent ac0db64081
commit 33372429dd
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
3 changed files with 60 additions and 17 deletions

View File

@ -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,
} }
} }
} }

View File

@ -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,

View File

@ -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());
} }
} }