1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::space1;
use nom::combinator::map;
use nom::multi::many0;
use nom::multi::many_till;
use nom::multi::separated_list0;

use super::keyword::filtered_keyword;
use super::keyword_todo::todo_keywords;
use super::OrgSource;
use crate::context::HeadlineLevelFilter;
use crate::error::Res;
use crate::types::Keyword;
use crate::GlobalSettings;

#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn scan_for_in_buffer_settings<'s>(
    input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
    // TODO: Optimization idea: since this is slicing the OrgSource at each character, it might be more efficient to do a parser that uses a search function like take_until, and wrap it in a function similar to consumed but returning the input along with the normal output, then pass all of that into a verify that confirms we were at the start of a line using the input we just returned.

    let keywords = many0(map(
        many_till(anychar, filtered_keyword(in_buffer_settings_key)),
        |(_, kw)| kw,
    ))(input);
    keywords
}

#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
    alt((
        tag_no_case("archive"),
        tag_no_case("category"),
        tag_no_case("columns"),
        tag_no_case("filetags"),
        tag_no_case("link"),
        tag_no_case("priorities"),
        tag_no_case("property"),
        tag_no_case("seq_todo"),
        tag_no_case("setupfile"),
        tag_no_case("startup"),
        tag_no_case("tags"),
        tag_no_case("todo"),
        tag_no_case("typ_todo"),
    ))(input)
}

pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
    keywords: Vec<Keyword<'sf>>,
    original_settings: &'g GlobalSettings<'g, 's>,
) -> Result<GlobalSettings<'g, 's>, String> {
    let mut new_settings = original_settings.clone();

    // Todo Keywords
    for kw in keywords.iter().filter(|kw| {
        kw.key.eq_ignore_ascii_case("todo")
            || kw.key.eq_ignore_ascii_case("seq_todo")
            || kw.key.eq_ignore_ascii_case("typ_todo")
    }) {
        let (_, (in_progress_words, complete_words)) =
            todo_keywords(kw.value).map_err(|err| err.to_string())?;
        new_settings
            .in_progress_todo_keywords
            .extend(in_progress_words.into_iter().map(str::to_string));
        new_settings
            .complete_todo_keywords
            .extend(complete_words.into_iter().map(str::to_string));
    }

    // Startup settings
    for kw in keywords
        .iter()
        .filter(|kw| kw.key.eq_ignore_ascii_case("startup"))
    {
        let (_remaining, settings) =
            separated_list0(space1::<&str, nom::error::Error<_>>, is_not(" \t"))(kw.value)
                .map_err(|err: nom::Err<_>| err.to_string())?;
        if settings.contains(&"odd") {
            new_settings.odd_levels_only = HeadlineLevelFilter::Odd;
        }
        if settings.contains(&"oddeven") {
            new_settings.odd_levels_only = HeadlineLevelFilter::OddEven;
        }
    }

    Ok(new_settings)
}