sable-markdown/src/parser/blocks/list.rs@main
raw
1use nom::{
2 IResult, Parser,
3 branch::alt,
4 character::complete::{char, one_of, space0},
5 combinator::{map, not, opt, peek, recognize, value, verify},
6 multi::{many_m_n, many0, many1},
7 sequence::{delimited, preceded, terminated},
8};
9
10use crate::{
11 ast::{ListBulletKind, ListItem, ListKind, ListOrderedKindOptions, TaskState},
12 parser::util::{line_terminated, not_eof_or_eol0, not_eof_or_eol1},
13};
14
15fn list_item_task_state(input: &str) -> IResult<&str, TaskState> {
16 delimited(
17 char('['),
18 alt((
19 value(TaskState::Complete, one_of("xX")),
20 value(TaskState::Incomplete, char(' ')),
21 )),
22 char(']'),
23 )
24 .parse(input)
25}
26
27fn list_marker(input: &str) -> IResult<&str, ListKind> {
28 alt((
29 list_marker_ordered,
30 list_marker_star,
31 list_marker_plus,
32 list_marker_dash,
33 ))
34 .parse(input)
35}
36
37fn list_marker_star(input: &str) -> IResult<&str, ListKind> {
38 map(char('*'), |_| ListKind::Bullet(ListBulletKind::Star)).parse(input)
39}
40
41fn list_marker_plus(input: &str) -> IResult<&str, ListKind> {
42 map(char('+'), |_| ListKind::Bullet(ListBulletKind::Plus)).parse(input)
43}
44
45fn list_marker_dash(input: &str) -> IResult<&str, ListKind> {
46 map(char('-'), |_| ListKind::Bullet(ListBulletKind::Dash)).parse(input)
47}
48
49fn list_marker_ordered(input: &str) -> IResult<&str, ListKind> {
50 map(
51 terminated(nom::character::complete::u64, one_of(".)")),
52 |start| ListKind::Ordered(ListOrderedKindOptions { start }),
53 )
54 .parse(input)
55}
56
57fn list_marker_followed_by_spaces(
58 input: &str,
59) -> IResult<&str, (ListKind, usize, Option<TaskState>)> {
60 let (remaining, kind) = delimited(
61 many_m_n(0, 3, char(' ')),
62 list_marker,
63 many_m_n(1, 4, char(' ')),
64 )
65 .parse(input)?;
66
67 let consumed = input.len() - remaining.len();
68
69 let (input, task_state) = opt(terminated(list_item_task_state, char(' '))).parse(remaining)?;
70
71 Ok((input, (kind, consumed, task_state)))
72}
73
74fn list_marker_followed_by_newline(
75 input: &str,
76) -> IResult<&str, (ListKind, usize, Option<TaskState>)> {
77 let (remaining, kind) = preceded(many_m_n(0, 3, char(' ')), list_marker).parse(input)?;
78
79 if let Ok((tail, _)) = line_terminated(space0).parse(remaining) {
83 let consumed = input.len() - remaining.len() + 1;
85
86 return Ok((tail, (kind, consumed, None)));
87 }
88
89 let (remaining, _) = many_m_n(0, 3, char(' ')).parse(remaining)?;
90 let consumed = input.len() - remaining.len() + 1;
91
92 let (remaining, task_state) = line_terminated(list_item_task_state).parse(remaining)?;
93
94 Ok((remaining, (kind, consumed, Some(task_state))))
95}
96
97pub(super) fn list_marker_with_span_size(
98 input: &str,
99) -> IResult<&str, (ListKind, usize, Option<TaskState>, String)> {
100 alt((
101 map(
102 list_marker_followed_by_newline,
103 |(list_kind, prefix_length, task_state)| {
104 (list_kind, prefix_length, task_state, String::new())
105 },
106 ),
107 (map(
108 (
109 list_marker_followed_by_spaces,
110 line_terminated(not_eof_or_eol0),
111 ),
112 |((list_kind, prefix_length, task_state), s)| {
113 (list_kind, prefix_length, task_state, s.to_string())
114 },
115 )),
116 ))
117 .parse(input)
118}
119
120fn list_item_rest_line(
121 list_kind: ListKind,
122 prefix_length: usize,
123) -> impl FnMut(&str) -> IResult<&str, Vec<&str>> {
124 move |input: &str| {
125 if input.is_empty() {
127 return Err(nom::Err::Error(nom::error::Error::new(
128 input,
129 nom::error::ErrorKind::Eof,
130 )));
131 }
132
133 let marker_parser = match list_kind {
134 ListKind::Ordered(_) => list_marker_ordered,
135 ListKind::Bullet(ListBulletKind::Star) => list_marker_star,
136 ListKind::Bullet(ListBulletKind::Plus) => list_marker_plus,
137 ListKind::Bullet(ListBulletKind::Dash) => list_marker_dash,
138 };
139
140 line_terminated(preceded(
141 peek(not(alt((
142 value((), crate::parser::blocks::thematic_break::thematic_break()),
143 value(
144 (),
145 (
146 verify(
147 recognize(many_m_n(0, prefix_length, char(' '))),
148 |indent: &str| indent.len() < prefix_length,
149 ),
150 marker_parser,
151 ),
152 ),
153 )))),
154 alt((
155 preceded(
157 many_m_n(0, prefix_length, char(' ')),
158 map(not_eof_or_eol1, |v| vec![v]),
159 ),
160 map(
162 (
163 recognize(many1(line_terminated(space0))),
164 preceded(
165 many_m_n(prefix_length, prefix_length, char(' ')),
166 not_eof_or_eol1,
167 ),
168 ),
169 |(newlines, content)| vec![newlines, content],
170 ),
171 )),
172 ))
173 .parse(input)
174 }
175}
176
177fn list_item_lines(
178 list_kind: ListKind,
179 prefix_length: usize,
180) -> impl FnMut(&str) -> IResult<&str, Vec<Vec<&str>>> {
181 move |input: &str| many0(list_item_rest_line(list_kind.clone(), prefix_length)).parse(input)
182}
183
184pub(super) fn list_item() -> impl FnMut(&str) -> IResult<&str, (ListKind, ListItem)> {
185 move |input: &str| {
186 let (input, (list_kind, item_prefix_length, task_state, first_line)) =
187 list_marker_with_span_size(input)?;
188
189 let (input, rest_lines) =
190 list_item_lines(list_kind.clone(), item_prefix_length).parse(input)?;
191
192 let total_size = first_line.len() + rest_lines.len();
193 let mut item_content = String::with_capacity(total_size);
194 if !first_line.is_empty() {
195 item_content.push_str(&first_line);
196 }
197 for line in rest_lines {
198 item_content.push('\n');
199 for subline in line {
200 item_content.push_str(subline);
201 }
202 }
203
204 let (_, blocks) = many0(crate::parser::blocks::block())
205 .parse(&item_content)
206 .map_err(|err| err.map_input(|_| input))?;
207
208 let blocks = blocks.into_iter().flatten().collect();
209
210 let item = ListItem {
211 task: task_state,
212 blocks,
213 };
214 Ok((input, (list_kind, item)))
215 }
216}
217
218pub(super) fn list() -> impl FnMut(&str) -> IResult<&str, crate::ast::List> {
219 move |input: &str| {
220 let (input, items) = many1(list_item()).parse(input)?;
221
222 let first_item = items.first().unwrap();
224
225 let list = crate::ast::List {
226 kind: first_item.0.clone(),
227 items: items.into_iter().map(|(_, item)| item).collect(),
228 };
229
230 Ok((input, list))
231 }
232}
233